Pārlūkot izejas kodu

Type strictly fetch parameters, add comments

Pascal Peridont 19 gadi atpakaļ
vecāks
revīzija
5837726160
2 mainītis faili ar 282 papildinājumiem un 120 dzēšanām
  1. 197 113
      std/mtwin/mail/Imap.hx
  2. 85 7
      std/mtwin/mail/Tools.hx

+ 197 - 113
std/mtwin/mail/Imap.hx

@@ -1,21 +1,60 @@
+/*
+ * Copyright (c) 2006, Motion-Twin
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MOTION-TWIN "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
 package mtwin.mail;
 
 import neko.Socket;
 import mtwin.mail.Exception;
 
-signature ImapConnectionInfo {
-	host: String, 
-	port: Int, 
-	user: String, 
-	pass: String
-}
-
 signature ImapMailbox {
 	name: String,
 	flags: List<String>,
 	hasChildren: Bool
 }
 
+enum ImapSection {
+	Flags;
+	Uid;
+	BodyStructure;
+	Envelope;
+	InternalDate;
+	Body(ss:ImapBodySection);
+	BodyPeek(ss:ImapBodySection);
+}
+
+enum ImapBodySection {
+	Header;
+	Mime;
+	Text;
+	SubSection(id:String,ss:ImapBodySection);
+}
+
+enum ImapRange {
+	Single(i:Int);
+	Range(s:Int,e:Int);
+	Composite(l:Array<ImapRange>);
+}
 
 class Imap {
 	public static var DEBUG = false;
@@ -23,84 +62,43 @@ class Imap {
 	var cnx : Socket;
 	var count : Int;
 
+	static var REG_RESP = ~/(OK|NO|BAD) (\[([^\]]+)\] )?(([A-Z]{2,}) )? ?(.*)/;
+	static var REG_EXISTS = ~/^([0-9]+) EXISTS$/;
+	static var REG_RECENT = ~/^([0-9]+) RECENT$/;
+	static var REG_UNSEEN = ~/^OK \[UNSEEN ([0-9]+)\]/;
+	static var REG_FETCH_MAIN = ~/([0-9]+) FETCH \(/;
+	static var REG_FETCH_PART = ~/^(BODY\[[A-Za-z0-9.]*\]|RFC822\.?[A-Z]*) \{([0-9]+)\}/;
+	static var REG_FETCH_FLAGS = ~/^FLAGS \(([ \\A-Za-z0-9$]*)\) */;
+	static var REG_FETCH_UID = ~/^UID ([0-9]+) */;
+	static var REG_FETCH_BODYSTRUCTURE = ~/^BODY(STRUCTURE)? \(/;
+	static var REG_FETCH_END = ~/^([A0-9]{4}) (OK|BAD|NO)/;
+	static var REG_LIST_RESP = ~/LIST \(([ \\A-Za-z0-9]*)\) "\." "([^"]+)"/;
 	static var REG_CRLF = ~/\r?\n/g;
-	function rmCRLF(s){
-		return REG_CRLF.replace(s, "");
-	}
 
