blob: 1165610093b6ec2f4414d2aaccf925dd6d3d2f45 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/signaling/signaling_address.h"
#include <string.h>
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "remoting/base/name_value_map.h"
#include "remoting/base/remoting_bot.h"
#include "remoting/base/service_urls.h"
#include "remoting/signaling/jid_util.h"
#include "third_party/libjingle_xmpp/xmllite/xmlelement.h"
namespace remoting {
namespace {
constexpr char kJingleNamespace[] = "urn:xmpp:jingle:1";
constexpr char kLcsResourcePrefix[] = "chromoting_lcs_";
// FTL JID format:
// user@domain.com/chromoting_ftl_(registration ID)
// The FTL JID is only used local to the program.
constexpr char kFtlResourcePrefix[] = "chromoting_ftl_";
// Represents the XML attrbute names for the various address fields in the
// iq stanza.
enum class Field { JID, CHANNEL, ENDPOINT_ID };
const NameMapElement<SignalingAddress::Channel> kChannelTypes[] = {
{SignalingAddress::Channel::LCS, "lcs"},
{SignalingAddress::Channel::XMPP, "xmpp"},
{SignalingAddress::Channel::FTL, "ftl"},
};
jingle_xmpp::QName GetQNameByField(Field attr, SignalingAddress::Direction direction) {
const char* attribute_name = nullptr;
switch (attr) {
case Field::JID:
attribute_name = (direction == SignalingAddress::FROM) ? "from" : "to";
break;
case Field::ENDPOINT_ID:
attribute_name = (direction == SignalingAddress::FROM)
? "from-endpoint-id"
: "to-endpoint-id";
break;
case Field::CHANNEL:
attribute_name =
(direction == SignalingAddress::FROM) ? "from-channel" : "to-channel";
break;
}
return jingle_xmpp::QName("", attribute_name);
}
SignalingAddress::Channel GetChannelType(std::string address) {
std::string bare_jid;
std::string resource;
bool has_jid_resource = SplitJidResource(address, &bare_jid, &resource);
if (has_jid_resource) {
if (resource.find(kLcsResourcePrefix) == 0) {
return SignalingAddress::Channel::LCS;
}
if (resource.find(kFtlResourcePrefix) == 0) {
return SignalingAddress::Channel::FTL;
}
}
return SignalingAddress::Channel::XMPP;
}
} // namespace
SignalingAddress::SignalingAddress()
: channel_(SignalingAddress::Channel::XMPP) {}
SignalingAddress::SignalingAddress(const std::string& address) {
channel_ = GetChannelType(address);
switch (channel_) {
case SignalingAddress::Channel::XMPP:
jid_ = NormalizeJid(address);
break;
case SignalingAddress::Channel::LCS:
endpoint_id_ = NormalizeJid(address);
jid_ = remoting::ServiceUrls::GetInstance()->directory_bot_jid();
break;
case SignalingAddress::Channel::FTL:
jid_ = NormalizeJid(address);
break;
default:
NOTREACHED();
}
DCHECK(!jid_.empty()) << "Missing JID.";
DCHECK(endpoint_id_.empty() != (channel_ == Channel::LCS))
<< "EndpointId should be set if and only if the Channel is LCS";
}
SignalingAddress::SignalingAddress(const std::string& jid,
const std::string& endpoint_id,
Channel channel)
: jid_(NormalizeJid(jid)),
endpoint_id_(NormalizeJid(endpoint_id)),
channel_(channel) {
DCHECK(!jid.empty());
}
bool SignalingAddress::operator==(const SignalingAddress& other) const {
return (other.jid_ == jid_) && (other.endpoint_id_ == endpoint_id_) &&
(other.channel_ == channel_);
}
bool SignalingAddress::operator!=(const SignalingAddress& other) const {
return !(*this == other);
}
// static
SignalingAddress SignalingAddress::CreateFtlSignalingAddress(
const std::string& username,
const std::string& registration_id) {
return SignalingAddress(base::StringPrintf("%s/%s%s", username.c_str(),
kFtlResourcePrefix,
registration_id.c_str()));
}
// static
SignalingAddress SignalingAddress::Parse(const jingle_xmpp::XmlElement* iq,
SignalingAddress::Direction direction,
std::string* error) {
std::string jid(iq->Attr(GetQNameByField(Field::JID, direction)));
if (jid.empty()) {
return SignalingAddress();
}
const jingle_xmpp::XmlElement* jingle =
iq->FirstNamed(jingle_xmpp::QName(kJingleNamespace, "jingle"));
if (!jingle || GetChannelType(jid) == SignalingAddress::Channel::FTL) {
return SignalingAddress(jid);
}
std::string type(iq->Attr(jingle_xmpp::QName(std::string(), "type")));
// For error IQs, invert the direction as the jingle node represents the
// original request.
if (type == "error") {
direction = (direction == FROM) ? TO : FROM;
}
std::string endpoint_id(
jingle->Attr(GetQNameByField(Field::ENDPOINT_ID, direction)));
std::string channel_str(
jingle->Attr(GetQNameByField(Field::CHANNEL, direction)));
SignalingAddress::Channel channel;
if (channel_str.empty()) {
channel = SignalingAddress::Channel::XMPP;
} else if (!NameToValue(kChannelTypes, channel_str, &channel)) {
*error = "Unknown channel: " + channel_str;
return SignalingAddress();
}
bool is_lcs = (channel == SignalingAddress::Channel::LCS);
if (is_lcs == endpoint_id.empty()) {
*error = (is_lcs ? "Missing |endpoint-id| for LCS channel"
: "|endpoint_id| should be empty for XMPP channel");
return SignalingAddress();
}
if (direction == FROM && is_lcs && !IsValidBotJid(jid)) {
*error = "Reject LCS message from untrusted sender: " + jid;
return SignalingAddress();
}
return SignalingAddress(jid, endpoint_id, channel);
}
void SignalingAddress::SetInMessage(jingle_xmpp::XmlElement* iq,
Direction direction) const {
DCHECK(!empty()) << "Signaling Address is empty";
if (direction == FROM) {
// The from JID is not used for routing but for sender identification on the
// receiving end. Use the ID for specificity.
iq->SetAttr(GetQNameByField(Field::JID, direction), id());
} else {
// The to JID is used for routing and must be a valid XMPP endpoint. Always
// use the XMPP JID.
iq->SetAttr(GetQNameByField(Field::JID, direction), jid_);
}
// Do not tamper the routing-info in the jingle tag for error IQ's, as
// it corresponds to the original message.
std::string type(iq->Attr(jingle_xmpp::QName(std::string(), "type")));
if (type == "error") {
return;
}
jingle_xmpp::XmlElement* jingle =
iq->FirstNamed(jingle_xmpp::QName(kJingleNamespace, "jingle"));
if (jingle) {
// Start from a fresh slate regardless of the previous address format.
jingle->ClearAttr(GetQNameByField(Field::CHANNEL, direction));
jingle->ClearAttr(GetQNameByField(Field::ENDPOINT_ID, direction));
// Only set the channel and endpoint_id in the LCS channel.
if (channel_ == SignalingAddress::Channel::LCS) {
jingle->AddAttr(GetQNameByField(Field::ENDPOINT_ID, direction),
endpoint_id_);
jingle->AddAttr(GetQNameByField(Field::CHANNEL, direction),
ValueToName(kChannelTypes, channel_));
}
}
}
bool SignalingAddress::GetFtlInfo(std::string* username,
std::string* registration_id) const {
if (channel_ != Channel::FTL) {
return false;
}
std::string resource;
bool has_jid_resource = SplitJidResource(jid_, username, &resource);
DCHECK(has_jid_resource);
size_t ftl_resource_prefix_length = strlen(kFtlResourcePrefix);
DCHECK_LT(ftl_resource_prefix_length, resource.length());
*registration_id = resource.substr(ftl_resource_prefix_length);
return true;
}
} // namespace remoting