/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8; -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* 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 "FunctionBrokerIPCUtils.h"

#if defined(XP_WIN)

#include <schannel.h>

/* these defines are missing from mingw headers */
#ifndef SP_PROT_TLS1_1_CLIENT
#define SP_PROT_TLS1_1_CLIENT 0x00000200
#endif

#ifndef SP_PROT_TLS1_2_CLIENT
#define SP_PROT_TLS1_2_CLIENT 0x00000800
#endif

namespace mozilla {
namespace plugins {

mozilla::LazyLogModule sPluginHooksLog("PluginHooks");

static const DWORD SCHANNEL_SUPPORTED_PROTOCOLS =
  SP_PROT_TLS1_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT;

static const DWORD SCHANNEL_SUPPORTED_FLAGS =
  SCH_CRED_MANUAL_CRED_VALIDATION | SCH_CRED_NO_DEFAULT_CREDS |
  SCH_CRED_REVOCATION_CHECK_END_CERT;

void
OpenFileNameIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn)
{
  mHwndOwner = nullptr;

  // Filter is double-NULL terminated.  mFilter should include the double-NULL.
  mHasFilter = aLpofn->lpstrFilter != nullptr;
  if (mHasFilter) {
    uint32_t dNullIdx = 0;
    while (aLpofn->lpstrFilter[dNullIdx] != L'\0' ||
           aLpofn->lpstrFilter[dNullIdx+1] != L'\0') {
      dNullIdx++;
    }
    mFilter.assign(aLpofn->lpstrFilter, dNullIdx+2);
  }
  mHasCustomFilter = aLpofn->lpstrCustomFilter != nullptr;
  if (mHasCustomFilter) {
    mCustomFilterIn = std::wstring(aLpofn->lpstrCustomFilter);
    mNMaxCustFilterOut =
      aLpofn->nMaxCustFilter - (wcslen(aLpofn->lpstrCustomFilter) + 1);
  }
  else {
    mNMaxCustFilterOut = 0;
  }
  mFilterIndex = aLpofn->nFilterIndex;
  mFile = std::wstring(aLpofn->lpstrFile);
  mNMaxFile = aLpofn->nMaxFile;
  mNMaxFileTitle =
    aLpofn->lpstrFileTitle != nullptr ? aLpofn->nMaxFileTitle : 0;
  mHasInitialDir = aLpofn->lpstrInitialDir != nullptr;
  if (mHasInitialDir) {
    mInitialDir = std::wstring(aLpofn->lpstrInitialDir);
  }
  mHasTitle = aLpofn->lpstrTitle != nullptr;
  if (mHasTitle) {
    mTitle = std::wstring(aLpofn->lpstrTitle);
  }
  mHasDefExt = aLpofn->lpstrDefExt != nullptr;
  if (mHasDefExt) {
    mDefExt = std::wstring(aLpofn->lpstrDefExt);
  }

  mFlags = aLpofn->Flags;
  // If the user sets OFN_ALLOWMULTISELECT then we require OFN_EXPLORER
  // as well.  Without OFN_EXPLORER, the method has ancient legacy
  // behavior that we don't support.
  MOZ_ASSERT((mFlags & OFN_EXPLORER) || !(mFlags & OFN_ALLOWMULTISELECT));

  // We ignore any visual customization and callbacks that the user set.
  mFlags &= ~(OFN_ENABLEHOOK | OFN_ENABLETEMPLATEHANDLE | OFN_ENABLETEMPLATE);

  mFlagsEx = aLpofn->FlagsEx;
}

void
OpenFileNameIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const
{
  aLpofn->lStructSize = sizeof(OPENFILENAMEW);
  aLpofn->hwndOwner = mHwndOwner;
  if (mHasFilter) {
    memcpy(const_cast<LPWSTR>(aLpofn->lpstrFilter),
           mFilter.data(), mFilter.size() * sizeof(wchar_t));
  }
  if (mHasCustomFilter) {
    aLpofn->nMaxCustFilter = mCustomFilterIn.size() + 1 + mNMaxCustFilterOut;
    wcscpy(aLpofn->lpstrCustomFilter, mCustomFilterIn.c_str());
    memset(aLpofn->lpstrCustomFilter + mCustomFilterIn.size() + 1, 0,
           mNMaxCustFilterOut * sizeof(wchar_t));
  }
  else {
    aLpofn->nMaxCustFilter = 0;
  }
  aLpofn->nFilterIndex = mFilterIndex;
  if (mNMaxFile > 0) {
    wcsncpy(aLpofn->lpstrFile, mFile.c_str(),
            std::min(static_cast<uint32_t>(mFile.size()+1), mNMaxFile));
    aLpofn->lpstrFile[mNMaxFile - 1] = L'\0';
  }
  aLpofn->nMaxFile = mNMaxFile;
  aLpofn->nMaxFileTitle = mNMaxFileTitle;
  if (mHasInitialDir) {
    wcscpy(const_cast<LPWSTR>(aLpofn->lpstrInitialDir), mInitialDir.c_str());
  }
  if (mHasTitle) {
    wcscpy(const_cast<LPWSTR>(aLpofn->lpstrTitle), mTitle.c_str());
  }
  aLpofn->Flags = mFlags;  /* TODO: Consider adding OFN_NOCHANGEDIR */
  if (mHasDefExt) {
    wcscpy(const_cast<LPWSTR>(aLpofn->lpstrDefExt), mDefExt.c_str());
  }
  aLpofn->FlagsEx = mFlagsEx;
}

void
OpenFileNameIPC::AllocateOfnStrings(LPOPENFILENAMEW aLpofn) const
{
  if (mHasFilter) {
    // mFilter is double-NULL terminated and it includes the double-NULL in its length.
    aLpofn->lpstrFilter =
      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mFilter.size())));
  }
  if (mHasCustomFilter) {
    aLpofn->lpstrCustomFilter =
      static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * (mCustomFilterIn.size() + 1 + mNMaxCustFilterOut)));
  }
  aLpofn->lpstrFile =
    static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * mNMaxFile));
  if (mNMaxFileTitle > 0) {
    aLpofn->lpstrFileTitle =
      static_cast<LPTSTR>(moz_xmalloc(sizeof(wchar_t) * mNMaxFileTitle));
  }
  if (mHasInitialDir) {
    aLpofn->lpstrInitialDir =
      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mInitialDir.size() + 1)));
  }
  if (mHasTitle) {
    aLpofn->lpstrTitle =
      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mTitle.size() + 1)));
  }
  if (mHasDefExt) {
    aLpofn->lpstrDefExt =
      static_cast<LPCTSTR>(moz_xmalloc(sizeof(wchar_t) * (mDefExt.size() + 1)));
  }
}

