backend/net: try to prevent hangup on conntection/read failure
[chiroptera.git] / chibackend / net / linesocket.d
blob5526ca695c24ad98b7b00ee399d1ec4da545e7fe
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, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 module chibackend.net.linesocket /*is aliced*/;
19 import std.socket;
21 import iv.alice;
22 import iv.cmdcon;
23 public import iv.dynstring;
24 import iv.gnutls;
25 import iv.sslsocket;
26 import iv.strex : indexOf;
29 // ////////////////////////////////////////////////////////////////////////// //
30 public class SocketLine {
31 protected:
32 Socket sk;
33 usize rdbufp;
34 uint rdbufsize;
35 uint rdbufused;
36 bool isgnutls;
37 bool mDebugDump = false;
39 private:
40 final @property char* rdbuf () nothrow @trusted @nogc { pragma(inline, true); return (cast(char*)rdbufp); }
41 final void freeRdBuf () nothrow @trusted @nogc {
42 if (rdbufp) {
43 import core.stdc.stdlib : free;
44 free(cast(void*)rdbufp);
45 rdbufp = 0;
49 private:
50 void growRdBuf () {
51 import core.stdc.stdlib : realloc;
52 enum delta = 8192;
53 auto nlen = rdbufsize+delta;
54 if (nlen >= 65536) { freeRdBuf(); throw new Exception("line too long"); }
55 auto nb = cast(char*)realloc(cast(void*)rdbufp, nlen);
56 if (nb is null) { freeRdBuf(); import core.exception : onOutOfMemoryErrorNoGC; onOutOfMemoryErrorNoGC(); }
57 rdbufp = cast(usize)nb;
58 rdbufsize = nlen;
61 bool isErrorAgain (const int res) nothrow @trusted @nogc {
62 if (res >= 0) return false;
63 if (isgnutls) {
64 return (res == GNUTLS_E_AGAIN || res == GNUTLS_E_INTERRUPTED);
65 } else {
66 import core.stdc.errno;
67 return (errno == EAGAIN || errno == EINTR);
71 public:
72 this (string server, ushort plainport, ushort tlsport, bool debugdump=false) {
73 mDebugDump = debugdump;
74 string caddr;
75 ushort cport;
76 bool isplain;
77 if (server.length >= 4 && (server[0..4] == "ssl:" || server[0..4] == "tls:")) {
78 if (tlsport == 0) throw new Exception("TLS port is not defined for '"~server[4..$].idup~"'");
79 conwriteln("connecting to [", server[4..$], ":", tlsport, "] (tls)...");
80 auto xsk = new SSLClientSocket(AddressFamily.INET, server[4..$]);
81 xsk.manualHandshake = false;
82 //xsk.connect(new InternetAddress(server[4..$], tlsport));
83 caddr = server[4..$];
84 cport = tlsport;
85 sk = xsk;
86 isgnutls = true;
87 isplain = false;
88 } else {
89 if (plainport == 0) throw new Exception("PLAIN port is not defined for '"~server.idup~"'");
90 conwriteln("connecting to [", server, ":", plainport, "] (plain)...");
91 sk = new TcpSocket();
92 caddr = server;
93 cport = plainport;
94 isgnutls = false;
95 isplain = true;
97 scope(failure) abort(doshutdown:false);
98 import core.time;
99 sk.setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, 5.seconds);
100 sk.setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, 5.seconds);
101 sk.connect(new InternetAddress(caddr, cport));
102 conwriteln("connected to [", caddr, ":", cport, "] (", (isplain ? "plain" : "tls"), ").");
103 if (isgnutls) {
104 conwriteln("session info: ", (cast(SSLClientSocket)sk).getSessionInfo);
108 ~this () nothrow @trusted @nogc { freeRdBuf(); }
110 void abort (bool doshutdown) {
111 if (sk !is null) {
112 try {
113 if (cast(SSLClientSocket)sk) {
114 sk.close();
115 } else {
116 if (doshutdown) sk.shutdown(SocketShutdown.BOTH);
117 sk.close();
119 } catch (Throwable e) {
120 conwriteln("CLOSE ERROR: ", e.msg);
122 delete sk;
123 sk = null;
125 freeRdBuf();
128 void close () {
129 if (sk is null) return;
130 bool doshutdown = true;
131 try { quit(); } catch (Throwable e) { doshutdown = false; }
132 try { abort(doshutdown); } catch (Throwable e) {}
135 void quit () {}
137 final @property bool active () { return (sk !is null && sk.isAlive); }
139 final void doSendRaw(bool full=true) (const(void)[] vdata) {
140 import core.sys.posix.sys.socket : MSG_NOSIGNAL;
141 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
142 if (vdata.length == 0) return;
143 //auto dataanchor = data; // 'cause we may get data which requires pointer to head
144 const(char)[] data = cast(const(char)[])vdata; // arg is not modified, and properly anchored
145 int againsLeft = 6;
146 for (;;) {
147 static if (full) {
148 if (data.length <= 1) break;
149 auto sd = sk.send(data[0..$-1], cast(SocketFlags)(MSG_NOSIGNAL|0x8000/*MSG_MORE*/));
150 } else {
151 if (data.length == 0) break;
152 auto sd = sk.send(data, cast(SocketFlags)(MSG_NOSIGNAL|0x8000/*MSG_MORE*/));
154 if (sd <= 0) {
155 if (isErrorAgain(sd)) {
156 againsLeft -= 1;
157 if (againsLeft != 0) continue;
159 abort(doshutdown:false);
160 import std.conv : to;
161 throw new Exception("send error ("~sd.to!string~")");
163 data = data[sd..$];
165 while (data.length) {
166 auto sd = sk.send(data, cast(SocketFlags)(MSG_NOSIGNAL));
167 if (sd <= 0) {
168 if (isErrorAgain(sd)) {
169 againsLeft -= 1;
170 if (againsLeft != 0) continue;
172 abort(doshutdown:false);
173 import std.conv : to;
174 throw new Exception("send error ("~sd.to!string~")");
176 data = data[sd..$];
181 final void doSend(A...) (const(char)[] fmt, A args) {
182 import std.format;
183 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
184 immutable ppos = fmt.indexOf('%');
185 if (ppos < 0) {
186 if (mDebugDump) {
187 if (fmt.length > 4 && fmt[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", fmt, "|");
189 doSendRaw!false(fmt); // not full yet
190 } else {
191 string s = fmt.format(args);
192 if (mDebugDump) {
193 if (s.length > 4 && s[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", s, "|");
195 doSendRaw!false(s); // not full yet
197 doSendRaw("\r\n");
201 final void doSend (const(char)[] str) {
202 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
203 if (mDebugDump) {
204 if (str.length > 4 && str[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", str, "|");
206 doSendRaw!false(str); // not full yet
207 doSendRaw("\r\n");
210 final void doSendCmdArg (const(char)[] cmd, const(char)[] arg=null) {
211 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
212 if (mDebugDump) {
213 if (cmd.length > 4 && cmd[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", cmd, " ", arg, "|");
215 doSendRaw!false(cmd); // not full yet
216 if (arg.length) {
217 doSendRaw!false(" "); // not full yet
218 doSendRaw!false(arg); // not full yet
220 doSendRaw("\r\n");
223 final void doSendCmdArg(T) (const(char)[] cmd, const T arg) if (__traits(isIntegral, T)) {
224 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
225 if (mDebugDump) {
226 if (cmd.length > 4 && cmd[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", cmd, " ", arg, "|");
228 doSendRaw!false(cmd); // not full yet
229 doSendRaw!false(" "); // not full yet
230 import core.stdc.stdio : snprintf;
231 static if (T.sizeof <= 4) {
232 char[32] buf = void;
233 static if (__traits(isUnsigned, T)) {
234 auto len = snprintf(buf.ptr, buf.length, "%u", cast(uint)arg);
235 } else {
236 auto len = snprintf(buf.ptr, buf.length, "%d", cast(int)arg);
238 doSendRaw!false(buf[0..len]); // not full yet
239 } else {
240 char[128] buf = void;
241 static if (__traits(isUnsigned, T)) {
242 auto len = snprintf(buf.ptr, buf.length, "%llu", cast(ulong)arg);
243 } else {
244 auto len = snprintf(buf.ptr, buf.length, "%lld", cast(long)arg);
246 doSendRaw!false(buf[0..len]); // not full yet
248 doSendRaw("\r\n");
251 //WARNING! result is valid only until the next `readLine()` call
252 // if `exact` is true, returns line with terminators
253 final const(char)[] readLine(bool exact=false) () {
254 import core.stdc.string : memmove;
255 if (sk is null) return null;
256 // throw away old line
257 // we cannot throw it after receiving, beause we are returning the slice with it, not a copy
258 //conwriteln("rdbufused=", rdbufused);
259 uint pos = 0;
260 while (pos < rdbufused && rdbuf[pos] != '\n') ++pos;
261 if (pos < rdbufused) {
262 assert(rdbuf[pos] == '\n');
263 ++pos;
265 if (pos > 0) {
266 //conwriteln("removed ", pos, " bytes out of ", rdbufused, " bytes");
267 assert(pos <= rdbufused);
268 if (pos < rdbufused) memmove(rdbuf, rdbuf+pos, rdbufused-pos);
269 rdbufused -= pos;
271 // check if we have another line buffered
272 //conwriteln("pos=", pos, "; rdbufused=", rdbufused);
273 pos = 0;
274 while (pos < rdbufused && rdbuf[pos] != '\n') ++pos;
275 if (pos < rdbufused) {
276 //conwriteln("GOT: ", pos, " out of ", rdbufused);
277 static if (exact) {
278 // keep terminators
279 return rdbuf[0..pos+1];
280 } else {
281 // remove terminators
282 if (pos > 0 && rdbuf[pos-1] == '\r') --pos;
283 if (mDebugDump) conwriteln("|<", rdbuf[0..pos], "|");
284 return rdbuf[0..pos];
287 // no buffered line, get more data
288 int againsLeft = 6;
289 for (;;) {
290 assert(rdbufused <= rdbufsize);
291 if (rdbufused >= rdbufsize) growRdBuf();
292 auto rc = sk.receive(rdbuf[rdbufused..rdbufsize], SocketFlags.NONE);
293 //conwriteln(rc, " bytes read");
294 //if (rc == 0) { return rdbuf[0..rdbufused]; }
295 if (rc == 0) {
296 abort(doshutdown:false);
297 throw new Exception("read error (connection closed)");
299 if (rc < 0) {
300 if (isErrorAgain(rc)) {
301 againsLeft -= 1;
302 if (againsLeft != 0) continue;
304 import std.conv : to;
305 abort(doshutdown:false);
306 throw new Exception("read error ("~rc.to!string~")");
308 // check for line end
309 uint stpos = rdbufused;
310 rdbufused += rc;
311 foreach (immutable idx; stpos..stpos+rc) {
312 if (rdbuf[idx] == '\n') {
313 if (mDebugDump) {
314 if (idx > 0 && rdbuf[idx-1] == '\r') {
315 conwriteln("|<", rdbuf[0..idx-1], "|");
316 } else {
317 conwriteln("|<", rdbuf[0..idx], "|");
320 static if (exact) {
321 // keep terminators
322 return rdbuf[0..idx+1];
323 } else {
324 // remove terminators
325 if (idx > 0 && rdbuf[idx-1] == '\r') return rdbuf[0..idx-1];
326 return rdbuf[0..idx];
333 static:
334 const(char)[] getStrToken (ref const(char)[] s) nothrow @trusted @nogc {
335 while (s.length && s[0] <= ' ') s = s[1..$];
336 if (s.length == 0) return null;
337 int pos = 0;
338 while (pos < s.length && s.ptr[pos] > ' ') ++pos;
339 auto res = s[0..pos];
340 s = s[pos..$];
341 while (s.length && s[0] <= ' ') s = s[1..$];
342 return res;
345 T getToken(T) (ref const(char)[] s) {
346 import std.conv : to;
347 auto tk = getStrToken(s);
348 return to!T(tk);