SmtpClient.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. //
  2. // System.Net.Mail.SmtpClient.cs
  3. //
  4. // Author:
  5. // Tim Coleman ([email protected])
  6. //
  7. // Copyright (C) Tim Coleman, 2004
  8. //
  9. //
  10. // Permission is hereby granted, free of charge, to any person obtaining
  11. // a copy of this software and associated documentation files (the
  12. // "Software"), to deal in the Software without restriction, including
  13. // without limitation the rights to use, copy, modify, merge, publish,
  14. // distribute, sublicense, and/or sell copies of the Software, and to
  15. // permit persons to whom the Software is furnished to do so, subject to
  16. // the following conditions:
  17. //
  18. // The above copyright notice and this permission notice shall be
  19. // included in all copies or substantial portions of the Software.
  20. //
  21. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  22. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  23. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  24. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  25. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  26. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  27. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28. //
  29. #if NET_2_0
  30. using System;
  31. using System.Collections.Generic;
  32. using System.ComponentModel;
  33. using System.IO;
  34. using System.Net;
  35. using System.Net.Mime;
  36. using System.Net.Sockets;
  37. using System.Security.Cryptography.X509Certificates;
  38. using System.Text;
  39. using System.Threading;
  40. namespace System.Net.Mail {
  41. public class SmtpClient
  42. {
  43. #region Fields
  44. string host;
  45. int port;
  46. int timeout;
  47. ICredentialsByHost credentials;
  48. bool useDefaultCredentials;
  49. string pickupDirectoryLocation;
  50. SmtpDeliveryMethod deliveryMethod;
  51. bool enableSsl;
  52. X509CertificateCollection clientCertificates;
  53. TcpClient client;
  54. NetworkStream stream;
  55. StreamWriter writer;
  56. StreamReader reader;
  57. int boundaryIndex;
  58. Mutex mutex = new Mutex ();
  59. const string MimeVersion = "1.0 (produced by Mono System.Net.Mail.SmtpClient)";
  60. #endregion // Fields
  61. #region Constructors
  62. public SmtpClient ()
  63. : this (null, 0)
  64. {
  65. }
  66. public SmtpClient (string host)
  67. : this (host, 0)
  68. {
  69. }
  70. [MonoTODO ("Load default settings from configuration.")]
  71. public SmtpClient (string host, int port)
  72. {
  73. Host = host;
  74. Port = port;
  75. }
  76. #endregion // Constructors
  77. #region Properties
  78. [MonoTODO]
  79. public X509CertificateCollection ClientCertificates {
  80. get { return clientCertificates; }
  81. }
  82. public ICredentialsByHost Credentials {
  83. get { return credentials; }
  84. set { credentials = value; }
  85. }
  86. public SmtpDeliveryMethod DeliveryMethod {
  87. get { return deliveryMethod; }
  88. set { deliveryMethod = value; }
  89. }
  90. public bool EnableSsl {
  91. get { return enableSsl; }
  92. set { enableSsl = value; }
  93. }
  94. public string Host {
  95. get { return host; }
  96. set { host = value; }
  97. }
  98. public string PickupDirectoryLocation {
  99. get { return pickupDirectoryLocation; }
  100. set { pickupDirectoryLocation = value; }
  101. }
  102. public int Port {
  103. get { return port; }
  104. [MonoTODO ("Check to make sure an email is not being sent.")]
  105. set {
  106. if (value <= 0)
  107. throw new ArgumentOutOfRangeException ();
  108. port = value;
  109. }
  110. }
  111. [MonoTODO]
  112. public ServicePoint ServicePoint {
  113. get { throw new NotImplementedException (); }
  114. }
  115. public int Timeout {
  116. get { return timeout; }
  117. [MonoTODO ("Check to make sure an email is not being sent.")]
  118. set {
  119. if (value <= 0)
  120. throw new ArgumentOutOfRangeException ();
  121. timeout = value;
  122. }
  123. }
  124. public bool UseDefaultCredentials {
  125. get { return useDefaultCredentials; }
  126. set { useDefaultCredentials = value; }
  127. }
  128. #endregion // Properties
  129. #region Events
  130. public event SendCompletedEventHandler SendCompleted;
  131. #endregion // Events
  132. #region Methods
  133. private void EndSection (string section)
  134. {
  135. SendData (String.Format ("--{0}--", section));
  136. }
  137. private string GenerateBoundary ()
  138. {
  139. string output = GenerateBoundary (boundaryIndex);
  140. boundaryIndex += 1;
  141. return output;
  142. }
  143. private static string GenerateBoundary (int index)
  144. {
  145. return String.Format ("--boundary_{0}_{1}", index, Guid.NewGuid ().ToString ("D"));
  146. }
  147. private bool IsError (SmtpResponse status)
  148. {
  149. return ((int) status.StatusCode) >= 400;
  150. }
  151. protected void OnSendCompleted (AsyncCompletedEventArgs e)
  152. {
  153. if (SendCompleted != null)
  154. SendCompleted (this, e);
  155. }
  156. private SmtpResponse Read ()
  157. {
  158. SmtpResponse response;
  159. char[] buf = new char [3];
  160. reader.Read (buf, 0, 3);
  161. reader.Read ();
  162. response.StatusCode = (SmtpStatusCode) Int32.Parse (new String (buf));
  163. response.Description = reader.ReadLine ();
  164. return response;
  165. }
  166. [MonoTODO ("Need to work on message attachments.")]
  167. public void Send (MailMessage message)
  168. {
  169. // Block while sending
  170. mutex.WaitOne ();
  171. SmtpResponse status;
  172. client = new TcpClient (host, port);
  173. stream = client.GetStream ();
  174. writer = new StreamWriter (stream);
  175. reader = new StreamReader (stream);
  176. boundaryIndex = 0;
  177. string boundary = GenerateBoundary ();
  178. bool hasAlternateViews = (message.AlternateViews.Count > 0);
  179. bool hasAttachments = (message.Attachments.Count > 0);
  180. status = Read ();
  181. if (IsError (status))
  182. throw new SmtpException (status.StatusCode);
  183. // HELO
  184. status = SendCommand (Command.Helo, Dns.GetHostName ());
  185. if (IsError (status))
  186. throw new SmtpException (status.StatusCode);
  187. // MAIL FROM:
  188. status = SendCommand (Command.MailFrom, message.From.Address);
  189. if (IsError (status))
  190. throw new SmtpException (status.StatusCode);
  191. // Send RCPT TO: for all recipients
  192. List<SmtpFailedRecipientException> sfre = new List<SmtpFailedRecipientException> ();
  193. for (int i = 0; i < message.To.Count; i ++) {
  194. status = SendCommand (Command.RcptTo, message.To [i].Address);
  195. if (IsError (status))
  196. sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.To [i].Address.ToString ()));
  197. }
  198. for (int i = 0; i < message.CC.Count; i ++) {
  199. status = SendCommand (Command.RcptTo, message.CC [i].Address);
  200. if (IsError (status))
  201. sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.CC [i].Address.ToString ()));
  202. }
  203. for (int i = 0; i < message.Bcc.Count; i ++) {
  204. status = SendCommand (Command.RcptTo, message.Bcc [i].Address);
  205. if (IsError (status))
  206. sfre.Add (new SmtpFailedRecipientException (status.StatusCode, message.Bcc [i].Address.ToString ()));
  207. }
  208. if (sfre.Count > 0)
  209. throw new SmtpFailedRecipientsException ("failed recipients", sfre.ToArray ());
  210. // DATA
  211. status = SendCommand (Command.Data);
  212. if (IsError (status))
  213. throw new SmtpException (status.StatusCode);
  214. // Figure out the message content type
  215. ContentType messageContentType = new ContentType ("text/plain");
  216. if (hasAttachments || hasAlternateViews) {
  217. //messageContentType = new ContentType ();
  218. messageContentType.Boundary = boundary;
  219. if (hasAttachments)
  220. messageContentType.MediaType = "multipart/mixed";
  221. else
  222. messageContentType.MediaType = "multipart/alternative";
  223. }
  224. // Send message headers
  225. SendHeader (HeaderName.From, message.From.ToString ());
  226. SendHeader (HeaderName.To, message.To.ToString ());
  227. if (message.CC.Count > 0)
  228. SendHeader (HeaderName.Cc, message.CC.ToString ());
  229. if (message.Bcc.Count > 0)
  230. SendHeader (HeaderName.Bcc, message.Bcc.ToString ());
  231. SendHeader (HeaderName.Subject, message.Subject);
  232. foreach (string s in message.Headers.AllKeys)
  233. SendHeader (s, message.Headers [s]);
  234. SendHeader ("Content-Type", messageContentType.ToString ());
  235. SendData ("");
  236. if (hasAlternateViews) {
  237. string innerBoundary = boundary;
  238. // The body is *technically* an alternative view. The body text goes FIRST because
  239. // that is most compatible with non-MIME readers.
  240. //
  241. // If there are attachments, then the main content-type is multipart/mixed and
  242. // the subpart has type multipart/alternative. Then all of the views have their
  243. // own types.
  244. //
  245. // If there are no attachments, then the main content-type is multipart/alternative
  246. // and we don't need this subpart.
  247. if (hasAttachments) {
  248. innerBoundary = GenerateBoundary ();
  249. ContentType contentType = new ContentType ("multipart/alternative");
  250. contentType.Boundary = innerBoundary;
  251. StartSection (boundary, contentType);
  252. }
  253. // Start the section for the body text. This is either section "1" or "0" depending
  254. // on whether there are attachments.
  255. StartSection (innerBoundary, messageContentType, TransferEncoding.QuotedPrintable);
  256. SendData (message.Body);
  257. // Send message attachments.
  258. SendAttachments (message.Attachments, innerBoundary);
  259. if (hasAttachments)
  260. EndSection (innerBoundary);
  261. }
  262. else {
  263. // If this is multipart then we need to send a boundary before the body.
  264. if (hasAttachments) {
  265. // FIXME: check this
  266. ContentType contentType = new ContentType ("multipart/alternative");
  267. StartSection (boundary, contentType, TransferEncoding.QuotedPrintable);
  268. }
  269. SendData (message.Body);
  270. }
  271. // Send attachments
  272. if (hasAttachments) {
  273. string innerBoundary = boundary;
  274. // If we have alternate views and attachments then we need to nest this part inside another
  275. // boundary. Otherwise, we are cool with the boundary we have.
  276. if (hasAlternateViews) {
  277. innerBoundary = GenerateBoundary ();
  278. ContentType contentType = new ContentType ("multipart/mixed");
  279. contentType.Boundary = innerBoundary;
  280. StartSection (boundary, contentType);
  281. }
  282. SendAttachments (message.Attachments, innerBoundary);
  283. if (hasAlternateViews)
  284. EndSection (innerBoundary);
  285. }
  286. SendData (".");
  287. status = Read ();
  288. if (IsError (status))
  289. throw new SmtpException (status.StatusCode);
  290. status = SendCommand (Command.Quit);
  291. writer.Close ();
  292. reader.Close ();
  293. stream.Close ();
  294. client.Close ();
  295. // Release the mutex to allow other threads access
  296. mutex.ReleaseMutex ();
  297. }
  298. public void Send (string from, string to, string subject, string body)
  299. {
  300. Send (new MailMessage (from, to, subject, body));
  301. }
  302. private void SendData (string data)
  303. {
  304. writer.WriteLine (data);
  305. writer.Flush ();
  306. }
  307. [MonoTODO]
  308. public void SendAsync (MailMessage message, object userToken)
  309. {
  310. Send (message);
  311. OnSendCompleted (new AsyncCompletedEventArgs (null, false, userToken));
  312. }
  313. public void SendAsync (string from, string to, string subject, string body, object userToken)
  314. {
  315. SendAsync (new MailMessage (from, to, subject, body), userToken);
  316. }
  317. [MonoTODO]
  318. public void SendAsyncCancel ()
  319. {
  320. throw new NotImplementedException ();
  321. }
  322. private void SendAttachments (AttachmentCollection attachments, string boundary)
  323. {
  324. for (int i = 0; i < attachments.Count; i += 1) {
  325. // FIXME: check this
  326. ContentType contentType = new ContentType ("multipart/alternative");
  327. StartSection (boundary, contentType, attachments [i].TransferEncoding);
  328. switch (attachments [i].TransferEncoding) {
  329. case TransferEncoding.Base64:
  330. byte[] content = new byte [attachments [i].ContentStream.Length];
  331. attachments [i].ContentStream.Read (content, 0, content.Length);
  332. SendData (Convert.ToBase64String (content, Base64FormattingOptions.InsertLineBreaks));
  333. break;
  334. case TransferEncoding.QuotedPrintable:
  335. StreamReader sr = new StreamReader (attachments [i].ContentStream);
  336. SendData (ToQuotedPrintable (sr.ReadToEnd ()));
  337. break;
  338. //case TransferEncoding.SevenBit:
  339. //case TransferEncoding.Unknown:
  340. default:
  341. SendData ("TO BE IMPLEMENTED");
  342. break;
  343. }
  344. }
  345. }
  346. private SmtpResponse SendCommand (string command, string data)
  347. {
  348. writer.Write (command);
  349. writer.Write (" ");
  350. SendData (data);
  351. return Read ();
  352. }
  353. private SmtpResponse SendCommand (string command)
  354. {
  355. writer.WriteLine (command);
  356. writer.Flush ();
  357. return Read ();
  358. }
  359. private void SendHeader (string name, string value)
  360. {
  361. SendData (String.Format ("{0}: {1}", name, value));
  362. }
  363. private void StartSection (string section, ContentType sectionContentType)
  364. {
  365. SendData (String.Format ("--{0}", section));
  366. SendHeader ("content-type", sectionContentType.ToString ());
  367. SendData ("");
  368. }
  369. private void StartSection (string section, ContentType sectionContentType,TransferEncoding transferEncoding)
  370. {
  371. SendData (String.Format ("--{0}", section));
  372. SendHeader ("content-type", sectionContentType.ToString ());
  373. SendHeader ("content-transfer-encoding", GetTransferEncodingName (transferEncoding));
  374. SendData ("");
  375. }
  376. private string ToQuotedPrintable (string input)
  377. {
  378. StringReader reader = new StringReader (input);
  379. StringWriter writer = new StringWriter ();
  380. int i;
  381. while ((i = reader.Read ()) > 0) {
  382. if (i > 127) {
  383. writer.Write ("=");
  384. writer.Write (Convert.ToString (i, 16).ToUpper ());
  385. }
  386. else
  387. writer.Write (Convert.ToChar (i));
  388. }
  389. return writer.GetStringBuilder ().ToString ();
  390. }
  391. private static string GetTransferEncodingName (TransferEncoding encoding)
  392. {
  393. switch (encoding) {
  394. case TransferEncoding.QuotedPrintable:
  395. return "quoted-printable";
  396. case TransferEncoding.SevenBit:
  397. return "7bit";
  398. case TransferEncoding.Base64:
  399. return "base64";
  400. }
  401. return "unknown";
  402. }
  403. /*
  404. [MonoTODO]
  405. private sealed ContextAwareResult IGetContextAwareResult.GetContextAwareResult ()
  406. {
  407. throw new NotImplementedException ();
  408. }
  409. */
  410. #endregion // Methods
  411. // The Command struct is used to store constant string values representing SMTP commands.
  412. private struct Command {
  413. public const string Data = "DATA";
  414. public const string Helo = "HELO";
  415. public const string MailFrom = "MAIL FROM:";
  416. public const string Quit = "QUIT";
  417. public const string RcptTo = "RCPT TO:";
  418. }
  419. // The HeaderName struct is used to store constant string values representing mail headers.
  420. private struct HeaderName {
  421. public const string ContentTransferEncoding = "Content-Transfer-Encoding";
  422. public const string ContentType = "Content-Type";
  423. public const string Bcc = "Bcc";
  424. public const string Cc = "Cc";
  425. public const string From = "From";
  426. public const string Subject = "Subject";
  427. public const string To = "To";
  428. public const string MimeVersion = "MIME-Version";
  429. public const string MessageId = "Message-ID";
  430. }
  431. // This object encapsulates the status code and description of an SMTP response.
  432. private struct SmtpResponse {
  433. public SmtpStatusCode StatusCode;
  434. public string Description;
  435. }
  436. }
  437. }
  438. #endif // NET_2_0