shithub: purgatorio

ref: 063c3f398e949895bb9795539e83a2e0be364376
dir: /appl/svc/webget/ftp.b/

View raw version
implement Transport;

include "sys.m";
	sys: Sys;

include "draw.m";

include "string.m";
	S: String;

include "bufio.m";
	B : Bufio;
	Iobuf: import Bufio;

include "message.m";
	M: Message;
	Msg, Nameval: import M;

include "url.m";
	U: Url;
	ParsedUrl: import U;

include "webget.m";

include "dial.m";
	DI: Dial;

include "wgutils.m";
	W: WebgetUtils;
	Fid, Req: import WebgetUtils;

include "transport.m";

FTPPORT: con "21";
DEBUG: con 1;

# Return codes
Extra, Success, Incomplete, TempFail, PermFail : con (1+iota);

init(w: WebgetUtils)
{
	sys = load Sys Sys->PATH;
	W = w;
	M = W->M;
	S = W->S;
	B = W->B;
	U = W->U;
	DI = W->DI;
}

connect(c: ref Fid, r: ref Req, donec: chan of ref Fid)
{
	mrep: ref Msg = nil;
	io, dio: ref Iobuf = nil;
	err := "";
	u := r.url;
	port := u.port;
	if(port == "")
		port = FTPPORT;
	addr := DI->netmkaddr(u.host, "tcp", port);

dummyloop:	# just for breaking out of on error
	for(;;) {
		W->log(c, sys->sprint("ftp: dialing %s", addr));
		net := DI->dial(addr, nil);
		if(net == nil) {
			err = sys->sprint("dial error: %r");
			break dummyloop;
		}
		io = B->fopen(net.dfd, sys->ORDWR);
		if(io == nil) {
			err = "cannot open network via bufio";
			break dummyloop;
		}

		# look for Hello
		(code, msg) := getreply(c, io);
		if(code != Success) {
			err = "instead of hello: " + msg;
			break dummyloop;
		}
		# logon
		err = sendrequest(c, io, "USER anonymous");
		if(err != "") 
			break dummyloop;
		(code, msg) = getreply(c, io);
		if(code != Success) {
			if(code == Incomplete) {
				# need password
				err = sendrequest(c, io, "PASS [email protected]");
				(code, msg) = getreply(c, io);
				if(code != Success) {
					err = "login failed: " + msg;
					break dummyloop;
				}
			}
			else {
				err = "login failed: " + msg;
				break dummyloop;
			}
		}
		# image type
		err = sendrequest(c, io, "TYPE I");
		(code, msg) = getreply(c, io);
		if(code != Success) {
			err = "can't set type I: " + msg;
			break dummyloop;
		}
		# passive mode
		err = sendrequest(c, io, "PASV");
		(code, msg) = getreply(c, io);
		if(code != Success) {
			err = "can't use passive mode: " + msg;
			break dummyloop;
		}
		(paddr, pport) := passvap(msg);
		if(paddr == "") {
			err = "passive mode protocol botch: " + msg;
			break dummyloop;
		}
		# dial data port
		daddr := "tcp!" + paddr + "!" + pport;
		W->log(c, sys->sprint("ftp: dialing data %s", daddr));
		(ok2, dnet) := sys->dial(daddr, nil);
		if(ok2 < 0) {
			err = sys->sprint("data dial error: %r");
			break dummyloop;
		}
		dio = B->fopen(dnet.dfd, sys->ORDWR);
		if(dio == nil) {
			err = "cannot open network via bufio";
			break dummyloop;
		}
		# tell remote to send file
		err = sendrequest(c, io, "RETR " + u.path);
		(code, msg) = getreply(c, io);
		if(code != Extra) {
			err = "passive mode retrieve failed: " + msg;
			break dummyloop;
		}

		mrep = Msg.newmsg();
W->log(c, "reading from dio now");
		err = W->getdata(dio, mrep, W->fixaccept(r.types), u);
W->log(c, "done reading from dio now, err=" + err);
		B->dio.close();
		if(err == "")
			W->okprefix(r, mrep);
		break dummyloop;
	}
	if(io != nil)
		B->io.close();
	if(dio != nil)
		B->dio.close();
	if(err != "")
		mrep = W->usererr(r, err);
	if(mrep != nil) {
		W->log(c, "ftp: reply ready for " + r.reqid + ": " + mrep.prefixline);
		r.reply = mrep;
		donec <-= c;
	}
}

getreply(c: ref Fid, io: ref Iobuf) : (int, string)
{
	for(;;) {
		line := B->io.gets('\n');
		n := len line;
		if(n == 0)
			break;
		if(DEBUG)
			W->log(c, "ftp: got reply: " + line);
		if(line[n-1] == '\n') {
			if(n > 2 && line[n-2] == '\r')
				line = line[0:n-2];
			else
				line = line[0:n-1];
		}
		rv := int line;
		if(rv >= 100 && rv < 600) {
			# if line is like '123-stuff'
			# then there will be more lines until
			# '123 stuff'
			if(len line<4 || line[3]==' ')
				return (rv/100, line);
		}
	}
	return (-1, "");
}

sendrequest(c: ref Fid, io: ref Iobuf, cmd: string) : string
{
	if(DEBUG)
		W->log(c, "ftp: send request: " + cmd);
	cmd = cmd + "\r\n";
	buf := array of byte cmd;
	n := len buf;
	if(B->io.write(buf, n) != n)
		return sys->sprint("write error: %r");
	return "";
}

passvap(s: string) : (string, string)
{
	# Parse reply to PASSV to find address and port numbers.
	# This is AI
	addr := "";
	port := "";
	(nil, v) := S->splitl(s, "(");
	if(v != "")
		s = v[1:];
	else
		(nil, s) = S->splitl(s, "0123456789");
	if(s != "") {
		(n, l) := sys->tokenize(s, ",");
		if(n >= 6) {
			addr = hd l + ".";
			l = tl l;
			addr += hd l + ".";
			l = tl l;
			addr += hd l + ".";
			l = tl l;
			addr += hd l;
			l = tl l;
			p1 := int hd l;
			p2 := int hd tl l;
			port = string (((p1&255)<<8)|(p2&255));
		}
	}
	return (addr, port);
}