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
>= 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
;
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
148 if (data
.length
<= 1) break;
149 auto sd
= sk
.send(data
[0..$-1], cast(SocketFlags
)(MSG_NOSIGNAL|
0x8000/*MSG_MORE*/));
151 if (data
.length
== 0) break;
152 auto sd
= sk
.send(data
, cast(SocketFlags
)(MSG_NOSIGNAL|
0x8000/*MSG_MORE*/));
155 if (isErrorAgain(sd
)) {
157 if (againsLeft
!= 0) continue;
159 abort(doshutdown
:false);
160 import std
.conv
: to
;
161 throw new Exception("send error ("~sd
.to
!string
~")");
165 while (data
.length
) {
166 auto sd
= sk
.send(data
, cast(SocketFlags
)(MSG_NOSIGNAL
));
168 if (isErrorAgain(sd
)) {
170 if (againsLeft
!= 0) continue;
172 abort(doshutdown
:false);
173 import std
.conv
: to
;
174 throw new Exception("send error ("~sd
.to
!string
~")");
181 final void doSend(A...) (const(char)[] fmt, A args) {
183 if (sk is null || !sk.isAlive) throw new Exception("can't send to closed socket");
184 immutable ppos = fmt.indexOf('%');
187 if (fmt.length > 4 && fmt[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", fmt, "|");
189 doSendRaw!false(fmt); // not full yet
191 string s = fmt.format(args);
193 if (s.length > 4 && s[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", s, "|");
195 doSendRaw!false(s); // not full yet
201 final void doSend (const(char)[] str) {
202 if (sk
is null ||
!sk
.isAlive
) throw new Exception("can't send to closed socket");
204 if (str.length
> 4 && str[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", str, "|");
206 doSendRaw
!false(str); // not full yet
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");
213 if (cmd
.length
> 4 && cmd
[0..4] == "PASS") conwriteln("|>PASS *|"); else conwriteln("|>", cmd
, " ", arg
, "|");
215 doSendRaw
!false(cmd
); // not full yet
217 doSendRaw
!false(" "); // not full yet
218 doSendRaw
!false(arg
); // not full yet
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");
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) {
233 static if (__traits(isUnsigned
, T
)) {
234 auto len
= snprintf(buf
.ptr
, buf
.length
, "%u", cast(uint)arg
);
236 auto len
= snprintf(buf
.ptr
, buf
.length
, "%d", cast(int)arg
);
238 doSendRaw
!false(buf
[0..len
]); // not full yet
240 char[128] buf
= void;
241 static if (__traits(isUnsigned
, T
)) {
242 auto len
= snprintf(buf
.ptr
, buf
.length
, "%llu", cast(ulong)arg
);
244 auto len
= snprintf(buf
.ptr
, buf
.length
, "%lld", cast(long)arg
);
246 doSendRaw
!false(buf
[0..len
]); // not full yet
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);
260 while (pos
< rdbufused
&& rdbuf
[pos
] != '\n') ++pos
;
261 if (pos
< rdbufused
) {
262 assert(rdbuf
[pos
] == '\n');
266 //conwriteln("removed ", pos, " bytes out of ", rdbufused, " bytes");
267 assert(pos
<= rdbufused
);
268 if (pos
< rdbufused
) memmove(rdbuf
, rdbuf
+pos
, rdbufused
-pos
);
271 // check if we have another line buffered
272 //conwriteln("pos=", pos, "; rdbufused=", rdbufused);
274 while (pos
< rdbufused
&& rdbuf
[pos
] != '\n') ++pos
;
275 if (pos
< rdbufused
) {
276 //conwriteln("GOT: ", pos, " out of ", rdbufused);
279 return rdbuf
[0..pos
+1];
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
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]; }
296 abort(doshutdown
:false);
297 throw new Exception("read error (connection closed)");
300 if (isErrorAgain(rc
)) {
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
;
311 foreach (immutable idx
; stpos
..stpos
+rc
) {
312 if (rdbuf
[idx
] == '\n') {
314 if (idx
> 0 && rdbuf
[idx
-1] == '\r') {
315 conwriteln("|<", rdbuf
[0..idx
-1], "|");
317 conwriteln("|<", rdbuf
[0..idx
], "|");
322 return rdbuf
[0..idx
+1];
324 // remove terminators
325 if (idx
> 0 && rdbuf
[idx
-1] == '\r') return rdbuf
[0..idx
-1];
326 return rdbuf
[0..idx
];
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;
338 while (pos
< s
.length
&& s
.ptr
[pos
] > ' ') ++pos
;
339 auto res
= s
[0..pos
];
341 while (s
.length
&& s
[0] <= ' ') s
= s
[1..$];
345 T
getToken(T
) (ref const(char)[] s
) {
346 import std
.conv
: to
;
347 auto tk
= getStrToken(s
);