/***************************************************************************
                          msnsockethttp.cpp  -  description
                             -------------------
    begin                : Thu Apr 4 2008
    copyright            : (C) 2008 by Valerio Pilo
    email                : valerio@kmess.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "msnsockethttp.h"

#include <config-kmess.h>
#include "../kmessapplication.h"
#include "../kmessdebug.h"

#include <QNetworkReply>
#include <QHostAddress>
#include <QSslError>

#include <KLocale>
#include <KMessageBox>


#ifdef KMESSDEBUG_CONNECTION // Area-specific debug statements
  #define KMESSDEBUG_CONNECTION_SOCKET_HTTP
  #define KMESSDEBUG_CONNECTION_SOCKET_HTTP_DUMPS
#endif


/**
 *  Default Gateway address/port
 *
 * This define holds the default address which KMess uses to send HTTP
 * messages. The port is usually 80.
 */
#define DEFAULT_GATEWAY_ADDRESS "gateway.messenger.hotmail.com"
#define DEFAULT_GATEWAY_PORT    80



/**
 * @brief The constructor
 *
 * Initializes the HTTP interface, prepares the default HTTP header we'll use
 * throughout the connection, and sets up the request/ping timer.
 */
MsnSocketHttp::MsnSocketHttp( ServerType serverType )
: MsnSocketBase( serverType )
, canSendRequests_( true )
, sendPings_( false )
{
  setObjectName( "MsnSocketHttp" );

  // Create the http interface
  http_ = new QNetworkAccessManager( this );

  // Connect the socket's signals for parsing
  connect( http_,          SIGNAL(          finished(QNetworkReply*) ),
           this,           SLOT  ( slotReplyReceived(QNetworkReply*) ) );
  connect( http_,          SIGNAL( proxyAuthenticationRequired(const QNetworkProxy&,QAuthenticator*) ),
           this,           SLOT  (           proxyAuthenticate(const QNetworkProxy&,QAuthenticator*) ) );
  // Connect the server request timer
  connect( &pollingTimer_, SIGNAL(           timeout()               ),
           this,           SLOT  (   slotQueryServer()               ) );

  // We need the timer to only fire once to trigger a single update;
  // the next one will be determined dynamically
  pollingTimer_.setSingleShot( true );

  // Set the default request values
  baseServerPath_ = QString  ( "/gateway/gateway.dll?"                                       );
  requestHeader_.setHeader   ( QNetworkRequest::ContentTypeHeader, "text/xml; charset=utf-8" );
  requestHeader_.setRawHeader( "Accept",                           "*" "/" "*"               );
  requestHeader_.setRawHeader( "User-Agent",                       "KMess/" KMESS_VERSION    );
  requestHeader_.setRawHeader( "Connection",                       "Keep-Alive"              );
  requestHeader_.setRawHeader( "Cache-Control",                    "no-cache"                );

#ifdef KMESSTEST
  KMESS_ASSERT( http_ != 0 );
#endif
}



/**
 * @brief The destructor
 *
 * Closes the connection, cleans up request queues, and deletes the interface.
 */
MsnSocketHttp::~MsnSocketHttp()
{
  // Disconnect from the server on exit
  if( connected_ )
  {
    disconnectFromServer();
  }

  // Stop the polling timer
  pollingTimer_.stop();

  // Clean up and delete the http interface
  cleanUp();
  delete http_;

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
  kmDebug() << "DESTROYED";
#endif
}



/**
 * @brief Clean up the connection
 *
 * This internal method empties all queues and removes any pending connection -
 * including any in-progress ones.
 */
void MsnSocketHttp::cleanUp()
{
  // Clear the request queues
  queuedRequests_    .clear();
}



/**
 * @brief Connect to the given server
 *
 * The initial connection should always be made through the main Gateway.
 * The first server response usually carries the new gateway IP
 * which will be used for the rest of the connection.
 *
 * @param  server  The server hostname or IP address.
 * @param  port    The port to connect to. Not used - all of our
 *                 traffic goes through the default port.
 */
