chromium/chrome/browser/ash/file_system_provider/operations/get_metadata.cc

// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ash/file_system_provider/operations/get_metadata.h"

#include <stdint.h>

#include <memory>
#include <string>
#include <tuple>
#include <utility>

#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_interface.h"
#include "chrome/common/extensions/api/file_system_provider.h"
#include "chrome/common/extensions/api/file_system_provider_internal.h"

namespace ash::file_system_provider::operations {
namespace {

// Convert |value| into |output|. If parsing fails, then returns false.
bool ConvertRequestValueToFileInfo(const RequestValue& value,
                                   int fields,
                                   bool root_entry,
                                   EntryMetadata* output) {
  using extensions::api::file_system_provider::EntryMetadata;
  using extensions::api::file_system_provider_internal::
      GetMetadataRequestedSuccess::Params;

  const Params* params = value.get_metadata_success_params();
  if (!params)
    return false;

  if (!ValidateIDLEntryMetadata(params->metadata, fields, root_entry))
    return false;

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_NAME)
    output->name = std::make_unique<std::string>(*params->metadata.name);

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_IS_DIRECTORY)
    output->is_directory =
        std::make_unique<bool>(*params->metadata.is_directory);

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_SIZE)
    output->size =
        std::make_unique<int64_t>(static_cast<int64_t>(*params->metadata.size));

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_MODIFICATION_TIME) {
    const std::string* input_modification_time =
        params->metadata.modification_time->additional_properties.FindString(
            "value");

    if (input_modification_time) {
      // Allow to pass invalid modification time, since there is no way to
      // verify it easily on any earlier stage.
      base::Time output_modification_time;
      std::ignore = base::Time::FromString(input_modification_time->c_str(),
                                           &output_modification_time);
      output->modification_time =
          std::make_unique<base::Time>(output_modification_time);
    }
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_MIME_TYPE &&
      params->metadata.mime_type) {
    output->mime_type =
        std::make_unique<std::string>(*params->metadata.mime_type);
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL &&
      params->metadata.thumbnail) {
    output->thumbnail =
        std::make_unique<std::string>(*params->metadata.thumbnail);
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_CLOUD_IDENTIFIER &&
      params->metadata.cloud_identifier) {
    output->cloud_identifier = std::make_unique<CloudIdentifier>(
        params->metadata.cloud_identifier->provider_name,
        params->metadata.cloud_identifier->id);
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_CLOUD_FILE_INFO &&
      params->metadata.cloud_file_info &&
      params->metadata.cloud_file_info->version_tag.has_value()) {
    output->cloud_file_info = std::make_unique<CloudFileInfo>(
        params->metadata.cloud_file_info->version_tag.value());
  }

  return true;
}

}  // namespace

bool ValidateIDLEntryMetadata(
    const extensions::api::file_system_provider::EntryMetadata& metadata,
    int fields,
    bool root_entry) {
  using extensions::api::file_system_provider::EntryMetadata;

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_IS_DIRECTORY &&
      !metadata.is_directory) {
    return false;
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_NAME &&
      (!metadata.name || !ValidateName(*metadata.name, root_entry))) {
    return false;
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_SIZE &&
      !metadata.size) {
    return false;
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_MODIFICATION_TIME) {
    if (!metadata.modification_time)
      return false;
    const std::string* input_modification_time =
        metadata.modification_time->additional_properties.FindString("value");
    if (!input_modification_time) {
      return false;
    }
  }

  // Empty MIME type is not allowed, but for backward compability it's
  // accepted. Note, that there is a warning in custom bindings for it.

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL &&
      metadata.thumbnail) {
    // Sanity check for the thumbnail format. Note, that another, more
    // granural check is done in custom bindings. Note, this is an extra check
    // only for the security reasons.
    const std::string expected_prefix = "data:";
    std::string thumbnail_prefix =
        metadata.thumbnail->substr(0, expected_prefix.size());
    base::ranges::transform(thumbnail_prefix, thumbnail_prefix.begin(),
                            ::tolower);

    if (expected_prefix != thumbnail_prefix)
      return false;
  }

  if (fields & ProvidedFileSystemInterface::METADATA_FIELD_CLOUD_IDENTIFIER &&
      (!metadata.cloud_identifier ||
       !ValidateCloudIdentifier(*metadata.cloud_identifier))) {
    return false;
  }

  return true;
}

bool ValidateName(const std::string& name, bool root_entry) {
  if (root_entry)
    return name.empty();
  return !name.empty() && name.find('/') == std::string::npos;
}

bool ValidateCloudIdentifier(
    const extensions::api::file_system_provider::CloudIdentifier&
        cloud_identifier) {
  return !cloud_identifier.provider_name.empty() &&
         !cloud_identifier.id.empty();
}

GetMetadata::GetMetadata(
    RequestDispatcher* dispatcher,
    const ProvidedFileSystemInfo& file_system_info,
    const base::FilePath& entry_path,
    ProvidedFileSystemInterface::MetadataFieldMask fields,
    ProvidedFileSystemInterface::GetMetadataCallback callback)
    : Operation(dispatcher, file_system_info),
      entry_path_(entry_path),
      fields_(fields),
      callback_(std::move(callback)) {
  DCHECK_NE(0, fields_);
}

GetMetadata::~GetMetadata() = default;

bool GetMetadata::Execute(int request_id) {
  using extensions::api::file_system_provider::GetMetadataRequestedOptions;

  GetMetadataRequestedOptions options;
  options.file_system_id = file_system_info_.file_system_id();
  options.request_id = request_id;
  options.entry_path = entry_path_.AsUTF8Unsafe();
  options.is_directory =
      fields_ & ProvidedFileSystemInterface::METADATA_FIELD_IS_DIRECTORY;
  options.name = fields_ & ProvidedFileSystemInterface::METADATA_FIELD_NAME;
  options.size = fields_ & ProvidedFileSystemInterface::METADATA_FIELD_SIZE;
  options.modification_time =
      fields_ & ProvidedFileSystemInterface::METADATA_FIELD_MODIFICATION_TIME;
  options.mime_type =
      fields_ & ProvidedFileSystemInterface::METADATA_FIELD_MIME_TYPE;
  options.thumbnail =
      fields_ & ProvidedFileSystemInterface::METADATA_FIELD_THUMBNAIL;
  options.cloud_identifier =
      fields_ & ProvidedFileSystemInterface::METADATA_FIELD_CLOUD_IDENTIFIER;
  options.cloud_file_info =
      fields_ & ProvidedFileSystemInterface::METADATA_FIELD_CLOUD_FILE_INFO;

  return SendEvent(
      request_id,
      extensions::events::FILE_SYSTEM_PROVIDER_ON_GET_METADATA_REQUESTED,
      extensions::api::file_system_provider::OnGetMetadataRequested::kEventName,
      extensions::api::file_system_provider::OnGetMetadataRequested::Create(
          options));
}

void GetMetadata::OnSuccess(/*request_id=*/int,
                            const RequestValue& result,
                            bool has_more) {
  DCHECK(callback_);
  std::unique_ptr<EntryMetadata> metadata(new EntryMetadata);
  const bool convert_result = ConvertRequestValueToFileInfo(
      result, fields_, entry_path_.AsUTF8Unsafe() == FILE_PATH_LITERAL("/"),
      metadata.get());

  if (!convert_result) {
    LOG(ERROR) << "Failed to parse a response for the get metadata operation.";
    std::move(callback_).Run(nullptr, base::File::FILE_ERROR_IO);
    return;
  }

  std::move(callback_).Run(std::move(metadata), base::File::FILE_OK);
}

void GetMetadata::OnError(/*request_id=*/int,
                          /*result=*/const RequestValue&,
                          base::File::Error error) {
  DCHECK(callback_);
  std::move(callback_).Run(nullptr, error);
}

}  // namespace ash::file_system_provider::operations