#include "UrlRouterPrivate.h" #include "BoostLog.h" #include "TemplateSegmentRule.h" #include #include UrlRouterPrivate::UrlRouterPrivate() { m_nodes.push_back(SegementNode{}); } void UrlRouterPrivate::insertImpl(boost::urls::string_view pattern, const std::shared_ptr &resource) { if (pattern.starts_with("/")) pattern.remove_prefix(1); auto segements = boost::urls::grammar::parse(pattern, templatePathRule); if (!segements) { segements.value(); } auto iterator = segements->begin(); auto end = segements->end(); auto currentNode = &m_nodes.front(); int level = 0; while (iterator != end) { boost::urls::string_view segement = (*iterator).string(); if (segement == ".") { ++iterator; continue; } else if (segement == "..") { if (currentNode == &m_nodes.front()) { --level; ++iterator; continue; } std::size_t parentIndex = currentNode->parentIndex; if (currentNode == &m_nodes.back() && !currentNode->resource && currentNode->childIndexes.empty()) { auto p = &m_nodes[parentIndex]; std::size_t currentIndex = currentNode - m_nodes.data(); p->childIndexes.erase(std::remove(p->childIndexes.begin(), p->childIndexes.end(), currentIndex)); m_nodes.pop_back(); } currentNode = &m_nodes[parentIndex]; ++iterator; continue; } if (level < 0) { ++level; ++iterator; continue; } auto cit = std::find_if(currentNode->childIndexes.begin(), currentNode->childIndexes.end(), [this, &iterator](std::size_t ci) -> bool { return m_nodes[ci].segment == *iterator; }); if (cit != currentNode->childIndexes.end()) { currentNode = &m_nodes[*cit]; } else { SegementNode child; child.segment = *iterator; std::size_t currentIndex = currentNode - m_nodes.data(); child.parentIndex = currentIndex; m_nodes.push_back(std::move(child)); m_nodes[currentIndex].childIndexes.push_back(m_nodes.size() - 1); if (m_nodes[currentIndex].childIndexes.size() > 1) { // keep nodes sorted auto &cs = m_nodes[currentIndex].childIndexes; std::size_t n = cs.size() - 1; while (n) { if (m_nodes[cs.begin()[n]].segment < m_nodes[cs.begin()[n - 1]].segment) std::swap(cs.begin()[n], cs.begin()[n - 1]); else break; --n; } } currentNode = &m_nodes.back(); } ++iterator; } if (level != 0) { boost::urls::detail::throw_invalid_argument(); } currentNode->resource = resource; currentNode->templatePath = pattern; } const AnyResource *UrlRouterPrivate::findImpl(boost::urls::segments_encoded_view path, boost::urls::string_view *&matches, boost::urls::string_view *&ids) const noexcept { if (path.empty()) path = boost::urls::segments_encoded_view("./"); const SegementNode *p = tryMatch(path.begin(), path.end(), &m_nodes.front(), 0, matches, ids); if (p) return p->resource.get(); return nullptr; } const SegementNode *UrlRouterPrivate::tryMatch(boost::urls::segments_encoded_base::const_iterator it, boost::urls::segments_encoded_base::const_iterator end, const SegementNode *current, int level, boost::urls::string_view *&matches, boost::urls::string_view *&ids) const { while (it != end) { boost::urls::pct_string_view s = *it; if (*s == ".") { // ignore segment ++it; continue; } else if (*s == "..") { // move back to the parent node ++it; if (level <= 0 && current != &m_nodes.front()) { if (!current->segment.isLiteral()) { --matches; --ids; } current = &m_nodes[current->parentIndex]; } else // there's no parent, so we // discount that from the implicit // tree beyond terminals --level; continue; } if (level < 0) { ++level; ++it; continue; } bool branch = false; if (current->childIndexes.size() > 1) { int branches_lb = 0; for (auto i : current->childIndexes) { auto &c = m_nodes[i]; if (c.segment.isLiteral() || !c.segment.hasModifier()) { // a literal path counts only // if it matches branches_lb += c.segment.match(s); } else { // everything not matching // a single path counts as // more than one path already branches_lb = 2; } if (branches_lb > 1) { // already know we need to // branch branch = true; break; } } } const SegementNode *r = nullptr; bool matchAny = false; for (auto index : current->childIndexes) { auto &child = m_nodes[index]; if (!child.segment.match(s)) continue; if (child.segment.isLiteral()) { if (branch) { r = tryMatch(std::next(it), end, &child, level, matches, ids); if (r) break; } else { current = &child; matchAny = true; break; } } else if (!child.segment.hasModifier()) { if (branch) { auto matches0 = matches; auto ids0 = ids; *matches++ = *it; *ids++ = child.segment.id(); r = tryMatch(std::next(it), end, &child, level, matches, ids); if (r) { break; } else { // rewind matches = matches0; ids = ids0; } } else { // only path possible *matches++ = *it; *ids++ = child.segment.id(); current = &child; matchAny = true; break; } } else if (child.segment.isOptional()) { auto matches0 = matches; auto ids0 = ids; *matches++ = *it; *ids++ = child.segment.id(); r = tryMatch(std::next(it), end, &child, level, matches, ids); if (r) break; matches = matches0; ids = ids0; *matches++ = {}; *ids++ = child.segment.id(); r = tryMatch(it, end, &child, level, matches, ids); if (r) break; matches = matches0; ids = ids0; } else { auto first = it; std::size_t ndotdot = 0; std::size_t nnondot = 0; auto it1 = it; while (it1 != end) { if (*it1 == "..") { ++ndotdot; if (ndotdot >= (nnondot + child.segment.isStar())) break; } else if (*it1 != ".") { ++nnondot; } ++it1; } if (it1 != end) break; auto matches0 = matches; auto ids0 = ids; *matches++ = *it; *ids++ = child.segment.id(); if (child.segment.isPlus()) { ++first; } auto start = end; while (start != first) { r = tryMatch(start, end, &child, level, matches, ids); if (r) { boost::urls::string_view prev = *std::prev(start); *matches0 = {matches0->data(), prev.data() + prev.size()}; break; } matches = matches0 + 1; ids = ids0 + 1; --start; } if (r) { break; } matches = matches0 + 1; ids = ids0 + 1; r = tryMatch(start, end, &child, level, matches, ids); if (r) { if (!child.segment.isPlus()) *matches0 = {}; break; } } } if (r) return r; if (!matchAny) ++level; ++it; } if (level != 0) { // the path ended below or above an existing node return nullptr; } if (!current->resource) { return findOptionalResource(current, m_nodes, matches, ids); } return current; } const SegementNode *UrlRouterPrivate::findOptionalResource(const SegementNode *root, const std::vector &ns, boost::urls::string_view *&matches, boost::urls::string_view *&ids) { BOOST_ASSERT(root); if (root->resource) return root; BOOST_ASSERT(!root->childIndexes.empty()); for (auto index : root->childIndexes) { auto &child = ns[index]; if (!child.segment.isOptional() && !child.segment.isStar()) continue; // Child nodes are also potentially optional. auto matches0 = matches; auto ids0 = ids; *matches++ = {}; *ids++ = child.segment.id(); auto n = findOptionalResource(&child, ns, matches, ids); if (n) return n; matches = matches0; ids = ids0; } return nullptr; } bool ChildIndexVector::empty() const { return m_size == 0; } size_t ChildIndexVector::size() const { return m_size; } size_t *ChildIndexVector::begin() { if (m_childIndexes) return m_childIndexes; return m_staticChildIndexes; } const size_t *ChildIndexVector::begin() const { return const_cast(this)->begin(); } size_t *ChildIndexVector::end() { return begin() + m_size; } const size_t *ChildIndexVector::end() const { return const_cast(this)->end(); } void ChildIndexVector::erase(size_t *it) { BOOST_ASSERT(it - begin() >= 0); std::memmove(it - 1, it, end() - it); --m_size; } void ChildIndexVector::push_back(size_t v) { if (m_size == N && !m_childIndexes) { m_childIndexes = new std::size_t[N * 2]; m_capcity = N * 2; std::memcpy(m_childIndexes, m_staticChildIndexes, N * sizeof(std::size_t)); } else if (m_childIndexes && m_size == m_capcity) { auto *tmp = new std::size_t[m_capcity * 2]; std::memcpy(tmp, m_childIndexes, m_capcity * sizeof(std::size_t)); delete[] m_childIndexes; m_childIndexes = tmp; m_capcity = m_capcity * 2; } begin()[m_size++] = v; }