blob: ea0caa407a68493c89e4027ad01d56e48e833684 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "storage/common/database/database_identifier.h"
#include <stddef.h>
#include <string>
#include <string_view>
#include "base/containers/contains.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "url/gurl.h"
#include "url/origin.h"
#include "url/url_canon.h"
namespace storage {
namespace {
// If the passed string is of the form "[1::2:3]", returns "[1__2_3]".
std::string EscapeIPv6Hostname(const std::string& hostname) {
// Shortest IPv6 hostname would be "[::1]".
if (hostname.size() < 5 || hostname.front() != '[' || hostname.back() != ']')
return hostname;
// Should be canonicalized before it gets this far.
// i.e. "[::ffff:8190:3426]" not "[::FFFF:129.144.52.38]"
DCHECK(base::ContainsOnlyChars(hostname, "[]:0123456789abcdef"));
std::string copy = hostname;
base::ReplaceChars(hostname, ":", "_", &copy);
return copy;
}
// If the passed string is of the form "[1__2_3]", returns "[1::2:3]".
std::string UnescapeIPv6Hostname(std::string_view hostname) {
if (hostname.size() < 5 || hostname.front() != '[' || hostname.back() != ']')
return std::string(hostname);
std::string copy(hostname);
base::ReplaceChars(hostname, "_", ":", &copy);
return copy;
}
static bool SchemeIsUnique(const std::string& scheme) {
return scheme == "about" || scheme == "data" || scheme == "javascript";
}
class DatabaseIdentifier {
public:
static const DatabaseIdentifier UniqueFileIdentifier();
static DatabaseIdentifier CreateFromOrigin(const GURL& origin);
static DatabaseIdentifier Parse(std::string_view identifier);
~DatabaseIdentifier();
std::string ToString() const;
GURL ToOrigin() const;
private:
DatabaseIdentifier();
DatabaseIdentifier(const std::string& scheme,
const std::string& hostname,
int port,
bool is_unique,
bool is_file);
std::string scheme_;
std::string hostname_;
int port_;
bool is_unique_;
bool is_file_;
};
// static
const DatabaseIdentifier DatabaseIdentifier::UniqueFileIdentifier() {
return DatabaseIdentifier("", "", 0, true, true);
}
// static
DatabaseIdentifier DatabaseIdentifier::CreateFromOrigin(const GURL& origin) {
if (!origin.is_valid() || origin.is_empty() || !origin.IsStandard() ||
SchemeIsUnique(origin.scheme())) {
return DatabaseIdentifier();
}
if (origin.SchemeIsFile())
return UniqueFileIdentifier();
int port = origin.IntPort();
if (port == url::PORT_INVALID)
return DatabaseIdentifier();
// We encode the default port for the specified scheme as 0. GURL
// canonicalizes this as an unspecified port.
if (port == url::PORT_UNSPECIFIED)
port = 0;
return DatabaseIdentifier(origin.scheme(),
origin.host(),
port,
false /* unique */,
false /* file */);
}
// static
DatabaseIdentifier DatabaseIdentifier::Parse(std::string_view identifier) {
if (!base::IsStringASCII(identifier))
return DatabaseIdentifier();
if (base::Contains(identifier, "..")) {
return DatabaseIdentifier();
}
static const char kForbidden[] = {'\\', '/', ':', '\0'};
if (identifier.find_first_of(kForbidden, 0, std::size(kForbidden)) !=
std::string::npos) {
return DatabaseIdentifier();
}
size_t first_underscore = identifier.find_first_of('_');
if (first_underscore == std::string::npos || first_underscore == 0)
return DatabaseIdentifier();
size_t last_underscore = identifier.find_last_of('_');
if (last_underscore == std::string::npos ||
last_underscore == first_underscore ||
last_underscore == identifier.size() - 1) {
return DatabaseIdentifier();
}
std::string scheme(identifier.data(), first_underscore);
if (scheme == "file")
return UniqueFileIdentifier();
// This magical set of schemes is always treated as unique.
if (SchemeIsUnique(scheme))
return DatabaseIdentifier();
std::string_view port_str = identifier.substr(last_underscore + 1);
int port = 0;
constexpr int kMaxPort = 65535;
if (!base::StringToInt(port_str, &port) || port < 0 || port > kMaxPort)
return DatabaseIdentifier();
std::string hostname = UnescapeIPv6Hostname(identifier.substr(
first_underscore + 1, last_underscore - first_underscore - 1));
GURL url(base::StrCat({scheme, "://", hostname, "/"}));
// If a url doesn't parse cleanly or doesn't round trip, reject it.
if (!url.is_valid() || url.scheme() != scheme || url.host() != hostname) {
return DatabaseIdentifier();
}
// Clear hostname for a non-special URL. This behavior existed before
// non-special URLs are properly supported, and we're keeping this for
// compatibility reasons.
if (!url.IsStandard()) {
hostname.clear();
}
return DatabaseIdentifier(scheme, hostname, port, false /* unique */, false);
}
DatabaseIdentifier::DatabaseIdentifier()
: port_(0),
is_unique_(true),
is_file_(false) {
}
DatabaseIdentifier::DatabaseIdentifier(const std::string& scheme,
const std::string& hostname,
int port,
bool is_unique,
bool is_file)
: scheme_(scheme),
hostname_(base::ToLowerASCII(hostname)),
port_(port),
is_unique_(is_unique),
is_file_(is_file) {
}
DatabaseIdentifier::~DatabaseIdentifier() = default;
std::string DatabaseIdentifier::ToString() const {
if (is_file_)
return "file__0";
if (is_unique_)
return "__0";
return scheme_ + "_" + EscapeIPv6Hostname(hostname_) + "_" +
base::NumberToString(port_);
}
GURL DatabaseIdentifier::ToOrigin() const {
if (is_file_)
return GURL("file:///");
if (is_unique_)
return GURL();
if (port_ == 0)
return GURL(scheme_ + "://" + hostname_);
return GURL(scheme_ + "://" + hostname_ + ":" + base::NumberToString(port_));
}
} // namespace
// static
std::string GetIdentifierFromOrigin(const GURL& origin) {
return DatabaseIdentifier::CreateFromOrigin(origin).ToString();
}
// static
std::string GetIdentifierFromOrigin(const url::Origin& origin) {
return GetIdentifierFromOrigin(origin.GetURL());
}
// static
url::Origin GetOriginFromIdentifier(const std::string& identifier) {
return url::Origin::Create(DatabaseIdentifier::Parse(identifier).ToOrigin());
}
// static
GURL GetOriginURLFromIdentifier(const std::string& identifier) {
return DatabaseIdentifier::Parse(identifier).ToOrigin();
}
// static
bool IsValidOriginIdentifier(const std::string& identifier) {
return GetOriginURLFromIdentifier(identifier).is_valid();
}
} // namespace storage