-- SendParser.mesa
-- Edited by Levin, February 17, 1981 10:49 AM
-- Edited by Schroeder, March 5, 1981 2:07 PM
-- Edited by Brotz, March 7, 1983 11:16 AM

DIRECTORY
Ascii USING [CR, SP],
Core USING [DMSUser],
csD: FROM "CoreStreamDefs" USING [Destroy, EndOfStream, Open, Read, Reset,
SetPosition, StreamHandle, WriteBlock],
dsD: FROM "DisplayDefs" USING [SetCursor],
exD: FROM "ExceptionDefs" USING [cantExpand, deliveryToDL, DisplayException,
expandingMsg],
LaurelSendDefs USING [AbortPoint, FromState, GetCharPosition, InitReadChar,
InsertRecipientInList, InsertReplyToField, MoveUnderline, ReadChar, ReportError,
ReportProgress, RetryThis, SendMode],
MailParseDefs USING [endOfList, FinalizeParse, GetFieldBody, GetFieldName,
InitializeParse, maxFieldNameSize, maxRecipientLength, ParseError, ParseHandle,
ParseNameList],
opD: FROM "OperationsDefs" USING [Expand, FileError],
Storage USING [Free, Node],
String USING [AppendChar, AppendString, AppendSubString, EquivalentString,
StringBoundsFault, SubStringDescriptor],
vmD: FROM "VirtualMgrDefs" USING [CharIndex];

SendParser: PROGRAM
IMPORTS csD, exD, dsD, LaurelSendDefs, MailParseDefs, opD, Storage, String
EXPORTS LaurelSendDefs =

BEGIN
OPEN LaurelSendDefs;


ParseForSend: PUBLIC PROCEDURE [user: Core.DMSUser, expandPublicDLs: BOOLEAN,
sendMode: SendMode, userFeedback: BOOLEAN]
RETURNS [unexpandedPublicDLs: CARDINAL, fromState: FromState, replyTo: BOOLEAN] =
BEGIN
pH: MailParseDefs.ParseHandle;

-- DL Expander

DLEntry: TYPE = RECORD
[next: DLHandle,
start, end: vmD.CharIndex,
isFile: BOOLEAN,
name: StringBody];
DLHandle: TYPE = POINTER TO DLEntry;

dlHead, currentDL: DLHandle ← NIL;
dlBuffer: csD.StreamHandle;

AcceptDL: PROCEDURE [name: STRING, isFile: BOOLEAN] =
BEGIN
dl: DLHandle;
lastDL: DLHandle ← NIL;
FOR dl ← dlHead, dl.next UNTIL dl = NIL DO
IF String.EquivalentString[name, @dl.name] THEN RETURN;
lastDL ← dl;
ENDLOOP;
dl ← Storage.Node[SIZE[DLEntry]+(name.length+1)/2];
dl↑ ← [next: NIL, start: errorStart, end: errorEnd-1, isFile: isFile,
name: [length: 0, maxlength: name.length, text: ]];
IF currentDL # NIL THEN BEGIN
dl.start ← currentDL.start;
dl.end ← currentDL.end;
END;
String.AppendString[@dl.name, name];
IF lastDL = NIL
THEN dlHead ← dl
ELSE lastDL.next ← dl;
END; -- of AcceptDL --

FlushDLList: PROCEDURE =
BEGIN
dl: DLHandle;
UNTIL dlHead = NIL DO
dl ← dlHead.next;
Storage.Free[dlHead];
dlHead ← dl;
ENDLOOP;
csD.Destroy[dlBuffer];
END; -- of FlushDLList --

ProcessDLList: PROCEDURE =
BEGIN
char: CHARACTER;
havePH: BOOLEAN ← FALSE;

GetDLChar: PROCEDURE RETURNS [CHARACTER] =
BEGIN
char ← csD.Read[dlBuffer
! csD.EndOfStream => {char ← MailParseDefs.endOfList; CONTINUE}];
IF char = Ascii.CR THEN char ← Ascii.SP;
RETURN[char];
END; -- of GetDLChar --

ReportProgress[exD.expandingMsg, NIL, TRUE];
dlBuffer ← csD.Open[NIL, byte, write];
BEGIN ENABLE UNWIND =>
{IF havePH THEN MailParseDefs.FinalizeParse[pH]; FlushDLList[]};
FOR currentDL ← dlHead, currentDL.next UNTIL currentDL = NIL DO
MoveUnderline[currentDL.start, currentDL.end];
publicDLExpansion ← ~currentDL.isFile;
DO
CopyFileToCoreStream[dlBuffer, @currentDL.name
! opD.FileError =>
BEGIN
AbortPoint[];
IF reason = cantConnect AND RetryThis[NIL, exD.cantExpand] THEN
{ReportProgress[exD.expandingMsg, NIL, TRUE]; LOOP};
ReportError[dlExpandError, errorString, currentDL.start, currentDL.end];
END];
EXIT;
ENDLOOP;
AbortPoint[];
pH ← MailParseDefs.InitializeParse[GetDLChar]; havePH ← TRUE;
MailParseDefs.ParseNameList[pH, ProcessName
! MailParseDefs.ParseError, String.StringBoundsFault =>
ReportError[dlSyntaxError, NIL, currentDL.start, currentDL.end]];
MailParseDefs.FinalizeParse[pH]; havePH ← FALSE;
csD.Reset[dlBuffer];
ENDLOOP;
END; -- ENABLE UNWIND
FlushDLList[];
END; -- of ProcessDLList --


ProcessName: PROCEDURE [name, registry: STRING, isFile, ignored: BOOLEAN]
RETURNS [write: BOOLEAN] =
BEGIN
publicDL, noRegistry: BOOLEAN;
recipient: STRING ← [MailParseDefs.maxRecipientLength];
errorEnd ← GetCharPosition[];
IF isFile AND name[0] = ’" THEN
BEGIN
-- remove quotes from file names (private dls). Ignore / quoting convention-there
-- shouldn’t be strange characters in private dl names anyway.
snDesc: String.SubStringDescriptor ← [name, 1, name.length - 2];
String.AppendSubString[recipient, @snDesc];
END
ELSE String.AppendString[recipient, name];
SELECT TRUE FROM
(noRegistry ← registry.length = 0) AND publicDLExpansion =>
-- catches file name or no registry in remote DL
ReportError[illegalRecipient, NIL, currentDL.start, currentDL.end];
isFile => AcceptDL[recipient, TRUE];
(publicDL ← recipient[recipient.length - 1] = ’↑) AND expandPublicDLs =>
BEGIN
String.AppendChar[recipient, ’.];
String.AppendString[recipient, IF noRegistry THEN user.registry ELSE registry];
AcceptDL[recipient, FALSE];
END;
ENDCASE =>
BEGIN
String.AppendChar[recipient, ’.];
String.AppendString[recipient, IF noRegistry THEN user.registry ELSE registry];
InsertRecipientInList[recipient];
haveRecipient ← TRUE;
IF publicDL THEN unexpandedPublicDLs ← unexpandedPublicDLs + 1;
END;
AbortPoint[];
errorStart ← errorEnd;
RETURN[FALSE]
END; -- of ProcessName --

ProcessFromName: PROCEDURE [name, registry: STRING, isFile, ignored: BOOLEAN]
RETURNS [write: BOOLEAN] =
BEGIN
SELECT fromState FROM
needsFrom =>
IF String.EquivalentString[name, user.name]
AND String.EquivalentString[registry, user.registry]
THEN fromState ← ok
ELSE fromState ← needsSender;
ENDCASE => fromState ← needsSender;
END; -- of ProcessFromName --

-- beginning of code of ParseForSend

fieldName: STRING ← [MailParseDefs.maxFieldNameSize];
publicDLExpansion, haveRecipient: BOOLEAN ← FALSE;
errorStart, errorEnd: vmD.CharIndex;

unexpandedPublicDLs ← 0;
replyTo ← FALSE;
fromState ← needsFrom;

InitReadChar[];
pH ← MailParseDefs.InitializeParse[ReadChar];

DO -- Until message processed or error detected
errorStart ← GetCharPosition[];
IF ~MailParseDefs.GetFieldName
[pH, fieldName ! MailParseDefs.ParseError => GO TO syntaxError]
THEN EXIT;
IF String.EquivalentString[fieldName, "Date"L]
OR String.EquivalentString[fieldName, "Sender"L] THEN GOTO syntaxError;
errorStart ← GetCharPosition[];
SELECT TRUE FROM
String.EquivalentString[fieldName, "To"L],
String.EquivalentString[fieldName, "cc"L],
String.EquivalentString[fieldName, "c"L],
String.EquivalentString[fieldName, "bcc"L] =>
MailParseDefs.ParseNameList[pH, ProcessName
! MailParseDefs.ParseError, String.StringBoundsFault => GO TO syntaxError];
String.EquivalentString[fieldName, "From"L] =>
BEGIN
IF fromState # needsFrom THEN GO TO syntaxError;
MailParseDefs.ParseNameList[pH, ProcessFromName
! MailParseDefs.ParseError, String.StringBoundsFault => GO TO syntaxError];
IF fromState = needsFrom THEN GO TO syntaxError;
END;
ENDCASE =>
BEGIN
IF String.EquivalentString[fieldName, "Reply-To"L] THEN replyTo ← TRUE;
MailParseDefs.GetFieldBody
[pH, fieldName ! MailParseDefs.ParseError => GO TO syntaxError];
END;
REPEAT syntaxError =>
BEGIN
MailParseDefs.FinalizeParse[pH];
errorEnd ← GetCharPosition[];
ReportError[messageSyntaxError, NIL, MIN[errorStart, errorEnd - 1], errorEnd];
END;
ENDLOOP; -- Main message parsing loop
MailParseDefs.FinalizeParse[pH];
IF userFeedback AND sendMode = blue AND ~replyTo
AND (dlHead # NIL OR unexpandedPublicDLs # 0)
THEN {exD.DisplayException[exD.deliveryToDL]; InsertReplyToField[user]};
IF dlHead # NIL THEN ProcessDLList[];
IF ~haveRecipient THEN ReportError[noRecipientsSpecified, NIL, 0, 0];
END; -- of ParseForSend --


CopyFileToCoreStream: PROCEDURE [stream: csD.StreamHandle, file: STRING] =
-- Assumes that stream is write, byte, positioned at 0. Will copy contents of file
-- into stream, using a backing temp file if the file is longer than one page.
BEGIN

AcceptBlock: PROCEDURE [p: POINTER, bytes: CARDINAL]
RETURNS [BOOLEAN] =
BEGIN
IF bytes > 0 THEN csD.WriteBlock[stream, p, 0, bytes];
RETURN[TRUE];
END; -- of AcceptBlock --

opD.Expand[file, AcceptBlock];
dsD.SetCursor[hourGlass];
csD.SetPosition[stream, 0];
END; -- of CopyFileToCoreStream --


END. -- of SendParser --