`Article.getTextToSend()` now returns non-sliceable array. don't forget about that!
[chiroptera.git] / maildb / net / linesocket.d
blobc6b7e527c0a7e1092dcb359e7700f09b84658039
1 /* E-Mail Client
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 module maildb.net.linesocket is aliced;
20 import std.socket;
22 import iv.cmdcon;
23 import iv.sslsocket;
26 // ////////////////////////////////////////////////////////////////////////// //
27 public class SocketLine {
28 protected:
29 Socket sk;
30 usize rdbufp;
31 uint rdbufsize;
32 uint rdbufused;
33 bool debugdump = false;
35 private:
36 final @property char* rdbuf () nothrow @trusted @nogc { pragma(inline, true); return (cast(char*)rdbufp); }
37 final void freeRdBuf () nothrow @trusted @nogc {
38 if (rdbufp) {
39 import core.stdc.stdlib : free;
40 free(cast(void*)rdbufp);
41 rdbufp = 0;
45 private:
46 void growRdBuf () {
47 import core.stdc.stdlib : realloc;
48 enum delta = 8192;
49 auto nlen = rdbufsize+delta;
50 if (nlen >= 2*1024*1024) { freeRdBuf(); throw new Exception("line too long"); }
51 auto nb = cast(char*)realloc(cast(void*)rdbufp, nlen);
52 if (nb is null) { freeRdBuf(); assert(0, "out of memory"); }
53 rdbufp = cast(usize)nb;
54 rdbufsize = nlen;
57 public:
58 this (string server, ushort plainport, ushort tlsport, bool adodump) {
59 debugdump = adodump;
60 if (server.length >= 4 && (server[0..4] == "ssl:" || server[0..4] == "tls:")) {
61 if (tlsport == 0) throw new Exception("TLS port is not defined for '"~server[4..$]~"'");
62 conwriteln("connecting to [", server[4..$], ":", tlsport, "]...");
63 auto xsk = new SSLClientSocket(AddressFamily.INET);
64 xsk.manualHandshake = false;
65 xsk.connect(new InternetAddress(server[4..$], tlsport));
66 sk = xsk;
67 } else {
68 if (plainport == 0) throw new Exception("PLAIN port is not defined for '"~server~"'");
69 conwriteln("connecting to [", server, ":", plainport, "]...");
70 sk = new TcpSocket(new InternetAddress(server, plainport));
72 scope(failure) close();
73 import core.time;
74 sk.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, 10.seconds);
75 sk.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, 10.seconds);
78 ~this () nothrow @trusted @nogc { freeRdBuf(); }
80 void abort () {
81 if (sk !is null) {
82 try {
83 if (cast(SSLClientSocket)sk) {
84 sk.close();
85 } else {
86 sk.shutdown(SocketShutdown.BOTH);
87 sk.close();
89 } catch (Exception e) {
90 conwriteln("CLOSE ERROR: ", e.msg);
92 sk = null;
94 freeRdBuf();
97 void close () {
98 try { quit(); } catch (Exception e) {}
99 try { abort(); } catch (Exception e) {}
102 void quit () {}
104 final @property bool active () { return (sk !is null && sk.isAlive); }
106 final void doSendRaw (const(char)[] data) {
107 import core.sys.posix.sys.socket : MSG_NOSIGNAL;
108 import std.format;
109 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
110 if (data.length == 0) return;
111 auto dataanchor = data; // 'cause we may get data which requires pointer to head
112 while (data.length > 1) {
113 auto sd = sk.send(data[0..$-1], cast(SocketFlags)(MSG_NOSIGNAL|0x8000/*MSG_MORE*/));
114 if (sd <= 0) { close(); throw new Exception("sending error"); }
115 data = data[sd..$];
117 while (data.length) {
118 auto sd = sk.send(data, cast(SocketFlags)(MSG_NOSIGNAL));
119 if (sd <= 0) { close(); throw new Exception("sending error"); }
120 data = data[sd..$];
124 final void doSend(A...) (string fmt, A args) {
125 import core.sys.posix.sys.socket : MSG_NOSIGNAL;
126 import std.format;
127 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
128 string s = fmt.format(args)~"\r";
129 if (debugdump) {
130 if (s.length > 4 && s[0..4] == "PASS") {
131 conwriteln("|>PASS *|");
132 } else {
133 conwriteln("|>", s[0..$-1], "|");
136 while (s.length) {
137 auto sd = sk.send(s, cast(SocketFlags)(MSG_NOSIGNAL|0x8000/*MSG_MORE*/));
138 if (sd <= 0) { close(); throw new Exception("sending error"); }
139 s = s[sd..$];
141 s = "\n";
142 while (s.length) {
143 auto sd = sk.send(s, cast(SocketFlags)(MSG_NOSIGNAL));
144 if (sd <= 0) { close(); throw new Exception("sending error"); }
145 s = s[sd..$];
149 //WARNING! result is valid only until next `readLine()` call
150 final const(char)[] readLine () {
151 import core.stdc.string : memmove;
152 if (sk is null) return null;
153 // throw away old line
154 //conwriteln("rdbufused=", rdbufused);
155 uint pos = 0;
156 while (pos < rdbufused && rdbuf[pos] != '\n') ++pos;
157 if (pos < rdbufused) {
158 assert(rdbuf[pos] == '\n');
159 ++pos;
161 if (pos > 0) {
162 //conwriteln("removed ", pos, " bytes out of ", rdbufused, " bytes");
163 assert(pos <= rdbufused);
164 if (pos < rdbufused) memmove(rdbuf, rdbuf+pos, rdbufused-pos);
165 rdbufused -= pos;
167 // check if we have another line buffered
168 //conwriteln("pos=", pos, "; rdbufused=", rdbufused);
169 pos = 0;
170 while (pos < rdbufused && rdbuf[pos] != '\n') ++pos;
171 if (pos < rdbufused) {
172 //conwriteln("GOT: ", pos, " out of ", rdbufused);
173 if (pos > 0 && rdbuf[pos-1] == '\r') --pos;
174 if (debugdump) conwriteln("|<", rdbuf[0..pos], "|");
175 return rdbuf[0..pos];
177 // no buffered line, get more data
178 for (;;) {
179 assert(rdbufused <= rdbufsize);
180 if (rdbufused >= rdbufsize) growRdBuf();
181 auto rc = sk.receive(rdbuf[rdbufused..rdbufsize], SocketFlags.NONE);
182 //conwriteln(rc, " bytes read");
183 //if (rc == 0) { return rdbuf[0..rdbufused]; }
184 if (rc <= 0) throw new Exception("read error");
185 // check for line end
186 uint stpos = rdbufused;
187 rdbufused += rc;
188 foreach (immutable idx; stpos..stpos+rc) {
189 if (rdbuf[idx] == '\n') {
190 if (idx > 0 && rdbuf[idx-1] == '\r') {
191 if (debugdump) conwriteln("|<", rdbuf[0..idx-1], "|");
192 return rdbuf[0..idx-1];
193 } else {
194 if (debugdump) conwriteln("|<", rdbuf[0..idx], "|");
195 return rdbuf[0..idx];
202 static:
203 const(char)[] getStrToken (ref const(char)[] s) nothrow @trusted @nogc {
204 while (s.length && s[0] <= ' ') s = s[1..$];
205 if (s.length == 0) return null;
206 int pos = 0;
207 while (pos < s.length && s.ptr[pos] > ' ') ++pos;
208 auto res = s[0..pos];
209 s = s[pos..$];
210 while (s.length && s[0] <= ' ') s = s[1..$];
211 return res;
214 T getToken(T) (ref const(char)[] s) {
215 import std.conv : to;
216 auto tk = getStrToken(s);
217 return to!T(tk);