diff --git a/java/org/apache/coyote/http2/Stream.java b/java/org/apache/coyote/http2/Stream.java index 2e3ff88ebf73..dfe4daaeebe8 100644 --- a/java/org/apache/coyote/http2/Stream.java +++ b/java/org/apache/coyote/http2/Stream.java @@ -18,6 +18,7 @@ import java.io.IOException; import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; @@ -307,18 +308,18 @@ public final void emitHeader(String name, String value) throws HpackException { getConnectionId(), getIdentifier())); } int queryStart = value.indexOf('?'); + String uri; if (queryStart == -1) { - coyoteRequest.requestURI().setString(value); - coyoteRequest.decodedURI().setString( - coyoteRequest.getURLDecoder().convert(value, false)); + uri = value; } else { - String uri = value.substring(0, queryStart); + uri = value.substring(0, queryStart); String query = value.substring(queryStart + 1); - coyoteRequest.requestURI().setString(uri); - coyoteRequest.decodedURI().setString( - coyoteRequest.getURLDecoder().convert(uri, false)); coyoteRequest.queryString().setString(query); } + // Bug 61120. Set the URI as bytes rather than String so any path + // parameters are correctly processed + byte[] uriBytes = uri.getBytes(StandardCharsets.ISO_8859_1); + coyoteRequest.requestURI().setBytes(uriBytes, 0, uriBytes.length); break; } case ":authority": { diff --git a/test/org/apache/coyote/http2/TestStream.java b/test/org/apache/coyote/http2/TestStream.java new file mode 100644 index 000000000000..52ff0165d6ff --- /dev/null +++ b/test/org/apache/coyote/http2/TestStream.java @@ -0,0 +1,97 @@ +/* + * 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.coyote.http2; + +import java.io.IOException; +import java.nio.ByteBuffer; + +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.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; + +public class TestStream extends Http2TestBase { + + /* + * https://bz.apache.org/bugzilla/show_bug.cgi?id=61120 + */ + @Test + public void testPathParam() throws Exception { + + enableHttp2(); + + Tomcat tomcat = getTomcatInstance(); + + Context ctxt = tomcat.addContext("", null); + Tomcat.addServlet(ctxt, "simple", new SimpleServlet()); + ctxt.addServletMappingDecoded("/simple", "simple"); + Tomcat.addServlet(ctxt, "pathparam", new PathParam()); + ctxt.addServletMappingDecoded("/pathparam", "pathparam"); + + tomcat.start(); + + openClientConnection(); + doHttpUpgrade(); + sendClientPreface(); + validateHttp2InitialResponse(); + + byte[] frameHeader = new byte[9]; + ByteBuffer headersPayload = ByteBuffer.allocate(128); + buildGetRequest(frameHeader, headersPayload, null, 3, + "/pathparam;jsessionid=" + PathParam.EXPECTED_SESSION_ID); + writeFrame(frameHeader, headersPayload); + + readSimpleGetResponse(); + + Assert.assertEquals( + "3-HeadersStart\n" + + "3-Header-[:status]-[200]\n" + + "3-Header-[content-type]-[text/plain;charset=UTF-8]\n" + + "3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" + + "3-HeadersEnd\n" + + "3-Body-2\n" + + "3-EndOfStream\n", output.getTrace()); + } + + + private static final class PathParam extends HttpServlet { + + private static final long serialVersionUID = 1L; + + public static final String EXPECTED_SESSION_ID = "0123456789ABCDEF"; + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + response.setContentType("text/plain"); + response.setCharacterEncoding("UTF-8"); + + if (EXPECTED_SESSION_ID.equals(request.getRequestedSessionId())) { + response.getWriter().write("OK"); + } else { + response.getWriter().write("FAIL"); + } + } + } +} diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index 2448b06ec432..10929ef8d7c4 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -69,6 +69,10 @@ 61086: Explicitly signal an empty request body for HTTP 205 responses. (markt) + + 61120: Do not ignore path parameters when processing HTTP/2 + requests. (markt) +