blob: fc7908e18e5229433edd267f0711717672f6d7d4 [file] [log] [blame]
// Copyright (c) 2012 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 "chrome/common/extensions/permissions/socket_permission_data.h"
#include <cstdlib>
#include <sstream>
#include <vector>
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "chrome/common/extensions/permissions/api_permission.h"
#include "chrome/common/extensions/permissions/socket_permission.h"
#include "url/url_canon.h"
namespace {
using content::SocketPermissionRequest;
using extensions::SocketPermissionData;
const char kColon = ':';
const char kDot = '.';
const char kWildcard[] = "*";
const char kInvalid[] = "invalid";
const char kTCPConnect[] = "tcp-connect";
const char kTCPListen[] = "tcp-listen";
const char kUDPBind[] = "udp-bind";
const char kUDPSendTo[] = "udp-send-to";
const char kUDPMulticastMembership[] = "udp-multicast-membership";
const char kResolveHost[] = "resolve-host";
const char kResolveProxy[] = "resolve-proxy";
const char kNetworkState[] = "network-state";
const int kWildcardPortNumber = 0;
const int kInvalidPort = -1;
SocketPermissionRequest::OperationType StringToType(const std::string& s) {
if (s == kTCPConnect)
return SocketPermissionRequest::TCP_CONNECT;
if (s == kTCPListen)
return SocketPermissionRequest::TCP_LISTEN;
if (s == kUDPBind)
return SocketPermissionRequest::UDP_BIND;
if (s == kUDPSendTo)
return SocketPermissionRequest::UDP_SEND_TO;
if (s == kUDPMulticastMembership)
return SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP;
if (s == kResolveHost)
return SocketPermissionRequest::RESOLVE_HOST;
if (s == kResolveProxy)
return SocketPermissionRequest::RESOLVE_PROXY;
if (s == kNetworkState)
return SocketPermissionRequest::NETWORK_STATE;
return SocketPermissionRequest::NONE;
}
const char* TypeToString(SocketPermissionRequest::OperationType type) {
switch (type) {
case SocketPermissionRequest::TCP_CONNECT:
return kTCPConnect;
case SocketPermissionRequest::TCP_LISTEN:
return kTCPListen;
case SocketPermissionRequest::UDP_BIND:
return kUDPBind;
case SocketPermissionRequest::UDP_SEND_TO:
return kUDPSendTo;
case SocketPermissionRequest::UDP_MULTICAST_MEMBERSHIP:
return kUDPMulticastMembership;
case SocketPermissionRequest::RESOLVE_HOST:
return kResolveHost;
case SocketPermissionRequest::RESOLVE_PROXY:
return kResolveProxy;
case SocketPermissionRequest::NETWORK_STATE:
return kNetworkState;
default:
return kInvalid;
}
}
bool StartsOrEndsWithWhitespace(const std::string& str) {
if (str.find_first_not_of(kWhitespaceASCII) != 0)
return true;
if (str.find_last_not_of(kWhitespaceASCII) != str.length() - 1)
return true;
return false;
}
} // namespace
namespace extensions {
SocketPermissionData::SocketPermissionData()
: pattern_(SocketPermissionRequest::NONE, std::string(), kInvalidPort) {
Reset();
}
SocketPermissionData::~SocketPermissionData() {
}
bool SocketPermissionData::operator<(const SocketPermissionData& rhs) const {
if (pattern_.type < rhs.pattern_.type)
return true;
if (pattern_.type > rhs.pattern_.type)
return false;
if (pattern_.host < rhs.pattern_.host)
return true;
if (pattern_.host > rhs.pattern_.host)
return false;
if (match_subdomains_ < rhs.match_subdomains_)
return true;
if (match_subdomains_ > rhs.match_subdomains_)
return false;
if (pattern_.port < rhs.pattern_.port)
return true;
return false;
}
bool SocketPermissionData::operator==(const SocketPermissionData& rhs) const {
return (pattern_.type == rhs.pattern_.type) &&
(pattern_.host == rhs.pattern_.host) &&
(match_subdomains_ == rhs.match_subdomains_) &&
(pattern_.port == rhs.pattern_.port);
}
bool SocketPermissionData::Check(
const APIPermission::CheckParam* param) const {
if (!param)
return false;
const SocketPermission::CheckParam& specific_param =
*static_cast<const SocketPermission::CheckParam*>(param);
const SocketPermissionRequest &request = specific_param.request;
if (pattern_.type != request.type)
return false;
std::string lhost = StringToLowerASCII(request.host);
if (pattern_.host != lhost) {
if (!match_subdomains_)
return false;
if (!pattern_.host.empty()) {
// Do not wildcard part of IP address.
url_parse::Component component(0, lhost.length());
url_canon::RawCanonOutputT<char, 128> ignored_output;
url_canon::CanonHostInfo host_info;
url_canon::CanonicalizeIPAddress(lhost.c_str(), component,
&ignored_output, &host_info);
if (host_info.IsIPAddress())
return false;
// host should equal one or more chars + "." + host_.
int i = lhost.length() - pattern_.host.length();
if (i < 2)
return false;
if (lhost.compare(i, pattern_.host.length(), pattern_.host) != 0)
return false;
if (lhost[i - 1] != kDot)
return false;
}
}
if (pattern_.port != request.port && pattern_.port != kWildcardPortNumber)
return false;
return true;
}
scoped_ptr<base::Value> SocketPermissionData::ToValue() const {
return scoped_ptr<base::Value>(new base::StringValue(GetAsString()));
}
bool SocketPermissionData::FromValue(const base::Value* value) {
std::string spec;
if (!value->GetAsString(&spec))
return false;
return Parse(spec);
}
bool SocketPermissionData::IsAddressBoundType() const {
return pattern_.type == SocketPermissionRequest::TCP_CONNECT ||
pattern_.type == SocketPermissionRequest::TCP_LISTEN ||
pattern_.type == SocketPermissionRequest::UDP_BIND ||
pattern_.type == SocketPermissionRequest::UDP_SEND_TO;
}
SocketPermissionData::HostType SocketPermissionData::GetHostType() const {
return pattern_.host.empty() ? SocketPermissionData::ANY_HOST :
match_subdomains_ ? SocketPermissionData::HOSTS_IN_DOMAINS :
SocketPermissionData::SPECIFIC_HOSTS;
}
const std::string SocketPermissionData::GetHost() const {
return pattern_.host;
}
content::SocketPermissionRequest& SocketPermissionData::pattern() {
// Clear the spec because the caller could mutate |this|.
spec_.clear();
return pattern_;
}
bool& SocketPermissionData::match_subdomains() {
// Clear the spec because the caller could mutate |this|.
spec_.clear();
return match_subdomains_;
}
// TODO(ikarienator): Rewrite this method to support IPv6.
bool SocketPermissionData::Parse(const std::string& permission) {
do {
pattern_.host.clear();
match_subdomains_ = true;
pattern_.port = kWildcardPortNumber;
spec_.clear();
std::vector<std::string> tokens;
base::SplitStringDontTrim(permission, kColon, &tokens);
if (tokens.empty() || tokens.size() > 3)
break;
pattern_.type = StringToType(tokens[0]);
if (pattern_.type == SocketPermissionRequest::NONE)
break;
if (tokens.size() == 1)
return true;
// Return an error if address is specified for permissions that don't
// need it (such as 'resolve-host').
if (!IsAddressBoundType())
break;
pattern_.host = tokens[1];
if (!pattern_.host.empty()) {
if (StartsOrEndsWithWhitespace(pattern_.host))
break;
pattern_.host = StringToLowerASCII(pattern_.host);
// The first component can optionally be '*' to match all subdomains.
std::vector<std::string> host_components;
base::SplitString(pattern_.host, kDot, &host_components);
DCHECK(!host_components.empty());
if (host_components[0] == kWildcard || host_components[0].empty()) {
host_components.erase(host_components.begin(),
host_components.begin() + 1);
} else {
match_subdomains_ = false;
}
pattern_.host = JoinString(host_components, kDot);
}
if (tokens.size() == 2 || tokens[2].empty() || tokens[2] == kWildcard)
return true;
if (StartsOrEndsWithWhitespace(tokens[2]))
break;
if (!base::StringToInt(tokens[2], &pattern_.port) ||
pattern_.port < 1 || pattern_.port > 65535)
break;
return true;
} while (false);
Reset();
return false;
}
const std::string& SocketPermissionData::GetAsString() const {
if (!spec_.empty())
return spec_;
spec_.reserve(64);
spec_.append(TypeToString(pattern_.type));
if (!IsAddressBoundType())
return spec_;
if (match_subdomains_) {
spec_.append(1, kColon).append(kWildcard);
if (!pattern_.host.empty())
spec_.append(1, kDot).append(pattern_.host);
} else {
spec_.append(1, kColon).append(pattern_.host);
}
if (pattern_.port == kWildcardPortNumber)
spec_.append(1, kColon).append(kWildcard);
else
spec_.append(1, kColon).append(base::IntToString(pattern_.port));
return spec_;
}
void SocketPermissionData::Reset() {
pattern_.type = SocketPermissionRequest::NONE;
pattern_.host.clear();
match_subdomains_ = false;
pattern_.port = kInvalidPort;
spec_.clear();
}
} // namespace extensions