#include "stdafx.h"
#include "PJNSMTPApp.h"
#include "PJNSMTPAppDlg.h"
#include "PJNSMTPAppConfigurationDlg.h"
#include "PJNSMTPApp.h"
#include "SendingDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif //#ifdef _DEBUG

//The class we use to display the progress control dialog
class CPJNSMTPConnectionEx : public CPJNSMTPConnection
{
public:
//Constructors / Destructors
  CPJNSMTPConnectionEx() noexcept;
  CPJNSMTPConnectionEx(const CPJNSMTPConnectionEx&) = delete;
  CPJNSMTPConnectionEx(CPJNSMTPConnectionEx&&) = delete;
  ~CPJNSMTPConnectionEx() = default; //NOLINT(modernize-use-override)

//Methods
  CPJNSMTPConnectionEx& operator=(const CPJNSMTPConnectionEx&) = delete;
  CPJNSMTPConnectionEx& operator=(CPJNSMTPConnectionEx&&) = delete;
  bool OnSendProgress(_In_ DWORD dwCurrentBytes, _In_ DWORD dwTotalBytes) override;

//Member variables
  CSendingDlg* m_pProgressDlg;
  bool m_bSetRange;
};

//The structure we pass to the worker thread
class CThreadSendData
{ 
public:
  CThreadSendData() noexcept : m_pConnection(nullptr),
                               m_pProgressDlg(nullptr),
                               m_pRecipients(nullptr),
                               m_pbyMessage(nullptr),
                               m_dwMessageSize(0),
                               m_pFrom(nullptr),
                               m_bSentOK(false),
                               m_dwMainThreadID(0),
                               m_ExceptionHr(S_OK)
  {
  }

//Member variables
  CPJNSMTPConnectionEx* m_pConnection;
  CSendingDlg* m_pProgressDlg;
  std::vector<CPJNSMTPAddress>* m_pRecipients;
  CString m_sFilename;
  const BYTE* m_pbyMessage;
  DWORD m_dwMessageSize;
  CPJNSMTPAddress* m_pFrom;
  bool m_bSentOK;
  DWORD m_dwMainThreadID;
  CString m_sExceptionMessage;
  CString m_sLastResponse;
  HRESULT m_ExceptionHr;
};


CPJNSMTPConnectionEx::CPJNSMTPConnectionEx() noexcept : m_pProgressDlg(nullptr),
                                                        m_bSetRange(false)
{
}

bool CPJNSMTPConnectionEx::OnSendProgress(_In_ DWORD dwCurrentBytes, _In_ DWORD dwTotalBytes)
{
  if (m_pProgressDlg != nullptr)
  {
    //Set the range
    if (!m_bSetRange)
    {
      m_bSetRange = true;
      m_pProgressDlg->m_ctrlProgress.SetRange(0, 100);
    }

    //Set the percentage done
    m_pProgressDlg->m_ctrlProgress.SetPos(dwCurrentBytes * 100 / dwTotalBytes);

    //Should we continue
    const bool bContinue = !m_pProgressDlg->AbortFlag();
    if (!bContinue)
      Disconnect(false);

    return bContinue;
  }
  else
    return true;
}


#pragma warning(suppress: 26455)
CPJNSMTPAppDlg::CPJNSMTPAppDlg(CWnd* pParent) : CDialog(CPJNSMTPAppDlg::IDD, pParent),
                                                m_bDNSLookup(FALSE)
{
  //Load in the values from the registry
  CWinApp* pApp = AfxGetApp();
  CString sSection(_T("Settings"));
  m_nPort = pApp->GetProfileInt(sSection, _T("Port"), 25);
  m_sAddress = pApp->GetProfileString(sSection, _T("Address"));
  m_sHost = pApp->GetProfileString(sSection, _T("Host"));
  m_sName = pApp->GetProfileString(sSection, _T("Name"));
  m_Auth = static_cast<CPJNSMTPConnection::AuthenticationMethod>(pApp->GetProfileInt(sSection, _T("Authenticate"), static_cast<int>(CPJNSMTPConnection::AuthenticationMethod::AUTH_NONE)));

  //Decrypt the username
  BYTE* pEncryptedUsername = nullptr;
  UINT nEncryptedUsernameLength = 0;
  if (pApp->GetProfileBinary(sSection, _T("UsernameE"), &pEncryptedUsername, &nEncryptedUsernameLength))
  {
    DATA_BLOB dbIn{};
    dbIn.cbData = nEncryptedUsernameLength;
    dbIn.pbData = pEncryptedUsername;
    DATA_BLOB dbOut{};
    if (CryptUnprotectData(&dbIn, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_UI_FORBIDDEN, &dbOut))
    {
#pragma warning(suppress: 26490)
      m_sUsername = reinterpret_cast<LPCWSTR>(dbOut.pbData);
      LocalFree(dbOut.pbData);
    }
  }
#pragma warning(suppress: 26409 26401)
  delete [] pEncryptedUsername;

  //Decrypt the password
  BYTE* pEncryptedPassword = nullptr;
  UINT nEncryptedPasswordLength = 0;
  if (pApp->GetProfileBinary(sSection, _T("PasswordE"), &pEncryptedPassword, &nEncryptedPasswordLength))
  {
    DATA_BLOB dbIn{};
    dbIn.cbData = nEncryptedPasswordLength;
    dbIn.pbData = pEncryptedPassword;
    DATA_BLOB dbOut{};
    if (CryptUnprotectData(&dbIn, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_UI_FORBIDDEN, &dbOut))
    {
#pragma warning(suppress: 26490)
      m_sPassword = reinterpret_cast<LPCWSTR>(dbOut.pbData);
      LocalFree(dbOut.pbData);
    }
  }
#pragma warning(suppress: 26401 26409)
  delete [] pEncryptedPassword;

#pragma warning(suppress: 26472)
  m_bAutoDial = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("Autodial"), FALSE));
  m_sBoundIP = pApp->GetProfileString(sSection, _T("BoundIP"));
  m_sEncodingFriendly = pApp->GetProfileString(sSection, _T("EncodingFriendly"), _T("Western European (ISO)"));
  m_sEncodingCharset = pApp->GetProfileString(sSection, _T("EncodingCharset"), _T("iso-8859-1"));
  m_AddressHeaderEncoding = static_cast<CPJNSMTPAddress::ENCODING>(pApp->GetProfileInt(sSection, _T("AddressHeaderEncoding"), static_cast<int>(CPJNSMTPAddress::ENCODING::None)));
