more memory cosmetix
[chiroptera.git] / chibackend / net / linesocket.d
blob8e39396983862cfef37940dd48ada434d9d39354
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 >= 2*1024*1024) { 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 for (;;) {
146 static if (full) {
147 if (data.length <= 1) break;
148 auto sd = sk.send(data[0..$-1], cast(SocketFlags)(MSG_NOSIGNAL|0x8000/*MSG_MORE*/));
149 } else {
150 if (data.length == 0) break;
151 auto sd = sk.send(data, cast(SocketFlags)(MSG_NOSIGNAL|0x8000/*MSG_MORE*/));
153 if (sd <= 0) {
154 if (isErrorAgain(sd)) continue;
155 abort(doshutdown:false);
156 import std.conv : to;
157 throw new Exception("send error ("~sd.to!string~")");
159 data = data[sd..$];
161 while (data.length) {
162 auto sd = sk.send(data, cast(SocketFlags)(MSG_NOSIGNAL));
163 if (sd <= 0) {
164 if (isErrorAgain(sd)) continue;
165 abort(doshutdown:false);
166 import std.conv : to;
167 throw new Exception("send error ("~sd.to!string~")");
169 data = data[sd..$];
174 final void doSend(A...) (const(char)[] fmt, A args) {
175 import std.format;
176 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
177 immutable ppos = fmt.indexOf('%');
178 if (ppos < 0) {
179 if (mDebugDump) {
180 if (fmt.length > 4 && fmt[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", fmt, "|");
182 doSendRaw!false(fmt); // not full yet
183 } else {
184 string s = fmt.format(args);
185 if (mDebugDump) {
186 if (s.length > 4 && s[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", s, "|");
188 doSendRaw!false(s); // not full yet
190 doSendRaw("\r\n");
194 final void doSend (const(char)[] str) {
195 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
196 if (mDebugDump) {
197 if (str.length > 4 && str[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", str, "|");
199 doSendRaw!false(str); // not full yet
200 doSendRaw("\r\n");
203 final void doSendCmdArg (const(char)[] cmd, const(char)[] arg=null) {
204 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
205 if (mDebugDump) {
206 if (cmd.length > 4 && cmd[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", cmd, " ", arg, "|");
208 doSendRaw!false(cmd); // not full yet
209 if (arg.length) {
210 doSendRaw!false(" "); // not full yet
211 doSendRaw!false(arg); // not full yet
213 doSendRaw("\r\n");
216 final void doSendCmdArg(T) (const(char)[] cmd, const T arg) if (__traits(isIntegral, T)) {
217 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
218 if (mDebugDump) {
219 if (cmd.length > 4 && cmd[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", cmd, " ", arg, "|");
221 doSendRaw!false(cmd); // not full yet
222 doSendRaw!false(" "); // not full yet
223 import core.stdc.stdio : snprintf;
224 static if (T.sizeof <= 4) {
225 char[32] buf = void;
226 static if (__traits(isUnsigned, T)) {
227 auto len = snprintf(buf.ptr, buf.length, "%u", cast(uint)arg);
228 } else {
229 auto len = snprintf(buf.ptr, buf.length, "%d", cast(int)arg);
231 doSendRaw!false(buf[0..len]); // not full yet
232 } else {
233 char[128] buf = void;
234 static if (__traits(isUnsigned, T)) {
235 auto len = snprintf(buf.ptr, buf.length, "%llu", cast(ulong)arg);
236 } else {
237 auto len = snprintf(buf.ptr, buf.length, "%lld", cast(long)arg);
239 doSendRaw!false(buf[0..len]); // not full yet
241 doSendRaw("\r\n");
244 //WARNING! result is valid only until the next `readLine()` call
245 // if `exact` is true, returns line with terminators
246 final const(char)[] readLine(bool exact=false) () {
247 import core.stdc.string : memmove;
248 if (sk is null) return null;
249 // throw away old line
250 // we cannot throw it after receiving, beause we are returning the slice with it, not a copy
251 //conwriteln("rdbufused=", rdbufused);
252 uint pos = 0;
253 while (pos < rdbufused && rdbuf[pos] != '\n') ++pos;
254 if (pos < rdbufused) {
255 assert(rdbuf[pos] == '\n');
256 ++pos;
258 if (pos > 0) {
259 //conwriteln("removed ", pos, " bytes out of ", rdbufused, " bytes");
260 assert(pos <= rdbufused);
261 if (pos < rdbufused) memmove(rdbuf, rdbuf+pos, rdbufused-pos);
262 rdbufused -= pos;
264 // check if we have another line buffered
265 //conwriteln("pos=", pos, "; rdbufused=", rdbufused);
266 pos = 0;
267 while (pos < rdbufused && rdbuf[pos] != '\n') ++pos;
268 if (pos < rdbufused) {
269 //conwriteln("GOT: ", pos, " out of ", rdbufused);
270 static if (exact) {
271 // keep terminators
272 return rdbuf[0..pos+1];
273 } else {
274 // remove terminators
275 if (pos > 0 && rdbuf[pos-1] == '\r') --pos;
276 if (mDebugDump) conwriteln("|<", rdbuf[0..pos], "|");
277 return rdbuf[0..pos];
280 // no buffered line, get more data
281 for (;;) {
282 assert(rdbufused <= rdbufsize);
283 if (rdbufused >= rdbufsize) growRdBuf();
284 auto rc = sk.receive(rdbuf[rdbufused..rdbufsize], SocketFlags.NONE);
285 //conwriteln(rc, " bytes read");
286 //if (rc == 0) { return rdbuf[0..rdbufused]; }
287 if (rc == 0) {
288 abort(doshutdown:false);
289 throw new Exception("read error (connection closed)");
291 if (rc <= 0) {
292 if (isErrorAgain(rc)) continue;
293 import std.conv : to;
294 abort(doshutdown:false);
295 throw new Exception("read error ("~rc.to!string~")");
297 // check for line end
298 uint stpos = rdbufused;
299 rdbufused += rc;
300 foreach (immutable idx; stpos..stpos+rc) {
301 if (rdbuf[idx] == '\n') {
302 if (mDebugDump) {
303 if (idx > 0 && rdbuf[idx-1] == '\r') {
304 conwriteln("|<", rdbuf[0..idx-1], "|");
305 } else {
306 conwriteln("|<", rdbuf[0..idx], "|");
309 static if (exact) {
310 // keep terminators
311 return rdbuf[0..idx+1];
312 } else {
313 // remove terminators
314 if (idx > 0 && rdbuf[idx-1] == '\r') return rdbuf[0..idx-1];
315 return rdbuf[0..idx];
322 static:
323 const(char)[] getStrToken (ref const(char)[] s) nothrow @trusted @nogc {
324 while (s.length && s[0] <= ' ') s = s[1..$];
325 if (s.length == 0) return null;
326 int pos = 0;
327 while (pos < s.length && s.ptr[pos] > ' ') ++pos;
328 auto res = s[0..pos];
329 s = s[pos..$];
330 while (s.length && s[0] <= ' ') s = s[1..$];
331 return res;
334 T getToken(T) (ref const(char)[] s) {
335 import std.conv : to;
336 auto tk = getStrToken(s);
337 return to!T(tk);