/* 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 "IPCStreamUtils.h"

#include "ipc/IPCMessageUtilsSpecializations.h"

#include "nsIHttpHeaderVisitor.h"
#include "nsIIPCSerializableInputStream.h"
#include "mozIRemoteLazyInputStream.h"

#include "mozilla/Assertions.h"
#include "mozilla/dom/File.h"
#include "mozilla/ipc/IPCStream.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/InputStreamLengthHelper.h"
#include "mozilla/RemoteLazyInputStreamParent.h"
#include "nsIMIMEInputStream.h"
#include "nsNetCID.h"

using namespace mozilla::dom;

namespace mozilla::ipc {

namespace {

class MIMEStreamHeaderVisitor final : public nsIHttpHeaderVisitor {
 public:
  explicit MIMEStreamHeaderVisitor(
      nsTArray<mozilla::ipc::HeaderEntry>& aHeaders)
      : mHeaders(aHeaders) {}

  NS_DECL_ISUPPORTS
  NS_IMETHOD VisitHeader(const nsACString& aName,
                         const nsACString& aValue) override {
    auto el = mHeaders.AppendElement();
    el->name() = aName;
    el->value() = aValue;
    return NS_OK;
  }

 private:
  ~MIMEStreamHeaderVisitor() = default;

  nsTArray<mozilla::ipc::HeaderEntry>& mHeaders;
};

NS_IMPL_ISUPPORTS(MIMEStreamHeaderVisitor, nsIHttpHeaderVisitor)

static bool SerializeLazyInputStream(nsIInputStream* aStream,
                                     IPCStream& aValue) {
  MOZ_ASSERT(aStream);
  MOZ_ASSERT(XRE_IsParentProcess());

  // If we're serializing a MIME stream, ensure we preserve header data which
  // would not be preserved by a RemoteLazyInputStream wrapper.
  if (nsCOMPtr<nsIMIMEInputStream> mimeStream = do_QueryInterface(aStream)) {
    MIMEInputStreamParams params;
    params.startedReading() = false;

    nsCOMPtr<nsIHttpHeaderVisitor> visitor =
        new MIMEStreamHeaderVisitor(params.headers());
    if (NS_WARN_IF(NS_FAILED(mimeStream->VisitHeaders(visitor)))) {
      return false;
    }

    nsCOMPtr<nsIInputStream> dataStream;
    if (NS_FAILED(mimeStream->GetData(getter_AddRefs(dataStream)))) {
      return false;
    }
    if (dataStream) {
      IPCStream data;
      if (!SerializeLazyInputStream(dataStream, data)) {
        return false;
      }
      params.optionalStream().emplace(std::move(data.stream()));
    }

    aValue.stream() = std::move(params);
    return true;
  }

  RefPtr<RemoteLazyInputStream> lazyStream =
      RemoteLazyInputStream::WrapStream(aStream);
  if (NS_WARN_IF(!lazyStream)) {
    return false;
  }

  aValue.stream() = RemoteLazyInputStreamParams(WrapNotNull(lazyStream));

  return true;
}

}  // anonymous namespace

bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream,
                        IPCStream& aValue, bool aAllowLazy) {
  nsCOMPtr<nsIInputStream> stream(std::move(aInputStream));
  if (!stream) {
    MOZ_ASSERT_UNREACHABLE(
        "Use the Maybe<...> overload to serialize optional nsIInputStreams");
    return false;
  }

  // When requesting a delayed start stream from the parent process, serialize
  // it as a remote lazy stream to avoid bloating payloads.
  if (aAllowLazy && XRE_IsParentProcess()) {
    return SerializeLazyInputStream(stream, aValue);
  }

  if (nsCOMPtr<nsIIPCSerializableInputStream> serializable =
          do_QueryInterface(stream)) {
    // If you change this size, please also update the payload size in
    // test_reload_large_postdata.html.
    const uint64_t kTooLargeStream = 1024 * 1024;

    uint32_t sizeUsed = 0;
    serializable->Serialize(aValue.stream(), kTooLargeStream, &sizeUsed);

    MOZ_ASSERT(sizeUsed <= kTooLargeStream);

    if (aValue.stream().type() == InputStreamParams::T__None) {
      MOZ_CRASH("Serialize failed!");
    }
    return true;
  }

  InputStreamHelper::SerializeInputStreamAsPipe(stream, aValue.stream());
  if (aValue.stream().type() == InputStreamParams::T__None) {
    // This can fail from OOM, etc.  We will likely MOZ_CRASH now
    return false;
  }
  return true;
}

bool SerializeIPCStream(already_AddRefed<nsIInputStream> aInputStream,
                        Maybe<IPCStream>& aValue, bool aAllowLazy) {
  nsCOMPtr<nsIInputStream> stream(std::move(aInputStream));
  if (!stream) {
    aValue.reset();
    return true;
  }

  IPCStream value;
  if (SerializeIPCStream(stream.forget(), value, aAllowLazy)) {
    aValue = Some(value);
    return true;
  }
  return false;
}

already_AddRefed<nsIInputStream> DeserializeIPCStream(const IPCStream& aValue) {
  return InputStreamHelper::DeserializeInputStream(aValue.stream());
}

already_AddRefed<nsIInputStream> DeserializeIPCStream(
    const Maybe<IPCStream>& aValue) {
  if (aValue.isNothing()) {
    return nullptr;
  }

  return DeserializeIPCStream(aValue.ref());
}

}  // namespace mozilla::ipc

void IPC::ParamTraits<nsIInputStream*>::Write(IPC::MessageWriter* aWriter,
                                              nsIInputStream* aParam) {
  mozilla::Maybe<mozilla::ipc::IPCStream> stream;
  if (!mozilla::ipc::SerializeIPCStream(do_AddRef(aParam), stream,
                                        /* aAllowLazy */ true)) {
    MOZ_CRASH("Failed to serialize nsIInputStream");
  }

  WriteParam(aWriter, stream);
}

bool IPC::ParamTraits<nsIInputStream*>::Read(IPC::MessageReader* aReader,
                                             RefPtr<nsIInputStream>* aResult) {
  mozilla::Maybe<mozilla::ipc::IPCStream> ipcStream;
  if (!ReadParam(aReader, &ipcStream)) {
    return false;
  }

  *aResult = mozilla::ipc::DeserializeIPCStream(ipcStream);
  return true;
}

void IPC::ParamTraits<mozilla::ipc::EagerIPCStream>::Write(
    IPC::MessageWriter* aWriter, const paramType& aParam) {
  mozilla::ipc::IPCStream stream;
  if (!mozilla::ipc::SerializeIPCStream(do_AddRef(aParam.mStream.get()), stream,
                                        /* aAllowLazy */ false)) {
    MOZ_CRASH("Failed to serialize EagerIPCStream");
  }

  WriteParam(aWriter, stream);
}

IPC::ReadResult<mozilla::ipc::EagerIPCStream> IPC::ParamTraits<
    mozilla::ipc::EagerIPCStream>::Read(IPC::MessageReader* aReader) {
  mozilla::ipc::IPCStream ipcStream;
  if (!ReadParam(aReader, &ipcStream)) {
    return {};
  }

  nsCOMPtr<nsIInputStream> stream =
      mozilla::ipc::DeserializeIPCStream(ipcStream);
  if (!stream) {
    return {};
  }

  return paramType{mozilla::WrapNotNull(stream)};
}
