From ddfd077072bee5d18c13ae111383ed85f9f42805 Mon Sep 17 00:00:00 2001 From: cxl Date: Sun, 22 Apr 2012 13:52:57 +0000 Subject: [PATCH] Core/SMTP git-svn-id: svn://ultimatepp.org/upp/trunk@4845 f0d560ea-af0d-0410-9eb7-867de7ffcac7 --- uppsrc/Core/Inet.h | 2 + uppsrc/Core/InetUtil.cpp | 38 ++ uppsrc/Core/SMTP/SMTP.h | 85 ++++ uppsrc/Core/SMTP/SMTP.upp | 6 + uppsrc/Core/SMTP/Smtp.cpp | 386 ++++++++++++++++++ uppsrc/Core/SMTP/init | 3 + .../Core/SSL/{InitExit.cpp => InitExit.icpp} | 0 uppsrc/Core/SSL/SSL.upp | 2 +- uppsrc/Core/SSL/init | 3 + uppsrc/Core/Value.h | 2 +- uppsrc/Core/src.tpp/Inet$en-us.tpp | 5 + uppsrc/Web/httpsrv.cpp | 38 -- uppsrc/Web/httpsrv.h | 2 - 13 files changed, 530 insertions(+), 42 deletions(-) create mode 100644 uppsrc/Core/SMTP/SMTP.h create mode 100644 uppsrc/Core/SMTP/SMTP.upp create mode 100644 uppsrc/Core/SMTP/Smtp.cpp create mode 100644 uppsrc/Core/SMTP/init rename uppsrc/Core/SSL/{InitExit.cpp => InitExit.icpp} (100%) diff --git a/uppsrc/Core/Inet.h b/uppsrc/Core/Inet.h index 388437657..6b0bbdde5 100644 --- a/uppsrc/Core/Inet.h +++ b/uppsrc/Core/Inet.h @@ -1,5 +1,7 @@ String WwwFormat(Time tm); +String MIMECharsetName(byte charset); + String UrlEncode(const char *s, const char *end); String UrlEncode(const char *s, int len); String UrlEncode(const String& s); diff --git a/uppsrc/Core/InetUtil.cpp b/uppsrc/Core/InetUtil.cpp index c0b7e06a3..f77be3233 100644 --- a/uppsrc/Core/InetUtil.cpp +++ b/uppsrc/Core/InetUtil.cpp @@ -15,6 +15,44 @@ String WwwFormat(Time tm) << ' ' << Sprintf("%2d:%02d:%02d +0100", tm.hour, tm.minute, tm.second); } +String MIMECharsetName(byte charset) +{ + if(charset == CHARSET_DEFAULT) + charset = GetDefaultCharset(); + switch(charset) { + case CHARSET_ISO8859_1: return "ISO-8859-1"; + case CHARSET_ISO8859_2: return "ISO-8859-2"; + case CHARSET_ISO8859_3: return "ISO-8859-3"; + case CHARSET_ISO8859_4: return "ISO-8859-4"; + case CHARSET_ISO8859_5: return "ISO-8859-5"; + case CHARSET_ISO8859_6: return "ISO-8859-6"; + case CHARSET_ISO8859_7: return "ISO-8859-7"; + case CHARSET_ISO8859_8: return "ISO-8859-8"; + case CHARSET_ISO8859_9: return "ISO-8859-9"; + case CHARSET_ISO8859_10: return "ISO-8859-10"; + case CHARSET_ISO8859_13: return "ISO-8859-13"; + case CHARSET_ISO8859_14: return "ISO-8859-14"; + case CHARSET_ISO8859_15: return "ISO-8859-15"; + case CHARSET_ISO8859_16: return "ISO-8859-16"; + case CHARSET_WIN1250: return "windows-1250"; + case CHARSET_WIN1251: return "windows-1251"; + case CHARSET_WIN1252: return "windows-1252"; + case CHARSET_WIN1253: return "windows-1253"; + case CHARSET_WIN1254: return "windows-1254"; + case CHARSET_WIN1255: return "windows-1255"; + case CHARSET_WIN1256: return "windows-1256"; + case CHARSET_WIN1257: return "windows-1257"; + case CHARSET_WIN1258: return "windows-1258"; +// case CHARSET_KOI8_R: +// case CHARSET_CP852: +// case CHARSET_MJK: + case CHARSET_TOASCII: return "us-ascii"; + case CHARSET_UTF8: return "UTF-8"; +// case CHARSET_UNICODE: + default: return Null; + } +} + static const char hex_digits[] = "0123456789ABCDEF"; String UrlEncode(const char *p, const char *e) diff --git a/uppsrc/Core/SMTP/SMTP.h b/uppsrc/Core/SMTP/SMTP.h new file mode 100644 index 000000000..00e73d250 --- /dev/null +++ b/uppsrc/Core/SMTP/SMTP.h @@ -0,0 +1,85 @@ +#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 text; + 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& From(const String& from, const String& name = Null); + Smtp& To(const String& t, const String& name, AS a = TO); + Smtp& To(const String& t, AS a = TO) { return To(t, Null, a); } + Smtp& Cc(const String& t, const String& name = Null) { return To(t, name, CC); } + Smtp& Bcc(const String& t, const String& name = Null) { return To(t, name, BCC); } + Smtp& ReplyTo(const String& r, const String& name = Null); + Smtp& Text(const String& t, const String& mime_ = Null) { text.Add(t); mime.Add(mime_); return *this; } + Smtp& NoHeader() { no_header = true; return *this; } + Smtp& NoHeaderSep() { no_header_sep = true; return *this; } + Smtp& TimeSent(Time t) { time_sent = t; return *this; } + Smtp& Subject(const String& s); + Smtp& AttachFile(const char *filename, const char *mime = 0); + Smtp& Attach(const char *name, const String& data, const char *mime = 0); + Smtp& Auth(const String& user, const String& pwd) { auth_user = user; auth_pwd = pwd; return *this; } + + 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.upp b/uppsrc/Core/SMTP/SMTP.upp new file mode 100644 index 000000000..a8b57c59f --- /dev/null +++ b/uppsrc/Core/SMTP/SMTP.upp @@ -0,0 +1,6 @@ +description "\3770,128,128"; + +file + SMTP.h, + Smtp.cpp; + diff --git a/uppsrc/Core/SMTP/Smtp.cpp b/uppsrc/Core/SMTP/Smtp.cpp new file mode 100644 index 000000000..03800b249 --- /dev/null +++ b/uppsrc/Core/SMTP/Smtp.cpp @@ -0,0 +1,386 @@ +#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 < text.GetCount(); i++) + delimiter = GetDelimiter(text[i], delimiter); + bool alter = text.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 < text.GetCount(); i++) { + String t = text[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(); + text.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 diff --git a/uppsrc/Core/SMTP/init b/uppsrc/Core/SMTP/init new file mode 100644 index 000000000..19a2ac45f --- /dev/null +++ b/uppsrc/Core/SMTP/init @@ -0,0 +1,3 @@ +#ifndef _Core_SMTP_icpp_init_stub +#define _Core_SMTP_icpp_init_stub +#endif diff --git a/uppsrc/Core/SSL/InitExit.cpp b/uppsrc/Core/SSL/InitExit.icpp similarity index 100% rename from uppsrc/Core/SSL/InitExit.cpp rename to uppsrc/Core/SSL/InitExit.icpp diff --git a/uppsrc/Core/SSL/SSL.upp b/uppsrc/Core/SSL/SSL.upp index c1e9cda52..cb27f6109 100644 --- a/uppsrc/Core/SSL/SSL.upp +++ b/uppsrc/Core/SSL/SSL.upp @@ -17,6 +17,6 @@ link(WIN32 MSC SO) /nodefaultlib:libc; file SSL.h, Util.cpp, - InitExit.cpp, + InitExit.icpp, Socket.cpp; diff --git a/uppsrc/Core/SSL/init b/uppsrc/Core/SSL/init index 5d16396f3..1d0bc8481 100644 --- a/uppsrc/Core/SSL/init +++ b/uppsrc/Core/SSL/init @@ -1,3 +1,6 @@ #ifndef _Core_SSL_icpp_init_stub #define _Core_SSL_icpp_init_stub +#define BLITZ_INDEX__ F6fe5614ef3a94410532ec46bf6f89f79 +#include "InitExit.icpp" +#undef BLITZ_INDEX__ #endif diff --git a/uppsrc/Core/Value.h b/uppsrc/Core/Value.h index c1d910157..0963a80ac 100644 --- a/uppsrc/Core/Value.h +++ b/uppsrc/Core/Value.h @@ -296,7 +296,7 @@ inline bool IsDateTime(const Value& v) { return v.Is() || v.Is