Files
apache-tomcat-8.5.51/test/org/apache/catalina/servlets/DefaultServletEncodingBaseTest.java
2024-11-30 19:03:49 +08:00

283 lines
11 KiB
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.servlets;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.apache.catalina.Context;
import org.apache.catalina.Wrapper;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.startup.TomcatBaseTest;
import org.apache.tomcat.util.buf.B2CConverter;
import org.apache.tomcat.util.buf.ByteChunk;
import org.apache.tomcat.util.http.parser.MediaType;
/*
* Note: This test is split using two base classes. This is because, as a single
* test, it takes so long to run it dominates the time taken to run the
* tests when running tests using multiple threads. For example, on a
* system with 12 cores, the tests take ~5 minutes per connector with this
* test as a single test and ~3.5 minutes per connector with this test
* split in two.
*/
@RunWith(Parameterized.class)
public abstract class DefaultServletEncodingBaseTest extends TomcatBaseTest {
@Parameterized.Parameters(name = "{index}: contextEnc[{0}], fileEnc[{1}], target[{2}]," +
" useInclude[{3}], outputEnc[{4}], callSetCharacterEnc[{5}], useWriter[{6}]")
public static Collection<Object[]> parameters() {
String[] encodings = new String[] {
"utf-8", "ibm850", "cp1252", "iso-8859-1" };
String[] targetFiles = new String[] {
"cp1252", "ibm850", "iso-8859-1", "utf-8-bom", "utf-8" };
List<Object[]> parameterSets = new ArrayList<>();
for (String contextResponseEncoding : encodings) {
for (String fileEncoding : encodings) {
for (String targetFile : targetFiles) {
for (Boolean useInclude : booleans) {
if (useInclude.booleanValue()) {
for (String outputEncoding : encodings) {
for (Boolean callSetCharacterEncoding : booleans) {
for (Boolean useWriter : booleans) {
parameterSets.add(new Object[] { contextResponseEncoding,
fileEncoding, targetFile,
useInclude, outputEncoding,
callSetCharacterEncoding, useWriter });
}
}
}
} else {
/*
* Not using include so ignore outputEncoding,
* callSetCharacterEncoding and useWriter
*
* Tests that do not use include are always expected to
* pass.
*/
String encoding = targetFile;
if (encoding.endsWith("-bom")) {
encoding = encoding.substring(0, encoding.length() - 4);
}
parameterSets.add(new Object[] { contextResponseEncoding, fileEncoding,
targetFile, useInclude, encoding, Boolean.FALSE,
Boolean.FALSE });
}
}
}
}
}
return parameterSets;
}
private static boolean getExpected(String fileEncoding, boolean useBom, String targetFile,
String outputEncoding, boolean callSetCharacterEncoding, boolean useWriter) {
if (useWriter || callSetCharacterEncoding) {
/*
* Using a writer or setting the output character encoding means the
* response will specify a character set. These cases therefore
* reduce to can the file be read with the correct encoding.
* (Assuming any BOM is always skipped in the included output.)
*/
if (targetFile.endsWith("-bom") && useBom ||
targetFile.startsWith(fileEncoding) ||
targetFile.equals("cp1252") && fileEncoding.equals("iso-8859-1") ||
targetFile.equals("iso-8859-1") && fileEncoding.equals("cp1252")) {
return true;
} else {
return false;
}
} else if (!(targetFile.startsWith(outputEncoding) ||
targetFile.equals("cp1252") && outputEncoding.equals("iso-8859-1") ||
targetFile.equals("iso-8859-1") && outputEncoding.equals("cp1252"))) {
/*
* The non-writer use cases read the target file as bytes. These
* cases therefore reduce to can the bytes from the target file be
* included in the output without corruption? The character used in
* the tests has been chosen so that, apart from iso-8859-1 and
* cp1252, the bytes vary by character set.
* (Assuming any BOM is always skipped in the included output.)
*/
return false;
} else {
return true;
}
}
@Parameter(0)
public String contextResponseEncoding;
@Parameter(1)
public String fileEncoding;
@Parameter(2)
public String targetFile;
@Parameter(3)
public boolean useInclude;
@Parameter(4)
public String outputEncoding;
@Parameter(5)
public boolean callSetCharacterEncoding;
@Parameter(6)
public boolean useWriter;
protected abstract boolean getUseBom();
@Test
public void testEncoding() throws Exception {
boolean expectedPass = getExpected(fileEncoding, getUseBom(), targetFile, outputEncoding,
callSetCharacterEncoding, useWriter);
Tomcat tomcat = getTomcatInstance();
File appDir = new File("test/webapp");
Context ctxt = tomcat.addContext("", appDir.getAbsolutePath());
ctxt.setResponseCharacterEncoding(contextResponseEncoding);
Wrapper defaultServlet = Tomcat.addServlet(ctxt, "default", DefaultServlet.class.getName());
defaultServlet.addInitParameter("fileEncoding", fileEncoding);
defaultServlet.addInitParameter("useBomIfPresent", Boolean.toString(getUseBom()));
ctxt.addServletMappingDecoded("/", "default");
if (useInclude) {
Tomcat.addServlet(ctxt, "include", new EncodingServlet(
outputEncoding, callSetCharacterEncoding, targetFile, useWriter));
ctxt.addServletMappingDecoded("/include", "include");
}
tomcat.start();
final ByteChunk res = new ByteChunk();
Map<String,List<String>> headers = new HashMap<>();
String target;
if (useInclude) {
target = "http://localhost:" + getPort() + "/include";
} else {
target = "http://localhost:" + getPort() + "/bug49nnn/bug49464-" + targetFile + ".txt";
}
int rc = getUrl(target, res, headers);
Assert.assertEquals(HttpServletResponse.SC_OK, rc);
String contentType = getSingleHeader("Content-Type", headers);
if (contentType != null) {
MediaType mediaType = MediaType.parseMediaType(new StringReader(contentType));
String charset = mediaType.getCharset();
if (charset == null) {
res.setCharset(B2CConverter.getCharset(outputEncoding));
} else {
res.setCharset(B2CConverter.getCharset(charset));
}
} else {
res.setCharset(B2CConverter.getCharset(outputEncoding));
}
String body = res.toString();
/*
* Remove BOM before checking content
* BOM (should be) removed by Tomcat when file is included
*/
if (!useInclude && targetFile.endsWith("-bom")) {
body = body.substring(1);
}
if (expectedPass) {
if (useInclude) {
Assert.assertEquals("\u00bd-\u00bd-\u00bd", body);
} else {
Assert.assertEquals("\u00bd", body);
}
} else {
if (useInclude) {
Assert.assertNotEquals("\u00bd-\u00bd-\u00bd", body);
} else {
Assert.assertNotEquals("\u00bd", body);
}
}
}
private static class EncodingServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private final String outputEncoding;
private final boolean callSetCharacterEncoding;
private final String includeTarget;
private final boolean useWriter;
public EncodingServlet(String outputEncoding, boolean callSetCharacterEncoding,
String includeTarget, boolean useWriter) {
this.outputEncoding = outputEncoding;
this.callSetCharacterEncoding = callSetCharacterEncoding;
this.includeTarget = includeTarget;
this.useWriter = useWriter;
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
if (callSetCharacterEncoding) {
resp.setCharacterEncoding(outputEncoding);
}
if (useWriter) {
PrintWriter pw = resp.getWriter();
pw.print("\u00bd-");
} else {
resp.getOutputStream().write("\u00bd-".getBytes(outputEncoding));
}
resp.flushBuffer();
RequestDispatcher rd =
req.getRequestDispatcher("/bug49nnn/bug49464-" + includeTarget + ".txt");
rd.include(req, resp);
if (useWriter) {
PrintWriter pw = resp.getWriter();
pw.print("-\u00bd");
} else {
resp.getOutputStream().write("-\u00bd".getBytes(outputEncoding));
}
}
}
}