ArpaSMTPSupportImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by: DCraft, December 20, 1983 5:54 pm
Last Edited by: Taft, January 23, 1984 1:27:58 pm PST
Hal Murray June 17, 1985 11:28:06 am PDT
John Larson, August 1, 1988 0:45:39 am PDT
DIRECTORY
ArpaMT USING [CheckFromField, Info, ParseHeaders, PrintHeaders, TranslateToArpa, TranslateToGrapevine],
ArpaSMTPControl USING [arpaExceptions, deadLetterName, deadLetterSenderName, defaultLogAcceptPriority, longGVMSName, notifyManagerNames],
ArpaSMTPDescr USING [CopyForReturn, Create, CreateFailed, Descr, EnumerateRawRecipients, GetArpaReversePath, GetFormat, GetGvSender, GetPrecedeMsgText, GetReturnPathLine, RawRecipProc, RetrieveMsgStream, Unparse, WrongState],
ArpaSMTPQueue USING [AddNewMessage],
ArpaSMTPSupport USING [LogPriority],
ArpaSMTPSyntax USING [EnumerateGVItems, GVItemProc],
BasicTime USING [GMT, MonthOfYear, Now, nullGMT, Unpack, Unpacked],
Convert USING [RopeFromInt, RopeFromTime],
IO USING [BreakProc, Close, CreateStream, CreateStreamProcs, EndOfStream, Error, Flush, GetBlock, GetChar, GetIndex, GetInfo, GetLength, GetTokenRope, int, PutChar, PutFR, PutRope, RIS, rope, RopeFromROS, ROS, SetIndex, STREAM, StreamProcs],
GVBasics USING [RName],
GVNames USING [IsMemberDirect, Membership],
Process USING [Detach],
RefText USING [AppendChar, ObtainScratch, ReleaseScratch],
Rope USING [Cat, Concat, Equal, Fetch, Find, FromRefText, IsEmpty, Length, ROPE, Substr],
SimpleMailer USING [SendMessage],
ViewerIO USING [CreateViewerStreams, GetViewerFromStream];
ArpaSMTPSupportImpl: CEDAR MONITOR
IMPORTS ArpaMT, ArpaSMTPControl, ArpaSMTPDescr, ArpaSMTPQueue, ArpaSMTPSyntax, BasicTime, Convert, IO, GVNames, Process, RefText, Rope, SimpleMailer, ViewerIO
EXPORTS ArpaSMTPSupport =
BEGIN
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
Descr: TYPE = ArpaSMTPDescr.Descr;
Logging Information
currentLogAcceptPriority: PUBLIC ArpaSMTPSupport.LogPriority ← ArpaSMTPControl.defaultLogAcceptPriority;
Log: PUBLIC PROC [priority: ArpaSMTPSupport.LogPriority,
note1, note2, note3, note4, note5, note6, note7, note8, note9, note10: Rope.ROPENIL] = {
now: BasicTime.GMT ← BasicTime.Now[];
IF priority >= currentLogAcceptPriority THEN
WriteToLog[priority, now,
note1, note2, note3, note4, note5, note6, note7, note8, note9, note10];
IF priority >= ATTENTION THEN { -- notify managers
date: ROPE ← RFC822Date[now];
from: ROPE ← ArpaSMTPControl.deadLetterSenderName;
to: LIST OF GVBasics.RName ← ArpaSMTPControl.notifyManagerNames;
subject: ROPE ← "Confusion in Mail Gateway";
body1: ROPE ← Rope.Cat[note1, note2, note3, note4, note5];
body2: ROPE ← Rope.Cat[note1, note2, note3, note4, note5];
body: ROPE ← Rope.Cat[body1, body2];
[] ← SimpleMailer.SendMessage[from: from, returnTo: from, to: to, subject: subject, body: body]
};
};
ForkNewMessage: PROC [descr: Descr, queue: ROPE] = TRUSTED {
Process.Detach[FORK ArpaSMTPQueue.AddNewMessage[descr, queue]]; };
HeaderParseError: PUBLIC PROC [recipList: LIST OF ROPE, descr: Descr] = {
ENABLE ArpaSMTPDescr.CreateFailed => CONTINUE;
now: BasicTime.GMT ← BasicTime.Now[];
date: ROPE ← Rope.Cat["Date: ", RFC822Date[now], "\n"];
from: ROPE ← Rope.Cat["From: ", ArpaSMTPControl.deadLetterSenderName, "\n"];
to: ROPE ← Rope.Cat["To: ", ArpaSMTPControl.arpaExceptions.first, "\n"];
subject: ROPE ← "Subject: Parsing Error in Message Header\n\n";
header: ROPE ← Rope.Cat[date, from, to, subject];
getReturnPathLine: ROPE ← ArpaSMTPDescr.GetReturnPathLine[descr];
getPrecedeMsgText: ROPE ← ArpaSMTPDescr.GetPrecedeMsgText[descr];
msgStream: IO.STREAM;
errors: IO.STREAMIO.ROS[];
before: IO.STREAMIO.ROS[];
after: IO.STREAMIO.ROS[];
info: ArpaMT.Info;
body: ROPE;
new: Descr;
msgStream ← ArpaSMTPDescr.RetrieveMsgStream[descr];
SELECT ArpaSMTPDescr.GetFormat[descr] FROM
arpa => {
info ← ArpaMT.ParseHeaders[file: msgStream, errStream: errors];
msgStream.Close[];
ArpaMT.PrintHeaders[info, before];
ArpaMT.TranslateToGrapevine[info];
ArpaMT.PrintHeaders[info, after]; };
gv => { -- Copied from SMTPSendImpl
AssignTextStream: ArpaSMTPSyntax.GVItemProc = {
currentIndex: INT;
IF itemHeader.type # Text THEN RETURN;
currentIndex ← msgStream.GetIndex[];
msgStream ← CreateSubrangeStream[
origStream: msgStream, min: currentIndex, max: currentIndex+itemHeader.length];
continue ← FALSE; };
ArpaSMTPSyntax.EnumerateGVItems[GVStream: msgStream, proc: AssignTextStream];
info ← ArpaMT.ParseHeaders[file: msgStream, errStream: errors];
msgStream.Close[];
ArpaMT.PrintHeaders[info, before];
ArpaMT.TranslateToArpa[info];
ArpaMT.PrintHeaders[info, after]; };
ENDCASE => ERROR;
body ← Rope.Cat[body, "Recipients: "];
FOR rest: LIST OF ROPE ← recipList, rest.rest UNTIL rest = NIL DO
IF rest # recipList THEN body ← Rope.Cat[body, ", "];
body ← Rope.Cat[body, rest.first];
ENDLOOP;
body ← Rope.Cat[body, "\n\n"];
body ← Rope.Cat[body, IO.RopeFromROS[errors], "\n"];
body ← Rope.Cat[body, "Before translation:\n\n", IO.RopeFromROS[before], "\n\n"];
body ← Rope.Cat[body, "After translation:\n\n"];
IF getReturnPathLine # NIL THEN body ← Rope.Cat[body, getReturnPathLine, "\n"];
IF getPrecedeMsgText # NIL THEN body ← Rope.Cat[body, getPrecedeMsgText, "\n"];
body ← Rope.Cat[body, IO.RopeFromROS[after], "\n\n"];
new ← ArpaSMTPDescr.Create[
gvSender: ArpaSMTPControl.deadLetterSenderName,
rawRecipients: ArpaSMTPControl.arpaExceptions,
format: arpa, -- don't have GV items
msgStream: IO.RIS[Rope.Cat[header, body]]];
ForkNewMessage[new, "HeaderParseError"]; };
Undeliverable: PROC [why: ROPE, descr: Descr] = {
ENABLE ArpaSMTPDescr.CreateFailed => CONTINUE;
now: BasicTime.GMT ← BasicTime.Now[];
date: ROPE ← Rope.Cat["Date: ", RFC822Date[now], "\n"];
from: ROPE ← Rope.Cat["From: ", ArpaSMTPControl.deadLetterSenderName, "\n"];
to: ROPE ← Rope.Cat["To: ", ArpaSMTPControl.deadLetterName, "\n"];
subject: ROPE ← "Subject: Undeliverable mail notification\n\n";
header: ROPE ← Rope.Cat[date, from, to, subject];
getReturnPathLine: ROPE ← ArpaSMTPDescr.GetReturnPathLine[descr];
getPrecedeMsgText: ROPE ← ArpaSMTPDescr.GetPrecedeMsgText[descr];
msgStream: IO.STREAM;
errors: IO.STREAMIO.ROS[];
before: IO.STREAMIO.ROS[];
info: ArpaMT.Info;
sender, body: ROPE;
new: Descr;
msgStream ← ArpaSMTPDescr.RetrieveMsgStream[descr];
SELECT ArpaSMTPDescr.GetFormat[descr] FROM
arpa => {
sender ← ArpaSMTPDescr.GetArpaReversePath[descr];
info ← ArpaMT.ParseHeaders[file: msgStream, errStream: errors];
msgStream.Close[];
ArpaMT.PrintHeaders[info, before]; };
gv => { -- Copied from SMTPSendImpl
AssignTextStream: ArpaSMTPSyntax.GVItemProc = {
currentIndex: INT;
IF itemHeader.type # Text THEN RETURN;
currentIndex ← msgStream.GetIndex[];
msgStream ← CreateSubrangeStream[
origStream: msgStream, min: currentIndex, max: currentIndex+itemHeader.length];
continue ← FALSE; };
sender ← ArpaSMTPDescr.GetGvSender[descr];
ArpaSMTPSyntax.EnumerateGVItems[GVStream: msgStream, proc: AssignTextStream];
info ← ArpaMT.ParseHeaders[file: msgStream, errStream: errors];
msgStream.Close[];
ArpaMT.PrintHeaders[info, before]; };
ENDCASE => ERROR;
body ← Rope.Cat[why, "\n\nThe message will be sent to:\n", sender, ".\n\n"];
body ← Rope.Cat[body, "The header of the message was:\n--------------------\n"];
IF getReturnPathLine # NIL THEN body ← Rope.Cat[body, getReturnPathLine, "\n"];
IF getPrecedeMsgText # NIL THEN body ← Rope.Cat[body, getPrecedeMsgText, "\n"];
body ← Rope.Cat[body, IO.RopeFromROS[before], "\n\n"];
new ← ArpaSMTPDescr.Create[
gvSender: ArpaSMTPControl.deadLetterSenderName,
rawRecipients: LIST[ArpaSMTPControl.deadLetterName],
format: arpa, -- don't have GV items
msgStream: IO.RIS[Rope.Cat[header, body]]];
ForkNewMessage[new, "Undeliverable"]; };
WriteToLog: ENTRY PROC [priority: ArpaSMTPSupport.LogPriority, now: BasicTime.GMT,
note1, note2, note3, note4, note5, note6, note7, note8, note9, note10: Rope.ROPENIL] = {
Monitored so we don't interleave messages on the log.
logViewerOut.PutRope[Convert.RopeFromTime[from: now, start: hours, end: seconds, useAMPM: FALSE, includeZone: FALSE]];
logViewerOut.PutRope[" "];
IF priority > noteworthy THEN {
logViewerOut.PutRope[logPriorityNames[priority]];
logViewerOut.PutRope[": "]; };
logViewerOut.PutRope[note1];
logViewerOut.PutRope[note2];
logViewerOut.PutRope[note3];
logViewerOut.PutRope[note4];
logViewerOut.PutRope[note5];
logViewerOut.PutRope[note6];
logViewerOut.PutRope[note7];
logViewerOut.PutRope[note8];
logViewerOut.PutRope[note9];
logViewerOut.PutRope[note10];
logViewerOut.PutChar['\n];
logViewerOut.Flush[];
};
logPriorityNames: PUBLIC ARRAY ArpaSMTPSupport.LogPriority OF ROPE
← ["verbose", "noteworthy", "Important", "ATTENTION", "CRITICAL"];
logViewerIn, logViewerOut: STREAM; -- initialized below
Housekeeping
There is similar code in SMTPQueueImpl
letNonMembersPlay: BOOLTRUE;
goodGuys, badGuys: LIST OF ROPE;
AuthorizationCheck: PUBLIC PROC [sender: ROPE] RETURNS [ok: BOOLEAN] = {
RETURN[TRUE]};
membership: GVNames.Membership;
IF Rope.Find[sender, "@"] # -1 THEN RETURN[TRUE]; -- Arpa => GV DL => Arpa
sender ← StripQuotes[sender];
FOR list: LIST OF ROPE ← goodGuys, list.rest UNTIL list = NIL DO
IF Rope.Equal[sender, list.first, FALSE] THEN RETURN[TRUE];
ENDLOOP;
FOR list: LIST OF ROPE ← badGuys, list.rest UNTIL list = NIL DO
IF Rope.Equal[sender, list.first, FALSE] THEN RETURN[letNonMembersPlay];
ENDLOOP;
membership ← GVNames.IsMemberDirect["ArpanetUsers^.X", sender];
SELECT membership FROM
yes => {
goodGuys ← CONS[sender, goodGuys];
RETURN[TRUE]; };
no => {
Log[important, "\"", sender, "\" isn't a member of ArpanetUsers^.X"];
badGuys ← CONS[sender, badGuys];
RETURN[letNonMembersPlay]; };
notGroup => Log[ATTENTION, "GV Claims ArpanetUsers^.X isn't a group"];
allDown => Log[ATTENTION, "GV Claims all servers for the X registry are down"];
ENDCASE => NULL;
RETURN[TRUE]; };
StripQuotes: PROC [old: ROPE] RETURNS [new: ROPE] = {
JSmith.OSBUNorth => JSmith.OSBUNorth
"John Smith".OSBUNorth => John Smith.OSBUNorth
length: INT ← Rope.Length[old];
new ← old;
IF length < 2 THEN RETURN;
IF Rope.Fetch[old, 0] # '\" THEN RETURN;
BEGIN
quoteSeen: BOOLEANFALSE;
text: REF TEXT ← RefText.ObtainScratch[length];
FOR i: INT IN [0..length) DO
c: CHAR = Rope.Fetch[new, i];
IF c = '\\ AND ~quoteSeen THEN { quoteSeen ← TRUE; LOOP; };
quoteSeen ← FALSE;
IF c = '\" THEN LOOP; -- Not quite right, but works for reasonable input
text ← RefText.AppendChar[text, c];
ENDLOOP;
new ← Rope.FromRefText[text];
RefText.ReleaseScratch[text];
END; };
CheckHeader: PUBLIC PROC [sender: ROPE, descr: Descr] RETURNS [ok: BOOLEANTRUE] = {
msgStream: IO.STREAM;
errors: IO.STREAMIO.ROS[];
precede: ROPE ← ArpaSMTPDescr.GetPrecedeMsgText[descr];
errorRope: ROPE;
info: ArpaMT.Info;
Beware: Returned mail uses the precede hackery. The actual descr has the same body and hence looks like it has the same header so it will hit the same problem each time it gets returned.
IF Rope.Find[precede, "\n\n"] #-1 THEN RETURN;
SELECT ArpaSMTPDescr.GetFormat[descr] FROM
arpa => RETURN; -- Don't check ARPA headers (yet)
gv => { -- Copied from SMTPSendImpl
AssignTextStream: ArpaSMTPSyntax.GVItemProc = {
currentIndex: INT;
IF itemHeader.type # Text THEN RETURN;
currentIndex ← msgStream.GetIndex[];
msgStream ← CreateSubrangeStream[
origStream: msgStream, min: currentIndex, max: currentIndex+itemHeader.length];
continue ← FALSE; };
ArpaSender: PROC[descr: Descr] RETURNS[BOOLEAN] = {
arpaReversePath: Rope.ROPE ← ArpaSMTPDescr.GetArpaReversePath[descr];
IF Rope.Find[ArpaSMTPDescr.GetGvSender[descr], "@"] > -1 THEN RETURN[TRUE];
IF Rope.Find[s1: arpaReversePath, s2: "Owners-"] > -1 THEN RETURN[TRUE];
RETURN[Rope.Find[arpaReversePath, "@"] > -1 AND
Rope.Find[s1: arpaReversePath, s2: "@Xerox", case: FALSE] = -1];
};
msgStream ← ArpaSMTPDescr.RetrieveMsgStream[descr];
ArpaSMTPSyntax.EnumerateGVItems[GVStream: msgStream, proc: AssignTextStream];
info ← ArpaMT.ParseHeaders[file: msgStream, errStream: errors];
msgStream.Close[];
errorRope ← IO.RopeFromROS[errors];
IF Rope.IsEmpty[errorRope] AND ~ArpaSender[descr] AND ~ArpaMT.CheckFromField[info] THEN
errorRope ← "A non-Ascii or unprintable character in the From or Sender fields (even if properly quoted) kills some mailers. If your address (shown below) ends with a \".ns\", check with CINSupport:All Areas to see if your domain should be registered with the Grapevine mail gateways.";
IF ~Rope.IsEmpty[errorRope] AND ~ArpaSender[descr] THEN {
recipients: ROPE;
CopyRawRecipient: ArpaSMTPDescr.RawRecipProc = {
IF recipients = NIL THEN recipients ← "Recipients: "
ELSE recipients ← Rope.Cat[recipients, ", "];
recipients ← Rope.Cat[recipients, rawRecipient]; };
ArpaSMTPDescr.EnumerateRawRecipients[descr, CopyRawRecipient, NIL];
errorRope ← Rope.Cat["The Arpa Mail Gateway encountered troubles in the header of your message. In order to avoid confusing other ARPA Internet sites, your message was not delivered to the following ARPA Internet recipients. (\n\n", recipients, "\n\n", errorRope];
NotifySender[descr, errorRope];
ok ← FALSE; }; };
ENDCASE => ERROR; };
Notification of Undeliverable Mail
NotifySender: PUBLIC PROC [descr: Descr,
why1, why2, why3, why4, why5: ROPENIL] = {
ENABLE {
ArpaSMTPDescr.CreateFailed =>
Log[ATTENTION,
"Unable to create descriptor to return item ", ArpaSMTPDescr.Unparse[descr],
" to sender. Being returned because: \"",
why1, why2, why3, why4, why5, "\".\nToo bad!"];
ArpaSMTPDescr.WrongState =>
Log[noteworthy,
"Asked to return item ", ArpaSMTPDescr.Unparse[descr], " to sender, because: \"",
why1, why2, why3, why4, why5,
"\"\nThe item has a null reverse path (possibly a return message itself). Too bad!"];
};
why: ROPE = Rope.Cat[why1, why2, why3, why4, why5];
new: Descr;
Undeliverable[why, descr];
new ← ArpaSMTPDescr.CopyForReturn[descr,
Rope.Cat[why,
"\n\nThe text of your message was\n--------------------\n"]];
Log[important,
ArpaSMTPDescr.Unparse[descr], " is being returned because:\n",
why1, why2, why3, why4, why5];
ForkNewMessage[new, "ReturnToSender"]; };
Current Date and Time (in SMTP format)
monthText: ARRAY BasicTime.MonthOfYear OF ROPE = ["JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC", "???"];
USzones: TYPE ~ [4..10];
zones1stChar: ARRAY USzones OF ROPE = ["A", "E", "C", "M", "P", "Y", "H"];
Now: PUBLIC PROC [compressed: BOOLFALSE] RETURNS [rope: ROPE, gmt: BasicTime.GMT] = {
Returns the current time in format specified for Arpa SMTP. The "compressed" form is suitable for a file name.
unpacked: BasicTime.Unpacked;
date, time: ROPE;
z: INT;
unpacked ← BasicTime.Unpack[gmt ← BasicTime.Now[]];
IF compressed THEN {
date ← IO.PutFR["%02G%02G%02G-",
IO.int[unpacked.year MOD 100],
IO.int[unpacked.month.ORD+1],
IO.int[unpacked.day]];
time ← IO.PutFR["%02g%02g%02g",
IO.int[unpacked.hour],
IO.int[unpacked.minute],
IO.int[unpacked.second]]; }
ELSE {
date ← IO.PutFR["%02g %g %02g ",
IO.int[unpacked.day],
IO.rope[monthText[unpacked.month]],
IO.int[unpacked.year MOD 100]];
time ← IO.PutFR["%02g:%02g:%02g ",
IO.int[unpacked.hour],
IO.int[unpacked.minute],
IO.int[unpacked.second]];
z ← unpacked.zone/60; -- gives zone in range -12 to 12
IF (FIRST[USzones] <= z) AND (z <= LAST[USzones]) THEN
time ← Rope.Cat[time, zones1stChar[z], IF unpacked.dst = yes THEN "D" ELSE "S", "T"]
ELSE {
IF unpacked.dst = yes THEN z ← z - 1;
IF z = 0 THEN time ← Rope.Concat[time, "GMT"]
ELSE time ← Rope.Cat[
time, IF z > 0 THEN "+" ELSE "-", Convert.RopeFromInt[ABS[z]]]; }; };
RETURN[Rope.Concat[date, time], gmt];
}; -- end Now
RFC822Date: PUBLIC PROC[gmt: BasicTime.GMT← BasicTime.nullGMT] RETURNS[date: ROPE] =
generates arpa standard time, dd mmm yy hh:mm:ss zzz
BEGIN OPEN IO;
upt: BasicTime.Unpacked ← BasicTime.Unpack[IF gmt = BasicTime.nullGMT THEN
BasicTime.Now[] ELSE gmt];
zone: ROPE;
weekday, month, tyme, year: ROPE;
timeFormat: ROPE = "%02g:%02g:%02g %g"; -- "hh:mm:ss zzz"
dateFormat: ROPE = "%2g %g %g %g"; -- "dd mmm yy timeFormat"
arpaNeg: BOOL ← upt.zone > 0;
aZone: INTABS[upt.zone];
zDif: INT ← aZone / 60;
zMul: INT ← zDif * 60;
IF (zMul = aZone) AND arpaNeg THEN {
IF upt.dst = yes THEN
SELECT zDif FROM
0 => zone← "UT";
4 => zone← "EDT";
5 => zone← "CDT";
6 => zone← "MDT";
8 => zone← "PDT";
ENDCASE
ELSE
SELECT zDif FROM
0 => zone← "UT";
5 => zone← "EST";
6 => zone← "CST";
7 => zone← "MST";
8 => zone← "PST";
ENDCASE; };
IF zone = NIL THEN {
mm: INT← aZone - zMul;
zone← PutFR[IF arpaNeg THEN "-%02g%02g" ELSE "+%02g%02g", int[zDif], int[mm]]; };
SELECT upt.month FROM
January => month← "Jan";
February => month← "Feb";
March => month← "Mar";
April => month← "Apr";
May => month← "May";
June => month← "Jun";
July => month← "Jul";
August => month← "Aug";
September => month← "Sep";
October => month← "Oct";
November => month← "Nov";
December => month← "Dec";
unspecified => ERROR;
ENDCASE => ERROR;
SELECT upt.weekday FROM
Monday => weekday← "Monday";
Tuesday => weekday← "Tuesday";
Wednesday => weekday ← "Wednesday";
Thursday => weekday ← "Thursday";
Friday => weekday ← "Friday";
Saturday => weekday ← "Saturday";
Sunday => weekday ← "Sunday";
unspecified => ERROR;
ENDCASE => ERROR;
year← Rope.Substr[PutFR[NIL, int[upt.year]], 2];
tyme← PutFR[timeFormat, int[upt.hour], int[upt.minute], int[upt.second], rope[zone]];
date← PutFR[dateFormat, int[upt.day], rope[month], rope[year], rope[tyme]];
date ← Rope.Cat[date, " (", weekday, ")"];
END;
Subrange Streams
CreateSubrangeStream: PUBLIC PROC [origStream: STREAM, min, max: INT] RETURNS [STREAM] = {
Create a new stream encompassing only a subrange of the original one. If the original stream is already a subrange stream, do not layer again.
newStreamData: REF SubrangeStreamData;
newBackingStream: STREAM;
IF origStream.GetInfo[].class = $Subrange THEN {
origStreamData: REF SubrangeStreamData = NARROW[origStream.streamData];
origMinIndex: INT = origStreamData.min;
origMaxIndex: INT = origStreamData.max;
newBackingStream ← origStream.backingStream;
newStreamData ← NEW[SubrangeStreamData ← [min: MAX[min, origMinIndex],
max: MIN[max, origMaxIndex]]];
}
ELSE {
newBackingStream ← origStream;
newStreamData ← NEW[SubrangeStreamData ←
[min: min,
max: MIN[max, origStream.GetLength[]]]];
};
RETURN[IO.CreateStream[
backingStream: newBackingStream,
streamProcs: SubrangeStreamProcs,
streamData: newStreamData]];
};
SubrangeStreamProcs: REF IO.StreamProcs = IO.CreateStreamProcs[
variety: input,
class: $Subrange,
getChar: SubrangeGetChar,
getBlock: SubrangeGetBlock,
endOf: SubrangeEndOf,
backup: SubrangeBackup, -- Bug in real one
setIndex: SubrangeSetIndex,
getLength: SubrangeGetLength];
SubrangeStreamData: TYPE = RECORD[min, max: INT]; -- Info is [min..max)
SubrangeBackup: PROC [self: STREAM, char: CHAR] = {
me: REF SubrangeStreamData = NARROW[self.streamData];
self.SetIndex[self.GetIndex[]-1]; };
SubrangeEndOf: PROC [self: STREAM] RETURNS [BOOL] = {
me: REF SubrangeStreamData = NARROW[self.streamData];
This assumes that GetChar/GetBlock set the index to the length after reading the last char.
RETURN[self.backingStream.GetIndex[] >= me.max]; };
SubrangeGetLength: PROC [self: STREAM] RETURNS [length: INT] = {
me: REF SubrangeStreamData = NARROW[self.streamData];
RETURN[me.max]; };
SubrangeSetIndex: PROC [self: STREAM, index: INT] = {
me: REF SubrangeStreamData = NARROW[self.streamData];
IF index > me.max OR index < me.min THEN ERROR IO.Error[BadIndex, self];
self.backingStream.SetIndex[index]; };
SubrangeGetBlock: PROC [self: STREAM, block: REF TEXT, startIndex: NAT ← 0, count: NATNAT.LAST] RETURNS [nBytesRead: NAT] = {
me: REF SubrangeStreamData = NARROW[self.streamData];
bytesLeft: INT = me.max - self.backingStream.GetIndex[];
bytesToRead: INT = MIN[count, bytesLeft];
natBytesToRead: NAT = bytesToRead;
nBytesRead ← self.backingStream.GetBlock[
block: block, startIndex: startIndex, count: natBytesToRead]; };
SubrangeGetChar: PROC [self: STREAM] RETURNS [CHAR] = {
me: REF SubrangeStreamData = NARROW[self.streamData];
fullStream: STREAM ~ self.backingStream;
IF fullStream.GetIndex[] >= me.max THEN IO.EndOfStream[self];
RETURN[fullStream.GetChar[]]; };
RopeFromSubrange: PUBLIC PROC [origStream: STREAM, min, max: INT] RETURNS [rope: ROPE] = {
EverythingProc: IO.BreakProc = {RETURN[other]};
subrangeStream: STREAM;
subrangeStream ← CreateSubrangeStream[origStream: origStream, min: min, max: max];
rope ← subrangeStream.GetTokenRope[EverythingProc !
IO.EndOfStream => CONTINUE ].token; };
Misc
[logViewerIn, logViewerOut] ← ViewerIO.CreateViewerStreams[
name: "MailGateway Log",
backingFile: "MailGateway.log"];
ViewerIO.GetViewerFromStream[logViewerOut].inhibitDestroy ← TRUE;
logViewerOut.PutRope["########## "];
logViewerOut.PutRope[ArpaSMTPControl.longGVMSName];
logViewerOut.PutRope[" Log ##########\n\n"];
END.