// Copyright 2019 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/sharing/shared_clipboard/remote_copy_message_handler.h"

#include <algorithm>
#include <optional>
#include <string>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "base/trace_event/trace_event.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/grit/generated_resources.h"
#include "components/sharing_message/proto/remote_copy_message.pb.h"
#include "components/sharing_message/proto/sharing_message.pb.h"
#include "components/sharing_message/sharing_metrics.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "ui/base/accelerators/accelerator.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/gfx/image/image.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/message_center/public/cpp/notifier_id.h"

namespace {

constexpr char kRemoteCopyAllowedOrigin[] = "https://googleusercontent.com";

constexpr size_t kMaxImageDownloadSize = 5 * 1024 * 1024;

const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
    net::DefineNetworkTrafficAnnotation("remote_copy_message_handler",
                                        R"(
          semantics {
            sender: "RemoteCopyMessageHandler"
            description:
              "Fetches an image from a URL specified in an FCM message."
            trigger:
              "The user sent an image to this device from another device that "
              "they control."
            data:
              "An image URL, from a Google storage service like blobstore."
            destination: GOOGLE_OWNED_SERVICE
          }
          policy {
            cookies_allowed: NO
            setting:
              "Users can disable this behavior by signing out of Chrome."
            policy_exception_justification:
              "Can be controlled via Chrome sign-in."
          })");

std::u16string GetTextNotificationTitle(const std::string& device_name) {
  return device_name.empty()
             ? l10n_util::GetStringUTF16(
                   IDS_SHARING_REMOTE_COPY_NOTIFICATION_TITLE_TEXT_CONTENT_UNKNOWN_DEVICE)
             : l10n_util::GetStringFUTF16(
                   IDS_SHARING_REMOTE_COPY_NOTIFICATION_TITLE_TEXT_CONTENT,
                   base::UTF8ToUTF16(device_name));
}

std::u16string GetImageNotificationTitle(const std::string& device_name) {
  return device_name.empty()
             ? l10n_util::GetStringUTF16(
                   IDS_SHARING_REMOTE_COPY_NOTIFICATION_TITLE_IMAGE_CONTENT_UNKNOWN_DEVICE)
             : l10n_util::GetStringFUTF16(
                   IDS_SHARING_REMOTE_COPY_NOTIFICATION_TITLE_IMAGE_CONTENT,
                   base::UTF8ToUTF16(device_name));
}

}  // namespace

RemoteCopyMessageHandler::RemoteCopyMessageHandler(Profile* profile)
    : profile_(profile), allowed_origin_(kRemoteCopyAllowedOrigin) {}

RemoteCopyMessageHandler::~RemoteCopyMessageHandler() = default;

void RemoteCopyMessageHandler::OnMessage(
    components_sharing_message::SharingMessage message,
    DoneCallback done_callback) {
  DCHECK(message.has_remote_copy_message());
  TRACE_EVENT0("sharing", "RemoteCopyMessageHandler::OnMessage");

  // First cancel any pending async tasks that might otherwise overwrite the
  // results of the more recent message.
  CancelAsyncTasks();

  device_name_ = message.sender_device_name();

  switch (message.remote_copy_message().content_case()) {
    case components_sharing_message::RemoteCopyMessage::kText:
      HandleText(message.remote_copy_message().text());
      break;
    case components_sharing_message::RemoteCopyMessage::kImageUrl:
      HandleImage(message.remote_copy_message().image_url());
      break;
    case components_sharing_message::RemoteCopyMessage::CONTENT_NOT_SET:
      NOTREACHED();
  }

  std::move(done_callback).Run(/*response=*/nullptr);
}

void RemoteCopyMessageHandler::HandleText(const std::string& text) {
  TRACE_EVENT1("sharing", "RemoteCopyMessageHandler::HandleText", "text_size",
               text.size());

  if (text.empty()) {
    Finish(RemoteCopyHandleMessageResult::kFailureEmptyText);
    return;
  }

  {
    ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
        .WriteText(base::UTF8ToUTF16(text));
  }
  ShowNotification(GetTextNotificationTitle(device_name_), SkBitmap());
  Finish(RemoteCopyHandleMessageResult::kSuccessHandledText);
}

void RemoteCopyMessageHandler::HandleImage(const std::string& image_url) {
  TRACE_EVENT0("sharing", "RemoteCopyMessageHandler::HandleImage");

  GURL url(image_url);

  if (!network::IsUrlPotentiallyTrustworthy(url)) {
    Finish(RemoteCopyHandleMessageResult::kFailureImageUrlNotTrustworthy);
    return;
  }

  if (!IsImageSourceAllowed(url)) {
    Finish(RemoteCopyHandleMessageResult::kFailureImageOriginNotAllowed);
    return;
  }

  auto request = std::make_unique<network::ResourceRequest>();
  request->url = url;
  // This request should be unauthenticated (no cookies), and shouldn't be
  // stored in the cache (this URL is only fetched once, ever.)
  request->load_flags = net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
  request->credentials_mode = network::mojom::CredentialsMode::kOmit;

  url_loader_ =
      network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation);
  // Unretained(this) is safe here because |this| owns |url_loader_|.
  url_loader_->DownloadToString(
      profile_->GetDefaultStoragePartition()
          ->GetURLLoaderFactoryForBrowserProcess()
          .get(),
      base::BindOnce(&RemoteCopyMessageHandler::OnURLLoadComplete,
                     base::Unretained(this)),
      kMaxImageDownloadSize);
}

bool RemoteCopyMessageHandler::IsImageSourceAllowed(const GURL& image_url) {
  // The actual image URL may have a hash in the subdomain. This means we
  // cannot match the entire host - we'll match the domain instead.
  return image_url.SchemeIs(allowed_origin_.scheme()) &&
         image_url.DomainIs(allowed_origin_.host()) &&
         image_url.EffectiveIntPort() == allowed_origin_.EffectiveIntPort();
}

void RemoteCopyMessageHandler::OnURLLoadComplete(
    std::optional<std::string> content) {
  TRACE_EVENT0("sharing", "RemoteCopyMessageHandler::OnURLLoadComplete");

  url_loader_.reset();
  if (!content || content->empty()) {
    Finish(RemoteCopyHandleMessageResult::kFailureNoImageContentLoaded);
    return;
  }

  ImageDecoder::Start(this, std::move(*content));
}

void RemoteCopyMessageHandler::OnImageDecoded(const SkBitmap& image) {
  TRACE_EVENT0("sharing", "RemoteCopyMessageHandler::OnImageDecoded");

  if (image.drawsNothing()) {
    Finish(RemoteCopyHandleMessageResult::kFailureDecodedImageDrawsNothing);
    return;
  }

  WriteImageAndShowNotification(image);
}

void RemoteCopyMessageHandler::OnDecodeImageFailed() {
  Finish(RemoteCopyHandleMessageResult::kFailureDecodeImageFailed);
}

void RemoteCopyMessageHandler::WriteImageAndShowNotification(
    const SkBitmap& image) {
  TRACE_EVENT1("sharing",
               "RemoteCopyMessageHandler::WriteImageAndShowNotification",
               "bytes", image.computeByteSize());

  {
    ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
        .WriteImage(image);
  }

  ShowNotification(GetImageNotificationTitle(device_name_), image);
  Finish(RemoteCopyHandleMessageResult::kSuccessHandledImage);
}

void RemoteCopyMessageHandler::ShowNotification(const std::u16string& title,
                                                const SkBitmap& image) {
  TRACE_EVENT0("sharing", "RemoteCopyMessageHandler::ShowNotification");

  message_center::RichNotificationData rich_notification_data;
  rich_notification_data.vector_small_image =
      &(features::IsRoundedIconsEnabled() ? kDevicesIcon : kDevicesOldIcon);
  rich_notification_data.renotify = true;

  ui::Accelerator paste_accelerator(ui::VKEY_V, ui::EF_PLATFORM_ACCELERATOR);

  message_center::Notification notification(
      message_center::NOTIFICATION_TYPE_SIMPLE,
      base::Uuid::GenerateRandomV4().AsLowercaseString(), title,
      l10n_util::GetStringFUTF16(
          IDS_SHARING_REMOTE_COPY_NOTIFICATION_DESCRIPTION,
          paste_accelerator.GetShortcutText()),
      ui::ImageModel(),
      /*display_source=*/std::u16string(),
      /*origin_url=*/GURL(), message_center::NotifierId(),
      rich_notification_data,
      /*delegate=*/nullptr);

  NotificationDisplayServiceFactory::GetForProfile(profile_)->Display(
      NotificationHandler::Type::SHARING, notification, /*metadata=*/nullptr);
}

void RemoteCopyMessageHandler::Finish(RemoteCopyHandleMessageResult result) {
  TRACE_EVENT1("sharing", "RemoteCopyMessageHandler::Finish", "result", result);
  device_name_.clear();
}

void RemoteCopyMessageHandler::CancelAsyncTasks() {
  url_loader_.reset();
  ImageDecoder::Cancel(this);
}
