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

#ifndef NET_COOKIES_COOKIE_MONSTER_CHANGE_DISPATCHER_H_
#define NET_COOKIES_COOKIE_MONSTER_CHANGE_DISPATCHER_H_

#include <map>
#include <memory>
#include <string>

#include "base/callback.h"
#include "base/callback_list.h"
#include "base/compiler_specific.h"
#include "base/containers/linked_list.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "net/cookies/cookie_change_dispatcher.h"
#include "url/gurl.h"

namespace net {

class CanonicalCookie;

// CookieChangeDispatcher implementation used by CookieMonster.
class CookieMonsterChangeDispatcher : public CookieChangeDispatcher {
 public:
  using CookieChangeCallbackList =
      base::CallbackList<void(const CanonicalCookie& cookie,
                              CookieChangeCause cause)>;

  CookieMonsterChangeDispatcher();
  ~CookieMonsterChangeDispatcher() override;

  // The key in CookieNameMap for a cookie name.
  static std::string NameKey(std::string name);

  // The key in CookieDomainName for a cookie domain.
  static std::string DomainKey(const std::string& domain);

  // The key in CookieDomainName for a listener URL.
  static std::string DomainKey(const GURL& url);

  // net::CookieChangeDispatcher
  std::unique_ptr<CookieChangeSubscription> AddCallbackForCookie(
      const GURL& url,
      const std::string& name,
      CookieChangeCallback callback) override WARN_UNUSED_RESULT;
  std::unique_ptr<CookieChangeSubscription> AddCallbackForUrl(
      const GURL& url,
      CookieChangeCallback callback) override WARN_UNUSED_RESULT;
  std::unique_ptr<CookieChangeSubscription> AddCallbackForAllChanges(
      CookieChangeCallback callback) override WARN_UNUSED_RESULT;

  // |notify_global_hooks| is true if the function should run the
  // global hooks in addition to the per-cookie hooks.
  //
  // TODO(pwnall): Remove |notify_global_hooks| and fix consumers.
  void DispatchChange(const CanonicalCookie& cookie,
                      CookieChangeCause cause,
                      bool notify_global_hooks);

 private:
  class Subscription : public base::LinkNode<Subscription>,
                       public CookieChangeSubscription {
   public:
    Subscription(base::WeakPtr<CookieMonsterChangeDispatcher> change_dispatcher,
                 std::string domain_key,
                 std::string name_key,
                 GURL url,
                 net::CookieChangeCallback callback);

    ~Subscription() override;

    // The lookup key used in the domain subscription map.
    //
    // The empty string means no domain filtering.
    const std::string& domain_key() const { return domain_key_; }
    // The lookup key used in the name subscription map.
    //
    // The empty string means no name filtering.
    const std::string& name_key() const { return name_key_; }

    // Dispatches a cookie change notification if the listener is interested.
    void DispatchChange(const net::CanonicalCookie& cookie,
                        net::CookieChangeCause change_cause);

   private:
    base::WeakPtr<CookieMonsterChangeDispatcher> change_dispatcher_;
    const std::string domain_key_;  // kGlobalDomainKey means no filtering.
    const std::string name_key_;    // kGlobalNameKey means no filtering.
    const GURL url_;                // empty() means no URL-based filtering.
    net::CookieOptions options_;
    const net::CookieChangeCallback callback_;

    void DoDispatchChange(const net::CanonicalCookie& cookie,
                          net::CookieChangeCause change_cause) const;

    // Used to post DoDispatchChange() calls to this subscription's thread.
    scoped_refptr<base::SingleThreadTaskRunner> task_runner_;

    THREAD_CHECKER(thread_checker_);

    // Used to cancel delayed calls to DoDispatchChange() when the subscription
    // gets destroyed.
    base::WeakPtrFactory<Subscription> weak_ptr_factory_;

    DISALLOW_COPY_AND_ASSIGN(Subscription);
  };

  // The last level of the subscription data structures.
  using SubscriptionList = base::LinkedList<Subscription>;

  // Buckets subscriptions according to cookie names.
  //
  // Map keys are cookie names, as we only support exact name matching.
  using CookieNameMap = std::map<std::string, SubscriptionList>;

  // Buckets subscriptions according to cookie domains.
  //
  // Map keys are the eTLD+1 of cookie domains. Cookies are either host-locked,
  // or visible to all the subdomain of a given domain. A cookie's scope cannot
  // exceed eTLD+1, so we stop there.
  using CookieDomainMap = std::map<std::string, CookieNameMap>;

  void DispatchChangeToDomainKey(const CanonicalCookie& cookie,
                                 CookieChangeCause cause,
                                 const std::string& domain_key);

  void DispatchChangeToNameKey(const CanonicalCookie& cookie,
                               CookieChangeCause cause,
                               CookieNameMap& name_map,
                               const std::string& name_key);

  // Inserts a subscription into the map.
  //
  // Called by the AddCallback* methods, after creating the Subscription.
  void LinkSubscription(Subscription* subscription);

  // Removes a subscription from the map.
  //
  // Called by the Subscription destructor.
  void UnlinkSubscription(Subscription* subscription);

  CookieDomainMap cookie_domain_map_;

  THREAD_CHECKER(thread_checker_);

  // Vends weak pointers to subscriptions.
  base::WeakPtrFactory<CookieMonsterChangeDispatcher> weak_ptr_factory_;

  DISALLOW_COPY_AND_ASSIGN(CookieMonsterChangeDispatcher);
};

}  // namespace net

#endif  // NET_COOKIES_COOKIE_MONSTER_CHANGE_DISPATCHER_H_
