/*
* 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
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
assertTrue("Response line is: " + client.getResponseLine(),
client.getResponseLine() != null && client.isResponse200());
assertEquals("OK", client.getResponseBody());
} else {
alv.validateAccessLog(1, 400, 0, 0);
// Connection aborted or response 400
assertTrue("Response line is: " + client.getResponseLine(),
client.getResponseLine() == null || client.isResponse400());
}
int maxHeaderCount =
((Integer) tomcat.getConnector().getProperty("maxHeaderCount")).intValue();
assertEquals(expectedMaxHeaderCount, maxHeaderCount);
if (maxHeaderCount > 0) {
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;
}
assertEquals(arraySize, alv.arraySize);
}
}
@Test
public void testHeaderLimits1() throws Exception {
// Bumping into maxHttpHeaderSize
Tomcat tomcat = getTomcatInstance();
setupHeadersTest(tomcat);
tomcat.getConnector().setProperty("maxHeaderCount", "-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().setProperty("maxHeaderCount", "-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) {
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;
}
}
}