/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Utility routines for checking content load/process policy settings,
 * and routines helpful for content policy implementors.
 *
 * XXXbz it would be nice if some of this stuff could be out-of-lined in
 * nsContentUtils.  That would work for almost all the callers...
 */

#ifndef __nsContentPolicyUtils_h__
#define __nsContentPolicyUtils_h__

#include "nsContentUtils.h"
#include "nsIContentPolicy.h"
#include "nsIContent.h"
#include "nsIScriptSecurityManager.h"
#include "nsIURI.h"
#include "nsServiceManagerUtils.h"

//XXXtw sadly, this makes consumers of nsContentPolicyUtils depend on widget
#include "nsIDocument.h"
#include "nsPIDOMWindow.h"

class nsACString;
class nsIPrincipal;

#define NS_CONTENTPOLICY_CONTRACTID   "@mozilla.org/layout/content-policy;1"
#define NS_CONTENTPOLICY_CATEGORY "content-policy"
#define NS_SIMPLECONTENTPOLICY_CATEGORY "simple-content-policy"
#define NS_CONTENTPOLICY_CID                              \
  {0x0e3afd3d, 0xeb60, 0x4c2b,                            \
     { 0x96, 0x3b, 0x56, 0xd7, 0xc4, 0x39, 0xf1, 0x24 }}

/**
 * Evaluates to true if val is ACCEPT.
 *
 * @param val the status returned from shouldProcess/shouldLoad
 */
#define NS_CP_ACCEPTED(val) ((val) == nsIContentPolicy::ACCEPT)

/**
 * Evaluates to true if val is a REJECT_* status
 *
 * @param val the status returned from shouldProcess/shouldLoad
 */
#define NS_CP_REJECTED(val) ((val) != nsIContentPolicy::ACCEPT)

// Offer convenient translations of constants -> const char*

// convenience macro to reduce some repetative typing...
// name is the name of a constant from this interface
#define CASE_RETURN(name)          \
  case nsIContentPolicy:: name :   \
    return #name

/**
 * Returns a string corresponding to the name of the response constant, or
 * "<Unknown Response>" if an unknown response value is given.
 *
 * The return value is static and must not be freed.
 *
 * @param response the response code
 * @return the name of the given response code
 */
inline const char *
NS_CP_ResponseName(int16_t response)
{
  switch (response) {
    CASE_RETURN( REJECT_REQUEST );
    CASE_RETURN( REJECT_TYPE    );
    CASE_RETURN( REJECT_SERVER  );
    CASE_RETURN( REJECT_OTHER   );
    CASE_RETURN( ACCEPT         );
  default:
    return "<Unknown Response>";
  }
}

/**
 * Returns a string corresponding to the name of the content type constant, or
 * "<Unknown Type>" if an unknown content type value is given.
 *
 * The return value is static and must not be freed.
 *
 * @param contentType the content type code
 * @return the name of the given content type code
 */
inline const char *
NS_CP_ContentTypeName(uint32_t contentType)
{
  switch (contentType) {
    CASE_RETURN( TYPE_OTHER                       );
    CASE_RETURN( TYPE_SCRIPT                      );
    CASE_RETURN( TYPE_IMAGE                       );
    CASE_RETURN( TYPE_STYLESHEET                  );
    CASE_RETURN( TYPE_OBJECT                      );
    CASE_RETURN( TYPE_DOCUMENT                    );
    CASE_RETURN( TYPE_SUBDOCUMENT                 );
    CASE_RETURN( TYPE_REFRESH                     );
    CASE_RETURN( TYPE_XBL                         );
    CASE_RETURN( TYPE_PING                        );
    CASE_RETURN( TYPE_XMLHTTPREQUEST              );
    CASE_RETURN( TYPE_OBJECT_SUBREQUEST           );
    CASE_RETURN( TYPE_DTD                         );
    CASE_RETURN( TYPE_FONT                        );
    CASE_RETURN( TYPE_MEDIA                       );
    CASE_RETURN( TYPE_WEBSOCKET                   );
    CASE_RETURN( TYPE_CSP_REPORT                  );
    CASE_RETURN( TYPE_XSLT                        );
    CASE_RETURN( TYPE_BEACON                      );
    CASE_RETURN( TYPE_FETCH                       );
    CASE_RETURN( TYPE_IMAGESET                    );
    CASE_RETURN( TYPE_WEB_MANIFEST                );
    CASE_RETURN( TYPE_INTERNAL_SCRIPT             );
    CASE_RETURN( TYPE_INTERNAL_WORKER             );
    CASE_RETURN( TYPE_INTERNAL_SHARED_WORKER      );
    CASE_RETURN( TYPE_INTERNAL_EMBED              );
    CASE_RETURN( TYPE_INTERNAL_OBJECT             );
    CASE_RETURN( TYPE_INTERNAL_FRAME              );
    CASE_RETURN( TYPE_INTERNAL_IFRAME             );
    CASE_RETURN( TYPE_INTERNAL_AUDIO              );
    CASE_RETURN( TYPE_INTERNAL_VIDEO              );
    CASE_RETURN( TYPE_INTERNAL_TRACK              );
    CASE_RETURN( TYPE_INTERNAL_XMLHTTPREQUEST     );
    CASE_RETURN( TYPE_INTERNAL_EVENTSOURCE        );
    CASE_RETURN( TYPE_INTERNAL_SERVICE_WORKER     );
    CASE_RETURN( TYPE_INTERNAL_SCRIPT_PRELOAD     );
    CASE_RETURN( TYPE_INTERNAL_IMAGE              );
    CASE_RETURN( TYPE_INTERNAL_IMAGE_PRELOAD      );
    CASE_RETURN( TYPE_INTERNAL_IMAGE_FAVICON      );
    CASE_RETURN( TYPE_INTERNAL_STYLESHEET         );
    CASE_RETURN( TYPE_INTERNAL_STYLESHEET_PRELOAD );
    CASE_RETURN( TYPE_SAVEAS_DOWNLOAD             );
    CASE_RETURN( TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS );
   default:
    return "<Unknown Type>";
  }
}

#undef CASE_RETURN

/* Passes on parameters from its "caller"'s context. */
#define CHECK_CONTENT_POLICY(action)                                          \
  PR_BEGIN_MACRO                                                              \
    nsCOMPtr<nsIContentPolicy> policy =                                       \
         do_GetService(NS_CONTENTPOLICY_CONTRACTID);                          \
    if (!policy)                                                              \
        return NS_ERROR_FAILURE;                                              \
                                                                              \
    return policy-> action (contentType, contentLocation, requestOrigin,      \
                            context, mimeType, extra, triggeringPrincipal,    \
                            decision);                                        \
  PR_END_MACRO

/* Passes on parameters from its "caller"'s context. */
#define CHECK_CONTENT_POLICY_WITH_SERVICE(action, _policy)                    \
  PR_BEGIN_MACRO                                                              \
    return _policy-> action (contentType, contentLocation, requestOrigin,     \
                             context, mimeType, extra, triggeringPrincipal,   \
                             decision);                                       \
  PR_END_MACRO

/**
 * Check whether we can short-circuit this check and bail out.  If not, get the
 * origin URI to use.
 *
 * Note: requestOrigin is scoped outside the PR_BEGIN_MACRO/PR_END_MACRO on
 * purpose */
#define CHECK_PRINCIPAL_AND_DATA(action)                                      \
  nsCOMPtr<nsIURI> requestOrigin;                                             \
  PR_BEGIN_MACRO                                                              \
  if (loadingPrincipal) {                                                     \
      nsCOMPtr<nsIScriptSecurityManager> secMan = aSecMan;                    \
      if (!secMan) {                                                          \
          secMan = do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);        \
      }                                                                       \
      if (secMan) {                                                           \
          bool isSystem;                                                      \
          /* We exempt most loads into any document with the system principal     \
           * from content policy checks, mostly as an optimization. Which means   \
           * that we need to apply this check to the loading principal, not the   \
           * principal that triggered the load. */                                \
          nsresult rv = secMan->IsSystemPrincipal(loadingPrincipal,           \
                                                  &isSystem);                 \
          NS_ENSURE_SUCCESS(rv, rv);                                          \
          if (isSystem && contentType != nsIContentPolicy::TYPE_DOCUMENT) {   \
              *decision = nsIContentPolicy::ACCEPT;                           \
              nsCOMPtr<nsINode> n = do_QueryInterface(context);               \
              if (!n) {                                                       \
                  nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(context);\
                  n = win ? win->GetExtantDoc() : nullptr;                    \
              }                                                               \
              if (n) {                                                        \
                  nsIDocument* d = n->OwnerDoc();                             \
                  if (d->IsLoadedAsData() || d->IsBeingUsedAsImage() ||       \
                      d->IsResourceDoc()) {                                   \
                      nsCOMPtr<nsIContentPolicy> dataPolicy =                 \
                          do_GetService(                                      \
                              "@mozilla.org/data-document-content-policy;1"); \
                      if (dataPolicy) {                                       \
                          nsContentPolicyType externalType =                  \
                              nsContentUtils::InternalContentPolicyTypeToExternal(contentType);\
                          dataPolicy-> action (externalType, contentLocation, \
                                               requestOrigin, context,        \
                                               mimeType, extra,               \
                                               triggeringPrincipal, decision);\
                      }                                                       \
                  }                                                           \
              }                                                               \
              return NS_OK;                                                   \
          }                                                                   \
      }                                                                       \
      nsresult rv = loadingPrincipal->GetURI(getter_AddRefs(requestOrigin));  \
      NS_ENSURE_SUCCESS(rv, rv);                                              \
  }                                                                           \
  PR_END_MACRO

