Index: content/smartTemplate-compose.js
===================================================================
--- content/smartTemplate-compose.js	(revision 694)
+++ content/smartTemplate-compose.js	(working copy)
@@ -689,38 +689,15 @@
 	function getProcessedText(templateText, idKey, composeType, ignoreHTML, isStationery) 
 	{
 		if (!templateText) return "";
+
 		if (typeof isStationery === 'undefined') isStationery = SmartTemplate4.PreprocessingFlags.isStationery;
 		SmartTemplate4.Util.logDebugOptional('functions.getProcessedTemplate', 'START =============  getProcessedText()   ==========');
 		SmartTemplate4.Util.logDebugOptional('functions.getProcessedTemplate', 'Process Text:\n' +
 		                                     templateText + '[END]');
 		var pref = SmartTemplate4.pref;
-		//Reset X to Today after each newline character
-		//except for lines ending in { or }; breaks the omission of non-existent CC??
-		templateText = templateText.replace(/\n/gm, "%X:=today%\n");
-		//replace this later!!
-		// templateText = templateText.replace(/{\s*%X:=today%\n/gm, "{\n");
-		// templateText = templateText.replace(/}\s*%X:=today%\n/gm, "}\n");
-		templateText = templateText.replace(/\[\[\s*%X:=today%\n/gm, "[[\n");
-		templateText = templateText.replace(/\]\]\s*%X:=today%\n/gm, "]]\n");
-
-		// ignoreHTML, e,g with signature, lets not do html processing
-		if (!ignoreHTML) {
-			// for Draft, let's just assume html for the moment.
-			if (!composeType || pref.isUseHtml(idKey, composeType, false)) {
-				templateText = templateText.replace(/( )+(<)|(>)( )+/gm, "$1$2$3$4");
-				if (pref.isReplaceNewLines(idKey, composeType, true))
-					{ templateText = templateText.replace(/>\n/gm, ">").replace(/\n/gm, "<br>"); }
-				//else
-				//	{ templateText = templateText.replace(/\n/gm, ""); }
-			} else {
-				templateText = SmartTemplate4.escapeHtml(templateText);
-				// Escape space, if compose is HTML
-				if (gMsgCompose.composeHTML)
-					{ templateText = templateText.replace(/ /gm, "&nbsp;"); }
-			}
-		}
+		
 		SmartTemplate4.calendar.init(); // set for default locale
-		let regular = SmartTemplate4.regularize(templateText, composeType, isStationery);
+		let regular = SmartTemplate4.regularize(templateText, composeType, isStationery, ignoreHTML, !composeType || pref.isUseHtml(idKey, composeType, false));
 		
 		// now that all replacements were done, lets run our global routines to replace / delete text, (such as J.B. "via Paypal")
 		regular = SmartTemplate4.parseModifier(regular); // run global replacement functions (deleteText, replaceText)
Index: content/smartTemplate-overlay.js
===================================================================
--- content/smartTemplate-overlay.js	(revision 694)
+++ content/smartTemplate-overlay.js	(working copy)
@@ -209,10 +209,13 @@
 	}
 
 	var msgContent = "";
-	while (inputStream.available()) {
-		msgContent = msgContent + inputStream.read(2048);
-		if (msgContent.search(/\r\n\r\n|\r\r|\n\n/) > 0) {
-			msgContent = msgContent.split(/\r\n\r\n.*|\r\r.*|\n\n.*/)[0] + "\r\n";
+	var contentCache = "";
+	while (inputStream.available()) { 
+		msgContent = msgContent + inputStream.read(2048); 
+		var p = msgContent.search(/\r\n\r\n|\r\r|\n\n/); //todo: it would be faster to just search in the new block (but also needs to check the last 3 bytes)
+		if (p > 0) {
+			contentCache = msgContent.substr(p + (msgContent[p] == msgContent[p+1] ? 2 : 4));
+			msgContent = msgContent.substr(0, p) + "\r\n";
 			break;
 		}
 		if (msgContent.length > 2048 * 8) {
@@ -232,10 +235,20 @@
 		var str = headers.extractHeader(header, false);
 		return str ? str : "";
 	};
+	
+	// -----------------------------------
+	// Get content
+	function content(size) {
+	  while (inputStream.available() && contentCache.length < size) 
+	    contentCache += inputStream.read(2048);
+	  if (contentCache.length > size) return contentCache.substr(0, size);
+	  else return contentCache;
+	};
 
 	// -----------------------------------
 	// Public methods
 	this.get = get;
+	this.content = content;
 
 	return null;
 };
@@ -499,7 +512,7 @@
 // -------------------------------------------------------------------
 // Regularize template message
 // -------------------------------------------------------------------
-SmartTemplate4.regularize = function(msg, type, isStationery)
+SmartTemplate4.regularize = function(msg, type, isStationery, ignoreHTML, isDraftLike)
 {
 	function getSubject(current) {
 		SmartTemplate4.Util.logDebugOptional('regularize', 'getSubject(' + current + ')');
@@ -527,13 +540,6 @@
 	// AG: I think this function is designed to break out a more specialized variable
 	// such as %toname()% to a simpler one, like %To%
 	function simplify(aString) {
-		// building a hash table?
-		// setRw2h("header", "reserved word",,,)
-		function setRw2h() {
-			for (var i = 1; i < arguments.length; i++) {
-				rw2h[arguments[i]] = arguments[0]; // set the type of each token: d.c., To, Cc, Date, From, Subject
-			}
-		}
 		// Check existence of a header related to the reserved word.
 		function chkRw(str, reservedWord, param) {
 			try{
@@ -566,21 +572,6 @@
 
 		SmartTemplate4.Util.logDebugOptional('regularize', 'simplify()');
 
-		// Reserved words that do not depend on the original message.
-		// identity(name) is the new ownname
-		// identity(mail) is the new ownmail
-		setRw2h("d.c.", "ownname", "ownmail", "deleteText", "replaceText",
-						"Y", "y", "m", "n", "d", "e", "H", "k", "I", "l", "M", "S", "T", "X", "A", "a", "B", "b", "p",
-						"X:=today", "dbg1", "datelocal", "dateshort", "date_tz", "tz_name", "sig", "newsgroup", "cwIso", 
-						"cursor", "identity", "quotePlaceholder", "language", "quoteHeader", "smartTemplate");
-
-		// Reserved words which depend on headers of the original message.
-		setRw2h("To", "to", "toname", "tomail");
-		setRw2h("Cc", "cc", "ccname", "ccmail");
-		setRw2h("Date", "X:=sent");
-		setRw2h("From", "from", "fromname", "frommail");
-		setRw2h("Subject", "subject");
-
 		// [AG] First Step: use the chkRws function to process any "broken out" parts that are embedded in {  .. } pairs
 		// aString = aString.replace(/{([^{}]+)}/gm, chkRws);
 		aString = aString.replace(/\[\[([^\[\]]+)\]\]/gm, chkRws);
@@ -617,11 +608,33 @@
 		} (hdr.get("Date"));
 	}
 	// rw2h["reserved word"] = "header"
-	var rw2h = new Array();
+	var rw2h = {};
+	// building a hash table?
+	// setRw2h("header", "reserved word",,,)
+	function setRw2h() {
+		for (var i = 1; i < arguments.length; i++) {
+			rw2h[arguments[i]] = arguments[0]; // set the type of each token: d.c., To, Cc, Date, From, Subject
+		}
+	}
+	// Reserved words that do not depend on the original message.
+	// identity(name) is the new ownname
+	// identity(mail) is the new ownmail
+	setRw2h("d.c.", "ownname", "ownmail", "deleteText", "replaceText",
+					"Y", "y", "m", "n", "d", "e", "H", "k", "I", "l", "M", "S", "T", "X", "A", "a", "B", "b", "p",
+					"X:=today", "dbg1", "datelocal", "dateshort", "date_tz", "tz_name", "sig", "newsgroup", "cwIso", 
+					"cursor", "identity", "quotePlaceholder", "language", "quoteHeader", "smartTemplate", "internal-javascript-ref",
+					"messageRaw" //depends on the original message, but not on any header
+					);
 
-	// AG: remove any parts ---in curly brackets-- (replace with  [[  ]] ) optional lines
-	msg = simplify(msg);
+	// Reserved words which depend on headers of the original message.
+	setRw2h("To", "to", "toname", "tomail");
+	setRw2h("Cc", "cc", "ccname", "ccmail");
+	setRw2h("Date", "X:=sent");
+	setRw2h("From", "from", "fromname", "frommail");
+	setRw2h("Subject", "subject");
 
+
+
 	// Convert PRTime to string
 	function prTime2Str(time, timeType, timezone) {
 		SmartTemplate4.Util.logDebugOptional('regularize','prTime2Str(' + time + ', ' + timeType + ', ' + timezone + ')');
@@ -898,7 +911,7 @@
 				SmartTemplate4.Util.logDebugOptional ('replaceReservedWords', text);
 			};
 			return s;
-		}
+		} 
 		var tm = new Date();
 		var d02 = function(val) { return ("0" + val).replace(/.(..)/, "$1"); }
 		var expand = function(str) { return str.replace(/%([\w-]+)%/gm, replaceReservedWords); }
@@ -1058,7 +1071,11 @@
 					//if(isStationery)
 					//	return dmy;
 					return '<div class="st4cursor">&nbsp;</div>'; 
+			  case "internal-javascript-ref":
+			    return javascriptResults[/\((.*)\)/.exec(arg)[1]];
 				// any headers (to/cc/from/date/subject/message-id/newsgroups, etc)
+				case "messageRaw": //returns the arg-th first characters of the content of the original message
+				  return hdr.content(arg?/\((.*)\)/.exec(arg)[1]*1:2048);
 				default:
 					var isStripQuote = RegExp(" " + token + " ", "i").test(
 					                   " Bcc Cc Disposition-Notification-To Errors-To From Mail-Followup-To Mail-Reply-To Reply-To" +
@@ -1084,8 +1101,114 @@
 		}
 		return SmartTemplate4.escapeHtml(token);
 	}
+	
+	var sandbox;
+	
+	function replaceJavascript(dmy, token) {
+	  if (!sandbox) {
+	    sandbox = new Components.utils.Sandbox(
+        window,
+        {
+        //  'sandboxName': aScript.id,
+          'sandboxPrototype': window,
+          'wantXrays': true
+        });
+        
+      //useful functions (especially if you want to change the template depending on the received message)
+      sandbox.choose = function(a){return a[Math.floor(Math.random()*a.length)]};
+      sandbox.String.prototype.contains = function(s, startIndex){return this.indexOf(s, startIndex) >= 0};
+      sandbox.String.prototype.containsSome = function(a){return a.some(function(s){return this.indexOf(s) >= 0}, this)};
+      sandbox.String.prototype.count = function(s, startIndex){
+        var count = 0; 
+        var pos = this.indexOf(s, startIndex);
+        while (pos != -1) { 
+          count += 1; 
+          pos = this.indexOf(s, pos + 1);
+        }
+        return count;
+      };        
+      sandbox.variable = function(name, arg){return replaceReservedWords("", name, arg?arg:"")};
+      var implicitNull = {};
+      //var strangeImplicitCall = function () { return this(implicitNull); }
+      var stringFunctionHack = new Function();
+      var props = ["charAt", "charCodeAt", "concat", "contains", "endsWith", "indexOf", "lastIndexOf", "localeCompare", "match", "quote", "repeat", "replace", "search", "slice", "split", "startsWith", "substr", "substring", "toLocaleLowerCase", "toLocaleUpperCase", "toLowerCase", "toUpperCase", "trim", "trimLeft", "trimRight",  "contains", "containsSome", "count"];
+      for (var i=0;i<props.length;i++) {
+        var s = props[i];
+        stringFunctionHack[s] = sandbox.String.prototype[s];
+      }
+      stringFunctionHack.valueOf = function(){return this(implicitNull);};
+      stringFunctionHack.toString = function(){return this(implicitNull);};
+        
+      for (var name in rw2h) {
+        sandbox[name] = (function(aname){return function(arg){
+          if (typeof arg === "undefined") return "%"+aname + "()%"; //do not allow name()            
+          if (arg === implicitNull) arg = "";
+          else arg = "("+arg+")";                         //handles the case %%name(arg)%% and returns the same as %name(arg)%
+          return replaceReservedWords("", aname, arg);
+        };})(name);
+        
+        sandbox[name].__proto__ = stringFunctionHack; //complex hack so that sandbox[name] is a function that can be called with (sandbox[name]) and (sandbox[name](...))
+        //does not work:( sandbox[name].__defineGetter__("length", (function(aname){return function(){return sandbox[aname].toString().length}})(name));
+        
+        //simpler hack (that does not have all string methods):
+//        sandbox[name].toString = strangeImplicitCall;     //handles the case %%name%% and returns the same as %name%
+//        sandbox[name].valueOf = strangeImplicitCall;
+      }
+      
+
+	  };
+	 //  alert(token);
+	  var x;
+	  try {
+	    x = Components.utils.evalInSandbox("("+token+").toString()", sandbox); 
+	    if (x.toString === String.prototype.toString) x = x.toString(); //prevent sandbox leak by templates that redefine toString (no idea if this works, or is actually needed)
+	    else x = "security violation";
+	  } catch (ex) {
+	    x = "ERR: "+ex;
+	  }
+	  javascriptResults.push(x);
+	 // alert(x);	  
+	  return "%internal-javascript-ref("+(javascriptResults.length-1)+")%"; //todo: safety checks (currently the sandbox is useless)
+	}
+	
+	//process javascript insertions first, so the javascript source is not broken by the remaining processing
+	var javascriptResults = []; //but cannot insert result now, or it would be double html escaped, so insert them later
+	msg = msg.replace(/%%((.|\n)*?)%%/gm, replaceJavascript);
+	
+	//Now do this chaotical stuff:
+	
+  //Reset X to Today after each newline character
+	//except for lines ending in { or }; breaks the omission of non-existent CC??
+	msg = msg.replace(/\n/gm, "%X:=today%\n");
+	//replace this later!!
+	// msg = msg.replace(/{\s*%X:=today%\n/gm, "{\n");
+	// msg = msg.replace(/}\s*%X:=today%\n/gm, "}\n");
+	msg = msg.replace(/\[\[\s*%X:=today%\n/gm, "[[\n");
+	msg = msg.replace(/\]\]\s*%X:=today%\n/gm, "]]\n");
+
+	// ignoreHTML, e,g with signature, lets not do html processing
+	if (!ignoreHTML) {
+		// for Draft, let's just assume html for the moment.
+		if (isDraftLike) {
+			msg = msg.replace(/( )+(<)|(>)( )+/gm, "$1$2$3$4");
+			if (pref.isReplaceNewLines(idKey, composeType, true))
+				{ msg = msg.replace(/>\n/gm, ">").replace(/\n/gm, "<br>"); }
+			//else
+			//	{ msg = msg.replace(/\n/gm, ""); }
+		} else {
+			msg = SmartTemplate4.escapeHtml(msg);
+			// Escape space, if compose is HTML
+			if (gMsgCompose.composeHTML)
+				{ msg = msg.replace(/ /gm, "&nbsp;"); }
+		}
+	}
+	// AG: remove any parts ---in curly brackets-- (replace with  [[  ]] ) optional lines
+	msg = simplify(msg);	
+	
 	msg = msg.replace(/%([\w-:=]+)(\([^)]+\))*%/gm, replaceReservedWords);
 	
+	if (sandbox) Components.utils.nukeSandbox(sandbox);
+	
 	SmartTemplate4.Util.logDebugOptional('regularize',"SmartTemplate4.regularize(" + msg + ")  ...ENDS");
 	return msg;
 };
