`Syntax10.Scn.FntSyntax10b.Scn.FntSyntax10i.Scn.Fntsp_VersionElemsAllocBeg#Syntax10.Scn.FntPowerMac WindowsWindowsPowerMac$Syntax14b.Scn.FntPowerMacWindows Syntax14b.Scn.Fntp_VersionElemsAllocEndp8FoldElemsNew<Syntax10.Scn.FntSyntax10i.Scn.FntTU (******************************************************************** This FTP client receives or sends a single file specified through an URL. Usage: SFTP.Get url [localFile] SFTP.Put url [localFile] URL syntax: "ftp://"[user[":"password]"@"]host[":"port]"/"path[";type="("a"|"i")] More information on this topic can be found in RFC-1738. For your convenience, you may omit the "ftp://" in your url. SFTP also uses the following entries from FTP.Profile: [FTP] Type; [User] DownloadsFolder, Mail and [Type] ASCII ********************************************************************)  88<Syntax10.Scn.FntSyntax10i.Scn.Fnt!" (**************************************************** Revision History: 960326: GO first release 960514: GO added Put and PutURL 960516: GO display bps rate when transfer is completed 960520: GO FTPB replaces FTPControl & FTPData 960521: GO extensions mapping is no longer case sensitive 960904: CS Strings.Append(Directories.delimiter) after GetDownloadsFolder 961120: GO ReadPassword echoes asterisks for each entered char 961219: GO The downloads folder is created if it does not exist. ****************************************************) 88#Syntax10.Scn.FntKK TCP, T := Texts, FTPB, Input, Files, Directories, O := Oberon, Strings; 88Syntax10b.Scn.FntSyntax10.Scn.FntBSyntax10i.Scn.Fnt $   % + ),      t profile = "FTP.Profile"; asciiType = 'a'; binaryType = 'i'; noErr* = 0; (* error codes *) cntlOpenErr* = 1; (* can't open control connection *) dataOpenErr* = 2; (* can't open data connection *) dataListenErr* = 3; (* can't listen on data connection *) replyTimeOutErr* = 4; (* request timed out *) notAvailErr* = 5; (* ftp service not available *) tempNotAvailErr* = 6; (* ftp service temporarily not available *) loginErr* = 7; (* login failed *) resolveAddrErr* = 9; (* can't resolve host address *) dataConnLostErr* = 10; (* data connection unexpectedly closed *) cntlConnLostErr* = 11; (* control connection unexpectedly closed *) fileNotFoundErr* = 40; (* file not found on server *) fileCreationErr* = 41; (* can't create local file *) fileOpenErr* = 42; (* can't open local file *) storeErr* = 43; (* can't store file on server *) unexpectedErr* = 99; 88Syntax10.Scn.FntSyntax10b.Scn.Fnt%L n ! " n ! "]Syntax10i.Scn.Fntu FTPTransmission* = POINTER TO FTPTransmissionDesc; FTPTransmissionDesc* = RECORD(FTPB.FTPTransmissionDesc) itsCntlConn: FTPB.FTPCntlConn END; Writer* = POINTER TO WriterDesc; WriterDesc* = RECORD(FTPB.WriterDesc) name: ARRAY 256 OF CHAR; itsFile: Files.File; itsRider: Files.Rider END; AsciiWriter* = POINTER TO AsciiWriterDesc; AsciiWriterDesc* = RECORD(WriterDesc) END; BinaryWriter* = POINTER TO BinaryWriterDesc; BinaryWriterDesc* = RECORD(WriterDesc) END; Reader* = POINTER TO ReaderDesc; ReaderDesc* = RECORD(FTPB.ReaderDesc) name: ARRAY 256 OF CHAR; itsFile: Files.File; itsRider: Files.Rider END; AsciiReader* = POINTER TO AsciiReaderDesc; AsciiReaderDesc* = RECORD(ReaderDesc) END; BinaryReader* = POINTER TO BinaryReaderDesc; BinaryReaderDesc* = RECORD(ReaderDesc) END; URLData = RECORD uid, pwd: ARRAY 64 OF CHAR; noPwd: BOOLEAN; (* no password given *) host: ARRAY 64 OF CHAR; port: INTEGER; path: ARRAY 256 OF CHAR; name: ARRAY 256 OF CHAR; type: CHAR END; 88<Syntax10b.Scn.FntSyntax10.Scn.Fnt w: T.Writer; 8*8#Syntax10.Scn.Fnt// BEGIN T.WriteString(w, str) END WriteString; 8&8#Syntax10.Scn.FntZZ BEGIN WriteString("(Error "); T.WriteInt(w, errNo, 0); WriteString(")") END WriteError; 88#Syntax10.Scn.Fnt;; BEGIN T.WriteLn(w); T.Append(O.Log, w.buf); END WriteLn; 86k8#Syntax10.Scn.Fntss BEGIN WHILE ~r.eot & ((ch = ' ') OR (ch = 0AX) OR (ch = 0DX) OR (ch = 09X)) DO T.Read(r, ch) END END SkipBlanks; 8N8_Syntax10.Scn.FntSyntax10i.Scn.Fnt= (* Opens a reader for readings arguments from O.Par.text. *) (* If the first non-blank char is a '^', the reader is re-opened *) (* for the most recent selection. Ch contains the first char of the first argument. *) VAR text: T.Text; beg, end, time: LONGINT; BEGIN success := TRUE; T.OpenReader(r, O.Par.text, O.Par.pos); T.Read(r, ch); SkipBlanks(r, ch); IF ~r.eot THEN IF ch = '^' THEN (* Params are in selection *) O.GetSelection(text, beg, end, time); IF time > 0 THEN (* the selection exists *) T.OpenReader(r, text, beg); T.Read(r, ch); SkipBlanks(r, ch) ELSE WriteString("No selection."); WriteLn; success := FALSE END END ELSE WriteString("No parameters."); WriteLn; success := FALSE END END GetArgsReader; 8K8CSyntax10.Scn.FntSyntax10i.Scn.Fnt (* Prompts for the user to enter a password. *) (* Doesn't echo input to screen, but draws an asterisk for each entered char. *) VAR i: INTEGER; ch: CHAR; BEGIN WriteString(prompt); T.Append(O.Log, w.buf); i := 0; Input.Read(ch); WHILE (ch # 0DX) & (i < LEN(passwd)) DO WriteString("*"); T.Append(O.Log, w.buf); passwd[i] := ch; INC(i); Input.Read(ch); END; passwd[i] := 0X; WriteLn(); END ReadPassword; 8g8_Syntax10.Scn.FntSyntax10i.Scn.Fnt# (* Searches the specified attribute in the profile and returns a scanner for the profile *) (* text if the search was successfull. The value of the attribute can be obtained by *) (* calling Texts.Scan(s). *) VAR profText: T.Text; found: BOOLEAN; BEGIN NEW(profText); T.Open(profText, profile); T.OpenScanner(s, profText, 0); found := FALSE; success := FALSE; REPEAT (* Search section *) REPEAT T.Scan(s) UNTIL s.eot OR (s.class = T.Char) & (s.c = '['); IF ~s.eot THEN T.Scan(s); found := (s.class = T.Name) & (s.s = section); IF found THEN (* Search attribute *) REPEAT T.Scan(s) UNTIL s.eot OR (s.class = T.Name) & (s.s = attr); IF ~s.eot THEN T.Scan(s); success := (s.class = T.Char) & (s.c = '='); END; END; END; UNTIL s.eot OR found END FindProfile; 82F8_Syntax10.Scn.FntSyntax10i.Scn.Fnt"\ VAR success: BOOLEAN; s: T.Scanner; BEGIN Strings.Cap(name); FindProfile(profile, "FTP", "Type", success, s); IF success THEN T.Scan(s); IF s.class = T.Name THEN IF s.s = "ASCII" THEN RETURN asciiType ELSIF s.s = "Binary" THEN RETURN binaryType ELSE (* Automatic *) FindProfile(profile, "Type", "ASCII", success, s); IF success THEN T.Scan(s); WHILE s.class = T.String DO Strings.Cap(s.s); (* actually a little bit dirty *) IF Strings.Match(name, s.s) THEN RETURN asciiType END; T.Scan(s) END END END END END; RETURN binaryType END GetFileType; 828CSyntax10.Scn.FntSyntax10i.Scn.Fnt!# VAR success: BOOLEAN; res: INTEGER; s: T.Scanner; BEGIN FindProfile(profile, "User", "Mail", success, s); IF success THEN T.Scan(s); IF s.class = T.String THEN COPY(s.s, email) ELSE TCP.GetHostName(email, res); Strings.Insert("oberon@", 0, email); END END END GetUserEMail; 878CSyntax10.Scn.FntSyntax10i.Scn.Fnt VAR success: BOOLEAN; s: T.Scanner; BEGIN FindProfile(profile, "User", "DownloadsFolder", success, s); IF success THEN T.Scan(s); IF s.class = T.String THEN COPY(s.s, path) ELSE path[0] := 0X; END END END GetDownloadsFolder; 8U8>Syntax10.Scn.Fntyz8FoldElemsNewSyntax10.Scn.Fnt9_8FoldElemsNew#Syntax10.Scn.Fnt BEGIN ch := CAP(ch); IF (ch >= 'A') THEN RETURN ORD(ch) - ORD('A') + 10 ELSE RETURN ORD(ch) - ORD('0') END END Hex; 8(H8#Syntax10.Scn.Fnt VAR k: INTEGER; BEGIN k := 0; WHILE delims[k] # 0X DO IF ch = delims[k] THEN RETURN TRUE ELSE INC(k) END END; RETURN FALSE END IsDelim; 8qSyntax10i.Scn.Fnt VAR j: INTEGER; PROCEDURE Hex(ch: CHAR): INTEGER; PROCEDURE IsDelim(ch: CHAR): BOOLEAN; BEGIN j := 0; WHILE (j < LEN(elem) - 1) & (url[i] # 0X) & ~IsDelim(url[i]) DO IF url[i] = '%' THEN (* encoded char *) elem[j] := CHR(Hex(url[i + 1])*16 + Hex(url[i + 2])); INC(i, 2) ELSE elem[j] := url[i]; INC(i) END; INC(j) END; elem[j] := 0X END GetURLElem; 8D=8#Syntax10.Scn.Fnt VAR n: INTEGER; BEGIN n := 0; WHILE (str[pos] >= '0') & (str[pos] <= '9') DO n := n*10 + ORD(str[pos]) - ORD('0'); INC(pos) END; RETURN n END StrToNum; 8 Syntax10i.Scn.FntF9 X  VAR i, j, k: INTEGER; temp: ARRAY 8 OF CHAR; PROCEDURE GetURLElem(delims: ARRAY OF CHAR; VAR elem: ARRAY OF CHAR); PROCEDURE StrToNum(str: ARRAY OF CHAR; VAR pos: INTEGER): INTEGER; BEGIN (* protocol *) success := FALSE; i := 0; IF Strings.Pos("ftp://", url, 0) = 0 THEN (* ftp:// is optional *) GetURLElem(":", temp); IF temp # "ftp" THEN RETURN END; IF url[i] # ':' THEN RETURN END; INC(i); IF url[i] # '/' THEN RETURN END; INC(i); IF url[i] # '/' THEN RETURN END; INC(i) END; (* user-id or hostname *) GetURLElem(":@/", urlData.uid); IF url[i] = ':' THEN (* password or port *) INC(i); GetURLElem(":@/", urlData.pwd); urlData.noPwd := FALSE; ELSE urlData.noPwd := TRUE; urlData.pwd := "" END; IF url[i] = '@' THEN (* hostname *) INC(i); GetURLElem(":/", urlData.host); IF url[i] = ':' THEN INC(i); urlData.port := StrToNum(url, i) ELSE urlData.port := FTPB.ftpCntlPort END ELSIF url[i] = '/' THEN COPY(urlData.uid, urlData.host); IF urlData.noPwd THEN urlData.port := FTPB.ftpCntlPort ELSE j := 0; urlData.port := StrToNum(urlData.pwd, j) END; urlData.uid := "anonymous"; GetUserEMail(urlData.pwd); urlData.noPwd := FALSE END; IF url[i] # '/' THEN RETURN END; (* path *) INC(i); GetURLElem(";", urlData.path); j := Strings.Length(urlData.path) - 1; WHILE (j > 0) & (urlData.path[j] # '/') DO DEC(j) END; IF urlData.path[j] = '/' THEN INC(j) END; k := j; WHILE urlData.path[j] # 0X DO urlData.name[j - k] := urlData.path[j]; INC(j) END; urlData.name[j - k] := 0X; urlData.path[k] := 0X; IF url[i] = ';' THEN (* Type *) INC(i); GetURLElem("=", temp); IF temp # "type" THEN RETURN END; IF url[i] # '=' THEN RETURN END; INC(i); urlData.type := url[i] ELSE urlData.type := GetFileType(urlData.name) END; success := TRUE; END ParseURL; 8 g8CSyntax10.Scn.FntLSyntax10i.Scn.Fnt W BEGIN w.itsFile := Files.New(w.name); Files.Set(w.itsRider, w.itsFile, 0) END Open; 88#Syntax10.Scn.Fnt.. BEGIN Files.Register(w.itsFile) END Close; 8 .8#Syntax10.Scn.Fnt@@ BEGIN Files.WriteBytes(w.itsRider, buffer, len) END PutBytes; 8 .&8sSyntax10.Scn.FntSyntax10i.Scn.Fnt*pVersionElemsAllocBeg#Syntax10.Scn.FntPowerMac WindowsWindowsPowerMac#Syntax10.Scn.Fnt__ FOR i := 0 TO len - 1 DO IF buffer[i] # 0AX THEN Files.Write(w.itsRider, buffer[i]) END END; Windows ,pVersionElemsAllocEndh VAR i: INTEGER; (* PowerMac only *) BEGIN  Files.WriteBytes(w.itsRider, buffer, len)  END PutBytes; 8 "8#Syntax10.Scn.FntZZ VAR w: AsciiWriter; BEGIN NEW(w); COPY(fileName, w.name); RETURN w END NewAsciiWriter; 8 "8#Syntax10.Scn.Fnt\\ VAR w: BinaryWriter; BEGIN NEW(w); COPY(fileName, w.name); RETURN w END NewBinaryWriter; 8 g8CSyntax10.Scn.FntLSyntax10i.Scn.Fnt W BEGIN r.itsFile := Files.Old(r.name); Files.Set(r.itsRider, r.itsFile, 0) END Open; 8 .<8#Syntax10.Scn.Fnt BEGIN len := 0; WHILE ~r.itsRider.eof & (len < LEN(buffer)) DO Files.Read(r.itsRider, buffer[len]); IF ~r.itsRider.eof THEN INC(len) END END; END GetBytes; 8 .8Syntax10.Scn.FntpVersionElemsAllocBeg#Syntax10.Scn.FntPowerMac WindowsWindowsPowerMac#Syntax10.Scn.Fnt len := 0; WHILE ~r.itsRider.eof & (len < LEN(buffer) - 1) DO Files.Read(r.itsRider, buffer[len]); IF ~r.itsRider.eof THEN IF buffer[len] = 0DX THEN INC(len); buffer[len] := 0AX END; INC(len) END END; Windows pVersionElemsAllocEnd BEGIN  len := 0; WHILE ~r.itsRider.eof & (len < LEN(buffer)) DO Files.Read(r.itsRider, buffer[len]); IF ~r.itsRider.eof THEN INC(len) END END;  END GetBytes; 8 "8#Syntax10.Scn.FntZZ VAR w: AsciiReader; BEGIN NEW(w); COPY(fileName, w.name); RETURN w END NewAsciiReader; 8 "8#Syntax10.Scn.Fnt\\ VAR w: BinaryReader; BEGIN NEW(w); COPY(fileName, w.name); RETURN w END NewBinaryReader; 8^w8QSyntax10.Scn.FntSyntax10i.Scn.Fnt9F.9 (* Converts the host address string to an IP address. *) VAR res: INTEGER; BEGIN IF (host[0] >= '0') & (host[0] <= '9') THEN (* host address in dotted-decimal notation *) TCP.HostByNumber(host, hostAddr, res) ELSE TCP.HostByName(host, hostAddr, res) END; success := res = TCP.Done; END ResolveAddress; 8M:8QSyntax10.Scn.FntSyntax10i.Scn.Fnt1@v (* Opens the control connection and logs in. *) VAR addr: TCP.IpAdr; success: BOOLEAN; cmd: ARRAY 256 OF CHAR; replyCode: INTEGER; replyText: T.Writer; acct: ARRAY 32 OF CHAR; BEGIN ResolveAddress(url.host, addr, success); IF ~success THEN res := resolveAddrErr; RETURN END; NEW(conn); conn.OpenOnPort(addr, url.port, success); IF ~success THEN res := cntlOpenErr; RETURN END; conn.WaitForReply(replyCode, replyText); CASE FTPB.GetReplyCategory(replyCode) OF FTPB.negativeConnection: res := replyTimeOutErr; conn.Close(); RETURN; | FTPB.positivePreliminary: res := tempNotAvailErr; conn.Close(); RETURN; | FTPB.negativeTransient: res := notAvailErr; conn.Close(); RETURN; | FTPB.positiveCompletion: res := noErr; ELSE res := unexpectedErr; conn.Close(); RETURN END; cmd := "USER "; Strings.Append(url.uid, cmd); conn.Request(cmd, replyCode, replyText); IF replyCode = 331 THEN IF url.noPwd THEN ReadPassword("Enter Password: ", url.pwd) END; cmd := "PASS "; Strings.Append(url.pwd, cmd); conn.Request(cmd, replyCode, replyText); END; IF replyCode = 332 THEN ReadPassword("Enter Account: ", acct); cmd := "ACCT "; Strings.Append(acct, cmd); conn.Request(cmd, replyCode, replyText); END; CASE FTPB.GetReplyCategory(replyCode) OF FTPB.negativeConnection: res := replyTimeOutErr; conn.Close(); RETURN; | FTPB.negativeTransient: res := notAvailErr; conn.Close(); RETURN; | FTPB.negativePermanent: res := loginErr; conn.Close(); RETURN; | FTPB.positiveCompletion: res := noErr; ELSE res := unexpectedErr; conn.Close(); RETURN END; (* Send TYPE command *) IF url.type = asciiType THEN conn.Request("TYPE A", replyCode, replyText) ELSE conn.Request("TYPE I", replyCode, replyText) END; IF FTPB.GetReplyCategory(replyCode) # FTPB.positiveCompletion THEN res := loginErr END END Open; 8)8CSyntax10.Scn.FntSyntax10i.Scn.FntC` (* Sends a QUIT command to the server and closes the connection *) BEGIN IF (conn # NIL) & conn.isOpen THEN conn.SendCommand("QUIT"); conn.Close() END END Close; 88#Syntax10.Scn.Fnt VAR conn: FTPB.FTPDataConn; replyCode: INTEGER; replyText: T.Writer; cmd: ARRAY 256 OF CHAR; success: BOOLEAN; BEGIN NEW(conn); conn.Listen(cntlConn, success); IF success THEN conn.GetPortCmd(cmd); cntlConn.Request(cmd, replyCode, replyText); IF replyCode # 200 THEN conn.Close(); res := dataOpenErr; RETURN END; cmd := "RETR "; Strings.Append(path, cmd); Strings.Append(name, cmd); cntlConn.Request(cmd, replyCode, replyText); IF FTPB.GetReplyCategory(replyCode) # FTPB.positivePreliminary THEN conn.Close(); res := fileNotFoundErr; RETURN END; conn.Accept(success); IF success THEN res := noErr; conn.ReceiveFile(trans, w) ELSE res := dataOpenErr END ELSE res := dataListenErr; END END GetFile; 88#Syntax10.Scn.Fnt VAR conn: FTPB.FTPDataConn; replyCode: INTEGER; replyText: T.Writer; cmd: ARRAY 256 OF CHAR; success: BOOLEAN; BEGIN NEW(conn); conn.Listen(cntlConn, success); IF success THEN conn.GetPortCmd(cmd); cntlConn.Request(cmd, replyCode, replyText); IF replyCode # 200 THEN conn.Close(); res := dataOpenErr; RETURN END; cmd := "STOR "; Strings.Append(path, cmd); Strings.Append(name, cmd); cntlConn.Request(cmd, replyCode, replyText); IF FTPB.GetReplyCategory(replyCode) # FTPB.positivePreliminary THEN conn.Close(); res := storeErr; RETURN END; conn.Accept(success); IF success THEN res := noErr; conn.SendFile(trans, r) ELSE res := dataOpenErr END ELSE res := dataListenErr; END END PutFile; 8! 8CSyntax10.Scn.FntCSyntax10i.Scn.Fnt0q VAR replyCode: INTEGER; replyText: T.Writer; i, bps: REAL; BEGIN (* Wait for "completed" message from server *) t.itsCntlConn.WaitForReply(replyCode, replyText); Close(t.itsCntlConn); IF success THEN i := (O.Time() - t.startTime)/Input.TimeUnit; IF i = 0 THEN i := 1 END; T.WriteString(w, "Transfer completed. ("); T.WriteInt(w, t.bytesTransferred, 0); T.WriteString(w, " bytes in "); T.WriteRealFix(w, i, 0, 1); T.WriteString(w, " sec. = "); bps := t.bytesTransferred/i; IF bps < 1024 THEN T.WriteRealFix(w, bps, 0, 1) ELSE T.WriteRealFix(w, bps/1024, 0, 1); T.WriteString(w, "K") END; T.WriteString(w, " bytes/sec.)"); T.WriteLn(w); ELSE WriteString("File transfer failed.") END; WriteLn END Completed; 8O8QSyntax10.Scn.FntSyntax10i.Scn.FntB"I- (* If localPath is empty, the file name is taken from the url *) VAR urlData: URLData; success: BOOLEAN; conn: FTPB.FTPCntlConn; w: Writer; i, j: INTEGER; fileName: ARRAY 256 OF CHAR; BEGIN ParseURL(url, urlData, success); IF success THEN Open(urlData, conn, res); IF res # noErr THEN Close(conn); RETURN END; IF localPath = "" THEN GetDownloadsFolder(fileName); IF (fileName # "") & (Directories.This(fileName) = NIL) THEN (* create downloads folder *) Directories.Create(fileName); WriteString("The downloads folder has been created."); WriteLn() END; Strings.Append(Directories.delimiter, fileName); Strings.Append(urlData.name, fileName) ELSE COPY(localPath, fileName) END; IF urlData.type = asciiType THEN w := NewAsciiWriter(fileName) ELSE w := NewBinaryWriter(fileName) END; IF w = NIL THEN res := fileCreationErr; Close(conn); RETURN END; IF trans = NIL THEN NEW(trans) END; trans.itsCntlConn := conn; GetFile(urlData.path, urlData.name, conn, w, trans, res); IF res # noErr THEN Close(conn) ELSE WriteString('Retrieving "'); WriteString(urlData.path); WriteString(urlData.name); WriteString('" '); IF urlData.type = asciiType THEN WriteString("(ASCII).") ELSE WriteString("(Binary).") END; WriteLn END END END GetURL; 8 O8CSyntax10.Scn.FntSyntax10i.Scn.FntB (* If localPath is empty, the file name is taken from the url *) VAR urlData: URLData; success: BOOLEAN; conn: FTPB.FTPCntlConn; r: Reader; i, j: INTEGER; fileName: ARRAY 256 OF CHAR; BEGIN ParseURL(url, urlData, success); IF success THEN Open(urlData, conn, res); IF res # noErr THEN Close(conn); RETURN END; IF localPath = "" THEN COPY(urlData.name, fileName) ELSE COPY(localPath, fileName) END; IF urlData.type = asciiType THEN r := NewAsciiReader(fileName) ELSE r := NewBinaryReader(fileName) END; IF r = NIL THEN res := fileOpenErr; Close(conn); RETURN END; IF trans = NIL THEN NEW(trans) END; trans.itsCntlConn := conn; PutFile(urlData.path, urlData.name, conn, r, trans, res); IF res # noErr THEN Close(conn) ELSE WriteString('Sending "'); WriteString(urlData.path); WriteString(urlData.name); WriteString('" '); IF urlData.type = asciiType THEN WriteString("(ASCII).") ELSE WriteString("(Binary).") END; WriteLn END END END PutURL; 8 n8#Syntax10.Scn.Fntpp VAR ch: CHAR; i: INTEGER; success: BOOLEAN; res: INTEGER; r: T.Reader; url, localPath: ARRAY 256 OF CHAR; trans: FTPTransmission; BEGIN GetArgsReader(r, ch, success); IF success THEN i := 0; WHILE ~r.eot & (ch > ' ') & (ch # '~') DO url[i] := ch; INC(i); T.Read(r, ch) END; url[i] := 0X; SkipBlanks(r, ch); localPath := ""; i := 0; WHILE ~r.eot & (ch > ' ') & (ch # '~') DO localPath[i] := ch; INC(i); T.Read(r, ch) END; localPath[i] := 0X; NEW(trans); GetURL(url, localPath, trans, res); IF res # noErr THEN WriteString("Can't retrieve file "); WriteError(res); WriteString("."); WriteLn END END END Get; 8 r8#Syntax10.Scn.Fntll VAR ch: CHAR; i: INTEGER; success: BOOLEAN; res: INTEGER; r: T.Reader; url, localPath: ARRAY 256 OF CHAR; trans: FTPTransmission; BEGIN GetArgsReader(r, ch, success); IF success THEN i := 0; WHILE ~r.eot & (ch > ' ') & (ch # '~') DO url[i] := ch; INC(i); T.Read(r, ch) END; url[i] := 0X; SkipBlanks(r, ch); localPath := ""; i := 0; WHILE ~r.eot & (ch > ' ') & (ch # '~') DO localPath[i] := ch; INC(i); T.Read(r, ch) END; localPath[i] := 0X; NEW(trans); PutURL(url, localPath, trans, res); IF res # noErr THEN WriteString("Can't send file "); WriteError(res); WriteString("."); WriteLn END END END Put; 8qE MODULE SFTP; (** A very simple FTP client. **) (** By Gnter Obiltschnig (g.obiltschnig@jk.uni-linz.ac.at) **) (* Compile for Windows 3.1/95/NT *) Description Revision History IMPORT CONST TYPE VAR (* Utility Procs *) PROCEDURE WriteString(str: ARRAY OF CHAR); PROCEDURE WriteError(errNo: INTEGER); PROCEDURE WriteLn; PROCEDURE SkipBlanks(VAR r: T.Reader; VAR ch: CHAR); PROCEDURE GetArgsReader(VAR r: T.Reader; VAR ch: CHAR; VAR success: BOOLEAN); PROCEDURE ReadPassword(prompt: ARRAY OF CHAR; VAR passwd: ARRAY OF CHAR); PROCEDURE FindProfile(profile, section, attr: ARRAY OF CHAR; VAR success: BOOLEAN; VAR s: T.Scanner); PROCEDURE GetFileType(name: ARRAY OF CHAR): CHAR; PROCEDURE GetUserEMail(VAR email: ARRAY OF CHAR); PROCEDURE GetDownloadsFolder(VAR path: ARRAY OF CHAR); PROCEDURE ParseURL(url: ARRAY OF CHAR; VAR urlData: URLData; VAR success: BOOLEAN); (* Writer *) PROCEDURE (w: Writer) Open*(); PROCEDURE (w: Writer) Close*(); PROCEDURE (w: BinaryWriter) PutBytes*(VAR buffer: ARRAY OF CHAR; VAR len: INTEGER); PROCEDURE (w: AsciiWriter) PutBytes*(VAR buffer: ARRAY OF CHAR; VAR len: INTEGER); PROCEDURE NewAsciiWriter*(fileName: ARRAY OF CHAR): Writer; PROCEDURE NewBinaryWriter*(fileName: ARRAY OF CHAR): Writer; (* Reader *) PROCEDURE (r: Reader) Open*(); PROCEDURE (r: BinaryReader) GetBytes*(VAR buffer: ARRAY OF CHAR; VAR len: INTEGER); PROCEDURE (r: AsciiReader) GetBytes*(VAR buffer: ARRAY OF CHAR; VAR len: INTEGER); PROCEDURE NewAsciiReader*(fileName: ARRAY OF CHAR): Reader; PROCEDURE NewBinaryReader*(fileName: ARRAY OF CHAR): Reader; (* Connection Handling *) PROCEDURE ResolveAddress(host: ARRAY OF CHAR; VAR hostAddr: TCP.IpAdr; VAR success: BOOLEAN); PROCEDURE Open(url: URLData; VAR conn: FTPB.FTPCntlConn; VAR res: INTEGER); PROCEDURE Close(conn: FTPB.FTPCntlConn); PROCEDURE GetFile(path, name: ARRAY OF CHAR; cntlConn: FTPB.FTPCntlConn; w: Writer; trans: FTPTransmission; VAR res: INTEGER); PROCEDURE PutFile(path, name: ARRAY OF CHAR; cntlConn: FTPB.FTPCntlConn; r: Reader; trans: FTPTransmission; VAR res: INTEGER); (* FTPTransmission *) PROCEDURE (t: FTPTransmission) Completed*(success: BOOLEAN); PROCEDURE GetURL*(url, localPath: ARRAY OF CHAR; VAR trans: FTPTransmission; VAR res: INTEGER); PROCEDURE PutURL*(url, localPath: ARRAY OF CHAR; VAR trans: FTPTransmission; VAR res: INTEGER); PROCEDURE Get*; PROCEDURE Put*; (* Initialization *) BEGIN T.OpenWriter(w); T.WriteString(w, "SFTP (GO, 19.12.96)"); T.WriteLn(w); T.Append(O.Log, w.buf) END SFTP.