-- MailStampFormat
-- Edited by Horning, January 18, 1978 10:00 AM.
-- Edited by Schroeder, February 24, 1981 5:14 PM.
-- Edited by Levin, February 25, 1981 9:55 AM.
-- Edited by Brotz, March 4, 1983 10:07 AM.

DIRECTORY
Ascii USING [CR, NUL, SP],
intCommon USING [user],
MailParseDefs USING [endOfInput, FinalizeParse, GetFieldBody, GetFieldName,
InitializeParse, maxRecipientLength, ParseError, ParseHandle, ParseNameList],
mfD: FROM "MailFormatDefs" USING [],
opD: FROM "OperationsDefs" USING [substringSeparator],
String USING [AppendChar, AppendDecimal, AppendString, EquivalentString,
StringBoundsFault, StringToNumber],
vmD: FROM "VirtualMgrDefs" USING [TOCFixedPartPtr];

MailStampFormat: PROGRAM
IMPORTS intC: intCommon, MailParseDefs, String
EXPORTS mfD =

BEGIN


BogusNumber: ERROR = CODE;

ParseStamp: PUBLIC PROCEDURE
[NextChar: PROC RETURNS [CHARACTER], tp: vmD.TOCFixedPartPtr]
RETURNS [stampOk: BOOLEAN] =
-- Reads the stamp with NextChar and fills into the TOCFixedPart located by tp the
-- deleted, seen, mark, offsetToHeader, and textLength fields. Leaves NextChar ready to
-- read the first character following the fixed part of the stamp. Expects NextChar to
-- return a null character if no more characters remain to be gotten. Returns FALSE if
-- could not parse a complete stamp.
BEGIN
startOfStamp: STRING = "*start*
"L;
i, j: CARDINAL;

ReadFive: PROCEDURE RETURNS [k: CARDINAL] =
BEGIN
char: CHARACTER;
k ← 0;
THROUGH [0 .. 5) DO
IF (char ← NextChar[]) ~IN [’0 .. ’9] THEN ERROR BogusNumber;
k ← k * 10 + (char - ’0);
ENDLOOP;
END; -- of ReadFive --

FOR i IN [0 .. 8) DO IF startOfStamp[i] # NextChar[] THEN GOTO notAStamp; ENDLOOP;

i ← ReadFive[ ! BogusNumber => GOTO notAStamp];
IF NextChar[] # Ascii.SP THEN GOTO notAStamp;
j ← ReadFive[ ! BogusNumber => GOTO notAStamp];
IF NextChar[] # Ascii.SP THEN GOTO notAStamp;

SELECT NextChar[] FROM
’D => tp.deleted ← TRUE;
’U => tp.deleted ← FALSE;
ENDCASE => GOTO notAStamp;

SELECT NextChar[] FROM
’S => tp.seen ← TRUE;
’U => tp.seen ← FALSE;
ENDCASE => GOTO notAStamp;

tp.offsetToHeader ← j;
IF i < j THEN GOTO notAStamp;
tp.textLength ← i - j;
tp.mark ← NextChar[];
IF tp.mark = Ascii.NUL OR NextChar[] # Ascii.CR THEN GOTO notAStamp;
tp.bogus ← FALSE;
RETURN[TRUE];

EXITS
notAStamp => {tp.bogus ← TRUE; RETURN[FALSE]};
END; -- of ParseStamp --


CreateStamp: PUBLIC PROC [tp: vmD.TOCFixedPartPtr, PutChar: PROC [CHARACTER]] =
-- This procedure may be used to update an existing stamp. The code discriminates
-- new/old by the value of offsetToHeader in tp. This should accordingly be set to 0
-- when a wholly new stamp is wanted. The practical effects concern only the
-- offsetToHeader afterwards, which has its old value in the update case and a standard
-- value in the genuine create case.
BEGIN

BinDec: PROCEDURE [i: CARDINAL] =
BEGIN
n: CARDINAL ← 10000;
j, k: CARDINAL;
FOR k IN [0 .. 3] DO
j ← 0;
WHILE i >= n DO
j ← j + 1;
i ← i - n;
ENDLOOP;
PutChar[’0 + j];
n ← n / 10;
ENDLOOP;
PutChar[’0 + i];
END; -- of BinDec --