#pragma warning(suppress: 26472)
  m_bMime = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("Mime"), FALSE));
#pragma warning(suppress: 26472)
  m_bHTML = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("HTML"), FALSE));
  m_Priority = static_cast<CPJNSMTPMessage::PRIORITY>(pApp->GetProfileInt(sSection, _T("Priority"), static_cast<int>(CPJNSMTPMessage::PRIORITY::None)));
  m_ConnectionType = static_cast<CPJNSMTPConnection::ConnectionType>(pApp->GetProfileInt(sSection, _T("ConnectionType"), static_cast<int>(CPJNSMTPConnection::ConnectionType::PlainText)));
  m_SSLProtocol = static_cast<CPJNSMTPConnection::SSLProtocol>(pApp->GetProfileInt(sSection, _T("SSLProtocol"), static_cast<int>(CPJNSMTPConnection::SSLProtocol::OSDefault)));
#pragma warning(suppress: 26472)
  m_bDSN = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("DSN"), FALSE));
#pragma warning(suppress: 26472)
  m_bDSNSuccess = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("DSNSuccess"), FALSE));
#pragma warning(suppress: 26472)
  m_bDSNFailure = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("DSNFailure"), FALSE));
#pragma warning(suppress: 26472)
  m_bDSNDelay = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("DSNDelay"), FALSE));
#pragma warning(suppress: 26472)
  m_bDSNHeaders = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("DSNHeaders"), TRUE));
#pragma warning(suppress: 26472)
  m_bMDN = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("MDN"), FALSE));
#pragma warning(suppress: 26472)
  m_bInitialClientResponse = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("InitialClientResponse"), FALSE));
#pragma warning(suppress: 26472)
  m_bDoEHLO = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("DoEHLO"), TRUE));
#pragma warning(suppress: 26472)
  m_bUsePunyCode = static_cast<BOOL>(pApp->GetProfileInt(sSection, _T("UsePunyCode"), TRUE));
  m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

#pragma warning(suppress: 26429)
void CPJNSMTPAppDlg::DoDataExchange(CDataExchange* pDX)
{
  //Let the base class do its thing
  __super::DoDataExchange(pDX);

  DDX_Control(pDX, IDC_BODY, m_wndBody);
  DDX_Text(pDX, IDC_BCC, m_sBCC);
  DDX_Text(pDX, IDC_BODY, m_sBody);
  DDX_Text(pDX, IDC_CC, m_sCC);
  DDX_Text(pDX, IDC_FILE, m_sFile);
  DDX_Text(pDX, IDC_SUBJECT, m_sSubject);
  DDX_Text(pDX, IDC_TO, m_sTo);
  DDX_Check(pDX, IDC_DNS_LOOKUP, m_bDNSLookup);

  if (pDX->m_bSaveAndValidate)
  {
    if (m_sTo.IsEmpty())
    {
      AfxMessageBox(_T("Please specify a valid To address"), MB_OK | MB_ICONEXCLAMATION);
      pDX->PrepareEditCtrl(IDC_TO);
      pDX->Fail();
    }
  }
}

#pragma warning(suppress: 26433 26440)
BEGIN_MESSAGE_MAP(CPJNSMTPAppDlg, CDialog) //NOLINT(modernize-avoid-c-arrays)
  ON_WM_PAINT()
  ON_WM_QUERYDRAGICON() //NOLINT(clang-diagnostic-error)
  ON_BN_CLICKED(IDC_CONFIGURATION, &CPJNSMTPAppDlg::OnConfiguration)
  ON_BN_CLICKED(IDC_SEND, &CPJNSMTPAppDlg::OnSend)
  ON_BN_CLICKED(IDC_BROWSE_FILE, &CPJNSMTPAppDlg::OnBrowseFile)
  ON_WM_DESTROY()
  ON_BN_CLICKED(IDC_SEND_FROM_DISK, &CPJNSMTPAppDlg::OnSendFromDisk)
  ON_BN_CLICKED(IDC_SEND_FROM_DISK_WITH_UI, &CPJNSMTPAppDlg::OnSendFromDiskWithUI)
  ON_BN_CLICKED(IDC_SEND_FROM_MEMORY, &CPJNSMTPAppDlg::OnSendFromMemory)
  ON_BN_CLICKED(IDC_SEND_FROM_MEMORY_WITH_UI, &CPJNSMTPAppDlg::OnSendFromMemoryWithUI)
  ON_BN_CLICKED(IDC_SAVE_TO_DISK, &CPJNSMTPAppDlg::OnSaveToDisk)
END_MESSAGE_MAP()

BOOL CPJNSMTPAppDlg::OnInitDialog()
{
  //Let the parent class do its thing
  __super::OnInitDialog();

  //Set big icon
  SetIcon(m_hIcon, TRUE);

  //allow as much text as possible in the body edit control
  m_wndBody.SetLimitText(0);

  return TRUE;
}

#pragma warning(suppress: 26434)
void CPJNSMTPAppDlg::OnPaint()
{
  if (IsIconic())
  {
    CPaintDC dc(this); //device context for painting

#pragma warning(suppress: 26490)
    SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

    //Center icon in client rectangle
    const int cxIcon = GetSystemMetrics(SM_CXICON);
    const int cyIcon = GetSystemMetrics(SM_CYICON);
    CRect rect;
    GetClientRect(&rect);
    const int x = (rect.Width() - cxIcon + 1) / 2;
    const int y = (rect.Height() - cyIcon + 1) / 2;

    //Draw the icon
    dc.DrawIcon(x, y, m_hIcon);
  }
  else
    __super::OnPaint();
}

#pragma warning(suppress: 26434)
HCURSOR CPJNSMTPAppDlg::OnQueryDragIcon() noexcept
{
#pragma warning(suppress: 26473)
  return static_cast<HCURSOR>(m_hIcon);
}

void CPJNSMTPAppDlg::OnConfiguration()
{
  CPJNSMTPAppConfigurationDlg dlg;
  dlg.m_sAddress = m_sAddress;
  dlg.m_sHost = m_sHost;
  dlg.m_sName = m_sName;
  dlg.m_nPort = m_nPort;
  dlg.m_Auth = static_cast<DWORD>(m_Auth);
  dlg.m_sUsername = m_sUsername;
  dlg.m_sPassword = m_sPassword;
  dlg.m_bAutoDial = m_bAutoDial;
  dlg.m_sBoundIP = m_sBoundIP;
  dlg.m_sEncodingCharset = m_sEncodingCharset;
  dlg.m_AddressHeaderEncoding = static_cast<DWORD>(m_AddressHeaderEncoding);
  dlg.m_sEncodingFriendly = m_sEncodingFriendly;
  dlg.m_bMime = m_bMime;
  dlg.m_bHTML = m_bHTML;
  dlg.m_Priority = static_cast<DWORD>(m_Priority);
  dlg.m_ConnectionType = static_cast<DWORD>(m_ConnectionType);
  dlg.m_SSLProtocol = static_cast<DWORD>(m_SSLProtocol);
  dlg.m_bDSN = m_bDSN;
  dlg.m_bDSNSuccess = m_bDSNSuccess;
  dlg.m_bDSNFailure = m_bDSNFailure;
  dlg.m_bDSNDelay = m_bDSNDelay;
  dlg.m_bDSNHeaders = m_bDSNHeaders;
  dlg.m_bMDN = m_bMDN;
  dlg.m_bInitialClientResponse = m_bInitialClientResponse;
  dlg.m_bDoEHLO = m_bDoEHLO;
  dlg.m_bUsePunyCode = m_bUsePunyCode;
  if (dlg.DoModal() == IDOK)
  {
    m_sAddress = dlg.m_sAddress;
    m_sHost = dlg.m_sHost;
    m_sName = dlg.m_sName;
    m_nPort = dlg.m_nPort;
    m_Auth = static_cast<CPJNSMTPConnection::AuthenticationMethod>(dlg.m_Auth);
    m_sUsername = dlg.m_sUsername;
    m_sPassword = dlg.m_sPassword;
    m_bAutoDial = dlg.m_bAutoDial;
    m_sBoundIP = dlg.m_sBoundIP;
    m_sEncodingCharset = dlg.m_sEncodingCharset;
    m_AddressHeaderEncoding = static_cast<CPJNSMTPAddress::ENCODING>(dlg.m_AddressHeaderEncoding);
    m_sEncodingFriendly = dlg.m_sEncodingFriendly;
    m_bMime = dlg.m_bMime;
    m_bHTML = dlg.m_bHTML;
    m_Priority = static_cast<CPJNSMTPMessage::PRIORITY>(dlg.m_Priority);
    m_ConnectionType = static_cast<CPJNSMTPConnection::ConnectionType>(dlg.m_ConnectionType);
    m_SSLProtocol = static_cast<CPJNSMTPConnection::SSLProtocol>(dlg.m_SSLProtocol);
    m_bDSN = dlg.m_bDSN;
    m_bDSNSuccess = dlg.m_bDSNSuccess;
    m_bDSNFailure = dlg.m_bDSNFailure;
    m_bDSNDelay = dlg.m_bDSNDelay;
    m_bDSNHeaders = dlg.m_bDSNHeaders;
    m_bMDN = dlg.m_bMDN;
    m_bInitialClientResponse = dlg.m_bInitialClientResponse;
    m_bDoEHLO = dlg.m_bDoEHLO;
    m_bUsePunyCode = dlg.m_bUsePunyCode;
  }
}

