// Copyright 2015 The Crashpad Authors
//
// 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 "snapshot/win/pe_image_resource_reader.h"

#include <algorithm>
#include <memory>

#include "base/logging.h"

namespace {

void AddLanguageAndNeutralSublanguage(std::vector<uint16_t>* languages,
                                      uint16_t language) {
  languages->push_back(language);
  if (SUBLANGID(language) != SUBLANG_NEUTRAL) {
    languages->push_back(MAKELANGID(PRIMARYLANGID(language), SUBLANG_NEUTRAL));
  }
}

}  // namespace

namespace crashpad {

PEImageResourceReader::PEImageResourceReader()
    : resources_subrange_reader_(),
      module_base_(0),
      initialized_() {
}

PEImageResourceReader::~PEImageResourceReader() {}

bool PEImageResourceReader::Initialize(
    const ProcessSubrangeReader& module_subrange_reader,
    const IMAGE_DATA_DIRECTORY& resources_directory_entry) {
  INITIALIZATION_STATE_SET_INITIALIZING(initialized_);

  module_base_ = module_subrange_reader.Base();

  if (!resources_subrange_reader_.InitializeSubrange(
          module_subrange_reader,
          module_base_ + resources_directory_entry.VirtualAddress,
          resources_directory_entry.Size,
          "resources")) {
    return false;
  }

  INITIALIZATION_STATE_SET_VALID(initialized_);
  return true;
}

bool PEImageResourceReader::FindResourceByID(uint16_t type,
                                             uint16_t name,
                                             uint16_t language,
                                             WinVMAddress* address,
                                             WinVMSize* size,
                                             uint32_t* code_page) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  // The root resource directory is at the beginning of the resources area
  // within the module.
  const uint32_t name_directory_offset =
      GetEntryFromResourceDirectoryByID(0, type, true);
  if (!name_directory_offset) {
    return false;
  }

  const uint32_t language_directory_offset =
      GetEntryFromResourceDirectoryByID(name_directory_offset, name, true);
  if (!language_directory_offset) {
    return false;
  }

  // The definition of IMAGE_RESOURCE_DIRECTORY_ENTRY in <winnt.h> has a comment
  // saying that its offsets are relative to “the resource directory of the data
  // associated with this directory entry”. That could be interpreted to mean
  // that language_directory_offset is relative to name_directory_offset, since
  // the language directory entry is found within the name directory. This is
  // not correct. All resource offsets are relative to the resources area within
  // the module.
  const uint32_t data_offset = GetEntryFromResourceDirectoryByLanguage(
      language_directory_offset, language);
  if (!data_offset) {
    return false;
  }

  IMAGE_RESOURCE_DATA_ENTRY data_entry;
  if (!resources_subrange_reader_.ReadMemory(
          resources_subrange_reader_.Base() + data_offset,
          sizeof(data_entry),
          &data_entry)) {
    LOG(WARNING) << "could not read resource data entry from "
                 << resources_subrange_reader_.name();
    return false;
  }

  // The definition of IMAGE_RESOURCE_DATA_ENTRY in <winnt.h> has a comment
  // saying that OffsetToData is relative to the beginning of the resource data.
  // This is not correct. It’s module-relative.
  *address = module_base_ + data_entry.OffsetToData;
  *size = data_entry.Size;
  if (code_page) {
    *code_page = data_entry.CodePage;
  }

  return true;
}

uint32_t PEImageResourceReader::GetEntryFromResourceDirectoryByID(
    uint32_t language_directory_offset,
    uint16_t id,
    bool want_subdirectory) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY> entries_by_id;
  if (!ReadResourceDirectory(
          language_directory_offset, nullptr, nullptr, &entries_by_id)) {
    return 0;
  }

  const auto entry_it =
      std::find_if(entries_by_id.begin(),
                   entries_by_id.end(),
                   [id](const IMAGE_RESOURCE_DIRECTORY_ENTRY& entry) {
                     return !entry.NameIsString && entry.Id == id;
                   });
  if (entry_it != entries_by_id.end()) {
    if ((entry_it->DataIsDirectory != 0) != want_subdirectory) {
      LOG(WARNING) << "expected " << (want_subdirectory ? "" : "non-")
                   << "directory for entry id " << id << " in "
                   << resources_subrange_reader_.name();
      return 0;
    }

    return entry_it->DataIsDirectory ? entry_it->OffsetToDirectory
                                     : entry_it->OffsetToData;
  }

  return 0;
}

