Skip to content

Commit 4fe96b1

Browse files
anonrigdanielleadams
authored andcommitted
src: avoid copying string in fs_permission
PR-URL: #47746 Reviewed-By: Rafael Gonzaga <[email protected]> Reviewed-By: Daeyeon Jeong <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Deokjin Kim <[email protected]> Reviewed-By: Darshan Sen <[email protected]>
1 parent 7771b61 commit 4fe96b1

File tree

2 files changed

+332
-0
lines changed

2 files changed

+332
-0
lines changed

src/permission/fs_permission.cc

+180
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#include "fs_permission.h"
2+
#include "base_object-inl.h"
3+
#include "util.h"
4+
#include "v8.h"
5+
6+
#include <fcntl.h>
7+
#include <limits.h>
8+
#include <stdlib.h>
9+
#include <algorithm>
10+
#include <filesystem>
11+
#include <string>
12+
#include <vector>
13+
14+
namespace {
15+
16+
std::string WildcardIfDir(const std::string& res) noexcept {
17+
uv_fs_t req;
18+
int rc = uv_fs_stat(nullptr, &req, res.c_str(), nullptr);
19+
if (rc == 0) {
20+
const uv_stat_t* const s = static_cast<const uv_stat_t*>(req.ptr);
21+
if (s->st_mode & S_IFDIR) {
22+
// add wildcard when directory
23+
if (res.back() == node::kPathSeparator) {
24+
return res + "*";
25+
}
26+
return res + node::kPathSeparator + "*";
27+
}
28+
}
29+
uv_fs_req_cleanup(&req);
30+
return res;
31+
}
32+
33+
void FreeRecursivelyNode(
34+
node::permission::FSPermission::RadixTree::Node* node) {
35+
if (node == nullptr) {
36+
return;
37+
}
38+
39+
if (node->children.size()) {
40+
for (auto& c : node->children) {
41+
FreeRecursivelyNode(c.second);
42+
}
43+
}
44+
45+
if (node->wildcard_child != nullptr) {
46+
delete node->wildcard_child;
47+
}
48+
delete node;
49+
}
50+
51+
bool is_tree_granted(node::permission::FSPermission::RadixTree* granted_tree,
52+
const std::string_view& param) {
53+
#ifdef _WIN32
54+
// is UNC file path
55+
if (param.rfind("\\\\", 0) == 0) {
56+
// return lookup with normalized param
57+
int starting_pos = 4; // "\\?\"
58+
if (param.rfind("\\\\?\\UNC\\") == 0) {
59+
starting_pos += 4; // "UNC\"
60+
}
61+
auto normalized = param.substr(starting_pos);
62+
return granted_tree->Lookup(normalized, true);
63+
}
64+
#endif
65+
return granted_tree->Lookup(param, true);
66+
}
67+
68+
} // namespace
69+
70+
namespace node {
71+
72+
namespace permission {
73+
74+
// allow = '*'
75+
// allow = '/tmp/,/home/example.js'
76+
void FSPermission::Apply(const std::string& allow, PermissionScope scope) {
77+
for (const auto& res : SplitString(allow, ',')) {
78+
if (res == "*") {
79+
if (scope == PermissionScope::kFileSystemRead) {
80+
deny_all_in_ = false;
81+
allow_all_in_ = true;
82+
} else {
83+
deny_all_out_ = false;
84+
allow_all_out_ = true;
85+
}
86+
return;
87+
}
88+
GrantAccess(scope, res);
89+
}
90+
}
91+
92+
void FSPermission::GrantAccess(PermissionScope perm, const std::string& res) {
93+
const std::string path = WildcardIfDir(res);
94+
if (perm == PermissionScope::kFileSystemRead) {
95+
granted_in_fs_.Insert(path);
96+
deny_all_in_ = false;
97+
} else if (perm == PermissionScope::kFileSystemWrite) {
98+
granted_out_fs_.Insert(path);
99+
deny_all_out_ = false;
100+
}
101+
}
102+
103+
bool FSPermission::is_granted(PermissionScope perm,
104+
const std::string_view& param = "") {
105+
switch (perm) {
106+
case PermissionScope::kFileSystem:
107+
return allow_all_in_ && allow_all_out_;
108+
case PermissionScope::kFileSystemRead:
109+
return !deny_all_in_ &&
110+
((param.empty() && allow_all_in_) || allow_all_in_ ||
111+
is_tree_granted(&granted_in_fs_, param));
112+
case PermissionScope::kFileSystemWrite:
113+
return !deny_all_out_ &&
114+
((param.empty() && allow_all_out_) || allow_all_out_ ||
115+
is_tree_granted(&granted_out_fs_, param));
116+
default:
117+
return false;
118+
}
119+
}
120+
121+
FSPermission::RadixTree::RadixTree() : root_node_(new Node("")) {}
122+
123+
FSPermission::RadixTree::~RadixTree() {
124+
FreeRecursivelyNode(root_node_);
125+
}
126+
127+
bool FSPermission::RadixTree::Lookup(const std::string_view& s,
128+
bool when_empty_return = false) {
129+
FSPermission::RadixTree::Node* current_node = root_node_;
130+
if (current_node->children.size() == 0) {
131+
return when_empty_return;
132+
}
133+
134+
unsigned int parent_node_prefix_len = current_node->prefix.length();
135+
const std::string path(s);
136+
auto path_len = path.length();
137+
138+
while (true) {
139+
if (parent_node_prefix_len == path_len && current_node->IsEndNode()) {
140+
return true;
141+
}
142+
143+
auto node = current_node->NextNode(path, parent_node_prefix_len);
144+
if (node == nullptr) {
145+
return false;
146+
}
147+
148+
current_node = node;
149+
parent_node_prefix_len += current_node->prefix.length();
150+
if (current_node->wildcard_child != nullptr &&
151+
path_len >= (parent_node_prefix_len - 2 /* slash* */)) {
152+
return true;
153+
}
154+
}
155+
}
156+
157+
void FSPermission::RadixTree::Insert(const std::string& path) {
158+
FSPermission::RadixTree::Node* current_node = root_node_;
159+
160+
unsigned int parent_node_prefix_len = current_node->prefix.length();
161+
int path_len = path.length();
162+
163+
for (int i = 1; i <= path_len; ++i) {
164+
bool is_wildcard_node = path[i - 1] == '*';
165+
bool is_last_char = i == path_len;
166+
167+
if (is_wildcard_node || is_last_char) {
168+
std::string node_path = path.substr(parent_node_prefix_len, i);
169+
current_node = current_node->CreateChild(node_path);
170+
}
171+
172+
if (is_wildcard_node) {
173+
current_node = current_node->CreateWildcardChild();
174+
parent_node_prefix_len = i;
175+
}
176+
}
177+
}
178+
179+
} // namespace permission
180+
} // namespace node