-	function debug(s:String){
-		if( DEBUG ) neko.Lib.print(Std.string(s)+"\n");
+	static function rmCRLF(s){
+		return REG_CRLF.replace(s, "");
 	}
 
-	function quote( s : String ) : String {
+	static function quote( s : String ) : String {
 		return "\""+s.split("\"").join("\\\"")+"\"";
 	}
-	
-	public function new(args: ImapConnectionInfo){
-		count = 0;
-		cnx = new Socket();
-		connect( args.host, args.port );
-		login( args.user, args.pass );
-	}
-
-	function command( command, args, r ){
-		count++;
-		var c = Std.string(count);
-		c = StringTools.lpad(c,"A000",4);
-		cnx.write( c+" "+command+" "+args+"\r\n" );
-		debug( "S: "+c+" "+command+" "+args );
 
-		if( !r ){
-			return null;
-		}
-		return read(c);
+	static function debug(s:String){
+		if( DEBUG ) neko.Lib.print(Std.string(s)+"\n");
 	}
 
-	static var REG_RESP = ~/(OK|NO|BAD) (\[([^\]]+)\] )?(([A-Z]{2,}) )? ?(.*)/;
-	function read( c ){
-		var resp = new List();
-		var sb : StringBuf = null;
-		while( true ){
-			var line = cnx.readLine();
-			debug("R: "+line);
-			line = rmCRLF(line);
+	//////
 
-			if( c != null && line.substr(0,4) == c ){
-				if( REG_RESP.match(line.substr(5,line.length-5)) ){
-					if( sb != null ){
-						resp.add( sb.toString() );
-					}
-					return {
-						result: resp,
-						success: REG_RESP.matched(1) == "OK",
-						error: REG_RESP.matched(1),
-						command: REG_RESP.matched(4),
-						response: REG_RESP.matched(6),
-						comment: REG_RESP.matched(3)
-					};
-				}else{
-					throw UnknowResponse(line);
-				}
-			}else{
-				if( StringTools.startsWith(line,"* ") ){
-					if( sb != null ){
-						resp.add( sb.toString() );
-					}
-					sb = new StringBuf();
-					sb.add( line.substr(2,line.length - 2) );
-				}else{
-					if( sb != null ){
-						sb.add( line+"\r\n" );
-					}else{
-						resp.add( line );
-					}
-				}
-			}
-		}
-		return null;
+	public function new(){
+		count = 0;
 	}
 
-	function connect( host : String, port : Int ){
+	/**
+		Connect to Imap Server
+	**/
+	public function connect( host : String, port : Int ){
+		if( cnx != null ) throw AlreadyConnected;
+		cnx = new Socket();
 		try{
 			cnx.connect( Socket.resolve(host), port );
 		}catch( e : Dynamic ){
@@ -111,39 +109,27 @@ class Imap {
 		cnx.readLine();
 	}
 
-	function login( user : String, pass : String ){	
+	/**
+		Login to server
+	**/
+	public function login( user : String, pass : String ){	
 		var r = command("LOGIN",user+" "+pass,true);
 		if( !r.success ){
 			throw BadResponse(r.response);
 		}
 	}
-	
-	/////////
-	//
-	static var REG_EXISTS = ~/^([0-9]+) EXISTS$/;
-	static var REG_RECENT = ~/^([0-9]+) RECENT$/;
-	static var REG_UNSEEN = ~/^OK \[UNSEEN ([0-9]+)\]/;
-	
-	public function select( mailbox : String ){
-		var r = command("SELECT",quote(mailbox),true);
-		if( !r.success ) 
-			throw BadResponse(r.response);
-		
-		var ret = {recent: 0,exists: 0,firstUnseen: null};
-		for( v in r.result ){
-			if( REG_EXISTS.match(v) ){
-				ret.exists = Std.parseInt(REG_EXISTS.matched(1));
-			}else if( REG_UNSEEN.match(v) ){
-				ret.firstUnseen = Std.parseInt(REG_UNSEEN.matched(1));
-			}else if( REG_RECENT.match(v) ){
-				ret.recent = Std.parseInt(REG_RECENT.matched(1));
-			}
-		}
 
-		return ret;
+	/**
+		Close connection to server
+	**/
+	public function close(){
+		cnx.close();
+		cnx = null;
 	}
 
-	static var REG_LIST_RESP = ~/LIST \(([ \\A-Za-z0-9]*)\) "\." "([^"]+)"/;
+	/**
+		List mailboxes that match pattern (all mailboxes if pattern is null)
+	**/
 	public function mailboxes( ?pattern : String ) : List<ImapMailbox> {
 		var r;
 		if( pattern == null ){
@@ -181,6 +167,31 @@ class Imap {
 		return ret;
 	}
 
+	/**
+		Select a mailbox
+	**/
+	public function select( mailbox : String ){
+		var r = command("SELECT",quote(mailbox),true);
+		if( !r.success ) 
+			throw BadResponse(r.response);
+		
+		var ret = {recent: 0,exists: 0,firstUnseen: null};
+		for( v in r.result ){
+			if( REG_EXISTS.match(v) ){
+				ret.exists = Std.parseInt(REG_EXISTS.matched(1));
+			}else if( REG_UNSEEN.match(v) ){
+				ret.firstUnseen = Std.parseInt(REG_UNSEEN.matched(1));
+			}else if( REG_RECENT.match(v) ){
+				ret.recent = Std.parseInt(REG_RECENT.matched(1));
+			}
+		}
+
+		return ret;
+	}
+
+	/**
+		Search for messages. Pattern syntax described in RFC 2060, section 6.4.4
+	**/
 	public function search( ?pattern : String ){
 		if( pattern == null ) pattern = "ALL";
 		var r = command("SEARCH",pattern,true);
@@ -202,35 +213,46 @@ class Imap {
 		return l;
 	}
 
-	public function fetchSearch( pattern : String, ?section : String ){
-		if( section == null ) section = "BODY.PEEK[]";
+	/**
+		Search for messages, fetch those found.
+	**/
+	public function fetchSearch( pattern : String, ?section : Array<ImapSection> ){
+		if( section == null ) section = [BodyPeek(null)];
 		var r = search(pattern);
 		if( r.length == 0 ) return new IntHash();
 
-		return fetchRange( r.join(","), section );
+		var t = new Array<ImapRange>();
+		for( i in r ){
+			t.push(Single(i));
+		}
+
+		return fetchRange( Composite(t), section );
 	}
 
-	public function fetchOne( id : Int, ?section : String, ?useUid : Bool ) {
-		if( section == null ) section = "BODY.PEEK[]";
+	/**
+		Fetch one message by its id ou uid
+	**/
+	public function fetchOne( id : Int, ?section : Array<ImapSection>, ?useUid : Bool ) {
+		if( section == null ) section = [BodyPeek(null)];
 		if( useUid == null ) useUid = false;
 
-		var r = fetchRange( Std.string(id), section, useUid );
+		var r = fetchRange( Single(id), section, useUid );
 		if( !r.exists(id) ){
 			throw ImapFetchError(id);
 		}
 		return r.get(id);
 	}
 
-	static var REG_FETCH_MAIN = ~/([0-9]+) FETCH \(/;
-	static var REG_FETCH_PART = ~/^(BODY\[[A-Za-z0-9.]*\]|RFC822\.?[A-Z]*) \{([0-9]+)\}/;
-	static var REG_FETCH_FLAGS = ~/^FLAGS \(([ \\A-Za-z0-9$]*)\) */;
-	static var REG_FETCH_UID = ~/^UID ([0-9]+) */;
-	static var REG_FETCH_BODYSTRUCTURE = ~/^BODY(STRUCTURE)? \(/;
-	static var REG_FETCH_END = ~/^([A0-9]{4}) (OK|BAD|NO)/;
-	public function fetchRange( range : String, ?section : String, ?useUid : Bool ){
-		if( range == null ) return null;
-		if( section == null ) section = "BODY[]";
+	/**
+		Fetch some messages
+	**/
+	public function fetchRange( iRange: ImapRange, ?iSection : Array<ImapSection>, ?useUid : Bool ){
+		if( iRange == null ) return null;
+		if( iSection == null ) iSection = [Body(null)];
 		if( useUid == null ) useUid = false;
+
+		var range = Tools.imapRangeString(iRange);
+		var section = Tools.imapSectionString(iSection);
 		
 		if( useUid )
 			command("UID FETCH",range+" "+section,false);
@@ -298,4 +320,66 @@ class Imap {
 
 		return ret;
 	}
+
+	//
+
+	function command( command, args, r ){
+		if( cnx == null )
+			throw NotConnected;
+
+		count++;
+		var c = Std.string(count);
+		c = StringTools.lpad(c,"A000",4);
+		cnx.write( c+" "+command+" "+args+"\r\n" );
+		debug( "S: "+c+" "+command+" "+args );
+
+		if( !r ){
+			return null;
+		}
+		return read(c);
+	}
+
+	
+	function read( c ){
+		var resp = new List();
+		var sb : StringBuf = null;
+		while( true ){
+			var line = cnx.readLine();
+			debug("R: "+line);
+			line = rmCRLF(line);
+
+			if( c != null && line.substr(0,4) == c ){
+				if( REG_RESP.match(line.substr(5,line.length-5)) ){
+					if( sb != null ){
+						resp.add( sb.toString() );
+					}
+					return {
+						result: resp,
+						success: REG_RESP.matched(1) == "OK",
+						error: REG_RESP.matched(1),
+						command: REG_RESP.matched(4),
+						response: REG_RESP.matched(6),
+						comment: REG_RESP.matched(3)
+					};
+				}else{
+					throw UnknowResponse(line);
+				}
+			}else{
+				if( StringTools.startsWith(line,"* ") ){
+					if( sb != null ){
+						resp.add( sb.toString() );
+					}
+					sb = new StringBuf();
+					sb.add( line.substr(2,line.length - 2) );
+				}else{
+					if( sb != null ){
+						sb.add( line+"\r\n" );
+					}else{
+						resp.add( line );
+					}
+				}
+			}
+		}
+		return null;
+	}	
 }

+ 85 - 7
std/mtwin/mail/Tools.hx

@@ -1,5 +1,31 @@
+/*
+ * Copyright (c) 2006, Motion-Twin
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY MOTION-TWIN "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE HAXE PROJECT CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ */
 package mtwin.mail;
 
+import mtwin.mail.Imap;
+
 class Tools {
 
 	static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
@@ -31,15 +57,15 @@ class Tools {
 		return ret;
 	}
 
-	public static function encodeBase64( content : String , crlf : String ){
-		return StringTools.rtrim(chunkSplit(StringTools.baseEncode( content, BASE64 ), 76, crlf)) + "==";
+	public static function encodeBase64( content : String ){
+		return StringTools.rtrim(chunkSplit(StringTools.baseEncode( content, BASE64 ), 76, "\r\n")) + "==";
 	}
 
-	public static function decodeBase64( content : String, crlf ){
-		return StringTools.baseDecode( StringTools.replace(StringTools.replace(content,crlf,""),"=",""), BASE64 );
+	public static function decodeBase64( content : String ){
+		return StringTools.baseDecode( StringTools.replace(StringTools.replace(content,"\r\n",""),"=",""), BASE64 );
 	}
 
-	public static function encodeQuotedPrintable( content : String, crlf : String ) : String{
+	public static function encodeQuotedPrintable( content : String ) : String{
 		var rs = new List();
 		var lines = splitLines( content );
 		
@@ -77,7 +103,7 @@ class Tools {
 			rs.add(line);
 		}
 
-		return rs.join(crlf);
+		return rs.join("\r\n");
 	}
 
 	public static function decodeQuotedPrintable( str : String ){
@@ -160,7 +186,7 @@ class Tools {
 			if( encoding == "q" ){
 				encoded = decodeQuotedPrintable(StringTools.replace(encoded,"_"," "));
 			}else if( encoding == "b" ){
-				encoded = decodeBase64(encoded,"\r\n");
+				encoded = decodeBase64(encoded);
 			}else{
 				throw "Unknow transfer-encoding: "+encoding;
 			}
@@ -276,4 +302,56 @@ class Tools {
 
 	}
 
+	public static function imapRangeString( r : ImapRange ) : String {
+		return switch( r ){
+			case Single(i): Std.string(i);
+			case Range(s,e): Std.string(s)+":"+Std.string(e);
+			case Composite(l):
+				var t = new List<String>();
+				for( e in l )
+					t.add(imapRangeString(e));
+				t.join(",");
+		}
+	}
+
+	public static function imapSectionString( a : Array<ImapSection> ) : String{
+		var r = new List();
+		
+		if( a == null || a.length < 1 )
+			return "";
+		
+		for( s in a ){
+			r.add( switch( s ){
+				case Flags: "FLAGS";
+				case Uid: "UID";
+				case BodyStructure: "BODYSTRUCTURE";
+				case Envelope: "ENVELOPE";
+				case InternalDate: "INTERNALDATE";
+				case Body(ss): "BODY["+imapBodySectionString(ss)+"]";
+				case BodyPeek(ss): "BODY.PEEK["+imapBodySectionString(ss)+"]";
+					
+			});
+		}
+		return "("+r.join(" ")+")";
+	}
+
+	static function imapBodySectionString( ss : ImapBodySection ){
+		if( ss == null )
+			return "";
+
+		return switch( ss ){
+			case Text: "TEXT";
+			case Header: "HEADER";
+			case Mime: "MIME";
+			case SubSection(id,nss):
+				var t = imapBodySectionString(nss);
+				if( id == null || id == "" ) 
+					t;
+				else if( t == "" )
+					id;
+				else
+					id+"."+t;
+		}
+	}
+
 }