ArpaSMTPDescrImpl.mesa
Last Edited by: DCraft, December 21, 1983 3:38 pm
Last Edited by: Taft, January 23, 1984 1:35:48 pm PST
Hal Murray May 28, 1985 4:52:46 pm PDT
John Larson, October 21, 1987 10:06:47 pm PDT
DIRECTORY
ArpaSMTPControl USING [deadLetterSenderName, deadLetterPath, itemExpiryTimeout],
ArpaSMTPDescr USING [Descr, Format, HostProc, InitialItemProc, PrintForm, RawRecipProc],
ArpaSMTPSupport USING [CreateSubrangeStream, Log, LogPriority, Now, RFC822Date, RopeFromSubrange],
BasicTime USING [GMT, Now, Period, Update],
Convert USING [Error, IntFromRope, RopeFromInt, RopeFromRope, TimeFromRope],
FS USING [Copy, Delete, EnumerateForInfo, Error, InfoProc, Rename, StreamOpen],
IO USING [BreakProc, Close, EndOfStream, GetBlock, GetIndex, GetLineRope, GetTokenRope, int, Put, PutBlock, PutChar, PutF, PutFR, PutRope, rope, RopeFromROS, ROS, SetIndex, SetLength, STREAM, time],
RefText USING [ObtainScratch],
Rope USING [Cat, Concat, Equal, Fetch, Find, Length, ROPE, Substr];
ArpaSMTPDescrImpl: CEDAR MONITOR
IMPORTS
ArpaSMTPControl, ArpaSMTPSupport, BasicTime, Convert, FS, IO, RefText, Rope
EXPORTS ArpaSMTPDescr =
BEGIN
---- Descriptors -----
Type Definitions and Instance Creation
Descr: TYPE = REF DescrRep;
DescrRep: PUBLIC TYPE = RECORD[
userHandle: INT,
source: ROPE,
format: ArpaSMTPDescr.Format,
arpaReversePath: ROPE,
gvSender: ROPE,
recipients: RecipientList,
createDate: BasicTime.GMT,
expiryDate: BasicTime.GMT,
precedeMsgText: ROPE,
returnPathLine: ROPE,
fileName: ROPE,
infoStartIndex: INT];
RecipientList: TYPE = REF RecipientListRep;
RecipientState: TYPE = {raw, processed};
RecipientListRep: TYPE = RECORD[
val: SELECT state: RecipientState FROM
raw => [raw: LIST OF ROPE],
processed => [processed: LIST OF HostAndUsers],
ENDCASE];
HostAndUsers: TYPE = RECORD[host: ROPE, users: LIST OF ROPE];
valuesRecipientState: ARRAY RecipientState OF ROPE = [raw: "raw", processed: "processed"];
ROPE: TYPE = Rope.ROPE;
STREAM: TYPE = IO.STREAM;
twoDays: INT = INT[2]*24*60*60;
Create: PUBLIC PROC [
arpaReversePath: ROPENIL,
gvSender: ROPENIL,
rawRecipients: LIST OF ROPE,
source: ROPENIL,
format: ArpaSMTPDescr.Format,
msgStream: STREAM,
precedeMsgText: ROPENIL,
returnPathLine: ROPENIL]
RETURNS [descr: Descr] = TRUSTED {
expiryDate: BasicTime.GMT;
expiryDate ← BasicTime.Update[BasicTime.Now[], ArpaSMTPControl.itemExpiryTimeout];
descr ← NEW[DescrRep ← [
userHandle: UniqueUserHandle[],
arpaReversePath: arpaReversePath,
gvSender: gvSender,
recipients: NEW[raw RecipientListRep ←[raw[rawRecipients]]],
createDate: BasicTime.Now[],
expiryDate: expiryDate,
source: source,
format: format,
precedeMsgText: precedeMsgText,
returnPathLine: returnPathLine,
fileName: UniqueFileName[],
infoStartIndex: -1 --undefined--]];
StoreEntireItem[descr, msgStream -- includes sucking message from msgStream
! BadItemFile => ERROR CreateFailed];
}; -- end Create
CopyForReturn: PUBLIC PROC [old: Descr, reason: ROPE] RETURNS [new: Descr] = {
newFileName: ROPE = UniqueFileName[];
expiryDate: BasicTime.GMT;
sender: ROPE;
date: ROPE ← Rope.Cat["Date: ", ArpaSMTPSupport.RFC822Date[BasicTime.Now[]], "\n"];
from: ROPE;
to: ROPE;
subject: ROPE ← "Subject: Undeliverable mail\n\n";
header: ROPE;
arpaReversePath: ROPE;
Copy the old file to the new one.
[] ← FS.Copy[old.fileName, newFileName ! FS.Error => { ArpaSMTPSupport.Log[
important,
"Failed to copy file ", old.fileName, " to ", newFileName,
"\nwhen trying to create new descriptor to return item to sender.\n",
"FS said ", error.explanation, "."];
ERROR CreateFailed}];
Create a new descriptor and write out the (changed) item info.
SELECT old.format FROM
gv => {
sender ← old.gvSender;
from ← ArpaSMTPControl.deadLetterSenderName; -- Mailer.PA
IF Rope.Find[sender, "@"] # -1 THEN
Rejecting ARPA recipient in GV DL, but sender came in from ARPA
from ← ArpaSMTPControl.deadLetterPath; -- Mailer.PA@Xerox.ARPA
arpaReversePath ← ArpaSMTPControl.deadLetterPath; };
arpa => {
sender ← old.arpaReversePath;
from ← ArpaSMTPControl.deadLetterPath;
arpaReversePath ← ArpaSMTPControl.deadLetterSenderName; }; -- SMTPSendImpl will add @Xerox
ENDCASE => ERROR;
IF sender.Fetch[0] # '@ THEN to ← Rope.Cat["To: ", sender, "\n"]
ELSE to ← Rope.Cat["To: RFC822.Requires.Something.Here <", sender, ">\n"];
from ← Rope.Cat["From: ", from, "\n"];
header ← Rope.Cat[date, from, to, subject, reason];
IF old.returnPathLine # NIL THEN header ← Rope.Cat[header, old.returnPathLine, "\n"];
expiryDate ← BasicTime.Update[BasicTime.Now[], ArpaSMTPControl.itemExpiryTimeout];
new ← NEW[DescrRep ← [
userHandle: UniqueUserHandle[],
arpaReversePath: arpaReversePath,
gvSender: ArpaSMTPControl.deadLetterSenderName,
recipients: NEW[raw RecipientListRep ← [raw[LIST[sender]]]],
createDate: BasicTime.Now[],
expiryDate: expiryDate,
source: NIL,
format: old.format,
precedeMsgText: Rope.Cat[header, old.precedeMsgText],
returnPathLine: NIL,
fileName: newFileName,
infoStartIndex: old.infoStartIndex]];
StoreItemInfo[new];
}; -- end CopyForReturn
Field Selection and Setting
GetUserHandle: PUBLIC PROC [self: Descr] RETURNS [INT] = {
RETURN[self.userHandle] };
GetFileName: PUBLIC PROC [self: Descr] RETURNS [ROPE] = {
RETURN[self.fileName]; };
GetExpiryDate: PUBLIC PROC [self: Descr] RETURNS [BasicTime.GMT] = {
RETURN[self.expiryDate] };
GetArpaReversePath: PUBLIC PROC [self: Descr] RETURNS [path: ROPE] = {
RETURN[self.arpaReversePath]; };
SetArpaReversePath: PUBLIC PROC [self: Descr, path: ROPE] = {
self.arpaReversePath ← path; };
GetGvSender: PUBLIC PROC [self: Descr] RETURNS [rName: ROPE] = {
RETURN[self.gvSender]; };
SetGvSender: PUBLIC PROC [self: Descr, rName: ROPE] = {
self.gvSender ← rName; };
EnumerateRawRecipients: PUBLIC PROC [self: Descr, proc: ArpaSMTPDescr.RawRecipProc, procData: REF ANYNIL] = {
recipients: LIST OF ROPE;
IF NOT ISTYPE[self.recipients, REF raw RecipientListRep] THEN
ERROR WrongState;
recipients ← NARROW[self.recipients, REF raw RecipientListRep].raw;
FOR restRecips: LIST OF ROPE ← recipients, restRecips.rest UNTIL restRecips = NIL DO
IF NOT proc[restRecips.first, procData] THEN RETURN;
ENDLOOP; };
RemoveRawRecipients: PUBLIC PROC [self: Descr] = {
IF NOT ISTYPE[self.recipients, REF raw RecipientListRep] THEN
ERROR WrongState;
NARROW[self.recipients, REF raw RecipientListRep].raw ← NIL; -- test this
StoreItemInfo[self]; };
AddProcessedRecipient: PUBLIC PROC [self: Descr, hostName: ROPE, userName: ROPE, checkDuplicate: BOOL] = {
WITH self.recipients SELECT FROM
raw: REF raw RecipientListRep =>
start a new processed recipient list (EnumerateRawRecipients will already have a handle on the raw one)
self.recipients ← NEW[processed RecipientListRep ←
[processed[LIST[HostAndUsers[hostName, LIST[userName]]]]]];
processed: REF processed RecipientListRep => {
add to the existing one
hostList: LIST OF HostAndUsers ← processed.processed;
FOR restHosts: LIST OF HostAndUsers ← hostList, restHosts.rest UNTIL restHosts = NIL DO
hostAndUsers: HostAndUsers = restHosts.first;
IF Rope.Equal[hostAndUsers.host, hostName, FALSE] THEN {
Correct host list found; add this user as appropriate.
userNames: LIST OF ROPE ← hostAndUsers.users;
IF checkDuplicate THEN
Check each user name, adding this at end if not found.
FOR userList: LIST OF ROPE ← userNames, userList.rest UNTIL userList = NIL DO
IF Rope.Equal[userList.first, userName] THEN EXIT;
REPEAT
FINISHED => userNames ← CONS[userName, userNames];
ENDLOOP
ELSE -- just add this user name at end
userNames ← CONS[userName, userNames];
restHosts.first.users ← userNames;
EXIT; };
REPEAT
FINISHED => -- not yet a HostAndUsers element for this host; create one
processed.processed ← CONS[HostAndUsers[hostName, LIST[userName]], hostList];
ENDLOOP; };
ENDCASE; }; -- end AddProcessedRecipient
RemoveRecipientHost: PUBLIC PROC [self: Descr, hostName: ROPE] = {
recipients: REF processed RecipientListRep;
hostList: LIST OF HostAndUsers;
IF NOT ISTYPE[self.recipients, REF processed RecipientListRep] THEN
ERROR WrongState;
recipients ← NARROW[self.recipients, REF processed RecipientListRep];
hostList ← recipients.processed;
IF Rope.Equal[hostList.first.host, hostName, FALSE] THEN
hostList ← hostList.rest
ELSE {
lastList: LIST OF HostAndUsers ← hostList;
FOR list: LIST OF HostAndUsers ← hostList.rest, list.rest UNTIL list = NIL DO
IF Rope.Equal[list.first.host, hostName, FALSE] THEN {lastList.rest ← list.rest; EXIT};
lastList ← list;
ENDLOOP; };
recipients.processed ← hostList;
self.recipients ← recipients;
StoreItemInfo[self]; };
EnumerateRecipientHosts: PUBLIC PROC [self: Descr, proc: ArpaSMTPDescr.HostProc, procData: REF ANYNIL] = {
recipients: LIST OF HostAndUsers;
IF NOT ISTYPE[self.recipients, REF processed RecipientListRep] THEN
ERROR WrongState;
recipients ← NARROW[self.recipients, REF processed RecipientListRep].processed;
FOR restHostRecipients: LIST OF HostAndUsers ← recipients, restHostRecipients.rest
UNTIL restHostRecipients = NIL DO
IF NOT proc[restHostRecipients.first.host, restHostRecipients.first.users, procData] THEN
RETURN;
ENDLOOP; };
MoreRecipients: PUBLIC PROC [self: Descr] RETURNS [BOOL] = {
WITH self.recipients SELECT FROM
raw: REF raw RecipientListRep => RETURN[raw.raw # NIL];
processed: REF processed RecipientListRep => RETURN[processed.processed # NIL];
ENDCASE => ERROR; };
StoreItemInfo: PUBLIC PROC [self: Descr] = see below
GetFormat: PUBLIC PROC [self: Descr] RETURNS [ArpaSMTPDescr.Format] = {
RETURN[self.format]; };
GetPrecedeMsgText: PUBLIC PROC [self: Descr] RETURNS [ROPE] = {
RETURN[self.precedeMsgText]; };
SetPrecedeMsgText: PUBLIC PROC [self: Descr, new: ROPE] = {
self.precedeMsgText ← new; };
GetReturnPathLine: PUBLIC PROC [self: Descr] RETURNS [ROPE] = {
RETURN[self.returnPathLine]; };
GetSource: PUBLIC PROC [self: Descr] RETURNS [ROPE] = {
IF self.source.Fetch[0] = '[ THEN RETURN[Rope.Cat[self.source, ".ARPA"]];
RETURN[self.source]; };
RetrieveMsgStream: PUBLIC PROC [self: Descr] RETURNS [STREAM] = {
RETURN[OpenMsgFileStream[self.fileName, self.infoStartIndex
! BadItemFile => ERROR PutOnBadQueue]]; };
UniqueID: PUBLIC PROC [self: Descr] RETURNS [rope: ROPE] = {
bang: INT;
rope ← self.fileName;
rope ← Rope.Substr[rope, itemFileNamePrefixLength]; -- Strip off leading "[]<>MG>Q>Item-"
bang ← Rope.Find[rope, "!"];
IF bang = -1 THEN RETURN;
rope ← Rope.Substr[rope, 0, bang-1]; };
Destroy: PUBLIC PROC[self: Descr] = {
FS.Delete[self.fileName]; };
Print: PUBLIC PROC [descr: Descr, out: STREAM, form: ArpaSMTPDescr.PrintForm ← long] = {
PutLine: PROC [r1, r2, r3, r4: ROPENIL] = {
out.PutRope[r1]; out.PutRope[r2]; out.PutRope[r3]; out.PutRope[r4]; out.PutChar['\n];
};
out.PutF["#%g (%g)\n", IO.int[descr.userHandle], IO.rope[descr.fileName]];
===== Recipients =====
WITH descr.recipients SELECT FROM
raw: REF raw RecipientListRep => {
out.PutRope["Recipients (raw): "];
FOR names: LIST OF ROPE ← raw.raw, names.rest UNTIL names = NIL DO
out.PutRope[names.first];
IF names.rest # NIL THEN out.PutRope[", "];
ENDLOOP;
out.PutChar['\n];
};
processed: REF processed RecipientListRep => {
out.PutRope["Recipients (processed): "];
FOR hosts: LIST OF HostAndUsers ← processed.processed, hosts.rest UNTIL hosts = NIL DO
hostAndUsers: HostAndUsers = hosts.first;
out.PutRope[hostAndUsers.host];
out.PutRope[": "];
FOR users: LIST OF ROPE ← hostAndUsers.users, users.rest UNTIL users = NIL DO
out.PutRope[users.first];
IF users.rest # NIL THEN out.PutRope[", "];
ENDLOOP;
IF hosts.rest # NIL THEN out.PutRope["; "];
ENDLOOP;
out.PutChar['\n];
};
ENDCASE;
===== Return Info =====
PutLine["Arpa Reverse Path: ", descr.arpaReversePath];
PutLine["GV Sender: ", descr.gvSender];
===== Dates =====
out.PutF["Create Date: %g\n", IO.time[descr.createDate]];
out.PutF["Expiry Date: %g\n", IO.time[descr.expiryDate]];
===== Item Format =====
PutLine["Item Format: ", valuesFormat[descr.format]];
===== Precede Message Text =====
PutLine["Precede Message Text: ", Convert.RopeFromRope[descr.precedeMsgText, TRUE]];
===== Return Path Line =====
PutLine["Return Path Line: ", Convert.RopeFromRope[descr.returnPathLine, TRUE]];
}; -- end Print
Unparse: PUBLIC PROC [descr: Descr, form: ArpaSMTPDescr.PrintForm ← short]
RETURNS [ROPE] = {
IF form = short THEN
RETURN[IO.PutFR["Msg #%g (%g)", IO.int[descr.userHandle], IO.rope[descr.fileName]]]
ELSE {
printStream: STREAM = IO.ROS[];
Print[descr, printStream, long];
RETURN[IO.RopeFromROS[printStream]];
};
};
CreateFailed: PUBLIC ERROR = CODE;
WrongState: PUBLIC ERROR = CODE;
PutOnBadQueue: PUBLIC ERROR = CODE;
----- Filing System -----
itemFileNamePrefix: ROPE = "[]<>MG>Q>Item-";
itemFileNamePrefixLength: INT = Rope.Length[itemFileNamePrefix];
Initialization
EnumerateInitialItems: PUBLIC PROC [proc: ArpaSMTPDescr.InitialItemProc] = {
ForEachItemFileDo: FS.InfoProc = {
PROC [fullFName, attachedTo: ROPE, created: BasicTime.GMT, bytes: INT, keep: CARDINAL] RETURNS [continue: BOOLEAN];
fileOK: BOOLTRUE;
simpleStart: INT;
fileName: ROPE;
descr: Descr;
continue ← TRUE;
Don't enumerate files created after the enumeration began (e.g. error report items to mgrs).
IF BasicTime.Period[from: enumerationStarted, to: created] >= 0 THEN RETURN;
IF Rope.Find[fullFName, "$"] > 0 THEN { -- Crash during reception
FS.Delete[fullFName];
RETURN; };
Remove the path prefix from filename so it will Rope.Equal what user types to ReadItemFile.
simpleStart ← Rope.Find[fullFName, itemFileNamePrefix, 0, FALSE];
IF simpleStart < 0 THEN RETURN; -- shouldn't occur
fileName ← Rope.Substr[fullFName, simpleStart];
descr ← RetrieveItemInfo[fileName ! BadItemFile => {fileOK ← FALSE; CONTINUE}];
IF fileOK THEN proc[descr];
RETURN;
};
enumerationStarted: BasicTime.GMT = BasicTime.Now[];
FS.EnumerateForInfo[Rope.Concat[itemFileNamePrefix, "*!H"], ForEachItemFileDo];
};
ReadItemFile: PUBLIC PROC [fileName: ROPE] RETURNS [Descr] = {
RETURN[RetrieveItemInfo[fileName ! BadItemFile =>
ERROR Failed[Rope.Cat["Unable to read ", fileName,
" as a valid item file. (See log for more details.)"]]]];
};
Failed: PUBLIC ERROR [reason: ROPE] = CODE;
Item Storage
titleInfoStartIndex: ROPE = "-------- Item Info starts at Position --------";
titleMsgBody: ROPE = "-------- Message Body --------";
titleRecipients: ROPE = "-------- Recipients --------";
titleInfoState: ROPE = "State:";
titleRecipientNames: ROPE = "Names:";
titleRecipientHost: ROPE = "Host:";
titleRecipientUsers: ROPE = "Users:";
titleReturnInfo: ROPE = "-------- Return Info --------";
titleReversePathVal: ROPE = "Path:";
titleDates: ROPE = "-------- Dates --------";
titleFormat: ROPE = "-------- Source and Format --------";
titlePrecedeMsgText: ROPE = "-------- Precede Message Text --------";
titleReturnPathLine: ROPE = "-------- Return Path Line --------";
valueStateRaw: ROPE = "raw";
valueStateProcessed: ROPE = "processed";
numPlaceHolder: ROPE = " "; -- Big enough for length of longest message
placeHolderFormat: ROPE = IO.PutFR["%%%dd", IO.int[Rope.Length[numPlaceHolder]]];
valuesFormat: ARRAY ArpaSMTPDescr.Format OF ROPE ← [gv: "GV", arpa: "ARPA"];
StoreEntireItem: PROC [descr: Descr, msgStream: STREAM] = {
tempName: ROPE = Rope.Cat[descr.fileName, "$"];
itemFile: STREAMNIL;
BEGIN
ENABLE {
FS.Error => {
ArpaSMTPSupport.Log[important,
"Unable to open stream/store entire item to \"", descr.fileName,
"\".\nFS reported \"", error.explanation,
"\".\nI will reject the incoming mail item."];
ERROR BadItemFile; };
UNWIND => IF itemFile # NIL THEN {itemFile.Close[]; FS.Delete[tempName]};
};
PutLine: PROC [line: ROPE] = INLINE {itemFile.PutRope[line]; itemFile.PutChar['\n]};
indexIndex, nBytesRead: INT;
buffer: REF TEXT;
itemFile ← FS.StreamOpen[fileName: tempName, accessOptions: create];
===== Info Start Index =====
PutLine[titleInfoStartIndex];
indexIndex ← itemFile.GetIndex[];
PutLine[numPlaceHolder];
===== Message Body =====
PutLine[titleMsgBody];
buffer ← RefText.ObtainScratch[512];
DO
nBytesRead ← msgStream.GetBlock[buffer]; -- catch stream failure signal (globally?)
itemFile.PutBlock[block: buffer, count: nBytesRead];
IF nBytesRead < buffer.maxLength THEN EXIT;
ENDLOOP;
===== Info Start Index (continued) =====
descr.infoStartIndex ← itemFile.GetIndex[];
itemFile.SetIndex[indexIndex];
itemFile.PutF[placeHolderFormat, IO.int[descr.infoStartIndex]];
itemFile.SetIndex[descr.infoStartIndex];
StoreItemInfoOnFileStream[descr, itemFile];
itemFile.Close[];
END;
FS.Rename[from: tempName, to: descr.fileName];
}; -- end StoreEntireItem
LookAtThis: SIGNAL = CODE;
StoreItemInfo: PUBLIC PROC [descr: Descr] = {
itemFile: STREAMNIL;
BEGIN
ENABLE {
FS.Error => {
IF error.group = lock THEN { ArpaSMTPSupport.Log[
important,
"Unable to update item file (lock conflict): ", descr.fileName,
".\nFS reported: ", error.explanation,
"This happens when we send a msg to GV and ARPA at the same time. Plunging on."];
CONTINUE; };
SIGNAL LookAtThis; ArpaSMTPSupport.Log[
ATTENTION,
"Unable to open/update item file: ", descr.fileName,
".\nFS reported: ", error.explanation,
"\nContinuing without saving the change (message may be multiply sent)."];
IF itemFile # NIL THEN {itemFile.Close[]; itemFile ← NIL};
CONTINUE;
};
UNWIND => IF itemFile # NIL THEN {itemFile.Close[]; itemFile ← NIL};
};
itemFile ← FS.StreamOpen[fileName: descr.fileName, accessOptions: write];
StoreItemInfoOnFileStream[descr, itemFile];
END;
};
StoreItemInfoOnFileStream: PROC [descr: Descr, itemFile: STREAM] = {
Newline: PROC = INLINE {itemFile.PutChar['\n]};
PutLine: PROC [line: ROPE] = INLINE {itemFile.PutRope[line]; Newline[]};
endIndex, textEndIndexIndex: INT;
itemFile.SetIndex[descr.infoStartIndex];
===== Recipients =====
PutLine[NIL]; -- separate from msg w/newline
PutLine[titleRecipients];
PutLine[titleInfoState];
WITH descr.recipients SELECT FROM
raw: REF raw RecipientListRep => {
numNames: INT ← 0;
numNamesIndex: INT;
PutLine[valuesRecipientState[raw]];
PutLine[titleRecipientNames];
numNamesIndex ← itemFile.GetIndex[];
PutLine[numPlaceHolder];
FOR names: LIST OF ROPE ← raw.raw, names.rest UNTIL names = NIL DO
PutLine[names.first];
numNames ← numNames + 1;
ENDLOOP;
endIndex ← itemFile.GetIndex[];
itemFile.SetIndex[numNamesIndex];
itemFile.PutF[placeHolderFormat, IO.int[numNames]];
itemFile.SetIndex[endIndex];
};
processed: REF processed RecipientListRep => {
numHosts: INT ← 0;
numHostsIndex: INT;
PutLine[valuesRecipientState[processed]];
numHostsIndex ← itemFile.GetIndex[];
PutLine[numPlaceHolder];
FOR hosts: LIST OF HostAndUsers ← processed.processed, hosts.rest
UNTIL hosts = NIL DO
numUsers: INT ← 0;
numUsersIndex: INT;
hostAndUsers: HostAndUsers = hosts.first;
PutLine[titleRecipientHost];
PutLine[hostAndUsers.host];
PutLine[titleRecipientUsers];
numUsersIndex ← itemFile.GetIndex[];
PutLine[numPlaceHolder];
FOR users: LIST OF ROPE ← hostAndUsers.users, users.rest UNTIL users = NIL DO
PutLine[users.first];
numUsers ← numUsers + 1;
ENDLOOP;
endIndex ← itemFile.GetIndex[];
itemFile.SetIndex[numUsersIndex];
itemFile.PutF[placeHolderFormat, IO.int[numUsers]];
itemFile.SetIndex[endIndex];
numHosts ← numHosts + 1;
ENDLOOP;
endIndex ← itemFile.GetIndex[];
itemFile.SetIndex[numHostsIndex];
itemFile.PutF[placeHolderFormat, IO.int[numHosts]];
itemFile.SetIndex[endIndex];
};
ENDCASE;
===== Return Info =====
PutLine[titleReturnInfo];
PutLine[descr.arpaReversePath];
PutLine[descr.gvSender];
===== Dates =====
PutLine[titleDates];
itemFile.Put[IO.time[descr.createDate]]; Newline[];
itemFile.Put[IO.time[descr.expiryDate]]; Newline[];
===== Source and Format =====
PutLine[titleFormat];
PutLine[descr.source];
PutLine[valuesFormat[descr.format]];
===== Precede Message Text =====
PutLine[titlePrecedeMsgText];
textEndIndexIndex ← itemFile.GetIndex[];
PutLine[numPlaceHolder];
itemFile.PutRope[descr.precedeMsgText]; -- may contain newlines
endIndex ← itemFile.GetIndex[];
itemFile.SetIndex[textEndIndexIndex];
itemFile.PutF[placeHolderFormat, IO.int[endIndex]];
itemFile.SetIndex[endIndex];
PutLine[NIL]; -- ensure separated from next field by newline
===== Return Path Line =====
PutLine[titleReturnPathLine];
itemFile.PutRope[descr.returnPathLine]; -- may contain newlines
itemFile.SetLength[itemFile.GetIndex[]];
itemFile.Close[];
}; -- end StoreItemInfo
ItemRetrieval
OpenMsgFileStream: PROC [fileName: ROPE, maxMsgIndex: INT] RETURNS [STREAM] = {
itemFile: STREAMNIL;
BEGIN
ENABLE {
FS.Error => { ArpaSMTPSupport.Log[ATTENTION,
"Unable to open stream/read message body from \"", fileName,
"\".\nFS reported \"", error.explanation,
"\".\nI will place descriptor on BadQueue. ",
"Edit file and Requeue descriptor."];
IF itemFile # NIL THEN {itemFile.Close[]; itemFile ← NIL};
ERROR BadItemFile;
};
BadItemFile => { ArpaSMTPSupport.Log[ATTENTION,
"Corrupt item file \"", fileName,
"\", at Position ", Convert.RopeFromInt[position],
".\nExpected \"", expected, "\", but found \"", found,
"\".\nI will place descriptor on BadQueue. Edit file and Requeue descriptor."];
IF itemFile # NIL THEN {itemFile.Close[]; itemFile ← NIL};
ERROR BadItemFile;
};
UNWIND => IF itemFile # NIL THEN itemFile.Close[];
};
GetLine: PROC [expected: ROPE] RETURNS [ROPE] = {
RETURN[itemFile.GetLineRope[! IO.EndOfStream =>
ERROR BadItemFile[expected, "EndOfFile", itemFile.GetIndex[]]]]
}; -- catches all EOFs since all reading done here
CheckLine: PROC [expected: ROPE] = {
found: ROPE = GetLine[expected];
IF NOT Rope.Equal[expected, found] THEN
ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)];
};
itemFile: IO.STREAMFS.StreamOpen[fileName];
===== Info Start Index =====
CheckLine[titleInfoStartIndex];
[] ← GetLine["Info Start Index"];
===== Message Body =====
CheckLine[titleMsgBody];
RETURN[ArpaSMTPSupport.CreateSubrangeStream[
origStream: itemFile, min: itemFile.GetIndex[], max: maxMsgIndex]];
END;
}; -- end OpenMsgFileStream
RetrieveItemInfo: PUBLIC PROC [fileName: ROPE] RETURNS [descr: Descr] = {
itemFile: STREAMNIL;
BEGIN
ENABLE {
FS.Error => { ArpaSMTPSupport.Log[ATTENTION,
"Unable to open/read item file \"", fileName,
"\".\nFS reported \"", error.explanation,
"\".\nEdit file and InitRead descriptor."];
IF itemFile # NIL THEN itemFile.Close[];
ERROR BadItemFile; -- caught by EnumerateInitialItems and ReadItemFile
};
BadItemFile => { ArpaSMTPSupport.Log[ATTENTION,
"Corrupt item file \"", fileName,
"\", at Position ", Convert.RopeFromInt[position],
".\nExpected \"", expected, "\", but found \"", found,
"\".\nIgnoring it; edit it and reread."];
IF itemFile # NIL THEN {itemFile.Close[]; itemFile ← NIL};
ERROR BadItemFile; -- caught by EnumerateInitialItems and ReadItemFile
};
UNWIND => IF itemFile # NIL THEN {itemFile.Close[]; itemFile ← NIL};
};
GetLine: PROC [expected: ROPE] RETURNS [ROPE] = {
RETURN[itemFile.GetLineRope[! IO.EndOfStream =>
ERROR BadItemFile[expected, "EndOfFile", itemFile.GetIndex[]]]]
}; -- catches all EOFs since all reading done here
CheckLine: PROC [expected: ROPE] = {
found: ROPE = GetLine[expected];
IF NOT Rope.Equal[expected, found] THEN
ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)];
};
GetInt: PROC [intName: ROPE] RETURNS [INT] = {
found: ROPE = GetLine[intName];
RETURN[Convert.IntFromRope[found ! Convert.Error =>
ERROR BadItemFile[intName, found, itemFile.GetIndex[]-(found.Length[]+1)]]];
};
GetRecipientState: PROC RETURNS [RecipientState] = {
expected: ROPE = "info state value (\"raw\" or (\"processed\")";
state: ROPE = GetLine[expected];
IF Rope.Equal[state, valuesRecipientState[raw]] THEN RETURN[raw];
IF Rope.Equal[state, valuesRecipientState[processed]] THEN RETURN[processed];
ERROR BadItemFile[expected, state, itemFile.GetIndex[]-(found.Length[]+1)];
};
EverythingProc: IO.BreakProc = {RETURN[other]};
recipients: RecipientList;
arpaReversePath, gvSender: ROPE;
createDate, expiryDate: BasicTime.GMT;
source: ROPE;
format: ArpaSMTPDescr.Format;
returnPathLine: ROPE;
precedeMsgText: ROPE;
infoStartIndex: INT;
textEndIndex: INT;
expected, found: ROPE;
itemFile ← FS.StreamOpen[fileName: fileName];
===== Info Start Index =====
itemFile.SetIndex[0];
CheckLine[titleInfoStartIndex];
infoStartIndex ← GetInt["Info Start Index"];
itemFile.SetIndex[infoStartIndex];
===== Recipients =====
CheckLine[NIL]; -- separated from msg w/newline
CheckLine[titleRecipients];
CheckLine[titleInfoState];
SELECT GetRecipientState[] FROM
raw => {
names: LIST OF ROPENIL;
CheckLine[titleRecipientNames];
THROUGH [0..GetInt["number of recipient names (INT)"]) DO
names ← CONS[GetLine["recipient name"], names];
ENDLOOP;
recipients ← NEW[raw RecipientListRep ← [raw[names]]];
};
processed => {
hosts: LIST OF HostAndUsers ← NIL;
THROUGH [0..GetInt["number of recipient hosts (INT)"]) DO
host: ROPE;
users: LIST OF ROPENIL;
CheckLine[titleRecipientHost];
host ← GetLine["recipient host name"];
CheckLine[titleRecipientUsers];
THROUGH [0..GetInt["number of users for given host (INT)"]) DO
users ← CONS[GetLine["recipient user name"], users];
ENDLOOP;
hosts ← CONS[HostAndUsers[host, users], hosts];
ENDLOOP;
recipients ← NEW[processed RecipientListRep ← [processed[hosts]]];
};
ENDCASE;
===== Return Info =====
CheckLine[titleReturnInfo];
expected ← "ARPA Return Path";
arpaReversePath ← GetLine[expected];
IF arpaReversePath.Length[] = 0 THEN arpaReversePath ← NIL;
expected ← "GV Sender";
gvSender ← GetLine[expected];
IF gvSender.Length[] = 0 THEN gvSender ← NIL;
===== Dates =====
CheckLine[titleDates];
expected ← "Create date";
found ← GetLine[expected];
createDate ← Convert.TimeFromRope[found ! Convert.Error =>
ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)]];
expected ← "Expiry date";
found ← GetLine[expected];
expiryDate ← Convert.TimeFromRope[found ! Convert.Error =>
ERROR BadItemFile[expected, found, itemFile.GetIndex[]-(found.Length[]+1)]];
===== Item Format =====
CheckLine[titleFormat];
expected ← "Source IP Address ([nnn.nnn.nnn.nnn])";
source ← GetLine[expected];
expected ← "item format (\"ARPA\" or \"GV\")";
found ← GetLine[expected];
SELECT TRUE FROM
Rope.Equal[found, valuesFormat[arpa]] => format ← arpa;
Rope.Equal[found, valuesFormat[gv]] => format ← gv;
ENDCASE => ERROR BadItemFile[expected, found, itemFile.GetIndex[]-found.Length[]-1];
===== Precede Message Text =====
CheckLine[titlePrecedeMsgText];
textEndIndex ← GetInt["Precede Msg Text end index"];
precedeMsgText ← ArpaSMTPSupport.RopeFromSubrange[ -- Empty => NIL
origStream: itemFile, min: itemFile.GetIndex[], max: textEndIndex];
CheckLine[NIL];
===== Return Path Line =====
CheckLine[titleReturnPathLine];
returnPathLine ← NIL; -- In case of empty
returnPathLine ← itemFile.GetTokenRope[EverythingProc ! IO.EndOfStream => CONTINUE].token;
itemFile.Close[];
descr ← NEW[DescrRep ← [
userHandle: UniqueUserHandle[],
arpaReversePath: arpaReversePath,
gvSender: gvSender,
recipients: recipients,
createDate: createDate,
expiryDate: expiryDate,
source: source,
format: format,
precedeMsgText: precedeMsgText,
returnPathLine: returnPathLine,
fileName: fileName,
infoStartIndex: infoStartIndex]];
RETURN[descr];
END; };
BadItemFile: ERROR [expected, found: ROPENIL, position: INT ← 0] = CODE;
Misc
UniqueUserHandle: ENTRY PROC RETURNS [INT] = {
nextUserHandle ← nextUserHandle + 1;
RETURN[nextUserHandle];
};
UniqueFileName: ENTRY PROC RETURNS [ROPE] = {
timeLiteral: ROPE;
[timeLiteral, ] ← ArpaSMTPSupport.Now[compressed: TRUE];
timeStampPostfix ← timeStampPostfix + 1;
RETURN[Rope.Cat[
itemFileNamePrefix, timeLiteral, "-", Convert.RopeFromInt[timeStampPostfix]]];
};
timeStampPostfix: INT ← 1000;
nextUserHandle: INT ← 1000;
END.