std::unique_ptr<CPJNSMTPMessage> CPJNSMTPAppDlg::CreateMessage()
{
  //Create the message
  std::unique_ptr<CPJNSMTPMessage> pMessage(std::make_unique<CPJNSMTPMessage>());
  CPJNSMTPBodyPart attachment;

  //Set the mime flag
#pragma warning(suppress: 26434 26486)
  pMessage->SetMime(m_bMime);

  //Set the charset of the message and all attachments
#pragma warning(suppress: 26486)
  pMessage->SetCharset(m_sEncodingCharset);
  attachment.SetCharset(m_sEncodingCharset);

  //Set the address header encoding value
  pMessage->m_AddressHeaderEncoding = m_AddressHeaderEncoding;

  //Set the message priority
  pMessage->m_Priority = m_Priority;

  //Setup the all the recipient types for this message
  pMessage->ParseMultipleRecipients(m_sTo, pMessage->m_To);
  if (!m_sCC.IsEmpty())
    pMessage->ParseMultipleRecipients(m_sCC, pMessage->m_CC);
  if (!m_sBCC.IsEmpty())
    pMessage->ParseMultipleRecipients(m_sBCC, pMessage->m_BCC);
  if (!m_sSubject.IsEmpty())
    pMessage->m_sSubject = m_sSubject;
  if (!m_sBody.IsEmpty())
  {
    if (m_bHTML)
#pragma warning(suppress: 26486)
      pMessage->AddHTMLBody(m_sBody);
    else
#pragma warning(suppress: 26486)
      pMessage->AddTextBody(m_sBody);
  }

  //Add the attachment(s) if necessary
  if (!m_sFile.IsEmpty())
#pragma warning(suppress: 26486)
    pMessage->AddMultipleAttachments(m_sFile);

  //Setup the from address
  if (m_sName.IsEmpty()) 
    pMessage->m_From = CPJNSMTPAddress(m_sAddress);
  else 
    pMessage->m_From = CPJNSMTPAddress(m_sName, m_sAddress);

#ifndef _DEBUG
  pMessage->m_sXMailer.clear(); //Release builds do not include a XMailer header
#endif //#ifndef _DEBUG

  if (m_bDSN)
  {
    pMessage->m_DSNReturnType = m_bDSNHeaders ? CPJNSMTPMessage::DSN_RETURN_TYPE::HeadersOnly : CPJNSMTPMessage::DSN_RETURN_TYPE::FullEmail;
    pMessage->m_dwDSN = CPJNSMTPMessage::DSN_NOT_SPECIFIED;
    if (m_bDSNSuccess)
      pMessage->m_dwDSN |= CPJNSMTPMessage::DSN_SUCCESS;
    if (m_bDSNFailure)
      pMessage->m_dwDSN |= CPJNSMTPMessage::DSN_FAILURE;
    if (m_bDSNDelay)
      pMessage->m_dwDSN |= CPJNSMTPMessage::DSN_DELAY;
  }
  else
    pMessage->m_dwDSN = CPJNSMTPMessage::DSN_NOT_SPECIFIED;

  if (m_bMDN)
    pMessage->m_MessageDispositionEmailAddresses.emplace_back(m_sAddress);

#ifdef _DEBUG
  //Add one custom header (for test purpose)
  pMessage->m_CustomHeaders.emplace_back(_T("X-Program: CSTMPMessageTester"));

  //Try out the copy constructor and operator= methods
#pragma warning(suppress: 26486)
  CPJNSMTPMessage copyOfMessage(*pMessage);
#endif //#ifdef _DEBUG

  return pMessage;
}

