Syntax10.Scn.FntBalloonElemsAllocSyntax10.Scn.FntSyntax10i.Scn.Fnt37UU?Syntax10b.Scn.Fnt?U&H"POP3Port" The TCP port POP3 is running on, should be 110 everywhere. "SMTPPort" The TCP port SMTP is running on, should be 25 everywhere. "Sentinel" Used as value for key in sentinel mails. Sentinel mails are mails pointed to by the 'mails' component of Mailbox. They are the beginning/end of the circular list of mails. "statRead" If statRead IN mail.status then mail has already been read. "statDeleted" If statDeleted IN mail.status then mail has been marked for deletion. "init" init, stat, append, delete and undelete are operation id's for notification messages (NotifyMsg). init means the box is initialized and must be redrawn completely by any view. The mail component of the NotifyMsg object will be NIL. "stat" init, stat, append, delete and undelete are operation id's for notification messages (NotifyMsg). stat means that a mail has changed its status. "append" init, stat, append, delete and undelete are operation id's for notification messages (NotifyMsg). append means that a mail has been appended to the mailbox "delete" init, stat, append, delete and undelete are operation id's for notification messages (NotifyMsg). delete means a mail has been marked for deletion in a mailbox. "undelete" init, stat, append, delete and undelete are operation id's for notification messages (NotifyMsg). undelete means a mail, formerly marked for deletion, is to be marked undeleted now. "lesser" Used in CompareMails procedures to indicate result of comparison. "equal" Used in CompareMails procedures to indicate result of comparison. "greater" Used in CompareMails procedures to indicate result of comparison. "HeaderTag" Magical number to indicate this is a mailbox file. "HeaderVersion" Version number of header format. "String" POINTER TO ARRAY OF CHAR; "StringList" POINTER TO ARRAY OF String; "Header" Header* = POINTER TO HeaderDesc; HeaderDesc* = RECORD name-, body-: String; next: Header END; Describes a mail's header; name contains the name of the header field body contains the content of the header field, without leading spaces/tabs. next points to next header line. "HeaderDesc" HeaderDesc* = RECORD name-, body-: String; next: Header END; Describes a mail's header; name contains the name of the header field body contains the content of the header field, without leading spaces/tabs. next points to next header line. "Mail" Mail* = POINTER TO MailDesc; MailDesc* = RECORD key*: INTEGER; sender-, subject-, reply-: String; size*: LONGINT; status-: SET; msgoffs*: LONGINT; (* offset of message in mailbox file *) next*: Mail; header-: Header; bodyOff-: LONGINT (* offset of mail body in text *) END; Describes a mail message. key is a number for the mail unique within its mailbox. sender, subject and reply are special fields from header. size is the size of the mail in bytes. status is a set, that may contain statRead, statDeleted to indicate the status of the mail. msgoffs is the offset of the message in the mailbox file next points to the next mail in the circular list of mails. An empty mailbox contains one element, where mail.key = Sentinel & mail.next = mail header points to the first header line of the mail. bodyOff is the offset of the mail body within a text containing header and body. "MailDesc" MailDesc* = RECORD key*: INTEGER; sender-, subject-, reply-: String; size*: LONGINT; status-: SET; msgoffs*: LONGINT; (* offset of message in mailbox file *) next*: Mail; header-: Header; bodyOff-: LONGINT (* offset of mail body in text *) END; Describes a mail message. key is a number for the mail unique within its mailbox. sender, subject and reply are special fields from header. size is the size of the mail in bytes. status is a set, that may contain statRead, statDeleted to indicate the status of the mail. msgoffs is the offset of the message in the mailbox file next points to the next mail in the circular list of mails. An empty mailbox contains one element, where mail.key = Sentinel & mail.next = mail header points to the first header line of the mail. bodyOff is the offset of the mail body within a text containing header and body. "Mailbox" Mailbox* = POINTER TO MailboxDesc; MailboxDesc* = RECORD mails: Mail; (* points to sentinel, mails.next = first mail *) name-: ARRAY 32 OF CHAR; next: Mailbox END; Describes a mailbox. mails points to the sentinel mail within the mailbox. An empty mailbox contains one element, where mail.key = Sentinel & mail.next = mail name is the filename of the mailbox, without any path information and without extension. next points to the next loaded mailbox. "MailboxDesc" MailboxDesc* = RECORD mails: Mail; (* points to sentinel, mails.next = first mail *) name-: ARRAY 32 OF CHAR; next: Mailbox END; Describes a mailbox. mails points to the sentinel mail within the mailbox. An empty mailbox contains one element, where mail.key = Sentinel & mail.next = mail name is the filename of the mailbox, without any path information and without extension. next points to the next loaded mailbox. "Reader" Reader* = RECORD mail-: Mail; eof-: BOOLEAN END; A reader is an iterator over a mailbox. mail is the mail currently pointed to by Reader. eof is true, if there are no more mails to be read in this mailbox. A reader can be opened on a mailbox by calling OpenReader(box, reader) Calling Read(reader) will advance the reader to the next mail in the box. "NotifyMsg" NotifyMsg* = RECORD (Display.FrameMsg) box-: Mailbox; op-: INTEGER; mail-: Mail END; A NotifyMsg is sent to all viewers whenever a change within a mailbox has occured. box points to the mailbox the change occured in. mail points to the mail affected by the change. May be NIL with certain operations. op is one of init, stat, append, delete, undelete indicating the type of operation. "SendDoneNotifier" SendDoneNotifier* = PROCEDURE (ok: BOOLEAN; error: ARRAY OF CHAR; V: Viewers.Viewer; backup: Texts.Buffer); When sending in background a SendDoneNotifier procedure is called when the sending process has terminated. ok is true, iff the mail has been sent. error contains an error/success message. V is the viewer to be restored, if ok = false. V may be NIL backup is the text to be displayed in V, if ok = false. backup may be NIL "SendTask" SendTask = POINTER TO SendTaskDesc; SendTaskDesc = RECORD (Oberon.TaskDesc) t: Texts.Text; r: Texts.Reader; v: Viewers.Viewer; b: Texts.Buffer; done: SendDoneNotifier; state: SHORTINT; conn: TCP.Connection; to, cc, bcc: StringList; curRec, nrRecOk: INTEGER (* number of recipients that were o.k. *) END; A SendTask is used for sending in the background. Its fields contain all relevant information for sending (t, r, conn, to, cc, bcc), notification (done) and error recovery (v, b) as well as internal state information (state, curRec, nrRecOk). "SendTaskDesc" SendTaskDesc = RECORD (Oberon.TaskDesc) t: Texts.Text; r: Texts.Reader; v: Viewers.Viewer; b: Texts.Buffer; done: SendDoneNotifier; state: SHORTINT; conn: TCP.Connection; to, cc, bcc: StringList; curRec, nrRecOk: INTEGER (* number of recipients that were o.k. *) END; A SendTask is used for sending in the background. Its fields contain all relevant information for sending (t, r, conn, to, cc, bcc), notification (done) and error recovery (v, b) as well as internal state information (state, curRec, nrRecOk). "Receiver" Receiver* = PROCEDURE (mail: Mail; t: Texts.Text); Procedure type for e-mail filters. "RetrDoneNotifier" RetrDoneNotifier* = PROCEDURE (ok: BOOLEAN; error: ARRAY OF CHAR; nOfMsgs: LONGINT); Used for notification when background receiving of mails was successful. "RetrTask" RetrTask = POINTER TO RetrTaskDesc; RetrTaskDesc = RECORD (Oberon.TaskDesc) user, password: ARRAY 32 OF CHAR; remove: BOOLEAN; curMsg, nrMsg: LONGINT; state: SHORTINT; rec: Receiver; (* box: Mailbox; *) w: Texts.Writer; lastBuf: ARRAY 3 OF CHAR; done: RetrDoneNotifier; conn: TCP.Connection END; Background receiving of mails is done by RetrTasks. user, password: Username and password with which to connect to POP server. remove: TRUE, iff mails should be removed after downloading from POP server. curMsg, nrMsg: message currently downloaded and total number of messages. state: internal state of download task rec: Whenever a mail has been received the Receiver rec is called. The rest of the variables of RetrTask is of internal concern only. "RetrTaskDesc" RetrTask = POINTER TO RetrTaskDesc; RetrTaskDesc = RECORD (Oberon.TaskDesc) user, password: ARRAY 32 OF CHAR; remove: BOOLEAN; curMsg, nrMsg: LONGINT; state: SHORTINT; rec: Receiver; (* box: Mailbox; *) w: Texts.Writer; lastBuf: ARRAY 3 OF CHAR; done: RetrDoneNotifier; conn: TCP.Connection END; Background receiving of mails is done by RetrTasks. user, password: Username and password with which to connect to POP server. remove: TRUE, iff mails should be removed after downloading from POP server. curMsg, nrMsg: message currently downloaded and total number of messages. state: internal state of download task rec: Whenever a mail has been received the Receiver rec is called. The rest of the variables of RetrTask is of internal concern only. "CompareMails" CompareMails* = PROCEDURE (mail1, mail2: Mail): SHORTINT; CompareMails procedures are used to compare two mails in order to sort them. "ConfigOK" true, iff configuration from registry (Mail.Profile) was successful, i.e. all required entries were found. "POPServer" name of POP server. cf. RFC1939 for POP protocoll. "SMTPServer" name of SMTP server. cf. RFC821 for SMTP protocoll. "POPServerAdr" Address of POP server. cf. RFC1939 for POP protocoll. "SMTPServerAdr" Address of SMTP server. cf. RFC821 for SMTP protocoll. "Reversepath" e-mail address of Mail-Tool user. Taken from Mail.Profile. "Domain" domain for HELO in SMTP. Taken from Mail.Profile. "mDir" Directory for mailbox files. Taken from Mail.Profile. "Conn" TCP Connection currently used for sending / receiving mails. "rpl" Reply to last server request. "boxes" Pointer to list of all mailboxes currently loaded. "SearchPatt2" PROCEDURE SearchPatt2* (T: Texts.Text; pat: ARRAY OF CHAR; pos: LONGINT): LONGINT; Searches for pat in t from pos, returning the position after the next occurence of pat. Returns -1 if pat could not be found in T. "Get" PROCEDURE Get* (key: ARRAY OF CHAR; VAR elem: ARRAY OF CHAR): BOOLEAN; Gets the profile entry 'key' from Mail.Profile, returning its content in elem. True is returned, if key could by found. "GetAsBuffer" PROCEDURE GetAsBuffer* (key: ARRAY OF CHAR; VAR buf: Texts.Buffer): BOOLEAN; Gets the profile entry 'key' from Mail.Profile, returning its content in buf. True is returned, if key could by found. "OpenReader" PROCEDURE OpenReader* (box: Mailbox; VAR R: Reader); OpenReader opens a reader R on mailbox box. After opening, R.mail points to the sentinel mail. To access the first real mail in the box, Read must be called. "Read" PROCEDURE Read* (VAR R: Reader); Read reads the next mail from box R is set to. If no more mails could be read R.eof = TRUE "WriteHeader" PROCEDURE WriteHeader (VAR R: Files.Rider; m: Mail); Writes the header for m to the file pointed to by R. "ReadHeader" PROCEDURE ReadHeader (VAR R: Files.Rider; m: Mail); Reads the header for m from the file pointed to by R. "GetParameter" PROCEDURE (head: Header) GetParameter* (paramName: ARRAY OF CHAR): String; Returns the parameter 'paramName' from header line head as String. Cf. RFC2045 for definition of "parameter". "GetHeaderField" PROCEDURE (head: Header) GetHeaderField* (fieldName: ARRAY OF CHAR): Header; Returns the headerline with name 'fieldName' from the header, whose first line is head. "BuildHeader" PROCEDURE BuildHeader* (VAR r: Texts.Reader; VAR header: Header); Builds header by reading from r. r must be set to a text containing valid RFC822 header fields. "ParseMail" PROCEDURE ParseMail* (T: Texts.Text; VAR mail: Mail); Parses T and set mail.subject, mail.sender, mail.reply and mail.header accordingly. Mail must be allocated already. "WriteLine" PROCEDURE WriteLine (line: ARRAY OF CHAR); Writes line to Conn. "ReadLine" PROCEDURE ReadLine (VAR line: ARRAY OF CHAR); Reads line from Conn. "Send" PROCEDURE Send (cmd, arg: ARRAY OF CHAR); Used to send a command. "Send2" PROCEDURE Send2 (cmd, arg1, arg2: ARRAY OF CHAR); Used to send a command. "WaitPOPReply" PROCEDURE WaitPOPReply (VAR ok: BOOLEAN); Waits for reply from POP server. ok is true, if a positve answer was sent from POP server. "WaitSMTPReply" PROCEDURE WaitSMTPReply (VAR code: INTEGER); Waits for reply from SMTP server. code is set to the return code of the SMTP server. "Abort" PROCEDURE Abort; Used to abort POP server connection. "PopOpen" PROCEDURE PopOpen (user, pwd: ARRAY OF CHAR; VAR done: BOOLEAN); Tries to esablish a connection to POP server using the login user and password pwd. done is true, iff connection could be established. "PopClose" PROCEDURE PopClose; Closes connection to POP server; "PopStat" PROCEDURE PopStat (VAR nofmsgs: LONGINT; VAR done: BOOLEAN); Sends "STAT" to POP server, trying to find out, how many messages there are. nofmsgs contains the number of messages done is true, iff the STAT command was successful. "PopRetr" PROCEDURE PopRetr (msgno: LONGINT; t: Texts.Text; VAR done: BOOLEAN); Sends "RETR" to POP server, trying to retrieve a message from server. msgno contains the number of the retrieved message t contains the message done is true, iff the RETR command was successful. "PopDelete" PROCEDURE PopDelete (msgno: LONGINT; VAR done: BOOLEAN); Sends "DELE" to POP server, trying to delete message msgno from server. done is true, iff the DELE command was successful. "SendHandler" PROCEDURE SendHandler; SendHandler is the Task handler for SendTask. It handles the stepwise sending of mail. The local constant bufSize can be used to change the amount of data sent at one step. Note: The assignment Conn := task.conn; at the beginning of SendHandler is a mild hack ;-) "SendInBackground" PROCEDURE SendInBackground* (T: Texts.Text; to, cc, bcc: StringList; doneProc: SendDoneNotifier; v: Viewers.Viewer; b: Texts.Buffer); SendInBackground sends message T to the recipients in to, cc and bcc, calling doneProc when finished. v and b are used to restore a given viewer with the content of buffer b, if the sending process for any reasons fails. (Delivery problems to one out of many addresses is not considered a failure). "MakeMailText" PROCEDURE MakeMailText* (T: Texts.Text; rcpts, cc, bcc, subject, attachments: ARRAY OF CHAR); MakeMailText inserts all relevant text parts into T which are needed to get a mailable text. It inserts the lines To:, cc:, bcc:, Subject: and Attach.:, into T, filling it with the given recipients / attachments. The resulting text can be used with PostOffice.SendInBackground. It is however recommended to use the Procedure MIME.Encode on the resulting text to ensure sending works. Cf. Mail.Send for an example. "AppendExtension" PROCEDURE AppendExtension* (VAR path: ARRAY OF CHAR); Appends '.mbx' to path, if neccessary. "AdaptFilename" PROCEDURE AdaptFilename (VAR filename: ARRAY OF CHAR); Adapts filename by prepending the mail directory mDir and appending ".mbx", if necessary. "GetMailFile" PROCEDURE GetMailFile (name: ARRAY OF CHAR): Files.File; Gets a Files.File for the mailbox 'name'. "InsertBox" PROCEDURE InsertBox (box: Mailbox); Inserts the new mailbox 'box' into the PostOffice. "Init" PROCEDURE (box: Mailbox) Init (boxname: ARRAY OF CHAR); Initializes box. "AppendMailStruct" PROCEDURE (box: Mailbox) AppendMailStruct (new: Mail); Appends new to the circular list of mails in box. "AppendMail" PROCEDURE (box: Mailbox) AppendMail* (m: Mail; msg: Texts.Text); Appends the mail m with text msg to the mailbox box. "GetText" PROCEDURE (box: Mailbox) GetText* (msgno: INTEGER; T: Texts.Text); Gets message nr. msgno from box returning the message in T. "GetMailByNr" PROCEDURE (box: Mailbox) GetMailByNr* (nr: INTEGER; VAR mail: Mail); Gets the mail object with key nr from box. "Load" PROCEDURE (box: Mailbox) Load; Loads a box from its file. "Store" PROCEDURE (box: Mailbox) Store*; Store a box to its file. "GetBox" PROCEDURE GetBox* (boxname: ARRAY OF CHAR; VAR box: Mailbox); Returns the mailbox named 'boxname' in box. If no such mailbox exists a new mailbox is created. "Delete" PROCEDURE (box: Mailbox) Delete* (msgno: INTEGER); Deletes message 'msgno' from box. "Undelete" PROCEDURE (box: Mailbox) Undelete* (msgno: INTEGER); Undeletes message 'msgno' from box. "MarkRead" PROCEDURE (box: Mailbox) MarkRead* (msgno: INTEGER); Marks message 'msgno' as read in box. "Move" PROCEDURE (src: Mailbox) Move* (dst: Mailbox; msgno: INTEGER); Moves message 'msgno' from mailbox src to mailbox dst. "Copy" PROCEDURE (src: Mailbox) Copy* (dst: Mailbox; msgno: INTEGER); Copies message 'msgno' from mailbox src to mailbox dst. "FetchNew" PROCEDURE (box: Mailbox) FetchNew* (user, password: ARRAY OF CHAR; VAR nofmsgs: LONGINT; remove: BOOLEAN; VAR done: BOOLEAN); Tries to establish a connection to the POP server using 'user' and 'password' and get all mails stored on the server. The number of retrieved messages is stored in nofmsgs. If remove is true retrieved messages are removed from the POP server. done is true, iff the operation was successful. New mails are stored into box. This procedure may block the system for a considerable amount of time. "CheckNew" PROCEDURE CheckNew* (user, password: ARRAY OF CHAR; VAR nofmsgs: LONGINT; VAR done: BOOLEAN); Tries to establish a connection to the POP server using 'user' and 'password' and check if mail is available on the server. The number of messages is stored in nofmsgs. done is true, iff the operation was successful. "Sort" PROCEDURE (box: Mailbox) Sort* (comp: CompareMails); Sorts mails in box, using the procedure comp to compare mails. "SearchPatt" PROCEDURE SearchPatt (t: Texts.Text; spos: LONGINT): LONGINT; SearchPatt is an auxiliary function used by ImportMailbox to determine the beginning of a new mail. "ImportMailbox" PROCEDURE ImportMailbox* (filename: ARRAY OF CHAR; into: ARRAY OF CHAR; VAR done: BOOLEAN); Tries to import the plain ascii file 'filename' into the mailbox called 'into'. done is true, iff the operation was successful. "Init" PROCEDURE Init; Reads entries from Mail.Profile, setting ConfigOK to true, iff all needed entries were found. Reads entries from InfoElemsAllocSyntax10.Scn.FntSyntax10i.Scn.Fnt$}+StampElemsAlloc1 Jun 997LinkElemsAllocPostOffice.Modxx"Title": PostOffice "Author": MH 3.2.1994 / 1.7.94; RLI 11.03.1998 "Abstract": Back end of mail application "Keywords": PostOffice SMTP POP Mail Mailbox "Version": 1.0 "From": 1994 "Until":  "Changes": 19 Oct 1998 RLI Fixed bug in BuildHeader  28 Oct 1998 RLI Fixed bug with sending on start of new line ... "Hints": This text can again contain arbitrary text elements! Syntax10i.Scn.Fnt78FoldElemsNew#Syntax10.Scn.Fnt Documentation ZParcElemsAlloc Z8Syntax10b.Scn.Fnt    U         &  ! 0)     p) +d 14+(*0)z ,  =8'8 98#Syntax10.Scn.Fnt VAR s: Texts.Scanner; r: Texts.Reader; i : INTEGER; text: Texts.Text; ch: CHAR; BEGIN i := 0; text := TextFrames.Text('Mail.Profile'); Texts.OpenScanner(s, text, 0); Texts.Scan(s); WHILE ((s.s # key) & (~s.eot)) DO Texts.Scan(s) END; IF (s.s = key) THEN Texts.OpenReader(r, text, Texts.Pos(s)); Texts.Read(r, ch); WHILE ((ch # 0DX) & ~r.eot & (ch # ' ')) DO elem[i] := ch; INC(i); Texts.Read (r, ch) END; elem[i] := 0X; RETURN TRUE ELSE RETURN FALSE END END Get; 8  78QSyntax10.Scn.FntSyntax10i.Scn.FnthT (* Gets profile entry 'key' and returns its value (including a terminating newline character) in buf; *) (* if buf is NIL buf is newly created; if buf # nil, the value is appended to buf *) VAR s: Texts.Scanner; r: Texts.Reader; text: Texts.Text; ch: CHAR; BEGIN text := TextFrames.Text('Mail.Profile'); Texts.OpenScanner(s, text, 0); Texts.Scan(s); WHILE ((s.s # key) & (~s.eot)) DO Texts.Scan(s) END; IF (s.s = key) THEN Texts.OpenReader(r, text, Texts.Pos(s)); Texts.Read(r, ch); WHILE ((ch # 0DX) & ~r.eot) DO Texts.Read (r, ch) END; IF buf = NIL THEN NEW(buf); Texts.OpenBuf(buf) END; Texts.Save(text, Texts.Pos(s), Texts.Pos(r), buf); RETURN TRUE ELSE RETURN FALSE END END GetAsBuffer; 88  88 8T88$Syntax10i.Scn.FntZZ Mailfile = {Header Message}. Header = F704 status4 . Message = textblocklen4 textblock . 87n8#Syntax10.Scn.Fntpp BEGIN Files.Write(R, HeaderTag); Files.Write(R, HeaderVersion); Files.WriteSet(R, m.status) END WriteHeader; 85\8#Syntax10.Scn.Fnt VAR version: CHAR; BEGIN Files.Read(R, version); ASSERT(version = HeaderVersion); Files.ReadSet(R, m.status) END ReadHeader; 8 88#Syntax10.Scn.Fnt VAR i: INTEGER; PROCEDURE Digit (x: LONGINT); BEGIN IF x >= 10 THEN Digit(x DIV 10) END; s[i] := CHR(x MOD 10 + ORD("0")); INC(i) END Digit; BEGIN i := 0; Digit(x); s[i] := 0X END IntToStr; 8<8#Syntax10.Scn.Fnt VAR beg: INTEGER; BEGIN x := 0; beg := 0; WHILE (s[beg] # 0X) & ((s[beg] < "0") OR ("9" < s[beg])) DO INC(beg) END; WHILE ("0" <= s[beg]) & (s[beg] <= "9") DO x := 10 * x + (ORD(s[beg]) - ORD("0")); INC(beg) END END StrToInt; 8)*8#Syntax10.Scn.Fnt VAR i, len: LONGINT; BEGIN i := 0; len := LEN(s); WHILE (i < len) & (s[i] # 0X) DO IF (s[i] >= 'a') & (s[i] <= 'z') THEN s[i] := CAP(s[i]) END; INC(i) END END UpStr; 8%8\8Syntax10.Scn.FntSyntax10i.Scn.Fnt,!O (* from RFC 2045: tspecials := "(" / ")" / "<" / ">" / "@" / "," / ";" / ":" / "\" / <"> "/" / "[" / "]" / "?" / "=" ; Must be in quoted-string, ; to use within parameter values *) BEGIN RETURN (ch = "(") OR (ch = ")") OR ((ch >= ":") & (ch <= "@")) OR (ch = ",") OR ((ch >= "[") & (ch <= "]")) OR (ch = "/") END IsTSpecial; 8  8%O8#Syntax10.Scn.Fnt VAR cur: Header; BEGIN cur := head; WHILE (cur # NIL) & (cur.name^ # fieldName) DO cur := cur.next END; RETURN cur END GetHeaderField; 8  ,-8Syntax10.Scn.Fnt8FoldElemsNewCSyntax10.Scn.FntrSyntax10b.Scn.FntB VAR nName, tmp: String; idx: INTEGER; BEGIN NEW(nName, 32); idx := 0; IF (ch = CR) OR r.eot THEN eoh := TRUE; RETURN END; WHILE (~r.eot) & (ch # ":") DO IF idx + 1 >= LEN(nName^) THEN tmp := nName; tmp[idx] := 0X; NEW(nName, LEN(nName^) * 2); COPY(tmp^, nName^) END; nName[idx] := ch; INC(idx); Texts.Read(r, ch); END; nName[idx] := 0X; NEW(name, idx + 1); COPY(nName^, name^); Texts.Read(r, ch) END ReadFieldName; 8B8#Syntax10.Scn.FntII VAR nBody, tmp: String; idx: LONGINT; end: BOOLEAN; BEGIN NEW(nBody, 64); idx := 0; end := FALSE; WHILE ~r.eot & ~end DO IF idx + 1 >= LEN(nBody^) THEN tmp := nBody; tmp[idx] := 0X; NEW(nBody, LEN(nBody^) * 2); COPY(tmp^, nBody^) END; IF ch = CR THEN Texts.Read(r, ch); IF (ch # TAB) & (ch # " ") THEN end := TRUE ELSE nBody[idx] := ch END; INC(idx) ELSE IF (idx > 0) OR (ch # " ") & (ch # TAB) THEN nBody[idx] := ch; INC(idx) END; Texts.Read(r, ch) END END; nBody[idx] := 0X; NEW(body, idx + 1); COPY(nBody^, body^) END ReadFieldBody; 8NMarkElemsAllocx VAR ch: CHAR; tail, prev: Header; eoh: BOOLEAN; PROCEDURE ReadFieldName (VAR r: Texts.Reader; VAR name: String; VAR eoh: BOOLEAN);  PROCEDURE ReadFieldBody (VAR r: Texts.Reader; VAR body: String);  BEGIN ASSERT(header # NIL); eoh := FALSE; prev := NIL; tail := header; Texts.Read(r, ch); ReadFieldName(r, tail.name, eoh); WHILE ~eoh DO UpStr(tail.name^); ReadFieldBody(r, tail.body); NEW(tail.next); prev := tail; tail := tail.next; ReadFieldName(r, tail.name, eoh) END; IF prev # NIL THEN prev.next := NIL ELSE  NEW(header.name, 13); COPY("CONTENT-TYPE", header.name^); NEW(header.body, 11); COPY("TEXT/PLAIN", header.body^); header.next := NIL; END END BuildHeader; 8  "8'\8(,,8#Syntax10.Scn.Fnt VAR i: INTEGER; BEGIN i := 0; WHILE line[i] # 0X DO INC(i) END; IF i > 0 THEN TCP.WriteBytes(Conn, line, 0, i) END; TCP.Write(Conn, CR); TCP.Write(Conn, LF) END WriteLine; 8/8#Syntax10.Scn.FntCC VAR received: LONGINT; ch: CHAR; BEGIN received := 0; LOOP TCP.Read(Conn, ch); IF ch = CR THEN TCP.Read(Conn, ch); IF ch = LF THEN line[received] := 0X; EXIT ELSE line[received] := CR; INC(received); line[received] := ch; INC(received) END ELSE line[received] := ch; INC(received) END END END ReadLine; 8+8#Syntax10.Scn.Fnt__ VAR s: ARRAY 64 OF CHAR; BEGIN COPY(cmd, s); Strings.Append(arg, s); WriteLine(s) END Send; 83c8#Syntax10.Scn.Fnt{{ VAR s: ARRAY 256 OF CHAR; BEGIN COPY(cmd, s); Strings.Append(arg1, s); Strings.Append(arg2, s); WriteLine(s) END Send2; 8+8<8.8#Syntax10.Scn.Fnt BEGIN ReadLine(rpl); WHILE rpl[3] = "-" DO ReadLine(rpl) END; IF ("0" <= rpl[0]) & (rpl[0] <= "9") THEN code := ORD(rpl[2]) - ORD("0") + 10 * (ORD(rpl[1]) - ORD("0")) + 100 * (ORD(rpl[0]) - ORD("0")) ELSE code := - 1 END END WaitSMTPReply; 8g8#Syntax10.Scn.Fntww VAR ok: BOOLEAN; BEGIN Out.String(rpl); Out.Ln; Send("QUIT", ""); WaitPOPReply(ok); TCP.Disconnect(Conn) END Abort; 8B8CSyntax10.Scn.FntSyntax10i.Scn.Fnt CONST timeout = 10000 (* ms *); VAR res: INTEGER; BEGIN TCP.Connect(Conn, TCP.AnyPort, POPServerAdr, POP3Port, timeout, res); IF res = TCP.Done THEN WaitPOPReply(done); IF ~done THEN Abort; RETURN END; Send("USER ", user); WaitPOPReply(done); IF ~done THEN Abort; RETURN END; Send("PASS ", pwd); WaitPOPReply(done); IF ~done THEN Abort; RETURN END ELSE Out.F("Error # while connecting", res); IF res = TCP.Timeout THEN Out.String(" (timeout)") END; Out.Ln; done := FALSE END END PopOpen; 8|8#Syntax10.Scn.Fntbb VAR ok: BOOLEAN; BEGIN Send("QUIT", ""); WaitPOPReply(ok); TCP.Disconnect(Conn) END PopClose; 8>f8#Syntax10.Scn.Fntxx BEGIN Send("STAT", ""); WaitPOPReply(done); IF done THEN StrToInt(rpl, nofmsgs) ELSE nofmsgs := 0 END END PopStat; 8:^8#Syntax10.Scn.Fnt VAR argument: ARRAY 16 OF CHAR; BEGIN IntToStr(msgno, argument); Send("DELE ", argument); WaitPOPReply(done) END PopDelete; 88QSyntax10.Scn.FntWSyntax10i.Scn.FntSyntax10b.Scn.Fnt! :'KH~@>Ik CONST timeout = 8000; bufSize = 8096; VAR task: SendTask; i, res, repl: INTEGER; buffer: ARRAY bufSize OF CHAR; ch, oldCh: CHAR; BEGIN task := Oberon.CurTask(SendTask); IF ~ConfigOK THEN Oberon.Remove(task); task.done(FALSE, "Configuration not ok. Check Mail.Profile", task.v, task.b) END; Conn := task.conn; CASE task.state OF 0: (* Connect to SMTP - Server *) TCP.Connect(task.conn, TCP.AnyPort, SMTPServerAdr, SMTPPort, timeout, res); IF res # TCP.Done THEN Oberon.Remove(task); task.done(FALSE, "Could not connect to SMTP-Server", task.v, task.b); Abort; RETURN END; task.state := 1; | 1: (* Wait for server & say HELO *) WaitSMTPReply(repl); IF repl # 220 THEN Oberon.Remove(task); task.done(FALSE, "Server is not responding.", task.v, task.b); Abort; RETURN END; Send("HELO ", Domain); task.state := 2; | 2: (* Wait for reply & send reversepath *) WaitSMTPReply(repl); IF repl # 250 THEN Oberon.Remove(task); task.done(FALSE, "Server is not responding.", task.v, task.b); Abort; RETURN END; Send2("MAIL FROM:<", Reversepath, ">"); task.state := 3; | 3: (* Wait for reply *) WaitSMTPReply(repl); IF repl # 250 THEN Oberon.Remove(task); task.done(FALSE, "Server did not accept sender's address.", task.v, task.b); Abort; RETURN END; task.curRec := 0; task.nrRecOk := 0; task.state := 4; | 4: (* Sending recipients *) IF task.curRec < LEN(task.to^) THEN Send2("RCPT TO: <", task.to[task.curRec]^, ">"); WaitSMTPReply(repl); IF (repl = 250) OR (repl = 251) THEN INC(task.nrRecOk) ELSE Out.String("PostOffice: Could not send to "); Out.String(task.to[task.curRec]^); Out.Ln END; INC(task.curRec) ELSE IF (task.to = task.bcc) OR (task.to = task.cc) & (task.bcc = NIL) OR (task.cc = NIL) & (task.bcc = NIL) THEN (* really done *) IF task.nrRecOk = 0 THEN Oberon.Remove(task); task.done(FALSE, "No valid recipients.", task.v, task.b); Abort; RETURN END; task.state := 5 ELSIF (task.to = task.cc) THEN (* done with cc, now do bcc *) task.to := task.bcc; task.curRec := 0 ELSE (* done with to, now do cc *) IF task.cc # NIL THEN task.to := task.cc; ELSE task.to := task.bcc; END; task.curRec := 0 END END; | 5: (* Sending DATA command *) Send("DATA", ""); WaitSMTPReply(repl); IF repl # 354 THEN Oberon.Remove(task); task.done(FALSE, "Server did not accept DATA command.", task.v, task.b); Abort; RETURN END; Texts.OpenReader(task.r, task.t, 0); task.state := 6; | 6: (* Sending data *) i := 0; oldCh := 0X; Texts.Read(task.r, ch); WHILE ~task.r.eot & (i + 1 < bufSize) DO buffer[i] := ch; INC(i); IF (ch = CR) THEN buffer[i] := LF; INC(i); ch := LF; ELSIF (ch = ".") & (oldCh = LF) THEN Texts.Read(task.r, ch); IF (ch = CR) THEN buffer[i] := "."; INC(i); buffer[i] := CR; INC(i); buffer[i] := LF; INC(i); ch := LF; ELSE buffer[i] := ch; INC(i); END; END; oldCh := ch; Texts.Read(task.r, ch) END; TCP.WriteBytes(Conn, buffer, 0, i); IF task.r.eot THEN WriteLine(""); WriteLine("."); WaitSMTPReply(repl); IF repl # 250 THEN Oberon.Remove(task); task.done(FALSE, "Server did not accept data.", task.v, task.b); Abort; RETURN END; task.state := 7 ELSE Texts.OpenReader(task.r, task.t, Texts.Pos(task.r) - 1) END; | 7: (* Closing connection *) Send("QUIT", ""); WaitSMTPReply(repl); IF repl # 221 THEN Oberon.Remove(task); task.done(FALSE, "Server did not accept QUIT command.", task.v, task.b); Abort; RETURN END; Oberon.Remove(task); task.done(TRUE, "Mail sent.", task.v, task.b) END; task.time := Oberon.Time() + 20 END SendHandler; 8 k8#Syntax10.Scn.Fnt VAR task: SendTask; BEGIN NEW(task); task.t := T; task.safe := FALSE; task.handle := SendHandler; task.done := doneProc; task.state := 0; task.to := to; task.cc := cc; task.bcc := bcc; NEW(task.conn); task.v := v; task.b := b; Oberon.Install(task) END SendInBackground; 8 G8#Syntax10.Scn.Fnt VAR header: Texts.Text; w: Texts.Writer; buf: Texts.Buffer; pos: LONGINT; BEGIN NEW(buf); Texts.OpenBuf(buf); header := TextFrames.Text("SendNew.Text"); Texts.Save(header, 0, header.len, buf); Texts.Insert(T, 0, buf); Texts.OpenWriter(w); pos := SearchPatt2(T, "To: ", 0); Texts.WriteString(w, rcpts); Texts.Insert(T, pos, w.buf); pos := SearchPatt2(T, "cc: ", 0); Texts.WriteString(w, cc); Texts.Insert(T, pos, w.buf); pos := SearchPatt2(T, "bcc: ", 0); Texts.WriteString(w, bcc); Texts.Insert(T, pos, w.buf); pos := SearchPatt2(T, "Subject: ", 0); Texts.WriteString(w, subject); Texts.Insert(T, pos, w.buf); pos := SearchPatt2(T, "Attach.:", 0); Texts.WriteString(w, attachments); Texts.Insert(T, pos, w.buf) END MakeMailText; 8  8#Syntax10.Scn.Fnt VAR ext: ARRAY 5 OF CHAR; BEGIN IF Strings.Length(path) >= 4 THEN Strings.Extract(path, Strings.Length(path) - 4, 4, ext); ELSE ext := ""; END; IF ext # ".mbx" THEN Strings.Append(".mbx", path) END END AppendExtension; 888#Syntax10.Scn.Fnt## VAR delimiter: ARRAY 2 OF CHAR; BEGIN delimiter[0] := Directories.delimiter; delimiter[1] := 0X; AppendExtension(filename); IF mDir[Strings.Length(mDir) - 1] # Directories.delimiter THEN Strings.Insert(delimiter, 0, filename) END; Strings.Insert(mDir, 0, filename) END AdaptFilename; 8:8#Syntax10.Scn.Fnt VAR f: Files.File; filename: ARRAY 512 OF CHAR; BEGIN COPY(name, filename); AdaptFilename(filename); f := Files.Old(filename); IF f = NIL THEN f := Files.New(filename); Files.Register(f) END; ASSERT(f # NIL); RETURN f END GetMailFile; 8%8#Syntax10.Scn.Fnt88 BEGIN box.next := boxes; boxes := box END InsertBox; 89Y8#Syntax10.Scn.Fnt BEGIN NEW(box.mails); box.mails.key := Sentinel; box.mails.next := box.mails; box.next := NIL; COPY(boxname, box.name) END Init; 8F88  8'{p8  "8#Syntax10.Scn.Fnt   VAR R: Reader; r: Files.Rider; f: Files.File; BEGIN f := GetMailFile(box.name); OpenReader(box, R); Read(R); WHILE ~R.eof & (R.mail.key # msgno) DO Read(R) END; IF ~R.eof THEN Files.Set(r, f, R.mail.msgoffs); Texts.Load(r, T) ELSE T := NIL END END GetText; 8   8#Syntax10.Scn.Fnt VAR t: Texts.Text; BEGIN mail := box.mails.next; WHILE (mail # NIL) & (mail.key # Sentinel) & (mail.key # nr) DO mail := mail.next END; t := TextFrames.Text(""); box.GetText(nr, t); ParseMail(t, mail) END GetMailByNr; 8 8!888#Syntax10.Scn.FntPPCOPY(box.name, filename); AdaptFilename(filename); fNew := Files.New(filename); W8!8#Syntax10.Scn.Fnt box.mails88 +q8#Syntax10.Scn.Fntmm VAR boxname: ARRAY 512 OF CHAR; pos, oldPos: INTEGER; BEGIN oldPos := -1; pos := Strings.Pos(Directories.delimiter, bName, 0); WHILE pos # -1 DO oldPos := pos; pos := Strings.Pos(Directories.delimiter, bName, pos+1); END; IF oldPos # -1 THEN Strings.Extract(bName, oldPos+1, Strings.Length(bName)-oldPos-1, boxname); ELSE COPY(bName, boxname); END; AppendExtension (boxname); ASSERT( Strings.Pos(".mbx", boxname, 0) # -1); box := boxes; WHILE (box # NIL) & (boxname # box.name) DO box := box.next END; IF box = NIL THEN NEW(box); box.Init(boxname); InsertBox(box); box.Load END END GetBox; 8 8#Syntax10.Scn.Fnt VAR R: Reader; msg: NotifyMsg; BEGIN OpenReader(box, R); Read(R); WHILE ~R.eof & (R.mail.key # msgno) DO Read(R) END; INCL(R.mail.status, statDeleted); msg.box := box; msg.op := delete; msg.mail := R.mail; Viewers.Broadcast(msg) END Delete; 8  8#Syntax10.Scn.Fnt VAR R: Reader; msg: NotifyMsg; BEGIN OpenReader(box, R); Read(R); WHILE ~R.eof & (R.mail.key # msgno) DO Read(R) END; EXCL(R.mail.status, statDeleted); msg.box := box; msg.op := undelete; msg.mail := R.mail; Viewers.Broadcast(msg) END Undelete; 8 8#Syntax10.Scn.Fnt VAR R: Reader; msg: NotifyMsg; BEGIN OpenReader(box, R); Read(R); WHILE ~R.eof & (R.mail.key # msgno) DO Read(R) END; INCL(R.mail.status, statRead); msg.box := box; msg.op := stat; msg.mail := R.mail; Viewers.Broadcast(msg) END MarkRead; 8 !88 !888S/8  [8s8  KC8#Syntax10.Scn.Fnt BEGIN IF ~ConfigOK THEN done := FALSE; RETURN END; PopOpen(user, password, done); IF done THEN PopStat(nofmsgs, done); PopClose END END CheckNew; 8&88?8y8 D888:8 8#Syntax10.Scn.Fnt IF ~Get('mDir', mDir) THEN ConfigOK := FALSE; Out.String("PostOffice: mDir undefined"); Out.Ln ELSE Strings.Append('/', mDir) END; Directories.Create(mDir); NEW(Conn); popConnOpen := FALSE; Init; 8HMODULE PostOffice;   (* SMTP / POP3; MH 3.2.1994 / 1.7.94; RLI 11.03.1998 *) (* The following entries from the section "PostOffice" in the registry are used to initialize the PostOffice: Key Description __________________________________________________________________________________ POPServer Server name where POP3 server is running SMTPServer Server name where SMTP server is running Reversepath Reversepath used for SMTP-command MAIL FROM: Usually the mail address of the sender Domain Domain name used for SMTP-command HELO Domain *) IMPORT Strings, Directories, Display, Oberon, TCP, Files, Texts, Out, TextFrames, Viewers; CONST POP3Port = 110; SMTPPort = 25; Sentinel = MAX(INTEGER); statRead* = 0; statDeleted* = 1; (* mail status *) init* = 0; stat* = 1; append* = 2; delete* = 4; undelete* = 5; (* notifier ops *) less* = -1; equal* = 0; greater* = 1; HeaderTag = 0F7X; HeaderVersion = 04X; CR = 0DX; LF = 0AX; TAB = 09X; TYPE String- = POINTER TO ARRAY OF CHAR; StringList- = POINTER TO ARRAY OF String; Header* = POINTER TO HeaderDesc; HeaderDesc* = RECORD name-, body-: String; next: Header END; Mail* = POINTER TO MailDesc; MailDesc* = RECORD key*: INTEGER; sender-, subject-, reply-: String; size*: LONGINT; status-: SET; msgoffs*: LONGINT; (* offset of message in mailbox file *) next*: Mail; header-: Header; bodyOff-: LONGINT (* offset of mail body in text *) END; Mailbox* = POINTER TO MailboxDesc; MailboxDesc* = RECORD mails: Mail; (* points to sentinel, mails.next = first mail *) name-: ARRAY 32 OF CHAR; next: Mailbox END; Reader* = RECORD mail-: Mail; eof-: BOOLEAN END; NotifyMsg* = RECORD (Display.FrameMsg) box-: Mailbox; op-: INTEGER; mail-: Mail END; SendDoneNotifier* = PROCEDURE (ok: BOOLEAN; error: ARRAY OF CHAR; V: Viewers.Viewer; backup: Texts.Buffer); SendTask = POINTER TO SendTaskDesc; SendTaskDesc = RECORD (Oberon.TaskDesc) t: Texts.Text; r: Texts.Reader; v: Viewers.Viewer; b: Texts.Buffer; done: SendDoneNotifier; state: SHORTINT; conn: TCP.Connection; to, cc, bcc: StringList; curRec, nrRecOk: INTEGER (* number of recipients that were o.k. *) END; Receiver* = PROCEDURE (mail: Mail; t: Texts.Text); Sender* = POINTER TO RECORD do*: PROCEDURE (V: Viewers.Viewer; t: Texts.Text); next*: Sender; END; RetrDoneNotifier* = PROCEDURE (ok: BOOLEAN; error: ARRAY OF CHAR; nOfMsgs: LONGINT); RetrTask = POINTER TO RetrTaskDesc; RetrTaskDesc = RECORD (Oberon.TaskDesc) user, password: ARRAY 32 OF CHAR; remove: BOOLEAN; curMsg, nrMsg: LONGINT; state: SHORTINT; rec: Receiver; (* box: Mailbox; *) w: Texts.Writer; lastBuf: ARRAY 3 OF CHAR; done: RetrDoneNotifier; conn: TCP.Connection END; CompareMails* = PROCEDURE (mail1, mail2: Mail): SHORTINT; VAR ConfigOK: BOOLEAN; (* TRUE iff configuration from registry succeeded *) POPServer, SMTPServer: ARRAY 64 OF CHAR; (* host names of POP3 and SMTP server *) POPServerAdr, SMTPServerAdr: TCP.IpAdr; (* internet addresses of POP3 and SMTP server *) Reversepath, Domain: ARRAY 128 OF CHAR; mDir*: ARRAY 256 OF CHAR; Conn: TCP.Connection; rpl: ARRAY 1024 OF CHAR; boxes: Mailbox; popConnOpen-: BOOLEAN; PROCEDURE ^GetBox* (bName: ARRAY OF CHAR; VAR box: Mailbox); (* --- Utility functions --- *) PROCEDURE SearchPatt2* (T: Texts.Text; pat: ARRAY OF CHAR; pos: LONGINT): LONGINT;  VAR i, len: LONGINT; r: Texts.Reader; found: BOOLEAN; ch : CHAR; BEGIN i := 0; WHILE pat[i] # 0X DO INC(i) END; len := i - 1; i := 0; found := TRUE; Texts.OpenReader(r, T, pos); Texts.Read(r, ch); WHILE ~r.eot DO IF ch = pat[i] THEN WHILE (i < len) & found DO IF ch = pat[i] THEN INC(i); ELSE i := 0; found := FALSE END; Texts.Read(r, ch) END; IF (i = len) & (ch = pat[i]) THEN RETURN Texts.Pos(r) END; found := TRUE ELSE Texts.Read(r, ch) END END; RETURN - 1 END SearchPatt2;  PROCEDURE Get* (key: ARRAY OF CHAR; VAR elem: ARRAY OF CHAR): BOOLEAN;  PROCEDURE GetAsBuffer* (key: ARRAY OF CHAR; VAR buf: Texts.Buffer): BOOLEAN;  (* --- Reader ---*) (* PROCEDURE DumpBox(box: Mailbox); VAR cur: Mail; BEGIN Out.String("Dump of Mailbox: "); Out.String(box.name); Out.String(", Address = "); Out.Int(SYSTEM.ADR(box), 0); Out.Ln; cur := box.mails.next; WHILE cur.key # Sentinel DO Out.Int(cur.key, 0); Out.Ln; cur := cur.next; END; END DumpBox; *) PROCEDURE OpenReader* (box: Mailbox; VAR R: Reader);  VAR realBox: Mailbox; BEGIN (* Out.String("OpenReader$"); DumpBox(box); *) GetBox(box.name, realBox); R.mail := realBox.mails; R.eof := FALSE END OpenReader;  PROCEDURE Read* (VAR R: Reader);  BEGIN R.mail := R.mail.next; R.eof := R.eof OR (R.mail.key = Sentinel) END Read;  (* --- mailbox file --- *) (* mailbox file format:  *) PROCEDURE WriteHeader (VAR R: Files.Rider; m: Mail);  PROCEDURE ReadHeader (VAR R: Files.Rider; m: Mail);  (* --- string operations --- *) PROCEDURE IntToStr (x: LONGINT; VAR s: ARRAY OF CHAR);  PROCEDURE StrToInt (VAR s: ARRAY OF CHAR; VAR x: LONGINT);  PROCEDURE UpStr (VAR s: ARRAY OF CHAR);  PROCEDURE (head: Header) GetParameter* (paramName: ARRAY OF CHAR): String;  VAR pos, pos2, len: INTEGER; str, body: String; PROCEDURE IsTSpecial (ch: CHAR): BOOLEAN;  BEGIN NEW(body, Strings.Length(head.body^) + 1); COPY(head.body^, body^); UpStr(body^); Strings.Cap(paramName); len := Strings.Length(paramName); pos := Strings.Pos(paramName, body^, 0); IF pos = - 1 THEN RETURN NIL END; WHILE body[pos + len] = " " DO INC(len) END; (* this is for _broken_ mailers only *) WHILE body[pos + len] # "=" DO pos := Strings.Pos(paramName, body^, pos+1); IF pos = - 1 THEN RETURN NIL END; END; pos := pos + len + 1; WHILE body[pos] = " " DO INC(pos) END; (* this is for _broken_ mailers only *) IF body[pos] = '"' THEN pos2 := Strings.Pos('"', body^, pos + 1); INC(pos) ELSE pos2 := pos; WHILE (body[pos2] >= " ") & (ORD(body[pos2]) <= 127) & ~IsTSpecial(body[pos2]) DO INC(pos2) END END; NEW(str, pos2 - pos + 2); Strings.Extract(head.body^, pos, pos2 - pos, str^); RETURN str END GetParameter;  PROCEDURE (head: Header) GetHeaderField* (fieldName: ARRAY OF CHAR): Header;  PROCEDURE BuildHeader* (VAR r: Texts.Reader; VAR header: Header);  PROCEDURE ParseMail* (T: Texts.Text; VAR mail: Mail);  VAR r: Texts.Reader; aux: Header; buf: Texts.Buffer; pos, pos2, start, end: INTEGER; BEGIN NEW(buf); Texts.OpenBuf (buf); NEW(mail.header); Texts.OpenReader(r, T, 0); BuildHeader(r, mail.header); (* --- extract special header fields *) aux := mail.header.GetHeaderField("FROM"); IF aux # NIL THEN pos := Strings.Pos("<", aux.body^, 0); IF (pos = - 1) OR (pos = 0) THEN pos := Strings.Length(aux.body^) + 1 END; NEW(mail.sender, pos); COPY(aux.body^, mail.sender^); mail.sender[pos-1] := 0X END; IF mail.subject = NIL THEN NEW(mail.subject, 13); COPY("", mail.subject^) END; aux := mail.header.GetHeaderField("RETURN-PATH"); IF aux = NIL THEN aux := mail.header.GetHeaderField("REPLY-TO"); IF aux = NIL THEN aux := mail.header.GetHeaderField("FROM") END END; IF aux # NIL THEN start := Strings.Pos("<", aux.body^, 0); end := Strings.Pos(">", aux.body^, 0); IF (start = -1) OR (end = -1) THEN NEW(mail.reply, Strings.Length(aux.body^)+1); COPY(aux.body^, mail.reply^); ELSE NEW(mail.reply, end-start); Strings.Extract(aux.body^, start+1, end-start-1, mail.reply^) END; ELSE NEW(mail.reply, 2); COPY("", mail.reply^); END; aux := mail.header.GetHeaderField("SUBJECT"); IF aux # NIL THEN mail.subject := aux.body END; mail.bodyOff := Texts.Pos(r); mail.size := T.len END ParseMail;  (* --- TCP / SMTP / POP3 routines --- *) PROCEDURE WriteLine (line: ARRAY OF CHAR);  PROCEDURE ReadLine (VAR line: ARRAY OF CHAR);  PROCEDURE Send (cmd, arg: ARRAY OF CHAR);  PROCEDURE Send2 (cmd, arg1, arg2: ARRAY OF CHAR);  PROCEDURE WaitPOPReply (VAR ok: BOOLEAN);  BEGIN ReadLine(rpl); ok := rpl[0] = "+" END WaitPOPReply;  PROCEDURE WaitSMTPReply (VAR code: INTEGER);  PROCEDURE Abort;  PROCEDURE PopOpen (user, pwd: ARRAY OF CHAR; VAR done: BOOLEAN);  PROCEDURE PopClose;  PROCEDURE PopStat (VAR nofmsgs: LONGINT; VAR done: BOOLEAN);  PROCEDURE PopDelete (msgno: LONGINT; VAR done: BOOLEAN);  PROCEDURE SendHandler;  PROCEDURE SendInBackground* (T: Texts.Text; to, cc, bcc: StringList; doneProc: SendDoneNotifier; v: Viewers.Viewer; b: Texts.Buffer);  PROCEDURE MakeMailText* (T: Texts.Text; rcpts, cc, bcc, subject, attachments: ARRAY OF CHAR);  (* --- Mailbox operations --- *) PROCEDURE AppendExtension* (VAR path: ARRAY OF CHAR);  PROCEDURE AdaptFilename (VAR filename: ARRAY OF CHAR);  PROCEDURE GetMailFile (name: ARRAY OF CHAR): Files.File;  PROCEDURE InsertBox (box: Mailbox);  PROCEDURE (box: Mailbox) Init (boxname: ARRAY OF CHAR);  PROCEDURE (box: Mailbox) InsertMailStruct (new: Mail; end: BOOLEAN);  (* sets new.key *) VAR m: Mail; maxKey: INTEGER; BEGIN m := box.mails; maxKey := -1; WHILE m.next.key # Sentinel DO IF m.next.key > maxKey THEN maxKey := m.next.key; END; m := m.next END; IF maxKey = -1 THEN new.key := 1 ELSE new.key := maxKey + 1 END; IF end THEN new.next := box.mails; m.next := new ELSE new.next := box.mails.next; box.mails.next := new; END; END InsertMailStruct;  PROCEDURE (box: Mailbox) AppendMail* (m: Mail; msg: Texts.Text);  (* sets m.msgoffs *) VAR R, R1: Files.Rider; f: Files.File; nMsg: NotifyMsg; BEGIN box.InsertMailStruct(m, FALSE); f := GetMailFile(box.name); Files.Set(R, f, Files.Length(f)); WriteHeader(R, m); R1 := R; (* for fixup of length of text block *) Files.WriteLInt(R, 0); m.msgoffs := Files.Pos(R); Texts.Store(R, msg); Files.WriteLInt(R1, Files.Pos(R) - m.msgoffs); (* length of text block *) Files.Close(f); nMsg.box := box; nMsg.op := append; nMsg.mail := m; Viewers.Broadcast(nMsg) END AppendMail;  PROCEDURE (box: Mailbox) GetText* (msgno: INTEGER; T: Texts.Text);  PROCEDURE (box: Mailbox) GetMailByNr* (nr: INTEGER; VAR mail: Mail);  PROCEDURE (box: Mailbox) Load;  VAR m: Mail; size: LONGINT; key: INTEGER; tag: CHAR; f: Files.File; r: Files.Rider; t: Texts.Text; BEGIN t := TextFrames.Text(""); f := GetMailFile(box.name); Files.Set(r, f, 0); key := 1; Files.Read(r, tag); WHILE ~r.eof & (tag = HeaderTag) DO NEW(m); ReadHeader(r, m); m.key := key; Files.ReadLInt(r, size); m.msgoffs := Files.Pos(r); Files.Set(r, f, Files.Pos(r) + size); box.InsertMailStruct(m, TRUE); box.GetText (key, t); ParseMail(t, m); Texts.Delete(t, 0, t.len); INC(key); Files.Read(r, tag) END END Load;  PROCEDURE (box: Mailbox) Store*;  VAR msgR: Files.Rider; textblockSize: LONGINT; ch: CHAR; mR: Reader; f, fNew: Files.File; r: Files.Rider; prev: Mail; msg: NotifyMsg; filename: ARRAY 512 OF CHAR; BEGIN f := GetMailFile(box.name); Create new file, which is currently temporary and registered _after_ mailbox is written Files.Set(r, fNew, 0); prev := Sentinel; OpenReader(box, mR); Read(mR); WHILE ~mR.eof DO IF ~(statDeleted IN mR.mail.status) THEN WriteHeader(r, mR.mail); Files.Set(msgR, f, mR.mail.msgoffs - 4); Files.ReadLInt(msgR, textblockSize); Files.WriteLInt(r, textblockSize); WHILE textblockSize > 0 DO Files.Read(msgR, ch); Files.Write(r, ch); DEC(textblockSize) END; prev := mR.mail ELSE IF prev = box.mails THEN box.mails := box.mails.next ELSE prev.next := mR.mail.next END END; Read(mR) END; Files.Register(fNew); box.Init(box.name); box.Load; msg.box := box; msg.op := init; msg.mail := NIL; Viewers.Broadcast(msg) END Store;  PROCEDURE GetBox* (bName: ARRAY OF CHAR; VAR box: Mailbox);  PROCEDURE (box: Mailbox) Delete* (msgno: INTEGER);  PROCEDURE (box: Mailbox) Undelete* (msgno: INTEGER);  PROCEDURE (box: Mailbox) MarkRead* (msgno: INTEGER);  PROCEDURE (src: Mailbox) Move* (dst: Mailbox; msgno: INTEGER);  VAR mail, mail2: Mail; t: Texts.Text; BEGIN src.GetMailByNr(msgno, mail); t := TextFrames.Text(""); src.GetText(msgno, t); NEW(mail2); mail2^ := mail^; dst.AppendMail (mail2, t); src.Delete(msgno) END Move;  PROCEDURE (src: Mailbox) Copy* (dst: Mailbox; msgno: INTEGER);  VAR mail, mail2: Mail; t: Texts.Text; BEGIN src.GetMailByNr(msgno, mail); t := TextFrames.Text(""); src.GetText(msgno, t); NEW(mail2); mail2^ := mail^; dst.AppendMail(mail2, t) END Copy;  PROCEDURE RetrHandler;  CONST bufSize = 8096; VAR task: RetrTask; done: BOOLEAN; t: Texts.Text; m: Mail; argument: ARRAY 64 OF CHAR; buffer: ARRAY bufSize OF CHAR; i, n: LONGINT; msg: NotifyMsg; BEGIN task := Oberon.CurTask(RetrTask); done := TRUE; Conn := task.conn; CASE task.state OF 0: PopOpen(task.user, task.password, done); task.password := ""; (* delete it from memory as fast as possible *) IF done THEN task.state := 1 ELSE Oberon.Remove(task); task.done(FALSE, "Could not login to POP-Server", 0); popConnOpen := FALSE; END; | 1: PopStat(task.nrMsg, done); IF ~done THEN Oberon.Remove(task); task.done(FALSE, "Could not get number of messages", 0); popConnOpen := FALSE; ELSE IF task.nrMsg = 0 THEN PopClose; Oberon.Remove(task); task.done(TRUE, "No messages on server", 0); popConnOpen := FALSE; ELSE task.curMsg := 1; task.state := 2 END; END; | 2: (* Send RETR command *) IntToStr(task.curMsg, argument); Send("RETR ", argument); WaitPOPReply(done); IF done THEN task.state := 3; Texts.OpenWriter(task.w); ELSE PopClose; Oberon.Remove(task); task.done(FALSE, "Could not retrieve message", task.curMsg-1); popConnOpen := FALSE; END; | 3: (* Receive data *) n := TCP.Available(Conn); IF n > bufSize THEN n := bufSize; END; IF n > 0 THEN TCP.ReadBytes(Conn, buffer, 0, n); END; FOR i := 0 TO n-1 DO IF buffer[i] # LF THEN Texts.Write(task.w, buffer[i]); END; task.lastBuf[0] := task.lastBuf[1]; task.lastBuf[1] := task.lastBuf[2]; task.lastBuf[2] := buffer[i]; END; IF (task.lastBuf[0] = ".") & (task.lastBuf[1] = CR) & (task.lastBuf[2] = LF) THEN task.state := 4; task.lastBuf[0] := "X"; END; | 4: (* Parse mail *) t := TextFrames.Text(""); Texts.Append(t, task.w.buf); Texts.Delete(t, t.len - 3, t.len); NEW(m); ParseMail(t, m); task.rec(m, t); IF task.remove THEN PopDelete(task.curMsg, done) END; INC(task.curMsg); (* msg.box := task.box; msg.op := append; msg.mail := m; Viewers.Broadcast(msg); *) (* task.box.Store; *) IF task.curMsg > task.nrMsg THEN task.state := 5; ELSE task.state := 2; END; | 5: (* Close connection *) PopClose; Oberon.Remove(task); task.done(TRUE, "New mails", task.nrMsg); popConnOpen := FALSE; END; task.conn := Conn; END RetrHandler;  PROCEDURE FetchNew* (user, password: ARRAY OF CHAR; rec: Receiver; remove: BOOLEAN; done: RetrDoneNotifier);  VAR rTask: RetrTask; BEGIN IF popConnOpen THEN done(FALSE, "A connection to the pop server still exists.", 0); ELSE popConnOpen := TRUE; NEW(rTask); rTask.rec := rec; rTask.remove := remove; rTask.handle := RetrHandler; NEW(rTask.conn); COPY(user, rTask.user); COPY(password, rTask.password); rTask.done := done; Oberon.Install(rTask) END; END FetchNew;  PROCEDURE CheckNew* (user, password: ARRAY OF CHAR; VAR nofmsgs: LONGINT; VAR done: BOOLEAN);  PROCEDURE (box: Mailbox) Sort* (comp: CompareMails; desc: BOOLEAN);  VAR swapped: BOOLEAN; cur, prev, next: Mail; msg: NotifyMsg; sortCriterion: INTEGER; BEGIN IF box.mails = box.mails.next THEN RETURN END; (* no mails in box *) IF desc THEN sortCriterion := less ELSE sortCriterion := greater END; REPEAT swapped := FALSE; cur := box.mails.next; prev := box.mails; next := cur.next; WHILE next # box.mails DO IF comp(cur, next) = sortCriterion THEN prev.next := next; cur.next := next.next; next.next := cur; swapped := TRUE END; prev := prev.next; cur := prev.next; next := cur.next END UNTIL ~swapped; msg.box := box; msg.op := init; msg.mail := NIL; Viewers.Broadcast(msg) END Sort;  (* --- Importing mails ---*) PROCEDURE SearchPatt (t: Texts.Text; spos: LONGINT): LONGINT;  VAR pos: LONGINT; r: Texts.Reader; ch: CHAR; ch2: CHAR; BEGIN pos := SearchPatt2(t, "From ", spos); IF pos = - 1 THEN RETURN - 1 END; Texts.OpenReader(r, t, pos - 1); Texts.Read(r, ch); Texts.OpenReader(r, t, pos - 6); Texts.Read(r, ch2); IF (ch = ":") OR (ch2 # 0DX) & (ch2 # 0AX) THEN RETURN SearchPatt(t, pos) ELSE RETURN pos END END SearchPatt;  PROCEDURE ImportMailbox* (filename: ARRAY OF CHAR; into: ARRAY OF CHAR; VAR done: BOOLEAN);  VAR f: Files.File; buf: Texts.Buffer; msg, t: Texts.Text; start, end: LONGINT; mail: Mail; box: Mailbox; exit: BOOLEAN; BEGIN GetBox(into, box); exit := FALSE; f := Files.Old(filename); IF (f = NIL) OR (Files.Length(f) < 6) THEN done := FALSE; RETURN END; NEW(buf); Texts.OpenBuf(buf); t := TextFrames.Text(filename); msg := TextFrames.Text(""); start := 0; end := SearchPatt(t, 1) - 5; WHILE ~exit DO IF end = t.len THEN exit := TRUE END; Texts.Save(t, start, end, buf); Texts.Delete(msg, 0, msg.len); Texts.Append(msg, buf); NEW(mail); mail.size := msg.len; ParseMail(msg, mail); box.AppendMail(mail, msg); start := end; end := SearchPatt(t, start + 1) - 5; IF end = - 6 THEN end := t.len END END; done := TRUE END ImportMailbox;  PROCEDURE Init;  VAR res: INTEGER; MailfileName, dummy: ARRAY 128 OF CHAR; BEGIN ConfigOK := TRUE; IF Get('POPServer', POPServer) THEN TCP.HostByName(POPServer, POPServerAdr, res); IF res # TCP.Done THEN ConfigOK := FALSE; Out.String("Could not find host "); Out.String(POPServer); Out.Ln END ELSE ConfigOK := FALSE; Out.String("PostOffice: POPServer host undefined"); Out.Ln END; IF Get('SMTPServer', SMTPServer) THEN TCP.HostByName(SMTPServer, SMTPServerAdr, res); IF res # TCP.Done THEN ConfigOK := FALSE; Out.String("Could not find host "); Out.String(SMTPServer); Out.Ln END ELSE ConfigOK := FALSE; Out.String("PostOffice: SMTPServer undefined"); Out.Ln END; IF ~Get('Reversepath', Reversepath) THEN ConfigOK := FALSE; Out.String("PostOffice: Reversepath undefined"); Out.Ln END; IF ~Get('Domain', Domain) THEN ConfigOK := FALSE; Out.String("PostOffice: Domain undefined"); Out.Ln END; IF Get('inBox', dummy) THEN Strings.Append(dummy, MailfileName) ELSE ConfigOK := FALSE; Out.String("PostOffice: Default inbox undefined"); Out.Ln END END Init;  BEGIN END PostOffice.