/*
* Copyright 2017 OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.filter;
import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.nio.charset.Charset;
import javax.faces.context.ExternalContext;
import javax.faces.context.PartialViewContext;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* <p>This filter will set the request body character encoding when not already set by the client. Even though
* JSF2/Facelets uses by default UTF-8 everywhere, which is the best charset choice these days, JSF2/Facelets might
* fail to set it to UTF-8 when something else has set it to a different value before JSF2/Facelets gets the chance to
* set it during the restore view phase. PrimeFaces 3.x for example is known to do that. During ajax requests, it will
* call {@link ExternalContext#getRequestParameterMap()} inside {@link PartialViewContext#isAjaxRequest()} right before
* building/restoring the view, which will implicitly set the request body character encoding to the server-default
* value, which is not UTF-8 per se.
*
* <h3>Installation</h3>
* <p>
* To get this filter to run, map it as follows in <code>web.xml</code>:
* <pre>
* <filter>
* <filter-name>characterEncodingFilter</filter-name>
* <filter-class>org.omnifaces.filter.CharacterEncodingFilter</filter-class>
* </filter>
* <filter-mapping>
* <filter-name>characterEncodingFilter</filter-name>
* <url-pattern>/*</url-pattern>
* </filter-mapping>
* </pre>
*
* <h3>Configuration (optional)</h3>
* <p>As JSF2/Facelets uses by default UTF-8 everywhere, the default charset is also set to UTF-8. When really
* necessary for some reason, then it can be overridden by specifying the <code>encoding</code> initialization
* parameter in the <code><filter></code> element as follows:
* <pre>
* <init-param>
* <description>The character encoding which is to be used to parse the HTTP request body. Defaults to UTF-8.</description>
* <param-name>encoding</param-name>
* <param-value>ISO-8859-1</param-value>
* </init-param>
* </pre>
* <p>
* Please note that this only affects HTTP POST requests, not HTTP GET requests. For HTTP GET requests, you should
* be specifying the charset at servletcontainer level (e.g. <code><Context URIEncoding="UTF-8"></code> in Tomcat,
* or <code><parameter-encoding default-charset="UTF-8"></code> in Glassfish, or
* <code><property name="org.apache.catalina.connector.USE_BODY_ENCODING_FOR_QUERY_STRING" value="true" /></code>
* in JBoss in combination with this filter). Also note that this doesn't affect
* HTTP responses in any way. For HTTP responses, you should be specifying the charset in
* <code><f:view encoding></code>, which also already defaults to UTF-8 by the way.
*
* <p><strong>See also</strong>:
* <br><a href="http://code.google.com/p/primefaces/issues/detail?id=2223">PrimeFaces issue 2223</a>
* <br><a href="http://stackoverflow.com/q/9634230/157882">Unicode input retrieved via PrimeFaces input components become corrupted</a>
*
* @author Bauke Scholtz
* @since 1.2
* @see HttpFilter
*/
public class CharacterEncodingFilter extends HttpFilter {
// Constants ------------------------------------------------------------------------------------------------------
private static final String INIT_PARAM_ENCODING = "encoding";
private static final Charset DEFAULT_ENCODING = UTF_8;
private static final String ERROR_ENCODING =
"The 'encoding' init param must represent a valid charset. Encountered an invalid charset of '%s'.";
// Vars -----------------------------------------------------------------------------------------------------------
private Charset encoding = DEFAULT_ENCODING;
// Actions --------------------------------------------------------------------------------------------------------
/**
* Initializes the filter parameters.
*/
@Override
public void init() throws ServletException {
String encodingParam = getInitParameter(INIT_PARAM_ENCODING);
if (encodingParam != null) {
try {
encoding = Charset.forName(encodingParam);
}
catch (Exception e) {
throw new ServletException(format(ERROR_ENCODING, encodingParam), e);
}
}
}
/**
* Perform the filtering job. Only if the request character encoding has not been set yet, then set it.
*/
@Override
public void doFilter
(HttpServletRequest request, HttpServletResponse response, HttpSession session, FilterChain chain)
throws ServletException, IOException
{
if (request.getCharacterEncoding() == null) {
request.setCharacterEncoding(encoding.name());
}
chain.doFilter(request, response);
}
}