SmtpAppender.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. #region Apache License
  2. //
  3. // Licensed to the Apache Software Foundation (ASF) under one or more
  4. // contributor license agreements. See the NOTICE file distributed with
  5. // this work for additional information regarding copyright ownership.
  6. // The ASF licenses this file to you under the Apache License, Version 2.0
  7. // (the "License"); you may not use this file except in compliance with
  8. // the License. You may obtain a copy of the License at
  9. //
  10. // http://www.apache.org/licenses/LICENSE-2.0
  11. //
  12. // Unless required by applicable law or agreed to in writing, software
  13. // distributed under the License is distributed on an "AS IS" BASIS,
  14. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. // See the License for the specific language governing permissions and
  16. // limitations under the License.
  17. //
  18. #endregion
  19. // .NET Compact Framework 1.0 has no support for System.Web.Mail
  20. // SSCLI 1.0 has no support for System.Web.Mail
  21. #if !NETCF && !SSCLI
  22. using System;
  23. using System.IO;
  24. using System.Text;
  25. #if NET_2_0 || MONO_2_0
  26. using System.Net.Mail;
  27. #else
  28. using System.Web.Mail;
  29. #endif
  30. using log4net.Layout;
  31. using log4net.Core;
  32. using log4net.Util;
  33. namespace log4net.Appender
  34. {
  35. /// <summary>
  36. /// Send an e-mail when a specific logging event occurs, typically on errors
  37. /// or fatal errors.
  38. /// </summary>
  39. /// <remarks>
  40. /// <para>
  41. /// The number of logging events delivered in this e-mail depend on
  42. /// the value of <see cref="BufferingAppenderSkeleton.BufferSize"/> option. The
  43. /// <see cref="SmtpAppender"/> keeps only the last
  44. /// <see cref="BufferingAppenderSkeleton.BufferSize"/> logging events in its
  45. /// cyclic buffer. This keeps memory requirements at a reasonable level while
  46. /// still delivering useful application context.
  47. /// </para>
  48. /// <note type="caution">
  49. /// Authentication and setting the server Port are only available on the MS .NET 1.1 runtime.
  50. /// For these features to be enabled you need to ensure that you are using a version of
  51. /// the log4net assembly that is built against the MS .NET 1.1 framework and that you are
  52. /// running the your application on the MS .NET 1.1 runtime. On all other platforms only sending
  53. /// unauthenticated messages to a server listening on port 25 (the default) is supported.
  54. /// </note>
  55. /// <para>
  56. /// Authentication is supported by setting the <see cref="Authentication"/> property to
  57. /// either <see cref="SmtpAuthentication.Basic"/> or <see cref="SmtpAuthentication.Ntlm"/>.
  58. /// If using <see cref="SmtpAuthentication.Basic"/> authentication then the <see cref="Username"/>
  59. /// and <see cref="Password"/> properties must also be set.
  60. /// </para>
  61. /// <para>
  62. /// To set the SMTP server port use the <see cref="Port"/> property. The default port is 25.
  63. /// </para>
  64. /// </remarks>
  65. /// <author>Nicko Cadell</author>
  66. /// <author>Gert Driesen</author>
  67. public class SmtpAppender : BufferingAppenderSkeleton
  68. {
  69. #region Public Instance Constructors
  70. /// <summary>
  71. /// Default constructor
  72. /// </summary>
  73. /// <remarks>
  74. /// <para>
  75. /// Default constructor
  76. /// </para>
  77. /// </remarks>
  78. public SmtpAppender()
  79. {
  80. }
  81. #endregion // Public Instance Constructors
  82. #region Public Instance Properties
  83. /// <summary>
  84. /// Gets or sets a comma- or semicolon-delimited list of recipient e-mail addresses (use semicolon on .NET 1.1 and comma for later versions).
  85. /// </summary>
  86. /// <value>
  87. /// <para>
  88. /// For .NET 1.1 (System.Web.Mail): A semicolon-delimited list of e-mail addresses.
  89. /// </para>
  90. /// <para>
  91. /// For .NET 2.0 (System.Net.Mail): A comma-delimited list of e-mail addresses.
  92. /// </para>
  93. /// </value>
  94. /// <remarks>
  95. /// <para>
  96. /// For .NET 1.1 (System.Web.Mail): A semicolon-delimited list of e-mail addresses.
  97. /// </para>
  98. /// <para>
  99. /// For .NET 2.0 (System.Net.Mail): A comma-delimited list of e-mail addresses.
  100. /// </para>
  101. /// </remarks>
  102. public string To
  103. {
  104. get { return m_to; }
  105. set { m_to = MaybeTrimSeparators(value); }
  106. }
  107. /// <summary>
  108. /// Gets or sets a comma- or semicolon-delimited list of recipient e-mail addresses
  109. /// that will be carbon copied (use semicolon on .NET 1.1 and comma for later versions).
  110. /// </summary>
  111. /// <value>
  112. /// <para>
  113. /// For .NET 1.1 (System.Web.Mail): A semicolon-delimited list of e-mail addresses.
  114. /// </para>
  115. /// <para>
  116. /// For .NET 2.0 (System.Net.Mail): A comma-delimited list of e-mail addresses.
  117. /// </para>
  118. /// </value>
  119. /// <remarks>
  120. /// <para>
  121. /// For .NET 1.1 (System.Web.Mail): A semicolon-delimited list of e-mail addresses.
  122. /// </para>
  123. /// <para>
  124. /// For .NET 2.0 (System.Net.Mail): A comma-delimited list of e-mail addresses.
  125. /// </para>
  126. /// </remarks>
  127. public string Cc
  128. {
  129. get { return m_cc; }
  130. set { m_cc = MaybeTrimSeparators(value); }
  131. }
  132. /// <summary>
  133. /// Gets or sets a semicolon-delimited list of recipient e-mail addresses
  134. /// that will be blind carbon copied.
  135. /// </summary>
  136. /// <value>
  137. /// A semicolon-delimited list of e-mail addresses.
  138. /// </value>
  139. /// <remarks>
  140. /// <para>
  141. /// A semicolon-delimited list of recipient e-mail addresses.
  142. /// </para>
  143. /// </remarks>
  144. public string Bcc
  145. {
  146. get { return m_bcc; }
  147. set { m_bcc = MaybeTrimSeparators(value); }
  148. }
  149. /// <summary>
  150. /// Gets or sets the e-mail address of the sender.
  151. /// </summary>
  152. /// <value>
  153. /// The e-mail address of the sender.
  154. /// </value>
  155. /// <remarks>
  156. /// <para>
  157. /// The e-mail address of the sender.
  158. /// </para>
  159. /// </remarks>
  160. public string From
  161. {
  162. get { return m_from; }
  163. set { m_from = value; }
  164. }
  165. /// <summary>
  166. /// Gets or sets the subject line of the e-mail message.
  167. /// </summary>
  168. /// <value>
  169. /// The subject line of the e-mail message.
  170. /// </value>
  171. /// <remarks>
  172. /// <para>
  173. /// The subject line of the e-mail message.
  174. /// </para>
  175. /// </remarks>
  176. public string Subject
  177. {
  178. get { return m_subject; }
  179. set { m_subject = value; }
  180. }
  181. /// <summary>
  182. /// Gets or sets the name of the SMTP relay mail server to use to send
  183. /// the e-mail messages.
  184. /// </summary>
  185. /// <value>
  186. /// The name of the e-mail relay server. If SmtpServer is not set, the
  187. /// name of the local SMTP server is used.
  188. /// </value>
  189. /// <remarks>
  190. /// <para>
  191. /// The name of the e-mail relay server. If SmtpServer is not set, the
  192. /// name of the local SMTP server is used.
  193. /// </para>
  194. /// </remarks>
  195. public string SmtpHost
  196. {
  197. get { return m_smtpHost; }
  198. set { m_smtpHost = value; }
  199. }
  200. /// <summary>
  201. /// Obsolete
  202. /// </summary>
  203. /// <remarks>
  204. /// Use the BufferingAppenderSkeleton Fix methods instead
  205. /// </remarks>
  206. /// <remarks>
  207. /// <para>
  208. /// Obsolete property.
  209. /// </para>
  210. /// </remarks>
  211. [Obsolete("Use the BufferingAppenderSkeleton Fix methods")]
  212. public bool LocationInfo
  213. {
  214. get { return false; }
  215. set { ; }
  216. }
  217. /// <summary>
  218. /// The mode to use to authentication with the SMTP server
  219. /// </summary>
  220. /// <remarks>
  221. /// <note type="caution">Authentication is only available on the MS .NET 1.1 runtime.</note>
  222. /// <para>
  223. /// Valid Authentication mode values are: <see cref="SmtpAuthentication.None"/>,
  224. /// <see cref="SmtpAuthentication.Basic"/>, and <see cref="SmtpAuthentication.Ntlm"/>.
  225. /// The default value is <see cref="SmtpAuthentication.None"/>. When using
  226. /// <see cref="SmtpAuthentication.Basic"/> you must specify the <see cref="Username"/>
  227. /// and <see cref="Password"/> to use to authenticate.
  228. /// When using <see cref="SmtpAuthentication.Ntlm"/> the Windows credentials for the current
  229. /// thread, if impersonating, or the process will be used to authenticate.
  230. /// </para>
  231. /// </remarks>
  232. public SmtpAuthentication Authentication
  233. {
  234. get { return m_authentication; }
  235. set { m_authentication = value; }
  236. }
  237. /// <summary>
  238. /// The username to use to authenticate with the SMTP server
  239. /// </summary>
  240. /// <remarks>
  241. /// <note type="caution">Authentication is only available on the MS .NET 1.1 runtime.</note>
  242. /// <para>
  243. /// A <see cref="Username"/> and <see cref="Password"/> must be specified when
  244. /// <see cref="Authentication"/> is set to <see cref="SmtpAuthentication.Basic"/>,
  245. /// otherwise the username will be ignored.
  246. /// </para>
  247. /// </remarks>
  248. public string Username
  249. {
  250. get { return m_username; }
  251. set { m_username = value; }
  252. }
  253. /// <summary>
  254. /// The password to use to authenticate with the SMTP server
  255. /// </summary>
  256. /// <remarks>
  257. /// <note type="caution">Authentication is only available on the MS .NET 1.1 runtime.</note>
  258. /// <para>
  259. /// A <see cref="Username"/> and <see cref="Password"/> must be specified when
  260. /// <see cref="Authentication"/> is set to <see cref="SmtpAuthentication.Basic"/>,
  261. /// otherwise the password will be ignored.
  262. /// </para>
  263. /// </remarks>
  264. public string Password
  265. {
  266. get { return m_password; }
  267. set { m_password = value; }
  268. }
  269. /// <summary>
  270. /// The port on which the SMTP server is listening
  271. /// </summary>
  272. /// <remarks>
  273. /// <note type="caution">Server Port is only available on the MS .NET 1.1 runtime.</note>
  274. /// <para>
  275. /// The port on which the SMTP server is listening. The default
  276. /// port is <c>25</c>. The Port can only be changed when running on
  277. /// the MS .NET 1.1 runtime.
  278. /// </para>
  279. /// </remarks>
  280. public int Port
  281. {
  282. get { return m_port; }
  283. set { m_port = value; }
  284. }
  285. /// <summary>
  286. /// Gets or sets the priority of the e-mail message
  287. /// </summary>
  288. /// <value>
  289. /// One of the <see cref="MailPriority"/> values.
  290. /// </value>
  291. /// <remarks>
  292. /// <para>
  293. /// Sets the priority of the e-mails generated by this
  294. /// appender. The default priority is <see cref="MailPriority.Normal"/>.
  295. /// </para>
  296. /// <para>
  297. /// If you are using this appender to report errors then
  298. /// you may want to set the priority to <see cref="MailPriority.High"/>.
  299. /// </para>
  300. /// </remarks>
  301. public MailPriority Priority
  302. {
  303. get { return m_mailPriority; }
  304. set { m_mailPriority = value; }
  305. }
  306. #if NET_2_0 || MONO_2_0
  307. /// <summary>
  308. /// Enable or disable use of SSL when sending e-mail message
  309. /// </summary>
  310. /// <remarks>
  311. /// This is available on MS .NET 2.0 runtime and higher
  312. /// </remarks>
  313. public bool EnableSsl
  314. {
  315. get { return m_enableSsl; }
  316. set { m_enableSsl = value; }
  317. }
  318. /// <summary>
  319. /// Gets or sets the reply-to e-mail address.
  320. /// </summary>
  321. /// <remarks>
  322. /// This is available on MS .NET 2.0 runtime and higher
  323. /// </remarks>
  324. public string ReplyTo
  325. {
  326. get { return m_replyTo; }
  327. set { m_replyTo = value; }
  328. }
  329. #endif
  330. /// <summary>
  331. /// Gets or sets the subject encoding to be used.
  332. /// </summary>
  333. /// <remarks>
  334. /// The default encoding is the operating system's current ANSI codepage.
  335. /// </remarks>
  336. public Encoding SubjectEncoding
  337. {
  338. get { return m_subjectEncoding; }
  339. set { m_subjectEncoding = value; }
  340. }
  341. /// <summary>
  342. /// Gets or sets the body encoding to be used.
  343. /// </summary>
  344. /// <remarks>
  345. /// The default encoding is the operating system's current ANSI codepage.
  346. /// </remarks>
  347. public Encoding BodyEncoding
  348. {
  349. get { return m_bodyEncoding; }
  350. set { m_bodyEncoding = value; }
  351. }
  352. #endregion // Public Instance Properties
  353. #region Override implementation of BufferingAppenderSkeleton
  354. /// <summary>
  355. /// Sends the contents of the cyclic buffer as an e-mail message.
  356. /// </summary>
  357. /// <param name="events">The logging events to send.</param>
  358. override protected void SendBuffer(LoggingEvent[] events)
  359. {
  360. // Note: this code already owns the monitor for this
  361. // appender. This frees us from needing to synchronize again.
  362. try
  363. {
  364. StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
  365. string t = Layout.Header;
  366. if (t != null)
  367. {
  368. writer.Write(t);
  369. }
  370. for(int i = 0; i < events.Length; i++)
  371. {
  372. // Render the event and append the text to the buffer
  373. RenderLoggingEvent(writer, events[i]);
  374. }
  375. t = Layout.Footer;
  376. if (t != null)
  377. {
  378. writer.Write(t);
  379. }
  380. SendEmail(writer.ToString());
  381. }
  382. catch(Exception e)
  383. {
  384. ErrorHandler.Error("Error occurred while sending e-mail notification.", e);
  385. }
  386. }
  387. #endregion // Override implementation of BufferingAppenderSkeleton
  388. #region Override implementation of AppenderSkeleton
  389. /// <summary>
  390. /// This appender requires a <see cref="Layout"/> to be set.
  391. /// </summary>
  392. /// <value><c>true</c></value>
  393. /// <remarks>
  394. /// <para>
  395. /// This appender requires a <see cref="Layout"/> to be set.
  396. /// </para>
  397. /// </remarks>
  398. override protected bool RequiresLayout
  399. {
  400. get { return true; }
  401. }
  402. #endregion // Override implementation of AppenderSkeleton
  403. #region Protected Methods
  404. /// <summary>
  405. /// Send the email message
  406. /// </summary>
  407. /// <param name="messageBody">the body text to include in the mail</param>
  408. virtual protected void SendEmail(string messageBody)
  409. {
  410. #if NET_2_0 || MONO_2_0
  411. // .NET 2.0 has a new API for SMTP email System.Net.Mail
  412. // This API supports credentials and multiple hosts correctly.
  413. // The old API is deprecated.
  414. // Create and configure the smtp client
  415. SmtpClient smtpClient = new SmtpClient();
  416. if (!String.IsNullOrEmpty(m_smtpHost))
  417. {
  418. smtpClient.Host = m_smtpHost;
  419. }
  420. smtpClient.Port = m_port;
  421. smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
  422. smtpClient.EnableSsl = m_enableSsl;
  423. if (m_authentication == SmtpAuthentication.Basic)
  424. {
  425. // Perform basic authentication
  426. smtpClient.Credentials = new System.Net.NetworkCredential(m_username, m_password);
  427. }
  428. else if (m_authentication == SmtpAuthentication.Ntlm)
  429. {
  430. // Perform integrated authentication (NTLM)
  431. smtpClient.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
  432. }
  433. using (MailMessage mailMessage = new MailMessage())
  434. {
  435. mailMessage.Body = messageBody;
  436. mailMessage.BodyEncoding = m_bodyEncoding;
  437. mailMessage.From = new MailAddress(m_from);
  438. mailMessage.To.Add(m_to);
  439. if (!String.IsNullOrEmpty(m_cc))
  440. {
  441. mailMessage.CC.Add(m_cc);
  442. }
  443. if (!String.IsNullOrEmpty(m_bcc))
  444. {
  445. mailMessage.Bcc.Add(m_bcc);
  446. }
  447. if (!String.IsNullOrEmpty(m_replyTo))
  448. {
  449. // .NET 4.0 warning CS0618: 'System.Net.Mail.MailMessage.ReplyTo' is obsolete:
  450. // 'ReplyTo is obsoleted for this type. Please use ReplyToList instead which can accept multiple addresses. http://go.microsoft.com/fwlink/?linkid=14202'
  451. #if !NET_4_0 && !MONO_4_0
  452. mailMessage.ReplyTo = new MailAddress(m_replyTo);
  453. #else
  454. mailMessage.ReplyToList.Add(new MailAddress(m_replyTo));
  455. #endif
  456. }
  457. mailMessage.Subject = m_subject;
  458. mailMessage.SubjectEncoding = m_subjectEncoding;
  459. mailMessage.Priority = m_mailPriority;
  460. // TODO: Consider using SendAsync to send the message without blocking. This would be a change in
  461. // behaviour compared to .NET 1.x. We would need a SendCompletedCallback to log errors.
  462. smtpClient.Send(mailMessage);
  463. }
  464. #else
  465. // .NET 1.x uses the System.Web.Mail API for sending Mail
  466. MailMessage mailMessage = new MailMessage();
  467. mailMessage.Body = messageBody;
  468. mailMessage.BodyEncoding = m_bodyEncoding;
  469. mailMessage.From = m_from;
  470. mailMessage.To = m_to;
  471. if (m_cc != null && m_cc.Length > 0)
  472. {
  473. mailMessage.Cc = m_cc;
  474. }
  475. if (m_bcc != null && m_bcc.Length > 0)
  476. {
  477. mailMessage.Bcc = m_bcc;
  478. }
  479. mailMessage.Subject = m_subject;
  480. #if !MONO && !NET_1_0 && !NET_1_1 && !CLI_1_0
  481. mailMessage.SubjectEncoding = m_subjectEncoding;
  482. #endif
  483. mailMessage.Priority = m_mailPriority;
  484. #if NET_1_1
  485. // The Fields property on the MailMessage allows the CDO properties to be set directly.
  486. // This property is only available on .NET Framework 1.1 and the implementation must understand
  487. // the CDO properties. For details of the fields available in CDO see:
  488. //
  489. // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cdosys/html/_cdosys_configuration_coclass.asp
  490. //
  491. try
  492. {
  493. if (m_authentication == SmtpAuthentication.Basic)
  494. {
  495. // Perform basic authentication
  496. mailMessage.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", 1);
  497. mailMessage.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendusername", m_username);
  498. mailMessage.Fields.Add("http://schemas.microsoft.com/cdo/configuration/sendpassword", m_password);
  499. }
  500. else if (m_authentication == SmtpAuthentication.Ntlm)
  501. {
  502. // Perform integrated authentication (NTLM)
  503. mailMessage.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate", 2);
  504. }
  505. // Set the port if not the default value
  506. if (m_port != 25)
  507. {
  508. mailMessage.Fields.Add("http://schemas.microsoft.com/cdo/configuration/smtpserverport", m_port);
  509. }
  510. }
  511. catch(MissingMethodException missingMethodException)
  512. {
  513. // If we were compiled against .NET 1.1 but are running against .NET 1.0 then
  514. // we will get a MissingMethodException when accessing the MailMessage.Fields property.
  515. ErrorHandler.Error("SmtpAppender: Authentication and server Port are only supported when running on the MS .NET 1.1 framework", missingMethodException);
  516. }
  517. #else
  518. if (m_authentication != SmtpAuthentication.None)
  519. {
  520. ErrorHandler.Error("SmtpAppender: Authentication is only supported on the MS .NET 1.1 or MS .NET 2.0 builds of log4net");
  521. }
  522. if (m_port != 25)
  523. {
  524. ErrorHandler.Error("SmtpAppender: Server Port is only supported on the MS .NET 1.1 or MS .NET 2.0 builds of log4net");
  525. }
  526. #endif // if NET_1_1
  527. if (m_smtpHost != null && m_smtpHost.Length > 0)
  528. {
  529. SmtpMail.SmtpServer = m_smtpHost;
  530. }
  531. SmtpMail.Send(mailMessage);
  532. #endif // if NET_2_0
  533. }
  534. #endregion // Protected Methods
  535. #region Private Instance Fields
  536. private string m_to;
  537. private string m_cc;
  538. private string m_bcc;
  539. private string m_from;
  540. private string m_subject;
  541. private string m_smtpHost;
  542. private Encoding m_subjectEncoding = Encoding.UTF8;
  543. private Encoding m_bodyEncoding = Encoding.UTF8;
  544. // authentication fields
  545. private SmtpAuthentication m_authentication = SmtpAuthentication.None;
  546. private string m_username;
  547. private string m_password;
  548. // server port, default port 25
  549. private int m_port = 25;
  550. private MailPriority m_mailPriority = MailPriority.Normal;
  551. #if NET_2_0 || MONO_2_0
  552. private bool m_enableSsl = false;
  553. private string m_replyTo;
  554. #endif
  555. #endregion // Private Instance Fields
  556. #region SmtpAuthentication Enum
  557. /// <summary>
  558. /// Values for the <see cref="SmtpAppender.Authentication"/> property.
  559. /// </summary>
  560. /// <remarks>
  561. /// <para>
  562. /// SMTP authentication modes.
  563. /// </para>
  564. /// </remarks>
  565. public enum SmtpAuthentication
  566. {
  567. /// <summary>
  568. /// No authentication
  569. /// </summary>
  570. None,
  571. /// <summary>
  572. /// Basic authentication.
  573. /// </summary>
  574. /// <remarks>
  575. /// Requires a username and password to be supplied
  576. /// </remarks>
  577. Basic,
  578. /// <summary>
  579. /// Integrated authentication
  580. /// </summary>
  581. /// <remarks>
  582. /// Uses the Windows credentials from the current thread or process to authenticate.
  583. /// </remarks>
  584. Ntlm
  585. }
  586. #endregion // SmtpAuthentication Enum
  587. private static readonly char[] ADDRESS_DELIMITERS = new char[] { ',', ';' };
  588. /// <summary>
  589. /// trims leading and trailing commas or semicolons
  590. /// </summary>
  591. private static string MaybeTrimSeparators(string s) {
  592. #if NET_2_0 || MONO_2_0
  593. return string.IsNullOrEmpty(s) ? s : s.Trim(ADDRESS_DELIMITERS);
  594. #else
  595. return s != null && s.Length > 0 ? s : s.Trim(ADDRESS_DELIMITERS);
  596. #endif
  597. }
  598. }
  599. }
  600. #endif // !NETCF && !SSCLI