324 lines
12 KiB
C++
324 lines
12 KiB
C++
#include "UrlRouterPrivate.h"
|
|
#include "BoostLog.h"
|
|
#include "TemplateSegmentRule.h"
|
|
#include <boost/url/decode_view.hpp>
|
|
#include <boost/url/rfc/detail/path_rules.hpp>
|
|
|
|
UrlRouterPrivate::UrlRouterPrivate() {
|
|
m_nodes.push_back(SegementNode{});
|
|
}
|
|
|
|
void UrlRouterPrivate::insertImpl(boost::urls::string_view pattern, const std::shared_ptr<AnyResource> &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<SegementNode> &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<ChildIndexVector *>(this)->begin();
|
|
}
|
|
|
|
size_t *ChildIndexVector::end() {
|
|
return begin() + m_size;
|
|
}
|
|
|
|
const size_t *ChildIndexVector::end() const {
|
|
return const_cast<ChildIndexVector *>(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;
|
|
}
|