/* 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/. */

#include "BodyExtractor.h"

#include "mozilla/UniquePtr.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FormData.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/URLSearchParams.h"
#include "mozilla/dom/XMLHttpRequest.h"
#include "nsContentUtils.h"
#include "nsDOMSerializer.h"
#include "nsIGlobalObject.h"
#include "nsIInputStream.h"
#include "nsIOutputStream.h"
#include "nsIStorageStream.h"
#include "nsStringStream.h"

namespace mozilla::dom {

static nsresult GetBufferDataAsStream(Vector<uint8_t>&& aData,
                                      nsIInputStream** aResult,
                                      uint64_t* aContentLength,
                                      nsACString& aContentType,
                                      nsACString& aCharset) {
  aContentType.SetIsVoid(true);
  aCharset.Truncate();

  *aContentLength = aData.length();

  nsCOMPtr<nsIInputStream> stream;
  nsresult rv = NS_NewByteInputStream(
      getter_AddRefs(stream),
      AsChars(Span(aData.extractOrCopyRawBuffer(), *aContentLength)),
      NS_ASSIGNMENT_ADOPT);
  NS_ENSURE_SUCCESS(rv, rv);

  stream.forget(aResult);

  return NS_OK;
}

template <>
nsresult BodyExtractor<const ArrayBuffer>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  Maybe<Vector<uint8_t>> body = mBody->CreateFromData<Vector<uint8_t>>();
  if (body.isNothing()) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return GetBufferDataAsStream(body.extract(), aResult, aContentLength,
                               aContentTypeWithCharset, aCharset);
}

template <>
nsresult BodyExtractor<const ArrayBufferView>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  Maybe<Vector<uint8_t>> body = mBody->CreateFromData<Vector<uint8_t>>();
  if (body.isNothing()) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  return GetBufferDataAsStream(body.extract(), aResult, aContentLength,
                               aContentTypeWithCharset, aCharset);
}

template <>
nsresult BodyExtractor<Document>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  NS_ENSURE_STATE(mBody);
  aCharset.AssignLiteral("UTF-8");

  nsresult rv;
  nsCOMPtr<nsIStorageStream> storStream;
  rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storStream));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIOutputStream> output;
  rv = storStream->GetOutputStream(0, getter_AddRefs(output));
  NS_ENSURE_SUCCESS(rv, rv);

  if (mBody->IsHTMLDocument()) {
    aContentTypeWithCharset.AssignLiteral("text/html;charset=UTF-8");

    nsString serialized;
    if (!nsContentUtils::SerializeNodeToMarkup(mBody, true, serialized, false,
                                               {})) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    nsAutoCString utf8Serialized;
    if (!AppendUTF16toUTF8(serialized, utf8Serialized, fallible)) {
      return NS_ERROR_OUT_OF_MEMORY;
    }

    uint32_t written;
    rv = output->Write(utf8Serialized.get(), utf8Serialized.Length(), &written);
    NS_ENSURE_SUCCESS(rv, rv);

    MOZ_ASSERT(written == utf8Serialized.Length());
  } else {
    aContentTypeWithCharset.AssignLiteral("application/xml;charset=UTF-8");

    auto serializer = MakeUnique<nsDOMSerializer>();

    // Make sure to use the encoding we'll send
    ErrorResult res;
    serializer->SerializeToStream(*mBody, output, u"UTF-8"_ns, res);
    if (NS_WARN_IF(res.Failed())) {
      return res.StealNSResult();
    }
  }

  output->Close();

  uint32_t length;
  rv = storStream->GetLength(&length);
  NS_ENSURE_SUCCESS(rv, rv);
  *aContentLength = length;

  rv = storStream->NewInputStream(0, aResult);
  NS_ENSURE_SUCCESS(rv, rv);
  return NS_OK;
}

template <>
nsresult BodyExtractor<const nsAString>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  nsCString encoded;
  if (!CopyUTF16toUTF8(*mBody, encoded, fallible)) {
    return NS_ERROR_OUT_OF_MEMORY;
  }

  uint32_t encodedLength = encoded.Length();
  nsresult rv = NS_NewCStringInputStream(aResult, std::move(encoded));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  *aContentLength = encodedLength;
  aContentTypeWithCharset.AssignLiteral("text/plain;charset=UTF-8");
  aCharset.AssignLiteral("UTF-8");
  return NS_OK;
}

template <>
nsresult BodyExtractor<nsIInputStream>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  aContentTypeWithCharset.AssignLiteral("text/plain");
  aCharset.Truncate();

  nsresult rv = mBody->Available(aContentLength);
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsIInputStream> stream(mBody);
  stream.forget(aResult);
  return NS_OK;
}

template <>
nsresult BodyExtractor<const Blob>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
                            aCharset);
}

template <>
nsresult BodyExtractor<const FormData>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
                            aCharset);
}

template <>
nsresult BodyExtractor<const URLSearchParams>::GetAsStream(
    nsIInputStream** aResult, uint64_t* aContentLength,
    nsACString& aContentTypeWithCharset, nsACString& aCharset) const {
  return mBody->GetSendInfo(aResult, aContentLength, aContentTypeWithCharset,
                            aCharset);
}

}  // namespace mozilla::dom
