DIRECTORY BasicTime USING [GMT, nullGMT, Unpacked, Now, Unpack], FS USING [ComponentPositions, Error, ExpandName, StreamOpen], GVBasics USING [ItemType, RName], GVMailParse, GVNames, GVSend USING [Abort, AddRecipient, AddToItem, CheckValidity, Create, Handle, Send, SendFailed, StartItem, StartSend, StartSendInfo], Icons, Menus, IO, Rope, RopeList USING [Append], RuntimeError USING [BoundsFault], TextNode, TiogaOps, UserProfile USING [Token], ViewerClasses USING [Viewer], ViewerOps, ViewerTools, UserCredentials USING [Get], WalnutDocumentRope, WalnutSendOps, WalnutSendOpsExtras, WalnutSendInternal, WalnutParseMsg USING [MessageFieldIndex, MessageInfo, messageParseArray, CanonicalName]; WalnutSendMailImpl: CEDAR MONITOR IMPORTS BasicTime, FS, GVMailParse, GVNames, GVSend, IO, Rope, RopeList, RuntimeError, TextNode, TiogaOps, ViewerOps, ViewerTools, UserCredentials, UserProfile, WalnutParseMsg, WalnutSendInternal, WalnutSendOps, WalnutSendOpsExtras EXPORTS WalnutDocumentRope, WalnutSendInternal, WalnutSendOps = BEGIN OPEN WalnutSendInternal, WalnutSendOpsExtras; RName: TYPE = GVBasics.RName; ROPE: TYPE = Rope.ROPE; Viewer: TYPE = ViewerClasses.Viewer; Send: PUBLIC PROC[v: Viewer, doClose: BOOL_ FALSE] RETURNS[sent: BOOL] = { oldMenu: Menus.Menu = v.menu; v.inhibitDestroy_ TRUE; BEGIN ENABLE UNWIND => GOTO out; ViewerOps.SetMenu[v, sendingMenu]; IF needToAuthenticate THEN { SenderReport["Authenticating user ..."]; IF ~AuthenticateUser[] THEN {ViewerOps.BlinkIcon[v, IF v.iconic THEN 0 ELSE 1]; ViewerOps.SetMenu[v, oldMenu]; v.inhibitDestroy _ FALSE; RETURN[FALSE] }; SenderReport[" ...ok\n"]; }; sent _ InternalSendMsg[v, doClose]; EXITS out => NULL; END; ViewerOps.SetMenu[v, oldMenu]; v.inhibitDestroy _ FALSE; }; AppendHeaderLine: PUBLIC PROC[v: Viewer, line: ROPE, changeSelection: BOOL _ FALSE] = BEGIN ENABLE RuntimeError.BoundsFault => GOTO exit; text: ROPE; i: INT _ 0; ch: CHAR; TRUSTED {text _ Create[LOOPHOLE [TiogaOps.ViewerDoc[v]]]}; DO -- find the double CR at the end of the headers UNTIL (ch _ text.Fetch[i]) = '\n DO i _ i + 1; ENDLOOP; IF (ch _ text.Fetch[i _ i + 1]) = '\n THEN EXIT; ENDLOOP; InsertIntoViewer[v, line, i-1, WalnutSendOps.labelFont, changeSelection]; ViewerTools.EnableUserEdits[v]; EXITS exit => SenderReport[IO.PutFR["Malformed headers; append of &g not done", IO.rope[line]]]; END; AuthenticateUser: PROC RETURNS [BOOL] = BEGIN OPEN WalnutSendOps; auth: GVNames.AuthenticateInfo; IF userRName.Length[] = 0 THEN {SenderReport["Please Login\n"]; RETURN[FALSE]}; SELECT auth _ GVNames.Authenticate[userRName, UserCredentials.Get[].password] FROM group => SenderReport["... Can't login as group\n"]; individual => {needToAuthenticate _ FALSE; RETURN[TRUE]}; notFound => {SenderReport[userRName]; SenderReport[" is invalid - please Login\n"]}; allDown => SenderReport["... No server responded\n"]; badPwd => SenderReport["... Your Password is invalid - please Login\n"]; ENDCASE; RETURN[FALSE]; END; InternalSendMsg: PROC[senderV: Viewer, doClose: BOOL] RETURNS[sendOk: BOOL] = BEGIN status: SendParseStatus; sPos, mPos: INT; formatting: ROPE; smr: SendingRec; addedTxtLength: INT; newTxt: ROPE; contents, currentText: ViewerTools.TiogaContents; senderInfo: SenderInfo _ NARROW[ViewerOps.FetchProp[senderV, $SenderInfo]]; Blink: PROC[v: Viewer] = { ViewerOps.BlinkIcon[v, IF v.iconic THEN 0 ELSE 1]}; sendOk _ FALSE; senderInfo.aborted _ FALSE; IF senderInfo.successfullySent AND ~senderV.newVersion THEN { SenderReport["\nDo you really want to send this message again?"]; IF ~Confirmation[senderInfo] THEN { SenderReport[" .. Not sent\n"]; senderInfo.successfullySent _ FALSE; RETURN} }; senderInfo.successfullySent _ FALSE; SenderReport["... Parsing..."]; smr _ NEW[SendMsgRecObject]; TRUSTED { smr.fullText _ Create[LOOPHOLE [TiogaOps.ViewerDoc[senderV]]] }; [status, sPos, mPos] _ ParseTextToBeSent[smr]; SELECT status FROM fieldNotAllowed => { IF sPos # mPos THEN { ShowErrorFeedback[senderV, sPos, mPos]; SenderReport[Rope.Substr[smr.fullText, MAX[0, sPos-1], mPos-sPos]]; SenderReport[" field is not allowed\n"]} ELSE SenderReport[IO.PutFR[" field at pos %g is not allowed\n", IO.int[sPos]]]; Blink[senderV]; RETURN }; syntaxError => { IF sPos # mPos THEN { ShowErrorFeedback[senderV, sPos, mPos]; SenderReport["\nSyntax error on line beginning with "]; SenderReport[Rope.Substr[smr.fullText, MAX[0, sPos-1], mPos-sPos]]} ELSE SenderReport[IO.PutFR["..... Syntax error at position %g ", IO.int[sPos]]]; Blink[senderV]; RETURN }; pdlNotFound, pdlSyntaxError => { Blink[senderV]; RETURN }; ENDCASE => NULL; IF CheckForAbortSend[senderInfo] THEN RETURN; IF (status = includesPublicDL OR status = includesPrivateDL OR smr.numRecipients > maxWithNoReplyTo) AND ~smr.replyTo THEN { howToReply: HowToReplyTo _ self; IF ~replyToSelf THEN { oldM: Menus.Menu _ senderV.menu; IF CheckForAbortSend[senderInfo] THEN RETURN; ViewerOps.SetMenu[senderV, replyToMenu]; SenderReport[ IO.PutFR["... %g public DLs and %g other recipients; please choose Reply-To option", IO.int[smr.numDLs], IO.int[smr.numRecipients]]]; SenderReport[ "\nClick Self to reply-to self, All to reply-to all, Cancel to cancel Send\n"]; howToReply _ ReplyToResponse[senderInfo]; ViewerOps.SetMenu[senderV, oldM]; }; IF howToReply # all THEN InternalInsert[senderV, "Reply-to: ", WalnutSendOps.userRName, smr.endHeadersPos-1, WalnutSendOps.labelFont]; IF howToReply = cancel THEN {SenderReport["\nDelivery cancelled. Reply-to: has been added\n"]; RETURN }; IF CheckForAbortSend[senderInfo] THEN RETURN; }; IF doClose AND ~senderV.iconic THEN ViewerOps.CloseViewer[senderV]; currentText _ ViewerTools.GetTiogaContents[senderV]; newTxt _ Rope.Cat[IF smr.from = NIL THEN "From: " ELSE "Sender: ", WalnutSendOps.userRName]; addedTxtLength _ newTxt.Length[]; InsertIntoViewer[senderV, newTxt, 0, WalnutSendOps.labelFont]; newTxt _ Rope.Concat["Date: ", InternalRFC822Date[BasicTime.nullGMT, TRUE]]; addedTxtLength _ newTxt.Length[] + addedTxtLength + 1; InsertIntoViewer[senderV, newTxt, 0, WalnutSendOps.labelFont]; contents _ ViewerTools.GetTiogaContents[senderV]; { last: INT _ contents.contents.Length[] - 1; IF contents.contents.Fetch[last] = '\000 THEN -- NULL for padding { smr.fullText _ Rope.Substr[contents.contents, 1, last-1]; formatting _ Rope.Concat["\000", contents.formatting] } ELSE { smr.fullText _ Rope.Substr[contents.contents, 1]; formatting _ contents.formatting } }; IF smr.subject.Length[] > 40 THEN smr.subject _ Rope.Concat[Rope.Substr[smr.subject, 0, 35], " ..."]; smr.subject _ Rope.Cat[" \"", smr.subject, "\" "]; SenderReport["... Sending "]; SenderReport[smr.subject]; IF senderInfo.successfullySent _ sendOk _ SendIt[smr, senderInfo, formatting] THEN { SenderReport[smr.subject]; SenderReport[" has been delivered\n"]; senderInfo.prevMsg _ currentText; } ELSE { DeleteChars[senderV, addedTxtLength]; SenderReport[smr.subject]; SenderReport[" NOT sent\n"]; }; END; RFC822Date: PUBLIC PROC[gmt: BasicTime.GMT _ BasicTime.nullGMT] RETURNS[date: ROPE] = { RETURN[InternalRFC822Date[gmt, FALSE]] }; InternalRFC822Date: PROC[gmt: BasicTime.GMT, withDay: BOOL] RETURNS[date: ROPE] = BEGIN OPEN IO; upt: BasicTime.Unpacked _ BasicTime.Unpack[IF gmt = BasicTime.nullGMT THEN BasicTime.Now[] ELSE gmt]; zone: ROPE; 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: INT _ ABS[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; 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]]; IF withDay THEN { dayOfWeek: ROPE; SELECT upt.weekday FROM Monday => dayOfWeek _ "Mon, "; Tuesday => dayOfWeek _ "Tue, "; Wednesday => dayOfWeek _ "Wed, "; Thursday => dayOfWeek _ "Thu, "; Friday => dayOfWeek _ "Fri, "; Saturday => dayOfWeek _ "Sat, "; Sunday => dayOfWeek _ "Sun, "; ENDCASE => dayOfWeek _ ""; date _ dayOfWeek.Concat[date]; }; END; ShowErrorFeedback: PUBLIC PROC[v: Viewer, start, end: INT] = BEGIN OPEN TiogaOps; ENABLE UNWIND => GOTO exit; startLoc, endLoc: Location; thisV: Ref = ViewerDoc[v]; beginning: Location _ [FirstChild[thisV], 0]; startLoc _ LocRelative[beginning, start]; endLoc _ LocRelative[beginning, end]; SetSelection[viewer: v, start: startLoc, end: endLoc, which: feedback]; EXITS exit => NULL; END; InsertIntoViewer: PUBLIC PROC [v: Viewer, what: ROPE, where: INT, labelFont: ROPE, changeSelection: BOOL _ FALSE] = { prefix: ROPE _ Rope.Substr[ what, 0, Rope.Find[s1: what, s2: ":"] ]; field: ROPE _ Rope.Replace[base: what, start: 0, len: prefix.Length, with: NIL]; InternalInsert[v, prefix, field, where, labelFont, changeSelection]; }; InternalInsert: PROC [v: Viewer, prefix, field: ROPE, where: INT, labelFont: ROPE, changeSelection: BOOL _ FALSE] = BEGIN OPEN TiogaOps; thisV: Ref = ViewerDoc[v]; InsertChars: PROC[root: Ref] = BEGIN insertLoc: Location; prevV: Viewer; prevStart, prevEnd: Location; prevLevel: SelectionGrain; cb, pd: BOOL; IF where < 0 THEN insertLoc _ LastLocWithin[LastChild[thisV]] ELSE insertLoc _ LocRelative[[FirstChild[thisV], 0], where]; [prevV, prevStart, prevEnd, prevLevel, cb, pd] _ GetSelection[primary]; ViewerTools.EnableUserEdits[v]; SelectPoint[v, insertLoc, primary]; IF where # 0 THEN TiogaOps.Break[]; -- make a new node IF labelFont # NIL THEN TiogaOps.SetLooks[labelFont, caret]; TiogaOps.InsertRope[prefix]; IF labelFont # NIL THEN TiogaOps.ClearLooks[caret]; TiogaOps.InsertRope[field]; IF where = 0 THEN TiogaOps.Break[]; -- make a new node ViewerTools.InhibitUserEdits[v]; IF ~changeSelection AND (prevV # v) AND (prevV#NIL) AND ~prevV.destroyed THEN SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd ! TiogaOps.SelectionError => CONTINUE]; END; LockViewerOpen[v]; IF thisV = NIL THEN InsertChars[thisV] ELSE CallWithLocks[InsertChars, thisV]; ReleaseOpenViewer[v]; END; DeleteChars: PUBLIC ENTRY PROC[v: Viewer, num: INT] = BEGIN ENABLE UNWIND => NULL; IF num # 0 THEN DeleteLeadingChars[v, num] END; DeleteLeadingChars: INTERNAL PROC[v: Viewer, num: INT] = BEGIN OPEN TiogaOps; thisV: Ref _ ViewerDoc[v]; DelChars: PROC[root: Ref] = BEGIN prevV: Viewer; prevStart, prevEnd: Location; prevLevel: SelectionGrain; cb, pd: BOOL; startLoc: Location _ [FirstChild[thisV], 0]; endLoc: Location _ LocRelative[startLoc, num]; [prevV, prevStart, prevEnd, prevLevel, cb, pd] _ GetSelection[primary]; ViewerTools.EnableUserEdits[v]; SetSelection[viewer: v, start: startLoc, end: endLoc]; Delete[]; GoToNextNode[]; Join[]; IF (prevV # v) AND (prevV#NIL) THEN SetSelection[prevV, prevStart, prevEnd, prevLevel, cb, pd]; END; IF thisV = NIL THEN DelChars[thisV] ELSE CallWithLocks[DelChars, thisV]; END; SendIt: PROC[smr: SendingRec, senderInfo: SenderInfo, formatting: ROPE] RETURNS[ sent: BOOLEAN] = { InitSending: ENTRY PROC = { ENABLE UNWIND => NULL; senderInfo.sendHandle _ GVSend.Create[]; senderInfo.aborted _ FALSE; }; FinishedSending: ENTRY PROC = { ENABLE UNWIND => NULL; senderInfo.sendHandle _ NIL }; InitSending[]; sent _ SendMessage[smr, senderInfo, formatting ! GVSend.SendFailed => { IF notDelivered THEN { SenderReport["\nCommunication failure during send. Retry?\n"]; IF Confirmation[senderInfo] THEN RETRY ELSE { sent _ FALSE; CONTINUE } } ELSE { SenderReport["\nCommunication failure, but message delivered\n"]; sent _ TRUE; CONTINUE; }; }]; FinishedSending[]; } ; SendMessage: PROC[smr: SendingRec, senderInfo: SenderInfo, formatting: ROPE] RETURNS[ sent: BOOLEAN ] = { DO ENABLE GVSend.SendFailed => CONTINUE; -- try again if SendFailed h: GVSend.Handle _ senderInfo.sendHandle; startInfo: GVSend.StartSendInfo; stepper: LIST OF RName; tempInteger: INT; numRecips: INT _ 0; numValidRecips: INT; firstInvalidUser: BOOL _ TRUE; numInvalidUsers: INTEGER _ 0; InvalidUserProc: PROC [ userNum: INT, userName: RName ] = { IF firstInvalidUser THEN {SenderReport["\nInvalid user(s): "]; firstInvalidUser _ FALSE}; SELECT numInvalidUsers _ numInvalidUsers + 1 FROM 1 => SenderReport[userName]; IN [2..5] => {SenderReport[", "]; SenderReport[userName]}; 6 => SenderReport[", ...\n"]; ENDCASE; } ; sent _ FALSE ; startInfo _ GVSend.StartSend[ handle: senderInfo.sendHandle, senderPwd: UserCredentials.Get[].password, sender: WalnutSendOps.userRName, returnTo: WalnutSendOps.userRName, validate: TRUE ] ; SELECT startInfo FROM badPwd => {SenderReport["\nInvalid password\n"]; RETURN}; badSender => {SenderReport["\nInvalid sender name\n"]; RETURN}; badReturnTo => {SenderReport["\nBad return-to field\n"]; RETURN}; allDown => {SenderReport["\nAll servers are down\n"]; RETURN}; ok => { stepper _ smr.to; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ]; numRecips _ numRecips + 1; stepper _ stepper.rest; ENDLOOP; IF CheckForAbortSend[senderInfo] THEN RETURN; stepper _ smr.cc; WHILE stepper # NIL DO GVSend.AddRecipient[ h, stepper.first ] ; numRecips _ numRecips + 1 ; stepper _ stepper.rest ; ENDLOOP ; IF CheckForAbortSend[senderInfo] THEN RETURN; IF (numValidRecips _ GVSend.CheckValidity[ h, InvalidUserProc]) = 0 THEN { SenderReport["\nThere were NO valid recipients; do you wish to send anyway?\n"]; IF CheckForAbortSend[senderInfo] OR ~Confirmation[senderInfo] THEN {GVSend.Abort[h]; RETURN}; }; IF numValidRecips # numRecips THEN { tempInteger _ numRecips-numValidRecips; SenderReport[IO.PutFR["\nThere were %g invalid recipients,", IO.int[tempInteger] ]]; SenderReport[" do you wish to send anyway?\n"]; IF CheckForAbortSend[senderInfo] OR ~Confirmation[senderInfo] THEN {GVSend.Abort[h]; RETURN}; }; IF CheckForAbortSend[senderInfo] THEN RETURN; SenderReport[IO.PutFR["..sending to %g recipients\n", IO.int[numValidRecips]]]; GVSend.StartItem[h, Text]; AddToItem[h, smr.fullText]; IF formatting#NIL THEN { -- send the formatting info as a second item GVSend.StartItem[h, WalnutSendOps.TiogaCTRL]; AddToItem[h, formatting] }; IF smr.voiceID.Length[] # 0 THEN { GVSend.StartItem[h, Audio]; AddToItem[h, smr.voiceID] }; ViewerOps.SetMenu[senderInfo.senderV, blankMenu]; IF CheckForAbortSend[senderInfo] THEN RETURN; GVSend.Send[ h ] ; sent _ TRUE; RETURN; } ; ENDCASE; ENDLOOP; }; AddToItem: PROC [handle: GVSend.Handle, buffer: ROPE] = { maxChunkSize: INT ~ 10000; size: INT ~ Rope.Size[buffer]; i: INT _ 0; UNTIL i = size DO chunkSize: INT ~ MIN[size-i, maxChunkSize]; GVSend.AddToItem[handle, Rope.Substr[buffer, i, chunkSize]]; i _ i + chunkSize; ENDLOOP; }; ParseTextToBeSent: PROC[msg: SendingRec] RETURNS[status: SendParseStatus, sPos, mPos: INT] = BEGIN OPEN GVMailParse; mLF: WalnutParseMsg.MessageInfo; tHeaders: LIST OF ROPE _ NIL; msgText: ROPE _ msg.fullText; lastCharPos: INT _ msgText.Length[] - 1; lastCharIsCR: BOOL _ (msgText.Fetch[lastCharPos] = '\n); countOfRecipients, dlCount: INT _ 0; pdlStatus: SendParseStatus _ ok; GetNextMsgChar: PROC[] RETURNS [ch: CHAR] = { IF mPos <= lastCharPos THEN ch _ Rope.Fetch[msgText, mPos] ELSE IF (mPos=lastCharPos+1) AND ~lastCharIsCR THEN ch _ '\n ELSE ch _ endOfInput; mPos _ mPos + 1; }; RNameListField: PROC[index: WalnutParseMsg.MessageFieldIndex] = BEGIN fieldBody, fbEnd: LIST OF RName _ NIL; ParsePvtDL: PROC [fname: ROPE] = { pdlH: ParseHandle _ NIL; -- need new handle for each PDL pdlStream: IO.STREAM _ NIL; fullName: ROPE; BEGIN ENABLE UNWIND => { IF pdlH # NIL THEN FinalizeParse[pdlH]; IF pdlStream # NIL THEN pdlStream.Close[]; }; GetPDLChar: PROC RETURNS [c: CHAR] = { -- No CR's allowed in file. IF pdlStream # NIL AND NOT pdlStream.EndOf[] THEN { IF (c _ pdlStream.GetChar[]) = '; THEN c _ endOfInput } ELSE c _ endOfInput; }; -- Extract filename from between quotes IF fname.Fetch[0] = '" THEN fname _ Rope.Substr[fname, 1, Rope.Length[fname]-2]; BEGIN cp: FS.ComponentPositions; wDir: ROPE _ UserProfile.Token["WalnutSend.DefaultDLDir"]; [fullName, cp, ] _ FS.ExpandName[fname, wDir ! FS.Error => { SenderReport[IO.PutFR["\n%g\n", IO.rope[error.explanation]]]; pdlStatus _ pdlSyntaxError; CONTINUE} ]; IF pdlStatus = pdlSyntaxError THEN RETURN; IF cp.ext.length = 0 THEN fullName _ fullName.Concat[".dl"]; END; pdlH _ InitializeParse[]; pdlStream _ FS.StreamOpen[fullName ! FS.Error => { SenderReport[IO.PutFR["\n%g\n", IO.rope[error.explanation]]]; pdlStatus _ pdlNotFound; CONTINUE} ]; IF pdlStream = NIL THEN RETURN; DO IF pdlStream.EndOf[] THEN { pdlStream.SetIndex[0]; EXIT } -- no : found ELSE { c: CHAR _ pdlStream.GetChar[]; IF c = ': THEN EXIT; -- leave positioned after : IF c = '\n THEN { SenderReport["\nCR's not allowed in private dl's\n"]; pdlStream.Close[]; pdlStatus _ pdlSyntaxError; RETURN; }; }; ENDLOOP; pdlH _ InitializeParse[]; ParseNameList[pdlH, GetPDLChar, AnotherRName, NIL]; FinalizeParse[pdlH]; pdlH _ NIL; pdlStream.Close[]; END; }; AnotherRName: PROC[r1, r2: ROPE, isFile, isNested: BOOL] RETURNS [ROPE, BOOLEAN] = BEGIN name: ROPE; IF isFile THEN { status _ includesPrivateDL; ParsePvtDL[r1]; RETURN[NIL, FALSE]; }; name _ WalnutParseMsg.CanonicalName[r1, r2]; IF fbEnd=NIL THEN fbEnd _ fieldBody _ CONS[name, NIL] ELSE fbEnd _ fbEnd.rest _ CONS[name, NIL]; -- IF isFile THEN status _ includesPrivateDL -- ELSE IF name.Find["^"] < 0 THEN countOfRecipients _ countOfRecipients + 1 IF name.Find["^"] < 0 AND name.Find["­"] < 0 THEN countOfRecipients _ countOfRecipients + 1 ELSE { IF status # includesPrivateDL THEN status _ includesPublicDL; dlCount _ dlCount + 1 }; RETURN[NIL, FALSE]; END; ParseNameList[pH, GetNextMsgChar, AnotherRName, NIL]; SELECT index FROM toF => IF msg.to = NIL THEN msg.to _ fieldBody ELSE IF fieldBody#NIL THEN msg.to _ RopeList.Append[msg.to, fieldBody]; ccF, cF, bccF => IF msg.cc = NIL THEN msg.cc _ fieldBody ELSE IF fieldBody#NIL THEN msg.cc _ RopeList.Append[msg.cc, fieldBody]; fromF => msg.from _ fieldBody.first; -- needs to be non-NIL replyToF => NULL; -- just to syntax check the ReplyTo field ENDCASE => ERROR; END; pH: ParseHandle; field: ROPE _ NIL; fieldNotRecognized: BOOL; mPos _ 0; -- where we are in the fulltext status _ ok; -- start with good status pH _ InitializeParse[]; DO sPos _ mPos; [field, fieldNotRecognized] _ GetFieldName[pH, GetNextMsgChar ! ParseError => { FinalizeParse[pH]; GOTO errorExit}]; IF ~fieldNotRecognized THEN EXIT; IF Rope.Equal[field, "Sender", FALSE] OR Rope.Equal[field, "Date", FALSE] THEN RETURN[fieldNotAllowed, sPos, mPos]; FOR i: WalnutParseMsg.MessageFieldIndex IN WalnutParseMsg.MessageFieldIndex DO { mLF _ WalnutParseMsg.messageParseArray[i]; IF Rope.Equal[WalnutParseMsg.messageParseArray[i].name, field, FALSE] THEN { fieldNotRecognized _ FALSE; SELECT mLF.fType FROM simpleRope => SELECT i FROM fromF => RNameListField[i ! ParseError => GOTO errorExit]; replyToF => { msg.replyTo _ TRUE; RNameListField[i ! ParseError => GOTO errorExit]; }; subjectF => msg.subject _ GetFieldBody[pH, GetNextMsgChar]; voiceF => msg.voiceID _ GetFieldBody[pH, GetNextMsgChar]; ENDCASE => [] _ GetFieldBody[pH, GetNextMsgChar, TRUE]; rCatList => [] _ GetFieldBody[pH, GetNextMsgChar, TRUE]; rNameList => RNameListField[i ! ParseError => GOTO errorExit]; ENDCASE => ERROR; EXIT }; }; ENDLOOP; IF fieldNotRecognized THEN [] _ GetFieldBody[pH, GetNextMsgChar]; -- skip anything not recognized ENDLOOP; FinalizeParse[pH]; msg.endHeadersPos _ mPos - 1; msg.numRecipients _ countOfRecipients; msg.numDLs _ dlCount; IF pdlStatus # ok THEN RETURN[pdlStatus, 0, 0]; EXITS errorExit => RETURN[syntaxError, sPos, mPos]; END; Create: PUBLIC PROC [doc: TextNode.Ref] RETURNS [r: ROPE] = { docSize: INT _ TextNode.LocOffset[[doc,0], TextNode.LastLocWithin[doc]]; r _ Rope.MakeRope[base: doc, size: docSize, fetch: Fetch]; IF docSize > 0 THEN { loc0: TextNode.Location = TextNode.LocRelative[[doc,0], 0]; loc1: TextNode.Location = TextNode.LocRelative[[doc,0], 1]; IF loc0 = loc1 THEN { r _ Rope.Substr[r, 1]; }; }; }; Fetch: PROC [data: REF, index: INT] RETURNS [CHAR] = { doc: TextNode.Ref _ NARROW[data]; loc: TextNode.Location = TextNode.LocRelative[[doc,0], index]; n: TextNode.RefTextNode = TextNode.NarrowToTextNode[loc.node]; IF n=NIL THEN ERROR; IF loc.where >= Rope.Size[n.rope] THEN RETURN ['\n]; RETURN [Rope.Fetch[n.rope, loc.where]] }; END. WalnutSendMailImpl.mesa Copyright c 1984 by Xerox Corporation. All rights reserved. Last Edited by: Willie-Sue, August 15, 1986 5:01:08 pm PDT Last Edited by: Donahue, October 2, 1985 7:59:30 am PDT Last Edited by: Neil Gunther, April 17, 1985 1:06:34 pm PST Hal Murray August 6, 1985 1:04:01 am PDT Contents: Implementation of the WalnutMsg Send operation ************************************************************************ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * first add the From:/Sender: line now put the date at the very front note that there is guaranteed to be formatting since we added some to make things look nice generates arpa standard time, dd mmm yy hh:mm:ss zzz generates arpa standard time, dd mmm yy hh:mm:ss zzz * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * check the syntax of the pdl now we are positioned at the beginning of the body of the message move from WalnutDocumentRopeImpl RRA: I think that this is a bug! But we have observed this, so we have to correct for it, I think. I wish that this were not the case. Κy˜šΟnœ™Jšœ Οmœ1™˜"šŸœŸ˜˜"JšŸœŸœŸœ˜-˜(˜ šŸœR˜TJšŸœŸœ˜0——˜ J˜O—J˜)J˜!—J˜——šŸœŸ˜˜>J˜.—JšŸœŸ˜˜CJšŸ˜J˜J˜——JšŸœŸœŸœ˜3—J˜J˜JšŸœ ŸœŸœ ˜CJ˜——Jšœ!™!J˜4˜Jš œ Ÿœ ŸœŸœ Ÿœ&˜S˜!J˜>Jšœ"™"JšœEŸœ˜LJ˜6J˜>J˜J˜2Jšœ[™[šœŸœ"˜-šŸœ'Ÿœ ˜B˜;J˜7——šŸœ˜˜3J˜%J˜———šŸœŸ˜!J˜C—J˜2J˜——J˜8šŸœK˜MšŸ˜˜CJ˜!—J˜—šŸ˜˜'J˜7J˜———JšŸœ˜—J˜š  œŸœŸœŸœŸœŸœ˜UJšœ4™4JšœŸœŸœ˜,J˜—Jš œŸœŸœ ŸœŸœŸœ˜Qšœ4™4šŸœŸœŸœ˜˜JšœŸœŸœŸœ˜K—JšœŸœ˜ JšœŸœ˜Jšœ Ÿœ ˜:Jšœ Ÿœ ˜>J˜Jšœ Ÿœ˜JšœŸœŸœ ˜JšœŸœ˜JšœŸœ ˜šŸœŸœ Ÿ˜"šœŸœŸ˜šŸœŸ˜J˜J˜J˜J˜J˜———šŸ˜šŸ˜šŸœŸ˜J˜J˜J˜J˜J˜JšŸœ˜—J˜J˜JšŸœŸœŸ˜šœŸœ˜Jšœ Ÿœ Ÿœ Ÿœ"˜OJ˜J˜—šŸœ Ÿ˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜J˜JšœŸœ˜JšŸœŸœ˜—J˜JšœŸœ˜1J˜VJ˜LšŸœ Ÿœ˜Jšœ Ÿœ˜šŸœŸ˜J˜Jšœ˜Jšœ!˜!Jšœ ˜ Jšœ˜Jšœ ˜ Jšœ˜JšŸœ˜—Jšœ˜J˜—J˜———JšŸœ˜J˜—šœŸœŸœŸœ˜JšŸœ8˜˜J˜šŸœ ŸœŸœ˜J˜(J˜J˜—JšŸœ˜JšŸœŸœŸœ˜-J˜šŸœ ŸœŸœ˜J˜)J˜J˜—JšŸœ˜ JšŸœŸœŸœ˜-šŸœBŸ˜H˜RšŸœŸœŸ˜BJšœŸœ˜—J˜—J˜—šŸœŸ˜"˜)Jšœ Ÿœ.Ÿœ˜TJ˜/šŸœŸœŸ˜BJšœŸœ˜—J˜J˜——JšŸœŸœŸœ˜-Jšœ Ÿœ'Ÿœ˜OJ˜J˜šŸœ ŸœŸœ ,˜EJ˜-J˜J˜—šŸœŸ˜ J˜:J˜—J˜1—J˜JšŸœŸœŸœ˜-J˜JšœŸœ˜ JšŸœ˜J˜JšŸœ˜—JšŸœ˜J˜—J˜š œŸœ!Ÿœ˜9KšœŸœ ˜KšœŸœ˜KšœŸœ˜ šŸœ Ÿ˜Kšœ ŸœŸœ˜+Kšœ<˜JšŸœŸœ˜JšŸ˜J˜———J˜—JšŸœ˜šŸœŸ˜Jšœ' ˜F———JšŸœ˜J˜—JšœA™AJ˜J˜—˜J˜'J˜JšŸœŸœŸœ˜/šŸ˜Jšœ Ÿœ˜-—JšŸœ˜—J˜šœ ™ J˜š œŸœŸœŸœŸœ˜=Jšœ Ÿœ<˜HKšœ:˜:šŸœ Ÿœ˜J˜;J˜;šŸœ Ÿœ˜Jšœˆ™ˆJ˜J˜—J˜—K˜—K˜š œŸœŸœ ŸœŸœŸœ˜6JšœŸœ˜!J˜>J˜>JšŸœŸœŸœŸœ˜JšŸœ ŸœŸœ˜4JšŸœ ˜&Jšœ˜—K˜J˜J˜—JšŸœ˜J˜—…—Rdoυ