00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013 #include "config.h"
00014
00015 #include "gloox.h"
00016
00017 #include "connectionbosh.h"
00018 #include "logsink.h"
00019 #include "prep.h"
00020 #include "tag.h"
00021 #include "util.h"
00022
00023 #include <string>
00024 #include <cstdlib>
00025 #include <cctype>
00026 #include <algorithm>
00027
00028 namespace gloox
00029 {
00030
00031 ConnectionBOSH::ConnectionBOSH( ConnectionBase* connection, const LogSink& logInstance,
00032 const std::string& boshHost, const std::string& xmppServer,
00033 int xmppPort )
00034 : ConnectionBase( 0 ),
00035 m_logInstance( logInstance ), m_parser( this ), m_boshHost( boshHost ), m_path( "/http-bind/" ),
00036 m_rid( 0 ), m_initialStreamSent( false ), m_openRequests( 0 ),
00037 m_maxOpenRequests( 2 ), m_wait( 30 ), m_hold( 2 ), m_streamRestart( false ),
00038 m_lastRequestTime( std::time( 0 ) ), m_minTimePerRequest( 0 ), m_bufferContentLength( 0 ),
00039 m_connMode( ModePipelining )
00040 {
00041 initInstance( connection, xmppServer, xmppPort );
00042 }
00043
00044 ConnectionBOSH::ConnectionBOSH( ConnectionDataHandler* cdh, ConnectionBase* connection,
00045 const LogSink& logInstance, const std::string& boshHost,
00046 const std::string& xmppServer, int xmppPort )
00047 : ConnectionBase( cdh ),
00048 m_logInstance( logInstance ), m_parser( this ), m_boshHost( boshHost ), m_path( "/http-bind/" ),
00049 m_rid( 0 ), m_initialStreamSent( false ), m_openRequests( 0 ),
00050 m_maxOpenRequests( 2 ), m_wait( 30 ), m_hold( 2 ), m_streamRestart( false ),
00051 m_lastRequestTime( std::time( 0 ) ), m_minTimePerRequest( 0 ), m_bufferContentLength( 0 ),
00052 m_connMode( ModePipelining )
00053 {
00054 initInstance( connection, xmppServer, xmppPort );
00055 }
00056
00057 void ConnectionBOSH::initInstance( ConnectionBase* connection, const std::string& xmppServer,
00058 const int xmppPort )
00059 {
00060
00061 prep::idna( xmppServer, m_server );
00062 m_port = xmppPort;
00063 if( m_port != -1 )
00064 {
00065 m_boshedHost = m_boshHost + ":" + util::int2string( m_port );
00066 }
00067
00068
00069 if( connection )
00070 {
00071 connection->registerConnectionDataHandler( this );
00072 m_connectionPool.push_back( connection );
00073 }
00074 }
00075
00076 ConnectionBOSH::~ConnectionBOSH()
00077 {
00078 util::clearList( m_activeConnections );
00079 util::clearList( m_connectionPool );
00080 }
00081
00082 ConnectionBase* ConnectionBOSH::newInstance() const
00083 {
00084 ConnectionBase* pBaseConn = 0;
00085
00086 if( !m_connectionPool.empty() )
00087 {
00088 pBaseConn = m_connectionPool.front()->newInstance();
00089 }
00090 else if( !m_activeConnections.empty() )
00091 {
00092 pBaseConn = m_activeConnections.front()->newInstance();
00093 }
00094 else
00095 {
00096 return 0;
00097 }
00098
00099 return new ConnectionBOSH( m_handler, pBaseConn, m_logInstance,
00100 m_boshHost, m_server, m_port );
00101 }
00102
00103 ConnectionError ConnectionBOSH::connect()
00104 {
00105 if( m_state >= StateConnecting )
00106 return ConnNoError;
00107
00108 if( !m_handler )
00109 return ConnNotConnected;
00110
00111 m_state = StateConnecting;
00112 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00113 "bosh initiating connection to server: " +
00114 ( ( m_connMode == ModePipelining ) ? std::string( "Pipelining" )
00115 : ( ( m_connMode == ModeLegacyHTTP ) ? std::string( "LegacyHTTP" )
00116 : std::string( "PersistentHTTP" ) ) ) );
00117 getConnection();
00118 return ConnNoError;
00119 }
00120
00121 void ConnectionBOSH::disconnect()
00122 {
00123 if( ( m_connMode == ModePipelining && m_activeConnections.empty() )
00124 || ( m_connectionPool.empty() && m_activeConnections.empty() ) )
00125 return;
00126
00127 if( m_state != StateDisconnected )
00128 {
00129 ++m_rid;
00130
00131 std::string requestBody = "<body rid='" + util::int2string( m_rid ) + "' ";
00132 requestBody += "sid='" + m_sid + "' ";
00133 requestBody += "type='terminal' ";
00134 requestBody += "xml:lang='en' ";
00135 requestBody += "xmlns='" + XMLNS_HTTPBIND + "'";
00136 if( m_sendBuffer.empty() )
00137 requestBody += "/>";
00138 else
00139 {
00140 requestBody += ">" + m_sendBuffer + "</body>";
00141 m_sendBuffer = EmptyString;
00142 }
00143 sendRequest( requestBody );
00144
00145 m_logInstance.dbg( LogAreaClassConnectionBOSH, "bosh disconnection request sent" );
00146 }
00147 else
00148 {
00149 m_logInstance.err( LogAreaClassConnectionBOSH,
00150 "disconnecting from server in a non-graceful fashion" );
00151 }
00152
00153 util::ForEach( m_activeConnections, &ConnectionBase::disconnect );
00154 util::ForEach( m_connectionPool, &ConnectionBase::disconnect );
00155
00156 m_state = StateDisconnected;
00157 if( m_handler )
00158 m_handler->handleDisconnect( this, ConnUserDisconnected );
00159 }
00160
00161 ConnectionError ConnectionBOSH::recv( int timeout )
00162 {
00163 if( m_state == StateDisconnected )
00164 return ConnNotConnected;
00165
00166 if( !m_connectionPool.empty() )
00167 m_connectionPool.front()->recv( 0 );
00168 if( !m_activeConnections.empty() )
00169 m_activeConnections.front()->recv( timeout );
00170
00171
00172
00173 if( ( m_openRequests == 0 || m_sendBuffer.size() > 0 ) && m_state == StateConnected )
00174 {
00175 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00176 "Sending empty request (or there is data in the send buffer)" );
00177 sendXML();
00178 }
00179
00180 return ConnNoError;
00181 }
00182
00183 bool ConnectionBOSH::send( const std::string& data )
00184 {
00185
00186 if( m_state == StateDisconnected )
00187 return false;
00188
00189 if( data.substr( 0, 2 ) == "<?" )
00190 {
00191
00192 {
00193 m_streamRestart = true;
00194 sendXML();
00195 return true;
00196 }
00197
00198
00199
00200
00201
00202
00203 }
00204 else if( data == "</stream:stream>" )
00205 return true;
00206
00207 m_sendBuffer += data;
00208 sendXML();
00209
00210 return true;
00211 }
00212
00213
00214 bool ConnectionBOSH::sendXML()
00215 {
00216 if( m_state != StateConnected )
00217 {
00218 m_logInstance.warn( LogAreaClassConnectionBOSH,
00219 "Data sent before connection established (will be buffered)" );
00220 return false;
00221 }
00222
00223 if( m_sendBuffer.empty() )
00224 {
00225 time_t now = time( 0 );
00226 unsigned int delta = (int)(now - m_lastRequestTime);
00227 if( delta < m_minTimePerRequest && m_openRequests > 0 )
00228 {
00229 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Too little time between requests: " + util::int2string( delta ) + " seconds" );
00230 return false;
00231 }
00232 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Send buffer is empty, sending empty request" );
00233 }
00234
00235 ++m_rid;
00236
00237 std::string requestBody = "<body rid='" + util::int2string( m_rid ) + "' ";
00238 requestBody += "sid='" + m_sid + "' ";
00239 requestBody += "xmlns='" + XMLNS_HTTPBIND + "'";
00240
00241 if( m_streamRestart )
00242 {
00243 requestBody += " xmpp:restart='true' to='" + m_server + "' xml:lang='en' xmlns:xmpp='"
00244 + XMLNS_XMPP_BOSH + "' />";
00245 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Restarting stream" );
00246 }
00247 else
00248 {
00249 requestBody += ">" + m_sendBuffer + "</body>";
00250 }
00251
00252 if( sendRequest( requestBody ) )
00253 {
00254 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Successfully sent m_sendBuffer" );
00255 m_sendBuffer = EmptyString;
00256 m_streamRestart = false;
00257 }
00258 else
00259 {
00260 --m_rid;
00261 m_logInstance.warn( LogAreaClassConnectionBOSH,
00262 "Unable to send. Connection not complete, or too many open requests,"
00263 " so added to buffer.\n" );
00264 }
00265
00266 return true;
00267 }
00268
00269
00270 bool ConnectionBOSH::sendRequest( const std::string& xml )
00271 {
00272 ConnectionBase* conn = getConnection();
00273 if( !conn )
00274 return false;
00275
00276 std::string request = "POST " + m_path;
00277 if( m_connMode == ModeLegacyHTTP )
00278 {
00279 request += " HTTP/1.0\r\n";
00280 request += "Connection: close\r\n";
00281 }
00282 else
00283 request += " HTTP/1.1\r\n";
00284
00285 request += "Host: " + m_boshedHost + "\r\n";
00286 request += "Content-Type: text/xml; charset=utf-8\r\n";
00287 request += "Content-Length: " + util::int2string( xml.length() ) + "\r\n";
00288 request += "User-Agent: gloox/" + GLOOX_VERSION + "\r\n\r\n";
00289 request += xml;
00290
00291
00292 if( conn->send( request ) )
00293 {
00294 m_lastRequestTime = time( 0 );
00295 ++m_openRequests;
00296 return true;
00297 }
00298
00299
00300
00301 return false;
00302 }
00303
00304 bool ci_equal( char ch1, char ch2 )
00305 {
00306 return std::toupper( (unsigned char)ch1 ) == std::toupper( (unsigned char)ch2 );
00307 }
00308
00309 std::string::size_type ci_find( const std::string& str1, const std::string& str2 )
00310 {
00311 std::string::const_iterator pos = std::search( str1.begin(), str1.end(),
00312 str2.begin(), str2.end(), ci_equal );
00313 if( pos == str1.end() )
00314 return std::string::npos;
00315 else
00316 return std::distance( str1.begin(), pos );
00317 }
00318
00319 const std::string ConnectionBOSH::getHTTPField( const std::string& field )
00320 {
00321 std::string::size_type fp = ci_find( m_bufferHeader, "\r\n" + field + ": " );
00322
00323 if( fp == std::string::npos )
00324 return EmptyString;
00325
00326 fp += field.length() + 4;
00327
00328 const std::string::size_type fp2 = m_bufferHeader.find( "\r\n", fp );
00329 if( fp2 == std::string::npos )
00330 return EmptyString;
00331
00332 return m_bufferHeader.substr( fp, fp2 - fp );
00333 }
00334
00335 ConnectionError ConnectionBOSH::receive()
00336 {
00337 ConnectionError err = ConnNoError;
00338 while( m_state != StateDisconnected && ( err = recv( 10 ) ) == ConnNoError )
00339 ;
00340 return err == ConnNoError ? ConnNotConnected : err;
00341 }
00342
00343 void ConnectionBOSH::cleanup()
00344 {
00345 m_state = StateDisconnected;
00346
00347 util::ForEach( m_activeConnections, &ConnectionBase::cleanup );
00348 util::ForEach( m_connectionPool, &ConnectionBase::cleanup );
00349 }
00350
00351 void ConnectionBOSH::getStatistics( long int& totalIn, long int& totalOut )
00352 {
00353 util::ForEach( m_activeConnections, &ConnectionBase::getStatistics, totalIn, totalOut );
00354 util::ForEach( m_connectionPool, &ConnectionBase::getStatistics, totalIn, totalOut );
00355 }
00356
00357 void ConnectionBOSH::handleReceivedData( const ConnectionBase* ,
00358 const std::string& data )
00359 {
00360 m_buffer += data;
00361 std::string::size_type headerLength = 0;
00362 while( ( headerLength = m_buffer.find( "\r\n\r\n" ) ) != std::string::npos )
00363 {
00364 m_bufferHeader = m_buffer.substr( 0, headerLength+2 );
00365
00366 const std::string& statusCode = m_bufferHeader.substr( 9, 3 );
00367 if( statusCode != "200" )
00368 {
00369 m_logInstance.warn( LogAreaClassConnectionBOSH,
00370 "Received error via legacy HTTP status code: " + statusCode
00371 + ". Disconnecting." );
00372 m_state = StateDisconnected;
00373 disconnect();
00374 }
00375
00376 m_bufferContentLength = atol( getHTTPField( "Content-Length" ).c_str() );
00377 if( !m_bufferContentLength )
00378 return;
00379
00380 if( m_connMode != ModeLegacyHTTP && ( getHTTPField( "Connection" ) == "close"
00381 || m_bufferHeader.substr( 0, 8 ) == "HTTP/1.0" ) )
00382 {
00383 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00384 "Server indicated lack of support for HTTP/1.1 - falling back to HTTP/1.0" );
00385 m_connMode = ModeLegacyHTTP;
00386 }
00387
00388 if( m_buffer.length() >= ( headerLength + 4 + m_bufferContentLength ) )
00389 {
00390 putConnection();
00391 --m_openRequests;
00392 std::string xml = m_buffer.substr( headerLength + 4, m_bufferContentLength );
00393 m_parser.feed( xml );
00394 m_buffer.erase( 0, headerLength + 4 + m_bufferContentLength );
00395 m_bufferContentLength = 0;
00396 m_bufferHeader = EmptyString;
00397 }
00398 else
00399 {
00400 m_logInstance.warn( LogAreaClassConnectionBOSH, "buffer length mismatch" );
00401 break;
00402 }
00403 }
00404 }
00405
00406 void ConnectionBOSH::handleConnect( const ConnectionBase* )
00407 {
00408 if( m_state == StateConnecting )
00409 {
00410 m_rid = rand() % 100000 + 1728679472;
00411
00412 Tag requestBody( "body" );
00413 requestBody.setXmlns( XMLNS_HTTPBIND );
00414 requestBody.setXmlns( XMLNS_XMPP_BOSH, "xmpp" );
00415
00416 requestBody.addAttribute( "content", "text/xml; charset=utf-8" );
00417 requestBody.addAttribute( "hold", (long)m_hold );
00418 requestBody.addAttribute( "rid", (long)m_rid );
00419 requestBody.addAttribute( "ver", "1.6" );
00420 requestBody.addAttribute( "wait", (long)m_wait );
00421 requestBody.addAttribute( "ack", 0 );
00422 requestBody.addAttribute( "secure", "false" );
00423 requestBody.addAttribute( "route", "xmpp:" + m_server + ":5222" );
00424 requestBody.addAttribute( "xml:lang", "en" );
00425 requestBody.addAttribute( "xmpp:version", "1.0" );
00426 requestBody.addAttribute( "to", m_server );
00427
00428 m_logInstance.dbg( LogAreaClassConnectionBOSH, "sending bosh connection request" );
00429 sendRequest( requestBody.xml() );
00430 }
00431 }
00432
00433 void ConnectionBOSH::handleDisconnect( const ConnectionBase* ,
00434 ConnectionError reason )
00435 {
00436 if( m_handler && m_state == StateConnecting )
00437 {
00438 m_state = StateDisconnected;
00439 m_handler->handleDisconnect( this, reason );
00440 return;
00441 }
00442
00443 switch( m_connMode )
00444 {
00445 case ModePipelining:
00446 m_connMode = ModeLegacyHTTP;
00447 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00448 "connection closed - falling back to HTTP/1.0 connection method" );
00449 break;
00450 case ModeLegacyHTTP:
00451 case ModePersistentHTTP:
00452
00453
00454 break;
00455 }
00456 }
00457
00458 void ConnectionBOSH::handleTag( Tag* tag )
00459 {
00460 if( !m_handler || tag->name() != "body" )
00461 return;
00462
00463 if( m_streamRestart )
00464 {
00465 m_streamRestart = false;
00466 m_logInstance.dbg( LogAreaClassConnectionBOSH, "sending spoofed <stream:stream>" );
00467 m_handler->handleReceivedData( this, "<?xml version='1.0' ?>"
00468 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams'"
00469 " xmlns='" + XMLNS_CLIENT + "' version='" + XMPP_STREAM_VERSION_MAJOR
00470 + "." + XMPP_STREAM_VERSION_MINOR + "' from='" + m_server + "' id ='"
00471 + m_sid + "' xml:lang='en'>" );
00472 }
00473
00474 if( tag->hasAttribute( "sid" ) )
00475 {
00476 m_state = StateConnected;
00477 m_sid = tag->findAttribute( "sid" );
00478
00479 if( tag->hasAttribute( "requests" ) )
00480 {
00481 const int serverRequests = atoi( tag->findAttribute( "requests" ).c_str() );
00482 if( serverRequests < m_maxOpenRequests )
00483 {
00484 m_maxOpenRequests = serverRequests;
00485 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00486 "bosh parameter 'requests' now set to " + tag->findAttribute( "requests" ) );
00487 }
00488 }
00489 if( tag->hasAttribute( "hold" ) )
00490 {
00491 const int maxHold = atoi( tag->findAttribute( "hold" ).c_str() );
00492 if( maxHold < m_hold )
00493 {
00494 m_hold = maxHold;
00495 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00496 "bosh parameter 'hold' now set to " + tag->findAttribute( "hold" ) );
00497 }
00498 }
00499 if( tag->hasAttribute( "wait" ) )
00500 {
00501 const int maxWait = atoi( tag->findAttribute( "wait" ).c_str() );
00502 if( maxWait < m_wait )
00503 {
00504 m_wait = maxWait;
00505 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00506 "bosh parameter 'wait' now set to " + tag->findAttribute( "wait" )
00507 + " seconds" );
00508 }
00509 }
00510 if( tag->hasAttribute( "polling" ) )
00511 {
00512 const int minTime = atoi( tag->findAttribute( "polling" ).c_str() );
00513 m_minTimePerRequest = minTime;
00514 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00515 "bosh parameter 'polling' now set to " + tag->findAttribute( "polling" )
00516 + " seconds" );
00517 }
00518
00519 if( m_state < StateConnected )
00520 m_handler->handleConnect( this );
00521
00522 m_handler->handleReceivedData( this, "<?xml version='1.0' ?>"
00523
00524
00525 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
00526 "xmlns='" + XMLNS_CLIENT
00527 + "' version='" + XMPP_STREAM_VERSION_MAJOR + "." + XMPP_STREAM_VERSION_MINOR
00528 + "' from='" + m_server + "' id ='" + m_sid + "' xml:lang='en'>" );
00529 }
00530
00531 if( tag->findAttribute( "type" ) == "terminate" )
00532 {
00533 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00534 "bosh connection closed by server: " + tag->findAttribute( "condition" ) );
00535 m_state = StateDisconnected;
00536 m_handler->handleDisconnect( this, ConnStreamClosed );
00537 return;
00538 }
00539
00540 const TagList& stanzas = tag->children();
00541 TagList::const_iterator it = stanzas.begin();
00542 for( ; it != stanzas.end(); ++it )
00543 m_handler->handleReceivedData( this, (*it)->xml() );
00544 }
00545
00546 ConnectionBase* ConnectionBOSH::getConnection()
00547 {
00548 if( m_openRequests > 0 && m_openRequests >= m_maxOpenRequests )
00549 {
00550 m_logInstance.warn( LogAreaClassConnectionBOSH,
00551 "Too many requests already open. Cannot send." );
00552 return 0;
00553 }
00554
00555 ConnectionBase* conn = 0;
00556 switch( m_connMode )
00557 {
00558 case ModePipelining:
00559 if( !m_activeConnections.empty() )
00560 {
00561 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Using default connection for Pipelining." );
00562 return m_activeConnections.front();
00563 }
00564 else if( !m_connectionPool.empty() )
00565 {
00566 m_logInstance.warn( LogAreaClassConnectionBOSH,
00567 "Pipelining selected, but no connection open. Opening one." );
00568 return activateConnection();
00569 }
00570 else
00571 m_logInstance.warn( LogAreaClassConnectionBOSH,
00572 "No available connections to pipeline on." );
00573 break;
00574 case ModeLegacyHTTP:
00575 case ModePersistentHTTP:
00576 {
00577 if( !m_connectionPool.empty() )
00578 {
00579 m_logInstance.dbg( LogAreaClassConnectionBOSH, "LegacyHTTP/PersistentHTTP selected, "
00580 "using connection from pool." );
00581 return activateConnection();
00582 }
00583 else if( !m_activeConnections.empty() )
00584 {
00585 m_logInstance.dbg( LogAreaClassConnectionBOSH, "No connections in pool, creating a new one." );
00586 conn = m_activeConnections.front()->newInstance();
00587 conn->registerConnectionDataHandler( this );
00588 m_connectionPool.push_back( conn );
00589 conn->connect();
00590 }
00591 else
00592 m_logInstance.warn( LogAreaClassConnectionBOSH,
00593 "No available connections to send on." );
00594 break;
00595 }
00596 }
00597 return 0;
00598 }
00599
00600 ConnectionBase* ConnectionBOSH::activateConnection()
00601 {
00602 ConnectionBase* conn = m_connectionPool.front();
00603 m_connectionPool.pop_front();
00604 if( conn->state() == StateConnected )
00605 {
00606 m_activeConnections.push_back( conn );
00607 return conn;
00608 }
00609
00610 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Connecting pooled connection." );
00611 m_connectionPool.push_back( conn );
00612 conn->connect();
00613 return 0;
00614 }
00615
00616 void ConnectionBOSH::putConnection()
00617 {
00618 ConnectionBase* conn = m_activeConnections.front();
00619
00620 switch( m_connMode )
00621 {
00622 case ModeLegacyHTTP:
00623 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Disconnecting LegacyHTTP connection" );
00624 conn->disconnect();
00625 conn->cleanup();
00626 m_activeConnections.pop_front();
00627 m_connectionPool.push_back( conn );
00628 break;
00629 case ModePersistentHTTP:
00630 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Deactivating PersistentHTTP connection" );
00631 m_activeConnections.pop_front();
00632 m_connectionPool.push_back( conn );
00633 break;
00634 case ModePipelining:
00635 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Keeping Pipelining connection" );
00636 default:
00637 break;
00638 }
00639 }
00640
00641 }