fixedStamp: STRING = "*start*
"L;
stampLength: CARDINAL = 24; --REMEMBER TO UPDATE THIS IF FORMAT CHANGED!
i, j: CARDINAL;

FOR i IN [0 .. fixedStamp.length) DO
PutChar[fixedStamp[i]];
ENDLOOP;
IF tp.offsetToHeader = 0 THEN tp.offsetToHeader ← stampLength;
--fixed length part of stamp; whole of a new one
j ← tp.offsetToHeader + tp.textLength;
BinDec[j]; PutChar[Ascii.SP]; --put out total text length
BinDec[tp.offsetToHeader]; PutChar[Ascii.SP]; --offset to header
PutChar[IF tp.deleted THEN ’D ELSE ’U];
PutChar[IF tp.seen THEN ’S ELSE ’U]; --but we may for seen ones
PutChar[tp.mark];
PutChar[Ascii.CR]; --fixed end
END; -- of CreateStamp --


ParseHeaderForTOC: PUBLIC PROCEDURE
[s: STRING, next: PROCEDURE RETURNS [CHARACTER]] =
-- Produces in ’s’ the TOC string that goes with the message whose characters ’next’ is
-- prepared to deliver.
BEGIN
OPEN mfD;

discardS: STRING ← [0];
which: STRING;
ph: MailParseDefs.ParseHandle ← MailParseDefs.InitializeParse[next];
fromS: STRING ← [100];
toS: STRING ← [MailParseDefs.maxRecipientLength];
dateS: STRING ← [25];
subjS: STRING ← [250];
sender: STRING ← [MailParseDefs.maxRecipientLength];
useFromS: BOOLEAN ← FALSE;

StandardizeDate: PROCEDURE [s: STRING] =
BEGIN
AtomType: TYPE = {none, number, alpha};
ix: CARDINAL ← 0;
i: [1 .. 12];
numbers: ARRAY [0 .. 1] OF [0 .. 31];
numbersSeen: CARDINAL ← 0;
atom: STRING = [3];
month: CARDINAL ← 0;
got: CARDINAL ← 0;
months: ARRAY [1 .. 12] OF STRING =
["Jan"L, "Feb"L, "Mar"L, "Apr"L, "May"L, "Jun"L,
"Jul"L, "Aug"L, "Sep"L, "Oct"L, "Nov"L, "Dec"L];

GetChar: PROCEDURE RETURNS [char: CHARACTER] = INLINE
BEGIN
IF ix >= s.length THEN RETURN[0C];
char ← s[ix];
ix ← ix + 1;
END; -- of GetChar --

CollectAtom: PROCEDURE [out: STRING] RETURNS [type: AtomType] = INLINE
BEGIN
char: CHARACTER;

Append: PROCEDURE =
{IF out.length < out.maxlength THEN String.AppendChar[out, char]};

out.length ← 0;
type ← none;
DO
char ← GetChar[];
SELECT char FROM
0C => RETURN;
IN [’0..’9] => IF type = alpha THEN EXIT ELSE {type ← number; Append[]};
IN [’a .. ’z], IN [’A .. ’Z] =>
IF type = number THEN EXIT ELSE {type ← alpha; Append[]};
ENDCASE => IF type ~= none THEN RETURN;
ENDLOOP;
ix ← ix - 1;
END; -- of CollectAtom --

