#include "POP3.h" 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; if(ToUpper(enc) == "B") r = Base64Decode(txt); else if(ToUpper(enc) == "Q") { txt.Replace(" ", "_"); r = QPDecode(txt); r.Replace("_", " "); } 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; } bool InetMessage::ReadHeader(VectorMap& hdr, Stream& ss) { for(;;) { if(ss.IsEof()) return false; String s = ss.GetLine(); if(s.IsEmpty()) return true; if(s[0] == ' ' || s[0] == '\t') { if(hdr.GetCount()) hdr.Top().Cat(DecodeHeaderValue(TrimLeft(s.Mid(1)))); } else { int q = s.Find(':'); if(q >= 0) hdr.Add(ToLower(s.Mid(0, q)), DecodeHeaderValue(TrimLeft(s.Mid(q + 1)))); } } } bool InetMessage::ReadHeader(const String& s) { part.Clear(); StringStream ss(s); return ReadHeader(part.Add().header, ss); } bool InetMessage::ReadPart(Stream& ss, int parent, int 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 content_type = p.header.Get("content-type", String()); if(!ToLower(content_type).StartsWith("multipart")) { p.body = LoadStream(ss); return true; } String boundary; int q = content_type.Find("boundary="); if(q < 0) return false; q += strlen("boundary="); int qq = content_type.Find(";", q); if(qq >= 0) boundary = content_type.Mid(q, qq); else boundary = content_type.Mid(q); if(*boundary == '\"') boundary = boundary.Mid(1); if(*boundary.Last() == '\"') boundary.Trim(boundary.GetCount() - 1); boundary = "--" + boundary; String end_boundary = boundary + "--"; for(;;) { String ln = ss.GetLine(); if(ln == boundary) break; if(ss.IsEof()) return false; } bool end = false; while(!end) { String body; for(;;) { if(ss.IsEof()) return false; String ln = ss.GetLine(); if(ln == boundary) break; if(ln == end_boundary) { end = true; break; } body << ln << "\r\n"; } 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")), "quoted-printable", QPDecode(body), "base64", Base64Decode(body), body); String content_type = ToLower(header.Get("content-type", Null)); int q = content_type.Find("charset="); if(q >= 0) { q += strlen("charset="); int qq = content_type.Find(';', q); String charset = qq >= 0 ? content_type.Mid(q, qq + 1) : content_type.Mid(q); int cs = CharsetByName(charset); if(cs >= 0) r = ToCharset(CHARSET_DEFAULT, r, cs, '?'); } return r; }