// Copyright (c) 2006-2008 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 CHROME_BROWSER_SSL_MANAGER_H_
#define CHROME_BROWSER_SSL_MANAGER_H_

#include <string>
#include <map>

#include "base/basictypes.h"
#include "base/observer_list.h"
#include "base/ref_counted.h"
#include "chrome/browser/provisional_load_details.h"
#include "chrome/browser/resource_dispatcher_host.h"
#include "chrome/browser/security_style.h"
#include "chrome/browser/views/info_bar_message_view.h"
#include "chrome/common/notification_registrar.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/render_messages.h"
#include "chrome/views/link.h"
#include "googleurl/src/gurl.h"
#include "net/base/net_errors.h"
#include "net/base/ssl_info.h"
#include "net/base/x509_certificate.h"
#include "webkit/glue/console_message_level.h"
#include "webkit/glue/resource_type.h"

class AutomationProvider;
class InfoBarItemView;
class NavigationEntry;
class LoadFromMemoryCacheDetails;
class LoadNotificationDetails;
class NavigationController;
class PrefService;
class ResourceRedirectDetails;
class ResourceRequestDetails;
class SSLErrorInfo;
class TabContents;
class Task;
class URLRequest;

// The SSLManager SSLManager controls the SSL UI elements in a TabContents.  It
// listens for various events that influence when these elements should or
// should not be displayed and adjusts them accordingly.
//
// There is one SSLManager per tab.
// The security state (secure/insecure) is stored in the navigation entry.
// Along with it are stored any SSL error code and the associated cert.
//

class SSLManager : public NotificationObserver {
 public:
  // An ErrorHandler carries information from the IO thread to the UI thread
  // and is dispatched to the appropriate SSLManager when it arrives on the
  // UI thread.  Subclasses should override the OnDispatched/OnDispatchFailed
  // methods to implement the actions that should be taken on the UI thread.
  // These methods can call the different convenience methods ContinueRequest/
  // CancelRequest/StartRequest to perform any required action on the URLRequest
  // the ErrorHandler was created with.
  // IMPORTANT NOTE: if you are not doing anything in
  // OnDispatched/OnDispatchFailed, make sure you call TakeNoAction().  This is
  // necessary for ensuring the instance is not leaked.
  class ErrorHandler : public base::RefCountedThreadSafe<ErrorHandler> {
   public:
     virtual ~ErrorHandler() { }

    // Find the appropriate SSLManager for the URLRequest and begin handling
    // this error.
    //
    // Call on UI thread.
    void Dispatch();

    // Available on either thread.
    const GURL& request_url() const { return request_url_; }

    // Call on the UI thread.
    SSLManager* manager() const { return manager_; };

    // Returns the TabContents this object is associated with.  Should be
    // called from the UI thread.
    TabContents* GetTabContents();

    // Cancels the associated URLRequest.
    // This method can be called from OnDispatchFailed and OnDispatched.
    void CancelRequest();

    // Continue the URLRequest ignoring any previous errors.  Note that some
    // errors cannot be ignored, in which case this will result in the request
    // being canceled.
    // This method can be called from OnDispatchFailed and OnDispatched.
    void ContinueRequest();

    // Cancels the associated URLRequest and mark it as denied.  The renderer
    // processes such request in a special manner, optionally replacing them
    // with alternate content (typically frames content is replaced with a
    // warning message).
    // This method can be called from OnDispatchFailed and OnDispatched.
    void DenyRequest();

    // Starts the associated URLRequest.  |filter_policy| specifies whether the
    // ResourceDispatcher should attempt to filter the loaded content in order
    // to make it secure (ex: images are made slightly transparent and are
    // stamped).
    // Should only be called when the URLRequest has not already been started.
    // This method can be called from OnDispatchFailed and OnDispatched.
    void StartRequest(FilterPolicy::Type filter_policy);

    // Does nothing on the URLRequest but ensures the current instance ref
    // count is decremented appropriately.  Subclasses that do not want to
    // take any specific actions in their OnDispatched/OnDispatchFailed should
    // call this.
    void TakeNoAction();

   protected:
    // Construct on the IO thread.
    ErrorHandler(ResourceDispatcherHost* resource_dispatcher_host,
                 URLRequest* request,
                 MessageLoop* ui_loop);

    // The following 2 methods are the methods subclasses should implement.
    virtual void OnDispatchFailed() { TakeNoAction(); }

    // Can use the manager_ member.
    virtual void OnDispatched() { TakeNoAction(); }

    // We cache the message loops to be able to proxy events across the thread
    // boundaries.
    MessageLoop* ui_loop_;
    MessageLoop* io_loop_;

    // Should only be accessed on the UI thread.
    SSLManager* manager_;  // Our manager.

    // The id of the URLRequest associated with this object.
    // Should only be accessed from the IO thread.
    ResourceDispatcherHost::GlobalRequestID request_id_;

    // The ResourceDispatcherHost we are associated with.
    ResourceDispatcherHost* resource_dispatcher_host_;

   private:
    // Completes the CancelRequest operation on the IO thread.
    // Call on the IO thread.
    void CompleteCancelRequest(int error);

    // Completes the ContinueRequest operation on the IO thread.
    //
    // Call on the IO thread.
    void CompleteContinueRequest();

    // Completes the StartRequest operation on the IO thread.
    // Call on the IO thread.
    void CompleteStartRequest(FilterPolicy::Type filter_policy);

    // Derefs this instance.
    // Call on the IO thread.
    void CompleteTakeNoAction();

    // We use these members to find the correct SSLManager when we arrive on
    // the UI thread.
    int render_process_host_id_;
    int tab_contents_id_;

    // This read-only member can be accessed on any thread.
    const GURL request_url_;  // The URL that we requested.

    // Should only be accessed on the IO thread
    bool request_has_been_notified_; // A flag to make sure we notify the
                                     // URLRequest exactly once.

    DISALLOW_EVIL_CONSTRUCTORS(ErrorHandler);
  };

  // A CertError represents an error that occurred with the certificate in an
  // SSL session.  A CertError object exists both on the IO thread and on the UI
  // thread and allows us to cancel/continue a request it is associated with.
  class CertError : public ErrorHandler {
   public:
    // These accessors are available on either thread
    const net::SSLInfo& ssl_info() const { return ssl_info_; }
    int cert_error() const { return cert_error_; }

    ResourceType::Type resource_type() const { return resource_type_; }
   private:
    // SSLManager is responsible for creating CertError objects.
    friend class SSLManager;

    // Construct on the IO thread.
    // We mark this method as private because it is tricky to correctly
    // construct a CertError object.
    CertError(ResourceDispatcherHost* resource_dispatcher_host,
              URLRequest* request,
              ResourceType::Type resource_type,
              int cert_error,
              net::X509Certificate* cert,
              MessageLoop* ui_loop);

    // ErrorHandler methods
    virtual void OnDispatchFailed() { CancelRequest(); }
    virtual void OnDispatched() { manager_->OnCertError(this); }

    // These read-only members can be accessed on any thread.
    net::SSLInfo ssl_info_;
    const int cert_error_; // The error we represent.

    // What kind of resource is associated with the requested that generated
    // that error.
    ResourceType::Type resource_type_;

    DISALLOW_EVIL_CONSTRUCTORS(CertError);
  };

  // The MixedContentHandler class is used to query what to do with
  // mixed content, from the IO thread to the UI thread.
  class MixedContentHandler : public ErrorHandler {
   public:
    // Created on the IO thread.
    MixedContentHandler(ResourceDispatcherHost* rdh,
                        URLRequest* request,
                        MessageLoop* ui_loop)
        : ErrorHandler(rdh, request, ui_loop) { }

   protected:
    virtual void OnDispatchFailed() { TakeNoAction(); }
    virtual void OnDispatched() { manager()->OnMixedContent(this); }

   private:
    DISALLOW_EVIL_CONSTRUCTORS(MixedContentHandler);
  };

  // The SSLManager will ask its delegate to decide how to handle events
  // relevant to SSL.  Delegates are expected to be stateless and intended to be
  // easily implementable.
  //
  // Delegates should interact with the rest of the browser only through their
  // parameters and through the delegate API of the SSLManager.
  //
  // If a delegate needs to do something tricky, consider having the SSLManager
  // do it instead.
  class Delegate {
   public:
    // An error occurred with the certificate in an SSL connection.
    virtual void OnCertError(const GURL& main_frame_url, CertError* error) = 0;

    // A request for a mixed-content resource was made.  Note that the resource
    // request was not started yet and the delegate is responsible for starting
    // it.
    virtual void OnMixedContent(
        NavigationController* navigation_controller,
        const GURL& main_frame_url,
        MixedContentHandler* mixed_content_handler) = 0;

