-- Mail file patcher

-- MailFilePatchImpl.mesa

-- Andrew Birrell  11-Mar-82 10:52:41

DIRECTORY
Ascii,
GlassDefs,
MailParse,
Segments	USING[ FileNameProblem ],
Streams,
String;

MailFilePatchImpl: PROGRAM
   IMPORTS GlassDefs, MailParse, Segments, Streams, String =

BEGIN

DoIt: PROC[str: GlassDefs.Handle] =
   BEGIN
   OPEN str;
   file: STRING = [99];
   DO IF ReadString["Mail file name: "L, file, word] = Ascii.DEL
      THEN EXIT
      ELSE BEGIN
           FOR i: CARDINAL DECREASING IN [0..file.length)
           DO IF file[i] = '. THEN EXIT;
           REPEAT FINISHED =>
             { WriteString[".mail"L]; String.AppendString[file, ".mail"L] };
           ENDLOOP;
           WriteString[" ..."L];
           WriteChar[Ascii.CR];
           IF Patch[str, file] THEN EXIT;
           END;
      WriteChar[Ascii.CR];
   ENDLOOP;
   WriteString["Done"L]; WriteChar[Ascii.CR];
   END;
      
Patch: PROC[str: GlassDefs.Handle, file: STRING] RETURNS[ok: BOOLEAN] =
   BEGIN
   OPEN str;
   stream: Streams.Handle = Streams.NewStream[
      name: file, access: Streams.ReadWrite ! Segments.FileNameProblem[] =>
      GOTO badFile ];
   ParseFile[str, stream !
             BadMailFile =>
               { WriteString["Bad mail file - run MailFileScavenger.laurel then re-run this program"L]; CONTINUE };
             UNWIND => Streams.Destroy[stream] ];
   Streams.Destroy[stream];
   ok ← TRUE;
   EXITS badFile =>
      { WriteString[" ... """L];
        WriteString[file];
        WriteString[""" doesn't exist"L];
        ok ← FALSE }
   END;

BogusNumber: ERROR = CODE;
BadMailFile: ERROR = CODE;

ParseStamp: PROCEDURE [NextChar: PROC RETURNS [CHARACTER]] RETURNS [offsetToHeader, textLength: CARDINAL] =
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 => NULL;
  'U => NULL;
  ENDCASE => GOTO notAStamp;

SELECT NextChar[] FROM
  'S => NULL;
  'U => NULL;
  ENDCASE => GOTO notAStamp;

offsetToHeader ← j;
IF i < j THEN GOTO notAStamp;
textLength ← i - j;
[] ← NextChar[];
IF NextChar[] # Ascii.CR THEN GOTO notAStamp;

EXITS
notAStamp => ERROR BadMailFile[];
END; -- of ParseStamp --

repair: STRING = "Name-too-long@Probably-Berkeley"L;

ParseFile: PROC[str: GlassDefs.Handle, stream: Streams.Handle] =
   BEGIN
   OPEN str;
   ch: CHARACTER;
   Get: PROC RETURNS[CHARACTER] =
     { RETURN[ ch ← IF Streams.Ended[stream] THEN MailParse.endOfInput
                    ELSE Streams.GetChar[stream] ] };
   Backup: PROC =
     { Streams.SetIndex[stream, Streams.GetIndex[stream]-1] };
   ph: MailParse.ParseHandle = MailParse.InitializeParse[Get, Backup];
   FOR msg: CARDINAL IN [1..LAST[CARDINAL]]
   UNTIL Streams.Ended[stream]
   DO -- for each message --
      mStart: LONG CARDINAL = Streams.GetIndex[stream];
      offsetToHeader: CARDINAL;
      textLength: CARDINAL;
      [offsetToHeader, textLength] ← ParseStamp[Get];
      Streams.SetIndex[stream, mStart+offsetToHeader];
      BEGIN
        s: STRING = [99];
        WHILE MailParse.GetFieldName[ph, s ! MailParse.ParseError => EXIT ]
        DO IF String.EquivalentString[s, "From"L]
           THEN BEGIN
                fStart: LONG CARDINAL = Streams.GetIndex[stream];
                ProcessFrom: PROC[name, reg, host: STRING,
                                  ignored: MailParse.NameInfo]
                          RETURNS[BOOLEAN] =
                  BEGIN
                  total: CARDINAL ← name.length + reg.length + host.length;
                  IF reg.length > 0 THEN total ← total+1;
                  IF host.length > 0 THEN total ← total+1;
                  IF total > MailParse.maxRecipientLength
                  THEN -- Laurel bug! --
                       BEGIN
                       fEnd: LONG CARDINAL = Streams.GetIndex[stream];
                       Streams.SetIndex[stream, fStart];
                       IF fEnd < fStart + 2 + repair.length THEN ERROR;
                       Streams.PutChar[stream, Ascii.SP];
                       FOR i: CARDINAL IN [0..repair.length)
                       DO Streams.PutChar[stream, repair[i]] ENDLOOP;
                       WHILE Streams.GetIndex[stream]+1 < fEnd
                       DO Streams.PutChar[stream, Ascii.SP] ENDLOOP;
                       WriteString["Patched ""From"" field in message "L];
                       WriteDecimal[msg];
                       WriteString[":
	"""L];
                       WriteString[name];
                       IF reg.length # 0 THEN WriteChar['.];
                       WriteString[reg];
                       IF host.length # 0 THEN WriteChar['@];
                       WriteString[host];
                       WriteChar['"];
                       WriteChar[Ascii.CR];
                       END;
                  RETURN[FALSE]
                  END;
                MailParse.ParseNameList[ph, ProcessFrom !
                  MailParse.ParseError => CONTINUE];
                END
           ELSE MailParse.GetFieldBody[ph, s, TRUE !
                  MailParse.ParseError => CONTINUE];
        ENDLOOP;
      END;
      Streams.SetIndex[stream, mStart+offsetToHeader+textLength];
   ENDLOOP;
   MailParse.FinalizeParse[ph];
   END;

GlassDefs.Listen[DoIt];

END.