PageRenderTime 46ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/uppsrc/Web/smtp.cpp

https://code.google.com/p/upp-mac/
C++ | 376 lines | 342 code | 29 blank | 5 comment | 127 complexity | 9fd6eec386a697328b11ed5ab43c8215 MD5 | raw file
Possible License(s): BSD-2-Clause, BSD-3-Clause, LGPL-3.0
  1. #include "Web.h"
  2. NAMESPACE_UPP
  3. //#define SMTP_DEBUG // uncomment this line to turn on LOG-based SMTP emulation
  4. //#define SMTP_LOG // uncomment this line to turn on command-line based logging of SMTP communication
  5. static String GetDelimiter(const char *b, const char *e, String init)
  6. {
  7. static const char delimiters[] =
  8. "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
  9. "ghijklmnopqrstuvxyz()+/:?0123456"
  10. "789abcdefghijklmnopqrstuvwxyzABC"
  11. "DEFGHIJKLMNOPQRSTUVWXYZ012345678";
  12. String out = init;
  13. if(b == e)
  14. return out;
  15. if(IsNull(out))
  16. out.Cat(delimiters[*b++ & 0x7F]);
  17. int l = out.GetLength();
  18. for(; b != e; b++)
  19. {
  20. b = (const char *)memchr(b, *out, e - b);
  21. if(!b || e - b < l)
  22. return out;
  23. if(!memcmp(b, out, l))
  24. {
  25. if(e - b == l)
  26. return out + '/';
  27. out.Cat(delimiters[b[l] & 0x7F]);
  28. }
  29. }
  30. return out;
  31. }
  32. static String GetDelimiter(String s, String init)
  33. {
  34. return GetDelimiter(s.Begin(), s.End(), init);
  35. }
  36. static void Send(Socket& socket, const String &s, String *transcript = 0, int timeout = 60)
  37. {
  38. #ifdef SMTP_LOG
  39. puts("Command: " + s);
  40. #endif
  41. if(transcript && !s.IsEmpty())
  42. {
  43. transcript -> Cat(s);
  44. if(*s.Last() != '\n')
  45. transcript -> Cat('\n');
  46. }
  47. #ifdef SMTP_DEBUG
  48. GetVppLog().Put(s);
  49. return;
  50. #endif
  51. dword end_time = (timeout >= 0 ? GetTickCount() + 1000 * timeout : 0xFFFFFFFF);
  52. const char *p = s.Begin(), *e = s.End();
  53. while(p != e)
  54. {
  55. if((dword)GetTickCount() > end_time)
  56. throw Exc(t_("Communication Failure: Timeout."));
  57. int amount = socket.WriteRaw(p, int(e - p)), err;
  58. if(amount > 0)
  59. { // some data has been sent - reset timeout
  60. p += amount;
  61. end_time = (timeout >= 0 ? GetTickCount() + 1000 * timeout : 0xFFFFFFFF);
  62. }
  63. else if(amount == 0)
  64. throw Exc(t_("Error writing data to socket: communication port closed."));
  65. else if((err = Socket::GetErrorCode()) != SOCKERR(EWOULDBLOCK)) {
  66. String str;
  67. throw Exc(str << t_("Error writing data to socket, error code: ") << err);
  68. }
  69. else
  70. Sleep(100);
  71. }
  72. }
  73. static String SendRecv(Socket& socket, const String& s, String *transcript = 0, int timeout = 60)
  74. {
  75. Send(socket, s, transcript, timeout);
  76. #ifdef SMTP_DEBUG
  77. return "250 OK";
  78. #endif
  79. String dest;
  80. dword end_time = (timeout >= 0 ? GetTickCount() + 1000 * timeout : 0xFFFFFFFF);
  81. for(;;)
  82. {
  83. if((dword)GetTickCount() > end_time)
  84. throw Exc(t_("Communication Failure: Timeout."));
  85. char buffer[100];
  86. int amount = socket.ReadRaw(buffer, sizeof(buffer)), err;
  87. if(amount > 0)
  88. {
  89. dest.Cat(buffer, amount);
  90. while(--amount >= 0)
  91. if(buffer[amount] == '\n')
  92. {
  93. if(transcript && !dest.IsEmpty())
  94. {
  95. transcript -> Cat(dest);
  96. if(*dest.Last() != '\n')
  97. transcript -> Cat('\n');
  98. }
  99. #ifdef SMTP_LOG
  100. puts("Reply: " + dest);
  101. #endif
  102. return dest;
  103. }
  104. }
  105. else if(amount == 0)
  106. throw Exc(t_("Error reading data from socket: communication port closed."));
  107. else if((err = Socket::GetErrorCode()) != SOCKERR(EWOULDBLOCK))
  108. throw Exc(NFormat(t_("Error reading socket, error code: %s"), err));
  109. else
  110. Sleep(100);
  111. }
  112. }
  113. static void SendRecvOK(Socket& socket, const String& s, String *transcript = 0, int timeout = 60)
  114. {
  115. String ans = SendRecv(socket, s, transcript, timeout);
  116. if(ans[0] != '2' || ans[1] != '5' || ans[2] != '0')
  117. throw Exc(ans);
  118. }
  119. //////////////////////////////////////////////////////////////////////
  120. // SmtpMail::
  121. SmtpMail::SmtpMail()
  122. : port(25)
  123. , no_header(false)
  124. , no_header_sep(false)
  125. , time_sent(GetSysTime())
  126. {
  127. }
  128. static const char default_mime[] = "application/octet-stream";
  129. SmtpMail& SmtpMail::AttachFile(const char *filename, const char *mime)
  130. {
  131. Attachment& attach = attachments.Add();
  132. attach.name = GetFileNamePos(filename);
  133. attach.mime = (mime ? mime : default_mime);
  134. attach.file = filename;
  135. return *this;
  136. }
  137. SmtpMail& SmtpMail::Attach(const char *name, String data, const char *mime)
  138. {
  139. Attachment& attach = attachments.Add();
  140. attach.name = name;
  141. attach.mime = (mime ? mime : default_mime);
  142. attach.data = data;
  143. return *this;
  144. }
  145. bool SmtpMail::Send()
  146. {
  147. Socket socket;
  148. String ipaddr;
  149. try
  150. {
  151. if(IsNull(host))
  152. throw Exc(t_("Host not set."));
  153. if(to.IsEmpty())
  154. throw Exc(t_("Recipient not set."));
  155. #ifdef SMTP_DEBUG
  156. ipaddr = "1.2.3.4";
  157. #else
  158. Socket::Init();
  159. dword my_addr;
  160. if(!ClientSocket(socket, host, port, true, &my_addr, 10000, true))
  161. throw Exc(NFormat("Cannot open socket %s:%d: %s", host, port, Socket::GetErrorText()));
  162. ipaddr
  163. << (int)((my_addr >> 0) & 0xFF) << '.'
  164. << (int)((my_addr >> 8) & 0xFF) << '.'
  165. << (int)((my_addr >> 16) & 0xFF) << '.'
  166. << (int)((my_addr >> 24) & 0xFF);
  167. #endif
  168. String *trans_ptr = (transcript ? &transcript_text : 0);
  169. String ans;
  170. // receive initial message & send hello
  171. SendRecv(socket, Null, trans_ptr, 30);
  172. String org;
  173. int pos = from.Find('@');
  174. if(pos >= 0)
  175. {
  176. int start = ++pos, len = from.GetLength();
  177. while(pos < len && from[pos] != '>')
  178. pos++;
  179. org = from.Mid(start, pos - start);
  180. }
  181. else
  182. org << ipaddr;
  183. SendRecvOK(socket, "HELO " + org + "\r\n", trans_ptr);
  184. if(!IsNull(auth_user)) {
  185. String ans = SendRecv(socket, "AUTH LOGIN\r\n", trans_ptr);
  186. while(ans[0] != '2')
  187. if(ans[0] == '3' && ans[1] == '3' && ans[2] == '4' && ans[3] == ' ') {
  188. String param = Base64Decode(ans.GetIter(4), ans.End());
  189. if(param == "Username:")
  190. ans = SendRecv(socket, Base64Encode(auth_user) + "\r\n", trans_ptr);
  191. else if(param == "Password:")
  192. ans = SendRecv(socket, Base64Encode(auth_pwd) + "\r\n", trans_ptr);
  193. else
  194. throw Exc(ans);
  195. }
  196. else
  197. throw Exc(ans);
  198. }
  199. SendRecvOK(socket, "MAIL FROM:<" + from + ">\r\n", trans_ptr);
  200. for(int i = 0; i < to.GetCount(); i++)
  201. SendRecv(socket, "RCPT TO:<" + to[i] + ">\r\n", trans_ptr);
  202. ans = SendRecv(socket, "DATA\r\n", trans_ptr);
  203. #ifndef SMTP_DEBUG
  204. if(memcmp(ans, "354", 3))
  205. throw Exc(ans);
  206. #endif
  207. String delimiter = "?";
  208. for(int i = 0; i < text.GetCount(); i++)
  209. delimiter = GetDelimiter(text[i], delimiter);
  210. bool alter = text.GetCount() > 1;
  211. bool multi = !attachments.IsEmpty();
  212. { // format message
  213. String msg;
  214. if(!no_header)
  215. { // generate message header
  216. msg << "From: " << from << "\r\n";
  217. static const AS as_list[] = { TO, CC, BCC };
  218. static const char *as_name[] = { "To", "CC", "BCC" };
  219. for(int a = 0; a < __countof(as_list); a++)
  220. {
  221. int pos = 0;
  222. for(int i = 0; i < as.GetCount(); i++)
  223. if(as[i] == as_list[a])
  224. {
  225. if(pos && pos + to[i].GetLength() >= 70)
  226. {
  227. msg << "\r\n ";
  228. pos = 5;
  229. }
  230. else if(pos)
  231. {
  232. msg << ", ";
  233. pos += 2;
  234. }
  235. else
  236. {
  237. msg << as_name[a] << ": ";
  238. pos = (int)strlen(as_name[a]) + 2;
  239. }
  240. msg << to[i];
  241. }
  242. if(pos)
  243. msg << "\r\n";
  244. }
  245. if(!IsNull(subject))
  246. msg << "Subject: " << subject << "\r\n";
  247. if(!IsNull(reply_to))
  248. msg << "Reply-To: " << reply_to << "\r\n";
  249. if(!IsNull(time_sent)) {
  250. static const char *dayofweek[] =
  251. { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
  252. static const char *month[] =
  253. { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
  254. msg << "Date: "
  255. << dayofweek[DayOfWeek(time_sent)] << ", "
  256. << (int)time_sent.day << ' ' << month[time_sent.month - 1] << ' ' << (int)time_sent.year
  257. << ' ' << Sprintf("%2d:%02d:%02d +0100", time_sent.hour, time_sent.minute, time_sent.second)
  258. << "\r\n";
  259. }
  260. if(multi || alter)
  261. msg << "Content-Type: Multipart/" << (alter ? "alternative" : "mixed")
  262. << "; boundary=\"" << delimiter << "\"\r\n"
  263. "\r\n";
  264. }
  265. for(int i = 0; i < text.GetCount(); i++) {
  266. String t = text[i], m = mime[i];
  267. if(!no_header) {
  268. if(multi || alter)
  269. msg << "--" << delimiter << "\r\n";
  270. if(IsNull(m))
  271. m << "text/plain; charset=\"" << MIMECharsetName(CHARSET_DEFAULT) << "\"";
  272. msg << "Content-Type: " << m << "\r\n"
  273. "Content-Transfer-Encoding: quoted-printable\r\n";
  274. }
  275. if(!no_header_sep)
  276. msg << "\r\n";
  277. bool begin = true;
  278. for(const char *p = t.Begin(), *e = t.End(); p != e; p++)
  279. if(*p >= 33 && *p <= 126 && *p != '=' && (*p != '.' || !begin)) {
  280. msg.Cat(*p);
  281. begin = false;
  282. }
  283. else if(*p == '.' && begin) {
  284. msg.Cat("..");
  285. begin = false;
  286. }
  287. else if(*p == ' ' && p + 1 != e && p[1] != '\r' && p[1] != '\n') {
  288. msg.Cat(' ');
  289. begin = false;
  290. }
  291. else if(*p == '\r')
  292. ;
  293. else if(*p == '\n') {
  294. msg.Cat("\r\n");
  295. begin = true;
  296. }
  297. else {
  298. static const char hex[] = "0123456789ABCDEF";
  299. msg.Cat('=');
  300. msg.Cat(hex[(*p >> 4) & 15]);
  301. msg.Cat(hex[*p & 15]);
  302. }
  303. if(!begin)
  304. msg.Cat("\r\n");
  305. }
  306. for(int i = 0; i < attachments.GetCount(); i++) {
  307. const Attachment& a = attachments[i];
  308. One<Stream> source;
  309. if(!IsNull(a.file)) {
  310. One<FileIn> fi = new FileIn(a.file);
  311. if(fi -> IsOpen())
  312. source = -fi;
  313. }
  314. else
  315. source = new StringStream(a.data);
  316. msg << "--" << delimiter << "\r\n"
  317. "Content-Type: " << a.mime << "; name=\"" << a.name << "\"\r\n"
  318. "Content-Transfer-Encoding: base64\r\n"
  319. "Content-Disposition: attachment; filename=\"" << a.name << "\"\r\n"
  320. "\r\n";
  321. char buffer[54];
  322. for(int c; (c = source -> Get(buffer, sizeof(buffer))) != 0;)
  323. {
  324. msg.Cat(Base64Encode(buffer, buffer + c));
  325. msg.Cat('\r');
  326. msg.Cat('\n');
  327. if(msg.GetLength() >= 65536) {
  328. UPP::Send(socket, msg, trans_ptr);
  329. msg = Null;
  330. }
  331. }
  332. }
  333. if(multi || alter)
  334. msg << "--" << delimiter << "--\r\n";
  335. msg.Cat(".\r\n");
  336. SendRecvOK(socket, msg, trans_ptr);
  337. }
  338. SendRecv(socket, "QUIT\r\n", trans_ptr);
  339. return true;
  340. }
  341. catch(Exc e)
  342. {
  343. error = e;
  344. return false;
  345. }
  346. }
  347. END_UPP_NAMESPACE