All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.xwiki.velocity.tools.EscapeTool Maven / Gradle / Ivy

The newest version!
/*
 * 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.velocity.tools;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.codec.net.BCodec;
import org.apache.commons.codec.net.QCodec;
import org.apache.commons.codec.net.QuotedPrintableCodec;
import org.apache.commons.text.StringEscapeUtils;
import org.apache.commons.text.translate.CharSequenceTranslator;
import org.apache.commons.text.translate.LookupTranslator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.xml.XMLUtils;

/**
 * 

* Tool for working with escaping in Velocity templates. It provides methods to escape outputs for Velocity, Java, * JavaScript, HTML, XML and SQL. *

*

* Extends the default EscapeTool from velocity-tools since the XML escape performed by it doesn't work inside HTML * content, since {@code apos} is not a valid HTML entity name, and it always escapes non-ASCII characters, which * increases the HTML length considerably, while also making the source unreadable. *

* * @version $Id: 3fe2f1709839ec15b1c3f69be13ff503e5a0782d $ * @since 2.7RC1 */ public class EscapeTool extends org.apache.velocity.tools.generic.EscapeTool { /** * The name of the standard binding to access this tool in XWiki. * * @since 14.1RC1 * @since 13.10.3 */ public static final String DEFAULT_KEY = "escapetool"; private static final Logger LOGGER = LoggerFactory.getLogger(EscapeTool.class); /** Equals sign. */ private static final String EQUALS = "="; /** And sign. */ private static final String AND = "&"; private static final CharSequenceTranslator XWIKI_ESCAPE_HTML4 = StringEscapeUtils.ESCAPE_HTML4.with( new LookupTranslator(Map.ofEntries( Map.entry("{", "{"), Map.entry("}", "}") )) ); /** * Change the default key defined in {@link org.apache.velocity.tools.generic.EscapeTool}. */ public EscapeTool() { setKey(DEFAULT_KEY); } /** * Escapes the HTML special characters in a String using HTML entities. This overrides the base * implementation from Velocity in order to also escape characters potentially harmful in the context of XWiki, * such as curly brackets. * * @param content the string to escape, may be {@code null} * @return a new escaped {@code String}, {@code null} if {@code null} input */ @Override public String html(Object content) { if (content == null) { return null; } return XWIKI_ESCAPE_HTML4.translate(String.valueOf(content)); } /** * Escapes the XML special characters in a String using numerical XML entities. This overrides the base * implementation from Velocity, which is over-zealous and escapes any non-ASCII character. Since XWiki works with * Unicode-capable encodings (UTF-8), there is no need to escape non-special characters. * * @param content the text to escape, may be {@code null} * @return a new escaped {@code String}, {@code null} if {@code null} input */ @Override public String xml(Object content) { return XMLUtils.escape(Objects.toString(content, null)); } /** * Escapes the characters in a String using JSON String rules: escapes with backslash double quotes, * back and forward slashes, newlines, the control characters {@code \b}, {@code \t} and {@code \f}, and with * {@code \}{@code uXXXX} any non-ASCII characters. Unlike {@link #javascript(Object)}, it does not escape {@code '} * , which is not a special character in JSON, and it would be a syntax error to do so. * * @param string the string to escape, may be {@code null}; any non-string object will be converted to a string * first, using {@code String.valueOf(obj)} * @return String with escaped values, {@code null} if {@code null} input * @since 6.1M1 */ public String json(Object string) { if (string == null) { return null; } return StringEscapeUtils.escapeJson(String.valueOf(string)); } /** * Encode a text using the Quoted-Printable format, as specified in section 6.7 of RFC 2045. UTF-8 is used as the character encoding, and no line * breaking is performed. * * @param content the text to encode * @return the text converted into the Quoted-Printable format */ public String quotedPrintable(Object content) { if (content != null) { try { return new QuotedPrintableCodec().encode(String.valueOf(content)); } catch (EncoderException ex) { // Just return null } } return null; } /** * Encode a text using the Q encoding specified in RFC 2047. UTF-8 * is used as the character encoding, and no line breaking is performed. The resulting text is already wrapped with * the encoded word markers, starting with {@code =?UTF-8?Q?} and ending with {@code ?=}. * * @param content the text to encode * @return the text converted into an encoded word using the Q encoding */ public String q(Object content) { if (content != null) { try { return new QCodec().encode(String.valueOf(content)).replace(' ', '_'); } catch (EncoderException ex) { // Just return null } } return null; } /** * Encode a text using the B encoding specified in RFC 2047. UTF-8 * is used as the character encoding, and no line breaking is performed. The resulting text is already wrapped with * the encoded word markers, starting with {@code =?UTF-8?B?} and ending with {@code ?=}. * * @param content the text to encode * @return the text converted into an encoded word using the B encoding */ public String b(Object content) { if (content != null) { try { return new BCodec().encode(String.valueOf(content)); } catch (EncoderException ex) { // Just return null } } return null; } /** * Properly escape a parameter map representing a query string, so that it can be safely used in an URL. Parameters * can have multiple values in which case the value in the map is either an array or a {@link Collection}. If the * parameter name is {@code null} (the key is {@code null}) then the parameter is ignored. {@code null} values are * serialized as an empty string. * * @param parametersMap Map representing the query string. * @return the safe query string representing the passed parameters * @since 5.2M1 */ public String url(Map parametersMap) { StringBuilder queryStringBuilder = new StringBuilder(); for (Map.Entry entry : parametersMap.entrySet()) { if (entry.getKey() == null) { // Skip the parameter if its name is null. continue; } String cleanKey = this.url(entry.getKey()); Object mapValues = entry.getValue(); if (mapValues != null && mapValues.getClass().isArray()) { // A parameter with multiple values. Object[] values = (Object[]) mapValues; for (Object value : values) { addQueryStringPair(cleanKey, value, queryStringBuilder); } } else if (mapValues != null && Collection.class.isAssignableFrom(mapValues.getClass())) { // A parameter with multiple values. Collection values = (Collection) mapValues; for (Object value : values) { addQueryStringPair(cleanKey, value, queryStringBuilder); } } else { addQueryStringPair(cleanKey, mapValues, queryStringBuilder); } } return queryStringBuilder.toString(); } /** * Method to add an key / value pair to a query String. * * @param cleanKey Already escaped key * @param rawValue Raw value associated to the key * @param queryStringBuilder String Builder containing the current query string */ private void addQueryStringPair(String cleanKey, Object rawValue, StringBuilder queryStringBuilder) { // Serialize null values as an empty string. String valueAsString = rawValue == null ? "" : String.valueOf(rawValue); String cleanValue = this.url(valueAsString); if (queryStringBuilder.length() != 0) { queryStringBuilder.append(AND); } queryStringBuilder.append(cleanKey).append(EQUALS).append(cleanValue); } /** * Escapes a CSS identifier. *

* See https://drafts.csswg.org/cssom/#serialize-an-identifier. *

* * @param identifier the identifier to escape * @return the escaped identifier * @since 6.4.7 * @since 7.1.4 * @since 7.4M1 */ public String css(String identifier) { try { return new CSSIdentifierSerializer().serialize(identifier); } catch (IllegalArgumentException e) { LOGGER.warn("Failed to escape CSS identifier. Root cause: [{}]", e.getMessage()); return null; } } /** * We override the implementation so that we sync it with the encoding strategy we use for generating URLs. Namely * we encode all characters and we encode space as {@code %20} and not as {@code +} in the query string. * * @param string the url to encode * @return the encoded URL * @since 8.3M1 */ @Override public String url(Object string) { // TODO: Introduce a xwiki-commons-url module and move this code in it so that we can share it with // platform's XWikiServletURLFactory and functional test TestUtils class. String encodedURL = null; if (string != null) { try { encodedURL = URLEncoder.encode(String.valueOf(string), "UTF-8"); } catch (UnsupportedEncodingException e) { // Should not happen (UTF-8 is always available) throw new RuntimeException("Missing charset [UTF-8]", e); } // The previous call will convert " " into "+" (and "+" into "%2B") so we need to convert "+" into "%20" // It's ok since %20 is allowed in both the URL path and the query string (and anchor). encodedURL = encodedURL.replaceAll("\\+", "%20"); } return encodedURL; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy