From 1e56faee187f01939fcf9ec5a925ea9b144838d6 Mon Sep 17 00:00:00 2001 From: Oliver Drobnik Date: Fri, 7 Mar 2025 12:02:24 +0100 Subject: [PATCH] Removed unnessary imports, and implemented Linux functions for MIME handling #3 --- Demos/SwiftIMAPCLI/OSLogHandler.swift | 7 +- Demos/SwiftIMAPCLI/main.swift | 35 +++-- Demos/SwiftSMTPCLI/OSLogHandler.swift | 97 ++++++------ Demos/SwiftSMTPCLI/main.swift | 8 +- .../Extensions/String+Utilities.swift | 66 -------- .../IMAP/Handler/FetchHeadersHandler.swift | 1 - .../IMAP/Handler/FetchPartHandler.swift | 1 - .../IMAP/Handler/FetchStructureHandler.swift | 1 - .../IMAP/Handler/LogoutHandler.swift | 1 - .../SwiftIMAP/IMAP/Handler/MoveHandler.swift | 1 - .../IMAP/Handler/SelectHandler.swift | 1 - .../Extensions/String+Hostname.swift | 101 ++++++++++++ .../Extensions/String+MIME.swift | 118 ++++++++++++++ .../Extensions/StringExtensions.swift | 145 ------------------ Sources/SwiftSMTP/SMTPServer.swift | 1 - 15 files changed, 304 insertions(+), 280 deletions(-) create mode 100644 Sources/SwiftMailCore/Extensions/String+Hostname.swift create mode 100644 Sources/SwiftMailCore/Extensions/String+MIME.swift diff --git a/Demos/SwiftIMAPCLI/OSLogHandler.swift b/Demos/SwiftIMAPCLI/OSLogHandler.swift index 5de0378..f0d43a6 100644 --- a/Demos/SwiftIMAPCLI/OSLogHandler.swift +++ b/Demos/SwiftIMAPCLI/OSLogHandler.swift @@ -6,9 +6,12 @@ // import Foundation -import OSLog import Logging +#if canImport(OSLog) + +import OSLog + // Custom LogHandler that bridges Swift Logging to OSLog struct OSLogHandler: LogHandler { let label: String @@ -57,3 +60,5 @@ struct OSLogHandler: LogHandler { os_log("%{public}@", log: log, type: type, message.description) } } + +#endif diff --git a/Demos/SwiftIMAPCLI/main.swift b/Demos/SwiftIMAPCLI/main.swift index 35303ba..780d3f5 100644 --- a/Demos/SwiftIMAPCLI/main.swift +++ b/Demos/SwiftIMAPCLI/main.swift @@ -8,25 +8,32 @@ import Logging import SwiftDotenv import NIOIMAP +#if canImport(OSLog) + +import OSLog + // Set default log level to info - will only show important logs // Per the cursor rules: Use OS_LOG_DISABLE=1 to see log output as needed LoggingSystem.bootstrap { label in - // Create an OSLog-based logger - let category = label.split(separator: ".").last?.description ?? "default" - let osLogger = OSLog(subsystem: "com.cocoanetics.SwiftIMAPCLI", category: category) - - // Set log level to info by default (or trace if verbose logging is enabled) - var handler = OSLogHandler(label: label, log: osLogger) - // Check if we need verbose logging - if ProcessInfo.processInfo.environment["ENABLE_DEBUG_OUTPUT"] == "1" { - handler.logLevel = .trace - } else { - handler.logLevel = .info - } - - return handler + // Create an OSLog-based logger + let category = label.split(separator: ".").last?.description ?? "default" + let osLogger = OSLog(subsystem: "com.cocoanetics.SwiftIMAPCLI", category: category) + + // Set log level to info by default (or trace if SWIFT_LOG_LEVEL is set to trace) + var handler = OSLogHandler(label: label, log: osLogger) + + // Check if we need verbose logging + if ProcessInfo.processInfo.environment["ENABLE_DEBUG_OUTPUT"] == "1" { + handler.logLevel = .trace + } else { + handler.logLevel = .info + } + + return handler } +#endif + // Create a logger for the main application using Swift Logging let logger = Logger(label: "com.cocoanetics.SwiftIMAPCLI.Main") diff --git a/Demos/SwiftSMTPCLI/OSLogHandler.swift b/Demos/SwiftSMTPCLI/OSLogHandler.swift index 5de0378..1b50dc5 100644 --- a/Demos/SwiftSMTPCLI/OSLogHandler.swift +++ b/Demos/SwiftSMTPCLI/OSLogHandler.swift @@ -6,54 +6,59 @@ // import Foundation -import OSLog import Logging +#if canImport(OSLog) + +import OSLog + // Custom LogHandler that bridges Swift Logging to OSLog struct OSLogHandler: LogHandler { - let label: String - let log: OSLog - - // Required property for LogHandler protocol - var logLevel: Logging.Logger.Level = .debug // Set to debug to capture all logs - - // Required property for LogHandler protocol - var metadata = Logging.Logger.Metadata() - - // Required subscript for LogHandler protocol - subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? { - get { - return metadata[metadataKey] - } - set { - metadata[metadataKey] = newValue - } - } - - // Initialize with a label and OSLog instance - init(label: String, log: OSLog) { - self.label = label - self.log = log - } - - // Required method for LogHandler protocol - func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, source: String, file: String, function: String, line: UInt) { - // Map Swift Logging levels to OSLog types - let type: OSLogType - switch level { - case .trace, .debug: - type = .debug - case .info, .notice: - type = .info - case .warning: - type = .default - case .error: - type = .error - case .critical: - type = .fault - } - - // Log the message using OSLog - os_log("%{public}@", log: log, type: type, message.description) - } + let label: String + let log: OSLog + + // Required property for LogHandler protocol + var logLevel: Logging.Logger.Level = .debug // Set to debug to capture all logs + + // Required property for LogHandler protocol + var metadata = Logging.Logger.Metadata() + + // Required subscript for LogHandler protocol + subscript(metadataKey metadataKey: String) -> Logging.Logger.Metadata.Value? { + get { + return metadata[metadataKey] + } + set { + metadata[metadataKey] = newValue + } + } + + // Initialize with a label and OSLog instance + init(label: String, log: OSLog) { + self.label = label + self.log = log + } + + // Required method for LogHandler protocol + func log(level: Logging.Logger.Level, message: Logging.Logger.Message, metadata: Logging.Logger.Metadata?, source: String, file: String, function: String, line: UInt) { + // Map Swift Logging levels to OSLog types + let type: OSLogType + switch level { + case .trace, .debug: + type = .debug + case .info, .notice: + type = .info + case .warning: + type = .default + case .error: + type = .error + case .critical: + type = .fault + } + + // Log the message using OSLog + os_log("%{public}@", log: log, type: type, message.description) + } } + +#endif diff --git a/Demos/SwiftSMTPCLI/main.swift b/Demos/SwiftSMTPCLI/main.swift index 03cc7d5..e62bcd7 100644 --- a/Demos/SwiftSMTPCLI/main.swift +++ b/Demos/SwiftSMTPCLI/main.swift @@ -3,11 +3,15 @@ import Foundation import SwiftSMTP -import OSLog import Logging import SwiftDotenv import SwiftMailCore + +#if canImport(OSLog) + +import OSLog + // Set default log level to info - will only show important logs // Per the cursor rules: Use OS_LOG_DISABLE=1 to see log output as needed LoggingSystem.bootstrap { label in @@ -28,6 +32,8 @@ LoggingSystem.bootstrap { label in return handler } +#endif + // Create a logger for the main application using Swift Logging let logger = Logger(label: "com.cocoanetics.SwiftSMTPCLI.Main") diff --git a/Sources/SwiftIMAP/Extensions/String+Utilities.swift b/Sources/SwiftIMAP/Extensions/String+Utilities.swift index 1ecf16e..6d098c0 100644 --- a/Sources/SwiftIMAP/Extensions/String+Utilities.swift +++ b/Sources/SwiftIMAP/Extensions/String+Utilities.swift @@ -3,74 +3,8 @@ import Foundation import NIOIMAPCore -import UniformTypeIdentifiers extension String { - /// Sanitize a filename to ensure it's valid - /// - Returns: A sanitized filename - func sanitizedFileName() -> String { - let invalidCharacters = CharacterSet(charactersIn: ":/\\?%*|\"<>") - return self - .components(separatedBy: invalidCharacters) - .joined(separator: "_") - .replacingOccurrences(of: " ", with: "_") - } - - /// Get a file extension for a given MIME type - /// - Parameter mimeType: The full MIME type (e.g., "text/plain", "image/jpeg") - /// - Returns: An appropriate file extension (without the dot) - static func fileExtension(for mimeType: String) -> String? { - // Try to get the UTType from the MIME type - if let utType = UTType(mimeType: mimeType) { - // Get the preferred file extension - if let preferredExtension = utType.preferredFilenameExtension { - return preferredExtension - } - } - - return nil - } - - // Helper function to get MIME type from file URL using UTI - static func mimeType(for fileExtension: String) -> String { - // First try to get UTType from file extension - - if let utType = UTType(filenameExtension: fileExtension) { - // If we have a UTType, try to get its MIME type - if let mimeType = utType.preferredMIMEType { - return mimeType - } - } - - - // Fallback to common extensions if UTI doesn't work - let pathExtension = fileExtension.lowercased() - switch pathExtension { - case "jpg", "jpeg": - return "image/jpeg" - case "png": - return "image/png" - case "gif": - return "image/gif" - case "svg": - return "image/svg+xml" - case "pdf": - return "application/pdf" - case "txt": - return "text/plain" - case "html", "htm": - return "text/html" - case "doc", "docx": - return "application/msword" - case "xls", "xlsx": - return "application/vnd.ms-excel" - case "zip": - return "application/zip" - default: - return "application/octet-stream" - } - } - /// Parse a string range (e.g., "1:10") into a SequenceSet /// - Returns: A SequenceSet object /// - Throws: An error if the range string is invalid diff --git a/Sources/SwiftIMAP/IMAP/Handler/FetchHeadersHandler.swift b/Sources/SwiftIMAP/IMAP/Handler/FetchHeadersHandler.swift index ced710f..8f0a5b4 100644 --- a/Sources/SwiftIMAP/IMAP/Handler/FetchHeadersHandler.swift +++ b/Sources/SwiftIMAP/IMAP/Handler/FetchHeadersHandler.swift @@ -2,7 +2,6 @@ // A specialized handler for IMAP fetch headers operations import Foundation -import os.log @preconcurrency import NIOIMAP import NIOIMAPCore import NIO diff --git a/Sources/SwiftIMAP/IMAP/Handler/FetchPartHandler.swift b/Sources/SwiftIMAP/IMAP/Handler/FetchPartHandler.swift index e4282d2..d3f7818 100644 --- a/Sources/SwiftIMAP/IMAP/Handler/FetchPartHandler.swift +++ b/Sources/SwiftIMAP/IMAP/Handler/FetchPartHandler.swift @@ -2,7 +2,6 @@ // A specialized handler for IMAP fetch part operations import Foundation -import os.log @preconcurrency import NIOIMAP import NIOIMAPCore import NIO diff --git a/Sources/SwiftIMAP/IMAP/Handler/FetchStructureHandler.swift b/Sources/SwiftIMAP/IMAP/Handler/FetchStructureHandler.swift index d911e78..d1ab1b6 100644 --- a/Sources/SwiftIMAP/IMAP/Handler/FetchStructureHandler.swift +++ b/Sources/SwiftIMAP/IMAP/Handler/FetchStructureHandler.swift @@ -2,7 +2,6 @@ // A specialized handler for IMAP fetch structure operations import Foundation -import os.log @preconcurrency import NIOIMAP import NIOIMAPCore import NIO diff --git a/Sources/SwiftIMAP/IMAP/Handler/LogoutHandler.swift b/Sources/SwiftIMAP/IMAP/Handler/LogoutHandler.swift index c538f14..9b8f1b5 100644 --- a/Sources/SwiftIMAP/IMAP/Handler/LogoutHandler.swift +++ b/Sources/SwiftIMAP/IMAP/Handler/LogoutHandler.swift @@ -2,7 +2,6 @@ // Handler for IMAP LOGOUT command import Foundation -import os.log @preconcurrency import NIOIMAP import NIOIMAPCore import NIO diff --git a/Sources/SwiftIMAP/IMAP/Handler/MoveHandler.swift b/Sources/SwiftIMAP/IMAP/Handler/MoveHandler.swift index 5becf7c..439c5dd 100644 --- a/Sources/SwiftIMAP/IMAP/Handler/MoveHandler.swift +++ b/Sources/SwiftIMAP/IMAP/Handler/MoveHandler.swift @@ -2,7 +2,6 @@ // Handler for IMAP MOVE command import Foundation -import os.log @preconcurrency import NIOIMAP import NIOIMAPCore import NIO diff --git a/Sources/SwiftIMAP/IMAP/Handler/SelectHandler.swift b/Sources/SwiftIMAP/IMAP/Handler/SelectHandler.swift index 3c99367..ea4c165 100644 --- a/Sources/SwiftIMAP/IMAP/Handler/SelectHandler.swift +++ b/Sources/SwiftIMAP/IMAP/Handler/SelectHandler.swift @@ -2,7 +2,6 @@ // Handler for IMAP SELECT command import Foundation -import os.log @preconcurrency import NIOIMAP import NIOIMAPCore import NIO diff --git a/Sources/SwiftMailCore/Extensions/String+Hostname.swift b/Sources/SwiftMailCore/Extensions/String+Hostname.swift new file mode 100644 index 0000000..0eb9fa9 --- /dev/null +++ b/Sources/SwiftMailCore/Extensions/String+Hostname.swift @@ -0,0 +1,101 @@ +// String+Hostname.swift +// Hostname and IP-related extensions for String + +import Foundation + +#if canImport(Darwin) +import Darwin +#else +import Glibc +#endif + +extension String { + /** + Get the local hostname for EHLO/HELO commands + - Returns: The local hostname + */ + public static var localHostname: String { + #if os(macOS) && !targetEnvironment(macCatalyst) + // Host is only available on macOS + if let hostname = Host.current().name { + return hostname + } + #else + // Use system call on Linux and other platforms + var hostname = [CChar](repeating: 0, count: Int(256)) // Linux typically uses 256 as max hostname length + if gethostname(&hostname, hostname.count) == 0 { + if let name = String(validatingUTF8: hostname), !name.isEmpty, name != "localhost" { + return name + } + } + #endif + + // Try to get a local IP address as a fallback + if let localIP = String.localIPAddress { + return "[\(localIP)]" + } + + // Use a domain-like format as a last resort + return "swift-mail-client.local" + } + + /** + Get the local IP address + - Returns: The local IP address as a string, or nil if not available + */ + public static var localIPAddress: String? { + var ifaddr: UnsafeMutablePointer? + + guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { + return nil + } + + defer { + freeifaddrs(ifaddr) + } + + // Iterate through linked list of interfaces + var currentAddr: UnsafeMutablePointer? = firstAddr + var foundAddress: String? = nil + + while let addr = currentAddr { + let interface = addr.pointee + + // Check for IPv4 or IPv6 interface + let addrFamily = interface.ifa_addr.pointee.sa_family + if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) { + // Check interface name starts with "en" (Ethernet) or "wl" (WiFi) + let name = String(cString: interface.ifa_name) + if name.hasPrefix("en") || name.hasPrefix("wl") || name.hasPrefix("eth") { + // Convert interface address to a human readable string + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + + #if canImport(Darwin) + let saLen = socklen_t(interface.ifa_addr.pointee.sa_len) + #else + let saLen = addrFamily == UInt8(AF_INET) ? + socklen_t(MemoryLayout.size) : + socklen_t(MemoryLayout.size) + #endif + + // Get address info + if getnameinfo(interface.ifa_addr, + saLen, + &hostname, socklen_t(hostname.count), + nil, 0, + NI_NUMERICHOST) == 0 { + if let address = String(validatingUTF8: hostname) { + foundAddress = address + break + } + } + } + } + + // Move to next interface + currentAddr = interface.ifa_next + } + + return foundAddress + } +} \ No newline at end of file diff --git a/Sources/SwiftMailCore/Extensions/String+MIME.swift b/Sources/SwiftMailCore/Extensions/String+MIME.swift new file mode 100644 index 0000000..9e5e9eb --- /dev/null +++ b/Sources/SwiftMailCore/Extensions/String+MIME.swift @@ -0,0 +1,118 @@ +// String+MIME.swift +// MIME-related extensions for String + +import Foundation + +#if canImport(UniformTypeIdentifiers) +import UniformTypeIdentifiers +#endif + +extension String { + /// Sanitize a filename to ensure it's valid + /// - Returns: A sanitized filename + public func sanitizedFileName() -> String { + let invalidCharacters = CharacterSet(charactersIn: ":/\\?%*|\"<>") + return self + .components(separatedBy: invalidCharacters) + .joined(separator: "_") + .replacingOccurrences(of: " ", with: "_") + } + + #if canImport(UniformTypeIdentifiers) + /// Get a file extension for a given MIME type + /// - Parameter mimeType: The full MIME type (e.g., "text/plain", "image/jpeg") + /// - Returns: An appropriate file extension (without the dot) + public static func fileExtension(for mimeType: String) -> String? { + // Try to get the UTType from the MIME type + if let utType = UTType(mimeType: mimeType) { + // Get the preferred file extension + return utType.preferredFilenameExtension + } + return nil + } + + /// Get MIME type for a file extension + /// - Parameter fileExtension: The file extension (without dot) + /// - Returns: The corresponding MIME type, or application/octet-stream if unknown + public static func mimeType(for fileExtension: String) -> String { + // Try to get UTType from file extension + if let utType = UTType(filenameExtension: fileExtension), + let mimeType = utType.preferredMIMEType { + return mimeType + } + return "application/octet-stream" + } + #else + // MIME type mappings for Linux and other platforms + private static let mimeTypes: [String: String] = { + loadMimeTypeMappings() + }() + + private static let extensionTypes: [String: String] = { + // Create reverse mapping + var extMap: [String: String] = [:] + for (ext, mime) in mimeTypes { + extMap[mime] = ext + } + return extMap + }() + + private static func loadMimeTypeMappings() -> [String: String] { + let path = "/etc/mime.types" + guard let contents = try? String(contentsOfFile: path) else { + return fallbackMimeTypes() + } + + var mimeMap: [String: String] = [:] + for line in contents.split(separator: "\n") { + let trimmed = line.trimmingCharacters(in: .whitespaces) + if trimmed.isEmpty || trimmed.hasPrefix("#") { + continue + } + + let parts = trimmed.split(separator: .whitespaces, omittingEmptySubsequences: true) + if let mimeType = parts.first { + for ext in parts.dropFirst() { + mimeMap[String(ext)] = String(mimeType) + } + } + } + + // If the system file was empty or had no valid entries, use fallback + return mimeMap.isEmpty ? fallbackMimeTypes() : mimeMap + } + + private static func fallbackMimeTypes() -> [String: String] { + return [ + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "png": "image/png", + "gif": "image/gif", + "svg": "image/svg+xml", + "pdf": "application/pdf", + "txt": "text/plain", + "html": "text/html", + "htm": "text/html", + "doc": "application/msword", + "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "xls": "application/vnd.ms-excel", + "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "zip": "application/zip" + ] + } + + /// Get a file extension for a given MIME type + /// - Parameter mimeType: The full MIME type (e.g., "text/plain", "image/jpeg") + /// - Returns: An appropriate file extension (without the dot) + public static func fileExtension(for mimeType: String) -> String? { + return extensionTypes[mimeType] + } + + /// Get MIME type for a file extension + /// - Parameter fileExtension: The file extension (without dot) + /// - Returns: The corresponding MIME type, or application/octet-stream if unknown + public static func mimeType(for fileExtension: String) -> String { + return mimeTypes[fileExtension.lowercased()] ?? "application/octet-stream" + } + #endif +} diff --git a/Sources/SwiftMailCore/Extensions/StringExtensions.swift b/Sources/SwiftMailCore/Extensions/StringExtensions.swift index 740c973..4e0d24a 100644 --- a/Sources/SwiftMailCore/Extensions/StringExtensions.swift +++ b/Sources/SwiftMailCore/Extensions/StringExtensions.swift @@ -2,8 +2,6 @@ // String extensions for the SwiftMailCore library import Foundation -import Darwin -import UniformTypeIdentifiers extension String { /// Redacts sensitive information that appears after the specified keyword @@ -62,147 +60,4 @@ extension String { return "\(preservedPart) [credentials redacted]" } } - - /** - Get the local hostname for EHLO/HELO commands - - Returns: The local hostname - */ - public static var localHostname: String { - // Try to get the actual hostname -#if os(macOS) && !targetEnvironment(macCatalyst) - // Host is only available on macOS - if let hostname = Host.current().name { - return hostname - } -#else - // Use ProcessInfo for Apple platforms - let hostname = ProcessInfo.processInfo.hostName - if !hostname.isEmpty && hostname != "localhost" { - return hostname - } -#endif - - // Try to get a local IP address as a fallback - if let localIP = String.localIPAddress { - return "[\(localIP)]" - } - - // Use a domain-like format as a last resort - return "swift-mail-client.local" - } - - /** - Get the local IP address - - Returns: The local IP address as a string, or nil if not available - */ - public static var localIPAddress: String? { - var ifaddr: UnsafeMutablePointer? - - guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else { - return nil - } - - defer { - freeifaddrs(ifaddr) - } - - // Iterate through linked list of interfaces - var currentAddr: UnsafeMutablePointer? = firstAddr - var foundAddress: String? = nil - - while let addr = currentAddr { - let interface = addr.pointee - - // Check for IPv4 or IPv6 interface - let addrFamily = interface.ifa_addr.pointee.sa_family - if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) { - // Check interface name starts with "en" (Ethernet) or "wl" (WiFi) - let name = String(cString: interface.ifa_name) - if name.hasPrefix("en") || name.hasPrefix("wl") { - // Convert interface address to a human readable string - var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) - - // Get address info - getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len), - &hostname, socklen_t(hostname.count), - nil, socklen_t(0), NI_NUMERICHOST) - - if let address = String(validatingUTF8: hostname) { - foundAddress = address - break - } - } - } - - // Move to next interface - currentAddr = interface.ifa_next - } - - return foundAddress - } - - /// Sanitize a filename to ensure it's valid - /// - Returns: A sanitized filename - func sanitizedFileName() -> String { - let invalidCharacters = CharacterSet(charactersIn: ":/\\?%*|\"<>") - return self - .components(separatedBy: invalidCharacters) - .joined(separator: "_") - .replacingOccurrences(of: " ", with: "_") - } - - /// Get a file extension for a given MIME type - /// - Parameter mimeType: The full MIME type (e.g., "text/plain", "image/jpeg") - /// - Returns: An appropriate file extension (without the dot) - public static func fileExtension(for mimeType: String) -> String? { - // Try to get the UTType from the MIME type - if let utType = UTType(mimeType: mimeType) { - // Get the preferred file extension - if let preferredExtension = utType.preferredFilenameExtension { - return preferredExtension - } - } - - return nil - } - - // Helper function to get MIME type from file URL using UTI - public static func mimeType(for fileExtension: String) -> String { - // First try to get UTType from file extension - - if let utType = UTType(filenameExtension: fileExtension) { - // If we have a UTType, try to get its MIME type - if let mimeType = utType.preferredMIMEType { - return mimeType - } - } - - - // Fallback to common extensions if UTI doesn't work - let pathExtension = fileExtension.lowercased() - switch pathExtension { - case "jpg", "jpeg": - return "image/jpeg" - case "png": - return "image/png" - case "gif": - return "image/gif" - case "svg": - return "image/svg+xml" - case "pdf": - return "application/pdf" - case "txt": - return "text/plain" - case "html", "htm": - return "text/html" - case "doc", "docx": - return "application/msword" - case "xls", "xlsx": - return "application/vnd.ms-excel" - case "zip": - return "application/zip" - default: - return "application/octet-stream" - } - } } diff --git a/Sources/SwiftSMTP/SMTPServer.swift b/Sources/SwiftSMTP/SMTPServer.swift index b6b4b20..ca74470 100644 --- a/Sources/SwiftSMTP/SMTPServer.swift +++ b/Sources/SwiftSMTP/SMTPServer.swift @@ -2,7 +2,6 @@ // A Swift SMTP client that encapsulates connection logic import Foundation -import os.log import NIO import NIOCore import NIOSSL