void MsnSocketHttp::connectToServer( const QString& server, const quint16 port )
{
  // We use our default HTTP port only
  Q_UNUSED( port );

  // When no server is specified, use the default msn one
  QString desiredAddress;
  if( server.isEmpty() )
  {
    desiredAddress = DEFAULT_GATEWAY_ADDRESS;
  }
  else
  {
    desiredAddress = server;
  }

#ifdef KMESSTEST
  // Redirect to a local socket if started with --usetestserver
  if( static_cast<KMessApplication*>( kapp )->getUseTestServer() )
  {
    desiredAddress = static_cast<KMessApplication*>( kapp )->getTestServer() + ":8008";
  }
#endif

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
  kmDebug() << "Connecting to server at " << desiredAddress << ":" << DEFAULT_GATEWAY_PORT << ".";
#endif

  // Set up the gateway where to send our messages
  setGateway( desiredAddress );

  // Translate the server type to a 2-letter string to send in our requests
  QString serverType;
  switch( serverType_ )
  {
    case SERVER_NOTIFICATION: serverType = "NS"; break;
    case SERVER_SWITCHBOARD:  serverType = "SB"; break;
    default:
      kmWarning() << "Unknown type of server" << serverType_ << "!";
      return;
  }

  // Set the parameters for the next request
  nextRequestParameters_ = "Action=open&Server=" + serverType + "&IP=" + desiredAddress;

  // Flush any active or pending request if the interface is closing a previous connection
  cleanUp();

  // Set our internal state variables
  connected_       = true;
  canSendRequests_ = true;

  // Signal we're ready
  emit connected();
  emit statusMessage( i18n( "Connected" ), false );
}



/**
 * @brief Disconnect from the server
 *
 * This method closes the connection to the server and does some cleaning up.
 * It empties all buffers, and resets the internal state.
 * To avoid losing data, all pending requests are merged into a single huge
 * request and sent altogether, before closing the connection. We won't
 * receive responses for them; but at least we lose no outgoing data.
 *
 * @param  isTransfer  When set, a disconnected() signal won't be fired.
 *                     This is used to handle transfers to another server
 *                     without noticing a disconnect.
 */
void MsnSocketHttp::disconnectFromServer( bool isTransfer )
{
  // Stop pinging
  setSendPings( false );
  pollingTimer_.stop();

  // Just clean up if we are disconnected already
  if( ! connected_ )
  {
    return;
  }

  connected_ = false;

  // Make sure all remaining requests get sent, so that we don't lose messages.
  // Even if we're disconnecting because of an error, send them anyway: we'll ignore the
  // responses anyway.
  if( ! queuedRequests_.isEmpty() )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    kmDebug() << "Sending" << queuedRequests_.count() << "remaining requests before closing.";
#endif

    QNetworkRequest request( requestHeader_ );
    QByteArray data;

    // Get the oldest message
    RequestInfo info = queuedRequests_.takeFirst();
    data             = info.data;

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    int mergedRequests = 0;
#endif
    // Merge together all the queued data to save bandwidth
    while( ! queuedRequests_.isEmpty() )
    {
      const RequestInfo &queuedRequest = queuedRequests_.first();

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
      mergedRequests++;
#endif

      // Merge the data
      data.append( queuedRequest.data );

      // Remove the request from the queue, since it is going to be sent now
      queuedRequests_.takeFirst();
    }
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    kmDebug() << "Merged" << mergedRequests << "requests out from the queue.";
#endif

    // Set up the header
    request.setHeader( QNetworkRequest::ContentLengthHeader, data.size() );
    request.setUrl( QUrl( gatewayAddress_ + baseServerPath_ + "SessionID=" + nextRequestId_ ) );

    // There should be no content-type header if there is no data
    if( data.size() == 0 )
    {
      request.setHeader( QNetworkRequest::ContentTypeHeader, QVariant() );
    }

    // Send it
    http_->post( request, data );
/*
    // Wait until this request has been sent, using a local loop.
    QEventLoop loop( this );

    // Block the timer signals before starting, we're just using it to timeout the request
    pollingTimer_.blockSignals( true );
    pollingTimer_.start( 1000 );

    while( http_->currentId() != 0 && pollingTimer_.isActive() )
    {
      loop.processEvents();
    }

    // Enable back its signals now
    pollingTimer_.blockSignals( false );

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    if( http_->currentId() != 0 && ! pollingTimer_.isActive() )
    {
      kmDebug() << "Could not send the last request within timeout.";
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP_DUMPS
      kmDebug() << "Full dump:" << endl
              << header.toString()
              << QString::fromUtf8( data );
#endif
    }
#endif
*/
  }

  // Clear the queues, so pending messages will be ignored
  cleanUp();

  // Do not signal a disconnection when we are transferring to another gateway
  if( isTransfer )
  {
    return;
  }

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
  kmDebug() << "Emitting disconnection signal.";
