FTP module

pull/3237/head
sha0coder 2019-12-27 19:08:44 +01:00 committed by Alexander Medvednikov
parent 2f23ee4818
commit 7518d2d0dc
3 changed files with 338 additions and 0 deletions

2
vlib/ftp/.gitignore vendored 100644
View File

@ -0,0 +1,2 @@
ftp_test
*.bak

279
vlib/ftp/ftp.v 100644
View File

@ -0,0 +1,279 @@
/*
basic ftp module
RFC-959
https://tools.ietf.org/html/rfc959
Methods:
ftp.connect(host)
ftp.login(user,passw)
pwd := ftp.pwd()
ftp.cd(folder)
dtp := ftp.pasv()
ftp.dir()
ftp.get(file)
dtp.read()
dtp.close()
ftp.close()
*/
module ftp
import net
const (
Connected = 220
SpecifyPassword = 331
LoggedIn = 230
LoginFirst = 503
Anonymous = 530
OpenDataConnection = 150
CloseDataConnection = 226
CommandOk = 200
Denied = 550
PassiveMode = 227
Complete = 226
)
struct DTP {
mut:
sock net.Socket
ip string
port int
}
fn (dtp DTP) read() []byte {
mut data := []byte
for {
buf,len := dtp.sock.recv(1024)
if len == 0 { break }
for i:=0;i<len;i++ {
data << buf[i]
}
}
return data
}
fn (dtp DTP) close() {
dtp.sock.close() or {}
}
struct FTP {
mut:
sock net.Socket
buffer_size int
}
pub fn new() FTP {
mut f := FTP{}
f.buffer_size = 1024
return f
}
fn (ftp FTP) write(data string) ?int {
$if debug {
println('FTP.v >>> $data')
}
n := ftp.sock.send_string(data + '\n') or {
return error('cannot send data')
}
return n
}
fn (ftp FTP) read() (int,string) {
mut data := ftp.sock.read_line()
$if debug {
println('FTP.v <<< $data')
}
if data.len < 5 {
return 0,''
}
code := data[0..3].int()
if data[4] == `-` {
for {
data = ftp.sock.read_line()
if data[0..3].int() == code {
break
}
}
}
return code,data
}
pub fn (ftp mut FTP) connect(ip string) bool {
sock := net.dial(ip, 21) or {
return false
}
ftp.sock = sock
code,_ := ftp.read()
if code == Connected {
return true
}
return false
}
pub fn (ftp FTP) login(user, passwd string) bool {
ftp.write('USER '+user) or {
println('ERROR sending user')
return false
}
mut data := ''
mut code := 0
code,data = ftp.read()
if code == LoggedIn {
return true
}
if code != SpecifyPassword {
return false
}
ftp.write('PASS '+passwd) or {
println('ERROR sending password')
return false
}
code,data = ftp.read()
if code == LoggedIn {
return true
}
return false
}
pub fn (ftp FTP) close() {
send_quit := 'QUIT\r\n'
ftp.sock.send_string(send_quit) or {}
ftp.sock.close() or {}
}
pub fn (ftp FTP) pwd() string {
ftp.write('PWD') or {
return ''
}
_,data := ftp.read()
spl := data.split('"')
if spl.len >= 2 {
return spl[1]
}
return data
}
pub fn (ftp FTP) cd(dir string) {
ftp.write('CWD $dir') or { return }
mut code, mut data := ftp.read()
match code {
Denied {
println("CD $dir denied!")
}
Complete {
code,data = ftp.read()
}
else {}
}
println('cd $data')
}
fn new_dtp(msg string) ?DTP {
// it receives a control message 227 like:
// '227 Entering Passive Mode (209,132,183,61,48,218)'
if !msg.contains('(') || !msg.contains(')') || !msg.contains(',') {
return error('bad message')
}
t := msg.split('(')[1].split(')')[0].split(',')
ip := t[0]+'.'+t[1]+'.'+t[2]+'.'+t[3]
port := t[4].int()*256+t[5].int()
sock := net.dial(ip, port) or {
return error('Cant connect to the data channel')
}
dtp := DTP {
sock : sock
ip: ip
port: port
}
return dtp
}
fn (ftp FTP) pasv() ?DTP {
ftp.write('PASV') or {}
code,data := ftp.read()
println("pass: $data")
if code != PassiveMode {
return error('pasive mode not allowed')
}
dtp := new_dtp(data)
return dtp
}
pub fn (ftp FTP) dir() ?[]string {
dtp := ftp.pasv() or {
return error('cannot establish data connection')
}
ftp.write('LIST') or {}
code,_ := ftp.read()
if code == Denied {
return error('list denied')
}
if code != OpenDataConnection {
return error('data channel empty')
}
list_dir := dtp.read()
result,_ := ftp.read()
if result != CloseDataConnection {
println('LIST not ok')
}
dtp.close()
mut dir := []string
sdir := string(byteptr(list_dir.data))
for lfile in sdir.split('\n') {
if lfile.len >1 {
spl := lfile.split(' ')
dir << spl[spl.len-1]
}
}
return dir
}
pub fn (ftp FTP) get(file string) ?[]byte {
dtp := ftp.pasv() or {
return error('cant stablish data connection')
}
ftp.write('RETR $file') or {}
code,_ := ftp.read()
if code == Denied {
return error('permission denied')
}
if code != OpenDataConnection {
return error('data connection not ready')
}
blob := dtp.read()
dtp.close()
return blob
}

View File

@ -0,0 +1,57 @@
module main
import ftp
fn test_all() {
mut ftp := ftp.new()
// ftp.rediris.org
connected := ftp.connect('ftp.redhat.com')
assert connected
if connected {
println("connected")
loggedin := ftp.login('ftp','ftp')
assert loggedin
if loggedin {
println('logged-in')
pwd := ftp.pwd()
println('pwd: $pwd')
ftp.cd('/')
folder := ftp.dir() or {
eprintln('cannot list folder')
return
}
for file in folder {
println(file)
}
ftp.cd('/suse/linux/enterprise/11Server/en/SAT-TOOLS/SRPMS/')
dir_list := ftp.dir() or {
eprintln('cannot list folder')
return
}
assert dir_list.len > 5
println('$dir_list.len files:')
for file in dir_list {
println('$file')
}
blob := ftp.get('katello-host-tools-3.3.5-8.sles11_4sat.src.rpm') or {
eprintln("couldn't download it")
return
}
assert blob.len > 1024
println('downloaded $blob.len bytes')
}
ftp.close()
}
}