void CPJNSMTPAppDlg::OnSend()
{
  if (UpdateData(TRUE))
  {
    if ((m_sHost.IsEmpty() && !m_bDNSLookup) || m_sAddress.IsEmpty()) 
    {
      AfxMessageBox(_T("You need to configure your settings first"));
      OnConfiguration();
    }
    else 
    {
      //Display a wait cursor
      CWaitCursor wc;

      std::unique_ptr<CPJNSMTPMessage> pMessage;
      try
      {
        //Create the message
        pMessage = CreateMessage();

        CPJNSMTPString sHost;
        bool bSend = true;
        if (m_bDNSLookup)
        {
          if (pMessage->m_To.size() == 0)
          {
            AfxMessageBox(_T("At least one receipient must be specified to use the DNS lookup option"));
            bSend = false;
          }
          else
          {
#pragma warning(suppress: 26446 26489)
            CPJNSMTPString sAddress(pMessage->m_To[0].m_sEmailAddress);
            const CPJNSMTPString::size_type nAt = sAddress.find(_T("@"));
            if (nAt == CPJNSMTPString::npos)
            {
              CString sMsg;
              sMsg.Format(_T("Unable to determine the domain for the email address \"%s\""), sAddress.c_str());
              AfxMessageBox(sMsg);
              bSend = false;
            }
            else
            {
              //We just pick the first MX record found, other implementations could ask the user
              //or automatically pick the lowest priority record
              CPJNSMTPString sDomain(sAddress.substr(nAt + 1));
              std::vector<CPJNSMTPString> servers;
              std::vector<WORD> priorities;
              if (!m_Connection.MXLookup(sDomain.c_str(), servers, priorities))
              {
                CString sMsg;
                sMsg.Format(_T("Unable to perform a DNS MX lookup for the domain \"%s\", Error Code:%u"), sDomain.c_str(), GetLastError());
                AfxMessageBox(sMsg);
                bSend = false;
              }
              else
              {
#pragma warning(suppress: 26446)
                sHost = servers[0];
              }
            }
          }
        }
        else
          sHost = m_sHost;

        //Instantiate the class which does the work for us
        if (bSend)
        {
        #ifdef _DEBUG
          //Turn out all the audit flags for debug builds to allows you to see all the data sent and received by the class for diagnostic purposes
          m_Connection.SetAuditFlags(SSLWrappers::CSSL::AUDIT_DATA | SSLWrappers::CSSL::AUDIT_EVENTS | SSLWrappers::CSSL::AUDIT_ERRORS);
        #endif //#ifdef _DEBUG

          //Disconnect if already connected
          if (m_Connection.IsConnected())
            m_Connection.Disconnect(false);

          //Auto connect to the internet?
          if (m_bAutoDial)
            m_Connection.ConnectToInternet();

          m_Connection.SetDoEHLO(m_bDoEHLO);
          m_Connection.SetUsePunyCode(m_bUsePunyCode);

          //Connect and send the message
          m_Connection.SetSSLProtocol(m_SSLProtocol);
          m_Connection.SetBindAddress(m_sBoundIP);
          m_Connection.SetInitialClientResponse(m_bInitialClientResponse);
          m_Connection.Connect(sHost.c_str(), m_Auth, m_sUsername, m_sPassword, m_nPort, m_ConnectionType);
#pragma warning(suppress: 26486)
          m_Connection.SendMessage(*pMessage);
          m_Connection.Disconnect(true);

          //Auto disconnect from the internet
          if (m_bAutoDial)
            m_Connection.CloseInternetConnection();
        }
      }
#pragma warning(suppress: 26496)
      catch(CPJNSMTPException& e)
      {
        //Display the error
        CString sMsg;
        CString sError;
        e.GetErrorMessage(sError.GetBuffer(4096), 4096);
        sError.ReleaseBuffer();
        sMsg.Format(_T("An error occurred sending the message, Error:%08X, %s, Last Response:%s"), e.m_hr, sError.GetString(), e.m_sLastResponse.c_str());
        AfxMessageBox(sMsg, MB_ICONSTOP);
      }
    }
  }
}

void CPJNSMTPAppDlg::OnBrowseFile()
{
  CDataExchange DX(this, TRUE);
#pragma warning(suppress: 26486)
  DDX_Text(&DX, IDC_FILE, m_sFile);
  CFileDialog dlg(TRUE, nullptr, nullptr, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("All Files (*.*)|*.*||"));
  if (dlg.DoModal() == IDOK)
  {
    CString sNewFile(dlg.GetPathName());
    if (m_sFile.GetLength())
    {
      m_sFile += _T(", ");
      m_sFile += sNewFile;
    }
    else
      m_sFile = sNewFile;

    //Update the UI
    CDataExchange DX2(this, FALSE);
#pragma warning(suppress: 26486)
    DDX_Text(&DX2, IDC_FILE, m_sFile);
  }
}

#pragma warning(suppress: 26434)
void CPJNSMTPAppDlg::OnDestroy()
{
  //Let the parent class do its thing
  __super::OnDestroy();

  //Save in the values from the registry
#pragma warning(suppress: 26429)
  CWinApp* pApp = AfxGetApp();
  CString sSection(_T("Settings"));
  pApp->WriteProfileInt(sSection, _T("Port"), m_nPort);
  pApp->WriteProfileString(sSection, _T("Address"), m_sAddress);
  pApp->WriteProfileString(sSection, _T("Host"), m_sHost);
  pApp->WriteProfileString(sSection, _T("Name"), m_sName);
  pApp->WriteProfileInt(sSection, _T("Authenticate"), static_cast<int>(m_Auth));
  
  //Encrypt the username
  CStringW sTempUsername(m_sUsername);
  DATA_BLOB dbIn{};
  const int nUsernameLength = sTempUsername.GetLength();
  dbIn.cbData = (nUsernameLength + 1) * sizeof(wchar_t);
#pragma warning(suppress: 26490)
  dbIn.pbData = reinterpret_cast<BYTE*>(sTempUsername.GetBuffer(nUsernameLength));
  DATA_BLOB dbOut{};
  if (CryptProtectData(&dbIn, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_UI_FORBIDDEN, &dbOut))
  {
    pApp->WriteProfileBinary(sSection, _T("UsernameE"), dbOut.pbData, dbOut.cbData);
    LocalFree(dbOut.pbData);
  }
  sTempUsername.ReleaseBuffer();
  
  //Encrypt the password
  CStringW sTempPassword(m_sPassword);
  const int nPasswordLength = sTempPassword.GetLength();
  dbIn.cbData = (nPasswordLength + 1) * sizeof(wchar_t);
#pragma warning(suppress: 26490)
  dbIn.pbData = reinterpret_cast<BYTE*>(sTempPassword.GetBuffer(nPasswordLength));
  if (CryptProtectData(&dbIn, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_UI_FORBIDDEN, &dbOut))
  {
    pApp->WriteProfileBinary(sSection, _T("PasswordE"), dbOut.pbData, dbOut.cbData);
    LocalFree(dbOut.pbData);
  }
  sTempPassword.ReleaseBuffer();

  pApp->WriteProfileInt(sSection, _T("Autodial"), m_bAutoDial);
  pApp->WriteProfileString(sSection, _T("BoundIP"), m_sBoundIP);
  pApp->WriteProfileString(sSection, _T("EncodingFriendly"), m_sEncodingFriendly);
  pApp->WriteProfileString(sSection, _T("EncodingCharset"), m_sEncodingCharset);
  pApp->WriteProfileInt(sSection, _T("AddressHeaderEncoding"), static_cast<int>(m_AddressHeaderEncoding));
  pApp->WriteProfileInt(sSection, _T("Mime"), m_bMime);
  pApp->WriteProfileInt(sSection, _T("HTML"), m_bHTML);
  pApp->WriteProfileInt(sSection, _T("Priority"), static_cast<int>(m_Priority));
  pApp->WriteProfileInt(sSection, _T("ConnectionType"), static_cast<int>(m_ConnectionType));
  pApp->WriteProfileInt(sSection, _T("SSLProtocol"), static_cast<int>(m_SSLProtocol));
  pApp->WriteProfileInt(sSection, _T("DSN"), m_bDSN);
  pApp->WriteProfileInt(sSection, _T("DSNSuccess"), m_bDSNSuccess);
  pApp->WriteProfileInt(sSection, _T("DSNFailure"), m_bDSNFailure);
  pApp->WriteProfileInt(sSection, _T("DSNDelay"), m_bDSNDelay);
  pApp->WriteProfileInt(sSection, _T("DSNHeaders"), m_bDSNHeaders);
  pApp->WriteProfileInt(sSection, _T("MDN"), m_bMDN);
  pApp->WriteProfileInt(sSection, _T("InitialClientResponse"), m_bInitialClientResponse);
  pApp->WriteProfileInt(sSection, _T("DoEHLO"), m_bDoEHLO);
  pApp->WriteProfileInt(sSection, _T("UsePunyCode"), m_bUsePunyCode);
}

void CPJNSMTPAppDlg::OnSendFromDiskWithUI()
{
  SendFromDisk(true);
}

void CPJNSMTPAppDlg::OnSendFromDisk()
{
  SendFromDisk(false);
}

void CPJNSMTPAppDlg::OnSendFromMemoryWithUI()
{
  SendFromMemory(true);
}

void CPJNSMTPAppDlg::OnSendFromMemory()
{
  SendFromMemory(false);
}

void CPJNSMTPAppDlg::SendFromDisk(bool bUI)
{
  if (UpdateData(TRUE))
  {
    if (m_sHost.IsEmpty() || m_sAddress.IsEmpty())
    {
      AfxMessageBox(_T("You need to configure your settings first"));
      OnConfiguration();
    }
    else
    {
      //Bring up a standard File Open dialog 
      CFileDialog dlg(TRUE, nullptr, nullptr, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("Email Files (*.eml)|*.eml|All Files (*.*)|*.*||"), nullptr);
      if (dlg.DoModal() == IDOK)
      {
        //Display a wait cursor
        CWaitCursor wait;

        //Instantiate the class which does the work for us
        CPJNSMTPConnectionEx connection;

        //Auto connect to the internet?
        if (m_bAutoDial)
          connection.ConnectToInternet();

        connection.SetDoEHLO(m_bDoEHLO);

        //Connect and send the message
        try
        {
          connection.SetBindAddress(m_sBoundIP);
          connection.Connect(m_sHost, m_Auth, m_sUsername, m_sPassword, m_nPort, m_ConnectionType);

          //Get the from field
          CPJNSMTPAddress from(m_sName, m_sAddress);

          //Get the recipients (for testing purposes we only bother looking at m_sTo)
          std::vector<CPJNSMTPAddress> Recipients;
          CPJNSMTPMessage::ParseMultipleRecipients(m_sTo, Recipients);

          //Send the message
          if (!bUI)
          {
            CPJNSMTPAsciiString sENVID;
#pragma warning(suppress: 26486)
            connection.SendMessage(dlg.GetPathName(), Recipients, from, sENVID);
          }
          else
          {
            //The progress dialog instance
            CSendingDlg dlg2;

            //Let the connection class know about the progress dialog
            connection.m_pProgressDlg = &dlg2;

            //Fill in the structure we need to pass to the worker function
            CThreadSendData tsd;
            tsd.m_pConnection = &connection;
            tsd.m_pProgressDlg = &dlg2;
            tsd.m_pRecipients = &Recipients;
            tsd.m_pFrom = &from;
            tsd.m_dwMainThreadID = GetCurrentThreadId();
            tsd.m_sFilename = dlg.GetPathName();

            //Create the worker thread which will do the actual sending
            CWinThread* pWorkerThread = AfxBeginThread(_SendingWorkerThread, &tsd, THREAD_PRIORITY_NORMAL, CREATE_SUSPENDED);
            pWorkerThread->m_bAutoDelete = FALSE;
            pWorkerThread->ResumeThread();

            //Bring up the progress dialog
            dlg2.DoModal();

            //Wait for the thread to close down
#pragma warning(suppress: 26486)
            WaitForSingleObject(pWorkerThread->m_hThread, INFINITE);
            delete pWorkerThread;

            if (!tsd.m_bSentOK)
            {
              CString sMsg;
              sMsg.Format(_T("An error occurred sending the message, Error:%08X, %s, Last Response:%s"), tsd.m_ExceptionHr, tsd.m_sExceptionMessage.GetString(), tsd.m_sLastResponse.GetString());
              AfxMessageBox(sMsg, MB_ICONSTOP);
            }
          }
        }
#pragma warning(suppress: 26496)
        catch(CPJNSMTPException& e)
        {
          //Display the error
          CString sError;
          e.GetErrorMessage(sError.GetBuffer(4096), 4096);
          sError.ReleaseBuffer();
          CString sMsg;
          sMsg.Format(_T("An error occurred sending the message, Error:%08X, %s, Last Response:%s"), e.m_hr, sError.GetString(), e.m_sLastResponse.c_str());
          AfxMessageBox(sMsg, MB_ICONSTOP);
        }

        //Auto disconnect from the internet
        if (m_bAutoDial)
          connection.CloseInternetConnection();
      }	
    }
  }
}

void CPJNSMTPAppDlg::OnSaveToDisk()
{
  if (UpdateData(TRUE))
  {
    //Bring up a standard File Save dialog 
    CFileDialog dlg(FALSE, nullptr, nullptr, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("Email Files (*.eml)|*.eml|All Files (*.*)|*.*||"), nullptr);
    if (dlg.DoModal() == IDOK)
    {
      //And save it to disk
#pragma warning(suppress: 26489)
      std::unique_ptr<CPJNSMTPMessage> pMessage(CreateMessage());
      try
      {
#pragma warning(suppress: 26486)
        pMessage->SaveToDisk(dlg.GetPathName());
      }
#pragma warning(suppress: 26496)
      catch(CPJNSMTPException& e)
      {
        CString sError;
        e.GetErrorMessage(sError.GetBuffer(4096), 4096);
        sError.ReleaseBuffer();
        CString sMsg;
        sMsg.Format(_T("Failed to save message to disk, Error:%08X, %s"), e.m_hr, sError.GetString());
        AfxMessageBox(sMsg, MB_ICONSTOP);
      }
    }
  }
}

UINT CPJNSMTPAppDlg::_SendingWorkerThread(LPVOID lpParam)
{
  //Validate our parameters
  auto pTSD = static_cast<CThreadSendData*>(lpParam);
#pragma warning(suppress: 26496)
  AFXASSUME(pTSD);
#pragma warning(suppress: 26496)
  AFXASSUME(pTSD->m_pConnection != nullptr);
#pragma warning(suppress: 26496)
  AFXASSUME(pTSD->m_pRecipients != nullptr);
#pragma warning(suppress: 26496)
  AFXASSUME(pTSD->m_pFrom != nullptr);
  ASSERT(pTSD->m_pProgressDlg != nullptr);

  AttachThreadInput(pTSD->m_dwMainThreadID, GetCurrentThreadId(), TRUE);

  //Wait until the dialog has been created before we continue
#pragma warning(suppress: 26489)
  WaitForSingleObject(pTSD->m_pProgressDlg->m_evtInitialized, INFINITE);

  //Do the actual sending
  try
  {
    CPJNSMTPAsciiString sENVID;
    if (pTSD->m_sFilename.GetLength())
#pragma warning(suppress: 26486 26489)
      pTSD->m_pConnection->SendMessage(pTSD->m_sFilename, *pTSD->m_pRecipients, *pTSD->m_pFrom, sENVID);
    else
#pragma warning(suppress: 26486 26489)
      pTSD->m_pConnection->SendMessage(pTSD->m_pbyMessage, pTSD->m_dwMessageSize, *pTSD->m_pRecipients, *pTSD->m_pFrom, sENVID);
    pTSD->m_bSentOK = !pTSD->m_pProgressDlg->AbortFlag();
  }
#pragma warning(suppress: 26496)
  catch(CPJNSMTPException& e)
  {
    pTSD->m_ExceptionHr = e.m_hr;
    CString sError;
    e.GetErrorMessage(sError.GetBuffer(4096), 4096);
    sError.ReleaseBuffer();
    pTSD->m_sExceptionMessage = sError;
    pTSD->m_sLastResponse = e.m_sLastResponse.c_str();
    pTSD->m_bSentOK = false;
  }

  pTSD->m_pProgressDlg->SetSafeToClose();
  pTSD->m_pProgressDlg->PostMessage(WM_CLOSE);

  return 0L;
}

