Index: webrtc/libjingle/xmpp/xmppengineimpl.cc |
diff --git a/webrtc/libjingle/xmpp/xmppengineimpl.cc b/webrtc/libjingle/xmpp/xmppengineimpl.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..e77a1d93497fe7d291ff042d1dd7cabbffa14562 |
--- /dev/null |
+++ b/webrtc/libjingle/xmpp/xmppengineimpl.cc |
@@ -0,0 +1,446 @@ |
+/* |
+ * Copyright 2004 The WebRTC Project Authors. All rights reserved. |
+ * |
+ * Use of this source code is governed by a BSD-style license |
+ * that can be found in the LICENSE file in the root of the source |
+ * tree. An additional intellectual property rights grant can be found |
+ * in the file PATENTS. All contributing project authors may |
+ * be found in the AUTHORS file in the root of the source tree. |
+ */ |
+ |
+#include "webrtc/libjingle/xmpp/xmppengineimpl.h" |
+ |
+#include <algorithm> |
+#include <sstream> |
+#include <vector> |
+ |
+#include "webrtc/libjingle/xmllite/xmlelement.h" |
+#include "webrtc/libjingle/xmllite/xmlprinter.h" |
+#include "webrtc/libjingle/xmpp/constants.h" |
+#include "webrtc/libjingle/xmpp/saslhandler.h" |
+#include "webrtc/libjingle/xmpp/xmpplogintask.h" |
+#include "webrtc/base/common.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_) { |
+ 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 |