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*/;
23 public import iv
.dynstring
;
26 import iv
.strex
: indexOf
;
29 // ////////////////////////////////////////////////////////////////////////// //
30 public class SocketLine
{
37 bool mDebugDump
= false;
40 final @property char* rdbuf () nothrow @trusted @nogc { pragma(inline
, true); return (cast(char*)rdbufp
); }
41 final void freeRdBuf () nothrow @trusted @nogc {
43 import core
.stdc
.stdlib
: free
;
44 free(cast(void*)rdbufp
);
51 import core
.stdc
.stdlib
: realloc
;
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
;
61 bool isErrorAgain (const int res
) nothrow @trusted @nogc {
62 if (res
>= 0) return false;
64 return (res
== GNUTLS_E_AGAIN || res
== GNUTLS_E_INTERRUPTED
);
66 import core
.stdc
.errno
;
67 return (errno
== EAGAIN || errno
== EINTR
);
72 this (string server
, ushort plainport
, ushort tlsport
, bool debugdump
=false) {
73 mDebugDump
= debugdump
;
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));
89 if (plainport
== 0) throw new Exception("PLAIN port is not defined for '"~server
.idup
~"'");
90 conwriteln("connecting to [", server
, ":", plainport
, "] (plain)...");
97 scope(failure
) abort(doshutdown
:false);
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"), ").");
104 conwriteln("session info: ", (cast(SSLClientSocket
)sk
).getSessionInfo
);
108 ~this () nothrow @trusted @nogc { freeRdBuf(); }
110 void abort (bool doshutdown
) {
113 if (cast(SSLClientSocket
)sk
) {
116 if (doshutdown
) sk
.shutdown(SocketShutdown
.BOTH
);
119 } catch (Throwable e
) {
120 conwriteln("CLOSE ERROR: ", e
.msg
);
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
) {}
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
147 if (data
.length
<= 1) break;
148 auto sd
= sk
.send(data
[0..$-1], cast(SocketFlags
)(MSG_NOSIGNAL|
0x8000/*MSG_MORE*/));
150 if (data
.length
== 0) break;
151 auto sd
= sk
.send(data
, cast(SocketFlags
)(MSG_NOSIGNAL|
0x8000/*MSG_MORE*/));
154 if (isErrorAgain(sd
)) continue;
155 abort(doshutdown
:false);
156 import std
.conv
: to
;
157 throw new Exception("send error ("~sd
.to
!string
~")");
161 while (data
.length
) {
162 auto sd
= sk
.send(data
, cast(SocketFlags
)(MSG_NOSIGNAL
));
164 if (isErrorAgain(sd
)) continue;
165 abort(doshutdown
:false);
166 import std
.conv
: to
;
167 throw new Exception("send error ("~sd
.to
!string
~")");
174 final void doSend(A...) (const(char)[] fmt, A args) {
176 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
177 immutable ppos = fmt.indexOf('%');
180 if (fmt.length > 4 && fmt[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", fmt, "|");
182 doSendRaw!false(fmt); // not full yet
184 string s = fmt.format(args);
186 if (s.length > 4 && s[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", s, "|");
188 doSendRaw!false(s); // not full yet
194 final void doSend (const(char)[] str) {
195 if (sk
is null ||
!sk
.isAlive
) throw new Exception("can't send to closed socket");
197 if (str.length
> 4 && str[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", str, "|");
199 doSendRaw
!false(str); // not full yet
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");
206 if (cmd
.length
> 4 && cmd
[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", cmd
, " ", arg
, "|");
208 doSendRaw
!false(cmd
); // not full yet
210 doSendRaw
!false(" "); // not full yet
211 doSendRaw
!false(arg
); // not full yet
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");
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) {
226 static if (__traits(isUnsigned
, T
)) {
227 auto len
= snprintf(buf
.ptr
, buf
.length
, "%u", cast(uint)arg
);
229 auto len
= snprintf(buf
.ptr
, buf
.length
, "%d", cast(int)arg
);
231 doSendRaw
!false(buf
[0..len
]); // not full yet
233 char[128] buf
= void;
234 static if (__traits(isUnsigned
, T
)) {
235 auto len
= snprintf(buf
.ptr
, buf
.length
, "%llu", cast(ulong)arg
);
237 auto len
= snprintf(buf
.ptr
, buf
.length
, "%lld", cast(long)arg
);
239 doSendRaw
!false(buf
[0..len
]); // not full yet
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);
253 while (pos
< rdbufused
&& rdbuf
[pos
] != '\n') ++pos
;
254 if (pos
< rdbufused
) {
255 assert(rdbuf
[pos
] == '\n');
259 //conwriteln("removed ", pos, " bytes out of ", rdbufused, " bytes");
260 assert(pos
<= rdbufused
);
261 if (pos
< rdbufused
) memmove(rdbuf
, rdbuf
+pos
, rdbufused
-pos
);
264 // check if we have another line buffered
265 //conwriteln("pos=", pos, "; rdbufused=", rdbufused);
267 while (pos
< rdbufused
&& rdbuf
[pos
] != '\n') ++pos
;
268 if (pos
< rdbufused
) {
269 //conwriteln("GOT: ", pos, " out of ", rdbufused);
272 return rdbuf
[0..pos
+1];
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
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]; }
288 abort(doshutdown
:false);
289 throw new Exception("read error (connection closed)");
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
;
300 foreach (immutable idx
; stpos
..stpos
+rc
) {
301 if (rdbuf
[idx
] == '\n') {
303 if (idx
> 0 && rdbuf
[idx
-1] == '\r') {
304 conwriteln("|<", rdbuf
[0..idx
-1], "|");
306 conwriteln("|<", rdbuf
[0..idx
], "|");
311 return rdbuf
[0..idx
+1];
313 // remove terminators
314 if (idx
> 0 && rdbuf
[idx
-1] == '\r') return rdbuf
[0..idx
-1];
315 return rdbuf
[0..idx
];
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;
327 while (pos
< s
.length
&& s
.ptr
[pos
] > ' ') ++pos
;
328 auto res
= s
[0..pos
];
330 while (s
.length
&& s
[0] <= ' ') s
= s
[1..$];
334 T
getToken(T
) (ref const(char)[] s
) {
335 import std
.conv
: to
;
336 auto tk
= getStrToken(s
);