DIRECTORY Ascii, AuthenticationP14V2, Basics, CHEntriesP0V0, IO, MailFormatP1516V3, MailTransportP17V5, MailAnswer, MailBasics, MailBasicsItemTypes, MailParse, MailSend, MailSendSidedoor, MSBasics, MailUtils, MSSend, MSUtils, Rope, RuntimeError USING [BoundsFault], SerializedFiling, XNSAuth, XNSCH, XNSCHName, XNSCredentials, XNSWKS; MSMailSendImpl: CEDAR MONITOR IMPORTS Ascii, IO, MailAnswer, MailParse, MailSend, MailSendSidedoor, MailUtils, MSSend, MSUtils, Rope, RuntimeError, SerializedFiling, XNSAuth, XNSCH, XNSCredentials, XNSCHName ~ BEGIN OPEN CHEntries: CHEntriesP0V0, MailFormat: MailFormatP1516V3, MailTransport: MailTransportP17V5; ROPE: TYPE ~ Rope.ROPE; STREAM: TYPE ~ IO.STREAM; Handle: TYPE ~ MailSend.MailSendHandle; RName: TYPE ~ MailBasics.RName; RNameList: TYPE ~ MailBasics.RNameList; SendingCredentialsList: TYPE ~ MailSend.SendingCredentialsList; BodyPartInfo: TYPE ~ MailSend.BodyPartInfo; maxForMultinationalNote: INT ¬ 8100; maxForAttribute: INT ¬ LONG[LAST[CARD16] - 1]; vanillaNSTextFile: SerializedFiling.AttributeType ~ 17; -- magic?? useHeadingAttributes: BOOL ¬ TRUE; SendViaXNS: PROC [msH: Handle, validate, sendEvenIfInvalidNames: BOOL] RETURNS [sent: BOOL, failureReason: ROPE, invalidNames: RNameList] ~ { DummyAbortSendProc: MailSendSidedoor.AbortSendProc ~ { RETURN[FALSE] }; [sent, failureReason, invalidNames, ] ¬ SendViaXNSWithAbort[msH, validate, sendEvenIfInvalidNames, TRUE, DummyAbortSendProc]; }; SendViaXNSWithAbort: ENTRY PROC [msH: Handle, validate, sendEvenIfInvalidNames, allowDLRecipients: BOOL, abortSend: MailSendSidedoor.AbortSendProc] RETURNS [sent: BOOL ¬ FALSE, failureReason: ROPE, invalidNames, dlsNotAllowed, fakeDls: RNameList] ~ { ENABLE UNWIND => NULL; credentials: MailSend.SendingCredentials; startSendInfo: MSSend.StartSendInfo; xnsH: MSSend.Handle; multinationalNote, plainText, formatting: ROPE ¬ NIL; nsTextFileText: ROPE ¬ NIL; header, headerText: ROPE ¬ NIL; DoAbort: PROC = { sent ¬ FALSE; failureReason ¬ "User Abort"; }; credentials ¬ GetCredentials[msH.credentialsList]; IF credentials = NIL THEN RETURN[FALSE, "No cleariinghouse credentials.", NIL, NIL, NIL]; IF abortSend[msH, FALSE] THEN { DoAbort[]; RETURN }; FOR item: LIST OF BodyPartInfo ¬ msH.bodyParts, item.rest WHILE item # NIL DO thisItem: BodyPartInfo ¬ item.first; SELECT thisItem.type FROM MailBasicsItemTypes.header => headerText ¬ thisItem.data; MailBasicsItemTypes.multinationalNote => multinationalNote ¬ thisItem.data; MailBasicsItemTypes.plainTextForFormatting => plainText ¬ thisItem.data; MailBasicsItemTypes.tioga1 => formatting ¬ thisItem.data; ENDCASE; IF abortSend[msH, FALSE] THEN { DoAbort[]; RETURN }; ENDLOOP; BEGIN IF ( plainText # NIL ) AND ( formatting # NIL ) THEN { msg: ROPE = "%g for formatted message (%g bytes) is longer than the max allowed (%g bytes)\n"; sum: INT ¬ plainText.Length[] + formatting.Length[]; IF sum > maxForAttribute THEN RETURN[FALSE, IO.PutFR[msg, [rope["Plain Text + Formatting"]], [integer[plainText.Length[]]], [integer[maxForAttribute]] ], NIL, NIL, NIL]; }; END; IF useHeadingAttributes THEN { IF ( multinationalNote.Length[] > maxForMultinationalNote ) THEN { sf: SerializedFiling.SerializedFile ¬ [version: 2]; strm: STREAM ¬ IO.ROS[]; segment: SerializedFiling.Segment ¬ NEW[SerializedFiling.SegmentObject[2]]; sf.file.attributes ¬ NEW[SerializedFiling.AttributeSequenceObject [1] ] ; sf.file.attributes.body[0].type ¬ vanillaNSTextFile; segment.body[0] ¬ 0; segment.body[1] ¬ 2; -- magic, from looking at other serialized files sf.file.attributes.body[0].value ¬ segment; sf ¬ SerializedFiling.SetRopeContents[sf, multinationalNote]; SerializedFiling.PutSerializedFile[strm, sf]; nsTextFileText ¬ IO.RopeFromROS[strm]; multinationalNote ¬ NIL; }; } ELSE { IF ( multinationalNote.Length[] > maxForMultinationalNote ) OR ( plainText # NIL ) THEN { sf: SerializedFiling.SerializedFile ¬ [version: 2]; strm: STREAM ¬ IO.ROS[]; segment: SerializedFiling.Segment ¬ NEW[SerializedFiling.SegmentObject[2]]; isTioga: BOOL ¬ ( plainText # NIL ); IF isTioga THEN sf.file.attributes ¬ NEW[SerializedFiling.AttributeSequenceObject [3] ] ELSE sf.file.attributes ¬ NEW[SerializedFiling.AttributeSequenceObject [1] ] ; sf.file.attributes.body[0].type ¬ vanillaNSTextFile; segment.body[0] ¬ 0; segment.body[1] ¬ 2; -- magic, from looking at other serialized files sf.file.attributes.body[0].value ¬ segment; sf ¬ SerializedFiling.SetRopeContents[sf, multinationalNote]; IF isTioga THEN { sf.file.attributes.body[1].type ¬ MailFormat.lastInterlispType-1; sf ¬ SerializedFiling.SetRopeAttribute[sf, MailFormat.lastInterlispType-1, plainText]; sf.file.attributes.body[2].type ¬ MailFormat.lastInterlispType; sf ¬ SerializedFiling.SetRopeAttribute[sf, MailFormat.lastInterlispType, formatting]; plainText ¬ formatting ¬ NIL; }; SerializedFiling.PutSerializedFile[strm, sf]; nsTextFileText ¬ IO.RopeFromROS[strm]; multinationalNote ¬ NIL; }; }; BEGIN ENABLE RuntimeError.BoundsFault => GOTO cant; errorCode: MailParse.ParseErrorCode; index: INT; included: MSUtils.NameBodyPairList; notIncluded: MSUtils.NameBodyPairList; IF useHeadingAttributes THEN [header, errorCode, index, included, notIncluded] ¬ MSUtils.HeaderFromText[headerText, TRUE, plainText, formatting] ELSE [header, errorCode, index, included, notIncluded] ¬ MSUtils.HeaderFromText[headerText, TRUE, NIL, NIL]; SELECT errorCode FROM none => NULL; badFieldName => RETURN[FALSE, "badFieldName in header", NIL, NIL, NIL]; badFieldBody => RETURN[FALSE, "badFieldBody in header", NIL, NIL, NIL]; truncated => RETURN[FALSE, "header truncated", NIL, NIL, NIL]; ENDCASE => ERROR; EXITS cant => RETURN[FALSE, "Message + heading info is too long to send formatted", NIL, NIL, NIL]; END; IF abortSend[msH, FALSE] THEN { DoAbort[]; RETURN }; xnsH ¬ MSSend.Create[]; startSendInfo ¬ MSSend.StartSend[handle: xnsH, senderPwd: credentials.credentials.password, sender: MSUtils.XNSRNameFromRope[credentials.credentials.rName.name], returnTo: MSUtils.XNSRNameFromRope[credentials.returnTo.name]]; SELECT startSendInfo FROM ok => credentials.authenticated ¬ TRUE; badPwd => RETURN[FALSE, "badPwd.", NIL, NIL, NIL]; badSender => RETURN[FALSE, "badSender.", NIL, NIL, NIL]; badReturnTo => RETURN[FALSE, "badReturnTo", NIL, NIL, NIL]; allDown => RETURN[FALSE, "allDown.", NIL, NIL, NIL]; ENDCASE => ERROR; FOR recip: MailBasics.RNameList ¬ msH.recipients, recip.rest WHILE recip # NIL DO thisRecip: MSBasics.RName ¬ MSRNameFromMailBasicsRName[recip.first]; MSSend.AddRecipient[xnsH, thisRecip]; ENDLOOP; MSSend.StartItem[xnsH, MailBasicsItemTypes.header]; MSSend.AddToItem[xnsH, header]; IF multinationalNote # NIL THEN { MSSend.StartItem[xnsH, MailBasicsItemTypes.multinationalNote]; MSSend.AddToItem[xnsH, multinationalNote]; }; IF nsTextFileText # NIL THEN { MSSend.StartItem[xnsH, MailBasicsItemTypes.nsTextFile]; MSSend.AddToItem[xnsH, nsTextFileText]; }; IF abortSend[msH, TRUE] THEN { DoAbort[]; RETURN }; sent ¬ TRUE; -- It's success unless MSSend.SendFailed is raised... BEGIN xnsInvalidNames: MSSend.InvalidNameList; [sent, xnsInvalidNames] ¬ MSSend.Send[xnsH, validate, allowDLRecipients ! MSSend.SendFailed => { sent ¬ NOT notDelivered; -- why not just FALSE ??? failureReason ¬ why; CONTINUE; };]; IF ( xnsInvalidNames # NIL ) AND ( xnsInvalidNames.length > 0 ) THEN FOR i: CARDINAL IN [0..xnsInvalidNames.length) DO invalidNames ¬ CONS[GetIthRecipient[msH.recipients, xnsInvalidNames[i].id-1], invalidNames]; ENDLOOP; IF NOT allowDLRecipients THEN [invalidNames, dlsNotAllowed, fakeDls] ¬ WhatKindOfInvalid[invalidNames]; END; }; GetIthRecipient: PROC[rL: RNameList, i: CARDINAL] RETURNS[MailBasics.RName] = { UNTIL i = 0 DO IF rL = NIL THEN EXIT; rL ¬ rL.rest; i ¬ i - 1; ENDLOOP; RETURN[IF rL = NIL THEN [$none, NIL] ELSE rL.first]; }; WhatKindOfInvalid: PROC[nL: RNameList] RETURNS[iL, dL, fake: RNameList] = { c: XNSCH.Conversation ¬ XNSCH.InitiateConversation[]; FOR rL: RNameList ¬ nL, rL.rest UNTIL rL = NIL DO item: XNSCH.Item; noSuch: BOOL ¬ FALSE; item ¬ XNSCH.LookupItemProperty[c, XNSCHName.NameFromRope[rL.first.name], CHEntries.userData ! XNSCH.Error => { noSuch ¬ ( code = noSuchObject ); CONTINUE; } ].item; SELECT TRUE FROM noSuch => iL ¬ CONS[rL.first, iL]; -- no such object ( item # NIL ) => fake ¬ CONS[rL.first, fake]; -- has a file service ENDCASE => dL ¬ CONS[rL.first, dL]; -- no file service => dl?? ENDLOOP; XNSCH.TerminateConversation[c]; }; XNSUserCredentialsProc: MailUtils.UserCredentialsProc = { password, nameAsRope: ROPE; identity: XNSCredentials.Identity ¬ XNSCredentials.GetIdentity[]; IF identity = NIL THEN RETURN[NIL]; [nameAsRope, password] ¬ GetCHName[identity]; IF password = NIL THEN RETURN[NIL]; creds ¬ NEW[MailUtils.CredentialsRec ¬ [[$xns, nameAsRope], password] ]; }; GetCHName: PROC[chidentity: XNSAuth.Identity] RETURNS[nameAsRope, password: ROPE] = { chname: XNSCHName.Name; chconv: XNSCH.Conversation; [chname, password, ] ¬ XNSAuth.GetIdentityDetails[chidentity]; chconv ¬ XNSCH.InitiateConversation[chidentity]; chname ¬ XNSCH.Lookup[chconv, chname]; -- get distinquished name XNSCH.TerminateConversation[chconv]; RETURN[XNSCHName.RopeFromName[chname], password]; }; XNSLoggedInUserProc: MailUtils.LoggedInUserProc = { simpleCreds: MailUtils.Credentials ¬ XNSUserCredentialsProc[]; IF simpleCreds = NIL THEN RETURN[FALSE]; RETURN[ ( simpleCreds.rName.name.Equal[creds.rName.name, FALSE] ) AND ( simpleCreds.password.Equal[creds.password, FALSE] ) ]; }; XNSLocalNameProc: MailUtils.LocalNameProc = { name: XNSCHName.Name; name ¬ XNSCHName.NameFromRope[rName.name ! XNSCHName.FieldTooLong => CONTINUE]; RETURN[ name.object ]; }; XNSWhoIsLoggedInProc: MailUtils.WhoIsLoggedInProc = { name: XNSAuth.Name ~ XNSAuth.GetIdentityDetails[XNSCredentials.GetIdentity[]].name; RETURN[XNSCHName.RopeFromName[name]]; }; GetCredentials: PROC [credentialsList: SendingCredentialsList] RETURNS [credentials: MailSend.SendingCredentials] ~ { simpleCreds: MailUtils.Credentials; FOR cl: SendingCredentialsList ¬ credentialsList, cl.rest WHILE cl # NIL DO IF cl.first.credentials.rName.ns = $xns THEN RETURN[cl.first]; ENDLOOP; simpleCreds ¬ XNSUserCredentialsProc[]; IF simpleCreds = NIL THEN RETURN[NIL]; credentials ¬ NEW[MailSend.SendingCredentialsRec ¬ [ credentials: simpleCreds, authenticated: TRUE, returnTo: simpleCreds.rName] ]; RETURN[credentials]; }; MSRNameFromMailBasicsRName: PROC[mbRName: MailBasics.RName] RETURNS[msRName: MSBasics.RName] = { SELECT mbRName.ns FROM $xns => RETURN[MSUtils.XNSRNameFromRope[mbRName.name]]; ENDCASE => RETURN[MSUtils.XNSRNameFromRope[Rope.Concat[mbRName.name, "::"]]]; }; highXNS: MSBasics.BodyPartType = 499; MSItemFromMailBasicsItem: PROC[type: MailBasics.ItemType] RETURNS[msType: MSBasics.BodyPartType] = { SELECT type FROM MailBasicsItemTypes.lastItem => msType ¬ MSBasics.lastBodyPart; MailBasicsItemTypes.tioga1 => msType ¬ MailBasicsItemTypes.fullFormatting; --special mapping < highXNS => msType ¬ type; -- direct map ENDCASE => msType ¬ highXNS; -- hopefully won't see these for awhile }; MSParseNameProc: MailParse.ParseNameProc = { temp: STREAM; name: XNSCHName.Name; ok: BOOL ¬ TRUE; pvtDL: BOOL ¬ FALSE; char: CHAR; lookForSemiNext: BOOL ¬ FALSE; i: INT ¬ uninterpreted.Length[]; ropeName: ROPE; IF i = 0 THEN RETURN[TRUE, [$xns, NIL]]; recognized ¬ FALSE; UNTIL i = 0 DO SELECT uninterpreted.Fetch[i-1] FROM ' , '\t => i ¬ i - 1; ENDCASE => EXIT; ENDLOOP; IF i = 0 THEN RETURN[TRUE, [$xns, NIL]]; temp ¬ IO.ROS[]; FOR j: INT IN [0..i) DO SELECT ( char ¬ uninterpreted.Fetch[j] ) FROM ': => lookForSemiNext ¬ TRUE; '; => { IF lookForSemiNext THEN { pvtDL ¬ TRUE; EXIT }; temp.PutChar[char]; }; '@ => { IF lookForSemiNext THEN temp.PutChar[':]; temp.PutChar['%]; }; ENDCASE => { IF lookForSemiNext THEN temp.PutChar[':]; lookForSemiNext ¬ FALSE; temp.PutChar[char]; }; ENDLOOP; IF pvtDL THEN RETURN[TRUE, [$file, IO.RopeFromROS[temp]] ]; ropeName ¬ IO.RopeFromROS[temp]; RETURN[TRUE, [$xns, ropeName]]; }; CharIndex: TYPE = INT; nullIndex: CharIndex = LAST[CharIndex]; DuplicateNameRecord: TYPE = RECORD [rName: RName, seenOnSecondPass: BOOL]; DuplicateName: TYPE = REF DuplicateNameRecord; MakeHeader: MailAnswer.AnswerProc = { outBuffer: STREAM; getCharIndex: CharIndex; pH: MailParse.ParseHandle; havePH: BOOL ¬ FALSE; idIndex: CharIndex ¬ nullIndex; dateIndex: CharIndex ¬ nullIndex; subjectIndex: CharIndex ¬ nullIndex; fromFieldErrorIndex: CharIndex ¬ nullIndex; startOfHeaderLine: CharIndex ¬ nullIndex; parseErrorEncountered: BOOL ¬ FALSE; StuffRope: PROC[r: ROPE] = { outBuffer.PutRope[r] }; FieldIndex: TYPE = {id, reply, sender, from, to, cc, c, bcc, date, subject}; -- order is significant! knownField: ARRAY FieldIndex OF ROPE = ["Message-ID", "Reply-to", "Sender", "From", "To", "cc", "c", "bcc", "Date", "Subject"]; -- order corresponds to FieldIndex answerTarget: FieldIndex ¬ SUCC[from]; originName: RName; originXNSCHName: XNSCHName.Name; -- ThreePartName originIndex: FieldIndex ¬ reply; -- anything except sender or from answerTargetBodyCharIndex: CharIndex ¬ nullIndex; targetEqualsOrigin: BOOL ¬ TRUE; namesOutput: BOOL ¬ FALSE; replyerCCed: BOOL ¬ FALSE; ccCount: CARDINAL ¬ 0; duplicateHead: LIST OF DuplicateName; GetNextChar: PROC RETURNS [char: CHAR] = { IF getCharIndex >= inputLength THEN RETURN [MailParse.endOfInput]; char ¬ getChar[getCharIndex]; getCharIndex ¬ getCharIndex + 1; }; InitParse: PROC = { pH ¬ MailParse.InitializeParse[]; havePH ¬ TRUE }; FinParse: PROC = { MailParse.FinalizeParse[pH]; havePH ¬ FALSE }; ProcessFields: PROC [Inner: PROC [index: FieldIndex] RETURNS[parseError: BOOL] ] RETURNS[errorSeen: BOOL] = { OPEN MailParse; fieldName: ROPE; getCharIndex ¬ 0; InitParse[]; errorSeen ¬ FALSE; DO { startOfHeaderLine ¬ getCharIndex; fieldName ¬ GetFieldName[pH, GetNextChar ! MailParse.ParseError => GOTO badIndex]; IF fieldName = NIL THEN EXIT; FOR i: FieldIndex IN FieldIndex DO IF fieldName.Equal[knownField[i], FALSE] THEN { IF Inner[i] THEN errorSeen ¬ TRUE; EXIT}; REPEAT FINISHED => [] ¬ GetFieldBody[pH, GetNextChar, TRUE]; ENDLOOP; EXITS badIndex => DO SELECT GetNextChar[] FROM '\r, '\l, MailParse.endOfInput => EXIT; ENDCASE; ENDLOOP; }; ENDLOOP; FinParse[]; }; -- of ProcessFields -- AnalyzeOrigin: PROC [index: FieldIndex] = { fieldBodyStartIndex: CharIndex = getCharIndex; ProcessName: PROC [rName: RName] RETURNS[nameToWrite: ROPE ¬ NIL] = { IF ~(originIndex = sender OR index = originIndex OR index = reply) THEN { originIndex ¬ index; originName ¬ rName; originXNSCHName ¬ XNSCHName.NameFromRope[rName.name ! XNSCHName.FieldTooLong => CONTINUE ]; originName.name ¬ MSUtils.StrippedName[originName.name, NIL, NIL]; }; IF index < answerTarget OR (index = from AND answerTarget = sender) THEN { answerTarget ¬ index; answerTargetBodyCharIndex ¬ fieldBodyStartIndex; }; }; -- of ProcessName -- MailParse.NameList[pH, $xns, GetNextChar, ProcessName ! MailParse.ParseError => { IF index = from THEN fromFieldErrorIndex ¬ fieldBodyStartIndex ELSE { errorIndex ¬ fieldBodyStartIndex; }; [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE]; CONTINUE; }]; }; -- of AnalyzeOrigin -- FirstPass: PROC [index: FieldIndex] RETURNS[parseError: BOOL] = { parseError ¬ FALSE; SELECT index FROM id => { idIndex ¬ getCharIndex; [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE] }; IN [reply .. from] => AnalyzeOrigin[index]; IN [to .. bcc] => IF FillNameField[firstPass: TRUE] THEN parseError ¬ TRUE; date => { dateIndex ¬ getCharIndex; [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE] }; subject => { subjectIndex ¬ getCharIndex; [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE] }; ENDCASE => [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE]; }; -- of FirstPass -- FillField: PROC = { field: ROPE ¬ MailParse.GetFieldBody[pH, GetNextChar]; IF field.Length[] > 120 THEN -- magic number to correspond to AnswerImpl { outBuffer.PutRope[field.Substr[0, 120]]; outBuffer.PutRope[" ..."]} ELSE outBuffer.PutRope[field]; }; -- of FillField -- AddedToDuplicateList: PROC [rName: RName, firstPass: BOOL] RETURNS [added: BOOL] = { s: ROPE ¬ rName.name; FOR itemL: LIST OF DuplicateName ¬ duplicateHead, itemL.rest UNTIL itemL = NIL DO IF Rope.Equal[itemL.first.rName.name, s, FALSE] THEN { IF firstPass THEN RETURN[FALSE]; added ¬ ~itemL.first.seenOnSecondPass; itemL.first.seenOnSecondPass ¬ TRUE; RETURN }; ENDLOOP; duplicateHead ¬ CONS[NEW[DuplicateNameRecord ¬ [rName: rName, seenOnSecondPass: FALSE]], duplicateHead]; RETURN[TRUE] }; -- of AddedToDuplicateList -- ProcessAnswerTarget: PROC = { namesOutput: BOOL ¬ FALSE; ProcessName: PROC [rName: RName] RETURNS[nameToWrite: ROPE] = { rName.name ¬ MSUtils.StrippedName[rName.name, originXNSCHName.domain, originXNSCHName.organization]; IF rName.name = NIL THEN RETURN; [] ¬ AddedToDuplicateList[rName, FALSE]; IF namesOutput THEN outBuffer.PutRope[", "]; namesOutput ¬ TRUE; RETURN[rName.name]; }; -- of ProcessName -- getCharIndex ¬ answerTargetBodyCharIndex; InitParse[]; MailParse.NameList[pH, $xns, GetNextChar, ProcessName, StuffRope ! MailParse.ParseError => { [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE]; CONTINUE }]; FinParse[]; }; -- of ProcessAnswerTarget -- AnalyzeAnswerTarget: PROC = { ProcessName: PROC [rName: RName] RETURNS[nameToWrite: ROPE ¬ NIL] = { rName.name ¬ MSUtils.StrippedName[rName.name, originXNSCHName.domain, originXNSCHName.organization]; targetEqualsOrigin ¬ targetEqualsOrigin AND Rope.Equal[rName.name, originName.name, FALSE]; IF ~AddedToDuplicateList[rName, TRUE] THEN ccCount ¬ ccCount - 1; }; -- of ProcessName -- IF answerTargetBodyCharIndex = 0 THEN ERROR MailParse.ParseError[badFieldName]; getCharIndex ¬ answerTargetBodyCharIndex; InitParse[]; MailParse.NameList[pH, $xns, GetNextChar, ProcessName, ! MailParse.ParseError => { [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE]; answerError ¬ TRUE; CONTINUE }]; FinParse[]; }; -- of AnalyzeAnswerTarget -- FillNameField: PROC [firstPass: BOOL] RETURNS[parseError: BOOL] = { lineIndex: INT ¬ getCharIndex; firstOutput: BOOL ¬ TRUE; ProcessName: PROC [rName: RName] RETURNS[nameToWrite: ROPE ¬ NIL] = { new: BOOL; rName.name ¬ MSUtils.StrippedName[rName.name, originXNSCHName.domain, originXNSCHName.organization]; new ¬ AddedToDuplicateList[rName, firstPass]; IF NOT new THEN RETURN[NIL]; IF firstPass THEN { ccCount ¬ ccCount + 1; IF ~replyerCCed AND rName.name.Equal[userRName.name, FALSE] THEN replyerCCed ¬ TRUE; RETURN[NIL] }; IF rName.name = NIL THEN RETURN; IF firstOutput THEN firstOutput ¬ FALSE; IF namesOutput THEN outBuffer.PutRope[", "]; namesOutput ¬ TRUE; RETURN[rName.name]; }; -- of ProcessName -- parseError ¬ FALSE; MailParse.NameList[pH, $xns, GetNextChar, ProcessName, StuffRope ! MailParse.ParseError => { [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE]; parseError ¬ TRUE; errorIndex ¬ lineIndex; CONTINUE; }; ]; }; -- of FillNameField -- SecondPass: PROC [index: FieldIndex] RETURNS[parseError: BOOL ¬ FALSE] = { SELECT index FROM IN [to .. bcc] => [] ¬ FillNameField[firstPass: FALSE]; ENDCASE => [] ¬ MailParse.GetFieldBody[pH, GetNextChar, TRUE]; }; -- of SecondPass -- answerError ¬ FALSE; errorIndex ¬ nullIndex; outBuffer ¬ IO.ROS[]; { answerError ¬ ProcessFields[FirstPass ! MailParse.ParseError => GO TO BadMessage]; IF answerTargetBodyCharIndex = nullIndex THEN { IF havePH THEN FinParse[]; RETURN[TRUE, answer, IF ( fromFieldErrorIndex # nullIndex ) THEN fromFieldErrorIndex ELSE errorIndex]; }; AnalyzeAnswerTarget[ ! MailParse.ParseError => GO TO BadMessage]; outBuffer.PutRope["Subject: Re: "]; IF subjectIndex # nullIndex THEN { subject: ROPE; pos: INT ¬ 0; len: INT; foundRe: BOOL ¬ FALSE; prevRe: INT ¬ 0; getCharIndex ¬ subjectIndex; InitParse[]; subject ¬ MailParse.GetFieldBody[pH, GetNextChar]; FinParse[]; len ¬ subject.Length[]; DO np: INT ¬ subject.Find["Re:", pos, FALSE]; IF np < 0 THEN EXIT; IF ~(np = prevRe OR np = prevRe + 1) THEN EXIT; -- not at beginning foundRe ¬ TRUE; prevRe ¬ np; pos ¬ np+3; -- skip over Re: ENDLOOP; IF foundRe AND (pos < len) THEN WHILE subject.Fetch[pos] = IO.SP DO pos ¬ pos + 1; ENDLOOP; outBuffer.PutRope[subject.Substr[pos]]; }; outBuffer.PutRope["\rIn-reply-to: "]; IF idIndex = nullIndex THEN { outBuffer.PutChar['"]; IF (IF answerTarget = reply THEN targetEqualsOrigin ELSE (ccCount = 0 OR (replyerCCed AND ccCount = 1))) THEN outBuffer.PutRope["Your"] ELSE { orName: ROPE ¬ originName.name; orLen: INT ¬ orName.Length[]; outBuffer.PutRope[orName]; outBuffer.PutChar['']; IF ( orLen # 0 ) OR Ascii.Lower[orName.Fetch[orLen - 1]] # 's THEN outBuffer.PutChar['s]; }; outBuffer.PutRope[" message of "]; InitParse[]; IF dateIndex # nullIndex THEN {getCharIndex ¬ dateIndex; FillField[]}; outBuffer.PutChar['"]; } ELSE { getCharIndex ¬ idIndex; InitParse[]; FillField[]; }; FinParse[]; outBuffer.PutRope["\rTo: "]; ProcessAnswerTarget[]; outBuffer.PutRope[IF cForCopies THEN "\rc: " ELSE "\rcc: "]; IF answerTarget = reply THEN outBuffer.PutRope[MSUtils.StrippedName[userRName.name, originXNSCHName.domain, originXNSCHName.organization]] ELSE [] ¬ ProcessFields[SecondPass ! MailParse.ParseError => GO TO BadMessage]; outBuffer.PutChar['\r]; answer ¬ IO.RopeFromROS[outBuffer]; EXITS BadMessage => { IF havePH THEN FinParse[]; answerError ¬ TRUE; IF errorIndex = nullIndex THEN { errorIndex ¬ startOfHeaderLine; }; }; }; RETURN[answerError, answer, errorIndex] }; Init: PROC = { mailSendProcs: MailSend.MailSendProcsRef ¬ NEW[MailSend.MailSendProcs ¬ [ which: $xns, Send: SendViaXNS ]]; mailSendSidedoorProcs: MailSendSidedoor.MailSendSidedoorProcsRef ¬ NEW[MailSendSidedoor.MailSendSidedoorProcs ¬ [ which: $xns, SendWithAbort: SendViaXNSWithAbort ]]; MailSend.RegisterMailSendProcs[mailSendProcs]; MailSendSidedoor.RegisterMailSendSidedoorProcs[mailSendSidedoorProcs]; MailParse.RegisterNameProc[$xns, MSParseNameProc]; MailAnswer.RegisterAnswerProc[$xns, MakeHeader]; MailUtils.RegisterUserCredentialsProc[$xns, XNSUserCredentialsProc, XNSLoggedInUserProc, XNSLocalNameProc, XNSWhoIsLoggedInProc]; }; Init[]; END. V MSMailSendImpl.mesa Copyright Σ 1988, 1989, 1990, 1991 by Xerox Corporation. All rights reserved. Doug Terry, November 11, 1988 12:23:03 pm PST Wes Irish, January 26, 1989 6:31:17 pm PST Willie-Sue, July 9, 1990 2:56:02 pm PDT Willie-Sue Orr, July 9, 1990 2:58:24 pm PDT -0100 -0100 Willie-s, December 10, 1991 7:56 pm PST Buffers MailSend calls building a message until Send is called. Once Send is called the message now residing in the handle is sent using the appropriate transport (as explicitly specified by the user or determined by this module if not explicitly specified by the user). SimpleFeedback, Registered procedures create the encoded header look at user data, which is their file service (dl's won't have this) don't use distinquished name - it might be in a different domain/org from the login name MUST used distinguished name so retrieving works!!! at least get the format correct PROC[uninterpreted: ROPE] RETURNS [recognized: BOOL, rName: RName] remove trailing white space we do this for checking only - don't want the default domain & org name _ XNSCHName.NameFromRope[ropeName ! XNSCHName.FieldTooLong => { ok _ FALSE; CONTINUE} ]; IF NOT ok THEN RETURN; AnswerProc: TYPE ~ PROC[getChar: PROC [INT] RETURNS [CHAR], inputLength: INT, userRName: RName, cForCopies: BOOL _ FALSE ] RETURNS [answerError: BOOL, answer: ROPE, errorIndex: INT]; SimpleFeedback.PutF[$xnsmail, $oneLiner, $debug, "errorIndex _ fieldBodyStartIndex (%g)\n", [integer[errorIndex]] ]; SimpleFeedback.PutF[$xnsmail, $oneLiner, $debug, "errorIndex _ lineIndex (%g)\n", [integer[errorIndex]] ]; main body of AnswerImpl SimpleFeedback.PutF[$xnsmail, $oneLiner, $debug, "errorIndex _ nullIndex (%g)\n", [integer[errorIndex]] ]; find out who it's from and where the interesting indices are make Subject field make In-reply-to field fill in target (To:) field of answer form fill in cc: field empty line at end of header SimpleFeedback.PutF[$xnsmail, $oneLiner, $debug, "errorIndex _ startOfHeaderLine (%g)\n", [integer[errorIndex]] ]; SimpleFeedback.PutF[$xnsmail, $oneLiner, $debug, "errorIndex: (%g)\n", [integer[errorIndex]] ]; Κ1–(cedarcode) style•NewlineDelimiter ™codešΟb™Kšœ ΟeœC™NKšœ*Οk™-Kšœ*™*K™'K™8K™'—K˜K™K™šŸ ˜ K˜Kšœ˜Kšœ˜Kšœ˜KšŸœ˜Kšœ˜Kšœ˜K˜ Kšœ ˜ K˜K˜ K˜ K˜K˜ K˜ Kšœ˜Kšœ˜Kšœ˜Kšœ Ÿœ˜!K˜K™Kšœ˜KšŸœ˜Kšœ ˜ K˜KšŸœ˜—K˜šΡblnœŸœŸ˜KšŸœŸœ€Ÿœ˜±šœŸ˜K˜šŸ˜KšΟn œ˜Kš‘ œ‘ œ˜A——K˜KšŸœŸœŸœ˜KšŸœŸœŸœŸœ˜KšœŸœ˜'KšœŸœ˜Kšœ Ÿœ˜'KšœŸœ#˜?KšœŸœ˜+K˜KšœŸœ˜$Kš œŸœŸœŸœŸœ˜.Kšœ8Οc ˜BK˜KšœŸœŸœ˜"—head™š Πbn œŸœ1ŸœŸœŸœŸœ˜Kš‘œ%ŸœŸœ˜GKšœcŸœ˜}K˜K˜—š£œŸœŸœDŸœ,˜“KšŸœŸœŸœŸœ6˜fKšŸœŸœŸœ˜Kšœ)˜)Kšœ$˜$Kšœ˜Kšœ*ŸœŸœ˜5KšœŸœŸœ˜KšœŸœŸœ˜š‘œŸœ˜KšœŸœ˜ K˜K˜—K˜K˜2KšŸœŸœŸœŸœŸœ$ŸœŸœŸœ˜YKšŸœŸœŸœŸœ˜4š ŸœŸœŸœ)ŸœŸœŸ˜MK˜$šŸœŸ˜K˜9K˜KK˜HK˜9KšŸœ˜—KšŸœŸœŸœŸœ˜4KšŸœ˜—šŸ˜š ŸœŸœŸœŸœŸœ˜6KšœŸœU˜^KšœŸœ,˜4šŸœŸ˜Kš ŸœŸœŸœlŸœŸœŸœ˜‹—K˜—KšŸœ˜—K™šŸœŸœ˜šŸœ:Ÿœ˜BK˜3KšœŸœŸœŸœ˜Kšœ%Ÿœ$˜LKšœŸœ1˜IK˜4K˜Kšœ’0˜EK˜+K˜=K˜-KšœŸœ˜&KšœŸœ˜K˜K˜—šŸœ˜šŸœ:ŸœŸœŸœ˜YK˜3KšœŸœŸœŸœ˜Kšœ%Ÿœ$˜LKšœ ŸœŸœ˜$Kš Ÿœ ŸœŸœ0ŸœŸœ1˜¦K˜4K˜Kšœ’0˜EK˜+K˜=šŸœ Ÿœ˜K˜AK˜VK˜?K˜UKšœŸœ˜K˜—K˜-KšœŸœ˜&KšœŸœ˜K˜K˜—K˜——K™šŸ˜KšŸœŸœ˜-Kšœ$˜$KšœŸœ˜ Kšœ#˜#Kšœ&˜&šŸœŸœXŸœ˜KšŸœXŸœŸœŸœ˜l—šŸœ Ÿ˜KšœŸœ˜ Kš œŸœŸœŸœŸœŸœ˜GKš œŸœŸœŸœŸœŸœ˜GKš œ ŸœŸœŸœŸœŸœ˜>KšŸœŸœ˜—šŸœ˜ Kš ŸœŸœ:ŸœŸœŸœ˜V—KšŸœ˜—K˜KšŸœŸœŸœŸœ˜4K˜K•StartOfExpansionf[handle: GVSend.Handle, senderPwd: ROPE, sender: ROPE, returnTo: ROPE _ NIL, validate: BOOL]˜αšŸœŸ˜Kšœ"Ÿœ˜'Kš œ ŸœŸœ ŸœŸœŸœ˜2Kš œ ŸœŸœŸœŸœŸœ˜8Kš œŸœŸœŸœŸœŸœ˜Kšœ*˜*K˜—šŸœŸœŸœ˜K˜7Kšœ'˜'K˜—K˜KšŸœŸœŸœŸœ˜3KšœŸœ’œ’˜CšŸ˜Kšœ(˜(˜Gšœ˜KšœŸœ’˜3K˜KšŸœ˜ Kšœ˜——šŸœŸœŸœ Ÿ˜DšŸœŸœŸœŸ˜1KšœŸœI˜\KšŸœ˜—šŸœŸœŸ˜K˜I——KšŸœ˜—K˜K˜—š‘œŸœŸœŸœ˜OšŸœŸ˜KšŸœŸœŸœŸœ˜K˜ K˜ KšŸœ˜—Kš ŸœŸœŸœŸœ ŸœŸœ ˜4K˜K˜—š‘œŸœŸœ˜KKšœŸœŸœ˜5šŸœŸœŸœŸ˜1KšœŸœ˜KšœŸœŸœ˜K™EšœŸœSŸœ ˜oK˜!KšŸœ˜ K˜ —šŸœŸœŸ˜KšœŸœ’˜4Kšœ Ÿœ Ÿœ’˜DKšŸœ Ÿœ’˜>—KšŸœ˜—KšŸœ˜K˜K˜—š‘œ#˜9KšœŸœ˜K˜AKš Ÿœ ŸœŸœŸœŸœ˜#K˜-Kš Ÿœ ŸœŸœŸœŸœ˜#K–[name: CHNameP2V0.Name]šœŸœ=˜HK˜K˜—š‘ œŸœŸœŸœ˜UKšœ˜KšœŸœ˜K˜>Kšœ ’ œA™XKš3™3Kšœ Ÿœ"˜0Kšœ Ÿœ’˜AKšŸœ˜$KšŸœ+˜1K˜K™—š‘œ ˜3K˜>Kš ŸœŸœŸœŸœŸœ˜(KšŸœ3ŸœŸœ-Ÿœ˜~K˜K˜—š‘œ˜-Kšœ˜KšœEŸœ˜OKšŸœ˜K˜K˜—š‘œ!˜5K˜SKšŸœ˜%K˜K˜—š£œŸœ+Ÿœ/˜uK˜#šŸœ7ŸœŸœŸ˜KKšŸœ&ŸœŸœ ˜>KšŸœ˜—K–[name: CHNameP2V0.Name]˜'Kš ŸœŸœŸœŸœŸœ˜&šœŸœ#˜4Kšœ˜KšœŸœ˜Kšœ˜—KšŸœ˜K˜K˜—š‘œŸœŸœ˜`K™šŸœ Ÿ˜KšœŸœ)˜7KšŸœŸœ<˜M—K˜K˜—šœ%˜%K˜—š‘œŸœŸœ#˜dšŸœŸ˜K˜?KšœK’˜\Kšœ’ ˜*KšŸœ’'˜E—K˜K˜—š‘œ˜,KšŸœŸœŸœŸœ™BK™KšœŸœ˜ Kšœ˜KšœŸœŸœ˜KšœŸœŸœ˜KšœŸœ˜ KšœŸœŸœ˜KšœŸœ˜ Kšœ Ÿœ˜Kš ŸœŸœŸœŸœ Ÿœ˜(Kšœ Ÿœ˜šŸœŸ˜šŸœŸ˜$K˜KšŸœŸœ˜—KšŸœ˜—Kš ŸœŸœŸœŸœ Ÿœ˜(KšœŸœŸœ˜šŸœŸœŸœŸ˜šŸœ#Ÿ˜-KšœŸœ˜šœ˜KšŸœŸœ ŸœŸœ˜/Kšœ˜K˜—šœ˜KšŸœŸœ˜)Kšœ˜K˜—šŸœ˜ KšŸœŸœ˜)KšœŸœ˜Kšœ˜Kšœ˜——KšŸœ˜—Kš ŸœŸœŸœŸœ Ÿœ˜;K™BKšœ Ÿœ˜ KšœJŸœŸœ™]KšŸœŸœŸœŸœ™K˜KšŸœŸœ˜K˜K˜—Kšœ ŸœŸœ˜šœŸœ ˜'K˜—KšœŸœŸœ"Ÿœ˜JKšœŸœŸœ˜.˜K˜—š‘ œ˜%šœ ŸœŸœ ŸœŸœŸœŸœŸœ ŸœŸœ™zKšŸœŸœ ŸœŸœ™;—K™Kšœ Ÿœ˜K˜Kšœ˜KšœŸœŸœ˜K˜K˜!K˜$K˜+K˜)KšœŸœŸœ˜$Kš‘ œŸœŸœŸœ˜4K˜šœ Ÿœ˜Kšœ:’˜R—šœ Ÿœ ŸœŸœ˜&K˜DKšœ’"˜6—KšœŸœ˜&K˜Kšœ˜Kšœ!’˜1Kšœ!’"˜C˜1K˜—šœŸœŸœ˜ K˜—Kšœ ŸœŸœ˜Kšœ ŸœŸœ˜Kšœ Ÿœ˜KšœŸœŸœ˜%K˜š‘ œŸœŸœŸœ˜*KšŸœŸœŸœ˜BK˜K˜ Kšœ˜—K˜Kš‘ œŸœ0Ÿœ˜FK˜Kš‘œŸœ+Ÿœ˜AK˜š‘ œŸœ‘œŸœŸœ ŸœŸœ ŸœŸœ ˜~Kšœ Ÿœ˜K˜K˜ Kšœ Ÿœ˜šŸœ˜K˜!˜ Kšœ7Ÿœ ˜F—KšŸœ ŸœŸœŸœ˜šŸœŸœ Ÿ˜"šŸœ ŸœŸœ˜/KšŸœ Ÿœ Ÿœ˜"KšŸœ˜—KšŸœŸœ'Ÿœ˜šŸœ˜K˜!Kšœt™tK˜——Kšœ-Ÿœ˜3KšŸ ˜ Kšœ˜——Kšœ’˜—K˜š‘ œŸœŸœ Ÿœ˜AKšœ Ÿœ˜šŸœŸ˜šœ˜K˜Kšœ-Ÿœ˜2Kšœ˜—KšŸœ)˜+Kš ŸœŸœŸœŸœŸœ˜Kšœ ˜ K˜Kšœ-Ÿœ˜2Kšœ˜—˜ K˜Kšœ-Ÿœ˜2Kšœ˜—KšŸœ1Ÿœ˜>—šœ’˜K˜——K˜š‘ œŸœ˜KšœŸœ+˜6šŸœŸœ’+˜HK˜EKšŸœ˜—Kšœ’˜—K˜š ‘œŸœŸœŸœ Ÿœ˜TKšœŸœ˜š ŸœŸœŸœ+Ÿœ ŸœŸ˜QšŸœ'ŸœŸœ˜6KšŸœ ŸœŸœŸœ˜ K˜&KšœŸœ˜$KšŸ˜Kšœ˜—KšŸœ˜—KšœŸœŸœ8Ÿœ˜hKšŸœŸœ˜ Kšœ’˜ —K˜š‘œŸœ˜Kšœ ŸœŸœ˜š‘ œŸœŸœŸœ˜?K˜dKšŸœŸœŸœŸœ˜ Kšœ!Ÿœ˜(KšŸœ Ÿœ˜,KšœŸœ˜KšŸœ ˜Kšœ’˜—K˜K˜)K˜ šœ\˜\Kšœ-Ÿœ˜3KšŸœ˜ —K˜ Kšœ’˜—K˜š‘œŸœ˜š ‘ œŸœŸœŸœŸœ˜EK˜dKšœ(Ÿœ)Ÿœ˜[KšŸœŸœŸœ˜AKšœ’˜—K˜KšŸœŸœŸœ$˜OK˜)K˜ šœR˜RKšœ.Ÿœ˜4KšœŸœ˜KšœŸœ˜ —K˜ Kšœ’˜—K˜š ‘ œŸœ ŸœŸœ Ÿœ˜CKšœ Ÿœ˜Kšœ ŸœŸœ˜š ‘ œŸœŸœŸœŸœ˜EKšœŸœ˜ K˜dK˜-Kš ŸœŸœŸœŸœŸœ˜šŸœ Ÿœ˜K˜Kš ŸœŸœ"ŸœŸœŸœ˜TKšŸœŸœ˜ Kšœ˜—KšŸœŸœŸœŸœ˜ KšŸœ ŸœŸœ˜(KšŸœ Ÿœ˜,KšœŸœ˜KšŸœ ˜Kšœ’˜—K˜Kšœ Ÿœ˜šœB˜Bšœ˜Kšœ-Ÿœ˜3Kšœ Ÿœ˜K˜Kšœj™jKšŸœ˜ K˜K˜——Kšœ’˜—K˜š ‘ œŸœŸœ ŸœŸœ˜JšŸœŸ˜KšŸœ.Ÿœ˜7KšŸœ1Ÿœ˜>—Kšœ’˜—K˜Kšœ™K˜KšœŸœ˜K˜Kšœj™jKšœ ŸœŸœ˜K˜šœ˜K˜—Kšœ<™