/*
Module : PJNSmtp.cpp
Purpose: Implementation for a set of C++ classes to encapsulate the SMTP protocol
Created: PJN / 22-05-1998
History: PJN / 15-06-1998 1. Fixed the case where a single dot occurs on its own
                          in the body of a message
                          2. Class now supports Reply-To Header Field
                          3. Class now supports file attachments
         PJN / 18-06-1998 1. Fixed a memory overwrite problem which was occurring 
                         with the buffer used for encoding base64 attachments
         PJN / 27-06-1998 1. The case where a line begins with a "." but contains
                          other text is now also catered for. See RFC821, Section 4.5.2
                          for further details.
                          2. m_sBody in CPJNSMTPMessage has now been made protected.
                          Client applications now should call AddBody instead. This
                          ensures that FixSingleDot is only called once even if the 
                          same message is sent a number of times.
                          3. Fixed a number of problems with how the MIME boundaries
                          were defined and sent.
                          4. Got rid of an unreferenced formal parameter 
                          compiler warning when doing a release build
         PJN / 11-09-1998 1. VC 5 project file is now provided
                          2. Attachment array which the message class contains now uses
                          references instead of pointers.
                          3. Now uses Sleep(0) to yield our time slice instead of Sleep(100),
                          this is the preferred way of writting polling style code in Win32
                          without serverly impacting performance.
                          4. All Trace statements now display the value as returned from
                          GetLastError
                          5. A number of extra asserts have been added
                          6. A AddMultipleRecipients function has been added which supports added a 
                          number of recipients at one time from a single string
                          7. Extra trace statements have been added to help in debugging
         PJN / 12-09-1998 1. Removed a couple of unreferenced variable compiler warnings when code
                          was compiled with Visual C++ 6.0
                          2. Fixed a major bug which was causing an ASSERT when the CSMTPAttachment
                          destructor was being called in the InitInstance of the sample app. 
                          This was inadvertingly introduced for the 1.2 release. The fix is to revert 
                          fix 2. as done on 11-09-1998. This will also help to reduce the number of 
                          attachment images kept in memory at one time.
         PJN / 18-01-1999 1. Full CC & BCC support has been added to the classes
         PJN / 22-02-1999 1. Addition of a Get and SetTitle function which allows a files attachment 
                          title to be different that the original filename
                          2. AddMultipleRecipients now ignores addresses if they are empty.
                          3. Improved the reading of responses back from the server by implementing
                          a growable receive buffer
                          4. timeout is now 60 seconds when building for debug
         PJN / 25-03-1999 1. Now sleeps for 250 ms instead of yielding the time slice. This helps 
                          reduce CPU usage when waiting for data to arrive in the socket
         PJN / 14-05-1999 1. Fixed a bug with the way the code generates time zone fields in the Date headers.
         PJN / 10-09-1999 1. Improved CPJNSMTPMessage::GetHeader to include mime field even when no attachments
                          are included.
         PJN / 16-02-2000 1. Fixed a problem which was occuring when code was compiled with VC++ 6.0.
         PJN / 19-03-2000 1. Fixed a problem in GetHeader on Non-English Windows machines
                          2. Now ships with a VC 5 workspace. I accidentally shipped a VC 6 version in one of the previous versions of the code.
                          3. Fixed a number of UNICODE problems
                          4. Updated the sample app to deliberately assert before connecting to the author's SMTP server.
         PJN / 28-03-2000 1. Set the release mode timeout to be 10 seconds. 2 seconds was causing problems for slow dial
                          up networking connections.
         PJN / 07-05-2000 1. Addition of some ASSERT's in CPJNSMTPSocket::Connect
         PP  / 16-06-2000 The following modifications were done by Puneet Pawaia
                          1. Removed the base64 encoder from this file
                          2. Added the base64 encoder/decoder implementation in a separate 
                          file. This was done because base64 decoding was not part of 
                          the previous implementation
                          3. Added support for ESMTP connection. The class now attempts to 
                          authenticate the user on the ESMTP server using the username and
                          passwords supplied. For this connect now takes the username and 
                          passwords as parameters. These can be null in which case ESMTP 
                          authentication is not attempted
                          4. This class can now handle AUTH LOGIN and AUTH LOGIN PLAIN authentication
                          schemes on 
         PP  / 19-06-2000 The following modifications were done by Puneet Pawaia
                          1. Added the files md5.* containing the MD5 digest generation code
                          after modifications so that it compiles with VC++ 6
                          2. Added the CRAM-MD5 login procedure.
         PJN / 10-07-2000 1. Fixed a problem with sending attachments > 1K in size
                          2. Changed the parameters to CPJNSMTPConnection::Connect
         PJN / 30-07-2000 1. Fixed a bug in AuthLogin which was transmitting the username and password
                          with an extra "=" which was causing the login to failure. Thanks to Victor Vogelpoel for
                          finding this.
         PJN / 05-09-2000 1. Added a CSMTP_NORSA preprocessor macro to allow the CPJNSMTPConnection code to be compiled
                          without the dependence on the RSA code.
         PJN / 28-12-2000 1. Removed an unused variable from ConnectESMTP.
                          2. Allowed the hostname as sent in the HELO command to be specified at run time 
                          in addition to using the hostname of the client machine
                          3. Fixed a problem where high ascii characters were not being properly encoded in
                          the quoted-printable version of the body sent.
                          4. Added support for user definable charset's for the message body.
                          5. Mime boundaries are now always sent irrespective if whether attachments are included or
                          not. This is required as the body is using quoted-printable.
                          6. Fixed a bug in sendLines which was causing small message bodies to be sent incorrectly
                          7. Now fully supports custom headers in the SMTP message
                          8. Fixed a copy and paste bug where the default port for the SMTP socket class was 110.
                          9. You can now specify the address on which the socket is bound. This enables the programmer
                          to decide on which NIC data should be sent from. This is especially useful on a machine
                          with multiple IP addresses.
                          10. Addition of functions in the SMTP connection class to auto dial and auto disconnect to 
                          the Internet if you so desire.
                          11. Sample app has been improved to allow Auto Dial and binding to IP addresses to be configured.
         PJN / 26-02-2001 1. Charset now defaults to ISO 8859-1 instead of us-ascii
                          2. Removed a number of unreferrenced variables from the sample app.
                          3. Headers are now encoded if they contain non ascii characters.
                          4. Fixed a bug in getLine, Thanks to Lev Evert for spotting this one.
                          5. Made the charset value a member of the message class instead of the connection class
                          6. Sample app now fully supports specifying the charset of the message
                          7. Added a AddMultipleAttachments method to CPJNSMTPMessage
                          8. Attachments can now be copied to each other via new methods in CSMTPAttachment
                          9. Message class now contains copies of the attachments instead of pointers to them
                          10. Sample app now allows multiple attachments to be added
                          11. Removed an unnecessary assert in QuotedPrintableEncode
                          12. Added a Mime flag to the CPJNSMTPMessage class which allows you to decide whether or not a message 
                          should be sent as MIME. Note that if you have attachments, then mime is assumed.
                          13. CSMTPAttachment class has now become CPJNSMTPBodyPart in anticipation of full support for MIME and 
                          MHTML email support
                          14. Updated copright message in source code and documentation
                          15. Fixed a bug in GetHeader related to _tsetlocale usage. Thanks to Sean McKinnon for spotting this
                          problem.
                          16. Fixed a bug in SendLines when sending small attachments. Thanks to Deng Tao for spotting this
                          problem.
                          17. Removed the need for SendLines function entirely.
                          18. Now fully supports HTML email (aka MHTML)
                          19. Updated copyright messages in code and in documentation
         PJN / 17-06-2001 1. Fixed a bug in CPJNSMTPMessage::HeaderEncode where spaces were not being interpreted correctly. Thanks
                          to Jim Alberico for spotting this.
                          2. Fixed 2 issues with ReadResponse both having to do with multi-line responses. Thanks to Chris Hanson 
                          for this update.
         PJN / 25-06-2001 1. Code now links in Winsock and RPCRT40 automatically. This avoids client code having to specify it in 
                          their linker settings. Thanks to Malte and Phillip for spotting this issue.
                          2. Updated sample code in documentation. Thanks to Phillip for spotting this.
                          3. Improved the code in CPJNSMTPBodyPart::SetText to ensure lines are correctly wrapped. Thanks to 
                          Thomas Moser for this fix.
         PJN / 01-07-2001 1. Modified QuotedPrintableEncode to prevent the code to enter in an infinite loop due to a long word i.e. 
                          bigger than SMTP_MAXLINE, in this case, the word is breaked. Thanks to Manuel Gustavo Saraiva for this fix.
                          2. Provided a new protected variable in CPJNSMTPBodyPart called m_bQuotedPrintable to bypass the 
                          QuotedPrintableEncode function in cases that we don't want that kind of correction. Again thanks to 
                          Manuel Gustavo Saraiva for this fix.
         PJN / 15-07-2001 1. Improved the error handling in the function CPJNSMTPMessage::AddMultipleAttachments. In addition the 
                          return value has been changed from BOOL to int
         PJN / 11-08-2001 1. Fixed a bug in QuotedPrintableEncode which was wrapping encoding characters across multiple lines. 
                          Thanks to Roy He for spotting this.
                          2. Provided a "SendMessage" method which sends a email directly from disk. This allows you 
                          to construct your own emails and the use the class just to do the sending. This function also has the 
                          advantage that it efficiently uses memory and reports progress.
                          3. Provided support for progress notification and cancelling via the "OnSendProgress" virtual method.
         PJN / 29-09-2001 1. Fixed a bug in ReadResponse which occured when you disconnected via Dial-Up Networking
                          while a connection was active. This was originally spotted in my POP3 class.
                          2. Fixed a problem in CPJNSMTPBodyPart::GetHeader which was always including the "quoted-printable" even when 
                          m_bQuotedPrintable for that body part was FALSE. Thanks to "jason" for spotting this.
         PJN / 12-10-2001 1. Fixed a problem where GetBody was reporting the size as 1 bigger than it should have been. Thanks
                          to "c f" for spotting this problem.
                          2. Fixed a bug in the TRACE statements when a socket connection cannot be made.
                          3. The sample app now displays a filter of "All Files" when selecting attachments to send
                          4. Fixed a problem sending 0 byte attachments. Thanks to Deng Tao for spotting this problem.
         PJN / 11-01-2002 1. Now includes a method to send a message directly from memory. Thanks to Tom Allebrandi for this
                          suggestion.
                          2. Change function name "IsReadible" to be "IsReadable". I was never very good at English!.
                          3. Fixed a bug in CPJNSMTPBodyPart::QuotedPrintableEncode. If a line was exactly 76 characters long plus 
                          \r\n it produced an invalid soft linebreak of "\r=\r\n\n". Thanks to Gerald Egert for spotting this problem.
         PJN / 29-07-2002 1. Fixed an access violation in CPJNSMTPBodyPart::QuotedPrintableEncode. Thanks to Fergus Gallagher for 
                          spotting this problem.
                          2. Fixed a problem where in very rare cases, the QuotedPrintableEncode function produces a single dot in a 
                          line, when inserting the "=" to avoid the mail server's maxline limit. I inserted a call to FixSingleDot 
                          after calling the QuotedPrintableEncode function in GetBody. Thanks to Andreas Kappler for spotting this
                          problem.
                          3. Fixed an issue in CPJNSMTPBodyPart::GetHeader where to ensure some mail clients can correctly handle
                          body parts and attachments which have a filename with spaces in it. Thanks to Andreas kappler for spotting
                          this problem.
                          4. QP encoding is now only used when you specify MIME. This fixes a bug as reported by David Terracino.
                          5. Removed now unused "API Reference" link in HTML file supported the code.
         PJN / 10-08-2002 1. Fixed a number of uncaught file exceptions in CPJNSMTPBodyPart::GetBody, CPJNSMTPMessage::SaveToDisk, and 
                          CPJNSMTPConnection::SendMessage. Thanks to John Allan Miller for reporting this problem.
                          2. The CPJNSMTPConnection::SendMessage version of the method which sends a file directly from disk, now fails if the
                          file is empty.
                          3. Improved the sample app by displaying a wait cursor while a message from file is being sent
                          4. Improved the sample app by warning the user if mail settings are missing and then bringing up the configuration
                          dialog.
         PJN / 20-09-2002 1. Fixed a problem where the code "Coder.EncodedMessage" was not being converted from an ASCII string to a UNICODE 
                          string in calls to CString::Format. This was occurring when sending the username and password for "AUTH LOGIN" support
                          in addition to sending the "username digest" for "AUTH CRAM-MD5" support. Thanks to Serhiy Pavlov for reporting this
                          problem.
                          2. Removed a number of calls to CString::Format and instead replaced with string literal CString constructors instead.
         PJN / 03-10-2002 1. Quoted printable encoding didn't work properly in UNICODE. (invalid conversion from TCHAR to BYTE). Thanks to
                          Serhiy Pavlov for reporting this problem.
                          2. Subject encoding didn't work properly in UNICODE. (invalid conversion from TCHAR to BYTE). Thanks to Serhiy Pavlov
                          for reporting this problem.
                          3. It didn't insert "charset" tag into root header for plain text messages (now it includes "charset" into plain text 
                          messages too). Thanks to Serhiy Pavlov for reporting this problem.
         PJN / 04-10-2002 1. Fixed an issue where the header / body separator was not being sent correctly for mails with attachments or when
                          the message is MIME encoded. Thanks to Serhiy Pavlov for reporting this problem.
                          2. Fixed a bug in QuotedPrintableEncode and HeaderEncode which was causing the errors. Thanks to Antonio Maiorano 
                          for reporting this problem.
                          3. Fixed an issue with an additional line separator being sent between the header and body of emails. This was only
                          evident in mail clients if a non mime email without attachments was sent.
         PJN / 11-12-2002 1. Review all TRACE statements for correctness
                          2. Provided a virtual OnError method which gets called with error information 
         PJN / 07-02-2003 1. Addition of a "BOOL bGracefully" argument to Disconnect so that when an application cancels the sending of a 
                          message you can pass FALSE and close the socket without properly terminating the SMTP conversation. Thanks to
                          "nabocorp" for this nice addition.
         PJN / 19-03-2003 1. Addition of copy constructors and operator= to CPJNSMTPMessage class. Thanks to Alexey Vasilyev for this suggestion.
         PJN / 13-04-2003 1. Fixed a bug in the handling of EHLO responses. Thanks to "Senior" for the bug report and the fix.
         PJN / 16-04-2003 1. Enhanced the CPJNSMTPAddress constructor to parse out the email address and friendly name rather than assume
                          it is an email address.
                          2. Reworked the syntax of the CPJNSMTPMessage::ParseMultipleRecipients method. Also now internally this function
                          uses the new CPJNSMTPAddress constructor
         PJN / 19-04-2003 1. Fixed a bug in the CPJNSMTPAddress constructor where I was mixing up the friendly name in the "<>" separators,
                          when it should have been the email address. 
         PJN / 04-05-2003 1. Fixed an issue where the class doesn't convert the mail body and subject to the wanted encoding but rather changes 
                          the encoding of the email in the email header. Since the issue of supporting several languages is a complicated one 
                          I've decided that we could settle for sending all CPJNSMTPConnection emails in UTF-8. I've added some conversion functions 
                          to the class that - at this point - always converts the body and subject of the email to UTF-8. A big thanks to Itamar 
                          Kerbel for this nice addition.
                          2. Moved code and sample app to VC 6.
         PJN / 05-05-2003 1. Reworked the way UTF8 encoding is now done. What you should do if you want to use UTF-8 encoding is set the charset
                          to "UTF-8" and use either QP or base 64 encoding.
                          2. Reworked the automatic encoding of the subject line to use the settings as taken from the root SMTP body part
                          3. Only the correct headers according to the MIME RFC are now encoded.
                          4. QP encoding is the encoding mechanism now always used for headers.
                          5. Headers are now only encoded if required to be encoded.
         PJN / 12-05-2003 1. Fixed a bug where the X-Mailer header was being sent incorrectly. Thanks to Paolo Vernazza for reporting this problem.
                          2. Addition of X-Mailer header is now optional. In addition sample app now does not send the X-Mailer header.
                          3. The sample app now does not send the Reply-To header.
         PJN / 18-08-2003 1. Modified the return value from the ConnectToInternet method. Instead of it being a boolean, it is now an enum. This
                          allows client code to differentiate between two conditions that it couldn't previously, namely when an internet connection
                          already existed or if a new connection (presumably a dial-up connection was made). This allows client code to then
                          disconnect if a new connection was required. Thanks to Pete Arends for this nice addition.
         PJN / 05-10-2003 1. Reworked the CPJNSMTPConnection::ReadResponse method to use the timeout provided by IsReadable rather than calling sleep.
                          Thanks to Clarke Brunt for reporting this issue.
         PJN / 03-11-2003 1. Simplified the code in CPJNSMTPConnection::ReadResponse. Thanks to Clarke Brunt for reporting this issue.
         PJN / 03-12-2003 1. Made code which checks the Login responses which contain "Username" and "Password" case insensitive. Thanks to 
                          Zhang xiao Pan for reporting this problem.
         PJN / 11-12-2003 1. Fixed an unreferrenced variable in CPJNSMTPConnection::OnError as reported by VC.Net 2003
                          2. DEBUG_NEW macro is now only used on VC 6 and not on VC 7 or VC 7.1. This avoids a problem on these compilers
                          where a conflict exists between it and the STL header files. Thanks to Alex Evans for reporting this problem.
         PJN / 31-01-2004 1. Fixed a bug in CPJNSMTPBodyPart::GetBody where the size of the body part was incorrectly calculating the size if the
                          encoded size was an exact multiple of 76. Thanks to Kurt Emanuelson and Enea Mansutti for reporting this problem.
         PJN / 07-02-2004 1. Fixed a bug in CPJNSMTPBodyPart::SetText where the code would enter an endless loop in the Replace function. It has now 
                          been replaced with CString::Replace. This now means that the class will not now compile on VC 5. Thanks to Johannes Philipp 
                          Grohs for reporting this problem.
                          2. Fixed a number of warnings when the code is compiled with VC.Net 2003. Thanks to Edward Livingston for reporting
                          this issue.
         PJN / 18-02-2004 1. You can now optionally set the priority of an email thro the variable CPJNSMTPMessage::m_Priority. Thanks to Edward 
                          Livingston for suggesting this addition.
         PJN / 04-03-2004 1. To avoid conflicts with the ATL Server class of the same name, namely "CSMTPConnection", the class is now called 
                          "CPJNSMTPConnection". To provide for easy upgrading of code, "CSMTPConnection" is now defined to be "CPJSMTPConnection" 
                          if the code detects that the ATL Server SMTP class is not included. Thanks to Ken Jones for reporting this issue.
         PJN / 13-03-2004 1. Fixed a problem where the CPJNSMTPBodyPart::m_dwMaxAttachmentSize value was not being copied in the CPJNSMTPBodyPart::operator=
                          method. Thanks to Gerald Egert for reporting this problem and the fix.
         PJN / 05-06-2004 1. Fixed a bug in CSMTPConnection::ReadResponse, where the wrong parameters were being null terminated. Thanks to "caowen" 
                          for this update.
                          2. Updated the CSMTPConnection::ReadResponse function to handle multiline responses in all cases. Thanks to Thomas Liebethal
                          for reporting this issue.
         PJN / 07-06-2004 1. Fixed a potential buffer overflow issue in CPJNSMTPConnection::ReadResponse when certain multi line responses are received
         PJN / 30-09-2004 1. Fixed a parsing issue in CPJNSMTPConnection::ReadResponse when multi line responses are read in as multiple packets. 
                          Thanks to Mark Smith for reporting this problem.
                          2. Reworked the code which supports the various authentication mechanisms to support the correct terms. What was called 
                          "AUTH LOGIN PLAIN" support now more correctly uses the term "AUTH PLAIN". The names of the functions and enums have now 
                          also been reworked.
                          3. Did a review of the sample app code provided with the class so that the name of the modules, projects, exe etc is now
                          "PJNSMTPApp". Also reworked the name of some helper classes as well as the module name which supports the main class.
                          4. Reworked CPJNSMTPConnection::AuthLoginPlain (now called AuthPlain) to correctly handle the case where an invalid
                          response is received when expecting the username: response. Thanks to Mark Smith for reporting this problem.
         PJN / 23-12-2004 "Name" field in Content-Type headers is now quoted just like the filename field. Thanks to Mark Smith for reporting this
                          issue in conjunction with the mail client Eudora.
         PJN / 23-01-2005 1. All classes now uses exceptions to indicate errors. This means the whole area of handling errors in the code a whole lot
                          simpler. For example the OnError mechanism is gone along with all the string literals in the code. The actual code itself
                          is a whole lot simpler also. You should carefully review your code as a lot of the return values from methods (especially
                          CPJNSMTPConnection) are now void and will throw CSMTPException's. Thanks to Mark Smith for prompting this update.
                          2. General tidy up of the code following the testing of the new exception based code.
         PJN / 06-04-2005 1. Addition of PJNSMTP_EXT_CLASS define to the classes to allow the classes to be easily incorporated into extension DLLs.
                          Thanks to Arnaud Faucher for suggesting this addition.
                          2. Now support NTLM authentication. Thanks to Arnaud Faucher for this nice addition. NTLM Authentication is provided by
                          a new reusable class called "CNTLMClientAuth" in PJNNTLMAuth.cpp/h".
                          3. Fixed a bug in the sample app in the persistence of the Authentication setting. Thanks to Arnaud Faucher for reporting
                          this issue.
         PJN / 26-03-2005 1. Fixed compile problems with the code when the Force Conformance In For Loop Scope compile setting is used in 
                          Visual Studio .NET 2003. Thanks to Alexey Kuznetsov for reporting this problem.
                          2. Fixed compile problems when the code is compiled using the Detect 64-bit Portability Issues setting in Visual Studio
                          .NET 2003.
         PJN / 18-04-2005 1. Addition of a simple IsConnected() function inline with the author's CPop3Connection class. Thanks to Alexey Kuznetsov
                          for prompting this addition.
                          2. Addition of a MXLookup function which provides for convenient DNS lookups of MX records (the IP addresses of SMTP mail 
                          servers for a specific host domain). Internally this new function uses the new DNS functions which are provided with 
                          Windows 2000 or later. To ensure that the code continues to work correctly on earlier versions of Windows, the function 
                          pointers for the required functions are constructed at runtime using GetProcAddress. You can use this function to 
                          discover the IP address of the mail servers responsible for a specific domain. This allows you to deliver email directly 
                          to a domain rather than through a third party mail server. Thanks to Hans Dietrich for suggesting this nice addition.
         PJN / 23-04-2005 1. The code now uses a MS Crypto API implementation of the MD5 HMAC algorithm instead of custom code based on the RSA 
                          MD5 code. This means that there is no need for the RSA MD5.cpp, MD5.h and glob-md5.h to be included in client applications. 
                          In addition the build configurations for excluding the RSA MD5 code has been removed from the sample app. The new classes to 
                          perform the MD5 hashing are contained in PJNMD5.h and are generic enough to be included in client applications in their
                          own right.
         PJN / 01-05-2005 1. Now uses the author'ss CWSocket sockets wrapper class.
                          2. Added support for connecting via Socks4, Socks5 and HTTP proxies 
                          3. The last parameter to Connect has now been removed and is configured via an accessor / mutator pair Get/SetBoundAddress.
                          4. CSMTPException class is now called CPJNSMTPException.
                          5. Class now uses the Base64 class as provided with CWSocket. This means that the modules Base64Coder.cpp/h are no longer
                          distributed or required and should be removed from any projects which use the updated SMTP classes.
                          6. Fixed a bug in SaveToDisk method whereby existing files were not being truncated prior to saving the email message.
                          7. All calls to allocate heap memory now generate a CPJNSMTPException if the allocation fails.
                          8. Addition of a MXLookupAvailable() function so that client code can know a priori if DNS MX lookups are supported.
                          9. Sample app now allows you to use the MXLookup method to determine the mail server to use based on the first email address
                          you provide in the To field.
         PJN / 03-05-2005 1. Fixed a number of warnings when the code is compiled in Visual Studio .NET 2003. Thanks to Nigel Delaforce for
                          reporting these issues.
         PJN / 24-07-2005 1. Now includes support for parsing "(...)" comments in addresses. Thanks to Alexey Kuznetsov for this nice addition.
                          2. Fixed an issue where the mail header could contain a "Content-Transfer-Encoding" header. This header should only be 
                          applied to headers of MIME body parts rather than the mail header itself. Thanks to Jeroen van der Laarse, Terence Dwyer
                          and Bostjan Erzen for reporting this issue and the required fix.
                          3. Now calling both AddTextBody and AddHTMLBody both set the root body part MIME type to multipart/mixed. Previously
                          the code comments said one thing and did another (set it to "multipart/related"). Thanks to Bostjan Erzen for reporting
                          this issue.
         PJN / 14-08-2005 1. Minor update to include comments next to various locations in the code to inform users what to do if they get compile
                          errors as a result of compiling the code without the Platform SDK being installed. Thanks to Gudjon Adalsteinsson for 
                          prompting this update.
                          2. Fixed an issue when the code calls the function DnsQuery when the code is compiled for UNICODE. This was the result of 
                          a documentation error in the Platform SDK which incorrectly says that DnsQuery_W takes a ASCII string when in actual fact 
                          it takes a UNICODE string. Thanks to John Oustalet III for reporting this issue.
         PJN / 05-09-2005 1. Fixed an issue in MXLookup method where memory was not being freed correctly in the call to DnsRecordListFree. Thanks 
                          to Raimund Mettendorf for reporting this bug.
                          2. Fixed an issue where lines which had a single dot i.e. "." did not get mailed correctly. Thanks to Raimund Mettendorf
                          for reporting this issue.
                          3. Removed the call to FixSingleDot from SetText method as it is done at runtime as the email as being sent. This was 
                          causing problems for fixing up the dot issue when MIME encoded messages were being sent.
                          4. Fixed an issue where the FixSingleDot function was being called when saving a message to disk. This was incorrect
                          and it should only be done when actually sending an SMTP email.
         PJN / 07-09-2005 1. Fixed another single dot issue, this time where the body is composed of just a single dot. Thanks to Raimund Mettendorf
                          for reporting this issue.
                          2. Fixed a typo in the accessor function CPJNSMTPConnection::GetBoundAddress which was incorrectly called 
                          CPJNSMTPConnection::SetBoundAddress.
         PJN / 29-09-2005 1. Removed linkage to secur32.lib as all SSPI functions are now constructed at runtime using GetProcAddress. Thanks to 
                          Emir Kapic for this update.
                          2. 250 or 251 is now considered a successful response to the RCPT command. Thanks to Jian Peng for reporting this issue.
                          3. Fixed potential memory leaks in the implementation of NTLM authentication as the code was throwing exceptions
                          which the authentication code was not expected to handle.
         PJN / 02-10-2005 1. MXLookup functionality of the class can now be excluded via the preprocessor define "CPJNSMTP_NOMXLOOKUP". Thanks to
                          Thiago Luiz for prompting this update. 
                          2. Sample app now does not bother asking for username and password if authentication type is NTLM. Thanks to Emir Kapic
                          for spotting this issue.
                          3. Documentation has been updated to refer to issues with NTLM Authentication and SSPI. Thanks to Emir Kapic for prompting
                          this update.
                          4. Fixed an update error done for the previous version which incorrectly supported the additional 251 command for the "MAIL"
                          command instead of more correctly the "RCPT" command. Thanks to Jian Peng for reporting this issue.
         PJN / 07-10-2005 1. Fixed an issue where file attachments were incorrectly including a charset value in their headers. Thanks to Asle Rokstad
                          for reporting this issue.
         PJN / 16-11-2005 1. Now includes full support for connecting via SSL. For example sending mail thro Gmail using the server 
                          "smtp.gmail.com" on port 465. Thanks to Vladimir Sukhoy for providing this very nice addition (using of course the author's 
                          very own OpenSSL wrappers!).
         PJN / 29-06-2006 1. Code now uses new C++ style casts rather than old style C casts where necessary. 
                          2. Integrated ThrowPJNSMTPException function into CPJNSMTPConnection class and renamed to ThrowPJNSMTPException
                          3. Combined the functionality of the _PJNSMTP_DATA class into the main CPJNSMTPConnection class.
                          4. Updated the code to clean compile on VC 2005
                          5. Updated copyright details.
                          6. Removed unnecessary checks for failure to allocate via new since in MFC these will throw CMemoryExceptions!.
                          7. Optimized CPJNSMTPException constructor code
         PJN / 14-07-2006 1. Fixed a bug in CPJNSMTPConnection where when compiled in UNICODE mode, NTLM authentication would fail. Thanks to 
                          Wouter Demuynck for providing the fix for this bug.
                          2. Reworked the CPJNSMTPConnection::NTLMAuthPhase(*) methods to throw standard CPJNSMTPException exceptions if there are any 
                          problems detected. This can be done now that the CNTLMClientAuth class is resilient to exceptions being thrown.
         PJN / 17-07-2006 1. Updated the code in the sample app to allow > 30000 characters to be entered into the edit box which contains the body
                          of the email. This is achieved by placing a call to CEdit::SetLimitText in the OnInitDialog method of the dialog. Thanks to 
                          Thomas Noone for reporting this issue.
         PJN / 13-10-2006 1. Fixed an issue in Disconnect where if the call to ReadCommandResponse threw an exception, we would not close down the 
                          socket or set m_bConnected to FALSE. The code has been updated to ensure these are now done. This just provides a more
                          consistent debugging experience. Thanks to Alexey Kuznetsov for reporting this issue.
                          2. CPJNSMTPException::GetErrorMessage now uses the user default locale for formatting error strings.
                          3. CPJNSMTPMessage::ParseMultipleRecipients now supports email addresses of the form ""Friendly, More Friendly" <emailaddress>"
                          Thanks to Xiao-li Ling for this very nice addition.
                          4. Fixed some issues with cleaning up of header and body parts in CPJNSMTPConnection::SendBodyPart and 
                          CPJNSMTPMessage::WriteToDisk. Thanks to Xiao-li Ling for reporting this issue.
         PJN / 09-11-2006 1. Reverted CPJNSMTPException::GetErrorMessage to use the system default locale. This is consistent with how MFC
                          does its own error handling.
                          2. Now includes comprehensive support for DSN's (Delivery Status Notifications) as specified in RFC 3461. Thanks to 
                          Riccardo Raccuglia for prompting this update.
                          3. CPJNSMTPBodyPart::GetBody and CPJNSMTPConnection::SendMessage(const CString& sMessageOnFile... now does not open the disk
                          file for exclusive read access, meaning that other apps can read from the file. Thanks to Wouter Demuynck for reporting
                          this issue.
                          4. Fixed an issue in CPJNSMTPConnection::SendMessage(const CString& sMessageOnFile... where under certain circumstances
                          we could end up deleting the local "pSendBuf" buffer twice. Thanks to Izidor Rozman for reporting this issue.
         PJN / 30-03-2007 1. Fixed a bug in CPJNSMTPConnection::SetHeloHostname where an unitialized stack variable could potentially be used. Thanks
                          to Anthony Kowalski for reporting this bug.
                          2. Updated copyright details.
         PJN / 01-08-2007 1. AuthCramMD5, ConnectESMTP, ConnectSMTP, AuthLogin, and AuthPlain methods have now been made virtual.
                          2. Now includes support for a Auto Authentication protocol. This will detect what authentication methods the SMTP server 
                          supports and uses the "most secure" method available. If you do not agree with the order in which the protocol is chosen,
                          a virtual function called "ChooseAuthenticationMethod" is provided. Thanks to "zhangbo" for providing this very nice 
                          addition to the code.
                          3. Removed the definition for the defunct "AuthNTLM" method.
         PJN / 13-11-2007 1. Major update to the code to use VC2005 specific features. This version of the code and onwards will be supported only
                          on VC 2005 or later. Thanks to Andrey Babushkin for prompting this update. Please note that there has been breaking changes
                          to the names and layout of various methods and member variables. You should carefully analyze the sample app included in the
                          download as well as your client code for the required updates you will need to make.
                          2. Sample app included in download is now build for Unicode and links dynamically to MFC. It also links dynamically to the 
                          latest OpenSSL 0.9.8g dlls.
         PJN / 19-11-2007 1. The "Date:" header is now formed using static lookup arrays for the day of week and month names instead of using 
                          CTime::Format. This helps avoid generating invalid Date headers on non English version of Windows where the local language 
                          versions of these strings would end up being used. Thanks to feedback at the codeproject page for the bug report (specifically 
                          http://www.codeproject.com/internet/csmtpconn.asp?df=100&forumid=472&mpp=50&select=2054493#xx2054493xx for reporting this issue).
         PJN / 22-12-2007 1. Following feedback from Selwyn Stevens that the AUTH PLAIN mechanism was failing for him while trying to send mail using the
                          gmail SMTP server, I decided to go back and re-research the area of SMTP authentication again<g>. It turns out that the way I was 
                          implementing AUTH PLAIN support was not the same as that specified in the relevant RFCs. The code has now been updated to be
                          compliant with that specified in the RFC. I've confirmed the fix as operating correctly by using the gmail SMTP mail server
                          at "smtp.gmail.com" on the SMTP/SSL port of 465. I've also taken the opportunity to update the documentation to include a section
                          with links to various RFC's and web pages for background reference material
         PJN / 24-12-2007 1. CPJNSMTPException::GetErrorMessage now uses the FORMAT_MESSAGE_IGNORE_INSERTS flag. For more information please see Raymond
                          Chen's blog at http://blogs.msdn.com/oldnewthing/archive/2007/11/28/6564257.aspx. Thanks to Alexey Kuznetsov for reporting this
                          issue.
                          2. All username and password temp strings are now securely destroyed using SecureZeroMemory.
         PJN / 31-12-2007 1. Updated the sample app to clean compile on VC 2008
                          2. Fixed a x64 compile problems in RemoveCustomHeader & AddMultipleAttachments.
                          3. Fixed a x64 compile problem in CPJNSMTPAppConfigurationDlg::CBAddStringAndData
                          4. CPJNSMTPException::GetErrorMessage now uses Checked::tcsncpy_s
                          5. Other Minor coding updates to CPJNSMTPException::GetErrorMessage
         PJN / 02-02-2008 1. Updated copyright details.
                          2. Fixed a bug in CPJNSMTPMessage::FormDateHeader where sometimes an invalid "Date:" header is created. The bug will manifest 
                          itself for anywhere that is ahead of GMT, where the difference isn't a multiple of one hour. According to the Windows Time Zone 
                          applet, this bug would have occured in: Tehran, Kabul, Chennai, Kolkata, Mumbai, New Delhi, Sri Jayawardenepura, Kathmandu, Yangon, 
                          Adelaide and Darwin. Thanks to Selwyn Stevens for reporting this bug and providing the fix.
         PJN / 01-03-2008 1. CPJNSMTP Priority enum values, specifically NO_PRIORITY have been renamed to avoid clashing with a #define of the same name in the
                          Windows SDK header file WinSpool.h. The enum values for DSN_RETURN_TYPE have also been renamed to maintain consistency. Thanks to 
                          Zoran Buntic for reporting this issue. 
                          2. Fixed compile problems related to the recent changes in the Base64 class. Thanks to Mat Berchtold for reporting this issue.
                          3. Since the code is now for VC 2005 or later only, the code now uses the Base64 encoding support from the ATL atlenc.h header file.
                          Thanks to Mat Berchtold for reporting this optimization. This means that client projects no longer need to include Base64.cpp/h in
                          their projects.
         PJN / 31-05-2008 1. Code now compiles cleanly using Code Analysis (/analyze)
                          2. Removed the use of the function QuotedPrintableEncode and replaced with ATL::QPEncode
                          3. Removed the use of the function QEncode and replaced with ATL::QEncode
                          4. Reworked ReadResponse to use CStringA in line with the implementation in the POP3 class of the author.
         PJN / 20-07-2008 1. Fixed a bug in ReadResponse where the code is determining if it has received the terminator. Thanks to Tony Cool for reporting
                          this bug.
         PJN / 27-07-2008 1. Updated code to compile correctly using _ATL_CSTRING_EXPLICIT_CONSTRUCTORS define
                          2. CPJNSMTPMessage::GetHeader now correctly ensures all long headers are properly folded. In addition this function has been reworked
                          to create the header internally as an ASCII string rather than as a TCHAR style CString.
         PJN / 16-08-2008 1. Updated the AUTH_AUTO login support to fall back to no authentication if no authentication scheme is supported by the SMTP server.
                          Thanks to Mat Berchtold for this update.
         PJN / 02-11-2008 1. Improvements to CPJNSMTPAddress constructor which takes a single string. The code now removes redundant quotes.
                          2. CPJNSMTPAddress::GetRegularFormat now forms the regular form of the email address before it Q encodes it
                          3. The sample app now uses DPAPI to encrypt the username & password configuration settings
                          4. The sample app is now linked against the latest OpenSSL v0.9.8i dlls
                          4. Updated coding references in the html documentation. In addition the zip file now includes the OpenSSL dlls. Thanks to Michael 
                          Grove for reporting this issues.
         PJN / 03-11-2008 1. For best compatibility purposes with existing SMTP mail servers, all message headers which include email addresses are now not
                          Q encoded. Also the friendly part of the email address is quoted. Thanks to Christian Egging for reporting this issue.
                          2. Optimized use of CT2A class throughout the code
         PJN / 25-06-2009 1. Updated copyright details
                          2. Fixed a bug where the Reply-To email address was not correctly added to the message. Thanks to Dmitriy Maksimov for reporting this
                          issue.
                          3. Updated the sample app's project settings to more modern default values.
                          4. Updated the sample exe to ship with OpenSSL v0.98k
                          5. Fixed a bug in CPJNSMTPConnection::SendBodyPart where it would cause errors from OpenSSL when trying to send a body of 0 bytes in size.
                          This can occur when MHTML based emails are sent and you are using a "multipart/related" body part. Thanks to "atota" for reporting this
                          issue
         PJN / 13-11-2009 1. Now includes comprehensive support for MDN's (Message Disposition Notifications) as specified in RFC 3798. Thanks to 
                          Ron Brunton for prompting this update.
                          2. Rewrote CPJNSMTPMessage::SaveToDisk method to use CAtlFile
         PJN / 17-12-2009 1. The sample app is now linked against the latest OpenSSL v0.9.8l dlls
                          2. Updated the sample code in the documentation to better describe how to use the various classes. Thanks to Dionisis Kofos for reporting
                          this issue.
         PJN / 23-05-2010 1. Updated copyright details
                          2. Updated sample app to compile cleanly on Visual Studio 2010
                          3. The sample app is now linked against the latest OpenSSL v1.0.0 dlls
                          4. Removed an unused "sRet" variable in CPJNSMTPConnection::AuthCramMD5. THanks to Alain Danteny for reporting this issue.
                          5. Replaced all calls to memcpy with memcpy_s
                          6. The code now supports STARTTLS encryption as defined in RFC 3207.
                          7. If the call to DnsQuery fails, the error value is now preserved using SetLastError
                          8. AddTextBody now sets the mime type of the root body part multipart/alternative. This is a more appropriate value to use which is better
                          supported by more email clients. Thanks to Thane Hubbell for reporting this issue.
         PJN / 04-06-2010 1. Updated the code and sample app to compile cleanly when CPJNSMTP_NOSSL is defined. Thanks to "loggerlogger" for reporting this issue.
         PJN / 10-07-2010 1. Following feedback from multiple users of PJNSMTP, including Chris Bamford and "loggerlogger", the change from "multipart/mixed" to
                          "multipart/alternative" has now been reverted. Using "multipart/mixed" is a more appropriate value to use. Testing by various clients
                          have shown that this setting works correctly when sending HTML based email in GMail, Thunderbird and Outlook. Unfortunately this is the 
                          price I must pay when accepting end user contributions to my code base which I cannot easily test. Going forward I plan to be much more
                          discerning on what modifications I will accept to avoid these issues.
                          2. The CPJNSMTPBodyPart now has the concept of whether or not the body is considered an attachment. Previously the code assumed that if 
                          you set the m_sFilename parameter that the body part was an attachment. Instead now the code uses the "m_bAttachment" member variable. 
                          This allows an in memory representation of an attachment to be added to a message without the need to write it to an intermediate file
                          first. For example, here would be the series of steps you would need to go through to add an in memory jpeg image to an email and have it
                          appear as an attachment:
                          
                          const BYTE* pInMemoryImage = pointer to your image;
                          DWORD dwMemoryImageSize = size in bytes of pInMemoryImage;
                          CPJNSMPTBase64Encode encode;
                          encode.Encode(pInMemoryImage, dwMemoryImageSize, ATL_BASE64_FLAG_NONE);
                          CPJNSMTPBodyPart imageBodyPart;
                          imageBodyPart.SetRawBody(encode.Result());
                          imageBodyPart.SetAttachment(true);
                          imageBodyPart.SetContentType(_T("image/jpg"));
                          imageBodyPart.SetTitle(_T("the name of the image"));
                          CPJNSMTPMessage message;
                          message.AddBodyPart(imageBodyPart);
                          
                          Thanks to Stephan Eizinga for suggesting this nice addition.
         PJN / 28-11-2010 1. AddTextBody and AddHTMLBody now allow the root body part's MIME type to be changed. Thanks to Thane Hubbell for prompting this update.
                          2. Added a CPJNSMTPBodyPart::GetBoundary method.
                          3. Added some sample code to the sample app's CPJNSMTPAppDlg constructor to show how to create a message used SMTP body parts. Thanks to
                          Thane Hubbell for prompting this update.
                          4. CPJNSMTPBodyPart::GetHeader now allows quoted-printable and raw attachments to be used
                          5. The sample app is now linked against the latest OpenSSL v1.0.0b dlls
         PJN / 18-12-2010 1. Remove the methods Set/GetQuotedPrintable and Set/GetBase64 from CPJNSMTPBodyPart and replaced them with new 
                          Set/GetContentTransferEncoding methods which works with a simple enum. 
                          2. CPJNSMTPBodyPart::SetAttachment and CPJNSMTPBodyPart::SetFilename now automatically sets the Content-Transfer-Encoding to base64 for 
                          attachments. Thanks to Christian Egging for reporting this issue.
                          3. The sample app is now linked against the latest OpenSSL v1.0.0c dlls
         PJN / 08-02-2011 1. Updated copyright details
                          2. Updated code to support latest SSL and Sockets class from the author. This means that the code now supports IPv6 SMTP servers
                          3. Connect method now allows binding to a specific IP address
         PJN / 13-02-2011 1. Remove the IP binding address parameter from the Connect method as there was already support for binding via the Set/GetBoundAddress
                          methods.
                          2. Set/GetBoundAddress have been renamed Set/GetBindAddress for consistency with the sockets class
         PJN / 01-04-2011 1. Reintroduced the concept of Address Header encoding. By default this setting is off, but can be enabled via a new
                          CPJNSMPTMessage::m_bAddressHeaderEncoding boolean value. Thanks to Bostjan Erzen for reporting this issue.
         PJN / 04-12-2011 1. Updated CPJNSMTPBodyPart::GetHeader to encode the title of a filename if it contains non-ASCII characters. Thanks to Anders Gustafsson
                          for reporting this issue.
                          2. The sample app is now linked against the latst OpenSSL v1.0.0e dlls.
         PJN / 12-08-2012 1. STARTTLS support code now uses TLSv1_client_method OpenSSL function instead of SSLv23_client_method. This fixes a problem where sending
                          emails using Hotmail / Windows Live was failing.
                          2. CPJNSMTPConnection::_Close now provides a bGracefully parameter
                          3. Updated the code to clean compile on VC 2012
                          4. The sample app is now linked against the latst OpenSSL v1.0.1c dlls.
                          5. SendMessage now throws a CPJNSMTPException if OnSendProgress returns FALSE.
                          6. Addition of a new ConnectionType called "AutoUpgradeToSTARTTLS" which will automatically upgrade a plain text connection to STARTTLS
                          if the code detects that the SMTP server supports STARTTLS. If the server doesn't support STARTTLS then the connection will remain as if
                          you specified "PlainText"
                          7. Reworked the code to determine if it should connect using EHLO instead of HELO into a new virtual method called DoEHLO.
                          8. Reworked the internals of the ConnectESMTP method.
         PJN / 23-09-2012 1. Removed SetContentBase and GetContentBase methods as Content-Base header is deprecated from MHTML. For details see 
                          http://www.ietf.org/mail-archive/web/apps-discuss/current/msg03220.html. Thanks to Mat Berchtold for reporting this issue.
                          2. Removed the now defunct parameter from the AddHTMLBody method.
                          3. Removed the FoldHeader method as it was only used by the m_sXMailer header.
                          4. Updated the logic in the FolderSubjectHeader method to correctly handle the last line of text to fold.
         PJN / 24-09-2012 1. Removed an unnecessary line of code from CPJNSMTPConnection::SendRCPTForRecipient. Thanks to Mat Berchtold for reporting this issue.
         PJN / 30-09-2012 1. Updated the code to avoid DLL planting security issues when calling LoadLibrary. Thanks to Mat Berchtold for reporting this issue.
         PJN / 25-11-2012 1. Fixed some issues in the code when CPJNSMTP_NOSSL was defined. Looks like support for compiling without SSL support has been 
                          broken for a few versions. Thanks to Bostjan Erzen for reporting this issue.
         PJN / 12-03-2013 1. Updated copyright details.
                          2. CPJNSMTPAddressArray typedef is now defined in the CPJNSMTPMessage class instead of in the global namespace. In addition this typedef
                          class is now known as "CAddressArray"
                          3. The sample project included in the download now links to the various OpenSSL libraries via defines in stdafx.h instead of via project
                          settings. Thanks to Ed Nafziger for suggesting this nice addition. 
                          4. The sample app no longer adds the author's hotmail address to the list of MDN's. Thanks to Ed Nafziger for spotting this.
                          5. The sample app is now linked against the latest OpenSSL v1.0.1e dlls.
                          6. Sample app now statically links to MFC
         PJN / 03-06-2013 1. The demo app now disables the MIME checkbox after checking it if the email is to be sent as HTML
                          2. Fixed a bug in the sample app where the mime type of the root body part would be incorrectly set to an empty string when it should 
                          use the default which is "multipart/mixed". Thanks to Ting L for reporting this bug.
         PJN / 05-01-2014 1. Updated copyright details.
                          2. Updated the code to clean compile on VC 2013
                          3. Fixed a problem with NTLM auth where the code did not correctly set the "m_nSize" parameter value correctly in 
                          CPJNSMPTBase64Encode::Encode & Decode. Thanks to John Pendleton for reporting this bug.
         PJN / 18-01-2013 1. The ConvertToUTF8 method now also handles conversion of ASCII data to UTF8. Thanks to Jean-Christophe Voogden for this update.
                          2. The CPJNSMTPConnection class now provides Set/GetSSLProtocol methods. This allows client code to specify the exact flavour of SSL which
                          the code should speak. Supported protocols are SSL v2/v3, TLS v1.0, TLS v1.1, TLS v1.2 and DTLS v1.0. This is required for some SMTP
                          servers which use the SSLv2 or v3 protocol instead of the more modern TLS v1 protocol. The default is to use TLS v1.0.
                          You may need to use this compatibility setting for the likes of IBM Domino SMTP servers which only support the older SSLv2/v3 setting. 
                          Thanks to Jean-Christophe Voogden for this update.
                          3. Removed all the proxy connection methods as they cannot be easily supported / tested by the author.
                          4. Reworked the code in CPJNSMTPConnection::ConnectESMTP to handle all variants of the 250 response as well as operate case insensitively
                          which is required by the ESMTP RFC.
                          5. Made more methods virtual to facilitate further client customisation
                          6. Fixed an issue in the DoSTARTTLS method where the socket would be left in a connected state if SSL negotiation failed. This would result in
                          later code which sent on the socket during the tear down phase hanging. Thanks to Jean-Christophe Voogden for reporting this issue.
                          7. The sample app is now linked against the latest OpenSSL v1.0.1f dlls.
         PJN / 26-01-2014 1. Updated ASSERT logic at the top of the ConnectESMTP method. Thanks to Jean-Christophe Voogden for reporting this issue.
         PJN / 09-02-2014 1. Fixed a compile problem in the CreateSSLSocket method when the CPJNSMTP_NOSSL preprocessor macro is defined.
                          2. Reworked the logic which does SecureZeroMemory on sensitive string data
         PJN / 13-04-2014 1. The sample app is now linked against the latest OpenSSL v1.0.1g dlls. This version is the patched version of OpenSSL which does not
                          suffer from the Heartbleed bug.
                          2. Please note that by default OpenSSL does not do host name validation. The sample app provided with the PJNSMTP code also does not do
                          host name validation. This means that as it stands the sample app is vulnerable to man in the middle attacks if you use SSL/TLS to connect
                          to a SMTP server. For further information and sample code which you should incorporate into your real SMTP client applications, please see
                          http://wiki.openssl.org/index.php/Hostname_validation, https://github.com/iSECPartners/ssl-conservatory and 
                          http://archives.seul.org/libevent/users/Feb-2013/msg00043.html.
         PJN / 15-11-2014 1. Removed the defunct method CPJNSMTPBodyPart::HexDigit.
                          2. Reworked the CPJNSMTPBodyPart::GetBody method to use ATL::CAtlFile and ATL::CHeapPtr
                          3. CPJNSMTPConnection now takes a static dependency on Wininet.dll instead of using GetProcAddress.
                          4. CPJNSMTPConnection now takes a static dependency on Dnsapi.dll instead of using GetProcAddress.
                          5. The default timeout set in the CPJNSMTPConnection constructor is now 60 seconds for both debug and release builds
                          6. Removed the now defunct CPJNSMTPConnection::MXLookupAvailable method 
                          7. Removed the now defunct CPJNSMTP_NOMXLOOKUP preprocessor value
                          8. Removed the now defunct PJNLoadLibraryFromSystem32.h module from the distribution
                          9. CPJNSMTPConnection::SendMessage has been reworked to use ATL::CAtlFile and ATL::CHeapPtr
                          10. CPJNSMTPConnection::SendMessage now does a UTF-8 conversion on the body of the email when sending
                          a plain email i.e. no HTML or mime if the charset is UTF-8. Thanks to Oliver Pfister for reporting this issue.
                          11. Sample app has been updated to compile cleanly on VS 2013 Update 3 and higher
                          12. The sample app shipped with the source code is now Visual Studio 2008 and as of this release the code is only supported on Visual Studio 
                          2008 and later
                          13. The sample app is now linked against the latest OpenSSL v1.0.1j dlls
         PJN / 18-11-2014 1. Fixed a bug in CPJNSMTPBodyPart::GetBody where file attachments were always being treated as 0 in size. Thanks to Oliver Pfister for 
                          reporting this issue.
         PJN / 14-12-2014 1. Updated the code to use the author's SSLWrappers classes (http://www.naughter.com/sslwrappers.html) to provide the SSL functionality 
                          for PJNSMTP instead of OpenSSL. Please note that the SSLWrappers classes itself depends on the author's CryptoWrappers classes 
                          (http://www.naughter.com/cryptowrappers.html) also. You will need to download both of these libraries and copy their modules into the 
                          PJNSMTP directory. Also note that the SSLWrappers and CryptoWrapper classes are only supported on VC 2013 or later and this means that 
                          the PJNSMTP SSL support is only supported when you compile with VC 2013 or later. The solution files included in the download is now for 
                          VC 2013.
                          2. With the change to using SSLWrappers, the code can now support additional flavours of SSL protocol. They are: SSL v2 on its own, 
                          SSL v3 on its own, The OS SChannel SSL default and AnyTLS. Please see the CPJNSMTPConnection::SSLProtocol enum for more details. The 
                          sample app has been updated to allow these values to be used and the default enum value is now "OSDefault".
         PJN / 16-12-2014 1. Updated the code to use the latest v1.03 version of SSLWrappers
         PJN / 16-01-2015 1. Updated copright details
                          2. CPJNSMTPMessage::m_ReplyTo is now an array instead of a single address. Thanks to Bostjan Erzen for suggesting this update.
         PJN / 25-01-2015 1. Addition of CPJNSMTPBodyPart::InsertChildBodyPart and CPJNSMTPMessage::InsertBodyPart methods. These new methods inserts a body part 
                          as the first element in the hierarchy of child body parts instead of to the end. In addition the CPJNSMTPMessage::AddTextBody 
                          and CPJNSMTPMessage::AddHTMLBody methods now call CPJNSMTPBodyPart::InsertChildBodyPart instead of CPJNSMTPBodyPart::AddChildBodyPart 
                          to ensure that the body text part of the SMTP message appears as the first child body part. This helps avoid issues where Microsoft 
                          Exchange modifies multipart messages where the body text is moved to an attachment called ATT00001: For further information please see 
                          http://kb.mit.edu/confluence/pages/viewpage.action?pageId=4981187 and http://support2.microsoft.com/kb/969854. Thanks to Marco Veldman 
                          for reporting this issue.
                          2. Removed support for DTLSv1 SSL protocol as this is not applicable to SMTP as DTLS is designed for UDP and not TCP. This removal helps
                          resolve an issue where the code did not compile in VC 2013 if you used the "Visual Studio 2013 - Windows XP (v120_xp)" Platform Toolset.
                          Thanks to Bostjan Erzen for reporting this issue.
         PJN / 31-01-2015 1. Fixed an error in CPJNSMTPException::GetErrorMessage where it would not use the full HRESULT value when calling FormatMessage. Thanks
                          to Bostjan Erzen for reporting this issue.
                          2. Improved the error handling in CPJNSMTPConnection::Connect when SSL connection errors occur. Thanks to Bostjan Erzen for reporting 
                          this issue.
                          3. Improved the error handling in CPJNSMTPConnection::DoSTARTTLS when SSL connection errors occur. Thanks to Bostjan Erzen for reporting 
                          this issue.
         PJN / 01-03-2015 1. The DoEHLO method has been updated to check for DSN requirements. This change means that the code will connect with a EHLO instead of 
                          HELO when a DSN is requested. The code will now also check whether DSN's are supported by the server when attempting to send the DSN.
                          If the server does not support DSN's in this scenario then a new custom exception will be thrown by the code in the 
                          CPJNSMTPConnection::FormMailFromCommand method. This ensures the code behaves more consistently with respect to the DSN RFC. Thanks to 
                          Philip Mitchell for prompting this update.
                          2. Change the enum value for CPJNSMTPMessage::DSN_NOT_SPECIFIED to 0 from 0xFFFFFFFF.
         PJN / 29-03-2015 1. Updated CPJNSMTPConnection::AuthLogin to handle more variants of "username:" and "password:" SMTP server responses. Thanks to Wang Le
                          for reporting this issue.
         PJN / 01-06-2015 1. Removed unused CPJNSMTPMessage::ConvertHTMLToPlainText method.
                          2. Removed the linkage in the test app's configuration dialog between sending a message with a HTML body and requiring that the message
                          is MIME encoded. 
                          3. Removed the code from the test app which added a plain text attachment to the message in debug builds.
                          4. Updated CPJNSMTPMessage::AddHTMLBody to handle the case where the message is not MIME encoded. In this case the root body part of the 
                          message is simply set to HTML. Thanks to Thane Hubbell for reporting this issue.
         PJN / 12-05-2016 1. Updated copyright details.
                          2. Minor update to the sample app to refer to "SSL / TLS" in the configuration dialog.
                          3. Verfied the code compiles cleanly using VC 2015.
         PJN / 11-08-2016 1. Reworked the classes to optionally compile without MFC. By default the classes now use STL classes and idioms but if you define 
                          CWSOCKET_MFC_EXTENSTIONS the classes will revert back to the MFC behaviour.
                          2. Reworked the CPJNSMPTBase64Encode class to use ATL::CHeapPtr
                          3. Reworked the CPJNSMPTQPEncode class to use ATL::CHeapPtr
                          4. Reworked the CPJNSMPTQEncode class to use ATL::CHeapPtr
                          5. Added SAL annotations to all the code
                          6. CPJNSMTPBodyPart::ConvertToUTF8 now internally uses ATL::CHeapPtr
                          7. The CPJNSMTPMessage::m_CustomHeaders member variable is now public. As such the 
                          AddCustomHeader, GetCustomHeader, GetNumberOfCustomHeaders & RemoveCustomHeader
                          methods have been removed.
                          8. Removed unnecessary code which catches and rethrows a CPJNSMTPException exception from the version of CPJNSMTPConnection::SendMessage 
                          which sends a message from a memory buffer 
                          9. Reworked the CPJNSMTPMessage::ParseMultipleRecipients method to avoid the use of the C++ new allocator
                          10. Reworked the CPJNSMTPMessage::AddMultipleAttachments method to avoid the use of the C++ new allocator
                          11. Reworked the sample app to exercise the SendMessage method which sends from a memory buffer
         PJN / 17-08-2016 1. Reworked CPJNSMTPConnection class to be inherited from SSLWrappers::CSocket. This allows
                          for easier overloading of the various SSLWrappers class framework virtual functions
         PJN / 19-08-2016 1. Reworked the CPJNSMTPException::GetErrorMessage to not bother putting the last response into the return error message text.
                          2. CPJNSMTPConnection class is now protected inherited from SSLWrappers::CSocket.
         PJN / 15-09-2016 1. Added support for XOAUTH2 authentication. For more information about this SMTP authentication mechanism please see 
                          https://developers.google.com/gmail/xoauth2_protocol.
                          2. Reworked the CPJNSMTPConnection::AuthPlain to provide the userid in the authorization identity value. This is a more compatible form
                          of the auth request and avoids issues using AuthPlain against the gmail SMTP servers.
                          3. Added Initial Client Response (SASL-IR) support to the AuthLogin, AuthPlain & AuthXOAUTH2 methods. For more information about SASL-IR
                          please see https://tools.ietf.org/html/rfc4959.
         PJN / 13-10-2016 1. Fixed a bug in the declaration of the CPJNSMTPConnection class when the CPJNSMTP_NONTLM preprocessor value is defined. Thanks to Chris 
                          Korda for reporting this issue.
                          2. Provided a new overload of the CPJNSMTPBodyPart::AddChildBodyPart method to allow an already heap allocated bodyPart to be added.
                          3. Provided a new overload of the CPJNSMTPBodyPart::InsertChildBodyPart method to allow an already heap allocated bodyPart to be added.
                          4. Made most of the methods of CPJNSMTPBodyPart virtual to allow for easier end user customization of its functionality.
                          5. Provided a new overload of the CPJNSMTPMessage::AddChildBodyPart method to allow an already heap allocated bodyPart to be added.
                          6. Provided a new overload of the CPJNSMTPMessage::InsertChildBodyPart method to allow an already heap allocated bodyPart to be added.
                          7. Made most of the methods of CPJNSMTPMessage virtual to allow for easier end user customization of its functionality.
         PJN / 14-11-2016 1. Removed unnecessary validation of pszUsername & pszPassword parameters in CPJNSMTPConnection::ConnectESMTP. Thanks to Christopher 
                          Craft for reporting this issue.
                          2. Added validation of pszUsername & pszPassword parameters in CPJNSMTPConnection::AuthLogin, CPJNSMTPConnection::AuthPlain &  
                          CPJNSMTPConnection::AuthCramMD5 methods. Thanks to Christopher Craft for reporting this issue.
                          3. Improved the SAL annotations on the CPJNSMTPConnection::ConnectESMTP method.
                          4. Reworked CPJNSMTPBodyPart::ConvertToUTF8 to use ATL::CW2A
                          5. Reworked the non MFC code path of the SMTP code to now be based on a new CPJNSMTP_MFC_EXTENSIONS preprocessor values instead of the 
                          existing CWSOCKET_MFC_EXTENSIONS preprocessor value which not only applies to the socket class
                          6. Optimized the usage of ATL::CT2A & ATL::CA2T throughout the code
         PJN / 14-04-2017 1. Updated copyright details.
                          2. Updated code to compile cleanly on VC 2017
         PJN / 28-04-2017 1. Updated the code to compile cleanly using /permissive-.
         PJN / 09-09-2017 1. Replaced CString::operator LPC*STR() calls throughout the codebase with CString::GetString calls
         PJN / 18-12-2017 1. Updated the code to compile cleanly when _ATL_NO_AUTOMATIC_NAMESPACE is defined.
         PJN / 24-06-2018 1. Updated copyright details.
                          2. Fixed a number of C++ core guidelines compiler warnings. These changes mean that
                          the code will now only compile on VC 2017 or later.
         PJN / 25-08-2018 1. Fixed a number of compiler warnings when using VS 2017 15.8.1
         PJN / 04-12-2018 1. Fixed a number of compiler warnings when using VS 2017 15.9.3
         PJN / 26-12-2018 1. Updated PJNSmtp.h file to warn if memory header file is not already included.
                          2. Fix up a number of compile problems when various CPJNSMTP_* preprocessor values are defined
         PJN / 07-04-2019 1. Updated copyright details.
                          2. Fixed some compiler warnings when compiled with Visual Studio 2019
                          3. Removed code which supports the now defunct CPJNSMTP_MFC_EXTENSIONS codepath
                          4. Removed all dependencies on now defunct IDS_PJNSMTP* string resources
         PJN / 13-04-2019 1. Added a new CPJNSMTPMessage::AddAttachment method. This new method allows you to add a single attachment to a SMTP message.
                          Internally the existing functionality in CPJNSMTPMessage::AddMultipleAttachments now calls this method.
                          2. The return value for the method CPJNSMTPBodyPart::SetFilename has changed from bool to void.
                          3. Removed the Disconnect call from the CPJNSMTPConnection destructor. This prevents the code stalling for up to the m_dwTimeout
                          value. Instead client code is expected to explicitly call the Disconnect method to close the connection. Also improved the sample
                          app code to avoid stalling for m_dwTimeout in the method CPJNSMTPAppDlg::OnSend.
         PJN / 14-04-2019 1. Replaced BOOL with bool throughout the code.
         PJN / 20-12-2019 1. Fixed various Clang-Tidy static code analysis warnings in the code.
                          2. Updated initialization of various structs to use C++ 11 list initialization.
         PJN / 08-01-2020 1. Updated copyright details.
                          2. Fixed more Clang-Tidy static code analysis warnings in the code.
         PJN / 25-01-2020 1. Fixed a bug in CPJNSMTPAddress::StringTrim which caused strings of the form "pjna@naughter.com; " to not be parsed correctly.
                          Thanks to Martin Richter for reporting this bug.
         PJN / 02-05-2020 1. Added support for International email and internationalized email addresses. To support this email addresses can now be
                          encoded as UTF8 in mail headers. Sender and recipient email addresses are now also punycode encoded during the SMTP 
                          conversation. Thanks to Martin Richer for supplying this enhancement.
                          2. Provided new SetDoEHLO and GetDoEHLO methods. This eliminates the need for the DoEHLO method.
                          3. Fixed more Clang-Tidy static code analysis warnings in the code.
         PJN / 13-09-2020 1. CPJNSMTPConnection is now derived publically from SSLWrappers::CSocket. This allows all the public methods of 
                          SSLWrappers::CSocket to be accessible from client code. Thanks to Martin Richter for prompting this update.
                          2. Provided a new virtual CPJNSMTPConnection::TraceCommunication method. This method allows you to see all the data sent and 
                          received by the class for diagnostic purposes. For example you could provide an option in your app to log the data for customer 
                          support issues. Thanks to Martin Richter for providing this update.
                          3. Improved the error handling in CPJNSMTPAddress::PunyCode to handle errors reported by the underlying Win32 API of IdnToAscii 
                          which the code uses to do PunyCode encoding of email addresses to support International email and internationalized email 
                          addresses. The issue is that the code uses an "IDN_EMAIL_ADDRESS" flag to this IdnToAscii API and this flag is only supported on 
                          versions of Windows starting with Windows 8. On earlier versions of Windows such as Windows 7 and Windows 2008 R2, this API fails
                          when you use the IDN_EMAIL_ADDRESS flag. The code now handles this error by throwing an exception rather than ignoring the return 
                          value from the API. Previously when the code ignored the return value, it would then crash when it tried to dereference a zero 
                          length string. If you must run this code on now unsupported versions of Windows, then please make sure you disable Punycode 
                          encoding using the new functions described in the next release note below. Thanks to Darron Redman and Martin Richter for 
                          reporting this issue.
                          4. Added a new boolean configuration value to the CPJNSMTPConnection class to determine if punycode encoded should be used. These 
                          new methods are SetUsePunyCode and GetUsePunyCode. As described in the previous release note this is necessary to support the code 
                          running without errors on versions of Windows earlier than Windows 8. I have added a checkbox to the sample application to 
                          demonstrate using this. By using SetUsePunyCode(false), the code will not of course be able to support internationalized email 
                          addresses on these downlevel versions of Windows.

Copyright (c) 1998 - 2020 by PJ Naughter (Web: www.naughter.com, Email: pjna@naughter.com)

All rights reserved.

Copyright / Usage Details:

You are allowed to include the source code in any product (commercial, shareware, freeware or otherwise)
when your product is released in binary form. You are allowed to modify the source code in any way you want
except you cannot modify the copyright details at the top of each module. If you want to distribute source
code with your application, then you are only allowed to distribute versions released by the author. This is
to maintain a single distribution point for the source code.

Please note that I have been informed that CPJNSMTPConnection is being used to develop and send unsolicted bulk mail.
This was not the intention of the code and the author explicitly forbids use of the code for any software of this kind.

*/


