head	1.1;
access;
symbols;
locks; strict;
comment	@# @;


1.1
date	2001.07.19.00.49.15;	author dlr;	state Exp;
branches;
next	;


desc
@@


1.1
log
@Ported from Joist.
@
text
@/* ================================================================
 * Copyright (c) 2000 CollabNet.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 3. The end-user documentation included with the redistribution, if
 * any, must include the following acknowlegement: "This product includes
 * software developed by CollabNet (http://www.collab.net/)."
 * Alternately, this acknowlegement may appear in the software itself, if
 * and wherever such third-party acknowlegements normally appear.
 *
 * 4. The hosted project names must not be used to endorse or promote
 * products derived from this software without prior written
 * permission. For written permission, please contact info@@collab.net.
 *
 * 5. Products derived from this software may not use the "Tigris" name
 * nor may "Tigris" appear in their names without prior written
 * permission of CollabNet.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED 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 COLLABNET OR ITS 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.
 *
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of CollabNet.
 */

package org.tigris.helm.tool;

import java.util.*;

import org.apache.oro.text.regex.*;
import org.apache.oro.text.perl.*;

/**
  * Checks that Content contains only tags from an accepted set.
  *
  * <p>This does not purport to validate syntactically or semantically correct
  * HTML.  Its sole purpose is to detect the use of dangerous tags and
  * attributes that can be used to execute scripts and other nefarious misdeeds.
  *
  Allowed tags:<br>
  &lt;A&gt; &lt;ABBR&gt; &lt;ACRONYM&gt; &lt;AREA&gt; &lt;B&gt; &lt;BASE&gt; &lt;BASEFONT&gt; &lt;BIG&gt; &lt;BDO&gt; &lt;BLINK&gt; &lt;BLOCKQUOTE&gt; &lt;BR&gt; &lt;CAPTION&gt; &lt;CENTER&gt; &lt;CITE&gt; &lt;CODE&gt; &lt;COL&gt; &lt;COLGROUP&gt; &lt;DD&gt; &lt;DEL&gt; &lt;DFN&gt; &lt;DIV&gt; &lt;DL&gt; &lt;DT&gt; &lt;EM&gt; &lt;FIELDSET&gt; &lt;FONT&gt; &lt;H1&gt; &lt;H2&gt; &lt;H3&gt; &lt;H4&gt; &lt;H5&gt; &lt;H6&gt; &lt;HR&gt; &lt;I&gt; &lt;IMG&gt; &lt;INS&gt; &lt;KBD&gt; &lt;LI&gt; &lt;LABEL&gt; &lt;LEGEND&gt; &lt;LINK&gt; &lt;MAP&gt; &lt;MENU&gt; &lt;MULTICOL&gt; &lt;NOBR&gt; &lt;NOFRAMES&gt; &lt;NOSCRIPT&gt; &lt;OL&gt; &lt;OPTGROUP&gt; &lt;P&gt; &lt;PARAM&gt; &lt;PRE&gt; &lt;Q&gt; &lt;S&gt; &lt;SAMP&gt; &lt;SMALL&gt; &lt;SPACER&gt; &lt;SPAN&gt; &lt;STRIKE&gt; &lt;STRONG&gt; &lt;STYLE&gt; &lt;SUB&gt; &lt;SUP&gt; &lt;TBODY&gt; &lt;TD&gt; &lt;TFOOT&gt; &lt;TH&gt; &lt;THEAD&gt; &lt;TR&gt; &lt;TT&gt; &lt;TABLE&gt; &lt;U&gt; &lt;UL&gt; &lt;VAR&gt; &lt;WBR&gt;
  <p>
  Allowed attributes:<br>
  &lt;ALIGN&gt; &lt;BACKGROUND&gt; &lt;BGCOLOR&gt; &lt;CHAR&gt; &lt;CHAROFF&gt; &lt;CITE&gt; &lt;CLEAR&gt; &lt;COLOR&gt; &lt;COLS&gt; &lt;FACE&gt; &lt;GUTTER&gt; &lt;HEIGHT&gt; &lt;HREF&gt; &lt;SIZE&gt; &lt;START&gt; &lt;TYPE&gt; &lt;WIDTH&gt; &lt;COMPACT&gt; &lt;NOSHADE&gt; &lt;NOWRAP&gt;
  *
  * @@author <a href="mailto:davidp@@dataweb-systems.com">David C. Pellegrini</a>
  * @@author <a href="mailto:jon@@latchkey.com">Jon S. Stevens</a>
  * @@author Michael Salmon <a href="mailto:ms@@collab.net">ms@@collab.net</a>
  * @@version $Revision: 1.13 $
  */
