/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set ts=4 sw=4 sts=4 et cin: */
/* 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/. */

#ifndef nsNetUtil_inl
#define nsNetUtil_inl

#include "mozilla/Services.h"

#include "nsComponentManagerUtils.h"
#include "nsIBufferedStreams.h"
#include "nsIChannel.h"
#include "nsIFile.h"
#include "nsIFileStreams.h"
#include "nsIFileURL.h"
#include "nsIHttpChannel.h"
#include "nsIInputStreamChannel.h"
#include "nsIIOService.h"
#include "nsINestedURI.h"
#include "nsINode.h"
#include "nsIProtocolHandler.h"
#include "nsIStandardURL.h"
#include "nsIStreamLoader.h"
#include "nsIURI.h"
#include "nsIURIWithPrincipal.h"
#include "nsIWritablePropertyBag2.h"
#include "nsNetCID.h"
#include "nsStringStream.h"

#ifdef MOZILLA_INTERNAL_API
// Don't allow functions that end up in nsNetUtil.cpp to be inlined out.
#define INLINE_IF_EXTERN MOZ_NEVER_INLINE
#else
// Make sure that functions included via nsNetUtil.h don't get multiply defined.
#define INLINE_IF_EXTERN MOZ_ALWAYS_INLINE
#endif

#ifdef MOZILLA_INTERNAL_API

INLINE_IF_EXTERN already_AddRefed<nsIIOService>
do_GetIOService(nsresult *error /* = 0 */)
{
    nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
    if (error)
        *error = io ? NS_OK : NS_ERROR_FAILURE;
    return io.forget();
}

INLINE_IF_EXTERN already_AddRefed<nsINetUtil>
do_GetNetUtil(nsresult *error /* = 0 */)
{
    nsCOMPtr<nsIIOService> io = mozilla::services::GetIOService();
    nsCOMPtr<nsINetUtil> util;
    if (io)
        util = do_QueryInterface(io);

    if (error)
        *error = !!util ? NS_OK : NS_ERROR_FAILURE;
    return util.forget();
}

#else

INLINE_IF_EXTERN const nsGetServiceByContractIDWithError
do_GetIOService(nsresult *error /* = 0 */)
{
    return nsGetServiceByContractIDWithError(NS_IOSERVICE_CONTRACTID, error);
}

INLINE_IF_EXTERN const nsGetServiceByContractIDWithError
do_GetNetUtil(nsresult *error /* = 0 */)
{
    return do_GetIOService(error);
}
#endif

// private little helper function... don't call this directly!
MOZ_ALWAYS_INLINE nsresult
net_EnsureIOService(nsIIOService **ios, nsCOMPtr<nsIIOService> &grip)
{
    nsresult rv = NS_OK;
    if (!*ios) {
        grip = do_GetIOService(&rv);
        *ios = grip;
    }
    return rv;
}

INLINE_IF_EXTERN nsresult
NS_URIChainHasFlags(nsIURI   *uri,
                    uint32_t  flags,
                    bool     *result)
{
    nsresult rv;
    nsCOMPtr<nsINetUtil> util = do_GetNetUtil(&rv);
    NS_ENSURE_SUCCESS(rv, rv);

    return util->URIChainHasFlags(uri, flags, result);
}

INLINE_IF_EXTERN nsresult
NS_NewURI(nsIURI **result,
          const nsACString &spec,
          const char *charset /* = nullptr */,
          nsIURI *baseURI /* = nullptr */,
          nsIIOService *ioService /* = nullptr */)     // pass in nsIIOService to optimize callers
{
    nsresult rv;
    nsCOMPtr<nsIIOService> grip;
    rv = net_EnsureIOService(&ioService, grip);
    if (ioService)
        rv = ioService->NewURI(spec, charset, baseURI, result);
    return rv;
}

INLINE_IF_EXTERN nsresult
NS_NewURI(nsIURI **result,
          const nsAString &spec,
          const char *charset /* = nullptr */,
          nsIURI *baseURI /* = nullptr */,
          nsIIOService *ioService /* = nullptr */)     // pass in nsIIOService to optimize callers
{
    return NS_NewURI(result, NS_ConvertUTF16toUTF8(spec), charset, baseURI, ioService);
}

INLINE_IF_EXTERN nsresult
NS_NewURI(nsIURI **result,
          const char *spec,
          nsIURI *baseURI /* = nullptr */,
          nsIIOService *ioService /* = nullptr */)     // pass in nsIIOService to optimize callers
{
    return NS_NewURI(result, nsDependentCString(spec), nullptr, baseURI, ioService);
}

INLINE_IF_EXTERN nsresult
NS_NewFileURI(nsIURI **result,
              nsIFile *spec,
              nsIIOService *ioService /* = nullptr */)     // pass in nsIIOService to optimize callers
{
    nsresult rv;
    nsCOMPtr<nsIIOService> grip;
    rv = net_EnsureIOService(&ioService, grip);
    if (ioService)
        rv = ioService->NewFileURI(spec, result);
    return rv;
}

INLINE_IF_EXTERN nsresult
NS_NewChannelInternal(nsIChannel           **outChannel,
                      nsIURI                *aUri,
                      nsINode               *aLoadingNode,
                      nsIPrincipal          *aLoadingPrincipal,
                      nsIPrincipal          *aTriggeringPrincipal,
                      nsSecurityFlags        aSecurityFlags,
                      nsContentPolicyType    aContentPolicyType,
                      nsILoadGroup          *aLoadGroup /* = nullptr */,
                      nsIInterfaceRequestor *aCallbacks /* = nullptr */,
                      nsLoadFlags            aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
                      nsIIOService          *aIoService /* = nullptr */)
{
  NS_ENSURE_ARG_POINTER(outChannel);

  nsCOMPtr<nsIIOService> grip;
  nsresult rv = net_EnsureIOService(&aIoService, grip);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIChannel> channel;
  rv = aIoService->NewChannelFromURI2(
         aUri,
         aLoadingNode ?
           aLoadingNode->AsDOMNode() : nullptr,
         aLoadingPrincipal,
         aTriggeringPrincipal,
         aSecurityFlags,
         aContentPolicyType,
         getter_AddRefs(channel));
  NS_ENSURE_SUCCESS(rv, rv);

  if (aLoadGroup) {
    rv = channel->SetLoadGroup(aLoadGroup);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (aCallbacks) {
    rv = channel->SetNotificationCallbacks(aCallbacks);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
    // Retain the LOAD_REPLACE load flag if set.
    nsLoadFlags normalLoadFlags = 0;
    channel->GetLoadFlags(&normalLoadFlags);
    rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
    NS_ENSURE_SUCCESS(rv, rv);
  }

  channel.forget(outChannel);
  return NS_OK;
}

INLINE_IF_EXTERN nsresult
NS_NewChannelInternal(nsIChannel           **outChannel,
                      nsIURI                *aUri,
                      nsILoadInfo           *aLoadInfo,
                      nsILoadGroup          *aLoadGroup /* = nullptr */,
                      nsIInterfaceRequestor *aCallbacks /* = nullptr */,
                      nsLoadFlags            aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
                      nsIIOService          *aIoService /* = nullptr */)
{
  // NS_NewChannelInternal is mostly called for channel redirects. We should allow
  // the creation of a channel even if the original channel did not have a loadinfo
  // attached.
  NS_ENSURE_ARG_POINTER(outChannel);

  nsCOMPtr<nsIIOService> grip;
  nsresult rv = net_EnsureIOService(&aIoService, grip);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIChannel> channel;
  rv = aIoService->NewChannelFromURIWithLoadInfo(
         aUri,
         aLoadInfo,
         getter_AddRefs(channel));
  NS_ENSURE_SUCCESS(rv, rv);

  if (aLoadGroup) {
    rv = channel->SetLoadGroup(aLoadGroup);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (aCallbacks) {
    rv = channel->SetNotificationCallbacks(aCallbacks);
    NS_ENSURE_SUCCESS(rv, rv);
  }

  if (aLoadFlags != nsIRequest::LOAD_NORMAL) {
    // Retain the LOAD_REPLACE load flag if set.
    nsLoadFlags normalLoadFlags = 0;
    channel->GetLoadFlags(&normalLoadFlags);
    rv = channel->SetLoadFlags(aLoadFlags | (normalLoadFlags & nsIChannel::LOAD_REPLACE));
    NS_ENSURE_SUCCESS(rv, rv);
  }

  channel.forget(outChannel);
  return NS_OK;
}

INLINE_IF_EXTERN nsresult /* NS_NewChannelPrincipal */
NS_NewChannel(nsIChannel           **outChannel,
              nsIURI                *aUri,
              nsIPrincipal          *aLoadingPrincipal,
              nsSecurityFlags        aSecurityFlags,
              nsContentPolicyType    aContentPolicyType,
              nsILoadGroup          *aLoadGroup /* = nullptr */,
              nsIInterfaceRequestor *aCallbacks /* = nullptr */,
              nsLoadFlags            aLoadFlags /* = nsIRequest::LOAD_NORMAL */,
              nsIIOService          *aIoService /* = nullptr */)
{
  return NS_NewChannelInternal(outChannel,
                               aUri,
                               nullptr, // aLoadingNode,
                               aLoadingPrincipal,
                               nullptr, // aTriggeringPrincipal
                               aSecurityFlags,
                               aContentPolicyType,
                               aLoadGroup,
                               aCallbacks,
                               aLoadFlags,
                               aIoService);
}

INLINE_IF_EXTERN nsresult
NS_NewStreamLoader(nsIStreamLoader        **result,
                   nsIStreamLoaderObserver *observer,
                   nsIRequestObserver      *requestObserver /* = nullptr */)
{
    nsresult rv;
    nsCOMPtr<nsIStreamLoader> loader =
        do_CreateInstance(NS_STREAMLOADER_CONTRACTID, &rv);
    if (NS_SUCCEEDED(rv)) {
        rv = loader->Init(observer, requestObserver);
        if (NS_SUCCEEDED(rv)) {
            *result = nullptr;
            loader.swap(*result);
        }
    }
    return rv;
}

INLINE_IF_EXTERN nsresult
NS_NewLocalFileInputStream(nsIInputStream **result,
                           nsIFile         *file,
                           int32_t          ioFlags       /* = -1 */,
                           int32_t          perm          /* = -1 */,
                           int32_t          behaviorFlags /* = 0 */)
{
    nsresult rv;
    nsCOMPtr<nsIFileInputStream> in =
        do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
    if (NS_SUCCEEDED(rv)) {
        rv = in->Init(file, ioFlags, perm, behaviorFlags);
        if (NS_SUCCEEDED(rv))
            in.forget(result);
    }
    return rv;
}

INLINE_IF_EXTERN nsresult
NS_NewLocalFileOutputStream(nsIOutputStream **result,
                            nsIFile          *file,
                            int32_t           ioFlags       /* = -1 */,
                            int32_t           perm          /* = -1 */,
                            int32_t           behaviorFlags /* = 0 */)
{
    nsresult rv;
    nsCOMPtr<nsIFileOutputStream> out =
        do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
    if (NS_SUCCEEDED(rv)) {
        rv = out->Init(file, ioFlags, perm, behaviorFlags);
        if (NS_SUCCEEDED(rv))
            out.forget(result);
    }
    return rv;
}

INLINE_IF_EXTERN MOZ_WARN_UNUSED_RESULT nsresult
NS_NewBufferedInputStream(nsIInputStream **result,
                          nsIInputStream  *str,
                          uint32_t         bufferSize)
{
    nsresult rv;
    nsCOMPtr<nsIBufferedInputStream> in =
        do_CreateInstance(NS_BUFFEREDINPUTSTREAM_CONTRACTID, &rv);
    if (NS_SUCCEEDED(rv)) {
        rv = in->Init(str, bufferSize);
        if (NS_SUCCEEDED(rv)) {
            in.forget(result);
        }
    }
    return rv;
}

INLINE_IF_EXTERN nsresult
NS_NewPostDataStream(nsIInputStream  **result,
                     bool              isFile,
                     const nsACString &data)
{
    nsresult rv;

    if (isFile) {
        nsCOMPtr<nsIFile> file;
        nsCOMPtr<nsIInputStream> fileStream;

        rv = NS_NewNativeLocalFile(data, false, getter_AddRefs(file));
        if (NS_SUCCEEDED(rv)) {
            rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), file);
            if (NS_SUCCEEDED(rv)) {
                // wrap the file stream with a buffered input stream
                rv = NS_NewBufferedInputStream(result, fileStream, 8192);
            }
        }
        return rv;
    }

    // otherwise, create a string stream for the data (copies)
    nsCOMPtr<nsIStringInputStream> stream
        (do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv));
    if (NS_FAILED(rv))
        return rv;

    rv = stream->SetData(data.BeginReading(), data.Length());
    if (NS_FAILED(rv))
        return rv;

    stream.forget(result);
    return NS_OK;
}

INLINE_IF_EXTERN bool
NS_IsOffline()
{
    bool offline = true;
    bool connectivity = true;
    nsCOMPtr<nsIIOService> ios = do_GetIOService();
    if (ios) {
        ios->GetOffline(&offline);
        ios->GetConnectivity(&connectivity);
    }
    return offline || !connectivity;
}

#endif // nsNetUtil_inl
