current commit
261021.13 · src: fix the fs permission tree node on wildcard nodes
#ifndef SRC_PERMISSION_FS_PERMISSION_H_
#define SRC_PERMISSION_FS_PERMISSION_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <memory>
#include <vector>
#include "node_mutex.h"
#include "permission/permission_base.h"
#include "util.h"

namespace node::permission {

class FSPermission final : public PermissionBase {
 public:
  void Apply(Environment* env,
             std::span<const std::string> allow,
             PermissionScope scope) override;
  void Drop(Environment* env,
            PermissionScope scope,
            std::string_view param) override;
  // Safe to call from any thread: Drop() rewrites the trees on the
  // thread that owns `env`, which this waits out.
  bool is_granted(Environment* env,
                  PermissionScope perm,
                  std::string_view param) const override;

  struct RadixTree {
    struct Node {
      std::string prefix;
      std::vector<std::pair<char, std::unique_ptr<Node>>> children;
      std::unique_ptr<Node> wildcard_child;
      bool is_leaf = false;

      explicit Node(std::string_view pre) : prefix(pre) {}

      Node() = default;

      Node* FindChild(char label) const {
        for (const auto& [c, node] : children) {
          if (c == label) return node.get();
        }
        return nullptr;
      }

      void SetChild(char label, std::unique_ptr<Node> node) {
        for (auto& [c, n] : children) {
          if (c == label) {
            n = std::move(node);
            return;
          }
        }
        children.emplace_back(label, std::move(node));
      }

      Node* CreateChild(std::string_view path_prefix) {
        if (path_prefix.empty() && !is_leaf) {
          is_leaf = true;
          return this;
        }

        CHECK(!path_prefix.empty());
        char label = path_prefix[0];

        Node* child = FindChild(label);
        if (child == nullptr) {
          auto new_child = std::make_unique<Node>(path_prefix);
          child = new_child.get();
          children.emplace_back(label, std::move(new_child));
          return child;
        }
        bool child_was_end_node = child->IsEndNode();

        // swap prefix
        size_t i = 0;
        size_t prefix_len = path_prefix.length();
        for (; i < child->prefix.length(); ++i) {
          if (i >= prefix_len || path_prefix[i] != child->prefix[i]) {
            std::string parent_prefix(child->prefix.substr(0, i));
            std::string child_prefix(child->prefix.substr(i));

            child->prefix = child_prefix;
            auto split_child = std::make_unique<Node>(parent_prefix);
            Node* split_raw = split_child.get();
            std::unique_ptr<Node> detached;
            for (auto& [c, n] : children) {
              if (c == label) {
                detached = std::move(n);
                break;
              }
            }
            split_child->children.emplace_back(child_prefix[0],
                                               std::move(detached));
            SetChild(parent_prefix[0], std::move(split_child));

            return split_raw->CreateChild(path_prefix.substr(i));
          }
        }
        if (child_was_end_node) {
          child->is_leaf = true;
        }
        return child->CreateChild(path_prefix.substr(i));
      }

      Node* CreateWildcardChild() {
        if (wildcard_child != nullptr) {
          return wildcard_child.get();
        }
        wildcard_child = std::make_unique<Node>();
        return wildcard_child.get();
      }

      Node* NextNode(std::string_view path, size_t idx) const {
        if (idx >= path.length()) {
          return nullptr;
        }

        // wildcard node takes precedence
        if (children.size() > 1) {
          Node* wc = FindChild('*');
          if (wc != nullptr) {
            return wc;
          }
        }

        Node* child = FindChild(path[idx]);
        if (child == nullptr) {
          return nullptr;
        }
        // match prefix
        size_t prefix_len = child->prefix.length();
        for (size_t i = 0; i < prefix_len; ++i) {
          if (child->prefix[i] == '*') {
            return child;
          }

          // Handle optional trailing
          // path = /home/subdirectory
          // child = subdirectory/*
          if (idx >= path.length()) {
            if (child->prefix[i] == node::kPathSeparator) {
              continue;
            }
            // Path is exhausted but prefix expects more characters
            return nullptr;
          }

          if (path[idx++] != child->prefix[i]) {
            return nullptr;
          }
        }
        return child;
      }

      // A node can be a *end* node and have children
      // E.g: */slower*, */slown* are inserted:
      // /slow
      // ---> er
      // ---> n
      // If */slow* is inserted right after, it will create an
      // empty node
      // /slow
      // ---> '\000' ASCII (0) || \0
      // ---> er
      // ---> n
      bool IsEndNode() const {
        if (children.empty()) {
          return true;
        }
        return is_leaf;
      }
    };

    RadixTree();
    ~RadixTree();
    void Insert(const std::string& s);
    void Clear();
    bool Lookup(std::string_view s) const { return Lookup(s, false); }
    bool Lookup(std::string_view s, bool when_empty_return) const;

   private:
    std::unique_ptr<Node> root_node_;
  };

 private:
  void GrantAccess(PermissionScope scope, const std::string& param);
  void RevokeAccess(PermissionScope scope, const std::string& param);
  void RebuildTree(PermissionScope scope);
  // Guards everything below: Apply() and Drop() write, is_granted() reads.
  RwLock lock_;
  // fs granted on startup
  RadixTree granted_in_fs_;
  RadixTree granted_out_fs_;

  std::vector<std::string> granted_paths_in_;
  std::vector<std::string> granted_paths_out_;

  bool deny_all_in_ : 1 = true;
  bool deny_all_out_ : 1 = true;

  bool allow_all_in_ : 1 = false;
  bool allow_all_out_ : 1 = false;
};

}  // namespace node::permission

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif  // SRC_PERMISSION_FS_PERMISSION_H_