diff src/nabble/view/web/template/HtmlListNamespace.java @ 0:7ecd1a4ef557

add content
author Franklin Schmidt <fschmidt@gmail.com>
date Thu, 21 Mar 2019 19:15:52 -0600
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/nabble/view/web/template/HtmlListNamespace.java	Thu Mar 21 19:15:52 2019 -0600
@@ -0,0 +1,920 @@
+package nabble.view.web.template;
+
+import fschmidt.html.Html;
+import fschmidt.html.HtmlCdata;
+import fschmidt.html.HtmlScript;
+import fschmidt.html.HtmlStyle;
+import fschmidt.html.HtmlTag;
+import fschmidt.html.HtmlTextContainer;
+import fschmidt.util.java.HtmlUtils;
+import fschmidt.util.java.ObjectUtils;
+import nabble.model.FileUpload;
+import nabble.model.Message;
+import nabble.model.ModelHome;
+import nabble.model.Node;
+import nabble.model.User;
+import nabble.naml.compiler.Command;
+import nabble.naml.compiler.CommandSpec;
+import nabble.naml.compiler.IPrintWriter;
+import nabble.naml.compiler.Interpreter;
+import nabble.naml.compiler.Namespace;
+import nabble.naml.compiler.ScopedInterpreter;
+import nabble.naml.namespaces.ListSequence;
+import nabble.view.lib.Jtp;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+
+@Namespace (
+	name = "html_list",
+	global = true
+)
+public final class HtmlListNamespace extends ListSequence<Object> {
+	private static final Logger logger = LoggerFactory.getLogger(HtmlListNamespace.class);
+
+	private final Message.Source source;
+	private final Message.Format format;
+
+	public HtmlListNamespace(Html list,Message.Source source, Message.Format  format) {
+		super(list);
+		this.source = source;
+		this.format = format;
+	}
+
+	public String toString() {
+		return ObjectUtils.join(elements);
+	}
+
+	public String toMailText() {
+		return htmlToTextMail2(elements);
+	}
+
+	public static final CommandSpec process_raw_tags = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_raw_tags(IPrintWriter out,Interpreter interp) {
+		processRaw(elements);
+	}
+
+	public static final CommandSpec process_cdata_tags = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_cdata_tags(IPrintWriter out,Interpreter interp) {
+		processCDATA(elements);
+	}
+
+	public static final CommandSpec process_quotes = CommandSpec.NO_OUTPUT()
+		.scopedParameters("wrote")
+		.optionalParameters("max_quoted_lines")
+		.build()
+	;
+
+	@Command public void process_quotes(IPrintWriter out,ScopedInterpreter<AuthorWroteNamespace> interp) {
+		int maxQuotedLines = interp.getArgAsInt("max_quoted_lines",10);
+		processQuotes(elements,maxQuotedLines, interp);
+	}
+
+	public static final CommandSpec process_quotes_as_text = CommandSpec.NO_OUTPUT()
+		.scopedParameters("wrote")
+		.build()
+	;
+
+	@Command public void process_quotes_as_text(IPrintWriter out,ScopedInterpreter<AuthorWroteNamespace> interp) {
+		// In order to avoid complexity, the algorithm should process
+		// just one type of line breaks. Since the desired output is TEXT,
+		// the chosen line break is CRLF. Thus we have to convert <br>
+		// into CRLF below.
+		if (format == Message.Format.HTML) {
+			convertBRIntoCRLF(elements);
+		}
+		// All lines breaks are CRLF now...
+		processQuotesText(elements, 0, interp);
+	}
+
+	private void convertBRIntoCRLF(List<Object> elements) {
+		for (int i = 0; i < elements.size(); i++) {
+			Object o = elements.get(i);
+			if (o instanceof HtmlTag && ((HtmlTag) o).getName().equals("br"))
+				elements.set(i, "\n");
+			else if (o instanceof String) {
+				elements.set(i, ((String)o).replaceAll("\r?\n", ""));
+			}
+		}
+	}
+
+	public static final CommandSpec process_email = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_email(IPrintWriter out,Interpreter interp) {
+		processEmail(elements, source);
+	}
+
+	public static final CommandSpec set_target_to_top = CommandSpec.NO_OUTPUT;
+
+	@Command public void set_target_to_top(IPrintWriter out,Interpreter interp) {
+		setTargetToTop(elements);
+	}
+
+	public static final CommandSpec add_nofollow = CommandSpec.NO_OUTPUT()
+		.optionalParameters("accept_rel_follow")
+		.build()
+	;
+
+	@Command public void add_nofollow(IPrintWriter out,Interpreter interp) {
+		boolean acceptRelFollow = interp.getArgAsBoolean("accept_rel_follow", false);
+		addNofollow(elements, acceptRelFollow);
+	}
+
+	public static final CommandSpec process_smilies = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_smilies(IPrintWriter out,Interpreter interp) {
+		processSmilies(elements,true);
+	}
+
+	public static final CommandSpec process_smilies_as_text = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_smilies_as_text(IPrintWriter out,Interpreter interp) {
+		processSmilies(elements,false);
+	}
+
+	public static final CommandSpec process_file_tags = CommandSpec.NO_OUTPUT;
+
+	@Command public void process_file_tags(IPrintWriter out,Interpreter interp) {
+		FileUpload.processFileTags(elements,source);
+	}
+
+
+
+	// security
+
+	public static final CommandSpec disable_banned_tags = CommandSpec.NO_OUTPUT()
+		.dotParameter("banned_tags")
+		.optionalParameters("remove")
+		.build()
+	;
+
+	@Command public void disable_banned_tags(IPrintWriter out,Interpreter interp) {
+		String[] tags = splitAndTrim(interp.getArgString("banned_tags"));
+		boolean remove = interp.getArgAsBoolean("remove", format.isMail());
+		disableBannedTags(elements,remove,tags);
+	}
+
+	public static final CommandSpec disable_invalid_urls = CommandSpec.NO_OUTPUT()
+		.dotParameter("url_attributes")
+		.build()
+	;
+
+	@Command public void disable_invalid_urls(IPrintWriter out,Interpreter interp) {
+		String[] attrs = splitAndTrim(interp.getArgString("url_attributes"));
+		disableInvalidUrls(elements,false,attrs);
+	}
+
+	public static final CommandSpec disable_javascript_urls = CommandSpec.NO_OUTPUT()
+		.dotParameter("url_attributes")
+		.build()
+	;
+
+	@Command public void disable_javascript_urls(IPrintWriter out,Interpreter interp) {
+		String[] attrs = splitAndTrim(interp.getArgString("url_attributes"));
+		disableJavascriptUrls(elements,false,attrs);
+	}
+
+	public static final CommandSpec disable_on_event = CommandSpec.NO_OUTPUT;
+
+	@Command public void disable_on_event(IPrintWriter out,Interpreter interp) {
+		disableOnEvent(elements,false);
+	}
+
+	public static final CommandSpec disable_scripts = CommandSpec.NO_OUTPUT;
+
+	@Command public void disable_scripts(IPrintWriter out,Interpreter interp) {
+		disableScripts(elements,false);
+	}
+
+	public static final CommandSpec disable_style_blocks = CommandSpec.NO_OUTPUT()
+		.optionalParameters("remove")
+		.build()
+	;
+
+	@Command public void disable_style_blocks(IPrintWriter out,Interpreter interp) {
+		boolean remove = interp.getArgAsBoolean("remove", format.isMail());
+		disableStyleBlocks(elements,remove);
+	}
+
+
+	static String[] splitAndTrim(String s) {
+		final String[] a = s.split(",");
+		for( int i=0; i<a.length; i++ ) {
+			a[i] = a[i].trim();
+		}
+		return a;
+	}
+
+
+
+
+	public static final CommandSpec process_tag = CommandSpec.NO_OUTPUT()
+		.parameters("tag")
+		.scopedParameters("do")
+		.dotParameter("do")
+		.build()
+	;
+
+	@Command public void process_tag(IPrintWriter out,ScopedInterpreter<TagNamespace> interp) {
+		String tagName = interp.getArgString("tag");
+		for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				if( tag.getName().equals(tagName) ) {
+					TagNamespace ns = new TagNamespace(tag);
+					String replacement = interp.getArgString(ns,"do");
+					i.set(replacement);
+				}
+			}
+		}
+	}
+
+	public static final CommandSpec process_text = CommandSpec.NO_OUTPUT()
+		.parameters("text")
+		.dotParameter("replacement")
+		.build()
+	;
+
+	@Command public void process_text(IPrintWriter out,Interpreter interp) {
+		String text = interp.getArgString("text");
+		String replacement = interp.getArgString("replacement");
+		for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof String ) {
+				String s = (String) curr;
+				if (text.equals(s)) {
+					i.set(replacement);
+				}
+			}
+		}
+	}
+
+	@Namespace (
+		name = "html_tag",
+		global = false,
+		transparent = true
+	)
+	public static final class TagNamespace {
+		private final HtmlTag tag;
+
+		private TagNamespace(HtmlTag tag) {
+			this.tag = tag;
+		}
+
+		@Command public void tag_as_string(IPrintWriter out,Interpreter interp) {
+			out.print(tag);
+		}
+
+		public static final CommandSpec tag_attribute = new CommandSpec.Builder()
+			.dotParameter("name")
+			.build()
+		;
+
+		@Command public void tag_attribute(IPrintWriter out,Interpreter interp) {
+			out.print( HtmlTag.unquote(tag.getAttributeValue(interp.getArgString("name"))) );
+		}
+
+
+		public static final CommandSpec tag_has_attribute = new CommandSpec.Builder()
+			.dotParameter("name")
+			.build()
+		;
+
+		@Command public void tag_has_attribute(IPrintWriter out,Interpreter interp) {
+			out.print( tag.getAttributeValue(interp.getArgString("name")) != null );
+		}
+
+	}
+
+
+	public static final CommandSpec process_embed = new CommandSpec.Builder()
+		.dotParameter("regex")
+		.build()
+	;
+
+	@Command public void process_embed(IPrintWriter out,Interpreter interp) {
+		Pattern ptn = Pattern.compile( interp.getArgString("regex") );
+		for( ListIterator<Object> i = elements.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				if( tag.getName().equals("nabble_embed") ) {
+					if( tag.isEmpty() ) {  // no good
+						i.set( new Embedded(HtmlUtils.htmlEncode(tag.toString())) );
+					} else {
+						List<Object> list = new ArrayList<Object>();
+						while(true) {
+							i.remove();
+							if( !i.hasNext() ) {  // no closing tag
+								list.add(0,tag);
+								elements.add( new Embedded(HtmlUtils.htmlEncode(ObjectUtils.join(list))) );
+								return;
+							}
+							curr = i.next();
+							if( curr instanceof HtmlTag ) {
+								HtmlTag tag2 = (HtmlTag)curr;
+								if( tag2.getName().equals("/nabble_embed") )  // done
+									break;
+							}
+							list.add(curr);
+						}
+						String text = ObjectUtils.join(list);
+						if( ptn.matcher(text).matches() ) {  // ok
+							i.set( new Embedded(text) );
+						} else {  // not ok
+							list.add(0,tag);
+							list.add(curr);
+							i.set( new Embedded(HtmlUtils.htmlEncode(ObjectUtils.join(list))) );
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private static class Embedded {
+		private final String text;
+
+		Embedded(String text) {
+			this.text = text;
+		}
+
+		public String toString() {
+			return text;
+		}
+
+	}
+
+
+
+	// from MessageFormatImpls
+
+
+	private static void processRaw(List<Object> list) {
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if( o instanceof HtmlTextContainer ) {
+				HtmlTextContainer container = (HtmlTextContainer)o;
+				if( container.startTag.getName().equalsIgnoreCase("raw") ) {
+					HtmlTag preTag = new HtmlTag(container.startTag);
+					preTag.setName("pre");
+					i.set( new Embedded(
+						preTag.toString() + HtmlUtils.htmlEncode(container.text) + "</pre>"
+					) );
+				}
+			}
+		}
+	}
+
+	private static void processCDATA(List<Object> list) {
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if (o instanceof HtmlCdata) {
+				HtmlCdata container = (HtmlCdata) o;
+				container.toString();
+				i.set("<pre>" + HtmlUtils.htmlEncode("<![CDATA[" + container.text + "]]>") + "</pre>");
+			}
+		}
+	}
+
+
+	private static final HtmlTag blockquote = new HtmlTag("blockquote");
+	private static final HtmlTag _blockquote = new HtmlTag("/blockquote");
+	private static final HtmlTag divQuote = new HtmlTag("div");
+	private static final HtmlTag divQuoteAuthor = new HtmlTag("div");
+	private static final HtmlTag divQuoteMessage = new HtmlTag("div");
+	private static final HtmlTag divQuoteMessageHidden = new HtmlTag("div");
+	private static final HtmlTag _div = new HtmlTag("/div");
+	static {
+		blockquote.setAttribute("class",HtmlTag.quote("quote dark-border-color"));
+		divQuote.setAttribute("class",HtmlTag.quote("quote light-border-color"));
+		divQuoteAuthor.setAttribute("class",HtmlTag.quote("quote-author"));
+		divQuoteAuthor.setAttribute("style",HtmlTag.quote("font-weight: bold;"));
+		divQuoteMessage.setAttribute("class",HtmlTag.quote("quote-message"));
+		divQuoteMessageHidden.setAttribute("class",HtmlTag.quote("quote-message shrinkable-quote"));
+	}
+
+
+	@Namespace (
+		name = "html_list_author_wrote",
+		global = true
+	)
+	public static class AuthorWroteNamespace {
+		private final String authorStr;
+
+		private AuthorWroteNamespace(String authorStr) {
+			this.authorStr = authorStr;
+		}
+
+		@Command public void author(IPrintWriter out,Interpreter interp) {
+			out.print(authorStr);
+		}
+	}
+
+	private static class QuoteInfo {
+		final int i;
+		final int nBreaks;
+
+		QuoteInfo(int i,int nBreaks) {
+			this.i = i;
+			this.nBreaks = nBreaks;
+		}
+	}
+
+	private static final Set<String> breakingTags = new HashSet<String>(Arrays.asList(
+		"br",
+		"div",
+		"p"
+	));
+
+	private static void processQuotes(List<Object> list, int maxQuotedLines, ScopedInterpreter<AuthorWroteNamespace> interp) {
+		processQuotes(list,maxQuotedLines,0, interp);
+	}
+
+	private static QuoteInfo processQuotes(List<Object> list,int maxQuotedLines,int start, ScopedInterpreter<AuthorWroteNamespace> interp) {
+		int nBreaks = 0;
+		int n = list.size();
+		for( int i=start; i<n; i++ ) {
+			Object o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("/quote") )
+				return new QuoteInfo(i,nBreaks);
+			if( tagName.equals("quote") ) {
+				QuoteInfo qi = processQuotes(list,maxQuotedLines,i+1, interp);
+				if( qi==null ) {
+					list.set(i,HtmlUtils.htmlEncode(tag.toString()));
+					return null;
+				}
+				int closeQuote = qi.i;
+				nBreaks += qi.nBreaks + 1;
+				int fromEnd = list.size() - closeQuote;
+
+				list.remove(i);
+				nBreaks -= removeBr(list,i);
+				list.add(i++,blockquote);
+				list.add(i++,divQuote);
+				list.add(i++,"\n");
+				String author = HtmlTag.unquote(tag.getAttributeValue("author"));
+				if( author != null ) {
+					list.add(i++,divQuoteAuthor);
+					list.add(i++,interp.getArgString(new AuthorWroteNamespace(author),"wrote"));
+					list.add(i++,_div);
+					list.add(i++,"\n");
+				}
+				list.add(i++, qi.nBreaks < maxQuotedLines ? divQuoteMessage : divQuoteMessageHidden );
+//				list.add(i++, divQuoteMessage );
+
+				i = list.size() - fromEnd;
+				int brs = removeBrUp(list,i-1);
+				nBreaks -= brs;
+				i -= brs;
+				list.remove(i);
+				nBreaks -= removeBr(list,i);
+				list.add(i++,_div);
+				list.add(i++,"\n");
+				list.add(i++,_div);
+				list.add(i++,_blockquote);
+				list.add(i++,"\n");
+				i--;
+
+				n = list.size();
+			}
+			else if( breakingTags.contains(tagName) ) {
+				nBreaks++;
+			}
+		}
+		return null;
+	}
+
+
+	private static final HtmlTag _a = new HtmlTag("/a");
+
+	private static void processEmail(List<Object> list,Message.Source src) {
+		int count = 0;
+		int n = list.size() - 3 + 1;
+		for( int i=0; i<n; i++ ) {
+			Object o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			if( !tag.getName().toLowerCase().equals("email") )
+				continue;
+			o = list.get(i+1);
+			if( !(o instanceof String) )
+				continue;
+			String email = (String)o;
+			o = list.get(i+2);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag endTag = (HtmlTag)o;
+			if( !endTag.getName().toLowerCase().equals("/email") )
+				continue;
+			list.remove(i);
+			list.remove(i);
+			list.remove(i);
+			HtmlTag a = new HtmlTag("a");
+			StringBuilder href = new StringBuilder();
+			href.append( "/user/SendEmail.jtp" );
+			if( src==null || src instanceof Message.TempSource) {
+				href.append( "?type=email&email=" ).append( HtmlUtils.urlEncode(email) );
+			} else {
+				if( src instanceof Node ) {
+					Node node = (Node)src;
+					href.append( "?type=node&node=" ).append( node.getId() );
+				} else if( src instanceof User ) {
+					User user = (User)src;
+					href.append( "?type=sig&user=" ).append( user.getId() );
+				} else {
+					throw new RuntimeException("src="+src);
+				}
+				href.append( "&i=" ).append( count++ );
+			}
+			a.setAttribute("href",HtmlTag.quote(href.toString()));
+			a.setAttribute("target","\"_top\"");
+			a.setAttribute("rel","\"nofollow\"");
+			list.add(i++,a);
+			list.add(i++,ModelHome.hideEmail(email));
+			list.add(i,_a);
+		}
+	}
+
+
+	private static void setTargetToTop(List<Object> list) {
+		for( Object o : list ) {
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				if( tag.getName().toLowerCase().equals("a") ) {
+					if( tag.getAttributeValue("target") == null ) {
+						tag.setAttribute("target","\"_top\"");
+					}
+				}
+			}
+		}
+	}
+
+	private static void addNofollow(List<Object> list, boolean acceptRelFollow) {
+		for( Object o : list ) {
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				String tagName = tag.getName().toLowerCase();
+				if (tagName.equals("a")) {
+					if (acceptRelFollow && "\"follow\"".equals(tag.getAttributeValue("rel")))
+						continue;
+					tag.setAttribute("rel","\"nofollow\"");
+					tag.setAttribute("link","\"external\"");
+				}
+			}
+		}
+	}
+
+
+
+	// from HtmlSecurity
+
+	private static void disable(ListIterator<Object> i,Object curr,boolean removeViolation) {
+		if( removeViolation ) {
+			i.remove();
+		} else {
+			i.set(HtmlUtils.htmlEncode(curr.toString()));
+		}
+	}
+
+	public static void disableBannedTags(List<Object> html,boolean removeViolation,String... tagNames) {
+		Set<String> tagNameSet = new HashSet<String>();
+		for( String tagName : tagNames ) {
+			tagNameSet.add(tagName);
+			tagNameSet.add("/"+tagName);
+		}
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				if( tagNameSet.contains(tag.getName().toLowerCase()) ) {
+					disable(i,tag,removeViolation);
+				}
+			}
+		}
+	}
+
+	private static final URL baseUrl;
+	static {
+		try {
+			baseUrl = new URL("https://www.nabble.com/");  // any valid URL is fine here
+		} catch(MalformedURLException e) {
+			logger.error("",e);
+			System.exit(-1);
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static void disableInvalidUrls(List<Object> html,boolean removeViolation,String... parameters) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				for (String attrName: parameters) {
+					String val = HtmlTag.unquote(tag.getAttributeValue(attrName));
+					if (val != null) {
+						try {
+							new URL(baseUrl,val).toURI();
+						} catch(MalformedURLException e) {
+							disable(i,tag,removeViolation);
+							break;
+						} catch(URISyntaxException e) {
+							disable(i,tag,removeViolation);
+							break;
+						}
+					}
+				}
+			}
+		}
+	}
+
+	private static void disableJavascriptUrls(List<Object> html,boolean removeViolation,String... parameters) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				for (String attrName: parameters) {
+					String val = HtmlTag.unquote(tag.getAttributeValue(attrName));
+					if (val != null && val.toLowerCase().startsWith("javascript:")) {
+						disable(i,tag,removeViolation);
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	private static void disableOnEvent(List<Object> html,boolean removeViolation) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)curr;
+				for( String attrName : tag.getAttributeNames() ) {
+					if (HtmlTag.unquote(attrName).toLowerCase().startsWith("on")) {
+						disable(i,tag,removeViolation);
+						break;
+					}
+				}
+			}
+		}
+	}
+
+	private static final Pattern urchin = Pattern.compile("\\s*_uacct\\s*=\\s*[\"'][^\"']+[\"']\\s*;\\s*urchinTracker\\(\\);\\s*",Pattern.MULTILINE);
+
+	private static void disableScripts(List<Object> html,boolean removeViolation) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlScript ) {
+				HtmlScript script = (HtmlScript)curr;
+				String src = HtmlTag.unquote(script.startTag.getAttributeValue("src"));
+				if( src != null ) {
+					if( script.text.trim().length()==0 ) {
+						if( src.equals("http://www.google-analytics.com/urchin.js") )
+							continue;
+					}
+				} else {
+					if( urchin.matcher(script.text).matches() )
+						continue;
+				}
+				disable(i,script,removeViolation);
+			}
+		}
+	}
+
+	private static void disableStyleBlocks(List<Object> html,boolean removeViolation) {
+		for( ListIterator<Object> i = html.listIterator(); i.hasNext(); ) {
+			Object curr = i.next();
+			if( curr instanceof HtmlStyle ) {
+				disable(i,curr,removeViolation);
+			}
+		}
+	}
+
+
+	private static String htmlToTextMail2(List<Object> html) {
+	    StringBuilder buf = new StringBuilder();
+	    for (int i = 0; i < html.size(); i++) {
+			Object next = html.get(i);
+	        if( next instanceof String ) {
+	            buf.append(next);
+	        } else if( next instanceof HtmlTag) {
+	        	HtmlTag tag = (HtmlTag)next;
+				String tagName = tag.getName().toLowerCase();
+	        	String separator = Message.htmlSeparators.get(tagName);
+	        	if (separator!=null && buf.length()>0 && buf.charAt(buf.length()-1)!='\n')
+	        		buf.append(separator);
+	        	if (tagName.equals("img")) {
+	        		String src = tag.getAttributeValue("src");
+	        		if (src!=null) {
+	        			buf.append('<');
+	        			buf.append(HtmlTag.unquote(src));
+	        			buf.append("> ");
+	        		}
+	        	}
+	        	else if (tagName.equals("a")) {
+	        		String src = tag.getAttributeValue("href");
+	        		if (src!=null) {
+						String anchorText = html.get(i+1).toString();
+						buf.append(anchorText.trim());
+						buf.append(" <");
+	        			buf.append(HtmlTag.unquote(src));
+	        			buf.append("> ");
+						i++;
+	        		}
+	        	}
+	        }
+	    }
+	    Message.wrapQuoteText(buf);
+	    return buf.toString();
+	}
+
+
+	private static int processQuotesText(List<Object> list, int start, ScopedInterpreter<AuthorWroteNamespace> interp) {
+		int n = list.size();
+		for( int i=start; i<n; i++ ) {
+			Object o = list.get(i);
+			if( !(o instanceof HtmlTag) )
+				continue;
+			HtmlTag tag = (HtmlTag)o;
+			String tagName = tag.getName().toLowerCase();
+			if( tagName.equals("/quote") )
+				return i;
+			if( tagName.equals("quote") ) {
+				int closeQuote = processQuotesText(list, i+1, interp);
+				if( closeQuote == -1 )
+					return -1;
+				int fromEnd = list.size() - closeQuote;
+
+				list.remove(i);
+				removeCRLF(list, i, 0);
+				if (i > 0)
+					list.add(i++,"\n");
+
+				String author = HtmlTag.unquote(tag.getAttributeValue("author"));
+				if( author != null ) {
+					list.add(i++,interp.getArgString(new AuthorWroteNamespace(author),"wrote"));
+				}
+				int begin = i;
+				i = list.size() - fromEnd;
+				removeCRLFUp(list, i-1, -1);
+				list.remove(i);
+				removeCRLF(list, i, 0);
+				for (int j=begin;j<i;j++)
+					quoteText(list,j);
+
+				list.add(i, "\n");
+				n = list.size();
+			}
+		}
+		return -1;
+	}
+
+	private static void quoteText(List<Object> list,int i) {
+		Object obj = list.get(i);
+		if( !(obj instanceof String) )
+			return;
+		String s = (String) obj;
+		if (s.length() == 0)
+			return;
+
+		removeCRLFUp(list, i-1, -1);
+		s = CRLF_UP_PTN.matcher(s).replaceAll("");
+
+		if (!s.startsWith("\n") && !s.startsWith("\r\n") )
+			s = "\n" + s;
+
+		s = s.replaceAll("\n", "\n> ");
+		s = s.replaceAll("\n> >", "\n>>");
+
+		list.set(i, s + '\n');
+		removeCRLF(list, i+1, +1);
+	}
+
+	private static final Pattern CRLF_PTN = Pattern.compile("^(\\r?\\n){1}");
+	private static final Pattern CRLF_UP_PTN = Pattern.compile("(\\r?\\n){1}$");
+	private static final Pattern BR_PTN = Pattern.compile("^(\\s*<br/?>){1,2}");
+	private static final Pattern BR_UP_PTN = Pattern.compile("(<br/?>\\s*){1,2}$");
+
+	private static void removeCRLF(List<Object> list, int i, int increment) {
+		Object obj = i >= 0 && i < list.size()? list.get(i) : null;
+		if (obj instanceof String) {
+			String s = (String) obj;
+			while (s.length() == 0 && increment != 0) {
+				i += increment;
+				obj = i >= 0 && i < list.size()? list.get(i) : null;
+				if (obj == null || !(obj instanceof String))
+					return;
+				s = (String) obj;
+			}
+			s = CRLF_PTN.matcher(s).replaceFirst("");
+			list.set(i, s);
+		}
+	}
+
+	private static void removeCRLFUp(List<Object> list, int i, int increment) {
+		Object obj = i >= 0 && i < list.size()? list.get(i) : null;
+		if (obj instanceof String) {
+			String s = (String) obj;
+			while (s.length() == 0 && increment != 0) {
+				i += increment;
+				obj = i >= 0 && i < list.size()? list.get(i) : null;
+				if (obj == null || !(obj instanceof String))
+					return;
+				s = (String) obj;
+			}
+			s = CRLF_UP_PTN.matcher(s).replaceFirst("");
+			list.set(i, s);
+		}
+	}
+
+	private static int removeBr(List<Object> list,int i) {
+		return removeBr(list,i,0);
+	}
+
+	private static int removeBr(List<Object> list,int i,int count) {
+		if( count==2 )
+			return count;
+		if( i >= list.size() )
+			return count;
+		Object obj = list.get(i);
+		if( obj instanceof String ) {
+			String s = (String)obj;
+			s = BR_PTN.matcher(s).replaceAll("");
+			list.set(i,s);
+		} else if( obj instanceof HtmlTag ) {
+			HtmlTag tag = (HtmlTag)obj;
+			if( tag.getName().equalsIgnoreCase("br") ) {
+				list.remove(i);
+				return removeBr(list,i,count+1);
+			}
+		}
+		return count;
+	}
+
+	private static int removeBrUp(List<Object> list,int i) {
+		return removeBrUp(list,i,0);
+	}
+
+	private static int removeBrUp(List<Object> list,int i,int count) {
+		if( count==2 )
+			return count;
+		if( i < 0 )
+			return count;
+		Object obj = list.get(i);
+		if( obj instanceof String ) {
+			String s = (String)obj;
+			s = BR_UP_PTN.matcher(s).replaceAll("");
+			list.set(i,s);
+		} else if( obj instanceof HtmlTag ) {
+			HtmlTag tag = (HtmlTag)obj;
+			if( tag.getName().equalsIgnoreCase("br") ) {
+				list.remove(i);
+				return removeBrUp(list,i-1,count+1);
+			}
+		}
+		return count;
+	}
+
+
+//	private static final String smiliesDir = "http://" + Jtp.getDefaultHost() + "/images/smiley/";
+	private static final String smiliesDir = "/images/smiley/";
+
+	private static void processSmilies(List<Object> list,boolean isHtml) {
+		for( ListIterator<Object> i=list.listIterator(); i.hasNext(); ) {
+			Object o = i.next();
+			if( o instanceof HtmlTag ) {
+				HtmlTag tag = (HtmlTag)o;
+				if( tag.getName().toLowerCase().equals("smiley") ) {
+					if( isHtml ) {
+						String s = HtmlTag.unquote(tag.getAttributeValue("image"));
+						if( s != null ) {
+							HtmlTag img = new HtmlTag("img class='smiley' src='"+smiliesDir+s+"' /");
+							i.set(img);
+						}
+					}
+				}
+			}
+		}
+	}
+
+
+}