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

#include "components/webapps/isolated_web_apps/reading/response_reader.h"

#include <memory>

#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/types/expected.h"
#include "base/types/expected_macros.h"
#include "components/web_package/mojom/web_bundle_parser.mojom-forward.h"
#include "components/web_package/signed_web_bundles/signed_web_bundle_id.h"
#include "components/webapps/isolated_web_apps/reading/signed_web_bundle_reader.h"
#include "services/network/public/cpp/resource_request.h"
#include "url/gurl.h"

namespace web_app {

namespace {

network::ResourceRequest RemoveQuery(
    network::ResourceRequest resource_request) {
  GURL::Replacements replacements;
  replacements.ClearQuery();
  resource_request.url = resource_request.url.ReplaceComponents(replacements);
  return resource_request;
}

}  // namespace

IsolatedWebAppResponseReaderImpl::IsolatedWebAppResponseReaderImpl(
    std::unique_ptr<SignedWebBundleReader> reader,
    content::BrowserContext* browser_context)
    : reader_(std::move(reader)), browser_context_(*browser_context) {}

IsolatedWebAppResponseReaderImpl::~IsolatedWebAppResponseReaderImpl() = default;

web_package::SignedWebBundleIntegrityBlock
IsolatedWebAppResponseReaderImpl::GetIntegrityBlock() {
  return reader_->GetIntegrityBlock();
}

void IsolatedWebAppResponseReaderImpl::ReadResponse(
    const network::ResourceRequest& resource_request,
    ReadResponseCallback callback) {
  // Remove query parameters from the request URL, if it has any. Resources
  // within Signed Web Bundles used for Isolated Web Apps never have username,
  // password, or fragment, just like resources within Signed Web Bundles and
  // normal Web Bundles. Removing these from request URLs is done by the
  // `SignedWebBundleReader`. However, in addition, resources in Signed Web
  // Bundles used for Isolated Web Apps can also never have query parameters,
  // which we need to remove here.
  //
  // Conceptually, we treat the resources in Signed Web Bundles for Isolated Web
  // Apps more like files served by a file server (which also strips query
  // parameters before looking up the file), and not like HTTP exchanges like
  // they are used for Signed Exchanges (SXG).
  reader_->ReadResponse(
      resource_request.url.has_query() ? RemoveQuery(resource_request)
                                       : resource_request,
      base::BindOnce(&IsolatedWebAppResponseReaderImpl::OnResponseRead,
                     // `base::Unretained` is safe to use here, since `this`
                     // owns `reader_`.
                     base::Unretained(this), std::move(callback)));
}

void IsolatedWebAppResponseReaderImpl::OnResponseRead(
    ReadResponseCallback callback,
    base::expected<web_package::mojom::BundleResponsePtr,
                   SignedWebBundleReader::ReadResponseError> response_head) {
  std::move(callback).Run(
      std::move(response_head)
          .transform([this](web_package::mojom::BundleResponsePtr ptr) {
            // Since `this` owns `reader_`, we only pass a weak pointer to it to
            // the `Response` object. If `this` is deleted, it makes sense that
            // the pointer to the `reader_` contained in `Response` also becomes
            // invalid.
            return Response(std::move(ptr), reader_->AsWeakPtr());
          })
          .transform_error(&Error::FromSignedWebBundleReaderError));
}

void IsolatedWebAppResponseReaderImpl::Close(base::OnceClosure callback) {
  reader_->Close(base::BindOnce(&IsolatedWebAppResponseReaderImpl::OnClosed,
                                base::Unretained(this), std::move(callback)));
}

void IsolatedWebAppResponseReaderImpl::OnClosed(base::OnceClosure callback) {
  std::move(callback).Run();
}

IsolatedWebAppResponseReader::Response::Response(
    web_package::mojom::BundleResponsePtr head,
    base::WeakPtr<SignedWebBundleReader> reader)
    : head_(std::move(head)), reader_(std::move(reader)) {}

IsolatedWebAppResponseReader::Response::Response(Response&&) = default;

IsolatedWebAppResponseReader::Response&
IsolatedWebAppResponseReader::Response::operator=(Response&&) = default;

IsolatedWebAppResponseReader::Response::~Response() = default;

void IsolatedWebAppResponseReader::Response::ReadBody(
    mojo::ScopedDataPipeProducerHandle producer_handle,
    base::OnceCallback<void(net::Error net_error)> callback) {
  if (!reader_ || reader_->IsClosed()) {
    // The weak pointer to `reader_` might no longer be valid when this is
    // called. Also the reader might be closed.
    std::move(callback).Run(net::ERR_FAILED);
    return;
  }
  reader_->ReadResponseBody(head_->Clone(), std::move(producer_handle),
                            std::move(callback));
}

// static
IsolatedWebAppResponseReader::Error
IsolatedWebAppResponseReader::Error::FromSignedWebBundleReaderError(
    const SignedWebBundleReader::ReadResponseError& error) {
  using Type = SignedWebBundleReader::ReadResponseError::Type;
  switch (error.type) {
    case Type::kFormatError:
      return Error(Error::Type::kFormatError, error.message);
    case Type::kParserInternalError:
      return Error(Error::Type::kParserInternalError, error.message);
    case Type::kResponseNotFound:
      return Error(Error::Type::kResponseNotFound, error.message);
  }
}

}  // namespace web_app
