Author: markt Date: Wed Jun 5 09:58:22 2013 New Revision: 1489785 URL: http://svn.apache.org/r1489785 Log: Add CORS Filter Patch by Mohit Soni.
Added: tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/CorsFilter.java - copied, changed from r1489385, tomcat/trunk/java/org/apache/catalina/filters/CorsFilter.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TestCorsFilter.java - copied unchanged from r1489385, tomcat/trunk/test/org/apache/catalina/filters/TestCorsFilter.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterFilterChain.java - copied unchanged from r1489385, tomcat/trunk/test/org/apache/catalina/filters/TesterFilterChain.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterFilterConfigs.java - copied unchanged from r1489385, tomcat/trunk/test/org/apache/catalina/filters/TesterFilterConfigs.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterHttpServletRequest.java - copied unchanged from r1489385, tomcat/trunk/test/org/apache/catalina/filters/TesterHttpServletRequest.java tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterServletContext.java - copied unchanged from r1489385, tomcat/trunk/test/org/apache/catalina/filters/TesterServletContext.java tomcat/tc7.0.x/trunk/webapps/docs/images/cors-flowchart.png - copied unchanged from r1489390, tomcat/trunk/webapps/docs/images/cors-flowchart.png Modified: tomcat/tc7.0.x/trunk/ (props changed) tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/LocalStrings.properties tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml tomcat/tc7.0.x/trunk/webapps/docs/config/filter.xml Propchange: tomcat/tc7.0.x/trunk/ ------------------------------------------------------------------------------ Merged /tomcat/trunk:r1489385,1489390,1489738 Copied: tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/CorsFilter.java (from r1489385, tomcat/trunk/java/org/apache/catalina/filters/CorsFilter.java) URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/CorsFilter.java?p2=tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/CorsFilter.java&p1=tomcat/trunk/java/org/apache/catalina/filters/CorsFilter.java&r1=1489385&r2=1489785&rev=1489785&view=diff ============================================================================== --- tomcat/trunk/java/org/apache/catalina/filters/CorsFilter.java (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/CorsFilter.java Wed Jun 5 09:58:22 2013 @@ -133,10 +133,10 @@ public final class CorsFilter implements public CorsFilter() { - this.allowedOrigins = new HashSet<>(); - this.allowedHttpMethods = new HashSet<>(); - this.allowedHttpHeaders = new HashSet<>(); - this.exposedHeaders = new HashSet<>(); + this.allowedOrigins = new HashSet<String>(); + this.allowedHttpMethods = new HashSet<String>(); + this.allowedHttpHeaders = new HashSet<String>(); + this.exposedHeaders = new HashSet<String>(); } @@ -349,7 +349,7 @@ public final class CorsFilter implements // Section 6.2.4 String accessControlRequestHeadersHeader = request.getHeader( CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_HEADERS); - List<String> accessControlRequestHeaders = new LinkedList<>(); + List<String> accessControlRequestHeaders = new LinkedList<String>(); if (accessControlRequestHeadersHeader != null && !accessControlRequestHeadersHeader.trim().isEmpty()) { String[] headers = accessControlRequestHeadersHeader.trim().split( @@ -729,7 +729,7 @@ public final class CorsFilter implements if (allowedHttpHeaders != null) { Set<String> setAllowedHttpHeaders = parseStringToSet(allowedHttpHeaders); - Set<String> lowerCaseHeaders = new HashSet<>(); + Set<String> lowerCaseHeaders = new HashSet<String>(); for (String header : setAllowedHttpHeaders) { String lowerCase = header.toLowerCase(); lowerCaseHeaders.add(lowerCase); @@ -759,7 +759,7 @@ public final class CorsFilter implements } } catch (NumberFormatException e) { throw new ServletException( - sm.getString("corsFilter.invalidPreFlightMaxAge"), e); + sm.getString("corsFilter.invalidPreflightMaxAge"), e); } } @@ -785,7 +785,7 @@ public final class CorsFilter implements splits = new String[] {}; } - Set<String> set = new HashSet<>(); + Set<String> set = new HashSet<String>(); if (splits.length > 0) { for (String split : splits) { set.add(split.trim()); @@ -1030,13 +1030,14 @@ public final class CorsFilter implements * */ public static final Collection<String> HTTP_METHODS = - new HashSet<>(Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT", - "DELETE", "TRACE", "CONNECT")); + new HashSet<String>(Arrays.asList("OPTIONS", "GET", "HEAD", "POST", + "PUT", "DELETE", "TRACE", "CONNECT")); /** * {@link Collection} of non-simple HTTP methods. Case sensitive. */ public static final Collection<String> COMPLEX_HTTP_METHODS = - new HashSet<>(Arrays.asList("PUT", "DELETE", "TRACE", "CONNECT")); + new HashSet<String>(Arrays.asList("PUT", "DELETE", "TRACE", + "CONNECT")); /** * {@link Collection} of Simple HTTP methods. Case sensitive. * @@ -1044,7 +1045,7 @@ public final class CorsFilter implements * >http://www.w3.org/TR/cors/#terminology</a> */ public static final Collection<String> SIMPLE_HTTP_METHODS = - new HashSet<>(Arrays.asList("GET", "POST", "HEAD")); + new HashSet<String>(Arrays.asList("GET", "POST", "HEAD")); /** * {@link Collection} of Simple HTTP request headers. Case in-sensitive. @@ -1053,7 +1054,7 @@ public final class CorsFilter implements * >http://www.w3.org/TR/cors/#terminology</a> */ public static final Collection<String> SIMPLE_HTTP_REQUEST_HEADERS = - new HashSet<>(Arrays.asList("Accept", "Accept-Language", + new HashSet<String>(Arrays.asList("Accept", "Accept-Language", "Content-Language")); /** @@ -1063,8 +1064,9 @@ public final class CorsFilter implements * >http://www.w3.org/TR/cors/#terminology</a> */ public static final Collection<String> SIMPLE_HTTP_RESPONSE_HEADERS = - new HashSet<>(Arrays.asList("Cache-Control", "Content-Language", - "Content-Type", "Expires", "Last-Modified", "Pragma")); + new HashSet<String>(Arrays.asList("Cache-Control", + "Content-Language", "Content-Type", "Expires", + "Last-Modified", "Pragma")); /** * {@link Collection} of Simple HTTP request headers. Case in-sensitive. @@ -1073,7 +1075,8 @@ public final class CorsFilter implements * >http://www.w3.org/TR/cors/#terminology</a> */ public static final Collection<String> SIMPLE_HTTP_REQUEST_CONTENT_TYPE_VALUES = - new HashSet<>(Arrays.asList("application/x-www-form-urlencoded", + new HashSet<String>(Arrays.asList( + "application/x-www-form-urlencoded", "multipart/form-data", "text/plain")); // ------------------------------------------------ Configuration Defaults Modified: tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/LocalStrings.properties URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/LocalStrings.properties?rev=1489785&r1=1489784&r2=1489785&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/LocalStrings.properties (original) +++ tomcat/tc7.0.x/trunk/java/org/apache/catalina/filters/LocalStrings.properties Wed Jun 5 09:58:22 2013 @@ -14,6 +14,12 @@ # limitations under the License. addDefaultCharset.unsupportedCharset=Specified character set [{0}] is not supported +corsFilter.invalidPreflightMaxAge=Unable to parse preflightMaxAge +corsFilter.nullRequest=HttpServletRequest object is null +corsFilter.nullRequestType=CORSRequestType object is null +corsFilter.onlyHttp=CORS doesn't support non-HTTP request or response +corsFilter.wrongType1=Expects a HttpServletRequest object of type [{0}] +corsFilter.wrongType2=Expects a HttpServletRequest object of type [{0}] or [{1}] csrfPrevention.invalidRandomClass=Unable to create Random source using class [{0}] filterbase.noSuchProperty=The property "{0}" is not defined for filters of type "{1}" Modified: tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java?rev=1489785&r1=1489784&r2=1489785&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java (original) +++ tomcat/tc7.0.x/trunk/test/org/apache/catalina/filters/TesterHttpServletResponse.java Wed Jun 5 09:58:22 2013 @@ -19,7 +19,10 @@ package org.apache.catalina.filters; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.Locale; import javax.servlet.ServletOutputStream; @@ -36,10 +39,66 @@ import org.apache.catalina.connector.Req */ public class TesterHttpServletResponse implements HttpServletResponse { + private PrintWriter pw; + private List<String> headerNames = new ArrayList<>(); + private List<String> headerValues = new ArrayList<>(); + private int status; + public TesterHttpServletResponse() { // NOOP } + + @Override + public PrintWriter getWriter() throws IOException { + if (pw == null) { + pw = new PrintWriter(new StringWriter()); + } + return pw; + } + + + @Override + public String getHeader(String name) { + int index = headerNames.indexOf(name); + if (index != -1) { + return headerValues.get(index); + } + return null; + } + + + @Override + public void setHeader(String name, String value) { + int index = headerNames.indexOf(name); + if (index != -1) { + headerValues.set(index, value); + } else { + headerNames.add(name); + headerValues.add(value); + } + } + + + @Override + public void addHeader(String name, String value) { + headerNames.add(name); + headerValues.add(value); + } + + + @Override + public int getStatus() { + return status; + } + + + @Override + public void setStatus(int status) { + this.status = status; + } + + public void setAppCommitted( @SuppressWarnings("unused") boolean appCommitted) {/* NOOP */} public boolean isAppCommitted() { return false; } @@ -124,8 +183,6 @@ public class TesterHttpServletResponse i @Override public Locale getLocale() { return null; } @Override - public PrintWriter getWriter() throws IOException { return null; } - @Override public boolean isCommitted() { return false; } @Override public void reset() {/* NOOP */} @@ -140,14 +197,10 @@ public class TesterHttpServletResponse i @Override public void setLocale(Locale locale) {/* NOOP */} @Override - public String getHeader(String name) { return null; } - @Override public Collection<String> getHeaderNames() { return null; } @Override public Collection<String> getHeaders(String name) { return null; } public String getMessage() { return null; } - @Override - public int getStatus() { return -1; } public void reset(@SuppressWarnings("unused") int status, @SuppressWarnings("unused") String message) {/* NOOP */} @Override @@ -155,8 +208,6 @@ public class TesterHttpServletResponse i @Override public void addDateHeader(String name, long value) {/* NOOP */} @Override - public void addHeader(String name, String value) {/* NOOP */} - @Override public void addIntHeader(String name, int value) {/* NOOP */} @Override public boolean containsHeader(String name) { return false; } @@ -188,11 +239,7 @@ public class TesterHttpServletResponse i @Override public void setDateHeader(String name, long value) {/* NOOP */} @Override - public void setHeader(String name, String value) {/* NOOP */} - @Override public void setIntHeader(String name, int value) {/* NOOP */} - @Override - public void setStatus(int status) {/* NOOP */} /** @deprecated */ @Override @Deprecated Modified: tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml?rev=1489785&r1=1489784&r2=1489785&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/changelog.xml Wed Jun 5 09:58:22 2013 @@ -126,6 +126,11 @@ application. Patch provided by Sergey Tcherednichenko. (markt) </add> <add> + <bug>55046</bug>: Add a Servlet Filter that implements + <a href="http://www.w3.org/TR/cors/">CORS</a>. Patch provided by Mohit + Soni. (markt) + </add> + <add> <bug>55052</bug>: JULI's LogManager now additionally looks for logging properties without prefixes if the property cannot be found with a prefix. (markt) Modified: tomcat/tc7.0.x/trunk/webapps/docs/config/filter.xml URL: http://svn.apache.org/viewvc/tomcat/tc7.0.x/trunk/webapps/docs/config/filter.xml?rev=1489785&r1=1489784&r2=1489785&view=diff ============================================================================== --- tomcat/tc7.0.x/trunk/webapps/docs/config/filter.xml (original) +++ tomcat/tc7.0.x/trunk/webapps/docs/config/filter.xml Wed Jun 5 09:58:22 2013 @@ -98,6 +98,158 @@ </section> +<section name="CORS Filter"> + <subsection name="Introduction"> + <p>This filter is an implementation of W3C's CORS (Cross-Origin Resource + Sharing) <a href="http://www.w3.org/TR/cors/">specification</a>, which is a + mechanism that enables cross-origin requests.</p> + <p>The filter works by adding required <code>Access-Control-*</code> headers + to HttpServletResponse object. The filter also protects against HTTP + response splitting. If request is invalid, or is not permitted, then request + is rejected with HTTP status code 403 (Forbidden). A + <a href="../images/cors-flowchart.png">flowchart</a> that + demonstrates request processing by this filter is available.</p> + <p>The minimal configuration required to use this filter is:</p> + <source> +<filter> + <filter-name>CorsFilter</filter-name> + <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> +</filter> +<filter-mapping> + <filter-name>CorsFilter</filter-name> + <url-pattern>/*</url-pattern> +</filter-mapping> + </source> + </subsection> + <subsection name="Filter Class Name"> + <p>The filter class name for the CORS Filter is + <strong><code>org.apache.catalina.filters.CorsFilter</code></strong>.</p> + </subsection> + <subsection name="Initialisation parameters"> + <p>The CORS Filter supports following initialisation parameters:</p> + <attributes> + <attribute name="cors.allowed.origins" required="false"> + <p>A list of <a href="http://tools.ietf.org/html/rfc6454">origins</a> + that are allowed to access the resource. A <code>*</code> can be + specified to enable access to resource from any origin. Otherwise, a + whitelist of comma separated origins can be provided. Eg: <code> + http://www.w3.org, https://www.apache.org</code>. + <strong>Defaults:</strong> <code>*</code> (Any origin is allowed to + access the resource).</p> + </attribute> + <attribute name="cors.allowed.methods" required="false"> + <p>A comma separated list of HTTP methods that can be used to access the + resource, using cross-origin requests. These are the methods which will + also be included as part of <code>Access-Control-Allow-Methods</code> + header in pre-flight response. Eg: <code>GET, POST</code>. + <strong>Defaults:</strong> <code>GET, POST, HEAD, OPTIONS</code></p> + </attribute> + <attribute name="cors.allowed.headers" required="false"> + <p>A comma separated list of request headers that can be used when + making an actual request. These headers will also be returned as part + of <code>Access-Control-Allow-Headers</code> header in a pre-flight + response. Eg: <code>Origin,Accept</code>. <strong>Defaults:</strong> + <code>Origin, Accept, X-Requested-With, Content-Type, + Access-Control-Request-Method, Access-Control-Request-Headers</code></p> + </attribute> + <attribute name="cors.exposed.headers" required="false"> + <p>A comma separated list of headers other than simple response headers + that browsers are allowed to access. These are the headers which will + also be included as part of <code>Access-Control-Expose-Headers</code> + header in the pre-flight response. Eg: + <code>X-CUSTOM-HEADER-PING,X-CUSTOM-HEADER-PONG</code>. + <strong>Default:</strong> None. Non-simple headers are not exposed by + default.</p> + </attribute> + <attribute name="cors.preflight.maxage" required="false"> + <p>The amount of seconds, browser is allowed to cache the result of the + pre-flight request. This will be included as part of + <code>Access-Control-Max-Age</code> header in the pre-flight response. + A negative value will prevent CORS Filter from adding this response + header to pre-flight response. <strong>Defaults:</strong> + <code>1800</code></p> + </attribute> + <attribute name="cors.support.credentials" required="false"> + <p>A flag that indicates whether the resource supports user credentials. + This flag is exposed as part of + <code>Access-Control-Allow-Credentials</code> header in a pre-flight + response. It helps browser determine whether or not an actual request + can be made using credentials. <strong>Defaults:</strong> + <code>true</code></p> + </attribute> + <attribute name="cors.request.decorate" required="false"> + <p>A flag to control if CORS specific attributes should be added to + HttpServletRequest object or not. <strong>Defaults:</strong> + <code>true</code></p> + </attribute> + </attributes> + <p>Here's an example of a more advanced configuration, that overrides + defaults:</p> + <source> +<filter> + <filter-name>CorsFilter</filter-name> + <filter-class>org.apache.catalina.filters.CorsFilter</filter-class> + <init-param> + <param-name>cors.allowed.origins</param-name> + <param-value>*</param-value> + </init-param> + <init-param> + <param-name>cors.allowed.methods</param-name> + <param-value>GET,POST,HEAD,OPTIONS,PUT</param-value> + </init-param> + <init-param> + <param-name>cors.allowed.headers</param-name> + <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value> + </init-param> + <init-param> + <param-name>cors.exposed.headers</param-name> + <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value> + </init-param> + <init-param> + <param-name>cors.support.credentials</param-name> + <param-value>true</param-value> + </init-param> + <init-param> + <param-name>cors.preflight.maxage</param-name> + <param-value>10</param-value> + </init-param> +</filter> +<filter-mapping> + <filter-name>CorsFilter</filter-name> + <url-pattern>/*</url-pattern> +</filter-mapping> + </source> + </subsection> + <subsection name="CORS Filter and HttpServletRequest attributes"> + <p>CORS Filter adds information about the request, in HttpServletRequest + object, for consumption downstream. Following attributes are set, if + <code>cors.request.decorate</code> initialisation parameter is + <code>true</code>:</p> + <ul> + <li><strong>cors.isCorsRequest:</strong> Flag to determine if request is + a CORS request.</li> + <li><strong>cors.request.origin:</strong> The Origin URL, i.e. the URL of + the page from where the request originated.</li> + <li><strong>cors.request.type:</strong> Type of CORS request. Possible + values: + <ul> + <li><code>SIMPLE</code>: A request which is not preceded by a + pre-flight request.</li> + <li><code>ACTUAL</code>: A request which is preceded by a pre-flight + request.</li> + <li><code>PRE_FLIGHT</code>: A pre-flight request.</li> + <li><code>NOT_CORS</code>: A normal same-origin request.</li> + <li><code>INVALID_CORS</code>: A cross-origin request, which is + invalid.</li> + </ul> + </li> + <li><strong>cors.request.headers:</strong> Request headers sent as + <code>Access-Control-Request-Headers</code> header, for a pre-flight + request. + </li> + </ul> + </subsection> +</section> <section name="CSRF Prevention Filter"> @@ -525,6 +677,49 @@ FINE: Request "/docs/config/manager.html </section> +<section name="Failed Request Filter"> + + <subsection name="Introduction"> + + <p>This filter triggers parameters parsing in a request and rejects the + request if some parameters were skipped during parameter parsing because + of parsing errors or request size limitations (such as + <code>maxParameterCount</code> attribute in a + <a href="http.html">Connector</a>). + This filter can be used to ensure that none parameter values submitted by + client are lost.</p> + + <p>Note that parameter parsing may consume the body of an HTTP request, so + caution is needed if the servlet protected by this filter uses + <code>request.getInputStream()</code> or <code>request.getReader()</code> + calls. In general the risk of breaking a web application by adding this + filter is not so high, because parameter parsing does check content type + of the request before consuming the request body.</p> + + <p>Note, that for the POST requests to be parsed correctly, a + <code>SetCharacterEncodingFilter</code> filter must be configured above + this one. See CharacterEncoding page in the FAQ for details.</p> + + <p>The request is rejected with HTTP status code 400 (Bad Request).</p> + + </subsection> + + <subsection name="Filter Class Name"> + + <p>The filter class name for the Failed Request Filter is + <strong><code>org.apache.catalina.filters.FailedRequestFilter</code> + </strong>.</p> + + </subsection> + + <subsection name="Initialisation parameters"> + + <p>The Failed Request Filter does not support any initialization parameters.</p> + + </subsection> + +</section> + <section name="Remote Address Filter"> <subsection name="Introduction"> @@ -1265,52 +1460,6 @@ org.apache.catalina.filters.RequestDumpe </section> - -<section name="Failed Request Filter"> - - <subsection name="Introduction"> - - <p>This filter triggers parameters parsing in a request and rejects the - request if some parameters were skipped during parameter parsing because - of parsing errors or request size limitations (such as - <code>maxParameterCount</code> attribute in a - <a href="http.html">Connector</a>). - This filter can be used to ensure that none parameter values submitted by - client are lost.</p> - - <p>Note that parameter parsing may consume the body of an HTTP request, so - caution is needed if the servlet protected by this filter uses - <code>request.getInputStream()</code> or <code>request.getReader()</code> - calls. In general the risk of breaking a web application by adding this - filter is not so high, because parameter parsing does check content type - of the request before consuming the request body.</p> - - <p>Note, that for the POST requests to be parsed correctly, a - <code>SetCharacterEncodingFilter</code> filter must be configured above - this one. See CharacterEncoding page in the FAQ for details.</p> - - <p>The request is rejected with HTTP status code 400 (Bad Request).</p> - - </subsection> - - <subsection name="Filter Class Name"> - - <p>The filter class name for the Failed Request Filter is - <strong><code>org.apache.catalina.filters.FailedRequestFilter</code> - </strong>.</p> - - </subsection> - - <subsection name="Initialisation parameters"> - - <p>The Failed Request Filter does not support any initialization parameters.</p> - - </subsection> - -</section> - - </body> - </document> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org