chromium/android_webview/browser/cookie_manager.h

// 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.

#ifndef ANDROID_WEBVIEW_BROWSER_COOKIE_MANAGER_H_
#define ANDROID_WEBVIEW_BROWSER_COOKIE_MANAGER_H_

#include <memory>
#include <vector>

#include "android_webview/browser/aw_cookie_access_policy.h"
#include "base/android/jni_array.h"
#include "base/android/scoped_java_ref.h"
#include "base/containers/circular_deque.h"
#include "base/files/file_path.h"
#include "base/no_destructor.h"
#include "base/thread_annotations.h"
#include "base/threading/thread.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/cookie_manager.mojom-forward.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"

class GURL;

namespace base {
class SingleThreadTaskRunner;
}

namespace net {
class CookieStore;
class CanonicalCookie;
}

namespace android_webview {

class AwBrowserContext;

// CookieManager creates and owns WebView's CookieStore, in addition to handling
// calls into the CookieStore from Java.
//
// Since Java calls can be made on the IO Thread, and must synchronously return
// a result, and the CookieStore API allows it to asynchronously return results,
// the CookieStore must be run on its own thread, to prevent deadlock.
//
// Initialization:
//
// There are two possible scenarios: 1) The CookieManager is used before the
// Network Service is initialized. 2) The CookieManager is not used until after
// the Network Service is initialized (during content initialization).
//
// Case 2) is straightforward: When the
// ContentBrowserClient::ConfigureNetworkContextParams was called
// AwContentBrowserClient will finally call
// CookieManager::SwapMojoCookieManagerAsync by calling
// CookieManager::SetMojoCookieManager, setting the |mojo_cookie_manager_|
// member of CookieManager (the AW one; it's an unfortunately overloaded term).
//
// In case 1), the CookieManager creates a provisional CookieStore
// |cookie_store_|, which it uses for all operations (because the
// network::mojom::CookieManager doesn't exist yet): For every cookie task
// it receives, the CookieManager first checks for the presence of a
// |mojo_cookie_manager_|, and if it doesn't exist, the CookieManager checks for
// the presence of a provisionally-created CookieStore, creating one if it
// doesn't exist (in GetCookieStore). Then whichever one it found will handle
// the cookie task.
//
// When it comes time to create the NetworkContext, which comes with a
// network::mojom::CookieManager, the provisionally-created CookieStore needs to
// transfer its contents (with the results of the pre-content-initialization
// cookie tasks) to the newly created network::mojom::CookieManager. It does
// this by flushing its contents to disk and then calling the same method,
// CookieManager::SwapMojoCookieManagerAsync, which binds the newly created
// network::mojom::CookieManager to |mojo_cookie_manager_|. Thereafter, any
// cookie tasks will be handled by |mojo_cookie_manager_| because it now exists.
//
// This works because the newly created network::mojom::CookieManager reads from
// the same on-disk backing store that the provisionally-created CookieStore
// just flushed its contents to.
//
// Why is this not a race condition? This was addressed in crbug.com/933461.
// If the CookieManager receives cookie tasks while the flush is in progress,
// those tasks are added to a task queue, which is not executed until after the
// new |mojo_cookie_manager_| has finished being set. The new
// |mojo_cookie_manager_| only loads from disk upon receiving a task (*not* upon
// creation, importantly; see CookieMonster::FetchAllCookiesIfNecessary, which
// is only called if cookie tasks are received), so it will not try to load from
// disk until the flush is complete.
class CookieManager {
 public:
  static CookieManager* GetDefaultInstance();

  // If you want to construct the CookieManager for the default profile, use a
  // null parent_context, as the default AwBrowserContext does not own its
  // CookieManager (for legacy reasons). All non-default profile CookieManagers
  // are owned by an AwBrowserContext - a non-null parent_context.
  explicit CookieManager(AwBrowserContext* parent_context);
  ~CookieManager();

  CookieManager(const CookieManager&) = delete;
  CookieManager& operator=(const CookieManager&) = delete;

  // Passes a |cookie_manager_remote|, which this will use for CookieManager
  // APIs going forward. Only called in the Network Service path, with the
  // intention this is called once during content initialization (when we create
  // the only NetworkContext). Note: no other cookie tasks will be processed
  // while this operation is running.
  void SetMojoCookieManager(
      mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote);

  base::android::ScopedJavaLocalRef<jobject> GetJavaCookieManager();

  // Configure whether or not this CookieManager should workaround cookies
  // specified for insecure URLs with the 'Secure' directive. See
  // |workaround_http_secure_cookies_| for the default behavior. This should not
  // be needed in production, as the default is the desirable behavior.
  void SetWorkaroundHttpSecureCookiesForTesting(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj,
      jboolean allow);
  void SetShouldAcceptCookies(JNIEnv* env,
                              const base::android::JavaParamRef<jobject>& obj,
                              jboolean accept);
  jboolean GetShouldAcceptCookies(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj);
  void SetCookie(JNIEnv* env,
                 const base::android::JavaParamRef<jobject>& obj,
                 const base::android::JavaParamRef<jstring>& url,
                 const base::android::JavaParamRef<jstring>& value,
                 const base::android::JavaParamRef<jobject>& java_callback);
  void SetCookieSync(JNIEnv* env,
                     const base::android::JavaParamRef<jobject>& obj,
                     const base::android::JavaParamRef<jstring>& url,
                     const base::android::JavaParamRef<jstring>& value);

  base::android::ScopedJavaLocalRef<jstring> GetCookie(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj,
      const base::android::JavaParamRef<jstring>& url);

  base::android::ScopedJavaLocalRef<jobjectArray> GetCookieInfo(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj,
      const base::android::JavaParamRef<jstring>& url);

  void RemoveAllCookies(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj,
      const base::android::JavaParamRef<jobject>& java_callback);
  void RemoveSessionCookies(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj,
      const base::android::JavaParamRef<jobject>& java_callback);
  void RemoveAllCookiesSync(JNIEnv* env,
                            const base::android::JavaParamRef<jobject>& obj);
  void RemoveSessionCookiesSync(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj);
  void RemoveExpiredCookies(JNIEnv* env,
                            const base::android::JavaParamRef<jobject>& obj);
  void FlushCookieStore(JNIEnv* env,
                        const base::android::JavaParamRef<jobject>& obj);
  jboolean HasCookies(JNIEnv* env,
                      const base::android::JavaParamRef<jobject>& obj);
  bool GetAllowFileSchemeCookies();
  jboolean GetAllowFileSchemeCookies(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj);

  // Configures whether CookieManager and WebView instances will honor requests
  // to set cookies for file:// scheme URLs. This method must be called (and
  // must finish execution) before calling any other WebView APIs which modify
  // the cookie store (otherwise, this is not guaranteed to succeed).
  //
  // This blocks the calling thread until its work is done to achieve this
  // guarantee (otherwise other mojo::Remote<network::mojom::CookieManager>
  // instances might be able to modify the underlying net::CookieStore before
  // this call finishes.
  void SetAllowFileSchemeCookies(
      JNIEnv* env,
      const base::android::JavaParamRef<jobject>& obj,
      jboolean allow);

  base::FilePath GetCookieStorePath();

  AwCookieAccessPolicy* cookie_access_policy() {
    return &cookie_access_policy_;
  }

 private:
  // Returns the CookieStore, creating it if necessary. This must only be called
  // on the CookieStore TaskRunner.
  net::CookieStore* GetCookieStore();

  // Gets the Network Service CookieManager if it's been passed via
  // |SetMojoCookieManager|. Otherwise (if Network Service is disabled or
  // content layer has not yet initialized the NetworkContext), this returns
  // nullptr (and |GetCookieStore| should be used installed). This must only be
  // called on the CookieStore TaskRunner.
  network::mojom::CookieManager* GetMojoCookieManager();

  void ExecCookieTaskSync(
      base::OnceCallback<void(base::OnceCallback<void(bool)>)> task);
  void ExecCookieTaskSync(
      base::OnceCallback<void(base::OnceCallback<void(int)>)> task);
  void ExecCookieTaskSync(base::OnceCallback<void(base::OnceClosure)> task);
  void ExecCookieTask(base::OnceClosure task);
  // Runs all queued-up cookie tasks in |tasks_|.
  void RunPendingCookieTasks();

  void SetCookieHelper(const GURL& host,
                       const std::string& value,
                       base::OnceCallback<void(bool)> callback);

  void SetWorkaroundHttpSecureCookiesAsyncHelper(bool allow,
                                                 base::OnceClosure complete);

  void GotCookies(const std::vector<net::CanonicalCookie>& cookies);
  void GetCookieListAsyncHelper(const GURL& host,
                                net::CookieList* result,
                                base::OnceClosure complete);
  void GetCookieListCompleted(
      base::OnceClosure complete,
      net::CookieList* result,
      const net::CookieAccessResultList& value,
      const net::CookieAccessResultList& excluded_cookies);

  void RemoveSessionCookiesHelper(base::OnceCallback<void(bool)> callback);
  void RemoveAllCookiesHelper(base::OnceCallback<void(bool)> callback);
  void RemoveCookiesCompleted(base::OnceCallback<void(bool)> callback,
                              uint32_t num_deleted);

  void FlushCookieStoreAsyncHelper(base::OnceClosure complete);

  void SetMojoCookieManagerAsync(
      mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
      base::OnceClosure complete);
  void SwapMojoCookieManagerAsync(
      mojo::PendingRemote<network::mojom::CookieManager> cookie_manager_remote,
      base::OnceClosure complete);

  void HasCookiesAsyncHelper(bool* result, base::OnceClosure complete);
  void HasCookiesCompleted(base::OnceClosure complete,
                           bool* result,
                           const net::CookieList& cookies);

  void SetAllowFileSchemeCookiesAsyncHelper(bool allow,
                                            base::OnceClosure complete);
  // |can_change_schemes| indicates whether or not this call was successful,
  // indicating whether we may update |allow_file_scheme_cookies_|.
  void SetAllowFileSchemeCookiesCompleted(base::OnceClosure complete,
                                          bool allow,
                                          bool can_change_schemes);
  void MigrateCookieStorePath();

  // The client hint cache should be cleared if cookies are cleared, but if
  // cookies are cleared before the browser starts we need a way flag the
  // need to clear them later.
  void ClearClientHintsCachedPerOriginMapIfNeeded();

  // Returns the AwBrowserContext associated with the same profile as this
  // CookieManager. For the default profile, the AwBrowserContext is not
  // guaranteed to be initialized, so it may be null.
  AwBrowserContext* GetContext() const;
  // Get the storage path for the profile this CookieManager is associated with.
  base::FilePath GetContextPath() const;

  // Java object reference.
  base::android::ScopedJavaGlobalRef<jobject> java_obj_;

  // If this is the CookieManager for the default profile this will be null,
  // otherwise it will point to the non-default AwBrowserContext which owns the
  // CookieManager.
  const raw_ptr<AwBrowserContext> parent_context_;

  bool should_clear_client_hints_cached_per_origin_map_{false};

  base::FilePath cookie_store_path_;

  // This protects the following bool, as it's used on multiple threads.
  base::Lock allow_file_scheme_cookies_lock_;
  // True if cookies should be allowed for file URLs. Can only be changed prior
  // to creating the CookieStore.
  bool allow_file_scheme_cookies_ GUARDED_BY(allow_file_scheme_cookies_lock_);
  // True once the cookie store has been created. Just used to track when
  // |allow_file_scheme_cookies_| can no longer be modified. Only accessed on
  // |cookie_store_task_runner_|.
  bool cookie_store_created_;

  // Whether or not to workaround 'Secure' cookies set on insecure URLs. See
  // MaybeFixUpSchemeForSecureCookieAndGetSameSite. Only accessed on
  // |cookie_store_task_runner_|. Defaults to false starting for apps targeting
  // >= R.
  bool workaround_http_secure_cookies_;

  base::Thread cookie_store_client_thread_;
  base::Thread cookie_store_backend_thread_;

  scoped_refptr<base::SingleThreadTaskRunner> cookie_store_task_runner_;
  std::unique_ptr<net::CookieStore> cookie_store_;

  // Tracks if we're in the middle of a call to SetMojoCookieManager(). See the
  // note in SetMojoCookieManager(). Must only be accessed on
  // |cookie_store_task_runner_|.
  bool setting_new_mojo_cookie_manager_;

  // The cookie access policy is responsible for configuring when WebView allows
  // cookies both globally, and per request.
  AwCookieAccessPolicy cookie_access_policy_;

  // |tasks_| is a queue we manage, to allow us to delay tasks until after
  // SetMojoCookieManager()'s work is done. This is modified on different
  // threads, so accesses must be guarded by |task_queue_lock_|.
  base::Lock task_queue_lock_;
  base::circular_deque<base::OnceClosure> tasks_ GUARDED_BY(task_queue_lock_);

  // The CookieManager shared with the NetworkContext.
  mojo::Remote<network::mojom::CookieManager> mojo_cookie_manager_;
};

}  // namespace android_webview

#endif  // ANDROID_WEBVIEW_BROWSER_COOKIE_MANAGER_H_