最近有一个需求,需要App自己后台发送邮件,于是在网上找了一圈,最后,最有参考价值的一篇文章iOS 发送邮件SKPSMTPMessage,在此感谢此文章的作者,毕竟站在巨人的肩膀做事情,会事半功倍
本篇会把本人已踩过的坑都填平,希望对小伙伴们有所帮助
在使用半年后,发现了一些问题,修改了这个SKPSMTPMessage库里面的源码,在文章最后有更新代码
最终效果

坑一:思想上的坑
这个坑是思想上的错误,为什么这么说呢,请看下图

哇,这个库竟然是8年前的耶,都老古董了,估计不能用了吧,还是去找找别的库,看看吧,本人就是犯了这个思想上的坑,打心底就不想使用它,但找了很久,也没有更合适的第三方库(如果有其他的库,可以在后台发送邮件,请在下方评论区留言,本人将不胜感激)
这里给小伙伴们打一强心针,*SKPSMTPMessage这个库,在2022年,即使是swift工程里面依然可以使用*,OC工程里面肯定也是可以使用的
坑二:集成进去报错
前提:集成方式
本人这边是直接下载源码,把文件放到工程里面,没有使用pod的方式,如下

如下图,有很多错误
1.Cast of Objective-C pointer type 'NSString *' to C pointer type 'CFStringRef' (aka 'const struct __CFString *') requires a bridged cast
2.'autorelease' is unavailable: not available in automatic reference counting mode
3.ARC forbids explicit message send of 'autorelease'

大家不要慌,因为它是OC库,又是很久远的代码,应该还是手动管理内存MRC,所以我们沿着这个思路,我们只要让工程,兼容MRC模式,就可以了,
解决办法:只需要在NSStream+SKPSMTPExtensions.m和SKPSMTPMessage.m后面加上-fno-objc-arc

坑三:按上面提到的文章iOS 发送邮件SKPSMTPMessage,里面的说法,会有问题
如下图

应该是swift的版本更新导致的,正确的做法,在工程名-Bridgin-Header.h文件里面导入如下代码:
#import "SKPSMTPMessage.h"

坑四,使用过程中的坑
如何使用,大体方法,请参考iOS 发送邮件SKPSMTPMessage这里面的做法,这里只强调一点;文章里面提到
emailServer.pass = "xxxxxxxxxxxxxxx" //密码或者授权码
最好是用授权码,最好是用授权码,最好是用授权码; 密码有时候是会有问题的,不能正常的发送邮件出去

2022-12-11更新
在调试hotmail邮箱过程中,发现SKPSMTPMessage这个库,会有闪退的问题,经过多方探索,对SKPSMTPMessage.m文件的源码进行了相应修改,源码放下面
//
// SKPSMTPMessage.m
//
// Created by Ian Baird on 10/28/08.
//
// Copyright (c) 2008 Skorpiostech, Inc. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
#import "SKPSMTPMessage.h"
#import "NSData+Base64Additions.h"
#import "NSStream+SKPSMTPExtensions.h"
#import "HSK_CFUtilities.h"
NSString *kSKPSMTPPartContentDispositionKey = @"kSKPSMTPPartContentDispositionKey";
NSString *kSKPSMTPPartContentTypeKey = @"kSKPSMTPPartContentTypeKey";
NSString *kSKPSMTPPartMessageKey = @"kSKPSMTPPartMessageKey";
NSString *kSKPSMTPPartContentTransferEncodingKey = @"kSKPSMTPPartContentTransferEncodingKey";
#define SHORT_LIVENESS_TIMEOUT 20.0
#define LONG_LIVENESS_TIMEOUT 60.0
@interface SKPSMTPMessage ()
@property(nonatomic, retain) NSMutableString *inputString;
@property(retain) NSTimer *connectTimer;
@property(retain) NSTimer *watchdogTimer;
- (void)parseBuffer;
- (BOOL)sendParts;
- (void)cleanUpStreams;
- (void)startShortWatchdog;
- (void)stopWatchdog;
- (NSString *)formatAnAddress:(NSString *)address;
- (NSString *)formatAddresses:(NSString *)addresses;
@end
@implementation SKPSMTPMessage
@synthesize login, pass, relayHost, relayPorts, subject, fromEmail, toEmail, parts, requiresAuth, inputString, wantsSecure, \
delegate, connectTimer, connectTimeout, watchdogTimer, validateSSLChain;
@synthesize ccEmail;
@synthesize bccEmail;
#pragma mark -
#pragma mark Memory & Lifecycle
- (id)init
{
static NSArray *defaultPorts = nil;
if (!defaultPorts)
{
defaultPorts = [[NSArray alloc] initWithObjects:[NSNumber numberWithShort:587], [NSNumber numberWithShort:25], [NSNumber numberWithShort:465], nil];
}
if ((self = [super init]))
{
// Setup the default ports
self.relayPorts = defaultPorts;
// setup a default timeout (8 seconds)
connectTimeout = 8.0;
// by default, validate the SSL chain
validateSSLChain = YES;
}
return self;
}
- (void)dealloc
{
NSLog(@"dealloc %@", self);
self.login = nil;
self.pass = nil;
self.relayHost = nil;
self.relayPorts = nil;
self.subject = nil;
self.fromEmail = nil;
self.toEmail = nil;
self.ccEmail = nil;
self.bccEmail = nil;
self.parts = nil;
self.inputString = nil;
[inputStream release];
inputStream = nil;
[outputStream release];
outputStream = nil;
[self.connectTimer invalidate];
self.connectTimer = nil;
[self stopWatchdog];
[super dealloc];
}
- (id)copyWithZone:(NSZone *)zone
{
SKPSMTPMessage *smtpMessageCopy = [[[self class] allocWithZone:zone] init];
smtpMessageCopy.delegate = self.delegate;
smtpMessageCopy.fromEmail = self.fromEmail;
smtpMessageCopy.login = self.login;
smtpMessageCopy.parts = [[self.parts copy] autorelease];
smtpMessageCopy.pass = self.pass;
smtpMessageCopy.relayHost = self.relayHost;
smtpMessageCopy.requiresAuth = self.requiresAuth;
smtpMessageCopy.subject = self.subject;
smtpMessageCopy.toEmail = self.toEmail;
smtpMessageCopy.wantsSecure = self.wantsSecure;
smtpMessageCopy.validateSSLChain = self.validateSSLChain;
smtpMessageCopy.ccEmail = self.ccEmail;
smtpMessageCopy.bccEmail = self.bccEmail;
return smtpMessageCopy;
}
#pragma mark -
#pragma mark Connection Timers
- (void)startShortWatchdog
{
NSLog(@"*** starting short watchdog ***");
[self performSelector:@selector(connectionWatchdog:) withObject:nil afterDelay:SHORT_LIVENESS_TIMEOUT];
// self.watchdogTimer = [NSTimer scheduledTimerWithTimeInterval:SHORT_LIVENESS_TIMEOUT target:self selector:@selector(connectionWatchdog:) userInfo:nil repeats:NO];
}
- (void)startLongWatchdog
{
NSLog(@"*** starting long watchdog ***");
[self performSelector:@selector(connectionWatchdog:) withObject:nil afterDelay:LONG_LIVENESS_TIMEOUT];
// self.watchdogTimer = [NSTimer scheduledTimerWithTimeInterval:LONG_LIVENESS_TIMEOUT target:self selector:@selector(connectionWatchdog:) userInfo:nil repeats:NO];
}
- (void)stopWatchdog
{
[self.class cancelPreviousPerformRequestsWithTarget:self selector:@selector(connectionWatchdog:) object:nil];
NSLog(@"*** stopping watchdog ***");
// [self.watchdogTimer invalidate];
// self.watchdogTimer = nil;
}
#pragma mark Watchdog Callback
- (void)connectionWatchdog:(NSTimer *)aTimer
{
[self cleanUpStreams];
// No hard error if we're wating on a reply
if (sendState != kSKPSMTPWaitingQuitReply)
{
NSError *error = [NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMPTErrorConnectionTimeout
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Timeout sending message.", @"server timeout fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Try sending your message again later.", @"server generic error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]];
[delegate messageFailed:self error:error];
}
else
{
[delegate messageSent:self];
}
}
#pragma mark -
#pragma mark Connection Handling
- (BOOL)preflightCheckWithError:(NSError **)error {
CFHostRef host = CFHostCreateWithName(NULL, (CFStringRef)self.relayHost);
CFStreamError streamError;
if (!CFHostStartInfoResolution(host, kCFHostAddresses, &streamError)) {
NSString *errorDomainName;
switch (streamError.domain) {
case kCFStreamErrorDomainCustom:
errorDomainName = @"kCFStreamErrorDomainCustom";
break;
case kCFStreamErrorDomainPOSIX:
errorDomainName = @"kCFStreamErrorDomainPOSIX";
break;
case kCFStreamErrorDomainMacOSStatus:
errorDomainName = @"kCFStreamErrorDomainMacOSStatus";
break;
default:
errorDomainName = [NSString stringWithFormat:@"Generic CFStream Error Domain %ld", streamError.domain];
break;
}
if (error)
*error = [NSError errorWithDomain:errorDomainName
code:streamError.error
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Error resolving address.", NSLocalizedDescriptionKey,
@"Check your SMTP Host name", NSLocalizedRecoverySuggestionErrorKey, nil]];
CFRelease(host);
return NO;
}
Boolean hasBeenResolved;
CFHostGetAddressing(host, &hasBeenResolved);
if (!hasBeenResolved) {
if(error)
*error = [NSError errorWithDomain:@"SKPSMTPMessageError" code:kSKPSMTPErrorNonExistentDomain userInfo:
[NSDictionary dictionaryWithObjectsAndKeys:@"Error resolving host.", NSLocalizedDescriptionKey,
@"Check your SMTP Host name", NSLocalizedRecoverySuggestionErrorKey, nil]];
CFRelease(host);
return NO;
}
CFRelease(host);
return YES;
}
- (BOOL)send
{
NSAssert(sendState == kSKPSMTPIdle, @"Message has already been sent!");
if (requiresAuth)
{
NSAssert(login, @"auth requires login");
NSAssert(pass, @"auth requires pass");
}
NSAssert(relayHost, @"send requires relayHost");
NSAssert(subject, @"send requires subject");
NSAssert(fromEmail, @"send requires fromEmail");
NSAssert(toEmail, @"send requires toEmail");
NSAssert(parts, @"send requires parts");
NSError *error = nil;
if (![self preflightCheckWithError:&error]) {
[delegate messageFailed:self error:error];
return NO;
}
if (![relayPorts count])
{
dispatch_async(dispatch_get_main_queue(), ^{
[delegate messageFailed:self
error:[NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorConnectionFailed
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Unable to connect to the server.", @"server connection fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Try sending your message again later.", @"server generic error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]]];
});
return NO;
}
// Grab the next relay port
short relayPort = [[relayPorts objectAtIndex:0] shortValue];
// Pop this off the head of the queue.
self.relayPorts = ([relayPorts count] > 1) ? [relayPorts subarrayWithRange:NSMakeRange(1, [relayPorts count] - 1)] : [NSArray array];
NSLog(@"C: Attempting to connect to server at: %@:%d", relayHost, relayPort);
self.connectTimer = [NSTimer timerWithTimeInterval:connectTimeout target:self selector:@selector(connectionConnectedCheck:) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.connectTimer forMode:NSDefaultRunLoopMode];
[NSStream getStreamsToHostNamed:relayHost port:relayPort inputStream:&inputStream outputStream:&outputStream];
if ((inputStream != nil) && (outputStream != nil))
{
sendState = kSKPSMTPConnecting;
isSecure = NO;
[inputStream retain];
[outputStream retain];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
self.inputString = [NSMutableString string];
return YES;
}
else
{
[self.connectTimer invalidate];
self.connectTimer = nil;
[delegate messageFailed:self
error:[NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorConnectionFailed
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Unable to connect to the server.", @"server connection fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Try sending your message again later.", @"server generic error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]]];
return NO;
}
}
#pragma mark -
#pragma mark <NSStreamDelegate>
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
switch(eventCode)
{
case NSStreamEventHasBytesAvailable:
{
uint8_t buf[1024];
memset(buf, 0, sizeof(uint8_t) * 1024);
unsigned int len = 0;
len = [(NSInputStream *)stream read:buf maxLength:1024];
if(len)
{
NSString *tmpStr = [[NSString alloc] initWithBytes:buf length:len encoding:NSUTF8StringEncoding];
[inputString appendString:tmpStr];
[tmpStr release];
[self parseBuffer];
}
break;
}
case NSStreamEventEndEncountered:
{
[self stopWatchdog];
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[stream release];
stream = nil; // stream is ivar, so reinit it
if (sendState != kSKPSMTPMessageSent)
{
[delegate messageFailed:self
error:[NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorConnectionInterrupted
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"The connection to the server was interrupted.", @"server connection interrupted error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Try sending your message again later.", @"server generic error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]]];
}
break;
}
}
}
- (NSString *)formatAnAddress:(NSString *)address {
NSString *formattedAddress;
NSCharacterSet *whitespaceCharSet = [NSCharacterSet whitespaceCharacterSet];
if (([address rangeOfString:@"<"].location == NSNotFound) && ([address rangeOfString:@">"].location == NSNotFound)) {
formattedAddress = [NSString stringWithFormat:@"RCPT TO:<%@>\r\n", [address stringByTrimmingCharactersInSet:whitespaceCharSet]];
}
else {
formattedAddress = [NSString stringWithFormat:@"RCPT TO:%@\r\n", [address stringByTrimmingCharactersInSet:whitespaceCharSet]];
}
return(formattedAddress);
}
- (NSString *)formatAddresses:(NSString *)addresses {
NSCharacterSet *splitSet = [NSCharacterSet characterSetWithCharactersInString:@";,"];
NSMutableString *multipleRcptTo = [NSMutableString string];
if ((addresses != nil) && (![addresses isEqualToString:@""])) {
if( [addresses rangeOfString:@";"].location != NSNotFound || [addresses rangeOfString:@","].location != NSNotFound ) {
NSArray *addressParts = [addresses componentsSeparatedByCharactersInSet:splitSet];
for( NSString *address in addressParts ) {
[multipleRcptTo appendString:[self formatAnAddress:address]];
}
}
else {
[multipleRcptTo appendString:[self formatAnAddress:addresses]];
}
}
return(multipleRcptTo);
}
- (void)parseBuffer
{
// Pull out the next line
NSScanner *scanner = [NSScanner scannerWithString:inputString];
NSString *tmpLine = nil;
NSError *error = nil;
BOOL encounteredError = NO;
BOOL messageSent = NO;
while (![scanner isAtEnd])
{
BOOL foundLine = [scanner scanUpToCharactersFromSet:[NSCharacterSet newlineCharacterSet]
intoString:&tmpLine];
if (foundLine)
{
// [self stopWatchdog];
NSLog(@"S: %@", tmpLine);
switch (sendState)
{
case kSKPSMTPConnecting:
{
if ([tmpLine hasPrefix:@"220 "])
{
sendState = kSKPSMTPWaitingEHLOReply;
NSString *ehlo = [NSString stringWithFormat:@"EHLO %@\r\n", @"localhost"];
NSLog(@"C: %@", ehlo);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[ehlo UTF8String], [ehlo lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
break;
}
case kSKPSMTPWaitingEHLOReply:
{
// Test auth login options
if ([tmpLine hasPrefix:@"250-AUTH"])
{
NSRange testRange;
testRange = [tmpLine rangeOfString:@"CRAM-MD5"];
if (testRange.location != NSNotFound)
{
serverAuthCRAMMD5 = YES;
}
testRange = [tmpLine rangeOfString:@"PLAIN"];
if (testRange.location != NSNotFound)
{
serverAuthPLAIN = YES;
}
testRange = [tmpLine rangeOfString:@"LOGIN"];
if (testRange.location != NSNotFound)
{
serverAuthLOGIN = YES;
}
testRange = [tmpLine rangeOfString:@"DIGEST-MD5"];
if (testRange.location != NSNotFound)
{
serverAuthDIGESTMD5 = YES;
}
}
else if ([tmpLine hasPrefix:@"250-8BITMIME"])
{
server8bitMessages = YES;
}
else if ([tmpLine hasPrefix:@"250-STARTTLS"] && !isSecure && wantsSecure)
{
// if we're not already using TLS, start it up
sendState = kSKPSMTPWaitingTLSReply;
NSString *startTLS = @"STARTTLS\r\n";
NSLog(@"C: %@", startTLS);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[startTLS UTF8String], [startTLS lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
else if ([tmpLine hasPrefix:@"250 "])
{
if (self.requiresAuth)
{
// Start up auth
if (serverAuthPLAIN)
{
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *loginString = [NSString stringWithFormat:@"\000%@\000%@", login, pass];
NSString *authString = [NSString stringWithFormat:@"AUTH PLAIN %@\r\n", [[loginString dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
else if (serverAuthLOGIN)
{
sendState = kSKPSMTPWaitingLOGINUsernameReply;
NSString *authString = @"AUTH LOGIN\r\n";
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
else
{
error = [NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorUnsupportedLogin
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Unsupported login mechanism.", @"server unsupported login fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Your server's security setup is not supported, please contact your system administrator or use a supported email account like MobileMe.", @"server security fail error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]];
encounteredError = YES;
}
}
else
{
// Start up send from
sendState = kSKPSMTPWaitingFromReply;
NSString *mailFrom = [NSString stringWithFormat:@"MAIL FROM:<%@>\r\n", fromEmail];
NSLog(@"C: %@", mailFrom);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[mailFrom UTF8String], [mailFrom lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
}
break;
}
case kSKPSMTPWaitingTLSReply:
{
if ([tmpLine hasPrefix:@"220 "])
{
// Attempt to use TLSv1
CFMutableDictionaryRef sslOptions = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(sslOptions, kCFStreamSSLLevel, kCFStreamSocketSecurityLevelTLSv1);
if (!self.validateSSLChain)
{
// Don't validate SSL certs. This is terrible, please complain loudly to your BOFH.
NSLog(@"WARNING: Will not validate SSL chain!!!");
CFDictionarySetValue(sslOptions, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse);
CFDictionarySetValue(sslOptions, kCFStreamSSLAllowsExpiredCertificates, kCFBooleanTrue);
CFDictionarySetValue(sslOptions, kCFStreamSSLAllowsExpiredRoots, kCFBooleanTrue);
CFDictionarySetValue(sslOptions, kCFStreamSSLAllowsAnyRoot, kCFBooleanTrue);
}
NSLog(@"Beginning TLSv1...");
CFReadStreamSetProperty((CFReadStreamRef)inputStream, kCFStreamPropertySSLSettings, sslOptions);
CFWriteStreamSetProperty((CFWriteStreamRef)outputStream, kCFStreamPropertySSLSettings, sslOptions);
CFRelease(sslOptions);
// restart the connection
sendState = kSKPSMTPWaitingEHLOReply;
isSecure = YES;
NSString *ehlo = [NSString stringWithFormat:@"EHLO %@\r\n", @"localhost"];
NSLog(@"C: %@", ehlo);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[ehlo UTF8String], [ehlo lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
/*
else
{
error = [NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorTLSFail
userInfo:[NSDictionary dictionaryWithObject:@"Unable to start TLS"
forKey:NSLocalizedDescriptionKey]];
encounteredError = YES;
}
*/
}
}
case kSKPSMTPWaitingLOGINUsernameReply:
{
if ([tmpLine hasPrefix:@"334 VXNlcm5hbWU6"])
{
sendState = kSKPSMTPWaitingLOGINPasswordReply;
NSString *authString = [NSString stringWithFormat:@"%@\r\n", [[login dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
break;
}
case kSKPSMTPWaitingLOGINPasswordReply:
{
if ([tmpLine hasPrefix:@"334 UGFzc3dvcmQ6"])
{
sendState = kSKPSMTPWaitingAuthSuccess;
NSString *authString = [NSString stringWithFormat:@"%@\r\n", [[pass dataUsingEncoding:NSUTF8StringEncoding] encodeBase64ForData]];
NSLog(@"C: %@", authString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[authString UTF8String], [authString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
break;
}
case kSKPSMTPWaitingAuthSuccess:
{
if ([tmpLine hasPrefix:@"235 "])
{
sendState = kSKPSMTPWaitingFromReply;
NSString *mailFrom = server8bitMessages ? [NSString stringWithFormat:@"MAIL FROM:<%@> BODY=8BITMIME\r\n", fromEmail] : [NSString stringWithFormat:@"MAIL FROM:<%@>\r\n", fromEmail];
NSLog(@"C: %@", mailFrom);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[mailFrom cStringUsingEncoding:NSASCIIStringEncoding], [mailFrom lengthOfBytesUsingEncoding:NSASCIIStringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
else if ([tmpLine hasPrefix:@"535 "])
{
error =[NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorInvalidUserPass
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Invalid username or password.", @"server login fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Go to Email Preferences in the application and re-enter your username and password.", @"server login error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]];
encounteredError = YES;
}
break;
}
case kSKPSMTPWaitingFromReply:
{
// toc 2009-02-18 begin changes per mdesaro issue 18 - http://code.google.com/p/skpsmtpmessage/issues/detail?id=18
// toc 2009-02-18 begin changes to support cc & bcc
if ([tmpLine hasPrefix:@"250 "]) {
sendState = kSKPSMTPWaitingToReply;
NSMutableString *multipleRcptTo = [NSMutableString string];
[multipleRcptTo appendString:[self formatAddresses:toEmail]];
[multipleRcptTo appendString:[self formatAddresses:ccEmail]];
[multipleRcptTo appendString:[self formatAddresses:bccEmail]];
NSLog(@"C: %@", multipleRcptTo);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[multipleRcptTo UTF8String], [multipleRcptTo lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
break;
}
case kSKPSMTPWaitingToReply:
{
if ([tmpLine hasPrefix:@"250 "])
{
sendState = kSKPSMTPWaitingForEnterMail;
NSString *dataString = @"DATA\r\n";
NSLog(@"C: %@", dataString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[dataString UTF8String], [dataString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
else if ([tmpLine hasPrefix:@"530 "])
{
error =[NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorNoRelay
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Relay rejected.", @"server relay fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Your server probably requires a username and password.", @"server relay fail error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]];
encounteredError = YES;
}
else if ([tmpLine hasPrefix:@"550 "])
{
error =[NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorInvalidMessage
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"To address rejected.", @"server to address fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Please re-enter the To: address.", @"server to address fail error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]];
encounteredError = YES;
}
break;
}
case kSKPSMTPWaitingForEnterMail:
{
if ([tmpLine hasPrefix:@"354 "])
{
sendState = kSKPSMTPWaitingSendSuccess;
if (![self sendParts])
{
error = [outputStream streamError];
encounteredError = YES;
}
}
break;
}
case kSKPSMTPWaitingSendSuccess:
{
if ([tmpLine hasPrefix:@"250 "])
{
sendState = kSKPSMTPWaitingQuitReply;
NSString *quitString = @"QUIT\r\n";
NSLog(@"C: %@", quitString);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[quitString UTF8String], [quitString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
error = [outputStream streamError];
encounteredError = YES;
}
else
{
[self startShortWatchdog];
}
}
else if ([tmpLine hasPrefix:@"550 "])
{
error =[NSError errorWithDomain:@"SKPSMTPMessageError"
code:kSKPSMTPErrorInvalidMessage
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:NSLocalizedString(@"Failed to logout.", @"server logout fail error description"),NSLocalizedDescriptionKey,
NSLocalizedString(@"Try sending your message again later.", @"server generic error recovery"),NSLocalizedRecoverySuggestionErrorKey,nil]];
encounteredError = YES;
}
}
case kSKPSMTPWaitingQuitReply:
{
if ([tmpLine hasPrefix:@"221 "])
{
sendState = kSKPSMTPMessageSent;
messageSent = YES;
}
}
}
}
else
{
break;
}
}
self.inputString = [[[inputString substringFromIndex:[scanner scanLocation]] mutableCopy] autorelease];
if (messageSent)
{
[self cleanUpStreams];
[delegate messageSent:self];
}
else if (encounteredError)
{
[self cleanUpStreams];
[delegate messageFailed:self error:error];
}
}
- (BOOL)sendParts
{
NSMutableString *message = [[NSMutableString alloc] init];
static NSString *separatorString = @"--SKPSMTPMessage--Separator--Delimiter\r\n";
CFUUIDRef uuidRef = CFUUIDCreate(kCFAllocatorDefault);
NSString *uuid = (NSString *)CFUUIDCreateString(kCFAllocatorDefault, uuidRef);
CFRelease(uuidRef);
NSDate *now = [[NSDate alloc] init];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss Z"];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];//加入这一句代码
[message appendFormat:@"Date: %@\r\n", [dateFormatter stringFromDate:now]];
[message appendFormat:@"Message-id: <%@@%@>\r\n", [(NSString *)uuid stringByReplacingOccurrencesOfString:@"-" withString:@""], self.relayHost];
[now release];
[dateFormatter release];
[uuid release];
[message appendFormat:@"From:%@\r\n", fromEmail];
if ((self.toEmail != nil) && (![self.toEmail isEqualToString:@""]))
{
[message appendFormat:@"To:%@\r\n", self.toEmail];
}
if ((self.ccEmail != nil) && (![self.ccEmail isEqualToString:@""]))
{
[message appendFormat:@"Cc:%@\r\n", self.ccEmail];
}
[message appendString:@"Content-Type: multipart/mixed; boundary=SKPSMTPMessage--Separator--Delimiter\r\n"];
[message appendString:@"Mime-Version: 1.0 (SKPSMTPMessage 1.0)\r\n"];
[message appendFormat:@"Subject:%@\r\n\r\n",subject];
[message appendString:separatorString];
NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
[message release];
NSLog(@"C: %s", [messageData bytes]);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[messageData bytes], [messageData length]) < 0)
{
return NO;
}
message = [[NSMutableString alloc] init];
for (NSDictionary *part in parts)
{
if ([part objectForKey:kSKPSMTPPartContentDispositionKey])
{
[message appendFormat:@"Content-Disposition: %@\r\n", [part objectForKey:kSKPSMTPPartContentDispositionKey]];
}
[message appendFormat:@"Content-Type: %@\r\n", [part objectForKey:kSKPSMTPPartContentTypeKey]];
[message appendFormat:@"Content-Transfer-Encoding: %@\r\n\r\n", [part objectForKey:kSKPSMTPPartContentTransferEncodingKey]];
[message appendString:[part objectForKey:kSKPSMTPPartMessageKey]];
[message appendString:@"\r\n"];
[message appendString:separatorString];
}
[message appendString:@"\r\n.\r\n"];
NSLog(@"C: %@", message);
if (CFWriteStreamWriteFully((CFWriteStreamRef)outputStream, (const uint8_t *)[message UTF8String], [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding]) < 0)
{
[message release];
return NO;
}
[self startLongWatchdog];
[message release];
return YES;
}
- (void)connectionConnectedCheck:(NSTimer *)aTimer
{
if (sendState == kSKPSMTPConnecting)
{
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[inputStream release];
inputStream = nil;
[outputStream close];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[outputStream release];
outputStream = nil;
// Try the next port - if we don't have another one to try, this will fail
sendState = kSKPSMTPIdle;
[self send];
}
self.connectTimer = nil;
}
- (void)cleanUpStreams
{
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[inputStream release];
inputStream = nil;
[outputStream close];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[outputStream release];
outputStream = nil;
}
@end
结尾
2022年还在更新iOS技术文章,实属不易,小伴们,觉得有点用的话,或者已经看到这里面来的请点赞加关注吧~~
后续分享更多iOS原生技术及物联网技术相关文章。如果有任何疑问的话,欢迎在下方留言~