#endif

  emit disconnected();
}



// Dump the content of requests and replies
void MsnSocketHttp::dump( void *obj, QByteArray data, bool isRequest )
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP_DUMPS
  if( isRequest )
  {
    QNetworkRequest *request = (QNetworkRequest *)obj;
    kmDebug() << "-- Request dump below --" << endl
             << "URL:" << request->url() << endl
             << "Contents:" << QString::fromUtf8( data );

    int i = 1;
    kmDebug() << request->rawHeaderList().size() << "header items present:";
    foreach( const QByteArray &headerName, request->rawHeaderList() )
    {
      kmDebug() << "(" << i++ << ")" << headerName << ": " << request->rawHeader( headerName );
    }
    kmDebug() << "-- Request dump above --";
  }
  else
  {
    QNetworkReply *reply = (QNetworkReply *)obj;
    kmDebug() << "-- Reply dump below --" << endl
             << "URL:" << reply->url() << endl
             << "Contents:" << QString::fromUtf8( data );

    int i = 1;
    kmDebug() << reply->rawHeaderList().size() << "header items present:";
    foreach( const QByteArray &headerName, reply->rawHeaderList() )
    {
      kmDebug() << "(" << i++ << ")" << headerName << ": " << reply->rawHeader( headerName );
    }
    kmDebug() << "-- Reply dump above --";
  }
#else
  Q_UNUSED( obj );
  Q_UNUSED( data );
  Q_UNUSED( isRequest );
#endif
}



/**
 * @brief Return the local IP address
 *
 * Using HTTP, we don't really care about our address. Not just that,
 * we're probably behind a firewall, so finding out our real IP wouldn't
 * help either.
 * This method just returns "127.0.0.1".
 *
 * @returns The IP address the socket uses at the system.
 */
QString MsnSocketHttp::getLocalIp() const
{
  // We don't care about our own IP, return a generic one
  return QHostAddress( QHostAddress::LocalHost ).toString();
}



/**
 * @brief Sends the next queued request
 *
 * Data to send is stored in a queue of requests. This method just takes the
 * oldest message and sends it.
 */
void MsnSocketHttp::sendNextRequest()
{
  // Verify if we can send another message, if there is any
  if( ! connected_ || ! canSendRequests_ )
  {
    return;
  }

  QDateTime now( QDateTime::currentDateTime() );

  // Do not send messages too fast, or the server will ignore them
  qint64 elapsedMsecs;
#if QT_VERSION >= 0x040700
  elapsedMsecs = lastRequestTime_.msecsTo( now );
#else
  // Workaround: adapted code of QDateTime::msecsTo() since it's only in Qt 4.7+
  // Snippet licensed LGPL 2.1, copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
  elapsedMsecs = ( static_cast<qint64>( lastRequestTime_.date().daysTo( now.date() ) )
                 * static_cast<qint64>( 86400000/*MSECS_PER_DAY*/ ) )
                 + static_cast<qint64>( lastRequestTime_.time().msecsTo( now.time() ) );
#endif

  // Fix clock skews
  if( elapsedMsecs < 0 )
  {
    lastRequestTime_ = now;
    elapsedMsecs = 0;
  }

  if( elapsedMsecs <= 1000 )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    kmDebug() << "Not sending another message in" << elapsedMsecs << "more milliseconds.";
#endif

    // The polling method will call this method back
    pollingTimer_.start( 1000 - elapsedMsecs );
    return;
  }

  // Lock the sending process: only one message at most can be 'on the wire' in any given moment
  canSendRequests_ = false;

  bool sendingPollingRequest = false;
  QNetworkRequest request( requestHeader_ );
  QByteArray data;
  QString url( gatewayAddress_ + baseServerPath_ );

  // When the queue is empty, send polling requests
  if( queuedRequests_.isEmpty() )
  {
    sendingPollingRequest = true;
    url += "Action=poll&SessionID=" + nextRequestId_;
    request.setHeader( QNetworkRequest::ContentLengthHeader, 0 );
    request.setHeader( QNetworkRequest::ContentTypeHeader, QVariant() ); // There should be no content-type header.
  }
  else
  {
    // Get the oldest message
    RequestInfo info = queuedRequests_.takeFirst();
    data             = info.data;

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    int mergedRequests = 0;
#endif
    // Merge together all the queued data to save bandwidth
    while( ! queuedRequests_.isEmpty() )
    {
      const RequestInfo &queuedRequest = queuedRequests_.first();

      // Stop merging requests if we get a special one
      if( ! queuedRequest.requestQueryString.isEmpty() )
      {
        break;
      }

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
      mergedRequests++;
#endif

      // Merge the data
      data.append( queuedRequest.data );

      // Remove the request from the queue, since it is going to be sent now
      queuedRequests_.takeFirst();
    }
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    kmDebug() << "Merged" << mergedRequests << "requests out from the queue.";
#endif

    // Set up the header
    request.setHeader( QNetworkRequest::ContentLengthHeader, data.size() );

    if( info.requestQueryString.isEmpty() )
    {
      url += "SessionID=" + nextRequestId_;
    }
    else
    {
      url += info.requestQueryString;
    }


    // There should be no content-type header if there is no data
    if( data.size() == 0 )
    {
      request.setHeader( QNetworkRequest::ContentTypeHeader, QVariant() );
    }
  }

  // Send it
  request.setUrl( QUrl( url ) );
  http_->post( request, data );

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
  kmDebug() << "Sent a request. Queue size now:" << queuedRequests_.size();