    // We have started a resource request for the given URL.
    virtual void OnRequestStarted(SSLManager* manager,
                                  const GURL& url,
                                  ResourceType::Type resource_type,
                                  int ssl_cert_id,
                                  int ssl_cert_status) = 0;

    // Returns the default security style for a given URL.
    virtual SecurityStyle GetDefaultStyle(const GURL& url) = 0;
  };

  // An info bar with a message and an optional link that runs a task when
  // clicked.
  class SSLInfoBar : public InfoBarItemView,
                     public views::LinkController {
   public:
    SSLInfoBar(SSLManager* manager,
               const std::wstring& message,
               const std::wstring& link_text,
               Task* task);

    virtual ~SSLInfoBar();

    const std::wstring GetMessageText() const;

    // views::LinkController method.
    virtual void LinkActivated(views::Link* source, int event_flags);

   private:
    views::Label* label_;
    views::Link* link_;
    SSLManager* manager_;
    scoped_ptr<Task> task_;

    DISALLOW_COPY_AND_ASSIGN(SSLInfoBar);
  };

  static void RegisterUserPrefs(PrefService* prefs);

  // Construct an SSLManager for the specified tab.
  // If |delegate| is NULL, SSLPolicy::GetDefaultPolicy() is used.
  SSLManager(NavigationController* controller, Delegate* delegate);

  ~SSLManager();

  //////////////////////////////////////////////////////////////////////////////
  // Delegate API
  //
  // The SSL manager expects these methods to be called by its delegate.  They
  // exist to make Delegates easy to implement.

  // Ensure that the specified message is displayed to the user.  This will
  // display an InfoBar at the top of the associated tab.
  void ShowMessage(const std::wstring& msg);

  // Same as ShowMessage but also contains a link that when clicked run the
  // specified task.  The SSL Manager becomes the owner of the task.
  void ShowMessageWithLink(const std::wstring& msg,
                           const std::wstring& link_text,
                           Task* task);

  // Sets the maximum security style for the page.  If the current security
  // style is lower than |style|, this will not have an effect on the security
  // indicators.
  //
  // It will return true if the navigation entry was updated or false if
  // nothing changed. The caller is responsible for broadcasting
  // NOTIFY_SSY_STATE_CHANGED if it returns true.
  bool SetMaxSecurityStyle(SecurityStyle style);

  // Logs a message to the console of the page.
  void AddMessageToConsole(const std::wstring& msg,
                           ConsoleMessageLevel level);

  // Records that |cert| is permitted to be used for |host| in the future.
  void DenyCertForHost(net::X509Certificate* cert, const std::string& host);

  // Records that |cert| is not permitted to be used for |host| in the future.
  void AllowCertForHost(net::X509Certificate* cert, const std::string& host);

  // Queries whether |cert| is allowed or denied for |host|.
  net::X509Certificate::Policy::Judgment QueryPolicy(
      net::X509Certificate* cert, const std::string& host);

  // Allow mixed/unsafe content to be visible (non filtered) for the specified
  // URL.
  // Note that the current implementation allows on a host name basis.
  void AllowShowInsecureContentForURL(const GURL& url);

  // Returns whether the specified URL is allowed to show insecure (mixed or
  // unsafe) content.
  bool CanShowInsecureContent(const GURL& url);

  //
  //////////////////////////////////////////////////////////////////////////////

  // The delegate of the SSLManager.  This value may be changed at any time,
  // but it is not permissible for it to be NULL.
  Delegate* delegate() const { return delegate_; }
  void set_delegate(Delegate* delegate) { delegate_ = delegate; }

  // Entry point for SSLCertificateErrors.  This function begins the process
  // of resolving a certificate error during an SSL connection.  SSLManager
  // will adjust the security UI and either call |Cancel| or
  // |ContinueDespiteLastError| on the URLRequest.
  //
  // Called on the IO thread.
  static void OnSSLCertificateError(ResourceDispatcherHost* resource_dispatcher,
                                    URLRequest* request,
                                    int cert_error,
                                    net::X509Certificate* cert,
                                    MessageLoop* ui_loop);

  // Called when a mixed-content sub-resource request has been detected.  The
  // request is not started yet.  The SSLManager will make a decision on whether
  // to filter that request's content (with the filter_policy flag).
  // TODO (jcampan): Implement a way to just cancel the request.  This is not
  // straight-forward as canceling a request that has not been started will
  // not remove from the pending_requests_ of the ResourceDispatcherHost.
  // Called on the IO thread.
  static void OnMixedContentRequest(ResourceDispatcherHost* resource_dispatcher,
                                    URLRequest* request,
                                    MessageLoop* ui_loop);

