text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "third_party/blink/renderer/modules/cache_storage/inspector_cache_storage_agent.h"
  
  #include <algorithm>
  #include <memory>
  #include <utility>
  #include "base/macros.h"
  #include "base/memory/scoped_refptr.h"
  #include "services/service_manager/public/cpp/interface_provider.h"
  #include "third_party/blink/public/platform/modules/cache_storage/cache_storage.mojom-blink.h"
  #include "third_party/blink/public/platform/modules/serviceworker/web_service_worker_request.h"
  #include "third_party/blink/public/platform/modules/serviceworker/web_service_worker_response.h"
  #include "third_party/blink/public/platform/platform.h"
  #include "third_party/blink/public/platform/web_security_origin.h"
  #include "third_party/blink/public/platform/web_string.h"
  #include "third_party/blink/public/platform/web_url.h"
  #include "third_party/blink/public/platform/web_vector.h"
  #include "third_party/blink/renderer/core/dom/document.h"
  #include "third_party/blink/renderer/core/execution_context/execution_context.h"
  #include "third_party/blink/renderer/core/fileapi/file_reader_loader.h"
  #include "third_party/blink/renderer/core/fileapi/file_reader_loader_client.h"
  #include "third_party/blink/renderer/core/frame/local_frame.h"
  #include "third_party/blink/renderer/core/inspector/inspected_frames.h"
  #include "third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h"
  #include "third_party/blink/renderer/platform/blob/blob_data.h"
  #include "third_party/blink/renderer/platform/heap/handle.h"
  #include "third_party/blink/renderer/platform/network/http_header_map.h"
  #include "third_party/blink/renderer/platform/shared_buffer.h"
  #include "third_party/blink/renderer/platform/weborigin/kurl.h"
  #include "third_party/blink/renderer/platform/weborigin/security_origin.h"
  #include "third_party/blink/renderer/platform/wtf/functional.h"
  #include "third_party/blink/renderer/platform/wtf/ref_counted.h"
  #include "third_party/blink/renderer/platform/wtf/text/base64.h"
  #include "third_party/blink/renderer/platform/wtf/time.h"
  #include "third_party/blink/renderer/platform/wtf/vector.h"
  
  using blink::protocol::Array;
  // Renaming Cache since there is another blink::Cache.
  using ProtocolCache = blink::protocol::CacheStorage::Cache;
  using blink::protocol::CacheStorage::Cache;
  using blink::protocol::CacheStorage::CachedResponse;
  using blink::protocol::CacheStorage::DataEntry;
  using blink::protocol::CacheStorage::Header;
  // Renaming Response since there is another blink::Response.
  using ProtocolResponse = blink::protocol::Response;
  
  using DeleteCacheCallback =
      blink::protocol::CacheStorage::Backend::DeleteCacheCallback;
  using DeleteEntryCallback =
      blink::protocol::CacheStorage::Backend::DeleteEntryCallback;
  using RequestCacheNamesCallback =
      blink::protocol::CacheStorage::Backend::RequestCacheNamesCallback;
  using RequestEntriesCallback =
      blink::protocol::CacheStorage::Backend::RequestEntriesCallback;
  using RequestCachedResponseCallback =
      blink::protocol::CacheStorage::Backend::RequestCachedResponseCallback;
  
  namespace blink {
  
  namespace {
  
  String BuildCacheId(const String& security_origin, const String& cache_name) {
    String id(security_origin);
    id.append('|');
    id.append(cache_name);
    return id;
  }
  
  ProtocolResponse ParseCacheId(const String& id,
                                String* security_origin,
                                String* cache_name) {
    size_t pipe = id.find('|');
    if (pipe == WTF::kNotFound)
      return ProtocolResponse::Error("Invalid cache id.");
    *security_origin = id.Substring(0, pipe);
    *cache_name = id.Substring(pipe + 1);
    return ProtocolResponse::OK();
  }
  
  ProtocolResponse GetExecutionContext(InspectedFrames* frames,
                                       const String& security_origin,
                                       ExecutionContext** context) {
    LocalFrame* frame = frames->FrameWithSecurityOrigin(security_origin);
    if (!frame)
      return ProtocolResponse::Error("No frame with origin " + security_origin);
  
    blink::Document* document = frame->GetDocument();
    if (!document)
      return ProtocolResponse::Error("No execution context found");
  
    *context = document;
  
    return ProtocolResponse::OK();
  }
  
  ProtocolResponse AssertCacheStorage(
      const String& security_origin,
      InspectedFrames* frames,
      InspectorCacheStorageAgent::CachesMap* caches,
      mojom::blink::CacheStorage** result) {
    scoped_refptr<const SecurityOrigin> sec_origin =
        SecurityOrigin::CreateFromString(security_origin);
  
    // Cache Storage API is restricted to trustworthy origins.
    if (!sec_origin->IsPotentiallyTrustworthy()) {
      return ProtocolResponse::Error(
          sec_origin->IsPotentiallyTrustworthyErrorMessage());
    }
  
    ExecutionContext* context = nullptr;
    ProtocolResponse response =
        GetExecutionContext(frames, security_origin, &context);
    if (!response.isSuccess())
      return response;
  
    auto it = caches->find(security_origin);
  
    if (it == caches->end()) {
      mojom::blink::CacheStoragePtr cache_storage_ptr;
      context->GetInterfaceProvider()->GetInterface(
          mojo::MakeRequest(&cache_storage_ptr));
      *result = cache_storage_ptr.get();
      caches->Set(security_origin, std::move(cache_storage_ptr));
    } else {
      *result = it->value.get();
    }
  
    return ProtocolResponse::OK();
  }
  
  ProtocolResponse AssertCacheStorageAndNameForId(
      const String& cache_id,
      InspectedFrames* frames,
      String* cache_name,
      InspectorCacheStorageAgent::CachesMap* caches,
      mojom::blink::CacheStorage** result) {
    String security_origin;
    ProtocolResponse response =
        ParseCacheId(cache_id, &security_origin, cache_name);
    if (!response.isSuccess())
      return response;
    return AssertCacheStorage(security_origin, frames, caches, result);
  }
  
  CString CacheStorageErrorString(mojom::blink::CacheStorageError error) {
    switch (error) {
      case mojom::blink::CacheStorageError::kErrorNotImplemented:
        return CString("not implemented.");
      case mojom::blink::CacheStorageError::kErrorNotFound:
        return CString("not found.");
      case mojom::blink::CacheStorageError::kErrorExists:
        return CString("cache already exists.");
      case mojom::blink::CacheStorageError::kErrorQuotaExceeded:
        return CString("quota exceeded.");
      case mojom::blink::CacheStorageError::kErrorCacheNameNotFound:
        return CString("cache not found.");
      case mojom::blink::CacheStorageError::kErrorQueryTooLarge:
        return CString("operation too large.");
      case mojom::blink::CacheStorageError::kErrorStorage:
        return CString("storage failure.");
      case mojom::blink::CacheStorageError::kSuccess:
        // This function should only be called upon error.
        break;
    }
    NOTREACHED();
    return "";
  }
  
  struct DataRequestParams {
    String cache_name;
    int skip_count;
    int page_size;
  };
  
  struct RequestResponse {
    String request_url;
    String request_method;
    HTTPHeaderMap request_headers;
    int response_status;
    String response_status_text;
    double response_time;
    HTTPHeaderMap response_headers;
  };
  
  class ResponsesAccumulator : public RefCounted<ResponsesAccumulator> {
   public:
    ResponsesAccumulator(int num_responses,
                         const DataRequestParams& params,
                         mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr,
                         std::unique_ptr<RequestEntriesCallback> callback)
        : params_(params),
          num_responses_left_(num_responses),
          responses_(static_cast<size_t>(num_responses)),
          cache_ptr_(std::move(cache_ptr)),
          callback_(std::move(callback)) {}
  
    void Dispatch(const Vector<WebServiceWorkerRequest>& requests) {
      for (const auto& request : requests) {
        cache_ptr_->Match(
            request, mojom::blink::QueryParams::New(),
            WTF::Bind(
                [](scoped_refptr<ResponsesAccumulator> accumulator,
                   WebServiceWorkerRequest request,
                   mojom::blink::MatchResultPtr result) {
                  if (result->is_status()) {
                    accumulator->SendFailure(result->get_status());
                  } else {
                    accumulator->AddRequestResponsePair(request,
                                                        result->get_response());
                  }
                },
                scoped_refptr<ResponsesAccumulator>(this), request));
      }
    }
  
    void AddRequestResponsePair(
        const WebServiceWorkerRequest& request,
        const mojom::blink::FetchAPIResponsePtr& response) {
      DCHECK_GT(num_responses_left_, 0);
      RequestResponse& request_response =
          responses_.at(responses_.size() - num_responses_left_);
  
      request_response.request_url = request.Url().GetString();
      request_response.request_method = request.Method();
      request_response.request_headers = request.Headers();
      request_response.response_status = response->status_code;
      request_response.response_status_text = response->status_text;
      request_response.response_time = response->response_time.ToDoubleT();
      for (const auto& header : response->headers) {
        request_response.response_headers.Set(AtomicString(header.key),
                                              AtomicString(header.value));
      }
  
      if (--num_responses_left_ != 0)
        return;
  
      std::sort(responses_.begin(), responses_.end(),
                [](const RequestResponse& a, const RequestResponse& b) {
                  return WTF::CodePointCompareLessThan(a.request_url,
                                                       b.request_url);
                });
      if (params_.skip_count > 0)
        responses_.EraseAt(0, params_.skip_count);
      bool has_more = false;
      if (static_cast<size_t>(params_.page_size) < responses_.size()) {
        responses_.EraseAt(params_.page_size,
                           responses_.size() - params_.page_size);
        has_more = true;
      }
      std::unique_ptr<Array<DataEntry>> array = Array<DataEntry>::create();
      for (const auto& request_response : responses_) {
        std::unique_ptr<DataEntry> entry =
            DataEntry::create()
                .setRequestURL(request_response.request_url)
                .setRequestMethod(request_response.request_method)
                .setRequestHeaders(
                    SerializeHeaders(request_response.request_headers))
                .setResponseStatus(request_response.response_status)
                .setResponseStatusText(request_response.response_status_text)
                .setResponseTime(request_response.response_time)
                .setResponseHeaders(
                    SerializeHeaders(request_response.response_headers))
                .build();
        array->addItem(std::move(entry));
      }
      callback_->sendSuccess(std::move(array), has_more);
    }
  
    void SendFailure(const mojom::blink::CacheStorageError& error) {
      callback_->sendFailure(ProtocolResponse::Error(
          String::Format("Error requesting responses for cache %s : %s",
                         params_.cache_name.Utf8().data(),
                         CacheStorageErrorString(error).data())));
    }
  
    std::unique_ptr<Array<Header>> SerializeHeaders(
        const HTTPHeaderMap& headers) {
      std::unique_ptr<Array<Header>> result = Array<Header>::create();
      for (HTTPHeaderMap::const_iterator it = headers.begin(),
                                         end = headers.end();
           it != end; ++it) {
        result->addItem(
            Header::create().setName(it->key).setValue(it->value).build());
      }
      return result;
    }
  
   private:
    DataRequestParams params_;
    int num_responses_left_;
    Vector<RequestResponse> responses_;
    mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr_;
    std::unique_ptr<RequestEntriesCallback> callback_;
  
    DISALLOW_COPY_AND_ASSIGN(ResponsesAccumulator);
  };
  
  class GetCacheKeysForRequestData {
   public:
    GetCacheKeysForRequestData(
        const DataRequestParams& params,
        mojom::blink::CacheStorageCacheAssociatedPtrInfo cache_ptr_info,
        std::unique_ptr<RequestEntriesCallback> callback)
        : params_(params), callback_(std::move(callback)) {
      cache_ptr_.Bind(std::move(cache_ptr_info));
    }
  
    void Dispatch(std::unique_ptr<GetCacheKeysForRequestData> self) {
      cache_ptr_->Keys(
          base::Optional<WebServiceWorkerRequest>(),
          mojom::blink::QueryParams::New(),
          WTF::Bind(
              [](DataRequestParams params,
                 std::unique_ptr<GetCacheKeysForRequestData> self,
                 mojom::blink::CacheKeysResultPtr result) {
                if (result->is_status()) {
                  self->callback_->sendFailure(
                      ProtocolResponse::Error(String::Format(
                          "Error requesting requests for cache %s: %s",
                          params.cache_name.Utf8().data(),
                          CacheStorageErrorString(result->get_status()).data())));
                } else {
                  auto& requests = result->get_keys();
                  if (requests.IsEmpty()) {
                    std::unique_ptr<Array<DataEntry>> array =
                        Array<DataEntry>::create();
                    self->callback_->sendSuccess(std::move(array), false);
                    return;
                  }
                  scoped_refptr<ResponsesAccumulator> accumulator =
                      base::AdoptRef(new ResponsesAccumulator(
                          requests.size(), params, std::move(self->cache_ptr_),
                          std::move(self->callback_)));
                  accumulator->Dispatch(result->get_keys());
                }
              },
              params_, std::move(self)));
    }
  
   private:
    DataRequestParams params_;
    mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr_;
    std::unique_ptr<RequestEntriesCallback> callback_;
  
    DISALLOW_COPY_AND_ASSIGN(GetCacheKeysForRequestData);
  };
  
  class CachedResponseFileReaderLoaderClient final
      : private FileReaderLoaderClient {
   public:
    static void Load(scoped_refptr<BlobDataHandle> blob,
                     std::unique_ptr<RequestCachedResponseCallback> callback) {
      new CachedResponseFileReaderLoaderClient(std::move(blob),
                                               std::move(callback));
    }
  
    void DidStartLoading() override {}
  
    void DidFinishLoading() override {
      std::unique_ptr<CachedResponse> response =
          CachedResponse::create()
              .setBody(Base64Encode(data_->Data(), data_->size()))
              .build();
      callback_->sendSuccess(std::move(response));
      dispose();
    }
  
    void DidFail(FileError::ErrorCode error) override {
      callback_->sendFailure(ProtocolResponse::Error(String::Format(
          "Unable to read the cached response, error code: %d", error)));
      dispose();
    }
  
    void DidReceiveDataForClient(const char* data,
                                 unsigned data_length) override {
      data_->Append(data, data_length);
    }
  
   private:
    CachedResponseFileReaderLoaderClient(
        scoped_refptr<BlobDataHandle>&& blob,
        std::unique_ptr<RequestCachedResponseCallback>&& callback)
        : loader_(
              FileReaderLoader::Create(FileReaderLoader::kReadByClient, this)),
          callback_(std::move(callback)),
          data_(SharedBuffer::Create()) {
      loader_->Start(std::move(blob));
    }
  
    ~CachedResponseFileReaderLoaderClient() override = default;
  
    void dispose() { delete this; }
  
    std::unique_ptr<FileReaderLoader> loader_;
    std::unique_ptr<RequestCachedResponseCallback> callback_;
    scoped_refptr<SharedBuffer> data_;
  
    DISALLOW_COPY_AND_ASSIGN(CachedResponseFileReaderLoaderClient);
  };
  
  }  // namespace
  
  InspectorCacheStorageAgent::InspectorCacheStorageAgent(InspectedFrames* frames)
      : frames_(frames) {}
  
  InspectorCacheStorageAgent::~InspectorCacheStorageAgent() = default;
  
  void InspectorCacheStorageAgent::Trace(blink::Visitor* visitor) {
    visitor->Trace(frames_);
    InspectorBaseAgent::Trace(visitor);
  }
  
  void InspectorCacheStorageAgent::requestCacheNames(
      const String& security_origin,
      std::unique_ptr<RequestCacheNamesCallback> callback) {
    scoped_refptr<const SecurityOrigin> sec_origin =
        SecurityOrigin::CreateFromString(security_origin);
  
    // Cache Storage API is restricted to trustworthy origins.
    if (!sec_origin->IsPotentiallyTrustworthy()) {
      // Don't treat this as an error, just don't attempt to open and enumerate
      // the caches.
      callback->sendSuccess(Array<ProtocolCache>::create());
      return;
    }
  
    mojom::blink::CacheStorage* cache_storage = nullptr;
  
    ProtocolResponse response =
        AssertCacheStorage(security_origin, frames_, &caches_, &cache_storage);
    if (!response.isSuccess()) {
      callback->sendFailure(response);
      return;
    }
  
    cache_storage->Keys(WTF::Bind(
        [](String security_origin,
           std::unique_ptr<RequestCacheNamesCallback> callback,
           const Vector<String>& caches) {
          std::unique_ptr<Array<ProtocolCache>> array =
              Array<ProtocolCache>::create();
          for (auto& cache : caches) {
            array->addItem(ProtocolCache::create()
                               .setSecurityOrigin(security_origin)
                               .setCacheName(cache)
                               .setCacheId(BuildCacheId(security_origin, cache))
                               .build());
          }
          callback->sendSuccess(std::move(array));
        },
        security_origin, std::move(callback)));
  }
  
  void InspectorCacheStorageAgent::requestEntries(
      const String& cache_id,
      int skip_count,
      int page_size,
      std::unique_ptr<RequestEntriesCallback> callback) {
    String cache_name;
    mojom::blink::CacheStorage* cache_storage = nullptr;
    ProtocolResponse response = AssertCacheStorageAndNameForId(
        cache_id, frames_, &cache_name, &caches_, &cache_storage);
    if (!response.isSuccess()) {
      callback->sendFailure(response);
      return;
    }
    DataRequestParams params;
    params.cache_name = cache_name;
    params.page_size = page_size;
    params.skip_count = skip_count;
  
    cache_storage->Open(
        cache_name,
        WTF::Bind(
            [](DataRequestParams params,
               std::unique_ptr<RequestEntriesCallback> callback,
               mojom::blink::OpenResultPtr result) {
              if (result->is_status()) {
                callback->sendFailure(ProtocolResponse::Error(String::Format(
                    "Error requesting cache %s: %s",
                    params.cache_name.Utf8().data(),
                    CacheStorageErrorString(result->get_status()).data())));
              } else {
                auto request = std::make_unique<GetCacheKeysForRequestData>(
                    params, std::move(result->get_cache()), std::move(callback));
                auto* request_ptr = request.get();
                request_ptr->Dispatch(std::move(request));
              }
            },
            params, std::move(callback)));
  }
  
  void InspectorCacheStorageAgent::deleteCache(
      const String& cache_id,
      std::unique_ptr<DeleteCacheCallback> callback) {
    String cache_name;
    mojom::blink::CacheStorage* cache_storage = nullptr;
    ProtocolResponse response = AssertCacheStorageAndNameForId(
        cache_id, frames_, &cache_name, &caches_, &cache_storage);
    if (!response.isSuccess()) {
      callback->sendFailure(response);
      return;
    }
    cache_storage->Delete(
        cache_name,
        WTF::Bind(
            [](std::unique_ptr<DeleteCacheCallback> callback,
               mojom::blink::CacheStorageError error) {
              if (error == mojom::blink::CacheStorageError::kSuccess) {
                callback->sendSuccess();
              } else {
                callback->sendFailure(ProtocolResponse::Error(
                    String::Format("Error requesting cache names: %s",
                                   CacheStorageErrorString(error).data())));
              }
            },
            std::move(callback)));
  }
  
  void InspectorCacheStorageAgent::deleteEntry(
      const String& cache_id,
      const String& request,
      std::unique_ptr<DeleteEntryCallback> callback) {
    String cache_name;
    mojom::blink::CacheStorage* cache_storage = nullptr;
    ProtocolResponse response = AssertCacheStorageAndNameForId(
        cache_id, frames_, &cache_name, &caches_, &cache_storage);
    if (!response.isSuccess()) {
      callback->sendFailure(response);
      return;
    }
    cache_storage->Open(
        cache_name,
        WTF::Bind(
            [](String cache_name, String request,
               std::unique_ptr<DeleteEntryCallback> callback,
               mojom::blink::OpenResultPtr result) {
              if (result->is_status()) {
                callback->sendFailure(ProtocolResponse::Error(String::Format(
                    "Error requesting cache %s: %s", cache_name.Utf8().data(),
                    CacheStorageErrorString(result->get_status()).data())));
              } else {
                Vector<mojom::blink::BatchOperationPtr> batch_operations;
                batch_operations.push_back(mojom::blink::BatchOperation::New());
                auto& operation = batch_operations.back();
                operation->operation_type = mojom::blink::OperationType::kDelete;
                operation->request.SetURL(KURL(request));
                operation->request.SetMethod("GET");
  
                mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr;
                cache_ptr.Bind(std::move(result->get_cache()));
                auto* cache = cache_ptr.get();
                cache->Batch(
                    std::move(batch_operations),
                    WTF::Bind(
                        [](mojom::blink::CacheStorageCacheAssociatedPtr cache_ptr,
                           std::unique_ptr<DeleteEntryCallback> callback,
                           mojom::blink::CacheStorageError error) {
                          if (error !=
                              mojom::blink::CacheStorageError::kSuccess) {
                            callback->sendFailure(
                                ProtocolResponse::Error(String::Format(
                                    "Error deleting cache entry: %s",
                                    CacheStorageErrorString(error).data())));
                          } else {
                            callback->sendSuccess();
                          }
                        },
                        std::move(cache_ptr), std::move(callback)));
              }
            },
            cache_name, request, std::move(callback)));
  }
  
  void InspectorCacheStorageAgent::requestCachedResponse(
      const String& cache_id,
      const String& request_url,
      std::unique_ptr<RequestCachedResponseCallback> callback) {
    String cache_name;
    mojom::blink::CacheStorage* cache_storage = nullptr;
    ProtocolResponse response = AssertCacheStorageAndNameForId(
        cache_id, frames_, &cache_name, &caches_, &cache_storage);
    if (!response.isSuccess()) {
      callback->sendFailure(response);
      return;
    }
    WebServiceWorkerRequest request;
    request.SetURL(KURL(request_url));
    request.SetMethod("GET");
    cache_storage->Match(
        request, mojom::blink::QueryParams::New(),
        WTF::Bind(
            [](std::unique_ptr<RequestCachedResponseCallback> callback,
               mojom::blink::MatchResultPtr result) {
              if (result->is_status()) {
                callback->sendFailure(ProtocolResponse::Error(String::Format(
                    "Unable to read cached response: %s",
                    CacheStorageErrorString(result->get_status()).data())));
              } else {
                std::unique_ptr<protocol::DictionaryValue> headers =
                    protocol::DictionaryValue::create();
                if (!result->get_response()->blob) {
                  callback->sendSuccess(
                      CachedResponse::create().setBody("").build());
                  return;
                }
                CachedResponseFileReaderLoaderClient::Load(
                    std::move(result->get_response()->blob), std::move(callback));
              }
            },
            std::move(callback)));
  }
  }  // namespace blink
  