Skip to content

Commit

Permalink
Retrieve hostname for message-id as private var from 'from' address (… (
Browse files Browse the repository at this point in the history
#49)

* Retrieve hostname for message-id as private var from 'from' address (#45)

* Fix message id

* add unit test for message-id

* Trigger build

* Linux fixes
  • Loading branch information
quanvo87 authored Aug 31, 2017
1 parent 205c746 commit 4b07014
Show file tree
Hide file tree
Showing 35 changed files with 372 additions and 640 deletions.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![Swift-SMTP bird](https://github.com/IBM-Swift/Swift-SMTP/blob/master/Assets/swift-smtp-bird.png)

Swift package for sending emails through an SMTP server.
Swift package for sending emails to an SMTP server.

[![Build Status](https://travis-ci.com/IBM-Swift/Swift-SMTP.svg?token=prrUzhsjZyXD9LxyWxge&branch=master)](https://travis-ci.com/IBM-Swift/Swift-SMTP.svg?token=prrUzhsjZyXD9LxyWxge&branch=master)
![macOS](https://img.shields.io/badge/os-macOS-green.svg?style=flat)
Expand Down Expand Up @@ -49,10 +49,10 @@ Create a `Mail` object and use your `smtp` handle to send it. To set the sender
let drLight = User(name: "Dr. Light", email: "[email protected]")
let megaman = User(name: "Megaman", email: "[email protected]")

let mail = smtp.makeMail(from: drLight,
to: [megaman],
subject: "Humans and robots living together in harmony and equality.",
text: "That was my ultimate wish.")
let mail = Mail(from: drLight,
to: [megaman],
subject: "Humans and robots living together in harmony and equality.",
text: "That was my ultimate wish.")

smtp.send(mail) { (err) in
if let err = err {
Expand All @@ -67,12 +67,12 @@ Add Cc and Bcc:
let roll = User(name: "Roll", email: "[email protected]")
let zero = User(name: "Zero", email: "[email protected]")

let mail = smtp.makeMail(from: drLight,
to: [megaman],
cc: [roll],
bcc: [zero],
subject: "Robots should be used for the betterment of mankind.",
text: "Any other use would be...unethical.")
let mail = Mail(from: drLight,
to: [megaman],
cc: [roll],
bcc: [zero],
subject: "Robots should be used for the betterment of mankind.",
text: "Any other use would be...unethical.")

smtp.send(mail)
```
Expand All @@ -99,10 +99,10 @@ let dataAttachment = Attachment(data: data,
inline: false) // send as a standalone attachment

// Create a `Mail` and include the `Attachment`s
let mail = smtp.makeMail(from: from,
to: [to],
subject: "Check out this image and JSON file!",
attachments: [htmlAttachment, dataAttachment]) // attachments we created earlier
let mail = Mail(from: from,
to: [to],
subject: "Check out this image and JSON file!",
attachments: [htmlAttachment, dataAttachment]) // attachments we created earlier

// Send the mail
smtp.send(mail)
Expand Down
14 changes: 10 additions & 4 deletions Sources/Mail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ public struct Mail {
return "<\(uuid).Swift-SMTP@\(hostname)>"
}

let hostname: String
private var hostname: String {
let fullEmail = from.email
let atIndex = fullEmail.characters.index(of: "@")
let hostStart = fullEmail.index(after: atIndex!)
let hostnameVal = fullEmail.substring(from: hostStart)

return hostnameVal
}

let from: User
let to: [User]
let cc: [User]
Expand Down Expand Up @@ -57,16 +65,14 @@ public struct Mail {
/// overwrite each other. Defaults to none. The
/// following will be ignored: CONTENT-TYPE,
/// CONTENT-DISPOSITION, CONTENT-TRANSFER-ENCODING.
public init(hostname: String,
from: User,
public init(from: User,
to: [User],
cc: [User] = [],
bcc: [User] = [],
subject: String = "",
text: String = "",
attachments: [Attachment] = [],
additionalHeaders: [String: String] = [:]) {
self.hostname = hostname
self.from = from
self.to = to
self.cc = cc
Expand Down
38 changes: 0 additions & 38 deletions Sources/SMTP.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,44 +110,6 @@ public struct SMTP {
self.timeout = timeout
}

/// Returns a `Mail`.
///
/// - Parameters:
/// - from: The `User` that the `Mail` will be sent from.
/// - to: Array of `User`s to send the `Mail` to.
/// - cc: Array of `User`s to cc. Defaults to none.
/// - bcc: Array of `User`s to bcc. Defaults to none.
/// - subject: Subject of the `Mail`. Defaults to none.
/// - text: Text of the `Mail`. Defaults to none.
/// - attachments: Array of `Attachment`s for the `Mail`. If the `Mail`
/// has multiple `Attachment`s that are alternatives to
/// to plain text, the last one will be used as the
/// alternative (all the `Attachments` will still be
/// sent). Defaults to none.
/// - additionalHeaders: Additional headers for the `Mail`. Header keys
/// are capitalized and duplicate keys will
/// overwrite each other. Defaults to none. The
/// following will be ignored: CONTENT-TYPE,
/// CONTENT-DISPOSITION, CONTENT-TRANSFER-ENCODING.
public func makeMail(from: User,
to: [User],
cc: [User] = [],
bcc: [User] = [],
subject: String = "",
text: String = "",
attachments: [Attachment] = [],
additionalHeaders: [String: String] = [:]) -> Mail {
return Mail(hostname: hostname,
from: from,
to: to,
cc: cc,
bcc: bcc,
subject: subject,
text: text,
attachments: attachments,
additionalHeaders: additionalHeaders)
}

/// Send an email.
///
/// - Parameters:
Expand Down
26 changes: 24 additions & 2 deletions Tests/SwiftSMTPTests/Constant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,23 @@ let localPort: SwiftSMTP.Port? = nil
let localSecure: Bool? = nil
let localAuthMethods: [AuthMethod]? = nil

let validMessageIdMsg = "Valid Message-Id header found"
let invalidMessageIdMsg = "Message-Id header missing or invalid"
let multipleMessageIdsMsg = "More than one Message-Id header found"


let hostname: String = {
if let localHostname = localHostname {
return localHostname
} else {
return "smtp.gmail.com"
}
let url = credentialsDir.appendingPathComponent("/hostname.txt")
guard
let data = try? Data(contentsOf: url),
let hostname = String(data: data, encoding: .utf8)
else {
return "smtp.gmail.com"
}
return hostname.trimmingCharacters(in: NSCharacterSet.whitespacesAndNewlines)
}()

let email: String = {
Expand Down Expand Up @@ -91,6 +102,17 @@ let authMethods: [AuthMethod] = {
}
}()

let senderEmailDomain: String = {
let senderEmail = email
if let atIndex = senderEmail.characters.index(of: "@") {
let domainStart = senderEmail.index(after: atIndex)
let domainVal = senderEmail.substring(from: domainStart)

return domainVal
}
return "gmail.com"
}()

let domainName = "localhost"
let timeout: UInt = 10

Expand Down
20 changes: 10 additions & 10 deletions Tests/SwiftSMTPTests/TestDataSender.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class TestDataSender: XCTestCase {
case .failure(_): XCTFail()
case .success(let socket):
let attachment = Attachment(data: data, mime: "application/json", name: "file.json")
let mail = smtp.makeMail(from: from, to: [to], subject: #function, text: text, attachments: [attachment])
let mail = Mail(from: from, to: [to], subject: #function, text: text, attachments: [attachment])

sender = Sender(socket: socket, pending: [mail], progress: nil) { (sent, failed) in
XCTAssertEqual(sent.count, 1)
Expand Down Expand Up @@ -103,7 +103,7 @@ class TestDataSender: XCTestCase {
case .failure(_): XCTFail()
case .success(let socket):
let attachment = Attachment(filePath: imgFilePath)
let mail = smtp.makeMail(from: from, to: [to], subject: #function, text: text, attachments: [attachment])
let mail = Mail(from: from, to: [to], subject: #function, text: text, attachments: [attachment])

sender = Sender(socket: socket, pending: [mail], progress: nil) { (sent, failed) in
XCTAssertEqual(sent.count, 1)
Expand Down Expand Up @@ -156,7 +156,7 @@ class TestDataSender: XCTestCase {
case .failure(_): XCTFail()
case .success(let socket):
let attachment = Attachment(htmlContent: html)
let mail = smtp.makeMail(from: from, to: [to], subject: #function, text: text, attachments: [attachment])
let mail = Mail(from: from, to: [to], subject: #function, text: text, attachments: [attachment])

sender = Sender(socket: socket, pending: [mail], progress: nil) { (sent, failed) in
XCTAssertEqual(sent.count, 1)
Expand All @@ -183,7 +183,7 @@ class TestDataSender: XCTestCase {
func testSendData() {
let x = expectation(description: "Send mail with data attachment.")
let dataAttachment = Attachment(data: data, mime: "application/json", name: "file.json")
let mail = smtp.makeMail(from: from, to: [to], subject: "Data attachment", text: text, attachments: [dataAttachment])
let mail = Mail(from: from, to: [to], subject: "Data attachment", text: text, attachments: [dataAttachment])
smtp.send(mail) { (err) in
XCTAssertNil(err, String(describing: err))
x.fulfill()
Expand All @@ -194,7 +194,7 @@ class TestDataSender: XCTestCase {
func testSendFile() {
let x = expectation(description: "Send mail with file attachment.")
let fileAttachment = Attachment(filePath: imgFilePath)
let mail = smtp.makeMail(from: from, to: [to], subject: "File attachment", text: text, attachments: [fileAttachment])
let mail = Mail(from: from, to: [to], subject: "File attachment", text: text, attachments: [fileAttachment])
smtp.send(mail) { (err) in
XCTAssertNil(err, String(describing: err))
x.fulfill()
Expand All @@ -205,7 +205,7 @@ class TestDataSender: XCTestCase {
func testSendHTML() {
let x = expectation(description: "Send mail with HTML attachment.")
let htmlAttachment = Attachment(htmlContent: html, alternative: false)
let mail = smtp.makeMail(from: from, to: [to], subject: "HTML attachment", text: text, attachments: [htmlAttachment])
let mail = Mail(from: from, to: [to], subject: "HTML attachment", text: text, attachments: [htmlAttachment])
smtp.send(mail) { (err) in
XCTAssertNil(err, String(describing: err))
x.fulfill()
Expand All @@ -216,7 +216,7 @@ class TestDataSender: XCTestCase {
func testSendHTMLAlternative() {
let x = expectation(description: "Send mail with HTML as alternative to text.")
let htmlAttachment = Attachment(htmlContent: html)
let mail = smtp.makeMail(from: from, to: [to], subject: "HTML alternative attachment", text: text, attachments: [htmlAttachment])
let mail = Mail(from: from, to: [to], subject: "HTML alternative attachment", text: text, attachments: [htmlAttachment])
smtp.send(mail) { (err) in
XCTAssertNil(err, String(describing: err))
x.fulfill()
Expand All @@ -228,7 +228,7 @@ class TestDataSender: XCTestCase {
let x = expectation(description: "Send mail with multiple attachments.")
let fileAttachment = Attachment(filePath: imgFilePath)
let htmlAttachment = Attachment(htmlContent: html, alternative: false)
let mail = smtp.makeMail(from: from, to: [to], subject: "Multiple attachments", text: text, attachments: [fileAttachment, htmlAttachment])
let mail = Mail(from: from, to: [to], subject: "Multiple attachments", text: text, attachments: [fileAttachment, htmlAttachment])
smtp.send(mail) { (err) in
XCTAssertNil(err, String(describing: err))
x.fulfill()
Expand All @@ -238,7 +238,7 @@ class TestDataSender: XCTestCase {

func testSendNonASCII() {
let x = expectation(description: "Send mail with non ASCII character.")
let mail = smtp.makeMail(from: from, to: [to], subject: "Non ASCII", text: "💦")
let mail = Mail(from: from, to: [to], subject: "Non ASCII", text: "💦")
smtp.send(mail) { (err) in
XCTAssertNil(err, String(describing: err))
x.fulfill()
Expand All @@ -250,7 +250,7 @@ class TestDataSender: XCTestCase {
let x = expectation(description: "Send mail with an attachment that references a related attachment.")
let fileAttachment = Attachment(filePath: imgFilePath, additionalHeaders: ["CONTENT-ID": "megaman-pic"])
let htmlAttachment = Attachment(htmlContent: "<html><img src=\"cid:megaman-pic\"/>\(text)</html>", relatedAttachments: [fileAttachment])
let mail = smtp.makeMail(from: from, to: [to], subject: "HTML with related attachment", text: text, attachments: [htmlAttachment])
let mail = Mail(from: from, to: [to], subject: "HTML with related attachment", text: text, attachments: [htmlAttachment])
smtp.send(mail) { (err) in
XCTAssertNil(err, String(describing: err))
x.fulfill()
Expand Down
36 changes: 35 additions & 1 deletion Tests/SwiftSMTPTests/TestMiscellaneous.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ extension TestMiscellaneous {
}

func testMailHeaders() {
let headers = smtp.makeMail(from: from, to: [to], cc: [to2], subject: "Test", text: text, additionalHeaders: ["header": "val"]).headersString
let headers = Mail(from: from, to: [to], cc: [to2], subject: "Test", text: text, additionalHeaders: ["header": "val"]).headersString

let to_ = "TO: =?UTF-8?Q?Megaman?= <\(email)>"
XCTAssert(headers.contains(to_), "Mail header did not contain \(to_)")
Expand All @@ -78,6 +78,9 @@ extension TestMiscellaneous {
let mimeVersion = "MIME-VERSION: 1.0 (Swift-SMTP)"
XCTAssert(headers.contains(mimeVersion), "Mail header did not contain \(mimeVersion)")

let messageIdSearchResponse = findMessageId(inString: headers)
XCTAssert(validMessageIdMsg == messageIdSearchResponse, messageIdSearchResponse)

XCTAssert(headers.contains("HEADER"), "Mail header did not contain \"header\".")
XCTAssert(headers.contains("val"), "Mail header did not contain \"val\".")
}
Expand All @@ -97,3 +100,34 @@ extension TestMiscellaneous {
XCTAssertEqual(user.mime, expected, "result: \(user.mime) != expected: \(expected)")
}
}

// Utilities
extension TestMiscellaneous {
fileprivate func findMessageId(inString compareString: String) -> String {
let messageIdHeaderPrefix = "MESSAGE-ID: <"
// example uuid: E621E1F8-C36C-495A-93FC-0C247A3E6E5F
let uuidRegEx = "[A-Z0-9]{8}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{12}"
let messageIdHeaderSuffix = ".Swift-SMTP@\(senderEmailDomain)>"
let regexPattern = "\(messageIdHeaderPrefix)\(uuidRegEx)\(messageIdHeaderSuffix)"

guard let regex = try? NSRegularExpression(pattern: regexPattern, options: .anchorsMatchLines) else {
return "Unable to create Regular Expression object"
}

let rangeLocation = 0
let rangeLength = NSString(string: compareString).length
let searchRange = NSMakeRange(rangeLocation, rangeLength)

// run the regex
let matches = regex.matches(in: compareString, options: .withoutAnchoringBounds, range: searchRange)

switch matches.count {
case 0:
return invalidMessageIdMsg
case 1:
return validMessageIdMsg
default:
return multipleMessageIdsMsg
}
}
}
Loading

0 comments on commit 4b07014

Please sign in to comment.