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:
parent
5e4dd571d4
commit
b47d5486db
1 changed files with 75 additions and 10 deletions
|
|
@ -79,7 +79,17 @@
|
||||||
|
|
||||||
|
|
||||||
import asyncdispatch, asyncnet, nativesockets, strutils, parseutils, os, times
|
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):
|
when defined(nimPreviewSlimSystem):
|
||||||
import std/assertions
|
import std/assertions
|
||||||
|
|
@ -95,6 +105,9 @@ type
|
||||||
jobInProgress*: bool
|
jobInProgress*: bool
|
||||||
job*: FtpJob
|
job*: FtpJob
|
||||||
dsockConnected*: bool
|
dsockConnected*: bool
|
||||||
|
useTls: bool
|
||||||
|
when defined(ssl):
|
||||||
|
sslContext: SslContext
|
||||||
|
|
||||||
FtpJobType* = enum
|
FtpJobType* = enum
|
||||||
JRetrText, JRetr, JStore
|
JRetrText, JRetr, JStore
|
||||||
|
|
@ -179,9 +192,20 @@ proc pasv(ftp: AsyncFtpClient) {.async.} =
|
||||||
var ip = nums[0 .. ^3]
|
var ip = nums[0 .. ^3]
|
||||||
var port = nums[^2 .. ^1]
|
var port = nums[^2 .. ^1]
|
||||||
var properPort = port[0].parseInt()*256+port[1].parseInt()
|
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
|
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 =
|
proc normalizePathSep(path: string): string =
|
||||||
return replace(path, '\\', '/')
|
return replace(path, '\\', '/')
|
||||||
|
|
||||||
|
|
@ -198,12 +222,28 @@ proc connect*(ftp: AsyncFtpClient) {.async.} =
|
||||||
# Handle 220 messages from the server
|
# Handle 220 messages from the server
|
||||||
assertReply(reply, "220")
|
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 != "":
|
if ftp.user != "":
|
||||||
assertReply(await(ftp.send("USER " & ftp.user)), "230", "331")
|
assertReply(await(ftp.send("USER " & ftp.user)), "230", "331")
|
||||||
|
|
||||||
if ftp.pass != "":
|
if ftp.pass != "":
|
||||||
assertReply(await(ftp.send("PASS " & ftp.pass)), "230")
|
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.} =
|
proc pwd*(ftp: AsyncFtpClient): Future[string] {.async.} =
|
||||||
## Returns the current working directory.
|
## Returns the current working directory.
|
||||||
let wd = await ftp.send("PWD")
|
let wd = await ftp.send("PWD")
|
||||||
|
|
@ -220,15 +260,15 @@ proc cdup*(ftp: AsyncFtpClient) {.async.} =
|
||||||
|
|
||||||
proc getLines(ftp: AsyncFtpClient): Future[string] {.async.} =
|
proc getLines(ftp: AsyncFtpClient): Future[string] {.async.} =
|
||||||
## Downloads text data in ASCII mode
|
## Downloads text data in ASCII mode
|
||||||
result = ""
|
|
||||||
assert ftp.dsockConnected
|
assert ftp.dsockConnected
|
||||||
while ftp.dsockConnected:
|
while ftp.dsockConnected:
|
||||||
let r = await ftp.dsock.recvLine()
|
let r = await ftp.dsock.recvLine()
|
||||||
if r == "":
|
if r.len == 0:
|
||||||
|
ftp.dsock.close()
|
||||||
ftp.dsockConnected = false
|
ftp.dsockConnected = false
|
||||||
else:
|
else:
|
||||||
result.add(r & "\n")
|
if result.len > 0: result.add "\n"
|
||||||
|
result.add r
|
||||||
assertReply(await(ftp.expectReply()), "226")
|
assertReply(await(ftp.expectReply()), "226")
|
||||||
|
|
||||||
proc listDirs*(ftp: AsyncFtpClient, dir = ""): Future[seq[string]] {.async.} =
|
proc listDirs*(ftp: AsyncFtpClient, dir = ""): Future[seq[string]] {.async.} =
|
||||||
|
|
@ -322,13 +362,14 @@ proc getFile(ftp: AsyncFtpClient, file: File, total: BiggestInt,
|
||||||
|
|
||||||
if dataFut.finished:
|
if dataFut.finished:
|
||||||
let data = dataFut.read
|
let data = dataFut.read
|
||||||
if data != "":
|
if data.len > 0:
|
||||||
progress.inc(data.len)
|
progress.inc(data.len)
|
||||||
progressInSecond.inc(data.len)
|
progressInSecond.inc(data.len)
|
||||||
file.write(data)
|
file.write(data)
|
||||||
dataFut = ftp.dsock.recv(BufferSize)
|
dataFut = ftp.dsock.recv(BufferSize)
|
||||||
else:
|
else:
|
||||||
ftp.dsockConnected = false
|
ftp.dsockConnected = false
|
||||||
|
ftp.dsock.close()
|
||||||
|
|
||||||
assertReply(await(ftp.expectReply()), "226")
|
assertReply(await(ftp.expectReply()), "226")
|
||||||
|
|
||||||
|
|
@ -371,7 +412,7 @@ proc doUpload(ftp: AsyncFtpClient, file: File,
|
||||||
while ftp.dsockConnected:
|
while ftp.dsockConnected:
|
||||||
if sendFut == nil or sendFut.finished:
|
if sendFut == nil or sendFut.finished:
|
||||||
# TODO: Async file reading.
|
# TODO: Async file reading.
|
||||||
let len = file.readBuffer(addr(data[0]), 4000)
|
let len = file.readBuffer(addr data[0], 4000)
|
||||||
setLen(data, len)
|
setLen(data, len)
|
||||||
if len == 0:
|
if len == 0:
|
||||||
# File finished uploading.
|
# File finished uploading.
|
||||||
|
|
@ -422,7 +463,7 @@ proc removeDir*(ftp: AsyncFtpClient, dir: string) {.async.} =
|
||||||
assertReply(await ftp.send("RMD " & dir), "250")
|
assertReply(await ftp.send("RMD " & dir), "250")
|
||||||
|
|
||||||
proc newAsyncFtpClient*(address: string, port = Port(21),
|
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.
|
## Creates a new `AsyncFtpClient` object.
|
||||||
new result
|
new result
|
||||||
result.user = user
|
result.user = user
|
||||||
|
|
@ -432,8 +473,17 @@ proc newAsyncFtpClient*(address: string, port = Port(21),
|
||||||
result.progressInterval = progressInterval
|
result.progressInterval = progressInterval
|
||||||
result.dsockConnected = false
|
result.dsockConnected = false
|
||||||
result.csock = newAsyncSocket()
|
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")
|
var ftp = newAsyncFtpClient("example.com", user = "test", pass = "test")
|
||||||
proc main(ftp: AsyncFtpClient) {.async.} =
|
proc main(ftp: AsyncFtpClient) {.async.} =
|
||||||
await ftp.connect()
|
await ftp.connect()
|
||||||
|
|
@ -448,4 +498,19 @@ when not defined(testing) and isMainModule:
|
||||||
await ftp.removeDir("deleteme")
|
await ftp.removeDir("deleteme")
|
||||||
echo("Finished")
|
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 main(ftp)
|
||||||
|
waitFor main1(ftp)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue