41 return codepoint == 0x00ADU || codepoint == 0x061CU ||
42 (codepoint >= 0x200BU && codepoint <= 0x200FU) ||
43 codepoint == 0x2028U || codepoint == 0x2029U ||
44 (codepoint >= 0x202AU && codepoint <= 0x202EU) ||
45 (codepoint >= 0x2060U && codepoint <= 0x206FU) ||
46 codepoint == 0x3164U || codepoint == 0xFEFFU ||
52 std::string_view context) {
53 const auto fail = [&]() -> std::uint32_t {
54 throw std::runtime_error(std::string(context) +
55 " contains malformed UTF-8");
58 const auto first =
static_cast<unsigned char>(value[offset++]);
63 std::size_t continuation_count = 0;
64 std::uint32_t codepoint = 0;
65 std::uint32_t minimum = 0;
66 if (first >= 0xC2U && first <= 0xDFU) {
67 continuation_count = 1;
68 codepoint = first & 0x1FU;
70 }
else if (first >= 0xE0U && first <= 0xEFU) {
71 continuation_count = 2;
72 codepoint = first & 0x0FU;
74 }
else if (first >= 0xF0U && first <= 0xF4U) {
75 continuation_count = 3;
76 codepoint = first & 0x07U;
82 if (continuation_count > value.size() - offset) {
85 for (std::size_t index = 0; index < continuation_count; ++index) {
86 const auto next =
static_cast<unsigned char>(value[offset++]);
87 if ((next & 0xC0U) != 0x80U) {
90 codepoint = (codepoint << 6U) | (next & 0x3FU);
92 if (codepoint < minimum || codepoint > 0x10FFFFU ||
93 (codepoint >= 0xD800U && codepoint <= 0xDFFFU)) {
111 void validateUrl(std::string_view value, std::string_view context) {
112 const std::size_t separator = value.find(
"://");
113 if (separator == std::string_view::npos || separator == 0 ||
114 separator + 3 >= value.size()) {
115 throw std::runtime_error(std::string(context) +
116 " is not a valid URL");
118 const std::string_view scheme = value.substr(0, separator);
119 if (std::isalpha(
static_cast<unsigned char>(scheme.front())) == 0 ||
120 !std::all_of(scheme.begin() + 1, scheme.end(), [](
char value) {
121 const auto character = static_cast<unsigned char>(value);
122 return std::isalnum(character) != 0 || character ==
'+' ||
123 character ==
'-' || character ==
'.';
125 throw std::runtime_error(std::string(context) +
126 " has an invalid URL scheme");
128 constexpr std::array<std::string_view, 5> ALLOWED_SCHEMES{
129 "http",
"https",
"file",
"rtsp",
"rtmp"};
130 std::string lowered(scheme);
131 std::transform(lowered.begin(), lowered.end(), lowered.begin(),
132 [](
unsigned char character) {
133 return static_cast<char>(std::tolower(character));
135 if (std::find(ALLOWED_SCHEMES.begin(), ALLOWED_SCHEMES.end(),
136 lowered) == ALLOWED_SCHEMES.end()) {
137 throw std::runtime_error(std::string(context) +
138 " uses an unsupported URL scheme");
140 const std::string_view payload = value.substr(separator + 3);
141 if (std::any_of(payload.begin(), payload.end(), [](
char value) {
142 const auto character = static_cast<unsigned char>(value);
143 return character ==
' ' || character ==
'\\' ||
144 character ==
'<' || character ==
'>' ||
145 character ==
'"' || character ==
'{' ||
148 throw std::runtime_error(std::string(context) +
149 " contains an invalid URL character");
151 if (lowered !=
"file") {
152 const std::size_t authority_end = payload.find_first_of(
"/?#");
153 if (authority_end == 0) {
154 throw std::runtime_error(std::string(context) +
155 " is missing a URL host");
158 for (std::size_t index = 0; index < payload.size(); ++index) {
159 if (payload[index] !=
'%') {
162 if (index + 2 >= payload.size() ||
163 std::isxdigit(
static_cast<unsigned char>(
164 payload[index + 1])) == 0 ||
165 std::isxdigit(
static_cast<unsigned char>(
166 payload[index + 2])) == 0) {
167 throw std::runtime_error(std::string(context) +
168 " contains an invalid URL escape");