-- Grapevine: Lily: message composition
-- [Indigo]<Grapevine>Lily>LilySend.mesa
-- Andrew Birrell 19-Oct-81 14:35:45
DIRECTORY
Answer USING[ Block, MakeHeader ],
Ascii,
BodyDefs USING[ maxRNameLength, RName, Timestamp ],
GlassDefs USING[ Handle, TimeOut ],
HeapDefs,
Inline USING[ COPY, LowHalf ],
LilyAccessDefs USING[ Handle, Read ],
LilyCommandDefs USING[ HelpSendOptions ],
LilyIODefs USING[ AppendFromInput, Confirm ],
MailParse,
SendDefs,
String USING[ AppendString, EquivalentString, StringBoundsFault ],
Storage USING[ Node, Free ],
Time USING[ Append, Current, Packed, Unpack ];
LilySend: PROGRAM
IMPORTS AnswerDefs: Answer, GlassDefs, HeapDefs, Inline, LilyCommandDefs,
LilyAccessDefs, LilyIODefs, MailParse, SendDefs, String, Storage,
Time
EXPORTS LilyCommandDefs =
BEGIN
-- Begin message reader/writer abstraction
-- Messages are constructed in Writers, then buffered in readers --
bLength: CARDINAL = 16 * 8 -- assumed to be multiple of 8 bytes --;
lastChar: CHARACTER = MailParse.endOfInput;
Writer: TYPE = POINTER TO WriterObj;
WriterObj: TYPE = RECORD[buffer: PACKED ARRAY [0..bLength) OF CHARACTER,
pos: CARDINAL ← 0,
length: CARDINAL ← 0,
obj: HeapDefs.WriterHandle ];
Reader: TYPE = POINTER TO ReaderObj;
ReaderObj: TYPE = RECORD[buffer: PACKED ARRAY [0..bLength) OF CHARACTER,
pos: CARDINAL ← 0,
length: CARDINAL ← 0,
maxlength: CARDINAL ← 0,
beyondEnd: CARDINAL ← 0,
obj: HeapDefs.ReaderHandle ];
GetWriter: PROC RETURNS[b: Writer] =
BEGIN
b ← Storage.Node[SIZE[WriterObj]];
b.pos ← b.length ← 0;
b.obj ← HeapDefs.HeapStartWrite[temp];
END;
SubWrite: PROC[b: Writer] =
BEGIN
bWriteUnit: CARDINAL = bLength/8 -- words --;
HeapDefs.HeapWriteData[b.obj,[@b.buffer, bWriteUnit]];
b.pos ← b.pos - MIN[b.pos, bWriteUnit*2];
Inline.COPY[from: @b.buffer + bWriteUnit, to: @b.buffer,
nwords: bLength/2-bWriteUnit];
END;
Append: PROC[b: Writer, s: STRING] =
BEGIN
FOR i: CARDINAL IN [0..s.length)
DO IF b.pos = bLength THEN SubWrite[b];
b.buffer[b.pos] ← s[i]; b.pos ← b.pos+1;
ENDLOOP;
b.length ← b.length + s.length;
END;
AppendC: PROC[b: Writer, c: CHARACTER] =
BEGIN
IF c = lastChar THEN RETURN;
IF b.pos = bLength THEN SubWrite[b];
b.buffer[b.pos] ← c; b.pos ← b.pos+1; b.length ← b.length + 1;
END;
Unwrite: PROC[b: Writer] RETURNS[prev: CHARACTER] =
BEGIN
IF b.pos > 0
THEN { b.pos ← b.pos-1; b.length ← b.length-1; prev ← b.buffer[b.pos] }
ELSE prev ← lastChar;
END;
Peek: PROC[b: Writer, str: GlassDefs.Handle] =
BEGIN
-- type last few chars from writer; we have "b.pos" of them available --
OPEN str;
-- we want up to 30 characters ... --
wanted: CARDINAL ← 30;
-- ... but can only show as many as we have buffered ... --
IF b.pos < wanted THEN wanted ← b.pos;
-- ... and don't want more than one line ... --
FOR i: CARDINAL DECREASING IN [b.pos-wanted..b.pos)
DO IF i+10 < b.pos -- ... unless the line is very short --
AND b.buffer[i] = Ascii.CR
THEN { wanted ← b.pos - i - 1; EXIT };
ENDLOOP;
IF b.length > wanted --there will be non-shown chars--
AND ( b.pos <= wanted --we don't know preceding chars--
OR b.buffer[b.pos-wanted-1] # Ascii.CR --more chars on this line-- )
THEN WriteString["..."L];
FOR i: CARDINAL IN [ b.pos-wanted .. b.pos )
DO WriteChar[b.buffer[i]] ENDLOOP;
END;
AbandonWriter: PROC[b: Writer] =
BEGIN
HeapDefs.HeapAbandonWrite[b.obj];
Storage.Free[b];
END;
SubRead: PROC[r: Reader] =
BEGIN
IF r.pos MOD 2 # 0 THEN ERROR;
IF r.pos > bLength/2
THEN BEGIN
Inline.COPY[from: @r.buffer+bLength/8, to: @r.buffer,
nwords: bLength/2 - bLength/8];
r.pos ← r.pos - bLength/4;
END;
[] ← HeapDefs.HeapReadData[r.obj,
[@r.buffer + r.pos/2, (MIN[r.length,bLength-r.pos]+1)/2]];
END;
GetReader: PROC[b: Writer] RETURNS[r: Reader] =
BEGIN
Accept: PROC[obj: HeapDefs.ObjectNumber] =
{ r.obj ← HeapDefs.HeapStartRead[obj] };
WHILE b.pos > 0 DO SubWrite[b] ENDLOOP;
r ← Storage.Node[SIZE[ReaderObj]];
r.maxlength ← b.length;
HeapDefs.HeapEndWrite[b.obj, Accept];
Storage.Free[b];
ResetReader[r];
END;
ResetReader: PROC[r: Reader] =
BEGIN
HeapDefs.SetReaderOffset[r.obj, HeapDefs.objectStart];
r.pos ← 0; r.length ← r.maxlength;
IF r.length > 0 THEN SubRead[r];
r.beyondEnd ← 0;
END;
Read: PROC[r: Reader] RETURNS[c: CHARACTER] =
BEGIN
IF r.length = 0
THEN { r.beyondEnd ← r.beyondEnd+1; RETURN[lastChar] };
IF r.pos = bLength THEN SubRead[r];
c ← r.buffer[r.pos]; r.pos ← r.pos+1; r.length ← r.length-1;
END;
Unread: PROC[r: Reader] =
BEGIN
IF r.beyondEnd > 0
THEN r.beyondEnd ← r.beyondEnd-1
ELSE IF r.pos > 0
THEN { r.pos ← r.pos-1; r.length ← r.length+1 }
ELSE NULL -- error --;
END;
AbandonReader: PROC[r: Reader] =
BEGIN
HeapDefs.HeapEndRead[r.obj];
Storage.Free[r];
END;
-- End of message reader/writer abstraction: should be moved to
-- separate Defs file someday
AppendToWriter: PROC[str: GlassDefs.Handle, b: Writer,
prompt: STRING, peekFirst: BOOLEAN ← TRUE]
RETURNS[end: CHARACTER] =
BEGIN
OPEN str;
MyWrite: PROC[c: CHARACTER] = { AppendC[b,c] };
MyUnwrite: PROC RETURNS[CHARACTER] = { RETURN[ Unwrite[b] ] };
WriteString[prompt];
WriteString[" (ESC to terminate):"L];
WriteChar[Ascii.CR]; IF peekFirst THEN Peek[b, str];
DO end ← LilyIODefs.AppendFromInput[str, ReadChar[],
MyWrite, MyUnwrite, any];
IF end = Ascii.ControlR THEN Peek[b, str] ELSE EXIT;
ENDLOOP;
END;
LowerCase: PROC[c: CHARACTER] RETURNS[CHARACTER] = INLINE
{ RETURN[ IF c IN ['A..'Z] THEN 'a + (c-'A) ELSE c ] };
Find: PROC[ pattern: STRING,
r: Reader,
w: Writer ]
RETURNS[ match: BOOLEAN ] =
BEGIN
pPos: CARDINAL ← 0;
pBase: CARDINAL ← 0;
literal: BOOLEAN ← FALSE;
char: CHARACTER ← Read[r];
DO SELECT TRUE FROM
pPos = pattern.length => { match ← TRUE; Unread[r]; EXIT };
char = lastChar => { match ← FALSE; EXIT };
literal => NULL;
pattern[pPos] = '' => { pPos←pPos+1; literal ← TRUE; LOOP };
pattern[pPos] = '* => { pPos←pPos+1; pBase←pPos; LOOP };
ENDCASE => NULL;
IF LowerCase[pattern[pPos]] # LowerCase[char]
THEN pPos ← pBase
ELSE pPos ← pPos+1;
literal ← FALSE; AppendC[w, char]; char ← Read[r];
ENDLOOP;
END;
Edit: PROC[str: GlassDefs.Handle, r: Reader] RETURNS[ new: Reader ] =
BEGIN
OPEN str;
b: Writer ← GetWriter[];
BEGIN
ENABLE UNWIND => AbandonWriter[b];
DO ENABLE GlassDefs.TimeOut => GOTO timeOut;
pattern: STRING = [128];
WriteChar[Ascii.CR];
IF ReadString["Find pattern (ESC to terminate): "L,
pattern, any] = Ascii.DEL THEN GOTO del;
IF pattern.length = 0 THEN EXIT;
WriteString[" ... "L]; SendNow[];
IF Find[pattern, r, b]
THEN BEGIN
WriteChar[Ascii.CR];
IF AppendToWriter[str, b, "New text"L] = Ascii.DEL
THEN GOTO del;
END
ELSE { WriteString["not found"L]; EXIT };
REPEAT
del => { AbandonWriter[b]; str.WriteString[" XXX"L]; RETURN[r] };
timeOut => { WriteChar[Ascii.CR]; WriteString["*** time-out"L] };
ENDLOOP;
END;
CopyToWriter[r, b];
AbandonReader[r]; new ← GetReader[b];
END;
CopyToWriter: PROC[r: Reader, b: Writer] =
BEGIN
DO c: CHARACTER = Read[r];
IF c = lastChar THEN EXIT;
AppendC[b,c];
ENDLOOP;
END;
AppendText: PROC[str: GlassDefs.Handle, r: Reader, prompt: STRING]
RETURNS[ new: Reader ] =
BEGIN
OPEN str;
b: Writer ← GetWriter[];
CopyToWriter[r,b];
BEGIN
[] ← AppendToWriter[str, b, prompt, FALSE !
UNWIND => AbandonWriter[b]; GlassDefs.TimeOut => GOTO timeOut];
EXITS timeOut => { WriteChar[Ascii.CR]; WriteString["*** time-out"L] };
END;
AbandonReader[r]; new ← GetReader[b];
END;
Type: PROC[str: GlassDefs.Handle, r: Reader] =
BEGIN
OPEN str;
WriteChar[Ascii.CR];
DO c: CHARACTER = Read[r];
IF c = lastChar THEN EXIT;
IF DelTyped[] THEN EXIT;
WriteChar[c];
ENDLOOP;
END;
ReallySend: PROC[str: GlassDefs.Handle, r: Reader,
user, password: STRING ]
RETURNS[ ok: BOOLEAN ] =
BEGIN
OPEN str;
sHandle: SendDefs.Handle = SendDefs.Create[];
senderNeeded: BOOLEAN ← FALSE;
arpaHostNeeded: BOOLEAN ← FALSE;
SendNow[];
BEGIN
ENABLE
BEGIN
SendDefs.SendFailed =>
{ WriteString["server failure: retrying .. "L]; RETRY };
UNWIND => SendDefs.Destroy[sHandle];
END;
pHandle: MailParse.ParseHandle;
ReadInput: PROC RETURNS[ c: CHARACTER ] =
{ RETURN[Read[r] ] };
Backup: PROC =
{ Unread[r] };
info: SendDefs.StartSendInfo = SendDefs.StartSend[handle: sHandle,
senderPwd: password, sender: user, validate: TRUE];
ok ← TRUE;
IF DelTyped[] THEN GOTO del;
SELECT info FROM
badPwd =>
{ WriteString["incorrect password"L]; GOTO bad };
badSender =>
{ WriteString["invalid user name"L]; GOTO bad };
allDown =>
{ WriteString["can't contact any mail server"L]; GOTO bad };
ok =>
NULL;
ENDCASE => ERROR;
pHandle ← MailParse.InitializeParse[ReadInput, Backup, FALSE];
IF ok -- parse to find recipients --
THEN BEGIN
ENABLE
BEGIN
MailParse.ParseError => GOTO badHeader;
UNWIND => MailParse.FinalizeParse[pHandle];
END;
field: STRING = [MailParse.maxFieldNameSize];
registry: STRING = [BodyDefs.maxRNameLength];
WriteString["parsing ... "L]; SendNow[];
AppendRegistry[registry, user];
WHILE MailParse.GetFieldName[pHandle, field]
DO SELECT TRUE FROM
String.EquivalentString[field, "cc"L],
String.EquivalentString[field, "to"L] =>
BEGIN
ProcessName: PROC[sn, na, arpa: STRING,
info: MailParse.NameInfo]
RETURNS[ BOOLEAN ] =
BEGIN
ENABLE String.StringBoundsFault => GOTO tooLong;
recipient: BodyDefs.RName = [BodyDefs.maxRNameLength];
String.AppendString[recipient, sn];
IF arpa.length > 0
THEN BEGIN
arpaHostNeeded ← TRUE;
IF na.length > 0
THEN BEGIN
String.AppendString[recipient, "."L];
String.AppendString[recipient, na];
END;
String.AppendString[recipient, "@"L];
String.AppendString[recipient, arpa];
String.AppendString[recipient, ".ArpaGateway"L];
END
ELSE BEGIN
String.AppendString[recipient, "."L];
String.AppendString[recipient,
IF na.length = 0 THEN registry ELSE na];
END;
SendDefs.AddRecipient[sHandle, recipient];
RETURN[ TRUE ]
EXITS tooLong =>
BEGIN
WriteChar[Ascii.CR];
WriteString["Recipient name too long: "L];
WriteString[sn];
IF na.length >0
THEN { WriteChar['.]; WriteString[na] };
IF arpa.length > 0
THEN { WriteChar['@]; WriteString[arpa] };
ok ← FALSE;
RETURN[TRUE];
END;
END;
MailParse.ParseNameList[pHandle, ProcessName];
END;
ENDCASE =>
BEGIN
fieldBody: STRING = [BodyDefs.maxRNameLength--or so--];
MailParse.GetFieldBody[pHandle, fieldBody];
SELECT TRUE FROM
String.EquivalentString[field, "Date"L],
String.EquivalentString[field, "Sender"L] =>
BEGIN
ok ← FALSE;
WriteChar[Ascii.CR]; WriteString[field];
WriteString[" not allowed; Lily will provide it"L];
END;
String.EquivalentString[field, "From"] =>
senderNeeded ← TRUE;
ENDCASE => NULL;
END;
ENDLOOP;
EXITS badHeader =>
IF ok
THEN BEGIN
ok ← FALSE;
WriteChar[Ascii.CR];
WriteString["Syntax error in header"L];
END;
END;
MailParse.FinalizeParse[pHandle];
IF ok -- validate recipients --
THEN BEGIN
Notify: PROC[n: CARDINAL, who: BodyDefs.RName] =
BEGIN
ok ← FALSE;
WriteChar[Ascii.CR];
WriteString["Invalid recipient: "L];
WriteString[who];
END;
IF DelTyped[] THEN GOTO del;
IF SendDefs.CheckValidity[sHandle, Notify] = 0 AND ok
THEN BEGIN
ok ← FALSE;
WriteChar[Ascii.CR];
WriteString["No valid recipients"L];
END;
END;
IF ok -- send text and commit --
THEN BEGIN
wsBuffer: STRING = [64] --must be even--;
ws: PROCEDURE[s: STRING] =
BEGIN
FOR index: CARDINAL IN [0..s.length)
DO wc[s[index]] ENDLOOP;
END;
wc: PROC[c: CHARACTER] = INLINE
BEGIN
IF wsBuffer.length = wsBuffer.maxlength THEN FlushWS[];
wsBuffer[wsBuffer.length] ← c;
wsBuffer.length ← wsBuffer.length + 1;
END;
FlushWS: PROC =
BEGIN
SendDefs.AddToItem[sHandle,
DESCRIPTOR[@(wsBuffer.text), wsBuffer.length] ];
wsBuffer.length ← 0;
END;
wt: PROCEDURE[t: Time.Packed] =
BEGIN
s: STRING = [30];
Time.Append[s, Time.Unpack[t], TRUE];
ws[s];
END;
cr: STRING = "
"L;
WriteString["sending ... "L]; SendNow[];
SendDefs.StartText[sHandle];
ws[IF senderNeeded THEN "Sender: "L ELSE "From: "L]; ws[user];
IF arpaHostNeeded THEN ws[" @ PARC-MAXC"L];
ws[cr];
ws["Date: "L]; wt[Time.Current[]]; ws[cr];
ResetReader[r];
DO c: CHARACTER = Read[r];
IF c = lastChar THEN EXIT;
wc[c];
ENDLOOP;
FlushWS[];
IF DelTyped[] THEN GOTO del;
SendDefs.Send[sHandle];
WriteString["sent"L];
END;
EXITS
bad => ok ← FALSE;
del => { ok ← FALSE; Flush[]; WriteString[" delivery cancelled"L] };
END;
SendDefs.Destroy[sHandle];
END;
AppendRegistry: PROC[reg, user: STRING] =
BEGIN
pos: CARDINAL ← user.length;
WHILE pos > 0
DO IF user[pos-1] = '. THEN EXIT;
pos ← pos-1;
ENDLOOP;
WHILE pos < user.length AND reg.length < reg.maxlength
DO reg[reg.length] ← user[pos]; pos ← pos+1;
reg.length ← reg.length+1;
ENDLOOP;
END;
SendOptions: PROC[str: GlassDefs.Handle, r: Reader, user, password: STRING] =
BEGIN
OPEN str;
DO ENABLE UNWIND => AbandonReader[r];
BEGIN
ENABLE GlassDefs.TimeOut => GOTO noChars;
ResetReader[r];
WriteChar[Ascii.CR];
WriteString["Option: "L];
SELECT LowerCase[ReadChar[]] FROM
'a => { WriteString["Append"L]; SendNow[];
r ← AppendText[str,r," extra text"L] };
'e => { WriteString["Edit"L]; r ← Edit[str, r] };
'h => { WriteString["Help"L]; LilyCommandDefs.HelpSendOptions[str] };
'q => { WriteString["Quit without sending"L];
IF LilyIODefs.Confirm[str] = yes THEN EXIT };
's => BEGIN
WriteString["Send"L];
IF LilyIODefs.Confirm[str] = yes
AND ReallySend[str, r, user, password]
THEN EXIT;
END;
't => { WriteString["Type"L]; Type[str, r] };
Ascii.DEL => GOTO del
ENDCASE =>
BEGIN
WriteChar['?]; WriteChar[Ascii.CR];
WriteString["Options are: Append, Edit, Help, Quit, Send, Type"L];
END;
IF DelTyped[] THEN GOTO del;
EXITS
noChars =>
BEGIN
WriteChar[Ascii.CR];
WriteString["Type any character to continue sending ... "L];
[] ← ReadChar[ ! GlassDefs.TimeOut => GOTO going ];
EXITS going => { WriteString["abandoned sending"L]; EXIT }
END;
del => { Flush[]; WriteString[" XXX"L] };
END;
ENDLOOP;
AbandonReader[r];
END;
HeaderItem: PROC[str: GlassDefs.Handle, b: Writer, name: STRING]
RETURNS[ notDel: BOOLEAN ] =
BEGIN
OPEN str;
s: STRING = [128];
WriteChar[Ascii.CR];
IF ReadString[name, s, line] = Ascii.DEL
THEN { WriteString[" XXX"L]; notDel ← FALSE }
ELSE BEGIN
notDel ← TRUE;
IF s.length # 0
THEN { Append[b, name]; Append[b, s]; AppendC[b, Ascii.CR] };
END;
END;
SendOrForward: PROC[ str: GlassDefs.Handle, old: Reader,
user, password: STRING ] =
BEGIN
OPEN str;
b: Writer = GetWriter[];
BEGIN
ENABLE
BEGIN
GlassDefs.TimeOut => GOTO timeOut;
UNWIND => { AbandonWriter[b]; IF old#NIL THEN AbandonReader[old] };
END;
IF HeaderItem[str, b, "Subject: "L]
AND HeaderItem[str, b, "To: "L]
AND HeaderItem[str, b, "cc: "L]
THEN BEGIN
AppendC[b, Ascii.CR]; WriteChar[Ascii.CR];
[] ← AppendToWriter[str, b,
IF old=NIL THEN "Message"L ELSE "Covering note"L, FALSE];
END
ELSE BEGIN
AbandonWriter[b]; IF old#NIL THEN AbandonReader[old];
RETURN -- user didn't really want to send anything! --
END;
EXITS timeOut => { WriteChar[Ascii.CR]; WriteString["*** time-out"L] };
END;
IF old#NIL
THEN BEGIN
Append[b, "
----------
"L];
CopyToWriter[old, b]; AbandonReader[old];
END;
SendOptions[str, GetReader[b], user, password];
END;
Send: PUBLIC PROC[ str: GlassDefs.Handle, user, password: STRING ] =
{ SendOrForward[str, NIL, user, password] };
Forward: PUBLIC PROC[ str: GlassDefs.Handle, msg: LilyAccessDefs.Handle,
user, password: STRING ] =
BEGIN
b: Writer = GetWriter[];
Copier: PROC[postmark: BodyDefs.Timestamp,
sender: BodyDefs.RName,
readChar: PROC RETURNS[CHARACTER],
backup: PROC] =
BEGIN
DO c: CHARACTER = readChar[];
IF c = lastChar THEN EXIT;
AppendC[b,c];
ENDLOOP;
END;
LilyAccessDefs.Read[msg, Copier ! UNWIND => AbandonWriter[b]];
SendOrForward[str, GetReader[b], user, password];
END;
CopyHeader: PROC[msg: LilyAccessDefs.Handle]
RETURNS[r: Reader, length: CARDINAL] =
BEGIN
b: Writer = GetWriter[];
Copier: PROC[postmark: BodyDefs.Timestamp,
sender: BodyDefs.RName,
readChar: PROC RETURNS[CHARACTER],
backup: PROC] =
BEGIN
cr: BOOLEAN ← FALSE;
DO c: CHARACTER = readChar[];
-- terminate on double CR's --
IF c = lastChar
THEN { IF NOT cr THEN { AppendC[b, Ascii.CR]; length←length+1 };
AppendC[b, Ascii.CR]; length←length+1; EXIT };
AppendC[b,c]; length←length+1;
IF c = Ascii.CR
THEN{ IF cr THEN EXIT ELSE cr ← TRUE }
ELSE cr ← FALSE;
ENDLOOP;
END;
length ← 0;
LilyAccessDefs.Read[msg, Copier ! UNWIND => AbandonWriter[b]];
r ← GetReader[b];
END;
Answer: PUBLIC PROC[ str: GlassDefs.Handle, msg: LilyAccessDefs.Handle,
user, password: STRING ] =
BEGIN
OPEN str;
old: Reader;
oldLength: CARDINAL;
oldPos: CARDINAL ← 0;
GetChar: PROC[n: CARDINAL] RETURNS[CHARACTER] =
BEGIN
IF oldPos # n
THEN BEGIN
ResetReader[old];
THROUGH [0..n) DO [] ← Read[old] ENDLOOP;
oldPos ← n;
END;
oldPos ← oldPos+1;
RETURN[ Read[old] ]
END;
GetPages: PROC[n: CARDINAL] RETURNS[ LONG POINTER ] =
{ RETURN[ Storage.Node[n*256] ] };
FreePages: PROC[ p: LONG POINTER ] =
{ Storage.Free[ Inline.LowHalf[p] ] };
arpaHosts: ARRAY[0..3) OF STRING ← ["PARC-MAXC"L, "PARC"L, "MAXC"L];
w: Writer;
PutBlock: PROC[block: AnswerDefs.Block] =
BEGIN
FOR i: CARDINAL IN [0..block.length)
DO AppendC[w, block.buffer[i]] ENDLOOP;
END;
userSN: STRING = [64];
userReg: STRING = [64];
dotPos: CARDINAL ← 0;
FOR i: CARDINAL DECREASING IN [0..user.length)
DO IF user[i] = '. THEN { dotPos ← i; EXIT } ENDLOOP;
FOR i: CARDINAL IN [0..dotPos)
DO userSN[i] ← user[i] ENDLOOP;
userSN.length ← dotPos;
FOR i: CARDINAL IN (dotPos..user.length)
DO userReg[i-dotPos-1] ← user[i] ENDLOOP;
userReg.length ← user.length-dotPos-1;
WriteString["parsing ... "L]; SendNow[];
[old, oldLength] ← CopyHeader[msg];
w ← GetWriter[];
IF AnswerDefs.MakeHeader[GetChar, oldLength, PutBlock,
GetPages, FreePages, userSN, userReg,
DESCRIPTOR[arpaHosts] !
UNWIND => { AbandonReader[old]; AbandonWriter[w] }]
THEN BEGIN
ENABLE UNWIND => AbandonWriter[w];
AbandonReader[old];
WriteString["syntax error in message - can't answer it"L];
END
ELSE BEGIN
new: Reader;
AbandonReader[old];
new ← GetReader[w];
BEGIN
ENABLE UNWIND => AbandonReader[new];
WriteString["ok"L];
Type[str, new]; ResetReader[new];
new ← AppendText[str, new, "Reply"L];
END;
SendOptions[str, new, user, password];
END;
END;
END.