From 15137f71c303dc88bc4acd7db3b9ca35b6f3681e Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Tue, 29 Jun 2021 02:38:08 +0300 Subject: [PATCH] Basic authorization implementation for HTTP client. (#204) * Basic authorization implementation for HTTP client. Add tests for basic authorization. * Bump chronos version to 3.0.5. --- chronos.nimble | 2 +- chronos/apps/http/httpclient.nim | 26 ++++++++++++++++---------- chronos/apps/http/httpcommon.nim | 1 + tests/testhttpclient.nim | 14 ++++++++++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/chronos.nimble b/chronos.nimble index 583c5ba7..7f6f8380 100644 --- a/chronos.nimble +++ b/chronos.nimble @@ -1,5 +1,5 @@ packageName = "chronos" -version = "3.0.4" +version = "3.0.5" author = "Status Research & Development GmbH" description = "Chronos" license = "Apache License 2.0 or MIT" diff --git a/chronos/apps/http/httpclient.nim b/chronos/apps/http/httpclient.nim index 38fb7c7c..efca2cdf 100644 --- a/chronos/apps/http/httpclient.nim +++ b/chronos/apps/http/httpclient.nim @@ -7,7 +7,7 @@ # Apache License, version 2.0, (LICENSE-APACHEv2) # MIT license (LICENSE-MIT) import std/[uri, tables, strutils, sequtils] -import stew/[results, base10], httputils +import stew/[results, base10, base64], httputils import ../../asyncloop, ../../asyncsync import ../../streams/[asyncstream, tlsstream, chunkstream, boundstream] import httptable, httpcommon, httpagent, httpbodyrw, multipart @@ -846,20 +846,26 @@ proc prepareRequest(request: HttpClientRequestRef): string {. toLowerAscii(request.headers.getString(TransferEncodingHeader)) == "chunked" # We use ChronosIdent as `User-Agent` string if its not set. - if UserAgentHeader notin request.headers: - discard request.headers.hasKeyOrPut(UserAgentHeader, ChronosIdent) + discard request.headers.hasKeyOrPut(UserAgentHeader, ChronosIdent) # We use request's hostname as `Host` string if its not set. - if HostHeader notin request.headers: - discard request.headers.hasKeyOrPut(HostHeader, request.address.hostname) + discard request.headers.hasKeyOrPut(HostHeader, request.address.hostname) # We set `Connection` to value according to flags if its not set. if ConnectionHeader notin request.headers: if HttpClientRequestFlag.CloseConnection in request.flags: - discard request.headers.hasKeyOrPut(ConnectionHeader, "close") + request.headers.add(ConnectionHeader, "close") else: - discard request.headers.hasKeyOrPut(ConnectionHeader, "keep-alive") + request.headers.add(ConnectionHeader, "keep-alive") # We set `Accept` to accept any content if its not set. - if AcceptHeader notin request.headers: - discard request.headers.hasKeyOrPut(AcceptHeader, "*/*") + discard request.headers.hasKeyOrPut(AcceptHeader, "*/*") + + # We will send `Authorization` information only if username or password set, + # and `Authorization` header is not present in request's headers. + if len(request.address.username) > 0 or len(request.address.password) > 0: + if AuthorizationHeader notin request.headers: + let auth = request.address.username & ":" & request.address.password + let header = "Basic " & + Base64Pad.encode(auth.toOpenArrayByte(0, len(auth) - 1)) + request.headers.add(AuthorizationHeader, header) # Here we perform automatic detection: if request was created with non-zero # body and `Content-Length` header is missing we will create one with size @@ -867,7 +873,7 @@ proc prepareRequest(request: HttpClientRequestRef): string {. if ContentLengthHeader notin request.headers: if len(request.buffer) > 0: let slength = Base10.toString(uint64(len(request.buffer))) - discard request.headers.hasKeyOrPut(ContentLengthHeader, slength) + request.headers.add(ContentLengthHeader, slength) request.bodyFlag = if ContentLengthHeader in request.headers: diff --git a/chronos/apps/http/httpcommon.nim b/chronos/apps/http/httpcommon.nim index fb8fb732..5692b0f0 100644 --- a/chronos/apps/http/httpcommon.nim +++ b/chronos/apps/http/httpcommon.nim @@ -30,6 +30,7 @@ const ExpectHeader* = "expect" ServerHeader* = "server" LocationHeader* = "location" + AuthorizationHeader* = "authorization" UrlEncodedContentType* = "application/x-www-form-urlencoded" MultipartContentType* = "multipart/form-data" diff --git a/tests/testhttpclient.nim b/tests/testhttpclient.nim index 34da4683..9e6f5378 100644 --- a/tests/testhttpclient.nim +++ b/tests/testhttpclient.nim @@ -688,6 +688,17 @@ suite "HTTP client testing suite": await server.closeWait() return "redirect-" & $res + proc testBasicAuthorization(): Future[bool] {.async.} = + var session = createSession(true, maxRedirections = 10) + let url = parseUri("https://guest:guest@jigsaw.w3.org/HTTP/Basic/") + let resp = await session.fetch(url) + await session.closeWait() + if (resp.status == 200) and + ("Your browser made it!" in cast[string](resp.data)): + return true + else: + return false + test "HTTP all request methods test": let address = initTAddress("127.0.0.1:30080") check waitFor(testMethods(address, false)) == 18 @@ -752,6 +763,9 @@ suite "HTTP client testing suite": let address = initTAddress("127.0.0.1:30080") check waitFor(testRequestRedirectTest(address, true, 4)) == "redirect-true" + test "HTTPS basic authorization test": + check waitFor(testBasicAuthorization()) == true + test "Leaks test": proc getTrackerLeaks(tracker: string): bool = let tracker = getTracker(tracker)