void CPJNSMTPAppDlg::SendFromMemory(bool bUI)
{
  if (UpdateData(TRUE))
  {
    if (m_sHost.IsEmpty() || m_sAddress.IsEmpty())
    {
      AfxMessageBox(_T("You need to configure your settings first"));
      OnConfiguration();
    }
    else
    {
      //Bring up a standard File Open dialog 
      CFileDialog dlg(TRUE, nullptr, nullptr, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, _T("Email Files (*.eml)|*.eml|All Files (*.*)|*.*||"), nullptr);
      if (dlg.DoModal() == IDOK)
      {
        //Display a wait cursor
        CWaitCursor wait;

        //Open up the file we want to send
        ATL::CAtlFile file;
        CString sFile(dlg.GetPathName());
        HRESULT hr = file.Create(sFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN);
        if (FAILED(hr))
        {
          CString sMsg;
          sMsg.Format(_T("Failed to open the file:\'%s\', Error:%08X"), sFile.GetString(), hr);
          AfxMessageBox(sMsg);
          return;
        }

        //Get the length of the file (we only support sending files less than 4GB!)
        ULONGLONG nFileSize = 0;
        hr = file.GetSize(nFileSize);
        if (FAILED(hr))
        {
          CString sMsg;
          sMsg.Format(_T("Failed to obtain the file length for the file:\'%s\', Error:%08X"), sFile.GetString(), hr);
          AfxMessageBox(sMsg);
          return;
        }
        if (nFileSize >= ULONG_MAX)
        {
          CString sMsg;
          sMsg.Format(_T("The file \'%s\' is too large to send"), sFile.GetString());
          AfxMessageBox(sMsg);
          return;
        }
        if (nFileSize == 0)
        {
          CString sMsg;
          sMsg.Format(_T("The file \'%s\' does not contain any data to send"), sFile.GetString());
          AfxMessageBox(sMsg);
          return;
        }

        //Allocate some memory to read in the file
        std::vector<BYTE> byMessage;
#pragma warning(suppress: 26472)
        byMessage.resize(static_cast<size_t>(nFileSize));

        //Read in the file contents
#pragma warning(suppress: 26472)
        hr = file.Read(byMessage.data(), static_cast<DWORD>(nFileSize));
        if (FAILED(hr))
        {
          CString sMsg;
          sMsg.Format(_T("Failed to read from the file:%s, Error:%08X"), sFile.GetString(), hr);
          AfxMessageBox(sMsg);
          return;
        }

        //Instantiate the class which does the work for us
        CPJNSMTPConnectionEx connection;

        //Auto connect to the internet?
        if (m_bAutoDial)
          connection.ConnectToInternet();

        connection.SetDoEHLO(m_bDoEHLO);

        //Connect and send the message
        try
        {
          connection.SetBindAddress(m_sBoundIP);
          connection.Connect(m_sHost, m_Auth, m_sUsername, m_sPassword, m_nPort, m_ConnectionType);

          //Get the from field
          CPJNSMTPAddress from(m_sName, m_sAddress);

          //Get the recipients (for testing purposes we only bother looking at m_sTo)
          std::vector<CPJNSMTPAddress> Recipients;
          CPJNSMTPMessage::ParseMultipleRecipients(m_sTo, Recipients);

          //Send the message
          if (!bUI)
          {
            CPJNSMTPAsciiString sENVID;
#pragma warning(suppress: 26472 26486)
            connection.SendMessage(byMessage.data(), static_cast<DWORD>(nFileSize), Recipients, from, sENVID);
          }
          else
          {
            //The progress dialog instance
            CSendingDlg dlg2;

            //Let the connection class know about the progress dialog
            connection.m_pProgressDlg = &dlg2;

            //Fill in the structure we need to pass to the worker function
            CThreadSendData tsd;
            tsd.m_pConnection = &connection;
            tsd.m_pProgressDlg = &dlg2;
            tsd.m_pRecipients = &Recipients;
            tsd.m_pFrom = &from;
            tsd.m_dwMainThreadID = GetCurrentThreadId();
            tsd.m_pbyMessage = byMessage.data();
#pragma warning(suppress: 26472)
            tsd.m_dwMessageSize = static_cast<DWORD>(nFileSize);

            //Create the worker thread which will do the actual sending
            CWinThread* pWorkerThread = AfxBeginThread(_SendingWorkerThread, &tsd, THREAD_PRIORITY_NORMAL, CREATE_SUSPENDED);
            pWorkerThread->m_bAutoDelete = FALSE;
            pWorkerThread->ResumeThread();

            //Bring up the progress dialog
            dlg2.DoModal();

            //Wait for the thread to close down
#pragma warning(suppress: 26486)
            WaitForSingleObject(pWorkerThread->m_hThread, INFINITE);
            delete pWorkerThread;

            if (!tsd.m_bSentOK)
            {
              CString sMsg;
              sMsg.Format(_T("An error occurred sending the message, Error:%08X, %s, Last Response:%s"), tsd.m_ExceptionHr, tsd.m_sExceptionMessage.GetString(), tsd.m_sLastResponse.GetString());
              AfxMessageBox(sMsg, MB_ICONSTOP);
            }
          }
        }
#pragma warning(suppress: 26496)
        catch (CPJNSMTPException& e)
        {
          //Display the error
          CString sError;
          e.GetErrorMessage(sError.GetBuffer(4096), 4096);
          sError.ReleaseBuffer();
          CString sMsg;
          sMsg.Format(_T("An error occurred sending the message, Error:%08X, %s, Last Response:%s"), e.m_hr, sError.GetString(), e.m_sLastResponse.c_str());
          AfxMessageBox(sMsg, MB_ICONSTOP);
        }

        //Auto disconnect from the internet
        if (m_bAutoDial)
          connection.CloseInternetConnection();
      }
    }
  }
}
