diff options
author | Indrajith K L | 2022-12-03 17:00:20 +0530 |
---|---|---|
committer | Indrajith K L | 2022-12-03 17:00:20 +0530 |
commit | f5c4671bfbad96bf346bd7e9a21fc4317b4959df (patch) | |
tree | 2764fc62da58f2ba8da7ed341643fc359873142f /v_windows/v/vlib/net/ftp | |
download | cli-tools-windows-master.tar.gz cli-tools-windows-master.tar.bz2 cli-tools-windows-master.zip |
Diffstat (limited to 'v_windows/v/vlib/net/ftp')
-rw-r--r-- | v_windows/v/vlib/net/ftp/ftp.v | 265 | ||||
-rw-r--r-- | v_windows/v/vlib/net/ftp/ftp_test.v | 50 |
2 files changed, 315 insertions, 0 deletions
diff --git a/v_windows/v/vlib/net/ftp/ftp.v b/v_windows/v/vlib/net/ftp/ftp.v new file mode 100644 index 0000000..41b2cde --- /dev/null +++ b/v_windows/v/vlib/net/ftp/ftp.v @@ -0,0 +1,265 @@ +module ftp + +/* +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() +*/ +import net +import io + +const ( + connected = 220 + specify_password = 331 + logged_in = 230 + login_first = 503 + anonymous = 530 + open_data_connection = 150 + close_data_connection = 226 + command_ok = 200 + denied = 550 + passive_mode = 227 + complete = 226 +) + +struct DTP { +mut: + conn &net.TcpConn + reader io.BufferedReader + ip string + port int +} + +fn (mut dtp DTP) read() ?[]byte { + mut data := []byte{} + mut buf := []byte{len: 1024} + for { + len := dtp.reader.read(mut buf) or { break } + if len == 0 { + break + } + data << buf[..len] + } + return data +} + +fn (mut dtp DTP) close() { + dtp.conn.close() or { panic(err) } +} + +struct FTP { +mut: + conn &net.TcpConn + reader io.BufferedReader + buffer_size int +} + +pub fn new() FTP { + mut f := FTP{ + conn: 0 + } + f.buffer_size = 1024 + return f +} + +fn (mut zftp FTP) write(data string) ?int { + $if debug { + println('FTP.v >>> $data') + } + return zftp.conn.write('$data\r\n'.bytes()) +} + +fn (mut zftp FTP) read() ?(int, string) { + mut data := zftp.reader.read_line() ? + $if debug { + println('FTP.v <<< $data') + } + if data.len < 5 { + return 0, '' + } + code := data[..3].int() + if data[3] == `-` { + for { + data = zftp.reader.read_line() ? + if data[..3].int() == code && data[3] != `-` { + break + } + } + } + return code, data +} + +pub fn (mut zftp FTP) connect(ip string) ?bool { + zftp.conn = net.dial_tcp('$ip:21') ? + zftp.reader = io.new_buffered_reader(reader: zftp.conn) + code, _ := zftp.read() ? + if code == ftp.connected { + return true + } + return false +} + +pub fn (mut zftp FTP) login(user string, passwd string) ?bool { + zftp.write('USER $user') or { + $if debug { + println('ERROR sending user') + } + return false + } + mut code, _ := zftp.read() ? + if code == ftp.logged_in { + return true + } + if code != ftp.specify_password { + return false + } + zftp.write('PASS $passwd') or { + $if debug { + println('ERROR sending password') + } + return false + } + code, _ = zftp.read() ? + if code == ftp.logged_in { + return true + } + return false +} + +pub fn (mut zftp FTP) close() ? { + zftp.write('QUIT') ? + zftp.conn.close() ? +} + +pub fn (mut zftp FTP) pwd() ?string { + zftp.write('PWD') ? + _, data := zftp.read() ? + spl := data.split('"') // " + if spl.len >= 2 { + return spl[1] + } + return data +} + +pub fn (mut zftp FTP) cd(dir string) ? { + zftp.write('CWD $dir') or { return } + mut code, mut data := zftp.read() ? + match int(code) { + ftp.denied { + $if debug { + println('CD $dir denied!') + } + } + ftp.complete { + code, data = zftp.read() ? + } + else {} + } + $if debug { + println('CD $data') + } +} + +fn new_dtp(msg string) ?&DTP { + if !is_dtp_message_valid(msg) { + return error('Bad message') + } + ip, port := get_host_ip_from_dtp_message(msg) + mut dtp := &DTP{ + ip: ip + port: port + conn: 0 + } + conn := net.dial_tcp('$ip:$port') or { return error('Cannot connect to the data channel') } + dtp.conn = conn + dtp.reader = io.new_buffered_reader(reader: dtp.conn) + return dtp +} + +fn (mut zftp FTP) pasv() ?&DTP { + zftp.write('PASV') ? + code, data := zftp.read() ? + $if debug { + println('pass: $data') + } + if code != ftp.passive_mode { + return error('pasive mode not allowed') + } + dtp := new_dtp(data) ? + return dtp +} + +pub fn (mut zftp FTP) dir() ?[]string { + mut dtp := zftp.pasv() or { return error('Cannot establish data connection') } + zftp.write('LIST') ? + code, _ := zftp.read() ? + if code == ftp.denied { + return error('`LIST` denied') + } + if code != ftp.open_data_connection { + return error('Data channel empty') + } + list_dir := dtp.read() ? + result, _ := zftp.read() ? + if result != ftp.close_data_connection { + println('`LIST` not ok') + } + dtp.close() + mut dir := []string{} + sdir := list_dir.bytestr() + for lfile in sdir.split('\n') { + if lfile.len > 1 { + dir << lfile.after(' ').trim_space() + } + } + return dir +} + +pub fn (mut zftp FTP) get(file string) ?[]byte { + mut dtp := zftp.pasv() or { return error('Cannot stablish data connection') } + zftp.write('RETR $file') ? + code, _ := zftp.read() ? + if code == ftp.denied { + return error('Permission denied') + } + if code != ftp.open_data_connection { + return error('Data connection not ready') + } + blob := dtp.read() ? + dtp.close() + return blob +} + +fn is_dtp_message_valid(msg string) bool { + // An example of message: + // '227 Entering Passive Mode (209,132,183,61,48,218)' + return msg.contains('(') && msg.contains(')') && msg.contains(',') +} + +fn get_host_ip_from_dtp_message(msg string) (string, int) { + mut par_start_idx := -1 + mut par_end_idx := -1 + for i, c in msg { + if c == `(` { + par_start_idx = i + 1 + } else if c == `)` { + par_end_idx = i + } + } + data := msg[par_start_idx..par_end_idx].split(',') + ip := data[0..4].join('.') + port := data[4].int() * 256 + data[5].int() + return ip, port +} diff --git a/v_windows/v/vlib/net/ftp/ftp_test.v b/v_windows/v/vlib/net/ftp/ftp_test.v new file mode 100644 index 0000000..a62316d --- /dev/null +++ b/v_windows/v/vlib/net/ftp/ftp_test.v @@ -0,0 +1,50 @@ +import net.ftp + +fn test_ftp_cleint() { + $if !network ? { + return + } + // NB: this function makes network calls to external servers, + // that is why it is not a very good idea to run it in CI. + // If you want to run it manually, use: + // `v -d network vlib/net/ftp/ftp_test.v` + ftp_client_test_inside() or { panic(err) } +} + +fn ftp_client_test_inside() ? { + mut zftp := ftp.new() + // eprintln(zftp) + defer { + zftp.close() or { panic(err) } + } + connect_result := zftp.connect('ftp.redhat.com') ? + assert connect_result + login_result := zftp.login('ftp', 'ftp') ? + assert login_result + pwd := zftp.pwd() ? + assert pwd.len > 0 + zftp.cd('/') or { + assert false + return + } + dir_list1 := zftp.dir() or { + assert false + return + } + assert dir_list1.len > 0 + zftp.cd('/suse/linux/enterprise/11Server/en/SAT-TOOLS/SRPMS/') or { + assert false + return + } + dir_list2 := zftp.dir() or { + assert false + return + } + assert dir_list2.len > 0 + assert dir_list2.contains('katello-host-tools-3.3.5-8.sles11_4sat.src.rpm') + blob := zftp.get('katello-host-tools-3.3.5-8.sles11_4sat.src.rpm') or { + assert false + return + } + assert blob.len > 0 +} |