#endif
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP_DUMPS
  dump( &request, data, true );
#endif


  // Set the last request type
  lastRequestWasPolling_ = sendingPollingRequest;

  // Stop polling, the response to this message will carry new messages along
  pollingTimer_.stop();
  lastRequestTime_ = now;
}



/**
 * @brief Change the server connection gateway
 *
 * Set the address of the gateway, from this point on, all queued data will
 * be sent to the new gateway.
 *
 * @param  host  The new gateway hostname or IP address.
 */
void MsnSocketHttp::setGateway( QString host )
{
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
  kmDebug() << "Using server at" << host << ":" << DEFAULT_GATEWAY_PORT << "as gateway.";
#endif

  // Save the server address to use in our requests
  requestHeader_.setRawHeader( QByteArray( "Host" ), host.toUtf8() );

  gatewayAddress_ = "http://" + host;
}



/**
 * @brief Set whether to send pings or not
 *
 * When sending pings, the application receives a periodic signal
 * which is used to perform timed updates (like receiving display pics).
 *
 * @param  sendPings  True to send pings.
 */
void MsnSocketHttp::setSendPings( bool sendPings )
{
  sendPings_ = sendPings;
}



/**
 * @brief Send a polling message to the server, to retrieve pending commands
 *
 * This method gets called every two seconds: each time it checks if there still are
 * messages queued to be sent, and if not, it polls the gateway for new messages.
 */
void MsnSocketHttp::slotQueryServer()
{
  QDateTime now( QDateTime::currentDateTime() );

  // Emit a "ping" signal every about 30 seconds, to let the app perform scheduled tasks
  if( sendPings_ && ( ! lastPing_.isValid() || lastPing_.secsTo( now ) > 30 ) )
  {
    lastPing_ = now;
    emit pingSent();
  }

  // Don't send polling messages when: 1) we are disconnected; 2) a message is still being
  // responded at (we'll receive new messages along with the response); 3) there still are
  // messages to send (same reason as point 2).
  if( ! connected_ || ! canSendRequests_ )
  {
    return;
  }

  // If the queue isn't empty send the next message
  if( ! queuedRequests_.isEmpty() )
  {
    sendNextRequest();
    return;
  }

  // The last response had data; maybe there's more so keep checking
//   if( ! lastResponseWasEmpty_ )
//   {
    sendNextRequest();
//   }
//   else
  if( lastResponseWasEmpty_ )
  {
    // Send a poll five seconds after the last command and ten after the last polling request
    pollingTimer_.start( lastRequestWasPolling_ ? 10000 : 5000 );
  }
}



/**
 * @brief Read received data and pass along the contained commands
 *
 * This method parses the incoming raw data for the syntax used by the MSN protocol.
 * Data arrives here only when it's been received completely. This may make it appear
 * slower than usual TCP.
 * Incoming messages contain all new messages from the server, so we need to bounce
 * them back individually to MsnConnection.
 *
 * Example response message:
 * @code
HTTP/1.0 200 OK
Content-Length: 0
Content-Type: application/x-msn-messenger
X-MSN-Messenger: SessionID=1848331483.1171767614; GW-IP=207.46.108.49
X-MSN-Host: BY1MSG4176117.gateway.edge.messenger.live.com
X-MSNSERVER: BY1MSG4176117
Date: Thu, 14 Aug 2008 10:12:14 GMT
X-Cache: MISS from fw.akademy.lan
X-Cache-Lookup: MISS from fw.akademy.lan:3128
Via: 1.0 fw.akademy.lan:3128 (squid/2.6.STABLE18)
Connection: keep-alive

@endcode
 *
 * @param  reply  The response contents
 */
