From 46dd9f4f459eba16eb158ae352020c4f6aab1fee Mon Sep 17 00:00:00 2001 From: cxl Date: Wed, 25 Apr 2012 09:18:59 +0000 Subject: [PATCH] Smtp: AddHeader git-svn-id: svn://ultimatepp.org/upp/trunk@4854 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppsrc/Core/SMTP/SMTP.h | 173 +++--- uppsrc/Core/SMTP/Smtp.cpp | 774 ++++++++++++------------ uppsrc/Core/SMTP/src.tpp/SMTP$en-us.tpp | 12 + uppsrc/Web/smtp.cpp | 2 + uppsrc/Web/smtp.h | 3 + 5 files changed, 493 insertions(+), 471 deletions(-) diff --git a/uppsrc/Core/SMTP/SMTP.h b/uppsrc/Core/SMTP/SMTP.h index 6dd444856..ebc5906e9 100644 --- a/uppsrc/Core/SMTP/SMTP.h +++ b/uppsrc/Core/SMTP/SMTP.h @@ -1,85 +1,88 @@ -#ifndef _smtp_smtp_h -#define _smtp_smtp_h - -#include - -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 to; - Vector to_name; - Vector as; - Vector body; - Vector mime; // default: text/plain; charset= - Array 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 + +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 to; + Vector to_name; + Vector as; + Vector body; + Vector mime; // default: text/plain; charset= + Array 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 diff --git a/uppsrc/Core/SMTP/Smtp.cpp b/uppsrc/Core/SMTP/Smtp.cpp index bdbf6c0fd..ec0f3974d 100644 --- a/uppsrc/Core/SMTP/Smtp.cpp +++ b/uppsrc/Core/SMTP/Smtp.cpp @@ -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 source; - if(a.file.GetCount()) { - FileIn& in = source.Create(); - if(!in.Open(a.file)) - throw Exc("Unable to open attachment file " + a.file); - } - else - source.Create().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 source; + if(a.file.GetCount()) { + FileIn& in = source.Create(); + if(!in.Open(a.file)) + throw Exc("Unable to open attachment file " + a.file); + } + else + source.Create().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 diff --git a/uppsrc/Core/SMTP/src.tpp/SMTP$en-us.tpp b/uppsrc/Core/SMTP/src.tpp/SMTP$en-us.tpp index cc657260f..468a6831d 100644 --- a/uppsrc/Core/SMTP/src.tpp/SMTP$en-us.tpp +++ b/uppsrc/Core/SMTP/src.tpp/SMTP$en-us.tpp @@ -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, diff --git a/uppsrc/Web/smtp.cpp b/uppsrc/Web/smtp.cpp index a787f8fe4..696c4c5dc 100644 --- a/uppsrc/Web/smtp.cpp +++ b/uppsrc/Web/smtp.cpp @@ -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; } diff --git a/uppsrc/Web/smtp.h b/uppsrc/Web/smtp.h index 2ad620390..8185a49e9 100644 --- a/uppsrc/Web/smtp.h +++ b/uppsrc/Web/smtp.h @@ -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;