Index: /FCKeditor/trunk/_whatsnew.html
===================================================================
--- /FCKeditor/trunk/_whatsnew.html	(revision 2156)
+++ /FCKeditor/trunk/_whatsnew.html	(revision 2157)
@@ -40,4 +40,7 @@
 		<li>[<a target="_blank" href="http://dev.fckeditor.net/ticket/439">#439</a>] Added a new
 			right-click menu option for opening links in the editor.</li>
+		<li>[<a target="_blank" href="http://dev.fckeditor.net/ticket/2220">#2220</a>] Email links
+			from the Link dialog are now encoded by default to prevent being harvested by spammers.
+			(Kudos to asuter for proposing the patch) </li>
 	</ul>
 	<p>
Index: /FCKeditor/trunk/editor/dialog/fck_link/fck_link.js
===================================================================
--- /FCKeditor/trunk/editor/dialog/fck_link/fck_link.js	(revision 2156)
+++ /FCKeditor/trunk/editor/dialog/fck_link/fck_link.js	(revision 2157)
@@ -78,48 +78,196 @@
 var oParser = new Object() ;
 
-oParser.ParseEMailUrl = function( emailUrl )
+// This method simply returns the two inputs in numerical order. You can even
+// provide strings, as the method would parseInt() the values.
+oParser.SortNumerical = function(a, b)
+{
+	return parseInt( a ) - parseInt( b ) ;
+}
+
+oParser.ParseEMailParams = function(sParams)
+{
+	// Initialize the oEMailParams object.
+	var oEMailParams = new Object() ;
+	oEMailParams.Subject = '' ;
+	oEMailParams.Body = '' ;
+
+	var aMatch = sParams.match( /(^|^\?|&)subject=([^&]+)/i ) ;
+	if ( aMatch ) oEMailParams.Subject = decodeURIComponent( aMatch[2] ) ;
+
+	aMatch = sParams.match( /(^|^\?|&)body=([^&]+)/i ) ;
+	if ( aMatch ) oEMailParams.Body = decodeURIComponent( aMatch[2] ) ;
+
+	return oEMailParams ;
+}
+
+// This method returns either an object containing the email info, or FALSE
+// if the parameter is not an email link.
+oParser.ParseEMailUri = function( sUrl )
 {
 	// Initializes the EMailInfo object.
 	var oEMailInfo = new Object() ;
-	oEMailInfo.Address	= '' ;
-	oEMailInfo.Subject	= '' ;
-	oEMailInfo.Body		= '' ;
-
-	var oParts = emailUrl.match( /^([^\?]+)\??(.+)?/ ) ;
-	if ( oParts )
-	{
-		// Set the e-mail address.
-		oEMailInfo.Address = oParts[1] ;
-
-		// Look for the optional e-mail parameters.
-		if ( oParts[2] )
-		{
-			var oMatch = oParts[2].match( /(^|&)subject=([^&]+)/i ) ;
-			if ( oMatch ) oEMailInfo.Subject = decodeURIComponent( oMatch[2] ) ;
-
-			oMatch = oParts[2].match( /(^|&)body=([^&]+)/i ) ;
-			if ( oMatch ) oEMailInfo.Body = decodeURIComponent( oMatch[2] ) ;
-		}
-	}
-
-	return oEMailInfo ;
+	oEMailInfo.Address = '' ;
+	oEMailInfo.Subject = '' ;
+	oEMailInfo.Body = '' ;
+
+	var aLinkInfo = sUrl.match( /^(\w+):(.*)$/ ) ;
+	if ( aLinkInfo && aLinkInfo[1] == 'mailto' )
+	{
+		// This seems to be an unprotected email link.
+		var aParts = aLinkInfo[2].match( /^([^\?]+)\??(.+)?/ ) ;
+		if ( aParts )
+		{
+			// Set the e-mail address.
+			oEMailInfo.Address = aParts[1] ;
+
+			// Look for the optional e-mail parameters.
+			if ( aParts[2] )
+			{
+				var oEMailParams = oParser.ParseEMailParams( aParts[2] ) ;
+				oEMailInfo.Subject = oEMailParams.Subject ;
+				oEMailInfo.Body = oEMailParams.Body ;
+			}
+		}
+		return oEMailInfo ;
+	}
+	else if ( aLinkInfo && aLinkInfo[1] == 'javascript' )
+	{
+		// This may be a protected email.
+
+		// Try to match the url against the EMailProtectionFunction.
+		var func = FCKConfig.EMailProtectionFunction ;
+		if ( func != null )
+		{
+			try
+			{
+				// Escape special chars.
+				func = func.replace( /([/^$*+.?()\[\]])/g, '\\$1' ) ;
+
+				// Define the possible keys.
+				var keys = new Array('NAME', 'DOMAIN', 'SUBJECT', 'BODY') ;
+
+				// Get the order of the keys (hold them in the array <pos>) and
+				// the function replaced by regular expression patterns.
+				var sFunc = func ;
+				var pos = new Array() ;
+				for ( var i = 0 ; i < keys.length ; i ++ )
+				{
+					var rexp = new RegExp( keys[i] ) ;
+					var p = func.search( rexp ) ;
+					if ( p >= 0 )
+					{
+						sFunc = sFunc.replace( rexp, '\'([^\']*)\'' ) ;
+						pos[pos.length] = p + ':' + keys[i] ;
+					}
+				}
+
+				// Sort the available keys.
+				pos.sort( oParser.SortNumerical ) ;
+
+				// Replace the excaped single quotes in the url, such they do
+				// not affect the regexp afterwards.
+				aLinkInfo[2] = aLinkInfo[2].replace( /\\'/g, '###SINGLE_QUOTE###' ) ;
+
+				// Create the regexp and execute it.
+				var rFunc = new RegExp( '^' + sFunc + '$' ) ;
+				var aMatch = rFunc.exec( aLinkInfo[2] ) ;
+				if ( aMatch )
+				{
+					var aInfo = new Array();
+					for ( var i = 1 ; i < aMatch.length ; i ++ )
+					{
+						var k = pos[i-1].match(/^\d+:(.+)$/) ;
+						aInfo[k[1]] = aMatch[i].replace(/###SINGLE_QUOTE###/g, '\'') ;
+					}
+
+					// Fill the EMailInfo object that will be returned
+					oEMailInfo.Address = aInfo['NAME'] + '@' + aInfo['DOMAIN'] ;
+					oEMailInfo.Subject = decodeURIComponent( aInfo['SUBJECT'] ) ;
+					oEMailInfo.Body = decodeURIComponent( aInfo['BODY'] ) ;
+
+					return oEMailInfo ;
+				}
+			}
+			catch (e)
+			{
+			}
+		}
+
+		// Try to match the email against the encode protection.
+		var aMatch = aLinkInfo[2].match( /^location\.href='mailto:'\+(String\.fromCharCode\([\d,]+\))\+'(.*)'$/ ) ;
+		if ( aMatch )
+		{
+			// The link is encoded
+			oEMailInfo.Address = eval( aMatch[1] ) ;
+			if ( aMatch[2] )
+			{
+				var oEMailParams = oParser.ParseEMailParams( aMatch[2] ) ;
+				oEMailInfo.Subject = oEMailParams.Subject ;
+				oEMailInfo.Body = oEMailParams.Body ;
+			}
+			return oEMailInfo ;
+		}
+	}
+	return false;
 }
 
 oParser.CreateEMailUri = function( address, subject, body )
 {
-	var sBaseUri = 'mailto:' + address ;
-
-	var sParams = '' ;
-
-	if ( subject.length > 0 )
-		sParams = '?subject=' + encodeURIComponent( subject ) ;
-
-	if ( body.length > 0 )
-	{
-		sParams += ( sParams.length == 0 ? '?' : '&' ) ;
-		sParams += 'body=' + encodeURIComponent( body ) ;
-	}
-
-	return sBaseUri + sParams ;
+	// Switch for the EMailProtection setting.
+	switch ( FCKConfig.EMailProtection )
+	{
+		case 'function' :
+			var func = FCKConfig.EMailProtectionFunction ;
+			if ( func == null )
+			{
+				if ( FCKConfig.Debug )
+				{
+					alert('EMailProtection alert!\nNo function defined. Please set "FCKConfig.EMailProtectionFunction"') ;
+				}
+				return '';
+			}
+
+			// Split the email address into name and domain parts.
+			var aAddressParts = address.split( '@', 2 ) ;
+			if ( aAddressParts[1] == undefined )
+			{
+				aAddressParts[1] = '' ;
+			}
+
+			// Replace the keys by their values (embedded in single quotes).
+			func = func.replace(/NAME/g, "'" + aAddressParts[0].replace(/'/g, '\\\'') + "'") ;
+			func = func.replace(/DOMAIN/g, "'" + aAddressParts[1].replace(/'/g, '\\\'') + "'") ;
+			func = func.replace(/SUBJECT/g, "'" + encodeURIComponent( subject ).replace(/'/g, '\\\'') + "'") ;
+			func = func.replace(/BODY/g, "'" + encodeURIComponent( body ).replace(/'/g, '\\\'') + "'") ;
+			return 'javascript:' + func ;
+			break ;
+		case 'encode' :
+			var aParams = [] ;
+			var aAddressCode = [] ;
+
+			if ( subject.length > 0 )
+				aParams.push( 'subject='+ encodeURIComponent( subject ) ) ;
+			if ( body.length > 0 )
+				aParams.push( 'body=' + encodeURIComponent( body ) ) ;
+			for ( var i = 0 ; i < address.length ; i++ )
+				aAddressCode.push( address.charCodeAt( i ) ) ;
+
+			return 'javascript:location.href=\'mailto:\'+String.fromCharCode(' + aAddressCode.join( ',' ) + ')+\'?' + aParams.join( '&' ) + '\'' ;
+		default : // 'none'
+			var sBaseUri = 'mailto:' + address ;
+
+			var sParams = '' ;
+
+			if ( subject.length > 0 )
+				sParams = '?subject=' + encodeURIComponent( subject ) ;
+
+			if ( body.length > 0 )
+			{
+				sParams += ( sParams.length == 0 ? '?' : '&' ) ;
+				sParams += 'body=' + encodeURIComponent( body ) ;
+			}
+
+			return sBaseUri + sParams ;
+	}
 }
 
@@ -264,5 +412,16 @@
 	var sProtocol = oRegex.UriProtocol.exec( sHRef ) ;
 
-	if ( sProtocol )
+	// Search for a protected email link.
+	var oEMailInfo = oParser.ParseEMailUri( sHRef );
+
+	if ( oEMailInfo )
+	{
+		sType = 'email' ;
+
+		GetE('txtEMailAddress').value = oEMailInfo.Address ;
+		GetE('txtEMailSubject').value = oEMailInfo.Subject ;
+		GetE('txtEMailBody').value    = oEMailInfo.Body ;
+	}
+	else if ( sProtocol )
 	{
 		sProtocol = sProtocol[0].toLowerCase() ;
@@ -271,19 +430,6 @@
 		// Remove the protocol and get the remaining URL.
 		var sUrl = sHRef.replace( oRegex.UriProtocol, '' ) ;
-
-		if ( sProtocol == 'mailto:' )	// It is an e-mail link.
-		{
-			sType = 'email' ;
-
-			var oEMailInfo = oParser.ParseEMailUrl( sUrl ) ;
-			GetE('txtEMailAddress').value	= oEMailInfo.Address ;
-			GetE('txtEMailSubject').value	= oEMailInfo.Subject ;
-			GetE('txtEMailBody').value		= oEMailInfo.Body ;
-		}
-		else				// It is a normal link.
-		{
-			sType = 'url' ;
-			GetE('txtUrl').value = sUrl ;
-		}
+		sType = 'url' ;
+		GetE('txtUrl').value = sUrl ;
 	}
 	else if ( sHRef.substr(0,1) == '#' && sHRef.length > 1 )	// It is an anchor link.
Index: /FCKeditor/trunk/fckconfig.js
===================================================================
--- /FCKeditor/trunk/fckconfig.js	(revision 2156)
+++ /FCKeditor/trunk/fckconfig.js	(revision 2157)
@@ -76,4 +76,7 @@
 FCKConfig.FormatOutput		= true ;
 FCKConfig.FormatIndentator	= '    ' ;
+
+FCKConfig.EMailProtection = 'encode' ; // none | encode | function
+FCKConfig.EMailProtectionFunction = 'mt(NAME,DOMAIN,SUBJECT,BODY)' ;
 
 FCKConfig.StartupFocus	= false ;