UNTIL got = 3 DO
SELECT CollectAtom[atom] FROM
alpha =>
IF month = 0 THEN
FOR i IN [1 .. 12] DO
IF String.EquivalentString[months[i], atom] THEN
{month ← i; got ← got + 1; EXIT};
ENDLOOP;
number =>
IF numbersSeen < 2
AND (numbers[numbersSeen] ← String.StringToNumber[atom, 10]) <= 31
THEN {numbersSeen ← numbersSeen + 1; got ← got + 1};
ENDCASE => EXIT;
ENDLOOP;
s.length ← 0;
IF numbersSeen = 0 THEN GO TO GarbageDate;
IF month = 0 THEN
{IF numbersSeen < 2 OR (month ← numbers[0]) ~IN [1 .. 12]
OR (i ← numbers[1]) ~IN [1 .. 31]
THEN GO TO GarbageDate}
ELSE IF (i ← numbers[0]) ~IN [1 .. 31] THEN GO TO GarbageDate;
String.AppendString[s, months[month]]; IF month ~= 5 THEN String.AppendChar[s, ’.];
String.AppendChar[s, ’ ];
String.AppendDecimal[s, i];

EXITS
GarbageDate => String.AppendString[s, "bad date"L];
END; -- of StandardizeDate --

AppendToOrFrom: PROCEDURE =
-- If mail is from self (stripping off possible host name and/or registry), append
-- "To: <recipient>".
BEGIN
SELECT TRUE FROM
fromS.length = 0 => String.AppendString[s, "????"L];
String.EquivalentString[sender, intC.user.name] AND toS.length > 0 =>
{String.AppendString[s, "To: "L]; String.AppendString[s, toS]};
useFromS => String.AppendString[s, fromS];
ENDCASE => String.AppendString[s, sender];
END; -- of AppendToOrFrom --

ProcessFrom: PROCEDURE[name, reg: STRING, ignored1, isNested: BOOLEAN]
RETURNS [BOOLEAN] =
BEGIN
IF sender.length = 0 THEN
BEGIN
useFromS ← isNested;
IF String.EquivalentString[reg, intC.user.registry] THEN reg.length ← 0;
BEGIN ENABLE String.StringBoundsFault => GO TO Truncate;
name.length ← MIN[name.length, sender.maxlength];
String.AppendString[sender, name];
IF reg.length # 0 THEN
{String.AppendChar[sender, ’.]; String.AppendString[sender, reg]};
EXITS
Truncate => NULL;
END;
END
ELSE useFromS ← TRUE;
RETURN[FALSE]
END; -- of ProcessFrom --

GetFromFromS: PROCEDURE RETURNS [CHARACTER] =
BEGIN
IF fromSIndex >= fromS.length THEN RETURN[MailParseDefs.endOfInput];
fromSIndex ← fromSIndex + 1;
RETURN[fromS[fromSIndex - 1]];
END; -- of GetFromFromS --

fromSIndex: CARDINAL ← 0;
fromPH: MailParseDefs.ParseHandle;

DO
IF ~MailParseDefs.GetFieldName[ph, s ! MailParseDefs.ParseError => EXIT] THEN EXIT;
SELECT TRUE FROM
String.EquivalentString[s, "From"L] => which ← fromS;
String.EquivalentString[s, "To"L] => which ← toS;
String.EquivalentString[s, "Date"L] => which ← dateS;
String.EquivalentString[s, "Subject"L] => which ← subjS;
ENDCASE => which ← discardS;
MailParseDefs.GetFieldBody
[ph, which, FALSE ! MailParseDefs.ParseError => CONTINUE];
ENDLOOP;

fromPH ← MailParseDefs.InitializeParse[GetFromFromS];
MailParseDefs.ParseNameList[fromPH, ProcessFrom
! MailParseDefs.ParseError => {sender.length ← 0; useFromS ← TRUE; CONTINUE}];
MailParseDefs.FinalizeParse[fromPH];

s.length ← 0;
StandardizeDate[dateS];
String.AppendString[s, dateS];
String.AppendChar[s, opD.substringSeparator];
AppendToOrFrom[ ! String.StringBoundsFault => CONTINUE];
IF s.length = s.maxlength THEN s.length ← s.length - 1;
String.AppendChar[s, opD.substringSeparator];
IF s.length + subjS.length > s.maxlength THEN subjS.length ← s.maxlength - s.length;
String.AppendString[s, subjS];
MailParseDefs.FinalizeParse[ph];
END; -- of ParseHeaderForTOC --


END. -- of MailStampFormat --