FTP client is now able to connect to server over TLS by set `useTls =… (#17219)

* FTP client is now able to connect to server over TLS by set `useTls = true` in newAsyncFtpClient proc

* Update asyncftpclient.nim

* fix CI

* shouldn't use {.error.}

* Update lib/pure/asyncftpclient.nim

Co-authored-by: flywind <xzsflywind@gmail.com>
Co-authored-by: ringabout <43030857+ringabout@users.noreply.github.com>
Co-authored-by: Clay Sweetser <Varriount@users.noreply.github.com>
This commit is contained in:
Huy Doan
2022-10-09 13:29:22 +07:00
committed by GitHub
parent 5e4dd571d4
commit b47d5486db

View File

@@ -79,7 +79,17 @@
import asyncdispatch, asyncnet, nativesockets, strutils, parseutils, os, times
from net import BufferSize
from net import BufferSize, SslContext
when defined(ssl):
from net import SslHandshakeType, newContext, SslCVerifyMode
var defaultSslContext {.threadvar.}: SslContext
proc getSSLContext(): SslContext =
if defaultSSLContext == nil:
defaultSSLContext = newContext(verifyMode = CVerifyPeer)
result = defaultSSLContext
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -95,6 +105,9 @@ type
jobInProgress*: bool
job*: FtpJob
dsockConnected*: bool
useTls: bool
when defined(ssl):
sslContext: SslContext
FtpJobType* = enum
JRetrText, JRetr, JStore
@@ -179,9 +192,20 @@ proc pasv(ftp: AsyncFtpClient) {.async.} =
var ip = nums[0 .. ^3]
var port = nums[^2 .. ^1]
var properPort = port[0].parseInt()*256+port[1].parseInt()
await ftp.dsock.connect(ip.join("."), Port(properPort))
let address = ip.join(".")
await ftp.dsock.connect(address, Port(properPort))
ftp.dsockConnected = true
if ftp.useTls:
when defined(ssl):
try:
ftp.sslContext.wrapConnectedSocket(ftp.dsock, handshakeAsClient, address)
except:
ftp.dsock.close()
raise getCurrentException()
else:
doAssert false, "TLS support is not available. Cannot connect over TLS. Compile with -d:ssl to enable."
proc normalizePathSep(path: string): string =
return replace(path, '\\', '/')
@@ -198,12 +222,28 @@ proc connect*(ftp: AsyncFtpClient) {.async.} =
# Handle 220 messages from the server
assertReply(reply, "220")
if ftp.useTls:
when defined(ssl):
assertReply(await(ftp.send("AUTH TLS")), "234")
try:
ftp.sslContext.wrapConnectedSocket(ftp.csock, handshakeAsClient, ftp.address)
except:
ftp.csock.close()
raise getCurrentException()
else:
doAssert false, "TLS support is not available. Cannot connect over TLS. Compile with -d:ssl to enable."
if ftp.user != "":
assertReply(await(ftp.send("USER " & ftp.user)), "230", "331")
if ftp.pass != "":
assertReply(await(ftp.send("PASS " & ftp.pass)), "230")
if ftp.useTls:
assertReply(await(ftp.send("PBSZ 0")), "200")
assertReply(await(ftp.send("PROT P")), "200")
assertReply(await(ftp.send("TYPE I")), "200")
proc pwd*(ftp: AsyncFtpClient): Future[string] {.async.} =
## Returns the current working directory.
let wd = await ftp.send("PWD")
@@ -220,15 +260,15 @@ proc cdup*(ftp: AsyncFtpClient) {.async.} =
proc getLines(ftp: AsyncFtpClient): Future[string] {.async.} =
## Downloads text data in ASCII mode
result = ""
assert ftp.dsockConnected
while ftp.dsockConnected:
let r = await ftp.dsock.recvLine()
if r == "":
if r.len == 0:
ftp.dsock.close()
ftp.dsockConnected = false
else:
result.add(r & "\n")
if result.len > 0: result.add "\n"
result.add r
assertReply(await(ftp.expectReply()), "226")
proc listDirs*(ftp: AsyncFtpClient, dir = ""): Future[seq[string]] {.async.} =
@@ -322,13 +362,14 @@ proc getFile(ftp: AsyncFtpClient, file: File, total: BiggestInt,
if dataFut.finished:
let data = dataFut.read
if data != "":
if data.len > 0:
progress.inc(data.len)
progressInSecond.inc(data.len)
file.write(data)
dataFut = ftp.dsock.recv(BufferSize)
else:
ftp.dsockConnected = false
ftp.dsock.close()
assertReply(await(ftp.expectReply()), "226")
@@ -371,7 +412,7 @@ proc doUpload(ftp: AsyncFtpClient, file: File,
while ftp.dsockConnected:
if sendFut == nil or sendFut.finished:
# TODO: Async file reading.
let len = file.readBuffer(addr(data[0]), 4000)
let len = file.readBuffer(addr data[0], 4000)
setLen(data, len)
if len == 0:
# File finished uploading.
@@ -422,7 +463,7 @@ proc removeDir*(ftp: AsyncFtpClient, dir: string) {.async.} =
assertReply(await ftp.send("RMD " & dir), "250")
proc newAsyncFtpClient*(address: string, port = Port(21),
user, pass = "", progressInterval: int = 1000): AsyncFtpClient =
user, pass = "", progressInterval: int = 1000, useTls = false, sslContext: SslContext = nil): AsyncFtpClient =
## Creates a new `AsyncFtpClient` object.
new result
result.user = user
@@ -432,8 +473,17 @@ proc newAsyncFtpClient*(address: string, port = Port(21),
result.progressInterval = progressInterval
result.dsockConnected = false
result.csock = newAsyncSocket()
if useTls:
when defined(ssl):
result.useTls = true
if sslContext == nil:
result.sslContext = getSSLContext()
else:
result.sslContext = sslContext
else:
doAssert false, "TLS support is not available. Cannot connect over TLS. Compile with -d:ssl to enable."
when not defined(testing) and isMainModule:
when not defined(testing) and defined(ssl) and isMainModule:
var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
proc main(ftp: AsyncFtpClient) {.async.} =
await ftp.connect()
@@ -448,4 +498,19 @@ when not defined(testing) and isMainModule:
await ftp.removeDir("deleteme")
echo("Finished")
var ftps = newAsyncFtpClient("example.com", user = "test", pass = "test", useTls = true)
proc main1(ftp: AsyncFtpClient) {.async.} =
await ftps.connect()
echo await ftps.pwd()
echo await ftps.listDirs()
await ftps.store("payload.jpg", "payload.jpg")
await ftps.retrFile("payload.jpg", "payload2.jpg")
await ftps.rename("payload.jpg", "payload_renamed.jpg")
await ftps.store("payload.jpg", "payload_remove.jpg")
await ftps.removeFile("payload_remove.jpg")
await ftps.createDir("deleteme")
await ftps.removeDir("deleteme")
echo("Finished")
waitFor main(ftp)
waitFor main1(ftp)