Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,14 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouter {
/// Access the base path prefix
[[nodiscard]] auto base_path() const noexcept -> std::string_view;

/// Get the number of registered routes
[[nodiscard]] auto size() const noexcept -> std::size_t;

private:
Node root_;
std::string base_path_;
std::vector<std::pair<Identifier, std::vector<Argument>>> arguments_;
std::size_t size_{0};
};

/// @ingroup uritemplate
Expand Down Expand Up @@ -145,6 +149,9 @@ class SOURCEMETA_CORE_URITEMPLATE_EXPORT URITemplateRouterView {
/// Access the base path prefix
[[nodiscard]] auto base_path() const noexcept -> std::string_view;

/// Get the number of registered routes
[[nodiscard]] auto size() const noexcept -> std::size_t;

private:
std::vector<std::uint8_t> data_;
};
Expand Down
10 changes: 10 additions & 0 deletions src/core/uritemplate/uritemplate_router.cc
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ auto URITemplateRouter::base_path() const noexcept -> std::string_view {
return this->base_path_;
}

auto URITemplateRouter::size() const noexcept -> std::size_t {
return this->size_;
}

auto URITemplateRouter::add(const std::string_view uri_template,
const Identifier identifier,
const Identifier context,
Expand Down Expand Up @@ -141,6 +145,9 @@ auto URITemplateRouter::add(const std::string_view uri_template,

if (uri_template.empty()) {
auto &target = current ? *current : this->root_;
if (target.identifier == 0) {
this->size_ += 1;
}
target.identifier = identifier;
target.context = context;
if (!arguments.empty()) {
Expand Down Expand Up @@ -310,6 +317,9 @@ auto URITemplateRouter::add(const std::string_view uri_template,
}

if (!absorbed && current != nullptr) {
if (current->identifier == 0) {
this->size_ += 1;
}
current->identifier = identifier;
current->context = context;
if (!arguments.empty()) {
Expand Down
30 changes: 30 additions & 0 deletions src/core/uritemplate/uritemplate_router_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -663,4 +663,34 @@ auto URITemplateRouterView::base_path() const noexcept -> std::string_view {
return {string_table + header->base_path_offset, header->base_path_length};
}

auto URITemplateRouterView::size() const noexcept -> std::size_t {
if (this->data_.size() < sizeof(RouterHeader)) {
return 0;
}

const auto *header =
reinterpret_cast<const RouterHeader *>(this->data_.data());
if (header->magic != ROUTER_MAGIC || header->version != ROUTER_VERSION) {
return 0;
}

if (header->node_count == 0 ||
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URITemplateRouterView::size() only bounds-checks node_count against the total buffer size, but doesn’t validate string_table_offset/arguments_offset like match()/base_path() do. On a partially corrupt blob where node_count overlaps the string table, size() could return a non-zero count even though the view should be considered invalid.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

header->node_count > (this->data_.size() - sizeof(RouterHeader)) /
sizeof(SerializedNode)) {
return 0;
}

const auto *nodes = reinterpret_cast<const SerializedNode *>(
this->data_.data() + sizeof(RouterHeader));

std::size_t count = 0;
for (std::uint32_t index = 0; index < header->node_count; ++index) {
if (nodes[index].identifier != 0) {
count += 1;
}
}

return count;
}

} // namespace sourcemeta::core
47 changes: 47 additions & 0 deletions test/uritemplate/uritemplate_router_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1292,3 +1292,50 @@ TEST(URITemplateRouter, add_with_context_overwrites_previous_context) {
EXPECT_ROUTER_MATCH(router, "/users", 1, 20, captures);
EXPECT_EQ(captures.size(), 0);
}

TEST(URITemplateRouter, size_empty_router) {
const sourcemeta::core::URITemplateRouter router;
EXPECT_EQ(router.size(), 0);
}

TEST(URITemplateRouter, size_single_route) {
sourcemeta::core::URITemplateRouter router;
router.add("/users", 1);
EXPECT_EQ(router.size(), 1);
}

TEST(URITemplateRouter, size_multiple_routes) {
sourcemeta::core::URITemplateRouter router;
router.add("/users", 1);
router.add("/users/{id}", 2);
router.add("/posts", 3);
router.add("/posts/{id}", 4);
EXPECT_EQ(router.size(), 4);
}

TEST(URITemplateRouter, size_duplicate_route_does_not_increase) {
sourcemeta::core::URITemplateRouter router;
router.add("/users", 1);
router.add("/users", 2);
EXPECT_EQ(router.size(), 1);
}

TEST(URITemplateRouter, size_with_context_overwrite_does_not_increase) {
sourcemeta::core::URITemplateRouter router;
router.add("/users", 1, 10);
router.add("/users", 1, 20);
EXPECT_EQ(router.size(), 1);
}

TEST(URITemplateRouter, size_root_template) {
sourcemeta::core::URITemplateRouter router;
router.add("", 1);
EXPECT_EQ(router.size(), 1);
}

TEST(URITemplateRouter, size_with_base_path) {
sourcemeta::core::URITemplateRouter router{"/v1"};
router.add("/users", 1);
router.add("/posts", 2);
EXPECT_EQ(router.size(), 2);
}
59 changes: 59 additions & 0 deletions test/uritemplate/uritemplate_router_view_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2318,3 +2318,62 @@ TEST_F(URITemplateRouterViewTest,
EXPECT_ROUTER_MATCH(restored, "/users", 1, 20, captures);
EXPECT_EQ(captures.size(), 0);
}

TEST_F(URITemplateRouterViewTest, size_empty_router) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new URITemplateRouterView::size() tests cover empty/single/multiple/duplicate/base-path cases, but don’t cover root-template (router.add("", ...)) or context-overwrite scenarios. Adding those would help ensure the serialized view’s count stays aligned with URITemplateRouter::size() for these edge cases.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

{
const sourcemeta::core::URITemplateRouter router;
sourcemeta::core::URITemplateRouterView::save(router, this->path);
}

const sourcemeta::core::URITemplateRouterView restored{this->path};
EXPECT_EQ(restored.size(), 0);
}

TEST_F(URITemplateRouterViewTest, size_single_route) {
{
sourcemeta::core::URITemplateRouter router;
router.add("/users", 1);
sourcemeta::core::URITemplateRouterView::save(router, this->path);
}

const sourcemeta::core::URITemplateRouterView restored{this->path};
EXPECT_EQ(restored.size(), 1);
}

TEST_F(URITemplateRouterViewTest, size_multiple_routes) {
{
sourcemeta::core::URITemplateRouter router;
router.add("/users", 1);
router.add("/users/{id}", 2);
router.add("/posts", 3);
router.add("/posts/{id}", 4);
sourcemeta::core::URITemplateRouterView::save(router, this->path);
}

const sourcemeta::core::URITemplateRouterView restored{this->path};
EXPECT_EQ(restored.size(), 4);
}

TEST_F(URITemplateRouterViewTest, size_duplicate_route_does_not_increase) {
{
sourcemeta::core::URITemplateRouter router;
router.add("/users", 1);
router.add("/users", 2);
sourcemeta::core::URITemplateRouterView::save(router, this->path);
}

const sourcemeta::core::URITemplateRouterView restored{this->path};
EXPECT_EQ(restored.size(), 1);
}

TEST_F(URITemplateRouterViewTest, size_with_base_path) {
{
sourcemeta::core::URITemplateRouter router{"/v1"};
router.add("/users", 1);
router.add("/posts", 2);
sourcemeta::core::URITemplateRouterView::save(router, this->path);
}

const sourcemeta::core::URITemplateRouterView restored{this->path};
EXPECT_EQ(restored.size(), 2);
}
Loading