text: /*
   * libjingle
   * Copyright 2004--2005, Google Inc.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   *
   *  1. Redistributions of source code must retain the above copyright notice,
   *     this list of conditions and the following disclaimer.
   *  2. Redistributions in binary form must reproduce the above copyright notice,
   *     this list of conditions and the following disclaimer in the documentation
   *     and/or other materials provided with the distribution.
   *  3. The name of the author may not be used to endorse or promote products
   *     derived from this software without specific prior written permission.
   *
   * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
   * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
   * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
   * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
   * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   */
  
  #include "talk/xmpp/xmppengineimpl.h"
  
  #include <algorithm>
  #include <sstream>
  #include <vector>
  
  #include "talk/base/common.h"
  #include "talk/xmllite/xmlelement.h"
  #include "talk/xmllite/xmlprinter.h"
  #include "talk/xmpp/constants.h"
  #include "talk/xmpp/saslhandler.h"
  #include "talk/xmpp/xmpplogintask.h"
  
  namespace buzz {
  
  XmppEngine* XmppEngine::Create() {
    return new XmppEngineImpl();
  }
  
  
  XmppEngineImpl::XmppEngineImpl()
      : stanza_parse_handler_(this),
        stanza_parser_(&stanza_parse_handler_),
        engine_entered_(0),
        password_(),
        requested_resource_(STR_EMPTY),
        tls_option_(buzz::TLS_REQUIRED),
        login_task_(new XmppLoginTask(this)),
        next_id_(0),
        state_(STATE_START),
        encrypted_(false),
        error_code_(ERROR_NONE),
        subcode_(0),
        stream_error_(),
        raised_reset_(false),
        output_handler_(NULL),
        session_handler_(NULL),
        iq_entries_(new IqEntryVector()),
        sasl_handler_(),
        output_(new std::stringstream()) {
    for (int i = 0; i < HL_COUNT; i+= 1) {
      stanza_handlers_[i].reset(new StanzaHandlerVector());
    }
  
    // Add XMPP namespaces to XML namespaces stack.
    xmlns_stack_.AddXmlns("stream", "http://etherx.jabber.org/streams");
    xmlns_stack_.AddXmlns("", "jabber:client");
  }
  
  XmppEngineImpl::~XmppEngineImpl() {
    DeleteIqCookies();
  }
  
  XmppReturnStatus XmppEngineImpl::SetOutputHandler(
      XmppOutputHandler* output_handler) {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
  
    output_handler_ = output_handler;
  
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::SetSessionHandler(
      XmppSessionHandler* session_handler) {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
  
    session_handler_ = session_handler;
  
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::HandleInput(
      const char* bytes, size_t len) {
    if (state_ < STATE_OPENING || state_ > STATE_OPEN)
      return XMPP_RETURN_BADSTATE;
  
    EnterExit ee(this);
  
    // TODO: The return value of the xml parser is not checked.
    stanza_parser_.Parse(bytes, len, false);
  
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::ConnectionClosed(int subcode) {
    if (state_ != STATE_CLOSED) {
      EnterExit ee(this);
      // If told that connection closed and not already closed,
      // then connection was unpexectedly dropped.
      if (subcode) {
        SignalError(ERROR_SOCKET, subcode);
      } else {
        SignalError(ERROR_CONNECTION_CLOSED, 0);  // no subcode
      }
    }
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::SetTls(TlsOptions use_tls) {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
    tls_option_ = use_tls;
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::SetTlsServer(
      const std::string& tls_server_hostname,
      const std::string& tls_server_domain) {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
  
    tls_server_hostname_ = tls_server_hostname;
    tls_server_domain_= tls_server_domain;
  
    return XMPP_RETURN_OK;
  }
  
  TlsOptions XmppEngineImpl::GetTls() {
    return tls_option_;
  }
  
  XmppReturnStatus XmppEngineImpl::SetUser(const Jid& jid) {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
  
    user_jid_ = jid;
  
    return XMPP_RETURN_OK;
  }
  
  const Jid& XmppEngineImpl::GetUser() {
    return user_jid_;
  }
  
  XmppReturnStatus XmppEngineImpl::SetSaslHandler(SaslHandler* sasl_handler) {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
  
    sasl_handler_.reset(sasl_handler);
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::SetRequestedResource(
      const std::string& resource) {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
  
    requested_resource_ = resource;
  
    return XMPP_RETURN_OK;
  }
  
  const std::string& XmppEngineImpl::GetRequestedResource() {
    return requested_resource_;
  }
  
  XmppReturnStatus XmppEngineImpl::AddStanzaHandler(
      XmppStanzaHandler* stanza_handler,
      XmppEngine::HandlerLevel level) {
    if (state_ == STATE_CLOSED)
      return XMPP_RETURN_BADSTATE;
  
    stanza_handlers_[level]->push_back(stanza_handler);
  
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::RemoveStanzaHandler(
      XmppStanzaHandler* stanza_handler) {
    bool found = false;
  
    for (int level = 0; level < HL_COUNT; level += 1) {
      StanzaHandlerVector::iterator new_end =
        std::remove(stanza_handlers_[level]->begin(),
        stanza_handlers_[level]->end(),
        stanza_handler);
  
      if (new_end != stanza_handlers_[level]->end()) {
        stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
        found = true;
      }
    }
  
    if (!found)
      return XMPP_RETURN_BADARGUMENT;
  
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::Connect() {
    if (state_ != STATE_START)
      return XMPP_RETURN_BADSTATE;
  
    EnterExit ee(this);
  
    // get the login task started
    state_ = STATE_OPENING;
    if (login_task_) {
      login_task_->IncomingStanza(NULL, false);
      if (login_task_->IsDone())
        login_task_.reset();
    }
  
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::SendStanza(const XmlElement* element) {
    if (state_ == STATE_CLOSED)
      return XMPP_RETURN_BADSTATE;
  
    EnterExit ee(this);
  
    if (login_task_) {
      // still handshaking - then outbound stanzas are queued
      login_task_->OutgoingStanza(element);
    } else {
      // handshake done - send straight through
      InternalSendStanza(element);
    }
  
    return XMPP_RETURN_OK;
  }
  
  XmppReturnStatus XmppEngineImpl::SendRaw(const std::string& text) {
    if (state_ == STATE_CLOSED || login_task_)
      return XMPP_RETURN_BADSTATE;
  
    EnterExit ee(this);
  
    (*output_) << text;
  
    return XMPP_RETURN_OK;
  }
  
  std::string XmppEngineImpl::NextId() {
    std::stringstream ss;
    ss << next_id_++;
    return ss.str();
  }
  
  XmppReturnStatus XmppEngineImpl::Disconnect() {
    if (state_ != STATE_CLOSED) {
      EnterExit ee(this);
      if (state_ == STATE_OPEN)
        *output_ << "</stream:stream>";
      state_ = STATE_CLOSED;
    }
  
    return XMPP_RETURN_OK;
  }
  
  void XmppEngineImpl::IncomingStart(const XmlElement* start) {
    if (HasError() || raised_reset_)
      return;
  
    if (login_task_) {
      // start-stream should go to login task
      login_task_->IncomingStanza(start, true);
      if (login_task_->IsDone())
        login_task_.reset();
    }
    else {
      // if not logging in, it's an error to see a start
      SignalError(ERROR_XML, 0);
    }
  }
  
  void XmppEngineImpl::IncomingStanza(const XmlElement* stanza) {
    if (HasError() || raised_reset_)
      return;
  
    if (stanza->Name() == QN_STREAM_ERROR) {
      // Explicit XMPP stream error
      SignalStreamError(stanza);
    } else if (login_task_) {
      // Handle login handshake
      login_task_->IncomingStanza(stanza, false);
      if (login_task_->IsDone())
        login_task_.reset();
    } else if (HandleIqResponse(stanza)) {
      // iq is handled by above call
    } else {
      // give every "peek" handler a shot at all stanzas
      for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
        (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
      }
  
      // give other handlers a shot in precedence order, stopping after handled
      for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
        for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
          if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
            return;
        }
      }
  
      // If nobody wants to handle a stanza then send back an error.
      // Only do this for IQ stanzas as messages should probably just be dropped
      // and presence stanzas should certainly be dropped.
      std::string type = stanza->Attr(QN_TYPE);
      if (stanza->Name() == QN_IQ &&
          !(type == "error" || type == "result")) {
        SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
      }
    }
  }
  
  void XmppEngineImpl::IncomingEnd(bool isError) {
    if (HasError() || raised_reset_)
      return;
  
    SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
  }
  
  void XmppEngineImpl::InternalSendStart(const std::string& to) {
    std::string hostname = tls_server_hostname_;
    if (hostname.empty())
      hostname = to;
  
    // If not language is specified, the spec says use *
    std::string lang = lang_;
    if (lang.length() == 0)
      lang = "*";
  
    // send stream-beginning
    // note, we put a \r\n at tne end fo the first line to cause non-XMPP
    // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
    *output_ << "<stream:stream to=\"" << hostname << "\" "
             << "xml:lang=\"" << lang << "\" "
             << "version=\"1.0\" "
             << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
             << "xmlns=\"jabber:client\">\r\n";
  }
  
  void XmppEngineImpl::InternalSendStanza(const XmlElement* element) {
    // It should really never be necessary to set a FROM attribute on a stanza.
    // It is implied by the bind on the stream and if you get it wrong
    // (by flipping from/to on a message?) the server will close the stream.
    ASSERT(!element->HasAttr(QN_FROM));
  
    XmlPrinter::PrintXml(output_.get(), element, &xmlns_stack_);
  }
  
  std::string XmppEngineImpl::ChooseBestSaslMechanism(
      const std::vector<std::string>& mechanisms, bool encrypted) {
    return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
  }
  
  SaslMechanism* XmppEngineImpl::GetSaslMechanism(const std::string& name) {
    return sasl_handler_->CreateSaslMechanism(name);
  }
  
  void XmppEngineImpl::SignalBound(const Jid& fullJid) {
    if (state_ == STATE_OPENING) {
      bound_jid_ = fullJid;
      state_ = STATE_OPEN;
    }
  }
  
  void XmppEngineImpl::SignalStreamError(const XmlElement* stream_error) {
    if (state_ != STATE_CLOSED) {
      stream_error_.reset(new XmlElement(*stream_error));
      SignalError(ERROR_STREAM, 0);
    }
  }
  
  void XmppEngineImpl::SignalError(Error error_code, int sub_code) {
    if (state_ != STATE_CLOSED) {
      error_code_ = error_code;
      subcode_ = sub_code;
      state_ = STATE_CLOSED;
    }
  }
  
  bool XmppEngineImpl::HasError() {
    return error_code_ != ERROR_NONE;
  }
  
  void XmppEngineImpl::StartTls(const std::string& domain) {
    if (output_handler_) {
      // As substitute for the real (login jid's) domain, we permit
      // verifying a tls_server_domain_ instead, if one was passed.
      // This allows us to avoid running a proxy that needs to handle
      // valuable certificates.
      output_handler_->StartTls(
        tls_server_domain_.empty() ? domain : tls_server_domain_);
      encrypted_ = true;
    }
  }
  
  XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
      : engine_(engine),
    state_(engine->state_),
    error_(engine->error_code_) {
    engine->engine_entered_ += 1;
  }
  
  XmppEngineImpl::EnterExit::~EnterExit()  {
   XmppEngineImpl* engine = engine_;
  
   engine->engine_entered_ -= 1;
  
   bool closing = (engine->state_ != state_ &&
         engine->state_ == STATE_CLOSED);
   bool flushing = closing || (engine->engine_entered_ == 0);
  
   if (engine->output_handler_ && flushing) {
     std::string output = engine->output_->str();
     if (output.length() > 0)
       engine->output_handler_->WriteOutput(output.c_str(), output.length());
     engine->output_->str("");
  
     if (closing) {
       engine->output_handler_->CloseConnection();
       engine->output_handler_ = 0;
     }
   }
  
   if (engine->engine_entered_)
     return;
  
   if (engine->raised_reset_) {
     engine->stanza_parser_.Reset();
     engine->raised_reset_ = false;
   }
  
   if (engine->session_handler_) {
     if (engine->state_ != state_)
       engine->session_handler_->OnStateChange(engine->state_);
     // Note: Handling of OnStateChange(CLOSED) should allow for the
     // deletion of the engine, so no members should be accessed
     // after this line.
   }
  }
  
  }  // namespace buzz
  