mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-07-11 22:03:01 -06:00
Smtp: AddHeader
git-svn-id: svn://ultimatepp.org/upp/trunk@4854 f0d560ea-af0d-0410-9eb7-867de7ffcac7
This commit is contained in:
parent
208deb23c1
commit
46dd9f4f45
5 changed files with 493 additions and 471 deletions
|
|
@ -1,85 +1,88 @@
|
|||
#ifndef _smtp_smtp_h
|
||||
#define _smtp_smtp_h
|
||||
|
||||
#include <Core/Core.h>
|
||||
|
||||
NAMESPACE_UPP
|
||||
|
||||
class Smtp : public TcpSocket {
|
||||
struct Attachment
|
||||
{
|
||||
String name; // mail name
|
||||
String file; // source path (dynamic attachments only)
|
||||
String mime; // content type (application/octet-stream by default)
|
||||
String data;
|
||||
};
|
||||
|
||||
String host;
|
||||
int port; // default = 25
|
||||
bool ssl;
|
||||
String auth_user;
|
||||
String auth_pwd;
|
||||
String from;
|
||||
String from_name;
|
||||
Vector<String> to;
|
||||
Vector<String> to_name;
|
||||
Vector<char> as;
|
||||
Vector<String> body;
|
||||
Vector<String> mime; // default: text/plain; charset=<default application charset>
|
||||
Array<Attachment> attachments;
|
||||
int request_timeout;
|
||||
|
||||
bool no_header; // default = false
|
||||
bool no_header_sep; // default = false
|
||||
Time time_sent;
|
||||
String reply_to;
|
||||
String reply_to_name;
|
||||
String subject;
|
||||
|
||||
int start_time;
|
||||
String error;
|
||||
|
||||
void CheckFail();
|
||||
void Send(const String &s);
|
||||
String SendRecv(const String& s);
|
||||
void SendRecvOK(const String& s);
|
||||
|
||||
public:
|
||||
enum AS { TO, CC, BCC };
|
||||
|
||||
Smtp& RequestTimeout(int ms) { request_timeout = ms; return *this; }
|
||||
Smtp& Host(const String& h) { host = h; return *this; }
|
||||
Smtp& Port(int p) { port = p; return *this; }
|
||||
Smtp& SSL(bool b = true) { ssl = b; return *this; }
|
||||
Smtp& Auth(const String& user, const String& pwd) { auth_user = user; auth_pwd = pwd; return *this; }
|
||||
Smtp& From(const String& email, const String& name = Null);
|
||||
Smtp& To(const String& email, const String& name, AS a = TO);
|
||||
Smtp& To(const String& email, AS a = TO) { return To(email, Null, a); }
|
||||
Smtp& Cc(const String& email, const String& name = Null) { return To(email, name, CC); }
|
||||
Smtp& Bcc(const String& email, const String& name = Null) { return To(email, name, BCC); }
|
||||
Smtp& ReplyTo(const String& email, const String& name = Null);
|
||||
Smtp& TimeSent(Time t) { time_sent = t; return *this; }
|
||||
Smtp& Subject(const String& s);
|
||||
Smtp& Body(const String& s, const String& mime_ = Null) { body.Add(s); mime.Add(mime_); return *this; }
|
||||
Smtp& NoHeader() { no_header = true; return *this; }
|
||||
Smtp& NoHeaderSep() { no_header_sep = true; return *this; }
|
||||
Smtp& AttachFile(const char *filename, const char *mime = 0);
|
||||
Smtp& Attach(const char *name, const String& data, const char *mime = 0);
|
||||
|
||||
Smtp& New();
|
||||
|
||||
bool Send();
|
||||
|
||||
String GetError() const { return error; }
|
||||
|
||||
Smtp();
|
||||
|
||||
static void Trace(bool b = true);
|
||||
|
||||
static String Encode(const String& text);
|
||||
static String FormatAddr(const String& addr, const String& name);
|
||||
};
|
||||
|
||||
END_UPP_NAMESPACE
|
||||
|
||||
#endif
|
||||
#ifndef _smtp_smtp_h
|
||||
#define _smtp_smtp_h
|
||||
|
||||
#include <Core/Core.h>
|
||||
|
||||
NAMESPACE_UPP
|
||||
|
||||
class Smtp : public TcpSocket {
|
||||
struct Attachment
|
||||
{
|
||||
String name; // mail name
|
||||
String file; // source path (dynamic attachments only)
|
||||
String mime; // content type (application/octet-stream by default)
|
||||
String data;
|
||||
};
|
||||
|
||||
String host;
|
||||
int port; // default = 25
|
||||
bool ssl;
|
||||
String auth_user;
|
||||
String auth_pwd;
|
||||
String from;
|
||||
String from_name;
|
||||
Vector<String> to;
|
||||
Vector<String> to_name;
|
||||
Vector<char> as;
|
||||
Vector<String> body;
|
||||
Vector<String> mime; // default: text/plain; charset=<default application charset>
|
||||
Array<Attachment> attachments;
|
||||
int request_timeout;
|
||||
String add_header;
|
||||
|
||||
bool no_header; // default = false
|
||||
bool no_header_sep; // default = false
|
||||
Time time_sent;
|
||||
String reply_to;
|
||||
String reply_to_name;
|
||||
String subject;
|
||||
|
||||
int start_time;
|
||||
String error;
|
||||
|
||||
void CheckFail();
|
||||
void Send(const String &s);
|
||||
String SendRecv(const String& s);
|
||||
void SendRecvOK(const String& s);
|
||||
|
||||
public:
|
||||
enum AS { TO, CC, BCC };
|
||||
|
||||
Smtp& RequestTimeout(int ms) { request_timeout = ms; return *this; }
|
||||
Smtp& Host(const String& h) { host = h; return *this; }
|
||||
Smtp& Port(int p) { port = p; return *this; }
|
||||
Smtp& SSL(bool b = true) { ssl = b; return *this; }
|
||||
Smtp& Auth(const String& user, const String& pwd) { auth_user = user; auth_pwd = pwd; return *this; }
|
||||
Smtp& From(const String& email, const String& name = Null);
|
||||
Smtp& To(const String& email, const String& name, AS a = TO);
|
||||
Smtp& To(const String& email, AS a = TO) { return To(email, Null, a); }
|
||||
Smtp& Cc(const String& email, const String& name = Null) { return To(email, name, CC); }
|
||||
Smtp& Bcc(const String& email, const String& name = Null) { return To(email, name, BCC); }
|
||||
Smtp& ReplyTo(const String& email, const String& name = Null);
|
||||
Smtp& TimeSent(Time t) { time_sent = t; return *this; }
|
||||
Smtp& Subject(const String& s);
|
||||
Smtp& Body(const String& s, const String& mime_ = Null) { body.Add(s); mime.Add(mime_); return *this; }
|
||||
Smtp& NoHeader() { no_header = true; return *this; }
|
||||
Smtp& NoHeaderSep() { no_header_sep = true; return *this; }
|
||||
Smtp& AttachFile(const char *filename, const char *mime = 0);
|
||||
Smtp& Attach(const char *name, const String& data, const char *mime = 0);
|
||||
Smpt& AddHeader(const String& text) { add_header << text << "\r\n"; }
|
||||
Smtp& AddHeader(const char *id, const String& txt) { add_header << id << ": " << txt << "\r\n"; }
|
||||
|
||||
Smtp& New();
|
||||
|
||||
bool Send();
|
||||
|
||||
String GetError() const { return error; }
|
||||
|
||||
Smtp();
|
||||
|
||||
static void Trace(bool b = true);
|
||||
|
||||
static String Encode(const String& text);
|
||||
static String FormatAddr(const String& addr, const String& name);
|
||||
};
|
||||
|
||||
END_UPP_NAMESPACE
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,386 +1,388 @@
|
|||
#include "SMTP.h"
|
||||
|
||||
NAMESPACE_UPP
|
||||
|
||||
static bool sSmtpTrace;
|
||||
|
||||
#define LLOG(x) do { if(sSmtpTrace) RLOG(x); } while(0)
|
||||
|
||||
void Smtp::Trace(bool b)
|
||||
{
|
||||
sSmtpTrace = b;
|
||||
}
|
||||
|
||||
static String GetDelimiter(const char *b, const char *e, String init)
|
||||
{
|
||||
static const char delimiters[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
|
||||
"ghijklmnopqrstuvxyz()+/:?0123456"
|
||||
"789abcdefghijklmnopqrstuvwxyzABC"
|
||||
"DEFGHIJKLMNOPQRSTUVWXYZ012345678";
|
||||
|
||||
String out = init;
|
||||
if(b == e)
|
||||
return out;
|
||||
if(IsNull(out))
|
||||
out.Cat(delimiters[*b++ & 0x7F]);
|
||||
int l = out.GetLength();
|
||||
for(; b != e; b++)
|
||||
{
|
||||
b = (const char *)memchr(b, *out, e - b);
|
||||
if(!b || e - b < l)
|
||||
return out;
|
||||
if(!memcmp(b, out, l))
|
||||
{
|
||||
if(e - b == l)
|
||||
return out + '/';
|
||||
out.Cat(delimiters[b[l] & 0x7F]);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static String GetDelimiter(String s, String init)
|
||||
{
|
||||
return GetDelimiter(s.Begin(), s.End(), init);
|
||||
}
|
||||
|
||||
void Smtp::CheckFail()
|
||||
{
|
||||
if(msecs(start_time) > request_timeout)
|
||||
throw Exc(t_("Communication Failure: Timeout."));
|
||||
if(IsError())
|
||||
throw Exc("Connection error: " + GetErrorDesc());
|
||||
}
|
||||
|
||||
void Smtp::Send(const String &s)
|
||||
{
|
||||
LLOG("SMTP send: " << s);
|
||||
const char *p = s.Begin(), *e = s.End();
|
||||
while(p != e) {
|
||||
CheckFail();
|
||||
int amount = Put(p, int(e - p));
|
||||
p += amount;
|
||||
}
|
||||
}
|
||||
|
||||
String Smtp::SendRecv(const String& s)
|
||||
{
|
||||
Send(s);
|
||||
String reply;
|
||||
for(;;) {
|
||||
CheckFail();
|
||||
int c = Get();
|
||||
if(c >= 0) {
|
||||
if(c == '\n') {
|
||||
LLOG("Reply: " << reply);
|
||||
return reply;
|
||||
}
|
||||
reply.Cat(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::SendRecvOK(const String& s)
|
||||
{
|
||||
String ans = SendRecv(s);
|
||||
if(ans[0] != '2' || ans[1] != '5' || ans[2] != '0')
|
||||
throw Exc(ans);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Smtp::
|
||||
|
||||
static const char default_mime[] = "application/octet-stream";
|
||||
|
||||
String Smtp::Encode(const String& text)
|
||||
{
|
||||
String txt = ToCharset(CHARSET_UTF8, text);
|
||||
String r = "=?Utf-8?q?";
|
||||
for(const char *s = txt; *s; s++) {
|
||||
if((byte)*s < ' ' || (byte)*s > 127 || *s == '=' || *s == '?' || *s == ' ')
|
||||
r << '=' << FormatIntHexUpper((byte)*s, 2);
|
||||
else
|
||||
r.Cat(*s);
|
||||
}
|
||||
r << "?=";
|
||||
return r;
|
||||
}
|
||||
|
||||
Smtp& Smtp::To(const String& t, const String& name, AS a)
|
||||
{
|
||||
to.Add(t);
|
||||
to_name.Add(name);
|
||||
as.Add(a);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::Subject(const String& s)
|
||||
{
|
||||
subject = s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::ReplyTo(const String& r, const String& name)
|
||||
{
|
||||
reply_to = r;
|
||||
reply_to_name = name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::From(const String& f, const String& name)
|
||||
{
|
||||
from = f;
|
||||
from_name = name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::AttachFile(const char *filename, const char *mime)
|
||||
{
|
||||
Attachment& attach = attachments.Add();
|
||||
attach.name = GetFileNamePos(filename);
|
||||
attach.mime = (mime ? mime : default_mime);
|
||||
attach.file = filename;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::Attach(const char *name, const String& data, const char *mime)
|
||||
{
|
||||
Attachment& attach = attachments.Add();
|
||||
attach.name = name;
|
||||
attach.mime = (mime ? mime : default_mime);
|
||||
attach.data = data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
String Smtp::FormatAddr(const String& addr, const String& name)
|
||||
{
|
||||
String r;
|
||||
if(name.GetCount())
|
||||
r << "\"" << Encode(name) << "\" ";
|
||||
r << '<' << addr << '>';
|
||||
return r;
|
||||
}
|
||||
|
||||
bool Smtp::Send()
|
||||
{
|
||||
start_time = msecs();
|
||||
|
||||
String ipaddr;
|
||||
|
||||
try {
|
||||
if(IsNull(host))
|
||||
throw Exc(t_("Host not set."));
|
||||
|
||||
if(to.IsEmpty())
|
||||
throw Exc(t_("Recipient not set."));
|
||||
|
||||
if(!Connect(host, Nvl(port, ssl ? 465 : 25)))
|
||||
throw Exc(Format("Cannot open socket %s:%d: %s", host, port, GetErrorDesc()));
|
||||
|
||||
String ans;
|
||||
|
||||
if(ssl)
|
||||
if(!StartSSL())
|
||||
throw Exc("Unable to start SSL");
|
||||
|
||||
// receive initial message & send hello
|
||||
SendRecv(Null);
|
||||
String org;
|
||||
int pos = from.Find('@');
|
||||
if(pos >= 0) {
|
||||
int start = ++pos, len = from.GetLength();
|
||||
while(pos < len && from[pos] != '>')
|
||||
pos++;
|
||||
org = from.Mid(start, pos - start);
|
||||
}
|
||||
else
|
||||
org << TcpSocket::GetHostName();
|
||||
|
||||
SendRecvOK("HELO " + org + "\r\n");
|
||||
if(!IsNull(auth_user)) {
|
||||
String ans = SendRecv("AUTH LOGIN\r\n");
|
||||
while(ans[0] != '2')
|
||||
if(ans[0] == '3' && ans[1] == '3' && ans[2] == '4' && ans[3] == ' ') {
|
||||
String param = Base64Decode(ans.GetIter(4), ans.End());
|
||||
if(param == "Username:")
|
||||
ans = SendRecv(Base64Encode(auth_user) + "\r\n");
|
||||
else if(param == "Password:")
|
||||
ans = SendRecv(Base64Encode(auth_pwd) + "\r\n");
|
||||
else
|
||||
throw Exc(ans);
|
||||
}
|
||||
else
|
||||
throw Exc(ans);
|
||||
}
|
||||
SendRecvOK("MAIL FROM:<" + from + ">\r\n");
|
||||
for(int i = 0; i < to.GetCount(); i++)
|
||||
SendRecv("RCPT TO:<" + to[i] + ">\r\n");
|
||||
ans = SendRecv("DATA\r\n");
|
||||
|
||||
if(memcmp(ans, "354", 3))
|
||||
throw Exc(ans);
|
||||
|
||||
String delimiter = "?";
|
||||
for(int i = 0; i < body.GetCount(); i++)
|
||||
delimiter = GetDelimiter(body[i], delimiter);
|
||||
bool alter = body.GetCount() > 1;
|
||||
bool multi = !attachments.IsEmpty();
|
||||
|
||||
{ // format message
|
||||
String msg;
|
||||
if(!no_header) { // generate message header
|
||||
msg << "From: " << FormatAddr(from, from_name) << "\r\n";
|
||||
static const AS as_list[] = { TO, CC, BCC };
|
||||
static const char *as_name[] = { "To", "CC", "BCC" };
|
||||
for(int a = 0; a < __countof(as_list); a++)
|
||||
{
|
||||
int pos = 0;
|
||||
for(int i = 0; i < as.GetCount(); i++)
|
||||
if(as[i] == as_list[a])
|
||||
{
|
||||
if(pos && pos + to[i].GetLength() >= 70)
|
||||
{
|
||||
msg << "\r\n ";
|
||||
pos = 5;
|
||||
}
|
||||
else if(pos)
|
||||
{
|
||||
msg << ", ";
|
||||
pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg << as_name[a] << ": ";
|
||||
pos = (int)strlen(as_name[a]) + 2;
|
||||
}
|
||||
msg << FormatAddr(to[i], to_name[i]);
|
||||
}
|
||||
if(pos)
|
||||
msg << "\r\n";
|
||||
}
|
||||
if(!IsNull(subject))
|
||||
msg << "Subject: " << Encode(subject) << "\r\n";
|
||||
if(!IsNull(reply_to))
|
||||
msg << "Reply-To: " << FormatAddr(reply_to, reply_to_name) << "\r\n";
|
||||
if(!IsNull(time_sent)) {
|
||||
static const char *dayofweek[] =
|
||||
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||
static const char *month[] =
|
||||
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||
msg << "Date: "
|
||||
<< dayofweek[DayOfWeek(time_sent)] << ", "
|
||||
<< (int)time_sent.day << ' ' << month[time_sent.month - 1] << ' ' << (int)time_sent.year
|
||||
<< ' ' << Sprintf("%2d:%02d:%02d +0100", time_sent.hour, time_sent.minute, time_sent.second)
|
||||
<< "\r\n";
|
||||
}
|
||||
if(multi || alter)
|
||||
msg << "Content-Type: Multipart/" << (alter ? "alternative" : "mixed")
|
||||
<< "; boundary=\"" << delimiter << "\"\r\n"
|
||||
"\r\n";
|
||||
}
|
||||
|
||||
for(int i = 0; i < body.GetCount(); i++) {
|
||||
String t = body[i], m = mime[i];
|
||||
if(!no_header) {
|
||||
if(multi || alter)
|
||||
msg << "--" << delimiter << "\r\n";
|
||||
if(IsNull(m))
|
||||
m << "text/plain; charset=\"" << MIMECharsetName(CHARSET_DEFAULT) << "\"";
|
||||
msg << "Content-Type: " << m << "\r\n"
|
||||
"Content-Transfer-Encoding: quoted-printable\r\n";
|
||||
}
|
||||
if(!no_header_sep)
|
||||
msg << "\r\n";
|
||||
bool begin = true;
|
||||
for(const char *p = t.Begin(), *e = t.End(); p != e; p++)
|
||||
if(*p >= 33 && *p <= 126 && *p != '=' && (*p != '.' || !begin)) {
|
||||
msg.Cat(*p);
|
||||
begin = false;
|
||||
}
|
||||
else if(*p == '.' && begin) {
|
||||
msg.Cat("..");
|
||||
begin = false;
|
||||
}
|
||||
else if(*p == ' ' && p + 1 != e && p[1] != '\r' && p[1] != '\n') {
|
||||
msg.Cat(' ');
|
||||
begin = false;
|
||||
}
|
||||
else if(*p == '\r')
|
||||
;
|
||||
else if(*p == '\n') {
|
||||
msg.Cat("\r\n");
|
||||
begin = true;
|
||||
}
|
||||
else {
|
||||
static const char hex[] = "0123456789ABCDEF";
|
||||
msg.Cat('=');
|
||||
msg.Cat(hex[(*p >> 4) & 15]);
|
||||
msg.Cat(hex[*p & 15]);
|
||||
}
|
||||
|
||||
if(!begin)
|
||||
msg.Cat("\r\n");
|
||||
}
|
||||
for(int i = 0; i < attachments.GetCount(); i++) {
|
||||
const Attachment& a = attachments[i];
|
||||
One<Stream> source;
|
||||
if(a.file.GetCount()) {
|
||||
FileIn& in = source.Create<FileIn>();
|
||||
if(!in.Open(a.file))
|
||||
throw Exc("Unable to open attachment file " + a.file);
|
||||
}
|
||||
else
|
||||
source.Create<StringStream>().Open(a.data);
|
||||
msg << "--" << delimiter << "\r\n"
|
||||
"Content-Type: " << a.mime << "; name=\"" << a.name << "\"\r\n"
|
||||
"Content-Transfer-Encoding: base64\r\n"
|
||||
"Content-Disposition: attachment; filename=\"" << a.name << "\"\r\n"
|
||||
"\r\n";
|
||||
|
||||
char buffer[54];
|
||||
for(int c; (c = source -> Get(buffer, sizeof(buffer))) != 0;)
|
||||
{
|
||||
msg.Cat(Base64Encode(buffer, buffer + c));
|
||||
msg.Cat('\r');
|
||||
msg.Cat('\n');
|
||||
if(msg.GetLength() >= 65536) {
|
||||
Send(msg);
|
||||
msg = Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(multi || alter)
|
||||
msg << "--" << delimiter << "--\r\n";
|
||||
msg.Cat(".\r\n");
|
||||
SendRecvOK(msg);
|
||||
}
|
||||
|
||||
SendRecv("QUIT\r\n");
|
||||
return true;
|
||||
}
|
||||
catch(Exc e) {
|
||||
error = e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Smtp& Smtp::New() {
|
||||
to.Clear();
|
||||
to_name.Clear();
|
||||
as.Clear();
|
||||
body.Clear();
|
||||
mime.Clear();
|
||||
error.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp::Smtp()
|
||||
{
|
||||
port = Null;
|
||||
no_header = no_header_sep = false;
|
||||
time_sent = GetSysTime();
|
||||
request_timeout = 120000;
|
||||
}
|
||||
|
||||
END_UPP_NAMESPACE
|
||||
#include "SMTP.h"
|
||||
|
||||
NAMESPACE_UPP
|
||||
|
||||
static bool sSmtpTrace;
|
||||
|
||||
#define LLOG(x) do { if(sSmtpTrace) RLOG(x); } while(0)
|
||||
|
||||
void Smtp::Trace(bool b)
|
||||
{
|
||||
sSmtpTrace = b;
|
||||
}
|
||||
|
||||
static String GetDelimiter(const char *b, const char *e, String init)
|
||||
{
|
||||
static const char delimiters[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
|
||||
"ghijklmnopqrstuvxyz()+/:?0123456"
|
||||
"789abcdefghijklmnopqrstuvwxyzABC"
|
||||
"DEFGHIJKLMNOPQRSTUVWXYZ012345678";
|
||||
|
||||
String out = init;
|
||||
if(b == e)
|
||||
return out;
|
||||
if(IsNull(out))
|
||||
out.Cat(delimiters[*b++ & 0x7F]);
|
||||
int l = out.GetLength();
|
||||
for(; b != e; b++)
|
||||
{
|
||||
b = (const char *)memchr(b, *out, e - b);
|
||||
if(!b || e - b < l)
|
||||
return out;
|
||||
if(!memcmp(b, out, l))
|
||||
{
|
||||
if(e - b == l)
|
||||
return out + '/';
|
||||
out.Cat(delimiters[b[l] & 0x7F]);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static String GetDelimiter(String s, String init)
|
||||
{
|
||||
return GetDelimiter(s.Begin(), s.End(), init);
|
||||
}
|
||||
|
||||
void Smtp::CheckFail()
|
||||
{
|
||||
if(msecs(start_time) > request_timeout)
|
||||
throw Exc(t_("Communication Failure: Timeout."));
|
||||
if(IsError())
|
||||
throw Exc("Connection error: " + GetErrorDesc());
|
||||
}
|
||||
|
||||
void Smtp::Send(const String &s)
|
||||
{
|
||||
LLOG("SMTP send: " << s);
|
||||
const char *p = s.Begin(), *e = s.End();
|
||||
while(p != e) {
|
||||
CheckFail();
|
||||
int amount = Put(p, int(e - p));
|
||||
p += amount;
|
||||
}
|
||||
}
|
||||
|
||||
String Smtp::SendRecv(const String& s)
|
||||
{
|
||||
Send(s);
|
||||
String reply;
|
||||
for(;;) {
|
||||
CheckFail();
|
||||
int c = Get();
|
||||
if(c >= 0) {
|
||||
if(c == '\n') {
|
||||
LLOG("Reply: " << reply);
|
||||
return reply;
|
||||
}
|
||||
reply.Cat(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Smtp::SendRecvOK(const String& s)
|
||||
{
|
||||
String ans = SendRecv(s);
|
||||
if(ans[0] != '2' || ans[1] != '5' || ans[2] != '0')
|
||||
throw Exc(ans);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Smtp::
|
||||
|
||||
static const char default_mime[] = "application/octet-stream";
|
||||
|
||||
String Smtp::Encode(const String& text)
|
||||
{
|
||||
String txt = ToCharset(CHARSET_UTF8, text);
|
||||
String r = "=?Utf-8?q?";
|
||||
for(const char *s = txt; *s; s++) {
|
||||
if((byte)*s < ' ' || (byte)*s > 127 || *s == '=' || *s == '?' || *s == ' ')
|
||||
r << '=' << FormatIntHexUpper((byte)*s, 2);
|
||||
else
|
||||
r.Cat(*s);
|
||||
}
|
||||
r << "?=";
|
||||
return r;
|
||||
}
|
||||
|
||||
Smtp& Smtp::To(const String& t, const String& name, AS a)
|
||||
{
|
||||
to.Add(t);
|
||||
to_name.Add(name);
|
||||
as.Add(a);
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::Subject(const String& s)
|
||||
{
|
||||
subject = s;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::ReplyTo(const String& r, const String& name)
|
||||
{
|
||||
reply_to = r;
|
||||
reply_to_name = name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::From(const String& f, const String& name)
|
||||
{
|
||||
from = f;
|
||||
from_name = name;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::AttachFile(const char *filename, const char *mime)
|
||||
{
|
||||
Attachment& attach = attachments.Add();
|
||||
attach.name = GetFileNamePos(filename);
|
||||
attach.mime = (mime ? mime : default_mime);
|
||||
attach.file = filename;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp& Smtp::Attach(const char *name, const String& data, const char *mime)
|
||||
{
|
||||
Attachment& attach = attachments.Add();
|
||||
attach.name = name;
|
||||
attach.mime = (mime ? mime : default_mime);
|
||||
attach.data = data;
|
||||
return *this;
|
||||
}
|
||||
|
||||
String Smtp::FormatAddr(const String& addr, const String& name)
|
||||
{
|
||||
String r;
|
||||
if(name.GetCount())
|
||||
r << "\"" << Encode(name) << "\" ";
|
||||
r << '<' << addr << '>';
|
||||
return r;
|
||||
}
|
||||
|
||||
bool Smtp::Send()
|
||||
{
|
||||
start_time = msecs();
|
||||
|
||||
String ipaddr;
|
||||
|
||||
try {
|
||||
if(IsNull(host))
|
||||
throw Exc(t_("Host not set."));
|
||||
|
||||
if(to.IsEmpty())
|
||||
throw Exc(t_("Recipient not set."));
|
||||
|
||||
if(!Connect(host, Nvl(port, ssl ? 465 : 25)))
|
||||
throw Exc(Format("Cannot open socket %s:%d: %s", host, port, GetErrorDesc()));
|
||||
|
||||
String ans;
|
||||
|
||||
if(ssl)
|
||||
if(!StartSSL())
|
||||
throw Exc("Unable to start SSL");
|
||||
|
||||
// receive initial message & send hello
|
||||
SendRecv(Null);
|
||||
String org;
|
||||
int pos = from.Find('@');
|
||||
if(pos >= 0) {
|
||||
int start = ++pos, len = from.GetLength();
|
||||
while(pos < len && from[pos] != '>')
|
||||
pos++;
|
||||
org = from.Mid(start, pos - start);
|
||||
}
|
||||
else
|
||||
org << TcpSocket::GetHostName();
|
||||
|
||||
SendRecvOK("HELO " + org + "\r\n");
|
||||
if(!IsNull(auth_user)) {
|
||||
String ans = SendRecv("AUTH LOGIN\r\n");
|
||||
while(ans[0] != '2')
|
||||
if(ans[0] == '3' && ans[1] == '3' && ans[2] == '4' && ans[3] == ' ') {
|
||||
String param = Base64Decode(ans.GetIter(4), ans.End());
|
||||
if(param == "Username:")
|
||||
ans = SendRecv(Base64Encode(auth_user) + "\r\n");
|
||||
else if(param == "Password:")
|
||||
ans = SendRecv(Base64Encode(auth_pwd) + "\r\n");
|
||||
else
|
||||
throw Exc(ans);
|
||||
}
|
||||
else
|
||||
throw Exc(ans);
|
||||
}
|
||||
SendRecvOK("MAIL FROM:<" + from + ">\r\n");
|
||||
for(int i = 0; i < to.GetCount(); i++)
|
||||
SendRecv("RCPT TO:<" + to[i] + ">\r\n");
|
||||
ans = SendRecv("DATA\r\n");
|
||||
|
||||
if(memcmp(ans, "354", 3))
|
||||
throw Exc(ans);
|
||||
|
||||
String delimiter = "?";
|
||||
for(int i = 0; i < body.GetCount(); i++)
|
||||
delimiter = GetDelimiter(body[i], delimiter);
|
||||
bool alter = body.GetCount() > 1;
|
||||
bool multi = !attachments.IsEmpty();
|
||||
|
||||
{ // format message
|
||||
String msg;
|
||||
if(!no_header) { // generate message header
|
||||
msg << "From: " << FormatAddr(from, from_name) << "\r\n";
|
||||
static const AS as_list[] = { TO, CC, BCC };
|
||||
static const char *as_name[] = { "To", "CC", "BCC" };
|
||||
for(int a = 0; a < __countof(as_list); a++)
|
||||
{
|
||||
int pos = 0;
|
||||
for(int i = 0; i < as.GetCount(); i++)
|
||||
if(as[i] == as_list[a])
|
||||
{
|
||||
if(pos && pos + to[i].GetLength() >= 70)
|
||||
{
|
||||
msg << "\r\n ";
|
||||
pos = 5;
|
||||
}
|
||||
else if(pos)
|
||||
{
|
||||
msg << ", ";
|
||||
pos += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
msg << as_name[a] << ": ";
|
||||
pos = (int)strlen(as_name[a]) + 2;
|
||||
}
|
||||
msg << FormatAddr(to[i], to_name[i]);
|
||||
}
|
||||
if(pos)
|
||||
msg << "\r\n";
|
||||
}
|
||||
if(!IsNull(subject))
|
||||
msg << "Subject: " << Encode(subject) << "\r\n";
|
||||
if(!IsNull(reply_to))
|
||||
msg << "Reply-To: " << FormatAddr(reply_to, reply_to_name) << "\r\n";
|
||||
if(!IsNull(time_sent)) {
|
||||
static const char *dayofweek[] =
|
||||
{ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||
static const char *month[] =
|
||||
{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
||||
msg << "Date: "
|
||||
<< dayofweek[DayOfWeek(time_sent)] << ", "
|
||||
<< (int)time_sent.day << ' ' << month[time_sent.month - 1] << ' ' << (int)time_sent.year
|
||||
<< ' ' << Sprintf("%2d:%02d:%02d +0100", time_sent.hour, time_sent.minute, time_sent.second)
|
||||
<< "\r\n";
|
||||
}
|
||||
if(multi || alter)
|
||||
msg << "Content-Type: Multipart/" << (alter ? "alternative" : "mixed")
|
||||
<< "; boundary=\"" << delimiter << "\"\r\n"
|
||||
"\r\n";
|
||||
msg << add_header;
|
||||
}
|
||||
|
||||
for(int i = 0; i < body.GetCount(); i++) {
|
||||
String t = body[i], m = mime[i];
|
||||
if(!no_header) {
|
||||
if(multi || alter)
|
||||
msg << "--" << delimiter << "\r\n";
|
||||
if(IsNull(m))
|
||||
m << "text/plain; charset=\"" << MIMECharsetName(CHARSET_DEFAULT) << "\"";
|
||||
msg << "Content-Type: " << m << "\r\n"
|
||||
"Content-Transfer-Encoding: quoted-printable\r\n";
|
||||
}
|
||||
if(!no_header_sep)
|
||||
msg << "\r\n";
|
||||
bool begin = true;
|
||||
for(const char *p = t.Begin(), *e = t.End(); p != e; p++)
|
||||
if(*p >= 33 && *p <= 126 && *p != '=' && (*p != '.' || !begin)) {
|
||||
msg.Cat(*p);
|
||||
begin = false;
|
||||
}
|
||||
else if(*p == '.' && begin) {
|
||||
msg.Cat("..");
|
||||
begin = false;
|
||||
}
|
||||
else if(*p == ' ' && p + 1 != e && p[1] != '\r' && p[1] != '\n') {
|
||||
msg.Cat(' ');
|
||||
begin = false;
|
||||
}
|
||||
else if(*p == '\r')
|
||||
;
|
||||
else if(*p == '\n') {
|
||||
msg.Cat("\r\n");
|
||||
begin = true;
|
||||
}
|
||||
else {
|
||||
static const char hex[] = "0123456789ABCDEF";
|
||||
msg.Cat('=');
|
||||
msg.Cat(hex[(*p >> 4) & 15]);
|
||||
msg.Cat(hex[*p & 15]);
|
||||
}
|
||||
|
||||
if(!begin)
|
||||
msg.Cat("\r\n");
|
||||
}
|
||||
for(int i = 0; i < attachments.GetCount(); i++) {
|
||||
const Attachment& a = attachments[i];
|
||||
One<Stream> source;
|
||||
if(a.file.GetCount()) {
|
||||
FileIn& in = source.Create<FileIn>();
|
||||
if(!in.Open(a.file))
|
||||
throw Exc("Unable to open attachment file " + a.file);
|
||||
}
|
||||
else
|
||||
source.Create<StringStream>().Open(a.data);
|
||||
msg << "--" << delimiter << "\r\n"
|
||||
"Content-Type: " << a.mime << "; name=\"" << a.name << "\"\r\n"
|
||||
"Content-Transfer-Encoding: base64\r\n"
|
||||
"Content-Disposition: attachment; filename=\"" << a.name << "\"\r\n"
|
||||
"\r\n";
|
||||
|
||||
char buffer[54];
|
||||
for(int c; (c = source -> Get(buffer, sizeof(buffer))) != 0;)
|
||||
{
|
||||
msg.Cat(Base64Encode(buffer, buffer + c));
|
||||
msg.Cat('\r');
|
||||
msg.Cat('\n');
|
||||
if(msg.GetLength() >= 65536) {
|
||||
Send(msg);
|
||||
msg = Null;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(multi || alter)
|
||||
msg << "--" << delimiter << "--\r\n";
|
||||
msg.Cat(".\r\n");
|
||||
SendRecvOK(msg);
|
||||
}
|
||||
|
||||
SendRecv("QUIT\r\n");
|
||||
return true;
|
||||
}
|
||||
catch(Exc e) {
|
||||
error = e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Smtp& Smtp::New() {
|
||||
to.Clear();
|
||||
to_name.Clear();
|
||||
as.Clear();
|
||||
body.Clear();
|
||||
mime.Clear();
|
||||
error.Clear();
|
||||
add_header.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
Smtp::Smtp()
|
||||
{
|
||||
port = Null;
|
||||
no_header = no_header_sep = false;
|
||||
time_sent = GetSysTime();
|
||||
request_timeout = 120000;
|
||||
}
|
||||
|
||||
END_UPP_NAMESPACE
|
||||
|
|
|
|||
|
|
@ -112,6 +112,18 @@ tring][@(0.0.255) `&]_[*@3 data], [@(0.0.255) const]_[@(0.0.255) char]_`*[*@3 mi
|
|||
&]
|
||||
[s2; Attaches a String as file attachment..&]
|
||||
[s3; &]
|
||||
[s4;%- &]
|
||||
[s5;:Smtp`:`:AddHeader`(const String`&`):%- Smpt[@(0.0.255) `&]_[* AddHeader]([@(0.0.255) c
|
||||
onst]_[_^String^ String][@(0.0.255) `&]_[*@3 text])&]
|
||||
[s2; Adds additional [%-*@3 text] message header. Text should [* not]
|
||||
be terminated by CRLF.&]
|
||||
[s3; &]
|
||||
[s4;%- &]
|
||||
[s5;:Smtp`:`:AddHeader`(const char`*`,const String`&`):%- [_^Smtp^ Smtp][@(0.0.255) `&]_[* A
|
||||
ddHeader]([@(0.0.255) const]_[@(0.0.255) char]_`*[*@3 id], [@(0.0.255) const]_[_^String^ St
|
||||
ring][@(0.0.255) `&]_[*@3 txt])&]
|
||||
[s2; Adds additional field [%-*@3 id] with value [%-*@3 txt] to header.&]
|
||||
[s3; &]
|
||||
[s4; &]
|
||||
[s5;:Smtp`:`:New`(`):%- [_^Smtp^ Smtp][@(0.0.255) `&]_[* New]()&]
|
||||
[s2; Restarts Smtp for sending of next email (resets all addresses,
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ bool SmtpMail::Send()
|
|||
msg << "Content-Type: Multipart/" << (alter ? "alternative" : "mixed")
|
||||
<< "; boundary=\"" << delimiter << "\"\r\n"
|
||||
"\r\n";
|
||||
msg << add_header;
|
||||
}
|
||||
|
||||
for(int i = 0; i < text.GetCount(); i++) {
|
||||
|
|
@ -433,6 +434,7 @@ SmtpMail& SmtpMail::New()
|
|||
mime.Clear();
|
||||
error.Clear();
|
||||
transcript_text.Clear();
|
||||
add_header.Clear();
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ public:
|
|||
SmtpMail& AttachFile(const char *filename, const char *mime = 0);
|
||||
SmtpMail& Attach(const char *name, const String& data, const char *mime = 0);
|
||||
SmtpMail& Auth(const String& user, const String& pwd) { auth_user = user; auth_pwd = pwd; return *this; }
|
||||
SmtpMail& AddHeader(const String& text) { add_header << text << "\r\n"; return *this; }
|
||||
SmtpMail& AddHeader(const char *id, const String& txt) { add_header << id << ": " << txt << "\r\n"; return *this; }
|
||||
|
||||
SmtpMail& New();
|
||||
|
||||
|
|
@ -63,6 +65,7 @@ private:
|
|||
String reply_to;
|
||||
String reply_to_name;
|
||||
String subject;
|
||||
String add_header;
|
||||
|
||||
// state automaton
|
||||
String error;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue