From 7518d2d0dc24867ffb003867a030a31381a60183 Mon Sep 17 00:00:00 2001 From: sha0coder Date: Fri, 27 Dec 2019 19:08:44 +0100 Subject: [PATCH] FTP module --- vlib/ftp/.gitignore | 2 + vlib/ftp/ftp.v | 279 ++++++++++++++++++++++++++++++++++++++++++++ vlib/ftp/ftp_test.v | 57 +++++++++ 3 files changed, 338 insertions(+) create mode 100644 vlib/ftp/.gitignore create mode 100644 vlib/ftp/ftp.v create mode 100644 vlib/ftp/ftp_test.v diff --git a/vlib/ftp/.gitignore b/vlib/ftp/.gitignore new file mode 100644 index 0000000000..e3b23685ed --- /dev/null +++ b/vlib/ftp/.gitignore @@ -0,0 +1,2 @@ +ftp_test +*.bak diff --git a/vlib/ftp/ftp.v b/vlib/ftp/ftp.v new file mode 100644 index 0000000000..ce877d85d1 --- /dev/null +++ b/vlib/ftp/ftp.v @@ -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>> $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 +} diff --git a/vlib/ftp/ftp_test.v b/vlib/ftp/ftp_test.v new file mode 100644 index 0000000000..b48217e14b --- /dev/null +++ b/vlib/ftp/ftp_test.v @@ -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() + } +}