LCOV - code coverage report
Current view: top level - src/impl - url_parser.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 243 249 97.6 %
Date: 2026-06-27 19:56:04 Functions: 27 27 100.0 %
Branches: 361 554 65.2 %

           Branch data     Line data    Source code
       1                 :            : /*
       2                 :            :  * Copyright (C) 2026 by Thun Lu. All rights reserved.
       3                 :            :  * Author: Thun Lu <thun.lu@zohomail.cn>
       4                 :            :  * Repo:   https://github.com/thun-res/vlink
       5                 :            :  *  _    __   __      _           __
       6                 :            :  * | |  / /  / /     (_) ____    / /__
       7                 :            :  * | | / /  / /     / / / __ \  / //_/
       8                 :            :  * | |/ /  / /___  / / / / / / / ,<
       9                 :            :  * |___/  /_____/ /_/ /_/ /_/ /_/|_|
      10                 :            :  *
      11                 :            :  * Licensed under the Apache License, Version 2.0 (the "License");
      12                 :            :  * you may not use this file except in compliance with the License.
      13                 :            :  * You may obtain a copy of the License at
      14                 :            :  *
      15                 :            :  *     http://www.apache.org/licenses/LICENSE-2.0
      16                 :            :  *
      17                 :            :  * Unless required by applicable law or agreed to in writing, software
      18                 :            :  * distributed under the License is distributed on an "AS IS" BASIS,
      19                 :            :  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      20                 :            :  * See the License for the specific language governing permissions and
      21                 :            :  * limitations under the License.
      22                 :            :  */
      23                 :            : 
      24                 :            : #include "./impl/url_parser.h"
      25                 :            : 
      26                 :            : #include <exception>
      27                 :            : #include <map>
      28                 :            : #include <string>
      29                 :            : #include <utility>
      30                 :            : 
      31                 :            : #include "./base/exception.h"
      32                 :            : 
      33                 :            : namespace vlink {
      34                 :            : 
      35                 :          8 : [[maybe_unused]] static int64_t parse_port_value(const std::string& port_str) {
      36                 :            :   try {
      37         [ +  - ]:          8 :     auto parsed = std::stoul(port_str);
      38                 :            : 
      39         [ +  + ]:          8 :     if VUNLIKELY (parsed > 65535U) {
      40   [ +  -  +  -  :          1 :       throw Exception::RuntimeError("Port out of range (must be 0-65535): \"" + port_str + "\".");
                   +  - ]
      41                 :            :     }
      42                 :            : 
      43                 :          7 :     return static_cast<int64_t>(parsed);
      44         [ -  + ]:          1 :   } catch (const std::exception&) {
      45   [ +  -  +  -  :          1 :     throw Exception::RuntimeError("Invalid port \"" + port_str + "\".");
                   +  - ]
      46                 :          1 :   }
      47                 :            : }
      48                 :            : 
      49                 :            : // UrlParser
      50                 :         52 : UrlParser::UrlParser(const char* str, Category category, Separator separator)
      51                 :         52 :     : category_(category), separator_(separator) {
      52   [ +  -  +  + ]:         70 :   setup(std::string(str), category);
      53                 :        124 : }
      54                 :            : 
      55                 :        576 : UrlParser::UrlParser(const std::string& str, Category category, Separator separator)
      56                 :        576 :     : category_(category), separator_(separator) {
      57         [ +  + ]:        576 :   setup(str, category);
      58                 :        600 : }
      59                 :            : 
      60                 :         10 : UrlParser::UrlParser(const std::map<Component, std::string>& components, Category category, bool rooted,
      61                 :         10 :                      Separator separator)
      62                 :         10 :     : category_(category), is_rooted_(rooted), separator_(separator) {
      63   [ +  -  +  - ]:         10 :   if VLIKELY (components.count(Component::kTransport) != 0) {
      64   [ +  -  +  + ]:         10 :     if VUNLIKELY (components.at(Component::kTransport).empty()) {
      65         [ +  - ]:          1 :       throw Exception::RuntimeError("The URL transport prefix cannot be empty.");
      66                 :            :     }
      67                 :            : 
      68   [ +  -  +  - ]:          9 :     transport_ = components.at(Component::kTransport);
      69                 :            :   } else {
      70         [ #  # ]:          0 :     throw Exception::RuntimeError("A URL must include a transport prefix.");
      71                 :            :   }
      72                 :            : 
      73         [ +  + ]:          9 :   if (category == Category::kHierarchical) {
      74   [ +  -  +  + ]:          8 :     if VUNLIKELY (components.count(Component::kContent) != 0) {
      75         [ +  - ]:          1 :       throw Exception::RuntimeError("The content component is only valid for non-hierarchical URLs.");
      76                 :            :     }
      77                 :            : 
      78         [ +  - ]:          7 :     bool has_username = components.count(Component::kUsername) != 0;
      79         [ +  - ]:          7 :     bool has_password = components.count(Component::kPassword) != 0;
      80                 :            : 
      81   [ +  +  +  + ]:          7 :     if (has_username && has_password) {
      82   [ +  -  +  - ]:          1 :       username_ = components.at(Component::kUsername);
      83   [ +  -  +  - ]:          1 :       password_ = components.at(Component::kPassword);
      84   [ +  +  +  -  :          6 :     } else if VUNLIKELY ((has_username && !has_password) || (!has_username && has_password)) {
          +  +  +  -  -  
             +  -  +  +  
                      + ]
      85         [ +  - ]:          1 :       throw Exception::RuntimeError("If a username or password is supplied, both must be provided.");
      86                 :            :     }
      87                 :            : 
      88   [ +  -  +  - ]:          6 :     if (components.count(Component::kHost) != 0) {
      89   [ +  -  +  - ]:          6 :       host_ = components.at(Component::kHost);
      90                 :            :     }
      91                 :            : 
      92   [ +  -  +  + ]:          6 :     if (components.count(Component::kPort) != 0) {
      93   [ +  -  +  - ]:          1 :       port_ = parse_port_value(components.at(Component::kPort));
      94                 :            :     }
      95                 :            : 
      96   [ +  -  +  + ]:          6 :     if VLIKELY (components.count(Component::kPath) != 0) {
      97   [ +  -  +  - ]:          5 :       path_ = components.at(Component::kPath);
      98                 :            :     } else {
      99         [ +  - ]:          1 :       throw Exception::RuntimeError("A path is required on a hierarchical URL, even an empty path.");
     100                 :            :     }
     101                 :            : 
     102                 :            :   } else {
     103   [ +  -  +  -  :          1 :     if VUNLIKELY (components.count(Component::kUsername) != 0 || components.count(Component::kPassword) != 0 ||
          +  -  -  +  +  
          -  +  -  -  +  
          +  -  +  -  -  
          +  +  -  +  -  
             -  +  -  + ]
     104                 :            :                   components.count(Component::kHost) != 0 || components.count(Component::kPort) != 0 ||
     105                 :            :                   components.count(Component::kPath) != 0) {
     106         [ #  # ]:          0 :       throw Exception::RuntimeError("None of the hierarchical components are allowed in a non-hierarchical URL.");
     107                 :            :     }
     108                 :            : 
     109   [ +  -  +  - ]:          1 :     if VLIKELY (components.count(Component::kContent) != 0) {
     110   [ +  -  +  - ]:          1 :       content_ = components.at(Component::kContent);
     111                 :            :     } else {
     112         [ #  # ]:          0 :       throw Exception::RuntimeError("A non-hierarchical URL requires a content component, even if it is empty.");
     113                 :            :     }
     114                 :            :   }
     115                 :            : 
     116   [ +  -  +  + ]:          6 :   if (components.count(Component::kQuery) != 0) {
     117   [ +  -  +  - ]:          1 :     query_ = components.at(Component::kQuery);
     118                 :            :   }
     119                 :            : 
     120   [ +  -  +  + ]:          6 :   if (components.count(Component::kFragment) != 0) {
     121   [ +  -  +  - ]:          1 :     fragment_ = components.at(Component::kFragment);
     122                 :            :   }
     123                 :            : 
     124         [ +  - ]:          6 :   init_query_dictionary();
     125                 :         42 : }
     126                 :            : 
     127                 :          4 : UrlParser::UrlParser(const UrlParser& other, const std::map<Component, std::string>& replacements)
     128                 :          4 :     : category_(other.category_), is_rooted_(other.is_rooted_), separator_(other.separator_) {
     129   [ +  -  +  +  :          4 :   transport_ = (replacements.count(Component::kTransport)) ? replacements.at(Component::kTransport) : other.transport_;
             +  -  +  - ]
     130                 :            : 
     131         [ +  - ]:          4 :   if (category_ == Category::kHierarchical) {
     132   [ +  -  -  +  :          4 :     username_ = (replacements.count(Component::kUsername)) ? replacements.at(Component::kUsername) : other.username_;
             -  -  +  - ]
     133   [ +  -  -  +  :          4 :     password_ = (replacements.count(Component::kPassword)) ? replacements.at(Component::kPassword) : other.password_;
             -  -  +  - ]
     134   [ +  -  +  +  :          4 :     host_ = (replacements.count(Component::kHost)) ? replacements.at(Component::kHost) : other.host_;
             +  -  +  - ]
     135   [ +  -  -  +  :          4 :     port_ = (replacements.count(Component::kPort)) ? parse_port_value(replacements.at(Component::kPort)) : other.port_;
             -  -  -  - ]
     136   [ +  -  -  +  :          4 :     path_ = (replacements.count(Component::kPath)) ? replacements.at(Component::kPath) : other.path_;
             -  -  +  - ]
     137                 :            :   } else {
     138   [ #  #  #  #  :          0 :     content_ = (replacements.count(Component::kContent)) ? replacements.at(Component::kContent) : other.content_;
             #  #  #  # ]
     139                 :            :   }
     140                 :            : 
     141   [ +  -  +  +  :          4 :   query_ = (replacements.count(Component::kQuery)) ? replacements.at(Component::kQuery) : other.query_;
             +  -  +  - ]
     142   [ +  -  +  +  :          4 :   fragment_ = (replacements.count(Component::kFragment)) ? replacements.at(Component::kFragment) : other.fragment_;
             +  -  +  - ]
     143                 :            : 
     144         [ +  - ]:          4 :   init_query_dictionary();
     145                 :          4 : }
     146                 :            : 
     147                 :        586 : const std::string& UrlParser::get_transport() const { return transport_; }
     148                 :            : 
     149                 :          5 : UrlParser::Category UrlParser::get_category() const { return category_; }
     150                 :            : 
     151                 :          4 : const std::string& UrlParser::get_content() const {
     152         [ +  + ]:          4 :   if VUNLIKELY (category_ != Category::kNonHierarchical) {
     153         [ +  - ]:          1 :     throw Exception::RuntimeError("The content Component is only valid for non-hierarchical URLs.");
     154                 :            :   }
     155                 :            : 
     156                 :          3 :   return content_;
     157                 :            : }
     158                 :            : 
     159                 :          5 : const std::string& UrlParser::get_username() const {
     160         [ +  + ]:          5 :   if VUNLIKELY (category_ != Category::kHierarchical) {
     161         [ +  - ]:          1 :     throw Exception::RuntimeError("The username Component is only valid for hierarchical URLs.");
     162                 :            :   }
     163                 :            : 
     164                 :          4 :   return username_;
     165                 :            : }
     166                 :            : 
     167                 :          5 : const std::string& UrlParser::get_password() const {
     168         [ +  + ]:          5 :   if VUNLIKELY (category_ != Category::kHierarchical) {
     169         [ +  - ]:          1 :     throw Exception::RuntimeError("The password Component is only valid for hierarchical URLs.");
     170                 :            :   }
     171                 :            : 
     172                 :          4 :   return password_;
     173                 :            : }
     174                 :            : 
     175                 :        589 : const std::string& UrlParser::get_host() const {
     176         [ +  + ]:        589 :   if VUNLIKELY (category_ != Category::kHierarchical) {
     177         [ +  - ]:          1 :     throw Exception::RuntimeError("The host Component is only valid for hierarchical URLs.");
     178                 :            :   }
     179                 :            : 
     180                 :        588 :   return host_;
     181                 :            : }
     182                 :            : 
     183                 :          9 : int64_t UrlParser::get_port() const {
     184         [ +  + ]:          9 :   if VUNLIKELY (category_ != Category::kHierarchical) {
     185         [ +  - ]:          1 :     throw Exception::RuntimeError("The port Component is only valid for hierarchical URLs.");
     186                 :            :   }
     187                 :            : 
     188                 :          8 :   return port_;
     189                 :            : }
     190                 :            : 
     191                 :        584 : const std::string& UrlParser::get_path() const {
     192         [ +  + ]:        584 :   if VUNLIKELY (category_ != Category::kHierarchical) {
     193         [ +  - ]:          1 :     throw Exception::RuntimeError("The path Component is only valid for hierarchical URLs.");
     194                 :            :   }
     195                 :            : 
     196                 :        583 :   return path_;
     197                 :            : }
     198                 :            : 
     199                 :          3 : const std::string& UrlParser::get_query() const { return query_; }
     200                 :            : 
     201                 :        585 : const std::map<std::string, std::string>& UrlParser::get_query_dictionary() const { return query_dict_; }
     202                 :            : 
     203                 :        578 : const std::string& UrlParser::get_fragment() const { return fragment_; }
     204                 :            : 
     205                 :          7 : std::string UrlParser::to_string() const {
     206                 :          7 :   std::string full_url;
     207         [ +  - ]:          7 :   full_url.append(transport_);
     208         [ +  - ]:          7 :   full_url.append(":");
     209                 :            : 
     210         [ +  + ]:          7 :   if (category_ == Category::kNonHierarchical) {
     211         [ +  - ]:          1 :     full_url.append(content_);
     212         [ +  - ]:          6 :   } else if (content_.length() > path_.length()) {
     213         [ +  - ]:          6 :     full_url.append("//");
     214                 :            : 
     215   [ +  +  +  -  :          6 :     if (!(username_.empty() || password_.empty())) {
                   +  + ]
     216         [ +  - ]:          1 :       full_url.append(username_);
     217         [ +  - ]:          1 :       full_url.append(":");
     218         [ +  - ]:          1 :       full_url.append(password_);
     219         [ +  - ]:          1 :       full_url.append("@");
     220                 :            :     }
     221                 :            : 
     222         [ +  - ]:          6 :     full_url.append(host_);
     223                 :            : 
     224         [ +  + ]:          6 :     if (port_ != 0) {
     225         [ +  - ]:          1 :       full_url.append(":");
     226   [ +  -  +  - ]:          1 :       full_url.append(std::to_string(port_));
     227                 :            :     }
     228                 :            :   }
     229                 :            : 
     230         [ +  + ]:          7 :   if (is_rooted_) {
     231         [ +  - ]:          5 :     full_url.append("/");
     232                 :            :   }
     233                 :            : 
     234         [ +  - ]:          7 :   full_url.append(path_);
     235                 :            : 
     236         [ +  + ]:          7 :   if (!query_.empty()) {
     237         [ +  - ]:          1 :     full_url.append("?");
     238         [ +  - ]:          1 :     full_url.append(query_);
     239                 :            :   }
     240                 :            : 
     241         [ +  + ]:          7 :   if (!fragment_.empty()) {
     242         [ +  - ]:          1 :     full_url.append("#");
     243         [ +  - ]:          1 :     full_url.append(fragment_);
     244                 :            :   }
     245                 :            : 
     246                 :          7 :   return full_url;
     247                 :            : }  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     248                 :            : 
     249                 :        628 : void UrlParser::setup(const std::string& str, Category category) {
     250                 :            :   (void)category;
     251                 :            : 
     252                 :        628 :   size_t const url_length = str.length();
     253                 :            : 
     254         [ +  + ]:        628 :   if VUNLIKELY (url_length == 0) {
     255         [ +  - ]:          1 :     throw Exception::RuntimeError("URLs cannot be of zero length.");
     256                 :            :   }
     257                 :            : 
     258         [ +  + ]:        627 :   std::string::const_iterator cursor = parse_transport(str, str.begin());
     259         [ +  + ]:        621 :   cursor = parse_content(str, (cursor + 1));
     260                 :            : 
     261   [ +  +  +  +  :        617 :   if ((cursor != str.end()) && (*cursor == '?')) {
                   +  + ]
     262         [ +  - ]:        201 :     cursor = parse_query(str, (cursor + 1));
     263                 :            :   }
     264                 :            : 
     265   [ +  +  +  -  :        617 :   if ((cursor != str.end()) && (*cursor == '#')) {
                   +  + ]
     266         [ +  - ]:         16 :     cursor = parse_fragment(str, (cursor + 1));
     267                 :            :   }
     268                 :            : 
     269         [ +  + ]:        617 :   init_query_dictionary();
     270                 :        616 : }
     271                 :            : 
     272                 :        627 : std::string::const_iterator UrlParser::parse_transport(const std::string& str,
     273                 :            :                                                        std::string::const_iterator transport_start) {
     274                 :        627 :   std::string::const_iterator transport_end = transport_start;
     275                 :            : 
     276   [ +  +  +  +  :       3172 :   while ((transport_end != str.end()) && (*transport_end != ':')) {
                   +  + ]
     277   [ +  +  +  +  :       2547 :     if VUNLIKELY (!std::isalnum(*transport_end) && (*transport_end != '-') && (*transport_end != '+') &&
          +  +  +  -  +  
             +  +  -  +  
                      + ]
     278                 :            :                   (*transport_end != '.')) {
     279   [ +  -  +  -  :          2 :       throw Exception::RuntimeError("Invalid character found in the URL transport prefix. Supplied URL was: \"" + str +
                   +  - ]
     280                 :          4 :                                     "\".");
     281                 :            :     }
     282                 :            : 
     283                 :       2545 :     ++transport_end;
     284                 :            :   }
     285                 :            : 
     286         [ +  + ]:        625 :   if VUNLIKELY (transport_end == str.end()) {
     287   [ +  -  +  -  :          3 :     throw Exception::RuntimeError("End of URL found while parsing the transport prefix. Supplied URL was: \"" + str +
                   +  - ]
     288                 :          6 :                                   "\".");
     289                 :            :   }
     290                 :            : 
     291         [ +  + ]:        622 :   if VUNLIKELY (transport_start == transport_end) {
     292   [ +  -  +  -  :          1 :     throw Exception::RuntimeError("The URL transport prefix cannot be zero-length. Supplied URL was: \"" + str + "\".");
                   +  - ]
     293                 :            :   }
     294                 :            : 
     295         [ +  - ]:        621 :   transport_ = std::string(transport_start, transport_end);
     296                 :            : 
     297                 :        621 :   return transport_end;
     298                 :            : }
     299                 :            : 
     300                 :        621 : std::string::const_iterator UrlParser::parse_content(const std::string& str,
     301                 :            :                                                      std::string::const_iterator content_start) {
     302                 :        621 :   std::string::const_iterator content_end = content_start;
     303                 :            : 
     304   [ +  +  +  +  :      10759 :   while ((content_end != str.end()) && (*content_end != '?') && (*content_end != '#')) {
             +  +  +  + ]
     305                 :      10138 :     ++content_end;
     306                 :            :   }
     307                 :            : 
     308         [ +  - ]:        621 :   content_ = std::string(content_start, content_end);
     309                 :            : 
     310   [ +  +  +  -  :        621 :   if ((category_ == Category::kHierarchical) && (!content_.empty())) {
                   +  + ]
     311                 :        616 :     std::string::const_iterator path_start = content_.begin();
     312                 :        616 :     std::string::const_iterator path_end = content_.end();
     313                 :            : 
     314   [ +  -  +  + ]:        616 :     if (!content_.compare(0, 2, "//")) {
     315                 :        615 :       std::string::const_iterator authority_cursor = (content_.begin() + 2);
     316                 :            : 
     317         [ +  + ]:        615 :       if (content_.find_first_of('@') != std::string::npos) {
     318         [ +  + ]:          4 :         std::string::const_iterator userpass_divider = parse_username(str, content_, authority_cursor);
     319         [ +  - ]:          3 :         authority_cursor = parse_password(str, content_, (userpass_divider + 1));
     320                 :          3 :         ++authority_cursor;
     321                 :            :       }
     322                 :            : 
     323         [ +  + ]:        614 :       authority_cursor = parse_host(str, content_, authority_cursor);
     324                 :            : 
     325   [ +  +  +  +  :        613 :       if ((authority_cursor != content_.end()) && (*authority_cursor == ':')) {
                   +  + ]
     326         [ +  + ]:          8 :         authority_cursor = parse_port(str, content_, (authority_cursor + 1));
     327                 :            :       }
     328                 :            : 
     329   [ +  +  +  -  :        611 :       if ((authority_cursor != content_.end()) && (*authority_cursor == '/')) {
                   +  + ]
     330                 :        376 :         is_rooted_ = true;
     331                 :        376 :         path_start = authority_cursor + 1;
     332                 :            :       }
     333                 :            : 
     334         [ +  + ]:        611 :       if (authority_cursor == content_.end()) {
     335                 :        235 :         path_start = content_.end();
     336                 :            :       }
     337   [ +  -  +  - ]:          1 :     } else if (!content_.compare(0, 1, "/")) {
     338                 :          1 :       is_rooted_ = true;
     339                 :          1 :       ++path_start;
     340                 :            :     }
     341                 :            : 
     342         [ +  - ]:        612 :     path_ = std::string(path_start, path_end);
     343                 :            :   }
     344                 :            : 
     345                 :        617 :   return content_end;
     346                 :            : }
     347                 :            : 
     348                 :          4 : std::string::const_iterator UrlParser::parse_username(const std::string& str, const std::string& content,
     349                 :            :                                                       std::string::const_iterator username_start) {
     350                 :            :   (void)content;
     351                 :            : 
     352                 :          4 :   std::string::const_iterator username_end = username_start;
     353                 :            : 
     354   [ +  -  +  +  :         18 :   while (username_end != content.end() && *username_end != ':') {
                   +  + ]
     355         [ +  + ]:         15 :     if VUNLIKELY (*username_end == '@') {
     356         [ +  - ]:          1 :       throw Exception::RuntimeError(
     357   [ +  -  +  - ]:          2 :           "A username in the authority must be followed by ':password'. Supplied URL was: \"" + str + "\".");
     358                 :            :     }
     359                 :            : 
     360                 :         14 :     ++username_end;
     361                 :            :   }
     362                 :            : 
     363         [ -  + ]:          3 :   if VUNLIKELY (username_end == content.end()) {
     364         [ #  # ]:          0 :     throw Exception::RuntimeError(
     365                 :            :         "A username in the authority must be followed by ':password'. Supplied URL was: \"" +  // LCOV_EXCL_LINE
     366                 :            :                                                                                                // GCOVR_EXCL_LINE
     367                 :            :         str + "\".");  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     368                 :            :   }
     369                 :            : 
     370         [ +  - ]:          3 :   username_ = std::string(username_start, username_end);
     371                 :            : 
     372                 :          3 :   return username_end;
     373                 :            : }
     374                 :            : 
     375                 :          3 : std::string::const_iterator UrlParser::parse_password(const std::string& str, const std::string& content,
     376                 :            :                                                       std::string::const_iterator password_start) {
     377                 :            :   (void)content;
     378                 :            : 
     379                 :          3 :   std::string::const_iterator password_end = password_start;
     380                 :            : 
     381   [ +  -  +  +  :         14 :   while (password_end != content.end() && *password_end != '@') {
                   +  + ]
     382                 :         11 :     ++password_end;
     383                 :            :   }
     384                 :            : 
     385         [ -  + ]:          3 :   if VUNLIKELY (password_end == content.end()) {
     386         [ #  # ]:          0 :     throw Exception::RuntimeError(
     387                 :            :         "A password in the authority must be followed by '@'. Supplied URL was: \"" +  // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     388                 :            :         str +                                                                          // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     389                 :            :         "\".");                                                                        // LCOV_EXCL_LINE GCOVR_EXCL_LINE
     390                 :            :   }
     391                 :            : 
     392         [ +  - ]:          3 :   password_ = std::string(password_start, password_end);
     393                 :            : 
     394                 :          3 :   return password_end;
     395                 :            : }
     396                 :            : 
     397                 :        614 : std::string::const_iterator UrlParser::parse_host(const std::string& str, const std::string& content,
     398                 :            :                                                   std::string::const_iterator host_start) {
     399                 :        614 :   std::string::const_iterator host_end = host_start;
     400                 :            : 
     401         [ +  + ]:       4654 :   while (host_end != content.end()) {
     402         [ +  + ]:       4420 :     if (*host_end == '[') {
     403   [ +  +  +  +  :         15 :       while ((host_end != content.end()) && (*host_end != ']')) {
                   +  + ]
     404                 :         13 :         ++host_end;
     405                 :            :       }
     406                 :            : 
     407         [ +  + ]:          2 :       if VUNLIKELY (host_end == content.end()) {
     408         [ +  - ]:          1 :         throw Exception::RuntimeError(
     409                 :            :             "End of content Component encountered "
     410                 :            :             "while parsing the host Component. "
     411         [ +  - ]:          1 :             "Supplied URL was: \"" +
     412         [ +  - ]:          3 :             str + "\".");
     413                 :            :       }
     414                 :            : 
     415                 :          1 :       ++host_end;
     416                 :            : 
     417                 :          1 :       break;
     418                 :            :     }
     419                 :            : 
     420   [ +  +  +  +  :       4418 :     if ((*host_end == ':') || (*host_end == '/')) {
                   +  + ]
     421                 :        378 :       break;
     422                 :            :     }
     423                 :            : 
     424                 :       4040 :     ++host_end;
     425                 :            :   }
     426                 :            : 
     427         [ +  - ]:        613 :   host_ = std::string(host_start, host_end);
     428                 :        613 :   return host_end;
     429                 :            : }
     430                 :            : 
     431                 :          8 : std::string::const_iterator UrlParser::parse_port(const std::string& str, const std::string& content,
     432                 :            :                                                   std::string::const_iterator port_start) {
     433                 :          8 :   std::string::const_iterator port_end = port_start;
     434                 :            : 
     435   [ +  +  +  +  :         38 :   while ((port_end != content.end()) && (*port_end != '/')) {
                   +  + ]
     436         [ +  + ]:         31 :     if VUNLIKELY (!std::isdigit(*port_end)) {
     437         [ +  - ]:          1 :       throw Exception::RuntimeError(
     438                 :            :           "Invalid character while parsing the port. "
     439         [ +  - ]:          1 :           "Supplied URL was: \"" +
     440         [ +  - ]:          3 :           str + "\".");
     441                 :            :     }
     442                 :            : 
     443                 :         30 :     ++port_end;
     444                 :            :   }
     445                 :            : 
     446         [ +  - ]:          7 :   if (port_start != port_end) {
     447   [ +  -  +  + ]:          9 :     port_ = parse_port_value(std::string(port_start, port_end));
     448                 :            :   }
     449                 :            : 
     450                 :          6 :   return port_end;
     451                 :            : }
     452                 :            : 
     453                 :        201 : std::string::const_iterator UrlParser::parse_query(const std::string& str, std::string::const_iterator query_start) {
     454                 :        201 :   std::string::const_iterator query_end = query_start;
     455                 :            : 
     456   [ +  +  +  +  :       2846 :   while ((query_end != str.end()) && (*query_end != '#')) {
                   +  + ]
     457                 :       2645 :     ++query_end;
     458                 :            :   }
     459                 :            : 
     460         [ +  - ]:        201 :   query_ = std::string(query_start, query_end);
     461                 :            : 
     462                 :        201 :   return query_end;
     463                 :            : }
     464                 :            : 
     465                 :         16 : std::string::const_iterator UrlParser::parse_fragment(const std::string& str,
     466                 :            :                                                       std::string::const_iterator fragment_start) {
     467         [ +  - ]:         16 :   fragment_ = std::string(fragment_start, str.end());
     468                 :            : 
     469                 :         16 :   return str.end();
     470                 :            : }
     471                 :            : 
     472                 :        627 : void UrlParser::init_query_dictionary() {
     473         [ +  + ]:        627 :   if (!query_.empty()) {
     474         [ +  + ]:        203 :     char separator = (separator_ == Separator::kAmpersand) ? '&' : ';';
     475                 :        203 :     size_t carat = 0;
     476                 :        203 :     size_t stanza_end = query_.find_first_of(separator);
     477                 :            : 
     478                 :            :     do {
     479                 :            :       std::string stanza =
     480   [ +  +  +  - ]:        252 :           query_.substr(carat, ((stanza_end != std::string::npos) ? (stanza_end - carat) : std::string::npos));
     481                 :        252 :       size_t key_value_divider = stanza.find_first_of('=');
     482         [ +  - ]:        252 :       std::string key = stanza.substr(0, key_value_divider);
     483                 :        252 :       std::string value;
     484                 :            : 
     485         [ +  + ]:        252 :       if (key_value_divider != std::string::npos) {
     486         [ +  - ]:        251 :         value = stanza.substr((key_value_divider + 1));
     487                 :            :       }
     488                 :            : 
     489   [ +  -  +  + ]:        252 :       if VUNLIKELY (query_dict_.count(key) != 0) {
     490         [ +  - ]:          1 :         throw Exception::RuntimeError("Bad key in the query string!");
     491                 :            :       }
     492                 :            : 
     493         [ +  - ]:        251 :       query_dict_.emplace(key, std::move(value));
     494         [ +  + ]:        251 :       carat = ((stanza_end != std::string::npos) ? (stanza_end + 1) : std::string::npos);
     495                 :        251 :       stanza_end = query_.find_first_of(separator, carat);
     496   [ +  +  +  + ]:        254 :     } while ((stanza_end != std::string::npos) || (carat != std::string::npos));
     497                 :            :   }
     498                 :        626 : }
     499                 :            : 
     500                 :            : }  // namespace vlink

Generated by: LCOV version 1.14