LCOV - code coverage report
Current view: top level - src/impl - url_parser.cc (source / functions) Hit Total Coverage
Test: vlink Lines: 246 252 97.6 %
Date: 2026-07-26 14:05:51 Functions: 27 27 100.0 %
Branches: 365 566 64.5 %

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

Generated by: LCOV version 1.14