1 module mail.pop3; 2 3 import core.thread; 4 5 import std.algorithm; 6 import std.conv; 7 import std.string; 8 import std.encoding; 9 import std.uri; 10 import std.exception; 11 import std.typecons; 12 13 import mail.msg; 14 import mail.socket; 15 16 struct Pop3Reply 17 { 18 string code; 19 string message; 20 21 @property bool success() const 22 { 23 return code.startsWith("+OK"); 24 } 25 26 void toString(scope void delegate(const(char)[]) sink) const 27 { 28 sink(code); 29 sink(" "); 30 sink(message); 31 } 32 }; 33 34 class Pop3 35 { 36 private 37 { 38 Socket _sock; 39 string _lastCode; 40 string _lastData; 41 } 42 43 @property ulong length() 44 { 45 if (_sock.isOpen) 46 { 47 _sock.send("STAT\n"); 48 if (isOk) 49 { 50 return (_lastData.split(" ")[0]).to!ulong; 51 } 52 } 53 return 0; 54 } 55 56 @property ulong size() 57 { 58 if (_sock.isOpen) 59 { 60 _sock.send("STAT\n"); 61 if (isOk) 62 { 63 return (_lastData.split(" ")[1]).to!ulong; 64 } 65 } 66 return 0; 67 } 68 69 this(string host, ushort port = 110) 70 { 71 _sock = new Socket(host, port); 72 } 73 74 Pop3Reply connect() 75 { 76 if (_sock.connect()) 77 { 78 parseReply(); 79 return Pop3Reply(_lastCode, _lastData); 80 } 81 return Pop3Reply("-ERR"); 82 } 83 84 void disconnect() 85 { 86 _sock.disconnect(); 87 } 88 89 Pop3Reply auth(string username, string password) 90 { 91 auto r = query("USER " ~ username); 92 if (r.success) 93 { 94 r = query("PASS " ~ password); 95 } 96 return r; 97 } 98 99 Pop3Reply list() 100 { 101 return query("LIST", true); 102 } 103 104 Pop3Reply stat() 105 { 106 return query("STAT"); 107 } 108 109 Pop3Reply noop() 110 { 111 return query("NOOP"); 112 } 113 114 Pop3Reply rset() 115 { 116 return query("RSET"); 117 } 118 119 Pop3Reply retr(ulong id) 120 { 121 return query("RETR " ~ id.to!string, true); 122 } 123 124 Msg retrMsg(ulong id, bool canonize = true) 125 { 126 Msg m; 127 _sock.send("RETR " ~ id.to!string ~ "\n"); 128 parseReply(true); 129 if (_lastCode.startsWith("+OK")) 130 { 131 auto mData = _lastData.findSplit("\n")[2].strip; 132 if (canonize) 133 { 134 mData = mData.replace("\n..", "\n."); 135 } 136 import std.stdio; 137 138 m = Msg.parse(cast(ubyte[]) mData); 139 } 140 return m; 141 } 142 143 Headers getHeaders(ulong id) 144 { 145 _sock.send("TOP " ~ id.to!string ~ " 0\n"); 146 parseReply(true); 147 148 auto mData = _lastData.findSplit("\n")[2].strip; 149 mData = mData.replace("\n..", "\n."); 150 151 Msg m = Msg.parse(cast(ubyte[]) mData); 152 return m.headers; 153 } 154 155 Tuple!(ulong, string)[] getUIDLs() 156 { 157 import std.array : array; 158 159 query("UIDL", true); 160 if (!_lastCode.startsWith("+OK")) 161 return []; 162 return _lastData.split("\r\n").filter!(s => s.length > 2).map!((s) { 163 auto a = s.split(" "); 164 return tuple(a[0].to!ulong, a[1]); 165 }).array; 166 } 167 168 Pop3Reply dele(ulong id) 169 { 170 return query("DELE " ~ id.to!string); 171 } 172 173 Pop3Reply quit() 174 { 175 return query("QUIT"); 176 } 177 178 private: 179 void parseReply(bool multiline = false) 180 { 181 auto tmp = recvAll(multiline).findSplit(multiline ? "\r\n" : " "); 182 _lastCode = tmp[0]; 183 _lastData = tmp[2]; 184 } 185 186 bool isOk() 187 { 188 parseReply(); 189 return _lastCode == "+OK"; 190 } 191 192 Pop3Reply query(string command, bool multiline = false) 193 { 194 if (!_sock.isOpen) 195 return Pop3Reply("-ERR"); 196 _sock.send(command ~ "\n"); 197 parseReply(multiline); 198 return Pop3Reply(_lastCode, _lastData); 199 } 200 201 string recvAll(bool multiline = false) 202 { 203 string end = multiline ? "\r\n.\r\n" : "\r\n"; 204 string result; 205 ptrdiff_t cnt = 0; 206 do 207 { 208 auto tmp = _sock.receive(); 209 if (!tmp.length) 210 break; 211 result ~= tmp; 212 Thread.sleep(dur!"msecs"(1)); 213 } 214 while (!result.endsWith(end)); 215 216 if (!result.length) 217 return result; 218 219 return result[0 .. $ - end.length]; 220 } 221 } 222 223 unittest 224 { 225 auto reply = Pop3Reply("+OK", "Test\r\n"); 226 assert(reply.to!string == "+OK Test\r\n"); 227 }