DIRECTORY Ascii USING [Digit, Letter], MTMiscOps USING [Lookup, Table], MTTreeOps, MTP1 --P1-- USING [AddrSpecNode, DomainNode, DotWordsNode, InternalNode, MailboxNode, RouteAddrNode, RouteNode, TerminalNode], Rope USING [Cat, Concat, Equal, Fetch, Find, FromChar, IsEmpty, Length, ROPE, SkipTo, Substr, Translate], IPConfig USING [bitnetGateway, uucpGateway, csnetGateway, mailnetGateway, validDomains, ourLocalName], IPName USING [NormalizeName]; MTTreeOpsImpl: CEDAR PROGRAM IMPORTS Ascii, MTMiscOps, Rope, IPConfig, IPName EXPORTS MTTreeOps = BEGIN OPEN P1: MTP1, MTTreeOps; arpaRegistries: MTMiscOps.Table _ LIST["ARPA", "AG", "ArpaGateway", "NotArpa"]; arpaAliases: MTMiscOps.Table _ LIST["AG", "ArpaGateway"]; arpaTopDomain: Rope.ROPE _ "ARPA"; validDomains: MTMiscOps.Table _ IPConfig.validDomains; localDomain: Rope.ROPE _ "COM"; localHostName: Rope.ROPE _ IPConfig.ourLocalName; oldLocalHostName: Rope.ROPE _ "Xerox.ARPA"; localRegistry: Rope.ROPE _ "PA"; localHostNames: MTMiscOps.Table _ LIST["Xerox", "PARC.XEROX", "PARC-MAXC"]; -- Names for the local domain. The first is the preferred one and the others are aliases. bitnetGateway: Rope.ROPE _ IPConfig.bitnetGateway; uucpGateway: Rope.ROPE _ IPConfig.uucpGateway; csnetGateway: Rope.ROPE _ IPConfig.csnetGateway; mailnetGateway: Rope.ROPE _ IPConfig.mailnetGateway; TreeToRope: PUBLIC PROC [tree: REF ANY, squeezeWhiteSpace: BOOL _ FALSE, insertDiscretionaryBlanks: BOOL _ FALSE] RETURNS [r: Rope.ROPE] = { WhiteSpaceOption: TYPE ~ {none, ifMatch, required}; Splice: PROC [leadingWhiteSpace: WhiteSpaceOption, tree: LIST OF REF ANY, spaceAfterCommas: BOOL _ FALSE] RETURNS [r: Rope.ROPE, trailingWhiteSpace: WhiteSpaceOption] = { r2: Rope.ROPE; r _ NIL; WHILE tree # NIL DO [r2, leadingWhiteSpace] _ InternalTTR[tree.first, leadingWhiteSpace]; r _ r.Cat[r2]; IF spaceAfterCommas THEN WITH tree.first SELECT FROM tn: REF P1.TerminalNode => IF tn.text.Equal[","] THEN { leadingWhiteSpace _ required; IF insertDiscretionaryBlanks THEN r _ r.Cat[Rope.FromChar[discretionaryBlank]]; } ENDCASE => NULL; tree _ tree.rest; ENDLOOP; trailingWhiteSpace _ leadingWhiteSpace; }; InternalTTR: PROC [tree: REF ANY, leadingWhiteSpace: WhiteSpaceOption] RETURNS [r: Rope.ROPE, trailingWhiteSpace: WhiteSpaceOption] = { IF tree = NIL THEN RETURN[NIL, leadingWhiteSpace]; WITH tree SELECT FROM asn: REF P1.AddrSpecNode => RETURN Splice[leadingWhiteSpace, LIST[asn.localPart, asn.atSign, asn.domain]]; dn: REF P1.DomainNode => RETURN Splice[leadingWhiteSpace, LIST[dn.domain, dn.dot, dn.subDomain]]; dwn: REF P1.DotWordsNode => RETURN Splice[leadingWhiteSpace, LIST[dwn.dotWords, dwn.dot, dwn.word]]; in: P1.InternalNode => RETURN Splice[leadingWhiteSpace, in, TRUE]; mbn: REF P1.MailboxNode => RETURN Splice[leadingWhiteSpace, LIST[mbn.phrase, mbn.address]]; ran: REF P1.RouteAddrNode => RETURN Splice[leadingWhiteSpace, LIST[ran.lBrack, ran.route, ran.colon, ran.addrSpec, ran.rBrack]]; rn: REF P1.RouteNode => RETURN Splice[leadingWhiteSpace, LIST[rn.route, rn.comma, rn.atSign, rn.domain]]; tn: REF P1.TerminalNode => { needSpace: BOOL; trailingWhiteSpace _ IF tn.text.IsEmpty[] THEN none ELSE IF Ascii.Letter[tn.text.Fetch[]] OR Ascii.Digit[tn.text.Fetch[]] THEN ifMatch ELSE none; needSpace _ leadingWhiteSpace = required OR trailingWhiteSpace = required OR (leadingWhiteSpace = trailingWhiteSpace AND leadingWhiteSpace = ifMatch); r _ Rope.Cat[IF squeezeWhiteSpace THEN (IF needSpace THEN " " ELSE NIL) ELSE tn.whiteSpace, tn.text]; }; ENDCASE => ERROR; }; r _ InternalTTR[tree, none].r; }; TreeToSimpleRope: PUBLIC PROC [tree: REF ANY] RETURNS [rope: Rope.ROPE] = { Splice: PROC [tree: LIST OF REF ANY] RETURNS [rope: Rope.ROPE _ NIL] = { UNTIL tree = NIL DO rope _ Rope.Cat[rope, InternalTTR[tree.first]]; tree _ tree.rest; ENDLOOP; }; InternalTTR: PROC [tree: REF ANY] RETURNS [rope: Rope.ROPE] = { IF tree = NIL THEN RETURN[NIL]; WITH tree SELECT FROM asn: REF P1.AddrSpecNode => RETURN Splice[LIST[asn.localPart, asn.atSign, asn.domain]]; dn: REF P1.DomainNode => RETURN Splice[LIST[dn.domain, dn.dot, dn.subDomain]]; dwn: REF P1.DotWordsNode => RETURN Splice[LIST[dwn.dotWords, dwn.dot, dwn.word]]; in: P1.InternalNode => RETURN Splice[in]; mbn: REF P1.MailboxNode => RETURN Splice[LIST[mbn.phrase, mbn.address]]; ran: REF P1.RouteAddrNode => RETURN Splice[LIST[ran.lBrack, ran.route, ran.colon, ran.addrSpec, ran.rBrack]]; rn: REF P1.RouteNode => RETURN Splice[LIST[rn.route, rn.comma, rn.atSign, rn.domain]]; tn: REF P1.TerminalNode => rope _ tn.text; ENDCASE => ERROR; }; rope _ InternalTTR[tree]; }; IsLocalDomain: PROC [domain: REF P1.DomainNode, stripOldName: BOOL _ FALSE] RETURNS [BOOL] = { domainRope: Rope.ROPE _ NIL; hostRope: Rope.ROPE _ NIL; IF domain.domain = NIL THEN domainRope _ TreeToRope[domain.subDomain, TRUE] ELSE { domainRope _ TreeToRope[domain.domain, TRUE]; hostRope _ TreeToRope[domain, TRUE]; }; IF ~stripOldName AND Rope.Equal[hostRope, oldLocalHostName, FALSE] THEN RETURN[FALSE]; RETURN [MTMiscOps.Lookup[domainRope, localHostNames]]}; XeroxDomain: PUBLIC PROC RETURNS [domain: REF ANY --P1.DomainNode-- ] = { subDomain: REF P1.TerminalNode _ NEW[P1.TerminalNode _[text: localHostName]]; domain _ NEW[P1.DomainNode _ [subDomain: subDomain]]; }; DefaultDomain: PUBLIC PROC [tree: REF ANY] RETURNS [domainType: DomainType, domain: REF ANY] = { InternalDD: PROC [tree: REF ANY, mailboxNodeSeen: BOOL] RETURNS [domainType: DomainType, domain: REF ANY] = { IF tree = NIL OR (~mailboxNodeSeen AND ~(ISTYPE[tree, P1.InternalNode] OR ISTYPE[tree, REF P1.MailboxNode])) THEN RETURN[unknown, NIL]; WITH tree SELECT FROM asn: REF P1.AddrSpecNode => IF asn.domain # NIL AND IsLocalDomain[asn.domain] THEN { [domainType, domain] _ InternalDD[asn.localPart, mailboxNodeSeen]; IF domain = NIL THEN { domainType _ registry; domain _ NEW[P1.TerminalNode _ [text~localRegistry]]; }; } ELSE RETURN InternalDD[IF asn.domain = NIL THEN asn.localPart ELSE asn.domain, mailboxNodeSeen]; dn: REF P1.DomainNode => RETURN[arpaDomain, dn]; dwn: REF P1.DotWordsNode => RETURN[registry, IF dwn.dotWords # NIL THEN dwn.word ELSE NIL]; in: P1.InternalNode => WHILE in # NIL DO [domainType, domain] _ InternalDD[in.first, mailboxNodeSeen]; IF domain # NIL THEN RETURN; in _ in.rest; ENDLOOP; mbn: REF P1.MailboxNode => RETURN InternalDD[mbn.address, TRUE]; ran: REF P1.RouteAddrNode => { IF ran.route = NIL THEN domain _ NIL ELSE [domainType, domain] _ InternalDD[ran.route, mailboxNodeSeen]; IF domain = NIL THEN [domainType, domain] _ InternalDD[ran.addrSpec, mailboxNodeSeen]; }; rn: REF P1.RouteNode => { IF rn.route = NIL THEN domain _ NIL ELSE [domainType, domain] _ InternalDD[rn.route, mailboxNodeSeen]; IF domain = NIL AND ~IsLocalDomain[rn.domain] THEN { domainType _ arpaDomain; domain _ rn.domain; }; }; tn: REF P1.TerminalNode => domain _ NIL; -- "From: at Foo" (it actually happened!) ENDCASE => ERROR; }; [domainType, domain] _ InternalDD[tree, FALSE]; IF domain = NIL THEN domainType _ unknown; }; Quote: PROC [text: Rope.ROPE] RETURNS [quotedText: Rope.ROPE] = { length: INT _ Rope.Length[text]; i: INT _ 0; j: INT; quotedText _ "\""; WHILE i < length DO j _ text.SkipTo[pos: i, skip: "\\\""]; quotedText _ quotedText.Cat[text.Substr[i, j-i]]; IF j < length THEN { quotedText _ quotedText.Cat[Rope.FromChar['\\], Rope.FromChar[text.Fetch[j]]]; j _ j+1; }; i _ j; ENDLOOP; quotedText _ quotedText.Cat["\""]; }; AddArpa: PROC [domain: REF P1.DomainNode] RETURNS [newDomain: REF P1.DomainNode] = { in, temp, out: Rope.ROPE; IF MTMiscOps.Lookup[domain.subDomain.text, arpaAliases] THEN domain.subDomain.text _ arpaTopDomain; -- xxx.ArpaGateway => xxx.Arpa IF domain# NIL AND domain.dot # NIL AND Rope.Equal[domain.dot.text, "@"] THEN { RETURN[domain]; }; IF domain # NIL AND domain.domain # NIL AND domain.domain.dot # NIL AND Rope.Equal[domain.domain.dot.text, "@"] THEN { RETURN[domain.domain]; }; SELECT TRUE FROM domain.subDomain.text = localHostName => newDomain _ domain; MTMiscOps.Lookup[domain.subDomain.text, validDomains] => newDomain _ domain; ENDCASE => -- Default to ARPA newDomain _ NEW[P1.DomainNode _ [domain: domain, dot: NEW[P1.TerminalNode _ [text: "."]], subDomain: NEW[P1.TerminalNode _ [text: arpaTopDomain]]]]; in _ TreeToRope[newDomain]; temp _ FixupTail[in, ".ARPA.ARPA"]; IF Rope.Equal[temp, oldLocalHostName, FALSE] THEN RETURN; out _ IPName.NormalizeName[temp]; IF out = NIL AND Tailed[temp, ".ARPA"] THEN { temp _ StripTail[temp, ".ARPA"]; out _ IPName.NormalizeName[temp]; }; IF Rope.Equal[in, out, FALSE] OR out = NIL THEN RETURN; newDomain _ NEW[ P1.DomainNode _ [domain: NIL, dot: NIL, subDomain: NEW[P1.TerminalNode _ [text: out]]]]; }; FixupTail: PROC [old, tail: Rope.ROPE] RETURNS [new: Rope.ROPE] = { new _ old; IF Tailed[old, tail] THEN { new _ StripTail[old, tail]; new _ Rope.Concat[new, ".ARPA"]; }; }; Tailed: PROC [body, tail: Rope.ROPE] RETURNS [match: BOOL] = { bodyLength: INT = body.Length[]; tailLength: INT = tail.Length[]; back: Rope.ROPE; IF bodyLength <= tailLength THEN RETURN[FALSE]; back _ Rope.Substr[body, bodyLength-tailLength, tailLength]; IF Rope.Equal[back, tail, FALSE] THEN RETURN[TRUE]; RETURN[FALSE]; }; StripTail: PROC [body, tail: Rope.ROPE] RETURNS [new: Rope.ROPE] = { bodyLength: INT = body.Length[]; tailLength: INT = tail.Length[]; IF ~Tailed[body, tail] THEN RETURN[body]; RETURN[Rope.Substr[body, 0, bodyLength - tailLength]]}; Redirect: PROC [asn: REF P1.AddrSpecNode, domain, relayHost: Rope.ROPE] = { IF asn.domain = NIL THEN RETURN; IF asn.domain.subDomain = NIL THEN RETURN; IF asn.domain.domain = NIL THEN RETURN; -- xxx@domain IF asn.domain.domain.subDomain = NIL THEN RETURN; IF Rope.Equal[asn.domain.subDomain.text, domain, FALSE] THEN { host: Rope.ROPE _ asn.domain.domain.subDomain.text; asn.atSign.text _ "%"; asn.domain.domain.subDomain.text _ Rope.Cat[host, ".", domain]; asn.domain.dot.text _ "@"; asn.domain.subDomain.text _ relayHost; RETURN; }; IF asn.domain.domain.domain = NIL THEN RETURN; IF asn.domain.domain.domain.subDomain = NIL THEN RETURN; IF MTMiscOps.Lookup[asn.domain.subDomain.text, arpaRegistries] AND Rope.Equal[asn.domain.domain.subDomain.text, domain, FALSE] THEN { host: Rope.ROPE _ asn.domain.domain.domain.subDomain.text; asn.atSign.text _ "%"; asn.domain.domain.domain.subDomain.text _ Rope.Cat[host, ".", domain]; asn.domain.domain.dot.text _ "@"; asn.domain.domain.subDomain.text _ relayHost; RETURN; }; RETURN; }; UnRedirect: PROC [asn: REF P1.AddrSpecNode, domain, relayHost: Rope.ROPE] = { thisHost: Rope.ROPE; IF asn = NIL THEN RETURN; IF asn.domain = NIL THEN RETURN; IF asn.domain.subDomain = NIL THEN RETURN; IF asn.domain.domain = NIL THEN RETURN; IF asn.domain.domain.subDomain = NIL THEN RETURN; thisHost _ TreeToRope[asn.domain]; IF ~Rope.Equal[thisHost, relayHost, FALSE] THEN RETURN; IF asn.localPart = NIL THEN RETURN; WITH asn.localPart SELECT FROM lp: REF P1.AddrSpecNode => { IF lp.localPart = NIL THEN RETURN; WITH lp.localPart SELECT FROM dwn: REF P1.DotWordsNode => { perCent, pos, length: INT; userAndHost, user, host: Rope.ROPE; IF dwn.word = NIL THEN RETURN; IF ~Rope.Equal[dwn.word.text, domain, FALSE] THEN RETURN; IF dwn.dotWords = NIL THEN RETURN; IF dwn.dotWords.word = NIL THEN RETURN; userAndHost _ dwn.dotWords.word.text; perCent _ Rope.Find[userAndHost, "%"]; IF perCent # -1 THEN { DO pos _ Rope.Find[userAndHost, "%", perCent+1]; IF pos = -1 THEN EXIT; perCent _ pos; ENDLOOP; length _ Rope.Length[userAndHost]; host _ Rope.Substr[userAndHost, perCent+1, length-perCent-1]; user _ Rope.Substr[userAndHost, 0, perCent]; asn.domain.subDomain.text _ domain; asn.domain.domain.subDomain.text _ host; asn.domain.domain.dot _ NIL; IF asn.domain.domain # NIL THEN asn.domain.domain.domain _ NIL; dwn.dotWords.word.text _ user; asn.localPart _ dwn.dotWords; }; }; ENDCASE => RETURN; }; ENDCASE => RETURN; RETURN; }; AddDefaultRegistry: PROC [dotWords: REF P1.DotWordsNode, defaultDomain: REF P1.TerminalNode] RETURNS [newDotWords: REF P1.DotWordsNode] = { RETURN [NEW[P1.DotWordsNode _ [dotWords: dotWords, dot: NEW[P1.TerminalNode _ [text: "."]], word: IF defaultDomain # NIL THEN defaultDomain ELSE NEW[P1.TerminalNode _ [text: localRegistry]]]]]; }; AddDefaultDomain: PROC [addrSpec: REF P1.AddrSpecNode, defaultDomainType: DomainType, defaultDomain: REF ANY, addXerox: BOOL, onlyRegistryDefault: BOOL] RETURNS [newAddrSpec: REF P1.AddrSpecNode] = { localPart: REF P1.DotWordsNode _ NARROW[addrSpec.localPart]; IF defaultDomainType = registry OR defaultDomainType = unknown OR onlyRegistryDefault THEN { addrSpec.localPart _ localPart _ MaybeUnFixQuotes[localPart]; IF localPart.dotWords = NIL THEN addrSpec.localPart _ AddDefaultRegistry[localPart, IF defaultDomainType = registry THEN NARROW[defaultDomain] ELSE NIL]; IF addXerox THEN addrSpec _ AddDomain[addrSpec, NEW[P1.DomainNode _ [subDomain: NEW[P1.TerminalNode _ [text: localHostNames.first]]]], TRUE]; } ELSE addrSpec _ AddDomain[addrSpec, NARROW[defaultDomain]]; RETURN[addrSpec]; }; AddDomain: PROC [addrSpec: REF P1.AddrSpecNode, domain: REF P1.DomainNode, addXerox: BOOLEAN _ FALSE] RETURNS [newAddrSpec: REF P1.AddrSpecNode] = { localPart: REF P1.DotWordsNode _ NARROW[addrSpec.localPart]; addrSpec.localPart _ MaybeFixQuotes[localPart]; newAddrSpec _ NEW[P1.AddrSpecNode _ [localPart: addrSpec]]; newAddrSpec.atSign _ NEW[P1.TerminalNode _ [text: "@"]]; IF ~addXerox THEN newAddrSpec.domain _ AddArpa[domain] ELSE newAddrSpec.domain _ NARROW[XeroxDomain[]]; }; MaybeFixQuotes: PROC [localPart: REF P1.DotWordsNode] RETURNS [newLocalpart: REF P1.DotWordsNode] = { dotWords: REF P1.DotWordsNode _ NARROW[localPart.dotWords]; newLocalpart _ localPart; IF dotWords = NIL THEN RETURN; -- User IF dotWords.word = NIL THEN RETURN; IF dotWords.word.text = NIL THEN RETURN; IF dotWords.dotWords # NIL THEN RETURN; -- A.B.C IF Rope.Fetch[dotWords.word.text, 0] # '" THEN RETURN; -- User.PA (no Quotes) IF localPart.word.whiteSpace # NIL THEN RETURN; IF localPart.dot.whiteSpace # NIL THEN RETURN; IF ~Rope.Equal[localPart.dot.text, "."] THEN RETURN; dotWords.word.text _ Rope.Cat[ Rope.Substr[dotWords.word.text, 0, Rope.Length[dotWords.word.text]-1], ".", localPart.word.text, "\""]; dotWords.word.text _ FixupSpaces[dotWords.word.text]; -- Change spaces to underscores RETURN[dotWords]; }; FixupSpaces: PROC [raw: Rope.ROPE] RETURNS [user: Rope.ROPE] = BEGIN length: INT _ raw.Length[]; user _ raw; FOR i: INT IN [0..length) DO IF raw.Fetch[i] = ' THEN EXIT; REPEAT FINISHED => RETURN; ENDLOOP; user _ Rope.Translate[base: user, translator: SpaceToUnderbar]; END; FixupUnderbars: PROC [raw: Rope.ROPE] RETURNS [user: Rope.ROPE] = BEGIN length: INT _ raw.Length[]; user _ raw; FOR i: INT IN [0..length) DO IF raw.Fetch[i] = '_ THEN EXIT; REPEAT FINISHED => RETURN; ENDLOOP; user _ Rope.Translate[base: user, translator: UnderbarToSpace]; END; SpaceToUnderbar: PROC [old: CHAR] RETURNS [new: CHAR] = BEGIN IF old = ' THEN RETURN['_] ELSE RETURN[old]; END; UnderbarToSpace: PROC [old: CHAR] RETURNS [new: CHAR] = BEGIN IF old = '_ THEN RETURN[' ] ELSE RETURN[old]; END; MaybeUnFixQuotes: PROC [localPart: REF P1.DotWordsNode] RETURNS [newLocalpart: REF P1.DotWordsNode] = { name: Rope.ROPE _ TreeToRope[localPart, TRUE]; whiteSpace: Rope.ROPE _ NIL; hasQuotes: BOOL _ Rope.Fetch[name, 0] = '"; newLocalpart _ localPart; IF localPart = NIL THEN RETURN; IF localPart.word = NIL THEN RETURN; IF localPart.dotWords = NIL THEN IF ~hasQuotes THEN RETURN ELSE { FOR i: INT DECREASING IN [0..Rope.Length[name]) DO IF Rope.Fetch[name, i] = '. THEN { spaces: BOOL _ Rope.Find[name, " "] # -1 OR Rope.Find[name, "_"] # -1; IF spaces THEN localPart.dotWords _ NEW[P1.DotWordsNode _ [word: NEW[P1.TerminalNode _ [text: Rope.Substr[name, 0, i]]]]] ELSE localPart.dotWords _ NEW[P1.DotWordsNode _ [word: NEW[P1.TerminalNode _ [text: Rope.Substr[name, 1, i-1]]]]]; localPart.dotWords.word.text _ FixupUnderbars[localPart.dotWords.word.text]; IF spaces THEN localPart.dotWords.word.text _ Rope.Cat[localPart.dotWords.word.text, "\""]; localPart.dotWords.word.whiteSpace _ localPart.word.whiteSpace; localPart.dot _ NEW[P1.TerminalNode _ [text: "."]]; localPart.word.whiteSpace _ NIL; localPart.word.text _ Rope.Substr[name, i+1, Rope.Length[name]-i-2]; EXIT; }; ENDLOOP; RETURN; }; IF localPart.dotWords.word = NIL THEN RETURN; IF Rope.Find[name, "_"] # -1 THEN { SELECT TRUE FROM localPart.word.whiteSpace # NIL => whiteSpace _ localPart.word.whiteSpace; localPart.dotWords.word.whiteSpace # NIL => whiteSpace _ localPart.dotWords.word.whiteSpace; localPart.dotWords.dotWords = NIL => {}; localPart.dotWords.dotWords.word = NIL => {}; localPart.dotWords.dotWords.word.whiteSpace # NIL => whiteSpace _ localPart.dotWords.dotWords.word.whiteSpace; ENDCASE; FOR i: INT DECREASING IN [0..Rope.Length[name]) DO IF Rope.Fetch[name, i] = '. THEN { localPart.dotWords _ NEW[P1.DotWordsNode _ [word: NEW[P1.TerminalNode _ [text: Rope.Substr[name, 0, i]]]]]; localPart.dotWords.word.text _ Rope.Cat["\"", localPart.dotWords.word.text, "\""]; localPart.dotWords.word.text _ FixupUnderbars[localPart.dotWords.word.text]; localPart.dotWords.word.whiteSpace _ whiteSpace; localPart.dot _ NEW[P1.TerminalNode _ [text: "."]]; localPart.word.whiteSpace _ NIL; localPart.word.text _ Rope.Substr[name, i+1, Rope.Length[name]-i-1]; EXIT; }; ENDLOOP;}; }; ToArpa: PUBLIC PROC [tree: REF ANY, defaultDomainType: DomainType, defaultDomain: REF ANY] RETURNS [newTree: REF ANY] = { InternalTA: PROC [tree: REF ANY, mailboxNodeSeen: BOOL] RETURNS [newTree: REF ANY] = { IF tree = NIL OR (~mailboxNodeSeen AND ~(ISTYPE[tree, P1.InternalNode] OR ISTYPE[tree, REF P1.MailboxNode])) THEN RETURN[tree]; WITH tree SELECT FROM asn: REF P1.AddrSpecNode => SELECT TRUE FROM asn.domain = NIL => tree _ AddDefaultDomain[asn, defaultDomainType, defaultDomain, TRUE, FALSE]; NARROW[asn.localPart, REF P1.AddrSpecNode].domain = NIL => IF IsLocalDomain[asn.domain, TRUE] THEN tree _ AddDefaultDomain[NARROW[asn.localPart], defaultDomainType, defaultDomain, TRUE, TRUE] ELSE { Redirect[asn, "BITNET", bitnetGateway]; Redirect[asn, "CSNet", csnetGateway]; Redirect[asn, "Mailnet", mailnetGateway]; Redirect[asn, "UUCP", uucpGateway]; asn.domain _ NARROW[InternalTA[asn.domain, mailboxNodeSeen]]; }; ENDCASE => IF IsLocalDomain[asn.domain, TRUE] THEN tree _ InternalTA[asn.localPart, mailboxNodeSeen] ELSE { asn2: REF P1.AddrSpecNode _ NARROW[asn.localPart]; dwn: REF P1.DotWordsNode; WHILE asn2.domain # NIL DO -- Find the simple localPart. asn2 _ NARROW[asn2.localPart]; ENDLOOP; dwn _ NARROW[asn2.localPart]; WHILE dwn.dotWords # NIL DO -- Find the first word in it. dwn _ dwn.dotWords; ENDLOOP; asn.localPart _ NEW[P1.AddrSpecNode _ [localPart~NEW[P1.DotWordsNode _ [word~NEW[P1.TerminalNode _ [whiteSpace~dwn.word.whiteSpace, text~Quote[TreeToRope[asn.localPart, TRUE]]]]]]]]; asn.domain _ NARROW[InternalTA[asn.domain, mailboxNodeSeen]]; }; dn: REF P1.DomainNode => tree _ AddArpa[dn]; dwn: REF P1.DotWordsNode => NULL; in: P1.InternalNode => IF mailboxNodeSeen THEN tree _ LIST[NEW[P1.TerminalNode _ [text~Quote[TreeToRope[in, TRUE]]]]] ELSE WHILE in # NIL DO in.first _ InternalTA[in.first, mailboxNodeSeen]; in _ in.rest; ENDLOOP; mbn: REF P1.MailboxNode => mbn.address _ InternalTA[mbn.address, TRUE]; ran: REF P1.RouteAddrNode => IF ran.route = NIL THEN ran.addrSpec _ NARROW[InternalTA[ran.addrSpec, mailboxNodeSeen]] ELSE ran.route _ NARROW[InternalTA[ran.route, mailboxNodeSeen]]; rn: REF P1.RouteNode => IF rn.route = NIL THEN rn.domain _ NARROW[InternalTA[rn.domain, mailboxNodeSeen]] ELSE rn.route _ NARROW[InternalTA[rn.route, mailboxNodeSeen]]; tn: REF P1.TerminalNode => NULL; ENDCASE => ERROR; RETURN[tree]; }; RETURN InternalTA[tree, FALSE]; }; ToGrapevine: PUBLIC PROC [tree: REF ANY, defaultDomainType: DomainType, defaultDomain: REF ANY] RETURNS [newTree: REF ANY] = { InternalTGV: PROC [tree: REF ANY, mailboxNodeSeen: BOOL] RETURNS [newTree: REF ANY] = { IF tree = NIL OR (~mailboxNodeSeen AND ~(ISTYPE[tree, P1.InternalNode] OR ISTYPE[tree, REF P1.MailboxNode])) THEN RETURN[tree]; WITH tree SELECT FROM asn: REF P1.AddrSpecNode => SELECT TRUE FROM asn.domain = NIL => { IF defaultDomainType # registry THEN tree _ AddDefaultDomain[asn, defaultDomainType, defaultDomain, FALSE, FALSE]; }; NARROW[asn.localPart, REF P1.AddrSpecNode].domain = NIL => { IF IsLocalDomain[asn.domain] THEN tree _ AddDefaultDomain[NARROW[asn.localPart], defaultDomainType, defaultDomain, FALSE, TRUE] ELSE { UnRedirect[asn, "BITNET", bitnetGateway]; UnRedirect[asn, "CSNET", csnetGateway]; UnRedirect[asn, "Mailnet", mailnetGateway]; asn.domain _ NARROW[InternalTGV[asn.domain, mailboxNodeSeen]]; } }; IsLocalDomain[asn.domain] => tree _ InternalTGV[asn.localPart, mailboxNodeSeen] ENDCASE => { asn2: REF P1.AddrSpecNode _ NARROW[asn.localPart]; dwn: REF P1.DotWordsNode; WHILE asn2.domain # NIL DO -- Find the simple localPart. asn2 _ NARROW[asn2.localPart]; ENDLOOP; dwn _ NARROW[asn2.localPart]; WHILE dwn.dotWords # NIL DO -- Find the first word in it. dwn _ dwn.dotWords; ENDLOOP; asn.localPart _ NEW[P1.AddrSpecNode _ [localPart~NEW[P1.DotWordsNode _ [word~NEW[P1.TerminalNode _ [whiteSpace~dwn.word.whiteSpace, text~Quote[TreeToRope[asn.localPart, TRUE]]]]]]]]; asn.domain _ NARROW[InternalTGV[asn.domain, mailboxNodeSeen]]; }; dn: REF P1.DomainNode => IF dn # NIL THEN tree _ AddArpa[dn]; dwn: REF P1.DotWordsNode => NULL; in: P1.InternalNode => IF mailboxNodeSeen THEN tree _ LIST[NEW[P1.TerminalNode _ [text~Quote[TreeToRope[in, TRUE]]]]] ELSE WHILE in # NIL DO in.first _ InternalTGV[in.first, mailboxNodeSeen]; in _ in.rest; ENDLOOP; mbn: REF P1.MailboxNode => mbn.address _ InternalTGV[mbn.address, TRUE]; ran: REF P1.RouteAddrNode => IF ran.route = NIL THEN ran.addrSpec _ NARROW[InternalTGV[ran.addrSpec, mailboxNodeSeen]] ELSE { ran.route _ NARROW[InternalTGV[ran.route, mailboxNodeSeen]]; IF ran.route = NIL THEN ran.colon _ NIL; }; rn: REF P1.RouteNode => IF rn.route = NIL THEN IF rn.domain = NIL OR IsLocalDomain[rn.domain] THEN tree _ NIL ELSE rn.domain _ NARROW[InternalTGV[rn.domain, mailboxNodeSeen]] ELSE { rn.route _ NARROW[InternalTGV[rn.route, mailboxNodeSeen]]; IF rn.route = NIL THEN rn.comma _ NIL; }; tn: REF P1.TerminalNode => NULL; ENDCASE => ERROR; RETURN[tree]; }; RETURN InternalTGV[tree, FALSE]; }; END. ¬MTTreeOpsImpl.mesa Hal Murray September 18, 1985 0:42:03 am PDT John Larson, June 12, 1987 10:32:01 pm PDT Sharon Johnson, June 8, 1987 3:14:56 pm PDT The various ROPE constants used by this program. Convert the name list tree into a rope to put back into the mail header. If squeezeWhiteSpace is TRUE, then ignore the whiteSpace in the tree and insert the appropriate amount (i.e. no blanks except after top-level commas and between words. If insertDiscretionaryBlanks is TRUE, put discretionaryBlank chars whereever a line break is legal. When squeezing white space, the WhiteSpaceOptions for the preceding token and for this token are compared to determine whether to insert a space. This is only done in the TerminalNode arm of InternalTTR since everyone else just recurses. The spaceAfterCommas parameter can be used to force spaces after commas. It is only TRUE when pasting together the pieces of an InternalNode, which may have a list in it. The domains in a route-addr don't get spaces after their commas. Take a list of sub-trees and recursively call InternalTTR on them, keeping track of the WhiteSpaceOptions. Also implement the spaceAfterCommas option. Recursive tree walk to piece together the rope. Convert the name list tree into a rope skipping all comments. Recursive tree walk to piece together the rope. Temporary! Also, this is the wrong home for this. Given a name list tree, find the domain that should be used to default unqualified names in the header. The client is responsible for passing the name tree from the right header (From or Sender). The default domain is the domain (registry or Arpanet host) of the first qualified name in the tree. If domainType = arpaDomain, then domain points to an P1.DomainNode. If domainType = registry, then domain points to an P1.TerminalNode. DANGER! This routine had a lot of bugs during debugging. There are probably still a few. We do a recursive tree walk to find the domain. However, if we haven't encountered a MailboxNode during the descent, we only push on if we might find one further down. This prevents us from trying to extract domain information from group names and other potential garbage. If an Arpanet host name is present is non-local, use it. Else try to find a GV registry. If "@Xerox.COM" was present, then we'll force the default domain to be a GV registry, even if we have to make one up. Only return the word part if the dotwords part is there (i.e. if this is more than just a simple word). Use first one we find on the list. Try for a non-local host name on the route, but if it's not there, then look in the addr-spec. Use first non-local name on route. If all the names are local, return NIL. Transform a string into its quoted equivalent: add quotation marks and put a backslash in front of existing quotation marks and backslashes. Remove ".AG" or ".ArpaGateway" and add ".ARPA" to domain if not present. Destructive. Redirection left us a bogus tree Redirection left us a bogus tree In the old days, things were finished now. Mumble.EDU got mashed to Mumble.EDU.ARPA Ugh, fixup nicname. Note this builds a bogus tree because I'm too lazy to do it right User@Host.Domain => User%Host.Domain@RelayHost User@Host.Domain.ARPA => User%Host.Domain@RelayHost So far, we know it's somethingOrOther@relayHost Now, we know it's user%foo@relayHost, check for multiple %'s Adds default registry to dot-words if necessary. Add default domain information to addrSpec, which is passed in with no domain. Returned addr-spec may have registry and certainly will have Arpanet domain. If addXerox is true, then @Xerox.COM will be added if a registry is added. If onlyRegistryDefault is true, then the default added must be a registry. Destructive. "Joe Smith".OSBUNorth => "Joe_Smith.OSBUNorth" Joe_Smith.OSBUNorth => "Joe Smith".OSBUNorth Joe_C._Smith.OSBUNorth => "Joe C. Smith".OSBUNorth "Joe C. Smith.OSBUNorth" => "Joe C. Smith".OSBUNorth Given a name tree, return the tree modified to allow it to be sent on to the Arpanet. This is a destructive operation. The default domain passed in is added to unqualified names and @Xerox.COM is added to Grapevine names. Need to qualify. We have a name of the form rname@Xerox. We want to default the rname and leave the Xerox. We zap the domain field since AddDefaultDomain likes things that way. Hackery for User@Host.CSNet => User%Host@relay.cs.net Beware: There is similar code in SMTPSyntaxImpl We have multiple at signs and the last host is Xerox. Strip it off and process the rest of the address normally. We have multiple at signs with non-Xerox last host. Quote leading part and splice the leading white space from it onto the new node. Must have DL with @foo.dl syntax. There's an ordinary addr-spec in there to handle. Fix the first host in the route. End of the line. Given a name tree, return the tree modified to allow it to be sent to the Grapevine. This is a destructive operation. @Xerox.COM is removed from Grapevine names. Need to qualify. Why add .PA to (bogus) names that don't have a @Host We have a name of the form foo@Xerox.COM and we want to default the foo and remove the Xerox.COM. We have multiple at signs and the last host is Xerox. Strip it off and process the rest of the address normally. We have multiple at signs with non-Xerox last host. Quote leading part and splice the leading white space from it onto the new node. Must have DL with @foo.dl syntax. There's an ordinary addr-spec in there to handle. Fix the first host in the route. End of the line. Κp˜head™Icode™,L™*L™+code2šΟk ˜ Mšœœ˜Mšœ œ˜ M˜ MšœΟcœœm˜~Mšœœ>œ˜iMšœ œX˜fMšœœ˜——šœœ˜Mšœ)˜0Mšœ ˜Mš œ˜™0Mšœ"œ)˜OMšœœ˜9Mšœœ ˜"Mšœ6˜6Mšœœ ˜Mšœœ˜1Mšœœ˜+Mšœœ˜ Mšœ"œ'žY˜¦M˜M˜Mšœœ˜2Mšœœ˜.Mšœœ˜0Mšœœ˜4M˜—šΟn œœœœœœœœœœ œ˜ŒMšœΦ™ΦM™έMšœœ˜3M˜šŸœœ-œœœœœœœ œ+˜ͺM™—Mšœ œ˜Mšœœ˜šœœ˜MšœE˜EM˜šœ˜šœ œ˜šœœ˜šœœ˜Mšœ˜šœ˜!Mšœ/˜/———Mšœœ˜——M˜Mšœ˜—Mšœ*˜*—M˜š Ÿ œœœœ'œ œ+˜‡M™/Mšœœ œœ˜2šœœ˜šœœ˜Mšœœ)˜N—šœœ˜Mšœœ#˜H—šœœ˜Mšœœ#˜H—˜Mšœ%˜+—šœœ˜Mšœœ˜@—šœœ˜Mšœœ>˜c—šœœ˜Mšœœ,˜Q—šœœ˜Mšœ œ˜Mšœœœœœœœ œ˜‘Mšœ)œœ)œ˜–Mšœ œœœ œœœœ˜h—Mšœœ˜——M˜!—šŸœœœœœœ œ˜KMšœ=™=M˜šŸœœœœœœœ  œ˜Hšœœ˜Mšœ0˜0M˜Mšœ˜ ——M˜š Ÿ œœœœœ œ˜?M™/Mšœœ œœ˜šœœ˜Mšœœœœ)˜WMšœœœœ#˜NMšœœœœ#˜QMšœœ ˜)Mšœœœœ˜Hšœœ˜Mšœœ>˜P—šœœ˜Mšœœ,˜>—Mšœœ#˜*Mšœœ˜——M˜—M˜šŸ œœ œœœœœ˜^M™2Mšœœœ˜Mšœœœ˜M˜Mšœœœ+œ˜Kšœ˜Mšœ'œ˜-Mšœœ˜'—Mš œœ(œœœœ˜VMšœ1˜7M˜—šŸ œœœœ œœžœ˜IMšœ œœ)˜MMšœ œ,˜8—šŸ œœœœœœ"œœ˜`Mšœ°™°MšœX™XM˜šŸ œœœœœœ"œœ˜mM™’šœœœœœœœœ˜qMšœ œ˜—šœœ˜šœœ˜M™Yšœœœœ˜8M™uM˜Bšœ œœ˜M˜Mšœ œ.˜:——Mš œœ œœœœ˜`—Mšœœœ˜0šœœ˜M™gMš œ œœœ œœ˜?—˜M™"šœœ˜Mšœ=˜=Mšœ œ œ˜M˜ Mš˜——Mšœœœœ˜@šœœ˜M™^Mšœ œœ ˜$Mšœ>˜Cšœ œ˜MšœD˜D——šœœ˜M™KMšœ œœ ˜#š˜Mšœ<˜=—šœ œœœ˜4M˜M˜——Mšœœœž)˜RMšœœ˜—M˜—Mšœ(œ˜/Mšœ œœ˜-—š Ÿœœ œœœ˜AM™ŒMšœœ˜ Mšœœ˜ Mšœœ˜Mšœ˜šœ ˜M˜&M˜1šœ œ˜M˜NM˜ —M˜Mšœ˜—M˜%—š Ÿœœ œœ œ˜TM™VMšœœ˜šœ6˜Mšœ œ˜ Mšœ œ˜ Mšœ œ˜Mšœœœœ˜/Mšœ<˜Mšœ.™.Mšœ œ$˜3Mšœ˜Mšœ?˜?Mšœ˜Mšœ&˜&Mšœ˜ —M˜Mšœœœœ˜.Mšœ&œœœ˜8Mšœ<˜>šœ6œ˜FMšœ3™3Mšœ œ+˜:Mšœ˜MšœF˜FMšœ!˜!Mšœ-˜-Mšœ˜ —Mšœ˜ —šŸ œœœ*œ˜MMšœœ˜Mšœœœœ˜Mšœœœœ˜ Mšœœœœ˜*Mšœœœœ˜'Mšœœœœ˜1Mšœ"˜"Mšœ"œœœ˜7Mšœ/™/Mšœœœœ˜#šœœ˜šœœ˜Mšœœœœ˜"šœœ˜šœœ˜Mšœœ˜Mšœœ˜#Mšœ œœœ˜Mšœ$œœœ˜9Mšœœœœ˜"Mšœœœœ˜'Mšœ%˜%Mšœ&˜&šœœ˜Mšœ<™Mš˜Mšœœ˜M˜ šœœœ ˜Mšœœœ˜Mšœœœ˜Mšœ˜—Mšœ?˜?Mšœ˜M˜—š Ÿœœ œœ œ˜AMš˜Mšœœ˜M˜ šœœ ˜Mšœœœ˜Mšœœœ˜Mšœ˜—Mšœ?˜?Mšœ˜M˜M˜—š Ÿœœœœœ˜7Jš˜Jš œ œœœœ˜-Jšœ˜J˜—š Ÿœœœœœ˜7Jš˜Jš œ œœœœ˜-Jšœ˜J˜—Mš Ÿœœ œœœ˜g™Jšœ,™,Jšœ2™2Jšœ4™4—™Mšœ œœ˜.Jšœ ˜Jšœ œ˜+M˜Jšœ˜Jšœ œœœ˜Jšœœœœ˜$šœœœ˜!Jšœ œ˜šœ˜š œœ œœ˜2šœœ˜"Mšœœœ˜Fšœœ˜Mšœœœ5˜j—šœ˜Mšœœœ8˜m—MšœM˜MMšœœM˜[Mšœ?˜?Mšœœ ˜3Mšœœ˜ MšœD˜DMšœ˜—Mšœ˜ Mšœ˜——J˜J˜—Jšœœœœ˜-J˜šœœ˜#šœœœ˜Jšœœ+˜JJšœ%œ4˜\Jšœœ˜)Jšœ#œ˜.Jšœ.œ=˜nJšœ˜J˜—J˜š œœ œœ˜2šœœ˜"Mšœœœ6˜kMšœR˜RMšœL˜LMšœ0˜0Mšœœ ˜3Mšœœ˜ MšœD˜DMšœ˜—Mšœ˜ Mšœ˜———šŸœœœœœ0œœœ œœ˜yM™ίM˜šŸ œœœœœœ œœ˜VMšœœœœœœœœ œ˜šœœ˜šœœ˜šœœ˜šœ œ˜M™Mšœ?œœ˜L—šœœœ˜:šœœ˜'M™‘Mšœœ3œœ˜\—šœ˜Mšœ5™5Mšœ/™/Mšœ'˜'Mšœ%˜%Mšœ)˜)Mšœ#˜#M˜Mšœ œ-˜@——šœ˜ šœœ˜'M™qMšœ1˜1—šœ˜M™…Mšœœœ˜2Mšœœ˜M˜šœœœž˜9Mšœœ˜Mšœ˜—Mšœœ˜šœœœž˜:M˜Mšœ˜—Mšœœ:œYœ ˜ΆMšœ œ-˜@————Mšœœœ˜,Mšœœœ˜!˜šœ˜Mšœ!™!Mšœœœ.œ˜F—š˜šœœ˜Mšœ1˜1M˜ Mš˜———šœœ˜Mšœ&œ˜,—šœœ˜šœ œ˜M™1Mšœœ+˜@—š˜M™ Mšœ œ)˜;——šœœ˜šœ œ˜M™Mšœ œ(˜:—š˜Mšœ œ(˜9——Mšœœœ˜ Mšœœ˜—Mšœ ˜M˜—Mšœœ˜"—šŸ œœœœœ0œœœ œœ˜~M™’M˜šŸ œœœœœœ œœ˜WMšœœœœœœœœœ œ˜šœœ˜šœœ˜šœœ˜šœ œ˜M™šœ˜$Mšœ4™4Mšœ?œœ˜P——šœœœ˜<šœ˜!M™aMšœœ3œœ˜]—š˜Mšœ)˜)Mšœ'˜'Mšœ+˜+Mšœ œ0˜C——šœ˜™qMšœ2˜2——šœ˜ M™…Mšœœœ˜2Mšœœ˜M˜šœœœž˜9Mšœœ˜Mšœ˜—Mšœœ˜šœœœž˜:M˜Mšœ˜—Mš œœœœYœ ˜ΆMšœ œ+˜>M˜———Mš œœœœœ˜=Mšœœœ˜!˜šœ˜Mšœ!™!Mšœœœ.œ˜F—š˜šœœ˜Mšœ2˜2M˜ Mš˜———šœœ˜Mšœ'œ˜-—šœœ˜šœ œ˜M™1Mšœœ,˜A—šœ˜M™ Mšœ œ*˜š˜Mšœ œ)˜;——šœ˜Mšœ œ)˜:Mšœ œœ œ˜)——Mšœœœ˜ Mšœœ˜—Mšœ ˜M˜—Mšœœ˜$—Mšœ˜——…—W0‰L