Oberon/A2/Oberon.MIME.Mod

(* ETH Oberon, Copyright 2001 ETH Zuerich Institut fuer Computersysteme, ETH Zentrum, CH-8092 Zuerich. Refer to the "General ETH Oberon System Source License" contract available at&#58; http&#58;//www.oberon.ethz.ch/ *) MODULE MIME IN Oberon; (** portable *) &#9;IMPORT Streams, TextStreams, Files, Dates, Strings, Fonts, Texts, Display, Display3, &#9;&#9;Objects, Attributes, Gadgets, BasicFigures, Out, Oberon; &#9;CONST &#9;&#9;BufLen &#61; 1024; &#9;&#9;MaxLine &#61; BufLen-1; &#9;&#9;MaxSMTPLine &#61; 1000; &#9;&#9;MimeVersion* &#61; "Mime-Version&#58; 1.0"; &#9;&#9;TextMime* &#61; "text/plain"; ISOVer* &#61; "ISO-8859-1"; &#9;&#9;OberonMime* &#61; "application/compressed/oberon"; &#9;&#9;EncAuto* &#61; -1; EncBin* &#61; 0; Enc8Bit* &#61; 1; Enc7Bit* &#61; 2; EncQuoted* &#61; 3; EncBase64* &#61; 4; EncAsciiCoder* &#61; 5; &#9;&#9;EncAsciiCoderC* &#61; 6; EncAsciiCoderCPlain* &#61; 7; &#9;&#9;ContEncQuoted* &#61; "Content-Transfer-Encoding&#58; quoted-printable"; (* EncQuoted *) &#9;&#9;ContEnc7Bit* &#61; "Content-Transfer-Encoding&#58; 7bit"; (* Enc7Bit *) &#9;&#9;ContEnc8Bit* &#61; "Content-Transfer-Encoding&#58; 8bit"; (* EncBin, Enc8Bit*) &#9;&#9;ContEncBase64* &#61; "Content-Transfer-Encoding&#58; Base64"; (* EncBase64 *) &#9;TYPE &#9;&#9;OpenString* &#61; POINTER TO ARRAY OF CHAR; &#9;&#9;Header* &#61; POINTER TO HeaderDesc; &#9;&#9;HeaderDesc* &#61; RECORD &#9;&#9;&#9;fields*&#58; OpenString &#9;&#9;END; &#9;(** A list of the mime content-types supported. &#9;&#9;&#9;The mime-types supported by Oberon are described in the "MIME" section of oberon.ini. &#9;&#9;&#9;The syntax of a mime entry is&#58; &#9;&#9;&#9;&#9;mimedef &#61; mime "&#61;" &#91; suffix &#91; prefix &#93; &#93; . &#9;&#9;&#9;&#9;mime	The mime type, e.g&#58; "text/html", "image/gif", ... &#9;&#9;&#9;&#9;suffix	Suffix to be used for temporary files. &#9;&#9;&#9;&#9;prefix	Prefix to be used for temporary files . &#9;&#9;&#9;&#9;&#9;e.g. "c&#58;/temp/" writes the temporary files in the c&#58;/temp directory *) &#9;&#9;ContentType* &#61; POINTER TO ContentTypeDesc; &#9;&#9;ContentTypeDesc* &#61; RECORD &#9;&#9;&#9;typ*&#58; ARRAY 32 OF CHAR; &#9;&#9;&#9;subTyp*&#58; ARRAY 64 OF CHAR; &#9;&#9;&#9;suffix*&#58; ARRAY 8 OF CHAR; &#9;&#9;&#9;prefix*&#58; ARRAY 128 OF CHAR; &#9;&#9;&#9;support*&#58; BOOLEAN; &#9;&#9;&#9;next&#58; ContentType &#9;&#9;END; &#9;&#9;Content* &#61; POINTER TO ContentDesc; &#9;&#9;ContentDesc* &#61; RECORD &#9;&#9;&#9;h&#58; Header; &#9;&#9;&#9;pos, len*&#58; LONGINT; &#9;&#9;&#9;typ*&#58; ContentType; &#9;&#9;&#9;encoding*&#58; LONGINT (* 0&#58; binary; 1&#58; 8 bit; 2&#58; 7 bit; 3&#58; 7 bit, quoted; 4&#58; Base64; 5&#58; AsciiCoder; 6&#58; AsciiCoder % *) &#9;&#9;END; &#9;&#9;Part &#61; POINTER TO PartDesc; &#9;&#9;PartDesc &#61; RECORD &#9;&#9;&#9;name&#58; ARRAY 64 OF CHAR; &#9;&#9;&#9;no&#58; LONGINT; &#9;&#9;&#9;next&#58; Part &#9;&#9;END; &#9;VAR &#9;&#9;contTypes*&#58; ContentType; (** Root of the content-typ list. *) &#9;&#9;textCont*&#58; Content; &#9;PROCEDURE ReadHeader*(S, echo&#58; Streams.Stream; VAR h&#58; Header; VAR len&#58; LONGINT); &#9;&#9;VAR &#9;&#9;&#9;buf&#58; OpenString; &#9;&#9;&#9;bufLen, bufPos, begPos, begLen&#58; LONGINT; &#9;&#9;&#9;ch, eolc&#58; CHAR; &#9;&#9;&#9;end, eol, field, anyField, white&#58; BOOLEAN; &#9;&#9;PROCEDURE Read; &#9;&#9;BEGIN &#9;&#9;&#9;IF (ch # Strings.LF) &#38; (echo # NIL) THEN &#9;&#9;&#9;&#9;TextStreams.Write(echo, ch) &#9;&#9;&#9;END; &#9;&#9;&#9;Streams.Read(S, ch); INC(len) &#9;&#9;END Read; &#9;BEGIN &#9;&#9;len &#58;&#61; 0; begLen &#58;&#61; 0; anyField &#58;&#61; FALSE; &#9;&#9;NEW(h); bufLen &#58;&#61; 1024; NEW(h.fields, bufLen); bufPos &#58;&#61; 0; &#9;&#9;ch &#58;&#61; Strings.LF; Read; eol &#58;&#61; FALSE; &#9;&#9;REPEAT &#9;&#9;&#9;end &#58;&#61; TRUE; field &#58;&#61; FALSE; white &#58;&#61; FALSE; begPos &#58;&#61; bufPos; &#9;&#9;&#9;WHILE &#126;S.eos &#38; &#126;eol DO &#9;&#9;&#9;&#9;IF bufPos &#62;&#61; (bufLen-3) THEN &#9;&#9;&#9;&#9;&#9;h.fields&#91;bufPos&#93; &#58;&#61; 0X; &#9;&#9;&#9;&#9;&#9;INC(bufLen, 1024); NEW(buf, bufLen); &#9;&#9;&#9;&#9;&#9;COPY(h.fields^, buf^); h.fields &#58;&#61; buf &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF ((ch &#61; "&#58;") OR (ch &#61; "+")) &#38; &#126;field THEN	(* "+OK xxx octets" workaround *) &#9;&#9;&#9;&#9;&#9;end &#58;&#61; FALSE; field &#58;&#61; &#126;white; anyField &#58;&#61; anyField OR field &#9;&#9;&#9;&#9;ELSIF ch &#60;&#61; " " THEN &#9;&#9;&#9;&#9;&#9;ch &#58;&#61; " "; white &#58;&#61; TRUE &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;h.fields&#91;bufPos&#93; &#58;&#61; ch; INC(bufPos); &#9;&#9;&#9;&#9;Read; &#9;&#9;&#9;&#9;IF (ch &#61; Strings.CR) OR (ch &#61; Strings.LF) THEN &#9;&#9;&#9;&#9;&#9;eolc &#58;&#61; ch; Read; &#9;&#9;&#9;&#9;&#9;IF eolc # Strings.LF THEN &#9;&#9;&#9;&#9;&#9;&#9;IF ch # Strings.LF THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;eolc &#58;&#61; Strings.CR &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;Read; eolc &#58;&#61; Strings.LF &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;IF ch &#62; " " THEN &#9;&#9;&#9;&#9;&#9;&#9;eol &#58;&#61; TRUE &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;WHILE &#126;S.eos &#38; (ch &#60;&#61; " ") &#38; (ch # eolc) DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;Read &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;eol &#58;&#61; ch &#61; eolc; &#9;&#9;&#9;&#9;&#9;&#9;IF &#126;eol THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;h.fields&#91;bufPos&#93; &#58;&#61; " "; INC(bufPos) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;eol &#58;&#61; FALSE &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;end &#58;&#61; end OR (bufPos &#60;&#61; begPos) OR (ch &#61; Strings.CR) OR (ch &#61; Strings.LF); &#9;&#9;&#9;IF field THEN &#9;&#9;&#9;&#9;begLen &#58;&#61; len-1; &#9;&#9;&#9;&#9;IF S.eos &#38; &#126;((ch &#61; Strings.CR) OR (ch &#61; Strings.LF)) THEN &#9;&#9;&#9;&#9;&#9;INC(begLen) &#9;&#9;&#9;&#9;ELSIF &#126;S.eos THEN &#9;&#9;&#9;&#9;&#9;DEC(begLen) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;h.fields&#91;bufPos&#93; &#58;&#61; 0X; field &#58;&#61; FALSE; INC(bufPos); &#9;&#9;&#9;eol &#58;&#61; FALSE &#9;&#9;UNTIL end; &#9;&#9;len &#58;&#61; begLen; h.fields&#91;bufPos&#93; &#58;&#61; 0X; &#9;&#9;IF &#126;anyField THEN &#9;&#9;&#9;h.fields&#91;0&#93; &#58;&#61; 0X; h.fields&#91;1&#93; &#58;&#61; 0X &#9;&#9;END &#9;END ReadHeader; &#9;PROCEDURE FindFieldPos*(h&#58; Header; field&#58; ARRAY OF CHAR; VAR pos&#58; LONGINT); &#9;&#9;VAR len, i&#58; LONGINT; &#9;BEGIN &#9;&#9;len &#58;&#61; LEN(h.fields^); &#9;&#9;WHILE pos &#60; len DO &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE (pos &#60; len) &#38; (CAP(field&#91;i&#93;) &#61; CAP(h.fields&#91;pos&#93;)) &#38; (field&#91;i&#93; # 0X) DO &#9;&#9;&#9;&#9;INC(i); INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;IF (field&#91;i&#93; &#61; 0X) &#38; ((h.fields&#91;pos&#93; &#60;&#61; " ") OR (h.fields&#91;pos&#93; &#61; "&#58;")) THEN &#9;&#9;&#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # "&#58;") &#38; (h.fields&#91;pos&#93; # 0X) DO &#9;&#9;&#9;&#9;&#9;INC(pos) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF h.fields&#91;pos&#93; &#61; "&#58;" THEN &#9;&#9;&#9;&#9;&#9;INC(pos); &#9;&#9;&#9;&#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") &#38; (h.fields&#91;pos&#93; # 0X) DO &#9;&#9;&#9;&#9;&#9;&#9;INC(pos) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) DO &#9;&#9;&#9;&#9;&#9;INC(pos) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;pos &#58;&#61; -1 &#9;END FindFieldPos; &#9;PROCEDURE FindField*(h&#58; Header; field&#58; ARRAY OF CHAR)&#58; LONGINT; &#9;&#9;VAR pos&#58; LONGINT; &#9;BEGIN &#9;&#9;pos &#58;&#61; 0; FindFieldPos(h, field, pos); &#9;&#9;RETURN pos &#9;END FindField; &#9;PROCEDURE NextValue*(h&#58; Header; VAR pos&#58; LONGINT); &#9;&#9;VAR len&#58; LONGINT; &#9;BEGIN &#9;&#9;len &#58;&#61; LEN(h.fields^); &#9;&#9;IF (pos &#60; 0) OR (pos &#62;&#61; len) THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; # ";") DO &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;IF h.fields&#91;pos&#93; &#61; ";" THEN &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") DO &#9;&#9;&#9;INC(pos) &#9;&#9;END &#9;END NextValue; &#9;PROCEDURE HexVal(ch&#58; CHAR)&#58; LONGINT; &#9;BEGIN &#9;&#9;IF (ch &#62;&#61; "0") &#38; (ch &#60;&#61; "9") THEN &#9;&#9;&#9;RETURN ORD(ch)-ORD("0") &#9;&#9;ELSIF (ch &#62;&#61; "A") &#38; (ch &#60;&#61; "F") THEN &#9;&#9;&#9;RETURN ORD(ch)-ORD("A")+10 &#9;&#9;ELSIF (ch &#62;&#61; "a") &#38; (ch &#60;&#61; "f") THEN &#9;&#9;&#9;RETURN ORD(ch)-ORD("a")+10 &#9;&#9;END &#9;END HexVal; &#9;PROCEDURE DecodeValue(VAR value&#58; ARRAY OF CHAR); &#9;&#9;VAR &#9;&#9;&#9;i, j, l&#58; LONGINT; &#9;&#9;&#9;quoted&#58; BOOLEAN; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;quoted &#58;&#61; FALSE; &#9;&#9;i &#58;&#61; 0; j &#58;&#61; 0; l &#58;&#61; LEN(value); &#9;&#9;ch &#58;&#61; value&#91;i&#93;; &#9;&#9;WHILE ch # 0X DO &#9;&#9;&#9;ch &#58;&#61; Strings.ISOToOberon&#91;ORD(ch)&#93;; &#9;&#9;&#9;IF ch &#61; "&#61;" THEN &#9;&#9;&#9;&#9;IF &#126;quoted &#38; ((i+14) &#60; l) &#38; (value&#91;i+1&#93; &#61; "?") &#38; (value&#91;i+12&#93; &#61; "?") &#38; (value&#91;i+14&#93; &#61; "?") THEN &#9;&#9;&#9;&#9;&#9;quoted &#58;&#61; TRUE; INC(i, 14) &#9;&#9;&#9;&#9;ELSIF quoted &#38; Strings.IsHexDigit(value&#91;i+1&#93;) &#38; Strings.IsHexDigit(value&#91;i+2&#93;) THEN &#9;&#9;&#9;&#9;&#9;value&#91;j&#93; &#58;&#61; Strings.ISOToOberon&#91;HexVal(value&#91;i+1&#93;)*16+HexVal(value&#91;i+2&#93;)&#93;; INC(j); &#9;&#9;&#9;&#9;&#9;INC(i, 2) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;value&#91;j&#93; &#58;&#61; ch; INC(j) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF (ch &#61; "?") &#38; quoted &#38; (value&#91;i+1&#93; &#61; "&#61;") THEN &#9;&#9;&#9;&#9;quoted &#58;&#61; FALSE; INC(i) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;value&#91;j&#93; &#58;&#61; ch; INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;INC(i); ch &#58;&#61; value&#91;i&#93; &#9;&#9;END; &#9;&#9;value&#91;j&#93; &#58;&#61; 0X &#9;END DecodeValue; &#9;PROCEDURE ExtractValue*(h&#58; Header; pos&#58; LONGINT; VAR val&#58; ARRAY OF CHAR); &#9;&#9;VAR len, i, vlen&#58; LONGINT; &#9;BEGIN &#9;&#9;COPY("", val); &#9;&#9;len &#58;&#61; LEN(h.fields^); &#9;&#9;IF (pos &#60; 0) OR (pos &#62;&#61; len) THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") DO &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;vlen &#58;&#61; LEN(val)-1; i &#58;&#61; 0; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (i &#60; vlen) DO &#9;&#9;&#9;val&#91;i&#93; &#58;&#61; h.fields&#91;pos&#93;; INC(i); INC(pos) &#9;&#9;END; &#9;&#9;val&#91;i&#93; &#58;&#61; 0X; DEC(i); &#9;&#9;WHILE (i &#62; 0) &#38; (val&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;val&#91;i&#93; &#58;&#61; 0X; DEC(i) &#9;&#9;END; &#9;&#9;DecodeValue(val) &#9;END ExtractValue; &#9;PROCEDURE FindParam*(h&#58; Header; pos&#58; LONGINT; param&#58; ARRAY OF CHAR; VAR val&#58; ARRAY OF CHAR); &#9;&#9;VAR len, i, vlen&#58; LONGINT; &#9;BEGIN &#9;&#9;COPY("", val); &#9;&#9;len &#58;&#61; LEN(h.fields^); &#9;&#9;IF (pos &#60; 0) OR (pos &#62;&#61; len) THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") DO &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;vlen &#58;&#61; LEN(val)-1; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) DO &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE (pos &#60; len) &#38; (param&#91;i&#93; # 0X) &#38; (CAP(param&#91;i&#93;) &#61; CAP(h.fields&#91;pos&#93;)) DO &#9;&#9;&#9;&#9;INC(i); INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;IF param&#91;i&#93; &#61; 0X THEN &#9;&#9;&#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;&#9;INC(pos) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF h.fields&#91;pos&#93; &#61; "&#61;" THEN &#9;&#9;&#9;&#9;&#9;INC(pos); &#9;&#9;&#9;&#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;INC(pos) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;IF h.fields&#91;pos&#93; &#61; 022X THEN &#9;&#9;&#9;&#9;&#9;&#9;INC(pos) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;vlen &#58;&#61; LEN(val)-1; i &#58;&#61; 0; &#9;&#9;&#9;&#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; # 022X) &#38; (h.fields&#91;pos&#93; # ";") &#38; (i &#60; vlen) DO &#9;&#9;&#9;&#9;&#9;&#9;val&#91;i&#93; &#58;&#61; h.fields&#91;pos&#93;; INC(i); INC(pos) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;val&#91;i&#93; &#58;&#61; 0X; DEC(i); &#9;&#9;&#9;&#9;&#9;WHILE (i &#62; 0) &#38; (val&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;val&#91;i&#93; &#58;&#61; 0X; DEC(i) &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;END; &#9;&#9;&#9;NextValue(h, pos) &#9;&#9;END &#9;END FindParam; &#9;PROCEDURE ExtractEMail*(h&#58; Header; pos&#58; LONGINT; VAR email&#58; ARRAY OF CHAR); &#9;&#9;VAR &#9;&#9;&#9;len, i, len2&#58; LONGINT; &#9;&#9;&#9;end&#58; CHAR; &#9;BEGIN &#9;&#9;COPY("", email); &#9;&#9;len &#58;&#61; LEN(h.fields^); &#9;&#9;IF (pos &#60; 0) OR (pos &#62;&#61; len) THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; # "@") DO &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;IF h.fields&#91;pos&#93; &#61; "@" THEN &#9;&#9;&#9;WHILE (pos &#62;&#61; 0) &#38; (h.fields&#91;pos&#93; &#62; " ") &#38; (h.fields&#91;pos&#93; # "&#60;") DO &#9;&#9;&#9;&#9;DEC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;IF h.fields&#91;pos&#93; &#60;&#61; " " THEN &#9;&#9;&#9;&#9;INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;IF h.fields&#91;pos&#93; &#61; "&#60;" THEN &#9;&#9;&#9;&#9;INC(pos); end &#58;&#61; "&#62;" &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;end &#58;&#61; 0X &#9;&#9;&#9;END; &#9;&#9;&#9;len2 &#58;&#61; LEN(email)-1; i &#58;&#61; 0; &#9;&#9;&#9;WHILE (i &#60; len2) &#38; (pos &#60; len) &#38; (h.fields&#91;pos&#93; &#62; " ") &#38; (h.fields&#91;pos&#93; # end) DO &#9;&#9;&#9;&#9;email&#91;i&#93; &#58;&#61; h.fields&#91;pos&#93;; INC(i); INC(pos) &#9;&#9;&#9;END; &#9;&#9;&#9;email&#91;i&#93; &#58;&#61; 0X &#9;&#9;END &#9;END ExtractEMail; &#9;PROCEDURE StrToMonth(VAR str&#58; ARRAY OF CHAR)&#58; LONGINT; &#9;BEGIN &#9;&#9;CASE CAP(str&#91;0&#93;) OF &#9;&#9;&#9;"A"&#58; IF (CAP(str&#91;1&#93;) &#61; "P") OR (CAP(str&#91;1&#93;) &#61; "V") THEN (* April, Aprile, Avril *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 4 &#9;&#9;&#9;&#9;&#9;ELSE (* August, Agosto, Aout *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 8 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#124;"D"&#58; RETURN 12 (* December, Dezember, Dicembre, Decembre *) &#9;&#9;&#9;&#124;"F"&#58; RETURN 2 (* February, Febbbraio, Fevrier *) &#9;&#9;&#9;&#124;"G"&#58; IF CAP(str&#91;1&#93;) &#61; "E" THEN (* Gennaiv *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 1 &#9;&#9;&#9;&#9;&#9;ELSE (* Giugno *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 6 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#124;"J"&#58; IF CAP(str&#91;1&#93;) &#61; "A" THEN (* January, Januar, Janvier *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 1 &#9;&#9;&#9;&#9;&#9;ELSIF CAP(str&#91;2&#93;) &#61; "L" THEN (* July, Juli, Juillet *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 7 &#9;&#9;&#9;&#9;&#9;ELSE (* June, Juni, Juin *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 6 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#124;"L"&#58; RETURN 7 (* Luglio *) &#9;&#9;&#9;&#124;"M"&#58; IF CAP(str&#91;2&#93;) &#61; "R" THEN (* March, März, Mars *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 3 &#9;&#9;&#9;&#9;&#9;ELSIF CAP(str&#91;2&#93;) &#61; "Z" THEN (* Mazzo *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 3 &#9;&#9;&#9;&#9;&#9;ELSE (* May, Mai, Maggio *) &#9;&#9;&#9;&#9;&#9;&#9;RETURN 5 &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#124;"N"&#58; RETURN 11(* November, Novembre *) &#9;&#9;&#9;&#124;"O"&#58; RETURN 10 (* October, Oktober, Ottobre, Octobre *) &#9;&#9;&#9;&#124;"S"&#58; RETURN 9 (* September, Seltombre, Septembre *) &#9;&#9;ELSE &#9;&#9;END; &#9;&#9;RETURN 0 &#9;END StrToMonth; &#9;PROCEDURE GMTTime(h, m, s&#58; LONGINT; zone&#58; ARRAY OF CHAR; VAR time, date&#58; LONGINT)&#58; BOOLEAN; &#9;&#9;VAR dH, dM, i&#58; LONGINT; &#9;BEGIN &#9;&#9;IF (zone&#91;0&#93; &#61; "+") OR (zone&#91;0&#93; &#61; "-") THEN &#9;&#9;&#9;IF Strings.Length(zone) &#62; 5 THEN &#9;&#9;&#9;&#9;dH &#58;&#61; 0 &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;Strings.StrToInt(zone, dH) &#9;&#9;&#9;END; &#9;&#9;&#9;IF dH &#60; 0 THEN i &#58;&#61; -1 ELSE i &#58;&#61; 1 END; &#9;&#9;&#9;dM &#58;&#61; i*ABS(dH MOD 100); &#9;&#9;&#9;dH &#58;&#61; i*ABS(dH DIV 100) &#9;&#9;ELSE &#9;&#9;&#9;dH &#58;&#61; 0; dM &#58;&#61; 0; &#9;&#9;&#9;Strings.Upper(zone, zone); &#9;&#9;&#9;IF (zone &#61; "UT") OR (zone &#61; "GMT") OR (zone &#61; "AM") THEN &#9;&#9;&#9;&#9;(* nix *) &#9;&#9;&#9;ELSIF zone &#61; "EST" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -5 &#9;&#9;&#9;ELSIF zone &#61; "EDT" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -4 &#9;&#9;&#9;ELSIF zone &#61; "CST" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -6 &#9;&#9;&#9;ELSIF zone &#61; "CDT" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -5 &#9;&#9;&#9;ELSIF zone &#61; "MST" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -7 &#9;&#9;&#9;ELSIF zone &#61; "MDT" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -6 &#9;&#9;&#9;ELSIF zone &#61; "PST" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -8 &#9;&#9;&#9;ELSIF zone &#61; "PDT" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; -7 &#9;&#9;&#9;ELSIF zone &#61; "MET" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; +2 &#9;&#9;&#9;ELSIF (zone&#91;1&#93; &#60;&#61; " ") &#38; (zone&#91;0&#93; &#62;&#61; "A") &#38; (zone&#91;0&#93; &#60;&#61; "Y") &#38; (zone&#91;0&#93; # "J") THEN (* military *) &#9;&#9;&#9;&#9;IF zone&#91;0&#93; &#62;&#61; "N" THEN &#9;&#9;&#9;&#9;&#9;dH &#58;&#61; ORD(zone&#91;0&#93;)-ORD("N")+1 &#9;&#9;&#9;&#9;ELSIF zone&#91;0&#93; &#60; "J" THEN &#9;&#9;&#9;&#9;&#9;dH &#58;&#61; -(ORD(zone&#91;0&#93;)-ORD("A")+1) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;dH &#58;&#61; -(ORD(zone&#91;0&#93;)-ORD("A")) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSIF zone &#61; "PM" THEN &#9;&#9;&#9;&#9;dH &#58;&#61; +12 &#9;&#9;&#9;ELSE (* local time *) &#9;&#9;&#9;&#9;dH &#58;&#61; Dates.TimeDiff DIV 60; &#9;&#9;&#9;&#9;dM &#58;&#61; Dates.TimeDiff MOD 60 &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;h &#58;&#61; h-dH; m &#58;&#61; m-dM; &#9;&#9;WHILE m &#60; 0 DO &#9;&#9;&#9;DEC(h); INC(m, 60) &#9;&#9;END; &#9;&#9;h &#58;&#61; h+(m DIV 60); m &#58;&#61; m MOD 60; &#9;&#9;date &#58;&#61; Dates.AddDay(date, SHORT(h DIV 24)); h &#58;&#61; h MOD 24; &#9;&#9;time &#58;&#61; h*1000H + m*40H + s; &#9;&#9;RETURN TRUE &#9;END GMTTime; &#9;PROCEDURE GetClock*(VAR time, date&#58; LONGINT); &#9;BEGIN &#9;&#9;Oberon.GetClock(time, date); &#9;&#9;Dates.AddTime(time, date, -Dates.TimeDiff * 60)	(* convert to GMT *) &#9;END GetClock; &#9;PROCEDURE ExtractGMTDate*(h&#58; Header; pos&#58; LONGINT; VAR time, date&#58; LONGINT); &#9;&#9;VAR &#9;&#9;&#9;len, i, day, year, month, hour, min, sec&#58; LONGINT; &#9;&#9;&#9;mo, zone&#58; ARRAY 16 OF CHAR; &#9;&#9;&#9;j&#58; INTEGER; &#9;BEGIN &#9;&#9;time &#58;&#61; 0; date &#58;&#61; 0; &#9;&#9;len &#58;&#61; LEN(h.fields^); &#9;&#9;IF (pos &#60; 0) OR (pos &#62;&#61; len) THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;i &#58;&#61; pos; &#9;&#9;WHILE (i &#60; len) &#38; (h.fields&#91;i&#93; # 0X) &#38; (h.fields&#91;i&#93; # ",") DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;IF h.fields&#91;i&#93; &#61; "," THEN &#9;&#9;&#9;pos &#58;&#61; i+1 &#9;&#9;END; &#9;&#9;(* date *) &#9;&#9;j &#58;&#61; SHORT(pos); Strings.StrToIntPos(h.fields^, day, j); pos &#58;&#61; j; &#9;&#9;WHILE (i &#60; LEN(mo)-1) &#38; (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") DO &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; &#62; " ") DO &#9;&#9;&#9;mo&#91;i&#93; &#58;&#61; h.fields&#91;pos&#93;; INC(i); INC(pos) &#9;&#9;END; &#9;&#9;mo&#91;i&#93; &#58;&#61; 0X; month &#58;&#61; StrToMonth(mo); &#9;&#9;IF (month &#62;&#61; 1) &#38; (month &#60;&#61; 12) THEN &#9;&#9;&#9;j &#58;&#61; SHORT(pos); Strings.StrToIntPos(h.fields^, year, j); &#9;&#9;&#9;IF year &#62;&#61; 1900 THEN &#9;&#9;&#9;&#9;date &#58;&#61; (year-1900)*200H + month*20H + day &#9;&#9;&#9;ELSIF year &#60; 80 THEN &#9;&#9;&#9;&#9;date &#58;&#61; (year+100)*200H + month*20H + day &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;date &#58;&#61; year*200H + month*20H + day &#9;&#9;&#9;END; &#9;&#9;&#9;(* time *) &#9;&#9;&#9;Strings.StrToIntPos(h.fields^, hour, j); &#9;&#9;&#9;WHILE (j &#60; len) &#38; (h.fields&#91;j&#93; # 0X) &#38; (h.fields&#91;j&#93; # "&#58;") DO &#9;&#9;&#9;&#9;INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;IF h.fields&#91;j&#93; &#61; "&#58;" THEN &#9;&#9;&#9;&#9;INC(j); Strings.StrToIntPos(h.fields^, min, j) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;min &#58;&#61; 0 &#9;&#9;&#9;END; &#9;&#9;&#9;WHILE (j &#60; len) &#38; (h.fields&#91;j&#93; # 0X) &#38; (h.fields&#91;j&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;IF h.fields&#91;j&#93; &#61; "&#58;" THEN &#9;&#9;&#9;&#9;INC(j); Strings.StrToIntPos(h.fields^, sec, j) &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;sec &#58;&#61; 0 &#9;&#9;&#9;END; &#9;&#9;&#9;WHILE (j &#60; len) &#38; (h.fields&#91;j&#93; # 0X) &#38; (h.fields&#91;j&#93; &#60;&#61; " ") DO &#9;&#9;&#9;&#9;INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE (j &#60; len) &#38; (h.fields&#91;j&#93; &#62; " ") DO &#9;&#9;&#9;&#9;zone&#91;i&#93; &#58;&#61; h.fields&#91;j&#93;; INC(i); INC(j) &#9;&#9;&#9;END; &#9;&#9;&#9;zone&#91;i&#93; &#58;&#61; 0X; &#9;&#9;&#9;IF GMTTime(hour, min, sec, zone, time, date) THEN &#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;GetClock(time, date) &#9;END ExtractGMTDate; &#9;PROCEDURE EnumMIME(key, value&#58; ARRAY OF CHAR); &#9;&#9;VAR &#9;&#9;&#9;contType&#58; ContentType; &#9;&#9;&#9;i, j&#58; LONGINT; &#9;BEGIN &#9;&#9;NEW(contType); contType.next &#58;&#61; contTypes; contTypes &#58;&#61; contType; contType.support &#58;&#61; TRUE; &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE (key&#91;i&#93; # 0X) &#38; (key&#91;i&#93; # "/") DO &#9;&#9;&#9;contType.typ&#91;i&#93; &#58;&#61; key&#91;i&#93;; INC(i) &#9;&#9;END; &#9;&#9;contType.typ&#91;i&#93; &#58;&#61; 0X; Strings.Lower(contType.typ, contType.typ); &#9;&#9;IF key&#91;i&#93; &#61; "/" THEN &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;j &#58;&#61; 0; &#9;&#9;WHILE key&#91;i&#93; # 0X DO &#9;&#9;&#9;contType.subTyp&#91;j&#93; &#58;&#61; key&#91;i&#93;; INC(j); INC(i) &#9;&#9;END; &#9;&#9;contType.subTyp&#91;j&#93; &#58;&#61; 0X; Strings.Lower(contType.subTyp, contType.subTyp); &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE value&#91;i&#93; &#62; " " DO &#9;&#9;&#9;contType.suffix&#91;i&#93; &#58;&#61; value&#91;i&#93;; INC(i) &#9;&#9;END; &#9;&#9;contType.suffix&#91;i&#93; &#58;&#61; 0X; &#9;&#9;WHILE (value&#91;i&#93; # 0X) &#38; (value&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;j &#58;&#61; 0; &#9;&#9;WHILE value&#91;i&#93; &#62; " " DO &#9;&#9;&#9;contType.prefix&#91;j&#93; &#58;&#61; value&#91;i&#93;; &#9;&#9;&#9;INC(j); INC(i) &#9;&#9;END; &#9;&#9;contType.prefix&#91;j&#93; &#58;&#61; 0X; &#9;&#9;WHILE (value&#91;i&#93; # 0X) &#38; (value&#91;i&#93; &#60;&#61; " ") DO &#9;&#9;&#9;INC(i) &#9;&#9;END &#9;END EnumMIME; (** Find a content-type description. *) &#9;PROCEDURE GetContentType*(fullTyp&#58; ARRAY OF CHAR)&#58; ContentType; &#9;&#9;VAR &#9;&#9;&#9;contType&#58; ContentType; &#9;&#9;&#9;typ&#58; ARRAY 32 OF CHAR; &#9;&#9;&#9;subTyp&#58; ARRAY 64 OF CHAR; &#9;&#9;&#9;i, j&#58; LONGINT; &#9;BEGIN &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE (fullTyp&#91;i&#93; # 0X) &#38; (fullTyp&#91;i&#93; # "/") DO &#9;&#9;&#9;typ&#91;i&#93; &#58;&#61; fullTyp&#91;i&#93;; INC(i) &#9;&#9;END; &#9;&#9;typ&#91;i&#93; &#58;&#61; 0X; Strings.Lower(typ, typ); &#9;&#9;IF fullTyp&#91;i&#93; &#61; "/" THEN &#9;&#9;&#9;INC(i) &#9;&#9;END; &#9;&#9;j &#58;&#61; 0; &#9;&#9;WHILE fullTyp&#91;i&#93; # 0X DO &#9;&#9;&#9;subTyp&#91;j&#93; &#58;&#61; fullTyp&#91;i&#93;; INC(j); INC(i) &#9;&#9;END; &#9;&#9;subTyp&#91;j&#93; &#58;&#61; 0X; Strings.Lower(subTyp, subTyp); &#9;&#9;IF typ &#61; "" THEN &#9;&#9;&#9;typ &#58;&#61; "text" &#9;&#9;END; &#9;&#9;IF subTyp &#61; "" THEN &#9;&#9;&#9;subTyp &#58;&#61; "plain" &#9;&#9;END; &#9;&#9;contType &#58;&#61; contTypes; &#9;&#9;WHILE (contType # NIL) &#38; &#126;((contType.typ &#61; typ) &#38; (contType.subTyp &#61; subTyp)) DO &#9;&#9;&#9;contType &#58;&#61; contType.next &#9;&#9;END; &#9;&#9;IF contType &#61; NIL THEN &#9;&#9;&#9;NEW(contType); contType.next &#58;&#61; contTypes; contTypes &#58;&#61; contType; contType.support &#58;&#61; FALSE; &#9;&#9;&#9;COPY(typ, contType.typ); COPY(subTyp, contType.subTyp); &#9;&#9;&#9;contType.prefix &#58;&#61; ""; contType.suffix &#58;&#61; "" &#9;&#9;END; &#9;&#9;RETURN contType &#9;END GetContentType; &#9;PROCEDURE LoadTypes; &#9;&#9;VAR S&#58; Texts.Scanner; key&#58; ARRAY 64 OF CHAR; &#9;BEGIN &#9;&#9;contTypes &#58;&#61; NIL; &#9;&#9;Oberon.OpenScanner(S, "MIME"); &#9;&#9;IF S.class &#61; Texts.Inval THEN &#9;&#9;&#9;Out.String("Oberon.Text - MIME not found"); Out.Ln &#9;&#9;END; &#9;&#9;WHILE S.class IN &#123;Texts.Name, Texts.String&#125; DO &#9;&#9;&#9;COPY(S.s, key); Texts.Scan(S); &#9;&#9;&#9;IF (S.class &#61; Texts.Char) &#38; (S.c &#61; "&#61;") THEN &#9;&#9;&#9;&#9;Texts.Scan(S); &#9;&#9;&#9;&#9;IF S.class IN &#123;Texts.Name, Texts.String&#125; THEN &#9;&#9;&#9;&#9;&#9;EnumMIME(key, S.s); Texts.Scan(S) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE S.class &#58;&#61; Texts.Inval &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;NEW(textCont); textCont.typ &#58;&#61; GetContentType("text/plain"); textCont.encoding &#58;&#61; Enc8Bit; &#9;&#9;textCont.h &#58;&#61; NIL; textCont.pos &#58;&#61; 0; textCont.len &#58;&#61; MAX(LONGINT)-BufLen-1 &#9;END LoadTypes; (** Create a temporary file name for contType. *) &#9;PROCEDURE MakeTempName*(contType&#58; ContentType; VAR tempName&#58; ARRAY OF CHAR); &#9;BEGIN &#9;&#9;COPY(contType.prefix, tempName); &#9;&#9;Strings.Append(tempName, "Temp."); &#9;&#9;Strings.Append(tempName, contType.suffix) &#9;END MakeTempName; &#9;PROCEDURE ExtractContentType*(h&#58; Header; pos&#58; LONGINT; VAR cont&#58; Content); &#9;&#9;VAR &#9;&#9;&#9;len, i&#58; LONGINT; &#9;&#9;&#9;fullTyp&#58; ARRAY 32+64 OF CHAR; &#9;BEGIN &#9;&#9;NEW(cont); cont.typ &#58;&#61; GetContentType(TextMime); cont.encoding &#58;&#61; EncBin; &#9;&#9;cont.h &#58;&#61; h; cont.pos &#58;&#61; pos; &#9;&#9;len &#58;&#61; LEN(h.fields^); &#9;&#9;IF (pos &#60; 0) OR (pos &#62;&#61; len) THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; # 0X) &#38; (h.fields&#91;pos&#93; &#60;&#61; " ") DO &#9;&#9;&#9;INC(pos) &#9;&#9;END; &#9;&#9;i &#58;&#61; 0; &#9;&#9;WHILE (pos &#60; len) &#38; (h.fields&#91;pos&#93; &#62; " ") &#38; (h.fields&#91;pos&#93; # ";")DO &#9;&#9;&#9;fullTyp&#91;i&#93; &#58;&#61; h.fields&#91;pos&#93;; INC(i); INC(pos) &#9;&#9;END; &#9;&#9;fullTyp&#91;i&#93; &#58;&#61; 0X; cont.typ &#58;&#61; GetContentType(fullTyp); &#9;&#9;cont.pos &#58;&#61; pos; cont.len &#58;&#61; MAX(LONGINT) &#9;END ExtractContentType; &#9;PROCEDURE ReadText*(in&#58; Streams.Stream; VAR W&#58; Texts.Writer; cont&#58; Content; mail&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;buffer&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;len, rlen, i, offs, maxLen&#58; LONGINT; &#9;&#9;&#9;ch, ch1&#58; CHAR; &#9;&#9;&#9;iso, quoted, cr&#58; BOOLEAN; &#9;BEGIN &#9;&#9;iso &#58;&#61; cont.encoding IN &#123;Enc8Bit, Enc7Bit, EncQuoted&#125;; quoted &#58;&#61; cont.encoding &#61; EncQuoted; &#9;&#9;IF cont.h # NIL THEN &#9;&#9;&#9;FindParam(cont.h, cont.pos, "charset", buffer); iso &#58;&#61; iso OR (buffer # "") &#9;&#9;END; &#9;&#9;ch &#58;&#61; 0X; cr &#58;&#61; FALSE; &#9;&#9;offs &#58;&#61; 0; maxLen &#58;&#61; cont.len; &#9;&#9;in.mode &#58;&#61; Streams.binary; len &#58;&#61; in.Available(in); &#9;&#9;WHILE (maxLen &#62; 0) &#38; ((len &#62; 0) OR (&#126;in.eos &#38; in.buffer)) DO &#9;&#9;&#9;IF len &#62; (BufLen-2) THEN &#9;&#9;&#9;&#9;rlen &#58;&#61; BufLen-2 &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;rlen &#58;&#61; len &#9;&#9;&#9;END; &#9;&#9;&#9;IF rlen &#62; maxLen THEN &#9;&#9;&#9;&#9;rlen &#58;&#61; maxLen &#9;&#9;&#9;END; &#9;&#9;&#9;in.ReadBytes(in, buffer, rlen); DEC(maxLen, rlen); &#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;WHILE i &#60; rlen DO &#9;&#9;&#9;&#9;IF (buffer&#91;i&#93; &#61; Strings.CR) OR (buffer&#91;i&#93; &#61; Strings.LF) THEN &#9;&#9;&#9;&#9;&#9;IF (buffer&#91;i&#93; &#61; Strings.LF) &#38; cr THEN &#9;&#9;&#9;&#9;&#9;&#9;(* ignore LF after CR *) &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;Texts.WriteLn(W); &#9;&#9;&#9;&#9;&#9;&#9;IF mail &#38; (offs &#61; 1) &#38; (ch &#61; ".") THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;RETURN &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;offs &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;cr &#58;&#61; (buffer&#91;i&#93; &#61; Strings.CR) &#9;&#9;&#9;&#9;ELSIF iso THEN &#9;&#9;&#9;&#9;&#9;ch &#58;&#61; buffer&#91;i&#93;; &#9;&#9;&#9;&#9;&#9;IF &#126;quoted OR (ch # "&#61;") THEN &#9;&#9;&#9;&#9;&#9;&#9;ch &#58;&#61; Strings.ISOToOberon&#91;ORD(ch)&#93;; &#9;&#9;&#9;&#9;&#9;&#9;IF &#126;mail OR (offs &#62; 0) OR (ch # ".") THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.Write(W, ch) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;INC(i); &#9;&#9;&#9;&#9;&#9;&#9;IF i &#60; rlen THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;ch &#58;&#61; buffer&#91;i&#93;; INC(i) &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;Streams.Read(in, ch); INC(rlen); DEC(maxLen) &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;IF i &#60; rlen THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;ch1 &#58;&#61; buffer&#91;i&#93; &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;Streams.Read(in, ch1); INC(rlen); DEC(maxLen) &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;IF Strings.IsHexDigit(ch) &#38; Strings.IsHexDigit(ch1) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;ch &#58;&#61; Strings.ISOToOberon&#91;HexVal(ch)*16+HexVal(ch1)&#93;; &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.Write(W, ch); ch &#58;&#61; 0X &#9;&#9;&#9;&#9;&#9;&#9;ELSIF (ch1 &#61; Strings.LF) OR (ch &#61; Strings.LF) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;(* Texts.WriteLn(W); offs &#58;&#61; 0 *) &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.Write(W, "&#61;"); &#9;&#9;&#9;&#9;&#9;&#9;&#9;Texts.Write(W, ch); Texts.Write(W, ch1); &#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(offs, 2); ch &#58;&#61; ch1 &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;INC(offs) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;ch &#58;&#61; buffer&#91;i&#93;; &#9;&#9;&#9;&#9;&#9;IF &#126;mail OR (offs &#62; 0) OR (ch # ".") THEN &#9;&#9;&#9;&#9;&#9;&#9;Texts.Write(W, ch) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;INC(offs) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;END; &#9;&#9;&#9;DEC(len, rlen); &#9;&#9;&#9;IF len &#60;&#61; 0 THEN &#9;&#9;&#9;&#9;len &#58;&#61; in.Available(in) &#9;&#9;&#9;END &#9;&#9;END &#9;END ReadText; &#9;PROCEDURE SearchBoundary(F&#58; Files.File; VAR boundary&#58; ARRAY OF CHAR; VAR pos&#58; LONGINT)&#58; BOOLEAN; &#9;&#9;CONST &#9;&#9;&#9;MaxPatLen &#61; 128; &#9;&#9;VAR &#9;&#9;&#9;sPat&#58; ARRAY MaxPatLen OF CHAR; &#9;&#9;&#9;sDv&#58; ARRAY MaxPatLen + 1 OF LONGINT; &#9;&#9;&#9;i, l, sPatLen&#58; LONGINT; &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;prev, ch&#58; CHAR; &#9;&#9;PROCEDURE CalcDispVec; &#9;&#9;&#9;VAR i, j, d&#58; LONGINT; &#9;&#9;BEGIN &#9;&#9;&#9;i &#58;&#61; 1; d &#58;&#61; 1; &#9;&#9;&#9;WHILE i &#60;&#61; sPatLen DO &#9;&#9;&#9;&#9;j &#58;&#61; 0; &#9;&#9;&#9;&#9;WHILE (j + d &#60; sPatLen) &#38; (sPat&#91;j&#93; &#61; sPat&#91;j + d&#93;) DO &#9;&#9;&#9;&#9;&#9;INC(j) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;WHILE i &#60;&#61; j + d DO &#9;&#9;&#9;&#9;&#9;sDv&#91;i&#93; &#58;&#61; d; INC(i) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;INC(d) &#9;&#9;&#9;END &#9;&#9;END CalcDispVec; &#9;BEGIN &#9;&#9;COPY(boundary, sPat); &#9;&#9;sPatLen &#58;&#61; Strings.Length(sPat); &#9;&#9;CalcDispVec; prev &#58;&#61; 0X; &#9;&#9;IF sPatLen &#62; 0 THEN &#9;&#9;&#9;Files.Set(R, F, pos); prev &#58;&#61; ch; Files.Read(R, ch); &#9;&#9;&#9;INC(pos); l &#58;&#61; Files.Length(F); i &#58;&#61; 0; &#9;&#9;&#9;WHILE (i # sPatLen) &#38; (pos &#60;&#61; l) DO &#9;&#9;&#9;&#9;IF (i &#61; 0) &#38; (prev &#62;&#61; " ") THEN &#9;&#9;&#9;&#9;&#9;prev &#58;&#61; ch; Files.Read(R, ch); INC(pos) &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;IF ch &#61; sPat&#91;i&#93; THEN &#9;&#9;&#9;&#9;&#9;&#9;INC(i); &#9;&#9;&#9;&#9;&#9;&#9;IF i &#60; sPatLen THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;prev &#58;&#61; ch; Files.Read(R, ch); INC(pos) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSIF i &#61; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;prev &#58;&#61; ch; Files.Read(R, ch); INC(pos) &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;i &#58;&#61; i - sDv&#91;i&#93; &#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;i &#58;&#61; -1 &#9;&#9;END; &#9;&#9;RETURN i &#61; sPatLen &#9;END SearchBoundary; &#9;PROCEDURE TextEncoding*(h&#58; Header; pos&#58; LONGINT; cont&#58; Content); &#9;&#9;VAR &#9;&#9;&#9;val&#58; ARRAY 64 OF CHAR; &#9;&#9;&#9;i&#58; LONGINT; &#9;BEGIN &#9;&#9;ExtractValue(h, pos, val); &#9;&#9;i &#58;&#61; 0; Strings.CAPSearch("quoted", val, i); &#9;&#9;IF i &#62;&#61; 0 THEN &#9;&#9;&#9;cont.encoding &#58;&#61; EncQuoted &#9;&#9;ELSE &#9;&#9;&#9;i &#58;&#61; 0; Strings.Search("7", val, i); &#9;&#9;&#9;IF i &#62; 0 THEN &#9;&#9;&#9;&#9;cont.encoding &#58;&#61; Enc7Bit &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;cont.encoding &#58;&#61; Enc8Bit &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;cont.len &#58;&#61; MAX(LONGINT) &#9;END TextEncoding; &#9;PROCEDURE HorzRule(VAR W&#58; Texts.Writer; name&#58; ARRAY OF CHAR); &#9;&#9;VAR f&#58; BasicFigures.Figure; &#9;BEGIN &#9;&#9;NEW(f); BasicFigures.InitRect3D(f, Display.Width, 2); &#9;&#9;Gadgets.NameObj(f, name); &#9;&#9;Texts.WriteObj(W, f); Texts.WriteLn(W) &#9;END HorzRule; &#9;PROCEDURE DecodePart(F&#58; Files.File; beg, end&#58; LONGINT; T&#58; Texts.Text; VAR W&#58; Texts.Writer; VAR parts&#58; Part; mail&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;S, wS&#58; Streams.Stream; &#9;&#9;&#9;h&#58; Header; &#9;&#9;&#9;cont&#58; Content; &#9;&#9;&#9;pos, begPart, n, oldBeg&#58; LONGINT; &#9;&#9;&#9;val&#58; ARRAY 64 OF CHAR; &#9;&#9;&#9;part&#58; Part; &#9;&#9;&#9;ch&#58; CHAR; &#9;BEGIN &#9;&#9;Files.Set(R, F, beg); &#9;&#9;Files.Read(R, ch); INC(beg); &#9;&#9;WHILE (ch &#60;&#61; " ") &#38; (beg &#60;&#61; end) DO &#9;&#9;&#9;Files.Read(R, ch); INC(beg) &#9;&#9;END; &#9;&#9;IF ch &#61; "-" THEN &#9;&#9;&#9;RETURN &#9;&#9;END; &#9;&#9;NEW(part); &#9;&#9;IF parts &#61; NIL THEN &#9;&#9;&#9;part.no &#58;&#61; 0 &#9;&#9;ELSE &#9;&#9;&#9;part.no &#58;&#61; parts.no+1 &#9;&#9;END; &#9;&#9;part.next &#58;&#61; parts; parts &#58;&#61; part; &#9;&#9;S &#58;&#61; Streams.OpenFileReader(F, beg-1); S.mode &#58;&#61; Streams.iso8859; oldBeg &#58;&#61; beg-1; &#9;&#9;Texts.Append(T, W.buf); &#9;&#9;wS &#58;&#61; TextStreams.OpenWriter(T); begPart &#58;&#61; T.len; &#9;&#9;ReadHeader(S, wS, h, n); &#9;&#9;IF (n &#62; 0) &#38; &#126;((h.fields&#91;0&#93; &#61; 0X) &#38; (h.fields&#91;1&#93; &#61; 0X)) THEN &#9;&#9;&#9;wS.Flush(wS); INC(beg, n) &#9;&#9;ELSE &#9;&#9;&#9;DEC(beg) &#9;&#9;END; &#9;&#9;pos &#58;&#61; FindField(h, "Content-Type"); ExtractContentType(h, pos, cont); &#9;&#9;FindParam(h, pos, "name", part.name); &#9;&#9;Strings.IntToStr(part.no, val); HorzRule(W, val); &#9;&#9;Texts.Insert(T, begPart, W.buf); &#9;&#9;S &#58;&#61; Streams.OpenFileReader(F, beg); S.mode &#58;&#61; Streams.binary; &#9;&#9;pos &#58;&#61; FindField(h, "Content-Disposition"); &#9;&#9;IF pos &#62; 0 THEN &#9;&#9;&#9;pos &#58;&#61; FindField(h, "Content-Transfer-Encoding"); &#9;&#9;&#9;IF pos &#62; 0 THEN &#9;&#9;&#9;&#9;ExtractValue(h, pos, val); &#9;&#9;&#9;&#9;IF Strings.CAPPrefix("Base64", val) THEN &#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, "Base64.Decode "); &#9;&#9;&#9;&#9;&#9;pos &#58;&#61; FindField(h, "Content-Disposition"); &#9;&#9;&#9;&#9;&#9;FindParam(h, pos, "filename", val); &#9;&#9;&#9;&#9;&#9;IF part.name &#61; "" THEN &#9;&#9;&#9;&#9;&#9;&#9;COPY(val, part.name) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, val); Texts.WriteString(W, " &#126;"); Texts.WriteLn(W); &#9;&#9;&#9;&#9;&#9;Texts.Append(T, W.buf); cont.encoding &#58;&#61; EncBase64 &#9;&#9;&#9;&#9;ELSIF cont.typ.typ &#61; "text" THEN &#9;&#9;&#9;&#9;&#9;TextEncoding(h, pos, cont) &#9;&#9;&#9;&#9;ELSIF Strings.CAPPrefix("Quoted", val) THEN &#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, "QuotedPrintable.DecodeFile "); &#9;&#9;&#9;&#9;&#9;pos &#58;&#61; FindField(h, "Content-Disposition"); &#9;&#9;&#9;&#9;&#9;FindParam(h, pos, "filename", val); &#9;&#9;&#9;&#9;&#9;IF part.name &#61; "" THEN &#9;&#9;&#9;&#9;&#9;&#9;COPY(val, part.name) &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;Texts.WriteString(W, val); Texts.WriteString(W, " &#126;"); Texts.WriteLn(W); &#9;&#9;&#9;&#9;&#9;Texts.Append(T, W.buf); cont.encoding &#58;&#61; EncBin &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;cont.encoding &#58;&#61; EncBin &#9;&#9;&#9;&#9;END &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;cont.encoding &#58;&#61; EncBin &#9;&#9;&#9;END &#9;&#9;ELSE &#9;&#9;&#9;pos &#58;&#61; FindField(h, "Content-Transfer-Encoding"); &#9;&#9;&#9;TextEncoding(h, pos, cont) &#9;&#9;END; &#9;&#9;IF part.name &#61; "" THEN &#9;&#9;&#9;COPY(cont.typ.typ, part.name); &#9;&#9;&#9;Strings.AppendCh(part.name, "/"); &#9;&#9;&#9;Strings.Append(part.name, cont.typ.subTyp) &#9;&#9;END; &#9;&#9;Texts.Insert(T, begPart, W.buf); (*Texts.WriteLn(W);*) &#9;&#9;cont.len &#58;&#61; end-beg; ReadText(S, W, cont, mail); (*Texts.WriteLn(W)*) &#9;END DecodePart; &#9;PROCEDURE DecodeMultipartFile(F&#58; Files.File; VAR T&#58; Texts.Text; boundary&#58; ARRAY OF CHAR; mail&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;W&#58; Texts.Writer; &#9;&#9;&#9;last, next, parts&#58; Part; &#9;&#9;&#9;obj&#58; Objects.Object; &#9;&#9;&#9;cmd&#58; ARRAY 64 OF CHAR; &#9;&#9;&#9;pos, beg, end, len&#58; LONGINT; &#9;BEGIN &#9;&#9;NEW(T); Texts.Open(T, ""); Texts.OpenWriter(W); &#9;&#9;pos &#58;&#61; 0; end &#58;&#61; 0; len &#58;&#61; Files.Length(F); parts &#58;&#61; NIL; &#9;&#9;IF SearchBoundary(F, boundary, pos) THEN &#9;&#9;&#9;DecodePart(F, 0, pos-Strings.Length(boundary)-2, T, W, parts, mail); &#9;&#9;&#9;WHILE end &#60; len DO &#9;&#9;&#9;&#9;beg &#58;&#61; pos; &#9;&#9;&#9;&#9;IF SearchBoundary(F, boundary, pos) THEN &#9;&#9;&#9;&#9;&#9;end &#58;&#61; pos-Strings.Length(boundary)-2 &#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;end &#58;&#61; len+1 &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;DecodePart(F, beg, end, T, W, parts, mail) &#9;&#9;&#9;END &#9;&#9;END; &#9;&#9;Texts.Append(T, W.buf); &#9;&#9;last &#58;&#61; NIL; &#9;&#9;WHILE parts # NIL DO &#9;&#9;&#9;next &#58;&#61; parts.next; parts.next &#58;&#61; last; &#9;&#9;&#9;last &#58;&#61; parts; parts &#58;&#61; next &#9;&#9;END; &#9;&#9;Texts.WriteLn(W); &#9;&#9;parts &#58;&#61; last; &#9;&#9;WHILE parts # NIL DO &#9;&#9;&#9;Texts.WriteString(W, "&#91; "); &#9;&#9;&#9;Texts.SetColor(W, SHORT(Display3.blue)); &#9;&#9;&#9;Texts.WriteString(W, parts.name); &#9;&#9;&#9;Texts.SetColor(W, SHORT(Display3.textC)); &#9;&#9;&#9;obj &#58;&#61; Gadgets.CreateObject("TextGadgets.NewControl"); &#9;&#9;&#9;cmd &#58;&#61; "HTMLDocs.Locate &#39;"; &#9;&#9;&#9;Strings.IntToStr(parts.no, boundary); &#9;&#9;&#9;Strings.Append(cmd, boundary); &#9;&#9;&#9;Attributes.SetString(obj, "Cmd", cmd); &#9;&#9;&#9;Texts.WriteObj(W, obj); &#9;&#9;&#9;Texts.WriteString(W, " &#93; "); &#9;&#9;&#9;parts &#58;&#61; parts.next &#9;&#9;END; &#9;&#9;Texts.WriteLn(W); Texts.WriteLn(W); &#9;&#9;Texts.Insert(T, 0, W.buf) &#9;END DecodeMultipartFile; &#9;PROCEDURE ReadMultipartText*(in&#58; Streams.Stream; VAR T&#58; Texts.Text; cont&#58; Content; mail&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;h&#58; Header; &#9;&#9;&#9;F&#58; Files.File; &#9;&#9;&#9;R&#58; Files.Rider; &#9;&#9;&#9;buffer&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;boundary&#58; ARRAY 128 OF CHAR; &#9;&#9;&#9;len, rlen, i, state, maxLen&#58; LONGINT; &#9;BEGIN &#9;&#9;maxLen &#58;&#61; cont.len; state &#58;&#61; 1; h &#58;&#61; cont.h; &#9;&#9;F &#58;&#61; Files.New(""); Files.Set(R, F, 0); &#9;&#9;in.mode &#58;&#61; Streams.binary; len &#58;&#61; in.Available(in); &#9;&#9;WHILE (maxLen &#62; 0) &#38; ((len &#62; 0) OR &#126;in.eos) DO &#9;&#9;&#9;IF len &#62; BufLen THEN &#9;&#9;&#9;&#9;rlen &#58;&#61; BufLen &#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;rlen &#58;&#61; len &#9;&#9;&#9;END; &#9;&#9;&#9;IF rlen &#62; maxLen THEN &#9;&#9;&#9;&#9;rlen &#58;&#61; maxLen &#9;&#9;&#9;END; &#9;&#9;&#9;in.ReadBytes(in, buffer, rlen); DEC(maxLen, rlen); &#9;&#9;&#9;IF mail THEN &#9;&#9;&#9;&#9;i &#58;&#61; 0; &#9;&#9;&#9;&#9;WHILE (i &#60; rlen) &#38; (state # 2) DO &#9;&#9;&#9;&#9;&#9;IF (buffer&#91;i&#93; &#61; Strings.CR) OR (buffer&#91;i&#93; &#61; Strings.LF) THEN &#9;&#9;&#9;&#9;&#9;&#9;state &#58;&#61; 1 &#9;&#9;&#9;&#9;&#9;ELSIF (state &#62; 0) &#38; (buffer&#91;i&#93; &#61; ".") THEN &#9;&#9;&#9;&#9;&#9;&#9;INC(state); &#9;&#9;&#9;&#9;&#9;&#9;IF (i &#60; (rlen-1)) &#38; (buffer&#91;i+1&#93; &#61; ".") THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(i); INC(state) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;state &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;INC(i) &#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;IF state &#61; 2 THEN &#9;&#9;&#9;&#9;&#9;maxLen &#58;&#61; 0 &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;Files.WriteBytes(R, buffer, rlen); &#9;&#9;&#9;len &#58;&#61; in.Available(in) &#9;&#9;END; &#9;&#9;FindParam(h, cont.pos, "boundary", buffer); &#9;&#9;boundary &#58;&#61; "--"; Strings.Append(boundary, buffer); &#9;&#9;DecodeMultipartFile(F, T, boundary, mail) &#9;END ReadMultipartText; &#9;PROCEDURE HexDigit(i&#58; LONGINT)&#58; CHAR; &#9;BEGIN &#9;&#9;IF i &#60; 10 THEN &#9;&#9;&#9;RETURN CHR(i+ORD("0")) &#9;&#9;ELSE &#9;&#9;&#9;RETURN CHR(i-10+ORD("A")) &#9;&#9;END &#9;END HexDigit; &#9;PROCEDURE WriteText*(T&#58; Texts.Text; beg, end&#58; LONGINT; out&#58; Streams.Stream; cont&#58; Content; mail, crlf&#58; BOOLEAN); &#9;&#9;VAR &#9;&#9;&#9;buffer&#58; ARRAY BufLen OF CHAR; &#9;&#9;&#9;R&#58; Texts.Reader; &#9;&#9;&#9;i, j, offs&#58; LONGINT; &#9;&#9;&#9;ch&#58; CHAR; &#9;&#9;&#9;iso, quoted&#58; BOOLEAN; &#9;BEGIN &#9;&#9;iso &#58;&#61; cont.encoding IN &#123;Enc8Bit, Enc7Bit, EncQuoted&#125;; quoted &#58;&#61; cont.encoding &#61; EncQuoted; &#9;&#9;Texts.OpenReader(R, T, beg); &#9;&#9;offs &#58;&#61; 0; i &#58;&#61; 0; out.mode &#58;&#61; Streams.binary; &#9;&#9;Texts.Read(R, ch); INC(beg); &#9;&#9;WHILE &#126;R.eot &#38; (beg &#60;&#61; end) DO &#9;&#9;&#9;IF R.lib IS Fonts.Font THEN &#9;&#9;&#9;&#9;IF ch &#61; Strings.CR THEN &#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, buffer, i); &#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, Strings.CRLF, 2); &#9;&#9;&#9;&#9;&#9;offs &#58;&#61; 0; i &#58;&#61; 0 &#9;&#9;&#9;&#9;ELSIF mail &#38; (ch &#61; ".") &#38; (offs &#61; 0) THEN &#9;&#9;&#9;&#9;&#9;IF i &#62; (MaxLine-3) THEN &#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, buffer, i); i &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;buffer&#91;i&#93; &#58;&#61; ch; buffer&#91;i+1&#93; &#58;&#61; ch; INC(offs, 2); INC(i, 2) &#9;&#9;&#9;&#9;ELSIF ch # Strings.LF THEN &#9;&#9;&#9;&#9;&#9;IF iso THEN &#9;&#9;&#9;&#9;&#9;&#9;ch &#58;&#61; Strings.OberonToISO&#91;ORD(ch)&#93;; &#9;&#9;&#9;&#9;&#9;&#9;IF &#126;quoted OR ((ch &#60; CHR(128)) &#38; (ch # "&#61;")) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;buffer&#91;i&#93; &#58;&#61; ch &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF i &#62; (MaxLine-3) THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, buffer, i); i &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;&#9;buffer&#91;i&#93; &#58;&#61; "&#61;"; &#9;&#9;&#9;&#9;&#9;&#9;&#9;buffer&#91;i+1&#93; &#58;&#61; HexDigit((ORD(ch) DIV 16) MOD 16); &#9;&#9;&#9;&#9;&#9;&#9;&#9;buffer&#91;i+2&#93; &#58;&#61; HexDigit(ORD(ch) MOD 16); &#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(i, 2); INC(offs, 2) &#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;buffer&#91;i&#93; &#58;&#61; ch &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;INC(i); &#9;&#9;&#9;&#9;&#9;IF mail &#38; (offs &#62;&#61; (MaxSMTPLine-5)) THEN &#9;&#9;&#9;&#9;&#9;&#9;j &#58;&#61; i-1; &#9;&#9;&#9;&#9;&#9;&#9;WHILE (j &#62; 0) &#38; (buffer&#91;j&#93; &#62; " ") DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;DEC(j) &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;IF j &#62; 0 THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;INC(j); &#9;&#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, buffer, j); &#9;&#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, Strings.CRLF, 2); &#9;&#9;&#9;&#9;&#9;&#9;&#9;IF j &#60; i THEN &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;offs &#58;&#61; i-j; &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;FOR i &#58;&#61; j TO offs DO &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, buffer&#91;i&#93;, 1) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;offs &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;&#9;END &#9;&#9;&#9;&#9;&#9;&#9;ELSE &#9;&#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, buffer, i); &#9;&#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, Strings.CRLF, 2); offs &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;&#9;i &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;ELSIF i &#62;&#61; MaxLine THEN &#9;&#9;&#9;&#9;&#9;&#9;out.WriteBytes(out, buffer, i); i &#58;&#61; 0 &#9;&#9;&#9;&#9;&#9;END; &#9;&#9;&#9;&#9;&#9;INC(offs) &#9;&#9;&#9;&#9;END &#9;&#9;&#9;END; &#9;&#9;&#9;Texts.Read(R, ch); INC(beg) &#9;&#9;END; &#9;&#9;out.WriteBytes(out, buffer, i); &#9;&#9;IF crlf THEN &#9;&#9;&#9;out.WriteBytes(out, Strings.CRLF, 2) &#9;&#9;END &#9;END WriteText; &#9;PROCEDURE WriteISOMime*(S&#58; Streams.Stream; cont&#58; Content); &#9;BEGIN &#9;&#9;S.mode &#58;&#61; Streams.iso8859; &#9;&#9;TextStreams.WriteString(S, MimeVersion); TextStreams.WriteLn(S); &#9;&#9;TextStreams.WriteString(S, "Content-Type&#58; "); TextStreams.WriteString(S, TextMime); &#9;&#9;TextStreams.WriteString(S, "; charset&#61;"); TextStreams.WriteString(S, ISOVer); &#9;&#9;TextStreams.WriteLn(S); &#9;&#9;IF cont.encoding &#61; EncQuoted THEN &#9;&#9;&#9;TextStreams.WriteString(S, ContEncQuoted) &#9;&#9;ELSIF cont.encoding &#61; Enc7Bit THEN &#9;&#9;&#9;TextStreams.WriteString(S, ContEnc7Bit) &#9;&#9;ELSE &#9;&#9;&#9;TextStreams.WriteString(S, ContEnc8Bit) &#9;&#9;END; &#9;&#9;TextStreams.WriteLn(S); &#9;&#9;IF (cont.len &#62; 0) &#38; (cont.len &#60; MAX(LONGINT)) THEN &#9;&#9;&#9;TextStreams.WriteString(S, "Content-Length&#58; "); TextStreams.WriteInt(S, cont.len, 0); TextStreams.WriteLn(S) &#9;&#9;END &#9;END WriteISOMime; BEGIN &#9;LoadTypes END MIME.