* -------------------------------------
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Darin Fisher (original author)
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "url/third_party/mozilla/url_parse.h"
#include <stdlib.h>
#include <ostream>
#include <string_view>
#include "base/check_op.h"
#include "url/url_parse_internal.h"
#include "url/url_util.h"
#include "url/url_util_internal.h"
namespace url {
std::ostream& operator<<(std::ostream& os, const Parsed& parsed) {
return os << "{ scheme: " << parsed.scheme
<< ", username: " << parsed.username
<< ", password: " << parsed.password << ", host: " << parsed.host
<< ", port: " << parsed.port << ", path: " << parsed.path
<< ", query: " << parsed.query << ", ref: " << parsed.ref
<< ", has_opaque_path: " << parsed.has_opaque_path << " }";
}
Component MakeRange(size_t begin, size_t end) {
CHECK_LE(begin, end);
return Component(base::checked_cast<int>(begin),
base::checked_cast<int>(end - begin));
}
namespace {
inline bool IsPortDigit(char16_t ch) {
return ch >= '0' && ch <= '9';
}
template <typename CHAR>
size_t FindNextAuthorityTerminator(std::basic_string_view<CHAR> spec,
size_t start_offset,
ParserMode parser_mode) {
for (size_t i = start_offset; i < spec.length(); ++i) {
if (IsAuthorityTerminator(spec[i], parser_mode)) {
return i;
}
}
return spec.length();
}
template <typename CHAR>
void ParseUserInfo(std::basic_string_view<CHAR> spec,
const Component& user,
Component* username,
Component* password) {
int colon_offset = 0;
while (colon_offset < user.len && spec[user.begin + colon_offset] != ':') {
++colon_offset;
}
if (colon_offset < user.len) {
*username = Component(user.begin, colon_offset);
*password = MakeRange(user.begin + colon_offset + 1, user.begin + user.len);
} else {
*username = user;
*password = Component();
}
}
template <typename CHAR>
void ParseServerInfo(std::basic_string_view<CHAR> spec,
const Component& serverinfo,
Component* hostname,
Component* port_num) {
if (serverinfo.len == 0) {
hostname->reset();
port_num->reset();
return;
}
int ipv6_terminator = spec[serverinfo.begin] == '[' ? serverinfo.end() : -1;
int colon = -1;
for (int i = serverinfo.begin; i < serverinfo.end(); ++i) {
switch (spec[i]) {
case ']':
ipv6_terminator = i;
break;
case ':':
colon = i;
break;
}
}
if (colon > ipv6_terminator) {
*hostname = MakeRange(serverinfo.begin, colon);
if (hostname->len == 0)
hostname->reset();
*port_num = MakeRange(colon + 1, serverinfo.end());
} else {
*hostname = serverinfo;
port_num->reset();
}
}
template <typename CHAR>
void DoParseAuthority(std::basic_string_view<CHAR> spec,
const Component& auth,
ParserMode parser_mode,
Component* username,
Component* password,
Component* hostname,
Component* port_num) {
DCHECK(auth.is_valid()) << "We should always get an authority";
if (auth.len == 0) {
username->reset();
password->reset();
if (parser_mode == ParserMode::kSpecialURL) {
hostname->reset();
} else {
*hostname = Component(auth.begin, 0);
}
port_num->reset();
return;
}
int i = auth.begin + auth.len - 1;
while (i > auth.begin && spec[i] != '@')
i--;
if (spec[i] == '@') {
ParseUserInfo(spec, Component(auth.begin, i - auth.begin), username,
password);
ParseServerInfo(spec, MakeRange(i + 1, auth.begin + auth.len), hostname,
port_num);
} else {
username->reset();
password->reset();
ParseServerInfo(spec, auth, hostname, port_num);
}
}
template <typename CHAR>
inline std::pair<size_t, size_t> FindQueryAndRefParts(
std::basic_string_view<CHAR> spec,
const Component& path) {
size_t path_begin = static_cast<size_t>(path.begin);
size_t path_end = path.CheckedEnd();
size_t ref_separator =
spec.substr(0, path_end).find_first_of('#', path_begin);
size_t len_before_fragment =
ref_separator == std::basic_string_view<CHAR>::npos ? path_end
: ref_separator;
size_t query_separator =
spec.substr(0, len_before_fragment).find_first_of('?', path_begin);
return {query_separator, ref_separator};
}
template <typename CHAR>
void ParsePath(std::basic_string_view<CHAR> spec,
const Component& path,
Component* filepath,
Component* query,
Component* ref) {
DCHECK(path.is_valid());
auto [query_separator, ref_separator] = FindQueryAndRefParts(spec, path);
size_t file_end, query_end;
size_t path_end = path.CheckedEnd();
constexpr size_t npos = std::basic_string_view<CHAR>::npos;
if (ref_separator != npos) {
file_end = query_end = ref_separator;
*ref = MakeRange(ref_separator + 1, path_end);
} else {
file_end = query_end = path_end;
ref->reset();
}
if (query_separator != npos) {
file_end = query_separator;
*query = MakeRange(query_separator + 1, query_end);
} else {
query->reset();
}
size_t path_begin = static_cast<size_t>(path.begin);
if (file_end != path_begin) {
*filepath = MakeRange(path_begin, file_end);
} else {
filepath->reset();
}
}
template <typename CharT>
bool DoExtractScheme(std::basic_string_view<CharT> url, Component* scheme) {
size_t begin = 0;
while (begin < url.size() && ShouldTrimFromURL(url[begin])) {
begin++;
}
if (begin == url.size()) {
return false;
}
for (size_t i = begin; i < url.size(); i++) {
if (url[i] == ':') {
*scheme = MakeRange(begin, i);
return true;
}
}
return false;
}
template <typename CHAR>
void DoParseAfterSpecialScheme(std::basic_string_view<CHAR> spec,
int after_scheme,
Parsed* parsed) {
size_t num_slashes = CountConsecutiveSlashesOrBackslashes(spec, after_scheme);
size_t after_slashes = after_scheme + num_slashes;
size_t end_auth =
FindNextAuthorityTerminator(spec, after_slashes, ParserMode::kSpecialURL);
Component authority = MakeRange(after_slashes, end_auth);
Component full_path = MakeRange(end_auth, spec.length());
DoParseAuthority(spec, authority, ParserMode::kSpecialURL, &parsed->username,
&parsed->password, &parsed->host, &parsed->port);
ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);
}
template <typename CharT>
Parsed DoParseStandardUrl(std::basic_string_view<CharT> url) {
auto [begin, url_len] = TrimUrl(url);
url = url.substr(0, url_len);
int after_scheme;
Parsed parsed;
if (DoExtractScheme(url, &parsed.scheme)) {
after_scheme = parsed.scheme.end() + 1;
} else {
parsed.scheme.reset();
after_scheme = base::checked_cast<int>(begin);
}
DoParseAfterSpecialScheme(url, after_scheme, &parsed);
return parsed;
}
template <typename CHAR>
void DoParseAfterNonSpecialScheme(std::basic_string_view<CHAR> spec,
int after_scheme,
Parsed* parsed) {
size_t num_slashes = CountConsecutiveSlashes(spec, after_scheme);
size_t spec_len = spec.length();
if (num_slashes >= 2) {
parsed->has_opaque_path = false;
size_t after_slashes = after_scheme + 2;
size_t end_auth = FindNextAuthorityTerminator(spec, after_slashes,
ParserMode::kNonSpecialURL);
Component authority = MakeRange(after_slashes, end_auth);
DoParseAuthority(spec, authority, ParserMode::kNonSpecialURL,
&parsed->username, &parsed->password, &parsed->host,
&parsed->port);
Component full_path = MakeRange(end_auth, spec_len);
ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);
return;
}
if (num_slashes == 1) {
parsed->has_opaque_path = false;
} else {
parsed->has_opaque_path = true;
}
parsed->username.reset();
parsed->password.reset();
parsed->host.reset();
parsed->port.reset();
Component full_path(after_scheme, spec_len - after_scheme);
ParsePath(spec, full_path, &parsed->path, &parsed->query, &parsed->ref);
}
template <typename CharT>
Parsed DoParseNonSpecialUrl(std::basic_string_view<CharT> url,
bool trim_path_end) {
auto [begin, url_len] = TrimUrl(url, trim_path_end);
url = url.substr(0, url_len);
int after_scheme;
Parsed parsed;
if (DoExtractScheme(url, &parsed.scheme)) {
after_scheme = parsed.scheme.end() + 1;
} else {
parsed.scheme.reset();
after_scheme = 0;
}
DoParseAfterNonSpecialScheme(url, after_scheme, &parsed);
return parsed;
}
template <typename CharT>
Parsed DoParseFileSystemUrl(std::basic_string_view<CharT> url) {
auto [begin, url_len] = TrimUrl(url);
if (begin == url_len) {
return {};
}
size_t inner_start = std::basic_string_view<CharT>::npos;
Parsed parsed;
if (DoExtractScheme(url.substr(begin, url_len - begin), &parsed.scheme)) {
parsed.scheme.OffsetBy(begin);
size_t scheme_end = parsed.scheme.CheckedEnd();
if (scheme_end == url_len - 1) {
return parsed;
}
inner_start = scheme_end + 1;
} else {
return {};
}
Component inner_scheme;
std::basic_string_view inner_url =
url.substr(inner_start, url_len - inner_start);
if (DoExtractScheme(inner_url, &inner_scheme)) {
inner_scheme.OffsetBy(inner_start);
if (inner_scheme.CheckedEnd() == url_len - 1) {
return parsed;
}
} else {
return parsed;
}
Parsed inner_parsed;
if (CompareSchemeComponent(url, inner_scheme, kFileScheme)) {
inner_parsed = ParseFileUrl(inner_url);
} else if (CompareSchemeComponent(url, inner_scheme, kFileSystemScheme)) {
return parsed;
} else if (IsStandard(inner_scheme.AsViewOn(url))) {
inner_parsed = DoParseStandardUrl(inner_url);
} else {
return parsed;
}
inner_parsed.scheme.begin += inner_start;
inner_parsed.username.begin += inner_start;
inner_parsed.password.begin += inner_start;
inner_parsed.host.begin += inner_start;
inner_parsed.port.begin += inner_start;
inner_parsed.query.begin += inner_start;
inner_parsed.ref.begin += inner_start;
inner_parsed.path.begin += inner_start;
parsed.query = inner_parsed.query;
inner_parsed.query.reset();
parsed.ref = inner_parsed.ref;
inner_parsed.ref.reset();
parsed.set_inner_parsed(inner_parsed);
if (!inner_parsed.scheme.is_valid() || !inner_parsed.path.is_valid() ||
inner_parsed.inner_parsed()) {
return parsed;
}
if (!IsSlashOrBackslash(url[inner_parsed.path.begin])) {
return parsed;
}
int inner_path_end = inner_parsed.path.begin + 1;
while (static_cast<size_t>(inner_path_end) < url_len &&
!IsSlashOrBackslash(url[inner_path_end])) {
++inner_path_end;
}
parsed.path.begin = inner_path_end;
int new_inner_path_length = inner_path_end - inner_parsed.path.begin;
parsed.path.len = inner_parsed.path.len - new_inner_path_length;
parsed.inner_parsed()->path.len = new_inner_path_length;
return parsed;
}
template <typename CharT>
Parsed DoParsePathUrl(std::basic_string_view<CharT> url, bool trim_path_end) {
auto [scheme_begin, url_len] = TrimUrl(url, trim_path_end);
if (scheme_begin == url_len) {
return {};
}
size_t path_begin;
Parsed parsed;
if (ExtractScheme(url.substr(scheme_begin, url_len - scheme_begin),
&parsed.scheme)) {
parsed.scheme.OffsetBy(scheme_begin);
path_begin = parsed.scheme.CheckedEnd() + 1;
} else {
parsed.scheme.reset();
path_begin = scheme_begin;
}
if (path_begin == url_len) {
return parsed;
}
DCHECK_LT(path_begin, url_len);
ParsePath(url, MakeRange(path_begin, url_len), &parsed.path, &parsed.query,
&parsed.ref);
return parsed;
}
template <typename CharT>
Parsed DoParseMailtoUrl(std::basic_string_view<CharT> url) {
auto [begin, url_len] = TrimUrl(url);
if (begin == url_len) {
return {};
}
size_t path_begin = std::basic_string_view<CharT>::npos;
size_t path_end = std::basic_string_view<CharT>::npos;
Parsed parsed;
if (ExtractScheme(url.substr(begin, url_len - begin), &parsed.scheme)) {
parsed.scheme.OffsetBy(begin);
size_t scheme_end = parsed.scheme.CheckedEnd();
if (scheme_end != url_len - 1) {
path_begin = scheme_end + 1;
path_end = url_len;
}
} else {
parsed.scheme.reset();
path_begin = begin;
path_end = url_len;
}
for (size_t i = path_begin; i < path_end; ++i) {
if (url[i] == '?') {
parsed.query = MakeRange(i + 1, path_end);
path_end = i;
break;
}
}
if (path_begin == path_end) {
parsed.path.reset();
} else {
parsed.path = MakeRange(path_begin, path_end);
}
return parsed;
}
template <typename CHAR>
int DoParsePort(std::basic_string_view<CHAR> spec, const Component& component) {
const int kMaxDigits = 5;
if (component.is_empty())
return PORT_UNSPECIFIED;
Component digits_comp(component.end(), 0);
for (int i = 0; i < component.len; i++) {
if (spec[component.begin + i] != '0') {
digits_comp = MakeRange(component.begin + i, component.end());
break;
}
}
if (digits_comp.len == 0)
return 0;
if (digits_comp.len > kMaxDigits)
return PORT_INVALID;
char digits[kMaxDigits + 1];
for (int i = 0; i < digits_comp.len; i++) {
CHAR ch = spec[digits_comp.begin + i];
if (!IsPortDigit(ch)) {
return PORT_INVALID;
}
UNSAFE_TODO(digits[i]) = static_cast<char>(ch);
}
UNSAFE_TODO(digits[digits_comp.len]) = 0;
int port = atoi(digits);
if (port > 65535)
return PORT_INVALID;
return port;
}
template <typename CHAR>
void DoExtractFileName(std::basic_string_view<CHAR> spec,
const Component& path,
Component* file_name) {
if (path.is_empty()) {
file_name->reset();
return;
}
int file_end = path.end();
for (int i = path.end() - 1; i >= path.begin; i--) {
if (spec[i] == ';') {
file_end = i;
} else if (IsSlashOrBackslash(spec[i])) {
*file_name = MakeRange(i + 1, file_end);
return;
}
}
*file_name = MakeRange(path.begin, file_end);
return;
}
template <typename CharT>
bool DoExtractQueryKeyValue(std::basic_string_view<CharT> spec,
Component* query,
Component* key,
Component* value) {
if (!query->is_nonempty())
return false;
int start = query->begin;
int cur = start;
int end = query->end();
key->begin = cur;
while (cur < end && spec[cur] != '&' && spec[cur] != '=')
cur++;
key->len = cur - key->begin;
if (cur < end && spec[cur] == '=')
cur++;
value->begin = cur;
while (cur < end && spec[cur] != '&')
cur++;
value->len = cur - value->begin;
if (cur < end && spec[cur] == '&')
cur++;
*query = MakeRange(cur, end);
return true;
}
}
COMPONENT_EXPORT(URL)
std::ostream& operator<<(std::ostream& os, const Component& component) {
return os << '{' << component.begin << ", " << component.len << "}";
}
Parsed::Parsed() = default;
Parsed::Parsed(const Parsed& other)
: scheme(other.scheme),
username(other.username),
password(other.password),
host(other.host),
port(other.port),
path(other.path),
query(other.query),
ref(other.ref),
potentially_dangling_markup(other.potentially_dangling_markup),
has_opaque_path(other.has_opaque_path) {
if (other.inner_parsed_)
set_inner_parsed(*other.inner_parsed_);
}
Parsed& Parsed::operator=(const Parsed& other) {
if (this != &other) {
scheme = other.scheme;
username = other.username;
password = other.password;
host = other.host;
port = other.port;
path = other.path;
query = other.query;
ref = other.ref;
potentially_dangling_markup = other.potentially_dangling_markup;
has_opaque_path = other.has_opaque_path;
if (other.inner_parsed_)
set_inner_parsed(*other.inner_parsed_);
else
clear_inner_parsed();
}
return *this;
}
Parsed::~Parsed() {
delete inner_parsed_;
}
int Parsed::Length() const {
if (ref.is_valid())
return ref.end();
return CountCharactersBefore(REF, false);
}
int Parsed::CountCharactersBefore(ComponentType type,
bool include_delimiter) const {
if (type == SCHEME)
return scheme.begin;
int cur = 0;
if (scheme.is_valid())
cur = scheme.end() + 1;
if (username.is_valid()) {
if (type <= USERNAME)
return username.begin;
cur = username.end() + 1;
}
if (password.is_valid()) {
if (type <= PASSWORD)
return password.begin;
cur = password.end() + 1;
}
if (host.is_valid()) {
if (type <= HOST)
return host.begin;
cur = host.end();
}
if (port.is_valid()) {
if (type < PORT || (type == PORT && include_delimiter))
return port.begin - 1;
if (type == PORT)
return port.begin;
cur = port.end();
}
if (path.is_valid()) {
if (type <= PATH)
return path.begin;
cur = path.end();
}
if (query.is_valid()) {
if (type < QUERY || (type == QUERY && include_delimiter))
return query.begin - 1;
if (type == QUERY)
return query.begin;
cur = query.end();
}
if (ref.is_valid()) {
if (type == REF && !include_delimiter)
return ref.begin;
return ref.begin - 1;
}
return cur;
}
Component Parsed::GetContent() const {
const int begin = CountCharactersBefore(USERNAME, false);
const int len = Length() - begin;
return len ? Component(begin, len) : Component();
}
bool ExtractScheme(std::string_view url, Component* scheme) {
return DoExtractScheme(url, scheme);
}
bool ExtractScheme(std::u16string_view url, Component* scheme) {
return DoExtractScheme(url, scheme);
}
bool ExtractScheme(const char* url, int url_len, Component* scheme) {
return DoExtractScheme(std::string_view(url, url_len), scheme);
}
bool IsAuthorityTerminator(char16_t ch, ParserMode parser_mode) {
if (parser_mode == ParserMode::kSpecialURL) {
return IsSlashOrBackslash(ch) || ch == '?' || ch == '#';
}
return ch == '/' || ch == '?' || ch == '#';
}
void ExtractFileName(std::string_view url,
const Component& path,
Component* file_name) {
DoExtractFileName(url, path, file_name);
}
void ExtractFileName(std::u16string_view url,
const Component& path,
Component* file_name) {
DoExtractFileName(url, path, file_name);
}
bool ExtractQueryKeyValue(std::string_view url,
Component* query,
Component* key,
Component* value) {
return DoExtractQueryKeyValue(url, query, key, value);
}
bool ExtractQueryKeyValue(std::u16string_view url,
Component* query,
Component* key,
Component* value) {
return DoExtractQueryKeyValue(url, query, key, value);
}
void ParseAuthority(const char* spec,
const Component& auth,
Component* username,
Component* password,
Component* hostname,
Component* port_num) {
size_t length = auth.is_valid() ? auth.end() : 0;
DoParseAuthority(std::string_view(spec, length), auth,
ParserMode::kSpecialURL, username, password, hostname,
port_num);
}
void ParseAuthority(std::string_view spec,
const Component& auth,
ParserMode parser_mode,
Component* username,
Component* password,
Component* hostname,
Component* port_num) {
DoParseAuthority(spec, auth, parser_mode, username, password, hostname,
port_num);
}
void ParseAuthority(std::u16string_view spec,
const Component& auth,
ParserMode parser_mode,
Component* username,
Component* password,
Component* hostname,
Component* port_num) {
DoParseAuthority(spec, auth, parser_mode, username, password, hostname,
port_num);
}
int ParsePort(const char* url, const Component& port) {
return port.is_empty()
? PORT_UNSPECIFIED
: DoParsePort(
std::string_view(url, static_cast<size_t>(port.end())),
port);
}
int ParsePort(std::string_view url, const Component& port) {
return DoParsePort(url, port);
}
int ParsePort(std::u16string_view url, const Component& port) {
return DoParsePort(url, port);
}
Parsed ParseStandardUrl(std::string_view url) {
return DoParseStandardUrl(url);
}
Parsed ParseStandardUrl(std::u16string_view url) {
return DoParseStandardUrl(url);
}
void ParseStandardURL(const char* url, int url_len, Parsed* parsed) {
CHECK_GE(url_len, 0);
*parsed = DoParseStandardUrl(std::basic_string_view(url, url_len));
}
Parsed ParseNonSpecialUrl(std::string_view url) {
return DoParseNonSpecialUrl(url, true);
}
Parsed ParseNonSpecialUrl(std::u16string_view url) {
return DoParseNonSpecialUrl(url, true);
}
Parsed ParseNonSpecialUrlInternal(std::string_view url, bool trim_path_end) {
return DoParseNonSpecialUrl(url, trim_path_end);
}
Parsed ParseNonSpecialUrlInternal(std::u16string_view url, bool trim_path_end) {
return DoParseNonSpecialUrl(url, trim_path_end);
}
Parsed ParsePathUrl(std::string_view url, bool trim_path_end) {
return DoParsePathUrl(url, trim_path_end);
}
Parsed ParsePathUrl(std::u16string_view url, bool trim_path_end) {
return DoParsePathUrl(url, trim_path_end);
}
void ParsePathURL(const char* url,
int url_len,
bool trim_path_end,
Parsed* parsed) {
CHECK_GE(url_len, 0);
*parsed = ParsePathUrl(std::string_view(url, url_len), trim_path_end);
}
Parsed ParseFileSystemUrl(std::string_view url) {
return DoParseFileSystemUrl(url);
}
Parsed ParseFileSystemUrl(std::u16string_view url) {
return DoParseFileSystemUrl(url);
}
Parsed ParseMailtoUrl(std::string_view url) {
return DoParseMailtoUrl(url);
}
Parsed ParseMailtoUrl(std::u16string_view url) {
return DoParseMailtoUrl(url);
}
void ParsePathInternal(std::string_view spec,
const Component& path,
Component* filepath,
Component* query,
Component* ref) {
ParsePath(spec, path, filepath, query, ref);
}
void ParsePathInternal(std::u16string_view spec,
const Component& path,
Component* filepath,
Component* query,
Component* ref) {
ParsePath(spec, path, filepath, query, ref);
}
void ParseAfterSpecialScheme(std::string_view spec,
int after_scheme,
Parsed* parsed) {
DoParseAfterSpecialScheme(spec, after_scheme, parsed);
}
void ParseAfterSpecialScheme(std::u16string_view spec,
int after_scheme,
Parsed* parsed) {
DoParseAfterSpecialScheme(spec, after_scheme, parsed);
}
void ParseAfterNonSpecialScheme(std::string_view spec,
int after_scheme,
Parsed* parsed) {
DoParseAfterNonSpecialScheme(spec, after_scheme, parsed);
}
void ParseAfterNonSpecialScheme(std::u16string_view spec,
int after_scheme,
Parsed* parsed) {
DoParseAfterNonSpecialScheme(spec, after_scheme, parsed);
}
}