// static
void
OpenFileNameIPC::FreeOfnStrings(LPOPENFILENAMEW aLpofn)
{
  if (aLpofn->lpstrFilter) {
    free(const_cast<LPWSTR>(aLpofn->lpstrFilter));
  }
  if (aLpofn->lpstrCustomFilter) {
    free(aLpofn->lpstrCustomFilter);
  }
  if (aLpofn->lpstrFile) {
    free(aLpofn->lpstrFile);
  }
  if (aLpofn->lpstrFileTitle) {
    free(aLpofn->lpstrFileTitle);
  }
  if (aLpofn->lpstrInitialDir) {
    free(const_cast<LPWSTR>(aLpofn->lpstrInitialDir));
  }
  if (aLpofn->lpstrTitle) {
    free(const_cast<LPWSTR>(aLpofn->lpstrTitle));
  }
  if (aLpofn->lpstrDefExt) {
    free(const_cast<LPWSTR>(aLpofn->lpstrDefExt));
  }
}

void
OpenFileNameRetIPC::CopyFromOfn(LPOPENFILENAMEW aLpofn)
{
  if (aLpofn->lpstrCustomFilter != nullptr) {
    mCustomFilterOut =
      std::wstring(aLpofn->lpstrCustomFilter + wcslen(aLpofn->lpstrCustomFilter) + 1);
  }
  mFile.assign(aLpofn->lpstrFile, aLpofn->nMaxFile);
  if (aLpofn->lpstrFileTitle != nullptr) {
    mFileTitle.assign(aLpofn->lpstrFileTitle, wcslen(aLpofn->lpstrFileTitle) + 1);
  }
  mFileOffset = aLpofn->nFileOffset;
  mFileExtension = aLpofn->nFileExtension;
}

