#include <cstddef>
#include <cstdint>
#include <iostream>
#include <optional>
#include <string_view>
#include "ip_utils.hpp"
#include "uri_utils.hpp"
#include "utils.hpp"
namespace ddwaf {
namespace {
enum class token_type : uint8_t {
none,
scheme,
scheme_authority_or_path,
hierarchical_part,
authority,
userinfo,
host,
port,
ipv6address,
regname_or_ipv4address,
path,
query,
fragment,
};
constexpr const auto &npos = std::string_view::npos;
inline bool is_extended_query_set(char c) { return c == '[' || c == ']'; }
inline bool is_unreserved(char c)
{
return ddwaf::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~';
}
inline bool is_subdelim(char c)
{
return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' ||
c == '+' || c == ',' || c == ';' || c == '=';
}
inline bool is_scheme_char(char c) { return ddwaf::isalnum(c) || c == '.' || c == '-' || c == '+'; }
inline bool is_host_char(char c) { return is_unreserved(c) || is_subdelim(c) || c == '%'; }
inline bool is_path_char(char c)
{
return is_unreserved(c) || is_subdelim(c) || c == '%' || c == ':' || c == '@';
}
inline bool is_query_char(char c)
{
return is_extended_query_set(c) || is_path_char(c) || c == '/' || c == '?';
}
inline bool is_frag_char(char c) { return is_path_char(c) || c == '/' || c == '?'; }
inline bool is_userinfo_char(char c)
{
return is_unreserved(c) || is_subdelim(c) || c == ':' || c == '%';
}
inline bool is_regname_char(char c) { return is_unreserved(c) || is_subdelim(c) || c == '%'; }
}
std::optional<uri_decomposed> uri_parse(std::string_view uri)
{
uri_decomposed decomposed;
decomposed.raw = uri;
auto expected_token = token_type::scheme_authority_or_path;
auto lookahead_token = token_type::none;
std::size_t authority_end = npos;
std::string_view authority_substr;
for (std::size_t i = 0; i < uri.size();) {
auto current_token = expected_token;
expected_token = token_type::none;
switch (current_token) {
case token_type::scheme_authority_or_path: {
if (uri[i] == '/') {
if ((i + 1) < uri.size() && uri[i + 1] == '/') {
expected_token = token_type::authority;
i += 2;
} else {
expected_token = token_type::path;
}
} else if (isalpha(uri[i])) {
expected_token = token_type::scheme;
}
break;
}
case token_type::scheme: {
auto token_begin = i;
if (!isalpha(uri[i++])) {
return std::nullopt;
}
bool end_found = false;
while (i < uri.size()) {
const auto c = uri[i++];
if (c == ':') {
end_found = true;
break;
}
if (!is_scheme_char(c)) {
return std::nullopt;
}
}
if (!end_found) {
return std::nullopt;
}
expected_token = token_type::hierarchical_part;
decomposed.scheme = uri.substr(token_begin, i - 1);
break;
}
case token_type::hierarchical_part: {
if ((i + 1) < uri.size() && uri[i] == '/' && uri[i + 1] == '/') {
expected_token = token_type::authority;
i += 2;
} else {
expected_token = token_type::path;
}
break;
}
case token_type::authority: {
auto token_begin = i;
authority_end = uri.find_first_of("/?#", i);
if (authority_end != npos) {
const auto c = uri[authority_end];
if (c == '/') {
lookahead_token = token_type::path;
} else if (c == '?') {
lookahead_token = token_type::query;
} else if (c == '#') {
lookahead_token = token_type::fragment;
}
} else {
authority_end = uri.size();
}
if (authority_end > i) {
authority_substr = uri.substr(0, authority_end);
if (authority_substr.find('@', i) != npos) {
expected_token = token_type::userinfo;
} else {
expected_token = token_type::host;
}
decomposed.authority.index = token_begin;
decomposed.authority.raw = uri.substr(token_begin, authority_end - token_begin);
decomposed.scheme_and_authority = uri.substr(0, authority_end);
} else {
expected_token = lookahead_token;
}
break;
}
case token_type::userinfo: {
auto token_begin = i;
while (i < uri.size()) {
const auto c = uri[i++];
if (c == '@') {
decomposed.authority.userinfo = uri.substr(token_begin, i - token_begin - 1);
if (i == authority_end) {
expected_token = lookahead_token;
} else {
expected_token = token_type::host;
}
break;
}
if (!is_userinfo_char(c)) {
return std::nullopt;
}
}
break;
}
case token_type::host: {
if (uri[i] == '[') {
expected_token = token_type::ipv6address;
} else if (uri[i] == ':') { ++i;
expected_token = token_type::port;
} else if (is_host_char(uri[i])) {
expected_token = token_type::regname_or_ipv4address;
} else if (authority_end != uri.size()) {
expected_token = lookahead_token;
} else {
return std::nullopt;
}
break;
}
case token_type::regname_or_ipv4address: {
auto token_begin = i;
for (; i < authority_end; ++i) {
const auto c = uri[i];
if (c == ':') {
break;
}
if (!is_regname_char(c)) {
return std::nullopt;
}
}
decomposed.authority.host = uri.substr(token_begin, i - token_begin);
decomposed.authority.host_index = token_begin;
ipaddr parsed_ip{};
if (parse_ipv4(decomposed.authority.host, parsed_ip)) {
ipv4_to_ipv6(parsed_ip);
decomposed.authority.host_ip = parsed_ip;
}
if (i >= uri.size()) {
return decomposed;
}
if (uri[i] == ':') {
++i;
expected_token = token_type::port;
} else {
expected_token = lookahead_token;
}
break;
}
case token_type::ipv6address: {
auto token_begin = i;
bool end_found = false;
for (i += 1; i < uri.size(); ++i) {
const auto c = uri[i];
if (c == ']') {
end_found = true;
break;
}
if (!ddwaf::isxdigit(c) && c != ':') {
return std::nullopt;
}
}
if (!end_found || i == (token_begin + 1)) {
return std::nullopt;
}
ipaddr parsed_ip{};
auto host = uri.substr(token_begin + 1, i - (token_begin + 1));
if (!parse_ipv6(host, parsed_ip)) {
return std::nullopt;
}
decomposed.authority.host = host;
decomposed.authority.host_ip = parsed_ip;
decomposed.authority.host_index = token_begin + 1;
token_begin = ++i;
if (token_begin == authority_end) {
expected_token = lookahead_token;
} else if (i < uri.size() && uri[i] == ':') {
++i; expected_token = token_type::port;
} else {
return std::nullopt;
}
break;
}
case token_type::port: {
auto token_begin = i;
for (; i < authority_end; ++i) {
if (!ddwaf::isdigit(uri[i])) {
return std::nullopt;
}
}
auto port_substr = uri.substr(token_begin, i - token_begin);
if (!port_substr.empty()) {
if (auto [res, value] = from_string<uint16_t>(port_substr); res) {
decomposed.authority.port = value;
} else {
return std::nullopt;
}
}
if (authority_end == uri.size()) {
return decomposed;
}
expected_token = lookahead_token;
break;
}
case token_type::path: {
auto token_begin = i;
for (; i < uri.size(); ++i) {
const auto c = uri[i];
if (c == '?') {
expected_token = token_type::query;
break;
}
if (c == '#') {
expected_token = token_type::fragment;
break;
}
if (!is_path_char(c) && c != '/') {
return std::nullopt;
}
}
decomposed.path_index = token_begin;
if (i >= uri.size()) {
decomposed.path = decomposed.raw.substr(token_begin);
return decomposed;
}
decomposed.path = decomposed.raw.substr(token_begin, i - token_begin);
break;
}
case token_type::query: {
auto token_begin = ++i;
for (; i < uri.size(); ++i) {
const auto c = uri[i];
if (c == '#') {
expected_token = token_type::fragment;
break;
}
if (!is_query_char(c)) {
return std::nullopt;
}
}
if (i > token_begin) {
decomposed.query_index = token_begin;
if (i >= uri.size()) {
decomposed.query = decomposed.raw.substr(token_begin);
return decomposed;
}
decomposed.query = decomposed.raw.substr(token_begin, i - token_begin);
}
break;
}
case token_type::fragment: {
auto token_begin = ++i;
for (; i < uri.size(); ++i) {
const auto c = uri[i];
if (!is_frag_char(c)) {
return std::nullopt;
}
}
if (i > token_begin) {
decomposed.fragment_index = token_begin;
decomposed.fragment = uri.substr(token_begin);
}
return decomposed;
}
case token_type::none:
default:
return std::nullopt;
}
}
return decomposed;
}
std::ostream &operator<<(std::ostream &o, const uri_decomposed &uri)
{
o << "Scheme : " << uri.scheme << '\n'
<< "Userinfo : " << uri.authority.userinfo << '\n'
<< "Host : " << uri.authority.host << '\n'
<< "Port : " << uri.authority.port << '\n'
<< "Path : " << uri.path << '\n'
<< "Query : " << uri.query << '\n'
<< "Fragment : " << uri.fragment << '\n';
return o;
}
}