public void SendAsyncCancel() { }
public System.Threading.Tasks.Task SendMailAsync(System.Net.Mail.MailMessage message) { throw null; }
public System.Threading.Tasks.Task SendMailAsync(string from, string recipients, string subject, string body) { throw null; }
+ public System.Threading.Tasks.Task SendMailAsync(System.Net.Mail.MailMessage message, System.Threading.CancellationToken cancellationToken) { throw null; }
+ public System.Threading.Tasks.Task SendMailAsync(string from, string recipients, string subject, string body, System.Threading.CancellationToken cancellationToken) { throw null; }
}
public enum SmtpDeliveryFormat
{
public Task SendMailAsync(string from, string recipients, string subject, string body)
{
var message = new MailMessage(from, recipients, subject, body);
- return SendMailAsync(message);
+ return SendMailAsync(message, cancellationToken: default);
}
public Task SendMailAsync(MailMessage message)
{
+ return SendMailAsync(message, cancellationToken: default);
+ }
+
+ public Task SendMailAsync(string from, string recipients, string subject, string body, CancellationToken cancellationToken)
+ {
+ var message = new MailMessage(from, recipients, subject, body);
+ return SendMailAsync(message, cancellationToken);
+ }
+
+ public Task SendMailAsync(MailMessage message, CancellationToken cancellationToken)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ {
+ return Task.FromCanceled(cancellationToken);
+ }
+
// Create a TaskCompletionSource to represent the operation
var tcs = new TaskCompletionSource<object>();
+ CancellationTokenRegistration ctr = default;
+
+ // Indicates whether the CTR has been set - captured in handler
+ int state = 0;
+
// Register a handler that will transfer completion results to the TCS Task
SendCompletedEventHandler handler = null;
- handler = (sender, e) => HandleCompletion(tcs, e, handler);
+ handler = (sender, e) =>
+ {
+ if (e.UserState == tcs)
+ {
+ try
+ {
+ ((SmtpClient)sender).SendCompleted -= handler;
+ if (Interlocked.Exchange(ref state, 1) != 0)
+ {
+ // A CTR has been set, we have to wait until it completes before completing the task
+ ctr.Dispose();
+ }
+ }
+ catch (ObjectDisposedException) { } // SendAsyncCancel will throw if SmtpClient was disposed
+ finally
+ {
+ if (e.Error != null) tcs.TrySetException(e.Error);
+ else if (e.Cancelled) tcs.TrySetCanceled();
+ else tcs.TrySetResult(null);
+ }
+ }
+ };
SendCompleted += handler;
// Start the async operation.
throw;
}
- // Return the task to represent the asynchronous operation
- return tcs.Task;
- }
+ ctr = cancellationToken.Register(s =>
+ {
+ ((SmtpClient)s).SendAsyncCancel();
+ }, this);
- private void HandleCompletion(TaskCompletionSource<object> tcs, AsyncCompletedEventArgs e, SendCompletedEventHandler handler)
- {
- if (e.UserState == tcs)
+ if (Interlocked.Exchange(ref state, 1) != 0)
{
- try { SendCompleted -= handler; }
- finally
- {
- if (e.Error != null) tcs.TrySetException(e.Error);
- else if (e.Cancelled) tcs.TrySetCanceled();
- else tcs.TrySetResult(null);
- }
+ // SendCompleted was already invoked, ensure the CTR completes before returning the task
+ ctr.Dispose();
}
+
+ // Return the task to represent the asynchronous operation
+ return tcs.Task;
}
--- /dev/null
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Mail;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Systen.Net.Mail.Tests
+{
+ public class LoopbackSmtpServer : IDisposable
+ {
+ private static readonly ReadOnlyMemory<byte> s_messageTerminator = new byte[] { (byte)'\r', (byte)'\n' };
+ private static readonly ReadOnlyMemory<byte> s_bodyTerminator = new byte[] { (byte)'\r', (byte)'\n', (byte)'.', (byte)'\r', (byte)'\n' };
+
+ public bool ReceiveMultipleConnections = false;
+ public bool SupportSmtpUTF8 = false;
+ public bool AdvertiseNtlmAuthSupport = false;
+
+ private bool _disposed = false;
+ private readonly Socket _listenSocket;
+ private readonly ConcurrentBag<Socket> _socketsToDispose;
+ private long _messageCounter = new Random().Next(1000, 2000);
+
+ public readonly int Port;
+ public SmtpClient CreateClient() => new SmtpClient("localhost", Port);
+
+ public Action<Socket> OnConnected;
+ public Action<string> OnHelloReceived;
+ public Action<string, string> OnCommandReceived;
+ public Action<string> OnUnknownCommand;
+ public Action<Socket> OnQuitReceived;
+
+ public string ClientDomain { get; private set; }
+ public string MailFrom { get; private set; }
+ public string MailTo { get; private set; }
+ public string UsernamePassword { get; private set; }
+ public string Username { get; private set; }
+ public string Password { get; private set; }
+ public string AuthMethodUsed { get; private set; }
+ public ParsedMailMessage Message { get; private set; }
+
+ public int ConnectionCount { get; private set; }
+ public int MessagesReceived { get; private set; }
+
+ public LoopbackSmtpServer()
+ {
+ _socketsToDispose = new ConcurrentBag<Socket>();
+ _listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ _socketsToDispose.Add(_listenSocket);
+
+ _listenSocket.Bind(new IPEndPoint(IPAddress.Any, 0));
+ Port = ((IPEndPoint)_listenSocket.LocalEndPoint).Port;
+ _listenSocket.Listen(1);
+
+ _ = Task.Run(async () =>
+ {
+ do
+ {
+ var socket = await _listenSocket.AcceptAsync();
+ _socketsToDispose.Add(socket);
+ ConnectionCount++;
+ _ = Task.Run(async () => await HandleConnectionAsync(socket));
+ }
+ while (ReceiveMultipleConnections);
+ });
+ }
+
+ private async Task HandleConnectionAsync(Socket socket)
+ {
+ var buffer = new byte[1024].AsMemory();
+
+ async ValueTask<string> ReceiveMessageAsync(bool isBody = false)
+ {
+ var terminator = isBody ? s_bodyTerminator : s_messageTerminator;
+ int suffix = terminator.Length;
+
+ int received = 0;
+ do
+ {
+ int read = await socket.ReceiveAsync(buffer.Slice(received), SocketFlags.None);
+ if (read == 0) return null;
+ received += read;
+ }
+ while (received < suffix || !buffer.Slice(received - suffix, suffix).Span.SequenceEqual(terminator.Span));
+
+ MessagesReceived++;
+ return Encoding.UTF8.GetString(buffer.Span.Slice(0, received - suffix));
+ }
+ async ValueTask SendMessageAsync(string text)
+ {
+ var bytes = buffer.Slice(0, Encoding.UTF8.GetBytes(text, buffer.Span) + 2);
+ bytes.Span[^2] = (byte)'\r';
+ bytes.Span[^1] = (byte)'\n';
+ await socket.SendAsync(bytes, SocketFlags.None);
+ }
+
+ try
+ {
+ OnConnected?.Invoke(socket);
+ await SendMessageAsync("220 localhost");
+
+ string message = await ReceiveMessageAsync();
+ Debug.Assert(message.ToLower().StartsWith("helo ") || message.ToLower().StartsWith("ehlo "));
+ ClientDomain = message.Substring(5).ToLower();
+ OnCommandReceived?.Invoke(message.Substring(0, 4), ClientDomain);
+ OnHelloReceived?.Invoke(ClientDomain);
+
+ await SendMessageAsync("250-localhost, mock server here");
+ if (SupportSmtpUTF8) await SendMessageAsync("250-SMTPUTF8");
+ await SendMessageAsync("250 AUTH PLAIN LOGIN" + (AdvertiseNtlmAuthSupport ? " NTLM" : ""));
+
+ while ((message = await ReceiveMessageAsync()) != null)
+ {
+ int colonIndex = message.IndexOf(':');
+ string command = colonIndex == -1 ? message : message.Substring(0, colonIndex);
+ string argument = command.Length == message.Length ? string.Empty : message.Substring(colonIndex + 1).Trim();
+
+ OnCommandReceived?.Invoke(command, argument);
+
+ if (command.StartsWith("AUTH", StringComparison.OrdinalIgnoreCase))
+ {
+ var parts = command.Split(' ');
+ Debug.Assert(parts.Length > 1, "Expected an actual auth request");
+
+ AuthMethodUsed = parts[1];
+
+ if (parts[1].Equals("LOGIN", StringComparison.OrdinalIgnoreCase))
+ {
+ if (parts.Length == 2)
+ {
+ await SendMessageAsync("334 VXNlcm5hbWU6");
+ Username = Encoding.UTF8.GetString(Convert.FromBase64String(await ReceiveMessageAsync()));
+ }
+ else
+ {
+ Username = Encoding.UTF8.GetString(Convert.FromBase64String(parts[2]));
+ }
+ await SendMessageAsync("334 UGFzc3dvcmQ6");
+ Password = Encoding.UTF8.GetString(Convert.FromBase64String(await ReceiveMessageAsync()));
+ UsernamePassword = Username + Password;
+ await SendMessageAsync("235 Authentication successful");
+ }
+ else if (parts[1].Equals("NTLM", StringComparison.OrdinalIgnoreCase))
+ {
+ await SendMessageAsync("12345 I lied, I can't speak NTLM - here's an invalid response");
+ }
+ else await SendMessageAsync("504 scheme not supported");
+ continue;
+ }
+
+ switch (command.ToUpper())
+ {
+ case "MAIL FROM":
+ MailFrom = argument;
+ await SendMessageAsync("250 Ok");
+ break;
+
+ case "RCPT TO":
+ MailTo = argument;
+ await SendMessageAsync("250 Ok");
+ break;
+
+ case "DATA":
+ await SendMessageAsync("354 Start mail input; end with <CRLF>.<CRLF>");
+ string data = await ReceiveMessageAsync(true);
+ Message = ParsedMailMessage.Parse(data);
+ await SendMessageAsync("250 Ok: queued as " + Interlocked.Increment(ref _messageCounter));
+ break;
+
+ case "QUIT":
+ OnQuitReceived?.Invoke(socket);
+ await SendMessageAsync("221 Bye");
+ return;
+
+ default:
+ OnUnknownCommand?.Invoke(message);
+ await SendMessageAsync("500 Idk that command");
+ break;
+ }
+ }
+ }
+ catch { }
+ finally
+ {
+ try
+ {
+ socket.Shutdown(SocketShutdown.Both);
+ }
+ finally
+ {
+ socket?.Close();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ foreach (var socket in _socketsToDispose)
+ {
+ try
+ {
+ socket.Close();
+ }
+ catch { }
+ }
+ _socketsToDispose.Clear();
+ }
+ }
+
+
+ public class ParsedMailMessage
+ {
+ public readonly IReadOnlyDictionary<string, string> Headers;
+ public readonly string Body;
+
+ private string GetHeader(string name) => Headers.TryGetValue(name, out string value) ? value : "NOT-PRESENT";
+ public string From => GetHeader("From");
+ public string To => GetHeader("To");
+ public string Subject => GetHeader("Subject");
+
+ private ParsedMailMessage(Dictionary<string, string> headers, string body)
+ {
+ Headers = headers;
+ Body = body;
+ }
+
+ public static ParsedMailMessage Parse(string data)
+ {
+ Dictionary<string, string> headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ ReadOnlySpan<char> dataSpan = data;
+ string body = null;
+
+ while (!dataSpan.IsEmpty)
+ {
+ int endOfLine = dataSpan.IndexOf('\n');
+ Debug.Assert(endOfLine != -1, "Expected valid \r\n terminated lines");
+ var line = dataSpan.Slice(0, endOfLine).TrimEnd('\r');
+
+ if (line.IsEmpty)
+ {
+ body = dataSpan.Slice(endOfLine + 1).TrimEnd(stackalloc char[] { '\r', '\n' }).ToString();
+ break;
+ }
+ else
+ {
+ int colon = line.IndexOf(':');
+ Debug.Assert(colon != -1, "Expected a valid header");
+ headers.Add(line.Slice(0, colon).Trim().ToString(), line.Slice(colon + 1).Trim().ToString());
+ dataSpan = dataSpan.Slice(endOfLine + 1);
+ }
+ }
+
+ return new ParsedMailMessage(headers, body);
+ }
+ }
+ }
+}
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
+using Systen.Net.Mail.Tests;
using Xunit;
namespace System.Net.Mail.Tests
[Fact]
public void TestMailDelivery()
{
- SmtpServer server = new SmtpServer();
- SmtpClient client = new SmtpClient("localhost", server.EndPoint.Port);
- client.Credentials = new NetworkCredential("user", "password");
+ using var server = new LoopbackSmtpServer();
+ using SmtpClient client = server.CreateClient();
+ client.Credentials = new NetworkCredential("Foo", "Bar");
MailMessage msg = new MailMessage("foo@example.com", "bar@example.com", "hello", "howdydoo");
- string clientDomain = IPGlobalProperties.GetIPGlobalProperties().HostName.Trim().ToLower();
- try
- {
- Thread t = new Thread(server.Run);
- t.Start();
- client.Send(msg);
- t.Join();
+ client.Send(msg);
- Assert.Equal("<foo@example.com>", server.MailFrom);
- Assert.Equal("<bar@example.com>", server.MailTo);
- Assert.Equal("hello", server.Subject);
- Assert.Equal("howdydoo", server.Body);
- Assert.Equal(clientDomain, server.ClientDomain);
- }
- finally
- {
- server.Stop();
- }
+ Assert.Equal("<foo@example.com>", server.MailFrom);
+ Assert.Equal("<bar@example.com>", server.MailTo);
+ Assert.Equal("hello", server.Message.Subject);
+ Assert.Equal("howdydoo", server.Message.Body);
+ Assert.Equal(GetClientDomain(), server.ClientDomain);
+ Assert.Equal("Foo", server.Username);
+ Assert.Equal("Bar", server.Password);
+ Assert.Equal("LOGIN", server.AuthMethodUsed, StringComparer.OrdinalIgnoreCase);
}
[Fact]
[InlineData(null)]
public async Task TestMailDeliveryAsync(string body)
{
- SmtpServer server = new SmtpServer();
- SmtpClient client = new SmtpClient("localhost", server.EndPoint.Port);
+ using var server = new LoopbackSmtpServer();
+ using SmtpClient client = server.CreateClient();
MailMessage msg = new MailMessage("foo@example.com", "bar@example.com", "hello", body);
- string clientDomain = IPGlobalProperties.GetIPGlobalProperties().HostName.Trim().ToLower();
- try
- {
- Thread t = new Thread(server.Run);
- t.Start();
- await client.SendMailAsync(msg).TimeoutAfter((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
- t.Join();
-
- Assert.Equal("<foo@example.com>", server.MailFrom);
- Assert.Equal("<bar@example.com>", server.MailTo);
- Assert.Equal("hello", server.Subject);
- Assert.Equal(body ?? "", server.Body);
- Assert.Equal(clientDomain, server.ClientDomain);
- }
- finally
- {
- server.Stop();
- }
+ await client.SendMailAsync(msg).TimeoutAfter((int)TimeSpan.FromSeconds(30).TotalMilliseconds);
+
+ Assert.Equal("<foo@example.com>", server.MailFrom);
+ Assert.Equal("<bar@example.com>", server.MailTo);
+ Assert.Equal("hello", server.Message.Subject);
+ Assert.Equal(body ?? "", server.Message.Body);
+ Assert.Equal(GetClientDomain(), server.ClientDomain);
}
[Fact]
+ [PlatformSpecific(TestPlatforms.Windows)] // NTLM support required, see https://github.com/dotnet/corefx/issues/28961
public async Task TestCredentialsCopyInAsyncContext()
{
- SmtpServer server = new SmtpServer();
- SmtpClient client = new SmtpClient("localhost", server.EndPoint.Port);
+ using var server = new LoopbackSmtpServer();
+ using SmtpClient client = server.CreateClient();
MailMessage msg = new MailMessage("foo@example.com", "bar@example.com", "hello", "howdydoo");
- string clientDomain = IPGlobalProperties.GetIPGlobalProperties().HostName.Trim().ToLower();
CredentialCache cache = new CredentialCache();
- cache.Add("localhost", server.EndPoint.Port, "NTLM", CredentialCache.DefaultNetworkCredentials);
+ cache.Add("localhost", server.Port, "NTLM", CredentialCache.DefaultNetworkCredentials);
client.Credentials = cache;
- try
- {
- Thread t = new Thread(server.Run);
- t.Start();
- await client.SendMailAsync(msg);
- t.Join();
-
- Assert.Equal("<foo@example.com>", server.MailFrom);
- Assert.Equal("<bar@example.com>", server.MailTo);
- Assert.Equal("hello", server.Subject);
- Assert.Equal("howdydoo", server.Body);
- Assert.Equal(clientDomain, server.ClientDomain);
- }
- finally
- {
- server.Stop();
- }
+ // The mock server doesn't actually understand NTLM, but still advertises support for it
+ server.AdvertiseNtlmAuthSupport = true;
+ await Assert.ThrowsAsync<SmtpException>(async () => await client.SendMailAsync(msg));
+
+ Assert.Equal("NTLM", server.AuthMethodUsed, StringComparer.OrdinalIgnoreCase);
}
// If the server does not support `SMTPUTF8` or use `SmtpDeliveryFormat.SevenBit`, the server should received this subject.
const string subjectBase64 = "=?utf-8?B?VGVzdCDmtYvor5UgQ29udGFpbiDljIXlkKsgVVRGOA==?=";
- SmtpServer server = new SmtpServer();
+ using var server = new LoopbackSmtpServer();
+ using SmtpClient client = server.CreateClient();
// Setting up Server Support for `SMTPUTF8`.
server.SupportSmtpUTF8 = useSmtpUTF8;
- SmtpClient client = new SmtpClient("localhost", server.EndPoint.Port);
-
if (useSevenBit)
{
// Subject will be encoded by Base64.
MailMessage msg = new MailMessage("foo@example.com", "bar@example.com", subjectText, "hello \u9ad8\u575a\u679c");
msg.HeadersEncoding = msg.BodyEncoding = msg.SubjectEncoding = System.Text.Encoding.UTF8;
- try
+ if (useAsyncSend)
{
- Thread t = new Thread(server.Run);
- t.Start();
-
- if (useAsyncSend)
- {
- client.SendMailAsync(msg).Wait();
- }
- else
- {
- client.Send(msg);
- }
+ client.SendMailAsync(msg).Wait();
+ }
+ else
+ {
+ client.Send(msg);
+ }
- if (useSevenBit || !useSmtpUTF8)
- {
- Assert.Equal(subjectBase64, server.Subject);
- }
- else
- {
- Assert.Equal(subjectText, server.Subject);
- }
+ if (useSevenBit || !useSmtpUTF8)
+ {
+ Assert.Equal(subjectBase64, server.Message.Subject);
}
- finally
+ else
{
- server.Stop();
+ Assert.Equal(subjectText, server.Message.Subject);
}
}
+
+ [Fact]
+ public void SendMailAsync_CanBeCanceled_CancellationToken_SetAlready()
+ {
+ using var server = new LoopbackSmtpServer();
+ using SmtpClient client = server.CreateClient();
+
+ CancellationTokenSource cts = new CancellationTokenSource();
+ cts.Cancel();
+
+ var message = new MailMessage("foo@internet.com", "bar@internet.com", "Foo", "Bar");
+
+ Task sendTask = client.SendMailAsync(message, cts.Token);
+
+ // Tests an implementation detail - if a CT is already set a canceled task will be returned
+ Assert.True(sendTask.IsCanceled);
+ }
+
+ [Fact]
+ public async Task SendMailAsync_CanBeCanceled_CancellationToken()
+ {
+ using var server = new LoopbackSmtpServer();
+ using SmtpClient client = server.CreateClient();
+
+ server.ReceiveMultipleConnections = true;
+
+ // The server will introduce some fake latency so that the operation can be canceled before the request completes
+ ManualResetEvent serverMre = new ManualResetEvent(false);
+ server.OnConnected += _ => serverMre.WaitOne();
+
+ CancellationTokenSource cts = new CancellationTokenSource();
+
+ var message = new MailMessage("foo@internet.com", "bar@internet.com", "Foo", "Bar");
+
+ Task sendTask = client.SendMailAsync(message, cts.Token);
+
+ cts.Cancel();
+ await Task.Delay(500);
+ serverMre.Set();
+
+ await Assert.ThrowsAsync<TaskCanceledException>(async () => await sendTask);
+
+ // We should still be able to send mail on the SmtpClient instance
+ await client.SendMailAsync(message);
+
+ Assert.Equal("<foo@internet.com>", server.MailFrom);
+ Assert.Equal("<bar@internet.com>", server.MailTo);
+ Assert.Equal("Foo", server.Message.Subject);
+ Assert.Equal("Bar", server.Message.Body);
+ Assert.Equal(GetClientDomain(), server.ClientDomain);
+ }
+
+ private static string GetClientDomain() => IPGlobalProperties.GetIPGlobalProperties().HostName.Trim().ToLower();
}
}
+++ /dev/null
-// Licensed to the .NET Foundation under one or more agreements.
-// See the LICENSE file in the project root for more information.
-//
-// SmtpServer.cs - Dummy SMTP server used to test SmtpClient
-//
-// Author:
-// Raja R Harinath <harinath@hurrynot.org>
-//
-
-using System.Diagnostics;
-using System.IO;
-using System.Net.Sockets;
-using System.Text;
-
-namespace System.Net.Mail.Tests
-{
- public class SmtpServer
- {
- private string _mailfrom, _mailto, _subject, _body, _clientdomain;
-
- public string MailFrom => _mailfrom;
- public string MailTo => _mailto;
- public string Subject => _subject;
- public string Body => _body;
- public string ClientDomain => _clientdomain;
-
- private readonly TcpListener _server;
-
- public IPEndPoint EndPoint
- {
- get { return (IPEndPoint)_server.LocalEndpoint; }
- }
-
- public bool SupportSmtpUTF8 { get; set; }
-
- public SmtpServer()
- {
- IPAddress address = IPAddress.Loopback;
- _server = new TcpListener(address, 0);
- _server.Start(1);
- }
-
- private static void WriteNS(NetworkStream ns, string s)
- {
- Trace("response", s);
- byte[] bytes = Encoding.ASCII.GetBytes(s);
- ns.Write(bytes, 0, bytes.Length);
- }
-
- public void Stop()
- {
- _server.Stop();
- }
-
- public void Run()
- {
- try
- {
- string s;
- using (TcpClient client = _server.AcceptTcpClient())
- {
- Trace("connection", EndPoint.Port);
- using (NetworkStream ns = client.GetStream())
- {
- WriteNS(ns, "220 localhost\r\n");
- using (StreamReader r = new StreamReader(ns, Encoding.UTF8))
- {
- while ((s = r.ReadLine()) != null && Dispatch(ns, r, s))
- ;
- }
- }
- }
- }
- catch (SocketException e)
- {
- // The _server might have been stopped.
- if (e.SocketErrorCode != SocketError.Interrupted)
- throw;
- }
- }
-
- // return false == terminate
- private bool Dispatch(NetworkStream ns, StreamReader r, string s)
- {
- Trace("command", s);
- if (s.Length < 4)
- {
- WriteNS(ns, "502 Unrecognized\r\n");
- return false;
- }
-
- bool retval = true;
- switch (s.Substring(0, 4))
- {
- case "HELO":
- _clientdomain = s.Substring(5).Trim().ToLower();
- break;
- case "EHLO":
- _clientdomain = s.Substring(5).Trim().ToLower();
- WriteNS(ns, "250-localhost Hello" + s.Substring(5, s.Length - 5) + "\r\n");
- WriteNS(ns, "250-AUTH PLAIN\r\n");
- if (SupportSmtpUTF8)
- {
- WriteNS(ns, "250-SMTPUTF8\r\n");
- }
- break;
- case "QUIT":
- WriteNS(ns, "221 Quit\r\n");
- return false;
- case "MAIL":
- _mailfrom = s.Substring(10);
- break;
- case "RCPT":
- _mailto = s.Substring(8);
- break;
- case "DATA":
- WriteNS(ns, "354 Continue\r\n");
- while ((s = r.ReadLine()) != null)
- {
- if (s == ".")
- break;
-
- if (s.StartsWith("Subject:"))
- {
- _subject = s.Substring(9, s.Length - 9);
- }
- else if (s == "" && _body == null)
- {
- _body = r.ReadLine();
- }
- }
- Trace("end of data", s);
- retval = (s != null);
- break;
- default:
- WriteNS(ns, "502 Unrecognized\r\n");
- return true;
- }
-
- WriteNS(ns, "250 OK\r\n");
- return retval;
- }
-
- [Conditional("TEST")]
- private static void Trace(string key, object value)
- {
- Console.Error.WriteLine("{0}: {1}", key, value);
- }
- }
-}
<Compile Include="MailMessageTest.cs" />
<Compile Include="SmtpClientTest.cs" />
<Compile Include="SmtpExceptionTest.cs" />
- <Compile Include="SmtpServer.cs" />
+ <Compile Include="LoopbackSmtpServer.cs" />
<Compile Include="$(CommonTestPath)System\Diagnostics\Tracing\TestEventListener.cs">
<Link>Common\System\Diagnostics\Tracing\TestEventListener.cs</Link>
</Compile>