uint32_t PEImageResourceReader::GetEntryFromResourceDirectoryByLanguage(
    uint32_t resource_directory_offset,
    uint16_t language) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY> entries_by_language;
  if (!ReadResourceDirectory(
          resource_directory_offset, nullptr, nullptr, &entries_by_language)) {
    return 0;
  }

  if (entries_by_language.empty()) {
    return 0;
  }

  // https://msdn.microsoft.com/library/cc194810.aspx
  //
  // TODO(mark): It seems like FindResourceEx() might do something more complex.
  // It would be best to mimic its behavior.
  std::vector<uint16_t> try_languages;
  if (PRIMARYLANGID(language) != LANG_NEUTRAL) {
    AddLanguageAndNeutralSublanguage(&try_languages, language);
  } else {
    if (SUBLANGID(language) != SUBLANG_SYS_DEFAULT) {
      AddLanguageAndNeutralSublanguage(&try_languages,
                                       LANGIDFROMLCID(GetThreadLocale()));
      AddLanguageAndNeutralSublanguage(&try_languages,
                                       LANGIDFROMLCID(GetUserDefaultLCID()));
    }
    if (SUBLANGID(language) != SUBLANG_DEFAULT) {
      AddLanguageAndNeutralSublanguage(&try_languages,
                                       LANGIDFROMLCID(GetSystemDefaultLCID()));
    }
  }

  try_languages.push_back(MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));
  try_languages.push_back(MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT));

  for (const auto try_language : try_languages) {
    const auto entry_it = std::find_if(
        entries_by_language.begin(),
        entries_by_language.end(),
        [try_language](const IMAGE_RESOURCE_DIRECTORY_ENTRY& entry) {
          return !entry.NameIsString && entry.Id == try_language;
        });
    if (entry_it != entries_by_language.end()) {
      if (entry_it->DataIsDirectory) {
        LOG(WARNING) << "expected non-directory for entry language "
                     << try_language << " in "
                     << resources_subrange_reader_.name();
        return 0;
      }

      return entry_it->OffsetToData;
    }
  }

  // Fall back to the first entry in the list.
  const auto& entry = entries_by_language.front();
  if (entry.DataIsDirectory) {
    LOG(WARNING) << "expected non-directory for entry in "
                 << resources_subrange_reader_.name();
    return 0;
  }

  return entry.OffsetToData;
}

bool PEImageResourceReader::ReadResourceDirectory(
    uint32_t resource_directory_offset,
    IMAGE_RESOURCE_DIRECTORY* resource_directory,
    std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY>* named_entries,
    std::vector<IMAGE_RESOURCE_DIRECTORY_ENTRY>* id_entries) const {
  INITIALIZATION_STATE_DCHECK_VALID(initialized_);

  // resource_directory is optional, but it’s still needed locally even if the
  // caller isn’t interested in it.
  std::unique_ptr<IMAGE_RESOURCE_DIRECTORY> local_resource_directory;
  if (!resource_directory) {
    local_resource_directory.reset(new IMAGE_RESOURCE_DIRECTORY);
    resource_directory = local_resource_directory.get();
  }

  const WinVMAddress address =
      resources_subrange_reader_.Base() + resource_directory_offset;

  if (!resources_subrange_reader_.ReadMemory(
          address, sizeof(*resource_directory), resource_directory)) {
    LOG(WARNING) << "could not read resource directory from "
                 << resources_subrange_reader_.name();
    return false;
  }

  if (named_entries) {
    named_entries->clear();
    named_entries->resize(resource_directory->NumberOfNamedEntries);
    if (!named_entries->empty() &&
        !resources_subrange_reader_.ReadMemory(
            address + sizeof(*resource_directory),
            named_entries->size() * sizeof((*named_entries)[0]),
            &(*named_entries)[0])) {
      LOG(WARNING) << "could not read resource directory named entries from "
                   << resources_subrange_reader_.name();
      return false;
    }
  }

  if (id_entries) {
    id_entries->clear();
    id_entries->resize(resource_directory->NumberOfIdEntries);
    if (!id_entries->empty() &&
        !resources_subrange_reader_.ReadMemory(
            address + sizeof(*resource_directory) +
                resource_directory->NumberOfNamedEntries *
                    sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),
            id_entries->size() * sizeof((*id_entries)[0]),
            &(*id_entries)[0])) {
      LOG(WARNING) << "could not read resource directory ID entries from "
                   << resources_subrange_reader_.name();
      return false;
    }
  }

  return true;
}

}  // namespace crashpad
