1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.test.escaping.framework

File XMLEscapingValidator.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart7.png
64% of files have more coverage

Code metrics

22
52
9
1
234
122
24
0.46
5.78
9
2.67

Classes

Class Line # Actions
XMLEscapingValidator 40 52 0% 24 30
0.638554263.9%
 

Contributing tests

This file is covered by 1253 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.test.escaping.framework;
21   
22    import java.io.BufferedReader;
23    import java.io.IOException;
24    import java.io.InputStream;
25    import java.io.InputStreamReader;
26    import java.util.ArrayList;
27    import java.util.List;
28   
29    import org.xwiki.validator.ValidationError;
30    import org.xwiki.validator.ValidationError.Type;
31    import org.xwiki.validator.Validator;
32   
33    /**
34    * A validator that checks for proper XML escaping. The document must be constructed using the special test input string
35    * (see {@link #getTestString()}).
36    *
37    * @version $Id: 385e782c788dff17f6114d3b074b2d4cf8fa9dea $
38    * @since 2.5M1
39    */
 
40    public class XMLEscapingValidator implements Validator
41    {
42    /** Unescaped test string containing XML significant characters. */
43    private static final String INPUT_STRING = "aaa\"bbb'ccc>ddd<eee";
44   
45    /** Test for unescaped apostrophe. */
46    private static final String TEST_APOS = "bbb'ccc";
47   
48    /** Test for unescaped quote. */
49    private static final String TEST_QUOT = "aaa\"bbb";
50   
51    /** Test for unescaped tag start. */
52    private static final String TEST_LT = "ddd<eee";
53   
54    /** Test for unescaped tag end. */
55    private static final String TEST_GT = "ccc>ddd";
56   
57    /** JavaScript-escaped TEST_APOS. */
58    private static final String TEST_JS_APOS = "bbb\\'ccc";
59   
60    /** JavaScript-escaped TEST_QUOT. */
61    private static final String TEST_JS_QUOT = "aaa\\\"bbb";
62   
63    /** Source of the XML document to validate. */
64    private List<String> document = new ArrayList<String>();
65   
66    /** List of validation errors. */
67    private List<ValidationError> errors = new ArrayList<ValidationError>();
68   
69    /**
70    * Get the input string containing XML significant characters that should be used.
71    *
72    * @return test string to use
73    */
 
74  3115 toggle public static String getTestString()
75    {
76  3115 return INPUT_STRING;
77    }
78   
79    /**
80    * {@inheritDoc}
81    * <p>
82    * Clears previous list of validation errors.
83    * </p>
84    *
85    * @see org.xwiki.validator.Validator#setDocument(java.io.InputStream)
86    */
 
87  3018 toggle @Override
88    public void setDocument(InputStream document)
89    {
90  3018 BufferedReader reader = new BufferedReader(new InputStreamReader(document));
91  3018 String line;
92  3018 this.document = new ArrayList<String>();
93  3018 try {
94  ? while ((line = reader.readLine()) != null) {
95  2034969 this.document.add(line);
96    }
97    } catch (IOException exception) {
98  0 throw new RuntimeException("Could not read document: ", exception);
99    }
100  3018 clear();
101    }
102   
103    /**
104    * {@inheritDoc}
105    * <p>
106    * Throws {@link EscapingError} on errors.
107    * </p>
108    *
109    * @see org.xwiki.validator.Validator#validate()
110    */
 
111  3018 toggle @Override
112    public List<ValidationError> validate()
113    {
114  3018 clear();
115   
116  3018 int lineNr = 1;
117  3018 for (String line : this.document) {
118  2034969 checkStringDelimiters(line, lineNr);
119  2034969 checkTagDelimiter(line, lineNr, TEST_LT, "Unescaped < character");
120  2034969 checkTagDelimiter(line, lineNr, TEST_GT, "Unescaped > character");
121   
122  2034969 int idx;
123  ? if ((idx = line.indexOf("Error while parsing velocity page")) >= 0) {
124  0 this.errors.add(new ValidationError(Type.WARNING, lineNr, idx,
125    "Parse error in the response. The template was not evaluated correctly."));
126    }
127  ? if ((idx = line.indexOf("org.xwiki.rendering.macro.MacroExecutionException")) >= 0) {
128  0 this.errors.add(new ValidationError(Type.WARNING, lineNr, idx,
129    "Macro execution exception in the response."));
130    }
131  ? if ((idx = line.indexOf("Wrapped Exception: unexpected char:")) >= 0) {
132  0 this.errors.add(new ValidationError(Type.WARNING, lineNr, idx, "Possible SQL error trace."));
133    }
134    // TODO also check \ for JavaScript
135    // TODO check for overescaping
136  2034969 lineNr++;
137    }
138  3018 return this.errors;
139    }
140   
141    /**
142    * Check whether < and > are properly escaped. Attempts to avoid false positives caused by JavaScript escaping.
143    * Found problems are added to the internal list of escaping errors.
144    *
145    * @param line the line to check
146    * @param lineNr line number reported on failures
147    * @param testMatch the test string to search for, e.g. TEST_LT
148    * @param errorMessage error message to use on failures
149    */
 
150  4069938 toggle private void checkTagDelimiter(String line, int lineNr, String testMatch, String errorMessage)
151    {
152    // NOTE this method produces false NEGATIVES if JavaScript escaping is used where XML/URL escaping is needed
153  4069938 int idx = 0;
154  ? while ((idx = line.indexOf(testMatch, idx)) >= 0) {
155    // avoid false positives caused by JavaScript escaping
156  206 if (!isJavascriptEscaped(line, testMatch, idx)) {
157  0 this.errors.add(new ValidationError(Type.ERROR, lineNr, idx, errorMessage));
158    }
159  206 idx++;
160    }
161    }
162   
163    /**
164    * Check whether quote and apostrophe are properly escaped. Attempts to avoid false positives caused by XML escaping
165    * inside tags (where only <, > and & are escaped). Found problems are added to the internal list of escaping
166    * errors.
167    *
168    * @param line the line to check
169    * @param lineNr line number reported on failures
170    */
 
171  2034969 toggle private void checkStringDelimiters(String line, int lineNr)
172    {
173    // NOTE this method produces false NEGATIVES if XML-tag escaping method is used inside tag attributes (unlikely)
174  2034969 final int offset = INPUT_STRING.indexOf(TEST_APOS) - INPUT_STRING.indexOf(TEST_QUOT);
175  2034969 int idx = 0;
176  ? while ((idx = line.indexOf(TEST_APOS, idx)) >= 0) {
177    // ignore if quote was not escaped either
178  41 int expected_idx = idx - offset;
179  41 if (expected_idx < 0 || line.indexOf(TEST_QUOT, expected_idx) != expected_idx) {
180  0 this.errors.add(new ValidationError(Type.WARNING, lineNr, idx, "Unescaped ' character"));
181    }
182  41 idx++;
183    }
184  2034969 idx = 0;
185  ? while ((idx = line.indexOf(TEST_QUOT, idx)) >= 0) {
186    // ignore if apostrophe was not escaped either
187  41 int expected_idx = idx + offset;
188  41 if (expected_idx < 0 || line.indexOf(TEST_APOS, expected_idx) != expected_idx) {
189  0 this.errors.add(new ValidationError(Type.WARNING, lineNr, idx, "Unescaped \" character"));
190    }
191  41 idx++;
192    }
193    }
194   
195    /**
196    * Check if the matched test string appears to be JavaScript-escaped. Checks whether both ' and " appearing in the
197    * test string right before index are JavaScript-escaped. Used to avoid false positives in {@link #validate()}.
198    *
199    * @param line the line where the string was matched
200    * @param match substring of the test string that was matched, e.g. TEST_APOS
201    * @param index position where the match was found in line, as reported by {@link String#indexOf(String, int)}
202    * @return true if the found input string is JavaScript-escaped, false otherwise
203    */
 
204  206 toggle private boolean isJavascriptEscaped(String line, String match, int index)
205    {
206  206 int offset = INPUT_STRING.indexOf(match);
207  206 if (index < 0 || offset < 0) {
208  0 return false;
209    }
210    // JavaScript-escaping adds 2 characters
211  206 offset += 2;
212  206 int pos_apos = line.indexOf(TEST_JS_APOS, index - offset);
213  206 int pos_quot = line.indexOf(TEST_JS_QUOT, index - offset);
214  206 return (pos_apos >= 0 && pos_apos < index && pos_quot >= 0 && pos_quot < index);
215    }
216   
 
217  0 toggle @Override
218    public List<ValidationError> getErrors()
219    {
220  0 return this.errors;
221    }
222   
 
223  6036 toggle @Override
224    public void clear()
225    {
226  6036 this.errors = new ArrayList<ValidationError>();
227    }
228   
 
229  0 toggle @Override
230    public String getName()
231    {
232  0 return "XML ESCAPING";
233    }
234    }