src/permission/fs_permission.h

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#ifndef SRC_PERMISSION_FS_PERMISSION_H_
2+
#define SRC_PERMISSION_FS_PERMISSION_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include "v8.h"
7+
8+
#include <unordered_map>
9+
#include <vector>
10+
#include "permission/permission_base.h"
11+
#include "util.h"
12+
13+
namespace node {
14+
15+
namespace permission {
16+
17+
class FSPermission final : public PermissionBase {
18+
public:
19+
void Apply(const std::string& deny, PermissionScope scope) override;
20+
bool is_granted(PermissionScope perm, const std::string_view& param) override;
21+
22+
// For debugging purposes, use the gist function to print the whole tree
23+
// https://gist.github.com/RafaelGSS/5b4f09c559a54f53f9b7c8c030744d19
24+
struct RadixTree {
25+
struct Node {
26+
std::string prefix;
27+
std::unordered_map<char, Node*> children;
28+
Node* wildcard_child;
29+
30+
explicit Node(const std::string& pre)
31+
: prefix(pre), wildcard_child(nullptr) {}
32+
33+
Node() : wildcard_child(nullptr) {}
34+
35+
Node* CreateChild(std::string prefix) {
36+
char label = prefix[0];
37+
38+
Node* child = children[label];
39+
if (child == nullptr) {
40+
children[label] = new Node(prefix);
41+
return children[label];
42+
}
43+
44+
// swap prefix
45+
unsigned int i = 0;
46+
unsigned int prefix_len = prefix.length();
47+
for (; i < child->prefix.length(); ++i) {
48+
if (i > prefix_len || prefix[i] != child->prefix[i]) {
49+
std::string parent_prefix = child->prefix.substr(0, i);
50+
std::string child_prefix = child->prefix.substr(i);
51+
52+
child->prefix = child_prefix;
53+
Node* split_child = new Node(parent_prefix);
54+
split_child->children[child_prefix[0]] = child;
55+
children[parent_prefix[0]] = split_child;
56+
57+
return split_child->CreateChild(prefix.substr(i));
58+
}
59+
}
60+
return child->CreateChild(prefix.substr(i));
61+
}
62+
63+
Node* CreateWildcardChild() {
64+
if (wildcard_child != nullptr) {
65+
return wildcard_child;
66+
}
67+
wildcard_child = new Node();
68+
return wildcard_child;
69+
}
70+
71+
Node* NextNode(const std::string& path, unsigned int idx) {
72+
if (idx >= path.length()) {
73+
return nullptr;
74+
}
75+
76+
auto it = children.find(path[idx]);
77+
if (it == children.end()) {
78+
return nullptr;
79+
}
80+
auto child = it->second;
81+
// match prefix
82+
unsigned int prefix_len = child->prefix.length();
83+
for (unsigned int i = 0; i < path.length(); ++i) {
84+
if (i >= prefix_len || child->prefix[i] == '*') {
85+
return child;
86+
}
87+
88+
// Handle optional trailing
89+
// path = /home/subdirectory
90+
// child = subdirectory/*
91+
if (idx >= path.length() &&
92+
child->prefix[i] == node::kPathSeparator) {
93+
continue;
94+
}
95+
96+
if (path[idx++] != child->prefix[i]) {
97+
return nullptr;
98+
}
99+
}
100+
return child;
101+
}
102+
103+
// A node can be a *end* node and have children
104+
// E.g: */slower*, */slown* are inserted:
105+
// /slow
106+
// ---> er
107+
// ---> n
108+
// If */slow* is inserted right after, it will create an
109+
// empty node
110+
// /slow
111+
// ---> '\000' ASCII (0) || \0
112+
// ---> er
113+
// ---> n
114+
bool IsEndNode() {
115+
if (children.size() == 0) {
116+
return true;
117+
}
118+
return children['\0'] != nullptr;
119+
}
120+
};
121+
122+
RadixTree();
123+
~RadixTree();
124+
void Insert(const std::string& s);
125+
bool Lookup(const std::string_view& s) { return Lookup(s, false); }
126+
bool Lookup(const std::string_view& s, bool when_empty_return);
127+
128+
private:
129+
Node* root_node_;
130+
};
131+
132+
private:
133+
void GrantAccess(PermissionScope scope, const std::string& param);
134+
void RestrictAccess(PermissionScope scope,
135+
const std::vector<std::string>& params);
136+
// fs granted on startup
137+
RadixTree granted_in_fs_;
138+
RadixTree granted_out_fs_;
139+
140+
bool deny_all_in_ = true;
141+
bool deny_all_out_ = true;
142+
143+
bool allow_all_in_ = false;
144+
bool allow_all_out_ = false;
145+
};
146+
147+
} // namespace permission
148+
149+
} // namespace node
150+
151+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
152+
#endif // SRC_PERMISSION_FS_PERMISSION_H_

0 commit comments

Comments
 (0)