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

#ifndef NET_PROXY_RESOLUTION_WIN_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_
#define NET_PROXY_RESOLUTION_WIN_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_

#include <stddef.h>

#include <memory>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/threading/thread_checker.h"
#include "base/timer/timer.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_errors.h"
#include "net/base/net_export.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "url/gurl.h"

namespace base {
class TaskRunner;
}

namespace net {

class PacFileFetcher;
class URLRequestContext;

// For a given adapter, this class takes care of first doing a DHCP lookup
// to get the PAC URL, then if there is one, trying to fetch it.
class NET_EXPORT_PRIVATE DhcpPacFileAdapterFetcher {
 public:
  DhcpPacFileAdapterFetcher() = delete;

  // |url_request_context| must outlive DhcpPacFileAdapterFetcher.
  // |task_runner| will be used to post tasks to a thread.
  DhcpPacFileAdapterFetcher(URLRequestContext* url_request_context,
                            scoped_refptr<base::TaskRunner> task_runner);

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

  virtual ~DhcpPacFileAdapterFetcher();

  // Starts a fetch.  On completion (but not cancellation), |callback|
  // will be invoked with the network error indicating success or failure
  // of fetching a DHCP-configured PAC file on this adapter.
  //
  // On completion, results can be obtained via |GetPacScript()|, |GetPacURL()|.
  //
  // You may only call Fetch() once on a given instance of
  // DhcpPacFileAdapterFetcher.
  virtual void Fetch(const std::string& adapter_name,
                     CompletionOnceCallback callback,
                     const NetworkTrafficAnnotationTag traffic_annotation);

  // Cancels the fetch on this adapter.
  virtual void Cancel();

  // Returns true if in the FINISH state (not CANCEL).
  virtual bool DidFinish() const;

  // Returns the network error indicating the result of the fetch. Will
  // return IO_PENDING until the fetch is complete or cancelled. This is
  // the same network error passed to the |callback| provided to |Fetch()|.
  virtual int GetResult() const;

  // Returns the contents of the PAC file retrieved.  Only valid if
  // |IsComplete()| is true.  Returns the empty string if |GetResult()|
  // returns anything other than OK.
  virtual std::u16string GetPacScript() const;

  // Returns the PAC URL retrieved from DHCP.  Only guaranteed to be
  // valid if |IsComplete()| is true.  Returns an empty URL if no URL was
  // configured in DHCP.  May return a valid URL even if |result()| does
  // not return OK (this would indicate that we found a URL configured in
  // DHCP but failed to download it).
  virtual GURL GetPacURL() const;

  // Returns the PAC URL configured in DHCP for the given |adapter_name|, or
  // the empty string if none is configured.
  //
  // This function executes synchronously due to limitations of the Windows
  // DHCP client API.
  static std::string GetPacURLFromDhcp(const std::string& adapter_name);

  // Sanitizes a string returned via the DHCP API.
  static std::string SanitizeDhcpApiString(const char* data,
                                           size_t count_bytes);

 protected:
  // This is the state machine for fetching from a given adapter.
  //
  // The state machine goes from START->WAIT_DHCP when it starts
  // a worker thread to fetch the PAC URL from DHCP.
  //
  // In state WAIT_DHCP, if the DHCP query finishes and has no URL, it
  // moves to state FINISH.  If there is a URL, it starts a
  // PacFileFetcher to fetch it and moves to state WAIT_URL.
  //
  // It goes from WAIT_URL->FINISH when the PacFileFetcher completes.
  //
  // In state FINISH, completion is indicated to the outer class, with
  // the results of the fetch if a PAC script was successfully fetched.
  //
  // In state WAIT_DHCP, our timeout occurring can push us to FINISH.
  //
  // In any state except FINISH, a call to Cancel() will move to state
  // CANCEL and cause all outstanding work to be cancelled or its
  // results ignored when available.
  enum State {
    STATE_START,
    STATE_WAIT_DHCP,
    STATE_WAIT_URL,
    STATE_FINISH,
    STATE_CANCEL,
  };

  State state() const;

  // This inner class encapsulates work done on a worker pool thread.
  // By using a separate object, we can keep the main object completely
  // thread safe and let it be non-refcounted.
  class NET_EXPORT_PRIVATE DhcpQuery
      : public base::RefCountedThreadSafe<DhcpQuery> {
   public:
    DhcpQuery();

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

    // This method should run on a worker pool thread, via PostTaskAndReply.
    // After it has run, the |url()| method on this object will return the
    // URL retrieved.
    void GetPacURLForAdapter(const std::string& adapter_name);

    // Returns the URL retrieved for the given adapter, once the task has run.
    const std::string& url() const;

   protected:
    // Virtual method introduced to allow unit testing.
    virtual std::string ImplGetPacURLFromDhcp(const std::string& adapter_name);

    friend class base::RefCountedThreadSafe<DhcpQuery>;
    virtual ~DhcpQuery();

   private:
    // The URL retrieved for the given adapter.
    std::string url_;
  };

  // Virtual methods introduced to allow unit testing.
  virtual std::unique_ptr<PacFileFetcher> ImplCreateScriptFetcher();
  virtual scoped_refptr<DhcpQuery> ImplCreateDhcpQuery();
  virtual base::TimeDelta ImplGetTimeout() const;

 private:
  // Event/state transition handlers
  void OnDhcpQueryDone(scoped_refptr<DhcpQuery> dhcp_query,
                       const NetworkTrafficAnnotationTag traffic_annotation);
  void OnTimeout();
  void OnFetcherDone(int result);
  void TransitionToFinish();

  // TaskRunner for posting tasks to a worker thread.
  scoped_refptr<base::TaskRunner> task_runner_;

  // Current state of this state machine.
  State state_ = STATE_START;

  // A network error indicating result of operation.
  int result_ = ERR_IO_PENDING;

  // Empty string or the PAC script downloaded.
  std::u16string pac_script_;

  // Empty URL or the PAC URL configured in DHCP.
  GURL pac_url_;

  // Callback to let our client know we're done. Invalid in states
  // START, FINISH and CANCEL.
  CompletionOnceCallback callback_;

  // Fetcher to retrieve PAC files once URL is known.
  std::unique_ptr<PacFileFetcher> script_fetcher_;

  // Implements a timeout on the call to the Win32 DHCP API.
  base::OneShotTimer wait_timer_;

  const raw_ptr<URLRequestContext> url_request_context_;

  THREAD_CHECKER(thread_checker_);

  base::WeakPtrFactory<DhcpPacFileAdapterFetcher> weak_ptr_factory_{this};
};

}  // namespace net

#endif  // NET_PROXY_RESOLUTION_WIN_DHCP_PAC_FILE_ADAPTER_FETCHER_WIN_H_
