Package org.xwiki.test.escaping.framework

Source Code of org.xwiki.test.escaping.framework.XMLEscapingValidator

/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.test.escaping.framework;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import org.xwiki.validator.ValidationError;
import org.xwiki.validator.ValidationError.Type;
import org.xwiki.validator.Validator;


/**
* A validator that checks for proper XML escaping. The document must be constructed using the special
* test input string (see {@link #getTestString()}).
*
* @version $Id$
* @since 2.5M1
*/
public class XMLEscapingValidator implements Validator
{
    /** Unescaped test string containing XML significant characters. */
    private static final String INPUT_STRING = "aaa\"bbb'ccc>ddd<eee";

    /** Test for unescaped apostrophe. */
    private static final String TEST_APOS = "bbb'ccc";

    /** Test for unescaped quote. */
    private static final String TEST_QUOT = "aaa\"bbb";

    /** Test for unescaped tag start. */
    private static final String TEST_LT = "ddd<eee";

    /** Test for unescaped tag end. */
    private static final String TEST_GT = "ccc>ddd";

    /** JavaScript-escaped TEST_APOS. */
    private static final String TEST_JS_APOS = "bbb\\'ccc";

    /** JavaScript-escaped TEST_QUOT. */
    private static final String TEST_JS_QUOT = "aaa\\\"bbb";

    /** Expect an empty or non-empty document. */
    private boolean shouldBeEmpty = false;

    /** Source of the XML document to validate. */
    private List<String> document = new ArrayList<String>();

    /** List of validation errors. */
    private List<ValidationError> errors = new ArrayList<ValidationError>();

    /**
     * Get the input string containing XML significant characters that should be used.
     *
     * @return test string to use
     */
    public static String getTestString()
    {
        return INPUT_STRING;
    }

    /**
     * {@inheritDoc}
     * <p>
     * Clears previous list of validation errors.</p>
     *
     * @see org.xwiki.validator.Validator#setDocument(java.io.InputStream)
     */
    @Override
    public void setDocument(InputStream document)
    {
        BufferedReader reader = new BufferedReader(new InputStreamReader(document));
        String line;
        this.document = new ArrayList<String>();
        try {
            while ((line = reader.readLine()) != null) {
                this.document.add(line);
            }
        } catch (IOException exception) {
            throw new RuntimeException("Could not read document: ", exception);
        }
        clear();
    }

    /**
     * {@inheritDoc}
     * <p>
     * Throws {@link EscapingError} on errors.</p>
     *
     * @see org.xwiki.validator.Validator#validate()
     */
    @Override
    public List<ValidationError> validate()
    {
        clear();
        if (this.document.size() == 0 && !this.shouldBeEmpty) {
            this.errors.add(new ValidationError(Type.WARNING, 0, 0, "Unexpected empty response"));
        }
        if (this.document.size() > 0 && this.shouldBeEmpty) {
            this.errors.add(new ValidationError(Type.WARNING, 0, 0, "Unexpected non-empty content: \"" + getContent() + "\""));
        }
        int lineNr = 1;
        for (String line : this.document) {
            checkStringDelimiters(line, lineNr);
            checkTagDelimiter(line, lineNr, TEST_LT, "Unescaped < character");
            checkTagDelimiter(line, lineNr, TEST_GT, "Unescaped > character");

            int idx;
            if ((idx = line.indexOf("Error while parsing velocity page")) >= 0) {
                this.errors.add(new ValidationError(Type.WARNING, lineNr, idx,
                    "Parse error in the response. The template was not evaluated correctly."));
            }
            if ((idx = line.indexOf("org.xwiki.rendering.macro.MacroExecutionException")) >= 0) {
                this.errors.add(new ValidationError(Type.WARNING, lineNr, idx,
                    "Macro execution exception in the response."));
            }
            if ((idx = line.indexOf("Wrapped Exception: unexpected char:")) >= 0) {
                this.errors.add(new ValidationError(Type.WARNING, lineNr, idx, "Possible SQL error trace."));
            }
            // TODO also check \ for JavaScript
            // TODO check for overescaping
            lineNr++;
        }
        return this.errors;
    }

    /**
     *
     *
     * @return
     */
    private String getContent()
    {
        StringBuilder builder = new StringBuilder();
        for (String line : this.document) {
            builder.append(line).append('\n');
        }
        return builder.toString();
    }

