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
00278 if( m_connMode == ModeLegacyHTTP )
00279 {
00280 request += " HTTP/1.0\r\n";
00281 request += "Connection: close\r\n";
00282 }
00283 else
00284 {
00285 request += " HTTP/1.1\r\n";
00286 request += "Connection: keep-alive\r\n";
00287 }
00288
00289 request += "Host: " + m_boshedHost + "\r\n";
00290 request += "Content-Type: text/xml; charset=utf-8\r\n";
00291 request += "Content-Length: " + util::int2string( xml.length() ) + "\r\n";
00292 request += "User-Agent: gloox/" + GLOOX_VERSION + "\r\n\r\n";
00293 request += xml;
00294
00295
00296 if( conn->send( request ) )
00297 {
00298 m_lastRequestTime = time( 0 );
00299 ++m_openRequests;
00300 return true;
00301 }
00302
00303
00304
00305 return false;
00306 }
00307
00308 bool ci_equal( char ch1, char ch2 )
00309 {
00310 return std::toupper( (unsigned char)ch1 ) == std::toupper( (unsigned char)ch2 );
00311 }
00312
00313 std::string::size_type ci_find( const std::string& str1, const std::string& str2 )
00314 {
00315 std::string::const_iterator pos = std::search( str1.begin(), str1.end(),
00316 str2.begin(), str2.end(), ci_equal );
00317 if( pos == str1.end() )
00318 return std::string::npos;
00319 else
00320 return std::distance( str1.begin(), pos );
00321 }
00322
00323 const std::string ConnectionBOSH::getHTTPField( const std::string& field )
00324 {
00325 std::string::size_type fp = ci_find( m_bufferHeader, "\r\n" + field + ": " );
00326
00327 if( fp == std::string::npos )
00328 return EmptyString;
00329
00330 fp += field.length() + 4;
00331
00332 const std::string::size_type fp2 = m_bufferHeader.find( "\r\n", fp );
00333 if( fp2 == std::string::npos )
00334 return EmptyString;
00335
00336 return m_bufferHeader.substr( fp, fp2 - fp );
00337 }
00338
00339 ConnectionError ConnectionBOSH::receive()
00340 {
00341 ConnectionError err = ConnNoError;
00342 while( m_state != StateDisconnected && ( err = recv( 10 ) ) == ConnNoError )
00343 ;
00344 return err == ConnNoError ? ConnNotConnected : err;
00345 }
00346
00347 void ConnectionBOSH::cleanup()
00348 {
00349 m_state = StateDisconnected;
00350
00351 util::ForEach( m_activeConnections, &ConnectionBase::cleanup );
00352 util::ForEach( m_connectionPool, &ConnectionBase::cleanup );
00353 }
00354
00355 void ConnectionBOSH::getStatistics( long int& totalIn, long int& totalOut )
00356 {
00357 util::ForEach( m_activeConnections, &ConnectionBase::getStatistics, totalIn, totalOut );
00358 util::ForEach( m_connectionPool, &ConnectionBase::getStatistics, totalIn, totalOut );
00359 }
00360
00361 void ConnectionBOSH::handleReceivedData( const ConnectionBase* ,
00362 const std::string& data )
00363 {
00364 m_buffer += data;
00365
00366 std::string::size_type headerLength = 0;
00367 while( ( headerLength = m_buffer.find( "\r\n\r\n" ) ) != std::string::npos )
00368 {
00369 m_bufferHeader = m_buffer.substr( 0, headerLength + 2 );
00370
00371 const std::string& statusCode = m_bufferHeader.substr( 9, 3 );
00372 if( statusCode != "200" )
00373 {
00374 m_logInstance.warn( LogAreaClassConnectionBOSH,
00375 "Received error via legacy HTTP status code: " + statusCode
00376 + ". Disconnecting." );
00377 m_state = StateDisconnected;
00378 disconnect();
00379 }
00380
00381 m_bufferContentLength = strtol( getHTTPField( "Content-Length" ).c_str(), 0, 10 );
00382 if( !m_bufferContentLength )
00383 return;
00384
00385 if( m_connMode != ModeLegacyHTTP && ( getHTTPField( "Connection" ) == "close"
00386 || m_bufferHeader.substr( 0, 8 ) == "HTTP/1.0" ) )
00387 {
00388 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00389 "Server indicated lack of support for HTTP/1.1 - falling back to HTTP/1.0" );
00390 m_connMode = ModeLegacyHTTP;
00391 }
00392
00393 if( m_buffer.length() >= ( headerLength + 4 + m_bufferContentLength ) )
00394 {
00395 putConnection();
00396 --m_openRequests;
00397 std::string xml = m_buffer.substr( headerLength + 4, m_bufferContentLength );
00398 m_parser.feed( xml );
00399 m_buffer.erase( 0, headerLength + 4 + m_bufferContentLength );
00400 m_bufferContentLength = 0;
00401 m_bufferHeader = EmptyString;
00402 }
00403 else
00404 {
00405 m_logInstance.warn( LogAreaClassConnectionBOSH, "buffer length mismatch" );
00406 break;
00407 }
00408 }
00409 }
00410
00411 void ConnectionBOSH::handleConnect( const ConnectionBase* )
00412 {
00413 if( m_state == StateConnecting )
00414 {
00415 m_rid = rand() % 100000 + 1728679472;
00416
00417 Tag requestBody( "body" );
00418 requestBody.setXmlns( XMLNS_HTTPBIND );
00419 requestBody.setXmlns( XMLNS_XMPP_BOSH, "xmpp" );
00420
00421 requestBody.addAttribute( "content", "text/xml; charset=utf-8" );
00422 requestBody.addAttribute( "hold", (long)m_hold );
00423 requestBody.addAttribute( "rid", (long)m_rid );
00424 requestBody.addAttribute( "ver", "1.6" );
00425 requestBody.addAttribute( "wait", (long)m_wait );
00426 requestBody.addAttribute( "ack", 0 );
00427 requestBody.addAttribute( "secure", "false" );
00428 requestBody.addAttribute( "route", "xmpp:" + m_server + ":5222" );
00429 requestBody.addAttribute( "xml:lang", "en" );
00430 requestBody.addAttribute( "xmpp:version", "1.0" );
00431 requestBody.addAttribute( "to", m_server );
00432
00433 m_logInstance.dbg( LogAreaClassConnectionBOSH, "sending bosh connection request" );
00434 sendRequest( requestBody.xml() );
00435 }
00436 }
00437
00438 void ConnectionBOSH::handleDisconnect( const ConnectionBase* ,
00439 ConnectionError reason )
00440 {
00441 if( m_handler && m_state == StateConnecting )
00442 {
00443 m_state = StateDisconnected;
00444 m_handler->handleDisconnect( this, reason );
00445 return;
00446 }
00447
00448 switch( m_connMode )
00449 {
00450 case ModePipelining:
00451 m_connMode = ModeLegacyHTTP;
00452 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00453 "connection closed - falling back to HTTP/1.0 connection method" );
00454 break;
00455 case ModeLegacyHTTP:
00456 case ModePersistentHTTP:
00457
00458
00459 break;
00460 }
00461 }
00462
00463 void ConnectionBOSH::handleTag( Tag* tag )
00464 {
00465 if( !m_handler || tag->name() != "body" )
00466 return;
00467
00468 if( m_streamRestart )
00469 {
00470 m_streamRestart = false;
00471 m_logInstance.dbg( LogAreaClassConnectionBOSH, "sending spoofed <stream:stream>" );
00472 m_handler->handleReceivedData( this, "<?xml version='1.0' ?>"
00473 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams'"
00474 " xmlns='" + XMLNS_CLIENT + "' version='" + XMPP_STREAM_VERSION_MAJOR
00475 + "." + XMPP_STREAM_VERSION_MINOR + "' from='" + m_server + "' id ='"
00476 + m_sid + "' xml:lang='en'>" );
00477 }
00478
00479 if( tag->hasAttribute( "sid" ) )
00480 {
00481 m_state = StateConnected;
00482 m_sid = tag->findAttribute( "sid" );
00483
00484 if( tag->hasAttribute( "requests" ) )
00485 {
00486 const int serverRequests = atoi( tag->findAttribute( "requests" ).c_str() );
00487 if( serverRequests < m_maxOpenRequests )
00488 {
00489 m_maxOpenRequests = serverRequests;
00490 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00491 "bosh parameter 'requests' now set to " + tag->findAttribute( "requests" ) );
00492 }
00493 }
00494 if( tag->hasAttribute( "hold" ) )
00495 {
00496 const int maxHold = atoi( tag->findAttribute( "hold" ).c_str() );
00497 if( maxHold < m_hold )
00498 {
00499 m_hold = maxHold;
00500 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00501 "bosh parameter 'hold' now set to " + tag->findAttribute( "hold" ) );
00502 }
00503 }
00504 if( tag->hasAttribute( "wait" ) )
00505 {
00506 const int maxWait = atoi( tag->findAttribute( "wait" ).c_str() );
00507 if( maxWait < m_wait )
00508 {
00509 m_wait = maxWait;
00510 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00511 "bosh parameter 'wait' now set to " + tag->findAttribute( "wait" )
00512 + " seconds" );
00513 }
00514 }
00515 if( tag->hasAttribute( "polling" ) )
00516 {
00517 const int minTime = atoi( tag->findAttribute( "polling" ).c_str() );
00518 m_minTimePerRequest = minTime;
00519 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00520 "bosh parameter 'polling' now set to " + tag->findAttribute( "polling" )
00521 + " seconds" );
00522 }
00523
00524 if( m_state < StateConnected )
00525 m_handler->handleConnect( this );
00526
00527 m_handler->handleReceivedData( this, "<?xml version='1.0' ?>"
00528
00529
00530 "<stream:stream xmlns:stream='http://etherx.jabber.org/streams' "
00531 "xmlns='" + XMLNS_CLIENT
00532 + "' version='" + XMPP_STREAM_VERSION_MAJOR + "." + XMPP_STREAM_VERSION_MINOR
00533 + "' from='" + m_server + "' id ='" + m_sid + "' xml:lang='en'>" );
00534 }
00535
00536 if( tag->findAttribute( "type" ) == "terminate" )
00537 {
00538 m_logInstance.dbg( LogAreaClassConnectionBOSH,
00539 "bosh connection closed by server: " + tag->findAttribute( "condition" ) );
00540 m_state = StateDisconnected;
00541 m_handler->handleDisconnect( this, ConnStreamClosed );
00542 return;
00543 }
00544
00545 const TagList& stanzas = tag->children();
00546 TagList::const_iterator it = stanzas.begin();
00547 for( ; it != stanzas.end(); ++it )
00548 m_handler->handleReceivedData( this, (*it)->xml() );
00549 }
00550
00551 ConnectionBase* ConnectionBOSH::getConnection()
00552 {
00553 if( m_openRequests > 0 && m_openRequests >= m_maxOpenRequests )
00554 {
00555 m_logInstance.warn( LogAreaClassConnectionBOSH,
00556 "Too many requests already open. Cannot send." );
00557 return 0;
00558 }
00559
00560 ConnectionBase* conn = 0;
00561 switch( m_connMode )
00562 {
00563 case ModePipelining:
00564 if( !m_activeConnections.empty() )
00565 {
00566 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Using default connection for Pipelining." );
00567 return m_activeConnections.front();
00568 }
00569 else if( !m_connectionPool.empty() )
00570 {
00571 m_logInstance.warn( LogAreaClassConnectionBOSH,
00572 "Pipelining selected, but no connection open. Opening one." );
00573 return activateConnection();
00574 }
00575 else
00576 m_logInstance.warn( LogAreaClassConnectionBOSH,
00577 "No available connections to pipeline on." );
00578 break;
00579 case ModeLegacyHTTP:
00580 case ModePersistentHTTP:
00581 {
00582 if( !m_connectionPool.empty() )
00583 {
00584 m_logInstance.dbg( LogAreaClassConnectionBOSH, "LegacyHTTP/PersistentHTTP selected, "
00585 "using connection from pool." );
00586 return activateConnection();
00587 }
00588 else if( !m_activeConnections.empty() )
00589 {
00590 m_logInstance.dbg( LogAreaClassConnectionBOSH, "No connections in pool, creating a new one." );
00591 conn = m_activeConnections.front()->newInstance();
00592 conn->registerConnectionDataHandler( this );
00593 m_connectionPool.push_back( conn );
00594 conn->connect();
00595 }
00596 else
00597 m_logInstance.warn( LogAreaClassConnectionBOSH,
00598 "No available connections to send on." );
00599 break;
00600 }
00601 }
00602 return 0;
00603 }
00604
00605 ConnectionBase* ConnectionBOSH::activateConnection()
00606 {
00607 ConnectionBase* conn = m_connectionPool.front();
00608 m_connectionPool.pop_front();
00609 if( conn->state() == StateConnected )
00610 {
00611 m_activeConnections.push_back( conn );
00612 return conn;
00613 }
00614
00615 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Connecting pooled connection." );
00616 m_connectionPool.push_back( conn );
00617 conn->connect();
00618 return 0;
00619 }
00620
00621 void ConnectionBOSH::putConnection()
00622 {
00623 ConnectionBase* conn = m_activeConnections.front();
00624
00625 switch( m_connMode )
00626 {
00627 case ModeLegacyHTTP:
00628 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Disconnecting LegacyHTTP connection" );
00629 conn->disconnect();
00630 conn->cleanup();
00631 m_activeConnections.pop_front();
00632 m_connectionPool.push_back( conn );
00633 break;
00634 case ModePersistentHTTP:
00635 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Deactivating PersistentHTTP connection" );
00636 m_activeConnections.pop_front();
00637 m_connectionPool.push_back( conn );
00638 break;
00639 case ModePipelining:
00640 m_logInstance.dbg( LogAreaClassConnectionBOSH, "Keeping Pipelining connection" );
00641 default:
00642 break;
00643 }
00644 }
00645
00646 }