void
OpenFileNameRetIPC::AddToOfn(LPOPENFILENAMEW aLpofn) const
{
  if (aLpofn->lpstrCustomFilter) {
    LPWSTR secondString =
      aLpofn->lpstrCustomFilter + wcslen(aLpofn->lpstrCustomFilter) + 1;
    const wchar_t* customFilterOut = mCustomFilterOut.c_str();
    MOZ_ASSERT(wcslen(aLpofn->lpstrCustomFilter) + 1 +
               wcslen(customFilterOut) + 1 + 1 <= aLpofn->nMaxCustFilter);
    wcscpy(secondString, customFilterOut);
    secondString[wcslen(customFilterOut) + 1] = L'\0';  // terminated with two NULLs
  }
  MOZ_ASSERT(mFile.size() <= aLpofn->nMaxFile);
  memcpy(aLpofn->lpstrFile,
         mFile.data(), mFile.size() * sizeof(wchar_t));
  if (aLpofn->lpstrFileTitle != nullptr) {
    MOZ_ASSERT(mFileTitle.size() + 1 < aLpofn->nMaxFileTitle);
    wcscpy(aLpofn->lpstrFileTitle, mFileTitle.c_str());
  }
  aLpofn->nFileOffset = mFileOffset;
  aLpofn->nFileExtension = mFileExtension;
}

void
IPCSchannelCred::CopyFrom(const PSCHANNEL_CRED& aSCred)
{
  // We assert that the aSCred fields take supported values.
  // If they do not then we ignore the values we were given.
  MOZ_ASSERT(aSCred->dwVersion == SCHANNEL_CRED_VERSION);
  MOZ_ASSERT(aSCred->cCreds == 0);
  MOZ_ASSERT(aSCred->paCred == nullptr);
  MOZ_ASSERT(aSCred->hRootStore == nullptr);
  MOZ_ASSERT(aSCred->cMappers == 0);
  MOZ_ASSERT(aSCred->aphMappers == nullptr);
  MOZ_ASSERT(aSCred->cSupportedAlgs == 0);
  MOZ_ASSERT(aSCred->palgSupportedAlgs == nullptr);
  MOZ_ASSERT((aSCred->grbitEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS) ==
             aSCred->grbitEnabledProtocols);
  mEnabledProtocols =
    aSCred->grbitEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS;
  mMinStrength = aSCred->dwMinimumCipherStrength;
  mMaxStrength = aSCred->dwMaximumCipherStrength;
  MOZ_ASSERT(aSCred->dwSessionLifespan == 0);
  MOZ_ASSERT((aSCred->dwFlags & SCHANNEL_SUPPORTED_FLAGS) == aSCred->dwFlags);
  mFlags = aSCred->dwFlags & SCHANNEL_SUPPORTED_FLAGS;
  MOZ_ASSERT(aSCred->dwCredFormat == 0);
}

void
IPCSchannelCred::CopyTo(PSCHANNEL_CRED& aSCred) const
{
  // Validate values as they come from an untrusted process.
  memset(aSCred, 0, sizeof(SCHANNEL_CRED));
  aSCred->dwVersion = SCHANNEL_CRED_VERSION;
  aSCred->grbitEnabledProtocols =
    mEnabledProtocols & SCHANNEL_SUPPORTED_PROTOCOLS;
  aSCred->dwMinimumCipherStrength = mMinStrength;
  aSCred->dwMaximumCipherStrength = mMaxStrength;
  aSCred->dwFlags = mFlags & SCHANNEL_SUPPORTED_FLAGS;
}

