MSMailRetrieveImpl.mesa
Copyright Ó 1988, 1989, 1990, 1991 by Xerox Corporation. All rights reserved.
Doug Terry, December 12, 1988 6:06:03 pm PST
Willie-sue, November 6, 1992 11:23 am PST
Wes Irish, December 22, 1988 6:11:51 pm PST
The glue between MailRetrieveImpl & MSRetrieveImpl; taken from the old MailRetrieveImpl
DIRECTORY
Ascii,
BasicTime USING [FromNSTime, GMT],
Convert,
IO,
MailBasics,
MailBasicsFileTypes,
MailBasicsItemTypes,
MailBasicsMoreItemTypes,
MailRetrieve,
MailUtils USING [GeneratePostmark, GetTimeFromPostmark],
MSBasics,
MSMessage USING [GetHeaderRope, headingBodyPart],
MSRetrieve,
MSUtils,
PFS,
RefText,
Rope,
SerializedFiling,
SystemNames USING [UserName],
UserProfile,
XNSCHName USING [NameFromRope, RopeFromName];
MSMailRetrieveImpl: CEDAR PROGRAM
IMPORTS Ascii, BasicTime, Convert, IO, MailRetrieve, MailUtils, MSMessage, MSRetrieve, MSUtils, PFS, RefText, Rope, SerializedFiling, SystemNames, UserProfile, XNSCHName
~ BEGIN
STREAM: TYPE ~ IO.STREAM;
ROPE: TYPE ~ Rope.ROPE;
Handle: TYPE ~ MailRetrieve.Handle;
MBXState: TYPE ~ MailRetrieve.MBXState;
ServerState: TYPE ~ MailRetrieve.ServerState;
ServerType: TYPE ~ MailRetrieve.ServerType;
FailureReason: TYPE ~ MailRetrieve.FailureReason;
Handles
A RetrieveHandle allows clients to retrieval mail from one or more mailboxes on one or more different types of mail servers. Associated with each handle is a list of mailboxes. For each mailbox, there is a record that holds the information needed to interact with the mail service maintaining the appropriate mailbox.
XNS Mail Service:
MSInfo: TYPE ~ REF MSInfoRec;
MSInfoRec: TYPE ~ RECORD [
user: MSBasics.CHName,   -- user/mailbox name
envelope: MSBasics.Envelope,  -- envelope of current message
postmark: MailBasics.Timestamp, -- needed later if there is an attachment
sender: ROPE,  -- needed later if there is an attachment
newMsg: BOOL,   -- TRUE when envelope needs to be returned
systemMessage: ROPE,   -- derived from envelope if appropriate
msH: MSRetrieve.Handle,  -- XNS Mail Service handle for mailbox
handle: Handle,  -- for reproting state changes
plainTextFromHeader: ROPE, -- if in header, non-nil until returned as item
formattingFromHeader: ROPE, -- if in header, non-nil until returned as item
bodyAnnotSize: CARD32 ¬ 0, -- size of extra text in body of message
lastBodyPartType: MailBasicsItemTypes.ItemType ¬ MailBasicsItemTypes.header, -- for bodyAnnotSize fix
reportChanges: PROC[Handle, MBXState]
];
Global Variables
msInfoList: LIST OF MSInfo ¬ NIL;
tiogaTextOK: BOOL ¬ TRUE;
AttributeType: TYPE = SerializedFiling.AttributeType;
lastInterlispType: AttributeType = 4919; -- from MailFormatP1516V3
Registered procedures
MSClose: PROC [ref: REF] RETURNS [] ~ {
Releases resources used by this handle. Further use of this handle is illegal.
msInfo: MSInfo = NARROW[ref];
MSRetrieve.Close[msInfo.msH];
};
MSNewUser: PROC [handle: Handle, user: MailBasics.RName, password: ROPE, pollingInterval: CARDINAL, reportChanges: PROC[Handle, MBXState] ] RETURNS [REF] ~ {
Provides new user name and password, and starts authentication and mailbox checking. This can be called several times to associate several mailboxes with the handle.
msInfo: MSInfo ¬ NEW[MSInfoRec];
msInfo.user ¬ XNSCHName.NameFromRope[user.name];
msInfo.msH ¬ MSRetrieve.Create[pollingInterval, IF reportChanges#NIL THEN ChangeReportFromMS ELSE NIL];
msInfo.reportChanges ¬ reportChanges;
msInfo.handle ¬ handle;
MSRetrieve.NewUser[msInfo.msH, msInfo.user, password];
msInfoList ¬ CONS[msInfo, msInfoList];
RETURN[msInfo];
};
MSMailboxState: PROC [ref: REF] RETURNS [mboxState: MBXState] ~ {
Returns the current mailbox state. Will not return "unknown" or "userOK" (These change to "cantAuth" or "allDown" after suitable timeouts if necessary.)
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
mboxState ¬ FromMSMboxState[MSRetrieve.MailboxState[msInfo.msH]];
};
MSNextServer: PROC [ref: REF] RETURNS
[noMore: BOOLEAN, state: ServerState, type: ServerType] ~ {
Returns information about the next server in the mailbox site list of the user (or the first server if reset=TRUE), and that server becomes the "current server". If there is no such server, noMore=TRUE, in which case the next call to "NextServer" will start a new sequence of mail retrieval. If the state is "unknown", attempting to access the mailbox is inadvisable, as the server is probably down. If the state is "empty", there may in fact be mail, as the state is only a hint obtained by polling.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
msState: MSRetrieve.ServerState;
noMore ¬ TRUE; state ¬ unknown; type ¬ $unknown;
[noMore, msState] ¬ MSRetrieve.NextServer[msInfo.msH];
IF NOT noMore THEN {
state ¬ FromMSServerState[msState];
type ¬ $xns;
};
};
MSServerName: PROC [ref: REF] RETURNS [serverName: MailBasics.RName] ~ {
Provides the name of the current server.
msInfo: MSInfo = NARROW[ref];
serverName.ns ¬ $xns;
serverName.name ¬ XNSCHName.RopeFromName[MSRetrieve.ServerName[msInfo.msH]];
};
MSUserName: PROC [ref: REF] RETURNS [userName: MailBasics.RName] ~ {
Provides the name of the current mailbox being accessed.
msInfo: MSInfo = NARROW[ref];
userName.ns ¬ $xns;
userName.name ¬ XNSCHName.RopeFromName[msInfo.user];
};
MSNextMessage: PROC [ref: REF] RETURNS [msgExists, archived, deleted, read: BOOLEAN] ~ {
Returns information about the next message in the mailbox, and that message becomes the "current message". If there is no such message, msgExists=FALSE. If deleted=TRUE then the message is really just a placeholder and has been removed from the mailbox; you should not attempt to access the message. Returning archived=TRUE indicates that the message has been spilled to some file server, and accessing it is likely to be much slower. If read=TRUE then the message is not new, i.e. it has been previously marked as being read.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
[msgExists, read] ¬ MSRetrieve.NextMessage[msInfo.msH];
archived ¬ FALSE;
deleted ¬ FALSE;
IF msgExists THEN {
msInfo.envelope ¬ MSRetrieve.GetMessageEnvelope[msInfo.msH];
msInfo.systemMessage ¬ MSUtils.SystemMessageFromEnvelope[msInfo.envelope];
msInfo.newMsg ¬ TRUE;
};
};
MSStartMessage: PROC [ref: REF] RETURNS [postmark: MailBasics.Timestamp, sender: MailBasics.RName, returnTo: MailBasics.RName] ~ {
Returns information from the envelope of the message.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
msInfo.postmark ¬ postmark ¬ RopeFromMSPostmark[msInfo.envelope];
sender ¬ [ns: $xns, name: MSUtils.RopeFromMSRName[msInfo.envelope.originator]];
msInfo.sender ¬ sender.name;
Note: we should be able to get the proper "reply-to" name out of the message envelope if it is present; unfortunately, MSRetrieve doesn't provide the complete envelope so we'll reply to the originator for now.
returnTo ¬ sender;
};
boilerPlate: ROPE ~ "\r===========> msg from XNSMailUser <===========\r";
MSNextItem: PROC [ref: REF] RETURNS [ih: MailBasics.ItemHeader, currentItem: STREAM] ~ {
Skips the remainder of any previous item, then delivers the header of the next item.
This implementation is responsible for mediating between the types of items that Grapevine returns and those that this interface returns; ditto for XNS mail body parts.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
bp: MSBasics.BodyPartInfo;
isTiogaText: BOOL ¬ FALSE;
DoAttachment: PROC[baseName: ROPE, fileType: PFS.FileType] = {
msg: ROPE;
this: ROPE ¬ MSGetItemAsRope[ref];
we can't try to strip out the RFC822 extra headers inside an nsTextFile, because they are embedded inside a serialized file - we'd have to pick apart the serialized file and re-write it, which is too much of a pain
IF UserProfile.Boolean["XNSMail.FlushMsgsWithAttachments", FALSE] THEN {
who: ROPE = SystemNames.UserName[]; -- unix name needed
wDir: ROPE ¬ UserProfile.Token["XNSMail.attachmentsDir", IO.PutFR1["/tilde/%g/.cedar/xnsMailAttachments/", [rope[who]]] ];
fileName: ROPE ¬ IO.PutFR["%g-%g.txt", [rope[baseName]],
[rope[Intelligible[msInfo.postmark, msInfo.sender]]] ];
fullName: PFS.PATH ¬ PFS.AbsoluteName[short: PFS.PathFromRope[fileName], wDir: PFS.PathFromRope[wDir] ];
fileLength: INT ¬ 0;
outStream: STREAM;
outStream ¬ PFS.StreamOpen[fileName: fullName, accessOptions: create ! PFS.Error => CONTINUE];
IF outStream = NIL THEN {
fullName ¬ PFS.AbsoluteName[short: PFS.PathFromRope[fileName], wDir: PFS.PathFromRope[IO.PutFR1["/tilde/%g/", [rope[who]] ]] ];
outStream ¬ PFS.StreamOpen[fileName: fullName, accessOptions: create ! PFS.Error => CONTINUE];
IF outStream = NIL THEN ERROR MailRetrieve.Failed[$couldntCreateFile, PFS.RopeFromPath[fullName]];
};
outStream.PutRope[this];
fileLength ¬ outStream.GetLength[];
outStream.Close[];
msg ¬ IO.PutFLR["%g==> %g stored as:\r\t%g (%g bytes) <==\r\r",
LIST[[rope[boilerPlate]], [rope[baseName]], [rope[PFS.RopeFromPath[fullName]]], [integer[fileLength]]] ];
ih.type ¬ msInfo.lastBodyPartType ¬ MailBasicsMoreItemTypes.hasAttachment;
}
ELSE {
msg ¬ IO.PutFR["%g\r %g attachment not retrieved because XNSMail.FlushMsgsWithAttachments was FALSE\r\r", [rope[boilerPlate]], [rope[baseName]] ];
ih.type ¬ msInfo.lastBodyPartType ¬ MailBasicsItemTypes.multinationalNote;
};
currentItem ¬ IO.RIS[msg];
ih.length ¬ msg.Length[];
};
GetNSTextFile: PROC RETURNS[note, plainTextR, formattingR: ROPE] = {
sf: SerializedFiling.SerializedFile;
len: INT ~ msInfo.bodyAnnotSize;
sf ¬ SerializedFiling.GetSerializedFile[MSRetrieve.GetBodyPart[msInfo.msH]];
note ¬ SerializedFiling.GetRopeContents[sf];
IF ( len # 0 ) THEN {
msInfo.bodyAnnotSize ¬ 0;
IF len < note.Length[] THEN note ¬ note.Substr[len];
};
plainTextR ¬ SerializedFiling.GetRopeAttribute[sf, lastInterlispType-1];
formattingR ¬ SerializedFiling.GetRopeAttribute[sf, lastInterlispType];
};
IF msInfo.newMsg THEN { -- return the envelope
ih.type ¬ msInfo.lastBodyPartType ¬ MailBasicsItemTypes.envelope;
[currentItem, ih.length] ¬ BuildMSEnvelopeItem[msInfo.envelope];
msInfo.newMsg ¬ FALSE;
RETURN;
};
IF msInfo.systemMessage # NIL THEN { -- return the system message
ih.type ¬ msInfo.lastBodyPartType ¬ MailBasicsItemTypes.systemMessage;
currentItem ¬ IO.RIS[msInfo.systemMessage];
ih.length ¬ Rope.Length[msInfo.systemMessage];
msInfo.systemMessage ¬ NIL;
RETURN;
};
IF msInfo.plainTextFromHeader # NIL THEN {
ih.type ¬ msInfo.lastBodyPartType ¬ MailBasicsItemTypes.plainTextForFormatting;
currentItem ¬ IO.RIS[msInfo.plainTextFromHeader];
ih.length ¬ Rope.Length[msInfo.plainTextFromHeader];
msInfo.plainTextFromHeader ¬ NIL;
RETURN;
};
IF msInfo.formattingFromHeader # NIL THEN {
ih.type ¬ msInfo.lastBodyPartType ¬ MailBasicsItemTypes.tioga1;
currentItem ¬ IO.RIS[msInfo.formattingFromHeader];
ih.length ¬ Rope.Length[msInfo.formattingFromHeader];
msInfo.formattingFromHeader ¬ NIL;
RETURN;
};
bp ¬ MSRetrieve.NextBodyPart[msInfo.msH];
ih.type ¬ msInfo.lastBodyPartType ¬ MapMSBodyPartType[bp.type];
SELECT ih.type FROM
MailBasicsItemTypes.nsTextFile => IF UserProfile.Boolean["XNSMail.nsTextFileAsFile", FALSE]
THEN DoAttachment["NSTextFile", [3] ]
ELSE {
thows away plainText & formatting from GetNSTextFile
tempBodies: ROPE;
[tempBodies, , ] ¬ GetNSTextFile[];
currentItem ¬ IO.RIS[tempBodies];
ih.type ¬ msInfo.lastBodyPartType ¬ MailBasicsItemTypes.multinationalNote;
ih.length ¬ tempBodies.Length[];
};
MailBasicsItemTypes.vpDocument =>
DoAttachment["VPDocument", MailBasicsFileTypes.vpDocFileType];
MailBasicsItemTypes.vpFolder =>
DoAttachment["VPFolder", [3] ];
MailBasicsItemTypes.otherNSFile =>
DoAttachment["OtherNSFile", [3] ];
MailBasicsItemTypes.interpress =>
DoAttachment["Interpress", [3] ];
MailBasicsItemTypes.postscript =>
DoAttachment["Postscript", [3] ];
ENDCASE => [currentItem, ih.length] ¬ MapMSItemContents[bp, msInfo];
[currentItem, ih.length] ← MapMSItemContents[bp, msInfo];
};
MSGetItem: PROC [ref: REF] RETURNS [STREAM] ~ {
Provides an IO stream for reading the current item.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
RETURN[MSRetrieve.GetBodyPart[msInfo.msH]];
};
MSGetItemAsRope: PROC [ref: REF] RETURNS [rp: ROPE] ~ {
Provides the current item as a ROPE.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
rp ¬ MSRetrieve.GetBodyPartAsRope[msInfo.msH];
IF ( msInfo.lastBodyPartType = MailBasicsItemTypes.multinationalNote ) AND ( msInfo.bodyAnnotSize # 0 ) THEN {
ln: INT ¬ msInfo.bodyAnnotSize;
msInfo.bodyAnnotSize ¬ 0;
IF ln < rp.Length[] THEN RETURN[rp.Substr[ln]] ELSE RETURN[rp];
}
ELSE RETURN[rp];
};
MSGetItemViaCallback: PROC [ref: REF, proc: MailRetrieve.GetItemCallback] ~ {
Provides the current item via a callback.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
CheckForAbort: PROC RETURNS [BOOL] ~ {
RETURN[FALSE];
};
msInfo: MSInfo = NARROW[ref];
MSRetrieve.GetBodyPartViaCallback[msInfo.msH, proc];
};
MSMarkMessage: PROC [ref: REF] RETURNS [] ~ {
Marks current message as being read.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
MSRetrieve.MarkMessage[msInfo.msH];
};
MSDeleteMessage: PROC [ref: REF] RETURNS [] ~ {
Deletes current message.
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
MSRetrieve.DeleteMessage[msInfo.msH];
};
MSAccept: PROC [ref: REF] RETURNS [] ~ {
Flush the mailbox entirely (and irrecoverably).
ENABLE MSRetrieve.Failed => ERROR MailRetrieve.Failed[FromMSFailureReason[why], text];
msInfo: MSInfo = NARROW[ref];
MSRetrieve.Accept[msInfo.msH];
};
Postmarks
RopeFromMSPostmark: PROC [env: MSBasics.Envelope] RETURNS [id: ROPE] = {
RETURN [MailUtils.GeneratePostmark[gmt: BasicTime.FromNSTime[env.postmark.time], machine: MSUtils.RopeFromMSRName[env.postmark.postedAt]] ];
};
Intelligible: PROC[timeStamp: MailBasics.Timestamp, sender: ROPE] RETURNS[ROPE] = {
nameCount: INT ¬ 0;
timeBuf.length ¬ 0;
FOR i: INT IN [0..sender.Length[]) DO
c: CHAR = sender.Fetch[i];
IF ~(Ascii.Letter[c] OR Ascii.Digit[c]) THEN LOOP;
timeBuf ¬ RefText.AppendChar[timeBuf, c];
IF (nameCount ¬ nameCount + 1) >= 8 THEN EXIT;
ENDLOOP;
timeBuf ¬ RefText.AppendChar[timeBuf, '-]; -- after name
timeBuf ¬ Convert.AppendTimeRFC822[timeBuf, MailUtils.GetTimeFromPostmark[timeStamp]];
FOR i: NAT IN [0..timeBuf.length) DO
SELECT timeBuf[i] FROM
' , '\t => timeBuf[i] ¬ '-;
': => timeBuf[i] ¬ '.;
ENDCASE;
ENDLOOP;
RETURN[Rope.FromRefText[timeBuf]];
};
timeBuf: REF TEXT ¬ NEW[TEXT[RefText.line]];
Message body items
Grapevine item types and XNS body part types are mapped into a single number space as follows: The low XNS body part types (in the range [0..highXNS)) are mapped directly. Currently (as of November 30, 1988), there are no allocated XNS body part types that do not lie in this range. Any that are encountered are currently mapped to highXNS. The high Grapevine item types (in the range (lowGV..GVBasics.LastItem)) are also mapped directly. Known Grapevine item types that do not lie in this range include the server-supplied "envelope" items; these are handled on a case-by-case basis. Other item types that do not lie in the directly-mapped range are mapped to lowGV.
highXNS: MSBasics.BodyPartType = 499;
Note: envelope: MailBasics.ItemType = 500;
Note: postscript: MailBasics.ItemType = 510;
MapMSBodyPartType: PROC [type: MSBasics.BodyPartType] RETURNS [mapped: MailBasics.ItemType] ~ {
SELECT type FROM
MSBasics.lastBodyPart => mapped ¬ MailBasicsItemTypes.lastItem; -- last item;
< highXNS => mapped ¬ type; -- direct map
ENDCASE => mapped ¬ highXNS; -- hopefully won't see these for awhile
};
Item contents are generally passed to clients in the same form as they are retrieved from a mail service. In some cases, however, we transform the items before returning them. For example, Grapevine's Text items include both the header and the message body; we split these by reading the heading off the item stream and leaving the body. Also, Grapevine always returns the Recipients as the first and only envelope item; we map this to a complete envelope. For XNS mail, X.400 style headers are converted to RFC-822 text headers.
MapMSItemContents: PROC [bp: MSBasics.BodyPartInfo, msInfo: MSInfo] RETURNS [item: STREAM ¬ NIL, length: INT] ~ {
Returning a NIL item stream means that the item's contents will be requested from the mail service on demand. In any case, the length returned should be accurate.
SELECT bp.type FROM
MSMessage.headingBodyPart => { -- convert X.400 header to text
header, plainText, formatting: ROPE;
item ¬ MSRetrieve.GetBodyPart[msInfo.msH];
[header, plainText, formatting, msInfo.bodyAnnotSize] ¬ MSMessage.GetHeaderRope[item];
length ¬ Rope.Length[header];
item ¬ IO.RIS[header];
IF plainText # NIL AND ( tiogaTextOK ) THEN {
msInfo.plainTextFromHeader ¬ plainText;
msInfo.formattingFromHeader ¬ formatting;
};
};
ENDCASE => { -- return body part as is
length ¬ bp.sizeInBytes;
item ¬ NIL; -- get it from server if requested
};
};
Envelope information is put together into a single "envelope" item that looks like a RFC-822 header. For XNS mail, we have all of the relevant information in MSBasics.Envelope. For Grapevine, the StartMessage call returns all but the intended recipients, which is generally returned as the first item.
BuildMSEnvelopeItem: PROC [envelope: MSBasics.Envelope] RETURNS [item: STREAM, length: INT] ~ {
rope: ROPE ¬ MSUtils.RopeFromEnvelope[envelope];
item ¬ IO.RIS[rope];
length ¬ Rope.Length[rope];
};
Type conversions
FromMSMboxState: PROC [msState: MSRetrieve.MboxState] RETURNS [state: MBXState] ~ {
state ¬ VAL[ORD[msState]];
};
FromMSServerState: PROC [msState: MSRetrieve.ServerState] RETURNS [state: ServerState] ~ {
state ¬ VAL[ORD[msState]];
};
FromMSFailureReason: PROC [msWhy: MSRetrieve.FailureReason] RETURNS [why: FailureReason] ~ {
why ¬ SELECT msWhy FROM
$RPC => $communicationFailure,
$Protocol => $unknownFailure,
$Service => $connectionRejected,
$Authentication => $badCredentials,
ENDCASE => $unknownFailure;
};
State reporting
ChangeReportFromMS: MSRetrieve.ChangeReportProc ~ {
[state: MboxState, handle: Handle]
FOR mL: LIST OF MSInfo ¬ msInfoList, mL.rest UNTIL mL=NIL DO
IF mL.first.msH = handle THEN
mL.first.reportChanges[mL.first.handle, FromMSMboxState[state] ];
ENDLOOP;
};
InitProcs: PROC = {
mailRetrieveProcs: MailRetrieve.MailRetrieveProcsRef ¬
NEW[MailRetrieve.MailRetrieveProcs ¬ [
which: $xns,
Close: MSClose,
NewUser: MSNewUser,
MailboxState: MSMailboxState,
NextServer: MSNextServer,
ServerName: MSServerName,
UserName: MSUserName,
NextMessage: MSNextMessage,
StartMessage: MSStartMessage,
NextItem: MSNextItem,
GetItem: MSGetItem,
GetItemAsRope: MSGetItemAsRope,
GetItemViaCallback: MSGetItemViaCallback,
MarkMessage: MSMarkMessage,
DeleteMessage: MSDeleteMessage,
Accept: MSAccept
]];
MailRetrieve.RegisterMailRetrieveProcs[mailRetrieveProcs];
};
InitProcs[];
END.