Rewrote the implementation of parsing chunked transfer coding in
httpclient. Fixes #272.
This commit is contained in:
parent
6cb8edfce9
commit
3cbac13546
2 changed files with 55 additions and 59 deletions
|
|
@ -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):
|
||||||
|
var i = 0
|
||||||
|
if chunkSizeStr == "":
|
||||||
|
httpError("Server terminated connection prematurely")
|
||||||
while true:
|
while true:
|
||||||
case d[i]
|
case chunkSizeStr[i]
|
||||||
of '0'..'9':
|
of '0'..'9':
|
||||||
digitFound = true
|
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('0'))
|
||||||
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('0'))
|
|
||||||
of 'a'..'f':
|
of 'a'..'f':
|
||||||
digitFound = true
|
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('a') + 10)
|
||||||
chunkSize = chunkSize shl 4 or (ord(d[i]) - ord('a') + 10)
|
|
||||||
of 'A'..'F':
|
of 'A'..'F':
|
||||||
digitFound = true
|
chunkSize = chunkSize shl 4 or (ord(chunkSizeStr[i]) - ord('A') + 10)
|
||||||
chunkSize = chunkSize shl 4 or (ord(d[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
|
||||||
|
# We don't care about chunk-extensions.
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
httpError("Invalid chunk size: " & chunkSizeStr)
|
||||||
inc(i)
|
inc(i)
|
||||||
if not digitFound: httpError("Chunksize expected")
|
|
||||||
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'}:
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue