Rewrote the implementation of parsing chunked transfer coding in

httpclient. Fixes #272.
This commit is contained in:
Dominik Picheta 2012-12-23 11:22:42 +00:00
commit 3cbac13546
2 changed files with 55 additions and 59 deletions

View file

@ -80,62 +80,43 @@ proc fileError(msg: string) =
e.msg = msg e.msg = msg
raise e 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 = proc parseChunks(s: TSocket): string =
# get chunks:
var i = 0
result = "" result = ""
var d = s.recv().string var ri = 0
while true: while true:
var chunkSizeStr = ""
var chunkSize = 0 var chunkSize = 0
var digitFound = false if s.recvLine(chunkSizeStr):
while true: var i = 0
case d[i] if chunkSizeStr == "":
of '0'..'9': httpError("Server terminated connection prematurely")
digitFound = true while true:
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('0')) case chunkSizeStr[i]
of 'a'..'f': of '0'..'9':
digitFound = true chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0'))
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('a') + 10) of 'a'..'f':
of 'A'..'F': chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10)
digitFound = true of 'A'..'F':
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('A') + 10) chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10)
of '\0': of '\0':
d = string(s.recv()) break
i = -1 of ';':
else: break # http://tools.ietf.org/html/rfc2616#section-3.6.1
inc(i) # We don't care about chunk-extensions.
if not digitFound: httpError("Chunksize expected") break
else:
httpError("Invalid chunk size: " & chunkSizeStr)
inc(i)
if chunkSize <= 0: break if chunkSize <= 0: break
while charAt(d, i, s) notin {'\C', '\L', '\0'}: inc(i) result.setLen(ri+chunkSize)
if charAt(d, i, s) == '\C': inc(i) var bytesRead = 0
if charAt(d, i, s) == '\L': inc(i) while bytesRead != chunkSize:
else: httpError("CR-LF after chunksize expected") let ret = recv(s, addr(result[ri]), chunkSize-bytesRead)
ri += ret
var x = substr(d, i, i+chunkSize-1) bytesRead += ret
var size = x.len s.skip(2) # Skip \c\L
result.add(x) # Trailer headers will only be sent if the request specifies that we want
inc(i, size) # them: http://tools.ietf.org/html/rfc2616#section-3.6.1
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)
proc parseBody(s: TSocket, proc parseBody(s: TSocket,
headers: PStringTable): string = headers: PStringTable): string =
@ -250,7 +231,6 @@ proc request*(url: string, httpMethod = httpGET, extraHeaders = "",
## | Requests ``url`` with the specified ``httpMethod``. ## | Requests ``url`` with the specified ``httpMethod``.
## | Extra headers can be specified and must be seperated by ``\c\L`` ## | Extra headers can be specified and must be seperated by ``\c\L``
var r = parseUrl(url) var r = parseUrl(url)
var headers = substr($httpMethod, len("http")) var headers = substr($httpMethod, len("http"))
headers.add(" /" & r.path & r.query) headers.add(" /" & r.path & r.query)
@ -285,7 +265,7 @@ proc redirection(status: string): bool =
return True return True
proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = 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 ## | This proc also handles redirection
result = request(url) result = request(url)
for i in 1..maxRedirects: for i in 1..maxRedirects:
@ -295,7 +275,7 @@ proc get*(url: string, maxRedirects = 5, sslContext: PSSLContext = defaultSSLCon
result = request(locationHeader, sslContext = sslContext) result = request(locationHeader, sslContext = sslContext)
proc getContent*(url: string, sslContext: PSSLContext = defaultSSLContext): string = 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`` ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
var r = get(url, sslContext = sslContext) var r = get(url, sslContext = sslContext)
if r.status[0] in {'4','5'}: 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 = "", proc post*(url: string, extraHeaders = "", body = "",
maxRedirects = 5, sslContext: PSSLContext = defaultSSLContext): TResponse = 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 adds the necessary Content-Length header.
## | This proc also handles redirection. ## | This proc also handles redirection.
var xh = extraHeaders & "Content-Length: " & $len(body) & "\c\L" 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 = "", proc postContent*(url: string, extraHeaders = "", body = "",
sslContext: PSSLContext = defaultSSLContext): string = 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`` ## | Raises exceptions for the status codes ``4xx`` and ``5xx``
var r = post(url, extraHeaders, body) var r = post(url, extraHeaders, body)
if r.status[0] in {'4','5'}: if r.status[0] in {'4','5'}:

View file

@ -1164,11 +1164,13 @@ proc recvLineAsync*(socket: TSocket,
elif c == '\L': return RecvFullLine elif c == '\L': return RecvFullLine
add(line.string, c) 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. ## receives all the available data from the socket.
## Socket errors will result in an ``EOS`` error. ## Socket errors will result in an ``EOS`` error.
## If socket is not a connectionless socket and socket is not connected ## If socket is not a connectionless socket and socket is not connected
## ``""`` will be returned. ## ``""`` will be returned.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
const bufSize = 4000 const bufSize = 4000
result = newStringOfCap(bufSize).TaintedString result = newStringOfCap(bufSize).TaintedString
var pos = 0 var pos = 0
@ -1194,10 +1196,12 @@ proc recv*(socket: TSocket): TaintedString {.tags: [FReadIO].} =
if bytesRead != bufSize-1: break if bytesRead != bufSize-1: break
proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {. proc recvTimeout*(socket: TSocket, timeout: int): TaintedString {.
tags: [FReadIO].} = tags: [FReadIO], deprecated.} =
## overloaded variant to support a ``timeout`` parameter, the ``timeout`` ## overloaded variant to support a ``timeout`` parameter, the ``timeout``
## parameter specifies the amount of miliseconds to wait for data on the ## parameter specifies the amount of miliseconds to wait for data on the
## socket. ## socket.
##
## **Deprecated since version 0.9.2**: This function is not safe for use.
if socket.bufLen == 0: if socket.bufLen == 0:
var s = @[socket] var s = @[socket]
if s.select(timeout) != 1: if s.select(timeout) != 1:
@ -1298,13 +1302,25 @@ proc recvFromAsync*(socket: TSocket, data: var String, length: int,
return False return False
else: OSError() 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 ## 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 const bufSize = 1000
var buf = alloc(bufSize) var buf = alloc(bufSize)
while recv(socket, buf, bufSize) == bufSize: nil while recv(socket, buf, bufSize) == bufSize: nil
dealloc(buf) 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 {. proc send*(socket: TSocket, data: pointer, size: int): int {.
tags: [FWriteIO].} = tags: [FWriteIO].} =
## sends data to a socket. ## sends data to a socket.