2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
42#include "express/dispatcher/regex_utils.h"
44#include "express/Controller.h"
45#include "express/MountPoint.h"
46#include "express/Request.h"
48#ifndef DOXYGEN_SHOULD_SKIP_THIS
60 const unsigned char uc =
static_cast<
unsigned char>(c);
61 return (std::isalnum(uc) != 0) || (c ==
'_');
66 for (
const char c : path) {
114 if (openPos >= s.size() || s[openPos] !=
'(') {
118 bool escaped =
false;
119 bool inCharClass =
false;
122 for (std::size_t i = openPos; i < s.size(); ++i) {
156 innerOut.assign(s.substr(openPos + 1, closePosOut - openPos - 1));
170 out.reserve(pattern.size());
172 bool escaped =
false;
173 bool inCharClass =
false;
175 for (std::size_t i = 0; i < pattern.size(); ++i) {
176 const char c = pattern[i];
206 if ((i + 1) < pattern.size() && pattern[i + 1] ==
'?') {
221 if (c >=
'0' && c <=
'9') {
224 if (c >=
'a' && c <=
'f') {
227 if (c >=
'A' && c <=
'F') {
235 out.reserve(in.size());
237 for (std::size_t i = 0; i < in.size(); ++i) {
239 if (i + 2 >= in.size()) {
245 if (hi < 0 || lo < 0) {
248 out.push_back(
static_cast<
char>((hi << 4) | lo));
252 out.push_back(in[i]);
260 out.reserve(in.size());
262 for (std::size_t i = 0; i < in.size(); ++i) {
263 const char c = in[i];
268 if (c ==
'%' && i + 2 < in.size()) {
271 if (hi >= 0 && lo >= 0) {
272 out.push_back(
static_cast<
char>((hi << 4) | lo));
284 const std::vector<std::string>& names,
285 std::string_view reqPath,
286 std::map<std::string, std::string>& params,
287 std::size_t& consumedLength,
291 if (!std::regex_search(reqPath.begin(), reqPath.end(), m, rx)) {
295 consumedLength =
static_cast<std::size_t>(m.length(0));
296 const size_t g = (!m.empty()) ? (m.size() - 1) : 0;
297 const size_t n = std::min(names.size(), g);
299 for (size_t i = 0; i < n; ++i) {
300 if (!names[i].empty() && m[i + 1].matched) {
306 params[names[i]] = decoded;
312 inline std::unordered_map<std::string, std::string>
parseQuery(std::string_view qs) {
313 std::unordered_map<std::string, std::string> m;
315 while (i < qs.size()) {
316 const std::size_t amp = qs.find(
'&', i);
317 const std::string_view pair = (amp == std::string_view::npos) ? qs.substr(i) : qs.substr(i, amp - i);
318 const std::size_t eq = pair.find(
'=');
320 const std::string_view rawKey = (eq == std::string_view::npos) ? pair : pair.substr(0, eq);
321 const std::string_view rawVal = (eq == std::string_view::npos) ? std::string_view{} : pair.substr(eq + 1);
330 if (amp == std::string_view::npos) {
338 inline std::pair<std::regex, std::vector<std::string>>
339 compileParamRegex(std::string_view mountPath,
bool isPrefix,
bool strictRouting,
bool caseInsensitive) {
349 rx.reserve(mountPath.size() * 2U + 32U);
352 std::vector<std::string> names;
355 std::size_t splatIndex = 0;
357 for (std::size_t i = 0; i < mountPath.size();) {
358 const char c = mountPath[i];
362 if (i + 1 < mountPath.size()) {
374 std::size_t j = i + 1;
386 std::string name(mountPath.substr(i + 1, j - (i + 1)));
387 std::string paramPattern =
"[^/]+";
390 if (j < mountPath.size() && mountPath[j] ==
'(') {
392 std::size_t closePos = 0;
399 char modifier =
'\0';
400 if (j < mountPath.size() && (mountPath[j] ==
'?' || mountPath[j] ==
'+' || mountPath[j] ==
'*')) {
401 modifier = mountPath[j];
407 const bool hasLeadingSlash = (!rx.empty() && rx.back() ==
'/');
409 if (hasLeadingSlash && (modifier ==
'?' || modifier ==
'+' || modifier ==
'*')) {
412 if (modifier ==
'?') {
414 rx.append(paramPattern);
416 }
else if (modifier ==
'+') {
418 rx.append(paramPattern);
420 rx.append(paramPattern);
424 rx.append(paramPattern);
426 rx.append(paramPattern);
430 if (modifier ==
'?') {
432 rx.append(paramPattern);
434 }
else if (modifier ==
'+') {
436 rx.append(paramPattern);
438 rx.append(paramPattern);
440 }
else if (modifier ==
'*') {
442 rx.append(paramPattern);
444 rx.append(paramPattern);
448 rx.append(paramPattern);
453 names.emplace_back(std::move(name));
461 names.emplace_back(std::to_string(splatIndex++));
468 if (i + 1 < mountPath.size() && mountPath[i + 1] ==
'?') {
485 if (c ==
'?' || c ==
'+') {
497 rx.append(
"(?:/|$)");
499 if (!strictRouting) {
505 std::regex::flag_type flags = std::regex::ECMAScript;
506 if (caseInsensitive) {
507 flags |= std::regex::icase;
511 std::regex re(rx, flags);
514 names.emplace_back(
"__compiled");
516 return {std::move(re), std::move(names)};
517 }
catch (
const std::regex_error&) {
518 std::regex re(
"a^", flags);
520 names.emplace_back(
"__compiled");
523 return {std::move(re), std::move(names)};
527 inline bool boundaryPrefix(std::string_view path, std::string_view base,
bool caseInsensitive) {
534 if (base.size() == 1 && base[0] ==
'/') {
535 return !path.empty() && path.front() ==
'/';
539 if (base.size() > path.size()) {
543 auto eq = [&](
char a,
char b) {
544 return !caseInsensitive ? (a == b)
545 : (std::tolower(
static_cast<
unsigned char>(a)) == std::tolower(
static_cast<
unsigned char>(b)));
549 for (size_t i = 0; i < base.size(); ++i) {
550 if (!eq(path[i], base[i])) {
556 return (path.size() == base.size()) || (path[base.size()] ==
'/');
559 inline bool equalPath(std::string_view a, std::string_view b,
bool caseInsensitive) {
560 if (a.size() != b.size()) {
563 for (size_t i = 0; i < a.size(); ++i) {
564 if (!caseInsensitive ? (a[i] != b[i]) : !
ieq(a[i]
, b[i]
)) {
572 if (s.size() > 1 && s.back() ==
'/') {
573 return std::string_view(s.data(), s.size() - 1);
578 inline void splitPathAndQuery(std::string_view url, std::string_view& path, std::string_view& query) {
579 const std::size_t qpos = url.find(
'?');
580 if (qpos == std::string_view::npos) {
584 path = url.substr(0, qpos);
585 query = url.substr(qpos + 1);
589 bool methodMatches(std::string_view requestMethod,
const std::string& mountMethod) {
591 if (requestMethod ==
"HEAD" && mountMethod ==
"GET") {
594 return (mountMethod ==
"use") || (mountMethod ==
"all") || (requestMethod == mountMethod);
597 std::string
joinMountPath(std::string_view parentMountPath, std::string_view relativeMountPath) {
598 if (parentMountPath.empty()) {
599 return std::string(relativeMountPath);
601 if (relativeMountPath.empty()) {
602 return std::string(parentMountPath);
605 const bool parentSlash = (!parentMountPath.empty() && parentMountPath.back() ==
'/');
606 const bool relSlash = (!relativeMountPath.empty() && relativeMountPath.front() ==
'/');
608 if (parentSlash && relSlash) {
610 if (parentMountPath.size() == 1) {
611 return std::string(relativeMountPath);
613 return std::string(parentMountPath) + std::string(relativeMountPath.substr(1));
615 if (!parentSlash && !relSlash) {
616 return std::string(parentMountPath) +
"/" + std::string(relativeMountPath);
618 return std::string(parentMountPath) + std::string(relativeMountPath);
622 const std::string& absoluteMountPath,
624 std::regex* cachedRegex,
625 std::vector<std::string>* cachedNames,
627 bool caseInsensitiveRouting) {
632 std::string_view mountPath;
633 std::string_view ignoredMountQuery;
635 std::string_view requestPath;
636 std::string_view requestQueryString;
641 if (!strictRouting) {
645 if (mountPath.empty()) {
651 bool pathMatches =
false;
654 bool decodeError =
false;
655 std::size_t matchLen = 0;
658 if (cachedRegex !=
nullptr && cachedNames !=
nullptr) {
659 if (cachedNames->empty()) {
663 caseInsensitiveRouting
);
664 *cachedRegex = std::move(compiled.first);
665 *cachedNames = std::move(compiled.second);
672 caseInsensitiveRouting
);
688 result
.consumedLength = (mountPath.size() == 1 && mountPath[0] ==
'/') ? 0 : mountPath.size();
692 pathMatches =
equalPath(requestPath
, mountPath
, caseInsensitiveRouting
);
702 const std::string& absoluteMountPath,
704 std::regex& cachedRegex,
705 std::vector<std::string>& cachedNames,
707 bool caseInsensitiveRouting) {
709 controller
, absoluteMountPath
, mountPoint
, &cachedRegex
, &cachedNames
, strictRouting
, caseInsensitiveRouting
);
728 std::string_view fullPath;
729 std::string_view fullQuery;
736 std::string fullQueryCopy;
737 if (!fullQuery.empty()) {
738 fullQueryCopy.assign(fullQuery.begin(), fullQuery.end());
742 const std::size_t cl = std::min<std::size_t>(consumedLength, fullPath.size());
743 std::string_view consumed = fullPath.substr(0, cl);
744 const std::string_view remainder = (fullPath.size() > cl) ? fullPath.substr(cl) : std::string_view{};
748 if (consumed.size() == 1 && consumed[0] ==
'/') {
755 if (remainder.empty()) {
757 }
else if (remainder.front() ==
'/') {
758 remPath.assign(remainder.begin(), remainder.end());
761 remPath.append(remainder.begin(), remainder.end());
766 if (!fullQueryCopy.empty()) {
767 req
.url.push_back(
'?');
768 req
.url.append(fullQueryCopy);
786 if (mergeWithParent) {
787 for (
const auto& [k, v] :
backup_) {
791 for (
const auto& [k, v] : params) {
797 if (
req_ !=
nullptr) {
const std::shared_ptr< Request > & getRequest() const
std::map< std::string, std::string > params
std::map< std::string, std::string > backup_
ScopedParams(express::Request &req, const std::map< std::string, std::string > ¶ms, bool mergeWithParent)
std::string backupBaseUrl_
ScopedPathStrip(express::Request &req, bool enabled, std::size_t consumedLength)
bool decodeURIComponent(std::string_view in, std::string &out)
bool routeNeedsRegex(std::string_view path)
std::string makeInnerGroupsNonCapturing(std::string_view pattern)
std::string decodeQueryComponent(std::string_view in)
std::string joinMountPath(std::string_view parentMountPath, std::string_view relativeMountPath)
bool matchAndFillParamsAndConsume(const std::regex &rx, const std::vector< std::string > &names, std::string_view reqPath, std::map< std::string, std::string > ¶ms, std::size_t &consumedLength, bool &decodeError)
MountMatchResult matchMountPoint(express::Controller &controller, const std::string &absoluteMountPath, const express::MountPoint &mountPoint, std::regex &cachedRegex, std::vector< std::string > &cachedNames, bool strictRouting, bool caseInsensitiveRouting)
void splitPathAndQuery(std::string_view url, std::string_view &path, std::string_view &query)
bool boundaryPrefix(std::string_view path, std::string_view base, bool caseInsensitive)
std::pair< std::regex, std::vector< std::string > > compileParamRegex(std::string_view mountPath, bool isPrefix, bool strictRouting, bool caseInsensitive)
bool extractBalancedParenInner(std::string_view s, const std::size_t openPos, std::string &innerOut, std::size_t &closePosOut)
std::unordered_map< std::string, std::string > parseQuery(std::string_view qs)
bool isParamNameChar(const char c)
int hexToInt(const char c)
std::string_view trimOneTrailingSlash(std::string_view s)
bool isRegexMetaToEscape(const char c)
void appendEscapedLiteral(std::string &out, const char c)
bool equalPath(std::string_view a, std::string_view b, bool caseInsensitive)
static MountMatchResult matchMountPointImpl(express::Controller &controller, const std::string &absoluteMountPath, const express::MountPoint &mountPoint, std::regex *cachedRegex, std::vector< std::string > *cachedNames, bool strictRouting, bool caseInsensitiveRouting)
bool methodMatches(std::string_view requestMethod, const std::string &mountMethod)
std::size_t consumedLength
std::map< std::string, std::string > params
std::unordered_map< std::string, std::string > requestQueryPairs
std::string_view requestPath