20const std::map<const std::string, const uint16_t> special_scheme{
21 {
"file",
static_cast<uint16_t
>(NULL)},
22 {
"ftp",
static_cast<uint16_t
>(21)},
23 {
"http",
static_cast<uint16_t
>(80)},
24 {
"https",
static_cast<uint16_t
>(443)},
25 {
"ws",
static_cast<uint16_t
>(80)},
26 {
"wss",
static_cast<uint16_t
>(443)}};
28bool url_is_special(std::string_view a_str) {
29 return a_str ==
"ftp" || a_str ==
"file" || a_str ==
"http" ||
30 a_str ==
"https" || a_str ==
"ws" || a_str ==
"wss";
33bool is_in_userinfo_percent_encode_set(
const unsigned char a_chr) {
39 a_chr ==
' ' || a_chr ==
'"' || a_chr ==
'#' || a_chr ==
'<' ||
42 a_chr ==
'?' || a_chr ==
'`' || a_chr ==
'{' || a_chr ==
'}' ||
44 a_chr ==
'/' || a_chr ==
':' || a_chr ==
';' || a_chr ==
'=' ||
45 a_chr ==
'@' || (a_chr >=
'[' && a_chr <=
'^') || a_chr ==
'|';
48std::string UTF8_percent_encode(
const unsigned char a_chr) {
51 if (is_in_userinfo_percent_encode_set(a_chr)) {
52 std::ostringstream escaped;
54 escaped << std::uppercase << std::hex;
55 escaped <<
'%' << std::setw(2) << int(a_chr);
58 return std::string(
sizeof(a_chr), (
char)a_chr);
61std::string esc_url(std::string_view a_str) {
62 std::ostringstream escaped;
64 escaped << std::uppercase << std::hex;
66 for (
const char chr : a_str) {
67 if ((
unsigned char)chr <=
'\x1F' || (
unsigned char)chr >
'\x7E')
68 escaped <<
'%' << std::setw(2) << int((
unsigned char)chr);
70 escaped << (
unsigned char)chr;
87 std::streambuf* clog_old = std::clog.rdbuf();
89 std::ofstream clog_new(
"/dev/null");
90 std::clog.rdbuf(clog_new.rdbuf());
95 std::clog.rdbuf(clog_old);
100Url::operator std::string()
const {
return m_ser_url; }
104 this->clear_private();
107void Url::clear_private() {
110 m_input.reserve(m_given_url.size());
112 m_buffer.reserve(m_input.size() + 20);
122 m_port_num = (uint16_t)NULL;
126 m_atSignSeen =
false;
127 m_insideBrackets =
false;
128 m_passwordTokenSeen =
false;
131std::string Url::scheme()
const {
return m_scheme; }
133std::string Url::authority()
const {
return m_authority; }
135std::string Url::username()
const {
return m_username; }
137std::string Url::password()
const {
return m_password; }
139std::string Url::host()
const {
return m_host; }
141std::string Url::port()
const {
return m_port; }
143uint16_t Url::port_num()
const {
return m_port_num; }
145std::string Url::path()
const {
return m_path; }
147std::string Url::query()
const {
return m_query; }
149std::string Url::fragment()
const {
return m_fragment; }
152void Url::operator=(std::string_view a_given_url) {
154 m_given_url = a_given_url;
155 this->clear_private();
164 this->clean_and_copy_url_to_input();
166 m_state = STATE_SCHEME_START;
167 m_pointer = m_input.begin();
177 size_t guard = m_input.size() * 2 + 2;
179 std::clog <<
"DEBUG: guard = " << guard << std::endl;
184 for (; guard > 0; m_pointer++, guard--) {
185 if (m_state == STATE_NO_STATE)
189 case STATE_SCHEME_START:
190 this->fsm_scheme_start();
195 case STATE_NO_SCHEME:
196 this->fsm_no_scheme();
198 case STATE_PATH_OR_AUTHORITY:
199 this->fsm_path_or_authority();
201 case STATE_SPECIAL_AUTHORITY_SLASHES:
202 this->fsm_special_authority_slashes();
204 case STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES:
205 this->fsm_special_authority_ignore_slashes();
207 case STATE_AUTHORITY:
208 this->fsm_authority();
219 case STATE_SPECIAL_RELATIVE_OR_AUTHORITY:
220 this->fsm_special_relative_or_authority();
222 case STATE_PATH_START:
223 this->fsm_path_start();
228 case STATE_OPAQUE_PATH:
229 this->fsm_opaque_path();
240 throw std::out_of_range(
241 std::string((std::string)__FILE__ +
":" + std::to_string(__LINE__) +
242 ", Parsing URL " + __func__ +
243 ". State Machine doesn't finish regular."));
248void Url::clean_and_copy_url_to_input() {
250 std::clog <<
"DEBUG: Being on 'clean_and_copy_url_to_input'.\n";
256 auto it_leading = m_given_url.begin();
257 auto str_end = m_given_url.end();
258 while (it_leading < str_end && (
unsigned char)*it_leading <=
' ') {
259 if (it_leading >= str_end - 1)
266 auto it_trailing = m_given_url.end() - 1;
267 auto str_begin = m_given_url.begin();
268 while (it_trailing >= str_begin && (
unsigned char)*it_trailing <=
' ') {
269 if (it_trailing <= str_begin)
278 if ((
unsigned char)*it_leading >
' ') {
282 while (it_leading <= it_trailing) {
284 unsigned char c = (
unsigned char)*it_leading;
285 if (c ==
'\x0D' || c ==
'\x0A' || c ==
'\x09')
288 m_input.push_back((
char)std::tolower(c));
295 std::clog <<
"Warning: Removed " << invalid_chars
296 <<
" ASCII tab or newline character. Using \"" << m_input
297 <<
"\" now." << std::endl;
301void Url::fsm_scheme_start() {
303 std::clog <<
"DEBUG: Being on 'scheme_start_state' with \""
304 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
309 if (std::islower((
unsigned char)*m_pointer)) {
313 m_buffer.push_back((
char)std::tolower(*m_pointer));
315 m_state = STATE_SCHEME;
319 m_state = STATE_NO_SCHEME;
325void Url::fsm_scheme() {
327 std::clog <<
"DEBUG: Being on 'scheme state' with \""
328 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
331 const unsigned char c =
332 m_pointer < m_input.end() ? (
unsigned char)*m_pointer :
'\0';
337 isdigit(c) || c ==
'+' || c ==
'-' || c ==
'.')
341 m_buffer.push_back((
char)c);
343 }
else if (c ==
':') {
348 if (m_scheme ==
"file") {
349 if (m_pointer + 2 >= m_input.end() || *(m_pointer + 1) !=
'/' ||
350 *(m_pointer + 2) !=
'/')
351 std::clog <<
"Warning: 'file' scheme misses \"//\", ignoring."
353 m_state = STATE_FILE;
355 }
else if (url_is_special(m_scheme) && m_ser_base_url !=
"") {
356 m_state = STATE_SPECIAL_RELATIVE_OR_AUTHORITY;
358 }
else if (url_is_special(m_scheme)) {
359 m_state = STATE_SPECIAL_AUTHORITY_SLASHES;
361 }
else if (m_pointer + 1 < m_input.end() && *(m_pointer + 1) ==
'/') {
362 m_state = STATE_PATH_OR_AUTHORITY;
367 m_state = STATE_OPAQUE_PATH;
373 m_state = STATE_NO_SCHEME;
378void Url::fsm_no_scheme() {
380 std::clog <<
"DEBUG: Being on 'no_scheme_state' with input \"" << m_input
383 std::clog <<
"Error: no valid scheme found." << std::endl;
384 throw std::invalid_argument(
"Invalid URL: '" + esc_url(m_input) +
"'");
386 m_state = STATE_NO_STATE;
390void Url::fsm_special_relative_or_authority() {
392 std::clog <<
"DEBUG: Being on 'special_relative_or_authority_state' with \""
393 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
396 m_state = STATE_NO_STATE;
400void Url::fsm_path_or_authority() {
402 std::clog <<
"DEBUG: Being on 'path_or_authority_state' with \""
403 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
406 if (*m_pointer ==
'/') {
407 m_state = STATE_AUTHORITY;
409 m_state = STATE_PATH;
415void Url::fsm_special_authority_slashes() {
417 std::clog <<
"DEBUG: Being on 'special_authority_slashes_state' with \""
418 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
421 if (m_pointer + 1 < m_input.end() && *m_pointer ==
'/' &&
422 *(m_pointer + 1) ==
'/') {
423 m_state = STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES;
426 std::clog <<
"Warning: no \"//\" before authority: ignoring. Found \""
427 << std::string(m_pointer, m_input.end()) <<
"\"" << std::endl;
428 m_state = STATE_SPECIAL_AUTHORITY_IGNORE_SLASHES;
434void Url::fsm_special_authority_ignore_slashes() {
437 <<
"DEBUG: Being on 'special_authority_ignore_slashes_state' with \""
438 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
441 if (*m_pointer !=
'/' && *m_pointer !=
'\\') {
442 m_state = STATE_AUTHORITY;
445 std::clog <<
"Warning: '/' or '\\' not expected on authority: "
447 << std::string(m_pointer, m_input.end()) <<
"\"" << std::endl;
452void Url::fsm_authority() {
454 std::clog <<
"DEBUG: Being on 'authority_state' with \""
455 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
458 const unsigned char c =
459 m_pointer < m_input.end() ? (
unsigned char)*m_pointer :
'\0';
463 std::clog <<
"Status: '@' found for userinfo." << std::endl;
465 m_buffer.append(
"%40");
469 for (
auto& cp : m_buffer) {
470 if (cp ==
':' && !m_passwordTokenSeen) {
471 m_passwordTokenSeen =
true;
474 std::string encodedCodePoints =
475 UTF8_percent_encode((
unsigned char)cp);
476 if (m_passwordTokenSeen)
477 m_password += encodedCodePoints;
479 m_username += encodedCodePoints;
483 }
else if (m_pointer >= m_input.end() || c ==
'/' || c ==
'?' || c ==
'#' ||
484 (url_is_special(m_scheme) && c ==
'\\')) {
486 if (m_atSignSeen && m_buffer ==
"") {
487 std::clog <<
"Error: no valid authority." << std::endl;
488 throw std::invalid_argument(
"Invalid authority: '" + m_input +
"'");
490 m_pointer = m_pointer - (
long int)m_buffer.length() - 1;
492 m_state = STATE_HOST;
496 m_buffer.push_back((
char)c);
501void Url::fsm_host() {
503 std::clog <<
"DEBUG: Being on 'host_state' with \""
504 << std::string_view(m_pointer, m_input.end()) <<
"\", "
505 <<
"username = \"" << m_username <<
"\", password = \""
506 << m_password <<
"\"\n";
509 const unsigned char c =
510 m_pointer < m_input.end() ? (
unsigned char)*m_pointer :
'\0';
512 if (c ==
':' && !m_insideBrackets) {
514 if (m_buffer.empty()) {
515 std::clog <<
"Error: no valid hostname found." << std::endl;
516 throw std::invalid_argument(
"Invalid hostname: '" +
517 esc_url(m_input) +
"'");
522 m_host =
"dummy1.host.state";
524 m_state = STATE_PORT;
526 }
else if (c ==
'\0' || c ==
'/' || c ==
'?' || c ==
'#' ||
527 (url_is_special(m_scheme) && c ==
'\\')) {
531 if (url_is_special(m_scheme) && m_buffer.empty()) {
532 std::clog <<
"Error: no valid host found." << std::endl;
533 throw std::invalid_argument(
"Invalid hostname: '" +
534 esc_url(m_input) +
"'");
539 m_host =
"dummy2.host.state";
541 m_state = STATE_PATH_START;
545 m_insideBrackets =
true;
547 m_insideBrackets =
false;
549 m_buffer.push_back((
char)c);
554void Url::fsm_port() {
556 std::clog <<
"DEBUG: Being on 'port_state' with \""
557 << std::string_view(m_pointer, m_input.end()) <<
"\", "
558 <<
"host = \"" << m_host <<
"\"\n";
561 const unsigned char c =
562 m_pointer < m_input.end() ? (
unsigned char)*m_pointer :
'\0';
564 if (std::isdigit(c)) {
565 m_buffer.push_back((
char)c);
567 }
else if (c ==
'\0' || c ==
'/' || c ==
'?' || c ==
'#' ||
568 (url_is_special(m_scheme) && c ==
'\\')) {
570 if (!m_buffer.empty()) {
573 long unsigned int port_tmp{};
578 port_tmp = std::stoul(m_buffer);
580 }
catch (std::out_of_range& e) {
581 std::clog <<
"Error: " << e.what()
582 <<
". Port number out of range." << std::endl;
585 if (port_tmp > 65535) {
586 throw std::out_of_range(std::string(
587 (std::string)__FILE__ +
":" + std::to_string(__LINE__) +
588 ", Parsing port " + __func__ +
". Error: Port number " +
589 std::to_string(port_tmp) +
" is out of range."));
591 port = (uint16_t)port_tmp;
595 auto it = special_scheme.find(m_scheme);
596 if (it != special_scheme.end() && it->second == port) {
597 m_port_num = (uint16_t)NULL;
606 m_state = STATE_PATH_START;
610 std::clog <<
"Error: no valid port found." << std::endl;
611 throw std::invalid_argument(
"Invalid port: '" + esc_url(m_input) +
"'");
616void Url::fsm_file() {
618 std::clog <<
"DEBUG: Being on 'file_state' with \""
619 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
621 m_state = STATE_NO_STATE;
625void Url::fsm_path_start() {
627 std::clog <<
"DEBUG: Being on 'path_start_state' with \""
628 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
630 m_state = STATE_NO_STATE;
634void Url::fsm_path() {
636 std::clog <<
"DEBUG: Being on 'path_state' with \""
637 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
639 m_state = STATE_NO_STATE;
643void Url::fsm_opaque_path() {
645 std::clog <<
"DEBUG: Being on 'opaque_path_state' with \""
646 << std::string_view(m_pointer, m_input.end()) <<
"\"\n";
648 m_state = STATE_NO_STATE;
Reengineered Object Oriented UPnP+ program code.
Declaration of the 'class Url'. Not usable, work in progess.