| /* |
| * 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.nio.file.Files; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| 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 static org.apache.catalina.startup.SimpleHttpClient.CRLF; |
| import org.apache.catalina.Context; |
| import org.apache.catalina.Wrapper; |
| import org.apache.catalina.startup.ExpandWar; |
| import org.apache.catalina.startup.SimpleHttpClient; |
| import org.apache.catalina.startup.Tomcat; |
| import org.apache.catalina.startup.TomcatBaseTest; |
| import org.apache.tomcat.util.buf.ByteChunk; |
| |
| @RunWith(Parameterized.class) |
| public class TestDefaultServletPut extends TomcatBaseTest { |
| |
| private static final String START_TEXT= "Starting text"; |
| private static final int START_LEN = START_TEXT.length(); |
| private static final String PATCH_TEXT= "Ending *"; |
| private static final int PATCH_LEN = PATCH_TEXT.length(); |
| private static final String END_TEXT= "Ending * text"; |
| |
| @Parameterized.Parameters(name = "{index} rangeHeader [{0}]") |
| public static Collection<Object[]> parameters() { |
| List<Object[]> parameterSets = new ArrayList<>(); |
| |
| // Valid partial PUT |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 0-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.TRUE, END_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: ByTeS 0-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.TRUE, END_TEXT, Boolean.TRUE }); |
| // Full PUT |
| parameterSets.add(new Object[] { |
| "", null, PATCH_TEXT, Boolean.TRUE }); |
| // Invalid range |
| parameterSets.add(new Object[] { |
| "Content-Range: apples 0-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes00-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes0-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes=0-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes@0-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 9-7/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes -7/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 9-/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 9-X/" + START_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 0-5/" + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 0-5/0x5" + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 0-" + (PATCH_LEN) + "/" + PATCH_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| // Valid partial PUT but partial PUT is disabled |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 0-" + (PATCH_LEN-1) + "/" + START_LEN + CRLF, Boolean.TRUE, START_TEXT, Boolean.FALSE }); |
| // Errors due to incorrect length |
| parameterSets.add(new Object[] { |
| "Content-Range: bytes 0-1/" + PATCH_LEN + CRLF, Boolean.FALSE, START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { "Content-Range: bytes 0-" + PATCH_LEN + "/20" + CRLF, Boolean.FALSE, |
| START_TEXT, Boolean.TRUE }); |
| parameterSets.add(new Object[] { "Content-Range: bytes 0-" + (PATCH_LEN - 2) + "/20" + CRLF, Boolean.FALSE, |
| START_TEXT, Boolean.TRUE }); |
| return parameterSets; |
| } |
| |
| |
| private File tempDocBase; |
| |
| @Parameter(0) |
| public String contentRangeHeader; |
| |
| @Parameter(1) |
| public Boolean contentRangeHeaderValid; |
| |
| @Parameter(2) |
| public String expectedEndText; |
| |
| @Parameter(3) |
| public boolean allowPartialPut; |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| tempDocBase = Files.createTempDirectory(getTemporaryDirectory().toPath(), "put").toFile(); |
| } |
| |
| |
| /* |
| * Replaces the text at the start of START_TEXT with PATCH_TEXT. |
| */ |
| @Test |
| public void testPut() throws Exception { |
| // Configure a web app with a read/write default servlet |
| Tomcat tomcat = getTomcatInstance(); |
| Context ctxt = tomcat.addContext("", tempDocBase.getAbsolutePath()); |
| |
| Wrapper w = Tomcat.addServlet(ctxt, "default", DefaultServlet.class.getName()); |
| w.addInitParameter("readonly", "false"); |
| w.addInitParameter("allowPartialPut", Boolean.toString(allowPartialPut)); |
| ctxt.addServletMappingDecoded("/", "default"); |
| |
| tomcat.start(); |
| |
| // Disable caching |
| ctxt.getResources().setCachingAllowed(false); |
| |
| // Full PUT |
| PutClient putClient = new PutClient(getPort()); |
| |
| // @formatter:off |
| putClient.setRequest(new String[] { |
| "PUT /test.txt HTTP/1.1" + CRLF + |
| "Host: localhost:" + getPort() + CRLF + |
| "Content-Length: " + START_LEN + CRLF + |
| CRLF + |
| START_TEXT |
| }); |
| // @formatter:on |
| putClient.connect(); |
| putClient.processRequest(false); |
| Assert.assertTrue(putClient.isResponse201()); |
| putClient.disconnect(); |
| |
| putClient.reset(); |
| |
| // Partial PUT |
| putClient.connect(); |
| // @formatter:off |
| putClient.setRequest(new String[] { |
| "PUT /test.txt HTTP/1.1" + CRLF + |
| "Host: localhost:" + getPort() + CRLF + |
| contentRangeHeader + |
| "Content-Length: " + PATCH_LEN + CRLF + |
| CRLF + |
| PATCH_TEXT |
| }); |
| // @formatter:on |
| putClient.processRequest(false); |
| if (contentRangeHeaderValid == null) { |
| // Not present (so will do a full PUT, replacing the existing) |
| Assert.assertTrue(putClient.isResponse204()); |
| } else if (contentRangeHeaderValid.booleanValue() && allowPartialPut) { |
| // Valid |
| Assert.assertTrue(putClient.isResponse204()); |
| } else { |
| // Not valid |
| Assert.assertTrue(putClient.isResponse400()); |
| } |
| |
| // Check for the final resource |
| String path = "http://localhost:" + getPort() + "/test.txt"; |
| ByteChunk responseBody = new ByteChunk(); |
| |
| int rc = getUrl(path, responseBody, null); |
| |
| Assert.assertEquals(200, rc); |
| Assert.assertEquals(expectedEndText, responseBody.toString()); |
| } |
| |
| |
| @Override |
| public void tearDown() { |
| ExpandWar.deleteDir(tempDocBase, false); |
| } |
| |
| |
| private static class PutClient extends SimpleHttpClient { |
| |
| PutClient(int port) { |
| setPort(port); |
| } |
| |
| |
| @Override |
| public boolean isResponseBodyOK() { |
| return false; |
| } |
| } |
| } |