public class TagValidator
{
    static final private String[] ALL_TAGS =
    {"A","ABBR","ACRONYM","AREA","B","BASE","BASEFONT","BIG","BDO","BLINK",
     "BLOCKQUOTE","BR","CAPTION","CENTER","CITE","CODE","COL","COLGROUP",
     "DD","DEL","DFN","DIV","DL","DT","EM","FIELDSET","FONT",
     "H1","H2","H3","H4","H5","H6","HR","I","IMG","INS","KBD","LI",
     "LABEL","LEGEND","LINK","MAP","MENU","MULTICOL","NOBR","NOFRAMES","NOSCRIPT",
     "OL","OPTGROUP","P","PARAM","PRE","Q","S","SAMP","SMALL","SPACER","SPAN",
     "STRIKE", "STRONG","STYLE","SUB","SUP","TBODY","TD","TFOOT","TH","THEAD",
     "TR","TT","TABLE","U","UL","VAR","WBR"};
   
     static final public String[] TEXT_FORMAT_TAGS =
     {"B","BIG","FONT","H1","H2","H3","H4","H5","H6","I","SMALL","STRONG",
      "STRIKE","U"};

     static final public String[] LAYOUT_FORMAT_TAGS = 
     {"BLOCKQUOTE","BR","CENTER","COL","COLGROUP","DIV","LI","P","PRE","SPACER",
      "SPAN","TBODY","TD","TABLE","TR","TH","TFOOT","UL"};

    static final private String[] attributes =
    {"ALIGN","BACKGROUND","BGCOLOR","CHAR","CHAROFF","CITE","CLEAR","COLOR",
     "COLS","FACE","GUTTER","HEIGHT","HREF","SIZE","START","TYPE","WIDTH",
     "COMPACT","NOSHADE","NOWRAP"};
    final String anyAttribute = "[a-zA-Z]*";

    final private String frontRegExp = "<\\s*/?\\s*(?!\\s*";
    final private String middleRegExp = "\\s*).+\\s*\\b\\s*(\\s*(\\s*";
    final private String endRegExp = "\\s*)\\s*(\\s*=\\s*\"?[^<>\\s]+\"?\\s*)?\\s*)*\\s*>";
    
    // the tags to match with this object
    private ArrayList allowTags = null;

    final private String orDelim = "|";
    final private String lt = "&lt;";
    final private String gt = "&gt;";
    
    // For matching ... 
    static final private PatternCompiler matchCompiler = new Perl5Compiler();
    static final private PatternMatcher matchMatcher = new Perl5Matcher();
    static       private Pattern matchPattern = null;

    static final private String matchRegExp = "</?[^<>\\s]*>";  

    /**
     * populate the validator with some tags to allow
     */
    public TagValidator (String[] tags)
    {
        addAllowTags(tags);
    }

    /**
     * Constructor setting up a validator for ALL_TAGS
     */
    public TagValidator ()
    {
        mergeTagLists();
    }

    /**
        This method builds the regex for the pattern matching.
        We do this as a dynamic string because it makes it easier to 
        add new items.
     * @@return	A variable of type String 
    */
    private String buildRegex()
    {
        mergeTagLists();
        StringBuffer sb = new StringBuffer();
        sb.append ( frontRegExp );
        boolean firstTime = true;
        for ( int i=0; i < allowTags.size(); i++ )
        {
            if (firstTime)
            {
                sb.append((String)allowTags.get(i));
                firstTime = false;
            }
            else
            {
                sb.append(orDelim);
                sb.append((String)allowTags.get(i));
            }
        }
        sb.append ( middleRegExp );
        sb.append ( anyAttribute );
        firstTime = true;
        sb.append ( endRegExp );
        return sb.toString();
    }

    /**
        This method builds an HTML representation of all of the allowed Tags
     * @@return	A variable of type String 
    */
    private String buildError()
    {
        StringBuffer sb = new StringBuffer();
        boolean firstTime = true;
        sb.append ( "<b>Allowed tags:</b><br> " );
        for ( int i=0; i< ALL_TAGS.length; i++ )
        {
            if (firstTime)
            {
                sb.append(lt);
                sb.append(ALL_TAGS[i]);
                sb.append(gt);
                firstTime = false;
            }
            else
            {
                sb.append(" ");
                sb.append(lt);
                sb.append(ALL_TAGS[i]);
                sb.append(gt);
            }
        }
        firstTime = true;
        sb.append ( "<p><b>Allowed attributes:</b><br> " );
        for ( int i=0; i<attributes.length; i++ )
        {
            if (firstTime)
            {
                sb.append(lt);
                sb.append(attributes[i]);
                sb.append(gt);
                firstTime = false;
            }
            else
            {
                sb.append(" ");
                sb.append(lt);
                sb.append(attributes[i]);
                sb.append(gt);
            }
        }
        return sb.toString();
    }
    
    /**
     * Checks the input string for HTML tags that are not allowed.
     * <p>
     * Use negative-width lookahead to search for valid tags
     * 
     * @@param	inputString  A variable of type String
     * @@return	 a boolean indicating whether the HTML was OK
     */
    public boolean validate (String inputString)
    {
        // Bootstrap the first time through ...
        if (matchPattern == null)
        {
            synchronized(matchCompiler)
            {
                if (matchPattern == null)    // if it's STILL null  ;-)
                {
                    try
                    {
                        matchPattern = matchCompiler.compile(buildRegex(),
                            Perl5Compiler.CASE_INSENSITIVE_MASK);
                    }
                    catch (MalformedPatternException ignored){};
                }
            }
        }

        PatternMatcherInput input = new PatternMatcherInput(inputString);
        boolean clean = true;
        if (matchMatcher.contains(input, matchPattern))
        {
            clean = false;
        }
        return clean;
    }

    /**
     * Check input string for HTML with specific given tags only
     * 
     * @@param inputString The String to check for HTML 
     * @@param tags The String array of tags to check for {"br","a"} for ex.
     */
    public boolean validate (String inputString, String[] tags)
    {
        setAllowTags(tags);
        return validate(inputString);
    }
        

    /**
     * Check if there are any HTML tags in the string at all
     *
     * @@param inputString - String to check
     * @@return boolean indicating existence of HTML tag(s)
     */

    public static boolean containsHTML (String inputString)
    {
        boolean foundHTML = false;
        if (inputString != null && !inputString.equals(""))
        {
	    try
	    {
	        matchPattern = matchCompiler.compile
                    (matchRegExp, Perl5Compiler.CASE_INSENSITIVE_MASK);
                // First, strip out all occurrences of valid HTML tags.
                PatternMatcherInput input = new PatternMatcherInput(inputString);
	   
                if (matchMatcher.contains(input, matchPattern))
                {
                    foundHTML = true;
                }
            }
            catch (MalformedPatternException ignored)
            {}
        }
        return foundHTML;
    }

    /**
     * Escape HTML tag's so they can be displayed as static text
     * @@param String - to be cleaned
     * @@return String
     */
    public static String escapeHTML(String unclean)
    {
        Perl5Util util = new Perl5Util();
        try
        {
            unclean = util.substitute("s/</&lt;/g", unclean);
            unclean = util.substitute("s/>/&gt;/g", unclean);
            // it is now clean, somewhat
        }
        catch (MalformedPerl5PatternException ignored)
        {}
        return unclean;
    }

    /**
     * Appends given tags to validator's list of tags to allow
     *
     * @@param String[] newTags tags to match, form of {"h1","h2", etc..
     */
    public void addAllowTags(String[] newTags)
    {
        matchPattern = null;
        // let npe's pass if given a null list
        if (allowTags == null && newTags != null)
        {
            allowTags = new ArrayList(newTags.length);
        }
        for (int i = 0; i < newTags.length; i++)
        {
            allowTags.add(newTags[i]);
        }
    }

    public void setAllowTags (String[] newTags)
    {
        matchPattern = null;
        allowTags = null;
        addAllowTags(newTags);
    }
        
    private void mergeTagLists()
    {
        // only add ALL_TAGS if none are explicitly given
        if (allowTags == null)
        {
            addAllowTags(ALL_TAGS);
        }
    }

    /**
     * Clear out tags 
     */
    public void resetAllowTags()
    {
        matchPattern = null;
        allowTags = null;
    }
}
@
