From 56387c61ddaf37962a9174c47716716b497b385d Mon Sep 17 00:00:00 2001 From: Ryan Marcus Date: Wed, 27 Jul 2016 17:02:53 -0400 Subject: [PATCH 1/3] added code to send CONNECT request for https requests through proxies, fixes #4520 --- lib/pure/httpclient.nim | 68 +++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 16 deletions(-) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index bc964861d2..5fbb234300 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -387,6 +387,8 @@ proc format(p: MultipartData): tuple[header, body: string] = result.body.add("--" & bound & "\c\L" & s) result.body.add("--" & bound & "--\c\L") + + proc request*(url: string, httpMethod: string, extraHeaders = "", body = "", sslContext = defaultSSLContext, timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil): Response = @@ -399,6 +401,55 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", var hostUrl = if proxy == nil: r else: parseUri(url) var headers = substr(httpMethod, len("http")) # TODO: Use generateHeaders further down once it supports proxies. + + var s = newSocket() + if s == nil: raiseOSError(osLastError()) + var port = net.Port(80) + if r.scheme == "https": + when defined(ssl): + sslContext.wrapSocket(s) + port = net.Port(443) + else: + raise newException(HttpRequestError, + "SSL support is not available. Cannot connect over SSL.") + if r.port != "": + port = net.Port(r.port.parseInt) + + + # get the socket ready. If we are connecting through a proxy to SSL, + # send the appropiate CONNECT header. If not, simply connect to the proper + # host (which may still be the proxy, for normal HTTP) + if proxy != nil and hostUrl.scheme == "https": + var connectHeaders = "CONNECT " + let targetPort = if hostUrl.port == "": 443 else: hostUrl.port.parseInt + connectHeaders.add(hostUrl.hostname) + connectHeaders.add(":" & $targetPort) + connectHeaders.add(" HTTP/1.1\c\L") + connectHeaders.add("Host: " & hostUrl.hostname & ":" & $targetPort & "\c\L") + if proxy.auth != "": + let auth = base64.encode(proxy.auth, newline = "") + connectHeaders.add("Proxy-Authorization: basic " & auth & "\c\L") + connectHeaders.add("\c\L") + if timeout == -1: + s.connect(r.hostname, port) + else: + s.connect(r.hostname, port, timeout) + + s.send(connectHeaders) + let connectResult = parseResponse(s, false, timeout) + if not connectResult.status.startsWith("200"): + raise newException(HttpRequestError, + "The proxy server rejected a CONNECT request, " & + "so a secure connection could not be established.") + sslContext.wrapConnectedSocket(s, handshakeAsClient) + else: + if timeout == -1: + s.connect(r.hostname, port) + else: + s.connect(r.hostname, port, timeout) + + + # now that the socket is ready, prepare the headers if proxy == nil: headers.add ' ' if r.path[0] != '/': headers.add '/' @@ -422,23 +473,8 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", add(headers, "Proxy-Authorization: basic " & auth & "\c\L") add(headers, extraHeaders) add(headers, "\c\L") - var s = newSocket() - if s == nil: raiseOSError(osLastError()) - var port = net.Port(80) - if r.scheme == "https": - when defined(ssl): - sslContext.wrapSocket(s) - port = net.Port(443) - else: - raise newException(HttpRequestError, - "SSL support is not available. Cannot connect over SSL.") - if r.port != "": - port = net.Port(r.port.parseInt) - if timeout == -1: - s.connect(r.hostname, port) - else: - s.connect(r.hostname, port, timeout) + # headers are ready. send them, await the result, and close the socket. s.send(headers) if body != "": s.send(body) From 0a03b18ae97d3a484c1daf362e2b1f8208d06a8d Mon Sep 17 00:00:00 2001 From: Ryan Marcus Date: Wed, 27 Jul 2016 17:08:28 -0400 Subject: [PATCH 2/3] removed whitespace diffs --- lib/pure/httpclient.nim | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 5fbb234300..6aab8ed3dc 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -387,8 +387,6 @@ proc format(p: MultipartData): tuple[header, body: string] = result.body.add("--" & bound & "\c\L" & s) result.body.add("--" & bound & "--\c\L") - - proc request*(url: string, httpMethod: string, extraHeaders = "", body = "", sslContext = defaultSSLContext, timeout = -1, userAgent = defUserAgent, proxy: Proxy = nil): Response = From 0ada2aedfafbe489f9f73b8b622804f6d1c0ed7a Mon Sep 17 00:00:00 2001 From: Ryan Marcus Date: Wed, 27 Jul 2016 17:26:11 -0400 Subject: [PATCH 3/3] added when() block so that the compiler doesn't try to link SSL methods when SSL isn't available --- lib/pure/httpclient.nim | 45 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 6aab8ed3dc..8b4aafc81c 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -418,28 +418,31 @@ proc request*(url: string, httpMethod: string, extraHeaders = "", # send the appropiate CONNECT header. If not, simply connect to the proper # host (which may still be the proxy, for normal HTTP) if proxy != nil and hostUrl.scheme == "https": - var connectHeaders = "CONNECT " - let targetPort = if hostUrl.port == "": 443 else: hostUrl.port.parseInt - connectHeaders.add(hostUrl.hostname) - connectHeaders.add(":" & $targetPort) - connectHeaders.add(" HTTP/1.1\c\L") - connectHeaders.add("Host: " & hostUrl.hostname & ":" & $targetPort & "\c\L") - if proxy.auth != "": - let auth = base64.encode(proxy.auth, newline = "") - connectHeaders.add("Proxy-Authorization: basic " & auth & "\c\L") - connectHeaders.add("\c\L") - if timeout == -1: - s.connect(r.hostname, port) - else: - s.connect(r.hostname, port, timeout) + when defined(ssl): + var connectHeaders = "CONNECT " + let targetPort = if hostUrl.port == "": 443 else: hostUrl.port.parseInt + connectHeaders.add(hostUrl.hostname) + connectHeaders.add(":" & $targetPort) + connectHeaders.add(" HTTP/1.1\c\L") + connectHeaders.add("Host: " & hostUrl.hostname & ":" & $targetPort & "\c\L") + if proxy.auth != "": + let auth = base64.encode(proxy.auth, newline = "") + connectHeaders.add("Proxy-Authorization: basic " & auth & "\c\L") + connectHeaders.add("\c\L") + if timeout == -1: + s.connect(r.hostname, port) + else: + s.connect(r.hostname, port, timeout) - s.send(connectHeaders) - let connectResult = parseResponse(s, false, timeout) - if not connectResult.status.startsWith("200"): - raise newException(HttpRequestError, - "The proxy server rejected a CONNECT request, " & - "so a secure connection could not be established.") - sslContext.wrapConnectedSocket(s, handshakeAsClient) + s.send(connectHeaders) + let connectResult = parseResponse(s, false, timeout) + if not connectResult.status.startsWith("200"): + raise newException(HttpRequestError, + "The proxy server rejected a CONNECT request, " & + "so a secure connection could not be established.") + sslContext.wrapConnectedSocket(s, handshakeAsClient) + else: + raise newException(HttpRequestError, "SSL support not available. Cannot connect via proxy over SSL") else: if timeout == -1: s.connect(r.hostname, port)