00001 // 00002 // Connection.cpp 00003 // 00004 // Date: "$Date: 2005/09/26 00:09:36 $" 00005 // Revision: "$Revision: 1.27 $" 00006 // Last change by: "$Author: mogthecat $" 00007 // 00008 // Copyright (c) Shareaza Development Team, 2002-2005. 00009 // This file is part of SHAREAZA (www.shareaza.com) 00010 // 00011 // Shareaza is free software; you can redistribute it 00012 // and/or modify it under the terms of the GNU General Public License 00013 // as published by the Free Software Foundation; either version 2 of 00014 // the License, or (at your option) any later version. 00015 // 00016 // Shareaza is distributed in the hope that it will be useful, 00017 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00018 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00019 // GNU General Public License for more details. 00020 // 00021 // You should have received a copy of the GNU General Public License 00022 // along with Shareaza; if not, write to the Free Software 00023 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 00024 // 00025 00026 // CConnection holds a socket used to communicate with a remote computer, and is the root of a big inheritance tree 00027 // http://wiki.shareaza.com/static/Developers.Code.CConnection 00028 00029 // Copy in the contents of these files here before compiling 00030 #include "StdAfx.h" 00031 #include "Shareaza.h" 00032 #include "Settings.h" 00033 #include "Connection.h" 00034 #include "Network.h" 00035 #include "Security.h" 00036 #include "Buffer.h" 00037 #include "Statistics.h" 00038 00039 // If we are compiling in debug mode, replace the text "THIS_FILE" in the code with the name of this file 00040 #ifdef _DEBUG 00041 #undef THIS_FILE 00042 static char THIS_FILE[]=__FILE__; 00043 #define new DEBUG_NEW 00044 #endif 00045 00046 // Hard-coded settings for socket data transfer 00047 #define TEMP_BUFFER 10240 // Transfer data between the socket and a local 10 KB buffer of space 00048 00049 // Hard-coded settings for the bandwidth transfer meter 00050 #define METER_SECOND 1000 // Used by the bandwidth transfer meter, 1000 milliseconds is 1 second 00051 #define METER_MINIMUM 100 // If the current slot is less than a tenth of a second old, record more bytes transferred there 00052 #define METER_LENGTH 64 // Number of slots in the bandwidth meter, used to be 24, now 64 00053 #define METER_PERIOD 6000 // The bandwidth meter holds onto information for 6 seconds, used to be 2000, now 6000 00054 00056 // CConnection construction 00057 00058 // Make a new CConnection object 00059 CConnection::CConnection() 00060 { 00061 // Initialize member variables with null or default values 00062 m_hSocket = INVALID_SOCKET; // Set the actual Windows socket to the invalid value 00063 m_pInput = NULL; // Null pointers to input and output CBuffer objects 00064 m_pOutput = NULL; 00065 m_bInitiated = FALSE; // This connection hasn't been initiated or connected yet 00066 m_bConnected = FALSE; 00067 m_tConnected = 0; // This will be the tick count when the connection is made 00068 m_nQueuedRun = 0; // DoRun sets it to 0, QueueRun sets it to 2 (do) 00069 00070 // Zero the memory of the input and output TCPBandwidthMeter objects 00071 ZeroMemory( &m_mInput, sizeof(m_mInput) ); 00072 ZeroMemory( &m_mOutput, sizeof(m_mOutput) ); 00073 } 00074 00075 // Delete this CConnection object 00076 CConnection::~CConnection() 00077 { 00078 // Close the socket before the system deletes the object 00079 CConnection::Close(); 00080 } 00081 00083 // CConnection connect to 00084 00085 // Connect this CConnection object to a remote computer on the Internet 00086 // Takes pHost, a pointer to a SOCKADDR_IN structure, which is MFC's way of holding an IP address and port number 00087 // Returns true if connected 00088 BOOL CConnection::ConnectTo(SOCKADDR_IN* pHost) 00089 { 00090 // Call the next ConnectTo method, and return the result 00091 return ConnectTo( &pHost->sin_addr, htons( pHost->sin_port ) ); 00092 } 00093 00094 // Connect this CConnection object to a remote computer on the Internet 00095 // Takes pAddress, a Windows Sockets structure that holds an IP address, and takes the port number seprately 00096 // Returns true if connected 00097 BOOL CConnection::ConnectTo(IN_ADDR* pAddress, WORD nPort) 00098 { 00099 // Check inputs 00100 if ( m_hSocket != INVALID_SOCKET ) return FALSE; // Make sure the socket isn't already connected somehow 00101 if ( pAddress == NULL || nPort == 0 ) return FALSE; // Make sure we have an address and a nonzero port number 00102 if ( pAddress->S_un.S_addr == 0 ) return FALSE; // S_un.S_addr is the IP as a single unsigned 4-byte long 00103 00104 // The IP address is in the security list of government and corporate addresses we want to avoid 00105 if ( Security.IsDenied( pAddress ) ) 00106 { 00107 // Report that we aren't connecting to this IP address and return false 00108 theApp.Message( MSG_ERROR, IDS_NETWORK_SECURITY_OUTGOING, (LPCTSTR)CString( inet_ntoa( *pAddress ) ) ); 00109 return FALSE; 00110 } 00111 00112 // The IN_ADDR structure we just got passed isn't the same as the one already stored in this object 00113 if ( pAddress != &m_pHost.sin_addr ) 00114 { 00115 // Zero the memory of the entire SOCKADDR_IN structure m_pHost, and then copy in the sin_addr part 00116 ZeroMemory( &m_pHost, sizeof(m_pHost) ); 00117 m_pHost.sin_addr = *pAddress; 00118 } 00119 00120 // Fill in more parts of the m_pHost structure 00121 m_pHost.sin_family = PF_INET; // PF_INET means just normal IPv4, not IPv6 yet 00122 m_pHost.sin_port = htons( nPort ); // Copy the port number into the m_pHost structure 00123 m_sAddress = inet_ntoa( m_pHost.sin_addr ); // Save the IP address as a string of text 00124 00125 // Create a socket and store it in m_hSocket 00126 m_hSocket = socket( 00127 PF_INET, // Normal IPv4, not IPv6 00128 SOCK_STREAM, // The two-way sequenced reliable byte streams of TCP, not the datagrams of UDP 00129 IPPROTO_TCP ); // Again, we want TCP 00130 00131 // Choose asynchronous, non-blocking reading and writing on our new socket 00132 DWORD dwValue = 1; 00133 ioctlsocket( // Call Windows Sockets ioctlsocket to control the input/output mode of our new socket 00134 m_hSocket, // Give it our new socket 00135 FIONBIO, // Select the option for blocking i/o, should the program wait on read and write calls, or keep going? 00136 &dwValue ); // Nonzero, it should keep going 00137 00138 // If the OutHost string in connection settings has an IP address written in it 00139 if ( Settings.Connection.OutHost.GetLength() ) 00140 { 00141 // Read the text and copy the IP address and port into a new local MFC SOCKADDR_IN structure called pOutgoing 00142 SOCKADDR_IN pOutgoing; 00143 Network.Resolve( Settings.Connection.OutHost, 0, &pOutgoing ); 00144 00145 // S_addr is the IP address as a single long number, if it's not zero 00146 if ( pOutgoing.sin_addr.S_un.S_addr ) 00147 { 00148 // Call bind in Windows Sockets to associate the local address with the socket 00149 bind( 00150 m_hSocket, // Our socket 00151 (SOCKADDR*)&pOutgoing, // The IP address this computer appears to have on the Internet (do) 00152 sizeof(SOCKADDR_IN) ); // Tell bind how many bytes it can read at the pointer 00153 } 00154 } 00155 00156 // Try to connect to the remote computer 00157 if ( WSAConnect( 00158 m_hSocket, // Our socket 00159 (SOCKADDR*)&m_pHost, // The remote IP address and port number 00160 sizeof(SOCKADDR_IN), // How many bytes the function can read 00161 NULL, NULL, NULL, NULL ) ) // No advanced features 00162 { 00163 // If no error occurs, WSAConnect returns 0, so if we're here an error happened 00164 UINT nError = WSAGetLastError(); // Get the last Windows Sockets error number 00165 00166 // An error of "would block" is normal because connections can't be made instantly and this is a non-blocking socket 00167 if ( nError != WSAEWOULDBLOCK ) 00168 { 00169 // The error is something else, record it, close the socket, set the value of m_hSocket, and leave 00170 theApp.Message( MSG_DEBUG, _T("connect() error 0x%x"), nError ); 00171 closesocket( m_hSocket ); 00172 m_hSocket = INVALID_SOCKET; 00173 return FALSE; 00174 } 00175 } 00176 00177 // Delete the input and output CBuffer objects if they exist, and then create new ones 00178 if ( m_pInput ) delete m_pInput; 00179 if ( m_pOutput ) delete m_pOutput; 00180 m_pInput = new CBuffer( &Settings.Bandwidth.PeerIn ); 00181 m_pOutput = new CBuffer( &Settings.Bandwidth.PeerOut ); 00182 00183 // Record that we initiated this connection, and when it happened 00184 m_bInitiated = TRUE; 00185 m_tConnected = GetTickCount(); 00186 00187 // Record one more outgoing connection in the statistics 00188 Statistics.Current.Connections.Outgoing++; 00189 00190 // The connection was successfully attempted 00191 return TRUE; 00192 } 00193 00195 // CConnection accept an incoming connection 00196 00197 // Called when a remote computer wants to connect to us 00198 // When WSAAccept accepted the connection, it created a new socket hSocket for it and wrote the remote IP in pHost 00199 void CConnection::AcceptFrom(SOCKET hSocket, SOCKADDR_IN* pHost) 00200 { 00201 // Make sure the newly accepted socket is valid 00202 ASSERT( m_hSocket == INVALID_SOCKET ); 00203 00204 // Record the connection information here 00205 m_hSocket = hSocket; // Keep the socket here 00206 m_pHost = *pHost; // Copy the remote IP address into this object 00207 m_sAddress = inet_ntoa( m_pHost.sin_addr ); // Store it as a string also 00208 00209 // Make new input and output buffer objects 00210 m_pInput = new CBuffer( &Settings.Bandwidth.PeerIn ); 00211 m_pOutput = new CBuffer( &Settings.Bandwidth.PeerOut ); 00212 00213 // Facts about the connection 00214 m_bInitiated = FALSE; // We didn't initiate this connection 00215 m_bConnected = TRUE; // We're connected right now 00216 m_tConnected = GetTickCount(); // Record the time this happened 00217 00218 // Choose asynchronous, non-blocking reading and writing on the new socket 00219 DWORD dwValue = 1; 00220 ioctlsocket( m_hSocket, FIONBIO, &dwValue ); 00221 00222 // Record one more incoming connection in the statistics 00223 Statistics.Current.Connections.Incoming++; 00224 } 00225 00227 // CConnection attach to an existing connection 00228 00229 // Call to copy a connection object to this one (do) 00230 // Takes another connection object 00231 void CConnection::AttachTo(CConnection* pConnection) 00232 { 00233 // Make sure the socket isn't valid yet 00234 ASSERT( m_hSocket == INVALID_SOCKET ); // Make sure the socket here isn't valid yet 00235 ASSERT( pConnection != NULL ); // Make sure we got a CConnection object 00236 ASSERT( pConnection->m_hSocket != INVALID_SOCKET ); // And make sure its socket exists 00237 00238 // Copy values from the given CConnection object to this one 00239 m_pHost = pConnection->m_pHost; 00240 m_sAddress = pConnection->m_sAddress; 00241 m_hSocket = pConnection->m_hSocket; 00242 m_bInitiated = pConnection->m_bInitiated; 00243 m_bConnected = pConnection->m_bConnected; 00244 m_tConnected = pConnection->m_tConnected; 00245 m_pInput = pConnection->m_pInput; 00246 m_pOutput = pConnection->m_pOutput; 00247 m_sUserAgent = pConnection->m_sUserAgent; // But, we don't also copy across m_mInput and m_mOutput 00248 00249 // Record the current time in the input and output TCP bandwidth meters 00250 m_mInput.tLast = m_mOutput.tLast = GetTickCount(); 00251 00252 // Invalidate the socket in the given connection object so it's just here now 00253 pConnection->m_hSocket = INVALID_SOCKET; 00254 00255 // Null the input and output pointers 00256 pConnection->m_pInput = NULL; 00257 pConnection->m_pOutput = NULL; 00258 00259 // Zero the memory of the input and output TCPBandwidthMeter objects 00260 ZeroMemory( &pConnection->m_mInput, sizeof(m_mInput) ); 00261 ZeroMemory( &pConnection->m_mOutput, sizeof(m_mOutput) ); 00262 } 00263 00265 // CConnection close 00266 00267 // Call to close the connection represented by this object 00268 void CConnection::Close() 00269 { 00270 // Make sure this object exists, and is located entirely within the program's memory space 00271 ASSERT( this != NULL ); 00272 ASSERT( AfxIsValidAddress( this, sizeof(*this) ) ); 00273 00274 // The socket is valid 00275 if ( m_hSocket != INVALID_SOCKET ) 00276 { 00277 // Close it and mark it invalid 00278 closesocket( m_hSocket ); 00279 m_hSocket = INVALID_SOCKET; 00280 } 00281 00282 // Delete and mark null the input and output buffers 00283 if ( m_pOutput ) delete m_pOutput; 00284 m_pOutput = NULL; 00285 if ( m_pInput ) delete m_pInput; 00286 m_pInput = NULL; 00287 00288 // This connection object isn't connected any longer 00289 m_bConnected = FALSE; 00290 } 00291 00293 // CConnection run function 00294 00295 // Talk to the other computer, write the output buffer to the socket and read from the socket to the input buffer 00296 // Return true if this worked, false if we've lost the connection 00297 BOOL CConnection::DoRun() 00298 { 00299 // If this socket is invalid, call OnRun and return the result (do) 00300 if ( m_hSocket == INVALID_SOCKET ) return OnRun(); 00301 00302 // Setup pEvents to store the socket's internal information about network events 00303 WSANETWORKEVENTS pEvents; 00304 WSAEnumNetworkEvents( m_hSocket, NULL, &pEvents ); 00305 00306 // If the FD_CONNECT network event has occurred 00307 if ( pEvents.lNetworkEvents & FD_CONNECT ) 00308 { 00309 // If there is a nonzero error code for the connect operation 00310 if ( pEvents.iErrorCode[ FD_CONNECT_BIT ] != 0 ) 00311 { 00312 // This connection was dropped 00313 OnDropped( TRUE ); 00314 return FALSE; 00315 } 00316 00317 // The socket is now connected 00318 m_bConnected = TRUE; 00319 m_tConnected = m_mInput.tLast = m_mOutput.tLast = GetTickCount(); // Store the time 3 places 00320 00321 // Call CShakeNeighbour::OnConnected to start reading the handshake 00322 if ( ! OnConnected() ) return FALSE; 00323 } 00324 00325 // If the FD_CLOSE network event has occurred, set bClosed to true, otherwise set it to false 00326 BOOL bClosed = ( pEvents.lNetworkEvents & FD_CLOSE ) ? TRUE : FALSE; 00327 00328 // If the close event happened, null a pointer within the TCP bandwidth meter for input (do) 00329 if ( bClosed ) m_mInput.pLimit = NULL; 00330 00331 // Change the queued run state to 1 (do) 00332 m_nQueuedRun = 1; 00333 00334 // Write the contents of the output buffer to the remote computer, and read in data it sent us 00335 if ( ! OnWrite() ) return FALSE; // If this is a CShakeNeighbour object, calls CShakeNeighbour::OnWrite 00336 if ( ! OnRead() ) return FALSE; 00337 00338 // If the close event happened 00339 if ( bClosed ) 00340 { 00341 /* 00342 if ( pEvents.iErrorCode[ FD_CLOSE_BIT ] != 0 ) 00343 { 00344 CString strError; 00345 strError.Format( _T("Close Error: %i "), pEvents.iErrorCode[ FD_CLOSE_BIT ] ); 00346 theApp.Message(MSG_ERROR, strError ); 00347 } 00348 */ 00349 // Call OnDropped, telling it true if there is a close error 00350 OnDropped( pEvents.iErrorCode[ FD_CLOSE_BIT ] != 0 ); // True if there is an nonzero error code for the close bit 00351 return FALSE; 00352 } 00353 00354 // Make sure the handshake doesn't take too long 00355 if ( ! OnRun() ) return FALSE; // If this is a CShakeNeighbour object, calls CShakeNeighbour::OnRun 00356 00357 // If the queued run state is 2 and OnWrite returns false, leave here with false also 00358 if ( m_nQueuedRun == 2 && ! OnWrite() ) return FALSE; 00359 00360 // Change the queued run state back to 0 and report success (do) 00361 m_nQueuedRun = 0; 00362 return TRUE; 00363 } 00364 00365 // Call OnWrite if DoRun has just succeeded (do) 00366 void CConnection::QueueRun() 00367 { 00368 // If the queued run state is 1 or 2, make it 2, if it's 0, call OnWrite now (do) 00369 if ( m_nQueuedRun ) m_nQueuedRun = 2; // The queued run state is not 0, make it 2 and do nothing else here 00370 else OnWrite(); // The queued run state is 0, do a write (do) 00371 } 00372 00374 // CConnection socket event handlers 00375 00376 // Objects that inherit from CConnection have OnConnected methods that do things, unlike this one 00377 BOOL CConnection::OnConnected() 00378 { 00379 // Just return true 00380 return TRUE; 00381 } 00382 00383 // Objects that inherit from CConnection have OnDropped methods that do things, unlike this one 00384 void CConnection::OnDropped(BOOL bError) 00385 { 00386 // Do nothing 00387 } 00388 00389 // Objects that inherit from CConnection have OnRun methods that do things, unlike this one 00390 BOOL CConnection::OnRun() 00391 { 00392 // Just return true 00393 return TRUE; 00394 } 00395 00397 // CConnection read event handler 00398 00399 // Read data waiting in the socket into the input buffer 00400 BOOL CConnection::OnRead() 00401 { 00402 // Make sure the socket is valid 00403 if ( m_hSocket == INVALID_SOCKET ) return FALSE; 00404 00405 // Setup local variables 00406 BYTE pData[TEMP_BUFFER]; // Make a 10 KB data buffer called pData 00407 DWORD tNow = GetTickCount(); // The time right now 00408 DWORD nLimit = 0xFFFFFFFF; // Make the limit huge 00409 DWORD nTotal = 0; // Start the total at 0 00410 00411 // If we need to worry about throttling bandwidth, calculate nLimit, the number of bytes we are allowed to read now 00412 if ( m_mInput.pLimit // If the input bandwidth memter points to a limit 00413 && *m_mInput.pLimit // And that limit isn't 0 00414 && ( Settings.Live.BandwidthScale <= 100 // And either the bandwidth meter in program settings is (do) 00415 || m_mInput.bUnscaled ) ) // Or the input bandwidth meter in this object is unscaled (do) 00416 { 00417 // tCutoff is the tick count 1 second ago 00418 DWORD tCutoff = tNow - METER_SECOND; // METER_SECOND is 1 second 00419 00420 // nLimit is the number of bytes we've read in the last second 00421 // This part of the code has been translated into assembly language to make it faster 00422 /* 00423 // Loop across the first 24 times and histories stored in the input bandwidth meter 00424 DWORD* pHistory = m_mInput.pHistory; // Start the pointers at the beginning of the arrays 00425 DWORD* pTime = m_mInput.pTimes; 00426 nLimit = 0; // Count up the limit from 0 00427 for ( int nSeek = METER_LENGTH ; nSeek ; nSeek--, pHistory++, pTime++ ) 00428 { 00429 // If this time is within the last second, add its bytes to the limit 00430 if ( *pTime >= tCutoff ) nLimit += *pHistory; 00431 } 00432 */ 00433 // Here is the assembly language which does the same thing 00434 __asm 00435 { 00436 mov ecx, this 00437 mov ebx, -METER_LENGTH 00438 mov edx, tCutoff 00439 xor eax, eax 00440 _loop: cmp edx, [ ecx + ebx * 4 ]CConnection.m_mInput.pTimes + METER_LENGTH * 4 00441 jnbe _ignore 00442 add eax, [ ecx + ebx * 4 ]CConnection.m_mInput.pHistory + METER_LENGTH * 4 00443 _ignore: inc ebx 00444 jnz _loop 00445 mov nLimit, eax 00446 } 00447 00448 // nActual is the speed limit, set by the input bandwidth meter and the bandwidth scale in settings 00449 DWORD nActual = *m_mInput.pLimit; // Get the speed limit from the input bandwidth meter 00450 if ( Settings.Live.BandwidthScale < 100 && ! m_mInput.bUnscaled ) // The scale is turned down and we should use it 00451 { 00452 // Adjust actual based on the scale 00453 nActual = nActual * Settings.Live.BandwidthScale / 100; // If the settings scale is 50, this cuts actual in half 00454 } 00455 00456 // tCutoff is the number of bytes we can read now, multiply the speed limit with the elapsed time to get it 00457 tCutoff = nActual * ( tNow - m_mInput.tLastAdd ) / 1000; // Subtract tick counts and divide by 1000 to get seconds 00458 m_mInput.tLastAdd = tNow; // Record that the last add in the input bandwidth meter happened now 00459 00460 // nActual is the speed limit in bytes per second 00461 // nLimit is how many bytes we read in the last second 00462 // So, nActual - nLimit is the number of bytes we can still read this second 00463 // Set nLimit to this, or 0 if we're over the limit 00464 nLimit = ( nLimit >= nActual ) ? 0 : ( nActual - nLimit ); 00465 00466 // If the cutoff is even more restrictive than the limit, make it the limit instead 00467 // nLimit = speed limit in bytes per second - bytes we read in the last second 00468 // tCutoff = speed limit in bytes per second * elapsed time 00469 nLimit = min( nLimit, tCutoff ); 00470 } 00471 00472 // Loop until the limit has run out 00473 while ( nLimit ) 00474 { 00475 // Set nLength to nLimit or 4 KB, whichever is smaller 00476 int nLength = min( ( nLimit & 0xFFFFF ), DWORD(TEMP_BUFFER) ); 00477 00478 // Read the bytes from the socket 00479 nLength = recv( // nLength is the number of bytes we received from the socket 00480 m_hSocket, // Use the socket in this CConnection object 00481 (char*)pData, // Tell recv to write the data here 00482 nLength, // The size of the buffer, and how many bytes recv should write there 00483 0 ); // No special options 00484 if ( nLength <= 0 ) break; // Loop until nothing is left or an error occurs 00485 00486 // Record the time of this read in the input bandwidth meter 00487 m_mInput.tLast = tNow; 00488 00489 // If the length is positive and up to 4 KB 00490 if ( nLength > 0 && nLength <= TEMP_BUFFER ) 00491 { 00492 // Add the data to the input buffer in the connection object 00493 m_pInput->Add( pData, nLength ); 00494 } 00495 00496 // Include these new bytes in the total 00497 nTotal += nLength; 00498 00499 // If we are keeping track of the limit, subtract the bytes we just read from it 00500 if ( nLimit != 0xFFFFFFFF ) nLimit -= nLength; 00501 } 00502 00503 // If we are keeping track of history and we just read some bytes 00504 if ( m_mInput.pHistory && nTotal ) 00505 { 00506 // If it's less than 1/10 of a second since we last wrote some history information 00507 if ( tNow - m_mInput.tLastSlot < METER_MINIMUM ) 00508 { 00509 // Use the same place in the array as before 00510 m_mInput.pHistory[ m_mInput.nPosition ] += nTotal; 00511 00512 } // It's been more than a tenth of a second since we last recorded a read 00513 else 00514 { 00515 // Store the time and total in a new array slot 00516 m_mInput.nPosition = ( m_mInput.nPosition + 1 ) % METER_LENGTH; // Move to the next position in the array 00517 m_mInput.pTimes[ m_mInput.nPosition ] = tNow; // Record the new time 00518 m_mInput.pHistory[ m_mInput.nPosition ] = nTotal; // Store the bytes read next to it 00519 m_mInput.tLastSlot = tNow; // We just wrote some history information 00520 } 00521 } 00522 00523 // Add the bytes we read to the total in the bandwidth meter and the total in statistics 00524 m_mInput.nTotal += nTotal; 00525 Statistics.Current.Bandwidth.Incoming += nTotal; 00526 return TRUE; 00527 } 00528 00530 // CConnection write event handler 00531 00532 // Call to send the contents of the output buffer to the remote computer 00533 BOOL CConnection::OnWrite() 00534 { 00535 // Make sure the socket is valid and there is something to send 00536 if ( m_hSocket == INVALID_SOCKET ) return FALSE; 00537 if ( m_pOutput->m_nLength == 0 ) return TRUE; // There is nothing to send, so we succeed without doing anything 00538 00539 // Setup local variables 00540 DWORD tNow = GetTickCount(); // The time right now 00541 DWORD nLimit = 0xFFFFFFFF; // Make the limit huge 00542 00543 // If we need to worry about throttling bandwidth, calculate nLimit, the number of bytes we are allowed to write now 00544 if ( m_mOutput.pLimit // If the output bandwidth meter points to a limit 00545 && *m_mOutput.pLimit // And that limit isn't 0 00546 && ( Settings.Live.BandwidthScale <= 100 // And either the bandwidth meter in program settings is (do) 00547 || m_mOutput.bUnscaled ) ) // Or the input bandwidth meter in this object is unscaled (do) 00548 { 00549 // tCutoff is the tick count 1 second ago 00550 DWORD tCutoff = tNow - METER_SECOND; // METER_SECOND is 1 second 00551 00552 // nUsed is the number of bytes we've written in the last second 00553 // This part of the code has been translated into assembly language to make it faster 00554 /* 00555 // Loop across the first 24 times and histories stored in the output bandwidth meter 00556 DWORD* pHistory = m_mOutput.pHistory; // Start the pointers at the beginning of the arrays 00557 DWORD* pTime = m_mOutput.pTimes; 00558 DWORD nUsed = 0; // Count up used from 0 00559 for ( int nSeek = METER_LENGTH ; nSeek ; nSeek--, pHistory++, pTime++ ) 00560 { 00561 // If this time is within the last second, add its bytes to used 00562 if ( *pTime >= tCutoff ) nUsed += *pHistory; 00563 } 00564 */ 00565 // Here is the assembly language which does the same thing 00566 DWORD nUsed; 00567 __asm 00568 { 00569 mov ecx, this 00570 mov ebx, -METER_LENGTH 00571 mov edx, tCutoff 00572 xor eax, eax 00573 _loop: cmp edx, [ ecx + ebx * 4 ]CConnection.m_mOutput.pTimes + METER_LENGTH * 4 00574 jnbe _ignore 00575 add eax, [ ecx + ebx * 4 ]CConnection.m_mOutput.pHistory + METER_LENGTH * 4 00576 _ignore: inc ebx 00577 jnz _loop 00578 mov nUsed, eax 00579 } 00580 00581 // nLimit is the speed limit, set by the output bandwidth meter and the bandwidth scale in settings 00582 nLimit = *m_mOutput.pLimit; // Get the speed limit from the output bandwidth meter 00583 if ( Settings.Live.BandwidthScale < 100 && ! m_mOutput.bUnscaled ) // The scale is turned down and we should use it 00584 { 00585 // Adjust the limit lower based on the scale 00586 nLimit = nLimit * Settings.Live.BandwidthScale / 100; 00587 } 00588 00589 // The program is running in throttle mode 00590 if ( Settings.Uploads.ThrottleMode ) 00591 { 00592 // Set nLimit to the remaining bytes we're allowd to write this second 00593 nLimit = ( nUsed >= nLimit ) ? 0 : ( nLimit - nUsed ); 00594 00595 } // The program is not running in throttle mode 00596 else 00597 { 00598 // tCutoff is the number of bytes we can write now, multiply the speed limit with the elapsed time to get it 00599 tCutoff = nLimit * ( tNow - m_mOutput.tLastAdd ) / 1000; 00600 00601 // Set nLimit to the remaining bytes we're allowd to write this second 00602 nLimit = ( nUsed >= nLimit ) ? 0 : ( nLimit - nUsed ); 00603 00604 // If the cutoff is even more restrictive than the limit, make it the limit instead 00605 // nLimit = speed limit in bytes per second - bytes we read in the last second 00606 // tCutoff = speed limit in bytes per second * elapsed time 00607 nLimit = min( nLimit, tCutoff ); 00608 00609 // Record that the last add in the input bandwidth meter happened now 00610 m_mOutput.tLastAdd = tNow; 00611 } 00612 } 00613 00614 // Point pBuffer at the start of the output buffer, and set nBuffer to its length 00615 BYTE* pBuffer = m_pOutput->m_pBuffer; 00616 DWORD nBuffer = m_pOutput->m_nLength; 00617 00618 // Loop while we're under our limit and there are still bytes to write 00619 while ( nLimit && nBuffer ) 00620 { 00621 // nLength is the number of bytes we will write, set it to the limit or the length, whichever is smaller 00622 int nLength = (int)min( nLimit, nBuffer ); 00623 00624 // Send the bytes to the other computer through the socket 00625 nLength = send( // The send function returns how many bytes it sent 00626 m_hSocket, // Use the socket in this CConnection object 00627 (char*)pBuffer, // Tell send to read the data here 00628 nLength, // This is how many bytes are there to send 00629 0 ); // No special options 00630 if ( nLength <= 0 ) break; // Loop until nothing is left or an error occurs 00631 00632 // Move the pointer forward past the sent bytes, and subtract their size from the length 00633 pBuffer += nLength; 00634 nBuffer -= nLength; 00635 00636 // If we are keeping track of bandwidth, subtract the size of what we wrote from the limit 00637 if ( nLimit != 0xFFFFFFFF ) nLimit -= nLength; 00638 } 00639 00640 // The total number of bytes written is the length of the output buffer minus any bytes left still to write 00641 DWORD nTotal = ( m_pOutput->m_nLength - nBuffer ); 00642 00643 // We wrote some bytes into the socket 00644 if ( nTotal ) 00645 { 00646 // Remove them from the output buffer, otherwise they would sit there and get sent again the next time 00647 m_pOutput->Remove( nTotal ); 00648 m_mOutput.tLast = tNow; // Record that we last wrote now 00649 00650 // If it's less than 1/10 of a second since we last wrote some bandwidth history information 00651 if ( tNow - m_mOutput.tLastSlot < METER_MINIMUM ) 00652 { 00653 // Just add the bytes in the same time slot as before 00654 m_mOutput.pHistory[ m_mOutput.nPosition ] += nTotal; 00655 00656 } // It's been more than a tenth of a second since we last recorded a write 00657 else 00658 { 00659 // Store the time and total in a new array slot 00660 m_mOutput.nPosition = ( m_mOutput.nPosition + 1 ) % METER_LENGTH; // Move to the next position in the array 00661 m_mOutput.pTimes[ m_mOutput.nPosition ] = tNow; // Record the new time 00662 m_mOutput.pHistory[ m_mOutput.nPosition ] = nTotal; // Store the bytes written next to it 00663 m_mOutput.tLastSlot = tNow; // Mark down when we did this 00664 } 00665 00666 // Add the bytes we read to the total in the bandwidth meter and the total in statistics 00667 m_mOutput.nTotal += nTotal; 00668 Statistics.Current.Bandwidth.Outgoing += nTotal; 00669 } 00670 00671 // Report success 00672 return TRUE; 00673 } 00674 00676 // CConnection measure 00677 00678 // Calculate the input and output speeds for this connection 00679 void CConnection::Measure() 00680 { 00681 // Set tCutoff to the tick count exactly 2 seconds ago 00682 DWORD tCutoff = GetTickCount() - METER_PERIOD; // METER_PERIOD is 6 seconds 00683 00684 // This part of the code has been translated into assembly language to make it faster 00685 /* 00686 // Point to the start of the input and output history and time arrays 00687 DWORD* pInHistory = m_mInput.pHistory; 00688 DWORD* pInTime = m_mInput.pTimes; 00689 DWORD* pOutHistory = m_mOutput.pHistory; 00690 DWORD* pOutTime = m_mOutput.pTimes; 00691 00692 // Start counts at zero 00693 DWORD nInput = 0; 00694 DWORD nOutput = 0; 00695 00696 // Loop down the arrays 00697 for ( int tNow = METER_LENGTH ; tNow ; tNow-- ) 00698 { 00699 // If this record is from the last 2 seconds, add it to the input or output total 00700 if ( *pInTime >= tCutoff ) nInput += *pInHistory; 00701 if ( *pOutTime >= tCutoff ) nOutput += *pOutHistory; 00702 00703 // Move pointers forward to the next spot in the array 00704 pInHistory++, pInTime++; 00705 pOutHistory++, pOutTime++; 00706 } 00707 00708 // Set nMeasure for input and output as the average bytes read and written per second over the last 2 seconds 00709 m_mInput.nMeasure = nInput * 1000 / METER_PERIOD; 00710 m_mOutput.nMeasure = nOutput * 1000 / METER_PERIOD; 00711 */ 00712 // Here is the assembly language which does the same thing 00713 __asm 00714 { 00715 mov ebx, this 00716 mov edx, tCutoff 00717 xor eax, eax 00718 xor esi, esi 00719 mov ecx, -METER_LENGTH 00720 _loop: cmp edx, [ ebx + ecx * 4 ]CConnection.m_mInput.pTimes + METER_LENGTH * 4 00721 jnbe _ignoreIn 00722 add eax, [ ebx + ecx * 4 ]CConnection.m_mInput.pHistory + METER_LENGTH * 4 00723 _ignoreIn:cmp edx, [ ebx + ecx * 4 ]CConnection.m_mOutput.pTimes + METER_LENGTH * 4 00724 jnbe _ignoreOut 00725 add esi, [ ebx + ecx * 4 ]CConnection.m_mOutput.pHistory + METER_LENGTH * 4 00726 _ignoreOut:inc ecx 00727 jnz _loop 00728 xor edx, edx 00729 mov ecx, METER_PERIOD / 1000 00730 div ecx 00731 mov [ ebx ]CConnection.m_mInput.nMeasure, eax 00732 xor edx, edx 00733 mov eax, esi 00734 div ecx 00735 mov [ ebx ]CConnection.m_mOutput.nMeasure, eax 00736 } 00737 } 00738 00740 // CConnection HTML header reading 00741 00742 // Remove the headers from the input buffer, handing each to OnHeaderLine 00743 BOOL CConnection::ReadHeaders() 00744 { 00745 // Move the first line from the m_pInput buffer to strLine and do the contents of the while loop 00746 CString strLine; 00747 while ( m_pInput->ReadLine( strLine ) ) // ReadLine will return false when there are no more lines 00748 { 00749 // If the line is more than 20 KB, change it to the line too long error code 00750 if ( strLine.GetLength() > 20480 ) strLine = _T("#LINE_TOO_LONG#"); 00751 00752 // Find the first colon in the line 00753 int nPos = strLine.Find( _T(":") ); 00754 00755 // The line is empty, it's just a \n character 00756 if ( strLine.IsEmpty() ) 00757 { 00758 // Empty the last header member variable (do) 00759 m_sLastHeader.Empty(); 00760 00761 // Call the OnHeadersComplete method for the most advanced class that inherits from CConnection 00762 return OnHeadersComplete(); // Calls CShakeNeighbour::OnHeadersComplete() 00763 00764 } // The line starts with a space 00765 else if ( _istspace( strLine.GetAt( 0 ) ) ) // Get the first character in the string, and see if its a space 00766 { 00767 // The last header has length 00768 if ( m_sLastHeader.GetLength() ) 00769 { 00770 // Trim the spaces from both ends of the line, and see if it still has length 00771 strLine.TrimLeft(); 00772 strLine.TrimRight(); 00773 if ( strLine.GetLength() > 0 ) 00774 { 00775 // Give OnHeaderLine the last header and this line 00776 if ( ! OnHeaderLine( m_sLastHeader, strLine ) ) return FALSE; 00777 } 00778 } 00779 00780 } // The colon is at a distance greater than 1 and less than 64 00781 else if ( nPos > 1 && nPos < 64 ) // ":a" is 0 and "a:a" is 1, but "aa:a" is greater than 1 00782 { 00783 // The line is like "header:value", copy out both parts 00784 m_sLastHeader = strLine.Left( nPos ); 00785 CString strValue = strLine.Mid( nPos + 1 ); 00786 00787 // Trim spaces from both ends of the value, and see if it still has length 00788 strValue.TrimLeft(); 00789 strValue.TrimRight(); 00790 // Give OnHeaderLine this last header, and its value 00791 if ( ! OnHeaderLine( m_sLastHeader, strValue ) ) return FALSE; // Calls CShakeNeighbour::OnHeaderLine 00792 } 00793 } 00794 00795 // Send the contents of the output buffer to the remote computer 00796 OnWrite(); 00797 return TRUE; 00798 } 00799 00800 // Takes a header and its value 00801 // Reads and processes popular Gnutella headers 00802 // Returns true to have ReadHeaders keep going 00803 BOOL CConnection::OnHeaderLine(CString& strHeader, CString& strValue) 00804 { 00805 // It's the user agent header 00806 if ( strHeader.CompareNoCase( _T("User-Agent") ) == 0 ) 00807 { 00808 // Copy the value into the user agent member string 00809 m_sUserAgent = strValue; // This tells what software the remote computer is running 00810 return TRUE; // Have ReadHeaders keep going 00811 00812 } // It's the remote IP header 00813 else if ( strHeader.CompareNoCase( _T("Remote-IP") ) == 0 ) 00814 { 00815 // Add this address to our record of them 00816 Network.AcquireLocalAddress( strValue ); 00817 00818 } // It's the x my address, listen IP, or node header, like "X-My-Address: 10.254.0.16:6349" 00819 else if ( strHeader.CompareNoCase( _T("X-My-Address") ) == 0 || 00820 strHeader.CompareNoCase( _T("Listen-IP") ) == 0 || 00821 strHeader.CompareNoCase( _T("X-Node") ) == 0 || 00822 strHeader.CompareNoCase( _T("Node") ) == 0 ) 00823 { 00824 // Find another colon in the value 00825 int nColon = strValue.Find( ':' ); 00826 00827 // If the remote computer first contacted us and the colon is there but not first 00828 if ( ! m_bInitiated && nColon > 0 ) 00829 { 00830 // Read the number after the colon into nPort 00831 int nPort = GNUTELLA_DEFAULT_PORT; // Start out nPort as the default value, 6346 00832 if ( _stscanf( strValue.Mid( nColon + 1 ), _T("%lu"), &nPort ) == 1 // Make sure 1 number was found 00833 && nPort != 0 ) // Make sure the found number isn't 0 00834 { 00835 // Save the found port number in m_pHost 00836 m_pHost.sin_port = htons( nPort ); // Convert Windows little endian to big for the Internet with htons 00837 } 00838 } 00839 } 00840 00841 // Have ReadHeaders keep going 00842 return TRUE; 00843 } 00844 00845 // Classes that inherit from CConnection override this virtual method, adding code specific to them 00846 // Returns true 00847 BOOL CConnection::OnHeadersComplete() 00848 { 00849 // Just return true, it's CShakeNeighbour::OnHeadersComplete() that usually gets called instead of this method 00850 return TRUE; 00851 } 00852 00854 // CConnection header output helpers 00855 00856 // Compose text like "Listen-IP: 1.2.3.4:5" and prints it into the output buffer 00857 // Returns true if we are listening on a port and the header was sent, false otherwise 00858 BOOL CConnection::SendMyAddress() 00859 { 00860 // Only do something if we are listening on a port 00861 if ( Network.IsListening() ) 00862 { 00863 // Compose header text 00864 CString strHeader; 00865 00866 strHeader.Format( 00867 _T("Listen-IP: %s:%hu\r\n"), // Make it like "Listen-IP: 67.176.34.172:6346\r\n" 00868 (LPCTSTR)CString( inet_ntoa( Network.m_pHost.sin_addr ) ), // Insert the IP address like "67.176.34.172" 00869 htons( Network.m_pHost.sin_port ) ); // Our port number in big endian 00870 00871 // Print the line into the bottom of the output buffer 00872 m_pOutput->Print( strHeader ); // It will be sent to the remote computer on the next write 00873 00874 // Report that we are listening on a port, and the header is sent 00875 return TRUE; 00876 } 00877 00878 // We're not even listening on a port 00879 return FALSE; 00880 } 00881 00883 // CConnection blocked agent filter 00884 00885 // Call to determine if the remote computer is running software we'd rather not communicate with 00886 // Returns true to block or false to allow the program 00887 BOOL CConnection::IsAgentBlocked() 00888 { 00889 // Eliminate some obvious block and don't block cases 00890 if ( m_sUserAgent == _T("Fake Shareaza") ) return TRUE; // Block "Fake Shareaza" 00891 00892 // The remote computer didn't send a "User-Agent" header, or the sent blank text 00893 if ( m_sUserAgent.IsEmpty() ) // Blank user agent 00894 { 00895 // If settings say we should block that, return true 00896 if ( Settings.Gnutella.BlockBlankClients ) return TRUE; 00897 else return FALSE; 00898 } 00899 00900 // If the list of programs to block is empty, allow this program 00901 if ( Settings.Uploads.BlockAgents.IsEmpty() ) return FALSE; 00902 00903 // Get the list of blocked programs, and make a copy here of it all in lowercase letters 00904 CString strBlocked = Settings.Uploads.BlockAgents; 00905 CharLower( strBlocked.GetBuffer() ); 00906 strBlocked.ReleaseBuffer(); 00907 00908 // Get the name of the program running on the other side of the connection, and make it lowercase also 00909 CString strAgent = m_sUserAgent; 00910 CharLower( strAgent.GetBuffer() ); 00911 strAgent.ReleaseBuffer(); 00912 00913 // Loop through the list of programs to block 00914 for ( strBlocked += '|' ; strBlocked.GetLength() ; ) 00915 { 00916 // Break off a blocked program name from the start of the list 00917 CString strBrowser = strBlocked.SpanExcluding( _T("|;,") ); // Get the text before a puncutation mark 00918 strBlocked = strBlocked.Mid( strBrowser.GetLength() + 1 ); // Remove that much text from the start 00919 00920 // If the blocked list still exists and the blocked program and remote program match, block it 00921 if ( strBrowser.GetLength() > 0 && strAgent.Find( strBrowser ) >= 0 ) return TRUE; 00922 } 00923 00924 // Allow it 00925 return FALSE; 00926 } 00927 00929 // CConnection URL encodings 00930 00931 // Encodes unsafe characters in a string, turning "hello world" into "hello%20world", for instance 00932 // Takes text and returns a string 00933 CString CConnection::URLEncode(LPCTSTR pszInputT) 00934 { 00935 // Setup two strings, one with all the hexidecimal digits, the other with all the characters to find and encode 00936 static LPCTSTR pszHex = _T("0123456789ABCDEF"); // A string with all the hexidecimal digits 00937 static LPCSTR pszUnsafe = "<>\"#%{}|\\^~[]+?&@=:,"; // A string with all the characters unsafe for a URL 00938 00939 // The output string starts blank 00940 CString strOutput; 00941 00942 // If the input character pointer points to null or points to the null terminator, just return the blank output string 00943 if ( pszInputT == NULL || *pszInputT == 0 ) return strOutput; 00944 00945 // Map the wide character string to a new character set 00946 int nUTF8 = WideCharToMultiByte( 00947 CP_UTF8, // Translate using UTF-8, the default encoding for Unicode 00948 0, // Must be 0 for UTF-8 encoding 00949 pszInputT, // Points to the wide character string to be converted 00950 -1, // The string is null terminated 00951 NULL, // We just want to find out how long the buffer for the output string needs to be 00952 0, 00953 NULL, // Both must be NULL for UTF-8 encoding 00954 NULL ); 00955 00956 // If the converted text would take less than 2 bytes, which is 1 character, just return blank 00957 if ( nUTF8 < 2 ) return strOutput; 00958 00959 // Make a new array of CHARs which is nUTF8 bytes long 00960 LPSTR pszUTF8 = new CHAR[ nUTF8 ]; 00961 00962 // Call WideCharToMultiByte again, this time it has the output buffer and will actually do the conversion 00963 WideCharToMultiByte( CP_UTF8, 0, pszInputT, -1, pszUTF8, nUTF8, NULL, NULL ); 00964 00965 // Set the null terminator in pszUTF8 to right where you think it should be, and point a new character pointer at it 00966 pszUTF8[ nUTF8 - 1 ] = 0; 00967 LPCSTR pszInput = pszUTF8; 00968 00969 // Get the character buffer inside the output string, specifying how much larger to make it 00970 LPTSTR pszOutput = strOutput.GetBuffer( strlen( pszInput ) * 3 + 1 ); // Times 3 in case every character gets encoded 00971 00972 // Loop for each character of input text 00973 for ( ; *pszInput ; pszInput++ ) 00974 { 00975 // If the character code is 32 or less, more than 127, or in the unsafe list 00976 if ( *pszInput <= 32 || *pszInput > 127 || strchr( pszUnsafe, *pszInput ) != NULL ) 00977 { 00978 // Write a three letter code for it like %20 in the output text 00979 *pszOutput++ = _T('%'); 00980 *pszOutput++ = pszHex[ ( *pszInput >> 4 ) & 0x0F ]; 00981 *pszOutput++ = pszHex[ *pszInput & 0x0F ]; 00982 00983 } // The character doesn't need to be encoded 00984 else 00985 { 00986 // Just copy it across 00987 *pszOutput++ = (TCHAR)*pszInput; 00988 } 00989 } 00990 00991 // Null terminate the output text, and then close our direct manipulation of the string 00992 *pszOutput = 0; 00993 strOutput.ReleaseBuffer(); // This closes the string so Windows can again start managing its memory for us 00994 00995 // Free the memory we allocated with the new keyword above 00996 delete [] pszUTF8; 00997 00998 00999 // Return the URL-encoded, %20-filled text 01000 return strOutput; 01001 } 01002 01003 01004 // Decodes unsafe characters in a string, turning "hello%20world" into "hello world", for instance 01005 // Takes text and returns a string 01006 CString CConnection::URLDecode(LPCTSTR pszInput) 01007 { 01008 LPCTSTR pszLoop = pszInput; 01009 // Check each character of input text 01010 for ( ; *pszLoop ; pszLoop++ ) 01011 { 01012 if ( *pszLoop > 255 ) 01013 { 01014 // This URI is not properly encoded, and has unicode characters in it. URL-decode only 01015 return URLDecodeUnicode( pszInput ); 01016 } 01017 } 01018 01019 // This is a correctly formatted URI, which must be url-decoded, then UTF-8 decoded. 01020 return URLDecodeANSI( pszInput ); 01021 } 01022 01023 // Decodes a properly formatted URI, then UTF-8 decodes it 01024 CString CConnection::URLDecodeANSI(LPCTSTR pszInput) 01025 { 01026 // Setup local variables useful for the conversion 01027 TCHAR szHex[3] = { 0, 0, 0 }; // A 3 character long array filled with 3 null terminators 01028 CString strOutput; // The output string, which starts out blank 01029 int nHex; // The hex code of the character we found 01030 01031 // Allocate a new CHAR array big enough to hold the input characters and a null terminator 01032 LPSTR pszBytes = new CHAR[ _tcslen( pszInput ) + 1 ]; 01033 01034 // Point the output string pointer at this array 01035 LPSTR pszOutput = pszBytes; 01036 01037 // Loop for each character of input text 01038 for ( ; *pszInput ; pszInput++ ) 01039 { 01040 // We hit a %, which might be the start of something like %20 01041 if ( *pszInput == '%' ) 01042 { 01043 // Copy characters like "20" into szHex, making sure neither are null 01044 if ( ! ( szHex[0] = pszInput[1] ) ) break; 01045 if ( ! ( szHex[1] = pszInput[2] ) ) break; 01046 01047 // Read the text like "20" as a number, and store it in nHex 01048 if ( _stscanf( szHex, _T("%x"), &nHex ) != 1 ) break; 01049 if ( nHex < 1 ) break; // Make sure the number isn't 0 or negative 01050 01051 // That number is the code of a character, copy it into the output string 01052 *pszOutput++ = nHex; // And then move the output pointer to the next spot 01053 01054 // Move the input pointer past the two characters of the "20" 01055 pszInput += 2; 01056 01057 } // We hit a +, which is shorthand for space 01058 else if ( *pszInput == '+' ) 01059 { 01060 // Add a space to the output text, and move the pointer forward 01061 *pszOutput++ = ' '; 01062 01063 } // The input pointer is just on a normal character 01064 else 01065 { 01066 // Copy it across 01067 *pszOutput++ = (CHAR)*pszInput; 01068 } 01069 } 01070 01071 // Cap off the output text with a null terminator 01072 *pszOutput = 0; 01073 01074 // Copy the text from pszBytes into strOutput, converting it into Unicode 01075 int nLength = MultiByteToWideChar( CP_UTF8, 0, pszBytes, -1, NULL, 0 ); 01076 MultiByteToWideChar( CP_UTF8, 0, pszBytes, -1, strOutput.GetBuffer( nLength ), nLength ); 01077 01078 // Close the output string, we are done editing its buffer directly 01079 strOutput.ReleaseBuffer(); 01080 01081 // Free the memory we allocated above 01082 delete [] pszBytes; 01083 01084 // Return the output string 01085 return strOutput; 01086 } 01087 01088 01089 // Decodes encoded characters in a unicode string 01090 CString CConnection::URLDecodeUnicode(LPCTSTR pszInput) 01091 { 01092 // Setup local variables useful for the conversion 01093 TCHAR szHex[3] = { 0, 0, 0 }; // A 3 character long array filled with 3 null terminators 01094 CString strOutput; // The output string, which starts out blank 01095 int nHex; // The hex code of the character we found 01096 01097 // Allocate a new CHAR array big enough to hold the input characters and a null terminator 01098 LPTSTR pszBytes = strOutput.GetBuffer( _tcslen( pszInput ) ); 01099 01100 // Point the output string pointer at this array 01101 LPTSTR pszOutput = pszBytes; 01102 01103 // Loop for each character of input text 01104 for ( ; *pszInput ; pszInput++ ) 01105 { 01106 // We hit a %, which might be the start of something like %20 01107 if ( *pszInput == '%' ) 01108 { 01109 // Copy characters like "20" into szHex, making sure neither are null 01110 if ( ! ( szHex[0] = pszInput[1] ) ) break; 01111 if ( ! ( szHex[1] = pszInput[2] ) ) break; 01112 01113 // Read the text like "20" as a number, and store it in nHex 01114 if ( _stscanf( szHex, _T("%x"), &nHex ) != 1 ) break; 01115 if ( nHex < 1 ) break; // Make sure the number isn't 0 or negative 01116 01117 // That number is the code of a character, copy it into the output string 01118 *pszOutput++ = nHex; // And then move the output pointer to the next spot 01119 01120 // Move the input pointer past the two characters of the "20" 01121 pszInput += 2; 01122 01123 } // We hit a +, which is shorthand for space 01124 else if ( *pszInput == '+' ) 01125 { 01126 // Add a space to the output text, and move the pointer forward 01127 *pszOutput++ = ' '; 01128 01129 } // The input pointer is just on a normal character 01130 else 01131 { 01132 // Copy it across 01133 *pszOutput++ = (TCHAR)*pszInput; 01134 } 01135 } 01136 01137 // End the output text with a null terminator 01138 *pszOutput = 0; 01139 // Put the output into a CString 01140 strOutput.ReleaseBuffer(); 01141 // Return the output string 01142 return strOutput; 01143 } 01144 01145 // Takes some input text like "hello world" and a text tag like "hello" 01146 // Determines if the input starts with the text 01147 // Returns true or false 01148 BOOL CConnection::StartsWith(LPCTSTR pszInput, LPCTSTR pszText) 01149 { 01150 // See if input starts with text 01151 return _tcsnicmp( 01152 pszInput, // Compare input 01153 pszText, // With text 01154 _tcslen( pszText ) ) // But just the first text-length part of both 01155 == 0; // Return true if it matches, false if not 01156 }