From f456f4cd7694135507be73542ff1b660d62d0143 Mon Sep 17 00:00:00 2001 From: deuce <> Date: Tue, 10 Nov 2009 06:51:50 +0000 Subject: [PATCH] Add initial support for SEARCH command (only a few clauses missing) Tag TODO items with comments Fix off-by-one error in FETCH when INBOX is selected --- exec/imapservice.js | 267 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 247 insertions(+), 20 deletions(-) diff --git a/exec/imapservice.js b/exec/imapservice.js index 114583b617..863da2d1b9 100644 --- a/exec/imapservice.js +++ b/exec/imapservice.js @@ -12,25 +12,28 @@ load("sbbsdefs.js"); const RFC822HEADER = 0xb0; // from smbdefs.h var sepchar="|"; -var debug=true; +var debug=false; +var debugRX=true; -function debug_log(line) +function debug_log(line, rx) { if(debug) log(line); + else if(rx && debugRX) + log(line); } // Output handling functions function tagged(tag, msg, desc) { client.socket.send(tag+" "+msg+" "+desc+"\r\n"); - debug_log("IMAP Send: "+tag+" "+msg+" "+desc); + debug_log("IMAP Send: "+tag+" "+msg+" "+desc, false); } function untagged(msg) { client.socket.send("* "+msg+"\r\n"); - debug_log("IMAP Send: * "+msg); + debug_log("IMAP Send: * "+msg, false); } function handle_command(command, args, defs) @@ -77,9 +80,21 @@ function encode_string(str) return encode_binary(str); } +function get_from(hdr) +{ + // From + if(!hdr.from_net_type || !hdr.from_net_addr) /* local message */ + return(hdr.from + " <" + hdr.from.replace(/ /g,".").toLowerCase() + "@" + system.inetaddr + ">"); + else if(!hdr.from_net_addr.length) + return(hdr.from); + else if(hdr.from_net_addr.indexOf('@')!=-1) + return(hdr.from+" <"+hdr.from_net_addr+">"); + return(hdr.from+" <"+hdr.from.replace(/ /g,".").toLowerCase()+"@"+hdr.from_net_addr+">"); +} + function send_fetch_response(msgnum, format, uid) { - var idx=base.get_msg_index(msgnum); + var idx; var resp=''; var sent_uid=false; var i,j; @@ -93,6 +108,8 @@ function send_fetch_response(msgnum, format, uid) var seen_changed=false; var envelope; + idx=base.get_msg_index(msgnum); + function get_header() { if(hdr == undefined) { hdr=base.get_msg_header(msgnum); @@ -111,15 +128,7 @@ function send_fetch_response(msgnum, format, uid) rfc822.header += "Message-ID: "+hdr.id+"\r\n"; rfc822.header += "Date: "+hdr.date+"\r\n"; - // From - if(!hdr.from_net_type || !hdr.from_net_addr) /* local message */ - rfc822.header += "From: " + hdr.from + " <" + hdr.from.replace(/ /g,".").toLowerCase() + "@" + system.inetaddr + ">\r\n"; - else if(!hdr.from_net_addr.length) - rfc822.header += "From: "+hdr.from+"\r\n"; - else if(hdr.from_net_addr.indexOf('@')!=-1) - rfc822.header += "From: "+hdr.from+" <"+hdr.from_net_addr+">\r\n"; - else - rfc822.header += "From: "+hdr.from+" <"+hdr.from.replace(/ /g,".").toLowerCase()+"@"+hdr.from_net_addr+">\r\n"; + rfc822.header += "From: "+get_from(hdr)+"\r\n"; rfc822.header += "X-Comment-To: "+hdr.to+"\r\n"; if(hdr.path != undefined) @@ -306,6 +315,7 @@ function send_fetch_response(msgnum, format, uid) continue; } if(format[i].toUpperCase().substr(0,4)=='BODY') { + // TODO: Handle BODY* stuff correctly (MIME Decode) switch(format[i].toUpperCase()) { case 'BODY[TEXT]': set_seen_flag(); @@ -464,7 +474,7 @@ function parse_seq_set(set, uid) { } for(i=range[0]; i<=range[1]; i++) { if(base.subnum==65535) { - idx=base.get_msg_index(uid?parseInt(i,10):inbox_map.uid[parseInt(i,10)]); + idx=base.get_msg_index(uid?parseInt(i,10):inbox_map.uid[parseInt(i,10)-1]); if(idx==null || idx.to != user.number) continue; } @@ -905,17 +915,27 @@ function sublist(group, match, subscribed) var wmatch,wgroup; var fmatch; var re; + var has_sep=false; if(match=='') return([""]); - wmatch=group+match; + re=new RegExp("\\"+sepchar+"$"); + if(group.search(re)!=-1) + has_sep=true; + re=new RegExp("^\\"+sepchar); + if(match.search(re)!=-1) + has_sep=true; + wmatch=group; + if(wmatch.length > 0 && !has_sep) + wmatch += sepchar; + wmatch += match; wmatch=wmatch.replace(/([\\\^\$\+\?\.\(\)\|\{\}])/g,"\\$1"); wmatch=wmatch.replace(/\*/g, ".\*"); wmatch=wmatch.replace(/\%/g, "[^"+sepchar+"]\*"); wmatch="^"+wmatch+"$"; +log("wmatch: "+wmatch); re=new RegExp(wmatch); -log("WMATCH="+wmatch); if(re.test("INBOX")) ret.push("INBOX"); @@ -1329,6 +1349,197 @@ function do_store(seq, uid, item, data) } } +function datestr(date) +{ + return(strftime('%d-%b-%C%y', date)); +} + +function parse_date(date) +{ + var ret=0; + var match; + var m,d,y; + var dt; + + match=date.match(/^([0-9]{1,2})-([A-Za-z]{3})-([0-9]{4})$/); + if(match==null) + return(0); + d=parseInt(match[1],10); + m={Jan:1,Feb:2,Mar:3,Apr:4,May:5,Jun:6,Jul:7,Aug:8,Sep:9,Oct:10,Nov:11,Dec:12}[match[2]]; + y=parseInt(match[3],10); + if(m==undefined) + return(0); + if(d<1 || d>31) + return(0); + if(y<1970 || y>2099) + return(0); + dt=new Date(y,m,d); + return(dt.valueOf()/1000); +} + +function parse_rfc822_date(date) +{ + var dt=new Date(date); + +log("Date: "+date+"=="+dt.toString()+"=="+(dt.valueOf()/1000)); + return(dt.valueOf()/1000); +} + +function do_search(args, uid) +{ + var search_set={idx:[],hdr:[],body:[]}; + var i,j; + var failed; + var idx,hdr,body; + var result=[]; + + while(args.length) { + switch(args.shift().toUpperCase()) { + case 'ALL': + search_set.idx.push(function(idx) { return true; }); + break; + case 'ANSWERED': + search_set.idx.push(function(idx) { if(base.subnum==65535 && (idx.attr & MSG_REPLIED)) return true; return false; }); + break; + case 'BODY': + search_set.body.push(eval("function(body) { return(body.indexOf("+args.shift().toUpperCase().toSource()+")!=-1) }")); + break; + case 'DELETED': + search_set.idx.push(function(idx) { if(idx.attr & MSG_DELETE) return true; return false; }); + break; + case 'DRAFT': + search_set.idx.push(function(idx) { return false; }); + break; + case 'FLAGGED': + search_set.idx.push(function(idx) { if(idx.attr & MSG_VERIFIED) return true; return false; }); + break; + case 'FROM': + search_set.hdr.push(eval("function(hdr) { return(get_from(hdr).toUpperCase().indexOf("+args.shift().toUpperCase().toSource()+")!=-1) }")); + break; + case 'KEYWORD': + search_set.hdr.push(eval("function(hdr) { var flags="+parse_flags([args.shift()]).toSource()+"; if((hdr.attr & flags.attr)==flags.attr && (hdr.netattr & flags.netattr)==flags.netattr) return true; return false;}")); + break; + case 'NEW': + // TODO: We don't do \Recent in INBOX and don't do \Seen in subs + search_set.idx.push(eval("function(idx) { if(base.cfg==undefined) return false; if(msg_ptrs[base.cfg.code] < idx.number) return true; return false; }")); + break; + case 'OLD': + // TODO: We don't do \Recent in INBOX and don't do \Seen in subs + search_set.idx.push(eval("function(idx) { if(base.cfg==undefined) return true; if(msg_ptrs[base.cfg.code] < idx.number) return false; return true; }")); + break; + case 'RECENT': + // TODO: We don't do \Recent in INBOX + search_set.idx.push(eval("function(idx) { if(base.cfg==undefined) return false; if(msg_ptrs[base.cfg.code] < idx.number) return true; return false; }")); + break; + case 'SEEN': + // We don't do \Seen in subs + search_set.idx.push(function(idx) { if(base.subnum != 65535) return false; if(idx.attr & MSG_READ) return true; return false }); + break; + case 'SUBJECT': + search_set.hdr.push(eval("function(hdr) { return(hdr.subject.toUpperCase().indexOf("+args.shift().toUpperCase().toSource()+")!=-1) }")); + break; + case 'TO': + search_set.hdr.push(eval("function(hdr) { return(hdr.to.toUpperCase().indexOf("+args.shift().toUpperCase().toSource()+")!=-1) }")); + break; + case 'UID': + search_set.idx.push(eval("function(idx) { var good_uids="+parse_seq_set(args.shift(), true).toSource()+"; var i; for(i in good_uids) { if(good_uids[i]==idx.number) return true; } return false; }")); + break; + case 'UNANSWERED': + search_set.idx.push(function(idx) { if(base.subnum==65535 && (idx.attr & MSG_REPLIED)) return false; return true; }); + break; + case 'UNDELETED': + search_set.idx.push(function(idx) { if(idx.attr & MSG_DELETE) return false; return true; }); + break; + case 'UNDRAFT': + search_set.idx.push(function(idx) { return true; }); + break; + case 'UNFLAGGED': + search_set.idx.push(function(idx) { if(idx.attr & MSG_VERIFIED) return false; return true; }); + break; + case 'UNKEYWORD': + search_set.hdr.push(eval("function(hdr) { var flags="+parse_flags([args.shift()]).toSource()+"; if((hdr.attr & flags.attr)==flags.attr && (hdr.netattr & flags.netattr)==flags.netattr) return false; return true;}")); + break; + case 'BEFORE': + search_set.hdr.push(eval("function(hdr) { var before="+parse_date(args.shift()).toSource()+"; if(hdr.when_imported_time < before) return true; return false; }")); + break; + case 'ON': + search_set.hdr.push(eval("function(hdr) { var on="+datestr(parse_date(args.shift())).toSource()+"; if(datestr(hdr.when_imported_time) == on) return true; return false; }")); + break; + case 'SINCE': + search_set.hdr.push(eval("function(hdr) { var since="+parse_date(args[0]).toSource()+"; var since_str="+datestr(parse_date(args.shift())).toSource()+"; if(hdr.when_imported_time > since && datestr(hdr.when_imported_time) != since_str) return true; return false; }")); + break; + case 'SENTBEFORE': + search_set.hdr.push(eval("function(hdr) { var before="+parse_date(args.shift()).toSource()+"; if(parse_rfc822_date(hdr.date) < before) return true; return false; }")); + break; + case 'SENTON': + search_set.hdr.push(eval("function(hdr) { var on="+datestr(parse_date(args.shift())).toSource()+"; if(datestr(parse_rfc822_date(hdr.date)) == on) return true; return false; }")); + break; + case 'SENTSINCE': + search_set.hdr.push(eval("function(hdr) { var since="+parse_date(args[0]).toSource()+"; var since_str="+datestr(parse_date(args.shift())).toSource()+"; if(parse_rfc822_date(hdrdate) > since && datestr(parse_rfc822_date(hdr.date)) != since_str) return true; return false; }")); + break; + // TODO: Unhandled SEARCH terms + case 'HEADER': + args.shift(); + case 'LARGER': + case 'SMALLER': + // Calculate RFC822.SIZE + case 'CC': + // Is there a CC header? + case 'BCC': + // There's a BCC header? + case 'TEXT': + // Needs RFC822 + args.shift(); + case 'NOT': + case 'OR': + default: + search_set.idx.push(function(idx) { return false; }); + } + } + + for(i=base.first_msg; i<=base.last_msg; i++) { + failed=false; + idx=base.get_msg_index(i); + if(idx==null) + continue; + if(base.subnum==65535) { + if(idx.to != user.number) + continue; + } + if(search_set.idx.length > 0) { + for(j in search_set.idx) { + if(search_set.idx[j](idx)==false) + failed=true; + } + if(failed) + continue; + } + if(search_set.hdr.length > 0) { + hdr=base.get_msg_header(i); + for(j in search_set.hdr) { + if(search_set.hdr[j](hdr)==false) + failed=true; + } + if(failed) + continue; + } + body=base.get_msg_index(i); + if(search_set.body.length > 0) { + body=base.get_msg_body(i,true,true,true).toUpperCase(); + for(j in search_set.body) { + if(search_set.body[j](body)==false) + failed=true; + } + if(failed) + continue; + } + if(!failed) + result.push(uid?idx.number:(base.subnum==65535?inbox_map.offset[i]:idx.offset+1)); + } + + untagged("SEARCH "+result.join(" ")); +} + selected_command_handlers = { CHECK:{ arguments:0, @@ -1358,7 +1569,7 @@ selected_command_handlers = { handler:function(args) { var tag=args[0]; - tagged(tag, "OK", "How about I pretent to expunge and you pretend that's OK?"); + tagged(tag, "OK", "How about I pretend to expunge and you pretend that's OK?"); //tagged(tag, "NO", "Can't expunge... wait for maintenance"); }, }, @@ -1369,7 +1580,13 @@ selected_command_handlers = { handler:function(args) { var tag=args[0]; - tagged(tag, "NO", "Can't search... I'm useless."); + // TODO: Support (or ignore) CHARSET in search commands + if(args[1]=='CHARSET') { + tagged(tag, "NO", "I don't support CHARSET in SEARCH."); + return; + } + do_search(args.slice(1), false); + tagged(tag, "OK", "And that was your results."); } }, FETCH:{ @@ -1444,8 +1661,18 @@ selected_command_handlers = { do_store(seq, true, data_items, data); tagged(tag, "OK", "Stored 'em up (with UIDs)!"); break; + case 'SEARCH': + // TODO: Support (or ignore) CHARSET in SEARCH + if(args[2]=='CHARSET') { + tagged(tag, "NO", "I don't support CHARSET in SEARCH."); + return; + } + do_search(args.slice(2), true); + tagged(tag, "OK", "And that was your results."); + break; default: tagged(tag, "NO", "Help, I'm useless."); + break; } }, } @@ -1460,7 +1687,7 @@ client.socket.send("* OK Give 'er\r\n"); while(1) { line=client.socket.recvline(10240, 300); if(line != null) { - debug_log("IMAP RECV: "+line); + debug_log("IMAP RECV: "+line, true); parse_command(line); } else { -- GitLab