MODULE IMAP;
IMPORT DNS, IP, Streams, TCP, Strings, KernelLog, Classes := TFClasses, IMAPUtilities;
CONST
DEBUG = FALSE;
DEBUGLEVEL = 1;
DEAD* = -1;
NOAUTH* = 0;
AUTH* = 1;
SELECT* = 2;
LITERAL* = 0;
STRING* = 1;
LIST* = 2;
ATOM* = 3;
HEADER* = 4;
OK* = 0;
BAD = 1;
READBACKERR = 2;
SENDERR = 3;
TYPE
String = Strings.String;
Entry* = POINTER TO RECORD
data-: String;
list-: Classes.List;
type-: LONGINT;
command-: ARRAY 40 OF CHAR;
number-: LONGINT
END;
Connection* = OBJECT
VAR
in: Streams.Reader;
out: Streams.Writer;
tag: LONGINT;
state: LONGINT;
tagString: ARRAY 80 OF CHAR;
buffer: ARRAY 80 OF CHAR;
connection : TCP.Connection;
capability: Classes.List;
ret: Classes.List;
first: BOOLEAN;
logout: BOOLEAN;
PROCEDURE &Init* (VAR host: ARRAY OF CHAR; port: LONGINT; VAR result: LONGINT);
VAR
ip: IP.Adr;
res: LONGINT;
ret: Classes.List;
BEGIN
logout := FALSE;
first := TRUE;
state := DEAD;
IF DEBUG THEN KernelLog.String("WELCOME TO IMAP"); KernelLog.Ln END;
DNS.HostByName(host, ip, res);
IF res = DNS.Ok THEN
NEW(connection);
connection.Open(TCP.NilPort, ip, port, res);
IF res = TCP.Ok THEN
IF DEBUG THEN KernelLog.String("connected..."); KernelLog.Ln; END;
Streams.OpenReader(in, connection.Receive);
Streams.OpenWriter(out, connection.Send);
state := NOAUTH;
IF ReadResponse(ret) THEN
IF CheckResultCode(ret) THEN
result := OK;
ELSE
IF DEBUG THEN KernelLog.String("STATUS FAILURE"); KernelLog.Ln END;
state := DEAD;
result := BAD
END;
ELSE
IF DEBUG THEN KernelLog.String("CONNECT FAILURE OR BYE"); KernelLog.Ln END;
state := DEAD;
result := BAD
END
END;
ELSE
IF DEBUG THEN KernelLog.String("DNS FAILURE"); KernelLog.Ln END;
state := DEAD;
result := BAD
END
END Init;
PROCEDURE MakeOneArgumentCommand(command: ARRAY OF CHAR; VAR argument: ARRAY OF CHAR): String;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
arg, string: Strings.String;
BEGIN
NEW(buffer, 16);
w := buffer.GetWriter();
w.String(command);
w.String(" ");
arg := IMAPUtilities.NewString(argument);
IMAPUtilities.MakeQuotedString(arg);
w.String(arg^);
string := buffer.GetString();
RETURN string;
END MakeOneArgumentCommand;
PROCEDURE MakeTwoArgumentCommand(command: ARRAY OF CHAR; VAR argument1, argument2: ARRAY OF CHAR): String;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
arg, string: String;
BEGIN
NEW(buffer, 16);
w := buffer.GetWriter();
w.String(command);
w.String(" ");
arg := IMAPUtilities.NewString(argument1);
IMAPUtilities.MakeQuotedString(arg);
w.String(arg^);
w.String(" ");
arg := IMAPUtilities.NewString(argument2);
IMAPUtilities.MakeQuotedString(arg);
w.String(arg^);
string := buffer.GetString();
RETURN string;
END MakeTwoArgumentCommand;
PROCEDURE Login*(username: ARRAY OF CHAR; password: ARRAY OF CHAR):LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF state # NOAUTH THEN RETURN BAD END;
string := MakeTwoArgumentCommand("LOGIN", username, password);
RETURN SendToIMAPServer(string^,ret, AUTH, NOAUTH )
END Login;
PROCEDURE Logout*():LONGINT;
VAR
string: Strings.String;
value: LONGINT;
BEGIN {EXCLUSIVE}
logout := TRUE;
string := Strings.NewString("LOGOUT");
value := SendToIMAPServer(string^,ret, DEAD, DEAD);
IF connection # NIL THEN
connection.Close;
connection := NIL
END;
RETURN value
END Logout;
PROCEDURE GetCurrentState*():LONGINT;
BEGIN {EXCLUSIVE}
RETURN state
END GetCurrentState;
PROCEDURE Expunge*(VAR ret: Classes.List): LONGINT;
BEGIN {EXCLUSIVE}
RETURN SendToIMAPServer("EXPUNGE",ret,SELECT,SELECT)
END Expunge;
PROCEDURE Create*(mailbox: ARRAY OF CHAR; VAR ret: Classes.List): LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeOneArgumentCommand("CREATE", mailbox);
RETURN SendToIMAPServer(string^,ret,state,state)
END Create;
PROCEDURE Delete*(mailbox: ARRAY OF CHAR; VAR ret: Classes.List): LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeOneArgumentCommand("DELETE", mailbox);
RETURN SendToIMAPServer(string^,ret,state,state)
END Delete;
PROCEDURE Rename*(from, to: ARRAY OF CHAR; VAR ret: Classes.List): LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeTwoArgumentCommand("RENAME",from, to);
RETURN SendToIMAPServer(string^,ret,state,state)
END Rename;
PROCEDURE Select*(mailbox:ARRAY OF CHAR; VAR ret: Classes.List):LONGINT;
VAR
string: Strings.String;
BEGIN
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeOneArgumentCommand("SELECT", mailbox);
RETURN SendToIMAPServer(string^,ret,SELECT, state)
END Select;
PROCEDURE Examine*(mailbox: ARRAY OF CHAR): LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeOneArgumentCommand("EXAMINE", mailbox);
RETURN SendToIMAPServer(string^, ret, SELECT, state)
END Examine;
PROCEDURE List*(refName, mailbox: ARRAY OF CHAR; VAR ret: Classes.List):LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF(state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeTwoArgumentCommand("LIST", refName, mailbox);
RETURN SendToIMAPServer(string^, ret, state, state)
END List;
PROCEDURE Subscribe*(mailbox: ARRAY OF CHAR): LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeOneArgumentCommand("SUBSCRIBE", mailbox);
RETURN SendToIMAPServer(string^, ret, state, state)
END Subscribe;
PROCEDURE Unsubscribe*(mailbox: ARRAY OF CHAR): LONGINT;
VAR
string: Strings.String;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
string := MakeOneArgumentCommand("UNSUBSCRIBE", mailbox);
RETURN SendToIMAPServer(buffer, ret, state, state)
END Unsubscribe;
PROCEDURE Append*(VAR mailbox, message: ARRAY OF CHAR; VAR ret: Classes.List): LONGINT;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
path, string: String;
i: LONGINT;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
NEW(buffer, 16);
w := buffer.GetWriter();
w.String("APPEND ");
path := IMAPUtilities.NewString(mailbox);
IMAPUtilities.MakeQuotedString(path);
w.String(path^);
i := IMAPUtilities.StringLength(message);
w.String(" {");
w.Int(i,0);
w.String("}");
string := buffer.GetString();
RETURN SendContinuedCommand(string^, message, ret, state, state);
END Append;
PROCEDURE UIDCopy*(what, to: ARRAY OF CHAR; VAR ret: Classes.List): LONGINT;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
path,string: Strings.String;
BEGIN {EXCLUSIVE}
IF (state # SELECT) THEN RETURN BAD END;
NEW(buffer, 16);
w := buffer.GetWriter();
w.String("UID COPY ");
w.String(what);
w.String(" ");
path := IMAPUtilities.NewString(to);
IMAPUtilities.MakeQuotedString(path);
w.String(path^);
string := buffer.GetString();
RETURN SendToIMAPServer(string^,ret,state,state)
END UIDCopy;
PROCEDURE Close*():LONGINT;
BEGIN {EXCLUSIVE}
COPY("CLOSE",buffer);
RETURN SendToIMAPServer("CLOSE",ret,AUTH,state)
END Close;
PROCEDURE Noop*(VAR ret: Classes.List):LONGINT;
BEGIN {EXCLUSIVE}
RETURN SendToIMAPServer("NOOP",ret,state,state)
END Noop;
PROCEDURE Status*(VAR mailbox, items: ARRAY OF CHAR; VAR ret: Classes.List):LONGINT;
VAR
string: Strings.String;
buffer: Strings.Buffer;
w: Streams.Writer;
BEGIN {EXCLUSIVE}
IF (state # AUTH) & (state # SELECT) THEN RETURN BAD END;
NEW(buffer, 16);
w := buffer.GetWriter();
string := MakeOneArgumentCommand("STATUS", mailbox);
w.String(string^);
w.String(" ");
w.String(items);
string := buffer.GetString();
RETURN SendToIMAPServer(string^,ret,state,state)
END Status;
PROCEDURE Search*(VAR criteria: ARRAY OF CHAR; VAR ret: Classes.List): LONGINT;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
string: String;
BEGIN {EXCLUSIVE}
IF state # SELECT THEN RETURN BAD END;
NEW(buffer, 16);
w := buffer.GetWriter();
w.String("SEARCH ");
w.String(criteria);
string := buffer.GetString();
RETURN SendToIMAPServer(string^,ret,SELECT,state);
END Search;
PROCEDURE Fetch*(set: ARRAY OF CHAR; items: ARRAY OF CHAR; VAR ret: Classes.List):LONGINT;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
string: String;
BEGIN {EXCLUSIVE}
IF state # SELECT THEN RETURN BAD END;
NEW(buffer, 16);
w := buffer.GetWriter();
w.String("FETCH ");
w.String(set);
w.String(" ");
w.String(items);
string := buffer.GetString();
RETURN SendToIMAPServer(string^,ret,SELECT,SELECT)
END Fetch;
PROCEDURE UIDFetch*(set: ARRAY OF CHAR; items: ARRAY OF CHAR; VAR ret:Classes.List): LONGINT;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
string: String;
BEGIN {EXCLUSIVE}
IF state # SELECT THEN RETURN BAD END;
NEW(buffer, 16);
w := buffer.GetWriter();
w.String("UID FETCH ");
w.String(set);
w.String(" ");
w.String(items);
string := buffer.GetString();
RETURN SendToIMAPServer(string^, ret, SELECT, state);
END UIDFetch;
PROCEDURE UIDStore*(set: ARRAY OF CHAR; flags: ARRAY OF CHAR; plus: BOOLEAN; VAR ret: Classes.List):LONGINT;
VAR
buffer: Strings.Buffer;
w: Streams.Writer;
string: String;
BEGIN {EXCLUSIVE}
IF state # SELECT THEN RETURN BAD END;
NEW(buffer, 16);
w := buffer.GetWriter();
w.String("UID STORE ");
w.String(set);
IF plus THEN
w.String(" +FLAGS (");
ELSE
w.String(" -FLAGS (");
END;
w.String(flags);
w.String(")");
string := buffer.GetString();
RETURN SendToIMAPServer(string^,ret,SELECT,state)
END UIDStore;
PROCEDURE SendIMAPCommand(command: ARRAY OF CHAR): BOOLEAN;
VAR buffer: ARRAY 10 OF CHAR;
BEGIN
INC(tag);
tagString := "AOS";
Strings.IntToStr(tag,buffer);
Strings.Append(tagString,buffer);
out.String(tagString);out.String(" ");
out.String(command);out.Ln();
out.Update();
IF DEBUG THEN
KernelLog.String("IMAP: sending to server: <"); KernelLog.String(tagString);
KernelLog.String(" "); KernelLog.String(command); KernelLog.String(">"); KernelLog.Ln
END;
RETURN out.res = Streams.Ok;
END SendIMAPCommand;
PROCEDURE CheckResultCode(list: Classes.List):BOOLEAN;
VAR ent: Entry; entP:ANY;
BEGIN
entP := list.GetItem(list.GetCount()-1);ent := entP(Entry);
RETURN ent.command = "OK";
END CheckResultCode;
PROCEDURE SendContinuedCommand(VAR command, continuation: ARRAY OF CHAR; VAR ret: Classes.List; newstate, failstate: LONGINT): LONGINT;
BEGIN
IF state = DEAD THEN RETURN BAD END;
IF ~SendIMAPCommand(command) THEN
RETURN SENDERR;
END;
IF ~ReadResponse(ret) THEN
RETURN READBACKERR;
END;
IF ~SendContinuation(continuation) THEN
RETURN SENDERR;
END;
IF ~ReadResponse(ret) THEN
RETURN READBACKERR;
END;
IF CheckResultCode(ret) THEN
state := newstate;
RETURN OK;
END;
state := failstate;
RETURN BAD;
END SendContinuedCommand;
PROCEDURE SendContinuation(VAR continuation: ARRAY OF CHAR): BOOLEAN;
BEGIN
out.String(continuation);
out.Ln();
out.Update();
RETURN out.res = Streams.Ok;
END SendContinuation;
PROCEDURE SendToIMAPServer(command: ARRAY OF CHAR; VAR ret: Classes.List; newstate,failstate: LONGINT):LONGINT;
BEGIN
IF state = DEAD THEN RETURN BAD END;
IF SendIMAPCommand(command) THEN
IF ReadResponse(ret) THEN
IF CheckResultCode(ret) THEN
state := newstate;
IF DEBUG THEN KernelLog.String(" SUCCESS! state: "); KernelLog.Int(state,4); KernelLog.Ln;
IF DEBUGLEVEL > 1 THEN DBGList(ret) END;
END;
RETURN OK;
ELSE
state := failstate;
IF DEBUG THEN KernelLog.String(" FAILED GetResultCode! state: "); KernelLog.Int(state,4); KernelLog.Ln; END;
RETURN BAD
END;
ELSE
state := failstate;
IF DEBUG THEN KernelLog.String(" FAILED ReadBack! state: "); KernelLog.Int(state,4); KernelLog.Ln; END;
RETURN READBACKERR
END;
ELSE
state := failstate;
IF DEBUG THEN KernelLog.String(" FAILED Send state: "); KernelLog.Int(state,4); KernelLog.Ln; END;
RETURN SENDERR;
END
END SendToIMAPServer;
PROCEDURE ReadResponse(VAR ret: Classes.List): BOOLEAN;
VAR
buffer, tag: String;
i: LONGINT;
BEGIN
IF state = DEAD THEN RETURN FALSE END;
NEW(ret);
REPEAT
in.SkipWhitespace();
IF in.res # Streams.Ok THEN
IF DEBUG THEN KernelLog.String("IMAP: ReadResponse: Read failed at SkipWhiteSpace") END;
RETURN FALSE
END;
IF ~ ReadToken(tag) THEN
IF DEBUG THEN KernelLog.String("IMAP: ReadResponse: Read failed at ReadToken") END;
RETURN FALSE;
END;
IF DEBUG THEN KernelLog.String("tag is: "); KernelLog.String(tag^); KernelLog.Ln END;
IF (tag^ # "+") THEN
IF ~ ReadUToken(buffer) THEN
IF DEBUG THEN KernelLog.String("IMAP: ReadResponse: Read failed at ReadUtoken") END;
RETURN FALSE;
END;
IF (buffer[0] >="0") & (buffer[0] <= "9") THEN
Strings.StrToInt(buffer^,i);
IF ~ ReadUToken(buffer) THEN
IF DEBUG THEN KernelLog.String("IMAP: ReadResponse: Read failed at ReadUtoken2") END;
RETURN FALSE;
END;
IF ~ Parse(buffer^,i,TRUE,ret) THEN
IF DEBUG THEN KernelLog.String("IMAP: ReadResponse: Parse failed 1") END;
RETURN FALSE;
END;
ELSE
IF ~ Parse(buffer^,-1,FALSE,ret) THEN RETURN FALSE END
END;
ELSE
in.SkipLn();
RETURN TRUE;
END;
UNTIL (tag^ = tagString) OR ((tagString = "") & (tag^ = "*") & (buffer^="OK")) OR (state = DEAD);
RETURN TRUE;
END ReadResponse;
PROCEDURE Parse(command: ARRAY OF CHAR; num:LONGINT; numflag:BOOLEAN;
VAR ret:Classes.List):BOOLEAN;
VAR
dummy: BOOLEAN;
i: LONGINT;
list: Classes.List;
header, ent: Entry;
content: Classes.List;
ent2, ent3: Entry;
BEGIN
IF DEBUG THEN KernelLog.String("IMAP: Parse: command is "); KernelLog.String(command); KernelLog.Ln END;
i := 0;
NEW(header);
IF numflag THEN
header.number := num
END;
header.type := HEADER;
header.data := NIL;
header.list := NIL;
COPY(command,header.command);
ret.Add(header);
IF ~ReadResponseCode() THEN RETURN FALSE END;
IF command = "CAPABILITY" THEN
WHILE ~in.EOLN() DO
NEW(ent);
IF ~ReadAtom(ent.data) THEN RETURN FALSE END;
IF DEBUG THEN KernelLog.String("IMAP: Parse: Capability: "); KernelLog.String(ent.data^); KernelLog.Ln END;
in.SkipBytes(1);
ent.type := ATOM;
ret.Add(ent);
capability := ret
END;
ELSIF command = "EXISTS" THEN
RETURN TRUE;
ELSIF command = "EXPUNGE" THEN RETURN TRUE;
ELSIF command = "FETCH" THEN
NEW(list);
IF ~ReadList(list) THEN RETURN FALSE END;
header.list := list;
ELSIF command = "FLAGS" THEN
NEW(list);
IF ~ReadList(list) THEN RETURN FALSE END;
header.list := list;
ELSIF command = "RECENT" THEN
RETURN TRUE;
ELSIF command = "STATUS" THEN
IF in.Peek() = 22X THEN
IF ~ReadQuotedString(header.data) THEN RETURN FALSE; END;
ELSE
IF ~ReadAtom(header.data) THEN RETURN FALSE; END;
END;
NEW(list);
IF ~ReadList(list) THEN RETURN FALSE END;
header.list := list;
IF DEBUG THEN DBGList(list) END;
ELSIF (command = "SORT") OR (command = "SEARCH") THEN
NEW(list);
IF in.Peek() # 0DX THEN
IF ~ReadNumberEnumeration(list) THEN HALT(333); RETURN FALSE END;
END;
header.list := list;
ELSIF command = "BAD" THEN
IF DEBUG THEN KernelLog.String("IMAP: server said: BAD"); KernelLog.Ln END;
dummy := NextLine();
RETURN FALSE;
ELSIF command = "BYE" THEN
IF ~logout THEN
state := DEAD;
IF DEBUG THEN KernelLog.String("IMAP: kicked out by server"); KernelLog.Ln END;
connection.Close; connection := NIL;
RETURN FALSE;
END;
ELSIF (command = "LIST") THEN
NEW(list);
IF ~ReadList(list) THEN RETURN FALSE END;
NEW(ent);
ent.type := LIST;
ent.list := list;
NEW(content);
content.Add(ent);
in.SkipWhitespace();
NEW(ent2);
IF in.Peek() = 22X THEN
IF ~ReadQuotedString(ent2.data) THEN RETURN FALSE END;
ELSIF in.Peek() = "{" THEN
IF ~ReadLiteral(ent2.data) THEN RETURN FALSE END;
ELSE
IF ~ReadAtom(ent2.data) THEN RETURN FALSE END;
END;
content.Add(ent2);
in.SkipWhitespace();
NEW(ent3);
IF in.Peek() = 22X THEN
IF ~ReadQuotedString(ent3.data) THEN RETURN FALSE END;
ELSIF in.Peek() = "{" THEN
IF ~ReadLiteral(ent3.data) THEN RETURN FALSE END;
ELSE
IF ~ReadAtom(ent3.data) THEN RETURN FALSE END;
END;
content.Add(ent3);
header.list := content;
header.type := LIST;
IF DEBUG THEN DBGList(content) END;
ELSIF (command = "LSUB") THEN
KernelLog.String(command);KernelLog.String(": Not yet implemented");KernelLog.Ln;
HALT(999);
ELSIF (command = "PREAUTH") THEN
state := AUTH;
ELSIF (command = "OK") THEN
ELSIF (command = "NO") THEN
IF DEBUG THEN KernelLog.String("IMAP: server said: NO"); KernelLog.Ln END;
RETURN FALSE;
ELSE
KernelLog.String("IMAP: unknown keyword <<"); KernelLog.String(command);
KernelLog.String(">>. This is a IMAP parser error..."); KernelLog.Ln;
RETURN FALSE
END;
IF ~NextLine() THEN RETURN FALSE END;
RETURN TRUE;
END Parse;
PROCEDURE ReadResponseCode():BOOLEAN;
VAR
command,argument: String;
argi: LONGINT;
list,ret: Classes.List;
ent: Entry;
BEGIN
in.SkipSpaces();
IF in.res # Streams.Ok THEN
IF DEBUG THEN KernelLog.String("IMAP: ReadResponseCode failed"); KernelLog.Ln; END;
RETURN FALSE END;
IF in.Peek() # "[" THEN
IF DEBUG THEN KernelLog.String("No response code available");KernelLog.Ln; END;
RETURN TRUE;
ELSE
in.SkipBytes(1);
IF in.res # Streams.Ok THEN RETURN FALSE END;
IF ~ReadPToken(command) THEN
IF DEBUG THEN KernelLog.String("IMAP: ReadResponseCode: ReadPToken failed "); KernelLog.Ln; END;
RETURN FALSE
END;
ASSERT(command#NIL,1011);
in.SkipWhitespace();
IF in.res # Streams.Ok THEN RETURN FALSE END;
IF command^ = "ALERT" THEN
in.SkipBytes(1);
in.SkipWhitespace();
IF in.res # Streams.Ok THEN RETURN FALSE END;
IF ~ReadText(argument) THEN RETURN FALSE END;
ELSIF command^ = "NEWNAME" THEN
in.SkipLn();
ELSIF command^ = "PARSE" THEN
in.SkipBytes(1);
in.SkipWhitespace();
IF ~ReadText(argument) THEN RETURN FALSE END;
ELSIF command^ = "PERMANENTFLAGS" THEN
in.SkipWhitespace();
NEW(list);
IF ~ReadList(list) THEN RETURN FALSE END;
ELSIF command^ = "READ-ONLY" THEN
ELSIF command^ = "READ-WRITE" THEN
ELSIF command^ = "TRYCREATE" THEN
in.SkipLn();
ELSIF command^ = "UIDVALIDITY" THEN
IF ~ReadPToken(argument) THEN RETURN FALSE END;
Strings.StrToInt(argument^,argi);
ELSIF command^ = "UIDNEXT" THEN
IF ~ReadPToken(argument) THEN RETURN FALSE END;
Strings.StrToInt(argument^,argi);
ELSIF command^ = "UNSEEN" THEN
IF ~ReadPToken(argument) THEN RETURN FALSE END;
Strings.StrToInt(argument^,argi);
ELSIF command^ = "CAPABILITY" THEN
NEW(ret);
WHILE (in.Peek() # "]") DO
NEW(ent);
IF ~ReadPToken(ent.data) THEN RETURN FALSE END;
ent.type := ATOM;
ret.Add(ent)
END;
ELSIF command^ = "COPYUID" THEN
WHILE (in.Get() # "]" ) DO
END;
ELSIF command^ = "APPENDUID" THEN
WHILE (in.Get() # "]" ) DO
END;
ELSE
IF DEBUG THEN
KernelLog.String("IMAP: ReadResponseCode: unknown response code: ");
KernelLog.String("->");KernelLog.String(command^);
KernelLog.String("<-");KernelLog.Ln
END;
END
END;
RETURN TRUE
END ReadResponseCode;
PROCEDURE NextLine():BOOLEAN;
BEGIN
in.SkipLn();
RETURN in.res = Streams.Ok;
END NextLine;
PROCEDURE ReadText(VAR text: String):BOOLEAN;
VAR
b: Strings.Buffer;
w: Streams.Writer;
c: CHAR;
BEGIN
NEW(b, 16);
w := b.GetWriter();
WHILE ~in.EOLN() DO
c := in.Get();
IF in.res # Streams.Ok THEN RETURN FALSE END;
IF c ="\" THEN
c := in.Get();
IF in.res # Streams.Ok THEN RETURN FALSE END
END;
w.Char(c);
END;
text := b.GetString();
RETURN TRUE
END ReadText;
PROCEDURE ReadNumberEnumeration(VAR list: Classes.List):BOOLEAN;
VAR
ent: Entry;
size: LONGINT;
BEGIN
NEW(ent);
size := 0;
in.SkipWhitespace();
WHILE in.Peek() # 0DX DO
IF ~ReadAtom(ent.data) THEN KernelLog.String("RNE failed"); RETURN FALSE; END;
ASSERT(ent.data # NIL, 999);
list.Add(ent);
NEW(ent);
INC(size);
ASSERT(size < 2500000, 666);
IF in.Peek() # 0DX THEN
in.SkipWhitespace();
END;
END;
RETURN size # 0;
END ReadNumberEnumeration;
PROCEDURE ReadList(VAR list: Classes.List):BOOLEAN;
VAR
ent: Entry;
nlist: Classes.List;
BEGIN
in.SkipSpaces();
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
IF in.Peek() # "(" THEN RETURN FALSE END;
in.SkipBytes(1);
IF in.res # Streams.Ok THEN RETURN FALSE END;
ASSERT( in.Peek() # 0DX,1012);
WHILE in.Peek() # ")" DO
NEW(ent);
in.SkipSpaces();
IF in.res # Streams.Ok THEN RETURN FALSE END;
IF in.Peek() = "{" THEN
IF ~ReadLiteral(ent.data) THEN RETURN FALSE END;
ent.type := LITERAL;
ent.list := NIL;
ELSIF in.Peek() = 22X THEN
IF ~ReadQuotedString(ent.data) THEN RETURN FALSE END;
ent.type := STRING;
ent.list := NIL;
ELSIF in.Peek() = "(" THEN
NEW(nlist);
IF ~ReadList(nlist) THEN RETURN FALSE END;
ent.list := nlist;
ent.data := NIL;
ent.type := LIST;
ELSIF in.EOLN() THEN
RETURN FALSE;
ELSE
IF ~ReadAtom(ent.data) THEN RETURN FALSE END;
ent.type := ATOM;
ent.list := NIL
END;
IF in.EOLN() THEN RETURN FALSE END;
list.Add(ent);
IF ~in.EOLN() THEN
in.SkipSpaces();
IF in.res # Streams.Ok THEN RETURN FALSE END;
END;
END;
IF ~in.EOLN() & (in.Peek() = ")") THEN in.SkipBytes(1) END;
RETURN in.res = Streams.Ok;
END ReadList;
PROCEDURE ReadLiteral(VAR buffer:String): BOOLEAN;
VAR
data: ARRAY 80 OF CHAR;
i,size,len: LONGINT;
BEGIN
i := 0;
ASSERT(in.Peek() = "{",1013);
in.SkipBytes(1);
WHILE ((in.Peek() # "}") & ~in.EOLN() & (i<256)) DO
data[i]:= in.Get();
INC(i);
END;
IF (in.Peek() # "}") THEN RETURN FALSE END;
Strings.StrToInt(data,size);
in.SkipLn();
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
NEW(buffer,size+1);
in.Bytes(buffer^,0,size,len);
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
IF size=len THEN
buffer^[size] := 0X;
RETURN TRUE;
ELSE
RETURN FALSE;
END;
END ReadLiteral;
PROCEDURE ReadQuotedString(VAR buffer:String): BOOLEAN;
VAR
b: Strings.Buffer;
w: Streams.Writer;
c: CHAR;
BEGIN
NEW(b, 16);
w := b.GetWriter();
ASSERT(in.Peek() = 22X,1014);
in.SkipBytes(1);
WHILE ((in.Peek() # 22X) & ~in.EOLN()) DO
c := in.Get();
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
IF c="\" THEN
c := in.Get();
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
END;
w.Char(c);
END;
in.SkipBytes(1);
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
buffer := b.GetString();
RETURN TRUE;
END ReadQuotedString;
PROCEDURE ReadAtom(VAR buffer:String): BOOLEAN;
VAR
b: Strings.Buffer;
w: Streams.Writer;
end: BOOLEAN;
BEGIN
NEW(b, 16);
w := b.GetWriter();
IF in.Peek() ="\" THEN RETURN ReadFlag(buffer) END;
ASSERT (in.Peek() # 0DX,1015);
end := FALSE;
WHILE ((in.Peek() # " ") & ~in.EOLN() & (in.Peek() # "(") & (in.Peek() # ")") & (in.Peek() # "{") & (in.Peek() # "*")
& (in.Peek() # "%") & (in.Peek() # "\")) DO
ASSERT (in.Peek() # 0DX,1017);
w.Char(in.Get());
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
END;
buffer := b.GetString();
RETURN TRUE;
END ReadAtom;
PROCEDURE ReadFlag(VAR buffer:String): BOOLEAN;
VAR
b: Strings.Buffer;
w: Streams.Writer;
i: LONGINT;
end: BOOLEAN;
BEGIN
NEW(b, 16);
w := b.GetWriter();
ASSERT (in.Peek() # 0DX,1018);
end := FALSE;
WHILE ((in.Peek() # " ") & ~in.EOLN() & (in.Peek() # "(") & (in.Peek() # ")") & (in.Peek() # "{")
& (in.Peek() # "%") & ~ end) DO
ASSERT (in.Peek() # 0DX,1020);
IF ((in.Peek() = "\") & (i>1)) THEN end := TRUE END;
w.Char(in.Get());
IF ~(in.res=Streams.Ok) THEN RETURN FALSE END;
INC(i);
END;
buffer := b.GetString();
Strings.UpperCase(buffer^);
RETURN TRUE;
END ReadFlag;
PROCEDURE ReadToken(VAR token: String):BOOLEAN;
VAR
b: Strings.Buffer;
w: Streams.Writer;
c: CHAR;
i: LONGINT;
BEGIN
NEW(b,16);
w := b.GetWriter();
i := 0;
in.SkipWhitespace();
WHILE ((in.Peek() # " ") & (~ in.EOLN())) DO
ASSERT (in.Peek() # 0DX,1021);
w.Char(in.Get());
INC(i);
END;
IF ~ in.EOLN() THEN
c := in.Get();
END;
token := b.GetString();
RETURN i # 0;
END ReadToken;
PROCEDURE ReadUToken(VAR resp: String):BOOLEAN;
BEGIN
IF ReadToken(resp) THEN
IMAPUtilities.UpperCase(resp^);
RETURN TRUE;
ELSE
RETURN FALSE;
END
END ReadUToken;
PROCEDURE ReadPToken(VAR buffer: String):BOOLEAN;
VAR
b: Strings.Buffer;
w: Streams.Writer;
BEGIN
NEW(b, 16);
w := b.GetWriter();
ASSERT (in.Peek() # 0DX,1022);
in.SkipWhitespace();
IF in.res # Streams.Ok THEN RETURN FALSE END;
WHILE ((in.Peek() # " ") & ~in.EOLN() & (in.Peek() # "]")) DO
ASSERT (in.Peek() # 0DX,1023);
w.Char(in.Get());
IF in.res # Streams.Ok THEN RETURN FALSE END;
END;
buffer := b.GetString();
RETURN TRUE;
END ReadPToken;
PROCEDURE DBGList*(VAR listP: Classes.List);
VAR
ent: Entry;
entP: ANY;
text: String;
i: LONGINT;
list: Classes.List;
BEGIN
ASSERT(listP # NIL,1024);
list := listP(Classes.List);
KernelLog.String("-> processing list:"); KernelLog.Ln;
FOR i := 0 TO list.GetCount()-1 DO
entP := list.GetItem(i);ent := entP(Entry);
IF ent.type=LITERAL THEN
text := ent.data;
DBGLiteral(ent.data);
ELSIF ent.type=STRING THEN
DBGString(ent.data);
ELSIF ent.type=ATOM THEN
DBGAtom(ent.data);
ELSIF ent.type=LIST THEN
DBGList(ent.list);
ELSIF ent.type=HEADER THEN
KernelLog.String("HEADER");KernelLog.Ln;
KernelLog.String(ent.command);KernelLog.Ln;
KernelLog.String("Number");
KernelLog.Int(ent.number,5);KernelLog.Ln;
IF ent.list # NIL THEN
DBGList(ent.list);
END;
ELSE
HALT(1028);
END;
END;
KernelLog.String("<- processing list finished:"); KernelLog.Ln;
END DBGList;
PROCEDURE DBGLiteral(VAR text:String);
VAR
i,j: LONGINT;
BEGIN
KernelLog.String("processing Literal:");KernelLog.Ln;
j := IMAPUtilities.StringLength(text^);
FOR i := 0 TO j-1 DO
KernelLog.Char(text^[i]);
END;
KernelLog.Ln;
END DBGLiteral;
PROCEDURE DBGString(VAR text:String);
BEGIN
KernelLog.String("processing String:");KernelLog.Ln;
KernelLog.String(text^);KernelLog.Ln;
END DBGString;
PROCEDURE DBGAtom(VAR text:String);
BEGIN
KernelLog.String("processing Atom:");KernelLog.Ln;
KernelLog.String(text^);KernelLog.Ln;
END DBGAtom;
END Connection;
(* To make IMAP Mail-Compatible, one has to implement following procedure
PROCEDURE GetMailMessage(message: LONGINT):Mail.Message;
one could implement this for full RFC2060 conformance:
PROCEDURE Authenticate();
PROCEDURE StartTLS();
*)
END IMAP.