From 6cb8edfce94c9236dfd8a30380d7474a8ede6f87 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sat, 22 Dec 2012 23:03:28 +0000 Subject: recvLine now works with unbuffered ssl sockets. Added higher level recv functions. --- lib/pure/sockets.nim | 77 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 33 deletions(-) (limited to 'lib') diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 5f7ba6ac2..5ec831bcc 100755 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -62,6 +62,8 @@ type sslHandle: PSSL sslContext: PSSLContext sslNoHandshake: bool # True if needs handshake. + sslHasPeekChar: bool + sslPeekChar: char of false: nil TSocket* = ref TSocketImpl @@ -291,6 +293,7 @@ when defined(ssl): socket.sslContext = ctx socket.sslHandle = SSLNew(PSSLCTX(socket.sslContext)) socket.sslNoHandshake = false + socket.sslHasPeekChar = false if socket.sslHandle == nil: SSLError() @@ -849,11 +852,8 @@ proc checkBuffer(readfds: var seq[TSocket]): int = var res: seq[TSocket] = @[] result = 0 for s in readfds: - if s.isBuffered: - if s.bufLen <= 0 or s.currPos == s.bufLen: - res.add(s) - else: - inc(result) + if hasDataBuffered(s): + inc(result) else: res.add(s) readfds = res @@ -975,42 +975,46 @@ template retRead(flags, readBytes: int) = proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} = ## receives data from a socket + if size == 0: return if socket.isBuffered: if socket.bufLen == 0: retRead(0'i32, 0) - when true: - var read = 0 - while read < size: - if socket.currPos >= socket.bufLen: - retRead(0'i32, read) - - let chunk = min(socket.bufLen-socket.currPos, size-read) - var d = cast[cstring](data) - copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk) - read.inc(chunk) - socket.currPos.inc(chunk) - else: - var read = 0 - while read < size: - if socket.currPos >= socket.bufLen: - retRead(0'i32, read) - - var d = cast[cstring](data) - d[read] = socket.buffer[socket.currPos] - read.inc(1) - socket.currPos.inc(1) + var read = 0 + while read < size: + if socket.currPos >= socket.bufLen: + retRead(0'i32, read) + let chunk = min(socket.bufLen-socket.currPos, size-read) + var d = cast[cstring](data) + copyMem(addr(d[read]), addr(socket.buffer[socket.currPos]), chunk) + read.inc(chunk) + socket.currPos.inc(chunk) + result = read else: when defined(ssl): if socket.isSSL: - result = SSLRead(socket.sslHandle, data, size) + if socket.sslHasPeekChar: + copyMem(data, addr(socket.sslPeekChar), 1) + socket.sslHasPeekChar = false + if size-1 > 0: + var d = cast[cstring](data) + result = SSLRead(socket.sslHandle, addr(d[1]), size-1) + 1 + else: + result = 1 + else: + result = SSLRead(socket.sslHandle, data, size) else: result = recv(socket.fd, data, size.cint, 0'i32) else: result = recv(socket.fd, data, size.cint, 0'i32) +proc recv*(socket: TSocket, data: var string, size: int): int = + ## higher-level version of the above + data.setLen(size) + result = recv(socket, cstring(data), size) + proc waitFor(socket: TSocket, waited: var float, timeout: int): int {. tags: [FTime].} = ## returns the number of characters available to be read. In unbuffered @@ -1045,6 +1049,11 @@ proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {. result = read +proc recv*(socket: TSocket, data: var string, size: int, timeout: int): int = + # higher-level version of the above + data.setLen(size) + result = recv(socket, cstring(data), size, timeout) + proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} = if socket.isBuffered: result = 1 @@ -1057,8 +1066,12 @@ proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} = else: when defined(ssl): if socket.isSSL: - raise newException(ESSL, "Sorry, you cannot use recvLine on an unbuffered SSL socket.") - + if not socket.sslHasPeekChar: + result = SSLRead(socket.sslHandle, addr(socket.sslPeekChar), 1) + socket.sslHasPeekChar = true + + c = socket.sslPeekChar + return result = recv(socket.fd, addr(c), 1, MSG_PEEK) proc recvLine*(socket: TSocket, line: var TaintedString): bool {. @@ -1068,13 +1081,11 @@ proc recvLine*(socket: TSocket, line: var TaintedString): bool {. ## will be set to it. ## ## ``True`` is returned if data is available. ``False`` usually suggests an - ## error, EOS exceptions are not raised in favour of this. + ## error, EOS exceptions are not raised and ``False`` is simply returned + ## instead. ## ## If the socket is disconnected, ``line`` will be set to ``""`` and ``True`` ## will be returned. - ## - ## **Warning:** Using this function on a unbuffered ssl socket will result - ## in an error. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") -- cgit 1.4.1-2-gfad0 From 3cbac135465d63a320ff5fcdb5d3ba4ba89da4c8 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 23 Dec 2012 11:22:42 +0000 Subject: Rewrote the implementation of parsing chunked transfer coding in httpclient. Fixes #272. --- lib/pure/httpclient.nim | 92 +++++++++++++++++++------------------------------ lib/pure/sockets.nim | 22 ++++++++++-- 2 files changed, 55 insertions(+), 59 deletions(-) (limited to 'lib') diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 20b448123..5be4af8a4 100755 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -80,62 +80,43 @@ proc fileError(msg: string) = e.msg = msg raise e -proc charAt(d: var string, i: var int, s: TSocket): char {.inline.} = - result = d[i] - while result == '\0': - d = string(s.recv()) - i = 0 - result = d[i] - proc parseChunks(s: TSocket): string = - # get chunks: - var i = 0 result = "" - var d = s.recv().string + var ri = 0 while true: + var chunkSizeStr = "" var chunkSize = 0 - var digitFound = false - while true: - case d[i] - of '0'..'9': - digitFound = true - chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('0')) - of 'a'..'f': - digitFound = true - chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('a') + 10) - of 'A'..'F': - digitFound = true - chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('A') + 10) - of '\0': - d = string(s.recv()) - i = -1 - else: break - inc(i) - if not digitFound: httpError("Chunksize expected") + if s.recvLine(chunkSizeStr): + var i = 0 + if chunkSizeStr == "": + httpError("Server terminated connection prematurely") + while true: + case chunkSizeStr[i] + of '0'..'9': + chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0')) + of 'a'..'f': + chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10) + of 'A'..'F': + chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10) + of '\0': + break + of ';': + # http://tools.ietf.org/html/rfc2616#section-3.6.1 + # We don't care about chunk-extensions. + break + else: + httpError("Invalid chunk size: " & chunkSizeStr) + inc(i) if chunkSize <= 0: break - while charAt(d, i, s) notin {'\C', '\L', '\0'}: inc(i) - if charAt(d, i, s) == '\C': inc(i) - if charAt(d, i, s) == '\L': inc(i) - else: httpError("CR-LF after chunksize expected") - - var x = substr(d, i, i+chunkSize-1) - var size = x.len - result.add(x) - inc(i, size) - if size < chunkSize: - # read in the rest: - var missing = chunkSize - size - var L = result.len - setLen(result, L + missing) - while missing > 0: - var bytesRead = s.recv(addr(result[L]), missing) - inc(L, bytesRead) - dec(missing, bytesRead) - # next chunk: - d = string(s.recv()) - i = 0 - # skip trailing CR-LF: - while charAt(d, i, s) in {'\C', '\L'}: inc(i) + result.setLen(ri+chunkSize) + var bytesRead = 0 + while bytesRead != chunkSize: + let ret = recv(s, addr(result[ri]), chunkSize-bytesRead) + ri += ret + bytesRead += ret + s.skip(2) # Skip \c\L + # Trailer headers will only be sent if the request specifies that we want + # them: http://tools.ietf.org/html/rfc2616#section-3.6.1 proc parseBody(s: TSocket, headers: PStringTable): string = @@ -250,7 +231,6 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "", ## | Requests ``url`` with the specified ``httpMethod``. ## | Extra headers can be specified and must be seperated by ``\c\L`` var r = parseUrl(url) - var headers = substr($httpMethod, len("http")) headers.add(" /" & r.path & r.query) @@ -285,7 +265,7 @@ proc redirection(status: string): bool = return True proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = - ## | GET's the ``url`` and returns a ``TResponse`` object + ## | GETs the ``url`` and returns a ``TResponse`` object ## | This proc also handles redirection result = request(url) for i in 1..maxRedirects: @@ -295,7 +275,7 @@ proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLCon result = request(locationHeader, sslContext = sslContext) proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): string = - ## | GET's the body and returns it as a string. + ## | GETs the body and returns it as a string. ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` var r = get(url, sslContext = sslContext) if r.status[0] in {'4','5'}: @@ -305,7 +285,7 @@ proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): stri proc post*(url: string, extraHeaders = "", body = "", maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = - ## | POST's ``body`` to the ``url`` and returns a ``TResponse`` object. + ## | POSTs ``body`` to the ``url`` and returns a ``TResponse`` object. ## | This proc adds the necessary Content-Length header. ## | This proc also handles redirection. var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L" @@ -319,7 +299,7 @@ proc post*(url: string, extraHeaders = "", body = "", proc postContent*(url: string, extraHeaders = "", body = "", sslContext: PSSLContext = defaultSSLContext): string = - ## | POST's ``body`` to ``url`` and returns the response's body as a string + ## | POSTs ``body`` to ``url`` and returns the response's body as a string ## | Raises exceptions for the status codes ``4xx`` and ``5xx`` var r = post(url, extraHeaders, body) if r.status[0] in {'4','5'}: diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 5ec831bcc..fa7112f5b 100755 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -1164,11 +1164,13 @@ proc recvLineAsync*(socket: TSocket, elif c == '\L': return RecvFullLine add(line.string, c) -proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} = +proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO], deprecated.} = ## receives all the available data from the socket. ## Socket errors will result in an ``EOS`` error. ## If socket is not a connectionless socket and socket is not connected ## ``""`` will be returned. + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 4000 result = newStringOfCap(bufSize).TaintedString var pos = 0 @@ -1194,10 +1196,12 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} = if bytesRead != bufSize-1: break proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {. - tags: [FReadIO].} = + tags: [FReadIO], deprecated.} = ## overloaded variant to support a ``timeout`` parameter, the ``timeout`` ## parameter specifies the amount of miliseconds to wait for data on the ## socket. + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. if socket.bufLen == 0: var s = @[socket] if s.select(timeout) != 1: @@ -1298,13 +1302,25 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int, return False else: OSError() -proc skip*(socket: TSocket) {.tags: [FReadIO].} = +proc skip*(socket: TSocket) {.tags: [FReadIO], deprecated.} = ## skips all the data that is pending for the socket + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 1000 var buf = alloc(bufSize) while recv(socket, buf, bufSize) == bufSize: nil dealloc(buf) +proc skip*(socket: TSocket, size: int) = + ## Skips ``size`` amount of bytes. + ## + ## Returns the number of skipped bytes. + var dummy = alloc(size) + var bytesSkipped = 0 + while bytesSkipped != size: + bytesSkipped += recv(socket, dummy, size-bytesSkipped) + dealloc(dummy) + proc send*(socket: TSocket, data: pointer, size: int): int {. tags: [FWriteIO].} = ## sends data to a socket. -- cgit 1.4.1-2-gfad0 From da609fc4454a8e57cbdd9d0415bb7fed1f7d1ce1 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 23 Dec 2012 14:05:16 +0000 Subject: Fixed many deprecation warnings. asyncio.recvLine now throws an exception when an error occurs. Added sockets.SocketError. --- lib/pure/asyncio.nim | 8 +++- lib/pure/ftpclient.nim | 78 ++++++++++++++------------------------- lib/pure/redis.nim | 50 +++++++++++++++---------- lib/pure/sockets.nim | 93 ++++++++++++++++++++++++++++++++++++++++++++--- tests/compile/tircbot.nim | 15 ++++---- 5 files changed, 161 insertions(+), 83 deletions(-) (limited to 'lib') diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index 133d8c24e..be375a1a1 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -440,6 +440,9 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool = ## sockets properly. This function guarantees that ``line`` is a full line, ## if this function can only retrieve some data; it will save this data and ## add it to the result when a full line is retrieved. + ## + ## Unlike ``sockets.recvLine`` this function will raise an EOS or ESSL + ## exception if an error occurs. setLen(line.string, 0) var dataReceived = "".TaintedString var ret = s.socket.recvLineAsync(dataReceived) @@ -458,6 +461,7 @@ proc recvLine*(s: PAsyncSocket, line: var TaintedString): bool = of RecvDisconnected: result = true of RecvFail: + s.SocketError(async = true) result = false proc send*(sock: PAsyncSocket, data: string) = @@ -594,7 +598,9 @@ when isMainModule: proc testRead(s: PAsyncSocket, no: int) = echo("Reading! " & $no) - var data = s.getSocket.recv() + var data = "" + if not s.recvLine(data): + OSError() if data == "": echo("Closing connection. " & $no) s.close() diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim index ad3d7a3bb..e656d001e 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/pure/ftpclient.nim @@ -96,8 +96,9 @@ type EFTP* = object of ESynch proc FTPClient*(address: string, port = TPort(21), - user, pass = ""): TFTPClient = - ## Create a ``TFTPClient`` object. + user, pass = ""): PFTPClient = + ## Create a ``PFTPClient`` object. + new(result) result.user = user result.pass = pass result.address = address @@ -278,11 +279,20 @@ proc getLines(ftp: PFTPClient, async: bool = false): bool = ## It doesn't if `async` is true, because it doesn't check for 226 then. if ftp.dsockConnected: var r = TaintedString"" - if getDSock(ftp).recvAsync(r): - if r.string != "": - ftp.job.lines.add(r.string) - else: - ftp.dsockConnected = False + if ftp.isAsync: + if ftp.asyncDSock.recvLine(r): + if r.string == "": + ftp.dsockConnected = false + else: + ftp.job.lines.add(r.string & "\n") + else: + assert(not async) + if ftp.dsock.recvLine(r): + if r.string == "": + ftp.dsockConnected = false + else: + ftp.job.lines.add(r.string & "\n") + else: OSError() if not async: var readSocks: seq[TSocket] = @[ftp.getCSock()] @@ -389,7 +399,7 @@ proc list*(ftp: PFTPClient, dir: string = "", async = false): string = proc retrText*(ftp: PFTPClient, file: string, async = false): string = ## Retrieves ``file``. File must be ASCII text. ## If ``async`` is true, this function will return immediately and - ## it will be your job to call ``poll`` to progress this operation. + ## it will be your job to call asyncio's ``poll`` to progress this operation. ftp.createJob(getLines, JRetrText) ftp.pasv() assertReply ftp.send("RETR " & file.normalizePathSep), ["125", "150"] @@ -404,12 +414,14 @@ proc retrText*(ftp: PFTPClient, file: string, async = false): string = proc getFile(ftp: PFTPClient, async = false): bool = if ftp.dsockConnected: var r = "".TaintedString + var bytesRead = 0 var returned = false if async: if not ftp.isAsync: raise newException(EFTP, "FTPClient must be async.") - returned = ftp.AsyncDSock.recvAsync(r) + bytesRead = ftp.AsyncDSock.recvAsync(r, BufferSize) + returned = bytesRead != -1 else: - r = getDSock(ftp).recv() + bytesRead = getDSock(ftp).recv(r, BufferSize) returned = true let r2 = r.string if r2 != "": @@ -429,8 +441,9 @@ proc getFile(ftp: PFTPClient, async = false): bool = proc retrFile*(ftp: PFTPClient, file, dest: string, async = false) = ## Downloads ``file`` and saves it to ``dest``. Usage of this function ## asynchronously is recommended to view the progress of the download. - ## The ``EvRetr`` event is given by ``poll`` when the download is finished, - ## and the ``filename`` field will be equal to ``file``. + ## The ``EvRetr`` event is passed to the specified ``handleEvent`` function + ## when the download is finished, and the ``filename`` field will be equal + ## to ``file``. ftp.createJob(getFile, JRetr) ftp.job.file = open(dest, mode = fmWrite) ftp.pasv() @@ -492,8 +505,9 @@ proc store*(ftp: PFTPClient, file, dest: string, async = false) = ## Uploads ``file`` to ``dest`` on the remote FTP server. Usage of this ## function asynchronously is recommended to view the progress of ## the download. - ## The ``EvStore`` event is given by ``poll`` when the upload is finished, - ## and the ``filename`` field will be equal to ``file``. + ## The ``EvStore`` event is passed to the specified ``handleEvent`` function + ## when the upload is finished, and the ``filename`` field will be + ## equal to ``file``. ftp.createJob(doUpload, JStore) ftp.job.file = open(file) ftp.job.total = ftp.job.file.getFileSize() @@ -518,16 +532,6 @@ proc close*(ftp: PFTPClient) = ftp.csock.close() ftp.dsock.close() -discard """proc getSocket(h: PObject): tuple[info: TInfo, sock: TSocket] = - result = (SockIdle, InvalidSocket) - var ftp = PAsyncFTPClient(h) - if ftp.jobInProgress: - case ftp.job.typ - of JRetrText, JRetr, JStore: - if ftp.dsockStatus == SockConnecting or ftp.dsockStatus == SockConnected: - result = (ftp.dsockStatus, ftp.dsock) - else: result = (SockIdle, ftp.dsock)""" - proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = if ftp.jobInProgress: assertReply ftp.expectReply(), "226" # Make sure the transfer completed. @@ -550,32 +554,6 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = ftp.handleEvent(ftp, r) -discard """proc handleConnect(h: PObject) = - var ftp = PAsyncFTPClient(h) - ftp.dsockStatus = SockConnected - assert(ftp.jobInProgress) - if ftp.job.typ == JStore: - ftp.dele.mode = MWriteable - else: - ftp.dele.mode = MReadable""" - -discard """proc handleRead(h: PObject) = - var ftp = PAsyncFTPClient(h) - assert(ftp.jobInProgress) - assert(ftp.job.typ != JStore) - # This can never return true, because it shouldn't check for code - # 226 from csock. - assert(not ftp.job.prc(ftp[], true)) -""" - -discard """proc csockGetSocket(h: PObject): tuple[info: TInfo, sock: TSocket] = - # This only returns the csock if a job is in progress. Otherwise handle read - # would capture data which is not for it to capture. - result = (SockIdle, InvalidSocket) - var ftp = PAsyncFTPClient(h) - if ftp.jobInProgress: - result = (SockConnected, ftp.csock)""" - proc AsyncFTPClient*(address: string, port = TPort(21), user, pass = "", handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} = diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index 921f9a3d8..bb6ea6768 100755 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -49,26 +49,35 @@ proc raiseNoOK(status: string) = raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) proc parseStatus(r: TRedis): TRedisStatus = - var line = r.socket.recv.string - - if line[0] == '-': - raise newException(ERedis, strip(line)) - if line[0] != '+': - raiseInvalidReply('+', line[0]) + var line = "" + if r.socket.recvLine(line): + if line == "": + raise newException(ERedis, "Server closed connection prematurely") - return line.substr(1, line.len-3) # Strip '+' and \c\L. + if line[0] == '-': + raise newException(ERedis, strip(line)) + if line[0] != '+': + raiseInvalidReply('+', line[0]) + + return line.substr(1) # Strip '+' + else: + OSError() proc parseInteger(r: TRedis): TRedisInteger = - var line = r.socket.recv.string - - if line[0] == '-': - raise newException(ERedis, strip(line)) - if line[0] != ':': - raiseInvalidReply(':', line[0]) - - # Strip ':' and \c\L. - if parseBiggestInt(line, result, 1) == 0: - raise newException(EInvalidReply, "Unable to parse integer.") + var line = "" + if r.socket.recvLine(line): + if line == "": + raise newException(ERedis, "Server closed connection prematurely") + + if line[0] == '-': + raise newException(ERedis, strip(line)) + if line[0] != ':': + raiseInvalidReply(':', line[0]) + + # Strip ':' + if parseBiggestInt(line, result, 1) == 0: + raise newException(EInvalidReply, "Unable to parse integer.") + else: OSError() proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString @@ -838,8 +847,11 @@ proc save*(r: TRedis) = proc shutdown*(r: TRedis) = ## Synchronously save the dataset to disk and then shut down the server r.sendCommand("SHUTDOWN") - var s = r.socket.recv() - if s.string.len != 0: raise newException(ERedis, s.string) + var s = "".TaintedString + if r.socket.recvLine(s): + if s.string.len != 0: raise newException(ERedis, s.string) + else: + OSError() proc slaveof*(r: TRedis, host: string, port: string) = ## Make the server a slave of another instance, or promote it as master diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index fa7112f5b..053c1711a 100755 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -47,12 +47,15 @@ when defined(ssl): TSSLAcceptResult* = enum AcceptNoClient = 0, AcceptNoHandshake, AcceptSuccess +const + BufferSize*: int = 4000 ## size of a buffered socket's buffer + type TSocketImpl = object ## socket type fd: cint case isBuffered: bool # determines whether this socket is buffered. of true: - buffer: array[0..4000, char] + buffer: array[0..BufferSize, char] currPos: int # current index in buffer bufLen: int # current length of buffer of false: nil @@ -300,6 +303,48 @@ when defined(ssl): if SSLSetFd(socket.sslHandle, socket.fd) != 1: SSLError() +proc SocketError*(socket: TSocket, err: int = -1, async = false) = + ## Raises proper errors based on return values of ``recv`` functions. + ## + ## If ``async`` is ``True`` no error will be thrown in the case when the + ## error was caused by no data being available to be read. + ## + ## If ``err`` is not lower than 0 no exception will be raised. + when defined(ssl): + if socket.isSSL: + if err <= 0: + var ret = SSLGetError(socket.sslHandle, err.cint) + case ret + of SSL_ERROR_ZERO_RETURN: + SSLError("TLS/SSL connection failed to initiate, socket closed prematurely.") + of SSL_ERROR_WANT_CONNECT, SSL_ERROR_WANT_ACCEPT: + if async: + return + else: SSLError("Not enough data on socket.") + of SSL_ERROR_WANT_WRITE, SSL_ERROR_WANT_READ: + if async: + return + else: SSLError("Not enough data on socket.") + of SSL_ERROR_WANT_X509_LOOKUP: + SSLError("Function for x509 lookup has been called.") + of SSL_ERROR_SYSCALL, SSL_ERROR_SSL: + SSLError() + else: SSLError("Unknown Error") + + if err == -1 and not (when defined(ssl): socket.isSSL else: false): + if async: + when defined(windows): + # TODO: Test on Windows + var err = WSAGetLastError() + if err == WSAEWOULDBLOCK: + return + else: OSError() + else: + if errno == EAGAIN or errno == EWOULDBLOCK: + return + else: OSError() + else: OSError() + proc listen*(socket: TSocket, backlog = SOMAXCONN) {.tags: [FReadIO].} = ## Marks ``socket`` as accepting connections. ## ``Backlog`` specifies the maximum length of the @@ -1012,14 +1057,39 @@ proc recv*(socket: TSocket, data: pointer, size: int): int {.tags: [FReadIO].} = proc recv*(socket: TSocket, data: var string, size: int): int = ## higher-level version of the above + ## + ## When 0 is returned the socket's connection has been closed. + ## + ## This function will throw an EOS exception when an error occurs. A value + ## lower than 0 is never returned. + ## + ## **Note**: ``data`` must be initialised. + data.setLen(size) + result = recv(socket, cstring(data), size) + if result < 0: + data.setLen(0) + socket.SocketError(result) + data.setLen(result) + +proc recvAsync*(socket: TSocket, data: var string, size: int): int = + ## Async version of the above. + ## + ## When socket is non-blocking and no data is available on the socket, + ## ``-1`` will be returned and ``data`` will be ``""``. + ## + ## **Note**: ``data`` must be initialised. data.setLen(size) result = recv(socket, cstring(data), size) + if result < 0: + data.setLen(0) + socket.SocketError(async = true) + result = -1 + data.setLen(result) proc waitFor(socket: TSocket, waited: var float, timeout: int): int {. tags: [FTime].} = ## returns the number of characters available to be read. In unbuffered - ## sockets this is always 1, otherwise this may as big as the buffer, currently - ## 4000. + ## sockets this is always 1, otherwise this may as big as ``BufferSize``. result = 1 if socket.isBuffered and socket.bufLen != 0 and socket.bufLen != socket.currPos: result = socket.bufLen - socket.currPos @@ -1050,9 +1120,16 @@ proc recv*(socket: TSocket, data: pointer, size: int, timeout: int): int {. result = read proc recv*(socket: TSocket, data: var string, size: int, timeout: int): int = - # higher-level version of the above + ## higher-level version of the above. + ## + ## Similar to the non-timeout version this will throw an EOS exception + ## when an error occurs. data.setLen(size) result = recv(socket, cstring(data), size, timeout) + if result < 0: + data.setLen(0) + socket.SocketError() + data.setLen(result) proc peekChar(socket: TSocket, c: var char): int {.tags: [FReadIO].} = if socket.isBuffered: @@ -1080,7 +1157,7 @@ proc recvLine*(socket: TSocket, line: var TaintedString): bool {. ## added to ``line``, however if solely ``\r\L`` is received then ``line`` ## will be set to it. ## - ## ``True`` is returned if data is available. ``False`` usually suggests an + ## ``True`` is returned if data is available. ``False`` suggests an ## error, EOS exceptions are not raised and ``False`` is simply returned ## instead. ## @@ -1112,6 +1189,8 @@ proc recvLine*(socket: TSocket, line: var TaintedString, timeout: int): bool {. tags: [FReadIO, FTime].} = ## variant with a ``timeout`` parameter, the timeout parameter specifies ## how many miliseconds to wait for data. + ## + ## ``ETimeout`` will be raised if ``timeout`` is exceeded. template addNLIfEmpty(): stmt = if line.len == 0: line.add("\c\L") @@ -1210,12 +1289,14 @@ proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {. return socket.recv proc recvAsync*(socket: TSocket, s: var TaintedString): bool {. - tags: [FReadIO].} = + tags: [FReadIO], deprecated.} = ## receives all the data from a non-blocking socket. If socket is non-blocking ## and there are no messages available, `False` will be returned. ## Other socket errors will result in an ``EOS`` error. ## If socket is not a connectionless socket and socket is not connected ## ``s`` will be set to ``""``. + ## + ## **Deprecated since version 0.9.2**: This function is not safe for use. const bufSize = 1000 # ensure bufSize capacity: setLen(s.string, bufSize) diff --git a/tests/compile/tircbot.nim b/tests/compile/tircbot.nim index 10482c3f6..d16c99b69 100644 --- a/tests/compile/tircbot.nim +++ b/tests/compile/tircbot.nim @@ -272,7 +272,7 @@ proc handleWebMessage(state: PState, line: string) = message.add(limitCommitMsg(commit["message"].str)) # Send message to #nimrod. - state.ircClient[].privmsg(joinChans[0], message) + state.ircClient.privmsg(joinChans[0], message) elif json.existsKey("redisinfo"): assert json["redisinfo"].existsKey("port") #let redisPort = json["redisinfo"]["port"].num @@ -336,10 +336,11 @@ proc hubConnect(state: PState) = state.dispatcher.register(state.sock) -proc handleIrc(irc: var TAsyncIRC, event: TIRCEvent, state: PState) = +proc handleIrc(irc: PAsyncIRC, event: TIRCEvent, state: PState) = case event.typ + of EvConnected: nil of EvDisconnected: - while not state.ircClient[].isConnected: + while not state.ircClient.isConnected: try: state.ircClient.connect() except: @@ -355,12 +356,12 @@ proc handleIrc(irc: var TAsyncIRC, event: TIRCEvent, state: PState) = let msg = event.params[event.params.len-1] let words = msg.split(' ') template pm(msg: string): stmt = - state.ircClient[].privmsg(event.origin, msg) + state.ircClient.privmsg(event.origin, msg) case words[0] of "!ping": pm("pong") of "!lag": - if state.ircClient[].getLag != -1.0: - var lag = state.ircClient[].getLag + if state.ircClient.getLag != -1.0: + var lag = state.ircClient.getLag lag = lag * 1000.0 pm($int(lag) & "ms between me and the server.") else: @@ -433,7 +434,7 @@ proc open(port: TPort = TPort(5123)): PState = res.hubPort = port res.hubConnect() let hirc = - proc (a: var TAsyncIRC, ev: TIRCEvent) = + proc (a: PAsyncIRC, ev: TIRCEvent) = handleIrc(a, ev, res) # Connect to the irc server. res.ircClient = AsyncIrc(ircServer, nick = botNickname, user = botNickname, -- cgit 1.4.1-2-gfad0 From f83881dd4e42edc3f6c2b3dfdbe661defa096c60 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Sun, 23 Dec 2012 14:31:16 +0000 Subject: Fixed symbol conflict in the sockets module on Windows. --- lib/pure/sockets.nim | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index 053c1711a..86f06324a 100755 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -1504,8 +1504,7 @@ proc sendTo*(socket: TSocket, address: string, port: TPort, result = socket.sendTo(address, port, cstring(data), data.len) when defined(Windows): - const - SOCKET_ERROR = -1 + const IOCPARM_MASK = 127 IOC_IN = int(-2147483648) FIONBIO = int(IOC_IN or ((sizeof(int) and IOCPARM_MASK) shl 16) or @@ -1518,7 +1517,7 @@ when defined(Windows): proc setBlocking(s: TSocket, blocking: bool) = when defined(Windows): var mode = clong(ord(not blocking)) # 1 for non-blocking, 0 for blocking - if SOCKET_ERROR == ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)): + if ioctlsocket(TWinSocket(s.fd), FIONBIO, addr(mode)) == -1: OSError() else: # BSD sockets var x: int = fcntl(s.fd, F_GETFL, 0) -- cgit 1.4.1-2-gfad0 From d7d4caf45a835975b5f9c328ed8ca408d55cd4fc Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Tue, 25 Dec 2012 14:06:18 +0000 Subject: Added set_markup to gtk2 wrapper. --- lib/wrappers/gtk/gtk2.nim | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/wrappers/gtk/gtk2.nim b/lib/wrappers/gtk/gtk2.nim index a1bfa7fe1..7094bfd5d 100755 --- a/lib/wrappers/gtk/gtk2.nim +++ b/lib/wrappers/gtk/gtk2.nim @@ -16596,6 +16596,8 @@ proc message_dialog_new*(parent: PWindow, flags: TDialogFlags, thetype: TMessageType, buttons: TButtonsType, message_format: cstring): PMessageDialog{.varargs, cdecl, importc: "gtk_message_dialog_new", dynlib: lib.} +proc set_markup*(msgDialog: PMessageDialog, str: cstring) {.cdecl, + importc: "gtk_message_dialog_set_markup", dynlib: lib.} proc signal_new*(name: cstring, signal_flags: TSignalRunType, object_type: TType, function_offset: guint, marshaller: TSignalMarshaller, return_val: TType, n_args: guint): guint{. -- cgit 1.4.1-2-gfad0