void MsnSocketHttp::slotReplyReceived( QNetworkReply *reply )
{
  if( ! connected_ )
  {
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
    kmDebug() << "Disconnected, ignoring reply.";
#endif
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP_DUMPS
    dump( reply, reply->readAll(), false );
#endif

    // We're supposed to clean up
    reply->abort();
    reply->deleteLater();

    return;
  }

  // Get the message header and contents
  QNetworkRequest request      = reply->request();
  QByteArray      responseData = reply->readAll();
  bool            hasMsnHeader = reply->hasRawHeader( "X-MSN-Messenger" );

  // Catch errors. Also, catch responses without the "X-MSN-Messenger" header:
  // responses without it aren't coming from an MSN server, so maybe there is
  // some HTTP redirection happening, like an authentication web page (e.g. web-based login proxy)
  if( reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() != 200 || ! hasMsnHeader )
  {
    kmWarning() << "Received a type" << reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt()
               << "response.";
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP_DUMPS
    dump( reply, responseData, false );
#endif

    // Report the right type of error
    QString   errorMsg( reply->errorString() );
    ErrorType errorType;
    switch( reply->error() )
    {
      case QNetworkReply::NoError:
        // Invalid MSN responses (web authentication redirects often replace the
        // original response contents). Treat like a connection error.
      case QNetworkReply::HostNotFoundError:
      case QNetworkReply::ConnectionRefusedError:
      case QNetworkReply::TimeoutError:
        errorType = ERROR_CONNECTING;
        break;

      case QNetworkReply::RemoteHostClosedError:
      case QNetworkReply::UnknownNetworkError:
        errorType = ERROR_DROP;
        break;

      case QNetworkReply::UnknownContentError:
        errorType = ERROR_DATA;
        break;

      case QNetworkReply::ContentAccessDenied:
      case QNetworkReply::ContentNotFoundError:
      case QNetworkReply::ContentOperationNotPermittedError:
      case QNetworkReply::ProtocolUnknownError:
      case QNetworkReply::ProtocolInvalidOperationError:
      case QNetworkReply::ProtocolFailure:
        errorType = ERROR_INTERNAL;
        break;

      default:
        errorType = ERROR_UNKNOWN;
        errorMsg = i18nc( "Error message shown with HTTP connection",
                          "%1 (Internal error code: %2)", errorMsg, reply->error() );
        break;
    }

    // We may be on a network where you need to auth via a webpage
    if( errorType == ERROR_CONNECTING
    && ( ! hasMsnHeader || reply->attribute( QNetworkRequest::RedirectionTargetAttribute ) != QVariant().toUrl() ) )
    {
      errorType = ERROR_CONNECTING_GATEWAY;
      errorMsg = i18nc( "Error message shown with HTTP connection",
                        "%1 (Internal error code: %2)<br/>"
                        "<i>Response:</i> %3 %4<br/>"
                        "<i>Redirection target:</i> %5",
                        errorMsg,
                        reply->error(),
                        reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toString(),
                        reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString(),
                        reply->attribute( QNetworkRequest::RedirectionTargetAttribute ).toString()
                      );
    }

    // Ignore errors when disconnected: avoids duplicate error dialogs when two
    // following requests are answered with an error.
    if( connected_ )
    {
      emit error( errorMsg, errorType );
    }

    // We're supposed to clean up
    reply->abort();
    reply->deleteLater();

    // Allow the connection to continue working: if the error is fatal, the
    // connection will be disconnected so no more messages will be sent
    canSendRequests_ = true;
    sendNextRequest();
    return;
  }

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP_DUMPS
  dump( reply, responseData, false );
#endif

  // Extract the MSN informative header from the response
  QList<QByteArray> msnInfo = reply->rawHeader( "X-MSN-Messenger" ).split( ';' );

  // Extract the MSN session information from the headers
  foreach( QString item, msnInfo )
  {
    item = item.trimmed();
    int keyEnd   = item.indexOf( '=' );
    QString key  ( item.left( keyEnd ) );
    QString value( item.mid ( keyEnd + 1 ) );

    if( key == "SessionID" )
    {
      // Every message has a different session ID, which needs to be sent along
      // with the next message.
      nextRequestId_ = value;
    }
    else if( key == "GW-IP" )
    {
      // Change the gateway, if we didn't do it already
      if( ! gatewayAddress_.contains( value ) )
      {
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
        kmDebug() << "Switching gateways:" << gatewayAddress_ << "->" << value;
#endif
        setGateway( value );
      }
    }
    else if( key == "Session" )
    {
      // Session close request. We've been kicked or the connection was closed by us first.
      // Do nothing if we are disconnected already.
      // TODO Behavior when receiving this command needs to be tested.
      if( value == "close" && connected_ )
      {
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
        kmDebug() << "Received disconnection command";
#endif

        cleanUp();
        disconnectFromServer();
      }
    }
    else
    {
#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
      kmDebug() << "Unknown attribute received in the MSN Header:" << item;
#endif
    }
  }

  lastResponseWasEmpty_ = true;

  // Extract all new messages from the response data
  if( ! responseData.isEmpty() )
  {
    int         startingPos = 0;
    QStringList command;
    bool        hasPayload = false;
    QString     commandLine;
    int         commandLineSize;
    QByteArray  payloadData;

    // Loop until the entire response has been parsed
    while( true )
    {
      if( startingPos >= responseData.size() )
      {
        break;
      }

      // Retrieve the first available command from the response body
      commandLineSize = responseData.indexOf( "\r\n", startingPos ) - startingPos + 2; // Add the newline characters to the command size
      commandLine     = QString::fromUtf8( responseData.mid( startingPos, commandLineSize ) );
      command         = commandLine.trimmed().split(" ");

      // Stop if there's nothing more to parse (unlikely, most times the first
      // 'if' statement above ends the loop)
      if( command[0].isEmpty() )
      {
        break;
      }

      // See if it's a payload command, and extract the payload from the response data

      // See if it's a payload command or an error
      if( isErrorCommand( command[0] ) )
      {
        // If the command contains a payload length parameter, also parse it
        hasPayload = ( command.size() > 2 );
      }
      else
      {
        hasPayload = isPayloadCommand( command[0] );
      }

      if( hasPayload )
      {
        int payloadSize = command[ command.count() - 1 ].toInt();  // Last arg is payload length.
        payloadData = responseData.mid( startingPos + commandLineSize, payloadSize );

        startingPos += commandLineSize + payloadSize;
      }
      else // Not a payload message, just go on from right after the command
      {
        payloadData.clear();

        startingPos += commandLineSize;
      }

      // We've received a message, hand it over to MsnConnection for interpretation
      emit dataReceived( command, payloadData );
      lastResponseWasEmpty_ = false;
    }
  }

  // Allow sending new messages, now that this has been done
  canSendRequests_ = true;

  // We're supposed to clean up
  reply->abort();
  reply->deleteLater();

  // Keep querying for more
  slotQueryServer();
}



