36 [[nodiscard]]
bool isUnsafeUnicodeControl(std::uint32_t codepoint) {
return codepoint == 0x00ADU || codepoint == 0x061CU || (codepoint >= 0x200BU && codepoint <= 0x200FU) || codepoint == 0x2028U || codepoint == 0x2029U || (codepoint >= 0x202AU && codepoint <= 0x202EU) || (codepoint >= 0x2060U && codepoint <= 0x206FU) || codepoint == 0x3164U || codepoint == 0xFEFFU ||
isUnicodeNoncharacter(codepoint); }
38 [[nodiscard]] std::uint32_t
decodeCodepoint(std::string_view value, std::size_t &offset, std::string_view context) {
39 const auto fail = [&]() -> std::uint32_t {
throw std::runtime_error(std::string(context) +
" contains malformed UTF-8"); };
41 const auto first =
static_cast<unsigned char>(value[offset++]);
46 std::size_t continuation_count = 0;
47 std::uint32_t codepoint = 0;
48 std::uint32_t minimum = 0;
49 if (first >= 0xC2U && first <= 0xDFU) {
50 continuation_count = 1;
51 codepoint = first & 0x1FU;
53 }
else if (first >= 0xE0U && first <= 0xEFU) {
54 continuation_count = 2;
55 codepoint = first & 0x0FU;
57 }
else if (first >= 0xF0U && first <= 0xF4U) {
58 continuation_count = 3;
59 codepoint = first & 0x07U;
65 if (continuation_count > value.size() - offset) {
68 for (std::size_t index = 0; index < continuation_count; ++index) {
69 const auto next =
static_cast<unsigned char>(value[offset++]);
70 if ((next & 0xC0U) != 0x80U) {
73 codepoint = (codepoint << 6U) | (next & 0x3FU);
75 if (codepoint < minimum || codepoint > 0x10FFFFU || (codepoint >= 0xD800U && codepoint <= 0xDFFFU)) {
88 void validateUrl(std::string_view value, std::string_view context) {
89 const std::size_t separator = value.find(
"://");
90 if (separator == std::string_view::npos || separator == 0 || separator + 3 >= value.size()) {
91 throw std::runtime_error(std::string(context) +
" is not a valid URL");
93 const std::string_view scheme = value.substr(0, separator);
94 if (std::isalpha(
static_cast<unsigned char>(scheme.front())) == 0 || !std::all_of(scheme.begin() + 1, scheme.end(), [](
char value) {
95 const auto character = static_cast<unsigned char>(value);
96 return std::isalnum(character) != 0 || character ==
'+' || character ==
'-' || character ==
'.';
98 throw std::runtime_error(std::string(context) +
" has an invalid URL scheme");
100 constexpr std::array<std::string_view, 5> ALLOWED_SCHEMES{
"http",
"https",
"file",
"rtsp",
"rtmp"};
101 std::string lowered(scheme);
102 std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](
unsigned char character) { return static_cast<char>(std::tolower(character)); });
103 if (std::find(ALLOWED_SCHEMES.begin(), ALLOWED_SCHEMES.end(), lowered) == ALLOWED_SCHEMES.end()) {
104 throw std::runtime_error(std::string(context) +
" uses an unsupported URL scheme");
106 const std::string_view payload = value.substr(separator + 3);
107 if (std::any_of(payload.begin(), payload.end(), [](
char value) {
108 const auto character = static_cast<unsigned char>(value);
109 return character ==
' ' || character ==
'\\' || character ==
'<' || character ==
'>' || character ==
'"' || character ==
'{' || character ==
'}';
111 throw std::runtime_error(std::string(context) +
" contains an invalid URL character");
113 if (lowered !=
"file") {
114 const std::size_t authority_end = payload.find_first_of(
"/?#");
115 if (authority_end == 0) {
116 throw std::runtime_error(std::string(context) +
" is missing a URL host");
119 for (std::size_t index = 0; index < payload.size(); ++index) {
120 if (payload[index] !=
'%') {
123 if (index + 2 >= payload.size() || std::isxdigit(
static_cast<unsigned char>(payload[index + 1])) == 0 || std::isxdigit(
static_cast<unsigned char>(payload[index + 2])) == 0) {
124 throw std::runtime_error(std::string(context) +
" contains an invalid URL escape");