void
IPCInternetBuffers::CopyFrom(const LPINTERNET_BUFFERSA& aBufs)
{
  mBuffers.Clear();

  LPINTERNET_BUFFERSA inetBuf = aBufs;
  while (inetBuf) {
    MOZ_ASSERT(inetBuf->dwStructSize == sizeof(INTERNET_BUFFERSA));
    Buffer* ipcBuf = mBuffers.AppendElement();

    ipcBuf->mHeader.SetIsVoid(inetBuf->lpcszHeader == nullptr);
    if (inetBuf->lpcszHeader) {
      ipcBuf->mHeader.Assign(inetBuf->lpcszHeader, inetBuf->dwHeadersLength);
    }
    ipcBuf->mHeaderTotal = inetBuf->dwHeadersTotal;

    ipcBuf->mBuffer.SetIsVoid(inetBuf->lpvBuffer == nullptr);
    if (inetBuf->lpvBuffer) {
      ipcBuf->mBuffer.Assign(static_cast<char*>(inetBuf->lpvBuffer),
                             inetBuf->dwBufferLength);
    }
    ipcBuf->mBufferTotal = inetBuf->dwBufferTotal;
    inetBuf = inetBuf->Next;
  }
}

void
IPCInternetBuffers::CopyTo(LPINTERNET_BUFFERSA& aBufs) const
{
  MOZ_ASSERT(!aBufs);

  LPINTERNET_BUFFERSA lastBuf = nullptr;
  for (size_t idx = 0; idx < mBuffers.Length(); ++idx) {
    const Buffer& ipcBuf = mBuffers[idx];
    LPINTERNET_BUFFERSA newBuf =
      static_cast<LPINTERNET_BUFFERSA>(moz_xcalloc(1, sizeof(INTERNET_BUFFERSA)));
    if (idx == 0) {
      aBufs = newBuf;
    } else {
      MOZ_ASSERT(lastBuf);
      lastBuf->Next = newBuf;
      lastBuf = newBuf;
    }

    newBuf->dwStructSize = sizeof(INTERNET_BUFFERSA);

    newBuf->dwHeadersTotal = ipcBuf.mHeaderTotal;
    if (!ipcBuf.mHeader.IsVoid()) {
      newBuf->lpcszHeader =
        static_cast<LPCSTR>(moz_xmalloc(ipcBuf.mHeader.Length()));
      memcpy(const_cast<char*>(newBuf->lpcszHeader), ipcBuf.mHeader.Data(),
             ipcBuf.mHeader.Length());
      newBuf->dwHeadersLength = ipcBuf.mHeader.Length();
    }

    newBuf->dwBufferTotal = ipcBuf.mBufferTotal;
    if (!ipcBuf.mBuffer.IsVoid()) {
      newBuf->lpvBuffer = moz_xmalloc(ipcBuf.mBuffer.Length());
      memcpy(newBuf->lpvBuffer, ipcBuf.mBuffer.Data(),
             ipcBuf.mBuffer.Length());
      newBuf->dwBufferLength = ipcBuf.mBuffer.Length();
    }
  }
}

/* static */ void
IPCInternetBuffers::FreeBuffers(LPINTERNET_BUFFERSA& aBufs)
{
  if (!aBufs) {
    return;
  }
  while (aBufs) {
    LPINTERNET_BUFFERSA temp = aBufs->Next;
    free(const_cast<char*>(aBufs->lpcszHeader));
    free(aBufs->lpvBuffer);
    free(aBufs);
    aBufs = temp;
  }
}

void
IPCPrintDlg::CopyFrom(const LPPRINTDLGW& aDlg)
{
  // DLP: Trouble -- my prior impl "worked" but didn't return anything
  // AFAIR.  So... ???  But it printed a page!!!  How?!
  MOZ_ASSERT_UNREACHABLE("TODO: DLP:");
}

void
IPCPrintDlg::CopyTo(LPPRINTDLGW& aDlg) const
{
  MOZ_ASSERT_UNREACHABLE("TODO: DLP:");
}

} // namespace plugins
} // namespace mozilla

#endif // defined(XP_WIN)
