From 9c2cce092358177b403fbd396fa2ee7b39eecc47 Mon Sep 17 00:00:00 2001 From: Huw Davies Date: Mon, 2 Oct 2017 09:47:26 +0100 Subject: [PATCH] winhttp: Move the authorization code above send_request(). Signed-off-by: Huw Davies Signed-off-by: Hans Leidekker Signed-off-by: Alexandre Julliard --- dlls/winhttp/request.c | 2095 ++++++++++++++++++++++++------------------------ 1 file changed, 1048 insertions(+), 1047 deletions(-) diff --git a/dlls/winhttp/request.c b/dlls/winhttp/request.c index f3f4cf85f8a..05f4b963ddb 100644 --- a/dlls/winhttp/request.c +++ b/dlls/winhttp/request.c @@ -850,1362 +850,1363 @@ BOOL WINAPI WinHttpQueryHeaders( HINTERNET hrequest, DWORD level, LPCWSTR name, return ret; } -static LPWSTR concatenate_string_list( LPCWSTR *list, int len ) -{ - LPCWSTR *t; - LPWSTR str; - - for( t = list; *t ; t++ ) - len += strlenW( *t ); - len++; - str = heap_alloc( len * sizeof(WCHAR) ); - if (!str) return NULL; - *str = 0; +#define ARRAYSIZE(array) (sizeof(array) / sizeof((array)[0])) - for( t = list; *t ; t++ ) - strcatW( str, *t ); +static const WCHAR basicW[] = {'B','a','s','i','c',0}; +static const WCHAR ntlmW[] = {'N','T','L','M',0}; +static const WCHAR passportW[] = {'P','a','s','s','p','o','r','t',0}; +static const WCHAR digestW[] = {'D','i','g','e','s','t',0}; +static const WCHAR negotiateW[] = {'N','e','g','o','t','i','a','t','e',0}; - return str; +static const struct +{ + const WCHAR *str; + unsigned int len; + DWORD scheme; } +auth_schemes[] = +{ + { basicW, ARRAYSIZE(basicW) - 1, WINHTTP_AUTH_SCHEME_BASIC }, + { ntlmW, ARRAYSIZE(ntlmW) - 1, WINHTTP_AUTH_SCHEME_NTLM }, + { passportW, ARRAYSIZE(passportW) - 1, WINHTTP_AUTH_SCHEME_PASSPORT }, + { digestW, ARRAYSIZE(digestW) - 1, WINHTTP_AUTH_SCHEME_DIGEST }, + { negotiateW, ARRAYSIZE(negotiateW) - 1, WINHTTP_AUTH_SCHEME_NEGOTIATE } +}; +static const unsigned int num_auth_schemes = sizeof(auth_schemes)/sizeof(auth_schemes[0]); -static LPWSTR build_header_request_string( request_t *request, LPCWSTR verb, - LPCWSTR path, LPCWSTR version ) +static enum auth_scheme scheme_from_flag( DWORD flag ) { - static const WCHAR crlf[] = {'\r','\n',0}; - static const WCHAR space[] = { ' ',0 }; - static const WCHAR colon[] = { ':',' ',0 }; - static const WCHAR twocrlf[] = {'\r','\n','\r','\n', 0}; - LPWSTR requestString; - DWORD len, n; - LPCWSTR *req; - UINT i; - LPWSTR p; + int i; - /* allocate space for an array of all the string pointers to be added */ - len = (request->num_headers) * 4 + 10; - req = heap_alloc( len * sizeof(LPCWSTR) ); - if (!req) return NULL; + for (i = 0; i < num_auth_schemes; i++) if (flag == auth_schemes[i].scheme) return i; + return SCHEME_INVALID; +} - /* add the verb, path and HTTP version string */ - n = 0; - req[n++] = verb; - req[n++] = space; - req[n++] = path; - req[n++] = space; - req[n++] = version; +static DWORD auth_scheme_from_header( WCHAR *header ) +{ + unsigned int i; - /* Append custom request headers */ - for (i = 0; i < request->num_headers; i++) + for (i = 0; i < num_auth_schemes; i++) { - if (request->headers[i].is_request) - { - req[n++] = crlf; - req[n++] = request->headers[i].field; - req[n++] = colon; - req[n++] = request->headers[i].value; - - TRACE("Adding custom header %s (%s)\n", - debugstr_w(request->headers[i].field), - debugstr_w(request->headers[i].value)); - } + if (!strncmpiW( header, auth_schemes[i].str, auth_schemes[i].len ) && + (header[auth_schemes[i].len] == ' ' || !header[auth_schemes[i].len])) return auth_schemes[i].scheme; } - - if( n >= len ) - ERR("oops. buffer overrun\n"); - - req[n] = NULL; - requestString = concatenate_string_list( req, 4 ); - heap_free( req ); - if (!requestString) return NULL; - - /* - * Set (header) termination string for request - * Make sure there are exactly two new lines at the end of the request - */ - p = &requestString[strlenW(requestString)-1]; - while ( (*p == '\n') || (*p == '\r') ) - p--; - strcpyW( p+1, twocrlf ); - - return requestString; + return 0; } -static BOOL read_reply( request_t *request ); - -static BOOL secure_proxy_connect( request_t *request ) +static BOOL query_auth_schemes( request_t *request, DWORD level, LPDWORD supported, LPDWORD first ) { - static const WCHAR verbConnect[] = {'C','O','N','N','E','C','T',0}; - static const WCHAR fmt[] = {'%','s',':','%','u',0}; + DWORD index = 0, supported_schemes = 0, first_scheme = 0; BOOL ret = FALSE; - LPWSTR path; - connect_t *connect = request->connect; - path = heap_alloc( (strlenW( connect->hostname ) + 13) * sizeof(WCHAR) ); - if (path) + for (;;) { - LPWSTR requestString; - - sprintfW( path, fmt, connect->hostname, connect->hostport ); - requestString = build_header_request_string( request, verbConnect, - path, http1_1 ); - heap_free( path ); - if (requestString) - { - LPSTR req_ascii = strdupWA( requestString ); + WCHAR *buffer; + DWORD size, scheme; - heap_free( requestString ); - if (req_ascii) - { - int len = strlen( req_ascii ), bytes_sent; + size = 0; + query_headers( request, level, NULL, NULL, &size, &index ); + if (get_last_error() != ERROR_INSUFFICIENT_BUFFER) break; - ret = netconn_send( request->netconn, req_ascii, len, &bytes_sent ); - heap_free( req_ascii ); - if (ret) - ret = read_reply( request ); - } + index--; + if (!(buffer = heap_alloc( size ))) return FALSE; + if (!query_headers( request, level, NULL, buffer, &size, &index )) + { + heap_free( buffer ); + return FALSE; } - } - return ret; -} + scheme = auth_scheme_from_header( buffer ); + heap_free( buffer ); + if (!scheme) continue; -#ifndef INET6_ADDRSTRLEN -#define INET6_ADDRSTRLEN 46 -#endif + if (!first_scheme) first_scheme = scheme; + supported_schemes |= scheme; -static WCHAR *addr_to_str( struct sockaddr_storage *addr ) -{ - char buf[INET6_ADDRSTRLEN]; - void *src; + ret = TRUE; + } - switch (addr->ss_family) + if (ret) { - case AF_INET: - src = &((struct sockaddr_in *)addr)->sin_addr; - break; - case AF_INET6: - src = &((struct sockaddr_in6 *)addr)->sin6_addr; - break; - default: - WARN("unsupported address family %d\n", addr->ss_family); - return NULL; + *supported = supported_schemes; + *first = first_scheme; } - if (!inet_ntop( addr->ss_family, src, buf, sizeof(buf) )) return NULL; - return strdupAW( buf ); + return ret; } -static CRITICAL_SECTION connection_pool_cs; -static CRITICAL_SECTION_DEBUG connection_pool_debug = +/*********************************************************************** + * WinHttpQueryAuthSchemes (winhttp.@) + */ +BOOL WINAPI WinHttpQueryAuthSchemes( HINTERNET hrequest, LPDWORD supported, LPDWORD first, LPDWORD target ) { - 0, 0, &connection_pool_cs, - { &connection_pool_debug.ProcessLocksList, &connection_pool_debug.ProcessLocksList }, - 0, 0, { (DWORD_PTR)(__FILE__ ": connection_pool_cs") } -}; -static CRITICAL_SECTION connection_pool_cs = { &connection_pool_debug, -1, 0, 0, 0, 0 }; + BOOL ret = FALSE; + request_t *request; -static struct list connection_pool = LIST_INIT( connection_pool ); + TRACE("%p, %p, %p, %p\n", hrequest, supported, first, target); -void release_host( hostdata_t *host ) -{ - LONG ref; + if (!(request = (request_t *)grab_object( hrequest ))) + { + set_last_error( ERROR_INVALID_HANDLE ); + return FALSE; + } + if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST) + { + release_object( &request->hdr ); + set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE ); + return FALSE; + } + if (!supported || !first || !target) + { + release_object( &request->hdr ); + set_last_error( ERROR_INVALID_PARAMETER ); + return FALSE; - EnterCriticalSection( &connection_pool_cs ); - if (!(ref = --host->ref)) list_remove( &host->entry ); - LeaveCriticalSection( &connection_pool_cs ); - if (ref) return; + } - assert( list_empty( &host->connections ) ); - heap_free( host->hostname ); - heap_free( host ); -} + if (query_auth_schemes( request, WINHTTP_QUERY_WWW_AUTHENTICATE, supported, first )) + { + *target = WINHTTP_AUTH_TARGET_SERVER; + ret = TRUE; + } + else if (query_auth_schemes( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, supported, first )) + { + *target = WINHTTP_AUTH_TARGET_PROXY; + ret = TRUE; + } -static BOOL connection_collector_running; + release_object( &request->hdr ); + if (ret) set_last_error( ERROR_SUCCESS ); + return ret; +} -static DWORD WINAPI connection_collector(void *arg) +static UINT encode_base64( const char *bin, unsigned int len, WCHAR *base64 ) { - unsigned int remaining_connections; - netconn_t *netconn, *next_netconn; - hostdata_t *host, *next_host; - ULONGLONG now; + UINT n = 0, x; + static const char base64enc[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - do + while (len > 0) { - /* FIXME: Use more sophisticated method */ - Sleep(5000); - remaining_connections = 0; - now = GetTickCount64(); - - EnterCriticalSection(&connection_pool_cs); + /* first 6 bits, all from bin[0] */ + base64[n++] = base64enc[(bin[0] & 0xfc) >> 2]; + x = (bin[0] & 3) << 4; - LIST_FOR_EACH_ENTRY_SAFE(host, next_host, &connection_pool, hostdata_t, entry) + /* next 6 bits, 2 from bin[0] and 4 from bin[1] */ + if (len == 1) { - LIST_FOR_EACH_ENTRY_SAFE(netconn, next_netconn, &host->connections, netconn_t, entry) - { - if (netconn->keep_until < now) - { - TRACE("freeing %p\n", netconn); - list_remove(&netconn->entry); - netconn_close(netconn); - } - else - { - remaining_connections++; - } - } + base64[n++] = base64enc[x]; + base64[n++] = '='; + base64[n++] = '='; + break; } + base64[n++] = base64enc[x | ((bin[1] & 0xf0) >> 4)]; + x = (bin[1] & 0x0f) << 2; - if (!remaining_connections) connection_collector_running = FALSE; - - LeaveCriticalSection(&connection_pool_cs); - } while(remaining_connections); + /* next 6 bits 4 from bin[1] and 2 from bin[2] */ + if (len == 2) + { + base64[n++] = base64enc[x]; + base64[n++] = '='; + break; + } + base64[n++] = base64enc[x | ((bin[2] & 0xc0) >> 6)]; - FreeLibraryAndExitThread( winhttp_instance, 0 ); + /* last 6 bits, all from bin [2] */ + base64[n++] = base64enc[bin[2] & 0x3f]; + bin += 3; + len -= 3; + } + base64[n] = 0; + return n; } -static void cache_connection( netconn_t *netconn ) +static inline char decode_char( WCHAR c ) { - TRACE( "caching connection %p\n", netconn ); + if (c >= 'A' && c <= 'Z') return c - 'A'; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; + return 64; +} - EnterCriticalSection( &connection_pool_cs ); +static unsigned int decode_base64( const WCHAR *base64, unsigned int len, char *buf ) +{ + unsigned int i = 0; + char c0, c1, c2, c3; + const WCHAR *p = base64; - netconn->keep_until = GetTickCount64() + DEFAULT_KEEP_ALIVE_TIMEOUT; - list_add_head( &netconn->host->connections, &netconn->entry ); + while (len > 4) + { + if ((c0 = decode_char( p[0] )) > 63) return 0; + if ((c1 = decode_char( p[1] )) > 63) return 0; + if ((c2 = decode_char( p[2] )) > 63) return 0; + if ((c3 = decode_char( p[3] )) > 63) return 0; - if (!connection_collector_running) + if (buf) + { + buf[i + 0] = (c0 << 2) | (c1 >> 4); + buf[i + 1] = (c1 << 4) | (c2 >> 2); + buf[i + 2] = (c2 << 6) | c3; + } + len -= 4; + i += 3; + p += 4; + } + if (p[2] == '=') { - HMODULE module; - HANDLE thread; + if ((c0 = decode_char( p[0] )) > 63) return 0; + if ((c1 = decode_char( p[1] )) > 63) return 0; - GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const WCHAR*)winhttp_instance, &module ); + if (buf) buf[i] = (c0 << 2) | (c1 >> 4); + i++; + } + else if (p[3] == '=') + { + if ((c0 = decode_char( p[0] )) > 63) return 0; + if ((c1 = decode_char( p[1] )) > 63) return 0; + if ((c2 = decode_char( p[2] )) > 63) return 0; - thread = CreateThread(NULL, 0, connection_collector, NULL, 0, NULL); - if (thread) + if (buf) { - CloseHandle( thread ); - connection_collector_running = TRUE; + buf[i + 0] = (c0 << 2) | (c1 >> 4); + buf[i + 1] = (c1 << 4) | (c2 >> 2); } - else + i += 2; + } + else + { + if ((c0 = decode_char( p[0] )) > 63) return 0; + if ((c1 = decode_char( p[1] )) > 63) return 0; + if ((c2 = decode_char( p[2] )) > 63) return 0; + if ((c3 = decode_char( p[3] )) > 63) return 0; + + if (buf) { - FreeLibrary( winhttp_instance ); + buf[i + 0] = (c0 << 2) | (c1 >> 4); + buf[i + 1] = (c1 << 4) | (c2 >> 2); + buf[i + 2] = (c2 << 6) | c3; } + i += 3; } - - LeaveCriticalSection( &connection_pool_cs ); + return i; } -static DWORD map_secure_protocols( DWORD mask ) +static struct authinfo *alloc_authinfo(void) { - DWORD ret = 0; - if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_SSL2) ret |= SP_PROT_SSL2_CLIENT; - if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_SSL3) ret |= SP_PROT_SSL3_CLIENT; - if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1) ret |= SP_PROT_TLS1_CLIENT; - if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1) ret |= SP_PROT_TLS1_1_CLIENT; - if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2) ret |= SP_PROT_TLS1_2_CLIENT; + struct authinfo *ret; + + if (!(ret = heap_alloc( sizeof(*ret) ))) return NULL; + + SecInvalidateHandle( &ret->cred ); + SecInvalidateHandle( &ret->ctx ); + memset( &ret->exp, 0, sizeof(ret->exp) ); + ret->scheme = 0; + ret->attr = 0; + ret->max_token = 0; + ret->data = NULL; + ret->data_len = 0; + ret->finished = FALSE; return ret; } -static BOOL ensure_cred_handle( session_t *session ) +void destroy_authinfo( struct authinfo *authinfo ) { - SCHANNEL_CRED cred; - SECURITY_STATUS status; + if (!authinfo) return; - if (session->cred_handle_initialized) return TRUE; + if (SecIsValidHandle( &authinfo->ctx )) + DeleteSecurityContext( &authinfo->ctx ); + if (SecIsValidHandle( &authinfo->cred )) + FreeCredentialsHandle( &authinfo->cred ); - memset( &cred, 0, sizeof(cred) ); - cred.dwVersion = SCHANNEL_CRED_VERSION; - cred.grbitEnabledProtocols = map_secure_protocols( session->secure_protocols ); - if ((status = AcquireCredentialsHandleW( NULL, (WCHAR *)UNISP_NAME_W, SECPKG_CRED_OUTBOUND, NULL, &cred, - NULL, NULL, &session->cred_handle, NULL )) != SEC_E_OK) + heap_free( authinfo->data ); + heap_free( authinfo ); +} + +static BOOL get_authvalue( request_t *request, DWORD level, DWORD scheme, WCHAR *buffer, DWORD len ) +{ + DWORD size, index = 0; + for (;;) { - WARN( "AcquireCredentialsHandleW failed: 0x%08x\n", status ); - return FALSE; + size = len; + if (!query_headers( request, level, NULL, buffer, &size, &index )) return FALSE; + if (auth_scheme_from_header( buffer ) == scheme) break; } - session->cred_handle_initialized = TRUE; return TRUE; } -static BOOL open_connection( request_t *request ) +static BOOL do_authorization( request_t *request, DWORD target, DWORD scheme_flag ) { - BOOL is_secure = request->hdr.flags & WINHTTP_FLAG_SECURE; - hostdata_t *host = NULL, *iter; - netconn_t *netconn = NULL; - connect_t *connect; - WCHAR *addressW = NULL; - INTERNET_PORT port; - DWORD len; - - if (request->netconn) goto done; - - connect = request->connect; - port = connect->serverport ? connect->serverport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80); + struct authinfo *authinfo, **auth_ptr; + enum auth_scheme scheme = scheme_from_flag( scheme_flag ); + const WCHAR *auth_target, *username, *password; + WCHAR auth_value[2048], *auth_reply; + DWORD len = sizeof(auth_value), len_scheme, flags; + BOOL ret; - EnterCriticalSection( &connection_pool_cs ); + if (scheme == SCHEME_INVALID) return FALSE; - LIST_FOR_EACH_ENTRY( iter, &connection_pool, hostdata_t, entry ) + switch (target) { - if (iter->port == port && !strcmpW( connect->servername, iter->hostname ) && !is_secure == !iter->secure) + case WINHTTP_AUTH_TARGET_SERVER: + if (!get_authvalue( request, WINHTTP_QUERY_WWW_AUTHENTICATE, scheme_flag, auth_value, len )) + return FALSE; + auth_ptr = &request->authinfo; + auth_target = attr_authorization; + if (request->creds[TARGET_SERVER][scheme].username) { - host = iter; - host->ref++; - break; + username = request->creds[TARGET_SERVER][scheme].username; + password = request->creds[TARGET_SERVER][scheme].password; } - } - - if (!host) - { - if ((host = heap_alloc( sizeof(*host) ))) + else { - host->ref = 1; - host->secure = is_secure; - host->port = port; - list_init( &host->connections ); - if ((host->hostname = strdupW( connect->servername ))) - { - list_add_head( &connection_pool, &host->entry ); - } - else - { - heap_free( host ); - host = NULL; - } + username = request->connect->username; + password = request->connect->password; } - } - - LeaveCriticalSection( &connection_pool_cs ); - - if (!host) return FALSE; + break; - for (;;) - { - EnterCriticalSection( &connection_pool_cs ); - if (!list_empty( &host->connections )) + case WINHTTP_AUTH_TARGET_PROXY: + if (!get_authvalue( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, scheme_flag, auth_value, len )) + return FALSE; + auth_ptr = &request->proxy_authinfo; + auth_target = attr_proxy_authorization; + if (request->creds[TARGET_PROXY][scheme].username) { - netconn = LIST_ENTRY( list_head( &host->connections ), netconn_t, entry ); - list_remove( &netconn->entry ); + username = request->creds[TARGET_PROXY][scheme].username; + password = request->creds[TARGET_PROXY][scheme].password; } - LeaveCriticalSection( &connection_pool_cs ); - if (!netconn) break; + else + { + username = request->connect->session->proxy_username; + password = request->connect->session->proxy_password; + } + break; - if (netconn_is_alive( netconn )) break; - TRACE("connection %p no longer alive, closing\n", netconn); - netconn_close( netconn ); - netconn = NULL; + default: + WARN("unknown target %x\n", target); + return FALSE; } + authinfo = *auth_ptr; - if (!connect->resolved && netconn) + switch (scheme) { - connect->sockaddr = netconn->sockaddr; - connect->resolved = TRUE; - } - - if (!connect->resolved) + case SCHEME_BASIC: { - len = strlenW( host->hostname ) + 1; - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESOLVING_NAME, host->hostname, len ); + int userlen, passlen; - if (!netconn_resolve( host->hostname, port, &connect->sockaddr, request->resolve_timeout )) - { - release_host( host ); - return FALSE; - } - connect->resolved = TRUE; + if (!username || !password) return FALSE; + if ((!authinfo && !(authinfo = alloc_authinfo())) || authinfo->finished) return FALSE; - if (!(addressW = addr_to_str( &connect->sockaddr ))) - { - release_host( host ); - return FALSE; - } - len = strlenW( addressW ) + 1; - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_NAME_RESOLVED, addressW, len ); - } + userlen = WideCharToMultiByte( CP_UTF8, 0, username, strlenW( username ), NULL, 0, NULL, NULL ); + passlen = WideCharToMultiByte( CP_UTF8, 0, password, strlenW( password ), NULL, 0, NULL, NULL ); - if (!netconn) + authinfo->data_len = userlen + 1 + passlen; + if (!(authinfo->data = heap_alloc( authinfo->data_len ))) return FALSE; + + WideCharToMultiByte( CP_UTF8, 0, username, -1, authinfo->data, userlen, NULL, NULL ); + authinfo->data[userlen] = ':'; + WideCharToMultiByte( CP_UTF8, 0, password, -1, authinfo->data + userlen + 1, passlen, NULL, NULL ); + + authinfo->scheme = SCHEME_BASIC; + authinfo->finished = TRUE; + break; + } + case SCHEME_NTLM: + case SCHEME_NEGOTIATE: { - if (!addressW && !(addressW = addr_to_str( &connect->sockaddr ))) + SECURITY_STATUS status; + SecBufferDesc out_desc, in_desc; + SecBuffer out, in; + ULONG flags = ISC_REQ_CONNECTION|ISC_REQ_USE_DCE_STYLE|ISC_REQ_MUTUAL_AUTH|ISC_REQ_DELEGATE; + const WCHAR *p; + BOOL first = FALSE; + + if (!authinfo) { - release_host( host ); - return FALSE; - } + TimeStamp exp; + SEC_WINNT_AUTH_IDENTITY_W id; + WCHAR *domain, *user; - TRACE("connecting to %s:%u\n", debugstr_w(addressW), port); + if (!username || !password || !(authinfo = alloc_authinfo())) return FALSE; - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER, addressW, 0 ); + first = TRUE; + domain = (WCHAR *)username; + user = strchrW( username, '\\' ); - if (!(netconn = netconn_create( host, &connect->sockaddr, request->connect_timeout ))) - { - heap_free( addressW ); - release_host( host ); - return FALSE; - } - netconn_set_timeout( netconn, TRUE, request->send_timeout ); - netconn_set_timeout( netconn, FALSE, request->recv_timeout ); - if (is_secure) - { - if (connect->session->proxy_server && - strcmpiW( connect->hostname, connect->servername )) + if (user) user++; + else { - if (!secure_proxy_connect( request )) + user = (WCHAR *)username; + domain = NULL; + } + id.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; + id.User = user; + id.UserLength = strlenW( user ); + id.Domain = domain; + id.DomainLength = domain ? user - domain - 1 : 0; + id.Password = (WCHAR *)password; + id.PasswordLength = strlenW( password ); + + status = AcquireCredentialsHandleW( NULL, (SEC_WCHAR *)auth_schemes[scheme].str, + SECPKG_CRED_OUTBOUND, NULL, &id, NULL, NULL, + &authinfo->cred, &exp ); + if (status == SEC_E_OK) + { + PSecPkgInfoW info; + status = QuerySecurityPackageInfoW( (SEC_WCHAR *)auth_schemes[scheme].str, &info ); + if (status == SEC_E_OK) { - heap_free( addressW ); - netconn_close( netconn ); - return FALSE; + authinfo->max_token = info->cbMaxToken; + FreeContextBuffer( info ); } } - if (!ensure_cred_handle( connect->session ) || - !netconn_secure_connect( netconn, connect->hostname, request->security_flags, - &connect->session->cred_handle )) + if (status != SEC_E_OK) { - heap_free( addressW ); - netconn_close( netconn ); + WARN("AcquireCredentialsHandleW for scheme %s failed with error 0x%08x\n", + debugstr_w(auth_schemes[scheme].str), status); + heap_free( authinfo ); return FALSE; } + authinfo->scheme = scheme; } + else if (authinfo->finished) return FALSE; - request->netconn = netconn; - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER, addressW, strlenW(addressW) + 1 ); - } - else - { - TRACE("using connection %p\n", netconn); + if ((strlenW( auth_value ) < auth_schemes[authinfo->scheme].len || + strncmpiW( auth_value, auth_schemes[authinfo->scheme].str, auth_schemes[authinfo->scheme].len ))) + { + ERR("authentication scheme changed from %s to %s\n", + debugstr_w(auth_schemes[authinfo->scheme].str), debugstr_w(auth_value)); + destroy_authinfo( authinfo ); + *auth_ptr = NULL; + return FALSE; + } + in.BufferType = SECBUFFER_TOKEN; + in.cbBuffer = 0; + in.pvBuffer = NULL; - netconn_set_timeout( netconn, TRUE, request->send_timeout ); - netconn_set_timeout( netconn, FALSE, request->recv_timeout ); - request->netconn = netconn; + in_desc.ulVersion = 0; + in_desc.cBuffers = 1; + in_desc.pBuffers = ∈ + + p = auth_value + auth_schemes[scheme].len; + if (*p == ' ') + { + int len = strlenW( ++p ); + in.cbBuffer = decode_base64( p, len, NULL ); + if (!(in.pvBuffer = heap_alloc( in.cbBuffer ))) { + destroy_authinfo( authinfo ); + *auth_ptr = NULL; + return FALSE; + } + decode_base64( p, len, in.pvBuffer ); + } + out.BufferType = SECBUFFER_TOKEN; + out.cbBuffer = authinfo->max_token; + if (!(out.pvBuffer = heap_alloc( authinfo->max_token ))) + { + heap_free( in.pvBuffer ); + destroy_authinfo( authinfo ); + *auth_ptr = NULL; + return FALSE; + } + out_desc.ulVersion = 0; + out_desc.cBuffers = 1; + out_desc.pBuffers = &out; + + status = InitializeSecurityContextW( first ? &authinfo->cred : NULL, first ? NULL : &authinfo->ctx, + first ? request->connect->servername : NULL, flags, 0, + SECURITY_NETWORK_DREP, in.pvBuffer ? &in_desc : NULL, 0, + &authinfo->ctx, &out_desc, &authinfo->attr, &authinfo->exp ); + heap_free( in.pvBuffer ); + if (status == SEC_E_OK) + { + heap_free( authinfo->data ); + authinfo->data = out.pvBuffer; + authinfo->data_len = out.cbBuffer; + authinfo->finished = TRUE; + TRACE("sending last auth packet\n"); + } + else if (status == SEC_I_CONTINUE_NEEDED) + { + heap_free( authinfo->data ); + authinfo->data = out.pvBuffer; + authinfo->data_len = out.cbBuffer; + TRACE("sending next auth packet\n"); + } + else + { + ERR("InitializeSecurityContextW failed with error 0x%08x\n", status); + heap_free( out.pvBuffer ); + destroy_authinfo( authinfo ); + *auth_ptr = NULL; + return FALSE; + } + break; + } + default: + ERR("invalid scheme %u\n", scheme); + return FALSE; } + *auth_ptr = authinfo; -done: - request->read_pos = request->read_size = 0; - request->read_chunked = FALSE; - request->read_chunked_size = ~0u; - request->read_chunked_eof = FALSE; - heap_free( addressW ); - return TRUE; -} + len_scheme = auth_schemes[authinfo->scheme].len; + len = len_scheme + 1 + ((authinfo->data_len + 2) * 4) / 3; + if (!(auth_reply = heap_alloc( (len + 1) * sizeof(WCHAR) ))) return FALSE; -void close_connection( request_t *request ) -{ - if (!request->netconn) return; + memcpy( auth_reply, auth_schemes[authinfo->scheme].str, len_scheme * sizeof(WCHAR) ); + auth_reply[len_scheme] = ' '; + encode_base64( authinfo->data, authinfo->data_len, auth_reply + len_scheme + 1 ); - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION, 0, 0 ); - netconn_close( request->netconn ); - request->netconn = NULL; - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED, 0, 0 ); + flags = WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE; + ret = process_header( request, auth_target, auth_reply, flags, TRUE ); + heap_free( auth_reply ); + return ret; } -static BOOL add_host_header( request_t *request, DWORD modifier ) +static LPWSTR concatenate_string_list( LPCWSTR *list, int len ) { - BOOL ret; - DWORD len; - WCHAR *host; - static const WCHAR fmt[] = {'%','s',':','%','u',0}; - connect_t *connect = request->connect; - INTERNET_PORT port; + LPCWSTR *t; + LPWSTR str; - port = connect->hostport ? connect->hostport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80); + for( t = list; *t ; t++ ) + len += strlenW( *t ); + len++; - if (port == INTERNET_DEFAULT_HTTP_PORT || port == INTERNET_DEFAULT_HTTPS_PORT) - { - return process_header( request, attr_host, connect->hostname, modifier, TRUE ); - } - len = strlenW( connect->hostname ) + 7; /* sizeof(":65335") */ - if (!(host = heap_alloc( len * sizeof(WCHAR) ))) return FALSE; - sprintfW( host, fmt, connect->hostname, port ); - ret = process_header( request, attr_host, host, modifier, TRUE ); - heap_free( host ); - return ret; -} + str = heap_alloc( len * sizeof(WCHAR) ); + if (!str) return NULL; + *str = 0; -static void clear_response_headers( request_t *request ) -{ - unsigned int i; + for( t = list; *t ; t++ ) + strcatW( str, *t ); - for (i = 0; i < request->num_headers; i++) - { - if (!request->headers[i].field) continue; - if (!request->headers[i].value) continue; - if (request->headers[i].is_request) continue; - delete_header( request, i ); - i--; - } + return str; } -/* remove some amount of data from the read buffer */ -static void remove_data( request_t *request, int count ) +static LPWSTR build_header_request_string( request_t *request, LPCWSTR verb, + LPCWSTR path, LPCWSTR version ) { - if (!(request->read_size -= count)) request->read_pos = 0; - else request->read_pos += count; -} + static const WCHAR crlf[] = {'\r','\n',0}; + static const WCHAR space[] = { ' ',0 }; + static const WCHAR colon[] = { ':',' ',0 }; + static const WCHAR twocrlf[] = {'\r','\n','\r','\n', 0}; + LPWSTR requestString; + DWORD len, n; + LPCWSTR *req; + UINT i; + LPWSTR p; -/* read some more data into the read buffer */ -static BOOL read_more_data( request_t *request, int maxlen, BOOL notify ) -{ - int len; - BOOL ret; + /* allocate space for an array of all the string pointers to be added */ + len = (request->num_headers) * 4 + 10; + req = heap_alloc( len * sizeof(LPCWSTR) ); + if (!req) return NULL; - if (request->read_chunked_eof) return FALSE; + /* add the verb, path and HTTP version string */ + n = 0; + req[n++] = verb; + req[n++] = space; + req[n++] = path; + req[n++] = space; + req[n++] = version; - if (request->read_size && request->read_pos) + /* Append custom request headers */ + for (i = 0; i < request->num_headers; i++) { - /* move existing data to the start of the buffer */ - memmove( request->read_buf, request->read_buf + request->read_pos, request->read_size ); - request->read_pos = 0; + if (request->headers[i].is_request) + { + req[n++] = crlf; + req[n++] = request->headers[i].field; + req[n++] = colon; + req[n++] = request->headers[i].value; + + TRACE("Adding custom header %s (%s)\n", + debugstr_w(request->headers[i].field), + debugstr_w(request->headers[i].value)); + } } - if (maxlen == -1) maxlen = sizeof(request->read_buf); - if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE, NULL, 0 ); + if( n >= len ) + ERR("oops. buffer overrun\n"); - ret = netconn_recv( request->netconn, request->read_buf + request->read_size, - maxlen - request->read_size, 0, &len ); + req[n] = NULL; + requestString = concatenate_string_list( req, 4 ); + heap_free( req ); + if (!requestString) return NULL; - if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED, &len, sizeof(len) ); + /* + * Set (header) termination string for request + * Make sure there are exactly two new lines at the end of the request + */ + p = &requestString[strlenW(requestString)-1]; + while ( (*p == '\n') || (*p == '\r') ) + p--; + strcpyW( p+1, twocrlf ); - request->read_size += len; - return ret; + return requestString; } -/* discard data contents until we reach end of line */ -static BOOL discard_eol( request_t *request, BOOL notify ) -{ - do - { - char *eol = memchr( request->read_buf + request->read_pos, '\n', request->read_size ); - if (eol) - { - remove_data( request, (eol + 1) - (request->read_buf + request->read_pos) ); - break; - } - request->read_pos = request->read_size = 0; /* discard everything */ - if (!read_more_data( request, -1, notify )) return FALSE; - } while (request->read_size); - return TRUE; -} +static BOOL read_reply( request_t *request ); -/* read the size of the next chunk */ -static BOOL start_next_chunk( request_t *request, BOOL notify ) +static BOOL secure_proxy_connect( request_t *request ) { - DWORD chunk_size = 0; - - assert(!request->read_chunked_size || request->read_chunked_size == ~0u); - - if (request->read_chunked_eof) return FALSE; - - /* read terminator for the previous chunk */ - if (!request->read_chunked_size && !discard_eol( request, notify )) return FALSE; + static const WCHAR verbConnect[] = {'C','O','N','N','E','C','T',0}; + static const WCHAR fmt[] = {'%','s',':','%','u',0}; + BOOL ret = FALSE; + LPWSTR path; + connect_t *connect = request->connect; - for (;;) + path = heap_alloc( (strlenW( connect->hostname ) + 13) * sizeof(WCHAR) ); + if (path) { - while (request->read_size) - { - char ch = request->read_buf[request->read_pos]; - if (ch >= '0' && ch <= '9') chunk_size = chunk_size * 16 + ch - '0'; - else if (ch >= 'a' && ch <= 'f') chunk_size = chunk_size * 16 + ch - 'a' + 10; - else if (ch >= 'A' && ch <= 'F') chunk_size = chunk_size * 16 + ch - 'A' + 10; - else if (ch == ';' || ch == '\r' || ch == '\n') - { - TRACE("reading %u byte chunk\n", chunk_size); + LPWSTR requestString; - if (request->content_length == ~0u) request->content_length = chunk_size; - else request->content_length += chunk_size; + sprintfW( path, fmt, connect->hostname, connect->hostport ); + requestString = build_header_request_string( request, verbConnect, + path, http1_1 ); + heap_free( path ); + if (requestString) + { + LPSTR req_ascii = strdupWA( requestString ); - request->read_chunked_size = chunk_size; - if (!chunk_size) request->read_chunked_eof = TRUE; + heap_free( requestString ); + if (req_ascii) + { + int len = strlen( req_ascii ), bytes_sent; - return discard_eol( request, notify ); + ret = netconn_send( request->netconn, req_ascii, len, &bytes_sent ); + heap_free( req_ascii ); + if (ret) + ret = read_reply( request ); } - remove_data( request, 1 ); - } - if (!read_more_data( request, -1, notify )) return FALSE; - if (!request->read_size) - { - request->content_length = request->content_read = 0; - request->read_chunked_size = 0; - return TRUE; } } + return ret; } -static BOOL refill_buffer( request_t *request, BOOL notify ) +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +static WCHAR *addr_to_str( struct sockaddr_storage *addr ) { - int len = sizeof(request->read_buf); + char buf[INET6_ADDRSTRLEN]; + void *src; - if (request->read_chunked) - { - if (request->read_chunked_eof) return FALSE; - if (request->read_chunked_size == ~0u || !request->read_chunked_size) - { - if (!start_next_chunk( request, notify )) return FALSE; - } - len = min( len, request->read_chunked_size ); - } - else if (request->content_length != ~0u) + switch (addr->ss_family) { - len = min( len, request->content_length - request->content_read ); + case AF_INET: + src = &((struct sockaddr_in *)addr)->sin_addr; + break; + case AF_INET6: + src = &((struct sockaddr_in6 *)addr)->sin6_addr; + break; + default: + WARN("unsupported address family %d\n", addr->ss_family); + return NULL; } - - if (len <= request->read_size) return TRUE; - if (!read_more_data( request, len, notify )) return FALSE; - if (!request->read_size) request->content_length = request->content_read = 0; - return TRUE; + if (!inet_ntop( addr->ss_family, src, buf, sizeof(buf) )) return NULL; + return strdupAW( buf ); } -static void finished_reading( request_t *request ) +static CRITICAL_SECTION connection_pool_cs; +static CRITICAL_SECTION_DEBUG connection_pool_debug = { - static const WCHAR closeW[] = {'c','l','o','s','e',0}; - - BOOL close = FALSE; - WCHAR connection[20]; - DWORD size = sizeof(connection); + 0, 0, &connection_pool_cs, + { &connection_pool_debug.ProcessLocksList, &connection_pool_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": connection_pool_cs") } +}; +static CRITICAL_SECTION connection_pool_cs = { &connection_pool_debug, -1, 0, 0, 0, 0 }; - if (!request->netconn) return; +static struct list connection_pool = LIST_INIT( connection_pool ); - if (request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE) close = TRUE; - else if (query_headers( request, WINHTTP_QUERY_CONNECTION, NULL, connection, &size, NULL ) || - query_headers( request, WINHTTP_QUERY_PROXY_CONNECTION, NULL, connection, &size, NULL )) - { - if (!strcmpiW( connection, closeW )) close = TRUE; - } - else if (!strcmpW( request->version, http1_0 )) close = TRUE; - if (close) - { - close_connection( request ); - return; - } +void release_host( hostdata_t *host ) +{ + LONG ref; - cache_connection( request->netconn ); - request->netconn = NULL; -} + EnterCriticalSection( &connection_pool_cs ); + if (!(ref = --host->ref)) list_remove( &host->entry ); + LeaveCriticalSection( &connection_pool_cs ); + if (ref) return; -/* return the size of data available to be read immediately */ -static DWORD get_available_data( request_t *request ) -{ - if (request->read_chunked) return min( request->read_chunked_size, request->read_size ); - return request->read_size; + assert( list_empty( &host->connections ) ); + heap_free( host->hostname ); + heap_free( host ); } -/* check if we have reached the end of the data to read */ -static BOOL end_of_read_data( request_t *request ) -{ - if (!request->content_length) return TRUE; - if (request->read_chunked) return request->read_chunked_eof; - if (request->content_length == ~0u) return FALSE; - return (request->content_length == request->content_read); -} +static BOOL connection_collector_running; -static BOOL read_data( request_t *request, void *buffer, DWORD size, DWORD *read, BOOL async ) +static DWORD WINAPI connection_collector(void *arg) { - int count, bytes_read = 0; - - if (end_of_read_data( request )) goto done; + unsigned int remaining_connections; + netconn_t *netconn, *next_netconn; + hostdata_t *host, *next_host; + ULONGLONG now; - while (size) + do { - if (!(count = get_available_data( request ))) + /* FIXME: Use more sophisticated method */ + Sleep(5000); + remaining_connections = 0; + now = GetTickCount64(); + + EnterCriticalSection(&connection_pool_cs); + + LIST_FOR_EACH_ENTRY_SAFE(host, next_host, &connection_pool, hostdata_t, entry) { - if (!refill_buffer( request, async )) goto done; - if (!(count = get_available_data( request ))) goto done; + LIST_FOR_EACH_ENTRY_SAFE(netconn, next_netconn, &host->connections, netconn_t, entry) + { + if (netconn->keep_until < now) + { + TRACE("freeing %p\n", netconn); + list_remove(&netconn->entry); + netconn_close(netconn); + } + else + { + remaining_connections++; + } + } } - count = min( count, size ); - memcpy( (char *)buffer + bytes_read, request->read_buf + request->read_pos, count ); - remove_data( request, count ); - if (request->read_chunked) request->read_chunked_size -= count; - size -= count; - bytes_read += count; - request->content_read += count; - if (end_of_read_data( request )) goto done; - } - if (request->read_chunked && !request->read_chunked_size) refill_buffer( request, async ); -done: - TRACE( "retrieved %u bytes (%u/%u)\n", bytes_read, request->content_read, request->content_length ); + if (!remaining_connections) connection_collector_running = FALSE; - if (async) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, buffer, bytes_read ); - if (read) *read = bytes_read; - if (end_of_read_data( request )) finished_reading( request ); - return TRUE; + LeaveCriticalSection(&connection_pool_cs); + } while(remaining_connections); + + FreeLibraryAndExitThread( winhttp_instance, 0 ); } -/* read any content returned by the server so that the connection can be reused */ -static void drain_content( request_t *request ) +static void cache_connection( netconn_t *netconn ) { - DWORD size, bytes_read, bytes_total = 0, bytes_left = request->content_length - request->content_read; - char buffer[2048]; + TRACE( "caching connection %p\n", netconn ); - refill_buffer( request, FALSE ); - for (;;) + EnterCriticalSection( &connection_pool_cs ); + + netconn->keep_until = GetTickCount64() + DEFAULT_KEEP_ALIVE_TIMEOUT; + list_add_head( &netconn->host->connections, &netconn->entry ); + + if (!connection_collector_running) { - if (request->read_chunked) size = sizeof(buffer); + HMODULE module; + HANDLE thread; + + GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (const WCHAR*)winhttp_instance, &module ); + + thread = CreateThread(NULL, 0, connection_collector, NULL, 0, NULL); + if (thread) + { + CloseHandle( thread ); + connection_collector_running = TRUE; + } else { - if (bytes_total >= bytes_left) return; - size = min( sizeof(buffer), bytes_left - bytes_total ); + FreeLibrary( winhttp_instance ); } - if (!read_data( request, buffer, size, &bytes_read, FALSE ) || !bytes_read) return; - bytes_total += bytes_read; } + + LeaveCriticalSection( &connection_pool_cs ); } -static BOOL send_request( request_t *request, LPCWSTR headers, DWORD headers_len, LPVOID optional, - DWORD optional_len, DWORD total_len, DWORD_PTR context, BOOL async ) +static DWORD map_secure_protocols( DWORD mask ) { - static const WCHAR keep_alive[] = {'K','e','e','p','-','A','l','i','v','e',0}; - static const WCHAR no_cache[] = {'n','o','-','c','a','c','h','e',0}; - static const WCHAR length_fmt[] = {'%','l','d',0}; - - BOOL ret = FALSE; - connect_t *connect = request->connect; - session_t *session = connect->session; - WCHAR *req = NULL; - char *req_ascii; - int bytes_sent; - DWORD len, i, flags; - - clear_response_headers( request ); - drain_content( request ); + DWORD ret = 0; + if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_SSL2) ret |= SP_PROT_SSL2_CLIENT; + if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_SSL3) ret |= SP_PROT_SSL3_CLIENT; + if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1) ret |= SP_PROT_TLS1_CLIENT; + if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1) ret |= SP_PROT_TLS1_1_CLIENT; + if (mask & WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_2) ret |= SP_PROT_TLS1_2_CLIENT; + return ret; +} - flags = WINHTTP_ADDREQ_FLAG_ADD|WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA; - for (i = 0; i < request->num_accept_types; i++) - { - process_header( request, attr_accept, request->accept_types[i], flags, TRUE ); - } - if (session->agent) - process_header( request, attr_user_agent, session->agent, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); +static BOOL ensure_cred_handle( session_t *session ) +{ + SCHANNEL_CRED cred; + SECURITY_STATUS status; - if (connect->hostname) - add_host_header( request, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW ); + if (session->cred_handle_initialized) return TRUE; - if (total_len || (request->verb && !strcmpW( request->verb, postW ))) - { - WCHAR length[21]; /* decimal long int + null */ - sprintfW( length, length_fmt, total_len ); - process_header( request, attr_content_length, length, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); - } - if (!(request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE)) - { - process_header( request, attr_connection, keep_alive, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); - } - if (request->hdr.flags & WINHTTP_FLAG_REFRESH) - { - process_header( request, attr_pragma, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); - process_header( request, attr_cache_control, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); - } - if (headers && !add_request_headers( request, headers, headers_len, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE )) - { - TRACE("failed to add request headers\n"); - return FALSE; - } - if (!(request->hdr.disable_flags & WINHTTP_DISABLE_COOKIES) && !add_cookie_headers( request )) + memset( &cred, 0, sizeof(cred) ); + cred.dwVersion = SCHANNEL_CRED_VERSION; + cred.grbitEnabledProtocols = map_secure_protocols( session->secure_protocols ); + if ((status = AcquireCredentialsHandleW( NULL, (WCHAR *)UNISP_NAME_W, SECPKG_CRED_OUTBOUND, NULL, &cred, + NULL, NULL, &session->cred_handle, NULL )) != SEC_E_OK) { - WARN("failed to add cookie headers\n"); + WARN( "AcquireCredentialsHandleW failed: 0x%08x\n", status ); return FALSE; } + session->cred_handle_initialized = TRUE; + return TRUE; +} - if (context) request->hdr.context = context; - - if (!(ret = open_connection( request ))) goto end; - if (!(req = build_request_string( request ))) goto end; +static BOOL open_connection( request_t *request ) +{ + BOOL is_secure = request->hdr.flags & WINHTTP_FLAG_SECURE; + hostdata_t *host = NULL, *iter; + netconn_t *netconn = NULL; + connect_t *connect; + WCHAR *addressW = NULL; + INTERNET_PORT port; + DWORD len; - if (!(req_ascii = strdupWA( req ))) goto end; - TRACE("full request: %s\n", debugstr_a(req_ascii)); - len = strlen(req_ascii); + if (request->netconn) goto done; - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL, 0 ); + connect = request->connect; + port = connect->serverport ? connect->serverport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80); - ret = netconn_send( request->netconn, req_ascii, len, &bytes_sent ); - heap_free( req_ascii ); - if (!ret) goto end; + EnterCriticalSection( &connection_pool_cs ); - if (optional_len) + LIST_FOR_EACH_ENTRY( iter, &connection_pool, hostdata_t, entry ) { - if (!netconn_send( request->netconn, optional, optional_len, &bytes_sent )) goto end; - request->optional = optional; - request->optional_len = optional_len; - len += optional_len; + if (iter->port == port && !strcmpW( connect->servername, iter->hostname ) && !is_secure == !iter->secure) + { + host = iter; + host->ref++; + break; + } } - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_SENT, &len, sizeof(len) ); -end: - if (async) + if (!host) { - if (ret) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, NULL, 0 ); - else + if ((host = heap_alloc( sizeof(*host) ))) { - WINHTTP_ASYNC_RESULT result; - result.dwResult = API_SEND_REQUEST; - result.dwError = get_last_error(); - send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, &result, sizeof(result) ); + host->ref = 1; + host->secure = is_secure; + host->port = port; + list_init( &host->connections ); + if ((host->hostname = strdupW( connect->servername ))) + { + list_add_head( &connection_pool, &host->entry ); + } + else + { + heap_free( host ); + host = NULL; + } } } - heap_free( req ); - return ret; -} -static void task_send_request( task_header_t *task ) -{ - send_request_t *s = (send_request_t *)task; - send_request( s->hdr.request, s->headers, s->headers_len, s->optional, s->optional_len, s->total_len, s->context, TRUE ); - heap_free( s->headers ); -} + LeaveCriticalSection( &connection_pool_cs ); -/*********************************************************************** - * WinHttpSendRequest (winhttp.@) - */ -BOOL WINAPI WinHttpSendRequest( HINTERNET hrequest, LPCWSTR headers, DWORD headers_len, - LPVOID optional, DWORD optional_len, DWORD total_len, DWORD_PTR context ) -{ - BOOL ret; - request_t *request; + if (!host) return FALSE; - TRACE("%p, %s, 0x%x, %u, %u, %lx\n", - hrequest, debugstr_w(headers), headers_len, optional_len, total_len, context); + for (;;) + { + EnterCriticalSection( &connection_pool_cs ); + if (!list_empty( &host->connections )) + { + netconn = LIST_ENTRY( list_head( &host->connections ), netconn_t, entry ); + list_remove( &netconn->entry ); + } + LeaveCriticalSection( &connection_pool_cs ); + if (!netconn) break; - if (!(request = (request_t *)grab_object( hrequest ))) + if (netconn_is_alive( netconn )) break; + TRACE("connection %p no longer alive, closing\n", netconn); + netconn_close( netconn ); + netconn = NULL; + } + + if (!connect->resolved && netconn) { - set_last_error( ERROR_INVALID_HANDLE ); - return FALSE; + connect->sockaddr = netconn->sockaddr; + connect->resolved = TRUE; } - if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST) + + if (!connect->resolved) { - release_object( &request->hdr ); - set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE ); - return FALSE; - } + len = strlenW( host->hostname ) + 1; + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESOLVING_NAME, host->hostname, len ); - if (headers && !headers_len) headers_len = strlenW( headers ); + if (!netconn_resolve( host->hostname, port, &connect->sockaddr, request->resolve_timeout )) + { + release_host( host ); + return FALSE; + } + connect->resolved = TRUE; - if (request->connect->hdr.flags & WINHTTP_FLAG_ASYNC) + if (!(addressW = addr_to_str( &connect->sockaddr ))) + { + release_host( host ); + return FALSE; + } + len = strlenW( addressW ) + 1; + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_NAME_RESOLVED, addressW, len ); + } + + if (!netconn) { - send_request_t *s; + if (!addressW && !(addressW = addr_to_str( &connect->sockaddr ))) + { + release_host( host ); + return FALSE; + } - if (!(s = heap_alloc( sizeof(send_request_t) ))) return FALSE; - s->hdr.request = request; - s->hdr.proc = task_send_request; - s->headers = strdupW( headers ); - s->headers_len = headers_len; - s->optional = optional; - s->optional_len = optional_len; - s->total_len = total_len; - s->context = context; + TRACE("connecting to %s:%u\n", debugstr_w(addressW), port); - addref_object( &request->hdr ); - ret = queue_task( (task_header_t *)s ); - } - else - ret = send_request( request, headers, headers_len, optional, optional_len, total_len, context, FALSE ); + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTING_TO_SERVER, addressW, 0 ); - release_object( &request->hdr ); - if (ret) set_last_error( ERROR_SUCCESS ); - return ret; -} + if (!(netconn = netconn_create( host, &connect->sockaddr, request->connect_timeout ))) + { + heap_free( addressW ); + release_host( host ); + return FALSE; + } + netconn_set_timeout( netconn, TRUE, request->send_timeout ); + netconn_set_timeout( netconn, FALSE, request->recv_timeout ); + if (is_secure) + { + if (connect->session->proxy_server && + strcmpiW( connect->hostname, connect->servername )) + { + if (!secure_proxy_connect( request )) + { + heap_free( addressW ); + netconn_close( netconn ); + return FALSE; + } + } + if (!ensure_cred_handle( connect->session ) || + !netconn_secure_connect( netconn, connect->hostname, request->security_flags, + &connect->session->cred_handle )) + { + heap_free( addressW ); + netconn_close( netconn ); + return FALSE; + } + } -#define ARRAYSIZE(array) (sizeof(array) / sizeof((array)[0])) + request->netconn = netconn; + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTED_TO_SERVER, addressW, strlenW(addressW) + 1 ); + } + else + { + TRACE("using connection %p\n", netconn); -static const WCHAR basicW[] = {'B','a','s','i','c',0}; -static const WCHAR ntlmW[] = {'N','T','L','M',0}; -static const WCHAR passportW[] = {'P','a','s','s','p','o','r','t',0}; -static const WCHAR digestW[] = {'D','i','g','e','s','t',0}; -static const WCHAR negotiateW[] = {'N','e','g','o','t','i','a','t','e',0}; + netconn_set_timeout( netconn, TRUE, request->send_timeout ); + netconn_set_timeout( netconn, FALSE, request->recv_timeout ); + request->netconn = netconn; + } -static const struct -{ - const WCHAR *str; - unsigned int len; - DWORD scheme; +done: + request->read_pos = request->read_size = 0; + request->read_chunked = FALSE; + request->read_chunked_size = ~0u; + request->read_chunked_eof = FALSE; + heap_free( addressW ); + return TRUE; } -auth_schemes[] = -{ - { basicW, ARRAYSIZE(basicW) - 1, WINHTTP_AUTH_SCHEME_BASIC }, - { ntlmW, ARRAYSIZE(ntlmW) - 1, WINHTTP_AUTH_SCHEME_NTLM }, - { passportW, ARRAYSIZE(passportW) - 1, WINHTTP_AUTH_SCHEME_PASSPORT }, - { digestW, ARRAYSIZE(digestW) - 1, WINHTTP_AUTH_SCHEME_DIGEST }, - { negotiateW, ARRAYSIZE(negotiateW) - 1, WINHTTP_AUTH_SCHEME_NEGOTIATE } -}; -static const unsigned int num_auth_schemes = sizeof(auth_schemes)/sizeof(auth_schemes[0]); -static enum auth_scheme scheme_from_flag( DWORD flag ) +void close_connection( request_t *request ) { - int i; + if (!request->netconn) return; - for (i = 0; i < num_auth_schemes; i++) if (flag == auth_schemes[i].scheme) return i; - return SCHEME_INVALID; + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CLOSING_CONNECTION, 0, 0 ); + netconn_close( request->netconn ); + request->netconn = NULL; + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_CONNECTION_CLOSED, 0, 0 ); } -static DWORD auth_scheme_from_header( WCHAR *header ) +static BOOL add_host_header( request_t *request, DWORD modifier ) { - unsigned int i; + BOOL ret; + DWORD len; + WCHAR *host; + static const WCHAR fmt[] = {'%','s',':','%','u',0}; + connect_t *connect = request->connect; + INTERNET_PORT port; - for (i = 0; i < num_auth_schemes; i++) + port = connect->hostport ? connect->hostport : (request->hdr.flags & WINHTTP_FLAG_SECURE ? 443 : 80); + + if (port == INTERNET_DEFAULT_HTTP_PORT || port == INTERNET_DEFAULT_HTTPS_PORT) { - if (!strncmpiW( header, auth_schemes[i].str, auth_schemes[i].len ) && - (header[auth_schemes[i].len] == ' ' || !header[auth_schemes[i].len])) return auth_schemes[i].scheme; + return process_header( request, attr_host, connect->hostname, modifier, TRUE ); } - return 0; + len = strlenW( connect->hostname ) + 7; /* sizeof(":65335") */ + if (!(host = heap_alloc( len * sizeof(WCHAR) ))) return FALSE; + sprintfW( host, fmt, connect->hostname, port ); + ret = process_header( request, attr_host, host, modifier, TRUE ); + heap_free( host ); + return ret; } -static BOOL query_auth_schemes( request_t *request, DWORD level, LPDWORD supported, LPDWORD first ) +static void clear_response_headers( request_t *request ) { - DWORD index = 0, supported_schemes = 0, first_scheme = 0; - BOOL ret = FALSE; + unsigned int i; - for (;;) + for (i = 0; i < request->num_headers; i++) { - WCHAR *buffer; - DWORD size, scheme; - - size = 0; - query_headers( request, level, NULL, NULL, &size, &index ); - if (get_last_error() != ERROR_INSUFFICIENT_BUFFER) break; - - index--; - if (!(buffer = heap_alloc( size ))) return FALSE; - if (!query_headers( request, level, NULL, buffer, &size, &index )) - { - heap_free( buffer ); - return FALSE; - } - scheme = auth_scheme_from_header( buffer ); - heap_free( buffer ); - if (!scheme) continue; - - if (!first_scheme) first_scheme = scheme; - supported_schemes |= scheme; - - ret = TRUE; + if (!request->headers[i].field) continue; + if (!request->headers[i].value) continue; + if (request->headers[i].is_request) continue; + delete_header( request, i ); + i--; } +} - if (ret) - { - *supported = supported_schemes; - *first = first_scheme; - } - return ret; +/* remove some amount of data from the read buffer */ +static void remove_data( request_t *request, int count ) +{ + if (!(request->read_size -= count)) request->read_pos = 0; + else request->read_pos += count; } -/*********************************************************************** - * WinHttpQueryAuthSchemes (winhttp.@) - */ -BOOL WINAPI WinHttpQueryAuthSchemes( HINTERNET hrequest, LPDWORD supported, LPDWORD first, LPDWORD target ) +/* read some more data into the read buffer */ +static BOOL read_more_data( request_t *request, int maxlen, BOOL notify ) { - BOOL ret = FALSE; - request_t *request; + int len; + BOOL ret; - TRACE("%p, %p, %p, %p\n", hrequest, supported, first, target); + if (request->read_chunked_eof) return FALSE; - if (!(request = (request_t *)grab_object( hrequest ))) - { - set_last_error( ERROR_INVALID_HANDLE ); - return FALSE; - } - if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST) + if (request->read_size && request->read_pos) { - release_object( &request->hdr ); - set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE ); - return FALSE; + /* move existing data to the start of the buffer */ + memmove( request->read_buf, request->read_buf + request->read_pos, request->read_size ); + request->read_pos = 0; } - if (!supported || !first || !target) - { - release_object( &request->hdr ); - set_last_error( ERROR_INVALID_PARAMETER ); - return FALSE; + if (maxlen == -1) maxlen = sizeof(request->read_buf); - } + if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RECEIVING_RESPONSE, NULL, 0 ); - if (query_auth_schemes( request, WINHTTP_QUERY_WWW_AUTHENTICATE, supported, first )) - { - *target = WINHTTP_AUTH_TARGET_SERVER; - ret = TRUE; - } - else if (query_auth_schemes( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, supported, first )) - { - *target = WINHTTP_AUTH_TARGET_PROXY; - ret = TRUE; - } + ret = netconn_recv( request->netconn, request->read_buf + request->read_size, + maxlen - request->read_size, 0, &len ); - release_object( &request->hdr ); - if (ret) set_last_error( ERROR_SUCCESS ); + if (notify) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_RESPONSE_RECEIVED, &len, sizeof(len) ); + + request->read_size += len; return ret; } -static UINT encode_base64( const char *bin, unsigned int len, WCHAR *base64 ) +/* discard data contents until we reach end of line */ +static BOOL discard_eol( request_t *request, BOOL notify ) { - UINT n = 0, x; - static const char base64enc[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - - while (len > 0) + do { - /* first 6 bits, all from bin[0] */ - base64[n++] = base64enc[(bin[0] & 0xfc) >> 2]; - x = (bin[0] & 3) << 4; - - /* next 6 bits, 2 from bin[0] and 4 from bin[1] */ - if (len == 1) - { - base64[n++] = base64enc[x]; - base64[n++] = '='; - base64[n++] = '='; - break; - } - base64[n++] = base64enc[x | ((bin[1] & 0xf0) >> 4)]; - x = (bin[1] & 0x0f) << 2; - - /* next 6 bits 4 from bin[1] and 2 from bin[2] */ - if (len == 2) + char *eol = memchr( request->read_buf + request->read_pos, '\n', request->read_size ); + if (eol) { - base64[n++] = base64enc[x]; - base64[n++] = '='; + remove_data( request, (eol + 1) - (request->read_buf + request->read_pos) ); break; } - base64[n++] = base64enc[x | ((bin[2] & 0xc0) >> 6)]; - - /* last 6 bits, all from bin [2] */ - base64[n++] = base64enc[bin[2] & 0x3f]; - bin += 3; - len -= 3; - } - base64[n] = 0; - return n; + request->read_pos = request->read_size = 0; /* discard everything */ + if (!read_more_data( request, -1, notify )) return FALSE; + } while (request->read_size); + return TRUE; } -static inline char decode_char( WCHAR c ) +/* read the size of the next chunk */ +static BOOL start_next_chunk( request_t *request, BOOL notify ) { - if (c >= 'A' && c <= 'Z') return c - 'A'; - if (c >= 'a' && c <= 'z') return c - 'a' + 26; - if (c >= '0' && c <= '9') return c - '0' + 52; - if (c == '+') return 62; - if (c == '/') return 63; - return 64; -} + DWORD chunk_size = 0; -static unsigned int decode_base64( const WCHAR *base64, unsigned int len, char *buf ) -{ - unsigned int i = 0; - char c0, c1, c2, c3; - const WCHAR *p = base64; + assert(!request->read_chunked_size || request->read_chunked_size == ~0u); - while (len > 4) + if (request->read_chunked_eof) return FALSE; + + /* read terminator for the previous chunk */ + if (!request->read_chunked_size && !discard_eol( request, notify )) return FALSE; + + for (;;) { - if ((c0 = decode_char( p[0] )) > 63) return 0; - if ((c1 = decode_char( p[1] )) > 63) return 0; - if ((c2 = decode_char( p[2] )) > 63) return 0; - if ((c3 = decode_char( p[3] )) > 63) return 0; + while (request->read_size) + { + char ch = request->read_buf[request->read_pos]; + if (ch >= '0' && ch <= '9') chunk_size = chunk_size * 16 + ch - '0'; + else if (ch >= 'a' && ch <= 'f') chunk_size = chunk_size * 16 + ch - 'a' + 10; + else if (ch >= 'A' && ch <= 'F') chunk_size = chunk_size * 16 + ch - 'A' + 10; + else if (ch == ';' || ch == '\r' || ch == '\n') + { + TRACE("reading %u byte chunk\n", chunk_size); - if (buf) + if (request->content_length == ~0u) request->content_length = chunk_size; + else request->content_length += chunk_size; + + request->read_chunked_size = chunk_size; + if (!chunk_size) request->read_chunked_eof = TRUE; + + return discard_eol( request, notify ); + } + remove_data( request, 1 ); + } + if (!read_more_data( request, -1, notify )) return FALSE; + if (!request->read_size) { - buf[i + 0] = (c0 << 2) | (c1 >> 4); - buf[i + 1] = (c1 << 4) | (c2 >> 2); - buf[i + 2] = (c2 << 6) | c3; + request->content_length = request->content_read = 0; + request->read_chunked_size = 0; + return TRUE; } - len -= 4; - i += 3; - p += 4; } - if (p[2] == '=') - { - if ((c0 = decode_char( p[0] )) > 63) return 0; - if ((c1 = decode_char( p[1] )) > 63) return 0; +} - if (buf) buf[i] = (c0 << 2) | (c1 >> 4); - i++; - } - else if (p[3] == '=') - { - if ((c0 = decode_char( p[0] )) > 63) return 0; - if ((c1 = decode_char( p[1] )) > 63) return 0; - if ((c2 = decode_char( p[2] )) > 63) return 0; +static BOOL refill_buffer( request_t *request, BOOL notify ) +{ + int len = sizeof(request->read_buf); - if (buf) + if (request->read_chunked) + { + if (request->read_chunked_eof) return FALSE; + if (request->read_chunked_size == ~0u || !request->read_chunked_size) { - buf[i + 0] = (c0 << 2) | (c1 >> 4); - buf[i + 1] = (c1 << 4) | (c2 >> 2); + if (!start_next_chunk( request, notify )) return FALSE; } - i += 2; + len = min( len, request->read_chunked_size ); } - else + else if (request->content_length != ~0u) { - if ((c0 = decode_char( p[0] )) > 63) return 0; - if ((c1 = decode_char( p[1] )) > 63) return 0; - if ((c2 = decode_char( p[2] )) > 63) return 0; - if ((c3 = decode_char( p[3] )) > 63) return 0; - - if (buf) - { - buf[i + 0] = (c0 << 2) | (c1 >> 4); - buf[i + 1] = (c1 << 4) | (c2 >> 2); - buf[i + 2] = (c2 << 6) | c3; - } - i += 3; + len = min( len, request->content_length - request->content_read ); } - return i; + + if (len <= request->read_size) return TRUE; + if (!read_more_data( request, len, notify )) return FALSE; + if (!request->read_size) request->content_length = request->content_read = 0; + return TRUE; } -static struct authinfo *alloc_authinfo(void) +static void finished_reading( request_t *request ) { - struct authinfo *ret; + static const WCHAR closeW[] = {'c','l','o','s','e',0}; - if (!(ret = heap_alloc( sizeof(*ret) ))) return NULL; + BOOL close = FALSE; + WCHAR connection[20]; + DWORD size = sizeof(connection); - SecInvalidateHandle( &ret->cred ); - SecInvalidateHandle( &ret->ctx ); - memset( &ret->exp, 0, sizeof(ret->exp) ); - ret->scheme = 0; - ret->attr = 0; - ret->max_token = 0; - ret->data = NULL; - ret->data_len = 0; - ret->finished = FALSE; - return ret; + if (!request->netconn) return; + + if (request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE) close = TRUE; + else if (query_headers( request, WINHTTP_QUERY_CONNECTION, NULL, connection, &size, NULL ) || + query_headers( request, WINHTTP_QUERY_PROXY_CONNECTION, NULL, connection, &size, NULL )) + { + if (!strcmpiW( connection, closeW )) close = TRUE; + } + else if (!strcmpW( request->version, http1_0 )) close = TRUE; + if (close) + { + close_connection( request ); + return; + } + + cache_connection( request->netconn ); + request->netconn = NULL; } -void destroy_authinfo( struct authinfo *authinfo ) +/* return the size of data available to be read immediately */ +static DWORD get_available_data( request_t *request ) { - if (!authinfo) return; - - if (SecIsValidHandle( &authinfo->ctx )) - DeleteSecurityContext( &authinfo->ctx ); - if (SecIsValidHandle( &authinfo->cred )) - FreeCredentialsHandle( &authinfo->cred ); + if (request->read_chunked) return min( request->read_chunked_size, request->read_size ); + return request->read_size; +} - heap_free( authinfo->data ); - heap_free( authinfo ); +/* check if we have reached the end of the data to read */ +static BOOL end_of_read_data( request_t *request ) +{ + if (!request->content_length) return TRUE; + if (request->read_chunked) return request->read_chunked_eof; + if (request->content_length == ~0u) return FALSE; + return (request->content_length == request->content_read); } -static BOOL get_authvalue( request_t *request, DWORD level, DWORD scheme, WCHAR *buffer, DWORD len ) +static BOOL read_data( request_t *request, void *buffer, DWORD size, DWORD *read, BOOL async ) { - DWORD size, index = 0; - for (;;) + int count, bytes_read = 0; + + if (end_of_read_data( request )) goto done; + + while (size) { - size = len; - if (!query_headers( request, level, NULL, buffer, &size, &index )) return FALSE; - if (auth_scheme_from_header( buffer ) == scheme) break; + if (!(count = get_available_data( request ))) + { + if (!refill_buffer( request, async )) goto done; + if (!(count = get_available_data( request ))) goto done; + } + count = min( count, size ); + memcpy( (char *)buffer + bytes_read, request->read_buf + request->read_pos, count ); + remove_data( request, count ); + if (request->read_chunked) request->read_chunked_size -= count; + size -= count; + bytes_read += count; + request->content_read += count; + if (end_of_read_data( request )) goto done; } + if (request->read_chunked && !request->read_chunked_size) refill_buffer( request, async ); + +done: + TRACE( "retrieved %u bytes (%u/%u)\n", bytes_read, request->content_read, request->content_length ); + + if (async) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_READ_COMPLETE, buffer, bytes_read ); + if (read) *read = bytes_read; + if (end_of_read_data( request )) finished_reading( request ); return TRUE; } -static BOOL do_authorization( request_t *request, DWORD target, DWORD scheme_flag ) +/* read any content returned by the server so that the connection can be reused */ +static void drain_content( request_t *request ) { - struct authinfo *authinfo, **auth_ptr; - enum auth_scheme scheme = scheme_from_flag( scheme_flag ); - const WCHAR *auth_target, *username, *password; - WCHAR auth_value[2048], *auth_reply; - DWORD len = sizeof(auth_value), len_scheme, flags; - BOOL ret; - - if (scheme == SCHEME_INVALID) return FALSE; + DWORD size, bytes_read, bytes_total = 0, bytes_left = request->content_length - request->content_read; + char buffer[2048]; - switch (target) + refill_buffer( request, FALSE ); + for (;;) { - case WINHTTP_AUTH_TARGET_SERVER: - if (!get_authvalue( request, WINHTTP_QUERY_WWW_AUTHENTICATE, scheme_flag, auth_value, len )) - return FALSE; - auth_ptr = &request->authinfo; - auth_target = attr_authorization; - if (request->creds[TARGET_SERVER][scheme].username) - { - username = request->creds[TARGET_SERVER][scheme].username; - password = request->creds[TARGET_SERVER][scheme].password; - } + if (request->read_chunked) size = sizeof(buffer); else { - username = request->connect->username; - password = request->connect->password; - } - break; - - case WINHTTP_AUTH_TARGET_PROXY: - if (!get_authvalue( request, WINHTTP_QUERY_PROXY_AUTHENTICATE, scheme_flag, auth_value, len )) - return FALSE; - auth_ptr = &request->proxy_authinfo; - auth_target = attr_proxy_authorization; - if (request->creds[TARGET_PROXY][scheme].username) - { - username = request->creds[TARGET_PROXY][scheme].username; - password = request->creds[TARGET_PROXY][scheme].password; - } - else - { - username = request->connect->session->proxy_username; - password = request->connect->session->proxy_password; + if (bytes_total >= bytes_left) return; + size = min( sizeof(buffer), bytes_left - bytes_total ); } - break; - - default: - WARN("unknown target %x\n", target); - return FALSE; + if (!read_data( request, buffer, size, &bytes_read, FALSE ) || !bytes_read) return; + bytes_total += bytes_read; } - authinfo = *auth_ptr; +} - switch (scheme) - { - case SCHEME_BASIC: - { - int userlen, passlen; +static BOOL send_request( request_t *request, LPCWSTR headers, DWORD headers_len, LPVOID optional, + DWORD optional_len, DWORD total_len, DWORD_PTR context, BOOL async ) +{ + static const WCHAR keep_alive[] = {'K','e','e','p','-','A','l','i','v','e',0}; + static const WCHAR no_cache[] = {'n','o','-','c','a','c','h','e',0}; + static const WCHAR length_fmt[] = {'%','l','d',0}; - if (!username || !password) return FALSE; - if ((!authinfo && !(authinfo = alloc_authinfo())) || authinfo->finished) return FALSE; + BOOL ret = FALSE; + connect_t *connect = request->connect; + session_t *session = connect->session; + WCHAR *req = NULL; + char *req_ascii; + int bytes_sent; + DWORD len, i, flags; - userlen = WideCharToMultiByte( CP_UTF8, 0, username, strlenW( username ), NULL, 0, NULL, NULL ); - passlen = WideCharToMultiByte( CP_UTF8, 0, password, strlenW( password ), NULL, 0, NULL, NULL ); + clear_response_headers( request ); + drain_content( request ); - authinfo->data_len = userlen + 1 + passlen; - if (!(authinfo->data = heap_alloc( authinfo->data_len ))) return FALSE; + flags = WINHTTP_ADDREQ_FLAG_ADD|WINHTTP_ADDREQ_FLAG_COALESCE_WITH_COMMA; + for (i = 0; i < request->num_accept_types; i++) + { + process_header( request, attr_accept, request->accept_types[i], flags, TRUE ); + } + if (session->agent) + process_header( request, attr_user_agent, session->agent, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); - WideCharToMultiByte( CP_UTF8, 0, username, -1, authinfo->data, userlen, NULL, NULL ); - authinfo->data[userlen] = ':'; - WideCharToMultiByte( CP_UTF8, 0, password, -1, authinfo->data + userlen + 1, passlen, NULL, NULL ); + if (connect->hostname) + add_host_header( request, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW ); - authinfo->scheme = SCHEME_BASIC; - authinfo->finished = TRUE; - break; + if (total_len || (request->verb && !strcmpW( request->verb, postW ))) + { + WCHAR length[21]; /* decimal long int + null */ + sprintfW( length, length_fmt, total_len ); + process_header( request, attr_content_length, length, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); } - case SCHEME_NTLM: - case SCHEME_NEGOTIATE: + if (!(request->hdr.disable_flags & WINHTTP_DISABLE_KEEP_ALIVE)) { - SECURITY_STATUS status; - SecBufferDesc out_desc, in_desc; - SecBuffer out, in; - ULONG flags = ISC_REQ_CONNECTION|ISC_REQ_USE_DCE_STYLE|ISC_REQ_MUTUAL_AUTH|ISC_REQ_DELEGATE; - const WCHAR *p; - BOOL first = FALSE; + process_header( request, attr_connection, keep_alive, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); + } + if (request->hdr.flags & WINHTTP_FLAG_REFRESH) + { + process_header( request, attr_pragma, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); + process_header( request, attr_cache_control, no_cache, WINHTTP_ADDREQ_FLAG_ADD_IF_NEW, TRUE ); + } + if (headers && !add_request_headers( request, headers, headers_len, WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE )) + { + TRACE("failed to add request headers\n"); + return FALSE; + } + if (!(request->hdr.disable_flags & WINHTTP_DISABLE_COOKIES) && !add_cookie_headers( request )) + { + WARN("failed to add cookie headers\n"); + return FALSE; + } - if (!authinfo) - { - TimeStamp exp; - SEC_WINNT_AUTH_IDENTITY_W id; - WCHAR *domain, *user; + if (context) request->hdr.context = context; - if (!username || !password || !(authinfo = alloc_authinfo())) return FALSE; + if (!(ret = open_connection( request ))) goto end; + if (!(req = build_request_string( request ))) goto end; - first = TRUE; - domain = (WCHAR *)username; - user = strchrW( username, '\\' ); + if (!(req_ascii = strdupWA( req ))) goto end; + TRACE("full request: %s\n", debugstr_a(req_ascii)); + len = strlen(req_ascii); - if (user) user++; - else - { - user = (WCHAR *)username; - domain = NULL; - } - id.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE; - id.User = user; - id.UserLength = strlenW( user ); - id.Domain = domain; - id.DomainLength = domain ? user - domain - 1 : 0; - id.Password = (WCHAR *)password; - id.PasswordLength = strlenW( password ); + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDING_REQUEST, NULL, 0 ); - status = AcquireCredentialsHandleW( NULL, (SEC_WCHAR *)auth_schemes[scheme].str, - SECPKG_CRED_OUTBOUND, NULL, &id, NULL, NULL, - &authinfo->cred, &exp ); - if (status == SEC_E_OK) - { - PSecPkgInfoW info; - status = QuerySecurityPackageInfoW( (SEC_WCHAR *)auth_schemes[scheme].str, &info ); - if (status == SEC_E_OK) - { - authinfo->max_token = info->cbMaxToken; - FreeContextBuffer( info ); - } - } - if (status != SEC_E_OK) - { - WARN("AcquireCredentialsHandleW for scheme %s failed with error 0x%08x\n", - debugstr_w(auth_schemes[scheme].str), status); - heap_free( authinfo ); - return FALSE; - } - authinfo->scheme = scheme; - } - else if (authinfo->finished) return FALSE; + ret = netconn_send( request->netconn, req_ascii, len, &bytes_sent ); + heap_free( req_ascii ); + if (!ret) goto end; - if ((strlenW( auth_value ) < auth_schemes[authinfo->scheme].len || - strncmpiW( auth_value, auth_schemes[authinfo->scheme].str, auth_schemes[authinfo->scheme].len ))) + if (optional_len) + { + if (!netconn_send( request->netconn, optional, optional_len, &bytes_sent )) goto end; + request->optional = optional; + request->optional_len = optional_len; + len += optional_len; + } + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_SENT, &len, sizeof(len) ); + +end: + if (async) + { + if (ret) send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, NULL, 0 ); + else { - ERR("authentication scheme changed from %s to %s\n", - debugstr_w(auth_schemes[authinfo->scheme].str), debugstr_w(auth_value)); - destroy_authinfo( authinfo ); - *auth_ptr = NULL; - return FALSE; + WINHTTP_ASYNC_RESULT result; + result.dwResult = API_SEND_REQUEST; + result.dwError = get_last_error(); + send_callback( &request->hdr, WINHTTP_CALLBACK_STATUS_REQUEST_ERROR, &result, sizeof(result) ); } - in.BufferType = SECBUFFER_TOKEN; - in.cbBuffer = 0; - in.pvBuffer = NULL; + } + heap_free( req ); + return ret; +} - in_desc.ulVersion = 0; - in_desc.cBuffers = 1; - in_desc.pBuffers = ∈ +static void task_send_request( task_header_t *task ) +{ + send_request_t *s = (send_request_t *)task; + send_request( s->hdr.request, s->headers, s->headers_len, s->optional, s->optional_len, s->total_len, s->context, TRUE ); + heap_free( s->headers ); +} - p = auth_value + auth_schemes[scheme].len; - if (*p == ' ') - { - int len = strlenW( ++p ); - in.cbBuffer = decode_base64( p, len, NULL ); - if (!(in.pvBuffer = heap_alloc( in.cbBuffer ))) { - destroy_authinfo( authinfo ); - *auth_ptr = NULL; - return FALSE; - } - decode_base64( p, len, in.pvBuffer ); - } - out.BufferType = SECBUFFER_TOKEN; - out.cbBuffer = authinfo->max_token; - if (!(out.pvBuffer = heap_alloc( authinfo->max_token ))) - { - heap_free( in.pvBuffer ); - destroy_authinfo( authinfo ); - *auth_ptr = NULL; - return FALSE; - } - out_desc.ulVersion = 0; - out_desc.cBuffers = 1; - out_desc.pBuffers = &out; +/*********************************************************************** + * WinHttpSendRequest (winhttp.@) + */ +BOOL WINAPI WinHttpSendRequest( HINTERNET hrequest, LPCWSTR headers, DWORD headers_len, + LPVOID optional, DWORD optional_len, DWORD total_len, DWORD_PTR context ) +{ + BOOL ret; + request_t *request; - status = InitializeSecurityContextW( first ? &authinfo->cred : NULL, first ? NULL : &authinfo->ctx, - first ? request->connect->servername : NULL, flags, 0, - SECURITY_NETWORK_DREP, in.pvBuffer ? &in_desc : NULL, 0, - &authinfo->ctx, &out_desc, &authinfo->attr, &authinfo->exp ); - heap_free( in.pvBuffer ); - if (status == SEC_E_OK) - { - heap_free( authinfo->data ); - authinfo->data = out.pvBuffer; - authinfo->data_len = out.cbBuffer; - authinfo->finished = TRUE; - TRACE("sending last auth packet\n"); - } - else if (status == SEC_I_CONTINUE_NEEDED) - { - heap_free( authinfo->data ); - authinfo->data = out.pvBuffer; - authinfo->data_len = out.cbBuffer; - TRACE("sending next auth packet\n"); - } - else - { - ERR("InitializeSecurityContextW failed with error 0x%08x\n", status); - heap_free( out.pvBuffer ); - destroy_authinfo( authinfo ); - *auth_ptr = NULL; - return FALSE; - } - break; + TRACE("%p, %s, 0x%x, %u, %u, %lx\n", + hrequest, debugstr_w(headers), headers_len, optional_len, total_len, context); + + if (!(request = (request_t *)grab_object( hrequest ))) + { + set_last_error( ERROR_INVALID_HANDLE ); + return FALSE; } - default: - ERR("invalid scheme %u\n", scheme); + if (request->hdr.type != WINHTTP_HANDLE_TYPE_REQUEST) + { + release_object( &request->hdr ); + set_last_error( ERROR_WINHTTP_INCORRECT_HANDLE_TYPE ); return FALSE; } - *auth_ptr = authinfo; - len_scheme = auth_schemes[authinfo->scheme].len; - len = len_scheme + 1 + ((authinfo->data_len + 2) * 4) / 3; - if (!(auth_reply = heap_alloc( (len + 1) * sizeof(WCHAR) ))) return FALSE; + if (headers && !headers_len) headers_len = strlenW( headers ); - memcpy( auth_reply, auth_schemes[authinfo->scheme].str, len_scheme * sizeof(WCHAR) ); - auth_reply[len_scheme] = ' '; - encode_base64( authinfo->data, authinfo->data_len, auth_reply + len_scheme + 1 ); + if (request->connect->hdr.flags & WINHTTP_FLAG_ASYNC) + { + send_request_t *s; - flags = WINHTTP_ADDREQ_FLAG_ADD | WINHTTP_ADDREQ_FLAG_REPLACE; - ret = process_header( request, auth_target, auth_reply, flags, TRUE ); - heap_free( auth_reply ); + if (!(s = heap_alloc( sizeof(send_request_t) ))) return FALSE; + s->hdr.request = request; + s->hdr.proc = task_send_request; + s->headers = strdupW( headers ); + s->headers_len = headers_len; + s->optional = optional; + s->optional_len = optional_len; + s->total_len = total_len; + s->context = context; + + addref_object( &request->hdr ); + ret = queue_task( (task_header_t *)s ); + } + else + ret = send_request( request, headers, headers_len, optional, optional_len, total_len, context, FALSE ); + + release_object( &request->hdr ); + if (ret) set_last_error( ERROR_SUCCESS ); return ret; } -- 2.11.4.GIT