/
MailAddress.cs
318 lines (278 loc) · 12.6 KB
/
MailAddress.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Net.Mime;
using System.Text;
namespace System.Net.Mail
{
//
// This class stores the basic components of an e-mail address as described in RFC 2822 Section 3.4.
// Any parsing required is done with the MailAddressParser class.
//
public partial class MailAddress
{
// These components form an e-mail address when assembled as follows:
// "EncodedDisplayname" <userName@host>
private readonly Encoding _displayNameEncoding;
private readonly string _displayName;
private readonly string _userName;
private readonly string _host;
// For internal use only by MailAddressParser.
// The components were already validated before this is called.
internal MailAddress(string displayName, string userName, string domain, Encoding? displayNameEncoding)
{
_host = domain;
_userName = userName;
_displayName = displayName;
_displayNameEncoding = displayNameEncoding ?? Encoding.GetEncoding(MimeBasePart.DefaultCharSet);
Debug.Assert(_host != null,
"host was null in internal constructor");
Debug.Assert(userName != null,
"userName was null in internal constructor");
Debug.Assert(displayName != null,
"displayName was null in internal constructor");
}
public MailAddress(string address) : this(address, null, (Encoding?)null)
{
}
public MailAddress(string address, string? displayName) : this(address, displayName, (Encoding?)null)
{
}
//
// This constructor validates and stores the components of an e-mail address.
//
// Preconditions:
// - 'address' must not be null or empty.
//
// Postconditions:
// - The e-mail address components from the given 'address' are parsed, which should be formatted as:
// "EncodedDisplayname" <username@host>
// - If a 'displayName' is provided separately, it overrides whatever display name is parsed from the 'address'
// field. The display name does not need to be pre-encoded if a 'displayNameEncoding' is provided.
//
// A FormatException will be thrown if any of the components in 'address' are invalid.
public MailAddress(string address, string? displayName, Encoding? displayNameEncoding)
{
bool parseSuccess = TryParse(address, displayName, displayNameEncoding,
out (string displayName, string user, string host, Encoding displayNameEncoding) parsedData,
throwExceptionIfFail: true);
_displayName = parsedData.displayName;
_userName = parsedData.user;
_host = parsedData.host;
_displayNameEncoding = parsedData.displayNameEncoding;
Debug.Assert(parseSuccess);
}
/// <summary>
/// Create a new <see cref="MailAddress"/>. Does not throw an exception if the MailAddress cannot be created.
/// </summary>
/// <param name="address">A <see cref="string"/> that contains an email address.</param>
/// <param name="result">When this method returns, contains the <see cref="MailAddress"/> instance if address parsing succeed</param>
/// <returns>A <see cref="bool"/> value that is true if the <see cref="MailAddress"/> was successfully created; otherwise, false.</returns>
public static bool TryCreate([NotNullWhen(true)] string? address, [NotNullWhen(true)] out MailAddress? result) => TryCreate(address, displayName: null, out result);
/// <summary>
/// Create a new <see cref="MailAddress"/>. Does not throw an exception if the MailAddress cannot be created.
/// </summary>
/// <param name="address">A <see cref="string"/> that contains an email address.</param>
/// <param name="displayName">A <see cref="string"/> that contains the display name associated with address. This parameter can be null.</param>
/// <param name="result">When this method returns, contains the <see cref="MailAddress"/> instance if address parsing succeed</param>
/// <returns>A <see cref="bool"/> value that is true if the <see cref="MailAddress"/> was successfully created; otherwise, false.</returns>
public static bool TryCreate([NotNullWhen(true)] string? address, string? displayName, [NotNullWhen(true)] out MailAddress? result) => TryCreate(address, displayName, displayNameEncoding: null, out result);
/// <summary>
/// Create a new <see cref="MailAddress"/>. Does not throw an exception if the MailAddress cannot be created.
/// </summary>
/// <param name="address">A <see cref="string"/> that contains an email address.</param>
/// <param name="displayName">A <see cref="string"/> that contains the display name associated with address. This parameter can be null.</param>
/// <param name="displayNameEncoding">The <see cref="Encoding"/> that defines the character set used for displayName</param>
/// <param name="result">When this method returns, contains the <see cref="MailAddress"/> instance if address parsing succeed</param>
/// <returns>A <see cref="bool"/> value that is true if the <see cref="MailAddress"/> was successfully created; otherwise, false.</returns>
public static bool TryCreate([NotNullWhen(true)] string? address, string? displayName, Encoding? displayNameEncoding, [NotNullWhen(true)] out MailAddress? result)
{
if (TryParse(address, displayName, displayNameEncoding,
out (string displayName, string user, string host, Encoding displayNameEncoding) parsed,
throwExceptionIfFail: false))
{
result = new MailAddress(parsed.displayName, parsed.user, parsed.host, parsed.displayNameEncoding);
return true;
}
else
{
result = null;
return false;
}
}
private static bool TryParse([NotNullWhen(true)] string? address, string? displayName, Encoding? displayNameEncoding, out (string displayName, string user, string host, Encoding displayNameEncoding) parsedData, bool throwExceptionIfFail)
{
if (throwExceptionIfFail)
{
ArgumentException.ThrowIfNullOrEmpty(address);
}
else if (string.IsNullOrEmpty(address))
{
parsedData = default;
return false;
}
displayNameEncoding ??= Encoding.GetEncoding(MimeBasePart.DefaultCharSet);
displayName ??= string.Empty;
// Check for bounding quotes
if (!string.IsNullOrEmpty(displayName))
{
if (!MailAddressParser.TryNormalizeOrThrow(displayName, out displayName, throwExceptionIfFail))
{
parsedData = default;
return false;
}
if (displayName.Length >= 2 && displayName.StartsWith('\"') && displayName.EndsWith('\"'))
{
// Peel bounding quotes, they'll get re-added later.
displayName = displayName.Substring(1, displayName.Length - 2);
}
}
if (!MailAddressParser.TryParseAddress(address, out ParseAddressInfo info, throwExceptionIfFail))
{
parsedData = default;
return false;
}
// If we were not given a display name, use the one parsed from 'address'.
if (string.IsNullOrEmpty(displayName))
{
displayName = info.DisplayName;
}
parsedData = (displayName, info.User, info.Host, displayNameEncoding);
return true;
}
public string DisplayName
{
get
{
return _displayName;
}
}
public string User
{
get
{
return _userName;
}
}
private string GetUser(bool allowUnicode)
{
// Unicode usernames cannot be downgraded
if (!allowUnicode && !MimeBasePart.IsAscii(_userName, true))
{
throw new SmtpException(SR.Format(SR.SmtpNonAsciiUserNotSupported, Address));
}
return _userName;
}
public string Host
{
get
{
return _host;
}
}
private string GetHost(bool allowUnicode)
{
string domain = _host;
// Downgrade Unicode domain names
if (!allowUnicode && !MimeBasePart.IsAscii(domain, true))
{
IdnMapping mapping = new IdnMapping();
try
{
domain = mapping.GetAscii(domain);
}
catch (ArgumentException argEx)
{
throw new SmtpException(SR.Format(SR.SmtpInvalidHostName, Address), argEx);
}
}
return domain;
}
public string Address
{
get
{
return _userName + "@" + _host;
}
}
private string GetAddress(bool allowUnicode)
{
return GetUser(allowUnicode) + "@" + GetHost(allowUnicode);
}
private string SmtpAddress
{
get
{
return "<" + Address + ">";
}
}
internal string GetSmtpAddress(bool allowUnicode)
{
return "<" + GetAddress(allowUnicode) + ">";
}
/// <summary>
/// this returns the full address with quoted display name.
/// i.e. "some email address display name" <user@host>
/// if displayname is not provided then this returns only user@host (no angle brackets)
/// </summary>
public override string ToString()
{
if (string.IsNullOrEmpty(DisplayName))
{
return Address;
}
else
{
return "\"" + DisplayName.Replace("\"", "\\\"") + "\" " + SmtpAddress;
}
}
public override bool Equals([NotNullWhen(true)] object? value)
{
if (value == null)
{
return false;
}
return ToString().Equals(value.ToString(), StringComparison.InvariantCultureIgnoreCase);
}
public override int GetHashCode()
{
return StringComparer.InvariantCultureIgnoreCase.GetHashCode(ToString());
}
// Encodes the full email address, folding as needed
internal string Encode(int charsConsumed, bool allowUnicode)
{
string encodedAddress;
IEncodableStream encoder;
Debug.Assert(Address != null, "address was null");
//do we need to take into account the Display name? If so, encode it
if (!string.IsNullOrEmpty(_displayName))
{
//figure out the encoding type. If it's all ASCII and contains no CRLF then
//it does not need to be encoded for parity with other email clients. We will
//however fold at the end of the display name so that the email address itself can
//be appended.
if (MimeBasePart.IsAscii(_displayName, false) || allowUnicode)
{
encodedAddress = "\"" + _displayName + "\"";
}
else
{
//encode the displayname since it's non-ascii
encoder = EncodedStreamFactory.GetEncoderForHeader(_displayNameEncoding, false, charsConsumed);
encoder.EncodeString(_displayName, _displayNameEncoding);
encodedAddress = encoder.GetEncodedString();
}
//address should be enclosed in <> when a display name is present
encodedAddress += " " + GetSmtpAddress(allowUnicode);
}
else
{
//no display name, just return the address
encodedAddress = GetAddress(allowUnicode);
}
return encodedAddress;
}
}
}