From f6162ed9a45e4ef924b9d2c9fe8e2df75e20ba88 Mon Sep 17 00:00:00 2001 From: deuce <> Date: Tue, 3 Nov 2009 08:07:27 +0000 Subject: [PATCH] Better command parser, add handlers for all commands/states. --- exec/imapservice.js | 375 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 317 insertions(+), 58 deletions(-) diff --git a/exec/imapservice.js b/exec/imapservice.js index 2d50e82d1e..0438af1ae6 100644 --- a/exec/imapservice.js +++ b/exec/imapservice.js @@ -32,74 +32,202 @@ function untagged(msg) debug_log("IMAP Send: * "+msg); } -function parse_line(line) { - var args=[]; - var tmp; - var tmp2; - var i; - - tmp=line.split(/ /); - - while(tmp.length) { - if(tmp[0].substr(0,1)=='"') { - tmp2=''; - while(tmp[0].substr(-1)!='"') { - tmp2 += tmp.shift()+" "; +function handle_command(command, args, defs) +{ + if(defs[command] != undefined) { + if(defs[command].arguments != undefined) { + if(args.length-1 == defs[command].arguments) { + defs[command].handler(args); + return(true); } - tmp2 += tmp.shift(); - tmp2=tmp2.replace(/^"(.*)"$/, "$1"); - args.push(tmp2); - continue; } - if(tmp[0].substr(0,1)=='(') { - tmp2=[]; - while(tmp[0].substr(-1)!=')') { - tmp2 += tmp.shift(); + else if(defs[command].arguments_valid(args.length-1)) { + defs[command].handler(args); + return(true); + } + } + return false; +} + +function compNumbers(a,b) { + return(a-b); +} + +/* + * Parses a data items FETCH parameter for send_fetch_response() + */ +function parse_data_items(obj) +{ + if(typeof(obj)=='string') { + switch(obj.toUpperCase()) { + case 'ALL': + obj=["FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE"]; + break; + case 'FAST': + obj=["FLAGS","INTERNALDATE","RFC822.SIZE"]; + break; + case 'FULL': + obj=["FLAGS","INTERNALDATE","RFC822.SIZE","ENVELOPE","BODY"]; + break; + default: + obj=[]; + } + } + +} + +/* + * Returns an array of Message Numbers which correspond to the specified sets + */ +function parse_seq_set(set, uid) { + var response=[]; + var chunks=set.split(/,/); + var chunk; + var range; + var i; + var max; + var idx; + + if(uid) + max=base.last_msg; + else + max=base.total_msgs; + for(chunk in chunks) { + range=chunks[chunk].split(/:/); + if(range.length == 1) + range.push(range[0]); + if(range[0]=='*') + range[0]=max; + if(range[1]=='*') + range[1]=max; + range[0]=parseInt(range[0],10); + range[1]=parseInt(range[1],10); + if(range[0] > range[1]) { + i=range[0]; + range[0]=range[1]; + range[1]=i; + } + for(i=range[0]; i<=range[1]; i++) { + idx=base.get_msg_index(uid?false:true, parseInt(i,10)); + if(idx!=null) { + response.push(idx.number); } - tmp2 += tmp.shift(); - tmp2=tmp2.replace(/^"(.*)"$/, "$1"); - args.push(tmp2); - continue; } - if(tmp[0].search(/^{[0-9]+}$/) != -1) { - tmp2=tmp[0].replace(/^{([0-9]+)}$/, "$1"); - args.push(client.socket.recv(parseInt(tmp2))); - tmp2=client.socket.recvline(1024, 300); - tmp=tmp2.split(/ /); - continue; + } + response=response.sort(compNumbers); + for(i=0; i<response.length; i++) { + if(response[i]==response[i+1]) { + response.splice(i+1,1); + i--; + } + } + return(response); +} + +var line; +function parse_line() { + var at_start=true; + var in_quote=false; + var paren_depth=0; + var string_len; + var args=[]; + var pos; + + function parse_atom() { + var ret=''; + + while(line.length) { + switch(line.charAt(0)) { + case ')': + return(ret); + case ' ': + line=line.substr(1); + return(ret); + default: + ret += line.charAt(0); + line=line.substr(1); + break; + } } - args.push(tmp.shift()); + return(ret); } - if(args.length < 2) { - tagged(args[0], "BAD", "Bad dog, no cookie."); - return; + function parse_string() + { + var ret=''; + + line=line.replace(/^{([0-9]+)}$/, "$1"); + client.socket.send("+\r\n"); + ret=client.socket.recv(parseInt(line)); + line=client.socket.recvline(1024, 300); + + return(ret); } - command=args[1].toUpperCase(); - args.splice(1,1); - if(any_state_command_handlers[command]!=undefined) { - if(args.length-1 == any_state_command_handlers[command].arguments) { - any_state_command_handlers[command].handler(args); - return; + + function parse_quotedstring() { + var ret=''; + + line=line.substr(1); // Remove leading " + while(line.length) { + switch(line.charAt(0)) { + case '"': + line=line.substr(1); + return(ret); + default: + ret += line.charAt(0); + line=line.substr(1); + break; + } } + return(ret); } - switch(state) { - case UnAuthenticated: - if(unauthenticated_command_handlers[command]!=undefined) { - if(args.length-1 == unauthenticated_command_handlers[command].arguments) { - unauthenticated_command_handlers[command].handler(args); + + while(line.length) { + switch(line.charAt(0)) { + case '"': + args.push(parse_quotedstring()); + break; + case ')': + line=line.substr(1); + return(args); + case '(': + line=line.substr(1); + args.push(parse_line()); + break; + case '{': + args.push(parse_string()); + break; + case ' ': + line=line.substr(1); + break; + default: + args.push(parse_atom()); + break; + } + } + return(args); +} + +function execute_line(args) { + if(args.length >= 2) { + command=args[1].toUpperCase(); + args.splice(1,1); + if(handle_command(command, args, any_state_command_handlers)) + return; + switch(state) { + case UnAuthenticated: + if(handle_command(command, args, unauthenticated_command_handlers)) return; - } - } - break; - case Authenticated: - if(authenticated_command_handlers[command]!=undefined) { - if(args.length-1 == authenticated_command_handlers[command].arguments) { - authenticated_command_handlers[command].handler(args); + break; + case Authenticated: + if(handle_command(command, args, authenticated_command_handlers)) return; - } - } - break; + break; + case Selected: + if(handle_command(command, args, selected_command_handlers)) + return; + break; + } } tagged(args[0], "BAD", "Bad dog, no cookie."); } @@ -107,6 +235,7 @@ function parse_line(line) { // Global variables const UnAuthenticated=0; const Authenticated=1; +const Selected=2; var state=UnAuthenticated; var base; @@ -271,6 +400,7 @@ authenticated_command_handlers = { untagged("OK [UIDNEXT "+(base.last_msg+1)+"]"); untagged("OK [UIDVALIDITY 0]"); tagged(tag, "OK", "[READ-ONLY] Mailbox "+sub+" has been selected"); + state=Selected; }, }, EXAMINE:{ // Same as select onlt without the writability @@ -292,6 +422,7 @@ authenticated_command_handlers = { untagged("OK [UIDNEXT "+(base.last_msg+1)+"]"); untagged("OK [UIDVALIDITY 0]"); tagged(tag, "OK", "[READ-ONLY] Mailbox "+sub+" has been selected"); + state=Selected; }, }, CREATE:{ @@ -387,11 +518,138 @@ authenticated_command_handlers = { tagged(tag, "OK", "There you go."); }, }, + STATUS:{ + arguments:2, + handler:function(args) { + var tag=args[0]; + var sub=getsub(args[1]); + var items=args[2]; + var i; + var response=[]; + + if(typeof(items)!="object") + items=[items]; + base=new MsgBase(sub); + if(base == undefined || (!base.open())) { + tagged(tag, "NO", "Can't find your mailbox"); + return; + } + for(i in items) { + switch(items[i].toUpperCase()) { + case 'MESSAGES': + response.push("MESSAGES"); + response.push(base.total_msgs); + break; + case 'RECENT': + response.push("RECENT"); + response.push(0); + break; + case 'UIDNEXT': + response.push("UIDNEXT"); + response.push(base.last_msg+1); + break; + case 'UIDVALIDITY': + response.push("UIDVALIDITY"); + response.push(0); + break; + case 'UNSEEN': + response.push("UNSEEN"); + response.push(base.total_msgs); + break; + } + } + base.close(); + untagged("STATUS {"+(args[1].length)+"}\r\n"+args[1]+" ("+response.join(" ")+")"); + tagged(tag, "OK", "And that's the way it is."); + }, + }, + APPEND:{ + arguments_valid:function(count) { + if(count >= 2 && count <= 4) + return(true); + return(false); + }, + handler:function(args) { + var tag=args[0]; + + tagged(tag, "NO", "No appending yet... sorry."); + }, + }, +}; + +selected_command_handlers = { + CHECK:{ + arguments:0, + handler:function(args) { + var tag=args[0]; + + tagged(tag, "OK", "Check."); + }, + }, + CLOSE:{ + arguments:0, + handler:function(args) { + var tag=args[0]; + + base.close(); + tagged(tag, "OK", "Closed."); + state=Authenticated; + }, + }, + EXPUNGE:{ + arguments:0, + handler:function(args) { + var tag=args[0]; + + tagged(tag, "NO", "Can't expunge... wait for maintenance"); + }, + }, + SEARCH:{ + arguments_valid:function(count) { + return(count >= 1); + }, + handler:function(args) { + var tag=args[0]; + + tagged(tag, "NO", "Can't search... I'm useless."); + } + }, + FETCH:{ + arguments:2, + handler:function(args) { + var tag=args[0]; + var seq=parse_seq_set(args[1],false); + var data_items=parse_data_items(args[2]); + var i; + + for(i in seq) { + send_fetch_response(seq[i], data_items); + } + //tagged(tag, "OK", "There they are!"); + tagged(tag, "NO", "Can't fetch... I'm useless."); + }, + }, + STORE:{ + arguments:3, + handler:function(args) { + var tag=args[0]; + + tagged(tag, "NO", "This is read-only."); + }, + }, + COPY:{ + arguments:2, + handler:function(args) { + var tag=args[0]; + + tagged(tag, "NO", "Hah! You can't even FETCH yet and you want to COPY?!?!"); + }, + }, UID:{ arguments:3, handler:function(args) { var tag=args[0]; - + tagged(tag, "NO", "Help, I'm useless."); }, } @@ -403,7 +661,8 @@ while(1) { line=client.socket.recvline(1024, 300); if(line != null) { debug_log("IMAP RECV: "+line); - parse_line(line); + args=parse_line(); + execute_line(args); } else { untagged("BYE No lolligaggers here!"); -- GitLab