  // Called by CertError::Dispatch to kick off processing of the cert error by
  // the SSL manager.  The error originated from the ResourceDispatcherHost.
  //
  // Called on the UI thread.
  void OnCertError(CertError* error);

  // Called by MixedContentHandler::Dispatch to kick off processing of the
  // mixed-content resource request.  The info originated from the
  // ResourceDispatcherHost.
  //
  // Called on the UI thread.
  void OnMixedContent(MixedContentHandler* mixed_content);

  // Entry point for navigation.  This function begins the process of updating
  // the security UI when the main frame navigates to a new URL.
  //
  // Called on the UI thread.
  virtual void Observe(NotificationType type,
                       const NotificationSource& source,
                       const NotificationDetails& details);

  // Entry point for navigation.  This function begins the process of updating
  // the security UI when the main frame navigates.
  //
  // Called on the UI thread.
  void NavigationStateChanged();

  // Called when one of our infobars closes.
  void OnInfoBarClose(SSLInfoBar* info_bar);

  // Called to determine if there were any processed SSL errors from request.
  bool ProcessedSSLErrorFromRequest() const;

  NavigationController* controller() { return controller_; }

  // Convenience methods for serializing/deserializing the security info.
  static std::string SerializeSecurityInfo(int cert_id,
                                           int cert_status,
                                           int security_bits);
  static bool DeserializeSecurityInfo(const std::string& state,
                                      int* cert_id,
                                      int* cert_status,
                                      int* security_bits);

  // Sets |short_name| to <organization_name> [<country>] and |ca_name|
  // to something like:
  // "Verified by <issuer_organization_name>"
  static bool GetEVCertNames(const net::X509Certificate& cert,
                             std::wstring* short_name,
                             std::wstring* ca_name);

 private:
  // The AutomationProvider needs to access the InfoBars.
  friend class AutomationProvider;

  // SSLMessageInfo contains the information necessary for displaying a message
  // in an info-bar.
  struct SSLMessageInfo {
   public:
     explicit SSLMessageInfo(const std::wstring& text)
        : message(text),
          action(NULL) { }
     SSLMessageInfo(const std::wstring& message,
                    const std::wstring& link_text,
                    Task* action)
        : message(message), link_text(link_text), action(action) { }

     // Overridden so that std::find works.
     bool operator==(const std::wstring& other_message) const {
       // We are uniquing SSLMessageInfo by their message only.
       return message == other_message;
     }

     std::wstring message;
     std::wstring link_text;
     Task* action;
   };

  // Entry points for notifications to which we subscribe. Note that
  // DidCommitProvisionalLoad uses the abstract NotificationDetails type since
  // the type we need is in NavigationController which would create a circular
  // header file dependency.
  void DidLoadFromMemoryCache(LoadFromMemoryCacheDetails* details);
  void DidCommitProvisionalLoad(const NotificationDetails& details);
  void DidFailProvisionalLoadWithError(ProvisionalLoadDetails* details);
  void DidStartResourceResponse(ResourceRequestDetails* details);
  void DidReceiveResourceRedirect(ResourceRedirectDetails* details);

  // Convenience method for initializing navigation entries.
  void InitializeEntryIfNeeded(NavigationEntry* entry);

  // Shows the pending messages (in info-bars) if any.
  void ShowPendingMessages();

  // Clears any pending messages.
  void ClearPendingMessages();

  // Our delegate.  The delegate is responsible for making policy decisions.
  // Must not be NULL.
  Delegate* delegate_;

  // The NavigationController that owns this SSLManager.  We are responsible
  // for the security UI of this tab.
  NavigationController* controller_;

  // The list of currently visible SSL InfoBars.
  ObserverList<SSLInfoBar> visible_info_bars_;

  // Handles registering notifications with the NotificationService.
  NotificationRegistrar registrar_;

  // Certificate policies for each host.
  std::map<std::string, net::X509Certificate::Policy> cert_policy_for_host_;

  // Domains for which it is OK to show insecure content.
  std::set<std::string> can_show_insecure_content_for_host_;

  // The list of messages that should be displayed (in info bars) when the page
  // currently loading had loaded.
  std::vector<SSLMessageInfo> pending_messages_;

  DISALLOW_COPY_AND_ASSIGN(SSLManager);
};

#endif // CHROME_BROWSER_SSL_MANAGER_H_