/**
 * Alias for calling ShouldLoad on the content policy service.  Parameters are
 * the same as nsIContentPolicy::shouldLoad, except for the loadingPrincipal
 * and triggeringPrincipal parameters (which should be non-null if possible,
 * and have the same semantics as in LoadInfo), and the last two parameters,
 * which can be used to pass in pointer to some useful services if the caller
 * already has them. The origin URI to pass to shouldLoad will be the URI of
 * loadingPrincipal, unless loadingPrincipal is null (in which case a null
 * origin URI will be passed).
 */
inline nsresult
NS_CheckContentLoadPolicy(uint32_t          contentType,
                          nsIURI           *contentLocation,
                          nsIPrincipal     *loadingPrincipal,
                          nsIPrincipal     *triggeringPrincipal,
                          nsISupports      *context,
                          const nsACString &mimeType,
                          nsISupports      *extra,
                          int16_t          *decision,
                          nsIContentPolicy *policyService = nullptr,
                          nsIScriptSecurityManager* aSecMan = nullptr)
{
    CHECK_PRINCIPAL_AND_DATA(ShouldLoad);
    if (policyService) {
        CHECK_CONTENT_POLICY_WITH_SERVICE(ShouldLoad, policyService);
    }
    CHECK_CONTENT_POLICY(ShouldLoad);
}

/**
 * Alias for calling ShouldProcess on the content policy service.  Parameters
 * are the same as nsIContentPolicy::shouldLoad, except for the and
 * triggeringPrincipal parameters (which should be non-null if possible, and
 * have the same semantics as in nsLoadInfo), and the last parameter, which
 * can be used to pass in a pointer to a useful service if the caller already
 * has it.  The origin URI to pass to shouldLoad will be the URI of
 * loadingPrincipal, unless loadingPrincipal is null (in which case a null
 * origin URI will be passed).
 */
inline nsresult
NS_CheckContentProcessPolicy(uint32_t          contentType,
                             nsIURI           *contentLocation,
                             nsIPrincipal     *loadingPrincipal,
                             nsIPrincipal     *triggeringPrincipal,
                             nsISupports      *context,
                             const nsACString &mimeType,
                             nsISupports      *extra,
                             int16_t          *decision,
                             nsIContentPolicy *policyService = nullptr,
                             nsIScriptSecurityManager* aSecMan = nullptr)
{
    CHECK_PRINCIPAL_AND_DATA(ShouldProcess);
    if (policyService) {
        CHECK_CONTENT_POLICY_WITH_SERVICE(ShouldProcess, policyService);
    }
    CHECK_CONTENT_POLICY(ShouldProcess);
}

#undef CHECK_CONTENT_POLICY
#undef CHECK_CONTENT_POLICY_WITH_SERVICE

/**
 * Helper function to get an nsIDocShell given a context.
 * If the context is a document or window, the corresponding docshell will be
 * returned.
 * If the context is a non-document DOM node, the docshell of its ownerDocument
 * will be returned.
 *
 * @param aContext the context to find a docshell for (can be null)
 *
 * @return a WEAK pointer to the docshell, or nullptr if it could
 *     not be obtained
 *     
 * @note  As of this writing, calls to nsIContentPolicy::Should{Load,Process}
 * for TYPE_DOCUMENT and TYPE_SUBDOCUMENT pass in an aContext that either
 * points to the frameElement of the window the load is happening in
 * (in which case NS_CP_GetDocShellFromContext will return the parent of the
 * docshell the load is happening in), or points to the window the load is
 * happening in (in which case NS_CP_GetDocShellFromContext will return
 * the docshell the load is happening in).  It's up to callers to QI aContext
 * and handle things accordingly if they want the docshell the load is
 * happening in.  These are somewhat odd semantics, and bug 466687 has been
 * filed to consider improving them.
 */
inline nsIDocShell*
NS_CP_GetDocShellFromContext(nsISupports *aContext)
{
    if (!aContext) {
        return nullptr;
    }

    nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aContext);

    if (!window) {
        // our context might be a document (which also QIs to nsIDOMNode), so
        // try that first
        nsCOMPtr<nsIDocument> doc = do_QueryInterface(aContext);
        if (!doc) {
            // we were not a document after all, get our ownerDocument,
            // hopefully
            nsCOMPtr<nsIContent> content = do_QueryInterface(aContext);
            if (content) {
                doc = content->OwnerDoc();
            }
        }

        if (doc) {
            if (doc->GetDisplayDocument()) {
                doc = doc->GetDisplayDocument();
            }
            
            window = doc->GetWindow();
        }
    }

    if (!window) {
        return nullptr;
    }

    return window->GetDocShell();
}

#endif /* __nsContentPolicyUtils_h__ */
