@ 261107.7
current
/* Copyright 2017 - 2026 R. Thomas
 * Copyright 2017 - 2026 Quarkslab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <algorithm>

#include "logging.hpp"
#include "LIEF/Visitor.hpp"

#include "LIEF/utils.hpp"

#include "LIEF/PE/Binary.hpp"
#include "LIEF/PE/ResourceNode.hpp"
#include "LIEF/PE/ResourceDirectory.hpp"
#include "LIEF/PE/ResourceData.hpp"
#include "LIEF/PE/ResourcesManager.hpp"

#include "LIEF/BinaryStream/SpanStream.hpp"

#include "PE/Structures.hpp"

namespace LIEF::PE {

class TreeParser {
  public:
  TreeParser() = delete;
  TreeParser(BinaryStream& stream, uint64_t base_rva) :
    stream_(stream),
    base_offset_(stream.pos()),
    base_rva_(base_rva) {}

  TreeParser(BinaryStream& stream, Binary& pe) :
    stream_(stream),
    base_offset_(stream.pos()),

    pe_(&pe) {}

  std::unique_ptr<ResourceNode> parse() {
    const auto res_directory_table =
        stream_.peek<details::pe_resource_directory_table>();
    if (!res_directory_table) {
      return nullptr;
    }
    const size_t pos = stream_.pos();
    return parse_resource_node(*res_directory_table, pos, pos);
  }

  std::unique_ptr<ResourceNode> parse_resource_node(
      const details::pe_resource_directory_table& directory_table,
      uint32_t base_offset, uint32_t current_offset, uint32_t depth = 0
  );

  result<uint64_t> rva_to_offset(uint64_t rva) const {
    if (pe_ != nullptr) {
      return pe_->rva_to_offset(rva);
    }
    if (base_rva_ <= rva && rva < (base_rva_ + stream_.size())) {
      return (rva - base_rva_) + base_offset_;
    }
    return make_error_code(lief_errors::conversion_error);
  }

  private:
  BinaryStream& stream_;
  uint32_t base_offset_ = 0;
  uint32_t base_rva_ = 0;
  Binary* pe_ = nullptr;
  std::set<uint32_t> visited_;
};

std::unique_ptr<ResourceNode> TreeParser::parse_resource_node(
    const details::pe_resource_directory_table& directory_table,
    uint32_t base_offset, uint32_t current_offset, uint32_t depth
) {
  static constexpr auto MAX_DEPTH = 60;
  if (depth > MAX_DEPTH) {
    LIEF_WARN("Tree max depth reached (max: {})", MAX_DEPTH);
    return nullptr;
  }

  if (!visited_.insert(base_offset + current_offset).second) {
    if (visited_.size() == 1) {
      // Only print once
      LIEF_WARN("Infinite loop detected in resources");
    }
    return nullptr;
  }
  const uint32_t numberof_ID_entries = directory_table.NumberOfIDEntries;
  const uint32_t numberof_name_entries = directory_table.NumberOfNameEntries;

  size_t directory_array_offset =
      current_offset + sizeof(details::pe_resource_directory_table);
  auto entries_array =
      stream_.peek<details::pe_resource_directory_entries>(directory_array_offset);
  if (!entries_array) {
    return nullptr;
  }
  auto directory = std::make_unique<ResourceDirectory>(directory_table);
  directory->set_depth(depth);

  // Iterate over the children
  for (size_t idx = 0; idx < (numberof_name_entries + numberof_ID_entries); ++idx)
  {
    auto entry = stream_.peek<details::pe_resource_directory_entries>(
        directory_array_offset
    );
    if (!entry) {
      break;
    }
    directory_array_offset += sizeof(details::pe_resource_directory_entries);

    uint32_t data_rva = entry->RVA;
    uint32_t id = entry->NameID.IntegerID;

    result<std::u16string> name;

    // Get the resource name
    if ((id & 0x80000000) != 0u) {
      uint32_t offset = id & (~0x80000000);
      uint32_t string_offset = base_offset + offset;
      LIEF_DEBUG("base_offset={:#06x}, string_offset={:#06x}", base_offset,
                 string_offset);

      auto length = stream_.peek<uint16_t>(string_offset);
      if (length && *length <= 100) {
        name =
            stream_.peek_u16string_at(string_offset + sizeof(uint16_t), *length);
        if (!name) {
          LIEF_ERR("Corrupted node name for node id {}", id);
        }
      }
    }

    if ((0x80000000 & data_rva) == 0) { // We are on a leaf
      uint32_t offset = base_offset + data_rva;
      auto data_entry = stream_.peek<details::pe_resource_data_entry>(offset);
      if (!data_entry) {
        break;
      }

      if (!visited_.insert(offset).second) {
        break;
      }

      auto content_offset = rva_to_offset(data_entry->DataRVA);

      uint32_t content_size = data_entry->Size;
      uint32_t code_page = data_entry->Codepage;

      std::vector<uint8_t> leaf_data;
      if (content_offset && stream_.peek_data(leaf_data, *content_offset,
                                              content_size, data_entry->DataRVA))
      {
        auto node =
            std::make_unique<ResourceData>(std::move(leaf_data), code_page);

        node->set_depth(depth + 1);
        node->id(id);
        node->set_offset(*content_offset);
        if (name) {
          node->name(*name);
        }

        directory->push_child(std::move(node));
      } else {
        LIEF_DEBUG("Corrupted leaf for node id {}", id);
        break;
      }
    } else { // We are on a directory
      const uint32_t directory_rva = data_rva & (~0x80000000);
      const uint32_t offset = base_offset + directory_rva;
      auto next_dir_table =
          stream_.peek<details::pe_resource_directory_table>(offset);
      if (!next_dir_table) {
        LIEF_WARN("Corrupted directory for node id {}", id);
        break;
      }
      auto node =
          parse_resource_node(*next_dir_table, base_offset, offset, depth + 1);
      if (node == nullptr) {
        continue;
      }
      if (name) {
        node->name(*name);
      }
      node->id(id);
      directory->push_child(std::move(node));
    }
  }
  return directory;
}


std::unique_ptr<ResourceNode> ResourceNode::parse(BinaryStream& stream,
                                                  uint64_t rva) {
  TreeParser parser(stream, rva);
  return parser.parse();
}

std::unique_ptr<ResourceNode> ResourceNode::parse(const uint8_t* buffer,
                                                  size_t size, uint64_t rva) {
  SpanStream stream(buffer, size);
  return parse(stream, rva);
}

std::unique_ptr<ResourceNode> ResourceNode::parse(BinaryStream& stream,
                                                  const Binary& bin) {
  TreeParser parser(stream, const_cast<Binary&>(bin));
  return parser.parse();
}

ResourceNode::~ResourceNode() = default;

ResourceNode::ResourceNode(const ResourceNode& other) :
  Object{other},
  type_{other.type_},
  id_{other.id_},
  name_{other.name_},
  depth_{other.depth_} {
  childs_.reserve(other.childs_.size());
  for (const std::unique_ptr<ResourceNode>& node : other.childs_) {
    childs_.push_back(node->clone());
  }
}

ResourceNode& ResourceNode::operator=(const ResourceNode& other) {
  if (this == &other) {
    return *this;
  }
  type_ = other.type_;
  id_ = other.id_;
  name_ = other.name_;
  depth_ = other.depth_;

  childs_.reserve(other.childs_.size());
  for (const std::unique_ptr<ResourceNode>& node : other.childs_) {
    childs_.push_back(node->clone());
  }
  return *this;
}

void ResourceNode::swap(ResourceNode& other) {
  std::swap(type_, other.type_);
  std::swap(id_, other.id_);
  std::swap(name_, other.name_);
  std::swap(childs_, other.childs_);
  std::swap(depth_, other.depth_);
}

std::string ResourceNode::utf8_name() const {
  return u16tou8(name());
}

ResourceNode& ResourceNode::add_child(std::unique_ptr<ResourceNode> child) {
  child->depth_ = depth_ + 1;

  if (auto* dir = cast<ResourceDirectory>()) {
    child->has_name() ?
        dir->numberof_name_entries(dir->numberof_name_entries() + 1) :
        dir->numberof_id_entries(dir->numberof_id_entries() + 1);
    return **insert_child(std::move(child));
  }

  push_child(std::move(child));
  return *childs_.back();
}

void ResourceNode::delete_child(uint32_t id) {
  const auto it_node =
      std::find_if(childs_.begin(), childs_.end(),
                   [id](const std::unique_ptr<ResourceNode>& node) {
                     return node->id() == id;
                   });

  if (it_node == childs_.end()) {
    LIEF_ERR("Node with id {:d} not found", id);
    return;
  }

  delete_child(**it_node);
}

void ResourceNode::delete_child(const ResourceNode& node) {
  const auto it_node =
      std::find_if(childs_.begin(), childs_.end(),
                   [&node](const std::unique_ptr<ResourceNode>& intree_node) {
                     return *intree_node == node;
                   });

  if (it_node == childs_.end()) {
    LIEF_ERR("Node with id {} not found", node.id());
    return;
  }

  std::unique_ptr<ResourceNode>& inode = *it_node;

  if (auto* dir = cast<ResourceDirectory>()) {
    if (inode->has_name()) {
      dir->numberof_name_entries(dir->numberof_name_entries() - 1);
    } else {
      dir->numberof_id_entries(dir->numberof_id_entries() - 1);
    }
  }

  childs_.erase(it_node);
}

void ResourceNode::name(const std::string& name) {
  if (auto res = u8tou16(name)) {
    return this->name(std::move(*res));
  }
  LIEF_WARN("{} cannot be converted to UTF-16", name);
}


// This logic follows the description from the Microsoft documentation at
// https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#resource-directory-table
//
// "(remember that all the Name entries precede all the ID entries for the table).
// All entries for the table "are sorted in ascending order: the Name entries by
// case-sensitive string and the ID entries by numeric value."
ResourceNode::childs_t::iterator
    ResourceNode::insert_child(std::unique_ptr<ResourceNode> child) {
  const auto it =
      std::upper_bound(childs_.begin(), childs_.end(), child,
                       [](const std::unique_ptr<ResourceNode>& lhs,
                          const std::unique_ptr<ResourceNode>& rhs) {
                         if (lhs->has_name() && rhs->has_name()) {
                           // Case-sensitive string sort
                           return std::lexicographical_compare(lhs->name().begin(),
                                                               lhs->name().end(),
                                                               rhs->name().begin(),
                                                               rhs->name().end());
                         } else if (!lhs->has_name() && !rhs->has_name()) {
                           return lhs->id() < rhs->id();
                         } else {
                           // Named entries come first
                           return lhs->has_name();
                         }
                       });

  return childs_.insert(it, std::move(child));
}

const ResourceNode& ResourceNode::safe_get_at(size_t idx) const {
  class InvalidNode : public ResourceNode {
    public:
    InvalidNode() :
      ResourceNode(ResourceNode::TYPE::UNKNOWN) {}

    std::unique_ptr<ResourceNode> clone() const override {
      return nullptr;
    }
  };

  static InvalidNode INVALID;

  if (idx < childs_.size()) {
    return *childs_[idx];
  }
  return INVALID;
}

void ResourceNode::accept(Visitor& visitor) const {
  visitor.visit(*this);
}

bool operator==(const ResourceNode& LHS, const ResourceNode& RHS) {
  if (LHS.id() != RHS.id()) {
    return false;
  }

  if (LHS.utf8_name() != RHS.utf8_name()) {
    return false;
  }

  if (const auto* lhs_data = LHS.cast<ResourceData>()) {
    const auto* rhs_data = RHS.cast<ResourceData>();
    if (rhs_data == nullptr) {
      return false;
    }

    if (lhs_data->code_page() != rhs_data->code_page()) {
      return false;
    }

    if (lhs_data->reserved() != rhs_data->reserved()) {
      return false;
    }

    if (lhs_data->raw_content() != rhs_data->raw_content()) {
      return false;
    }
  }

  if (const auto* lhs_dir = LHS.cast<ResourceDirectory>()) {
    const auto* rhs_dir = RHS.cast<ResourceDirectory>();
    if (rhs_dir == nullptr) {
      return false;
    }

    if (lhs_dir->characteristics() != rhs_dir->characteristics()) {
      return false;
    }

    if (lhs_dir->time_date_stamp() != rhs_dir->time_date_stamp()) {
      return false;
    }

    if (lhs_dir->major_version() != rhs_dir->major_version()) {
      return false;
    }

    if (lhs_dir->minor_version() != rhs_dir->minor_version()) {
      return false;
    }

    if (lhs_dir->numberof_name_entries() != rhs_dir->numberof_name_entries()) {
      return false;
    }

    if (lhs_dir->numberof_id_entries() != rhs_dir->numberof_id_entries()) {
      return false;
    }

    auto children_lhs = lhs_dir->childs();
    auto children_rhs = rhs_dir->childs();
    if (children_lhs.size() != children_rhs.size()) {
      return false;
    }

    for (size_t i = 0; i < children_lhs.size(); ++i) {
      if (children_lhs[i] != children_rhs[i]) {
        return false;
      }
    }
  }
  return true;
}

std::ostream& operator<<(std::ostream& os, const ResourceNode& node) {
  ResourcesManager manager(const_cast<ResourceNode&>(node));
  os << manager.print();
  return os;
}


}