|
20 | 20 | #include <algorithm> |
21 | 21 | #include <cctype> |
22 | 22 | #include <cstddef> |
| 23 | +#include <unordered_map> |
23 | 24 |
|
24 | 25 | /** |
25 | 26 | * @brief Small string helpers (trim, case transform, prefix/suffix checks, split/join). |
@@ -344,6 +345,166 @@ namespace vix::utils |
344 | 345 | return std::string(b); |
345 | 346 | } |
346 | 347 |
|
| 348 | + inline std::string url_decode(std::string_view in) |
| 349 | + { |
| 350 | + std::string out; |
| 351 | + out.reserve(in.size()); |
| 352 | + |
| 353 | + for (size_t i = 0; i < in.size(); ++i) |
| 354 | + { |
| 355 | + const unsigned char c = static_cast<unsigned char>(in[i]); |
| 356 | + if (c == '+') |
| 357 | + { |
| 358 | + out.push_back(' '); |
| 359 | + } |
| 360 | + else if (c == '%' && i + 2 < in.size()) |
| 361 | + { |
| 362 | + auto hex = [](unsigned char ch) -> int |
| 363 | + { |
| 364 | + if (ch >= '0' && ch <= '9') |
| 365 | + return ch - '0'; |
| 366 | + if (ch >= 'a' && ch <= 'f') |
| 367 | + return 10 + (ch - 'a'); |
| 368 | + if (ch >= 'A' && ch <= 'F') |
| 369 | + return 10 + (ch - 'A'); |
| 370 | + return -1; |
| 371 | + }; |
| 372 | + |
| 373 | + int hi = hex(static_cast<unsigned char>(in[i + 1])); |
| 374 | + int lo = hex(static_cast<unsigned char>(in[i + 2])); |
| 375 | + if (hi >= 0 && lo >= 0) |
| 376 | + { |
| 377 | + out.push_back(static_cast<char>((hi << 4) | lo)); |
| 378 | + i += 2; |
| 379 | + } |
| 380 | + else |
| 381 | + { |
| 382 | + out.push_back(static_cast<char>(c)); |
| 383 | + } |
| 384 | + } |
| 385 | + else |
| 386 | + { |
| 387 | + out.push_back(static_cast<char>(c)); |
| 388 | + } |
| 389 | + } |
| 390 | + |
| 391 | + return out; |
| 392 | + } |
| 393 | + |
| 394 | + inline std::unordered_map<std::string, std::string> |
| 395 | + parse_query_string(std::string_view qs) |
| 396 | + { |
| 397 | + std::unordered_map<std::string, std::string> out; |
| 398 | + |
| 399 | + size_t pos = 0; |
| 400 | + while (pos < qs.size()) |
| 401 | + { |
| 402 | + size_t amp = qs.find('&', pos); |
| 403 | + if (amp == std::string_view::npos) |
| 404 | + amp = qs.size(); |
| 405 | + |
| 406 | + std::string_view pair = qs.substr(pos, amp - pos); |
| 407 | + if (!pair.empty()) |
| 408 | + { |
| 409 | + size_t eq = pair.find('='); |
| 410 | + std::string_view key, val; |
| 411 | + if (eq == std::string_view::npos) |
| 412 | + { |
| 413 | + key = pair; |
| 414 | + val = std::string_view{}; |
| 415 | + } |
| 416 | + else |
| 417 | + { |
| 418 | + key = pair.substr(0, eq); |
| 419 | + val = pair.substr(eq + 1); |
| 420 | + } |
| 421 | + |
| 422 | + auto key_dec = url_decode(key); |
| 423 | + auto val_dec = url_decode(val); |
| 424 | + if (!key_dec.empty()) |
| 425 | + { |
| 426 | + out[std::move(key_dec)] = std::move(val_dec); |
| 427 | + } |
| 428 | + } |
| 429 | + |
| 430 | + if (amp == qs.size()) |
| 431 | + break; |
| 432 | + pos = amp + 1; |
| 433 | + } |
| 434 | + |
| 435 | + return out; |
| 436 | + } |
| 437 | + |
| 438 | + inline std::unordered_map<std::string, std::string> |
| 439 | + extract_params_from_path(const std::string &pattern, std::string_view path) |
| 440 | + { |
| 441 | + std::unordered_map<std::string, std::string> params; |
| 442 | + |
| 443 | + std::size_t rpos = 0; |
| 444 | + std::size_t ppos = 0; |
| 445 | + |
| 446 | + while (rpos < pattern.size() && ppos <= path.size()) |
| 447 | + { |
| 448 | + if (pattern[rpos] == '{') |
| 449 | + { |
| 450 | + const std::size_t end_brace = pattern.find('}', rpos); |
| 451 | + if (end_brace == std::string::npos) |
| 452 | + { |
| 453 | + break; |
| 454 | + } |
| 455 | + |
| 456 | + const std::string name = pattern.substr(rpos + 1, end_brace - rpos - 1); |
| 457 | + |
| 458 | + const std::size_t next_slash = path.find('/', ppos); |
| 459 | + const std::string_view value = |
| 460 | + (next_slash == std::string_view::npos) |
| 461 | + ? path.substr(ppos) |
| 462 | + : path.substr(ppos, next_slash - ppos); |
| 463 | + |
| 464 | + if (!name.empty()) |
| 465 | + { |
| 466 | + params.emplace(name, std::string(value)); |
| 467 | + } |
| 468 | + |
| 469 | + rpos = end_brace + 1; |
| 470 | + if (next_slash == std::string_view::npos) |
| 471 | + { |
| 472 | + ppos = path.size(); |
| 473 | + } |
| 474 | + else |
| 475 | + { |
| 476 | + ppos = next_slash + 1; |
| 477 | + } |
| 478 | + |
| 479 | + continue; |
| 480 | + } |
| 481 | + |
| 482 | + if (ppos >= path.size() || pattern[rpos] != path[ppos]) |
| 483 | + { |
| 484 | + return {}; |
| 485 | + } |
| 486 | + |
| 487 | + ++rpos; |
| 488 | + ++ppos; |
| 489 | + } |
| 490 | + |
| 491 | + while (rpos < pattern.size()) |
| 492 | + { |
| 493 | + if (pattern[rpos] != '/') |
| 494 | + return {}; |
| 495 | + ++rpos; |
| 496 | + } |
| 497 | + |
| 498 | + while (ppos < path.size()) |
| 499 | + { |
| 500 | + if (path[ppos] != '/') |
| 501 | + return {}; |
| 502 | + ++ppos; |
| 503 | + } |
| 504 | + |
| 505 | + return params; |
| 506 | + } |
| 507 | + |
347 | 508 | } // namespace vix::utils |
348 | 509 |
|
349 | 510 | #endif // VIX_STRING_HPP |
0 commit comments