ref: d1540c7f666e3c5d636b48c956b444205b50502d
dir: /appl/svc/webget/ftp.b/
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 webget@webget.com");
(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);
}