| /* |
| * 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.tomcat.util.http; |
| |
| import java.io.IOException; |
| import java.lang.reflect.Field; |
| import java.net.SocketException; |
| |
| import javax.servlet.ServletException; |
| import javax.servlet.ServletRequest; |
| import javax.servlet.ServletResponse; |
| import javax.servlet.http.HttpServlet; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| import org.apache.catalina.Context; |
| import org.apache.catalina.connector.Request; |
| import org.apache.catalina.connector.Response; |
| import org.apache.catalina.startup.SimpleHttpClient; |
| import org.apache.catalina.startup.Tomcat; |
| import org.apache.catalina.startup.TomcatBaseTest; |
| import org.apache.catalina.valves.TesterAccessLogValve; |
| |
| public class TestMimeHeadersIntegration extends TomcatBaseTest { |
| |
| private HeaderCountLogValve alv; |
| |
| private void setupHeadersTest(Tomcat tomcat) { |
| Context ctx = tomcat.addContext("", getTemporaryDirectory() |
| .getAbsolutePath()); |
| Tomcat.addServlet(ctx, "servlet", new HttpServlet() { |
| private static final long serialVersionUID = 1L; |
| |
| @Override |
| public void service(ServletRequest req, ServletResponse res) |
| throws ServletException, IOException { |
| res.setContentType("text/plain; charset=ISO-8859-1"); |
| res.getWriter().write("OK"); |
| } |
| }); |
| ctx.addServletMappingDecoded("/", "servlet"); |
| |
| alv = new HeaderCountLogValve(); |
| tomcat.getHost().getPipeline().addValve(alv); |
| } |
| |
| private void runHeadersTest(final boolean successExpected, |
| final Tomcat tomcat, final int count, |
| final int expectedMaxHeaderCount) throws Exception { |
| tomcat.start(); |
| |
| String header = "A:B" + SimpleHttpClient.CRLF; |
| StringBuilder request = new StringBuilder(); |
| request.append("GET / HTTP/1.0" + SimpleHttpClient.CRLF); |
| for (int i = 0; i < count; i++) { |
| request.append(header); |
| } |
| request.append(SimpleHttpClient.CRLF); |
| |
| Client client = new Client(tomcat); |
| client.setRequest(new String[] { request.toString() }); |
| try { |
| client.connect(); |
| client.processRequest(); |
| client.disconnect(); |
| } catch (SocketException ex) { |
| // Connection was closed by Tomcat |
| if (successExpected) { |
| // unexpected |
| log.error(ex.getMessage(), ex); |
| } else { |
| log.warn(ex.getMessage(), ex); |
| } |
| } |
| if (successExpected) { |
| alv.validateAccessLog(1, 200, 0, 3000); |
| // Response 200 |
| Assert.assertTrue("Response line is: " + client.getResponseLine(), |
| client.getResponseLine() != null && client.isResponse200()); |
| Assert.assertEquals("OK", client.getResponseBody()); |
| } else { |
| alv.validateAccessLog(1, 400, 0, 0); |
| // Connection aborted or response 400 |
| Assert.assertTrue("Response line is: " + client.getResponseLine(), |
| client.getResponseLine() == null || client.isResponse400()); |
| } |
| int maxHeaderCount = tomcat.getConnector().getMaxHeaderCount(); |
| Assert.assertEquals(expectedMaxHeaderCount, maxHeaderCount); |
| if (maxHeaderCount > 0) { |
| Assert.assertEquals(maxHeaderCount, alv.arraySize); |
| } else if (maxHeaderCount < 0) { |
| int maxHttpHeaderSize = ((Integer) tomcat.getConnector() |
| .getAttribute("maxHttpHeaderSize")).intValue(); |
| int headerCount = Math.min(count, |
| maxHttpHeaderSize / header.length() + 1); |
| int arraySize = 1; |
| while (arraySize < headerCount) { |
| arraySize <<= 1; |
| } |
| Assert.assertEquals(arraySize, alv.arraySize); |
| } |
| } |
| |
| @Test |
| public void testHeaderLimits1() throws Exception { |
| // Bumping into maxHttpHeaderSize |
| Tomcat tomcat = getTomcatInstance(); |
| setupHeadersTest(tomcat); |
| tomcat.getConnector().setMaxHeaderCount(-1); |
| runHeadersTest(false, tomcat, 8 * 1024, -1); |
| } |
| |
| @Test |
| public void testHeaderLimits2() throws Exception { |
| // Can process 100 headers |
| Tomcat tomcat = getTomcatInstance(); |
| setupHeadersTest(tomcat); |
| runHeadersTest(true, tomcat, 100, 100); |
| } |
| |
| @Test |
| public void testHeaderLimits3() throws Exception { |
| // Cannot process 101 header |
| Tomcat tomcat = getTomcatInstance(); |
| setupHeadersTest(tomcat); |
| runHeadersTest(false, tomcat, 101, 100); |
| } |
| |
| @Test |
| public void testHeaderLimits4() throws Exception { |
| // Can change maxHeaderCount |
| Tomcat tomcat = getTomcatInstance(); |
| setupHeadersTest(tomcat); |
| tomcat.getConnector().setMaxHeaderCount(-1); |
| runHeadersTest(true, tomcat, 300, -1); |
| } |
| |
| private static final class HeaderCountLogValve extends TesterAccessLogValve { |
| public volatile int arraySize = -1; |
| |
| @Override |
| public void log(Request request, Response response, long time) { |
| super.log(request, response, time); |
| try { |
| MimeHeaders mh = request.getCoyoteRequest().getMimeHeaders(); |
| Field headersArrayField = MimeHeaders.class |
| .getDeclaredField("headers"); |
| headersArrayField.setAccessible(true); |
| arraySize = ((Object[]) headersArrayField.get(mh)).length; |
| } catch (Exception ex) { |
| Assert.assertNull(ex.getMessage(), ex); |
| } |
| } |
| } |
| |
| private static final class Client extends SimpleHttpClient { |
| public Client(Tomcat tomcat) { |
| setPort(tomcat.getConnector().getLocalPort()); |
| } |
| |
| @Override |
| public boolean isResponseBodyOK() { |
| return true; |
| } |
| } |
| } |