/**
 * @brief Write data to the gateway without any conversion
 *
 * This method creates a new request and queues it to be sent when possible.
 * See sendNextRequest() for more info on sending.
 *
 * @param  data  Contents of the message which will be sent
 * @return -1 on error, or else always the exact size of the sent data.
 */
qint64 MsnSocketHttp::writeBinaryData( const QByteArray &data )
{
  if( ! connected_ )
  {
    kmWarning() << "Attempted to write data to a disconnected interface, aborting.";
    return -1;
  }

  // Create the new request with the data
  RequestInfo newRequest;
  newRequest.data = data;

  // Special query strings override the default one
  if( ! nextRequestParameters_.isEmpty() )
  {
    newRequest.requestQueryString = nextRequestParameters_;
    nextRequestParameters_ = QString();
  }
  else
  {
    newRequest.requestQueryString = QString();
  }

  // Queue the new request
  queuedRequests_.append( newRequest );

#ifdef KMESSDEBUG_CONNECTION_SOCKET_HTTP
  kmDebug() << "Queued a new request";
#endif

  // Try to immediately send the new message, if possible
  sendNextRequest();

  return data.size();
}



/**
 * @brief Write a string to the gateway
 *
 * This is a convenience method for writeBinaryData, which can be used to directly send a QString.
 *
 * @param  data  The message which will be sent
 * @return -1 on error, or else always the exact size of the sent data.
 */
qint64 MsnSocketHttp::writeData( const QString &data )
{
  return writeBinaryData( data.toUtf8() );
}



#include "msnsockethttp.moc"