//////////////// Includes /////////////////////////////////////////////////////

#include "stdafx.h"
#include "PJNSmtp.h"
#include "PJNMD5.h"

#ifndef _WININET_
#include <wininet.h>
#pragma message("To avoid this message, please put wininet.h in your pre compiled header (usually stdafx.h)")
#endif //#ifndef _WININET_

#ifndef __ATLENC_H__
#pragma message("To avoid this message, please put atlenc.h in your pre compiled header (usually stdafx.h)")
#include <atlenc.h>
#endif //#ifndef __ATLENC_H__

#ifndef _ARRAY_
#pragma message("To avoid this message, please put array in your pre compiled header (usually stdafx.h)")
#include <array>
#endif //#ifndef _ARRAY_


//////////////// Macros / Locals //////////////////////////////////////////////

#pragma comment(lib, "wsock32.lib") //Automatically link in the Winsock dll
#pragma comment(lib, "rpcrt4.lib") //Automatically link in the RPC runtime dll
#pragma comment(lib, "wininet.lib") //Automatically link in the Wininet dll
#pragma comment(lib, "Dnsapi.lib") //Automatically link to the DNS dll
#pragma comment(lib, "Normaliz.lib") //Automatically link to the Normaliz.dll


//////////////// Implementation ///////////////////////////////////////////////

void CPJNSMPTBase64Encode::Encode(_In_reads_bytes_(nSize) const BYTE* pData, _In_ int nSize, _In_ DWORD dwFlags)
{
  //Tidy up any heap memory we have been using
  m_Buf.clear();

  //Calculate and allocate the buffer to store the encoded data
  int nEncodedSize = ATL::Base64EncodeGetRequiredLength(nSize, dwFlags);
#pragma warning(suppress: 26472)
  m_Buf.resize(static_cast<size_t>(nEncodedSize) + 1); //We allocate an extra byte so that we can null terminate the result

  //Finally do the encoding
  if (!ATL::Base64Encode(pData, nSize, m_Buf.data(), &nEncodedSize, dwFlags))
    CPJNSMTPConnection::ThrowPJNSMTPException(ERROR_INVALID_PARAMETER, FACILITY_WIN32);

  //Null terminate the data
#pragma warning(suppress: 26446)
  m_Buf[nEncodedSize] = '\0';
}

void CPJNSMPTBase64Encode::Decode(_In_reads_bytes_(nSize) LPCSTR pData, _In_ int nSize)
{
  //Tidy up any heap memory we have been using
  m_Buf.clear();

  //Calculate and allocate the buffer to store the encoded data
  int nDecodedSize = ATL::Base64DecodeGetRequiredLength(nSize);
#pragma warning(suppress: 26472)
  m_Buf.resize(static_cast<size_t>(nDecodedSize) + 1); //We allocate an extra byte so that we can null terminate the result

  //Finally do the encoding
#pragma warning(suppress: 26490)
  if (!ATL::Base64Decode(pData, nSize, reinterpret_cast<BYTE*>(m_Buf.data()), &nDecodedSize))
    CPJNSMTPConnection::ThrowPJNSMTPException(ERROR_INVALID_PARAMETER, FACILITY_WIN32);

  //Null terminate the data
#pragma warning(suppress: 26446)
  m_Buf[nSize] = '\0';
}

void CPJNSMPTBase64Encode::Encode(_In_z_ LPCSTR pszMessage, _In_ DWORD dwFlags)
{
#pragma warning(suppress: 26472 26490)
  Encode(reinterpret_cast<const BYTE*>(pszMessage), static_cast<int>(strlen(pszMessage)), dwFlags);
}

void CPJNSMPTBase64Encode::Decode(_In_z_ LPCSTR pszMessage)
{
#pragma warning(suppress: 26472)
  Decode(pszMessage, static_cast<int>(strlen(pszMessage)));
}


void CPJNSMPTQPEncode::Encode(_In_reads_bytes_(nSize) const BYTE* pData, _In_ int nSize, _In_ DWORD dwFlags)
{
  //Tidy up any heap memory we have been using
  m_Buf.clear();

  //Calculate and allocate the buffer to store the encoded data
  int nEncodedSize = ATL::QPEncodeGetRequiredLength(nSize); 
#pragma warning(suppress: 26472)
  m_Buf.resize(static_cast<size_t>(nEncodedSize) + 1); //We allocate an extra byte so that we can null terminate the result

  //Finally do the encoding
#pragma warning(suppress: 26465 26492)
  if (!ATL::QPEncode(const_cast<BYTE*>(pData), nSize, m_Buf.data(), &nEncodedSize, dwFlags)) //ATL::QPEncode is incorrectly defined as taking a BYTE* instead of a const BYTE* for the first parameter
    CPJNSMTPConnection::ThrowPJNSMTPException(ERROR_INVALID_PARAMETER, FACILITY_WIN32);

  //Null terminate the data
#pragma warning(suppress: 26446)
  m_Buf[nEncodedSize] = '\0';
}

void CPJNSMPTQPEncode::Encode(_In_z_ LPCSTR pszMessage, _In_ DWORD dwFlags)
{
#pragma warning(suppress: 26490 26472)
  Encode(reinterpret_cast<const BYTE*>(pszMessage), static_cast<int>(strlen(pszMessage)), dwFlags);
}


void CPJNSMPTQEncode::Encode(_In_reads_bytes_(nSize) const BYTE* pData, _In_ int nSize, _In_z_ LPCSTR szCharset)
{
  //Tidy up any heap memory we have been using
  m_Buf.clear();

  //Calculate and allocate the buffer to store the encoded data
  int nEncodedSize = ATL::QEncodeGetRequiredLength(nSize, ATL_MAX_ENC_CHARSET_LENGTH);
#pragma warning(suppress: 26472)
  m_Buf.resize(static_cast<size_t>(nEncodedSize) + 1); //We allocate an extra byte so that we can null terminate the result

  //Finally do the encoding
#pragma warning(suppress: 26465 26492)
  if (!ATL::QEncode(const_cast<BYTE*>(pData), nSize, m_Buf.data(), &nEncodedSize, szCharset)) //ATL::QEncode is incorrectly defined as taking a BYTE* instead of a const BYTE* for the first parameter
    CPJNSMTPConnection::ThrowPJNSMTPException(ERROR_INVALID_PARAMETER, FACILITY_WIN32);

  //Null terminate the data
#pragma warning(suppress: 26446)
  m_Buf[nEncodedSize] = '\0';
}

void CPJNSMPTQEncode::Encode(_In_z_ LPCSTR pszMessage, _In_z_ LPCSTR szCharset)
{
#pragma warning(suppress: 26490 26472)
  Encode(reinterpret_cast<const BYTE*>(pszMessage), static_cast<int>(strlen(pszMessage)), szCharset);
}


CPJNSMTPException::CPJNSMTPException(_In_ HRESULT hr, _In_ CPJNSMTPString sLastResponse) noexcept : m_hr(hr),
                                                                                                    m_sLastResponse(std::move(sLastResponse))
{
}

CPJNSMTPException::CPJNSMTPException(_In_ DWORD dwError, _In_ DWORD dwFacility, _In_ CPJNSMTPString sLastResponse) noexcept : m_hr(MAKE_HRESULT(SEVERITY_ERROR, dwFacility, dwError)),
                                                                                                                              m_sLastResponse(std::move(sLastResponse))
{
}

#pragma warning(suppress: 26429)
bool CPJNSMTPException::GetErrorMessage(_Out_writes_z_(nMaxError) LPTSTR lpszError, _In_ UINT nMaxError, _Out_opt_ PUINT pnHelpContext)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(lpszError != nullptr);
#pragma warning(suppress: 26477)
  ATLASSERT(nMaxError != 0);
#pragma warning(suppress: 26481)
  lpszError[0] = _T('\0');

  if (pnHelpContext != nullptr)
    *pnHelpContext = 0;

  //What will be the return value from this method (assume the worst)
  bool bSuccess = false;

  LPTSTR lpBuffer = nullptr;
  const DWORD dwReturn = FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                                       nullptr, m_hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_SYS_DEFAULT),
#pragma warning(suppress: 26490)
                                       reinterpret_cast<LPTSTR>(&lpBuffer), 0, nullptr);
  if (dwReturn == 0)
    *lpszError = _T('\0');
  else
  {
    bSuccess = true;
    ATL::Checked::tcsncpy_s(lpszError, nMaxError, lpBuffer, _TRUNCATE);
    LocalFree(lpBuffer);
  }

  return bSuccess;
}


CPJNSMTPAddress::CPJNSMTPAddress(_In_z_ LPCTSTR pszAddress)
{
  //The local variable which we will operate on
#pragma warning(suppress: 26489)
  CPJNSMTPString sTemp(pszAddress);
  StringTrim(sTemp);

  //divide the substring into friendly names and e-mail addresses
  CPJNSMTPString::size_type nMark = sTemp.find(_T('<'));
  CPJNSMTPString::size_type nMark2 = sTemp.find(_T('>'));
  if ((nMark != CPJNSMTPString::npos) && (nMark2 != CPJNSMTPString::npos) && (nMark2 > (nMark + 1)))
  {
    m_sEmailAddress = sTemp.substr(nMark + 1, nMark2 - nMark - 1);
#pragma warning(suppress: 26489)
    m_sFriendlyName = sTemp.substr(0, nMark);

    //Tidy up the parsed values
    StringTrim(m_sFriendlyName);
    m_sFriendlyName = RemoveQuotes(m_sFriendlyName);
    StringTrim(m_sEmailAddress);
    m_sEmailAddress = RemoveQuotes(m_sEmailAddress);
  }
  else
  {
    nMark = sTemp.find(_T('('));
    nMark2 = sTemp.find(_T(')'));
    if ((nMark != CPJNSMTPString::npos) && (nMark2 != CPJNSMTPString ::npos) && (nMark2 > (nMark + 1)))
    {
      m_sEmailAddress = sTemp.substr(0, nMark);
#pragma warning(suppress: 26489)
      m_sFriendlyName = sTemp.substr(nMark + 1, nMark2 - nMark - 1);

      //Tidy up the parsed values
      StringTrim(m_sFriendlyName);
      m_sFriendlyName = RemoveQuotes(m_sFriendlyName);
      StringTrim(m_sEmailAddress);
      m_sEmailAddress = RemoveQuotes(m_sEmailAddress);
    }
    else
      m_sEmailAddress = sTemp;
  }
}

CPJNSMTPAddress::CPJNSMTPAddress(_In_z_ LPCTSTR pszFriendly, _In_z_ LPCTSTR pszAddress) : m_sFriendlyName(pszFriendly),
                                                                                          m_sEmailAddress(pszAddress) 
{
#pragma warning(suppress: 26477)
  ATLASSERT(m_sEmailAddress.length()); //An empty address is not allowed
}

CPJNSMTPAsciiString CPJNSMTPAddress::GetRegularFormat(_In_ ENCODING encode, _In_ const CPJNSMTPString& sCharset) const
{
#pragma warning(suppress: 26477)
  ATLASSERT(m_sEmailAddress.length()); //Email Address must be valid

  //What will be the return value from this method
  CPJNSMTPAsciiString sAddress;

  if (m_sFriendlyName.length() == 0)
  {
    if (encode != ENCODING::EncodeUTF8)
      sAddress = CPJNSMTPBodyPart::ConvertToUTF8(m_sEmailAddress.c_str());
    else
    {
    #ifdef _UNICODE
      sAddress = ATL::CT2A(m_sEmailAddress.c_str());
    #else
      sAddress = m_sEmailAddress;
    #endif //#ifdef _UNICODE
    }
  }
  else
  {
    std::ostringstream ss;
    if (encode == ENCODING::None)
    {
    #ifdef _UNICODE
      ss << "\"" << ATL::CT2A(m_sFriendlyName.c_str()) << "\"" << "<" << ATL::CT2A(m_sEmailAddress.c_str()) << ">";
    #else
      ss << "\"" << m_sFriendlyName.c_str() << "\"" << "<" << m_sEmailAddress.c_str() << ">";
    #endif //#ifdef _UNICODE
      sAddress = ss.str();
    }
    else if (encode == ENCODING::EncodeUTF8)
    {
      ss << "\"" << CPJNSMTPBodyPart::HeaderEncode(m_sFriendlyName.c_str(), _T("utf-8")) << "\"";
      ss << "<" << CPJNSMTPBodyPart::ConvertToUTF8(m_sEmailAddress.c_str()).c_str() << ">";
    }
    else
    {
      ss << "\"" << CPJNSMTPBodyPart::HeaderEncode(m_sFriendlyName.c_str(), sCharset.c_str()) << "\"";
    #ifdef _UNICODE
      ss << "<" << ATL::CT2A(m_sEmailAddress.c_str()) << ">";
    #else
      ss << "<" << m_sEmailAddress.c_str() << ">";
    #endif //#ifdef _UNICODE
    }
    sAddress = ss.str();
  }
  return sAddress;
}

CPJNSMTPString CPJNSMTPAddress::RemoveQuotes(_In_ const CPJNSMTPString& sValue)
{
  //What will be the return value from this method
  CPJNSMTPString sReturn;

  const CPJNSMTPString::size_type nLength = sValue.length();
  if (nLength > 2)
  {
#pragma warning(suppress: 26446)
    if (sValue[0] == _T('"') && sValue[nLength - 1] == _T('"'))
      sReturn = sValue.substr(1, nLength - 2);
    else
      sReturn = sValue;
  }
  else
    sReturn = sValue;
  return sReturn;
}

CPJNSMTPAsciiString CPJNSMTPAddress::PunyCode(_In_z_ LPCTSTR pszStr)
{
  //Convert the TCHAR IDN string parameter to the ASCII encoded A-Labels format
#ifdef _UNICODE
  const int nMaxLen = IdnToAscii(IDN_EMAIL_ADDRESS, pszStr, -1, nullptr, 0);
#else
  ATL::CT2W wszStr(pszStr);
  const int nMaxLen = IdnToAscii(IDN_EMAIL_ADDRESS, wszStr, -1, nullptr, 0);
#endif //#ifdef _UNICODE
  if (nMaxLen == 0)
    CPJNSMTPConnection::ThrowPJNSMTPException(GetLastError(), FACILITY_WIN32);

  std::vector<WCHAR> sTemp;
  sTemp.resize(nMaxLen);
#ifdef _UNICODE
#pragma warning(suppress: 26446)
  IdnToAscii(IDN_EMAIL_ADDRESS, pszStr, -1, &sTemp[0], nMaxLen);
#else
#pragma warning(suppress: 26446)
  IdnToAscii(IDN_EMAIL_ADDRESS, wszStr, -1, &sTemp[0], nMaxLen);
#endif //#ifdef _UNICODE
#pragma warning(suppress: 26446)
  return CPJNSMTPAsciiString(ATL::CW2A(&sTemp[0]));
}

void CPJNSMTPAddress::StringTrim(_Inout_ CPJNSMTPString& sString)
{
  CPJNSMTPString::size_type nPos = sString.find_last_not_of(_T(" \n\r\t"));
  if (nPos == CPJNSMTPString::npos)
    sString.clear(); //string contains only white spaces! It is empty.
  else
  {
    sString.resize(nPos + 1);

    nPos = sString.find_first_not_of(_T(" \n\r\t"));
    if (nPos != std::string::npos)
#pragma warning(suppress: 26489)
      sString = sString.substr(nPos);
  }
}

void CPJNSMTPAddress::StringReplace(_Inout_ std::string& sString, _In_z_ LPCSTR pszToFind, _In_z_ LPCSTR pszToReplace)
{
  std::string::size_type nFind = sString.find(pszToFind);
  const size_t nFindLen = strlen(pszToFind);
  const size_t nReplaceLen = strlen(pszToReplace);
  while (nFind != std::string::npos)
  {
    sString.replace(nFind, nFindLen, pszToReplace);
    nFind = sString.find(pszToFind, nFind + nReplaceLen);
  }
}

void CPJNSMTPAddress::StringReplace(_Inout_ std::wstring& sString, _In_z_ LPCWSTR pszToFind, _In_z_ LPCWSTR pszToReplace)
{
  std::wstring::size_type nFind = sString.find(pszToFind);
  const size_t nFindLen = wcslen(pszToFind);
  const size_t nReplaceLen = wcslen(pszToReplace);
  while (nFind != std::wstring::npos)
  {
    sString.replace(nFind, nFindLen, pszToReplace);
    nFind = sString.find(pszToFind, nFind + nReplaceLen);
  }
}


#pragma warning(suppress: 26455)
CPJNSMTPBodyPart::CPJNSMTPBodyPart() : m_sContentType(_T("text/plain")),
                                       m_sCharset(_T("iso-8859-1")),
                                       m_pParentBodyPart(nullptr),
                                       m_sBoundary(CreateGUID()),
                                       m_bAttachment(false),
                                       m_pszRawBody(nullptr),
                                       m_ContentTransferEncoding(CONTENT_TRANSFER_ENCODING::NONE)
{
}

CPJNSMTPString CPJNSMTPBodyPart::CreateGUID()
{
  UUID uuid{};
  RPC_STATUS status = UuidCreate(&uuid);
  if ((status != RPC_S_OK) && (status != RPC_S_UUID_LOCAL_ONLY))
    CPJNSMTPConnection::ThrowPJNSMTPException(status, FACILITY_RPC);

  //Convert it to a string
#ifdef _UNICODE
  RPC_WSTR pszGuid = nullptr;
#else
  RPC_CSTR pszGuid = nullptr;
#endif //#ifdef _UNICODE
#pragma warning(suppress: 6102)
  status = UuidToString(&uuid, &pszGuid);
  if (status != RPC_S_OK)
    CPJNSMTPConnection::ThrowPJNSMTPException(status, FACILITY_RPC);

  //What will be the return value from this method
#pragma warning(suppress: 26490)
  CPJNSMTPString sGUID(reinterpret_cast<TCHAR*>(pszGuid));

  //Free up the temp memory
  RpcStringFree(&pszGuid);

  return sGUID;
}

void CPJNSMTPBodyPart::SetFilename(_In_ const CPJNSMTPString& sFilename)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(sFilename.length()); //You need to a valid Filename!

  //Hive away the filename and form the title from the filename
  ATL::CAtlString sPath;
  ATL::CAtlString sFname;
  ATL::CAtlString sExt;
  _tsplitpath_s(sFilename.c_str(), nullptr, 0, nullptr, 0, sFname.GetBuffer(_MAX_FNAME), _MAX_FNAME, sExt.GetBuffer(_MAX_EXT), _MAX_EXT);
  sExt.ReleaseBuffer();
  sFname.ReleaseBuffer();
  _tmakepath_s(sPath.GetBuffer(_MAX_PATH), _MAX_PATH, nullptr, nullptr, sFname, sExt);
  sPath.ReleaseBuffer();
  m_sFilename = sFilename;
  m_sTitle = sPath;

  //Also set the content type to be appropiate for an attachment
  m_sContentType = _T("application/octet-stream");

  //Also set the Content-Transfer-Encoding to base64
  SetContentTransferEncoding(CONTENT_TRANSFER_ENCODING::BASE64);

  //Also set the attachment setting
  m_bAttachment = true;
}

void CPJNSMTPBodyPart::SetRawBody(_In_z_ LPCSTR pszRawBody) noexcept
{
  m_pszRawBody = pszRawBody;
}

LPCSTR CPJNSMTPBodyPart::GetRawBody() noexcept
{
  return m_pszRawBody;
}

void CPJNSMTPBodyPart::SetText(_In_ const CPJNSMTPString& sText)
{
  m_sText = sText;

  //Ensure lines are correctly wrapped
  CPJNSMTPAddress::StringReplace(m_sText, _T("\r\n"), _T("\n"));
  CPJNSMTPAddress::StringReplace(m_sText, _T("\r"), _T("\n"));
  CPJNSMTPAddress::StringReplace(m_sText, _T("\n"), _T("\r\n"));

  //Also set the content type while we are at it
  m_sContentType = _T("text/plain");
}

void CPJNSMTPBodyPart::SetContentID(_In_z_ LPCTSTR pszContentID)
{
  m_sContentLocation.clear();
  m_sContentID = pszContentID; 
}

CPJNSMTPString CPJNSMTPBodyPart::GetContentID() const
{
  return m_sContentID;
}

void CPJNSMTPBodyPart::SetContentLocation(_In_z_ LPCTSTR pszContentLocation)
{
  m_sContentID.clear();
  m_sContentLocation = pszContentLocation;
}

CPJNSMTPString CPJNSMTPBodyPart::GetContentLocation() const
{
  return m_sContentLocation;
}

void CPJNSMTPBodyPart::SetAttachment(_In_ bool bAttachment) noexcept
{
  m_bAttachment = bAttachment;
  if (m_bAttachment)
    SetContentTransferEncoding(CONTENT_TRANSFER_ENCODING::BASE64);
}

CPJNSMTPAsciiString CPJNSMTPBodyPart::ConvertToUTF8(_In_z_ LPCTSTR pszText)
{
  //What will be the return value from this method
#ifdef _UNICODE
  return CPJNSMTPAsciiString(ATL::CW2A(pszText, CP_UTF8));
#else
  return CPJNSMTPAsciiString(ATL::CW2A(ATL::CA2W(pszText), CP_UTF8));
#endif
}

CPJNSMTPAsciiString CPJNSMTPBodyPart::GetHeader(_In_ const CPJNSMTPString& sRootCharset)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(m_pParentBodyPart != nullptr);

  CPJNSMTPString sHeaderT;
  if (m_bAttachment)
  {
    //Ok, it's an attachment

    //Check if filename title has non-ASCII characters
    bool bNeedTitleEscaping = false;
    for (CPJNSMTPString::size_type i=0; i<m_sTitle.length() && !bNeedTitleEscaping; i++)
    {
#pragma warning(suppress: 26446)
      const TCHAR cChar = m_sTitle[i];
      if (cChar <  _T(' ') || cChar > _T('~')) 
        bNeedTitleEscaping = true;
    }
    //Encode the title of the file if required
    CPJNSMTPString sTitle;
    if (bNeedTitleEscaping)
      sTitle = HeaderEncodeT(m_sTitle, sRootCharset);
    else
      sTitle = m_sTitle;

    //Form the header to go along with this body part
    if (m_ChildBodyParts.size())
    {
      switch (m_ContentTransferEncoding)
      {
        case CONTENT_TRANSFER_ENCODING::BASE64:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; name=\"") << sTitle << _T("\"; Boundary=\"") << m_sBoundary << _T("\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"") << sTitle << _T("\"\r\n");
          sHeaderT = ss.str();
          break;
        }
        case CONTENT_TRANSFER_ENCODING::QP:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; name=\"") << sTitle << _T("\"; Boundary=\"") << m_sBoundary << _T("\"\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: attachment; filename=\"") << sTitle << _T("\"\r\n");
          sHeaderT = ss.str();
          break;
        }
        default:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; name=\"") << sTitle << _T("\"; Boundary=\"") << m_sBoundary << _T("\"\r\nContent-Disposition: attachment; filename=\"") << sTitle << _T("\"\r\n");
          sHeaderT = ss.str();
          break;
        }
      }
    }
    else
    {
      switch (m_ContentTransferEncoding)
      {
        case CONTENT_TRANSFER_ENCODING::BASE64:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; name=\"") << sTitle << _T("\"\r\nContent-Transfer-Encoding: base64\r\nContent-Disposition: attachment; filename=\"") << sTitle << _T("\"\r\n");
          sHeaderT = ss.str();
          break;
        }
        case CONTENT_TRANSFER_ENCODING::QP:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; name=\"") << sTitle << _T("\"\r\nContent-Transfer-Encoding: quoted-printable\r\nContent-Disposition: attachment; filename=\"") << sTitle << _T("\"\r\n");
          sHeaderT = ss.str();
          break;
        }
        default:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; name=\"") << sTitle << _T("\"\r\nContent-Disposition: attachment; filename=\"") << sTitle << _T("\"\r\n");
          sHeaderT = ss.str();
          break;
        }
      }
    }
  }
  else
  {
    //ok, it's not an attachment

    //Form the header to go along with this body part
    if (m_ChildBodyParts.size())
    {
      switch (m_ContentTransferEncoding)
      {
        case CONTENT_TRANSFER_ENCODING::BASE64:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; charset=") << m_sCharset << _T("; Boundary=\"") << m_sBoundary << _T("\"\r\nContent-Transfer-Encoding: base64\r\n");
          sHeaderT = ss.str();
          break;
        }
        case CONTENT_TRANSFER_ENCODING::QP:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; charset=") << m_sCharset << _T("; Boundary=\"") << m_sBoundary << _T("\"\r\nContent-Transfer-Encoding: quoted-printable\r\n");
          sHeaderT = ss.str();
          break;
        }
        default:
        {  
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; charset=") << m_sCharset << _T("; Boundary=\"") << m_sBoundary << _T("\"\r\n");
          sHeaderT = ss.str();
          break;
        }
      }
    }
    else
    {
      switch (m_ContentTransferEncoding)
      {
        case CONTENT_TRANSFER_ENCODING::BASE64:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; charset=") << m_sCharset << _T("\r\nContent-Transfer-Encoding: base64\r\n");
          sHeaderT = ss.str();
          break;
        }
        case CONTENT_TRANSFER_ENCODING::QP:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; charset=") << m_sCharset << _T("\r\nContent-Transfer-Encoding: quoted-printable\r\n");
          sHeaderT = ss.str();
          break;
        }
        default:
        {
        #ifdef _UNICODE
          std::wostringstream ss;
        #else
          std::ostringstream ss;
        #endif //#ifdef _UNICODE
          ss << _T("\r\n--") << m_pParentBodyPart->m_sBoundary << _T("\r\nContent-Type: ") << m_sContentType << _T("; charset=") << m_sCharset << _T("\r\n");
          sHeaderT = ss.str();
          break;
        }
      }
    }
  }

  //Add the other headers
  if (m_sContentID.length())
  {
    CPJNSMTPString sLine(_T("Content-ID: "));
    sLine += m_sContentID;
    sLine += _T("\r\n");
    sHeaderT += sLine;
  }
  if (m_sContentLocation.length())
  {
    CPJNSMTPString sLine(_T("Content-Location: "));
    sLine += m_sContentLocation;
    sLine += _T("\r\n");
    sHeaderT += sLine;
  }
  sHeaderT += _T("\r\n");

#ifdef _UNICODE
  return CPJNSMTPAsciiString(ATL::CT2A(sHeaderT.c_str()));
#else
  return CPJNSMTPAsciiString(sHeaderT);
#endif //#ifdef _UNICODE
}

CPJNSMTPAsciiString CPJNSMTPBodyPart::GetBody(_In_ bool bDoSingleDotFix)
{
  //if the body is text we must convert it to the declared encoding, this could create some
  //problems since Windows conversion functions (such as WideCharToMultiByte) uses UINT
  //code page and not a String like HTTP uses.
  //For now we will add the support for UTF-8, to support other encodings we have to 
  //implement a mapping between the "internet name" of the encoding and its LCID(UINT)

  //What will be the return value from this method
  CPJNSMTPAsciiString sBodyA;
  
  if (m_pszRawBody != nullptr)
    sBodyA = m_pszRawBody;
  else if (m_sFilename.length())
  {
    //Ok, it's a file
    ATL::CAtlFile file;
    HRESULT hr = file.Create(m_sFilename.c_str(), GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN);
    if (FAILED(hr))
      CPJNSMTPConnection::ThrowPJNSMTPException(hr);

    //Get the length of the file (we only support sending files less than 2GB!)
    ULONGLONG nFileSize = 0;
    hr = file.GetSize(nFileSize);
    if (FAILED(hr))
      CPJNSMTPConnection::ThrowPJNSMTPException(hr);
    if (nFileSize >= INT_MAX)
      CPJNSMTPConnection::ThrowPJNSMTPException(ERROR_INVALID_DATA, FACILITY_WIN32);

    //Allocate some memory for the contents of the file
    std::vector<BYTE> body;
#pragma warning(suppress: 26472)
    body.resize(static_cast<size_t>(nFileSize + 1));

    //Read in the contents of the file
    DWORD dwBytesWritten = 0;
#pragma warning(suppress: 26472)
    hr = file.Read(body.data(), static_cast<DWORD>(nFileSize), dwBytesWritten);
    if (FAILED(hr))
      CPJNSMTPConnection::ThrowPJNSMTPException(hr);

    switch (m_ContentTransferEncoding)
    {
      case CONTENT_TRANSFER_ENCODING::BASE64:
      {
        //Do the encoding
        CPJNSMPTBase64Encode encode;
        encode.Encode(body.data(), dwBytesWritten, ATL_BASE64_FLAG_NONE);

        //Form the body for this body part
        sBodyA = encode.Result();
        break;
      }
      case CONTENT_TRANSFER_ENCODING::QP:
      {
        //Do the encoding
        CPJNSMPTQPEncode encode;
        encode.Encode(body.data(), dwBytesWritten, 0);

        //Form the body for this body part
        sBodyA = encode.Result();
        break;
      }
      default:
      {
        //Null terminate the data before we try to treat it as a char*
#pragma warning(suppress: 26446 26472)
        body[static_cast<size_t>(nFileSize)] = '\0';

        //The body of this body part is just the raw file contents
#pragma warning(suppress: 26490)
        sBodyA = reinterpret_cast<const char*>(body.data());
        break;
      }
    }
  }
  else
  {
    switch (m_ContentTransferEncoding)
    {
      case CONTENT_TRANSFER_ENCODING::BASE64:
      {
        //Do the UTF8 conversion if necessary
        CPJNSMTPAsciiString sBuff;
        if (_tcsicmp(m_sCharset.c_str(), _T("UTF-8")) == 0)
          sBuff = ConvertToUTF8(m_sText.c_str());
        else
        {
        #ifdef _UNICODE
          sBuff = ATL::CT2A(m_sText.c_str());
        #else
          sBuff = m_sText;
        #endif //#ifdef _UNICODE
        }

        //Do the encoding
        CPJNSMPTBase64Encode encode;
        encode.Encode(sBuff.c_str(), ATL_BASE64_FLAG_NONE);

        //Form the body for this body part
        sBodyA = encode.Result();
        break;
      }
      case CONTENT_TRANSFER_ENCODING::QP:
      {
        //Do the UTF8 conversion if necessary
        CPJNSMTPAsciiString sBuff;
        if (_tcsicmp(m_sCharset.c_str(), _T("UTF-8")) == 0)
          sBuff = ConvertToUTF8(m_sText.c_str());
        else
        {
        #ifdef _UNICODE
          sBuff = ATL::CT2A(m_sText.c_str());
        #else
          sBuff = m_sText;
        #endif //#ifdef _UNICODE
        }

        //Do the encoding
        CPJNSMPTQPEncode encode;
        encode.Encode(sBuff.c_str(), 0);
        sBodyA = encode.Result();
        if (bDoSingleDotFix)
          FixSingleDot(sBodyA);
        break;
      }
      default:
      {
        //Do the UTF8 conversion if necessary
        if (_tcsicmp(m_sCharset.c_str(), _T("UTF-8")) == 0)
          sBodyA = ConvertToUTF8(m_sText.c_str());
        else
        {
        #ifdef _UNICODE
          sBodyA = ATL::CT2A(m_sText.c_str());
        #else
          sBodyA = m_sText;
        #endif //#ifdef _UNICODE
        }

        //No encoding to do
        if (bDoSingleDotFix)
          FixSingleDot(sBodyA);
        break;
      }
    }
  }

  return sBodyA;
}

CPJNSMTPAsciiString CPJNSMTPBodyPart::GetFooter()
{
  //Form the MIME footer
  CPJNSMTPAsciiString sFooter;
  std::ostringstream ss;
#ifdef _UNICODE
  ss << "\r\n--" << ATL::CT2A(m_sBoundary.c_str()) << "--";
#else
  ss << "\r\n--" << m_sBoundary << "--";
#endif //#ifdef _UNICODE
  sFooter = ss.str();
  return sFooter;
}

CPJNSMTPBodyPart* CPJNSMTPBodyPart::GetParentBodyPart() noexcept
{
  return m_pParentBodyPart;
}

void CPJNSMTPBodyPart::FixSingleDot(_Inout_ CPJNSMTPAsciiString& sBody)
{
  //Handle a dot on the first line on its own (by replacing it with a double dot as referred to in 
  //section 4.5.2 of the SMTP RFC)
  if ((sBody.find(".\r\n") == 0) || (sBody == "."))
    sBody.insert(0, ".");

  //Also handle any subsequent single dot lines
  CPJNSMTPAddress::StringReplace(sBody, "\r\n.", "\r\n..");
}

CPJNSMTPBodyPart* CPJNSMTPBodyPart::FindFirstBodyPart(_In_ const CPJNSMTPString& sContentType)
{
#pragma warning(suppress: 26489)
  const auto iterFind = std::find_if(m_ChildBodyParts.begin(), m_ChildBodyParts.end(), [sContentType](const std::shared_ptr<CPJNSMTPBodyPart>& bodyPart) noexcept
    {
#pragma warning(suppress: 26489)
      return (bodyPart->m_sContentType == sContentType);
    });
  if (iterFind != m_ChildBodyParts.end())
#pragma warning(suppress: 26487)
    return iterFind->get();
  return nullptr;
}

CPJNSMTPAsciiString CPJNSMTPBodyPart::HeaderEncode(_In_ const CPJNSMTPString& sText, _In_ const CPJNSMTPString& sCharset)
{
  CPJNSMPTQEncode encode;

  //Do the UTF8 conversion if necessary
  CPJNSMTPAsciiString sLocalText;
  if (_tcsicmp(sCharset.c_str(), _T("UTF-8")) == 0)
    sLocalText = CPJNSMTPBodyPart::ConvertToUTF8(sText.c_str());
  else
  {
  #ifdef _UNICODE
    sLocalText = ATL::CT2A(sText.c_str());
  #else
    sLocalText = sText;
  #endif //#ifdef _UNICODE
  }
#ifdef _UNICODE
  encode.Encode(sLocalText.c_str(), ATL::CT2A(sCharset.c_str()));
#else
  encode.Encode(sLocalText.c_str(), sCharset.c_str());
#endif //#ifdef _UNICODE

  return CPJNSMTPAsciiString(encode.Result());
}

CPJNSMTPString CPJNSMTPBodyPart::HeaderEncodeT(_In_ const CPJNSMTPString& sText, _In_ const CPJNSMTPString& sCharset)
{
#ifdef _UNICODE
  return CPJNSMTPString(ATL::CA2T(HeaderEncode(sText, sCharset).c_str()));
#else
  return CPJNSMTPString(HeaderEncode(sText, sCharset));
#endif //#ifdef _UNICODE
}

//Note that this method does not absolutely ensure each line is a maximum of 78 characters
//long because if each line needs to be Q encoded, this will result in a variable expansion size 
//of the resultant encoded line. The function will most definitely not exceed the 998 
//characters limit though
CPJNSMTPAsciiString CPJNSMTPBodyPart::FoldSubjectHeader(_In_ const CPJNSMTPString& sSubject, _In_ const CPJNSMTPString& sCharset)
{
  //Do the UTF8 conversion if necessary
  CPJNSMTPAsciiString sLocalSubject;
  if (_tcsicmp(sCharset.c_str(), _T("UTF-8")) == 0)
    sLocalSubject = CPJNSMTPBodyPart::ConvertToUTF8(sSubject.c_str());
  else
  {
  #ifdef _UNICODE
    sLocalSubject = ATL::CT2A(sSubject.c_str());
  #else
    sLocalSubject = sSubject;
  #endif //#ifdef _UNICODE
  }

  //What will be the return value from this method
  CPJNSMTPAsciiString sOutputHeader("Subject: ");

  //Create an ASCII version of the charset string if required in the loop
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiCharset(ATL::CT2A(sCharset.c_str()));
#else
  CPJNSMTPAsciiString sAsciiCharset(sCharset);
#endif //#ifdef _UNICODE
#pragma warning(suppress: 26472)
  const int nCharsetLength = static_cast<int>(sAsciiCharset.length());

  //Fold the input string to a maximum of 78 characters per line
  LPCSTR pszLine = sLocalSubject.c_str();
#pragma warning(suppress: 26472)
  int nHeaderLeftLength = static_cast<int>(sLocalSubject.length());
  int nLineNumber = 0;
  while (nHeaderLeftLength > 78)
  {
    //Remember what line number we are working on. This is required 
    ++nLineNumber;
  
    //Calculate the line length to use for this line
    int nLineLength = 0;
    int nMaxLineLength = 78 - nCharsetLength - 7 - 9;
    if (nLineNumber != 1)
      --nMaxLineLength;
#pragma warning(suppress: 26477)
    ATLASSERT(nMaxLineLength > 0);

    if (nHeaderLeftLength > nMaxLineLength)
      nLineLength = nMaxLineLength;
    else
      nLineLength = nHeaderLeftLength;

    //copy the current line to a local buffer
    std::vector<char> sLineBuf;
    sLineBuf.resize(79);
#pragma warning(suppress: 26477)
    ATLASSUME(nLineLength < 79);
#pragma warning(suppress: 26486)
    strncpy_s(sLineBuf.data(), sLineBuf.size(), pszLine, nLineLength);
#pragma warning(suppress: 26446)
    sLineBuf[nLineLength] = '\0';

    //Prepare for the next time around
#pragma warning(suppress: 26481)
    pszLine += nLineLength;
    nHeaderLeftLength -= nLineLength;

    //Add the current line to the output parameter
    CPJNSMPTQEncode encode;
    encode.Encode(sLineBuf.data(), sAsciiCharset.c_str());
    sOutputHeader += encode.Result();

    //And prepare for folding the next line if we still have some header to process
    if (nHeaderLeftLength)
      sOutputHeader += "\r\n ";
  }

  //Finish up the remaining text left
  if (nHeaderLeftLength)
  {
    //Add the last line
    CPJNSMPTQEncode encode;
#pragma warning(suppress: 26486)
    encode.Encode(pszLine, sAsciiCharset.c_str());
    sOutputHeader += encode.Result();
  }

  return sOutputHeader;
}

void CPJNSMTPBodyPart::FixUpParentBodyPartPointers()
{
  for (auto& body : m_ChildBodyParts)
  {
    body->FixUpParentBodyPartPointers();
    body->m_pParentBodyPart = this;
  }
}


#pragma warning(suppress: 26455)
CPJNSMTPMessage::CPJNSMTPMessage() : m_sXMailer(_T("CPJNSMTPConnection v3.41")),
                                     m_Priority(PRIORITY::None),
                                     m_DSNReturnType(DSN_RETURN_TYPE::HeadersOnly),
                                     m_dwDSN(DSN_NOT_SPECIFIED),
                                     m_AddressHeaderEncoding(CPJNSMTPAddress::ENCODING::None),
                                     m_bMime(false)
{
}

void CPJNSMTPMessage::SetCharset(_In_z_ LPCTSTR sCharset)
{
  m_RootPart.SetCharset(sCharset);
}

CPJNSMTPString CPJNSMTPMessage::GetCharset() const
{
  return m_RootPart.GetCharset();
}

CPJNSMTPAsciiString CPJNSMTPMessage::FormDateHeader()
{
  //The static lookup arrays we use to form the day of week and month strings
  static std::array<const char*, 7> DOW{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  static std::array<const char*, 12> Month{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

  //Form the Timezone info which will form part of the Date header
  TIME_ZONE_INFORMATION tzi{};
  int nTZBias = 0;
  const DWORD dwTZI = GetTimeZoneInformation(&tzi);
  if (dwTZI == TIME_ZONE_ID_DAYLIGHT)
    nTZBias = tzi.Bias + tzi.DaylightBias;
  else if (dwTZI != TIME_ZONE_ID_INVALID)
    nTZBias = tzi.Bias;
  ATL::CAtlStringA sTZBias;
  sTZBias.Format("%+.2d%.2d", -nTZBias / 60, abs(nTZBias) % 60);

  //Get the local time
  SYSTEMTIME localTime;
  GetLocalTime(&localTime);

  //Validate the values in the SYSTEMTIME struct
#pragma warning(suppress: 26477)
  ATLASSUME(localTime.wMonth >= 1 && localTime.wMonth <= 12);
#pragma warning(suppress: 26477)
  ATLASSUME(localTime.wDayOfWeek <= 6);

  //Finally form the "Date:" header
  ATL::CAtlStringA sDate;
#pragma warning(suppress: 26446 26482)
  sDate.Format("Date: %s, %d %s %04d %02d:%02d:%02d %s\r\n", DOW[localTime.wDayOfWeek], static_cast<int>(localTime.wDay), Month[localTime.wMonth-1], static_cast<int>(localTime.wYear),
               static_cast<int>(localTime.wHour), static_cast<int>(localTime.wMinute), static_cast<int>(localTime.wSecond), sTZBias.GetString());
  return sDate.GetString();
}

CPJNSMTPAsciiString CPJNSMTPMessage::GetHeader()
{
  CPJNSMTPString sCharset(m_RootPart.GetCharset());

  //Add the From field. Note we do not support encoding mail address headers for compatibility purposes
  CPJNSMTPAsciiString sFrom("From: ");
  sFrom += m_From.GetRegularFormat(m_AddressHeaderEncoding, sCharset);
  CPJNSMTPAsciiString sHeader(sFrom);
  sHeader += "\r\n";
  
  //Add the "To:" field
  CPJNSMTPAsciiString sTo("To: ");
  const size_t nTo = m_To.size();
  for (size_t i=0; i<nTo; i++)
  {
#pragma warning(suppress: 26446)
    CPJNSMTPAsciiString sLine(m_To[i].GetRegularFormat(m_AddressHeaderEncoding, sCharset));
    if (i < (nTo - 1))
      sLine += ",";
    sTo += sLine;
    if (i < (nTo - 1))
      sTo += "\r\n ";
  }
  sHeader += sTo;
  sHeader += "\r\n";

  //Create the "Cc:" part of the header
  CPJNSMTPAsciiString sCc("Cc: ");
  const size_t nCC = m_CC.size();
  for (size_t i=0; i<nCC; i++)
  {
#pragma warning(suppress: 26446)
    CPJNSMTPAsciiString sLine(m_CC[i].GetRegularFormat(m_AddressHeaderEncoding, sCharset));
    if (i < (nCC - 1))
      sLine += ",";
    sCc += sLine;
    if (i < (nCC - 1))
      sCc += "\r\n ";
  }

  //Add the CC field if there is any CC recipients
  if (nCC)
  {
    sHeader += sCc;
    sHeader += "\r\n";
  }

  //add the subject
  CPJNSMTPAsciiString sSubject(CPJNSMTPBodyPart::FoldSubjectHeader(m_sSubject, sCharset));
  sHeader += sSubject;
  sHeader += "\r\n";

  //X-Mailer header
  if (m_sXMailer.length())
  {
    CPJNSMTPAsciiString sXMailer("X-Mailer: ");
  #ifdef _UNICODE
    sXMailer += ATL::CT2A(m_sXMailer.c_str());
  #else
    sXMailer += m_sXMailer;
  #endif //#ifdef _UNICODE
    sHeader += sXMailer;
    sHeader += "\r\n";
  }

  //Disposition-Notification-To header
  const size_t nMessageDispositionEmailAddresses = m_MessageDispositionEmailAddresses.size();
  if (nMessageDispositionEmailAddresses)
  {
    CPJNSMTPAsciiString sMDN("Disposition-Notification-To: ");
    for (size_t i=0; i<nMessageDispositionEmailAddresses; i++)
    {
    #ifdef _UNICODE
#pragma warning(suppress: 26446)
      sMDN += ATL::CT2A(m_MessageDispositionEmailAddresses[i].c_str());
    #else
#pragma warning(suppress: 26446)
      sMDN += m_MessageDispositionEmailAddresses[i];
    #endif //#ifdef _UNICODE
      if (i < (nMessageDispositionEmailAddresses - 1))
        sMDN += ",\r\n ";
    }
    sHeader += sMDN;
    sHeader += "\r\n";
  }

  //Add the Mime header if needed
  if (m_bMime)
  {
    if (m_RootPart.m_ChildBodyParts.size())
    {
      CPJNSMTPAsciiString sPartHeader;
      std::ostringstream ss;
    #ifdef _UNICODE
      ss << "MIME-Version: 1.0\r\nContent-Type: " << ATL::CT2A(m_RootPart.GetContentType().c_str()) << "; boundary=\"" << ATL::CT2A(m_RootPart.GetBoundary().c_str()) << "\"\r\n";
    #else
      ss << "MIME-Version: 1.0\r\nContent-Type: " << m_RootPart.GetContentType() << "; boundary=\"" << m_RootPart.GetBoundary() << "\"\r\n";
    #endif //#ifdef _UNICODE
      sPartHeader = ss.str();
      sHeader += sPartHeader;
    }
    else
    {
      CPJNSMTPAsciiString sPartHeader;
      std::ostringstream ss;
    #ifdef _UNICODE
      ss << "MIME-Version: 1.0\r\nContent-Type: " << ATL::CT2A(m_RootPart.GetContentType().c_str()) << "\r\n";
    #else
      ss << "MIME-Version: 1.0\r\nContent-Type: " << m_RootPart.GetContentType() << "\r\n";
    #endif //#ifdef _UNICODE
      sPartHeader = ss.str();
      sHeader += sPartHeader;
    }
  }
  else
  {
    CPJNSMTPAsciiString sPartHeader;
    std::ostringstream ss;
  #ifdef _UNICODE
    ss << "Content-Type: " << ATL::CT2A(m_RootPart.GetContentType().c_str()) << ";\r\n\tcharset=" << ATL::CT2A(m_RootPart.GetCharset().c_str()) << "\r\n";
  #else
    ss << "Content-Type: " << m_RootPart.GetContentType() << ";\r\n\tcharset=" << m_RootPart.GetCharset() << "\r\n";
  #endif //#ifdef _UNICODE
    sPartHeader = ss.str();
    sHeader += sPartHeader;
  }

  //Add the optional Reply-To header if needed
  const size_t nReplyTo = m_ReplyTo.size();
  if (nReplyTo)
  {
    CPJNSMTPAsciiString sReplyTo("Reply-To: ");
    for (size_t i=0; i<nReplyTo; i++)
    {
#pragma warning(suppress: 26446 26477)
      ATLASSERT(m_ReplyTo[i].m_sEmailAddress.length());
    #ifdef _UNICODE
#pragma warning(suppress: 26446)
      sReplyTo += ATL::CT2A(m_ReplyTo[i].m_sEmailAddress.c_str());
    #else
#pragma warning(suppress: 26446)
      sReplyTo += m_ReplyTo[i].m_sEmailAddress;
    #endif //#ifdef _UNICODE
      if (i < (nReplyTo - 1))
        sReplyTo += ",\r\n ";
    }

    sHeader += sReplyTo;
    sHeader += "\r\n";
  }

  //Date header
  sHeader += FormDateHeader();

  //The Priorty header
  switch (m_Priority)
  {
    case PRIORITY::None:
    {
      break;
    }
    case PRIORITY::Low:
    {
      sHeader += "X-Priority: 5\r\n";
      break;
    }
    case PRIORITY::Normal:
    {
      sHeader += "X-Priority: 3\r\n";
      break;
    }
    case PRIORITY::High:
    {
      sHeader += "X-Priority: 1\r\n";
      break;
    }
    default:
    {
#pragma warning(suppress: 26477)
      ATLASSERT(false);
      break;
    }
  }

  //Add the custom headers
  for (const auto& header : m_CustomHeaders)
  {
  #ifdef _UNICODE
    sHeader += ATL::CT2A(header.c_str());
  #else
    sHeader += header;
  #endif //#ifdef _UNICODE

    //Add line separators for each header
    sHeader += "\r\n";
  }

  //Return the result
  return sHeader;
}

size_t CPJNSMTPMessage::ParseMultipleRecipients(_In_z_ LPCTSTR pszRecipients, _Inout_ std::vector<CPJNSMTPAddress>& recipients)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszRecipients != nullptr);

  //Empty out the array
  recipients.clear();

  //Loop through the whole string, adding recipients as they are encountered
  CPJNSMTPString sRecipients(pszRecipients);
#pragma warning(suppress: 26429 26446)
  TCHAR* pszbuf = &(sRecipients[0]);
#pragma warning(suppress: 26472)
  const int nLength = static_cast<int>(sRecipients.length());
#pragma warning(suppress: 26477)
  ATLASSERT(nLength); //An empty string is now allowed

  bool bLeftQuotationMark = false;
  for (int pos=0, start=0; pos<=nLength; pos++)
  {
#pragma warning(suppress: 26481)
    if (bLeftQuotationMark && pszbuf[pos] != _T('"') )
      continue;
#pragma warning(suppress: 26481)
    if (bLeftQuotationMark && pszbuf[pos] == _T('"'))
    {
      bLeftQuotationMark = false;
      continue;
    }
#pragma warning(suppress: 26481)
    if (pszbuf[pos] == _T('"'))
    {
      bLeftQuotationMark = true;
      continue;
    }
  
    //Valid separators between addresses are ',' or ';'
#pragma warning(suppress: 26481)
    if ((pszbuf[pos] == _T(',')) || (pszbuf[pos] == _T(';')) || (pszbuf[pos] == 0))
    {
#pragma warning(suppress: 26481)
      pszbuf[pos] = 0; //Redundant when at the end of string, but who cares.
#pragma warning(suppress: 26481)
      CPJNSMTPString sTemp(&(pszbuf[start]));
      CPJNSMTPAddress::StringTrim(sTemp);

      //Let the CPJNSMTPAddress constructor do its work
      CPJNSMTPAddress To(sTemp.c_str());
#pragma warning(suppress: 26489)
      if (To.m_sEmailAddress.length())
#pragma warning(suppress: 26486)
        recipients.push_back(To);

      //Move on to the next position
      start = pos + 1;
    }
  }

  //Return the number of recipients parsed
#pragma warning(suppress: 26489)
  return recipients.size();
}

std::shared_ptr<CPJNSMTPBodyPart>& CPJNSMTPMessage::AddAttachment(_In_z_ LPCTSTR pszAttachment)
{
  std::shared_ptr<CPJNSMTPBodyPart> attachment(std::make_shared<CPJNSMTPBodyPart>());
#pragma warning(suppress: 26486)
  attachment->SetFilename(pszAttachment);
  SetMime(true);
  m_RootPart.m_ChildBodyParts.push_back(attachment);
#pragma warning(suppress: 26446 26487 26489)
  return m_RootPart.m_ChildBodyParts[m_RootPart.m_ChildBodyParts.size() - 1];
}

int CPJNSMTPMessage::AddMultipleAttachments(_In_z_ LPCTSTR pszAttachments)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszAttachments != nullptr);

  //What will be the return value from this method
  int nAttachments = 0;

  //Loop through the whole string, adding attachments as they are encountered
  CPJNSMTPString sAttachments(pszAttachments);
#pragma warning(suppress: 26429 26446)
  TCHAR* pszbuf = &(sAttachments[0]);
#pragma warning(suppress: 26472)
  const int nLength = static_cast<int>(sAttachments.length());
#pragma warning(suppress: 26477)
  ATLASSERT(nLength); //An empty string is now allowed

  for (int pos=0, start=0; pos<=nLength; pos++)
  {
    //Valid separators between attachments are ',' or ';'
#pragma warning(suppress: 26481)
    if ((pszbuf[pos] == _T(',')) || (pszbuf[pos] == _T(';')) || (pszbuf[pos] == 0))
    {
#pragma warning(suppress: 26481)
      pszbuf[pos] = 0; //Redundant when at the end of string, but who cares.
#pragma warning(suppress: 26481)
      CPJNSMTPString sTemp(&(pszbuf[start]));

      //Finally add the new attachment to the array of attachments
      CPJNSMTPAddress::StringTrim(sTemp);
      if (sTemp.length())
      {
        AddAttachment(sTemp.c_str());
        ++nAttachments;
      }

      //Move on to the next position
      start = pos + 1;
    }
  }

  return nAttachments;
}

void CPJNSMTPMessage::AddTextBody(_In_z_ LPCTSTR pszBody, _In_z_ LPCTSTR pszRootMIMEType)
{
  if (m_bMime)
  {
    CPJNSMTPBodyPart* pTextBodyPart = m_RootPart.FindFirstBodyPart(_T("text/plain"));
    if (pTextBodyPart != nullptr)
      pTextBodyPart->SetText(pszBody);
    else
    {
      //Create a text body part
      CPJNSMTPBodyPart oldRoot(m_RootPart);

      //Reset the root body part
      m_RootPart.SetCharset(oldRoot.GetCharset().c_str());
      m_RootPart.SetText(_T("This is a multi-part message in MIME format"));
      m_RootPart.SetContentType(pszRootMIMEType);

      //Just add the text/plain body part (directly to the root)
      std::shared_ptr<CPJNSMTPBodyPart> text(std::make_shared<CPJNSMTPBodyPart>());
#pragma warning(suppress: 26486)
      text->SetCharset(oldRoot.GetCharset().c_str());
#pragma warning(suppress: 26486)
      text->SetText(pszBody);

      //Hook everything up to the root body part
#pragma warning(suppress: 26444)
      m_RootPart.m_ChildBodyParts.insert(m_RootPart.m_ChildBodyParts.begin(), text);
    }
  }
  else
  {
    //Let the body part class do all the work
    m_RootPart.SetText(pszBody);
  }
}

void CPJNSMTPMessage::AddHTMLBody(_In_z_ LPCTSTR pszBody, _In_z_ LPCTSTR pszRootMIMEType)
{
  if (m_bMime)
  {
    CPJNSMTPBodyPart* pHtmlBodyPart = m_RootPart.FindFirstBodyPart(_T("text/html"));
    if (pHtmlBodyPart != nullptr)
      pHtmlBodyPart->SetText(pszBody);
    else
    {
      //Remember some of the old root settings before we write over it
      CPJNSMTPBodyPart oldRoot(m_RootPart);

      //Reset the root body part
      m_RootPart.SetCharset(oldRoot.GetCharset().c_str());
      m_RootPart.SetText(_T("This is a multi-part message in MIME format"));
      m_RootPart.SetContentType(pszRootMIMEType);

      //Just add the text/html body part (directly to the root)
      std::shared_ptr<CPJNSMTPBodyPart> html(std::make_shared<CPJNSMTPBodyPart>());
#pragma warning(suppress: 26486)
      html->SetCharset(oldRoot.GetCharset().c_str());
#pragma warning(suppress: 26486)
      html->SetText(pszBody);
#pragma warning(suppress: 26486)
      html->SetContentType(_T("text/html"));

      //Hook everything up to the root body part
#pragma warning(suppress: 26444)
      m_RootPart.m_ChildBodyParts.insert(m_RootPart.m_ChildBodyParts.begin(), html);
    }
  }
  else
  {
    m_RootPart.SetText(pszBody);
    m_RootPart.SetContentType(_T("text/html"));
  }
}

CPJNSMTPString CPJNSMTPMessage::GetHTMLBody()
{
  //What will be the return value from this method
  CPJNSMTPString sRet;

  if (m_RootPart.m_ChildBodyParts.size())
  {
#pragma warning(suppress: 26446)
    const CPJNSMTPBodyPart* pHtml = m_RootPart.m_ChildBodyParts[0].get();
#pragma warning(suppress: 26477)
    ATLASSUME(pHtml != nullptr);
    if (pHtml->GetContentType() == _T("text/html"))
      sRet = pHtml->GetText();
  }
  return sRet;
}

CPJNSMTPString CPJNSMTPMessage::GetTextBody()
{
  return m_RootPart.GetText();
}

void CPJNSMTPMessage::SetMime(bool bMime)
{
  if (m_bMime != bMime)
  {
    m_bMime = bMime;

    //Reset the body body parts
    m_RootPart.m_ChildBodyParts.clear();

    if (bMime)
    {
      CPJNSMTPString sText(GetTextBody());

      //Remember some of the old root settings before we write over it
      CPJNSMTPBodyPart oldRoot(m_RootPart);

      //Reset the root body part
      m_RootPart.SetCharset(oldRoot.GetCharset().c_str());
      m_RootPart.SetText(_T("This is a multi-part message in MIME format"));
      m_RootPart.SetContentType(_T("multipart/mixed"));

      //Also readd the body if non - empty
      if (sText.length())
        AddTextBody(sText.c_str());
    }
  }
}

void CPJNSMTPMessage::SaveToDisk(_In_z_ LPCTSTR pszFilename)
{
  //Open the file for writing
  ATL::CAtlFile file;
  HRESULT hr = file.Create(pszFilename, GENERIC_WRITE, 0, OPEN_ALWAYS);
  if (FAILED(hr))
    CPJNSMTPConnection::ThrowPJNSMTPException(hr);

  //Set the file back to 0 in size
  hr = file.SetSize(0);
  if (FAILED(hr))
    CPJNSMTPConnection::ThrowPJNSMTPException(hr);

  //Fix up all the body part pointers
  m_RootPart.FixUpParentBodyPartPointers();

  //Write out the Message Header
  CPJNSMTPAsciiString sHeader(GetHeader());
#pragma warning(suppress: 26472)
  hr = file.Write(sHeader.c_str(), static_cast<DWORD>(sHeader.length()));
  if (FAILED(hr))
    CPJNSMTPConnection::ThrowPJNSMTPException(hr);

  //Write out the separator
  hr = file.Write("\r\n", 2);
  if (FAILED(hr))
    CPJNSMTPConnection::ThrowPJNSMTPException(hr);

  //Write out the rest of the message
  if (m_RootPart.m_ChildBodyParts.size() || m_bMime)
  {
    //Write the root body part (and all its children)
    WriteToDisk(file, &m_RootPart, true, m_RootPart.GetCharset());
  }
  else
  {
    //Send the body
  #ifdef _UNICODE
    CPJNSMTPAsciiString sBody(ATL::CT2A(m_RootPart.GetText().c_str()));
  #else
    CPJNSMTPAsciiString sBody(m_RootPart.GetText());
  #endif //#ifdef _UNICODE
#pragma warning(suppress: 26472)
    hr = file.Write(sBody.c_str(), static_cast<DWORD>(sBody.length()));
    if (FAILED(hr))
      CPJNSMTPConnection::ThrowPJNSMTPException(hr);
  }
}

void CPJNSMTPMessage::WriteToDisk(ATL::CAtlFile& file, CPJNSMTPBodyPart* pBodyPart, bool bRoot, const CPJNSMTPString& sRootCharset)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSUME(pBodyPart != nullptr);

  if (!bRoot)
  {
    //First send this body parts header
#pragma warning(suppress: 26486)
    CPJNSMTPAsciiString sHeader(pBodyPart->GetHeader(sRootCharset));
#pragma warning(suppress: 26472)
    const HRESULT hr = file.Write(sHeader.c_str(), static_cast<DWORD>(sHeader.length()));
    if (FAILED(hr))
      CPJNSMTPConnection::ThrowPJNSMTPException(hr);
  }

  //Then the body parts body
#pragma warning(suppress: 26486)
  CPJNSMTPAsciiString sBody(pBodyPart->GetBody(false));
#pragma warning(suppress: 26472)
  HRESULT hr = file.Write(sBody.c_str(), static_cast<DWORD>(sBody.length()));
  if (FAILED(hr))
    CPJNSMTPConnection::ThrowPJNSMTPException(hr);

  //Recursively save all the child body parts
#pragma warning(suppress: 26486 26489)
  for (const auto& childBodyPart : pBodyPart->m_ChildBodyParts)
    WriteToDisk(file, childBodyPart.get(), false, sRootCharset);

  //Then the MIME footer if need be
#pragma warning(suppress: 26486)
  const bool bSendFooter = (pBodyPart->m_ChildBodyParts.size() != 0);
  if (bSendFooter)
  {
#pragma warning(suppress: 26486)
    CPJNSMTPAsciiString sFooter(pBodyPart->GetFooter());
#pragma warning(suppress: 26472)
    hr = file.Write(sFooter.c_str(), static_cast<DWORD>(sFooter.length()));
    if (FAILED(hr))
      CPJNSMTPConnection::ThrowPJNSMTPException(hr);
  }
}


#pragma warning(suppress: 26455)
CPJNSMTPConnection::CPJNSMTPConnection() : m_ConnectionType(ConnectionType::PlainText),
                                           m_bConnected(false),
                                           m_bDoEHLO(true),
                                           m_dwTimeout(60000),
                                           m_nLastCommandResponseCode(0),
                                           m_SSLProtocol(SSLProtocol::OSDefault),
                                           m_bCanDoDSN(false),
                                           m_bCanDoSTARTTLS(false),
                                           m_bCanDoSMTPUTF8(false),
                                           m_bDoInitialClientResponse(false),
                                           m_bUsePunyCode(true)
{
  SetHeloHostname(_T("auto"));
}

void CPJNSMTPConnection::ThrowPJNSMTPException(_In_ DWORD dwError, _In_ DWORD dwFacility, _In_ const CPJNSMTPString& sLastResponse)
{
  if (dwError == 0)
    dwError = WSAGetLastError();

  CPJNSMTPException e(dwError, dwFacility, sLastResponse);
  ATLTRACE(_T("Warning: throwing CPJNSMTPException for error %08X\n"), e.m_hr);
  throw e;
}

void CPJNSMTPConnection::ThrowPJNSMTPException(_In_ HRESULT hr, _In_ const CPJNSMTPString& sLastResponse)
{
  CPJNSMTPException e(hr, sLastResponse);
  ATLTRACE(_T("Warning: throwing CPJNSMTPException for error %08X\n"), e.m_hr);
  throw e;
}

void CPJNSMTPConnection::SetHeloHostname(_In_z_ LPCTSTR pszHostname)
{
  //retrieve the localhost name if we are supplied the magic "auto" host name
  if (_tcscmp(pszHostname, _T("auto")) == 0)
  {
    //retrieve the localhost name
    ATL::CAtlStringA sHostName;
    const int nGHN = gethostname(sHostName.GetBuffer(_MAX_PATH), _MAX_PATH);
    sHostName.ReleaseBuffer();
    if (nGHN == 0)
    {
    #ifdef _UNICODE
      m_sHeloHostname = ATL::CA2T(sHostName);
    #else
      m_sHeloHostname = sHostName;
    #endif //#ifdef _UNICODE
    }
    else
      m_sHeloHostname = pszHostname;
  }
  else
    m_sHeloHostname = pszHostname;
}

void CPJNSMTPConnection::_Send(const void* pBuffer, int nBuf)
{
  TraceCommunication("Send:", pBuffer, nBuf);
#ifndef CPJNSMTP_NOSSL
  if (GetSocket() != INVALID_SOCKET)
  {
    const SECURITY_STATUS ss = SendEncrypted(static_cast<const BYTE*>(pBuffer), nBuf);
    if (ss != SEC_E_OK)
      ThrowPJNSMTPException(ss);
  }
  else
#endif //#ifndef CPJNSMTP_NOSSL
    m_Socket.Send(pBuffer, nBuf);
}

#ifndef CPJNSMTP_NOSSL
void CPJNSMTPConnection::CreateSSLSocket()
{
  //Acquire the credentials
  if (m_SSLCredentials.ValidHandle())
    m_SSLCredentials.Free();
  switch (m_SSLProtocol)
  {
    case SSLProtocol::SSLv2:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = SP_PROT_SSL2_CLIENT;
       break;
    }
    case SSLProtocol::SSLv3:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = SP_PROT_SSL3_CLIENT;
       break;
    }
    case SSLProtocol::SSLv2orv3:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = SP_PROT_SSL2_CLIENT | SP_PROT_SSL3_CLIENT;
       break;
    }
    case SSLProtocol::TLSv1:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = SP_PROT_TLS1_0_CLIENT;
       break;
    }
    case SSLProtocol::TLSv1_1:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = SP_PROT_TLS1_1_CLIENT;
       break;
    }
    case SSLProtocol::TLSv1_2:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = SP_PROT_TLS1_2_CLIENT;
       break;
    }
    case SSLProtocol::OSDefault:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = 0;
       break;
    }
    case SSLProtocol::AnyTLS:
    {
       m_SSLCredentials.m_sslCredentials.cSupportedAlgs = SP_PROT_TLS1_0_CLIENT | SP_PROT_TLS1_1_CLIENT | SP_PROT_TLS1_2_CLIENT;
       break;
    }
    default:
    {
#pragma warning(suppress: 26477)
      ATLASSERT(false);
      break;
    }
  }

  const SECURITY_STATUS ss = m_SSLCredentials.AcquireClient();
  if (ss != SEC_E_OK)
    ThrowPJNSMTPException(ss);

  //Setup the SSLWrappers::CSocket instance
  if (ValidHandle())
    Delete();
  SetCachedCredentials(&m_SSLCredentials);
  SetReadTimeout(m_dwTimeout);
  SetWriteTimeout(m_dwTimeout);
  Attach(m_Socket);
}
#endif //#ifdef CPJNSMTP_NOSSL

void CPJNSMTPConnection::Connect(_In_z_ LPCTSTR pszHostName, _In_ AuthenticationMethod am, _In_opt_z_ LPCTSTR pszUsername, _In_opt_z_ LPCTSTR pszPassword, _In_ int nPort, _In_ ConnectionType connectionType)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszHostName != nullptr);
#pragma warning(suppress: 26477)
  ATLASSERT(!m_bConnected);

#ifndef CPJNSMTP_NOSSL
  m_ConnectionType = connectionType;
#else
  m_ConnectionType = ConnectionType::PlainText;
#endif //#ifndef CPJNSMTP_NOSSL
  m_sHostName = pszHostName;

  try
  {
#ifdef CWSOCKET_MFC_EXTENSIONS
    m_Socket.SetBindAddress(CWSocket::String(m_sBindAddress.c_str()));
#else
    m_Socket.SetBindAddress(m_sBindAddress);
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS
    m_Socket.CreateAndConnect(pszHostName, nPort, SOCK_STREAM, AF_INET);

  #ifndef CPJNSMTP_NOSSL
    if (m_ConnectionType == ConnectionType::SSL_TLS)
    {
      CreateSSLSocket();
      const SECURITY_STATUS ss = SSLConnect(pszHostName);
      if (ss != SEC_E_OK)
      {
        Detach();

        //Disconnect before we throw the exception
        try
        {
          Disconnect(false);
        }
        catch(CPJNSMTPException&)
        {
        }
        ThrowPJNSMTPException(ss);
      }
    }
  #endif //#ifdef CPJNSMTP_NOSSL
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    const DWORD dwError = e.m_nError;
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

    //Disconnect before we rethrow the exception
    try
    {
      Disconnect(true);
    }
    catch(CPJNSMTPException&)
    {
    }

    //rethrow the exception
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }

  //We're now connected
  m_bConnected = true;

  try
  {
    //check the response to the login
    if (!ReadCommandResponse(220))
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

    //Negotiate Extended SMTP connection if required
    if (m_bDoEHLO)
    {
#pragma warning(suppress: 26477)
      ATLASSUME(pszUsername != nullptr);
#pragma warning(suppress: 26477)
      ATLASSUME(pszPassword != nullptr);

      ConnectESMTP(m_sHeloHostname.c_str(), pszUsername, pszPassword, am);
    }
    else
      ConnectSMTP(m_sHeloHostname.c_str());
  }
#pragma warning(suppress: 26496)
  catch(CPJNSMTPException& e)
  {
    //Disconnect before we rethrow the exception
    try
    {
      Disconnect(true);
    }
    catch(CPJNSMTPException&)
    {
    }

    const HRESULT hr = e.m_hr;
    CPJNSMTPString sLastResponse(e.m_sLastResponse);

    //rethrow the exception
    ThrowPJNSMTPException(hr, sLastResponse);
  }
}

CPJNSMTPConnection::AuthenticationMethod CPJNSMTPConnection::ChooseAuthenticationMethod(_In_z_ LPCTSTR pszAuthMethods) noexcept
{
  //What will be the return value from this method
  AuthenticationMethod am = AuthenticationMethod::AUTH_AUTO;

  //Decide on which protocol to choose. Note we deliberately do not auto pick XOAUTH2 even if it is supported because
  //it requires some additional ahead of time setup work. If you really want to use XOAUTH2 authentication, then just
  //explicitly pick it as the authentication mechanism
  if (_tcsstr(pszAuthMethods, _T("NTLM")) != nullptr)
    am = AuthenticationMethod::AUTH_NTLM;
  else if (_tcsstr(pszAuthMethods, _T("CRAM-MD5")) != nullptr)
    am = AuthenticationMethod::AUTH_CRAM_MD5;
  else if (_tcsstr(pszAuthMethods, _T("LOGIN")) != nullptr)
    am = AuthenticationMethod::AUTH_LOGIN;
  else if (_tcsstr(pszAuthMethods, _T("PLAIN")) != nullptr)
    am = AuthenticationMethod::AUTH_PLAIN;
  else 
    am = AuthenticationMethod::AUTH_NONE;
  return am;
}

//This method implmements the STARTTLS functionality
#ifndef CPJNSMTP_NOSSL
void CPJNSMTPConnection::DoSTARTTLS()
{
  static const char* szStart = "STARTTLS\r\n";
 
  //Lets issue the STARTTLS command
  try
  {
#pragma warning(suppress: 26472 26485)
    _Send(szStart, static_cast<int>(strlen(szStart)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462 26429)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Check the response
  if (!ReadCommandResponse(220))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Hook up the SSL socket at this point
  CreateSSLSocket();

  //Do the SSL connect
  const SECURITY_STATUS ss = SSLConnect(m_sHostName.c_str());
  if (ss != SEC_E_OK)
  {
    Detach();
    ThrowPJNSMTPException(ss);
  }
}
#endif //#ifdef CPJNSMTP_NOSSL

void CPJNSMTPConnection::SendEHLO(_In_z_ LPCTSTR pszLocalName, _Inout_ CPJNSMTPString& sLastResponse)
{
  //Send the EHLO command
  CPJNSMTPAsciiString sBuf;
  std::ostringstream ss;
#ifdef _UNICODE
  ss << "EHLO " << ATL::CT2A(pszLocalName) << "\r\n";
#else
  ss << "EHLO " << pszLocalName << "\r\n";
#endif //#ifdef _UNICODE
  sBuf = ss.str();
  try
  {
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the EHLO command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  sLastResponse = GetLastCommandResponse();
}

//This method connects using RFC1869 ESMTP 
void CPJNSMTPConnection::ConnectESMTP(_In_z_ LPCTSTR pszLocalName, 
                                      _When_(am == AuthenticationMethod::AUTH_CRAM_MD5, _In_z_)
                                      _When_(am == AuthenticationMethod::AUTH_LOGIN, _In_z_)
                                      _When_(am == AuthenticationMethod::AUTH_PLAIN, _In_z_)
                                      _When_(am == AuthenticationMethod::AUTH_XOAUTH2, _In_z_)
                                      _When_((am != AuthenticationMethod::AUTH_CRAM_MD5) && (am != AuthenticationMethod::AUTH_LOGIN) && (am != AuthenticationMethod::AUTH_PLAIN) && (am != AuthenticationMethod::AUTH_XOAUTH2), _In_opt_z_)
                                      LPCTSTR pszUsername, 
                                      _When_(am == AuthenticationMethod::AUTH_CRAM_MD5, _In_z_)
                                      _When_(am == AuthenticationMethod::AUTH_LOGIN, _In_z_)
                                      _When_(am == AuthenticationMethod::AUTH_PLAIN, _In_z_)
                                      _When_(am == AuthenticationMethod::AUTH_XOAUTH2, _In_z_)
                                      _When_((am != AuthenticationMethod::AUTH_CRAM_MD5) && (am != AuthenticationMethod::AUTH_LOGIN) && (am != AuthenticationMethod::AUTH_PLAIN) && (am != AuthenticationMethod::AUTH_XOAUTH2), _In_opt_z_)
                                      LPCTSTR pszPassword, _In_ AuthenticationMethod am)
{
  //Send the EHLO command
  CPJNSMTPString sResponse;
  SendEHLO(pszLocalName, sResponse);
#pragma warning(suppress: 26489)
  CPJNSMTPString sUpperCaseResponse(sResponse);
#pragma warning(suppress: 26472 26444)
  std::transform(sUpperCaseResponse.begin(), sUpperCaseResponse.end(), sUpperCaseResponse.begin(), [](TCHAR c) noexcept -> TCHAR { return static_cast<TCHAR>(_totupper(c)); });

  //Check the server capabilities
  m_bCanDoDSN = (sUpperCaseResponse.find(_T("250-DSN\r\n")) != CPJNSMTPString::npos) || (sUpperCaseResponse.find(_T("250 DSN\r\n")) != CPJNSMTPString::npos);
  m_bCanDoSTARTTLS = (sUpperCaseResponse.find(_T("250-STARTTLS\r\n")) != CPJNSMTPString::npos) || (sUpperCaseResponse.find(_T("250 STARTTLS\r\n")) != CPJNSMTPString::npos);
  m_bCanDoSMTPUTF8 = (sUpperCaseResponse.find(_T("250-SMTPUTF8\r\n")) != CPJNSMTPString::npos) || (sUpperCaseResponse.find(_T("250 SMTPUTF8\r\n")) != CPJNSMTPString::npos);

  //Check to see if we should do STARTTLS
#ifndef CPJNSMTP_NOSSL
  bool bDoSTARTTLS = false;
  switch (m_ConnectionType)
  {
    case ConnectionType::STARTTLS:
    {
      //Yes we will be explicitly doing STARTTLS
      bDoSTARTTLS = true;
      break;
    }
    case ConnectionType::AutoUpgradeToSTARTTLS:
    {
      //Check the response to see if the server supports STARTTLS
      bDoSTARTTLS = m_bCanDoSTARTTLS;
      break;
    }
    default:
    {
      break;
    }
  }

  //Do STARTTLS if required to do so
  if (bDoSTARTTLS)
  {
    DoSTARTTLS();

    //Reissue the EHLO command to ensure we see the most up to date response values after we have done a STARTTLS
    SendEHLO(pszLocalName, sResponse);
    sUpperCaseResponse = sResponse;
#pragma warning(suppress: 26472 26444)
    std::transform(sUpperCaseResponse.begin(), sUpperCaseResponse.end(), sUpperCaseResponse.begin(), [](TCHAR c) noexcept -> TCHAR { return static_cast<TCHAR>(_totupper(c)); });
  }
#endif //#ifdef CPJNSMTP_NOSSL

  //If we are doing Auto authentication, then find the supported protocols of the SMTP server, then pick and use one
  if (am == AuthenticationMethod::AUTH_AUTO)
  {
    //Find the authentication methods supported by the SMTP server
    CPJNSMTPString::size_type nPosBegin = sUpperCaseResponse.find(_T("250-AUTH "));
    if (nPosBegin == CPJNSMTPString::npos)
      nPosBegin = sUpperCaseResponse.find(_T("250 AUTH "));
    if (nPosBegin != CPJNSMTPString::npos)
    {
      CPJNSMTPString sAuthMethods;
      const CPJNSMTPString::size_type nPosEnd = sUpperCaseResponse.find(_T("\r\n"), nPosBegin);
      if (nPosEnd != CPJNSMTPString::npos)
        sAuthMethods = sUpperCaseResponse.substr(nPosBegin + 9, nPosEnd - nPosBegin - 9);
      else
        sAuthMethods = sUpperCaseResponse.substr(nPosBegin + 9);

      //Now decide which protocol to choose via the virtual function to allow further client customization
      am = ChooseAuthenticationMethod(sAuthMethods.c_str());
    }
    else
    {
      //If AUTH is not found the server does not support authentication
      am = AuthenticationMethod::AUTH_NONE;
    }
  }

  //What we do next depends on what authentication protocol we are using
  switch (am)
  {
    case AuthenticationMethod::AUTH_CRAM_MD5:
    {
#pragma warning(suppress: 26477)
      ATLASSUME(pszUsername != nullptr);
#pragma warning(suppress: 26477)
      ATLASSUME(pszPassword != nullptr);

      AuthCramMD5(pszUsername, pszPassword);
      break;
    }
    case AuthenticationMethod::AUTH_LOGIN:
    {
#pragma warning(suppress: 26477)
      ATLASSUME(pszUsername != nullptr);
#pragma warning(suppress: 26477)
      ATLASSUME(pszPassword != nullptr);

      AuthLogin(pszUsername, pszPassword);
      break;
    }
    case AuthenticationMethod::AUTH_PLAIN:
    {
#pragma warning(suppress: 26477)
      ATLASSUME(pszUsername != nullptr);
#pragma warning(suppress: 26477)
      ATLASSUME(pszPassword != nullptr);

      AuthPlain(pszUsername, pszPassword);
      break;
    }
    case AuthenticationMethod::AUTH_XOAUTH2:
    {
#pragma warning(suppress: 26477)
      ATLASSUME(pszUsername != nullptr);
#pragma warning(suppress: 26477)
      ATLASSUME(pszPassword != nullptr);

      AuthXOAUTH2(pszUsername, pszPassword);
      break;
    }
  #ifndef CPJNSMTP_NONTLM
    case AuthenticationMethod::AUTH_NTLM:
    {
      const SECURITY_STATUS ss = NTLMAuthenticate(pszUsername, pszPassword);
      if (!SEC_SUCCESS(ss))
        ThrowPJNSMTPException(ss, m_sLastCommandResponse);
      break;
    }
  #endif //#ifdef CPJNSMTP_NONTLM
    case AuthenticationMethod::AUTH_AUTO:
    {
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
      break;
    }
    case AuthenticationMethod::AUTH_NONE:
    {
      //authentication not requried
      break;
    }
    default:
    {
#pragma warning(suppress: 26477)
      ATLASSERT(false);
      break;
    }
  }
}

//This method connects using standard RFC 821 SMTP
void CPJNSMTPConnection::ConnectSMTP(_In_z_ LPCTSTR pszLocalName)
{
  //The server does not support any extentions if we use standard SMTP
  m_bCanDoDSN = false;
  m_bCanDoSTARTTLS = false;
  m_bCanDoSMTPUTF8 = false;

  CPJNSMTPAsciiString sBuf;
  std::ostringstream ss;
#ifdef _UNICODE
  ss << "HELO " << ATL::CT2A(pszLocalName) << "\r\n";
#else
  ss << "HELO " << pszLocalName << "\r\n";
#endif //#ifdef _UNICODE
  sBuf = ss.str();
  try
  {
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the HELO command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

void CPJNSMTPConnection::Disconnect(_In_ bool bGracefully)
{
  //disconnect from the SMTP server if connected 
  HRESULT hr = S_OK;
  CPJNSMTPString sLastResponse;
  if (m_bConnected)
  {
    if (bGracefully)
    {
      static const char* sBuf = "QUIT\r\n";
      try
      {
#pragma warning(suppress: 26472)
        _Send(sBuf, static_cast<int>(strlen(sBuf)));
      }
    #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
      catch(CWSocketException* pEx)
      {
        hr = MAKE_HRESULT(SEVERITY_ERROR, pEx->m_nError, FACILITY_WIN32);
        pEx->Delete();
      }
    #else
#pragma warning(suppress: 26496)
      catch(CWSocketException& e)
      {
        hr = MAKE_HRESULT(SEVERITY_ERROR, e.m_nError, FACILITY_WIN32);
      }
    #endif //#ifdef CWSOCKET_MFC_EXTENSIONS

      //Check the response
      try
      {
        if (!ReadCommandResponse(221))
        {
          hr = MAKE_HRESULT(SEVERITY_ERROR, ERROR_UNEXP_NET_ERR, FACILITY_WIN32);
          sLastResponse = m_sLastCommandResponse;
        }
      }
#pragma warning(suppress: 26496)
      catch(CPJNSMTPException& e)
      {
        //Hive away the values in the local variables first. We do this to ensure that we 
        //end up executing the m_bConnected = false and _Close code below
        hr = e.m_hr;
        sLastResponse = e.m_sLastResponse;
      }
    }

    //Reset the connected state
    m_bConnected = false;
  }
 
  //free up our socket
#ifndef CPJNSMTP_NOSSL
  if (m_SSLCredentials.ValidHandle())
    m_SSLCredentials.Free();
  if (ValidHandle())
    Delete();
  Detach();
#endif //#ifdef CPJNSMTP_NOSSL
  m_Socket.Close();

  //Should we throw an expection
  if (FAILED(hr))
    ThrowPJNSMTPException(hr, sLastResponse);
}

void CPJNSMTPConnection::SendBodyPart(_In_ CPJNSMTPBodyPart* pBodyPart, _In_ bool bRoot, _In_z_ LPCTSTR pszRootCharset)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSUME(pBodyPart != nullptr);

  if (!bRoot)
  {
    //First send this body parts header
#pragma warning(suppress: 26486)
    CPJNSMTPAsciiString sHeader(pBodyPart->GetHeader(pszRootCharset));
    try
    {
      const CPJNSMTPAsciiString::size_type nHeaderLength = sHeader.length();
      if (nHeaderLength)
#pragma warning(suppress: 26472)
        _Send(sHeader.c_str(), static_cast<int>(nHeaderLength));
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS
  }

  //Then the body parts body
#pragma warning(suppress: 26486)
  CPJNSMTPAsciiString sBody(pBodyPart->GetBody(true));
  try
  {
    const CPJNSMTPAsciiString::size_type nBodyLength = sBody.length();
    if (nBodyLength)
#pragma warning(suppress: 26472)
      _Send(sBody.c_str(), static_cast<int>(nBodyLength));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //The recursively send all the child body parts
#pragma warning(suppress: 26486 26489)
  for (const auto& childBodyPart: pBodyPart->m_ChildBodyParts)
    SendBodyPart(childBodyPart.get(), false, pszRootCharset);

  //Then the MIME footer if need be
#pragma warning(suppress: 26486)
  const bool bSendFooter = (pBodyPart->m_ChildBodyParts.size() != 0);
  if (bSendFooter)
  {
#pragma warning(suppress: 26486)
    CPJNSMTPAsciiString sFooter(pBodyPart->GetFooter());
    try
    {
      const CPJNSMTPAsciiString::size_type nFooterLength = sFooter.length();
      if (nFooterLength)
#pragma warning(suppress: 26472)
        _Send(sFooter.c_str(), static_cast<int>(nFooterLength));
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS
  }
}

CPJNSMTPAsciiString CPJNSMTPConnection::FormMailFromCommand(_In_z_ LPCTSTR pszEmailAddress, _In_ DWORD dwDSN, _In_ CPJNSMTPMessage::DSN_RETURN_TYPE DSNReturnType, _Inout_ CPJNSMTPAsciiString& sENVID)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszEmailAddress != nullptr);
#pragma warning(suppress: 26477)
  ATLASSERT(_tcslen(pszEmailAddress));

  //Encode the email address using punycode if necessary
  CPJNSMTPAsciiString sEmailAddress;
  if (m_bUsePunyCode)
  {
    sEmailAddress = CPJNSMTPAddress::PunyCode(pszEmailAddress);

    //Fail if we have to send a non ascii email address and the server does not support SMTPUTF8
    if (!m_bCanDoSMTPUTF8 && (sEmailAddress != CPJNSMTPAsciiString(ATL::CT2A(pszEmailAddress))))
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32);
  }
  else
    sEmailAddress = ATL::CT2A(pszEmailAddress);

  //What will be the return value from this method
  CPJNSMTPAsciiString sBuf;

  if (dwDSN == CPJNSMTPMessage::DSN_NOT_SPECIFIED)
  {
    std::ostringstream ss;
    ss << "MAIL FROM:<" << sEmailAddress << ">\r\n";
    sBuf = ss.str();
  }
  else
  {
    //Fail if we have requested DSN and the server does not support it
    if (!m_bCanDoDSN)
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32);

    //Create an envelope ID if one has not been specified
    if (sENVID.length() == 0)
      sENVID = CreateNEWENVID();

    std::ostringstream ss;
    if (DSNReturnType == CPJNSMTPMessage::DSN_RETURN_TYPE::HeadersOnly)
      ss << "MAIL FROM:<" << sEmailAddress << "> RET=HDRS ENVID=" << sENVID << "\r\n";
    else
      ss << "MAIL FROM:<" << sEmailAddress << "> RET=FULL ENVID=" << sENVID << "\r\n";
    sBuf = ss.str();
  }

  return sBuf;
}

void CPJNSMTPConnection::SendMessage(_Inout_ CPJNSMTPMessage& message)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(m_bConnected); //Must be connected to send a message

  //Send the MAIL command
  CPJNSMTPAsciiString sBuf(FormMailFromCommand(message.m_From.m_sEmailAddress.c_str(), message.m_dwDSN, message.m_DSNReturnType, message.m_sENVID));

  try
  {
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS 

  //check the response to the MAIL command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Send the RCPT command, one for each recipient (includes the TO, CC & BCC recipients)

  //We must be sending the message to someone!
#pragma warning(suppress: 26477)
  ATLASSERT(message.m_To.size() + message.m_CC.size() + message.m_BCC.size());

  //First the "To" recipients
  for (const auto& recipient : message.m_To)
    SendRCPTForRecipient(message.m_dwDSN, recipient);

  //Then the "CC" recipients
  for (const auto& recipient : message.m_CC)
    SendRCPTForRecipient(message.m_dwDSN, recipient);

  //Then the "BCC" recipients
  for (const auto& recipient : message.m_BCC)
    SendRCPTForRecipient(message.m_dwDSN, recipient);

  //Send the DATA command
  static const char* szDataCommand = "DATA\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szDataCommand, static_cast<int>(strlen(szDataCommand)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the DATA command
  if (!ReadCommandResponse(354))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Send the Message Header
  CPJNSMTPAsciiString sHeader(message.GetHeader());
  try
  {
#pragma warning(suppress: 26472)
    _Send(sHeader.c_str(), static_cast<int>(sHeader.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Send the Header / body Separator
  static const char* szBodyHeader = "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szBodyHeader, static_cast<int>(strlen(szBodyHeader)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Now send the contents of the mail
  message.m_RootPart.FixUpParentBodyPartPointers();
  if (message.m_RootPart.m_ChildBodyParts.size() || message.m_bMime)
  {
    //Send the root body part (and all its children)
    SendBodyPart(&message.m_RootPart, true, message.GetCharset().c_str());
  }
  else
  {
    //Do the UTF8 conversion if necessary
    CPJNSMTPAsciiString sBody;
    if (_tcsicmp(message.m_RootPart.GetCharset().c_str(), _T("UTF-8")) == 0)
      sBody = CPJNSMTPBodyPart::ConvertToUTF8(message.m_RootPart.GetText().c_str());
    else
    {
    #ifdef _UNICODE
      sBody = ATL::CT2A(message.m_RootPart.GetText().c_str());
    #else
      sBody = message.m_RootPart.GetText();
    #endif //#ifdef _UNICODE
    }

    CPJNSMTPBodyPart::FixSingleDot(sBody);

    //Send the body
    try
    {
#pragma warning(suppress: 26472)
      _Send(sBody.c_str(), static_cast<int>(sBody.length()));
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS
  }

  //Send the end of message indicator
  static const char* szEOM = "\r\n.\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szEOM, static_cast<int>(strlen(szEOM)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the End of Message command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

#pragma warning(suppress: 26440)
bool CPJNSMTPConnection::OnSendProgress(_In_ DWORD /*dwCurrentBytes*/, _In_ DWORD /*dwTotalBytes*/)
{
  //By default just return true to allow the mail to continue to be sent
  return true;
}

void CPJNSMTPConnection::SendMessage(_In_reads_bytes_opt_(dwMessageSize) const BYTE* pMessage, _In_ DWORD dwMessageSize, _Inout_ std::vector<CPJNSMTPAddress>& Recipients, _In_ const CPJNSMTPAddress& From, _Inout_ CPJNSMTPAsciiString& sENVID, _In_ DWORD dwSendBufferSize, _In_ DWORD DSN, _In_ CPJNSMTPMessage::DSN_RETURN_TYPE DSNReturnType)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(m_bConnected); //Must be connected to send a message

  //Send the MAIL command
  try
  {
    CPJNSMTPAsciiString sBuf(FormMailFromCommand(From.m_sEmailAddress.c_str(), DSN, DSNReturnType, sENVID));
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the MAIL command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Must be sending to someone
#ifdef _DEBUG
  const size_t nRecipients = Recipients.size();
#pragma warning(suppress: 26477)
  ATLASSERT(nRecipients);
#endif //#ifdef _DEBUG

  //Send the RCPT command, one for each recipient
  for (const auto& recipient : Recipients)
    SendRCPTForRecipient(DSN, recipient);

  //Send the DATA command
  static const char* szDataCommand = "DATA\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szDataCommand, static_cast<int>(strlen(szDataCommand)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the DATA command
  if (!ReadCommandResponse(354))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Read and send the data a chunk at a time
  bool bMore = true;
  DWORD dwBytesSent = 0;
  const BYTE* pSendBuf = pMessage;
  do
  {
    const DWORD dwRead = min(dwSendBufferSize, dwMessageSize - dwBytesSent);
    dwBytesSent += dwRead;

    //Call the progress virtual method
    if (OnSendProgress(dwBytesSent, dwMessageSize))
    {
      try
      {
#pragma warning(suppress: 26486)
        _Send(pSendBuf, dwRead);
      }
    #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
      catch(CWSocketException* pEx)
      {
        const DWORD dwError = pEx->m_nError;
        pEx->Delete();
        ThrowPJNSMTPException(dwError, FACILITY_WIN32);
      }
    #else
#pragma warning(suppress: 26496)
      catch(CWSocketException& e)
      {
        ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
      }
    #endif //#ifdef CWSOCKET_MFC_EXTENSIONS
    }
    else
    {
      //Throw an exception to indicate that the sending was cancelled
      ThrowPJNSMTPException(ERROR_CANCELLED, FACILITY_WIN32);
    }

    //Prepare for the next time around
#pragma warning(suppress: 26481)
    pSendBuf += dwRead;
    bMore = (dwBytesSent < dwMessageSize);
  }
  while (bMore);

  //Send the end of message indicator
  static const char* szEOM = "\r\n.\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szEOM, static_cast<int>(strlen(szEOM)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the End of Message command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

void CPJNSMTPConnection::SendMessage(_In_z_ LPCTSTR pszMessageOnFile, _Inout_ std::vector<CPJNSMTPAddress>& Recipients, _In_ const CPJNSMTPAddress& From, _Inout_ CPJNSMTPAsciiString& sENVID, _In_ DWORD dwSendBufferSize, _In_ DWORD DSN, _In_ CPJNSMTPMessage::DSN_RETURN_TYPE DSNReturnType)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(m_bConnected); //Must be connected to send a message

  //Open up the file we want to send
  ATL::CAtlFile file;
  HRESULT hr = file.Create(pszMessageOnFile, GENERIC_READ, FILE_SHARE_READ, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN);
  if (FAILED(hr))
    ThrowPJNSMTPException(hr);

  //Get the length of the file (we only support sending files less than 4GB!)
  ULONGLONG nFileSize = 0;
  hr = file.GetSize(nFileSize);
  if (FAILED(hr))
    CPJNSMTPConnection::ThrowPJNSMTPException(hr);
  if ((nFileSize >= ULONG_MAX) || (nFileSize == 0))
    CPJNSMTPConnection::ThrowPJNSMTPException(ERROR_OPEN_FAILED, FACILITY_WIN32);

  //Send the MAIL command
#pragma warning(suppress: 26477)
  ATLASSERT(From.m_sEmailAddress.length());
  CPJNSMTPAsciiString sBuf(FormMailFromCommand(From.m_sEmailAddress.c_str(), DSN, DSNReturnType, sENVID));
  try
  {
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the MAIL command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Must be sending to someone
#ifdef _DEBUG
  const size_t nRecipients = Recipients.size();
#pragma warning(suppress: 26477)
  ATLASSERT(nRecipients);
#endif //#ifdef _DEBUG

  //Send the RCPT command, one for each recipient
  for (const auto& recipient : Recipients)
    SendRCPTForRecipient(DSN, recipient);

  //Send the DATA command
  static const char* szDataCommand = "DATA\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szDataCommand, static_cast<int>(strlen(szDataCommand)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the DATA command
  if (!ReadCommandResponse(354))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Allocate a buffer we will use in the sending
  std::vector<BYTE> sendBuf;
  sendBuf.resize(dwSendBufferSize);

  //Read and send the data a chunk at a time
  bool bMore = true;
  DWORD dwBytesSent = 0;
  do
  {
    //Read the chunk from file
    DWORD dwRead = 0;
    hr = file.Read(sendBuf.data(), dwSendBufferSize, dwRead);
    if (FAILED(hr))
      ThrowPJNSMTPException(hr);
    bMore = (dwRead == dwSendBufferSize);

    //Send the chunk
    if (dwRead)
    {
      dwBytesSent += dwRead;

      //Call the progress virtual method
#pragma warning(suppress: 26472)
      if (OnSendProgress(dwBytesSent, static_cast<DWORD>(nFileSize)))
      {
        try
        {
          _Send(sendBuf.data(), dwRead);
        }
      #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
        catch(CWSocketException* pEx)
        {
          const DWORD dwError = pEx->m_nError;
          pEx->Delete();
          ThrowPJNSMTPException(dwError, FACILITY_WIN32);
        }
      #else
#pragma warning(suppress: 26496)
        catch(CWSocketException& e)
        {
          ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
        }
      #endif //#ifdef CWSOCKET_MFC_EXTENSIONS
      }
      else
      {
        //Throw an exception to indicate that the sending was cancelled
        ThrowPJNSMTPException(ERROR_CANCELLED, FACILITY_WIN32);
      }
    }
  }
  while (bMore);

  //Send the end of message indicator
  static const char* szEOM = "\r\n.\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szEOM, static_cast<int>(strlen(szEOM)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the End of Message command
  if (!ReadCommandResponse(250))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

void CPJNSMTPConnection::SendRCPTForRecipient(_In_ DWORD dwDSN, _Inout_ const CPJNSMTPAddress& recipient)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(recipient.m_sEmailAddress.length()); //must have an email address for this recipient

  //Encode the email address using punycode if necessary
  CPJNSMTPAsciiString sEmailAddress;
  if (m_bUsePunyCode)
  {
    sEmailAddress = CPJNSMTPAddress::PunyCode(recipient.m_sEmailAddress.c_str());

    //Fail if we have to send a non ascii email address and the server does not support SMTPUTF8
    if (!m_bCanDoSMTPUTF8 && (sEmailAddress != CPJNSMTPAsciiString(ATL::CT2A(recipient.m_sEmailAddress.c_str()))))
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32);
  }
  else
    sEmailAddress = ATL::CT2A(recipient.m_sEmailAddress.c_str());

  //form the command to send
  CPJNSMTPAsciiString sBuf;
  if (dwDSN == CPJNSMTPMessage::DSN_NOT_SPECIFIED)
  {
    std::ostringstream ss;
    ss << "RCPT TO:<" << sEmailAddress << ">\r\n";
    sBuf = ss.str();
  }
  else
  {
    CPJNSMTPAsciiString sNotificationTypes;
    if (dwDSN & CPJNSMTPMessage::DSN_SUCCESS)
      sNotificationTypes = "SUCCESS";
    if (dwDSN & CPJNSMTPMessage::DSN_FAILURE)
    {
      if (sNotificationTypes.length())
        sNotificationTypes += ",";
      sNotificationTypes += "FAILURE";
    }
    if (dwDSN & CPJNSMTPMessage::DSN_DELAY)
    {
      if (sNotificationTypes.length())
        sNotificationTypes += ",";
      sNotificationTypes += "DELAY";
    }
    if (sNotificationTypes.length() == 0) //Not setting DSN_SUCCESS, DSN_FAILURE or DSN_DELAY in m_DSNReturnType implies we do not want DSN's
      sNotificationTypes += "NEVER";

    std::ostringstream ss;
    ss << "RCPT TO:<" << sEmailAddress << "> NOTIFY=" << sNotificationTypes << "\r\n";
    sBuf = ss.str();
  }

  //and send it
  try
  {
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the RCPT command (250 or 251) is considered success
  if (!ReadCommandResponse(250, 251))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

bool CPJNSMTPConnection::ReadCommandResponse(_In_ int nExpectedCode)
{
  CPJNSMTPAsciiString sResponse;
  sResponse.reserve(1024);
  bool bSuccess = ReadResponse(sResponse);
  if (bSuccess)
    bSuccess = (m_nLastCommandResponseCode == nExpectedCode);
  return bSuccess;
}

bool CPJNSMTPConnection::ReadCommandResponse(_In_ int nExpectedCode1, _In_ int nExpectedCode2)
{
  CPJNSMTPAsciiString sResponse;
  sResponse.reserve(1024);
  bool bSuccess = ReadResponse(sResponse);
  if (bSuccess)
    bSuccess = (m_nLastCommandResponseCode == nExpectedCode1) || (m_nLastCommandResponseCode == nExpectedCode2);
  return bSuccess;
}

bool CPJNSMTPConnection::ReadResponse(_Inout_ CPJNSMTPAsciiString& sResponse)
{
  bool bResult = false;
#ifndef CPJNSMTP_NOSSL
  if (GetSocket() != INVALID_SOCKET)
    bResult = ReadSSLResponse(sResponse);
  else
#endif //#ifndef CPJNSMTP_NOSSL
    bResult = ReadPlainTextResponse(sResponse);
#pragma warning(suppress: 26446 26472)
  TraceCommunication("Recv:", &sResponse[0], static_cast<int>(sResponse.length()));
  return bResult;
}

bool CPJNSMTPConnection::ReadPlainTextResponse(_Inout_ CPJNSMTPAsciiString& sResponse)
{
  //must have been created first
#pragma warning(suppress: 26477)
  ATLASSERT(m_bConnected);

  static const char* pszTerminator = "\r\n";
  static int nTerminatorLen = 2;

  //retrieve the reponse until we get the full response or a timeout occurs
  bool bFoundFullResponse = false;
  CPJNSMTPAsciiString::size_type nStartOfLastLine = 0;
  sResponse.clear();
  while (!bFoundFullResponse)
  {
    //check the socket for readability
    try
    {
      if (!m_Socket.IsReadible(m_dwTimeout)) //A timeout has occured so fail the function call
      {
        //Hive away the last command reponse
      #ifdef _UNICODE
#pragma warning(suppress: 26489)
        m_sLastCommandResponse = ATL::CA2T(sResponse.c_str());
      #else
#pragma warning(suppress: 26486 26489)
        m_sLastCommandResponse = sResponse;
      #endif //#ifdef _UNICODE
        return false;
      }
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS

    //receive the data from the socket
    std::vector<char> sBuf;
    sBuf.resize(4096);
    int nData = 0;
    try
    {
#pragma warning(suppress: 26472)
      nData = m_Socket.Receive(sBuf.data(), static_cast<int>(sBuf.size()) - 1);
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS

    //Handle a graceful disconnect
    if (nData == 0)
    {
      //Hive away the last command reponse
    #ifdef _UNICODE
#pragma warning(suppress: 26489)
      m_sLastCommandResponse = ATL::CA2T(sResponse.c_str());
    #else
#pragma warning(suppress: 26486 26489)
      m_sLastCommandResponse = sResponse;
    #endif //#ifdef _UNICODE
      ThrowPJNSMTPException(ERROR_GRACEFUL_DISCONNECT, FACILITY_WIN32, m_sLastCommandResponse);
    }

    //Null terminate the data received
#pragma warning(suppress: 26446)
    sBuf[nData] = '\0';

    //Grow the response data
#pragma warning(suppress: 26489)
    sResponse += sBuf.data();

    //Check to see if we got a full response yet
#pragma warning(suppress: 26489)
    const CPJNSMTPAsciiString::size_type nReceived = sResponse.length();
#pragma warning(suppress: 26429 26489)
    LPCSTR szResponse = sResponse.c_str();
#pragma warning(suppress: 26481)
    const bool bFoundTerminator = (strncmp(&szResponse[nReceived - nTerminatorLen], pszTerminator, nTerminatorLen) == 0);
    if (bFoundTerminator && (nReceived > 5))
    {
      //Find the start of the last line we have received
      CPJNSMTPAsciiString::size_type i = nReceived - 3;
      nStartOfLastLine = 0;
      while ((nStartOfLastLine == 0) && (i > 0))
      {
#pragma warning(suppress: 26481)
        if ((szResponse[i] == '\r') && (szResponse[i + 1] == '\n'))
          nStartOfLastLine = i + 2;
        else
        {
          //Prepare for the next loop around
          --i;
        }
      }
#pragma warning(suppress: 26481)
      bFoundFullResponse = (szResponse[nStartOfLastLine + 3] == ' ');
    }
  }

  //determine the Numeric response code
#pragma warning(suppress: 26489)
  CPJNSMTPAsciiString sCode(sResponse.substr(nStartOfLastLine, 3));
  sscanf_s(sCode.c_str(), "%d", &m_nLastCommandResponseCode);

  //Hive away the last command reponse
#ifdef _UNICODE
#pragma warning(suppress: 26489)
  m_sLastCommandResponse = ATL::CA2T(sResponse.c_str());
#else
#pragma warning(suppress: 26486 26489)
  m_sLastCommandResponse = sResponse;
#endif //#ifdef _UNICODE

  return true;
}

#ifndef CPJNSMTP_NOSSL
bool CPJNSMTPConnection::ReadSSLResponse(_Inout_ CPJNSMTPAsciiString& sResponse)
{
  //must have been created first
#pragma warning(suppress: 26477)
  ATLASSERT(m_bConnected);

  static const char* pszTerminator = "\r\n";
  static int nTerminatorLen = 2;

  sResponse.clear();

  //retrieve the reponse until we 
  //get the full response or a timeout occurs
  bool bFoundFullResponse = false;
  int nStartOfLastLine = 0;
  while (!bFoundFullResponse)
  {
    //receive the data from the socket
    std::vector<BYTE> message;
    const SECURITY_STATUS ss = GetEncryptedMessage(message);
    if (ss != SEC_E_OK)
      ThrowPJNSMTPException(ss);

    //Handle a graceful disconnect
    if (message.size() == 0)
    {
    #ifdef _UNICODE
#pragma warning(suppress: 26489)
      m_sLastCommandResponse = ATL::CA2T(sResponse.c_str());
    #else
#pragma warning(suppress: 26486 26489)
      m_sLastCommandResponse = sResponse;
    #endif //#ifdef _UNICODE

      ThrowPJNSMTPException(ERROR_GRACEFUL_DISCONNECT, FACILITY_WIN32, m_sLastCommandResponse);
    }

    //Append the data to the response
#pragma warning(suppress: 26481 26489 26490)
    sResponse.append(reinterpret_cast<const char*>(message.data()), reinterpret_cast<const char*>(message.data()) + message.size());

    //Check to see if we got a full response yet
#pragma warning(suppress: 26472 26489)
    const int nReceived = static_cast<int>(sResponse.length());
#pragma warning(suppress: 26429 26489)
    LPCSTR szResponse = sResponse.c_str();
#pragma warning(suppress: 26481)
    const bool bFoundTerminator = (strncmp(&szResponse[nReceived - nTerminatorLen], pszTerminator, nTerminatorLen) == 0);
    if (bFoundTerminator && (nReceived > 5))
    {
      //Find the start of the last line we have received
      int i = nReceived - 3;
      nStartOfLastLine = 0;
      while ((nStartOfLastLine == 0) && (i > 0))
      {
#pragma warning(suppress: 26481)
        if ((szResponse[i] == '\r') && (szResponse[i + 1] == '\n'))
          nStartOfLastLine = i + 2;
        else
        {
          //Prepare for the next loop around
          --i;
        }
      }
#pragma warning(suppress: 26481)
      bFoundFullResponse = (szResponse[nStartOfLastLine + 3] == ' ');
    }
  }

  //determine the numeric response code
#pragma warning(suppress: 26489)
  CPJNSMTPAsciiString sCode(sResponse.substr(nStartOfLastLine, 3));
  sscanf_s(sCode.c_str(), "%d", &m_nLastCommandResponseCode);

  //Hive away the last command reponse
#ifdef _UNICODE
#pragma warning(suppress: 26489)
  m_sLastCommandResponse = ATL::CA2T(sResponse.c_str());
#else
#pragma warning(suppress: 26486 26489)
  m_sLastCommandResponse = sResponse;
#endif //#ifdef _UNICODE

  return true;
}
#endif //#ifndef CPJNSMTP_NOSSL

void CPJNSMTPConnection::AuthLogin(_In_z_ LPCTSTR pszUsername, _In_z_ LPCTSTR pszPassword)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszUsername != nullptr);
#pragma warning(suppress: 26477)
  ATLASSERT(pszPassword != nullptr);

  CPJNSMTPAsciiString sAsciiLastCommandString;
  CPJNSMPTBase64Encode encode;

  //Send the AUTH LOGIN command if required
  if (!m_bDoInitialClientResponse)
  {
    static const char* szAuth = "AUTH LOGIN\r\n";
    try
    {
#pragma warning(suppress: 26472)
      _Send(szAuth, static_cast<int>(strlen(szAuth)));
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS

    //check the response to the AUTH LOGIN command
    if (!ReadCommandResponse(334))
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

    //Check that the response has a username request in it
  #ifdef _UNICODE
    sAsciiLastCommandString = ATL::CT2A(m_sLastCommandResponse.substr(4).c_str());
  #else
    sAsciiLastCommandString = m_sLastCommandResponse.substr(4);
  #endif //#ifdef _UNICODE
    encode.Decode(sAsciiLastCommandString.c_str());
    if (_strnicmp(encode.Result(), "username:", 9) != 0)
#pragma warning(suppress: 26489)
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
  }

  //send base64 encoded username
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiUsername(ATL::CT2A(pszUsername).operator LPSTR());
#else
  CPJNSMTPAsciiString sAsciiUsername(pszUsername);
#endif //#ifdef _UNICODE
  encode.Encode(sAsciiUsername.c_str(), ATL_BASE64_FLAG_NOCRLF);
  CPJNSMTPAsciiString sUser;
  if (m_bDoInitialClientResponse)
    sUser += "AUTH LOGIN ";
  sUser += encode.Result();
  sUser += "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(sUser.c_str(), static_cast<int>(sUser.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //check the response to the username 
  if (!ReadCommandResponse(334))
#pragma warning(suppress: 26489)
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Check that the response has a password request in it
#ifdef _UNICODE
#pragma warning(suppress: 26486 26489)
  sAsciiLastCommandString = ATL::CT2A(m_sLastCommandResponse.substr(4).c_str());
#else
#pragma warning(suppress: 26489)
  sAsciiLastCommandString = m_sLastCommandResponse.substr(4);
#endif //#ifdef _UNICODE
  encode.Decode(sAsciiLastCommandString.c_str());
  if (_strnicmp(encode.Result(), "password:", 9) != 0)
#pragma warning(suppress: 26489)
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //send password as base64 encoded
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiPassword(ATL::CT2A(pszPassword).operator LPSTR());
#else
  CPJNSMTPAsciiString sAsciiPassword(pszPassword);
#endif //#ifdef _UNICODE
  encode.Encode(sAsciiPassword.c_str(), ATL_BASE64_FLAG_NOCRLF);
  CPJNSMTPAsciiString sPwd(encode.Result());
  sPwd += "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(sPwd.c_str(), static_cast<int>(sPwd.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Check if authentication is successful
  if (!ReadCommandResponse(235))
#pragma warning(suppress: 26489)
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

void CPJNSMTPConnection::AuthPlain(_In_z_ LPCTSTR pszUsername, _In_z_ LPCTSTR pszPassword)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszUsername != nullptr);
#pragma warning(suppress: 26477)
  ATLASSERT(pszPassword != nullptr);

  //Send the AUTH PLAIN command if required
  if (!m_bDoInitialClientResponse)
  {
    static const char* szAuth = "AUTH PLAIN\r\n";
    try
    {
#pragma warning(suppress: 26472)
      _Send(szAuth, static_cast<int>(strlen(szAuth)));
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS

    //Check the response to the AUTH PLAIN command
    if (!ReadCommandResponse(334))
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
  }

  //Send the username and password in the base64 encoded AUTH PLAIN format of "authid\0userid\0passwd".
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiUserName(ATL::CT2A(pszUsername).operator LPSTR());
  CPJNSMTPAsciiString sAsciiPassword(ATL::CT2A(pszPassword).operator LPSTR());
#else
  CPJNSMTPAsciiString sAsciiUserName(pszUsername);
  CPJNSMTPAsciiString sAsciiPassword(pszPassword);
#endif //#ifdef _UNICODE
#pragma warning(suppress: 26472)
  const size_t nAsciiUserNameLen = static_cast<int>(sAsciiUserName.length());
#pragma warning(suppress: 26472)
  const size_t nAsciiPasswordLen = static_cast<int>(sAsciiPassword.length());
  const size_t nAuthLen = (nAsciiUserNameLen * 2) + nAsciiPasswordLen + 2;

  //Allocate some heap space for the auth request packet
  std::vector<char> authRequest;
  authRequest.resize(nAuthLen);

  //Form the auth request
  strcpy_s(authRequest.data(), nAuthLen, sAsciiUserName.c_str());
#pragma warning(suppress: 26481)
  strcpy_s(authRequest.data() + nAsciiUserNameLen + 1, nAuthLen - nAsciiUserNameLen - 1, sAsciiUserName.c_str());
#pragma warning(suppress: 26481)
  memcpy_s(authRequest.data() + (2*nAsciiUserNameLen) + 2, nAuthLen - (2*nAsciiUserNameLen) - 2, sAsciiPassword.c_str(), nAsciiPasswordLen);
  CPJNSMPTBase64Encode encode;
#pragma warning(suppress: 26472 26490)
  encode.Encode(reinterpret_cast<const BYTE*>(authRequest.data()), static_cast<int>(nAuthLen), ATL_BASE64_FLAG_NOCRLF);
  SecureZeroMemory(authRequest.data(), nAuthLen);
  
  CPJNSMTPAsciiString sAuth;
  if (m_bDoInitialClientResponse)
    sAuth += "AUTH PLAIN ";
  sAuth += encode.Result();
  sAuth += "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(sAuth.c_str(), static_cast<int>(sAuth.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Check if authentication is successful
  if (!ReadCommandResponse(235))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

void CPJNSMTPConnection::AuthCramMD5(_In_z_ LPCTSTR pszUsername, _In_z_ LPCTSTR pszPassword)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszUsername != nullptr);
#pragma warning(suppress: 26477)
  ATLASSERT(pszPassword != nullptr);

  //Send the AUTH CRAM-MD5 command
  static const char* szAuth = "AUTH CRAM-MD5\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(szAuth, static_cast<int>(strlen(szAuth)));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Check the response to the AUTH CRAM-MD5 command
  if (!ReadCommandResponse(334))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Get the base64 decoded challange 
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiLastCommandString(ATL::CT2A(m_sLastCommandResponse.substr(4).c_str()));
#else
#pragma warning(suppress: 26489)
  CPJNSMTPAsciiString sAsciiLastCommandString(m_sLastCommandResponse.substr(4));
#endif //#ifdef _UNICODE
  CPJNSMPTBase64Encode encode;
  encode.Decode(sAsciiLastCommandString.c_str());
  LPCSTR pszChallenge = encode.Result();

  //generate the MD5 digest from the challenge and password
  CPJNMD5 hmac;
  CPJNMD5Hash hash;
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiPassword(ATL::CT2A(pszPassword).operator LPSTR());
#else
  CPJNSMTPAsciiString sAsciiPassword(pszPassword);
#endif //#ifdef _UNICODE
#pragma warning(suppress: 26490 26472)
  if (!hmac.HMAC(reinterpret_cast<const BYTE*>(pszChallenge), static_cast<DWORD>(strlen(pszChallenge)), reinterpret_cast<const BYTE*>(sAsciiPassword.c_str()), static_cast<DWORD>(sAsciiPassword.length()), hash))
    ThrowPJNSMTPException(GetLastError(), FACILITY_WIN32);

  //make the CRAM-MD5 response
  CPJNSMTPString sCramDigest(pszUsername);
  sCramDigest += _T(" ");
#ifdef CPJNMD5_MFC_EXTENSIONS
  sCramDigest += hash.Format(false); //CRAM-MD5 requires a lowercase hash
#else
  sCramDigest += hash.Format(false).c_str(); //CRAM-MD5 requires a lowercase hash
#endif //#ifdef CPJNMD5_MFC_EXTENSIONS

  //send the digest response
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiCramDigest(ATL::CT2A(sCramDigest.c_str()));
#else
  CPJNSMTPAsciiString sAsciiCramDigest(sCramDigest);
#endif //#ifdef _UNICODE
  encode.Encode(sAsciiCramDigest.c_str(), ATL_BASE64_FLAG_NOCRLF);
  CPJNSMTPAsciiString sEncodedDigest(encode.Result());
  sEncodedDigest += "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(sEncodedDigest.c_str(), static_cast<int>(sEncodedDigest.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Check if authentication is successful
  if (!ReadCommandResponse(235))
#pragma warning(suppress: 26489)
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
}

void CPJNSMTPConnection::AuthXOAUTH2(_In_z_ LPCTSTR pszUsername, _In_z_ LPCTSTR pszAccessToken)
{
  //Validate our parameters
#pragma warning(suppress: 26477)
  ATLASSERT(pszUsername != nullptr);
#pragma warning(suppress: 26477)
  ATLASSERT(pszAccessToken != nullptr);

  //Send the AUTH XOAUTH2 command if required
  if (!m_bDoInitialClientResponse)
  {
    static const char* szAuth = "AUTH XOAUTH2\r\n";
    try
    {
#pragma warning(suppress: 26472)
      _Send(szAuth, static_cast<int>(strlen(szAuth)));
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
    catch (CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch (CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS

    //Check the response to the AUTH XOAUTH2 command
    if (!ReadCommandResponse(334))
      ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
  }

  //Send the username and access token in the base64 encoded XOAUTH2 format
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiUserName(ATL::CT2A(pszUsername).operator LPSTR());
  CPJNSMTPAsciiString sAsciiAccessToken(ATL::CT2A(pszAccessToken).operator LPSTR());
#else
  CPJNSMTPAsciiString sAsciiUserName(pszUsername);
  CPJNSMTPAsciiString sAsciiAccessToken(pszAccessToken);
#endif //#ifdef _UNICODE
#pragma warning(suppress: 26472)
  const size_t nAsciiUserNameLen = static_cast<int>(sAsciiUserName.length());
#pragma warning(suppress: 26472)
  const size_t nAsciiAccessTokenLen = static_cast<int>(sAsciiAccessToken.length());
  const size_t nAuthLen = nAsciiUserNameLen + nAsciiAccessTokenLen + 20;

  //Allocate some heap space for the auth request packet
  std::vector<char> authRequest;
  authRequest.resize(nAuthLen);

  //Form the auth request
  memcpy_s(authRequest.data(), nAuthLen, "user=", 5);
#pragma warning(suppress: 26481)
  memcpy_s(authRequest.data() + 5, nAuthLen - 5, sAsciiUserName.c_str(), nAsciiUserNameLen);
#pragma warning(suppress: 26446)
  authRequest[5 + nAsciiUserNameLen] = 0x01;
#pragma warning(suppress: 26481)
  memcpy_s(authRequest.data() + 6 + nAsciiUserNameLen, nAuthLen - 6 - nAsciiUserNameLen, "auth=Bearer ", 12);
#pragma warning(suppress: 26481)
  memcpy_s(authRequest.data() + 18 + nAsciiUserNameLen, nAuthLen - 18 - nAsciiUserNameLen, sAsciiAccessToken.c_str(), nAsciiAccessTokenLen);
#pragma warning(suppress: 26446)
  authRequest[18 + nAsciiUserNameLen + nAsciiAccessTokenLen] = 0x01;
#pragma warning(suppress: 26446)
  authRequest[19 + nAsciiUserNameLen + nAsciiAccessTokenLen] = 0x01;

  CPJNSMPTBase64Encode encode;
#pragma warning(suppress: 26490 26472)
  encode.Encode(reinterpret_cast<const BYTE*>(authRequest.data()), static_cast<int>(nAuthLen), ATL_BASE64_FLAG_NOCRLF);
  SecureZeroMemory(authRequest.data(), nAuthLen);

  CPJNSMTPAsciiString sAuth;
  if (m_bDoInitialClientResponse)
    sAuth += "AUTH XOAUTH2 ";
  sAuth += encode.Result();
  sAuth += "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(sAuth.c_str(), static_cast<int>(sAuth.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Check if authentication is successful
  if (!ReadCommandResponse(235))
  {
    try
    {
      _Send("\r\n", 2);
    }
  #ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26429 26462)
    catch(CWSocketException* pEx)
    {
      const DWORD dwError = pEx->m_nError;
      pEx->Delete();
      ThrowPJNSMTPException(dwError, FACILITY_WIN32);
    }
  #else
#pragma warning(suppress: 26496)
    catch(CWSocketException& e)
    {
      ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
    }
  #endif //#ifdef CWSOCKET_MFC_EXTENSIONS
    ReadCommandResponse(-1);
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);
  }
}

CPJNSMTPConnection::ConnectToInternetResult CPJNSMTPConnection::ConnectToInternet() noexcept
{
  //Check to see if an internet connection already exists.
  //bInternet = TRUE  internet connection exists.
  //bInternet = FALSE internet connection does not exist
  DWORD dwFlags = 0;
  const BOOL bInternet = InternetGetConnectedState(&dwFlags, 0);
  if (!bInternet)
  {
    //Attempt to establish internet connection, probably
    //using Dial-up connection. CloseInternetConnection() should be called when
    //as some time to drop the dial-up connection.
    const DWORD dwResult = InternetAttemptConnect(0);
    if (dwResult != ERROR_SUCCESS)
    {
      SetLastError(dwResult);
      return ConnectToInternetResult::Failure;
    }
    else
      return ConnectToInternetResult::NewConnection;
  }
  else
    return ConnectToInternetResult::ExistingConnection;
}

bool CPJNSMTPConnection::CloseInternetConnection() noexcept
{
  //Make sure any connection through a modem is 'closed'
  return InternetAutodialHangup(0) != 0;
}

#ifndef CPJNSMTP_NONTLM
SECURITY_STATUS CPJNSMTPConnection::NTLMAuthPhase1(_In_reads_bytes_(cbBuf) PBYTE pBuf, _In_ DWORD cbBuf)
{
  //Send the AUTH NTLM command with the initial data
  CPJNSMPTBase64Encode encode;
  encode.Encode(pBuf, cbBuf, ATL_BASE64_FLAG_NOCRLF);
  
  CPJNSMTPAsciiString sBuf("AUTH NTLM ");
  sBuf += encode.Result();
  sBuf += "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462 26429)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  return SEC_E_OK;
}

#pragma warning(suppress: 26429)
SECURITY_STATUS CPJNSMTPConnection::NTLMAuthPhase2(_Inout_updates_(cbBuf) PBYTE pBuf, _In_ DWORD cbBuf, _Out_ DWORD* pcbRead)
{
  //check the response to the AUTH NTLM command
  if (!ReadCommandResponse(334))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  //Decode the last response
#ifdef _UNICODE
  CPJNSMTPAsciiString sAsciiLastCommandString(ATL::CT2A(m_sLastCommandResponse.substr(4).c_str()));
#else
  CPJNSMTPAsciiString sAsciiLastCommandString(m_sLastCommandResponse.substr(4));
#endif //#ifdef _UNICODE
  CPJNSMPTBase64Encode encode;
  encode.Decode(sAsciiLastCommandString.c_str());

  //Store the results in the output parameters
  *pcbRead = encode.ResultSize();
  if (*pcbRead >= cbBuf)
    return SEC_E_INSUFFICIENT_MEMORY;
  memcpy_s(pBuf, cbBuf, encode.Result(), *pcbRead);

  return SEC_E_OK;
}

SECURITY_STATUS CPJNSMTPConnection::NTLMAuthPhase3(_In_reads_bytes_(cbBuf) PBYTE pBuf, _In_ DWORD cbBuf)
{
  //send base64 encoded version of the data
  CPJNSMPTBase64Encode encode;
  encode.Encode(pBuf, cbBuf, ATL_BASE64_FLAG_NOCRLF);

  CPJNSMTPAsciiString sBuf(encode.Result());
  sBuf += "\r\n";
  try
  {
#pragma warning(suppress: 26472)
    _Send(sBuf.c_str(), static_cast<int>(sBuf.length()));
  }
#ifdef CWSOCKET_MFC_EXTENSIONS
#pragma warning(suppress: 26462 26429)
  catch(CWSocketException* pEx)
  {
    const DWORD dwError = pEx->m_nError;
    pEx->Delete();
    ThrowPJNSMTPException(dwError, FACILITY_WIN32);
  }
#else
#pragma warning(suppress: 26496)
  catch(CWSocketException& e)
  {
    ThrowPJNSMTPException(e.m_nError, FACILITY_WIN32);
  }
#endif //#ifdef CWSOCKET_MFC_EXTENSIONS

  //Check if authentication is successful
  if (!ReadCommandResponse(235))
    ThrowPJNSMTPException(ERROR_UNEXP_NET_ERR, FACILITY_WIN32, m_sLastCommandResponse);

  return SEC_E_OK;
}
#endif //#ifdef CPJNSMTP_NONTLM

bool CPJNSMTPConnection::MXLookup(_In_z_ LPCTSTR lpszHostDomain, _Inout_ std::vector<CPJNSMTPString>& arrHosts, _Inout_ std::vector<WORD>& arrPreferences, _In_ WORD fOptions, _In_opt_ PIP4_ARRAY aipServers)
{
  //What will be the return value
  bool bSuccess = false;

  //Reset the output parameter arrays
  arrHosts.clear();
  arrPreferences.clear();

  //Do the DNS MX lookup
  PDNS_RECORD pRec = nullptr;
#pragma warning(suppress: 26486)
  const DNS_STATUS status = DnsQuery(lpszHostDomain, DNS_TYPE_MX, fOptions, aipServers, &pRec, nullptr);
  if (status == ERROR_SUCCESS)
  {
    bSuccess = true;

    PDNS_RECORD pRecFirst = pRec;
    while (pRec != nullptr)
    {
      //We're only interested in MX records
      if (pRec->wType == DNS_TYPE_MX)
      {
#pragma warning(suppress: 26489)
        arrHosts.emplace_back(pRec->Data.MX.pNameExchange);
#pragma warning(suppress: 26489)
        arrPreferences.push_back(pRec->Data.MX.wPreference);
      }
      pRec = pRec->pNext;
    }
    if (pRecFirst != nullptr)
      DnsRecordListFree(pRecFirst, DnsFreeRecordList);
  }
  else
    SetLastError(status);

  return bSuccess;
}

CPJNSMTPAsciiString CPJNSMTPConnection::CreateNEWENVID()
{
  //Remove all but the hex digits
#ifdef _UNICODE
  CPJNSMTPAsciiString sEnvID(ATL::CT2A(CPJNSMTPBodyPart::CreateGUID().c_str()));
#else
  CPJNSMTPAsciiString sEnvID(CPJNSMTPBodyPart::CreateGUID());
#endif //#ifdef _UNICODE
#pragma warning(suppress: 26444 26486)
  sEnvID.erase(std::remove(sEnvID.begin(), sEnvID.end(), '{'), sEnvID.end());
#pragma warning(suppress: 26444 26486)
  sEnvID.erase(std::remove(sEnvID.begin(), sEnvID.end(), '}'), sEnvID.end());
#pragma warning(suppress: 26444 26486)
  sEnvID.erase(std::remove(sEnvID.begin(), sEnvID.end(), '-'), sEnvID.end());
#pragma warning(suppress: 26472 26444)
  std::transform(sEnvID.begin(), sEnvID.end(), sEnvID.begin(), [](char c) noexcept -> char { return static_cast<char>(toupper(c)); });

  return sEnvID;
}

#pragma warning(suppress: 26440)
void CPJNSMTPConnection::TraceCommunication(_In_z_ PCSTR pszIntro, _In_reads_bytes_(nLen) const void* pszData, int nLen)
{
  //By default just write out the data via OutputDebugString in debug builds only. Derived classes are free to implement there own logic.
#ifdef _DEBUG
  OutputDebugStringA(pszIntro);
  int iPos = 0;
  while (iPos < nLen)
  {
    const int iOut = min(nLen - iPos, 256);
#pragma warning(suppress: 26481)
    ATL::CAtlStringA sBuffer(static_cast<const char*>(pszData) + iPos, iOut);
    OutputDebugStringA(sBuffer);
    iPos += iOut;
  }
#else
  UNREFERENCED_PARAMETER(pszIntro);
  UNREFERENCED_PARAMETER(pszData);
  UNREFERENCED_PARAMETER(nLen);
#endif //#ifdef _DEBUG
}
