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
|