/*++ Copyright (c) 1994 Microsoft Corporation Module Name: socket.cxx Abstract: This file contains general socket utilities. Contents: HTTP_REQUEST_HANDLE_OBJECT::OpenConnection CFsm_OpenConnection::RunSM HTTP_REQUEST_HANDLE_OBJECT::OpenConnection_Fsm HTTP_REQUEST_HANDLE_OBJECT::CloseConnection HTTP_REQUEST_HANDLE_OBJECT::ReleaseConnection HTTP_REQUEST_HANDLE_OBJECT::AbortConnection HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel CFsm_OpenProxyTunnel::RunSM HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel_Fsm HTTP_REQUEST_HANDLE_OBJECT::CloneResponseBuffer Author: Keith Moore (keithmo) 16-Nov-1994 Revision History: 18-Dec-1995 rfirth Reworked for C++ 27-Mar-1996 arthurbi Added OpenProxyTunnel Method --*/ #include #include #include "httpp.h" // // functions // DWORD HTTP_REQUEST_HANDLE_OBJECT::OpenConnection( IN BOOL bNewConnection, IN BOOL fNoCreate /* = FALSE */ ) /*++ Routine Description: Get a connection to the web server. Either use a pre-existing keep-alive connection from the global pool or create a new connection Arguments: bNewConnection - TRUE if we are NOT to get a connection from the keep-alive pool fNoCreate - TRUE if we should NOT create a new socket if a k-a isn't found. This is currently for the SSL tunneling case where we want to break and send a CONNECT if a k-a doesn't match our criteria. Return Value: DWORD Success - ERROR_SUCCESS Opened connection ERROR_IO_PENDING Operation will complete asynchronously Failure - --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::OpenConnection", "%B", bNewConnection )); DWORD error = DoFsm(New CFsm_OpenConnection(bNewConnection, this, fNoCreate)); DEBUG_LEAVE(error); return error; } DWORD CFsm_OpenConnection::RunSM( IN CFsm * Fsm ) /*++ Routine Description: Runs next OpenConnection state Arguments: Fsm - containing open connection state info Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "CFsm_OpenConnection::RunSM", "%#x", Fsm )); DWORD error; HTTP_REQUEST_HANDLE_OBJECT * pRequest; CFsm_OpenConnection * stateMachine = (CFsm_OpenConnection *)Fsm; START_SENDREQ_PERF(); pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext(); switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pRequest->OpenConnection_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } STOP_SENDREQ_PERF(); DEBUG_LEAVE(error); return error; } DWORD HTTP_REQUEST_HANDLE_OBJECT::OpenConnection_Fsm( IN CFsm_OpenConnection * Fsm ) /*++ Routine Description: Open connection FSM Arguments: Fsm - containing state info Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::OpenConnection_Fsm", "%#x", Fsm )); CFsm_OpenConnection & fsm = *Fsm; DWORD error = fsm.GetError(); CServerInfo * pServerInfo = GetServerInfo(); if (error != ERROR_SUCCESS) { goto quit; } // // BUGBUG - redundancy. Either put these in the FSM or figure out why we need // to do proxy name processing here // // // if this object was created from an InternetOpen() handle which specified // INTERNET_OPEN_TYPE_PROXY then we connect to the proxy, otherwise we // connect to the server specified in InternetConnect() // LPSTR hostName; LPSTR hostNameServer; DWORD hostLength; INTERNET_PORT hostPort; hostName = hostNameServer = GetHostName(&hostLength); hostPort = GetHostPort(); LPSTR proxyHostName; DWORD proxyHostNameLength; INTERNET_PORT proxyHostPort; GetProxyName(&proxyHostName, &proxyHostNameLength, &proxyHostPort ); if ((proxyHostName != NULL) && (proxyHostNameLength > 0)) { SetViaProxy(TRUE); hostName = proxyHostName; hostLength = proxyHostNameLength; hostPort = proxyHostPort; } INET_ASSERT(hostName != NULL); INET_ASSERT(hostPort != INTERNET_INVALID_PORT_NUMBER); if (fsm.GetState() != FSM_STATE_INIT) { switch (fsm.GetFunctionState()) { case FSM_STATE_1: goto get_continue; case FSM_STATE_2: goto connect_continue; default: INET_ASSERT(FALSE); error = ERROR_WINHTTP_INTERNAL_ERROR; goto quit; } } // // we may already have a keep-alive connection - don't ask for a new one. // This happens in the challenge phase of a multi-part (e.g. NTLM) auth // negotiation over keep-alive // if (IsWantKeepAlive() && !fsm.m_bNewConnection && (_Socket != NULL) && _Socket->IsOpen()) { //INET_ASSERT(_bKeepAliveConnection); //if ( IsTunnel() ) //{ // dprintf("Tunnel for nested req=%#x, ALREADY open on Socket=%#x\n", this, _Socket ); //} error = ERROR_SUCCESS; goto quit; } INET_ASSERT(pServerInfo != NULL); if (pServerInfo == NULL) { error = ERROR_WINHTTP_INTERNAL_ERROR; goto quit; } // // if this request wants a keep-alive connection AND we are allowed to use // one (i.e. not forced to generate a new connection) AND we can find one // then we're done, otherwise we have to generate a new connection // DWORD dwSocketFlags; dwSocketFlags = IsAsyncHandle() ? SF_NON_BLOCKING : 0; if ((IsWantKeepAlive() || (GetOpenFlags() & INTERNET_FLAG_KEEP_CONNECTION)) && !fsm.m_bNewConnection) { dwSocketFlags |= SF_KEEP_ALIVE; } if (GetOpenFlags() & WINHTTP_FLAG_SECURE) { dwSocketFlags |= SF_SECURE; } if ( IsTunnel() ) { dwSocketFlags |= SF_TUNNEL; // dprintf("Opening Tunnel for nested req=%#x, Socket Flags=%#x, K-A=%B, Secure=%B, N-B=%B\n", // this, dwSocketFlags, (dwSocketFlags & SF_KEEP_ALIVE), (dwSocketFlags & SF_SECURE), // (dwSocketFlags & SF_NON_BLOCKING)); } INET_ASSERT(_Socket == NULL); _Socket = NULL; fsm.SetFunctionState(FSM_STATE_1); // If m_fNoCreate flag is set, then we're attempting to find // a matching SSL tunnel that's already been established. error = DoFsm(new CFsm_GetConnection( dwSocketFlags, fsm.m_fNoCreate ? GetHostPort() : hostPort, GetTimeoutValue(WINHTTP_OPTION_CONNECT_TIMEOUT), 10000, // dwLimitTimeout &_Socket, pServerInfo, fsm.m_fNoCreate ? hostNameServer : NULL )); get_continue: if (error != ERROR_SUCCESS) { goto quit; } if (_Socket != NULL) { // // _bKeepAliveConnection now means "this is a pre-existing k-a socket". // Only meaningful when re-establishing connect when dropped by server // //if ( IsTunnel() ) //{ // dprintf("Tunnel for nested req=%#x\n opened on K-A Socket=%#x\n", this, _Socket ); //} //dprintf("%s existing K-A connection %#x\n", GetURL(), _Socket->GetSocket()); _bKeepAliveConnection = TRUE; // // Get any security Info // if (_Socket->IsSecure()) { if (m_pSecurityInfo != NULL) { /* SCLE ref */ m_pSecurityInfo->Release(); } /* SCLE ref */ m_pSecurityInfo = ((ICSecureSocket *)_Socket)->GetSecurityEntry(); ((ICSecureSocket*)_Socket)->SetSecureFlags(SECURITY_FLAG_SECURE); } // // successfully got keep-alive connection from the pool // DEBUG_PRINT(HTTP, INFO, ("%skeep-alive connection: socket %#x, port %d\n", _Socket->IsSecure() ? "SSL " : "", _Socket->GetSocket(), _Socket->GetSourcePort() )); goto quit; } else if (fsm.m_fNoCreate) { goto quit; } // // the socket didn't come from the pool // _bKeepAliveConnection = FALSE; // // we may already have a socket if we're reusing the object // if (GetOpenFlags() & WINHTTP_FLAG_SECURE) { _Socket = New ICSecureSocket(); // Search on the appropriate cache, session vs. global INTERNET_HANDLE_OBJECT * pInternet; pInternet = GetRootHandle (this); if (m_pSecurityInfo == NULL) { /* SCLE ref */ m_pSecurityInfo = (pInternet->GetSslSessionCache())->Find(GetHostName()); if (NULL == m_pSecurityInfo) { /* SCLE ref */ m_pSecurityInfo = New SECURITY_CACHE_LIST_ENTRY(GetHostName()); } } if (_Socket != NULL) { _Socket->SetEncryption(); /* SCLE ref */ ((ICSecureSocket *)_Socket)->SetSecurityEntry(m_pSecurityInfo); /* SCLE ref */ ((ICSecureSocket *)_Socket)->SetHostName(GetHostName(), pInternet->GetSslSessionCache()); ((ICSecureSocket *)_Socket)->SetSecureFlags(GetOpenFlags() & SECURITY_INTERNET_MASK); if (GetEnableFlags() & WINHTTP_ENABLE_SSL_REVOCATION) ((ICSecureSocket *)_Socket)->SetSecureFlags(SECURITY_FLAG_CHECK_REVOCATION); } } else { if (dwSocketFlags & SF_TUNNEL) { _Socket = New ICSecureSocket; ((ICSecureSocket *)_Socket)->ResetFlags(FALSE); } else { _Socket = New ICSocket; } } if (_Socket != NULL) { fsm.m_bCreatedSocket = TRUE; } else { // // balance number of available connections // ReleaseConnection(FALSE, FALSE, FALSE); error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } // // Turn on Socks, if needed. // GetSocksProxyName(&proxyHostName, &proxyHostNameLength, &proxyHostPort ); if ((proxyHostName != NULL) && (proxyHostNameLength > 0)) { _Socket->EnableSocks(proxyHostName, proxyHostPort); } // // NOTE: if secure connection is required, TargetServer must // be a fully qualified domain name. // The hostname is used in comparison with CN found in // the certificate. The hostname MUST NOT BE the // result of a DNS lookup. DNS lookups are open to // spoofing, and that may prevent a security from // being detected. // // // If we're Posting or sending data, make sure // the SSL connection code knows about it. Therefore we set // the flag "SF_SENDING_DATA" for the purposes of // generating errors if found while making the connection. // _Socket->SetPort(hostPort); fsm.SetFunctionState(FSM_STATE_2); error = _Socket->Connect(GetTimeoutValue(WINHTTP_OPTION_CONNECT_TIMEOUT), GetTimeoutValue(WINHTTP_OPTION_CONNECT_RETRIES), SF_INDICATE | (IsAsyncHandle() ? SF_NON_BLOCKING : 0) | (((GetMethodType() == HTTP_METHOD_TYPE_POST) || (GetMethodType() == HTTP_METHOD_TYPE_PUT)) ? SF_SENDING_DATA : 0) ); connect_continue: if (error == ERROR_SUCCESS) { //dprintf("%s NEW connection %#x\n", GetURL(), _Socket->GetSocket()); DEBUG_PRINT(HTTP, INFO, ("new connection: socket %#x\n", _Socket->GetSocket() )); //if ( IsTunnel() ) //{ // dprintf("Tunnel for nested req=%#x opened for Socket=%#x\n", this, _Socket ); //} /* 24918: Invalid ASSERT because the m_Socket member in _Socket can be invalidated due to closing handle : code downstream of this assert takes that into consideration. for eg. SetLinger puts the call in a try..except. */ //INET_ASSERT(_Socket->IsOpen()); //pServerInfo->AddActiveConnection(); // enable send and receive timeout - ignore any errors _Socket->SetTimeout(SEND_TIMEOUT, GetTimeoutValue(WINHTTP_OPTION_SEND_TIMEOUT) ); _Socket->SetTimeout(RECEIVE_TIMEOUT, GetTimeoutValue(WINHTTP_OPTION_RECEIVE_TIMEOUT) ); // // set zero linger: force connection closed at transport level when // we close the socket. Ignore the error // _Socket->SetLinger(TRUE, 0); } quit: if (error != ERROR_IO_PENDING) { // // if we created the socket but failed to connect then delete the socket // object // if ((error != ERROR_SUCCESS) && fsm.m_bCreatedSocket) { // // we created a socket so we must increase the available connection // count on failure // INET_ASSERT(_Socket != NULL); ReleaseConnection(TRUE, // close socket (if open) FALSE, // don't indicate TRUE // dispose of socket object ); } //dprintf("%s get/connect pending socket %#x\n", GetURL(), _Socket ? _Socket->GetSocket() : 0); fsm.SetDone(); } DEBUG_LEAVE(error); return error; } DWORD HTTP_REQUEST_HANDLE_OBJECT::CloseConnection( IN BOOL bForceClosed ) /*++ Routine Description: Performs the opposite of OpenConnection(), i.e. closes the socket or marks it not in use if keep-alive Arguments: bForceClosed - TRUE if we are to forcibly release a keep-alive connection (i.e. the server timed out before we did) Return Value: DWORD Success - ERROR_SUCCESS Failure - WSA error --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::CloseConnection", "%B", bForceClosed )); //dprintf("*** closing %s%s socket %#x\n", // (_bKeepAliveConnection || IsKeepAlive()) ? "K-A " : "", // GetURL(), // _Socket ? _Socket->GetSocket() : 0 // ); DWORD error = ERROR_SUCCESS; BOOL bClose = TRUE; BOOL bDelete = TRUE; if (_Socket == NULL) { DEBUG_PRINT(HTTP, WARNING, ("socket already deleted\n" )); goto quit; } if (_bKeepAliveConnection || IsKeepAlive()) { // // keep-alive connection: just return the connection to the pool // if ((IsContentLength() && (GetBytesInSocket() != 0)) || (IsChunkEncoding() && !IsDecodingFinished()) || IsNoLongerKeepAlive() || _Socket->IsClosed() || ((_State & 0x0F) < (HttpRequestStateObjectData & 0x0F))) { DEBUG_PRINT(HTTP, INFO, ("forcing %#x [%#x] closed: bytes left = %d/%d; no longer k-a = %B; closed = %B\n", _Socket, _Socket->GetSocket(), GetBytesInSocket(), GetContentLength(), IsNoLongerKeepAlive(), _Socket->IsClosed() )); //dprintf("forcing k-a %#x closed - bytes=%d/%d, no longer=%B, chunked=%B, chunk-finished=%B\n", // _Socket->GetSocket(), // GetBytesInSocket(), // GetContentLength(), // IsNoLongerKeepAlive(), // IsChunkEncoding(), // IsDecodingFinished() // ); bForceClosed = TRUE; } if (!bForceClosed) { bClose = FALSE; bDelete = FALSE; } else { //dprintf("%#x forced close\n", _Socket->GetSocket()); } } ReleaseConnection(bClose, TRUE, bDelete); _Socket = NULL; _bKeepAliveConnection = FALSE; _bNoLongerKeepAlive = FALSE; quit: DEBUG_LEAVE(error); return error; } VOID HTTP_REQUEST_HANDLE_OBJECT::ReleaseConnection( IN BOOL bClose, IN BOOL bIndicate, IN BOOL bDispose ) /*++ Routine Description: Releases the connection back to the server limited pool and optionally closes the socket handle and destroys the socket object Arguments: bClose - if TRUE, increments the available connection count in the server info object and closes the handle, else we are returning a keep-alive connection; after this call we no longer have a socket object owned by this request handle object bIndicate - TRUE if we indicate to the user when we close the socket handle bDispose - TRUE if we are disposing of the socket object (mutually exclusive with !bClose), in which case we will no longer have a socket object after this call returns Return Value: None. --*/ { DEBUG_ENTER((DBG_HTTP, None, "HTTP_REQUEST_HANDLE_OBJECT::ReleaseConnection", "%B, %B, %B", bClose, bIndicate, bDispose )); INET_ASSERT(_Socket != NULL); //INET_ASSERT(_Socket->IsOpen()); CServerInfo * pServerInfo = GetServerInfo(); // Always disconnect sockets which have been marked as authenticated. // This is to avoid posting data to IIS4 while preauthenticating // and inducing the server to close the connection. if (_Socket) bClose = (bClose || _Socket->IsAuthenticated()); ICSocket * pSocket = bClose ? NULL : _Socket; INET_ASSERT(pServerInfo != NULL); if (pServerInfo != NULL) { if (bClose && (_Socket != NULL)) { // // BUGBUG - this should be set based on bGraceful parameter // _Socket->SetLinger(FALSE, 0); //INET_ASSERT(!_bKeepAliveConnection || _bNoLongerKeepAlive); _Socket->Shutdown(SD_BOTH); _Socket->Disconnect(bIndicate ? SF_INDICATE : 0); if (bDispose) { _Socket->Dereference(); _Socket = NULL; } } else { _Socket = NULL; } //if (IsResponseHttp1_1() && IsKeepAlive()) { // pServerInfo->ReleasePipelinedConnection(pSocket); //} else { pServerInfo->ReleaseConnection(pSocket); //} } DEBUG_LEAVE(0); } DWORD HTTP_REQUEST_HANDLE_OBJECT::AbortConnection( IN BOOL bForce ) /*++ Routine Description: Aborts the current connection. Closes the socket and frees up all receive buffers. If the connection is keep-alive, we have the option to forcefully terminate the connection, or just return the socket to the keep-alive pool Arguments: bForce - if TRUE and keep-alive, forcefully close the keep-alive socket Return Value: DWORD Success - ERROR_SUCCESS Failure - WSA error --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::AbortConnection", "%B", bForce )); DWORD error; error = CloseConnection(bForce); if (error == ERROR_SUCCESS) { // // destroy all response variables. This is similar to ReuseObject() // except we don't change the object state, or reset the end-of-file // state // _ResponseHeaders.FreeHeaders(); FreeResponseBuffer(); ResetResponseVariables(); } DEBUG_LEAVE(error); return error; } DWORD HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel( VOID ) /*++ Routine Description: Creates a connection with the requested server via a Proxy tunnelling method. Works by creating a nested child HTTP and Connect request object. These objects send a "CONNECT" verb to the proxy server asking for a connection to made with the destination server. Upon completion the child objects are discarded. If a class 200 response is not received from proxy server, the proxy response is copied into this object and returned to the user. Arguments: none. Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NOT_ENOUGH_MEMORY Ran out of resources --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel", NULL )); DWORD error = DoFsm(New CFsm_OpenProxyTunnel(this)); DEBUG_LEAVE(error); return error; } DWORD CFsm_OpenProxyTunnel::RunSM( IN CFsm * Fsm ) /*++ Routine Description: Runs next OpenProxyTunnel state Arguments: Fsm - contains state info Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "CFsm_OpenProxyTunnel::RunSM", "%#x", Fsm )); DWORD error; HTTP_REQUEST_HANDLE_OBJECT * pRequest; CFsm_OpenProxyTunnel * stateMachine = (CFsm_OpenProxyTunnel *)Fsm; START_SENDREQ_PERF(); pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)Fsm->GetContext(); switch (Fsm->GetState()) { case FSM_STATE_INIT: case FSM_STATE_CONTINUE: error = pRequest->OpenProxyTunnel_Fsm(stateMachine); break; default: error = ERROR_WINHTTP_INTERNAL_ERROR; Fsm->SetDone(ERROR_WINHTTP_INTERNAL_ERROR); INET_ASSERT(FALSE); break; } STOP_SENDREQ_PERF(); DEBUG_LEAVE(error); return error; } DWORD HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel_Fsm( IN CFsm_OpenProxyTunnel * Fsm ) /*++ Routine Description: State machine for OpenProxyTunnel Arguments: Fsm - contains state info Return Value: DWORD Success - ERROR_SUCCESS Failure - --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::OpenProxyTunnel_Fsm", "%#x", Fsm )); CFsm_OpenProxyTunnel & fsm = *Fsm; DWORD error = fsm.GetError(); LPINTERNET_THREAD_INFO lpThreadInfo = fsm.GetThreadInfo(); // Need to bury error on blocked async item that failed to find // established SSL tunnel if (error != ERROR_SUCCESS && fsm.GetFunctionState() != FSM_STATE_2) { goto quit; } if (lpThreadInfo == NULL) { INET_ASSERT(FALSE); error = ERROR_WINHTTP_INTERNAL_ERROR; goto quit; } // // Need to use handle magic for async requests // so we'll have the reference before jumping. // INTERNET_HANDLE_OBJECT * pInternet; pInternet = GetRootHandle (this); if (fsm.GetState() != FSM_STATE_INIT) { switch (fsm.GetFunctionState()) { case FSM_STATE_1: goto send_continue; case FSM_STATE_2: goto keep_alive_tunnel; default: error = ERROR_WINHTTP_INTERNAL_ERROR; INET_ASSERT(FALSE); goto quit; } } // Do not continue if handle is in NTLM challenge state - we // already have a valid socket set up for tunnelling. if ((_Socket != NULL) && (GetAuthState() == AUTHSTATE_CHALLENGE)) { error = ERROR_SUCCESS; goto quit; } // First, try and fetch an already established tunnel // from the keep-alive pool. If so, we can avoid the nested // CONNECT request. // if (_Socket == NULL) { error = OpenConnection(FALSE, TRUE); if (error == ERROR_IO_PENDING) { fsm.SetFunctionState(FSM_STATE_2); goto quit; } keep_alive_tunnel: if (error == ERROR_SUCCESS && _Socket != NULL) { // No need to create nested request. We found an active SSL tunnel // for this server in the keep-alive pool. goto quit; } else { // Start over as normal tunnel since bypass to find // keep-alive failed. error = ERROR_SUCCESS; } } // // With the Internet Handle Object, // construct a new Connect Object, and new HttpRequest Object. // // // increment the nested request level around InternetConnect(). This is // required to stop InternetConnect() believing this is the async part of // a two-part (FTP) request (original async hackery) // _InternetIncNestingCount(); fsm.m_hConnect = InternetConnect(pInternet->GetPseudoHandle(), GetHostName(), GetHostPort(), 0, // no flags NULL ); _InternetDecNestingCount(1); if (!fsm.m_hConnect) { error = GetLastError(); INET_ASSERT(error != ERROR_IO_PENDING); goto quit; } // // Now do an Open Request. This will pick up the secure proxy flag. // fsm.m_hRequest = HttpOpenRequest(fsm.m_hConnect, "CONNECT", "/", // we don't need this for a CONNECT NULL, NULL, NULL, 0, NULL ); if (!fsm.m_hRequest) { error = GetLastError(); goto quit; } // // map the handle // error = MapHandleToAddress(fsm.m_hRequest, (LPVOID *)&fsm.m_hRequestMapped, FALSE); if ((error != ERROR_SUCCESS) || (fsm.m_hRequestMapped == NULL)) { goto quit; } fsm.m_pRequest = (HTTP_REQUEST_HANDLE_OBJECT *)fsm.m_hRequestMapped; // // we need to set the special secure proxy flag in the request object // fsm.m_pRequest->SetTunnel(); LPSTR proxyHostName; DWORD proxyHostNameLength; INTERNET_PORT proxyHostPort; GetProxyName(&proxyHostName, &proxyHostNameLength, &proxyHostPort ); fsm.m_pRequest->SetProxyName(proxyHostName, proxyHostNameLength, proxyHostPort ); // // Transfer any proxy user/pass from the handle. // LPSTR lpszUser, lpszPass; // Get username + password off of outer handle. if (GetUserAndPass(IS_PROXY, &lpszUser, &lpszPass)) { // This will automatically re-validate the username/password // on the tunneling handle. fsm.m_pRequest->SetProp (WINHTTP_OPTION_PROXY_USERNAME & WINHTTP_OPTION_MASK, lpszUser); fsm.m_pRequest->SetProp (WINHTTP_OPTION_PROXY_PASSWORD & WINHTTP_OPTION_MASK, lpszPass); } if (_pProxyCreds) { fsm.m_pRequest->_pProxyCreds = new WINHTTP_REQUEST_CREDENTIALS(_pProxyCreds->_AuthScheme, _pProxyCreds->_pszRealm, _pProxyCreds->_pszUserName, _pProxyCreds->_pszPassword); if (fsm.m_pRequest->_pProxyCreds == NULL) { // If we couldn't clone it, transfer it. This is not efficient, but it will work. fsm.m_pRequest->_pProxyCreds = _pProxyCreds; _pProxyCreds = NULL; // ownership transferred } } // // Transfer any authentication context to the tunnelling handle. // //fsm.m_pRequest->SetAuthCtx (_pTunnelAuthCtx); //dprintf("New tunnel request %#x making nested request= %#x\n", this, fsm.m_pRequest); // // Do the Nested SendRequest to the Proxy Server. // ie send the CONNECT method. // fsm.SetFunctionState(FSM_STATE_1); if (!HttpSendRequest(fsm.m_hRequest, NULL, 0, NULL, 0)) { error = GetLastError(); if (error == ERROR_IO_PENDING) { goto done; } goto quit; } send_continue: // // Check Status Code Returned from proxy Server Here. // If its not 200 we let the user view it as a Proxy Error // and DON'T continue our connection to the SSL/PCT Server. // //dprintf("Received Nested Response, Socket=%#x, org request=%#x, nested request=%#x\n", fsm.m_pRequest->_Socket, this, fsm.m_pRequest); _StatusCode = fsm.m_pRequest->GetStatusCode(); switch (_StatusCode) { case HTTP_STATUS_OK: break; case HTTP_STATUS_PROXY_AUTH_REQ: if ((error = CloneResponseBuffer(fsm.m_pRequest)) != ERROR_SUCCESS) goto quit; break; default: if ((error = CloneResponseBuffer(fsm.m_pRequest)) != ERROR_SUCCESS) goto quit; goto quit; } // // Transfer any authentication context back to the outer handle. // if ( _pTunnelAuthCtx ) { delete _pTunnelAuthCtx; } _pTunnelAuthCtx = fsm.m_pRequest->GetAuthCtx(); // Don't leave the potential for a deleted request reference // to be left in the trasferred context. if (_pTunnelAuthCtx) { _pTunnelAuthCtx->_pRequest = NULL; } fsm.m_pRequest->SetAuthCtx (NULL); _PreferredScheme = fsm.m_pRequest->_PreferredScheme; _SupportedSchemes = fsm.m_pRequest->_SupportedSchemes; _AuthTarget = fsm.m_pRequest->_AuthTarget; // // pull the socket handle from the socket object used to communicate with // the proxy // INET_ASSERT(fsm.m_pRequest->_Socket != NULL); /* if server returned anything other than 200 then we failed; revert to non- secure socket */ if (_Socket == NULL) { // transfer socket reference from nested request _Socket = fsm.m_pRequest->_Socket; fsm.m_pRequest->_Socket = NULL; // Socket should now be marked as secure ((ICSecureSocket *)_Socket)->ResetFlags(TRUE); } if(m_pSecurityInfo == NULL) { /* SCLE ref */ m_pSecurityInfo = pInternet->GetSslSessionCache()->Find(GetHostName()); if (NULL == m_pSecurityInfo) { /* SCLE ref */ m_pSecurityInfo = New SECURITY_CACHE_LIST_ENTRY(GetHostName()); } } if (_Socket != NULL) { INET_ASSERT(_Socket->IsSecure()); /* SCLE ref */ ((ICSecureSocket *)_Socket)->SetSecurityEntry(m_pSecurityInfo); /* SCLE ref */ ((ICSecureSocket *)_Socket)->SetHostName(GetHostName(), pInternet->GetSslSessionCache()); ((ICSecureSocket *)_Socket)->SetSecureFlags(GetOpenFlags() & SECURITY_INTERNET_MASK); if (GetEnableFlags() & WINHTTP_ENABLE_SSL_REVOCATION) ((ICSecureSocket *)_Socket)->SetSecureFlags(SECURITY_FLAG_CHECK_REVOCATION); // Update values for the established tunnel _Socket->SetPort(fsm.m_pRequest->GetHostPort()); } else { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } quit: if (fsm.m_hRequestMapped != NULL) { DereferenceObject((LPVOID)fsm.m_hRequestMapped); } if (fsm.m_hRequest != NULL) { BOOL bOk; bOk = WinHttpCloseHandle(fsm.m_hRequest); INET_ASSERT(bOk); } if (fsm.m_hConnect != NULL) { BOOL bOk; bOk = WinHttpCloseHandle(fsm.m_hConnect); INET_ASSERT(bOk); } // // We Reset the ThreadInfo back to the the previous // object handle, and context values. // if (lpThreadInfo != NULL) { _InternetSetObjectHandle(lpThreadInfo, GetPseudoHandle(), (HINTERNET)this); _InternetClearLastError(lpThreadInfo); } done: if (error != ERROR_IO_PENDING) { fsm.SetDone(); } DEBUG_LEAVE(error); return error; } // // private methods // PRIVATE DWORD HTTP_REQUEST_HANDLE_OBJECT::CloneResponseBuffer( IN HTTP_REQUEST_HANDLE_OBJECT *pChildRequestObj ) /*++ Routine Description: HTTP_REQUEST_HANDLE_OBJECT CloneResponseBuffer method. Copies a Child Request Object's Response Buffer into "this" request object. Also forces header parsing to be rerun on the header. Arguments: none. Return Value: DWORD Success - ERROR_SUCCESS Failure - ERROR_NOT_ENOUGH_MEMORY Ran out of resources --*/ { DEBUG_ENTER((DBG_HTTP, Dword, "HTTP_REQUEST_HANDLE_OBJECT::CloneResponseBuffer", "%#x", pChildRequestObj )); DWORD error; LPBYTE lpBuffer; error = ERROR_SUCCESS; lpBuffer = (LPBYTE)ALLOCATE_FIXED_MEMORY(pChildRequestObj->_BytesReceived); if ( lpBuffer == NULL ) { error = ERROR_NOT_ENOUGH_MEMORY; goto quit; } // // pull out headers, and data from Child Request into our request. // CopyMemory( lpBuffer, pChildRequestObj->_ResponseBuffer, pChildRequestObj->_BytesReceived ); // // Recreate and reparse our header structure into our Object, // this is kindof inefficent, but it only happens on errors // error = CreateResponseHeaders( (LPSTR*) &lpBuffer, pChildRequestObj->_BytesReceived ); if (error != ERROR_SUCCESS) { goto quit; } SetState(HttpRequestStateObjectData); // // record the amount of data immediately available to the app // SetAvailableDataLength(BufferedDataLength()); quit: if (lpBuffer) { FREE_MEMORY (lpBuffer); } DEBUG_LEAVE(error); return error; }