#include "POP3.h" #define LLOG(x) // DLOG(x) namespace Upp { String QDecode(const String& s) { String r, begin, end, chs, enc, txt; // q-encoded text format (RFC 2047): // =?(charset)?(encoding)?(encoded text)?= if(!SplitTo(s, '?', begin, chs, enc, txt, end)) return s; if(begin != "=" || end != "=") return s; enc = ToUpper(enc); if(enc == "B") r = Base64Decode(txt); else if(enc == "Q") r = QPDecode(txt, true); else return s; int charset = CharsetByName(chs); return charset >= 0 ? ToCharset(CHARSET_DEFAULT, r, charset, '?') : r; } String DecodeHeaderValue(const String& s) { String r, p, q; int pos = 0, part = 0, length = s.GetLength(); bool isq = false; while(pos < length) { if(s[pos] == '=' && s[pos + 1] == '?') { // Process q-encoded text. q.Cat(s.Mid(pos, 2)); pos += 2; while(pos < length) { if(s[pos] == '?' && s[pos + 1] == '=' && part == 2) { q.Cat(s.Mid(pos, 2)); r.Cat(QDecode(q)); q.Clear(); pos++; isq = true; part = 0; break; } else if(s[pos] == '?' && part < 2) part++; q.Cat(s[pos]); pos++; if(pos == length && !q.IsEmpty()) r.Cat(q); } } else { // Process plain text while(pos < length) { if(s[pos] == '=' && s[pos + 1] == '?') { pos--; break; } p.Cat(s[pos], 1); pos++; if(pos == length) isq = false; } bool haschar = false; for(int i = 0; i < p.GetLength(); i++) if(!IsSpace(p[i])) { haschar = true; break; } if((isq && haschar) || !isq) r.Cat(p); p.Clear(); } pos++; } return r; } String sEncode(const String& text) { for(const char *q = text; *q; q++) if((byte)*q < ' ' || (byte)*q > 127) { String txt = ToCharset(CHARSET_UTF8, text); String r = "=?UTF-8?Q?"; for(const char *s = txt; *s; s++) { if((byte)*s < ' ' || (byte)*s > 127 || findarg(*s, '=', '?', ' ', ',', '_') >= 0) r << '=' << FormatIntHexUpper((byte)*s, 2); else r.Cat(*s); } r << "?="; return r; } return text; } void FormatMessageHeader(String& r, const VectorMap& hdr) { for(int i = 0; i < hdr.GetCount(); i++) { String line; line << InitCaps(hdr.GetKey(i)) << ": "; String h = sEncode(hdr[i]); for(const char *s = h; *s; s++) { if(line.GetCount() >= 80) { r << line << "\r\n"; line = "\t"; } line.Cat(*s); } r << line << "\r\n"; } } static void sLn(String& r) { if(r.GetCount() == 0) return; if(r.GetCount() > 1) { const char *s = r.Last(); if(s[-1] == '\r' && s[0] == '\n') return; } r.Cat("\r\n"); } void InetMessage::PutBody(int pi, String& r, int level) const { if(level > 20) return; // Cycle protection FormatMessageHeader(r, part[pi].header); String boundary; String end_boundary; r << "\r\n"; if(part[pi].IsMultipart(boundary, end_boundary)) { for(int i = 0; i < part.GetCount(); i++) if(part[i].parent == pi) { sLn(r); r << boundary << "\r\n"; PutBody(i, r, level + 1); } sLn(r); r << end_boundary << "\r\n" ; } else r << part[pi].body << "\r\n"; } String InetMessage::GetMessage() const { String r; if(part.GetCount()) PutBody(0, r, 0); return r; } bool InetMessage::ReadHeader(VectorMap& hdr, Stream& ss) { hdr.Clear(); for(;;) { if(ss.IsEof()) return false; String s = ss.GetLine(); if(s.IsEmpty()) break; if(s[0] == ' ' || s[0] == '\t') { if(hdr.GetCount()) hdr.Top().Cat(s.Mid(1)); } else { int q = s.Find(':'); if(q >= 0) hdr.Add(ToLower(s.Mid(0, q)), TrimLeft(s.Mid(q + 1))); } } for(int i = 0; i < hdr.GetCount(); i++) hdr[i] = DecodeHeaderValue(hdr[i]); return true; } bool InetMessage::ReadHeader(const String& s) { LLOG("ReadHeader " << s.GetCount()); part.Clear(); StringStream ss(s); return ReadHeader(part.Add().header, ss); } bool InetMessage::Part::IsMultipart(String& boundary, String& end_boundary) const { MIMEHeader h(header.Get("content-type", String())); LLOG("content-type: " << h); if(ToLower(~h).StartsWith("multipart")) { boundary = "--" + h["boundary"]; end_boundary = boundary + "--"; return true; } return false; } bool InetMessage::ReadPart(Stream& ss, int parent, int level) { LLOG("ReadPart parent: " << parent << ", level: " << level); if(level > 5 || part.GetCount() > 200) // Sanity limit return false; int newparent = part.GetCount(); Part& p = part.Add(); p.parent = parent; if(!ReadHeader(p.header, ss)) return false; String boundary, end_boundary; if(!p.IsMultipart(boundary, end_boundary)) { p.body = LoadStream(ss); return true; } for(;;) { String ln = ss.GetLine(); if(ln == boundary) break; if(ss.IsEof()) return false; } bool end = false; while(!end) { String body; bool next = false; for(;;) { if(ss.IsEof()) return false; String ln = ss.GetLine(); if(ln == boundary) break; if(ln == end_boundary) { end = true; break; } if(next) body << "\r\n"; body << ln; next = true; } StringStream nss(body); ReadPart(nss, newparent, level + 1); } return true; } bool InetMessage::Read(const String& s) { part.Clear(); StringStream ss(s); return ReadPart(ss, Null, 0); } String InetMessage::Part::Decode() const { String r = decode(ToLower(header.Get("content-transfer-encoding", "")), "quoted-printable", QPDecode(body), "base64", Base64Decode(body), body); int cs = CharsetByName(MIMEHeader(ToLower(header.Get("content-type", Null)))["charset"]); if(cs >= 0) r = ToCharset(CHARSET_DEFAULT, r, cs, '?'); return r; } bool MIMEHeader::Parse(const char *s) { value.Clear(); param.Clear(); CParser p(s); try { CParser p(s); const char *b = p.GetPtr(); while(!p.IsEof() && !p.IsChar(';')) p.SkipTerm(); value = TrimBoth(String(b, p.GetPtr())); if(*value == '\'' && *value.Last() == '\'' || // mime type can be quoted... *value == '\"' && *value.Last() == '\"') value = TrimBoth(value.Mid(1, value.GetCount() - 2)); while(!p.IsEof()) { if(p.Char(';') && p.IsId()) { const char *b = p.GetPtr(); while(!p.IsEof() && !p.IsChar(';') && !p.IsChar('=')) p.SkipTerm(); String id = ToLower(TrimBoth(String(b, p.GetPtr()))); String val; if(p.Char('=')) { if(p.IsChar('\'')) val = p.ReadString('\''); else if(p.IsString()) val = p.ReadString(); else { const char *b = p.GetPtr(); while(!p.IsEof() && !p.IsChar(';')) p.SkipTerm(); val = TrimBoth(String(b, p.GetPtr())); } } param.Add(id, val); } else p.SkipTerm(); } } catch(CParser::Error) {} return value.GetCount(); } String MIMEHeader::ToString() const { String r = value; for(int i = 0; i < param.GetCount(); i++) r << "; " << param.GetKey(i) << '=' << AsCString(param[i]); return r; } Index ParseMessageIDs(const String& s) { Index ref; int q = 0; for(;;) { q = s.Find('<', q); if(q < 0) break; int w = s.Find('>', q); if(w < 0) break; ref.FindAdd(s.Mid(q + 1, w - q - 1)); q = w; } return ref; } String FormatMessageIDs(const Index& id) { String r; for(int i = 0; i < id.GetCount(); i++) if(id[i].GetCount()) { if(r.GetCount()) r << ' '; r << '<' << id[i] << '>'; } return r; } }