    /**
     * Check whether < and > are properly escaped. Attempts to avoid false positives caused by JavaScript escaping.
     * Found problems are added to the internal list of escaping errors.
     *
     * @param line the line to check
     * @param lineNr line number reported on failures
     * @param testMatch the test string to search for, e.g. TEST_LT
     * @param errorMessage error message to use on failures
     */
    private void checkTagDelimiter(String line, int lineNr, String testMatch, String errorMessage)
    {
        // NOTE this method produces false NEGATIVES if JavaScript escaping is used where XML/URL escaping is needed
        int idx = 0;
        while ((idx = line.indexOf(testMatch, idx)) >= 0) {
            // avoid false positives caused by JavaScript escaping
            if (!isJavascriptEscaped(line, testMatch, idx)) {
                this.errors.add(new ValidationError(Type.ERROR, lineNr, idx, errorMessage));
            }
            idx++;
        }
    }

    /**
     * Check whether quote and apostrophe are properly escaped. Attempts to avoid false positives caused by XML escaping
     * inside tags (where only <, > and & are escaped). Found problems are added to the internal list of escaping errors.
     *
     * @param line the line to check
     * @param lineNr line number reported on failures
     */
    private void checkStringDelimiters(String line, int lineNr)
    {
        // NOTE this method produces false NEGATIVES if XML-tag escaping method is used inside tag attributes (unlikely)
        final int offset = INPUT_STRING.indexOf(TEST_APOS) - INPUT_STRING.indexOf(TEST_QUOT);
        int idx = 0;
        while ((idx = line.indexOf(TEST_APOS, idx)) >= 0) {
            // ignore if quote was not escaped either
            int expected_idx = idx - offset;
            if (expected_idx < 0 || line.indexOf(TEST_QUOT, expected_idx) != expected_idx) {
                this.errors.add(new ValidationError(Type.WARNING, lineNr, idx, "Unescaped ' character"));
            }
            idx++;
        }
        idx = 0;
        while ((idx = line.indexOf(TEST_QUOT, idx)) >= 0) {
            // ignore if apostrophe was not escaped either
            int expected_idx = idx + offset;
            if (expected_idx < 0 || line.indexOf(TEST_APOS, expected_idx) != expected_idx) {
                this.errors.add(new ValidationError(Type.WARNING, lineNr, idx, "Unescaped \" character"));
            }
            idx++;
        }
    }

    /**
     * Check if the matched test string appears to be JavaScript-escaped. Checks whether both ' and " appearing in the
     * test string right before index are JavaScript-escaped. Used to avoid false positives in {@link #validate()}.
     *
     * @param line the line where the string was matched
     * @param match substring of the test string that was matched, e.g. TEST_APOS
     * @param index position where the match was found in line, as reported by {@link String#indexOf(String, int)}
     * @return true if the found input string is JavaScript-escaped, false otherwise
     */
    private boolean isJavascriptEscaped(String line, String match, int index)
    {
        int offset = INPUT_STRING.indexOf(match);
        if (index < 0 || offset < 0) {
            return false;
        }
        // JavaScript-escaping adds 2 characters
        offset += 2;
        int pos_apos = line.indexOf(TEST_JS_APOS, index - offset);
        int pos_quot = line.indexOf(TEST_JS_QUOT, index - offset);
        return (pos_apos >= 0 && pos_apos < index && pos_quot >= 0 && pos_quot < index);
    }

    /**
     * {@inheritDoc}
     * @see org.xwiki.validator.Validator#getErrors()
     */
    @Override
    public List<ValidationError> getErrors()
    {
        return this.errors;
    }

    /**
     * {@inheritDoc}
     * @see org.xwiki.validator.Validator#clear()
     */
    @Override
    public void clear()
    {
        this.errors = new ArrayList<ValidationError>();
    }

    /**
     * {@inheritDoc}
     * @see org.xwiki.validator.Validator#getName()
     */
    @Override
    public String getName()
    {
        return "XML ESCAPING";
    }

    /**
     * Set to true if empty document is valid. A validation error will be thrown if document is empty,
     * but {@link #shouldBeEmpty} is false and vice versa.
     *
     * @param value new value
     */
    public void setShouldBeEmpty(boolean value)
    {
        this.shouldBeEmpty = value;
    }
}
TOP

Related Classes of org.xwiki.test.escaping.framework.XMLEscapingValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.