1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.velocity.tools

File EscapeTool.java

 

Coverage histogram

../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

20
48
9
1
259
118
26
0.54
5.33
9
2.89

Classes

Class Line # Actions
EscapeTool 50 48 0% 26 1
0.98701398.7%
 

Contributing tests

This file is covered by 28 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.velocity.tools;
21   
22    import java.io.UnsupportedEncodingException;
23    import java.net.URLEncoder;
24    import java.util.Collection;
25    import java.util.Map;
26   
27    import org.apache.commons.codec.EncoderException;
28    import org.apache.commons.codec.net.BCodec;
29    import org.apache.commons.codec.net.QCodec;
30    import org.apache.commons.codec.net.QuotedPrintableCodec;
31    import org.apache.commons.lang3.StringEscapeUtils;
32    import org.slf4j.Logger;
33    import org.slf4j.LoggerFactory;
34    import org.xwiki.xml.XMLUtils;
35   
36    /**
37    * <p>
38    * Tool for working with escaping in Velocity templates. It provides methods to escape outputs for Velocity, Java,
39    * JavaScript, HTML, XML and SQL.
40    * </p>
41    * <p>
42    * Extends the default EscapeTool from velocity-tools since the XML escape performed by it doesn't work inside HTML
43    * content, since {@code apos} is not a valid HTML entity name, and it always escapes non-ASCII characters, which
44    * increases the HTML length considerably, while also making the source unreadable.
45    * </p>
46    *
47    * @version $Id: d6ce71c040c3bc2ccb9d686a7deed23215737d2d $
48    * @since 2.7RC1
49    */
 
50    public class EscapeTool extends org.apache.velocity.tools.generic.EscapeTool
51    {
52    private static final Logger LOGGER = LoggerFactory.getLogger(EscapeTool.class);
53   
54    /** Equals sign. */
55    private static final String EQUALS = "=";
56   
57    /** And sign. */
58    private static final String AND = "&";
59   
60    /**
61    * Escapes the XML special characters in a <code>String</code> using numerical XML entities. This overrides the base
62    * implementation from Velocity, which is over-zealous and escapes any non-ASCII character. Since XWiki works with
63    * Unicode-capable encodings (UTF-8), there is no need to escape non-special characters.
64    *
65    * @param content the text to escape, may be {@code null}
66    * @return a new escaped {@code String}, {@code null} if {@code null} input
67    */
 
68  106133 toggle @Override
69    public String xml(Object content)
70    {
71  106133 return XMLUtils.escape(content);
72    }
73   
74    /**
75    * Escapes the characters in a <code>String</code> using JSON String rules: escapes with backslash double quotes,
76    * back and forward slashes, newlines, the control characters {@code \b}, {@code \t} and {@code \f}, and with
77    * {@code \}{@code uXXXX} any non-ASCII characters. Unlike {@link #javascript(Object)}, it does not escape {@code '}
78    * , which is not a special character in JSON, and it would be a syntax error to do so.
79    *
80    * @param string the string to escape, may be {@code null}; any non-string object will be converted to a string
81    * first, using {@code String.valueOf(obj)}
82    * @return String with escaped values, {@code null} if {@code null} input
83    * @since 6.1M1
84    */
 
85  8 toggle public String json(Object string)
86    {
87  8 if (string == null) {
88  1 return null;
89    }
90  7 return StringEscapeUtils.escapeJson(String.valueOf(string));
91    }
92   
93    /**
94    * Encode a text using the Quoted-Printable format, as specified in section 6.7 of <a
95    * href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>. UTF-8 is used as the character encoding, and no line
96    * breaking is performed.
97    *
98    * @param content the text to encode
99    * @return the text converted into the Quoted-Printable format
100    */
 
101  4 toggle public String quotedPrintable(Object content)
102    {
103  4 if (content != null) {
104  3 try {
105  3 return new QuotedPrintableCodec().encode(String.valueOf(content));
106    } catch (EncoderException ex) {
107    // Just return null
108    }
109    }
110  1 return null;
111    }
112   
113    /**
114    * Encode a text using the Q encoding specified in <a href="http://www.ietf.org/rfc/rfc2047.txt">RFC 2047</a>. UTF-8
115    * is used as the character encoding, and no line breaking is performed. The resulting text is already wrapped with
116    * the encoded word markers, starting with {@code =?UTF-8?Q?} and ending with {@code ?=}.
117    *
118    * @param content the text to encode
119    * @return the text converted into an encoded word using the Q encoding
120    */
 
121  5 toggle public String q(Object content)
122    {
123  5 if (content != null) {
124  4 try {
125  4 return new QCodec().encode(String.valueOf(content)).replace(' ', '_');
126    } catch (EncoderException ex) {
127    // Just return null
128    }
129    }
130  1 return null;
131    }
132   
133    /**
134    * Encode a text using the B encoding specified in <a href="http://www.ietf.org/rfc/rfc2047.txt">RFC 2047</a>. UTF-8
135    * is used as the character encoding, and no line breaking is performed. The resulting text is already wrapped with
136    * the encoded word markers, starting with {@code =?UTF-8?B?} and ending with {@code ?=}.
137    *
138    * @param content the text to encode
139    * @return the text converted into an encoded word using the B encoding
140    */
 
141  4 toggle public String b(Object content)
142    {
143  4 if (content != null) {
144  3 try {
145  3 return new BCodec().encode(String.valueOf(content));
146    } catch (EncoderException ex) {
147    // Just return null
148    }
149    }
150  1 return null;
151    }
152   
153    /**
154    * Properly escape a parameter map representing a query string, so that it can be safely used in an URL. Parameters
155    * can have multiple values in which case the value in the map is either an array or a {@link Collection}. If the
156    * parameter name is {@code null} (the key is {@code null}) then the parameter is ignored. {@code null} values are
157    * serialized as an empty string.
158    *
159    * @param parametersMap Map representing the query string.
160    * @return the safe query string representing the passed parameters
161    * @since 5.2M1
162    */
 
163  13510 toggle public String url(Map<String, ?> parametersMap)
164    {
165  13510 StringBuilder queryStringBuilder = new StringBuilder();
166  13510 for (Map.Entry<String, ?> entry : parametersMap.entrySet()) {
167  22603 if (entry.getKey() == null) {
168    // Skip the parameter if its name is null.
169  1 continue;
170    }
171  22603 String cleanKey = this.url(entry.getKey());
172  22603 Object mapValues = entry.getValue();
173  22603 if (mapValues != null && mapValues.getClass().isArray()) {
174    // A parameter with multiple values.
175  1 Object[] values = (Object[]) mapValues;
176  1 for (Object value : values) {
177  3 addQueryStringPair(cleanKey, value, queryStringBuilder);
178    }
179  22603 } else if (mapValues != null && Collection.class.isAssignableFrom(mapValues.getClass())) {
180    // A parameter with multiple values.
181  9056 Collection<?> values = (Collection<?>) mapValues;
182  9056 for (Object value : values) {
183  9058 addQueryStringPair(cleanKey, value, queryStringBuilder);
184    }
185    } else {
186  13547 addQueryStringPair(cleanKey, mapValues, queryStringBuilder);
187    }
188    }
189  13509 return queryStringBuilder.toString();
190    }
191   
192    /**
193    * Method to add an key / value pair to a query String.
194    *
195    * @param cleanKey Already escaped key
196    * @param rawValue Raw value associated to the key
197    * @param queryStringBuilder String Builder containing the current query string
198    */
 
199  22605 toggle private void addQueryStringPair(String cleanKey, Object rawValue, StringBuilder queryStringBuilder)
200    {
201    // Serialize null values as an empty string.
202  22608 String valueAsString = rawValue == null ? "" : String.valueOf(rawValue);
203  22609 String cleanValue = this.url(valueAsString);
204  22607 if (queryStringBuilder.length() != 0) {
205  9110 queryStringBuilder.append(AND);
206    }
207  22608 queryStringBuilder.append(cleanKey).append(EQUALS).append(cleanValue);
208    }
209   
210    /**
211    * Escapes a CSS identifier.
212    * <p>
213    * See https://drafts.csswg.org/cssom/#serialize-an-identifier.
214    * </p>
215    *
216    * @param identifier the identifier to escape
217    * @return the escaped identifier
218    * @since 6.4.7
219    * @since 7.1.4
220    * @since 7.4M1
221    */
 
222  2 toggle public String css(String identifier)
223    {
224  2 try {
225  2 return new CSSIdentifierSerializer().serialize(identifier);
226    } catch (IllegalArgumentException e) {
227  1 LOGGER.warn("Failed to escape CSS identifier. {}", e.getMessage());
228  1 return null;
229    }
230    }
231   
232    /**
233    * We override the implementation so that we sync it with the encoding strategy we use for generating URLs. Namely
234    * we encode all characters and we encode space as {@code %20} and not as {@code +} in the query string.
235    *
236    * @param string the url to encode
237    * @return the encoded URL
238    * @since 8.3M1
239    */
 
240  55802 toggle @Override
241    public String url(Object string)
242    {
243    // TODO: Introduce a xwiki-commons-url module and move this code in it so that we can share it with
244    // platform's XWikiServletURLFactory and functional test TestUtils class.
245  55802 String encodedURL = null;
246  55802 if (string != null) {
247  55483 try {
248  55482 encodedURL = URLEncoder.encode(String.valueOf(string), "UTF-8");
249    } catch (UnsupportedEncodingException e) {
250    // Should not happen (UTF-8 is always available)
251  0 throw new RuntimeException("Missing charset [UTF-8]", e);
252    }
253    // The previous call will convert " " into "+" (and "+" into "%2B") so we need to convert "+" into "%20"
254    // It's ok since %20 is allowed in both the URL path and the query string (and anchor).
255  55483 encodedURL = encodedURL.replaceAll("\\+", "%20");
256    }
257  55803 return encodedURL;
258    }
259    }