wSyntax10.Scn.Fnt InfoElemsAllocVSyntax10.Scn.Fnt StampElemsAlloc22 Jun 99 "Title": MIME "Author": Robert Lichtenberger (RLI) "Abstract": Implementation of MIME (RFC2045-2049) "Keywords": MIME mail e-mail "Version": 1.0 "From": July 1998 "Until":  "Changes": 5 Oct 1998 RLI Bug in Text/HTML fixed 10 Nov 1998 RLI MIME delimiter reduced to alpha-characters 23 Nov 1998 RLI Fixed a bug with inline pictures in multipart/mixed mails "Hints": extensible implementation allows new media types to be integrated. BalloonElemsAlloc#Syntax10.Scn.Fnt,6,6"ok" If Decode returns ok then the mail could be decoded. "notExact" If Decode returns notExact then a decoder for a different subtype had to be used. "unknownContent" If Decode returns unknownContent then no decoding mechanism for this media type was available. "none" If GetEncodingRequirement returned none then the text can be sent as plain text. "quoted" If GetEncodingRequirement returned quoted then the text contains non-ASCII characters and should be encoded with quoted printable. "full" If GetEncodingRequirement returned full then the text contained elements or fonts that require full encoding. "maxCharSets" The maximum number of installable character sets. "ContentHandler" ContentHandler* = PROCEDURE (hdr: PostOffice.Header; tIn: Texts.Text; start, end: LONGINT; tOut: Texts.Text); A ContentHandler is a procedure that can handle the contents in tIn from start to end using the header information hdr and appending the decoded information to tOut. In some cases a ContentHandler may also insert information at positions other than the end of the text (e.g. attachments are put in the first line of the mail) ContentHandlers must be registered to handle media types by calling AddContentHandler. "CharsetProc" A charset procedure takes a character c and returns its closes equivalent in Oberon. "Charset" Charset* = RECORD name*: ARRAY 32 OF CHAR; proc*: CharsetProc END; A Charset is defined by its name an charset (conversion) procedure. Charsets may be installed by using AddCharsetProc. Whenever textual information with the charset x is encountered the installed Charset object with name x is searched and its proc used to convert the text. "Content" Content = POINTER TO ContentDesc; ContentDesc = RECORD type: String; handler: ContentHandler; next: Content END; A Content object defines a type / handler relation. Whenever a message of type x is found the corresponding ContentHandler is found by traversing the linked list of Content objects referred to by cHandler. "ContentDesc" ContentDesc = RECORD type: String; handler: ContentHandler; next: Content END; A Content object defines a type / handler relation. Whenever a message of type x is found the corresponding ContentHandler is found by traversing the linked list of Content objects referred to by cHandler. "Boundary" Boundary* = POINTER TO BoundaryDesc; BoundaryDesc* = RECORD str: String; pos-, end-: LONGINT; (* start of boundary, end of header *) next-: Boundary; header-: PostOffice.Header END; A boundary represents a MIME multipart boundary as described in RFC2046. str points to the text of the boundary. pos defines the start of the boundary within the message. end defines the end of the header following the boundary next points to the next MIME boundary with the same str. header points to the header following the boundary. "BoundaryDesc" BoundaryDesc* = RECORD str: String; pos-, end-: LONGINT; (* start of boundary, end of header *) next-: Boundary; header-: PostOffice.Header END; A boundary represents a MIME multipart boundary as described in RFC2046. str points to the text of the boundary. pos defines the start of the boundary within the message. end defines the end of the header following the boundary next points to the next MIME boundary with the same str. header points to the header following the boundary. "InsertTaskMarker" InsertTaskMarker = POINTER TO InsertTaskMarkerDesc; InsertTaskMarkerDesc = RECORD (Texts.ElemDesc) END; A rather simple extension of text elements which draws a big blue rectangle and is used as a landmark for late insertion of HTML - mails which are loaded in the background. "InsertTaskMarkerDesc" InsertTaskMarkerDesc = RECORD (Texts.ElemDesc) END; A rather simple extension of text elements which draws a big blue rectangle and is used as a landmark for late insertion of HTML - mails which are loaded in the background. "InsertTask" InsertTask = POINTER TO InsertTaskDesc; InsertTaskDesc = RECORD (Oberon.TaskDesc) t: Texts.Text; wTxt: Web.Text; filename: ARRAY 32 OF CHAR; marker: InsertTaskMarker END; A background task handled by HandleInsert that checks if wTxt has already been loaded completely in the background. If so, it removes the InsertTaskMarker marker and inserts wTxt instead. This "hack" is neccessary becaus HTML - files can only be loaded by background tasks. "InsertTaskDesc" InsertTaskDesc = RECORD (Oberon.TaskDesc) t: Texts.Text; wTxt: Web.Text; filename: ARRAY 32 OF CHAR; marker: InsertTaskMarker END; A background task handled by HandleInsert that checks if wTxt has already been loaded completely in the background. If so, it removes the InsertTaskMarker marker and inserts wTxt instead. This "hack" is neccessary becaus HTML - files can only be loaded by background tasks. "CID" CID = POINTER TO CIDDesc; CIDDesc = RECORD id: String; e: PElems.Elem; next: CID END; A content ID picture is stored as a result of multipart/related HTML mails which contain in-line pictures. All pictures contained in one such mail are stored under cid. "CIDDesc" CIDDesc = RECORD id: String; e: PElems.Elem; next: CID END; A content ID picture is stored as a result of multipart/related HTML mails which contain in-line pictures. All pictures contained in one such mail are stored under cid. "CIDScheme" A loader for URLs starting with 'cid:' "CIDSchemeDesc" A loader for URLs starting with 'cid:' "cHandler" The list of content handlers currently installed. "cid" The list of pictures decoded from the current multipart/related mail. "anonFileNr" Whenever a temporary file is needed this number is increased to ensure its name is unique. "charsetTbl" The installed character set conversion objects. "nrCharSets" The number of installed character set conversion objects. "z" Seed for random function. Initialized with Oberon.Time() in Init. "IsMime" PROCEDURE IsMime* (mail: PostOffice.Mail): BOOLEAN; Returns true if mail is a MIME message. "GetContentType" PROCEDURE GetContentType* (hdr: PostOffice.Header): String; Gets toplevel content type from hdr. Example: If the line Content-Type: text/plain; charset=us-ascii is found in hdr GetContentType will return the string 'text'. "GetContentSubType" PROCEDURE GetContentSubType* (hdr: PostOffice.Header): String; Gets content sub-type from hdr. Example: If the line Content-Type: text/plain; charset=us-ascii is found in hdr GetContentType will return the string 'plain'. "GetCompleteContentType" PROCEDURE GetCompleteContentType* (hdr: PostOffice.Header): String; Gets complete content type from hdr. Example: If the line Content-Type: text/plain; charset=us-ascii is found in hdr GetContentType will return the string 'text/plain'. "GetEncoding" PROCEDURE GetEncoding (hdr: PostOffice.Header): String; Gets the Content-Transfer-Encoding method from hdr. "AddContentHandler" PROCEDURE AddContentHandler* (contentType: ARRAY OF CHAR; handler: ContentHandler); Extends the set of known media types. If a message of content-Type 'contentType' is found, then handler will be called to decode it. "RemoveContentHandler" PROCEDURE RemoveContentHandler* (contentType: ARRAY OF CHAR); Removes the handler for content-Type 'contentType'. You may use this procedure if you encountered problems with a certain content handler. RemoveContentHandler should also be called for a handler if the handler's module is unloaded. (Use Kernel.TerminationHandlers) "GetContentHandler" PROCEDURE GetContentHandler* (contentType: ARRAY OF CHAR; VAR handler: ContentHandler; VAR exact: BOOLEAN); Gets the handler for 'contentType', yielding the result in handler. exact is true, if the returned handler matches the contentType description exactly. "Ascii" PROCEDURE Ascii (c: CHAR): CHAR; Handles ascii character sets. This procedure is extremely complicated ;-) "Iso88591" PROCEDURE Iso88591 (c: CHAR): CHAR; Handles ISO-8859-Part1 character set. Some characters have no exact equivalent under Oberon and may be mapped inaccurate or not at all. "GetCharsetProc" PROCEDURE GetCharsetProc* (hdr: PostOffice.Header): CharsetProc; Gets the charset procedure for the content-type in hdr. "AddCharsetProc" PROCEDURE AddCharsetProc* (cs: Charset); Adds the conversion object cs. All text of charset cs.name will be decoded with cs.proc. "ProcessQuotedPrintable" PROCEDURE ProcessQuotedPrintable (t: Texts.Text; begin, end: LONGINT; tOut: Texts.Text; charset: CharsetProc); Scans t from begin to end and replaces all occurences of quoted text (cf. RFC2045), using charset. The result is appended to tOut. "TextPlain" PROCEDURE TextPlain (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Decodes plain text with respect to proper charset and quoted printable encoding. "HandleMarker" PROCEDURE HandleMarker (E: Texts.Elem; VAR msg: Texts.ElemMsg); Handles InsertTaskMarkers. "InsertMarker" PROCEDURE InsertMarker (t: Texts.Text; pos: LONGINT): InsertTaskMarker; Inserts an InsertTaskMarker in t at pos. The inserted marker is returned. "FindElem" PROCEDURE FindElem (t: Texts.Text; e: Texts.Elem): LONGINT; Finds the element e in t, returning the element's position in t. -1 is returned if e is not in t. "HandleInsert" PROCEDURE HandleInsert; Task handler for the InsertTask that checks if wTxt has already been loaded completely in the background. If so, it removes the InsertTaskMarker marker and inserts wTxt instead. This "hack" is neccessary becaus HTML - files can only be loaded by background tasks. "TextHTML" PROCEDURE TextHTML (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Decodes HTML text by storing tIn to a file and opening that file as "file://"-URL in the background. Upon completion a InsertTask will display the loaded text. Character sets and quoted-printable encoding is supported by this procedure. "GetBoundaries" PROCEDURE GetBoundaries* (header: PostOffice.Header; t: Texts.Text; VAR bound: Boundary); Creates the list of boundaries bound with respect to the boundary parameter of the content type header field in header. "MultipartMixed" PROCEDURE MultipartMixed (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Handles multipart/mixed content type which is used for text & attachment mails most of the time. Decoding is done by calling a content handler for each part of the message. "MultipartAlternative" PROCEDURE MultipartAlternative (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Handles multipart/alternative content type which is used to transmit a message in more than one format. Decoding is done by calling the best content handler. (Cf. RFC2046) "PopupHandle" PROCEDURE PopupHandle (E: Texts.Elem; VAR msg: Texts.ElemMsg); Special handle for attachment popups that prevents the pop elements from popping up. "InsertAttachmentPopup" PROCEDURE InsertAttachmentPopup (fn: String; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Inserts a popup element with handler PopupHandle and the command Base64.Decode filename into tOut. "Image" PROCEDURE Image (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Decodes Images by one of the following methods: * Insert a PElem into tOut (inline picture) * Insert an attachment into tOut (attachment picture) * Insert a PElem into cid-list (inline picture in multipart/related content) "AppOctStream" PROCEDURE AppOctStream (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Handles attachments. "MessageRfc822" PROCEDURE MessageRfc822 (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Handles content type message/rfc822. The content of tIn is decoded and put into collapsed fold elements. "XOberon" PROCEDURE XOberon (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text); Handles content type application/x-oberon. The content of tIn is the ascii-encoded version of the text to restore in tOut. "GetCID" PROCEDURE GetCID (id: ARRAY OF CHAR): PElems.Elem; Gets the PElem for content-id id. "Open" PROCEDURE (s: CIDScheme) Open* (act: Web.UrlStack; txt: Web.Text; elem: Texts.Elem; useCache: BOOLEAN); Opens 'cid:'-URLs by looking up the cid list. Multipart related messages can be decoded this way. "Decode" PROCEDURE Decode* (mail: PostOffice.Mail; VAR t: Texts.Text; VAR res: INTEGER); Decodes mail with message t, yielding the result in res. "GetEncodingRequirements" PROCEDURE GetEncodingRequirement* (t: Texts.Text): INTEGER; Scans t and returns the level of needed encoding. Cf. none, quoted, full. "EncodeQuotedPrintable" PROCEDURE EncodeQuotedPrintable (t: Texts.Text; VAR w: Texts.Writer); Encodes t with the quoted printable encoding, yielding the result in w.buf. "EncodeTextPlain" PROCEDURE EncodeTextPlain (t: Texts.Text; VAR w: Texts.Writer); Encodes t as plain text. Elements are encoded as []. The result is returned in w.buf. "EncodeHTML" PROCEDURE EncodeHTML (t: Texts.Text; VAR w: Texts.Writer); Encodes t as HTML text, yielding the result in w.buf. "EncodeXOberon" PROCEDURE EncodeXOberon (t: Texts.Text; VAR w: Texts.Writer); Ascii-Encodes t, yielding the result in w.buf. "Uniform" PROCEDURE Uniform (): REAL; Returns a random number. "InitSeed" PROCEDURE InitSeed; Initializes random number generation. "GetRandomBound" PROCEDURE GetRandomBound (): String; Returns a random MIME boundary string. "Encode" PROCEDURE Encode* (VAR t: Texts.Text; att: PostOffice.StringList); Encodes t with attachments att according to the needed encoding requirements. "Init" Initializes MIME by adding a few content handlers, setting the seed for the random number generator, adding basic character set conversion routines and installing a loader for 'cid:' URLs. Syntax10i.Scn.FntSyntax10b.Scn.Fnt (D Ny^      %    68FoldElemsNew#Syntax10.Scn.Fnt   VAR buf: ARRAY 32 OF CHAR; digit: ARRAY 2 OF CHAR; BEGIN digit[1] := 0X; buf[0] := 0X; WHILE x > 0 DO digit[0] := CHR(ORD('0') + x MOD 10); Strings.Insert(digit, 0, buf); x := x DIV 10 END; NEW(str, Strings.Length(buf) + 1); COPY(buf, str^) END IntToStr; 8L8#Syntax10.Scn.Fnt VAR tmp: String; BEGIN COPY("$mimeTemp", filename); IntToStr(anonFileNr, tmp); Strings.Append(tmp^, filename); INC(anonFileNr); f := Files.New(filename); Files.Register(f) END CreateTempFile; 8" #8Syntax10.Scn.FntSyntax10i.Scn.Fnt2%,Syntax10b.Scn.Fnt  k (* Returns TRUE, iff mail is Mime 1.0-compliant *) VAR cur: PostOffice.Header; pos, pos2: INTEGER; bodyCopy: String; BEGIN cur := mail.header.GetHeaderField("MIME-VERSION"); IF (cur # NIL) & (LEN(cur.body^) >= 4) THEN (* field body may contain comments *) NEW(bodyCopy, LEN(cur.body^)); COPY(cur.body^, bodyCopy^); pos := Strings.Pos("(", bodyCopy^, 0); WHILE pos # - 1 DO pos2 := Strings.Pos(")", bodyCopy^, pos); IF pos2 # - 1 THEN Strings.Delete(bodyCopy^, pos, (pos2 - pos)) END; pos := Strings.Pos("(", bodyCopy^, pos) END; RETURN bodyCopy^ = "1.0" END; RETURN FALSE END IsMime; 8 #8CSyntax10.Scn.FntSyntax10i.Scn.Fntl  (* Returns a String containing the MIME type or NIL if no CONTENT-TYPE could be found (i.e. not MIME 1.0) *) VAR cur: PostOffice.Header; pos: INTEGER; type, default: String; BEGIN NEW(default, 5); COPY("text", default^); IF hdr = NIL THEN RETURN default END; cur := hdr.GetHeaderField("CONTENT-TYPE"); IF cur = NIL THEN RETURN default END; pos := Strings.Pos(cur.body^, "/", 0); IF pos = - 1 THEN RETURN default END; NEW(type, pos + 1); COPY(cur.body^, type^); type[pos] := 0X; RETURN type END GetContentType; 8 #'8qSyntax10.Scn.FntSyntax10i.Scn.FntlSyntax10b.Scn.Fntli (* Returns a String containing the MIME type or NIL if no CONTENT-TYPE could be found (i.e. not MIME 1.0) *) VAR cur: PostOffice.Header; pos, pos2: INTEGER; type, default: String; BEGIN NEW(default, 6); COPY("plain", default^); cur := hdr.GetHeaderField("CONTENT-TYPE"); IF cur = NIL THEN RETURN default END; pos := Strings.Pos("/", cur.body^, 0); pos2 := Strings.Pos(";", cur.body^, pos); IF pos = - 1 THEN RETURN default END; IF pos2 = - 1 THEN pos2 := SHORT(LEN(cur.body^)) END; NEW(type, pos2 - pos + 1); Strings.Extract(cur.body^, pos + 1, pos2 - pos - 1, type^); RETURN type END GetContentSubType; 8 #^8qSyntax10.Scn.FntSyntax10i.Scn.FntuSyntax10b.Scn.FntE2 (* Returns a String containing the complete MIME type or NIL if no CONTENT-TYPE could be found (i.e. not MIME 1.0) *) VAR cur: PostOffice.Header; type, default: String; pos: INTEGER; BEGIN NEW(default, 11); COPY("text/plain", default^); IF hdr = NIL THEN RETURN default END; cur := hdr.GetHeaderField("CONTENT-TYPE"); IF cur = NIL THEN RETURN default END; pos := Strings.Pos(";", cur.body^, 0); IF pos = - 1 THEN pos := SHORT(LEN(cur.body^)) END; NEW(type, pos + 2); Strings.Extract(cur.body^, 0, pos, type^); RETURN type END GetCompleteContentType; 898#Syntax10.Scn.Fnt VAR enc: PostOffice.Header; encoding: String; BEGIN enc := hdr.GetHeaderField("CONTENT-TRANSFER-ENCODING"); IF enc = NIL THEN RETURN NIL END; NEW(encoding, Strings.Length(enc.body^) + 1); COPY(enc.body^, encoding^); Strings.Cap(encoding^); RETURN encoding END GetEncoding; 8 88#Syntax10.Scn.Fnt VAR h: Content; BEGIN NEW(h); h.handler := handler; Strings.Cap(contentType); NEW(h.type, Strings.Length(contentType) + 1); COPY(contentType, h.type^); h.next := cHandler; cHandler := h END AddContentHandler; 8 8#Syntax10.Scn.Fnt VAR cur, prev: Content; BEGIN cur := cHandler; prev := NIL; WHILE (cur # NIL) & (cur.type^ # contentType) DO prev := cur; cur := cur.next END; IF prev = NIL THEN cHandler := cHandler.next ELSE prev.next := cur.next END END RemoveContentHandler; 8 P?8CSyntax10.Scn.FntSyntax10i.Scn.FntT) (* Retrieves Content handler for contentType; exact = TRUE if subtype was matched *) VAR cur, bestMatch: Content; mainType: String; pos: INTEGER; BEGIN pos := Strings.Pos(";", contentType, 0); IF pos # - 1 THEN contentType[pos] := 0X END; Strings.Cap(contentType); pos := Strings.Pos("/", contentType, 0); NEW(mainType, pos + 2); IF pos # - 1 THEN NEW(mainType, pos + 1); Strings.Extract(contentType, 0, pos, mainType^) ELSE mainType := NIL END; cur := cHandler; bestMatch := NIL; WHILE (cur # NIL) & ~Strings.Match(contentType, cur.type^) DO IF (bestMatch = NIL) & (Strings.Pos(mainType^, cur.type^, 0) # - 1) THEN bestMatch := cur END; cur := cur.next END; IF cur # NIL THEN handler := cur.handler; exact := TRUE ELSIF bestMatch # NIL THEN handler := bestMatch.handler; exact := FALSE ELSE handler := NIL; exact := FALSE END END GetContentHandler; 8"8#Syntax10.Scn.Fnt BEGIN RETURN c END Ascii; 8%8C,7111/#8 (%8#Syntax10.Scn.Fnt VAR ct: PostOffice.Header; name: String; charset: CharsetProc; i: INTEGER; BEGIN charset := NIL; ct := hdr.GetHeaderField("CONTENT-TYPE"); IF ct = NIL THEN charset := Ascii ELSE name := ct.GetParameter("charset"); IF name = NIL THEN charset := Ascii ELSE Strings.Cap(name^); i := 0; WHILE (i < nrCharSets) & (charsetTbl[i].name # name^) DO INC(i) END; IF charsetTbl[i].name # name^ THEN charset := Ascii ELSE charset := charsetTbl[i].proc END END END; IF charset = NIL THEN Out.String("MIME: Warning: unknown charset '"); Out.String(name^); Out.String("'. Defaulting to ASCII$"); charset := Ascii END; RETURN charset END GetCharsetProc; 8 8#Syntax10.Scn.FntJJ BEGIN charsetTbl[nrCharSets] := cs; INC(nrCharSets) END AddCharsetProc; 8$;W8CSyntax10.Scn.FntSyntax10i.Scn.Fntwg VAR f: Files.File; fr: Files.Rider; r: Texts.Reader; ch: CHAR; BEGIN f := Files.New(name); Files.Set(fr, f, 0); Texts.OpenReader(r, t, 0); Texts.Read(r, ch); WHILE ~r.eot DO IF ch = 0DX THEN ch := 0AX END ; (* for Unix ASCII files *) IF ch # Texts.ElemChar THEN Files.Write(fr, ch) END ; Texts.Read(r, ch) END ; Files.Register(f) END StoreAscii; 8 ?Z8#Syntax10.Scn.Fnt VAR ct: String; handler: ContentHandler; exact: BOOLEAN; tOut: Texts.Text; BEGIN tOut := TextFrames.Text(""); ct := GetCompleteContentType(mail.header); GetContentHandler(ct^, handler, exact); IF (handler # NIL) THEN handler(mail.header, t, mail.bodyOff, t.len, tOut); IF exact THEN res := ok ELSE res := notExact END; t := tOut ELSE res := unknownContent END END Decode; 8%V8CSyntax10.Scn.FntSyntax10i.Scn.FntMh (* PRE: c is uppercase *) BEGIN RETURN (c >= "A") & (c <= "F") OR (c >= "0") & (c <= "9") END IsHex; 8&d8#Syntax10.Scn.Fntzz BEGIN IF (c >= "0") & (c <= "9") THEN RETURN ORD(c) - ORD("0") ELSE RETURN ORD(c) - ORD("A") + 10 END END HexVal; 8p~8_Syntax10.Scn.FntSyntax10i.Scn.Fnt$ VAR r: Texts.Reader; ch, ch1, ch2: CHAR; code: INTEGER; BEGIN Texts.OpenBuf(w.buf); Texts.OpenReader(r, t, begin); Texts.Read(r, ch); WHILE ~r.eot & (Texts.Pos(r) <= end) DO IF ch = "=" THEN Texts.Read(r, ch1); IF IsHex(ch1) THEN (* Hex encoded character *) Texts.Read(r, ch2); IF IsHex(ch2) THEN code := HexVal(ch1) * 16 + HexVal(ch2); Texts.Write(w, charset(CHR(code))) ELSE Out.String("MIME: QuotedPrintable decoding error: Hex digit expected.$") END ELSIF (ch1 = NL) THEN (* soft line break *) (* do nothing *) ELSE Out.String("MIME: QuotedPrintable decoding error: Hex digit or newline expected after =.$") END ELSE Texts.Write(w, charset(ch)) END; Texts.Read(r, ch) END; Texts.Append(tOut, w.buf) END ProcessQuotedPrintable; 8  S8Syntax10.Scn.FntD8FoldElemsNew#Syntax10.Scn.FntTexts.OpenReader(r, tIn, begin); Texts.Read(r, ch); WHILE ~r.eot & (Texts.Pos(r) < end) DO Texts.Write(w, charset(ch)); Texts.Read(r, ch) END; 828#Syntax10.Scn.FntTexts.WriteLn(w); IF ~PostOffice.GetAsBuffer("MIMEDelimiter", w.buf) THEN Out.String("MIME: Error. No entry MIMEDelimiter in Profile") END; Texts.Append(tOut, w.buf);  8Q VAR st: String; r: Texts.Reader; charset: CharsetProc; ch: CHAR; BEGIN Texts.OpenBuf(w.buf); Texts.WriteLn(w); st := GetEncoding(hdr); charset := GetCharsetProc(hdr); IF (st # NIL) & (st^ = "QUOTED-PRINTABLE") THEN ProcessQuotedPrintable(tIn, begin, end, tOut, charset) ELSE Copy text END; Add delimiter END TextPlain; 8A#8Syntax10.Scn.FntmSyntax10b.Scn.Fnt 8Syntax10i.Scn.Fnt _ VAR e: InsertTaskMarker; x, y, w, h: INTEGER; keys: SET; BEGIN WITH E: InsertTaskMarker DO WITH msg: TextFrames.DisplayMsg DO IF ~msg.prepare THEN w := SHORT(E.W DIV unit); h := SHORT(E.H DIV unit); Display.ReplConst(3, msg.X0 + 1, msg.Y0 + 1, w - 2, h - 2, Display.replace) END | msg: TextFrames.TrackMsg DO IF msg.keys = {middle} THEN REPEAT Input.Mouse(keys, x, y); Oberon.DrawCursor(Oberon.Mouse, Oberon.Arrow, x, y) UNTIL keys = {} END | msg: Texts.CopyMsg DO NEW(e); Texts.CopyElem(E, e); msg.e := e ELSE (*ignore it*) END END END HandleMarker; 8I8#Syntax10.Scn.Fnt VAR e: InsertTaskMarker; BEGIN Texts.OpenBuf(w.buf); NEW(e); e.H := Fonts.Default.height * unit; e.W := e.H * 4; e.handle := HandleMarker; Texts.WriteElem(w, e); Texts.WriteLn(w); Texts.Insert(t, pos, w.buf); Texts.Insert(t, pos, w.buf); RETURN e END InsertMarker; 8=8#Syntax10.Scn.Fnt VAR r: Texts.Reader; BEGIN Texts.OpenReader(r, t, 0); Texts.ReadElem(r); WHILE ~r.eot & (r.elem # NIL) & (r.elem # e) DO Texts.ReadElem(r) END; IF (r.elem = e) THEN RETURN Texts.Pos(r) ELSE RETURN - 1 END END FindElem; 88#Syntax10.Scn.Fnt__ VAR t: InsertTask; pos: LONGINT; res: INTEGER; parc: TextFrames.Parc; copy: Texts.CopyMsg; BEGIN t := Oberon.CurTask(InsertTask); IF ~t.wTxt.loading THEN Texts.Save(t.wTxt, 0, t.wTxt.len, w.buf); TextFrames.defParc.handle(TextFrames.defParc, copy); parc := copy.e(TextFrames.Parc); Texts.WriteElem(w, parc); pos := FindElem(t.t, t.marker); IF pos # - 1 THEN Texts.Insert(t.t, pos, w.buf); Texts.Delete(t.t, pos - 1, pos) ELSE HALT(100); END; Oberon.Remove(t); cid := NIL; Files.Delete(t.filename, res); ASSERT(res = 0) END; t.time := Oberon.Time() + 50 END HandleInsert; 8  S88#Syntax10.Scn.Fnt--Texts.WriteLn(w); Texts.Append(tOut, w.buf); "88#Syntax10.Scn.Fnt66NEW(wTxt); Texts.Open(wTxt, ""); wTxt.notify := NIL; 8(8#Syntax10.Scn.FntTexts.OpenReader(r, tIn, begin); Texts.Read(r, ch); WHILE ~r.eot & (Texts.Pos(r) < end) DO Texts.Write(w, charset(ch)); Texts.Read(r, ch) END; Texts.Append(wTxt, w.buf) 8 E8#Syntax10.Scn.FntIF ~PostOffice.GetAsBuffer("MIMEDelimiter", w.buf) THEN Out.String("MIME: Error. No entry MIMEDelimiter in Profile") END; Texts.Append(tOut, w.buf);  88 B8Syntax10.Scn.FntSyntax10i.Scn.FntT_8FoldElemsNewCSyntax10.Scn.FntSSyntax10b.Scn.Fnt_Strings.Extract(hdr.body^, 0, 9, str); Strings.Cap(str); IF str # "MULTIPART" THEN RETURN END; $88#Syntax10.Scn.FntOOIF (cur.header # NIL) & (cur.header.name[0] = "-") THEN cur.header := NIL END;  8N^ (* Returns newly allocated Boundary-List in bound or NIL, if not multipart - MIME *) VAR hdr: PostOffice.Header; cur, prev: Boundary; bPos, len: LONGINT; dummy: String; str: ARRAY 16 OF CHAR; r: Texts.Reader; BEGIN bound := NIL; hdr := header.GetHeaderField("CONTENT-TYPE"); if not multipart message then return NEW(bound); cur := bound; dummy := hdr.GetParameter("boundary"); NEW(bound.str, Strings.Length(dummy^) + 3); COPY("--", bound.str^); Strings.Append(dummy^, bound.str^); len := Strings.Length(bound.str^); bPos := PostOffice.SearchPatt2(t, bound.str^, 0); WHILE bPos # - 1 DO cur.pos := bPos - len; Texts.OpenReader(r, t, bPos + 1); NEW(cur.header); PostOffice.BuildHeader(r, cur.header); cur.end := Texts.Pos(r); No headers beyond final boundary prev := cur; NEW(cur.next); cur := cur.next; cur.str := bound.str; bPos := PostOffice.SearchPatt2(t, bound.str^, bPos) END; IF prev # NIL THEN IF prev.header = NIL THEN prev.next := NIL ELSE Out.String("MIME: Missing boundary in mail.$"); prev.header := NIL; prev.pos := t.len END END END GetBoundaries; 8  S88  S;8#Syntax10.Scn.Fnt VAR bound, cur, best: Boundary; ct: String; handler, bestHandler: ContentHandler; exact: BOOLEAN; BEGIN GetBoundaries(hdr, tIn, bound); ASSERT(bound # NIL); cur := bound; bestHandler := NIL; WHILE (cur # NIL) & (cur.header # NIL) DO ct := GetCompleteContentType(cur.header); ASSERT(ct # NIL); GetContentHandler(ct^, handler, exact); IF (bestHandler = NIL) OR (exact) THEN bestHandler := handler; best := cur END; cur := cur.next END; IF (bestHandler # NIL) THEN bestHandler(best.header, tIn, best.end, best.next.pos - 1, tOut) ELSE Out.String("MIME: Could not find any handler for multipart/alternative message$"); END END MultipartAlternative; 8@8CSyntax10.Scn.FntSyntax10b.Scn.Fnt"> VAR src, dest: Files.File; srcName, destName: ARRAY 256 OF CHAR; res: INTEGER; buf: Texts.Buffer; cElem: AttachElem; BEGIN WITH E: AttachElem DO WITH msg: PopupElems.ExecMsg DO CreateTempFile(src, srcName); StoreAscii(E.data, srcName); src := Files.Old(srcName); ASSERT(src # NIL); Files.Close(src); COPY(E.name, destName); dest := Files.New(destName); Files.Register(dest); Web.DecodeFile("Base64", src, dest, res); Files.Close(dest); IF res # Web.done THEN Out.String("MIME: Decoding error.$"); RETURN ELSE Out.String("MIME: "); Out.String(destName); Out.String(" decoding done.$") END; Files.Delete(srcName, res); ASSERT(res = 0); | msg: Texts.CopyMsg DO IF msg.e = NIL THEN NEW(cElem); msg.e := cElem ELSE cElem := msg.e(AttachElem) END; PopupElems.Handle(E, msg); cElem.data := TextFrames.Text(""); NEW(buf); Texts.OpenBuf(buf); Texts.Save(E.data, 0, E.data.len, buf); Texts.Append(cElem.data, buf) ELSE PopupElems.Handle(E, msg) END ELSE PopupElems.Handle(E, msg) END END PopupHandle; 8gI8CSyntax10.Scn.FntnSyntax10i.Scn.Fnt*u VAR e: AttachElem; s: Texts.Scanner; ch: CHAR; pos: LONGINT; BEGIN Texts.OpenBuf(w.buf); NEW(e); COPY(fn^, e.name); e.small := TRUE; e.menu := TextFrames.Text(""); Texts.WriteString(w, "Change name in menu if it does not comply with Oberon naming conventions"); Texts.WriteLn(w); Texts.Append(e.menu, w.buf); Texts.Save(tIn, begin, end, w.buf); e.data := TextFrames.Text(""); Texts.Append(e.data, w.buf); PopupElems.MeasureMenu(e); e.handle := PopupHandle; Texts.WriteElem(w, e); Texts.Write(w, " "); Texts.OpenScanner(s, tOut, 0); Texts.Scan(s); IF (s.class = Texts.Name) & (s.s = "Attachments:") THEN (* not first attachment, e.g. multipart *) Texts.Read(s, ch); WHILE ch # NL DO pos := Texts.Pos(s); Texts.Read(s, ch) END; Texts.Insert(tOut, pos, w.buf) ELSE Texts.WriteLn(w); IF ~PostOffice.GetAsBuffer("MIMEDelimiter", w.buf) THEN Out.String("MIME: Error. No entry MIMEDelimiter in Profile") END; Texts.Insert(tOut, 0, w.buf); Texts.SetColor(w, 3); Texts.WriteString(w, "Attachments: "); Texts.SetColor(w, Display.white); Texts.Insert(tOut, 0, w.buf) END END InsertAttachmentPopup; 8  S8 Syntax10.Scn.Fnti8FoldElemsNew#Syntax10.Scn.Fnt44IF st = NIL THEN NEW(st, 5); COPY("7BIT", st^) END; 88Syntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.FntCreateTempFile(f, filename); 8m8Syntax10.Scn.Fnt#d8FoldElemsNew#Syntax10.Scn.FntzztTmp := TextFrames.Text(""); NEW(buf); Texts.OpenBuf(buf); Texts.Save(tIn, begin, end, buf); Texts.Append(tTmp, buf); #8PCreateTempFile(f2, filename2); Copy relevant part of tIn into tTmp StoreAscii(tTmp, filename2); f2 := Files.Old(filename2); ASSERT(f2 # NIL); 8Syntax10b.Scn.Fnt Create destination file Create source file Web.DecodeFile("Base64", f2, f, res); Files.Close(f); IF res # Web.done THEN Out.String("MIME: Base64 decoding error.$"); RETURN END; 88#Syntax10.Scn.Fnt==NEW(e); e.handle := PElems.Handler; li := Pictures.Load(f, 0); IF li # NIL THEN IF li.picture = NIL THEN NEW(li.picture); li.picture.Init(1, 1, 1) END; e.pic := li.picture; li.Do; li.Completed; Texts.OpenBuf(w.buf); Texts.WriteElem(w, e); Texts.Append(tOut, w.buf) END; 8y8#Syntax10.Scn.FnteeFiles.Delete(filename, res); ASSERT(res = 0); Files.Delete(filename2, res); ASSERT(res = 0); 88#Syntax10.Scn.Fntdisp := hdr.GetHeaderField("CONTENT-DISPOSITION"); IF disp # NIL THEN fn := disp.GetParameter("filename") END; IF fn = NIL THEN NEW(fn, 12); COPY("noname", fn^); IntToStr(anonFileNr, tmp); Strings.Append(fn^, tmp^) END;  88Syntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.FntCreateTempFile(f, filename); 8p8Syntax10.Scn.Fnt"e8FoldElemsNew#Syntax10.Scn.FntyytTmp := TextFrames.Text(""); NEW(buf); Texts.OpenBuf(buf); Texts.Save(tIn, begin, end, buf); Texts.Append(tTmp, buf); #8OCreateTempFile(f2, filename2); Copy relevant part of tIn into tTmp StoreAscii(tTmp, filename2); f2 := Files.Old(filename2); ASSERT(f2 # NIL); 8uMarkElemsAlloc;Syntax10b.Scn.Fnt Create destination file Create source file Web.DecodeFile("Base64", f2, f, res); IF res # Web.done THEN Out.String("MIME: Base64 decoding error.$"); HALT(99); RETURN END; 88#Syntax10.Scn.FntNEW(e); e.handle := PElems.Handler; li := Pictures.Load(f, 0); IF li # NIL THEN IF li.picture = NIL THEN NEW(li.picture); li.picture.Init(1, 1, 1) END; e.pic := li.picture; li.Do; li.Completed; nCid.e := e; nCid.next := cid; cid := nCid END; 8~8#Syntax10.Scn.Fnt``Files.Delete(filename, res); ASSERT(res = 0); Files.Delete(filename2, res); ASSERT(res = 0); 8)a VAR st, fn, tmp: String; filename, filename2: ARRAY 64 OF CHAR; f, f2: Files.File; e: PElems.Elem; li: Pictures.LoadInfo; res: INTEGER; disp, id: PostOffice.Header; inline: BOOLEAN; nCid: CID; tTmp: Texts.Text; buf: Texts.Buffer; BEGIN IF Modules.ThisMod("Jpeg") = NIL THEN Out.String("MIME: Could not load Jpeg - Module$"); END; st := GetEncoding(hdr); Default encoding Strings.Cap(st^); disp := hdr.GetHeaderField("CONTENT-DISPOSITION"); inline := TRUE; IF disp # NIL THEN inline := Strings.Pos("attachment", disp.body^, 0) # 0 END; id := hdr.GetHeaderField("CONTENT-ID"); IF st^ = "BASE64" THEN IF (id = NIL) THEN IF inline THEN Decode into temporary file f Insert PElem into tOut Delete temporary files ELSIF ~inline THEN Get filename InsertAttachmentPopup(fn, tIn, begin, end, tOut) END ELSE NEW(nCid); NEW(nCid.id, Strings.Length(id.body^) - 1); Strings.Extract(id.body^, 1, Strings.Length(id.body^) - 2, nCid.id^); Decode into temporary file f Insert PElem into cid-list Delete temporary files END ELSIF (st^ = "7BIT") OR (st^ = "QUOTED-PRINTABLE") THEN Out.String("MIME: Image in 7bit/quoted printable encoding, presenting in-line$"); TextPlain(hdr, tIn, begin, end, tOut) ELSE Out.String("MIME: Unknown encoding. Part of / all of the message will be ommited.$") END END Image; 8  S8Syntax10.Scn.Fnt^8FoldElemsNew#Syntax10.Scn.Fnt44IF st = NIL THEN NEW(st, 5); COPY("7BIT", st^) END; 88#Syntax10.Scn.Fntdisp := hdr.GetHeaderField("CONTENT-DISPOSITION"); IF disp # NIL THEN fn := disp.GetParameter("filename") END; IF fn = NIL THEN NEW(fn, 12); COPY("noname", fn^); IntToStr(anonFileNr, nr); Strings.Append(fn^, nr^) END;  8} VAR st, fn, nr: String; disp: PostOffice.Header; BEGIN fn := NIL; st := GetEncoding(hdr); Default encoding Strings.Cap(st^); Get filename IF st^ = "BASE64" THEN InsertAttachmentPopup(fn, tIn, begin, end, tOut) ELSIF (st^ = "7BIT") OR (st^ = "QUOTED-PRINTABLE") THEN Out.String("MIME: Don't know what to do with application-content, presenting in-line$"); TextPlain(hdr, tIn, begin, end, tOut) ELSE Out.String("MIME: Unknown encoding. Part of / all of the message will be ommited.$") END END AppOctStream; 8  Sv8rSyntax10.Scn.Fnt8FoldElemsNew#Syntax10.Scn.Fnt--Texts.WriteLn(w); Texts.Append(tOut, w.buf); #8(8#Syntax10.Scn.FntTexts.OpenReader(r, tIn, begin); Texts.Read(r, ch); WHILE ~r.eot & (Texts.Pos(r) < end) DO Texts.Write(w, charset(ch)); Texts.Read(r, ch) END; Texts.Append(tTmp, w.buf) 88#Syntax10.Scn.FntHHNEW(mail); PostOffice.ParseMail(tTmp, mail); Decode(mail, tTmp, res); 8 8#Syntax10.Scn.FntNEW(e); e.mode := FoldElems.colLeft; e.W := FoldElems.elemW; e.H := FoldElems.elemH; e.handle := FoldElems.FoldHandler; e.visible := TRUE; NEW(e.hidden); Texts.OpenBuf(e.hidden); Texts.Copy(w.buf, e.hidden); Texts.OpenBuf(w.buf); Texts.WriteElem(w, e); Texts.WriteString(w, "forwarded message"); NEW(e); e.mode := FoldElems.colRight; e.W := FoldElems.elemW; e.H := FoldElems.elemH; e.handle := FoldElems.FoldHandler; e.visible := TRUE; Texts.WriteElem(w, e); 8/ VAR st: String; tTmp: Texts.Text; r: Texts.Reader; charset: CharsetProc; ch: CHAR; e: FoldElems.Elem; mail: PostOffice.Mail; res: INTEGER; BEGIN Texts.OpenBuf(w.buf); Insert empty line before plain text tTmp := TextFrames.Text(""); st := GetEncoding(hdr); charset := GetCharsetProc(hdr); IF (st # NIL) & (st^ = "QUOTED-PRINTABLE") THEN ProcessQuotedPrintable(tIn, begin, end, tTmp, charset) ELSE Copy text END; Parse content of nested message Texts.Save(tTmp, 0, tTmp.len, w.buf); IF res = notExact THEN Out.String("MIME: Warning! Could not find exact handler for content in message$") ELSIF res = unknownContent THEN Out.String("MIME: Error! Could not find handler for content in message$") END; Insert Fold Elems Texts.Append(tOut, w.buf) END MessageRfc822; 8  Su8Syntax10.Scn.Fnt'^8FoldElemsNew#Syntax10.Scn.FnttTmp := TextFrames.Text(filename); NEW(buf); Texts.OpenBuf(buf); Texts.Save(tTmp, 0, tTmp.len, buf); Texts.Append(tOut, buf); %8< VAR f: Files.File; filename: ARRAY 256 OF CHAR; ok: BOOLEAN; res: INTEGER; tTmp: Texts.Text; buf: Texts.Buffer; BEGIN CreateTempFile(f, filename); AsciiCoder.Decode(tIn, begin, f, ok); IF ~ok THEN Out.String("MIME: Decoding error. Ascii-encoded mail could not be fully decoded.$") END; Append contents of 'filename' to tOut Files.Delete(filename, res); ASSERT(res = 0) END XOberon; 8  S]8#Syntax10.Scn.Fnt BEGIN Out.String("MIME: Handling audio/* as application/octet-stream$"); AppOctStream(hdr, tIn, begin, end, tOut); END Audio; 8  S]8#Syntax10.Scn.Fnt BEGIN Out.String("MIME: Handling video/* as application/octet-stream$"); AppOctStream(hdr, tIn, begin, end, tOut); END Video; 8  SV8#Syntax10.Scn.Fnt BEGIN Out.String("MIME: Handling unknown type as application/octet-stream$"); AppOctStream(hdr, tIn, begin, end, tOut); END Unknown; 8468#Syntax10.Scn.Fnt VAR cur: CID; BEGIN cur := cid; WHILE (cur # NIL) & (cur.id^ # id) DO cur := cur.next END; IF cur # NIL THEN RETURN cur.e ELSE RETURN NIL END END GetCID; 8J8#Syntax10.Scn.Fnt VAR e: PElems.Elem; BEGIN e := GetCID(act.url.path); IF e # NIL THEN elem(PElems.Elem).pic := e.pic ELSE Out.String("MIME: Request for CID '"); Out.String(act.url.path); Out.String("' could not be satisfied.$") END END Open; 8I 88H8r8#Syntax10.Scn.FntKK BEGIN Texts.Write(w, "="); Texts.Write(w, NL); lLength := 0 END SoftLB; 8h8#Syntax10.Scn.Fntvv BEGIN IF lLength + 4 > 76 THEN SoftLB(w) END; Texts.Write(w, "="); IF ORD(ch) DIV 16 > 9 THEN Texts.Write(w, CHR(ORD("A") + (ORD(ch) DIV 16) - 10)) ELSE Texts.Write(w, CHR(ORD("0") + (ORD(ch) DIV 16))) END; IF ORD(ch) MOD 16 > 9 THEN Texts.Write(w, CHR(ORD("A") + (ORD(ch) MOD 16) - 10)) ELSE Texts.Write(w, CHR(ORD("0") + (ORD(ch) MOD 16))) END END Enc; 88#Syntax10.Scn.FntTexts.WriteString(w, "MIME-Version: 1.0"); Texts.WriteLn(w); Texts.WriteString(w, "Content-Type: text/plain; charset=iso-8859-1"); Texts.WriteLn(w); Texts.WriteString(w, "Content-Transfer-Encoding: quoted-printable"); Texts.WriteLn(w); Texts.WriteLn(w);  88'8#Syntax10.Scn.Fnt VAR hn: ARRAY 128 OF CHAR; adr: String; dummy: INTEGER; BEGIN hn := ""; TCP.GetHostName(hn, dummy); IntToStr(anonFileNr, adr); INC(anonFileNr); NEW(id, Strings.Length(hn) + Strings.Length(adr^) + 1); COPY(hn, id^); Strings.Append(adr^, id^) END CreateCID; 8;8#Syntax10.Scn.FntMM VAR buf: ARRAY 64 OF CHAR; c: CID; BEGIN WITH e: PElems.Elem DO buf := ' '; Files.WriteBytes(r, buf, 3) ELSE oldHandler(r, e) END END HandlePics; 88#Syntax10.Scn.Fnt CONST A = 16807; M = 2147483647; Q = M DIV A; R = M MOD A; VAR gamma: LONGINT; BEGIN gamma := A * (z MOD Q) - R * (z DIV Q); IF gamma > 0 THEN z := gamma ELSE z := gamma + M END; RETURN z * (1.0 / M) END Uniform; 88#Syntax10.Scn.Fnt)) BEGIN z := Oberon.Time () END InitSeed; 8&8#Syntax10.Scn.Fnt VAR st: String; x: INTEGER; BEGIN NEW(st, 20); COPY("obrnrlz", st^); FOR x := 7 TO 16 DO IF Uniform() >= 0.5 THEN st^[x] := CHR(ENTIER(Uniform() * 26) + 65) ELSE st^[x] := CHR(ENTIER(Uniform() * 26) + 97) END; END; RETURN st END GetRandomBound; 8<8#Syntax10.Scn.Fnt VAR filename, filename2: ARRAY 128 OF CHAR; f, f2: Files.File; r: Files.Rider; tEnc: Texts.Text; bound: String; success: BOOLEAN; res: INTEGER; BEGIN Texts.WriteString(w, 'Content-Type: multipart/related; boundary="'); bound := GetRandomBound(); Texts.WriteString(w, bound^); Texts.WriteString(w, '"'); Texts.WriteLn(w); Texts.WriteLn(w); Strings.Insert("--", 0, bound^); Texts.WriteString(w, bound^); Texts.WriteLn(w); Texts.WriteString(w, "Content-Type: text/html"); Texts.WriteLn(w); Texts.WriteLn(w); CreateTempFile(f, filename); Files.Set(r, f, 0); oldHandler := HTML.handleElem; HTML.handleElem := HandlePics; HTML.StoreDoc(r, t); HTML.handleElem := oldHandler; Files.Close(f); Files.Set(r, f, 0); tEnc := TextFrames.Text(filename); Texts.Save(tEnc, 0, tEnc.len, w.buf); Files.Delete(filename, res); ASSERT(res = 0); Texts.WriteLn(w); WHILE cid # NIL DO Texts.WriteLn(w); Texts.WriteString(w, bound^); Texts.WriteLn(w); Texts.WriteString(w, "Content-Type: image/gif"); Texts.WriteLn(w); Texts.WriteString(w, "Content-ID: <"); Texts.WriteString(w, cid.id^); Texts.WriteString(w, ">"); Texts.WriteLn(w); Texts.WriteString(w, "Content-Transfer-Encoding: base64"); Texts.WriteLn(w); Texts.WriteLn(w); CreateTempFile(f, filename); GIF.Save(cid.e.pic, f, 0, success); ASSERT(success); Files.Close(f); CreateTempFile(f2, filename2); Web.EncodeFile("Base64", f, f2); Files.Close(f2); tEnc := TextFrames.Text(filename2); Texts.Save(tEnc, 0, tEnc.len, w.buf); Files.Delete(filename, res); ASSERT(res = 0); Files.Delete(filename2, res); ASSERT(res = 0); Texts.WriteLn(w); cid := cid.next END; Texts.WriteString(w, bound^); Texts.WriteString(w, "--"); Texts.WriteLn(w) END EncodeHTML; 8?8#Syntax10.Scn.Fnt   VAR f: Files.File; filename: ARRAY 256 OF CHAR; tOut: Texts.Text; tmp: String; res: INTEGER; BEGIN Texts.WriteString(w, "Content-Type: application/x-oberon"); Texts.WriteLn(w); Texts.WriteLn(w); COPY("tmpCode", filename); IntToStr(anonFileNr, tmp); Strings.Append(tmp^, filename); INC(anonFileNr); Texts.Close(t, filename); f := Files.Old(filename); tOut := TextFrames.Text(""); AsciiCoder.Code(f, tOut); Texts.Save(tOut, 0, tOut.len, w.buf); Files.Delete(filename, res); ASSERT(res = 0) END EncodeXOberon; 8 28 Syntax10.Scn.Fnt8FoldElemsNewCSyntax10.Scn.FntSyntax10b.Scn.Fnt! VAR pos, i: LONGINT; ch: CHAR; buffer: ARRAY 512 OF CHAR; st: String; BEGIN pos := PostOffice.SearchPatt2(t, field, 0); i := 0; IF pos = - 1 THEN NEW(st, 1); st^[0] := 0X; RETURN st END; Texts.OpenReader(r, t, pos); Texts.Read(r, ch); WHILE ~r.eot & ((ch = " ") OR (ch = TAB)) DO Texts.Read(r, ch) END; WHILE ~r.eot & (ch # NL) DO buffer[i] := ch; INC(i); Texts.Read(r, ch) END; buffer[i] := 0X; INC(i); NEW(st, i); COPY(buffer, st^); RETURN st END Get; 8Syntax10i.Scn.Fnt;8#Syntax10.Scn.FntNormal message"!;88Syntax10.Scn.Fnt78FoldElemsNew#Syntax10.Scn.FntCreateTempFile(f, filename); 8?IF att # NIL THEN Texts.WriteString(w, "MIME-Version: 1.0"); Texts.WriteLn(w); Texts.WriteString(w, 'Content-Type: multipart/mixed; boundary="'); bound := GetRandomBound(); Texts.WriteString(w, bound^); Texts.WriteString(w, '"'); Texts.WriteLn(w); Texts.WriteLn(w); Strings.Insert("--", 0, bound^); Texts.WriteString(w, bound^); Texts.WriteLn(w); Texts.Insert(tOut, 0, w.buf); FOR i := 0 TO SHORT(LEN(att^) - 1) DO Texts.WriteString(w, bound^); Texts.WriteLn(w); Texts.WriteString(w, "Content-Type: application/octet-stream"); Texts.WriteLn(w); Texts.WriteString(w, "Content-Transfer-Encoding: base64"); Texts.WriteLn(w); Texts.WriteString(w, 'Content-Disposition: attachment; filename="'); Texts.WriteString(w, att[i]^); Texts.WriteString(w, '"'); Texts.WriteLn(w); Texts.WriteLn(w); Create destination file Web.EncodeFile("Base64", Files.Old(att[i]^), f); tTmp := TextFrames.Text(filename); Texts.Save(tTmp, 0, tTmp.len, w.buf); Files.Delete(filename, res); ASSERT(res = 0); Texts.WriteLn(w) END; Texts.WriteString(w, bound^); Texts.WriteString(w, "--"); Texts.WriteLn(w) END; Texts.Append(tOut, w.buf);  88#Syntax10.Scn.Fnt  Texts.WriteString(w, "To: "); Texts.WriteString(w, to^); Texts.WriteLn(w); Texts.WriteString(w, "Subject: "); Texts.WriteString(w, subject^); Texts.WriteLn(w); Texts.WriteString(w, "Cc: "); Texts.WriteString(w, cc^); Texts.WriteLn(w); Texts.Insert(tOut, 0, w.buf); &8 VAR tOut, tTmp: Texts.Text; lvl, i, res: INTEGER; bound, subject, to, cc: String; r: Texts.Reader; f: Files.File; filename: ARRAY 64 OF CHAR; PROCEDURE Get (t: Texts.Text; field: ARRAY OF CHAR): String;  BEGIN cid := NIL; (* in case we have old cids lying around from decoding *) Texts.OpenBuf(w.buf); tOut := TextFrames.Text(""); subject := Get(t, "Subject:"); cc := Get(t, "cc:"); to := Get(t, "To:"); Texts.OpenReader(r, t, 0); Texts.ReadElem(r); Texts.Delete(t, 0, Texts.Pos(r)); lvl := GetEncodingRequirement(t); CASE lvl OF 0: (* Texts.WriteLn(w); Texts.Save(t, 0, t.len, w.buf); *) EncodeQuotedPrintable(t, w); (* avoid problems with mailtools not managing long lines *) | 1: EncodeQuotedPrintable(t, w); | 2: Texts.WriteString(w, "MIME-Version: 1.0"); Texts.WriteLn(w); Texts.WriteString(w, 'Content-Type: multipart/alternative; boundary="'); bound := GetRandomBound(); Texts.WriteString(w, bound^); Texts.WriteString(w, '"'); Texts.WriteLn(w); Texts.WriteLn(w); Strings.Insert("--", 0, bound^); Texts.WriteString(w, bound^); Texts.WriteLn(w); EncodeQuotedPrintable(t, w); Texts.WriteString(w, bound^); Texts.WriteLn(w); EncodeHTML(t, w); Texts.WriteString(w, bound^); Texts.WriteLn(w); EncodeXOberon(t, w); Texts.WriteString(w, bound^); Texts.WriteString(w, "--"); Texts.WriteLn(w) END; Texts.Append(tOut, w.buf);  Attachments Insert To, Subject & Cc at top of mail t := tOut END Encode; 8888M8 h-MODULE MIME;   IMPORT AsciiCoder, Display, FoldElems, Fonts, HTML, Input, Modules, PostOffice, PopupElems, Strings, TextFrames, Texts, Files, Web, Out, Oberon, PElems, Pictures, TCP, GIF; CONST (** decoding results *) ok* = 0; notExact* = 1; unknownContent* = 2; (** encoding requirements **) none* = 0; (* Plain ASCII without any font styles *) quoted* = 1; (* Special characters needed, using quoted printable and iso8859-1 *) full* = 2; (* Pictures / other elements are in text; use html and ascii-coded transfer *) maxCharSets = 10; NL = 0DX; TAB = 09X; unit = LONG(TextFrames.Unit); middle = 1; TYPE String = PostOffice.String; ContentHandler* = PROCEDURE (hdr: PostOffice.Header; tIn: Texts.Text; start, end: LONGINT; tOut: Texts.Text); CharsetProc* = PROCEDURE (ch: CHAR): CHAR; Charset* = RECORD name*: ARRAY 32 OF CHAR; proc*: CharsetProc END; Content = POINTER TO ContentDesc; ContentDesc = RECORD type: String; handler: ContentHandler; next: Content END; Boundary* = POINTER TO BoundaryDesc; BoundaryDesc* = RECORD str: String; pos-, end-: LONGINT; (* start of boundary, end of header *) next-: Boundary; header-: PostOffice.Header END; InsertTaskMarker = POINTER TO InsertTaskMarkerDesc; InsertTaskMarkerDesc = RECORD (Texts.ElemDesc) END; InsertTask = POINTER TO InsertTaskDesc; InsertTaskDesc = RECORD (Oberon.TaskDesc) t: Texts.Text; wTxt: Web.Text; filename: ARRAY 32 OF CHAR; marker: InsertTaskMarker END; CID = POINTER TO CIDDesc; CIDDesc = RECORD id: String; e: PElems.Elem; next: CID END; CIDScheme* = POINTER TO CIDSchemeDesc; CIDSchemeDesc* = RECORD (Web.LoaderDesc) END; AttachElem* = POINTER TO AttachElemDesc; AttachElemDesc* = RECORD (PopupElems.ElemDesc) data: Texts.Text END; VAR cHandler: Content; cid: CID; anonFileNr: INTEGER; charsetTbl: ARRAY maxCharSets OF Charset; nrCharSets: INTEGER; z: LONGINT; oldHandler: HTML.ElemHandle; w: Texts.Writer; ascii*: BOOLEAN; PROCEDURE IntToStr (x: LONGINT; VAR str: String);  PROCEDURE CreateTempFile (VAR f: Files.File; VAR filename: ARRAY OF CHAR);  (* --- Content-Type handling ---*) PROCEDURE IsMime* (mail: PostOffice.Mail): BOOLEAN;  PROCEDURE GetContentType* (hdr: PostOffice.Header): String;  PROCEDURE GetContentSubType* (hdr: PostOffice.Header): String;  PROCEDURE GetCompleteContentType* (hdr: PostOffice.Header): String;  PROCEDURE GetEncoding (hdr: PostOffice.Header): String;  (* --- Content Handlers --- *) PROCEDURE AddContentHandler* (contentType: ARRAY OF CHAR; handler: ContentHandler);  PROCEDURE RemoveContentHandler* (contentType: ARRAY OF CHAR);  PROCEDURE GetContentHandler* (contentType: ARRAY OF CHAR; VAR handler: ContentHandler; VAR exact: BOOLEAN);  (* --- Character sets --- *) PROCEDURE Ascii (c: CHAR): CHAR;  PROCEDURE Iso88591 (c: CHAR): CHAR;  (* Source: Informatik-Handbuch, Rechenberg / Pomberger, page 156 *) BEGIN CASE c OF 0C4X: RETURN CHR(128); (* *) | 0D6X: RETURN CHR(129); (* *) | 0DCX: RETURN CHR(130); (* *) | 0E4X: RETURN CHR(131); (* *) | 0F6X: RETURN CHR(132); (* *) | 0FCX: RETURN CHR(133); (* *) | 0C0X..0C3X, 0C5X: RETURN "A"; | 0C7X: RETURN "C"; | 0C8X..0CBX: RETURN "E"; | 0CCX..0CFX: RETURN "I"; | 0D0X: RETURN "D"; | 0D1X: RETURN "N"; | 0D2X..0D5X: RETURN "O"; | 0D9X..0DBX: RETURN "U"; | 0DFX: RETURN CHR(223); | 0E0X: RETURN CHR(139); (* *) | 0E1X: RETURN CHR(148); (* *) | 0E2X: RETURN CHR(134); (* *) | 0E3X, 0E5X: RETURN "a"; | 0E7X: RETURN CHR(147); (* *) | 0E8X: RETURN CHR(140); (* *) | 0E9X: RETURN CHR(144); (* *) | 0EAX: RETURN CHR(135); (* *) | 0EBX: RETURN CHR(145); (* *) | 0ECX: RETURN CHR(141); (* *) | 0EDX: RETURN "i"; | 0EEX: RETURN CHR(136); (* *) | 0EFX: RETURN CHR(146); (* *) | 0F1X: RETURN CHR(149); (* *) | 0F2X: RETURN CHR(142); (* *) | 0F3X: RETURN "o"; | 0F4X: RETURN CHR(137); (* *) | 0F5X: RETURN "o"; | 0F9X: RETURN CHR(143); (* *) | 0FAX: RETURN "u"; | 0FBX: RETURN CHR(138) (* *) ELSE RETURN c END END Iso88591;  PROCEDURE GetCharsetProc* (hdr: PostOffice.Header): CharsetProc;  PROCEDURE AddCharsetProc* (cs: Charset);  (* --- Content type handlers --- *) PROCEDURE StoreAscii (t: Texts.Text; name: ARRAY OF CHAR);  PROCEDURE Decode* (mail: PostOffice.Mail; VAR t: Texts.Text; VAR res: INTEGER);  PROCEDURE IsHex (c: CHAR): BOOLEAN;  PROCEDURE HexVal (c: CHAR): INTEGER;  PROCEDURE ProcessQuotedPrintable (t: Texts.Text; begin, end: LONGINT; tOut: Texts.Text; charset: CharsetProc);  PROCEDURE TextPlain (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  (* HTML - Hack *) PROCEDURE HandleMarker (E: Texts.Elem; VAR msg: Texts.ElemMsg);  PROCEDURE InsertMarker (t: Texts.Text; pos: LONGINT): InsertTaskMarker;  PROCEDURE FindElem (t: Texts.Text; e: Texts.Elem): LONGINT;  PROCEDURE HandleInsert;  PROCEDURE TextHTML (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  VAR st, tmp: String; wTxt: Web.Text; e: InsertTaskMarker; t: InsertTask; charset: CharsetProc; r: Texts.Reader; ch: CHAR; BEGIN Texts.OpenBuf(w.buf); charset := GetCharsetProc(hdr); Insert empty line before HTML text Create a new, empty Web.Text st := GetEncoding(hdr); IF (st # NIL) & (st^ = "QUOTED-PRINTABLE") THEN ProcessQuotedPrintable(tIn, begin, end, wTxt, charset) ELSE Copy text END; e := InsertMarker(tOut, tOut.len); NEW(t); t.t := tOut; t.wTxt := wTxt; t.marker := e; t.handle := HandleInsert; t.safe := FALSE; t.filename := "$mimeTemp"; IntToStr(anonFileNr, tmp); Strings.Append(tmp^, t.filename); Strings.Append(".html", t.filename); INC(anonFileNr); NEW(tmp, 128); COPY("file:///", tmp^); Strings.Append(t.filename, tmp^); StoreAscii(wTxt, t.filename); Web.OpenUrl("", tmp^, wTxt, NIL, NIL, Web.show); (* Will return immediately, installing a task for loading *) Oberon.Install(t); Add delimiter END TextHTML;  (* Multipart *) PROCEDURE GetBoundaries* (header: PostOffice.Header; t: Texts.Text; VAR bound: Boundary);  PROCEDURE MultipartMixed (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  VAR bound, cur: Boundary; ct: String; handler: ContentHandler; exact: BOOLEAN; BEGIN GetBoundaries(hdr, tIn, bound); ASSERT(bound # NIL); cur := bound; WHILE (cur # NIL) & (cur.header # NIL) DO ct := GetCompleteContentType(cur.header); ASSERT(ct # NIL); GetContentHandler(ct^, handler, exact); IF ~exact THEN Out.String("MIME: Warning could not get exact handler for "); Out.String(ct^); Out.Ln END; IF (handler # NIL) THEN handler(cur.header, tIn, cur.end, cur.next.pos - 1, tOut) ELSE Out.String("MIME: Error! Could not get any handler for "); Out.String(ct^); Out.Ln; Out.String("MIME: Part of the message will not be visible.$") END; cur := cur.next END END MultipartMixed;  PROCEDURE MultipartAlternative (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  (* Images / Attachments *) PROCEDURE PopupHandle (E: Texts.Elem; VAR msg: Texts.ElemMsg);  PROCEDURE InsertAttachmentPopup (fn: String; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  PROCEDURE Image (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  PROCEDURE AppOctStream (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  PROCEDURE MessageRfc822 (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  PROCEDURE XOberon (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  PROCEDURE Audio (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  PROCEDURE Video (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  PROCEDURE Unknown (hdr: PostOffice.Header; tIn: Texts.Text; begin, end: LONGINT; tOut: Texts.Text);  (* CID - Handling *) PROCEDURE GetCID (id: ARRAY OF CHAR): PElems.Elem;  PROCEDURE (s: CIDScheme) Open* (act: Web.UrlStack; txt: Web.Text; elem: Texts.Elem; useCache: BOOLEAN);  (* --- Encoding ---*) (* Strategy: Enocde t first, then wrap multipart around, if neccessary *) PROCEDURE GetEncodingRequirement* (t: Texts.Text): INTEGER;  VAR lvl: INTEGER; r: Texts.Reader; ch: CHAR; BEGIN IF ascii THEN ascii := FALSE; RETURN 0 END; lvl := none; Texts.OpenReader(r, t, 0); Texts.Read(r, ch); WHILE ~r.eot & (lvl < full) DO IF ORD(ch) > 127 THEN lvl := quoted END; IF ch = Texts.ElemChar THEN lvl := full END; IF (r.fnt # Fonts.Default) OR (r.col # 15) THEN lvl := full END; Texts.Read(r, ch) END; RETURN lvl END GetEncodingRequirement;  PROCEDURE EncodeQuotedPrintable (t: Texts.Text; VAR w: Texts.Writer);  VAR r: Texts.Reader; ch: CHAR; id: Texts.IdentifyMsg; lLength: INTEGER; PROCEDURE SoftLB (VAR w: Texts.Writer);  PROCEDURE Enc (ch: CHAR);  BEGIN Write header Texts.OpenReader(r, t, 0); Texts.Read(r, ch); lLength := 0; WHILE ~r.eot DO IF (ch = Texts.ElemChar) & (r.elem # NIL) THEN (* ch = Texts.ElemChar does not guarantee that r.elem contains in fact an element *) r.elem.handle(r.elem, id); IF lLength + 3 + Strings.Length(id.mod) > 76 THEN SoftLB(w) END; Texts.Write(w, "["); Texts.WriteString(w, id.mod); Texts.Write(w, "]"); lLength := lLength + 2 + Strings.Length(id.mod) ELSIF (ORD(ch) > 127) THEN CASE ch OF "": Enc(0C4X); |"": Enc(0D6X); |"": Enc(0DCX); |"": Enc(0E4X); |"": Enc(0F6X); |"": Enc(0FCX); |"": Enc(0E2X); |"": Enc(0EAX); |"": Enc(0EEX); |"": Enc(0F4X); |"": Enc(0FBX); |"": Enc(0E0X); |"": Enc(0E9X); |"": Enc(0ECX); |"": Enc(0F2X); |"": Enc(0F9X); |"": Enc(0E9X); |"": Enc(0EBX); |"": Enc(0EFX); |"": Enc(0E7X); |"": Enc(0E1X); |"": Enc(0F1X); |CHR(223): Enc(0DFX) ELSE Enc(ch) END ELSIF ch = "=" THEN Enc(ch) ELSE Texts.Write(w, ch); IF (ch = NL) THEN lLength := 0 ELSE INC(lLength) END; IF lLength > 75 THEN SoftLB(w) END END; Texts.Read(r, ch) END; Texts.WriteLn(w) END EncodeQuotedPrintable;  PROCEDURE CreateCID (VAR id: String);  PROCEDURE HandlePics (VAR r: Files.Rider; e: Texts.Elem);  PROCEDURE Uniform (): REAL;  PROCEDURE InitSeed;  PROCEDURE GetRandomBound (): String;  PROCEDURE EncodeHTML (t: Texts.Text; VAR w: Texts.Writer);  PROCEDURE EncodeXOberon (t: Texts.Text; VAR w: Texts.Writer);  PROCEDURE Encode* (VAR t: Texts.Text; att: PostOffice.StringList);  PROCEDURE Init;  VAR s: CIDScheme; cs: Charset; BEGIN InitSeed; cHandler := NIL; AddContentHandler("text/*", TextPlain); AddContentHandler("text/html", TextHTML); AddContentHandler("text/plain", TextPlain); AddContentHandler("multipart/mixed", MultipartMixed); AddContentHandler("multipart/alternative", MultipartAlternative); AddContentHandler("multipart/related", MultipartMixed); AddContentHandler("image/*", Image); AddContentHandler("application/x-oberon", XOberon); AddContentHandler("application/octet-stream", AppOctStream); AddContentHandler("message/delivery-status", TextPlain); AddContentHandler("message/rfc822", MessageRfc822); AddContentHandler("audio/*", Audio); AddContentHandler("video/*", Video); cs.name := "US-ASCII"; cs.proc := Ascii; AddCharsetProc(cs); cs.name := "ISO-8859-1"; cs.proc := Iso88591; AddCharsetProc(cs); NEW(s); s.scheme := "cid"; Web.InstallLoader(s) END Init;  BEGIN Init; anonFileNr := 0; cid := NIL; Texts.OpenWriter(w); ascii := FALSE; END MIME.