Skip to content

Commit 14a264e

Browse files
committed
Add atext validation tests and update documentation
- Add new AtextValidationTests - Update README - Update Header and RFC 5322 implementations - Improve readme verification tests
1 parent d4b732b commit 14a264e

File tree

5 files changed

+111
-47
lines changed

5 files changed

+111
-47
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ let message = RFC_5322.Message(
116116
messageId: "<[email protected]>",
117117
body: "Meeting summary...".data(using: .utf8)!,
118118
additionalHeaders: [
119-
RFC_5322.Header(name: RFC_5322.Header.Name("X-Priority"), value: "1"),
119+
RFC_5322.Header(name: "X-Priority", value: "1"),
120120
RFC_5322.Header(name: .contentType, value: "text/plain; charset=utf-8")
121121
]
122122
)
@@ -227,4 +227,4 @@ This library is released under the Apache License 2.0. See [LICENSE](LICENSE) fo
227227

228228
## Contributing
229229

230-
Contributions are welcome! Please feel free to submit a Pull Request.
230+
Contributions are welcome! Please feel free to submit a Pull Request.

Sources/RFC_5322/Header.swift

Lines changed: 37 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ extension RFC_5322.Header {
5858
/// ## Example
5959
///
6060
/// ```swift
61-
/// let from = RFC_5322.Header.Name.from
62-
/// let custom = RFC_5322.Header.Name("X-Custom-Header")
61+
/// let from: Self = fro
62+
/// let custom: Self = "X-Custom-Header"
6363
///
6464
/// var headers: [RFC_5322.Header] = []
6565
/// headers.append(.init(name: .messageId, value: "<[email protected]>"))
@@ -116,125 +116,121 @@ extension RFC_5322.Header {
116116

117117
extension RFC_5322.Header.Name {
118118
/// From: header (originator)
119-
public static let from = RFC_5322.Header.Name("From")
119+
public static let from: Self = "From"
120120

121121
/// To: header (primary recipients)
122-
public static let to = RFC_5322.Header.Name("To")
122+
public static let to: Self = "To"
123123

124124
/// Cc: header (carbon copy recipients)
125-
public static let cc = RFC_5322.Header.Name("Cc")
125+
public static let cc: Self = "Cc"
126126

127127
/// Bcc: header (blind carbon copy recipients)
128-
public static let bcc = RFC_5322.Header.Name("Bcc")
128+
public static let bcc: Self = "Bcc"
129129

130130
/// Subject: header
131-
public static let subject = RFC_5322.Header.Name("Subject")
131+
public static let subject: Self = "Subject"
132132

133133
/// Date: header
134-
public static let date = RFC_5322.Header.Name("Date")
134+
public static let date: Self = "Date"
135135

136136
/// Message-ID: header (unique message identifier)
137-
public static let messageId = RFC_5322.Header.Name("Message-ID")
137+
public static let messageId: Self = "Message-ID"
138138

139139
/// Reply-To: header
140-
public static let replyTo = RFC_5322.Header.Name("Reply-To")
140+
public static let replyTo: Self = "Reply-To"
141141

142142
/// Sender: header (actual sender if different from From)
143-
public static let sender = RFC_5322.Header.Name("Sender")
143+
public static let sender: Self = "Sender"
144144

145145
/// In-Reply-To: header (message being replied to)
146-
public static let inReplyTo = RFC_5322.Header.Name("In-Reply-To")
146+
public static let inReplyTo: Self = "In-Reply-To"
147147

148148
/// References: header (related messages)
149-
public static let references = RFC_5322.Header.Name("References")
149+
public static let references: Self = "References"
150150

151151
/// Resent-From: header
152-
public static let resentFrom = RFC_5322.Header.Name("Resent-From")
152+
public static let resentFrom: Self = "Resent-From"
153153

154154
/// Resent-To: header
155-
public static let resentTo = RFC_5322.Header.Name("Resent-To")
155+
public static let resentTo: Self = "Resent-To"
156156

157157
/// Resent-Date: header
158-
public static let resentDate = RFC_5322.Header.Name("Resent-Date")
158+
public static let resentDate: Self = "Resent-Date"
159159

160160
/// Resent-Message-ID: header
161-
public static let resentMessageId = RFC_5322.Header.Name("Resent-Message-ID")
161+
public static let resentMessageId: Self = "Resent-Message-ID"
162162

163163
/// Return-Path: header
164-
public static let returnPath = RFC_5322.Header.Name("Return-Path")
164+
public static let returnPath: Self = "Return-Path"
165165

166166
/// Received: header (mail transfer path)
167-
public static let received = RFC_5322.Header.Name("Received")
167+
public static let received: Self = "Received"
168168
}
169169

170170
// MARK: - MIME Headers (RFC 2045)
171171

172172
extension RFC_5322.Header.Name {
173173
/// Content-Type: header (media type)
174-
public static let contentType = RFC_5322.Header.Name("Content-Type")
174+
public static let contentType: Self = "Content-Type"
175175

176176
/// Content-Transfer-Encoding: header
177-
public static let contentTransferEncoding = RFC_5322.Header.Name("Content-Transfer-Encoding")
177+
public static let contentTransferEncoding: Self = "Content-Transfer-Encoding"
178178

179179
/// MIME-Version: header
180-
public static let mimeVersion = RFC_5322.Header.Name("MIME-Version")
180+
public static let mimeVersion: Self = "MIME-Version"
181181

182182
/// Content-Disposition: header
183-
public static let contentDisposition = RFC_5322.Header.Name("Content-Disposition")
183+
public static let contentDisposition: Self = "Content-Disposition"
184184

185185
/// Content-ID: header
186-
public static let contentId = RFC_5322.Header.Name("Content-ID")
186+
public static let contentId: Self = "Content-ID"
187187

188188
/// Content-Description: header
189-
public static let contentDescription = RFC_5322.Header.Name("Content-Description")
189+
public static let contentDescription: Self = "Content-Description"
190190
}
191191

192192
// MARK: - Common Extension Headers
193193

194194
extension RFC_5322.Header.Name {
195195
/// X-Mailer: header (mail client identification)
196-
public static let xMailer = RFC_5322.Header.Name("X-Mailer")
196+
public static let xMailer: Self = "X-Mailer"
197197

198198
/// X-Priority: header (message priority)
199-
public static let xPriority = RFC_5322.Header.Name("X-Priority")
199+
public static let xPriority: Self = "X-Priority"
200200

201201
/// List-Unsubscribe: header (mailing list unsubscribe)
202-
public static let listUnsubscribe = RFC_5322.Header.Name("List-Unsubscribe")
202+
public static let listUnsubscribe: Self = "List-Unsubscribe"
203203

204204
/// List-ID: header (mailing list identifier)
205-
public static let listId = RFC_5322.Header.Name("List-ID")
205+
public static let listId: Self = "List-ID"
206206

207207
/// Precedence: header
208-
public static let precedence = RFC_5322.Header.Name("Precedence")
208+
public static let precedence: Self = "Precedence"
209209

210210
/// Auto-Submitted: header
211-
public static let autoSubmitted = RFC_5322.Header.Name("Auto-Submitted")
211+
public static let autoSubmitted: Self = "Auto-Submitted"
212212
}
213213

214214
// MARK: - Apple Mail Headers
215215

216216
extension RFC_5322.Header.Name {
217217
/// X-Apple-Base-Url: header
218-
public static let xAppleBaseUrl = RFC_5322.Header.Name("X-Apple-Base-Url")
218+
public static let xAppleBaseUrl: Self = "X-Apple-Base-Url"
219219

220220
/// X-Universally-Unique-Identifier: header
221-
public static let xUniversallyUniqueIdentifier = RFC_5322.Header.Name(
222-
"X-Universally-Unique-Identifier"
223-
)
221+
public static let xUniversallyUniqueIdentifier: Self = "X-Universally-Unique-Identifier"
224222

225223
/// X-Apple-Mail-Remote-Attachments: header
226-
public static let xAppleMailRemoteAttachments = RFC_5322.Header.Name(
227-
"X-Apple-Mail-Remote-Attachments"
228-
)
224+
public static let xAppleMailRemoteAttachments: Self = "X-Apple-Mail-Remote-Attachments"
229225

230226
/// X-Apple-Windows-Friendly: header
231-
public static let xAppleWindowsFriendly = RFC_5322.Header.Name("X-Apple-Windows-Friendly")
227+
public static let xAppleWindowsFriendly: Self = "X-Apple-Windows-Friendly"
232228

233229
/// X-Apple-Mail-Signature: header
234-
public static let xAppleMailSignature = RFC_5322.Header.Name("X-Apple-Mail-Signature")
230+
public static let xAppleMailSignature: Self = "X-Apple-Mail-Signature"
235231

236232
/// X-Uniform-Type-Identifier: header
237-
public static let xUniformTypeIdentifier = RFC_5322.Header.Name("X-Uniform-Type-Identifier")
233+
public static let xUniformTypeIdentifier: Self = "X-Uniform-Type-Identifier"
238234
}
239235

240236
// MARK: - Name Protocol Conformances

Sources/RFC_5322/RFC 5322.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,10 @@ extension RFC_5322.EmailAddress {
168168
nonisolated(unsafe) private static let addressRegex = /(?:((?:\".*?\"|[^<]+)\s+))?<(.*?)@(.*?)>/
169169

170170
// Dot-atom regex: series of atoms separated by dots
171-
// More restrictive than RFC 5321 - no "!" or "|" allowed
171+
// RFC 5322 Section 3.2.3 defines atext (RFC 5321 references this same definition)
172+
// atext = ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"
172173
nonisolated(unsafe) private static let dotAtomRegex =
173-
/[a-zA-Z0-9#$%&'\*\+\-\/=\?\^_`\{\}~]+(?:\.[a-zA-Z0-9#$%&'\*\+\-\/=\?\^_`\{\}~]+)*/
174+
/[a-zA-Z0-9!#$%&'\*\+\-\/=\?\^_`\{\|}~]+(?:\.[a-zA-Z0-9!#$%&'\*\+\-\/=\?\^_`\{\|}~]+)*/
174175

175176
// Quoted string regex: allows any printable character except unescaped quotes
176177
nonisolated(unsafe) private static let quotedRegex = /(?:[^"\\\r\n]|\\["\\])+/
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// AtextValidationTests.swift
3+
// RFC 5322 Tests
4+
//
5+
// Tests to verify RFC 5322 Section 3.2.3 atext character support
6+
//
7+
8+
import Foundation
9+
import RFC_5322
10+
import Testing
11+
12+
@Suite("atext Character Validation")
13+
struct AtextValidationTests {
14+
15+
@Test("All RFC 5322 atext special characters are accepted")
16+
func allAtextCharsAccepted() throws {
17+
// RFC 5322 Section 3.2.3 defines atext as:
18+
// atext = ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"
19+
20+
let specialChars = "!#$%&'*+-/=?^_`{|}~"
21+
22+
// Test each character individually
23+
for char in specialChars {
24+
let email = try RFC_5322.EmailAddress("test\(char)[email protected]")
25+
#expect(email.localPart.description.contains(char))
26+
}
27+
}
28+
29+
@Test("Exclamation mark (!) is accepted in local-part")
30+
func exclamationMarkAccepted() throws {
31+
let email = try RFC_5322.EmailAddress("[email protected]")
32+
#expect(email.localPart.description == "user!tag")
33+
#expect(email.addressValue == "[email protected]")
34+
}
35+
36+
@Test("Pipe character (|) is accepted in local-part")
37+
func pipeCharacterAccepted() throws {
38+
let email = try RFC_5322.EmailAddress("user|[email protected]")
39+
#expect(email.localPart.description == "user|tag")
40+
#expect(email.addressValue == "user|[email protected]")
41+
}
42+
43+
@Test("All atext characters together in local-part")
44+
func allCharsTogetherAccepted() throws {
45+
let allChars = "test!#$%&'*+-/=?^_`{|}~user"
46+
let email = try RFC_5322.EmailAddress("\(allChars)@example.com")
47+
#expect(email.localPart.description == allChars)
48+
}
49+
50+
@Test("Multiple atext special characters in same address")
51+
func multipleSpecialChars() throws {
52+
// Test addresses with multiple special characters
53+
let testAddresses = [
54+
55+
56+
57+
58+
59+
"test!user|[email protected]"
60+
]
61+
62+
for address in testAddresses {
63+
let email = try RFC_5322.EmailAddress(address)
64+
#expect(email.addressValue == address)
65+
}
66+
}
67+
}

Tests/RFC_5322 Tests/ReadmeVerificationTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ struct ReadmeVerificationTests {
1515
@Test("README Line 54-57: Parse email address")
1616
func parseEmailAddress() throws {
1717
let email = try RFC_5322.EmailAddress("[email protected]")
18-
#expect(email.localPart.stringValue == "user")
18+
#expect(email.localPart.description == "user")
1919
#expect(email.domain.name == "example.com")
2020
}
2121

@@ -34,7 +34,7 @@ struct ReadmeVerificationTests {
3434
domain: .init("example.com")
3535
)
3636
#expect(addr.displayName == "Jane Smith")
37-
#expect(addr.localPart.stringValue == "jane")
37+
#expect(addr.localPart.description == "jane")
3838
#expect(addr.domain.name == "example.com")
3939
}
4040

0 commit comments

Comments
 (0)