AddDefaultCharsetFilter.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.catalina.filters;
import java.io.IOException;
import java.nio.charset.Charset;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpServletResponseWrapper;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
/**
* Filter that explicitly sets the default character set for media subtypes of the "text" type to ISO-8859-1, or another
* user defined character set. RFC2616 explicitly states that browsers must use ISO-8859-1 if no character set is
* defined for media with subtype "text". However, browsers may attempt to auto-detect the character set. This may be
* exploited by an attacker to perform an XSS attack. Internet Explorer has this behaviour by default. Other browsers
* have an option to enable it.<br>
* This filter prevents the attack by explicitly setting a character set. Unless the provided character set is
* explicitly overridden by the user - in which case they deserve everything they get - the browser will adhere to an
* explicitly set character set, thus preventing the XSS attack.
*/
public class AddDefaultCharsetFilter extends FilterBase {
// Log must be non-static as loggers are created per class-loader and this
// Filter may be used in multiple class loaders
private final Log log = LogFactory.getLog(AddDefaultCharsetFilter.class); // must not be static
private static final String DEFAULT_ENCODING = "ISO-8859-1";
private String encoding;
public void setEncoding(String encoding) {
this.encoding = encoding;
}
@Override
protected Log getLogger() {
return log;
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
if (encoding == null || encoding.length() == 0 || encoding.equalsIgnoreCase("default")) {
encoding = DEFAULT_ENCODING;
} else if (encoding.equalsIgnoreCase("system")) {
encoding = Charset.defaultCharset().name();
} else if (!Charset.isSupported(encoding)) {
throw new IllegalArgumentException(sm.getString("addDefaultCharset.unsupportedCharset", encoding));
}
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// Wrap the response
if (response instanceof HttpServletResponse) {
ResponseWrapper wrapped = new ResponseWrapper((HttpServletResponse) response, encoding);
chain.doFilter(request, wrapped);
} else {
chain.doFilter(request, response);
}
}
/**
* Wrapper that adds a character set for text media types if no character set is specified.
*/
public static class ResponseWrapper extends HttpServletResponseWrapper {
private String encoding;
public ResponseWrapper(HttpServletResponse response, String encoding) {
super(response);
this.encoding = encoding;
}
@Override
public void setContentType(String contentType) {
if (contentType != null && contentType.startsWith("text/")) {
if (!contentType.contains("charset=")) {
super.setContentType(contentType + ";charset=" + encoding);
} else {
super.setContentType(contentType);
encoding = getCharacterEncoding();
}
} else {
super.setContentType(contentType);
}
}
@Override
public void setHeader(String name, String value) {
if (name.trim().equalsIgnoreCase("content-type")) {
setContentType(value);
} else {
super.setHeader(name, value);
}
}
@Override
public void addHeader(String name, String value) {
if (name.trim().equalsIgnoreCase("content-type")) {
setContentType(value);
} else {
super.addHeader(name, value);
}
}
@Override
public void setCharacterEncoding(String charset) {
super.setCharacterEncoding(charset);
encoding = charset;
}
}
}