diff -Nru tomcat9-9.0.27/bin/catalina.sh tomcat9-9.0.31/bin/catalina.sh --- tomcat9-9.0.27/bin/catalina.sh 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/bin/catalina.sh 2020-02-05 19:26:48.000000000 +0000 @@ -343,6 +343,10 @@ shift fi +# TODO: Bugzilla 63815 +# This doesn't currently work (and can't be made to work) if values used in +# CATALINA_OPTS and/or JAVA_OPTS require quoting. See: +# https://bugs.openjdk.java.net/browse/JDK-8234808 if [ "$1" = "debug" ] ; then if $os400; then echo "Debug command not available on OS400" @@ -384,7 +388,7 @@ echo "Using Security Manager" fi shift - eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ + eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \ -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ -classpath "\"$CLASSPATH\"" \ -Djava.security.manager \ @@ -394,7 +398,7 @@ -Djava.io.tmpdir="\"$CATALINA_TMPDIR\"" \ org.apache.catalina.startup.Bootstrap "$@" start else - eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ + eval exec "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \ -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ -classpath "\"$CLASSPATH\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ @@ -452,7 +456,7 @@ echo "Using Security Manager" fi shift - eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ + eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \ -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ -classpath "\"$CLASSPATH\"" \ -Djava.security.manager \ @@ -464,7 +468,7 @@ >> "$CATALINA_OUT" 2>&1 "&" else - eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER $JAVA_OPTS $CATALINA_OPTS \ + eval $_NOHUP "\"$_RUNJAVA\"" "\"$LOGGING_CONFIG\"" $LOGGING_MANAGER "$JAVA_OPTS" "$CATALINA_OPTS" \ -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ -classpath "\"$CLASSPATH\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ @@ -517,7 +521,7 @@ fi fi - eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \ + eval "\"$_RUNJAVA\"" $LOGGING_MANAGER "$JAVA_OPTS" \ -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ -classpath "\"$CLASSPATH\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ @@ -604,7 +608,7 @@ elif [ "$1" = "configtest" ] ; then - eval "\"$_RUNJAVA\"" $LOGGING_MANAGER $JAVA_OPTS \ + eval "\"$_RUNJAVA\"" $LOGGING_MANAGER "$JAVA_OPTS" \ -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ -classpath "\"$CLASSPATH\"" \ -Dcatalina.base="\"$CATALINA_BASE\"" \ diff -Nru tomcat9-9.0.27/bin/daemon.sh tomcat9-9.0.31/bin/daemon.sh --- tomcat9-9.0.27/bin/daemon.sh 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/bin/daemon.sh 2020-02-05 19:26:48.000000000 +0000 @@ -199,49 +199,51 @@ case "$1" in run ) shift - "$JSVC" $* \ - $JSVC_OPTS \ - -java-home "$JAVA_HOME" \ - -pidfile "$CATALINA_PID" \ - -wait "$SERVICE_START_WAIT_TIME" \ + eval exec "\"$JSVC\"" $* \ + "$JSVC_OPTS" \ + -java-home "\"$JAVA_HOME\"" \ + -pidfile "\"$CATALINA_PID\"" \ + -wait $SERVICE_START_WAIT_TIME \ -nodetach \ - -outfile "&1" \ - -errfile "&2" \ - -classpath "$CLASSPATH" \ - "$LOGGING_CONFIG" $JAVA_OPTS $CATALINA_OPTS \ - -D$ENDORSED_PROP="$JAVA_ENDORSED_DIRS" \ - -Dcatalina.base="$CATALINA_BASE" \ - -Dcatalina.home="$CATALINA_HOME" \ - -Djava.io.tmpdir="$CATALINA_TMP" \ + -outfile "\"&1\"" \ + -errfile "\"&2\"" \ + -classpath "\"$CLASSPATH\"" \ + "\"$LOGGING_CONFIG\"" "$JAVA_OPTS" "$CATALINA_OPTS" \ + -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ + -Dcatalina.base="\"$CATALINA_BASE\"" \ + -Dcatalina.home="\"$CATALINA_HOME\"" \ + -Djava.io.tmpdir="\"$CATALINA_TMP\"" \ $CATALINA_MAIN exit $? ;; start ) - "$JSVC" $JSVC_OPTS \ - -java-home "$JAVA_HOME" \ + eval "\"$JSVC\"" \ + "$JSVC_OPTS" \ + -java-home "\"$JAVA_HOME\"" \ -user $TOMCAT_USER \ - -pidfile "$CATALINA_PID" \ - -wait "$SERVICE_START_WAIT_TIME" \ - -outfile "$CATALINA_OUT" \ - -errfile "&1" \ - -classpath "$CLASSPATH" \ - "$LOGGING_CONFIG" $JAVA_OPTS $CATALINA_OPTS \ - -D$ENDORSED_PROP="$JAVA_ENDORSED_DIRS" \ - -Dcatalina.base="$CATALINA_BASE" \ - -Dcatalina.home="$CATALINA_HOME" \ - -Djava.io.tmpdir="$CATALINA_TMP" \ + -pidfile "\"$CATALINA_PID\"" \ + -wait $SERVICE_START_WAIT_TIME \ + -outfile "\"$CATALINA_OUT\"" \ + -errfile "\"&1\"" \ + -classpath "\"$CLASSPATH\"" \ + "\"$LOGGING_CONFIG\"" "$JAVA_OPTS" "$CATALINA_OPTS" \ + -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ + -Dcatalina.base="\"$CATALINA_BASE\"" \ + -Dcatalina.home="\"$CATALINA_HOME\"" \ + -Djava.io.tmpdir="\"$CATALINA_TMP\"" \ $CATALINA_MAIN exit $? ;; stop ) - "$JSVC" $JSVC_OPTS \ + eval "\"$JSVC\"" \ + "$JSVC_OPTS" \ -stop \ - -pidfile "$CATALINA_PID" \ - -classpath "$CLASSPATH" \ - -D$ENDORSED_PROP="$JAVA_ENDORSED_DIRS" \ - -Dcatalina.base="$CATALINA_BASE" \ - -Dcatalina.home="$CATALINA_HOME" \ - -Djava.io.tmpdir="$CATALINA_TMP" \ + -pidfile "\"$CATALINA_PID\"" \ + -classpath "\"$CLASSPATH\"" \ + -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ + -Dcatalina.base="\"$CATALINA_BASE\"" \ + -Dcatalina.home="\"$CATALINA_HOME\"" \ + -Djava.io.tmpdir="\"$CATALINA_TMP\"" \ $CATALINA_MAIN exit $? ;; diff -Nru tomcat9-9.0.27/bin/tool-wrapper.sh tomcat9-9.0.31/bin/tool-wrapper.sh --- tomcat9-9.0.27/bin/tool-wrapper.sh 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/bin/tool-wrapper.sh 2020-02-05 19:26:48.000000000 +0000 @@ -146,8 +146,8 @@ # ----- Execute The Requested Command ----------------------------------------- -exec "$_RUNJAVA" $JAVA_OPTS $TOOL_OPTS \ - -D$ENDORSED_PROP="$JAVA_ENDORSED_DIRS" \ - -classpath "$CLASSPATH" \ - -Dcatalina.home="$CATALINA_HOME" \ +eval exec "\"$_RUNJAVA\"" "$JAVA_OPTS" "$TOOL_OPTS" \ + -D$ENDORSED_PROP="\"$JAVA_ENDORSED_DIRS\"" \ + -classpath "\"$CLASSPATH\"" \ + -Dcatalina.home="\"$CATALINA_HOME\"" \ org.apache.catalina.startup.Tool "$@" diff -Nru tomcat9-9.0.27/build.properties.default tomcat9-9.0.31/build.properties.default --- tomcat9-9.0.27/build.properties.default 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/build.properties.default 2020-02-05 19:26:48.000000000 +0000 @@ -25,7 +25,7 @@ # ----- Version Control Flags ----- version.major=9 version.minor=0 -version.build=27 +version.build=31 version.patch=0 version.suffix= diff -Nru tomcat9-9.0.27/build.xml tomcat9-9.0.31/build.xml --- tomcat9-9.0.27/build.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/build.xml 2020-02-05 19:26:48.000000000 +0000 @@ -110,6 +110,7 @@ + @@ -136,6 +137,7 @@ + @@ -201,6 +203,19 @@ value="-html5"/> + + + + + + + @@ -407,12 +422,17 @@ + + + + + @@ -683,12 +703,12 @@ deprecation="${compile.deprecation}" source="${compile.source}" target="${compile.target}" - release="${compile.release}" encoding="ISO-8859-1" includeAntRuntime="true" > + @@ -821,6 +841,12 @@ filesId="files.catalina-tribes" addOSGi="true" /> + + + @@ -1599,6 +1626,11 @@ + + + + + @@ -2367,6 +2399,8 @@ + + @@ -2392,6 +2426,8 @@ + + @@ -2622,6 +2658,11 @@ filesDir="java" filesId="files.catalina-tribes" /> + + + - - + - - -+ - - - diff -Nru tomcat9-9.0.27/debian/patches/0013-dont-look-for-build-properties-in-user-home.patch tomcat9-9.0.31/debian/patches/0013-dont-look-for-build-properties-in-user-home.patch --- tomcat9-9.0.27/debian/patches/0013-dont-look-for-build-properties-in-user-home.patch 2019-10-07 14:15:17.000000000 +0000 +++ tomcat9-9.0.31/debian/patches/0013-dont-look-for-build-properties-in-user-home.patch 2020-02-24 18:53:02.000000000 +0000 @@ -5,7 +5,7 @@ Forwarded: not-needed --- a/build.xml +++ b/build.xml -@@ -523,7 +523,6 @@ +@@ -543,7 +543,6 @@ @@ -13,7 +13,7 @@ -@@ -537,7 +536,6 @@ +@@ -557,7 +556,6 @@ diff -Nru tomcat9-9.0.27/debian/patches/0019-add-distribution-to-error-page.patch tomcat9-9.0.31/debian/patches/0019-add-distribution-to-error-page.patch --- tomcat9-9.0.27/debian/patches/0019-add-distribution-to-error-page.patch 2019-10-07 14:15:21.000000000 +0000 +++ tomcat9-9.0.31/debian/patches/0019-add-distribution-to-error-page.patch 2020-02-24 18:53:05.000000000 +0000 @@ -16,7 +16,7 @@ \ No newline at end of file --- a/build.xml +++ b/build.xml -@@ -236,6 +236,7 @@ +@@ -251,6 +251,7 @@ diff -Nru tomcat9-9.0.27/debian/patches/0025-invalid-configuration-exit-status.patch tomcat9-9.0.31/debian/patches/0025-invalid-configuration-exit-status.patch --- tomcat9-9.0.27/debian/patches/0025-invalid-configuration-exit-status.patch 2019-10-07 14:05:24.000000000 +0000 +++ tomcat9-9.0.31/debian/patches/0025-invalid-configuration-exit-status.patch 2020-02-24 18:51:33.000000000 +0000 @@ -3,7 +3,7 @@ Bug: https://bz.apache.org/bugzilla/show_bug.cgi?id=62607 --- a/java/org/apache/catalina/startup/Bootstrap.java +++ b/java/org/apache/catalina/startup/Bootstrap.java -@@ -472,6 +472,10 @@ +@@ -471,6 +471,10 @@ } else if (command.equals("start")) { daemon.setAwait(true); daemon.load(args); diff -Nru tomcat9-9.0.27/debian/patches/0027-java11-compilation.patch tomcat9-9.0.31/debian/patches/0027-java11-compilation.patch --- tomcat9-9.0.27/debian/patches/0027-java11-compilation.patch 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/debian/patches/0027-java11-compilation.patch 2020-02-24 19:40:24.000000000 +0000 @@ -0,0 +1,29 @@ +Description: Fixes the compilation with Java 11 +Author: Emmanuel Bourg +Forwarded: no +--- a/build.xml ++++ b/build.xml +@@ -702,11 +702,23 @@ + deprecation="${compile.deprecation}" + source="${compile.source}" + target="${compile.target}" ++ release="${compile.release}" + encoding="ISO-8859-1" ++ excludes="**/JmxRemoteLifecycleListener*" + includeAntRuntime="true" > + ++ ++ ++ + + + diff -Nru tomcat9-9.0.27/debian/patches/series tomcat9-9.0.31/debian/patches/series --- tomcat9-9.0.27/debian/patches/series 2019-10-07 14:05:24.000000000 +0000 +++ tomcat9-9.0.31/debian/patches/series 2020-02-24 19:03:51.000000000 +0000 @@ -1,4 +1,3 @@ -0002-do-not-load-AJP13-connector-by-default.patch 0004-split-deploy-webapps-target-from-deploy-target.patch 0005-skip-test-failures.patch 0009-Use-java.security.policy-file-in-catalina.sh.patch @@ -11,3 +10,4 @@ 0024-systemd-log-formatter.patch 0025-invalid-configuration-exit-status.patch 0026-easymock4-compatibility.patch +0027-java11-compilation.patch diff -Nru tomcat9-9.0.27/debian/tomcat9.service tomcat9-9.0.31/debian/tomcat9.service --- tomcat9-9.0.27/debian/tomcat9.service 2019-10-07 14:05:24.000000000 +0000 +++ tomcat9-9.0.31/debian/tomcat9.service 2020-02-24 19:44:17.000000000 +0000 @@ -6,6 +6,7 @@ Description=Apache Tomcat 9 Web Application Server Documentation=https://tomcat.apache.org/tomcat-9.0-doc/index.html After=network.target +RequiresMountsFor=/var/log/tomcat9 /var/lib/tomcat9 [Service] @@ -37,7 +38,6 @@ ReadWritePaths=/etc/tomcat9/Catalina/ ReadWritePaths=/var/lib/tomcat9/webapps/ ReadWritePaths=/var/log/tomcat9/ -RequiresMountsFor=/var/log/tomcat9 /var/lib/tomcat9 [Install] WantedBy=multi-user.target diff -Nru tomcat9-9.0.27/.gitignore tomcat9-9.0.31/.gitignore --- tomcat9-9.0.27/.gitignore 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/.gitignore 2020-02-05 19:26:48.000000000 +0000 @@ -23,6 +23,7 @@ work build.properties mvn.properties +.ant-targets-build.xml .checkstyle .classpath .fbprefs diff -Nru tomcat9-9.0.27/java/javax/el/ExpressionFactory.java tomcat9-9.0.31/java/javax/el/ExpressionFactory.java --- tomcat9-9.0.27/java/javax/el/ExpressionFactory.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/el/ExpressionFactory.java 2020-02-05 19:26:48.000000000 +0000 @@ -14,25 +14,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package javax.el; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.Iterator; import java.util.Map; import java.util.Properties; +import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; @@ -47,9 +45,6 @@ private static final boolean IS_SECURITY_ENABLED = (System.getSecurityManager() != null); - private static final String SERVICE_RESOURCE_NAME = - "META-INF/services/javax.el.ExpressionFactory"; - private static final String PROPERTY_NAME = "javax.el.ExpressionFactory"; private static final String PROPERTY_FILE; @@ -371,35 +366,20 @@ } private static String getClassNameServices(ClassLoader tccl) { - InputStream is = null; - if (tccl == null) { - is = ClassLoader.getSystemResourceAsStream(SERVICE_RESOURCE_NAME); - } else { - is = tccl.getResourceAsStream(SERVICE_RESOURCE_NAME); + ExpressionFactory result = null; + + ServiceLoader serviceLoader = ServiceLoader.load(ExpressionFactory.class, tccl); + Iterator iter = serviceLoader.iterator(); + while (result == null && iter.hasNext()) { + result = iter.next(); } - if (is != null) { - String line = null; - try (InputStreamReader isr = new InputStreamReader(is, "UTF-8"); - BufferedReader br = new BufferedReader(isr)) { - line = br.readLine(); - if (line != null && line.trim().length() > 0) { - return line.trim(); - } - } catch (UnsupportedEncodingException e) { - // Should never happen with UTF-8 - // If it does - ignore & return null - } catch (IOException e) { - throw new ELException(Util.message(null, "expressionFactory.readFailed", SERVICE_RESOURCE_NAME), e); - } finally { - try { - is.close(); - } catch (IOException ioe) {/*Ignore*/} - } + if (result == null) { + return null; } - return null; + return result.getClass().getName(); } private static String getClassNameJreDir() { diff -Nru tomcat9-9.0.27/java/javax/el/ImportHandler.java tomcat9-9.0.31/java/javax/el/ImportHandler.java --- tomcat9-9.0.27/java/javax/el/ImportHandler.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/el/ImportHandler.java 2020-02-05 19:26:48.000000000 +0000 @@ -137,7 +137,7 @@ standardPackages.put("javax.servlet.jsp", servletJspClassNames); Set javaLangClassNames = new HashSet<>(); - // Taken from Java 11 EA18 Javadoc + // Taken from Java 14 EA27 Javadoc // Interfaces javaLangClassNames.add("Appendable"); javaLangClassNames.add("AutoCloseable"); @@ -179,6 +179,7 @@ javaLangClassNames.add("Process"); javaLangClassNames.add("ProcessBuilder"); javaLangClassNames.add("ProcessBuilder.Redirect"); + javaLangClassNames.add("Record"); javaLangClassNames.add("Runtime"); javaLangClassNames.add("Runtime.Version"); javaLangClassNames.add("RuntimePermission"); diff -Nru tomcat9-9.0.27/java/javax/el/LocalStrings_pt_BR.properties tomcat9-9.0.31/java/javax/el/LocalStrings_pt_BR.properties --- tomcat9-9.0.27/java/javax/el/LocalStrings_pt_BR.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/el/LocalStrings_pt_BR.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,4 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +beanNameELResolver.beanReadOnly=O nome do bean [{0}] é apenas para leitura + +elProcessor.defineFunctionInvalidClass=A classe [{0}] não possui visibilidade pública +elProcessor.defineFunctionInvalidMethod=O método [{0}] da classe [{1}] não é um método público e estático + importHandler.invalidClassNameForStatic=A classe [{0}] especificada para o import estático [{1}] é inválida diff -Nru tomcat9-9.0.27/java/javax/el/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/javax/el/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/javax/el/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/el/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -28,7 +28,7 @@ importHandler.ambiguousImport=无法导入类[{0}],因为它与已导入的[{1}]冲突 importHandler.ambiguousStaticImport=无法处理静态导入[{0}],因为它与已导入的[{1}]冲突 importHandler.classNotFound=无法导入类[{0}],因为无法找到它 -importHandler.invalidClass=类[{0}]必须是公共的和非抽象的,并且不能是接口 +importHandler.invalidClass=类[{0}]必须是公共的、非抽象的、非接口且(对于Java 9+)在一个导出包 importHandler.invalidClassNameForStatic=为 static import [{1}] 指定的类 [{0}] 不可用 importHandler.invalidStaticName=导入 [{0}] 的静态方法或字段名称必须包含类 importHandler.staticNotFound=导入[{2}]的类[{1}]中找不到静态导入[{0}] @@ -43,7 +43,7 @@ propertyWriteError=在类型[{0}]上写入[{1}]时出错 staticFieldELResolver.methodNotFound=在类[{1}]上找不到名为[{0}]的匹配的公共静态方法 -staticFieldELResolver.notFound=在类[{1}]上找不到名为[{0}]的公共静态字段 +staticFieldELResolver.notFound=(Java 9+导出)类[{1}]上找不到名为[{0}]的公共静态字段 staticFieldELResolver.notWriteable=不允许写入静态字段(当前情况中为类[{1}]上的字段[{0}]) util.method.ambiguous=无法找到明确的方法:{0}.{1}({2}) diff -Nru tomcat9-9.0.27/java/javax/servlet/AsyncContext.java tomcat9-9.0.31/java/javax/servlet/AsyncContext.java --- tomcat9-9.0.27/java/javax/servlet/AsyncContext.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/servlet/AsyncContext.java 2020-02-05 19:26:48.000000000 +0000 @@ -100,7 +100,7 @@ void setTimeout(long timeout); /** - * Get the current. + * Get the current timeout. * * @return The timeout in milliseconds. 0 or less indicates no timeout. */ diff -Nru tomcat9-9.0.27/java/javax/servlet/GenericServlet.java tomcat9-9.0.31/java/javax/servlet/GenericServlet.java --- tomcat9-9.0.27/java/javax/servlet/GenericServlet.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/servlet/GenericServlet.java 2020-02-05 19:26:48.000000000 +0000 @@ -180,12 +180,12 @@ * Writes the specified message to a servlet log file, prepended by the * servlet's name. See {@link ServletContext#log(String)}. * - * @param msg + * @param message * a String specifying the message to be written to * the log file */ - public void log(String msg) { - getServletContext().log(getServletName() + ": " + msg); + public void log(String message) { + getServletContext().log(getServletName() + ": " + message); } /** diff -Nru tomcat9-9.0.27/java/javax/servlet/http/HttpServletRequestWrapper.java tomcat9-9.0.31/java/javax/servlet/http/HttpServletRequestWrapper.java --- tomcat9-9.0.27/java/javax/servlet/http/HttpServletRequestWrapper.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/servlet/http/HttpServletRequestWrapper.java 2020-02-05 19:26:48.000000000 +0000 @@ -116,8 +116,11 @@ } /** - * The default behavior of this method is to return getMapping() on the - * wrapped request object. + * The default behavior of this method is to return + * {@link HttpServletRequest#getHttpServletMapping()} on the wrapped request + * object. + * + * @since Servlet 4.0 */ @Override public HttpServletMapping getHttpServletMapping() { diff -Nru tomcat9-9.0.27/java/javax/servlet/http/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/javax/servlet/http/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/javax/servlet/http/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/servlet/http/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -22,3 +22,4 @@ http.method_not_implemented=这个servlet没有为这个URI实现方法[{0}] http.method_post_not_supported=此URL不支持Http方法POST http.method_put_not_supported=此URL不支持HTTP方法PUT +http.non_http=没有HTTP请求或响应 diff -Nru tomcat9-9.0.27/java/javax/servlet/http/PushBuilder.java tomcat9-9.0.31/java/javax/servlet/http/PushBuilder.java --- tomcat9-9.0.27/java/javax/servlet/http/PushBuilder.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/servlet/http/PushBuilder.java 2020-02-05 19:26:48.000000000 +0000 @@ -134,8 +134,8 @@ * fields are set to {@code null}: *
    *
  • {@code path}
  • - *
  • {@code etag}
  • - *
  • {@code lastModified}
  • + *
  • conditional request headers ({@code if-none-match} and + * {@code if-modified-since})
  • *
* * @throws IllegalStateException If this method is called when {@code path} diff -Nru tomcat9-9.0.27/java/javax/servlet/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/javax/servlet/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/javax/servlet/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/javax/servlet/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -20,3 +20,6 @@ httpMethodConstraintElement.invalidMethod=无效的HTTP.方法 value.true=true + +wrapper.nullRequest=请求不能为空 +wrapper.nullResponse=响应不能为空 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/AccessLog.java tomcat9-9.0.31/java/org/apache/catalina/AccessLog.java --- tomcat9-9.0.27/java/org/apache/catalina/AccessLog.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/AccessLog.java 2020-02-05 19:26:48.000000000 +0000 @@ -81,22 +81,21 @@ public void log(Request request, Response response, long time); /** - * Should this valve set request attributes for IP address, hostname, - * protocol and port used for the request? This are typically used in - * conjunction with the {@link org.apache.catalina.valves.AccessLogValve} - * which will otherwise log the original values. + * Should this valve use request attributes for IP address, hostname, + * protocol and port used for the request? * - * The attributes set are: + * The attributes used are: *
    *
  • org.apache.catalina.RemoteAddr
  • *
  • org.apache.catalina.RemoteHost
  • *
  • org.apache.catalina.Protocol
  • + *
  • org.apache.catalina.ServerName
  • *
  • org.apache.catalina.ServerPost
  • *
* * @param requestAttributesEnabled true causes the attributes - * to be set, false disables - * the setting of the attributes. + * to be used, false causes + * the original values to be used. */ public void setRequestAttributesEnabled(boolean requestAttributesEnabled); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ant/ServerinfoTask.java tomcat9-9.0.31/java/org/apache/catalina/ant/ServerinfoTask.java --- tomcat9-9.0.27/java/org/apache/catalina/ant/ServerinfoTask.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ant/ServerinfoTask.java 2020-02-05 19:26:48.000000000 +0000 @@ -14,8 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - package org.apache.catalina.ant; @@ -44,5 +42,4 @@ execute("/serverinfo"); } - } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/authenticator/AuthenticatorBase.java tomcat9-9.0.31/java/org/apache/catalina/authenticator/AuthenticatorBase.java --- tomcat9-9.0.27/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/authenticator/AuthenticatorBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -19,6 +19,7 @@ import java.io.IOException; import java.security.Principal; import java.security.cert.X509Certificate; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -33,6 +34,7 @@ import javax.security.auth.message.config.RegistrationListener; import javax.security.auth.message.config.ServerAuthConfig; import javax.security.auth.message.config.ServerAuthContext; +import javax.servlet.DispatcherType; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.Cookie; @@ -44,7 +46,6 @@ import org.apache.catalina.Context; import org.apache.catalina.Globals; import org.apache.catalina.LifecycleException; -import org.apache.catalina.Manager; import org.apache.catalina.Realm; import org.apache.catalina.Session; import org.apache.catalina.TomcatPrincipal; @@ -53,6 +54,7 @@ import org.apache.catalina.authenticator.jaspic.MessageInfoImpl; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; +import org.apache.catalina.filters.CorsFilter; import org.apache.catalina.filters.RemoteIpFilter; import org.apache.catalina.realm.GenericPrincipal; import org.apache.catalina.util.SessionIdGeneratorBase; @@ -63,9 +65,12 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; import org.apache.tomcat.util.descriptor.web.LoginConfig; import org.apache.tomcat.util.descriptor.web.SecurityConstraint; import org.apache.tomcat.util.http.FastHttpDateFormat; +import org.apache.tomcat.util.http.RequestUtil; import org.apache.tomcat.util.res.StringManager; /** @@ -237,12 +242,22 @@ */ protected SingleSignOn sso = null; + private AllowCorsPreflight allowCorsPreflight = AllowCorsPreflight.NEVER; + private volatile String jaspicAppContextID = null; private volatile Optional jaspicProvider = null; // ------------------------------------------------------------- Properties + public String getAllowCorsPreflight() { + return allowCorsPreflight.name().toLowerCase(Locale.ENGLISH); + } + + public void setAllowCorsPreflight(String allowCorsPreflight) { + this.allowCorsPreflight = AllowCorsPreflight.valueOf(allowCorsPreflight.trim().toUpperCase(Locale.ENGLISH)); + } + public boolean getAlwaysUseSession() { return alwaysUseSession; } @@ -521,7 +536,7 @@ if (constraints == null && !context.getPreemptiveAuthentication() && !authRequired) { if (log.isDebugEnabled()) { - log.debug(" Not subject to any constraint"); + log.debug("Not subject to any constraint"); } getNext().invoke(request, response); return; @@ -544,11 +559,11 @@ if (constraints != null) { // Enforce any user data constraint for this security constraint if (log.isDebugEnabled()) { - log.debug(" Calling hasUserDataPermission()"); + log.debug("Calling hasUserDataPermission()"); } if (!realm.hasUserDataPermission(request, response, constraints)) { if (log.isDebugEnabled()) { - log.debug(" Failed hasUserDataPermission() test"); + log.debug("Failed hasUserDataPermission() test"); } /* * ASSERT: Authenticator already set the appropriate HTTP status @@ -593,9 +608,17 @@ JaspicState jaspicState = null; + if ((authRequired || constraints != null) && allowCorsPreflightBypass(request)) { + if (log.isDebugEnabled()) { + log.debug("CORS Preflight request bypassing authentication"); + } + getNext().invoke(request, response); + return; + } + if (authRequired) { if (log.isDebugEnabled()) { - log.debug(" Calling authenticate()"); + log.debug("Calling authenticate()"); } if (jaspicProvider != null) { @@ -609,7 +632,7 @@ jaspicProvider != null && !authenticateJaspic(request, response, jaspicState, false)) { if (log.isDebugEnabled()) { - log.debug(" Failed authenticate() test"); + log.debug("Failed authenticate() test"); } /* * ASSERT: Authenticator already set the appropriate HTTP status @@ -622,11 +645,11 @@ if (constraints != null) { if (log.isDebugEnabled()) { - log.debug(" Calling accessControl()"); + log.debug("Calling accessControl()"); } if (!realm.hasResourcePermission(request, response, constraints, this.context)) { if (log.isDebugEnabled()) { - log.debug(" Failed accessControl() test"); + log.debug("Failed accessControl() test"); } /* * ASSERT: AccessControl method has already set the appropriate @@ -638,7 +661,7 @@ // Any and all specified constraints have been satisfied if (log.isDebugEnabled()) { - log.debug(" Successfully passed all security constraints"); + log.debug("Successfully passed all security constraints"); } getNext().invoke(request, response); @@ -648,6 +671,64 @@ } + protected boolean allowCorsPreflightBypass(Request request) { + boolean allowBypass = false; + + if (allowCorsPreflight != AllowCorsPreflight.NEVER) { + // First check to see if this is a CORS Preflight request + // This is a subset of the tests in CorsFilter.checkRequestType + if ("OPTIONS".equals(request.getMethod())) { + String originHeader = request.getHeader(CorsFilter.REQUEST_HEADER_ORIGIN); + if (originHeader != null && + !originHeader.isEmpty() && + RequestUtil.isValidOrigin(originHeader) && + !RequestUtil.isSameOrigin(request, originHeader)) { + String accessControlRequestMethodHeader = + request.getHeader(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD); + if (accessControlRequestMethodHeader != null && + !accessControlRequestMethodHeader.isEmpty()) { + // This appears to be a CORS Preflight request + if (allowCorsPreflight == AllowCorsPreflight.ALWAYS) { + allowBypass = true; + } else if (allowCorsPreflight == AllowCorsPreflight.FILTER) { + if (DispatcherType.REQUEST == request.getDispatcherType()) { + // Look at Filter configuration for the Context + // Can't cache this unless we add a listener to + // the Context to clear the cache on reload + for (FilterDef filterDef : request.getContext().findFilterDefs()) { + if (CorsFilter.class.getName().equals(filterDef.getFilterClass())) { + for (FilterMap filterMap : context.findFilterMaps()) { + if (filterMap.getFilterName().equals(filterDef.getFilterName())) { + if ((filterMap.getDispatcherMapping() & FilterMap.REQUEST) > 0) { + for (String urlPattern : filterMap.getURLPatterns()) { + if ("/*".equals(urlPattern)) { + allowBypass = true; + // No need to check other patterns + break; + } + } + } + // Found mappings for CORS filter. + // No need to look further + break; + } + } + // Found the CORS filter. No need to look further. + break; + } + } + } + } else { + // Unexpected enum type + } + } + } + } + } + return allowBypass; + } + + @Override public boolean authenticate(Request request, HttpServletResponse httpResponse) throws IOException { @@ -986,7 +1067,7 @@ associate(ssoId, request.getSessionInternal(true)); if (log.isDebugEnabled()) { - log.debug(" Reauthenticated cached principal '" + + log.debug("Reauthenticated cached principal '" + request.getUserPrincipal().getName() + "' with auth type '" + request.getAuthType() + "'"); } @@ -1020,7 +1101,31 @@ } - private void register(Request request, HttpServletResponse response, Principal principal, + /** + * Register an authenticated Principal and authentication type in our + * request, in the current session (if there is one), and with our + * SingleSignOn valve, if there is one. Set the appropriate cookie to be + * returned. + * + * @param request + * The servlet request we are processing + * @param response + * The servlet response we are generating + * @param principal + * The authenticated Principal to be registered + * @param authType + * The authentication type to be registered + * @param username + * Username used to authenticate (if any) + * @param password + * Password used to authenticate (if any) + * @param alwaysUseSession + * Should a session always be used once a user is authenticated? + * @param cache + * Should we cache authenticated Principals if the request is part of an + * HTTP session? + */ + protected void register(Request request, HttpServletResponse response, Principal principal, String authType, String username, String password, boolean alwaysUseSession, boolean cache) { @@ -1044,17 +1149,11 @@ if (session != null) { // If the principal is null then this is a logout. No need to change // the session ID. See BZ 59043. - if (changeSessionIdOnAuthentication && principal != null) { - String oldId = null; - if (log.isDebugEnabled()) { - oldId = session.getId(); - } - Manager manager = request.getContext().getManager(); - manager.changeSessionId(session); - request.changeSessionId(session.getId()); - if (log.isDebugEnabled()) { - log.debug(sm.getString("authenticator.changeSessionId", - oldId, session.getId())); + if (getChangeSessionIdOnAuthentication() && principal != null) { + String newSessionId = changeSessionID(request, session); + // If the current session ID is being tracked, update it. + if (session.getNote(Constants.SESSION_ID_NOTE) != null) { + session.setNote(Constants.SESSION_ID_NOTE, newSessionId); } } } else if (alwaysUseSession) { @@ -1062,21 +1161,9 @@ } // Cache the authentication information in our session, if any - if (cache) { - if (session != null) { - session.setAuthType(authType); - session.setPrincipal(principal); - if (username != null) { - session.setNote(Constants.SESS_USERNAME_NOTE, username); - } else { - session.removeNote(Constants.SESS_USERNAME_NOTE); - } - if (password != null) { - session.setNote(Constants.SESS_PASSWORD_NOTE, password); - } else { - session.removeNote(Constants.SESS_PASSWORD_NOTE); - } - } + if (session != null && cache) { + session.setAuthType(authType); + session.setPrincipal(principal); } // Construct a cookie to be returned to the client @@ -1142,6 +1229,20 @@ } + + protected String changeSessionID(Request request, Session session) { + String oldId = null; + if (log.isDebugEnabled()) { + oldId = session.getId(); + } + String newId = request.changeSessionId(); + if (log.isDebugEnabled()) { + log.debug(sm.getString("authenticator.changeSessionId", oldId, newId)); + } + return newId; + } + + @Override public void login(String username, String password, Request request) throws ServletException { Principal principal = doLogin(request, username, password); @@ -1301,4 +1402,11 @@ public MessageInfo messageInfo = null; public ServerAuthContext serverAuthContext = null; } + + + protected enum AllowCorsPreflight { + NEVER, + FILTER, + ALWAYS + } } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/authenticator/Constants.java tomcat9-9.0.31/java/org/apache/catalina/authenticator/Constants.java --- tomcat9-9.0.27/java/org/apache/catalina/authenticator/Constants.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/authenticator/Constants.java 2020-02-05 19:26:48.000000000 +0000 @@ -14,11 +14,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - - package org.apache.catalina.authenticator; - public class Constants { // Authentication methods for login configuration // Servlet spec schemes are defined in HttpServletRequest @@ -33,17 +30,13 @@ // SPNEGO authentication constants public static final String KRB5_CONF_PROPERTY = "java.security.krb5.conf"; public static final String DEFAULT_KRB5_CONF = "conf/krb5.ini"; - public static final String JAAS_CONF_PROPERTY = - "java.security.auth.login.config"; + public static final String JAAS_CONF_PROPERTY = "java.security.auth.login.config"; public static final String DEFAULT_JAAS_CONF = "conf/jaas.conf"; - public static final String DEFAULT_LOGIN_MODULE_NAME = - "com.sun.security.jgss.krb5.accept"; + public static final String DEFAULT_LOGIN_MODULE_NAME = "com.sun.security.jgss.krb5.accept"; // Cookie name for single sign on support - public static final String SINGLE_SIGN_ON_COOKIE = - System.getProperty( - "org.apache.catalina.authenticator.Constants.SSO_SESSION_COOKIE_NAME", - "JSESSIONIDSSO"); + public static final String SINGLE_SIGN_ON_COOKIE = System.getProperty( + "org.apache.catalina.authenticator.Constants.SSO_SESSION_COOKIE_NAME", "JSESSIONIDSSO"); // --------------------------------------------------------- Request Notes @@ -52,16 +45,18 @@ * The notes key to track the single-sign-on identity with which this * request is associated. */ - public static final String REQ_SSOID_NOTE = - "org.apache.catalina.request.SSOID"; - + public static final String REQ_SSOID_NOTE = "org.apache.catalina.request.SSOID"; - public static final String REQ_JASPIC_SUBJECT_NOTE = - "org.apache.catalina.authenticator.jaspic.SUBJECT"; + public static final String REQ_JASPIC_SUBJECT_NOTE = "org.apache.catalina.authenticator.jaspic.SUBJECT"; // ---------------------------------------------------------- Session Notes + /** + * The session id used as a CSRF marker when redirecting a user's request. + */ + public static final String SESSION_ID_NOTE = "org.apache.catalina.authenticator.SESSION_ID"; + /** * If the cache property of our authenticator is set, and @@ -70,19 +65,15 @@ * Realm.authenticate(), under the following keys: */ - /** * The notes key for the password used to authenticate this user. */ - public static final String SESS_PASSWORD_NOTE = - "org.apache.catalina.session.PASSWORD"; - + public static final String SESS_PASSWORD_NOTE = "org.apache.catalina.session.PASSWORD"; /** * The notes key for the username used to authenticate this user. */ - public static final String SESS_USERNAME_NOTE = - "org.apache.catalina.session.USERNAME"; + public static final String SESS_USERNAME_NOTE = "org.apache.catalina.session.USERNAME"; /** @@ -90,20 +81,17 @@ * cache required information prior to the completion of authentication. */ - /** * The previously authenticated principal (if caching is disabled). + * + * @deprecated Unused. Will be removed in Tomcat 10. */ - public static final String FORM_PRINCIPAL_NOTE = - "org.apache.catalina.authenticator.PRINCIPAL"; - + @Deprecated + public static final String FORM_PRINCIPAL_NOTE = "org.apache.catalina.authenticator.PRINCIPAL"; /** * The original request information, to which the user will be * redirected if authentication succeeds. */ - public static final String FORM_REQUEST_NOTE = - "org.apache.catalina.authenticator.REQUEST"; - - + public static final String FORM_REQUEST_NOTE = "org.apache.catalina.authenticator.REQUEST"; } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/authenticator/FormAuthenticator.java tomcat9-9.0.31/java/org/apache/catalina/authenticator/FormAuthenticator.java --- tomcat9-9.0.27/java/org/apache/catalina/authenticator/FormAuthenticator.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/authenticator/FormAuthenticator.java 2020-02-05 19:26:48.000000000 +0000 @@ -28,7 +28,6 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.catalina.Manager; import org.apache.catalina.Realm; import org.apache.catalina.Session; import org.apache.catalina.connector.Request; @@ -133,10 +132,6 @@ protected boolean doAuthenticate(Request request, HttpServletResponse response) throws IOException { - if (checkForCachedAuthentication(request, response, true)) { - return true; - } - // References to objects we will need later Session session = null; Principal principal = null; @@ -147,22 +142,16 @@ if (log.isDebugEnabled()) { log.debug("Checking for reauthenticate in session " + session); } - String username = - (String) session.getNote(Constants.SESS_USERNAME_NOTE); - String password = - (String) session.getNote(Constants.SESS_PASSWORD_NOTE); - if ((username != null) && (password != null)) { + String username = (String) session.getNote(Constants.SESS_USERNAME_NOTE); + String password = (String) session.getNote(Constants.SESS_PASSWORD_NOTE); + if (username != null && password != null) { if (log.isDebugEnabled()) { log.debug("Reauthenticating username '" + username + "'"); } - principal = - context.getRealm().authenticate(username, password); + principal = context.getRealm().authenticate(username, password); if (principal != null) { - session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal); + register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password); if (!matchRequest(request)) { - register(request, response, principal, - HttpServletRequest.FORM_AUTH, - username, password); return true; } } @@ -177,20 +166,7 @@ if (matchRequest(request)) { session = request.getSessionInternal(true); if (log.isDebugEnabled()) { - log.debug("Restore request from session '" - + session.getIdInternal() - + "'"); - } - principal = (Principal) - session.getNote(Constants.FORM_PRINCIPAL_NOTE); - register(request, response, principal, HttpServletRequest.FORM_AUTH, - (String) session.getNote(Constants.SESS_USERNAME_NOTE), - (String) session.getNote(Constants.SESS_PASSWORD_NOTE)); - // If we're caching principals we no longer need the username - // and password in the session, so remove them - if (cache) { - session.removeNote(Constants.SESS_USERNAME_NOTE); - session.removeNote(Constants.SESS_PASSWORD_NOTE); + log.debug("Restore request from session '" + session.getIdInternal() + "'"); } if (restoreRequest(request, session)) { if (log.isDebugEnabled()) { @@ -206,14 +182,18 @@ } } + // This check has to be after the previous check for a matching request + // because that matching request may also include a cached Principal. + if (checkForCachedAuthentication(request, response, true)) { + return true; + } + // Acquire references to objects we will need to evaluate String contextPath = request.getContextPath(); String requestURI = request.getDecodedRequestURI(); // Is this the action request from the login page? - boolean loginAction = - requestURI.startsWith(contextPath) && - requestURI.endsWith(Constants.FORM_ACTION); + boolean loginAction = requestURI.startsWith(contextPath) && requestURI.endsWith(Constants.FORM_ACTION); LoginConfig config = context.getLoginConfig(); @@ -241,8 +221,7 @@ saveRequest(request, session); } catch (IOException ioe) { log.debug("Request body too big to save during authentication"); - response.sendError(HttpServletResponse.SC_FORBIDDEN, - sm.getString("authenticator.requestBodyTooBig")); + response.sendError(HttpServletResponse.SC_FORBIDDEN, sm.getString("authenticator.requestBodyTooBig")); return false; } forwardToLoginPage(request, response, config); @@ -274,14 +253,21 @@ if (session == null) { session = request.getSessionInternal(false); } + if (session != null && getChangeSessionIdOnAuthentication()) { + // Does session id match? + String expectedSessionId = (String) session.getNote(Constants.SESSION_ID_NOTE); + if (expectedSessionId == null || !expectedSessionId.equals(request.getRequestedSessionId())) { + session.expire(); + session = null; + } + } if (session == null) { if (containerLog.isDebugEnabled()) { - containerLog.debug - ("User took so long to log on the session expired"); + containerLog.debug("User took so long to log on the session expired"); } if (landingPage == null) { - response.sendError(HttpServletResponse.SC_REQUEST_TIMEOUT, - sm.getString("authenticator.sessionExpired")); + response.sendError( + HttpServletResponse.SC_REQUEST_TIMEOUT, sm.getString("authenticator.sessionExpired")); } else { // Make the authenticator think the user originally requested // the landing page @@ -290,19 +276,13 @@ saved.setMethod("GET"); saved.setRequestURI(uri); saved.setDecodedRequestURI(uri); - request.getSessionInternal(true).setNote( - Constants.FORM_REQUEST_NOTE, saved); + request.getSessionInternal(true).setNote(Constants.FORM_REQUEST_NOTE, saved); response.sendRedirect(response.encodeRedirectURL(uri)); } return false; } - // Save the authenticated Principal in our session - session.setNote(Constants.FORM_PRINCIPAL_NOTE, principal); - - // Save the username and password as well - session.setNote(Constants.SESS_USERNAME_NOTE, username); - session.setNote(Constants.SESS_PASSWORD_NOTE, password); + register(request, response, principal, HttpServletRequest.FORM_AUTH, username, password); // Redirect the user to the original request URI (which will cause // the original request to be restored) @@ -312,8 +292,7 @@ } if (requestURI == null) { if (landingPage == null) { - response.sendError(HttpServletResponse.SC_BAD_REQUEST, - sm.getString("authenticator.formlogin")); + response.sendError(HttpServletResponse.SC_BAD_REQUEST, sm.getString("authenticator.formlogin")); } else { // Make the authenticator think the user originally requested // the landing page @@ -331,15 +310,12 @@ Response internalResponse = request.getResponse(); String location = response.encodeRedirectURL(requestURI); if ("HTTP/1.1".equals(request.getProtocol())) { - internalResponse.sendRedirect(location, - HttpServletResponse.SC_SEE_OTHER); + internalResponse.sendRedirect(location, HttpServletResponse.SC_SEE_OTHER); } else { - internalResponse.sendRedirect(location, - HttpServletResponse.SC_FOUND); + internalResponse.sendRedirect(location, HttpServletResponse.SC_FOUND); } } return false; - } @@ -380,6 +356,33 @@ } + @Override + protected void register(Request request, HttpServletResponse response, + Principal principal, String authType, String username, + String password, boolean alwaysUseSession, boolean cache) { + + super.register(request, response, principal, authType, username, password, alwaysUseSession, cache); + + // If caching an authenticated Principal is turned off, + // store username and password as session notes to use them for re-authentication. + if (!cache) { + Session session = request.getSessionInternal(false); + if (session != null) { + if (username != null) { + session.setNote(Constants.SESS_USERNAME_NOTE, username); + } else { + session.removeNote(Constants.SESS_USERNAME_NOTE); + } + if (password != null) { + session.setNote(Constants.SESS_PASSWORD_NOTE, password); + } else { + session.removeNote(Constants.SESS_PASSWORD_NOTE); + } + } + } + } + + /** * Called to forward to the login page * @@ -414,9 +417,8 @@ if (getChangeSessionIdOnAuthentication()) { Session session = request.getSessionInternal(false); if (session != null) { - Manager manager = request.getContext().getManager(); - manager.changeSessionId(session); - request.changeSessionId(session.getId()); + String newSessionId = changeSessionID(request, session); + session.setNote(Constants.SESSION_ID_NOTE, newSessionId); } } @@ -503,17 +505,24 @@ } // Is there a saved request? - SavedRequest sreq = - (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); + SavedRequest sreq = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); if (sreq == null) { return false; } // Is there a saved principal? - if (session.getNote(Constants.FORM_PRINCIPAL_NOTE) == null) { + if (cache && session.getPrincipal() == null || !cache && request.getPrincipal() == null) { return false; } + // Does session id match? + if (getChangeSessionIdOnAuthentication()) { + String expectedSessionId = (String) session.getNote(Constants.SESSION_ID_NOTE); + if (expectedSessionId == null || !expectedSessionId.equals(request.getRequestedSessionId())) { + return false; + } + } + // Does the request URI match? String decodedRequestURI = request.getDecodedRequestURI(); if (decodedRequestURI == null) { @@ -538,10 +547,9 @@ throws IOException { // Retrieve and remove the SavedRequest object from our session - SavedRequest saved = (SavedRequest) - session.getNote(Constants.FORM_REQUEST_NOTE); + SavedRequest saved = (SavedRequest) session.getNote(Constants.FORM_REQUEST_NOTE); session.removeNote(Constants.FORM_REQUEST_NOTE); - session.removeNote(Constants.FORM_PRINCIPAL_NOTE); + session.removeNote(Constants.SESSION_ID_NOTE); if (saved == null) { return false; } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/authenticator/jaspic/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/authenticator/jaspic/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/authenticator/jaspic/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/authenticator/jaspic/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +authConfigFactoryImpl.load=从[{0}]加载持久化提供者注册信息 authConfigFactoryImpl.zeroLengthAppContext=:)应用上下文名称的长度为0是无效的 authConfigFactoryImpl.zeroLengthMessageLayer=零长度的消息层名称是无效的 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/authenticator/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/authenticator/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/authenticator/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/authenticator/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -34,13 +34,20 @@ digestAuthenticator.cacheRemove=已从客户端 nonce 缓存中删除有效条目,以便为新条目腾出空间。重播攻击现在是可能的。为防止重播攻击的可能性,请降低nonceValidity或增加nonceCacheSize。此类型的进一步警告将被抑制5分钟。 +formAuthenticator.noErrorPage=没有为上下文[{0}]中的表单身份验证定义错误页 + +singleSignOn.debug.associate=SSO将应用程序会话[{1}]与SSO会话[{0}]关联 singleSignOn.debug.cookieCheck=SSO检查SSO cookie singleSignOn.debug.cookieNotFound=SSO没有找到SSO cookie +singleSignOn.debug.deregisterFail=SSO撤销登记SSO会话[{0}]失败,因为缓存中不包含这个SSO会话 +singleSignOn.debug.hasPrincipal=找到以前经过身份验证的主体[{0}] +singleSignOn.debug.invoke=SSO为[{0}]处理请求 singleSignOn.debug.principalCheck=SSO为SSO会话[{0}]寻找缓存的Principal singleSignOn.debug.principalFound=SSO 找到了带着认证类型的缓存代理 singleSignOn.debug.removeSession=SSO 从 SSO session [{1}] 中删除应用程序会话 [{0}] singleSignOn.debug.update=SSO 更新SSO 会话[{0}] 对认证 类型[{1}] singleSignOn.sessionExpire.hostNotFound=因为 Host 丢失,SSO 无法使 session [{0}] 失效 singleSignOn.sessionExpire.managerError=由于会话管理器在检索会话时抛出异常,导致单点登录无法使会话[{0}]失效 +singleSignOn.sessionExpire.managerNotFound=SSO无法使会话[{0}]过期,因为找不到管理器 spnegoAuthenticator.authHeaderNoToken=客户端发送的协商授权 header 未包含 token diff -Nru tomcat9-9.0.27/java/org/apache/catalina/connector/Connector.java tomcat9-9.0.31/java/org/apache/catalina/connector/Connector.java --- tomcat9-9.0.27/java/org/apache/catalina/connector/Connector.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/connector/Connector.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,7 @@ import javax.management.ObjectName; +import org.apache.catalina.Globals; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.Service; @@ -165,6 +166,16 @@ /** + * The flag that controls recycling of the facades of the request + * processing objects. If set to true the object facades + * will be discarded when the request is recycled. If the security + * manager is enabled, this setting is ignored and object facades are + * always discarded. + */ + protected boolean discardFacades = RECYCLE_FACADES; + + + /** * The redirect port for non-SSL to SSL redirects. */ protected int redirectPort = 443; @@ -372,6 +383,25 @@ } + /** + * @return true if the object facades are discarded, either + * when the discardFacades value is true or when the + * security manager is enabled. + */ + public boolean getDiscardFacades() { + return discardFacades || Globals.IS_SECURITY_ENABLED; + } + + + /** + * Set the recycling strategy for the object facades. + * @param discardFacades the new value of the flag + */ + public void setDiscardFacades(boolean discardFacades) { + this.discardFacades = discardFacades; + } + + /** * @return the "enable DNS lookups" flag. */ diff -Nru tomcat9-9.0.27/java/org/apache/catalina/connector/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/connector/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/connector/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/connector/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +coyoteAdapter.authorize=(:使用Tomcat的领域授权用户[{0}] coyoteAdapter.checkRecycled.response=遇到非回收的相应并强行回收。 coyoteAdapter.debug=变量[{0}]的值为[{1}]。 @@ -23,20 +24,28 @@ coyoteConnector.protocolHandlerNoAprLibrary=配置的协议[{0}]需要不可用的APR/本机库 coyoteConnector.protocolHandlerNoAprListener=配置的协议[{0}]需要不可用的aprlifecycleListener coyoteConnector.protocolHandlerPauseFailed=协议处理程序暂停失败 +coyoteConnector.protocolHandlerStartFailed=协议处理器启动失败 coyoteConnector.protocolHandlerStopFailed=协议处理程序.停止失败 coyoteInputStream.nbNotready=在非阻塞模式下,只有之前的读数据完成,并且isReady()方法返回true,你才可以使用 ServletInputStream 读取数据 +coyoteRequest.attributeEvent=属性事件侦听器引发的异常 +coyoteRequest.authenticate.ise=):提交响应后无法调用authenticate() coyoteRequest.changeSessionId=无法更改 session ID。 没有与此请求关联的 session。 coyoteRequest.chunkedPostTooLarge=由于请求参数数据太大,导致参数不能解析。因为当前请求是块状请求,后续也不会处理。如果应用程序需要接收大的POST请求,可以使用连接器的maxPostSize解决它。 coyoteRequest.filterAsyncSupportUnknown=无法确定是否有任何过滤器不支持异步处理 coyoteRequest.gssLifetimeFail=为用户主体 [{0}] 获取剩余生命期失败 +coyoteRequest.maxPostSizeExceeded=):大多部分请求包含的参数数据(不包括上载的文件)超过了关联连接器上设置的maxPostSize 的限制 coyoteRequest.noMultipartConfig=由于没有提供multi-part配置,无法处理parts coyoteRequest.sendfileNotCanonical=无法确定指定用于sendfile的文件[{0}]的规范名称 +coyoteRequest.sessionCreateCommitted=提交响应后无法创建会话 coyoteRequest.sessionEndAccessFail=在回收请求时,异常触发了对会话的结束访问。 coyoteRequest.setAttribute.namenull=不能在一个空的名字上调用setAttribute +coyoteRequest.uploadLocationInvalid=临时上传路径[{0}]无效 coyoteResponse.encoding.invalid=JRE无法识别编码[{0}] +coyoteResponse.getWriter.ise=当前响应已经调用了方法getOutputStream() +coyoteResponse.reset.ise=已经提交响应后无法调用reset() coyoteResponse.sendError.ise=响应提交后无法调用sendError() coyoteResponse.sendRedirect.note=

重定向到{0}

diff -Nru tomcat9-9.0.27/java/org/apache/catalina/connector/mbeans-descriptors.xml tomcat9-9.0.31/java/org/apache/catalina/connector/mbeans-descriptors.xml --- tomcat9-9.0.27/java/org/apache/catalina/connector/mbeans-descriptors.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/connector/mbeans-descriptors.xml 2020-02-05 19:26:48.000000000 +0000 @@ -44,6 +44,10 @@ description="Send AJP flush package for each explicit flush" type="boolean"/> + + @@ -83,7 +87,7 @@ + + diff -Nru tomcat9-9.0.27/java/org/apache/catalina/connector/Request.java tomcat9-9.0.31/java/org/apache/catalina/connector/Request.java --- tomcat9-9.0.27/java/org/apache/catalina/connector/Request.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/connector/Request.java 2020-02-05 19:26:48.000000000 +0000 @@ -105,10 +105,10 @@ import org.apache.tomcat.util.http.ServerCookie; import org.apache.tomcat.util.http.ServerCookies; import org.apache.tomcat.util.http.fileupload.FileItem; -import org.apache.tomcat.util.http.fileupload.FileUploadBase; -import org.apache.tomcat.util.http.fileupload.FileUploadBase.InvalidContentTypeException; import org.apache.tomcat.util.http.fileupload.FileUploadException; import org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory; +import org.apache.tomcat.util.http.fileupload.impl.InvalidContentTypeException; +import org.apache.tomcat.util.http.fileupload.impl.SizeException; import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload; import org.apache.tomcat.util.http.fileupload.servlet.ServletRequestContext; import org.apache.tomcat.util.http.parser.AcceptLanguage; @@ -495,7 +495,7 @@ recycleSessionInfo(); recycleCookieInfo(false); - if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) { + if (getDiscardFacades()) { parameterMap = new ParameterMap<>(); } else { parameterMap.setLocked(false); @@ -506,7 +506,7 @@ applicationMapping.recycle(); applicationRequest = null; - if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) { + if (getDiscardFacades()) { if (facade != null) { facade.clear(); facade = null; @@ -586,6 +586,16 @@ /** + * Get the recycling strategy of the facade objects. + * @return the value of the flag as set on the connector, or + * true if no connector is associated with this request + */ + public boolean getDiscardFacades() { + return (connector == null) ? true : connector.getDiscardFacades(); + } + + + /** * Filter chain associated with the request. */ protected FilterChain filterChain = null; @@ -2658,13 +2668,6 @@ } - /** - * Changes the session ID of the session associated with this request. - * - * @return the old session ID before it was changed - * @see javax.servlet.http.HttpSessionIdListener - * @since Servlet 3.1 - */ @Override public String changeSessionId() { @@ -2675,9 +2678,8 @@ } Manager manager = this.getContext().getManager(); - manager.changeSessionId(session); - String newSessionId = session.getId(); + String newSessionId = manager.rotateSessionId(session); this.changeSessionId(newSessionId); return newSessionId; @@ -2906,7 +2908,7 @@ } catch (InvalidContentTypeException e) { parameters.setParseFailedReason(FailReason.INVALID_CONTENT_TYPE); partsParseException = new ServletException(e); - } catch (FileUploadBase.SizeException e) { + } catch (SizeException e) { parameters.setParseFailedReason(FailReason.POST_TOO_LARGE); checkSwallowInput(); partsParseException = new IllegalStateException(e); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/connector/Response.java tomcat9-9.0.31/java/org/apache/catalina/connector/Response.java --- tomcat9-9.0.27/java/org/apache/catalina/connector/Response.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/connector/Response.java 2020-02-05 19:26:48.000000000 +0000 @@ -44,7 +44,6 @@ import javax.servlet.http.HttpServletResponseWrapper; import org.apache.catalina.Context; -import org.apache.catalina.Globals; import org.apache.catalina.Session; import org.apache.catalina.security.SecurityUtil; import org.apache.catalina.util.SessionConfig; @@ -229,7 +228,7 @@ isCharacterEncodingSet = false; applicationResponse = null; - if (Globals.IS_SECURITY_ENABLED || Connector.RECYCLE_FACADES) { + if (getRequest().getDiscardFacades()) { if (facade != null) { facade.clear(); facade = null; diff -Nru tomcat9-9.0.27/java/org/apache/catalina/Context.java tomcat9-9.0.31/java/org/apache/catalina/Context.java --- tomcat9-9.0.27/java/org/apache/catalina/Context.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/Context.java 2020-02-05 19:26:48.000000000 +0000 @@ -1393,6 +1393,7 @@ */ public JspConfigDescriptor getJspConfigDescriptor(); + /** * Set the JspConfigDescriptor for this context. * A null value indicates there is not JSP configuration. @@ -1401,6 +1402,7 @@ */ public void setJspConfigDescriptor(JspConfigDescriptor descriptor); + /** * Add a ServletContainerInitializer instance to this web application. * @@ -1411,6 +1413,7 @@ public void addServletContainerInitializer( ServletContainerInitializer sci, Set> classes); + /** * Is this Context paused whilst it is reloaded? * @@ -1426,6 +1429,7 @@ */ boolean isServlet22(); + /** * Notification that Servlet security has been dynamically set in a * {@link javax.servlet.ServletRegistration.Dynamic} @@ -1481,7 +1485,7 @@ /** * @return The version of this web application, used to differentiate * different versions of the same web application when using parallel - * deployment. + * deployment. If not specified, defaults to the empty string. */ public String getWebappVersion(); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/core/ApplicationContext.java tomcat9-9.0.31/java/org/apache/catalina/core/ApplicationContext.java --- tomcat9-9.0.27/java/org/apache/catalina/core/ApplicationContext.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/core/ApplicationContext.java 2020-02-05 19:26:48.000000000 +0000 @@ -1013,7 +1013,7 @@ Connector[] connectors = service.findConnectors(); // Need at least one SSL enabled connector to use the SSL session ID. for (Connector connector : connectors) { - if (Boolean.TRUE.equals(connector.getAttribute("SSLEnabled"))) { + if (Boolean.TRUE.equals(connector.getProperty("SSLEnabled"))) { supportedSessionTrackingModes.add(SessionTrackingMode.SSL); break; } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/core/AsyncContextImpl.java tomcat9-9.0.31/java/org/apache/catalina/core/AsyncContextImpl.java --- tomcat9-9.0.27/java/org/apache/catalina/core/AsyncContextImpl.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/core/AsyncContextImpl.java 2020-02-05 19:26:48.000000000 +0000 @@ -94,6 +94,9 @@ @Override public void fireOnComplete() { + if (log.isDebugEnabled()) { + log.debug(sm.getString("asyncContextImpl.fireOnComplete")); + } List listenersCopy = new ArrayList<>(); listenersCopy.addAll(listeners); @@ -124,6 +127,9 @@ Context context = this.context; if (result.get()) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("asyncContextImpl.fireOnTimeout")); + } ClassLoader oldCL = context.bind(false, null); try { List listenersCopy = new ArrayList<>(); @@ -327,6 +333,9 @@ List listenersCopy = new ArrayList<>(); listenersCopy.addAll(listeners); listeners.clear(); + if (log.isDebugEnabled()) { + log.debug(sm.getString("asyncContextImpl.fireOnStartAsync")); + } for (AsyncListenerWrapper listener : listenersCopy) { try { listener.fireOnStartAsync(event); @@ -401,6 +410,9 @@ request.getCoyoteRequest().action(ActionCode.ASYNC_ERROR, null); if (fireOnError) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("asyncContextImpl.fireOnError")); + } AsyncEvent errorEvent = new AsyncEvent(event.getAsyncContext(), event.getSuppliedRequest(), event.getSuppliedResponse(), t); List listenersCopy = new ArrayList<>(); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings_fr.properties tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings_fr.properties --- tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings_fr.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings_fr.properties 2020-02-05 19:26:48.000000000 +0000 @@ -94,6 +94,10 @@ asyncContextImpl.asyncDispachError=Erreur lors d'un dispatch asynchrone asyncContextImpl.asyncRunnableError=Erreur lors du traitement asynchrone du Runnable via AsyncContext.start() asyncContextImpl.dispatchingStarted=Une opération de dispatch asynchrone a déjà été appelée, plusieurs dispatch au cours d'un même cycle asynchrone n'est pas autorisé +asyncContextImpl.fireOnComplete=Déclenchement de l'évènement onComplete() sur tous les AsyncListeners +asyncContextImpl.fireOnError=Déclenchement de l'évènement onError() sur tous les AsyncListeners +asyncContextImpl.fireOnStartAsync=Déclenchement de l'évènement onStartAsync() sur tous les AsyncListeners +asyncContextImpl.fireOnTimeout=Déclenchement de l'évènement onTimeout() sur tous les AsyncListeners asyncContextImpl.noAsyncDispatcher=Le Servlet dispatcher retourné par le ServletContext ne supporte pas de dispatch asynchrone asyncContextImpl.onCompleteError=L''appel à onComplete() a échoué pour l''écouteur de type [{0}] asyncContextImpl.onErrorError=L''appel à onError() a échoué pour l''écouteur de type [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings_ko.properties tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings_ko.properties --- tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings_ko.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings_ko.properties 2020-02-05 19:26:48.000000000 +0000 @@ -94,6 +94,10 @@ asyncContextImpl.asyncDispachError=비동기 디스패치 도중 오류 발생 asyncContextImpl.asyncRunnableError=AsyncContext.start()를 통해, 비동기로 Runnable을 처리하는 도중 오류 발생 asyncContextImpl.dispatchingStarted=비동기 디스패치 오퍼레이션이 이미 호출되었습니다. 동일한 비동기 사이클 내에서, 추가적인 비동기 디스패치 오퍼레이션은 허용되지 않습니다. +asyncContextImpl.fireOnComplete=등록된 AsyncListener들에 onComplete() 이벤트를 호출합니다. +asyncContextImpl.fireOnError=등록된 AsyncListener들에 onError() 이벤트를 호출합니다. +asyncContextImpl.fireOnStartAsync=등록된 AsyncListener들에 onStartAsync() 이벤트를 호출합니다. +asyncContextImpl.fireOnTimeout=등록된 AsyncListener들에 onTimeout() 이벤트를 호출합니다. asyncContextImpl.noAsyncDispatcher=ServletContext로부터 반환된 디스패처는 비동기 디스패치를 지원하지 않습니다. asyncContextImpl.onCompleteError=타입 [{0}]의 리스너를 위한 onComplete() 호출이 실패했습니다. asyncContextImpl.onErrorError=타입 [{0}]의 리스너를 위한 onError() 호출이 실패했습니다. diff -Nru tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings.properties tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -94,6 +94,10 @@ asyncContextImpl.asyncDispachError=Error during asynchronous dispatch asyncContextImpl.asyncRunnableError=Error during processing of asynchronous Runnable via AsyncContext.start() asyncContextImpl.dispatchingStarted=Asynchronous dispatch operation has already been called. Additional asynchronous dispatch operation within the same asynchronous cycle is not allowed. +asyncContextImpl.fireOnComplete=Firing onComplete() event for any AsyncListeners +asyncContextImpl.fireOnError=Firing onError() event for any AsyncListeners +asyncContextImpl.fireOnStartAsync=Firing onStartAsync() event for any AsyncListeners +asyncContextImpl.fireOnTimeout=Firing onTimeout() event for any AsyncListeners asyncContextImpl.noAsyncDispatcher=The dispatcher returned from the ServletContext does not support asynchronous dispatching asyncContextImpl.onCompleteError=onComplete() call failed for listener of type [{0}] asyncContextImpl.onErrorError=onError() call failed for listener of type [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/core/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/core/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -17,33 +17,46 @@ applicationContext.addListener.iae.cnfe=无法创建类型为 [{0}] 的实例 applicationContext.addListener.iae.wrongType=指定的类型[{0}]不是预期的侦听器类型之一 applicationContext.addRole.ise=上下文被初始化后,角色不能再被添加到[{0}]中 +applicationContext.addServlet.ise=无法将servlet添加到上下文[{0}]中,因为该上下文已初始化 applicationContext.attributeEvent=属性事件监听器引发的异常 +applicationContext.invalidFilterName=由于筛选器名称[{0}]无效,无法添加筛选器定义。 applicationContext.invalidServletName=由于servlet名称[{0}]无效,无法添加对应servlet的定义。 applicationContext.mapping.error=映射中.的错误 +applicationContext.resourcePaths.iae=路径[{0}]不以“/”字符开头 applicationContext.setAttribute.namenull=Name 不能为 null +applicationContext.setSessionTimeout.ise=(:无法为上下文[{0}]设置会话超时,因为该上下文已初始化 applicationContext.setSessionTracking.iae.ssl=为上下文 [{0}] 请求的 session 跟踪模式包括 SSL 和至少一种其他模式。 SSL可能未配置其他模式。 applicationContext.setSessionTracking.ise=当上下文正在运行,无法设置上下文[{0}]的会话跟踪模式 +applicationDispatcher.serviceException=Servlet[{0}]的Servlet.service()抛出异常 applicationDispatcher.specViolation.response=原始的ServletResponse或包装后的ServletResponse未传递给RequestDispatcher,原因:违反了SRV.8.2和SRV.14.2.5.1\n\ \n +applicationFilterConfig.preDestroy=):为类型为[{1}]的名为[{0}]的筛选器调用preDestroy 失败 applicationFilterConfig.release=失败的销毁过滤器类型为[{1}]名称为[{0}] applicationFilterRegistration.nullInitParams=由于name和(或)value为null,无法为过滤器设置初始化参数。name为 [{0}],value为 [{1}] applicationPushBuilder.methodNotToken=HTTP方法必须是令牌(token),但 [{0}] 包含非令牌字符 +applicationServletRegistration.setServletSecurity.iae=为部署到名为[{1}]的上下文的Servlet[{0}]指定的空约束 + aprListener.aprInitError=基于APR的本地库加载失败.错误报告为[{0}] +aprListener.config=APR/OpenSSL配置:useAprConnector[{0}],useOpenSSL[{1}] +aprListener.enterAlreadyInFIPSMode=AprLifecycleListener 配置为强制进入FIPS模式,但库已处于FIPS模式[{0}] aprListener.initializeFIPSSuccess=成功的进入FIPS 模式 aprListener.initializingFIPS=初始化FIPS模式... aprListener.tcnVersion=安装了基于APR的Apache Tomcat Native库的旧版本[{0}],而Tomcat建议使用[{1}]的最低版本 aprListener.tooLateForFIPSMode=无法设置FIPSMode:SSL已初始化 aprListener.tooLateForSSLRandomSeed=无法设置 SSLRandomSeed:SSL已经初始化 +asyncContextImpl.noAsyncDispatcher=从ServletContext 返回的调度程序不支持异步调度 +asyncContextImpl.request.ise=调用方法complete()后或者任意一个dispatch()方法调用后,调用getRequest()方法是非法的 asyncContextImpl.requestEnded=AsyncContext关联的请求已经完成处理。 containerBase.backgroundProcess.cluster=异常处理集群[{0}]后台进程 containerBase.backgroundProcess.error=处理后台线程异常 +containerBase.backgroundProcess.valve=处理阀门[{0}]后台进程异常 containerBase.nullName=容器名称不能为null containerBase.threadedStartFailed=子容器启动失败 @@ -54,19 +67,26 @@ filterChain.filter=Filter 执行抛出一个异常 +jreLeakListener.classToInitializeFail=在tomcat启动期间未能加载类[{0}],以防止可能的内存泄漏。 + naming.addEnvEntry=添加环境条目 [{0}] naming.addResourceEnvRef=添加资源环境引用 [{0}] naming.invalidEnvEntryType=环境条目[{0}]没有一个有效哦的类型 naming.jmxRegistrationFailed=注册到JMX失败:[{0}] naming.namingContextCreationFailed=创建上下文名称失败 +naming.unbindFailed=解绑对象[{0}]失败 naming.wsdlFailed=未找到 wsdl 文件:[{0}] standardContext.applicationListener=配置应用程序监听器[{0}]错误 +standardContext.applicationSkipped=由于以前的错误,已跳过安装应用程序侦听器 +standardContext.backgroundProcess.loader=异常处理加载程序[{0}]后台进程 standardContext.backgroundProcess.resources=异常处理资源[{0}] 后台进程 standardContext.cookieProcessor.null=不允许将上下文的CookieProcessor 设置为null +standardContext.duplicateListener=当前上下文已经配置了监听器[{0}],重复的定义将被忽略。 standardContext.errorPage.required=ErrorPage不能为null standardContext.errorPage.warning=警告:在Servlet 2.4中,错误页位置 [{0}] 必须以"/"开头 standardContext.extensionValidationError=尝试校验必需的应用程序扩展时发生错误 +standardContext.filterFail=一个或多个筛选器启动失败。完整的详细信息将在相应的容器日志文件中找到 standardContext.filterMap.either=过滤器映射必须指定 standardContext.filterMap.name=Filter mapping 指定了一个未知的 filter名称 [{0}] standardContext.filterMap.pattern=过滤器映射中的 [{0}] 无效 @@ -74,9 +94,11 @@ standardContext.invalidWrapperClass=[{0}] 不是StandardWrapper的子类 standardContext.isUnavailable=此应用程序目前不可用 standardContext.listenerStart=异常将上下文初始化事件发送到类的侦听器实例.[{0}] +standardContext.listenerStop=例外情况发送上下文删除事件[{0}],以便列表实例 standardContext.loginConfig.errorPage=表单错误页[{0}]必须以"/"开始 standardContext.loginConfig.errorWarning=警告:Servlet 2.4中,表单错误页[{0}]必须以"/"开始 standardContext.loginConfig.loginPage=表单登录页面 [{0}] 必须以''/''开头 +standardContext.loginConfig.loginWarning=警告:在Servlet 2.4 中 Form 登录页[{0}] 必须以 "/" 开头 standardContext.manager=配置类为[{0}]的管理器 standardContext.managerFail=会话管理器无法启动 standardContext.namingResource.init.fail=未能初始化新的命名资源 @@ -92,24 +114,35 @@ standardContext.startingContext=启动Context[{0}]出现异常 standardContext.stop.asyncWaitInterrupted=等待卸载延迟毫秒以完成飞行中的异步请求时收到中断。上下文停止将继续,不会有进一步的延迟。 standardContext.stoppingContext=异常停止的上下文使用名为[{0}] +standardContext.threadBindingListenerError=上下文[{0}]配置的线程绑定监听器发生错误 +standardContext.urlPattern.patternWarning=警告:在Servlet 2.4中,URL模式[{0}]必须以“/”开头 +standardContext.workCreateException=无法从目录[{0}]和catalina_home[{1}]中为上下文[{2}]确定绝对工作目录 standardContext.workCreateFail=无法为上下文[{1}]创建工作目录[{0}] +standardContext.workPath=获取上下文[{0}]的工作路径时发生异常 standardContextValve.acknowledgeException=以100(继续)响应确认请求失败 +standardEngine.jvmRouteFail=无法从系统属性设置引擎的jvmRoute 属性 standardEngine.notParent=引擎不能有父容器 +standardEngine.start=正在启动 Servlet 引擎:[{0}] standardHost.noContext=没有配置上下文来处理此请求 standardHost.notContext=主机的子节点必须有上下文 +standardHost.nullName=主机名是必需的 standardServer.accept.timeout=在调用accept()方法之后,侦听shutdown命令的套接字经历了意外的超时[{0}]毫秒。 这是bug 56684的一个例子? standardServer.storeConfig.notAvailable=没有将StoreConfig实现注册为名为[{0}]的MBean,因此无法保存配置。合适的MBean通常通过StoreConfigLifecycleListener注册。 standardService.engine.stopFailed=失败停止关联的引擎 +standardService.stop.name=正在停止服务[{0}] +standardWrapper.allocate=分配一个servlet实例错误 standardWrapper.destroyInstance=servlet[{0}]实例管理销毁(destroy) 抛出异常 +standardWrapper.instantiate=实例化Servlet类[{0}]异常 standardWrapper.isUnavailable=Servlet [{0}]当前不可用。 standardWrapper.notChild=Wrapper容器内部不允许有子容器。 standardWrapper.notFound=Servlet [{0}] 不可用 +standardWrapper.serviceException=在路径为[{1}]的上下文中,servlet[{0}]的Servlet.service()引发异常 standardWrapper.unloading=无法分配servlet [{0}],因为它没有被加载 threadLocalLeakPreventionListener.containerEvent.error=异常处理容器事件[{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/core/StandardWrapperValve.java tomcat9-9.0.31/java/org/apache/catalina/core/StandardWrapperValve.java --- tomcat9-9.0.27/java/org/apache/catalina/core/StandardWrapperValve.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/core/StandardWrapperValve.java 2020-02-05 19:26:48.000000000 +0000 @@ -288,7 +288,6 @@ container.getLogger().error(sm.getString("standardWrapper.unloadException", wrapper.getName()), e); if (throwable == null) { - throwable = e; exception(request, response, e); } } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java tomcat9-9.0.31/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java --- tomcat9-9.0.27/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/filters/AddDefaultCharsetFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -106,17 +106,17 @@ } @Override - public void setContentType(String ct) { + public void setContentType(String contentType) { - if (ct != null && ct.startsWith("text/")) { - if (!ct.contains("charset=")) { - super.setContentType(ct + ";charset=" + encoding); + if (contentType != null && contentType.startsWith("text/")) { + if (!contentType.contains("charset=")) { + super.setContentType(contentType + ";charset=" + encoding); } else { - super.setContentType(ct); + super.setContentType(contentType); encoding = getCharacterEncoding(); } } else { - super.setContentType(ct); + super.setContentType(contentType); } } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/filters/Constants.java tomcat9-9.0.31/java/org/apache/catalina/filters/Constants.java --- tomcat9-9.0.27/java/org/apache/catalina/filters/Constants.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/filters/Constants.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,12 +25,34 @@ */ public final class Constants { + /** + * The session attribute key under which the CSRF nonce + * cache will be stored. + */ public static final String CSRF_NONCE_SESSION_ATTR_NAME = "org.apache.catalina.filters.CSRF_NONCE"; + /** + * The request attribute key under which the current + * requests's CSRF nonce can be found. + */ + public static final String CSRF_NONCE_REQUEST_ATTR_NAME = + "org.apache.catalina.filters.CSRF_REQUEST_NONCE"; + + /** + * The name of the request parameter which carries CSRF nonces + * from the client to the server for validation. + */ public static final String CSRF_NONCE_REQUEST_PARAM = "org.apache.catalina.filters.CSRF_NONCE"; + /** + * The servlet context attribute key under which the + * CSRF request parameter name can be found. + */ + public static final String CSRF_NONCE_REQUEST_PARAM_NAME_KEY = + "org.apache.catalina.filters.CSRF_NONCE_PARAM_NAME"; + public static final String METHOD_GET = "GET"; public static final String CSRF_REST_NONCE_HEADER_NAME = "X-CSRF-Token"; @@ -39,6 +61,17 @@ public static final String CSRF_REST_NONCE_HEADER_REQUIRED_VALUE = "Required"; + /** + * The session attribute key under which the CSRF REST nonce + * cache will be stored. + */ public static final String CSRF_REST_NONCE_SESSION_ATTR_NAME = "org.apache.catalina.filters.CSRF_REST_NONCE"; + + /** + * The servlet context attribute key under which the + * CSRF REST header name can be found. + */ + public static final String CSRF_REST_NONCE_HEADER_NAME_KEY = + "org.apache.catalina.filters.CSRF_REST_NONCE_HEADER_NAME"; } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/filters/CorsFilter.java tomcat9-9.0.31/java/org/apache/catalina/filters/CorsFilter.java --- tomcat9-9.0.27/java/org/apache/catalina/filters/CorsFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/filters/CorsFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.net.URI; -import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -39,6 +38,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.http.RequestUtil; import org.apache.tomcat.util.http.ResponseUtil; import org.apache.tomcat.util.res.StringManager; @@ -589,9 +589,9 @@ if (originHeader != null) { if (originHeader.isEmpty()) { requestType = CORSRequestType.INVALID_CORS; - } else if (!isValidOrigin(originHeader)) { + } else if (!RequestUtil.isValidOrigin(originHeader)) { requestType = CORSRequestType.INVALID_CORS; - } else if (isLocalOrigin(request, originHeader)) { + } else if (RequestUtil.isSameOrigin(request, originHeader)) { return CORSRequestType.NOT_CORS; } else { String method = request.getMethod(); @@ -634,36 +634,6 @@ } - private boolean isLocalOrigin(HttpServletRequest request, String origin) { - - // Build scheme://host:port from request - StringBuilder target = new StringBuilder(); - String scheme = request.getScheme(); - if (scheme == null) { - return false; - } else { - scheme = scheme.toLowerCase(Locale.ENGLISH); - } - target.append(scheme); - target.append("://"); - - String host = request.getServerName(); - if (host == null) { - return false; - } - target.append(host); - - int port = request.getServerPort(); - if ("http".equals(scheme) && port != 80 || - "https".equals(scheme) && port != 443) { - target.append(':'); - target.append(port); - } - - return origin.equalsIgnoreCase(target.toString()); - } - - /** * Return the lower case, trimmed value of the media type from the content * type. @@ -812,34 +782,13 @@ * @param origin The origin URI * @return true if the origin was valid * @see RFC952 + * + * @deprecated This will be removed in Tomcat 10 + * Use {@link RequestUtil#isValidOrigin(String)} */ + @Deprecated protected static boolean isValidOrigin(String origin) { - // Checks for encoded characters. Helps prevent CRLF injection. - if (origin.contains("%")) { - return false; - } - - // "null" is a valid origin - if ("null".equals(origin)) { - return true; - } - - // RFC6454, section 4. "If uri-scheme is file, the implementation MAY - // return an implementation-defined value.". No limits are placed on - // that value so treat all file URIs as valid origins. - if (origin.startsWith("file://")) { - return true; - } - - URI originURI; - try { - originURI = new URI(origin); - } catch (URISyntaxException e) { - return false; - } - // If scheme for URI is null, return false. Return true otherwise. - return originURI.getScheme() != null; - + return RequestUtil.isValidOrigin(origin); } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/filters/CsrfPreventionFilter.java tomcat9-9.0.31/java/org/apache/catalina/filters/CsrfPreventionFilter.java --- tomcat9-9.0.27/java/org/apache/catalina/filters/CsrfPreventionFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/filters/CsrfPreventionFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -24,6 +24,7 @@ import java.util.Set; import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -32,6 +33,9 @@ import javax.servlet.http.HttpServletResponseWrapper; import javax.servlet.http.HttpSession; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; + /** * Provides basic CSRF protection for a web application. The filter assumes * that: @@ -43,11 +47,14 @@ * */ public class CsrfPreventionFilter extends CsrfPreventionFilterBase { + private final Log log = LogFactory.getLog(CsrfPreventionFilter.class); private final Set entryPoints = new HashSet<>(); private int nonceCacheSize = 5; + private String nonceRequestParameterName = Constants.CSRF_NONCE_REQUEST_PARAM; + /** * Entry points are URLs that will not be tested for the presence of a valid * nonce. They are used to provide a way to navigate back to a protected @@ -78,6 +85,27 @@ this.nonceCacheSize = nonceCacheSize; } + /** + * Sets the request parameter name to use for CSRF nonces. + * + * @param parameterName The request parameter name to use + * for CSRF nonces. + */ + public void setNonceRequestParameterName(String parameterName) { + this.nonceRequestParameterName = parameterName; + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Set the parameters + super.init(filterConfig); + + // Put the expected request parameter name into the application scope + filterConfig.getServletContext().setAttribute( + Constants.CSRF_NONCE_REQUEST_PARAM_NAME_KEY, + nonceRequestParameterName); + } + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { @@ -94,6 +122,10 @@ if (Constants.METHOD_GET.equals(req.getMethod()) && entryPoints.contains(getRequestedPath(req))) { + if(log.isTraceEnabled()) { + log.trace("Skipping CSRF nonce-check for GET request to entry point " + getRequestedPath(req)); + } + skipNonceCheck = true; } @@ -106,18 +138,56 @@ if (!skipNonceCheck) { String previousNonce = - req.getParameter(Constants.CSRF_NONCE_REQUEST_PARAM); + req.getParameter(nonceRequestParameterName); + + if(previousNonce == null) { + if(log.isDebugEnabled()) { + log.debug("Rejecting request for " + getRequestedPath(req) + + ", session " + + (null == session ? "(none)" : session.getId()) + + " with no CSRF nonce found in request"); + } + + res.sendError(getDenyStatus()); + return; + } else if(nonceCache == null) { + if(log.isDebugEnabled()) { + log.debug("Rejecting request for " + getRequestedPath(req) + + ", session " + + (null == session ? "(none)" : session.getId()) + + " due to empty / missing nonce cache"); + } + + res.sendError(getDenyStatus()); + return; + } else if(!nonceCache.contains(previousNonce)) { + if(log.isDebugEnabled()) { + log.debug("Rejecting request for " + getRequestedPath(req) + + ", session " + + (null == session ? "(none)" : session.getId()) + + " due to invalid nonce " + previousNonce); + } - if (nonceCache == null || previousNonce == null || - !nonceCache.contains(previousNonce)) { res.sendError(getDenyStatus()); return; } + if(log.isTraceEnabled()) { + log.trace("Allowing request to " + getRequestedPath(req) + + " with valid CSRF nonce " + previousNonce); + } } if (nonceCache == null) { + if(log.isDebugEnabled()) { + log.debug("Creating new CSRF nonce cache with size=" + nonceCacheSize + " for session " + (null == session ? "(will create)" : session.getId())); + } + nonceCache = new LruCache<>(nonceCacheSize); if (session == null) { + if(log.isDebugEnabled()) { + log.debug("Creating new session to store CSRF nonce cache"); + } + session = req.getSession(true); } session.setAttribute( @@ -128,7 +198,12 @@ nonceCache.add(newNonce); - wResponse = new CsrfResponseWrapper(res, newNonce); + // Take this request's nonce and put it into the request + // attributes so pages can make direct use of it, rather than + // requiring the use of response.encodeURL. + request.setAttribute(Constants.CSRF_NONCE_REQUEST_ATTR_NAME, newNonce); + + wResponse = new CsrfResponseWrapper(res, nonceRequestParameterName, newNonce); } else { wResponse = response; } @@ -140,10 +215,12 @@ protected static class CsrfResponseWrapper extends HttpServletResponseWrapper { + private final String nonceRequestParameterName; private final String nonce; - public CsrfResponseWrapper(HttpServletResponse response, String nonce) { + public CsrfResponseWrapper(HttpServletResponse response, String nonceRequestParameterName, String nonce) { super(response); + this.nonceRequestParameterName = nonceRequestParameterName; this.nonce = nonce; } @@ -198,7 +275,7 @@ } else { sb.append('?'); } - sb.append(Constants.CSRF_NONCE_REQUEST_PARAM); + sb.append(nonceRequestParameterName); sb.append('='); sb.append(nonce); sb.append(anchor); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/filters/ExpiresFilter.java tomcat9-9.0.31/java/org/apache/catalina/filters/ExpiresFilter.java --- tomcat9-9.0.27/java/org/apache/catalina/filters/ExpiresFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/filters/ExpiresFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -40,6 +40,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; +import javax.servlet.http.MappingMatch; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -379,7 +380,7 @@ * {@link #isEligibleToExpirationHeaderGeneration(HttpServletRequest, XHttpServletResponse)} * *
  • - * {@link #getExpirationDate(XHttpServletResponse)}
  • + * {@link #getExpirationDate(HttpServletRequest, XHttpServletResponse)} * *

    Troubleshooting

    *

    @@ -1247,22 +1248,57 @@ return excludedResponseStatusCodes; } + /** - *

    * Returns the expiration date of the given {@link XHttpServletResponse} or * {@code null} if no expiration date has been configured for the * declared content type. - *

    *

    * {@code protected} for extension. - *

    * - * @param response The Servlet response + * @param response The wrapped HTTP response + * * @return the expiration date * @see HttpServletResponse#getContentType() + * + * @deprecated Will be removed in Tomcat 10. + * Use {@link #getExpirationDate(HttpServletRequest, XHttpServletResponse)} */ + @Deprecated protected Date getExpirationDate(XHttpServletResponse response) { + return getExpirationDate((HttpServletRequest) null, response); + } + + + /** + * Returns the expiration date of the given {@link XHttpServletResponse} or + * {@code null} if no expiration date has been configured for the + * declared content type. + *

    + * {@code protected} for extension. + * + * @param request The HTTP request + * @param response The wrapped HTTP response + * + * @return the expiration date + * @see HttpServletResponse#getContentType() + */ + protected Date getExpirationDate(HttpServletRequest request, XHttpServletResponse response) { String contentType = response.getContentType(); + if (contentType == null && request != null && + request.getHttpServletMapping().getMappingMatch() == MappingMatch.DEFAULT && + response.getStatus() == HttpServletResponse.SC_NOT_MODIFIED) { + // Default servlet normally sets the content type but does not for + // 304 responses. Look it up. + String servletPath = request.getServletPath(); + if (servletPath != null) { + int lastSlash = servletPath.lastIndexOf('/'); + if (lastSlash > -1) { + String fileName = servletPath.substring(lastSlash + 1); + contentType = request.getServletContext().getMimeType(fileName); + } + } + } if (contentType != null) { contentType = contentType.toLowerCase(Locale.ENGLISH); } @@ -1485,7 +1521,7 @@ return; } - Date expirationDate = getExpirationDate(response); + Date expirationDate = getExpirationDate(request, response); if (expirationDate == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("expiresFilter.noExpirationConfigured", diff -Nru tomcat9-9.0.27/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/filters/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -17,19 +17,32 @@ corsFilter.nullRequest=HttpServletRequest 对象为空 corsFilter.nullRequestType=CORSRequestType对象为空(null) corsFilter.onlyHttp=CORS不支持非HTTP请求或响应 +corsFilter.wrongType1=期望类型为[{0}]的HttpServletRequest对象 +corsFilter.wrongType2=期望类型为[{0}]或[{1}]的HttpServletRequest对象 -csrfPrevention.invalidRandomClass=不能使用class [{0}]创建随机源. +csrfPrevention.invalidRandomClass=不能使用class [{0}]创建随机源。 +expiresFilter.invalidDurationNumber=指令[{1}]中的持续时间(数字)[{0}]无效 +expiresFilter.noDurationFound=在指令[{0}]中找不到持续时间 expiresFilter.noExpirationConfigured=请求[{0}],其响应状态为[{1}]内容类型[{2}],未配置到期日期 expiresFilter.noExpirationConfiguredForContentType=没有为 content-type [{0}] 找到过期配置 +expiresFilter.responseAlreadyCommited=请求[{0}],无法对已提交的响应应用ExpiresFilter。 expiresFilter.startingPointInvalid=在指令[{1}]中无效的起点(访问|现在|修改|a<秒>|m<秒>)[{0}] +expiresFilter.startingPointNotFound=起始点(access|now|modification|a|m)未在指令[{0}]中找到 expiresFilter.unsupportedStartingPoint=不支持的起始点 [{0}] +expiresFilter.useDefaultConfiguration=对内容类型[{1}]使用默认的[{0}]返回[{2}] expiresFilter.useMatchingConfiguration=对内容类型[{2}]返回[{3}]使用[{0}]匹配[{1}] +filterbase.noSuchProperty=未为[{1}]类型的筛选器定义属性[{0}] + http.403=禁止访问指定资源 [{0}] 。 -httpHeaderSecurityFilter.clickjack.invalid=为防咔嗒顶部标题指定了无效值[{0}] +httpHeaderSecurityFilter.clickjack.invalid=为防点击挟持的响应消息头指定了非法值 [{0}] +httpHeaderSecurityFilter.committed=在进入HttpHeaderSecurityFilter的时候响应消息已经提交导致不能添加响应消息头 + +remoteCidrFilter.noRemoteIp=客户端没有 IP 地址,请求被拒绝。 -remoteCidrFilter.noRemoteIp=客户端没有 IP 地址。请求被拒绝。 +remoteIpFilter.invalidHostHeader=HTTP头 [{1}]中,为Host找到一个无效值 [{0}] +remoteIpFilter.invalidHostWithPort=HTTP头 [{1}]中的Host值 [{0}]包含一个被忽略的端口号 requestFilter.deny=基于属性:[{1}],[{0}]的请求被拒绝。 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java tomcat9-9.0.31/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java --- tomcat9-9.0.27/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/filters/RestCsrfPreventionFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,7 @@ import java.util.regex.Pattern; import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; @@ -89,6 +90,17 @@ private String pathsDelimiter = ","; @Override + public void init(FilterConfig filterConfig) throws ServletException { + // Set the parameters + super.init(filterConfig); + + // Put the expected request header name into the application scope + filterConfig.getServletContext().setAttribute( + Constants.CSRF_REST_NONCE_HEADER_NAME_KEY, + Constants.CSRF_REST_NONCE_HEADER_NAME); + } + + @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ha/backend/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/ha/backend/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/ha/backend/LocalStrings_zh_CN.properties 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ha/backend/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,16 @@ +# 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. + +multiCastSender.sendFailed=无法发送多播信息 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ha/deploy/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/ha/deploy/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/ha/deploy/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ha/deploy/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,7 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +farmWarDeployer.deleteFail=无法删除 [{0}] farmWarDeployer.hostOnly=FarmWarDeployer 只有做为 host cluster 的子元素是才生效 +farmWarDeployer.mbeanNameFail=无法为引擎[{0}]和主机[{1}]构造MBean对象名 farmWarDeployer.modInstall=从 [{1}] 安装 webapp [{0}] farmWarDeployer.modInstallFail=无法安装 WAR 文件 farmWarDeployer.msgIoe=无法读取服务器场部署文件消息。 @@ -22,7 +24,9 @@ farmWarDeployer.removeFailLocal=从[{0}]本地移除失败 farmWarDeployer.removeFailRemote=本地从[{0}]删除失败,其他经理有app在服务! farmWarDeployer.removeLocalFail=无法移除WAR文件 +farmWarDeployer.renameFail=将 [{0}] 重命名为 [{1}] 失败 farmWarDeployer.sendFragment=将群集战争片段路径[{0}],战争[{1}]发送到[{2}] +farmWarDeployer.servicingDeploy=应用程序[{0}]正在服务。再次触摸WAR文件[{1}]! farmWarDeployer.servicingUndeploy=正在处理应用程序[{0}],无法从备份群集节点中删除它 farmWarDeployer.undeployEnd=从[{0}]取消部署完成。 farmWarDeployer.undeployLocal=不能部署本地上下文[{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ha/session/DeltaManager.java tomcat9-9.0.31/java/org/apache/catalina/ha/session/DeltaManager.java --- tomcat9-9.0.27/java/org/apache/catalina/ha/session/DeltaManager.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ha/session/DeltaManager.java 2020-02-05 19:26:48.000000000 +0000 @@ -485,7 +485,12 @@ @Override public void changeSessionId(Session session) { - changeSessionId(session, true); + rotateSessionId(session); + } + + @Override + public String rotateSessionId(Session session) { + return rotateSessionId(session, true); } @Override @@ -493,12 +498,25 @@ changeSessionId(session, newId, true); } + /** + * @param session The session + * @param notify Notify change + * @deprecated Will be removed in Tomcat 10 + */ + @Deprecated protected void changeSessionId(Session session, boolean notify) { String orgSessionID = session.getId(); super.changeSessionId(session); if (notify) sendChangeSessionId(session.getId(), orgSessionID); } + protected String rotateSessionId(Session session, boolean notify) { + String orgSessionID = session.getId(); + String newId = super.rotateSessionId(session); + if (notify) sendChangeSessionId(session.getId(), orgSessionID); + return newId; + } + protected void changeSessionId(Session session, String newId, boolean notify) { String orgSessionID = session.getId(); super.changeSessionId(session, newId); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ha/session/DeltaSession.java tomcat9-9.0.31/java/org/apache/catalina/ha/session/DeltaSession.java --- tomcat9-9.0.27/java/org/apache/catalina/ha/session/DeltaSession.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ha/session/DeltaSession.java 2020-02-05 19:26:48.000000000 +0000 @@ -846,7 +846,9 @@ if (exclude(name, value)) { continue; } - attributes.put(name, value); + // ConcurrentHashMap does not allow null keys or values + if(null != value) + attributes.put(name, value); } isValid = isValidSave; diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ha/session/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/ha/session/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/ha/session/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ha/session/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -18,12 +18,17 @@ backupManager.startUnable=无法启动BackupManager: [{0}] backupManager.stopped=管理者[{0}]正在停止。 +deltaManager.createMessage.access=管理器[{0}]:创建会话为会话[{1}]存取消息 +deltaManager.createMessage.delta=管理器[{0}] ):为会话[{1}]创建增量请求消息 +deltaManager.createMessage.expire=管理器[{0}] (:为会话[{1}]创建会话过期消息 deltaManager.createSession.newSession=用id[{0}]创建一个扩展会话(DeltaSession),总数为 [{1}] deltaManager.foundMasterMember=复制主master 成员在上下文中被发现.\n deltaManager.loading.cnfe=加载持久化会话 [{0}] 时出现ClassNotFoundException deltaManager.loading.ioe=加载持久 session 时出现 IOException:[{0}] +deltaManager.managerLoad=从永久存储加载会话时发生异常 deltaManager.noContextManager=管理器[{0}]:回复[{1}]发送的“获取所有会话数据”消息,在[{2}] ms后收到“无匹配的上下文管理器”消息 deltaManager.noSessionState=管理者[{0}]:没有收到[{1}]发送的会话状态,在[{2}]毫秒之后超时。 +deltaManager.receiveMessage.accessed=管理器[{0}]:接收会话为会话[{1}]存取消息 deltaManager.receiveMessage.allSessionDataAfter=Manager [{0}]: session 状态反序列化 deltaManager.receiveMessage.allSessionDataBegin=管理者[{0}]:接收到所有会话数据状态 deltaManager.receiveMessage.delta.unknown=管理器[{0}]:未知会话的接收会话增量[{1}] @@ -31,6 +36,7 @@ deltaManager.receiveMessage.unloadingBegin=管理器[{0}]: 开始卸载会话 deltaManager.registerCluster=将管理器[{0}]注册到名为[{2}]的集群元素[{1}] deltaManager.sendMessage.newSession=\ 管理器 [{0}] 发送新的会话 [{1}] +deltaManager.sessionReceived=管理器[{0}];在[{1}]发送的会话状态在[{2}]毫秒内收到。 deltaManager.unableSerializeSessionID=无法序列化会话ID [{0}] deltaManager.unloading.ioe=当保存永久回话:[{0}] 时,抛出 IOException @@ -43,3 +49,7 @@ jvmRoute.changeSession=会话从[{0}]切换到[{1}] jvmRoute.missingJvmRouteAttribute=没有配置引擎jvmRoute属性! jvmRoute.notFoundManager=没有在 [{0}] 找到Cluster Manager +jvmRoute.set.orignalsessionid=在请求属性[{0}]值:[{1}]处设置原始会话ID +jvmRoute.valve.started=JvmRouteBinderValve 启动 + +standardSession.setAttribute.namenull=setAttribute:名称属性不能为空 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/loader/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/loader/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/loader/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/loader/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -14,14 +14,19 @@ # limitations under the License. webappClassLoader.addTransformer=将类文件转换器[{0}]添加到Web应用程序[{1}]。 +webappClassLoader.addTransformer.illegalArgument=Web应用程序[{0}]试图添加空类文件转换器。 webappClassLoader.checkThreadLocalsForLeaks.badValue=无法确定类型为 [{0}] 的值的字符串表示形式 webappClassLoader.checkThreadLocalsForLeaks.unknown=:)未知 webappClassLoader.checkThreadLocalsForLeaksNone=web应用程序 [{0}] 创建了1个ThreadLocal变量(键:[{2}] (类型[{1}]) ,值:[{4}](类型[{3}]) )。键仅被ThreadLocal Map弱引用,所以不是内存泄露。 webappClassLoader.clearJdbc=Web应用程序 [{0}] 注册了JDBC驱动程序 [{1}],但在Web应用程序停止时无法注销它。 为防止内存泄漏,JDBC驱动程序已被强制取消注册。 +webappClassLoader.getThreadGroupError=无法获得线程组[{0}]的父级。不可能检查所有线程是否存在潜在的内存泄漏。 webappClassLoader.jarsRemoved=一个或多个 JAR 已被从 Web 应用程序 [{0}] 中删除 webappClassLoader.jdbcRemoveFailed=Web应用程序 [{0}] 的JDBC驱动程序注销失败 webappClassLoader.readError=资源读取错误:不能加载 [{0}]. +webappClassLoader.stackTrace=Web应用程序[{0}]似乎启动了一个名为[{1}]的线程,但未能停止它。这很可能会造成内存泄漏。线程的堆栈跟踪:[{2}] +webappClassLoader.stopped=非法访问:此Web应用程序实例已停止。无法加载[{0}]。为了调试以及终止导致非法访问的线程,将抛出以下堆栈跟踪。 webappClassLoader.superCloseFail=调用父类的close()方法出现异常。 +webappClassLoader.transformError=检测错误:无法转换类[{0}],因为它的类文件格式是不合法的。 webappClassLoader.warnTimerThread=Web应用程序[{0}]似乎已通过java.util.Timer API启动了名为[{1}]的TimerThread,但未能将其停止。 为防止内存泄漏,计时器(以及相关联的线程)已被强制取消。 webappClassLoader.wrongVersion=(无法载入的.类 [{0}]) diff -Nru tomcat9-9.0.27/java/org/apache/catalina/manager/Constants.java tomcat9-9.0.31/java/org/apache/catalina/manager/Constants.java --- tomcat9-9.0.27/java/org/apache/catalina/manager/Constants.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/manager/Constants.java 2020-02-05 19:26:48.000000000 +0000 @@ -203,7 +203,7 @@ HTML_TAIL_SECTION = "


    \n" + "
    \n" + - " Copyright © 1999-2019, Apache Software Foundation" + + " Copyright © 1999-2020, Apache Software Foundation" + "
    \n" + "\n" + "\n" + diff -Nru tomcat9-9.0.27/java/org/apache/catalina/manager/host/Constants.java tomcat9-9.0.31/java/org/apache/catalina/manager/host/Constants.java --- tomcat9-9.0.27/java/org/apache/catalina/manager/host/Constants.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/manager/host/Constants.java 2020-02-05 19:26:48.000000000 +0000 @@ -81,7 +81,7 @@ public static final String HTML_TAIL_SECTION = "
    \n" + "
    \n" + - " Copyright © 1999-2019, Apache Software Foundation" + + " Copyright © 1999-2020, Apache Software Foundation" + "
    \n" + "\n" + "\n" + diff -Nru tomcat9-9.0.27/java/org/apache/catalina/manager/HTMLManagerServlet.java tomcat9-9.0.31/java/org/apache/catalina/manager/HTMLManagerServlet.java --- tomcat9-9.0.27/java/org/apache/catalina/manager/HTMLManagerServlet.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/manager/HTMLManagerServlet.java 2020-02-05 19:26:48.000000000 +0000 @@ -794,7 +794,7 @@ */ @Override public String getServletInfo() { - return "HTMLManagerServlet, Copyright (c) 1999-2019, The Apache Software Foundation"; + return "HTMLManagerServlet, Copyright (c) 1999-2020, The Apache Software Foundation"; } /** diff -Nru tomcat9-9.0.27/java/org/apache/catalina/manager/JMXProxyServlet.java tomcat9-9.0.31/java/org/apache/catalina/manager/JMXProxyServlet.java --- tomcat9-9.0.27/java/org/apache/catalina/manager/JMXProxyServlet.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/manager/JMXProxyServlet.java 2020-02-05 19:26:48.000000000 +0000 @@ -21,7 +21,9 @@ import java.util.Set; import javax.management.Attribute; +import javax.management.InstanceNotFoundException; import javax.management.MBeanException; +import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.MBeanServer; @@ -35,6 +37,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.catalina.mbeans.MBeanDumper; +import org.apache.catalina.tribes.util.StringManager; import org.apache.tomcat.util.modeler.Registry; /** @@ -51,6 +54,8 @@ // without any parameters. private static final String[] NO_PARAMETERS = new String[0]; + private static final StringManager sm = StringManager.getManager(JMXProxyServlet.class); + // ----------------------------------------------------- Instance Variables /** * MBean server. @@ -257,10 +262,27 @@ * call the requested operation. * @return The value returned by the requested operation. */ + @SuppressWarnings("null") // parameters can't be null if signature.length > 0 private Object invokeOperationInternal(String onameStr, String operation, String[] parameters) throws OperationsException, MBeanException, ReflectionException { ObjectName oname = new ObjectName(onameStr); - MBeanOperationInfo methodInfo = registry.getMethodInfo(oname, operation); + int paramCount = null == parameters ? 0 : parameters.length; + MBeanOperationInfo methodInfo = registry.getMethodInfo(oname, operation, paramCount); + if(null == methodInfo) { + // getMethodInfo returns null for both "object not found" and "operation not found" + MBeanInfo info = null; + try { + info = registry.getMBeanServer().getMBeanInfo(oname); + } catch (InstanceNotFoundException infe) { + throw infe; + } catch (Exception e) { + throw new IllegalArgumentException(sm.getString("jmxProxyServlet.noBeanFound", onameStr), e); + } + throw new IllegalArgumentException( + sm.getString("jmxProxyServlet.noOperationOnBean", + operation, Integer.valueOf(paramCount), onameStr, info.getClassName())); + } + MBeanParameterInfo[] signature = methodInfo.getSignature(); String[] signatureTypes = new String[signature.length]; Object[] values = new Object[signature.length]; diff -Nru tomcat9-9.0.27/java/org/apache/catalina/manager/LocalStrings.properties tomcat9-9.0.31/java/org/apache/catalina/manager/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/catalina/manager/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/manager/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -192,3 +192,6 @@ statusServlet.complete=Complete Server Status statusServlet.title=Server Status + +jmxProxyServlet.noOperationOnBean=Cannot find operation [{0}] with [{1}] arguments on object name [{2}], which is a [{3}] +jmxProxyServlet.noBeanFound=Cannot find MBean with object name [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/Manager.java tomcat9-9.0.31/java/org/apache/catalina/Manager.java --- tomcat9-9.0.27/java/org/apache/catalina/Manager.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/Manager.java 2020-02-05 19:26:48.000000000 +0000 @@ -215,11 +215,44 @@ * session ID. * * @param session The session to change the session ID for + * + * @deprecated Use {@link #rotateSessionId(Session)}. + * Will be removed in Tomcat 10 */ + @Deprecated public void changeSessionId(Session session); /** + * Change the session ID of the current session to a new randomly generated + * session ID. + * + * @param session The session to change the session ID for + * + * @return The new session ID + */ + public default String rotateSessionId(Session session) { + String newSessionId = null; + // Assume there new Id is a duplicate until we prove it isn't. The + // chances of a duplicate are extremely low but the current ManagerBase + // code protects against duplicates so this default method does too. + boolean duplicate = true; + do { + newSessionId = getSessionIdGenerator().generateSessionId(); + try { + if (findSession(newSessionId) == null) { + duplicate = false; + } + } catch (IOException ioe) { + // Swallow. An IOE means the ID was known so continue looping + } + } while (duplicate); + changeSessionId(session, newSessionId); + return newSessionId; + } + + + /** * Change the session ID of the current session to a specified session ID. * * @param session The session to change the session ID for diff -Nru tomcat9-9.0.27/java/org/apache/catalina/mapper/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/mapper/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/mapper/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/mapper/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,9 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +mapper.addContext.noHost=找不到主机 [{0}] mapper.addHostAlias.success=为虚拟主机 [{1}] 注册了别名 [{0}] mapperListener.pauseContext=根据服务需要,注册内容[{0}]已经重新加载 mapperListener.registerHost=这域名[{1}]注册主机[{0}],服务:[{2}] mapperListener.registerWrapper=为服务Service[{2}]在上下文Context[{1}]注册Wrapper[{0}] +mapperListener.unregisterContext=注销服务[{1}]的上下文[{0}] mapperListener.unregisterHost=在域[{1}]中.,不能注册主机[{0}]为服务[{2}] +mapperListener.unregisterWrapper=在上下文[{1}]中注销服务[{2}]的包装程序[{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/mapper/Mapper.java tomcat9-9.0.31/java/org/apache/catalina/mapper/Mapper.java --- tomcat9-9.0.27/java/org/apache/catalina/mapper/Mapper.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/mapper/Mapper.java 2020-02-05 19:26:48.000000000 +0000 @@ -732,6 +732,7 @@ * Map the specified URI. * @throws IOException */ + @SuppressWarnings("deprecation") // contextPath private final void internalMap(CharChunk host, CharChunk uri, String version, MappingData mappingData) throws IOException { @@ -1064,6 +1065,7 @@ /** * Exact mapping. */ + @SuppressWarnings("deprecation") // contextPath private final void internalMapExactWrapper (MappedWrapper[] wrappers, CharChunk path, MappingData mappingData) { MappedWrapper wrapper = exactFind(wrappers, path); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/mapper/MappingData.java tomcat9-9.0.31/java/org/apache/catalina/mapper/MappingData.java --- tomcat9-9.0.27/java/org/apache/catalina/mapper/MappingData.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/mapper/MappingData.java 2020-02-05 19:26:48.000000000 +0000 @@ -38,6 +38,10 @@ public Wrapper wrapper = null; public boolean jspWildCard = false; + /** + * @deprecated Unused. This will be removed in Tomcat 10. + */ + @Deprecated public final MessageBytes contextPath = MessageBytes.newInstance(); public final MessageBytes requestPath = MessageBytes.newInstance(); public final MessageBytes wrapperPath = MessageBytes.newInstance(); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java tomcat9-9.0.31/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java --- tomcat9-9.0.27/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/mbeans/JmxRemoteLifecycleListener.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,10 +25,11 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.UnknownHostException; +import java.rmi.AccessException; import java.rmi.AlreadyBoundException; +import java.rmi.NotBoundException; +import java.rmi.Remote; import java.rmi.RemoteException; -import java.rmi.registry.LocateRegistry; -import java.rmi.registry.Registry; import java.rmi.server.RMIClientSocketFactory; import java.rmi.server.RMIServerSocketFactory; import java.util.HashMap; @@ -61,7 +62,13 @@ * instance that is running behind a firewall. Only the ports are configured via * the listener. The remainder of the configuration is via the standard system * properties for configuring JMX. + * + * @deprecated The features provided by this listener are now available in the + * remote JMX capability included with the JRE. + * This listener will be removed in Tomcat 10 and may be removed + * from Tomcat 9.0.x some time after 2020-12-31. */ +@Deprecated public class JmxRemoteLifecycleListener extends SSLHostConfig implements LifecycleListener { private static final long serialVersionUID = 1L; @@ -292,8 +299,10 @@ @Override public void lifecycleEvent(LifecycleEvent event) { - // When the server starts, configure JMX/RMI - if (Lifecycle.START_EVENT.equals(event.getType())) { + if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) { + log.warn(sm.getString("jmxRemoteLifecycleListener.deprecated")); + } else if (Lifecycle.START_EVENT.equals(event.getType())) { + // When the server starts, configure JMX/RMI // Configure using standard JMX system properties init(); @@ -417,18 +426,6 @@ RMIClientSocketFactory registryCsf, RMIServerSocketFactory registrySsf, RMIClientSocketFactory serverCsf, RMIServerSocketFactory serverSsf) { - // Create the RMI registry - Registry registry; - try { - registry = LocateRegistry.createRegistry( - theRmiRegistryPort, registryCsf, registrySsf); - } catch (RemoteException e) { - log.error(sm.getString( - "jmxRemoteLifecycleListener.createRegistryFailed", - serverName, Integer.toString(theRmiRegistryPort)), e); - return null; - } - if (bindAddress == null) { bindAddress = "localhost"; } @@ -449,11 +446,26 @@ cs = new RMIConnectorServer(serviceUrl, theEnv, server, ManagementFactory.getPlatformMBeanServer()); cs.start(); - registry.bind("jmxrmi", server.toStub()); + Remote jmxServer = server.toStub(); + // Create the RMI registry + try { + /* + * JmxRegistry is registered as a side-effect of creation. + * This object is here so we can tell the IDE it is OK for it + * not to be used. + */ + @SuppressWarnings("unused") + JmxRegistry unused = new JmxRegistry(theRmiRegistryPort, registryCsf, registrySsf, "jmxrmi", jmxServer); + } catch (RemoteException e) { + log.error(sm.getString( + "jmxRemoteLifecycleListener.createRegistryFailed", + serverName, Integer.toString(theRmiRegistryPort)), e); + return null; + } log.info(sm.getString("jmxRemoteLifecycleListener.start", Integer.toString(theRmiRegistryPort), Integer.toString(theRmiServerPort), serverName)); - } catch (IOException | AlreadyBoundException e) { + } catch (IOException e) { log.error(sm.getString( "jmxRemoteLifecycleListener.createServerFailed", serverName), e); @@ -589,4 +601,43 @@ return true; } } + + + /* + * Better to use the internal API than re-invent the wheel. + */ + @SuppressWarnings("restriction") + private static class JmxRegistry extends sun.rmi.registry.RegistryImpl { + private static final long serialVersionUID = -3772054804656428217L; + private final String jmxName; + private final Remote jmxServer; + public JmxRegistry(int port, RMIClientSocketFactory csf, + RMIServerSocketFactory ssf, String jmxName, Remote jmxServer) throws RemoteException { + super(port, csf, ssf); + this.jmxName = jmxName; + this.jmxServer = jmxServer; + } + @Override + public Remote lookup(String name) + throws RemoteException, NotBoundException { + return (jmxName.equals(name)) ? jmxServer : null; + } + @Override + public void bind(String name, Remote obj) + throws RemoteException, AlreadyBoundException, AccessException { + } + @Override + public void unbind(String name) + throws RemoteException, NotBoundException, AccessException { + } + @Override + public void rebind(String name, Remote obj) + throws RemoteException, AccessException { + } + @Override + public String[] list() throws RemoteException { + return new String[] { jmxName }; + } + } + } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/mbeans/LocalStrings.properties tomcat9-9.0.31/java/org/apache/catalina/mbeans/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/catalina/mbeans/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/mbeans/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,7 @@ jmxRemoteLifecycleListener.createRegistryFailed=Unable to create the RMI registry for the [{0}] server using port [{1}] jmxRemoteLifecycleListener.createServerFailed=The JMX connector server could not be created or failed to start for the [{0}] server +jmxRemoteLifecycleListener.deprecated=The JmxRemoteLifecycleListener is deprecated as as the features it provides are now available in the remote JMX capability included with the JRE. This listener will be removed in Tomcat 10 and may be removed from Tomcat 9 some time after 2020-12-31. jmxRemoteLifecycleListener.destroyServerFailed=The JMX connector server could not be stopped for the [{0}] server jmxRemoteLifecycleListener.invalidRmiBindAddress=Invalid RMI bind address [{0}] jmxRemoteLifecycleListener.invalidSSLConfiguration=SSL configuration error diff -Nru tomcat9-9.0.27/java/org/apache/catalina/mbeans/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/mbeans/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/mbeans/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/mbeans/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,5 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +jmxRemoteLifecycleListener.createRegistryFailed=无法使用端口[{1}]为[{0}]服务器创建RMI注册表 +jmxRemoteLifecycleListener.createServerFailed=无法为[{0}]服务器创建JMX连接器服务器或启动失败 jmxRemoteLifecycleListener.invalidSSLConfiguration=SSL配置错误 jmxRemoteLifecycleListener.invalidURL=为[{0}]服务器[{1}]请求的JMX服务URL无效 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/realm/CombinedRealm.java tomcat9-9.0.31/java/org/apache/catalina/realm/CombinedRealm.java --- tomcat9-9.0.27/java/org/apache/catalina/realm/CombinedRealm.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/realm/CombinedRealm.java 2020-02-05 19:26:48.000000000 +0000 @@ -29,9 +29,11 @@ import org.apache.catalina.Lifecycle; import org.apache.catalina.LifecycleException; import org.apache.catalina.Realm; +import org.apache.catalina.Wrapper; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSName; @@ -346,22 +348,18 @@ public Principal authenticate(GSSContext gssContext, boolean storeCred) { if (gssContext.isEstablished()) { Principal authenticatedUser = null; - String username = null; - - GSSName name = null; + GSSName gssName = null; try { - name = gssContext.getSrcName(); + gssName = gssContext.getSrcName(); } catch (GSSException e) { log.warn(sm.getString("realmBase.gssNameFail"), e); return null; } - username = name.toString(); - for (Realm realm : realms) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authStart", - username, realm.getClass().getName())); + gssName, realm.getClass().getName())); } authenticatedUser = realm.authenticate(gssContext, storeCred); @@ -369,12 +367,12 @@ if (authenticatedUser == null) { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authFail", - username, realm.getClass().getName())); + gssName, realm.getClass().getName())); } } else { if (log.isDebugEnabled()) { log.debug(sm.getString("combinedRealm.authSuccess", - username, realm.getClass().getName())); + gssName, realm.getClass().getName())); } break; } @@ -386,6 +384,50 @@ return null; } + /** + * {@inheritDoc} + */ + @Override + public Principal authenticate(GSSName gssName, GSSCredential gssCredential) { + Principal authenticatedUser = null; + + for (Realm realm : realms) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authStart", + gssName, realm.getClass().getName())); + } + + authenticatedUser = realm.authenticate(gssName, gssCredential); + + if (authenticatedUser == null) { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authFail", + gssName, realm.getClass().getName())); + } + } else { + if (log.isDebugEnabled()) { + log.debug(sm.getString("combinedRealm.authSuccess", + gssName, realm.getClass().getName())); + } + break; + } + } + return authenticatedUser; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasRole(Wrapper wrapper, Principal principal, String role) { + for (Realm realm : realms) { + if (realm.hasRole(wrapper, principal, role)) { + return true; + } + } + return false; + } + @Override protected String getPassword(String username) { // This method should never be called diff -Nru tomcat9-9.0.27/java/org/apache/catalina/realm/JNDIRealm.java tomcat9-9.0.31/java/org/apache/catalina/realm/JNDIRealm.java --- tomcat9-9.0.27/java/org/apache/catalina/realm/JNDIRealm.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/realm/JNDIRealm.java 2020-02-05 19:26:48.000000000 +0000 @@ -62,6 +62,7 @@ import org.apache.catalina.LifecycleException; import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSName; /** *

    Implementation of Realm that works with a directory @@ -1527,7 +1528,6 @@ containerLog.debug("Found user by search [" + user + "]"); } } - if (userPassword == null && credentials != null && user != null) { // The password is available. Insert it since it may be required for // role searches. @@ -2222,7 +2222,7 @@ try { User user = getUser(open(), username, null); - if (user == null) { + if (user == null) { // User should be found... return null; } else { @@ -2246,6 +2246,22 @@ } @Override + protected Principal getPrincipal(GSSName gssName, + GSSCredential gssCredential) { + String name = gssName.toString(); + + if (isStripRealmForGss()) { + int i = name.indexOf('@'); + if (i > 0) { + // Zero so we don't leave a zero length name + name = name.substring(0, i); + } + } + + return getPrincipal(name, gssCredential); + } + + @Override protected Principal getPrincipal(String username, GSSCredential gssCredential) { @@ -2341,12 +2357,14 @@ roles = getRoles(context, user); } } finally { - restoreEnvironmentParameter(context, - Context.SECURITY_AUTHENTICATION, preservedEnvironment); - restoreEnvironmentParameter(context, - "javax.security.sasl.server.authentication", preservedEnvironment); - restoreEnvironmentParameter(context, "javax.security.sasl.qop", - preservedEnvironment); + if (gssCredential != null && isUseDelegatedCredential()) { + restoreEnvironmentParameter(context, + Context.SECURITY_AUTHENTICATION, preservedEnvironment); + restoreEnvironmentParameter(context, + "javax.security.sasl.server.authentication", preservedEnvironment); + restoreEnvironmentParameter(context, "javax.security.sasl.qop", + preservedEnvironment); + } } if (user != null) { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/realm/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/realm/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/realm/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/realm/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -21,15 +21,24 @@ jaasCallback.username=返回用户名 [{0}] +jaasMemoryLoginModule.invalidCredentials=用户名或密码不正确 +jaasMemoryLoginModule.noConfig=无法加载配置文件 [{0}] + +jaasRealm.authenticateFailure=用户 [{0}] 认证失败 jaasRealm.authenticateSuccess=用户名 [{0}] 已被成功认证为身份 [{1}] -- 主体也已创建 +jaasRealm.classNotFound=找不到类 [{0}] jaasRealm.failedLogin=由于登录失败,用户名 [{0}] 无法授权 jaasRealm.loginContextCreated=为用户名创建的JAAS 登陆上下文[{0}] jaasRealm.loginException=登录异常,认证用户名 [{0}] +jdbcRealm.authenticateFailure=用户名称[{0}]未校验成功 +jdbcRealm.open=打开数据库连接时发生异常 + jndiRealm.authenticateFailure=用户名[{0}]没有成功认证 jndiRealm.authenticateSuccess=用户名[{0}]成功认证 jndiRealm.cipherSuites=启用 [{0}] 作为 TLS 连接的加密套件。 jndiRealm.exception=执行认证异常 +jndiRealm.negotiatedTls=使用协议[{0}]协商的TLS连接 jndiRealm.open=打开目录服务器链接异常 lockOutRealm.authLockedUser=尝试对锁定的用户[{0}]进行身份验证 @@ -37,6 +46,7 @@ memoryRealm.loadExist=内存数据库文件[{0}]无法读取 memoryRealm.loadPath=从内存数据库文件 [{0}] 加载用户 memoryRealm.readXml=读取内存数据库文件时出现异常 +memoryRealm.xmlFeatureEncoding=配置Digester以允许XML文件中的java编码名称的异常。只支持IANA编码名称。 pbeCredentialHandler.invalidKeySpec=无法生成基于密码的密钥 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/realm/LockOutRealm.java tomcat9-9.0.31/java/org/apache/catalina/realm/LockOutRealm.java --- tomcat9-9.0.27/java/org/apache/catalina/realm/LockOutRealm.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/realm/LockOutRealm.java 2020-02-05 19:26:48.000000000 +0000 @@ -27,6 +27,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; import org.ietf.jgss.GSSException; import org.ietf.jgss.GSSName; @@ -200,6 +201,18 @@ return null; } + /** + * {@inheritDoc} + */ + @Override + public Principal authenticate(GSSName gssName, GSSCredential gssCredential) { + String username = gssName.toString(); + + Principal authenticatedUser = super.authenticate(gssName, gssCredential); + + return filterLockedAccounts(username, authenticatedUser); + } + /* * Filters authenticated principals to ensure that null is diff -Nru tomcat9-9.0.27/java/org/apache/catalina/realm/RealmBase.java tomcat9-9.0.31/java/org/apache/catalina/realm/RealmBase.java --- tomcat9-9.0.27/java/org/apache/catalina/realm/RealmBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/realm/RealmBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,7 +16,6 @@ */ package org.apache.catalina.realm; - import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.io.IOException; @@ -149,7 +148,6 @@ // ------------------------------------------------------------- Properties - /** * @return The HTTP status code used when the container needs to issue an * HTTP redirect to meet the requirements of a configured transport @@ -368,6 +366,7 @@ } } + /** * Try to authenticate with the specified username, which * matches the digest calculated using the given parameters using the @@ -497,16 +496,7 @@ } } - String name = gssName.toString(); - - if (isStripRealmForGss()) { - int i = name.indexOf('@'); - if (i > 0) { - // Zero so we don't leave a zero length name - name = name.substring(0, i); - } - } - return getPrincipal(name, gssCredential); + return getPrincipal(gssName, gssCredential); } } else { log.error(sm.getString("realmBase.gssContextNotEstablished")); @@ -518,6 +508,19 @@ /** + * {@inheritDoc} + */ + @Override + public Principal authenticate(GSSName gssName, GSSCredential gssCredential) { + if (gssName == null) { + return null; + } + + return getPrincipal(gssName, gssCredential); + } + + + /** * Execute a periodic task, such as reloading, etc. This method will be * invoked inside the classloading context of this container. Unexpected * throwables will be caught and logged. @@ -1220,6 +1223,16 @@ protected abstract Principal getPrincipal(String username); + /** + * Get the principal associated with the specified user name. + * + * @param username The user name + * @param gssCredential the GSS credential of the principal + * @return the principal associated with the given user name. + * @deprecated This will be removed in Tomcat 10 onwards. Use + * {@link #getPrincipal(GSSName, GSSCredential)} instead. + */ + @Deprecated protected Principal getPrincipal(String username, GSSCredential gssCredential) { Principal p = getPrincipal(username); @@ -1231,6 +1244,36 @@ return p; } + + /** + * Get the principal associated with the specified {@link GSSName}. + * + * @param gssName The GSS name + * @param gssCredential the GSS credential of the principal + * @return the principal associated with the given user name. + */ + protected Principal getPrincipal(GSSName gssName, + GSSCredential gssCredential) { + String name = gssName.toString(); + + if (isStripRealmForGss()) { + int i = name.indexOf('@'); + if (i > 0) { + // Zero so we don't leave a zero length name + name = name.substring(0, i); + } + } + + Principal p = getPrincipal(name); + + if (p instanceof GenericPrincipal) { + ((GenericPrincipal) p).setGssCredential(gssCredential); + } + + return p; + } + + /** * Return the Server object that is the ultimate parent for the container * with which this Realm is associated. If the server cannot be found (eg diff -Nru tomcat9-9.0.27/java/org/apache/catalina/Realm.java tomcat9-9.0.31/java/org/apache/catalina/Realm.java --- tomcat9-9.0.27/java/org/apache/catalina/Realm.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/Realm.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,8 @@ import org.apache.catalina.connector.Response; import org.apache.tomcat.util.descriptor.web.SecurityConstraint; import org.ietf.jgss.GSSContext; +import org.ietf.jgss.GSSCredential; +import org.ietf.jgss.GSSName; /** * A Realm is a read-only facade for an underlying security realm @@ -117,6 +119,22 @@ /** + * Try to authenticate using a {@link GSSName} + * + * Note that this default method will be turned into an abstract one in + * Tomcat 10. + * + * @param gssName The {@link GSSName} of the principal to look up + * @param gssCredential The {@link GSSCredential} of the principal, may be + * {@code null} + * @return the associated principal, or {@code null} if there is none + */ + public default Principal authenticate(GSSName gssName, GSSCredential gssCredential) { + return null; + } + + + /** * Try to authenticate using {@link X509Certificate}s * * @param certs Array of client certificates, with the first one in @@ -211,7 +229,9 @@ * Return roles associated with given principal * @param principal the {@link Principal} to get the roles for. * @return principal roles + * @deprecated This will be removed in Tomcat 10. */ + @Deprecated public String[] getRoles(Principal principal); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/servlets/DefaultServlet.java tomcat9-9.0.31/java/org/apache/catalina/servlets/DefaultServlet.java --- tomcat9-9.0.27/java/org/apache/catalina/servlets/DefaultServlet.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/servlets/DefaultServlet.java 2020-02-05 19:26:48.000000000 +0000 @@ -1815,14 +1815,18 @@ String rewrittenContextPath = rewriteUrl(contextPath); // Render the page header - sb.append("\r\n"); + sb.append("\r\n"); + /* TODO Activate this as soon as we use smClient with the request locales + sb.append("\r\n"); + */ sb.append("\r\n"); sb.append(""); sb.append(sm.getString("directory.title", directoryWebappPath)); sb.append("\r\n"); - sb.append(" "); + sb.append(" "); sb.append("\r\n"); sb.append(""); sb.append("

    "); @@ -1852,7 +1856,7 @@ } sb.append("

    "); - sb.append("
    "); + sb.append("
    "); sb.append("\r\n"); @@ -1949,12 +1953,12 @@ // Render the page footer sb.append("
    \r\n"); - sb.append("
    "); + sb.append("
    "); String readme = getReadme(resource, encoding); if (readme!=null) { sb.append(readme); - sb.append("
    "); + sb.append("
    "); } if (showServerInfo) { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/servlets/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -16,6 +16,7 @@ cgiServlet.expandFail=在路径[{0}] 到[{1}] 展开脚本失败. cgiServlet.expandOk=从路径[{0}]到[{1}]扩展脚本 cgiServlet.find.location=在 [{0}] 查找文件 +cgiServlet.find.path=在相对于CGI位置[{1}]的路径[{0}]处请求的CGI脚本 cgiServlet.runHeaderReaderFail=I/O 问题关闭请求头读操作 cgiServlet.runInvalidStatus=无效状态 [{0}] cgiServlet.runOutputStreamFail=关闭输出流时发生I/O问题 @@ -23,6 +24,7 @@ cgiServlet.runStdErrFail=I/O标准错误问题 defaultServlet.blockExternalSubset=用名称[{0}]和baseURI[{1}]阻止对外部子集的访问 +defaultServlet.noResources=找不到静态资源 directory.filename=文件名 directory.size=大小 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/servlets/WebdavServlet.java tomcat9-9.0.31/java/org/apache/catalina/servlets/WebdavServlet.java --- tomcat9-9.0.27/java/org/apache/catalina/servlets/WebdavServlet.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/servlets/WebdavServlet.java 2020-02-05 19:26:48.000000000 +0000 @@ -61,7 +61,10 @@ import org.xml.sax.SAXException; /** - * Servlet which adds support for WebDAV level 2. All the basic HTTP requests + * Servlet which adds support for + * WebDAV + * level 2. + * All the basic HTTP requests * are handled by the DefaultServlet. The WebDAVServlet must not be used as the * default servlet (ie mapped to '/') as it will not work in this configuration. *

    @@ -120,6 +123,8 @@ * http://host:port/context/webdavedit/content * * @author Remy Maucherat + * + * @see RFC 4918 */ public class WebdavServlet extends DefaultServlet { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/session/FileStore.java tomcat9-9.0.31/java/org/apache/catalina/session/FileStore.java --- tomcat9-9.0.27/java/org/apache/catalina/session/FileStore.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/session/FileStore.java 2020-02-05 19:26:48.000000000 +0000 @@ -127,17 +127,17 @@ @Override public int getSize() throws IOException { // Acquire the list of files in our storage directory - File file = directory(); - if (file == null) { + File dir = directory(); + if (dir == null) { return 0; } - String files[] = file.list(); + String files[] = dir.list(); // Figure out which files are sessions int keycount = 0; if (files != null) { - for (int i = 0; i < files.length; i++) { - if (files[i].endsWith(FILE_EXT)) { + for (String file : files) { + if (file.endsWith(FILE_EXT)) { keycount++; } } @@ -172,24 +172,23 @@ @Override public String[] keys() throws IOException { // Acquire the list of files in our storage directory - File file = directory(); - if (file == null) { + File dir = directory(); + if (dir == null) { return new String[0]; } - - String files[] = file.list(); + String files[] = dir.list(); // Bugzilla 32130 - if((files == null) || (files.length < 1)) { + if (files == null || files.length < 1) { return new String[0]; } // Build and return the list of session identifiers List list = new ArrayList<>(); int n = FILE_EXT.length(); - for (int i = 0; i < files.length; i++) { - if (files[i].endsWith(FILE_EXT)) { - list.add(files[i].substring(0, files[i].length() - n)); + for (String file : files) { + if (file.endsWith(FILE_EXT)) { + list.add (file.substring(0, file.length() - n)); } } return list.toArray(new String[list.size()]); @@ -210,11 +209,7 @@ public Session load(String id) throws ClassNotFoundException, IOException { // Open an input stream to the specified pathname, if any File file = file(id); - if (file == null) { - return null; - } - - if (!file.exists()) { + if (file == null || !file.exists()) { return null; } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/session/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/session/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/session/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/session/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,14 +13,21 @@ # See the License for the specific language governing permissions and # limitations under the License. +JDBCStore.checkConnectionClassNotFoundException=找不到 JDBC 驱动程序类 [{0}] +JDBCStore.checkConnectionDBClosed=数据库连接为空或已关闭。正在尝试重新连接。 JDBCStore.checkConnectionDBReOpenFail=重新打开数据库失败,数据库可能已经宕机。 +JDBCStore.checkConnectionSQLException=发生 SQL 异常 [{0}] +JDBCStore.loading=正在从数据库[{1}]加载会话[{0}] JDBCStore.missingDataSourceName=没有给出有效的 JNDI 名称。 JDBCStore.saving=保存Session [{0}] 到数据库 [{1}] +JDBCStore.wrongDataSource=无法打开 JNDI 数据源 [{0}] fileStore.deleteFailed=无法删除阻止创建会话存储位置的文件 [{0}] +fileStore.deleteSessionFailed=无法删除不再需要的文件[{0}] managerBase.contextNull=使用 Manager 之前,必须将 Context 设置为非 null 值 managerBase.createSession.ise=createSession:活跃session过多 +managerBase.sessionNotFound=找不到会话 [{0}] managerBase.setContextNotNew=如果Manager未处于NEW状态,则调用setContext()以更改与Manager关联的Context是非法的 persistentManager.deserializeError=错误反序列化会话[{0}]: [{1}] @@ -34,11 +41,15 @@ standardManager.loading.exception=加载持久化会话时发生异常 standardManager.managerLoad=从持久化存储加载会话发生异常 standardManager.managerUnload=卸载会话到持久存储的异常 +standardManager.unloading.nosessions=没有要卸载的持久会话 +standardSession.attributeEvent=会话属性事件侦听器引发异常 +standardSession.getAttribute.ise=getAttribute: 会话已失效 standardSession.getAttributeNames.ise=getAttributeNames:会话已失效 standardSession.getCreationTime.ise=getCreataionTime:会话已经无效 standardSession.getIdleTime.ise=getIdleTime: 已失效的会话 standardSession.getLastAccessedTime.ise=getLastAccessedTime: 会话已失效 +standardSession.getThisAccessedTime.ise=getThisAccessedTime:会话已经失效 standardSession.getValueNames.ise=getValueNames:会话已经失效 standardSession.logoutfail=当回话将过期登出用户异常 standardSession.notDeserializable=无法反序列化会话 [{1}] 的属性 [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/session/ManagerBase.java tomcat9-9.0.31/java/org/apache/catalina/session/ManagerBase.java --- tomcat9-9.0.27/java/org/apache/catalina/session/ManagerBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/session/ManagerBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -753,8 +753,15 @@ @Override public void changeSessionId(Session session) { + rotateSessionId(session); + } + + + @Override + public String rotateSessionId(Session session) { String newId = generateSessionId(); changeSessionId(session, newId, true, true); + return newId; } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/session/StandardSession.java tomcat9-9.0.31/java/org/apache/catalina/session/StandardSession.java --- tomcat9-9.0.27/java/org/apache/catalina/session/StandardSession.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/session/StandardSession.java 2020-02-05 19:26:48.000000000 +0000 @@ -1595,7 +1595,9 @@ if (exclude(name, value)) { continue; } - attributes.put(name, value); + // ConcurrentHashMap does not allow null keys or values + if(null != value) + attributes.put(name, value); } isValid = isValidSave; diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ssi/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/ssi/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/ssi/LocalStrings_zh_CN.properties 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ssi/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,16 @@ +# 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. + +expressionParseTree.extraNodes=创建额外节点 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/ssi/SSIServletExternalResolver.java tomcat9-9.0.31/java/org/apache/catalina/ssi/SSIServletExternalResolver.java --- tomcat9-9.0.27/java/org/apache/catalina/ssi/SSIServletExternalResolver.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/ssi/SSIServletExternalResolver.java 2020-02-05 19:26:48.000000000 +0000 @@ -22,6 +22,7 @@ import java.net.URL; import java.net.URLConnection; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Date; import java.util.Enumeration; @@ -35,7 +36,6 @@ import org.apache.catalina.connector.Connector; import org.apache.catalina.connector.Request; -import org.apache.coyote.Constants; import org.apache.tomcat.util.buf.B2CConverter; import org.apache.tomcat.util.buf.UDecoder; import org.apache.tomcat.util.http.RequestUtil; @@ -273,7 +273,7 @@ queryStringCharset = uriCharset; } else { // Use default as a last resort - queryStringCharset = Constants.DEFAULT_URI_CHARSET; + queryStringCharset = StandardCharsets.UTF_8; } retVal = UDecoder.URLDecode(queryString, queryStringCharset); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/startup/Bootstrap.java tomcat9-9.0.31/java/org/apache/catalina/startup/Bootstrap.java --- tomcat9-9.0.27/java/org/apache/catalina/startup/Bootstrap.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/startup/Bootstrap.java 2020-02-05 19:26:48.000000000 +0000 @@ -34,7 +34,6 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; - /** * Bootstrap loader for Catalina. This application constructs a class loader * for use in loading the Catalina internal classes (by accumulating all of the diff -Nru tomcat9-9.0.27/java/org/apache/catalina/startup/CatalinaBaseConfigurationSource.java tomcat9-9.0.31/java/org/apache/catalina/startup/CatalinaBaseConfigurationSource.java --- tomcat9-9.0.27/java/org/apache/catalina/startup/CatalinaBaseConfigurationSource.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/startup/CatalinaBaseConfigurationSource.java 2020-02-05 19:26:48.000000000 +0000 @@ -103,7 +103,12 @@ } // Then try URI. - URI uri = getURI(name); + URI uri = null; + try { + uri = getURI(name); + } catch (IllegalArgumentException e) { + throw new IOException(sm.getString("catalinaConfigurationSource.cannotObtainURL", name), e); + } // Obtain the input stream we need try { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/startup/ContextConfig.java tomcat9-9.0.31/java/org/apache/catalina/startup/ContextConfig.java --- tomcat9-9.0.27/java/org/apache/catalina/startup/ContextConfig.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/startup/ContextConfig.java 2020-02-05 19:26:48.000000000 +0000 @@ -1599,6 +1599,9 @@ entry = new DefaultWebXmlCacheEntry(webXmlDefaultFragment, globalTimeStamp, hostTimeStamp); hostWebXmlCache.put(host, entry); + // Add a Lifecycle listener to the Host that will remove it from + // the hostWebXmlCache once the Host is destroyed + host.addLifecycleListener(new HostWebXmlCacheCleaner()); } return webXmlDefaultFragment; @@ -1715,6 +1718,7 @@ } } + /** * Scan JARs that contain web-fragment.xml files that will be used to * configure this application to see if they also contain static resources. @@ -1784,6 +1788,7 @@ return getWebXmlSource(defaultWebXml, true); } + /** * Identify the host web.xml to be used and obtain an input source for * it. @@ -1956,6 +1961,7 @@ // validation is not enabled parseRequired = false; } + FragmentJarScannerCallback callback = new FragmentJarScannerCallback(webXmlParser, delegate, parseRequired); @@ -2681,6 +2687,18 @@ } } + private static class HostWebXmlCacheCleaner implements LifecycleListener { + + @Override + public void lifecycleEvent(LifecycleEvent event) { + + if (Lifecycle.AFTER_DESTROY_EVENT.equals(event.getType())) { + Host host = (Host) event.getSource(); + hostWebXmlCache.remove(host); + } + } + } + static class JavaClassCacheEntry { public final String superclassName; diff -Nru tomcat9-9.0.27/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/startup/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -15,7 +15,10 @@ catalina.incorrectPermissions=权限错误,此文件没有读取权限 catalina.init=服务器在[{0}]毫秒内初始化 +catalina.initError=初始化 Catalina 时出错 +catalina.noCluster=由于[{0}]未找到群集Ruleset。已禁用群集配置。 catalina.serverStartFail=所必需的服务组件启动失败,所以无法启动Tomcat +catalina.stopError=停止 Catalina 时出错 catalina.usage=用法: java org.apache.catalina.startup.Catalina [ -config {pathname} ] [ -nonaming ] { -help | start | stop } catalinaConfigurationSource.cannotObtainURL=无法获取相对路径 [{0}] 的URL。 检查catalina.base是否已设置。 @@ -64,6 +67,9 @@ contextConfig.unavailable=由于之前的错误,标记当前应用程序不可用 contextConfig.unknownUrlProtocol=注解解析过程中,URL协议[{0}]未识别。忽略URL[{1}]。 contextConfig.urlPatternValue=类文件[{1}]的urlPatterns和值属性上同时设置了注解[{0}] +contextConfig.xmlSettings=上下文[{0}]将解析web.xml和web-fragment.xml文件,验证为:[{1}],命名空间感知为(:[{2}] + +embedded.notmp=在[{0}]找不到指定的临时文件夹 engineConfig.cce=生命周期事件数据对象[{0}]不是一个引擎(Engine) engineConfig.stop=配置引擎,处理进程停止。 @@ -76,13 +82,21 @@ expandWar.missingJarEntry=无法获得 JarEntry [{0}] 的输入流 - WAR 文件是否已损坏? hostConfig.appBase=主机[{0}]的应用程序基础[{1}]不存在或不是目录。deployOnStartUp和autoDebug已设置为false,以防止部署错误。其他错误仍然可能发生。 +hostConfig.context.remove=移除上下文[{0}]错误 +hostConfig.deployDescriptor.blocked=(:未部署上下文路径为[{0}]的Web应用程序,因为它包含一个部署描述符[{1}],该描述符可能包含安全部署应用程序所需的配置,但此主机的DeployXML设置阻止了部署描述符的处理。应该在[{2}]创建适当的描述符来部署此应用程序。 hostConfig.deployDescriptor.error=部署描述符[{0}]时出错 +hostConfig.deployDescriptor.finished=部署描述符[{0}]的部署已在[{1}]ms内完成 +hostConfig.deployDescriptor.localDocBaseSpecified=(:在主机appBase 中指定了docBase [{0}],将被忽略 hostConfig.deployDir=把web 应用程序部署到目录 [{0}] +hostConfig.deployDir.error=无法部署应用目录 [{0}] hostConfig.deployWar.error=部署 Web 应用程序 archive [{0}] 时出错 hostConfig.deployWar.hiddenDir=将忽略目录[{0}],因为WAR [{1}]优先,unpackWAR为false +hostConfig.deployWar.threaded.error=等待WAR文件的多线程部署完成时出错 hostConfig.docBaseUrlInvalid=所提供的部署目录无法用URL来表示 +hostConfig.expand.error=解压WEB应用程序文件[{0}]时异常 hostConfig.ignorePath=忽略appBase中的路径[{0}]以进行自动部署 hostConfig.jmx.unregister=移除注册上下文[{0}]失败 +hostConfig.start=HostConfig: 开始处理 hostConfig.stop=:)Host配置:停止处理 tomcat.addWebapp.conflictChild=无法在[{0}]处部署到上下文路径[{1}],因为存在上下文[{2}] @@ -93,6 +107,7 @@ tomcat.noContextXml=不能找到web 应用的context.xml [{0}] userConfig.database=加载用户数据库异常 +userConfig.deploy.threaded.error=等待用户目录的多线程部署完成时出错 userConfig.deploying=正在部署用户 web 应用程序 userConfig.error=为用户 [{0}]部署web应用发生错误 userConfig.start=用户配置:处理开始 @@ -103,6 +118,7 @@ versionLoggerListener.os.version=OS.版本: {0} versionLoggerListener.prop=系统属性: {0} = {1} versionLoggerListener.serverInfo.server.built=服务器构建: {0} +versionLoggerListener.serverInfo.server.number=服务器版本号(:{0} versionLoggerListener.serverInfo.server.version=Server.服务器版本: {0} versionLoggerListener.vm.vendor=JVM.供应商: {0} versionLoggerListener.vm.version=JVM 版本: {0} diff -Nru tomcat9-9.0.27/java/org/apache/catalina/startup/Tomcat.java tomcat9-9.0.31/java/org/apache/catalina/startup/Tomcat.java --- tomcat9-9.0.27/java/org/apache/catalina/startup/Tomcat.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/startup/Tomcat.java 2020-02-05 19:26:48.000000000 +0000 @@ -18,13 +18,11 @@ import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; -import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.security.Principal; @@ -119,12 +117,17 @@ * this class. * *

    - * This class provides a set of convenience methods for configuring webapp - * contexts, all overloads of the method addWebapp. These methods - * create a webapp context, configure it, and then add it to a {@link Host}. - * They do not use a global default web.xml; rather, they add a lifecycle - * listener that adds the standard DefaultServlet, JSP processing, and welcome - * files. + * This class provides a set of convenience methods for configuring web + * application contexts; all overloads of the method addWebapp(). + * These methods are equivalent to adding a web application to the Host's + * appBase (normally the webapps directory). These methods create a Context, + * configure it with the equivalent of the defaults provided by + * conf/web.xml (see {@link #initWebappDefaults(String)} for + * details) and add the Context to a Host. These methods do not use a global + * default web.xml; rather, they add a {@link LifecycleListener} to configure + * the defaults. Any WEB-INF/web.xml and META-INF/context.xml packaged with the + * application will be processed normally. Normal web fragment and + * {@link javax.servlet.ServletContainerInitializer} processing will be applied. * *

    * In complex cases, you may prefer to use the ordinary Tomcat API to create @@ -223,17 +226,22 @@ hostname = s; } + /** - * This is equivalent to adding a web application to Tomcat's webapps - * directory. The equivalent of the default web.xml will be applied to the - * web application and any WEB-INF/web.xml and META-INF/context.xml packaged - * with the application will be processed normally. Normal web fragment and - * {@link javax.servlet.ServletContainerInitializer} processing will be - * applied. + * This is equivalent to adding a web application to a Host's appBase + * (usually Tomcat's webapps directory). By default, the equivalent of the + * default web.xml will be applied to the web application (see + * {@link #initWebappDefaults(String)}). This may be prevented by calling + * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any + * WEB-INF/web.xml and META-INF/context.xml + * packaged with the application will always be processed and normal web + * fragment and {@link javax.servlet.ServletContainerInitializer} processing + * will always be applied. * * @param contextPath The context mapping to use, "" for root context. - * @param docBase Base directory for the context, for static files. - * Must exist, relative to the server home + * @param docBase Base directory for the context, for static files. Must + * exist, relative to the server home + * * @return the deployed context */ public Context addWebapp(String contextPath, String docBase) { @@ -676,13 +684,24 @@ return ctx; } + /** - * @param host The host in which the context will be deployed + * This is equivalent to adding a web application to a Host's appBase + * (usually Tomcat's webapps directory). By default, the equivalent of the + * default web.xml will be applied to the web application (see + * {@link #initWebappDefaults(String)}). This may be prevented by calling + * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any + * WEB-INF/web.xml and META-INF/context.xml + * packaged with the application will always be processed and normal web + * fragment and {@link javax.servlet.ServletContainerInitializer} processing + * will always be applied. + * + * @param host The host in which the context will be deployed * @param contextPath The context mapping to use, "" for root context. - * @param docBase Base directory for the context, for static files. - * Must exist, relative to the server home + * @param docBase Base directory for the context, for static files. Must + * exist, relative to the server home + * * @return the deployed context - * @see #addWebapp(String, String) */ public Context addWebapp(Host host, String contextPath, String docBase) { LifecycleListener listener = null; @@ -698,14 +717,27 @@ return addWebapp(host, contextPath, docBase, listener); } + /** - * @param host The host in which the context will be deployed + * This is equivalent to adding a web application to a Host's appBase + * (usually Tomcat's webapps directory). By default, the equivalent of the + * default web.xml will be applied to the web application (see + * {@link #initWebappDefaults(String)}). This may be prevented by calling + * {@link #setAddDefaultWebXmlToWebapp(boolean)} with {@code false}. Any + * WEB-INF/web.xml and META-INF/context.xml + * packaged with the application will always be processed and normal web + * fragment and {@link javax.servlet.ServletContainerInitializer} processing + * will always be applied. + * + * @param host The host in which the context will be deployed * @param contextPath The context mapping to use, "" for root context. - * @param docBase Base directory for the context, for static files. - * Must exist, relative to the server home - * @param config Custom context configurator helper + * @param docBase Base directory for the context, for static files. Must + * exist, relative to the server home + * @param config Custom context configuration helper. Any configuration + * will be in addition to equivalent of the default + * web.xml configuration described above. + * * @return the deployed context - * @see #addWebapp(String, String) */ public Context addWebapp(Host host, String contextPath, String docBase, LifecycleListener config) { @@ -716,8 +748,9 @@ ctx.setPath(contextPath); ctx.setDocBase(docBase); - if (addDefaultWebXmlToWebapp) + if (addDefaultWebXmlToWebapp) { ctx.addLifecycleListener(getDefaultWebXmlListener()); + } ctx.setConfigFile(getWebappConfigFile(docBase, contextPath)); @@ -1007,22 +1040,32 @@ } } + /** - * Provide default configuration for a context. This is the programmatic - * equivalent of the default web.xml. + * Provide default configuration for a context. This is broadly the + * programmatic equivalent of the default web.xml and provides the following + * features: + *

      + *
    • Default servlet mapped to "/"
    • + *
    • JSP servlet mapped to "*.jsp" and ""*.jspx"
    • + *
    • Session timeout of 30 minutes
    • + *
    • MIME mappings (subset of those in conf/web.xml)
    • + *
    • Welcome files
    • + *
    + * TODO: Align the MIME mappings with conf/web.xml - possibly via a common + * file. * - * TODO: in normal Tomcat, if default-web.xml is not found, use this - * method - * - * @param contextPath The context to set the defaults for + * @param contextPath The path of the context to set the defaults for */ public void initWebappDefaults(String contextPath) { Container ctx = getHost().findChild(contextPath); initWebappDefaults((Context) ctx); } + /** - * Static version of {@link #initWebappDefaults(String)} + * Static version of {@link #initWebappDefaults(String)}. + * * @param ctx The context to set the defaults for */ public static void initWebappDefaults(Context ctx) { @@ -1415,33 +1458,8 @@ org.apache.catalina.startup.Tomcat tomcat = new org.apache.catalina.startup.Tomcat(); // Create a Catalina instance and let it parse the configuration files // It will also set a shutdown hook to stop the Server when needed - tomcat.init(new ConfigurationSource() { - protected final File userDir = new File(System.getProperty("user.dir")); - protected final URI userDirUri = userDir.toURI(); - @Override - public Resource getResource(String name) throws IOException { - File f = new File(name); - if (!f.isAbsolute()) { - f = new File(userDir, name); - } - if (f.isFile()) { - return new Resource(new FileInputStream(f), f.toURI()); - } else { - throw new FileNotFoundException(name); - } - } - @Override - public URI getURI(String name) { - File f = new File(name); - if (!f.isAbsolute()) { - f = new File(userDir, name); - } - if (f.isFile()) { - return f.toURI(); - } - return userDirUri.resolve(name); - } - }); + // Use the default configuration source + tomcat.init(null); boolean await = false; String path = ""; // Process command line parameters diff -Nru tomcat9-9.0.27/java/org/apache/catalina/startup/WebappServiceLoader.java tomcat9-9.0.31/java/org/apache/catalina/startup/WebappServiceLoader.java --- tomcat9-9.0.27/java/org/apache/catalina/startup/WebappServiceLoader.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/startup/WebappServiceLoader.java 2020-02-05 19:26:48.000000000 +0000 @@ -34,6 +34,7 @@ import javax.servlet.ServletContext; import org.apache.catalina.Context; +import org.apache.catalina.WebResource; import org.apache.tomcat.util.scan.JarFactory; /** @@ -58,6 +59,7 @@ * @see java.util.ServiceLoader */ public class WebappServiceLoader { + private static final String CLASSES = "/WEB-INF/classes/"; private static final String LIB = "/WEB-INF/lib/"; private static final String SERVICES = "META-INF/services/"; @@ -94,15 +96,28 @@ LinkedHashSet applicationServicesFound = new LinkedHashSet<>(); LinkedHashSet containerServicesFound = new LinkedHashSet<>(); - ClassLoader loader = servletContext.getClassLoader(); - // if the ServletContext has ORDERED_LIBS, then use that to specify the // set of JARs from WEB-INF/lib that should be used for loading services @SuppressWarnings("unchecked") - List orderedLibs = - (List) servletContext.getAttribute(ServletContext.ORDERED_LIBS); - if (orderedLibs != null) { - // handle ordered libs directly, ... + List orderedLibs = (List) servletContext.getAttribute(ServletContext.ORDERED_LIBS); + + // Handle application SCIs directly... + if (orderedLibs == null) { + // No ordered libs, so use every service definition we can find + WebResource[] resources = context.getResources().getClassLoaderResources("/" + configFile); + for (WebResource resource : resources) { + if (resource.isFile()) { + parseConfigFile(applicationServicesFound, resource.getURL()); + } + } + } else { + // Ordered libs so only use services defined in those libs and any + // in WEB-INF/classes + URL unpacked = servletContext.getResource(CLASSES + configFile); + if (unpacked != null) { + parseConfigFile(applicationServicesFound, unpacked); + } + for (String lib : orderedLibs) { URL jarUrl = servletContext.getResource(LIB + lib); if (jarUrl == null) { @@ -123,11 +138,11 @@ // no provider file found, this is OK } } - - // and the parent ClassLoader for all others - loader = context.getParentClassLoader(); } + // and use the parent ClassLoader for all other SCIs + ClassLoader loader = context.getParentClassLoader(); + Enumeration resources; if (loader == null) { resources = ClassLoader.getSystemResources(configFile); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/storeconfig/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/storeconfig/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/storeconfig/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/storeconfig/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -14,3 +14,10 @@ # limitations under the License. config.objectNameNotFound=目标[{0}]未找到 + +registry.loadClassFailed=无法加载类 [{0}] + +standardContextSF.cannotWriteFile=无法在 [{0}] 写入文件 + +storeFileMover.directoryCreationError=无法创建目录 [{0}] +storeFileMover.renameError=无法将 [{0}] 重命名为 [{1}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/group/GroupChannel.java tomcat9-9.0.31/java/org/apache/catalina/tribes/group/GroupChannel.java --- tomcat9-9.0.27/java/org/apache/catalina/tribes/group/GroupChannel.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/group/GroupChannel.java 2020-02-05 19:26:48.000000000 +0000 @@ -166,7 +166,7 @@ * channel.addInterceptor(A);
    * channel.addInterceptor(C);
    * channel.addInterceptor(B);
    - * Will result in a interceptor stack like this:
    + * Will result in an interceptor stack like this:
    * A -> C -> B
    * The complete stack will look like this:
    * Channel -> A -> C -> B -> ChannelCoordinator
    diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java tomcat9-9.0.31/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java --- tomcat9-9.0.27/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/group/interceptors/EncryptInterceptor.java 2020-02-05 19:26:48.000000000 +0000 @@ -349,9 +349,6 @@ return new BaseEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName); -// else if("ECB".equalsIgnoreCase(algorithmMode)) { - // Note: ECB is not an appropriate mode for secure communications. -// return new ECBEncryptionManager(algorithm, new SecretKeySpec(encryptionKey, algorithmName), providerName); else throw new IllegalArgumentException(sm.getString("encryptInterceptor.algorithm.unsupported-mode", algorithmMode)); } @@ -601,32 +598,6 @@ } } - @SuppressWarnings("unused") - private static class ECBEncryptionManager extends BaseEncryptionManager - { - public ECBEncryptionManager(String algorithm, SecretKeySpec secretKey, String providerName) - throws NoSuchAlgorithmException, NoSuchPaddingException, NoSuchProviderException { - super(algorithm, secretKey, providerName); - } - - private static final byte[] EMPTY_IV = new byte[0]; - - @Override - protected int getIVSize() { - return 0; - } - - @Override - protected byte[] generateIVBytes() { - return EMPTY_IV; - } - - @Override - protected AlgorithmParameterSpec generateIV(byte[] bytes, int offset, int length) { - return null; - } - } - static class ChannelConfigException extends ChannelException { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/group/interceptors/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/group/interceptors/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/group/interceptors/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/group/interceptors/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -17,7 +17,9 @@ domainFilterInterceptor.message.refused=从集群[{0}]中接收的消息被拒绝 encryptInterceptor.decrypt.error.short-message=解密消息失败: 结尾消息提前结束 +encryptInterceptor.decrypt.failed=无法解密信息 encryptInterceptor.encrypt.failed=无法加密信息 +encryptInterceptor.init.failed=初始化EncryptInterceptor失败 encryptInterceptor.tcpFailureDetector.ordering=加密拦截器必须位于TCP故障检测器的上游。请重新订购加密拦截器,将其列在通道拦截器管道中的TCP故障检测器之前。 gzipInterceptor.report=:)GZip 拦截器报告[\n\ @@ -39,6 +41,7 @@ messageDispatchInterceptor.unableAdd.queue=无法将消息添加到异步队列,队列 bug? messageDispatchInterceptor.warning.optionflag=警告!你正在覆盖异步选项标志,这将禁用其它程序可能用到的 Channel.SEND_OPTIONS_ASYNCHRONOUS。 +nonBlockingCoordinator.heartbeat.inconsistency=心跳发现不一致,重新启动选举 nonBlockingCoordinator.memberAlive.failed=无法执行成员活动检查,猜测成员下线。 nonBlockingCoordinator.processCoordinationMessage.failed=处理协调消息时出错。 可能是致命的。 @@ -49,6 +52,7 @@ tcpFailureDetector.failureDetection.failed=无法进行失败监测,假定成员宕机。[{0}] tcpFailureDetector.heartbeat.failed=TCP心跳检测器无法执行心跳 tcpFailureDetector.member.disappeared=认证完成。成员消失[{0}] +tcpFailureDetector.memberDisappeared.verify=(:收到的membermissed[{0}]消息。将验证。 tcpFailureDetector.still.alive=验证完成。成员 [{0}] 仍然存活 tcpFailureDetector.suspectMember.alive=验证可疑成员服务器还活着。[{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/group/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/group/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/group/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/group/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -17,5 +17,9 @@ channelCoordinator.invalid.startLevel=启动级别无效,有效级别为:SND_RX_SEQ,SND_TX_SEQ,MBR_TX_SEQ,MBR_RX_SEQ groupChannel.listener.alreadyExist=侦听器已存在:[{0}][{1}] +groupChannel.noDestination=没有指定目的地 +groupChannel.nullMessage=无法发送空消息 +groupChannel.optionFlag.conflict=拦截器选项标志冲突:[{0}] +groupChannel.unable.deserialize=无法反序列化消息:[{0}] groupChannel.unable.sendHeartbeat=无法通过Tribes拦截器堆栈发送心跳。 会再试一次。 groupChannel.warn.noUtilityExecutor=没有公共的executor 被设置时,创建一个 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java tomcat9-9.0.31/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java --- tomcat9-9.0.27/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/membership/cloud/CloudMembershipService.java 2020-02-05 19:26:48.000000000 +0000 @@ -147,7 +147,7 @@ public void setLocalMemberProperties(String listenHost, int listenPort, int securePort, int udpPort) { if (log.isDebugEnabled()) { log.debug(String.format("setLocalMemberProperties(%s, %d, %d, %d)", listenHost, - Integer.toString(listenPort), Integer.toString(securePort), Integer.toString(udpPort))); + Integer.valueOf(listenPort), Integer.valueOf(securePort), Integer.valueOf(udpPort))); } properties.setProperty("tcpListenHost", listenHost); properties.setProperty("tcpListenPort", String.valueOf(listenPort)); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/membership/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/membership/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/membership/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/membership/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,12 +13,20 @@ # See the License for the specific language governing permissions and # limitations under the License. +McastService.parseSoTimeout=无法解析SoTimeout:[{0}] +McastService.payload=无法发送负载更新 + mcastService.missing.property=McastService:缺少必需属性 [{0}]。 mcastServiceImpl.bind=尝试将多播套接字绑定到 [{0}:{1}] +mcastServiceImpl.bind.failed=绑定到多播地址失败。仅绑定到端口。 +mcastServiceImpl.error.receiving=接收mcast包时出错。睡眠500毫秒 mcastServiceImpl.invalid.startLevel=无效的启动级别。只接受以下级别:Channel.MBR_RX_SEQ或 Channel.MBR_TX_SEQ +mcastServiceImpl.invalid.stopLevel=无效的停止级别。只有Channel.MBR_RX_SEQ和Channel.MBR_TX_SEQ是可接受的级别 mcastServiceImpl.recovery=家族成员,运行恢复线程,广播不是功能。 mcastServiceImpl.recovery.stopFailed=恢复线程未能停止成员服务。 +mcastServiceImpl.recovery.successful=成员身份恢复成功。 +mcastServiceImpl.send.failed=无法发送多播信息 mcastServiceImpl.send.running=McastService.send已经运行 mcastServiceImpl.setInterface=设置多宿主多播接口为:[{0}] mcastServiceImpl.setSoTimeout=设置集群多播超时时间:[{0}] @@ -26,12 +34,14 @@ mcastServiceImpl.unable.join=无法加入多播组,请确保你的系统已启用多播。 mcastServiceImpl.unableReceive.broadcastMessage=无法接收广播消息。 -memberImpl.large.payload=负载太大对于处理许多... +memberImpl.large.payload=负载太大以至于难以处理 memberImpl.notEnough.bytes=成员包中的字节不够。 +memberImpl.package.small=成员包太小以至于不能校验。 staticMember.invalid.uuidLength=UUID必须正好是16个字节,而不是:[{0}] staticMembershipProvider.leftOver.ignored=消息 [{0}] 被忽略。 +staticMembershipProvider.pingThread.failed=无法发送ping。 staticMembershipProvider.startMembership.noReplies=0响应,可能超时 staticMembershipProvider.stopMembership.sendFailed=无法发送成员消息 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/tipis/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/tipis/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/tipis/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/tipis/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -15,14 +15,20 @@ abstractReplicatedMap.broadcast.noReplies=广播收到0回复,可能是超时了。 abstractReplicatedMap.leftOver.ignored=消息[{0}]被忽略 +abstractReplicatedMap.member.disappeared=成员[{0}]已消失。相关的映射项将重新定位到新节点。 abstractReplicatedMap.transferState.noReplies=传输状态,0响应,也许是超时。 abstractReplicatedMap.unable.get=无法复制 AbstractReplicatedMap.get 操作的数据 +abstractReplicatedMap.unable.put=无法复制AbstractReplicatedMap.Put操作的数据 +abstractReplicatedMap.unable.relocate=无法将[{0}]重新定位到新的备份节点 abstractReplicatedMap.unableSelect.backup=无法选择备用节点 abstractReplicatedMap.unableSend.startMessage=无法发送map启动消息。 +abstractReplicatedMap.unableStart=无法启动复制Map lazyReplicatedMap.unableReplicate.proxy=不能复制proxy key:[{0}]到备份:[{1}]. 原因是:[{2}] +mapMessage.deserialize.error.key=反序列化MapMessage主键失败 mapMessage.deserialize.error.value=MapMessage.value的反序列化误差 +replicatedMap.member.disappeared=成员[{0}]消失,关联的键值实体会重新关联到一个新的节点。 replicatedMap.relocate.complete=map 条目的重定位在 [{0}] ms内完成。 replicatedMap.unable.relocate=不能为一个新的备份节点重启定位[{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/bio/LocalStrings_pt_BR.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/bio/LocalStrings_pt_BR.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/bio/LocalStrings_pt_BR.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/bio/LocalStrings_pt_BR.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,4 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +bioReceiver.socket.closeFailed=Falha ao encerrar a conexão do socket + bioSender.send.again=Enviar dados novamente para [{0}:{1,number,integer}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/bio/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/bio/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/bio/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/bio/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -21,8 +21,12 @@ bioReplicationTask.socket.closeFailed=无法关闭套接字 bioReplicationTask.unable.service=不能服务bio套接字 +bioSender.ack.eof=在本地端口[{0}:{1,number,integer}]达到EOF bioSender.ack.missing=不能读确认表格:[{0}] {1,number,integer}] in {2,number,integer} 毫秒, 失去socket连接, 重试连接. bioSender.ack.wrong=在本地端口[{0}:{1,number,integer}]读取10个字节后丢失正确的ACK bioSender.closeSocket=发件人关闭套接字到[{0}:{1,number,integer}](关闭计数{2,数字,整数}) +bioSender.fail.AckReceived=收到一个失败的 ack ):org.apache.catalina.tribes.transport.Constants.FAIL_ACK_DATA bioSender.openSocket=发件人打开套接字到[{0}:{1,number,integer}](打开计数{2,数字,整数}) bioSender.send.again=再次发送数据到 [{0}:{1,number,integer}] + +pooledMultiSender.unable.retrieve.sender=无法获取数据发送器,超时([{0}] ms)错误 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/nio/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/nio/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/nio/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/nio/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -20,13 +20,17 @@ nioReceiver.run.fail=不能允许复制监听器 nioReceiver.start.fail=无法启动集群接收器 nioReceiver.stop.fail=无法关闭集群接收的选择器 +nioReceiver.stop.threadRunning=NioReceiver线程没有及时停止。关闭选择器时可能会观察到错误。 nioReceiver.threadpool.fail=ThreadPool 无法初始化。 监听器未启动。 nioReplicationTask.error.register.key=错误的注册key被读取:[{0}] nioReplicationTask.process.clusterMsg.failed=处理集群消息失败 nioReplicationTask.unable.ack=不能通过channel发送ack,channel已经断开?[{0}] +nioSender.datagram.already.established=数据报通道已经建立。连接可能正在进行中。 nioSender.not.connected=NioSender未连接,这是不应该发生的。 +nioSender.sender.disconnected=发件人已断开连接,无法处理选择密钥。 +nioSender.unable.receive.ack=无法接收确认消息。已到达套接字通道上的EOF。 nioSender.unknown.state=数据处于未知状态。readyOps = [{0}] parallelNioSender.send.fail.retrying=成员发送失败:[{0}]; 设置为怀疑并重试。 @@ -35,4 +39,5 @@ parallelNioSender.sender.disconnected.sendFailed=发送失败且sender已断开连接,不再重试。 pooledParallelSender.sender.disconnected=sender 未连接。 +pooledParallelSender.unable.open=无法打开nio选择器。 pooledParallelSender.unable.retrieveSender=无法从sender池中获取一个sender diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java --- tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java 2020-02-05 19:26:48.000000000 +0000 @@ -209,7 +209,6 @@ } } return result; - } private static class SendResult { @@ -372,4 +371,4 @@ if ( result ) try { selector.selectNow(); }catch (Exception e){/*Ignore*/} return result; } -} \ No newline at end of file +} diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/ReceiverBase.java tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/ReceiverBase.java --- tomcat9-9.0.27/java/org/apache/catalina/tribes/transport/ReceiverBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/transport/ReceiverBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -220,10 +220,10 @@ /** * Same as bind() except it does it for the UDP port - * @param socket The socket to bind - * @param portstart Starting port for bind attempts - * @param retries Number of times to attempt to bind (port incremented - * between attempts) + * @param socket The socket to bind + * @param portstart Starting port for bind attempts + * @param retries Number of times to attempt to bind (port incremented + * between attempts) * @return int The retry count * @throws IOException Socket bind error */ @@ -603,4 +603,4 @@ this.maxIdleTime = maxIdleTime; } -} \ No newline at end of file +} diff -Nru tomcat9-9.0.27/java/org/apache/catalina/tribes/util/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/tribes/util/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/tribes/util/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/tribes/util/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -17,3 +17,4 @@ arrays.length.outOfBounds=当前key下没有足够的元素,长度越界 executorFactory.not.running=执行器没有运行,无法强制把命令送入队列 +executorFactory.queue.full=队列已满 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/util/LifecycleBase.java tomcat9-9.0.31/java/org/apache/catalina/util/LifecycleBase.java --- tomcat9-9.0.27/java/org/apache/catalina/util/LifecycleBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/util/LifecycleBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -432,8 +432,8 @@ private void handleSubClassException(Throwable t, String key, Object... args) throws LifecycleException { - ExceptionUtils.handleThrowable(t); setStateInternal(LifecycleState.FAILED, null, false); + ExceptionUtils.handleThrowable(t); String msg = sm.getString(key, args); if (getThrowOnFailure()) { if (!(t instanceof LifecycleException)) { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/util/LocalStrings_pt_BR.properties tomcat9-9.0.31/java/org/apache/catalina/util/LocalStrings_pt_BR.properties --- tomcat9-9.0.27/java/org/apache/catalina/util/LocalStrings_pt_BR.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/util/LocalStrings_pt_BR.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,4 +13,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -customObjectInputStream.nomatch=A classe [{0}] não casou com a expressão regular [{1}] para classes permitidas para deserialização +customObjectInputStream.nomatch=A classe [{0}] não combina com a expressão regular [{1}] para classes permitidas para deserialização diff -Nru tomcat9-9.0.27/java/org/apache/catalina/util/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/util/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/util/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/util/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -23,9 +23,15 @@ introspection.classLoadFailed=加载 class [{0}] 失败 +lifecycleBase.alreadyStarted=在调用start()之后,在组件[{0}]上调用start()方法。第二个电话将被忽略。 +lifecycleBase.destroyStopFail=在失败组件[{0}]上调用Stop()以触发清理,但也失败了 lifecycleBase.initFail=初始化组件[{0}]失败。 +lifecycleBase.setState=设置状态从[{0}]到[{1}] + +lifecycleMBeanBase.registerFail=在组件初始化期间,无法注册名为{1}的对象{0}] netmask.cidrNegative=CIDR [{0}]为负数。 netmask.invalidAddress=地址 [{0}] 无效 +sessionIdGeneratorBase.randomAlgorithm=使用算法[{0}]初始化随机数生成器时发生异常 sessionIdGeneratorBase.randomProvider=使用程序提供的初始化随机数生成器异常[{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/util/TomcatCSS.java tomcat9-9.0.31/java/org/apache/catalina/util/TomcatCSS.java --- tomcat9-9.0.27/java/org/apache/catalina/util/TomcatCSS.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/util/TomcatCSS.java 2020-02-05 19:26:48.000000000 +0000 @@ -22,15 +22,13 @@ public class TomcatCSS { public static final String TOMCAT_CSS = - "h1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} " + - "h2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} " + - "h3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} " + - "body {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} " + - "b {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} " + - "p {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;} " + + "body {font-family:Tahoma,Arial,sans-serif;} " + + "h1, h2, h3, b {color:white;background-color:#525D76;} " + + "h1 {font-size:22px;} " + + "h2 {font-size:16px;} " + + "h3 {font-size:14px;} " + + "p {font-size:12px;} " + "a {color:black;} " + - "a.name {color:black;} " + ".line {height:1px;background-color:#525D76;border:none;}"; - -} \ No newline at end of file +} diff -Nru tomcat9-9.0.27/java/org/apache/catalina/valves/AbstractAccessLogValve.java tomcat9-9.0.31/java/org/apache/catalina/valves/AbstractAccessLogValve.java --- tomcat9-9.0.27/java/org/apache/catalina/valves/AbstractAccessLogValve.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/valves/AbstractAccessLogValve.java 2020-02-05 19:26:48.000000000 +0000 @@ -456,7 +456,13 @@ protected AccessLogElement[] logElements = null; /** - * Should this valve set request attributes for IP address, hostname, + * Array of elements where the value needs to be cached at the start of the + * request. + */ + protected CachedElement[] cachedElements = null; + + /** + * Should this valve use request attributes for IP address, hostname, * protocol and port used for the request. * Default is false. * @see #setRequestAttributesEnabled(boolean) @@ -563,6 +569,7 @@ this.pattern = pattern; } logElements = createLogElements(); + cachedElements = createCachedElements(logElements); } /** @@ -675,6 +682,9 @@ // to be cached in the request. request.getAttribute(Globals.CERTIFICATES_ATTR); } + for (CachedElement element : cachedElements) { + element.cache(request); + } getNext().invoke(request, response); } @@ -797,7 +807,20 @@ protected interface AccessLogElement { public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time); + } + /** + * Marks an AccessLogElement as needing to be have the value cached at the + * start of the request rather than just recorded at the end as the source + * data for the element may not be available at the end of the request. This + * typically occurs for remote network information, such as ports, IP + * addresses etc. when the connection is closed unexpectedly. These elements + * take advantage of these values being cached elsewhere on first request + * and do not cache the value in the element since the elements are + * state-less. + */ + protected interface CachedElement { + public void cache(Request request); } /** @@ -849,7 +872,7 @@ /** * write remote IP address - %a */ - protected class RemoteAddrElement implements AccessLogElement { + protected class RemoteAddrElement implements AccessLogElement, CachedElement { @Override public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time) { @@ -870,12 +893,19 @@ } buf.append(value); } + + @Override + public void cache(Request request) { + if (!requestAttributesEnabled) { + request.getRemoteAddr(); + } + } } /** * write remote host name - %h */ - protected class HostElement implements AccessLogElement { + protected class HostElement implements AccessLogElement, CachedElement { @Override public void addElement(CharArrayWriter buf, Date date, Request request, Response response, long time) { @@ -898,6 +928,13 @@ } buf.append(value); } + + @Override + public void cache(Request request) { + if (!requestAttributesEnabled) { + request.getRemoteHost(); + } + } } /** @@ -1183,7 +1220,7 @@ /** * write local or remote port for request connection - %p and %{xxx}p */ - protected class PortElement implements AccessLogElement { + protected class PortElement implements AccessLogElement, CachedElement { /** * Type of port to log @@ -1230,6 +1267,13 @@ } } } + + @Override + public void cache(Request request) { + if (portType == PortType.REMOTE) { + request.getRemotePort(); + } + } } /** @@ -1668,6 +1712,18 @@ return list.toArray(new AccessLogElement[0]); } + + private CachedElement[] createCachedElements(AccessLogElement[] elements) { + List list = new ArrayList<>(); + for (AccessLogElement element : elements) { + if (element instanceof CachedElement) { + list.add((CachedElement) element); + } + } + return list.toArray(new CachedElement[0]); + } + + /** * Create an AccessLogElement implementation which needs an element name. * @param name Header name diff -Nru tomcat9-9.0.27/java/org/apache/catalina/valves/AccessLogValve.java tomcat9-9.0.31/java/org/apache/catalina/valves/AccessLogValve.java --- tomcat9-9.0.27/java/org/apache/catalina/valves/AccessLogValve.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/valves/AccessLogValve.java 2020-02-05 19:26:48.000000000 +0000 @@ -610,10 +610,10 @@ // Log this message try { + message.write(System.lineSeparator()); synchronized(this) { if (writer != null) { message.writeTo(writer); - writer.println(""); if (!buffered) { writer.flush(); } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/valves/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/valves/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/valves/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/valves/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -18,10 +18,13 @@ accessLogValve.invalidPortType=端口类型 [{0}] 无效,使用服务器(本地)端口 accessLogValve.openFail=无法打开访问日志文件[{0}]。 accessLogValve.rotateFail=失败的循环切割访问日志. +accessLogValve.writeFail=无法写入日志消息[{0}] errorReportValve.description=描述 +errorReportValve.errorPageNotFound=在[{0}]无法找到静态错误页面 errorReportValve.exceptionReport=异常报告 errorReportValve.message=消息 +errorReportValve.note=):注意 errorReportValve.rootCauseInLogs=主要问题的全部 stack 信息可以在 server logs 里查看 errorReportValve.unknownReason=未知的原因 @@ -46,34 +49,48 @@ http.409.desc=由于和目标资源对当前状态发生冲突,所以请求无法完成。 http.409.reason=冲突 http.410.desc=原始服务器上不再可以访问目标资源,并且此条件可能是永久性的。 +http.411.reason=所需长度 http.412.desc=在服务器上测试时,请求头字段中给出的一个或多个条件被评估为false。 http.412.reason=前置条件失败 http.413.reason=有效载荷过大 http.414.desc=服务器拒绝为请求提供服务,因为请求目标比服务器愿意解释的要长。 http.415.desc=源服务器拒绝服务请求,因为有效负载的格式在目标资源上此方法不支持。 http.415.reason=不支持的媒体类型 +http.416.desc=(:请求的范围头字段中的任何范围都没有与选定资源的当前范围重叠,或者请求的范围集由于无效范围或小范围或重叠范围的过度请求而被拒绝。 http.416.reason=范围不满足 +http.417.desc=(:至少有一个入站服务器无法满足请求的Expect头字段中给定的期望。 http.417.reason=期望的失败 http.421.desc=请求被定向到一台无法响应的服务器 http.423.desc=源或目标资源的方法被锁 http.423.reason=已锁定 +http.424.desc=这个方法不能在这个资源上执行,因为请求操作依赖另一个操作,但是另一个操作失败了。 +http.424.reason=失败的依赖项 +http.426.desc=服务器拒绝使用当前协议执行请求,但可能愿意在客户端升级到其他协议后执行。 http.426.reason=需要升级 http.428.desc=原始服务器要求请求是有条件的。 http.429.reason=请求过多 http.431.reason=请求头的字段太大 +http.451.desc=服务器出于法律原因拒绝了此请求。 http.500.desc=服务器遇到一个意外的情况,阻止它完成请求。 http.502.desc=服务器在充当网关或代理时, 在尝试完成请求时, 从它访问的入站服务器收到无效响应。 http.503.desc=由于临时过载或计划维护,服务器当前无法处理请求,这可能会在一些延迟后得到缓解。 +http.504.desc=服务器在充当网关或代理时,没有从上游服务器接收到完成请求所需访问的及时响应。 +http.504.reason=网关超时 http.505.reason=HTTP 版本不支持 +http.506.desc=服务器内部配置错误:选取的变体资源配置为自身去处理透明的内容协商,因此在协商进程中不是一个合适的终点。 +http.507.desc=无法对资源执行该方法,因为服务器无法存储成功完成请求所需的表示。 http.507.reason=存储空间.不足 http.510.reason=没有.扩展 http.511.desc=客户端需要进行身份验证才能获得网络访问权限。 remoteCidrValve.noRemoteIp=客户端没有IP地址。请求被拒绝。 +remoteIpValve.invalidHostHeader=在HTTP请求头[{1}]中发现Host的无效值[{0}] remoteIpValve.invalidPortHeader=HTTP标头[{1}]中的端口找到的值[{0}]无效 requestFilterValve.configInvalid=为Remote [Addr | Host]阀门提供了一个或多个无效配置设置,阻止Valve及其父容器启动 requestFilterValve.deny=根据[{1}]配置拒绝[{0}]的请求 sslValve.invalidProvider=与此{[0}]请求关联的连接器上指定的SSL提供程序无效。 无法处理证书数据。 + +stuckThreadDetectionValve.notifyStuckThreadDetected=线程[{0}](id=[{6}])已处于活动状态[{1}]毫秒(自[{2}]起),以便为[{4}]提供相同的请求,并且可能被卡住(此StuckThreadDetectionValve的配置阈值为[{5}]秒)。总共有{3}个线程受此阀监视,可能被卡住。 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/LocalStrings_zh_CN.properties 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,16 @@ +# 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. + +rewriteValve.readError=读取配置时发生异常 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/QuotedStringTokenizer.java tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/QuotedStringTokenizer.java --- tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/QuotedStringTokenizer.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/QuotedStringTokenizer.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,135 @@ +/* + * 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.valves.rewrite; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +public class QuotedStringTokenizer { + + private Iterator tokenIterator; + private int tokenCount; + private int returnedTokens = 0; + + enum WordMode { + SPACES, QUOTED, ESCAPED, SIMPLE, COMMENT + } + + public QuotedStringTokenizer(String text) { + List tokens; + if (text != null) { + tokens = tokenizeText(text); + } else { + tokens = Collections.emptyList(); + } + this.tokenCount = tokens.size(); + this.tokenIterator = tokens.iterator(); + } + + private List tokenizeText(String inputText) { + List tokens = new ArrayList<>(); + int pos = 0; + int length = inputText.length(); + WordMode currentMode = WordMode.SPACES; + StringBuilder currentToken = new StringBuilder(); + while (pos < length) { + char currentChar = inputText.charAt(pos); + switch (currentMode) { + case SPACES: + currentMode = handleSpaces(currentToken, currentChar); + break; + case QUOTED: + currentMode = handleQuoted(tokens, currentToken, currentChar); + break; + case ESCAPED: + currentToken.append(currentChar); + currentMode = WordMode.QUOTED; + break; + case SIMPLE: + currentMode = handleSimple(tokens, currentToken, currentChar); + break; + case COMMENT: + if (currentChar == '\r' || currentChar == '\n') { + currentMode = WordMode.SPACES; + } + break; + default: + throw new IllegalStateException( + "Couldn't tokenize text '" + inputText + "' after position " + pos + " from mode " + currentMode); + } + pos++; + } + String possibleLastToken = currentToken.toString(); + if (!possibleLastToken.isEmpty()) { + tokens.add(possibleLastToken); + } + return tokens; + } + + private WordMode handleSimple(List tokens, StringBuilder currentToken, char currentChar) { + if (Character.isWhitespace(currentChar)) { + tokens.add(currentToken.toString()); + currentToken.setLength(0); + return WordMode.SPACES; + } else { + currentToken.append(currentChar); + } + return WordMode.SIMPLE; + } + + private WordMode handleQuoted(List tokens, StringBuilder currentToken, char currentChar) { + if (currentChar == '"') { + tokens.add(currentToken.toString()); + currentToken.setLength(0); + return WordMode.SPACES; + } else if (currentChar == '\\') { + return WordMode.ESCAPED; + } else { + currentToken.append(currentChar); + } + return WordMode.QUOTED; + } + + private WordMode handleSpaces(StringBuilder currentToken, char currentChar) { + if (!Character.isWhitespace(currentChar)) { + if (currentChar == '"') { + return WordMode.QUOTED; + } else if (currentChar == '#') { + return WordMode.COMMENT; + } else { + currentToken.append(currentChar); + return WordMode.SIMPLE; + } + } + return WordMode.SPACES; + } + + public boolean hasMoreTokens() { + return tokenIterator.hasNext(); + } + + public String nextToken() { + returnedTokens++; + return tokenIterator.next(); + } + + public int countTokens() { + return tokenCount - returnedTokens; + } +} diff -Nru tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/RewriteMap.java tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/RewriteMap.java --- tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/RewriteMap.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/RewriteMap.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,9 +16,58 @@ */ package org.apache.catalina.valves.rewrite; +/** + * Interface for user defined lookup/replacement logic that can be defined in + * a {@code rewrite.config} file by a {@code RewriteMap} directive. Such a map + * can then be used by a {@code RewriteRule} defined in the same file. + *

    + * An example {@code rewrite.config} file could look like: + *

    + * RewriteMap uc example.UpperCaseMap
    + *
    + * RewriteRule ^/(.*)$ ${uc:$1}
    + * 
    + * + * One parameter can be optionally appended to the {@code RewriteMap} directive. + * This could be used – for example – to specify a name of a file, that + * contains a lookup table used by the implementation of the map. + */ public interface RewriteMap { + /** + * Optional parameter that can be defined through the {@code RewriteMap} + * directive in the {@code rewrite.config} file. + * + * @param params the optional parameter + * @return value is currently ignored + */ public String setParameters(String params); + /** + * Optional parameters that can be defined through the {@code RewriteMap} + * directive in the {@code rewrite.config} file. + *

    + * This method will be called, if there are more than one parameters defined. + * + * @param params the optional parameters + */ + default void setParameters(String... params) { + if (params == null) { + return; + } + if (params.length > 1) { + throw new IllegalArgumentException("Too many parameters for this map"); + } + setParameters(params[0]); + } + + /** + * Maps a key to a replacement value.
    + * The method is free to return {@code null} to indicate, that the default + * value from the {@code RewriteRule} directive should be used. + * + * @param key used by the actual implementation to generate a mapped value + * @return mapped value or {@code null} + */ public String lookup(String key); } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/RewriteValve.java tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/RewriteValve.java --- tomcat9-9.0.27/java/org/apache/catalina/valves/rewrite/RewriteValve.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/valves/rewrite/RewriteValve.java 2020-02-05 19:26:48.000000000 +0000 @@ -572,7 +572,7 @@ * @return The condition, rule or map resulting from parsing the line */ public static Object parse(String line) { - StringTokenizer tokenizer = new StringTokenizer(line); + QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(line); if (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.equals("RewriteCond")) { diff -Nru tomcat9-9.0.27/java/org/apache/catalina/WebResourceRoot.java tomcat9-9.0.31/java/org/apache/catalina/WebResourceRoot.java --- tomcat9-9.0.27/java/org/apache/catalina/WebResourceRoot.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/WebResourceRoot.java 2020-02-05 19:26:48.000000000 +0000 @@ -140,7 +140,8 @@ * application. It must start with '/'. * * @return The objects that represents the class loader resources at the - * given path + * given path. There will always be at least one element although + * that element may represent a resource that is not present. */ WebResource[] getClassLoaderResources(String path); diff -Nru tomcat9-9.0.27/java/org/apache/catalina/webresources/CachedResource.java tomcat9-9.0.31/java/org/apache/catalina/webresources/CachedResource.java --- tomcat9-9.0.27/java/org/apache/catalina/webresources/CachedResource.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/webresources/CachedResource.java 2020-02-05 19:26:48.000000000 +0000 @@ -17,13 +17,28 @@ package org.apache.catalina.webresources; import java.io.ByteArrayInputStream; +import java.io.IOException; import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.nio.charset.Charset; +import java.security.Permission; import java.security.cert.Certificate; +import java.text.Collator; +import java.util.Arrays; +import java.util.Locale; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; import java.util.jar.Manifest; import org.apache.catalina.WebResource; import org.apache.catalina.WebResourceRoot; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; +import org.apache.tomcat.util.res.StringManager; /** * This class is designed to wrap a 'raw' WebResource and providing caching for @@ -32,6 +47,9 @@ */ public class CachedResource implements WebResource { + private static final Log log = LogFactory.getLog(CachedResource.class); + private static final StringManager sm = StringManager.getManager(CachedResource.class); + // Estimate (on high side to be safe) of average size excluding content // based on profiler data. private static final long CACHE_ENTRY_SIZE = 500; @@ -314,7 +332,48 @@ @Override public URL getURL() { - return webResource.getURL(); + /* + * We don't want applications using this URL to access the resource + * directly as that could lead to inconsistent results when the resource + * is updated on the file system but the cache entry has not yet + * expired. We saw this, for example, in JSP compilation. + * - last modified time was obtained via + * ServletContext.getResource("path").openConnection().getLastModified() + * - JSP content was obtained via + * ServletContext.getResourceAsStream("path") + * The result was that the JSP modification was detected but the JSP + * content was read from the cache so the non-updated JSP page was + * used to generate the .java and .class file + * + * One option to resolve this issue is to use a custom URL scheme for + * resource URLs. This would allow us, via registration of a + * URLStreamHandlerFactory, to control how the resources are accessed + * and ensure that all access go via the cache We took this approach for + * war: URLs so we can use jar:war:file: URLs to reference resources in + * unpacked WAR files. However, because URL.setURLStreamHandlerFactory() + * may only be caused once, this can cause problems when using other + * libraries that also want to use a custom URL scheme. + * + * The approach below allows us to insert a custom URLStreamHandler + * without registering a custom protocol. The only limitation (compared + * to registering a custom protocol) is that if the application + * constructs the same URL from a String, they will access the resource + * directly and not via the cache. + */ + URL resourceURL = webResource.getURL(); + if (resourceURL == null) { + return null; + } + try { + CachedResourceURLStreamHandler handler = + new CachedResourceURLStreamHandler(resourceURL, root, webAppPath, usesClassLoaderResources); + URL result = new URL(null, resourceURL.toExternalForm(), handler); + handler.setAssociatedURL(result); + return result; + } catch (MalformedURLException e) { + log.error(sm.getString("cachedResource.invalidURL", resourceURL.toExternalForm()), e); + return null; + } } @Override @@ -355,4 +414,188 @@ } return result; } + + + /* + * Mimics the behaviour of FileURLConnection.getInputStream for a directory. + * Deliberately uses default locale. + */ + private static InputStream buildInputStream(String[] files) { + Arrays.sort(files, Collator.getInstance(Locale.getDefault())); + StringBuilder result = new StringBuilder(); + for (String file : files) { + result.append(file); + // Every entry is followed by \n including the last + result.append('\n'); + } + return new ByteArrayInputStream(result.toString().getBytes(Charset.defaultCharset())); + } + + + private static class CachedResourceURLStreamHandler extends URLStreamHandler { + + private final URL resourceURL; + private final StandardRoot root; + private final String webAppPath; + private final boolean usesClassLoaderResources; + + private URL associatedURL = null; + + public CachedResourceURLStreamHandler(URL resourceURL, StandardRoot root, String webAppPath, + boolean usesClassLoaderResources) { + this.resourceURL = resourceURL; + this.root = root; + this.webAppPath = webAppPath; + this.usesClassLoaderResources = usesClassLoaderResources; + } + + protected void setAssociatedURL(URL associatedURL) { + this.associatedURL = associatedURL; + } + + @Override + protected URLConnection openConnection(URL u) throws IOException { + // This deliberately uses ==. If u isn't the URL object this + // URLStreamHandler was constructed for we do not want to use this + // URLStreamHandler to create a connection. + if (associatedURL != null && u == associatedURL) { + if ("jar".equals(associatedURL.getProtocol())) { + return new CachedResourceJarURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources); + } else { + return new CachedResourceURLConnection(resourceURL, root, webAppPath, usesClassLoaderResources); + } + } else { + // The stream handler has been inherited by a URL that was + // constructed from a cache URL. We need to break that link. + URL constructedURL = new URL(u.toExternalForm()); + return constructedURL.openConnection(); + } + } + } + + + /* + * Keep this in sync with CachedResourceJarURLConnection. + */ + private static class CachedResourceURLConnection extends URLConnection { + + private final StandardRoot root; + private final String webAppPath; + private final boolean usesClassLoaderResources; + private final URL resourceURL; + + protected CachedResourceURLConnection(URL resourceURL, StandardRoot root, String webAppPath, + boolean usesClassLoaderResources) { + super(resourceURL); + this.root = root; + this.webAppPath = webAppPath; + this.usesClassLoaderResources = usesClassLoaderResources; + this.resourceURL = resourceURL; + } + + @Override + public void connect() throws IOException { + // NO-OP + } + + @Override + public InputStream getInputStream() throws IOException { + WebResource resource = getResource(); + if (resource.isDirectory()) { + return buildInputStream(resource.getWebResourceRoot().list(webAppPath)); + } else { + return getResource().getInputStream(); + } + } + + @Override + public Permission getPermission() throws IOException { + // Doesn't trigger a call to connect for file:// URLs + return resourceURL.openConnection().getPermission(); + } + + @Override + public long getLastModified() { + return getResource().getLastModified(); + } + + @Override + public long getContentLengthLong() { + return getResource().getContentLength(); + } + + private WebResource getResource() { + return root.getResource(webAppPath, false, usesClassLoaderResources); + } + } + + + /* + * Keep this in sync with CachedResourceURLConnection. + */ + private static class CachedResourceJarURLConnection extends JarURLConnection { + + private final StandardRoot root; + private final String webAppPath; + private final boolean usesClassLoaderResources; + private final URL resourceURL; + + protected CachedResourceJarURLConnection(URL resourceURL, StandardRoot root, String webAppPath, + boolean usesClassLoaderResources) throws IOException { + super(resourceURL); + this.root = root; + this.webAppPath = webAppPath; + this.usesClassLoaderResources = usesClassLoaderResources; + this.resourceURL = resourceURL; + } + + @Override + public void connect() throws IOException { + // NO-OP + } + + @Override + public InputStream getInputStream() throws IOException { + WebResource resource = getResource(); + if (resource.isDirectory()) { + return buildInputStream(resource.getWebResourceRoot().list(webAppPath)); + } else { + return getResource().getInputStream(); + } + } + + @Override + public Permission getPermission() throws IOException { + // Doesn't trigger a call to connect for jar:// URLs + return resourceURL.openConnection().getPermission(); + } + + @Override + public long getLastModified() { + return getResource().getLastModified(); + } + + @Override + public long getContentLengthLong() { + return getResource().getContentLength(); + } + + private WebResource getResource() { + return root.getResource(webAppPath, false, usesClassLoaderResources); + } + + @Override + public JarFile getJarFile() throws IOException { + return ((JarURLConnection) resourceURL.openConnection()).getJarFile(); + } + + @Override + public JarEntry getJarEntry() throws IOException { + if (getEntryName() == null) { + return null; + } else { + return super.getJarEntry(); + } + } + } } diff -Nru tomcat9-9.0.27/java/org/apache/catalina/webresources/LocalStrings_ko.properties tomcat9-9.0.31/java/org/apache/catalina/webresources/LocalStrings_ko.properties --- tomcat9-9.0.27/java/org/apache/catalina/webresources/LocalStrings_ko.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/webresources/LocalStrings_ko.properties 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,8 @@ cache.objectMaxSizeTooBig=objectMaxSize를 위한 값 [{0}]kB이, maxSize/20인 최대한계값 보다 커서, [{1}]kB로 줄여졌습니다. cache.objectMaxSizeTooBigBytes=[{0}]kB를 캐시하기 위해, 최대 객체 크기로서 지정된 값이 Integer.MAX_VALUE 바이트보다 큰데, Integer.MAX_VALUE는 캐시될 수 있는 최대 크기입니다. 한계 값을 Integer.MAX_VALUE 바이트로 설정하겠습니다. +cachedResource.invalidURL=URL [{0}]이(가) 유효하지 않기 때문에 CachedResourceURLStreamHandler 인스턴스를 생성할 수 없습니다. + classpathUrlStreamHandler.notFound=쓰레드 컨텍스트 클래스로더 또는 현재 클래스의 클래스로더를 사용하여, 리소스 [{0}]을(를) 로드할 수 없습니다. dirResourceSet.manifestFail=[{0}](으)로부터 manifest를 읽지 못했습니다. diff -Nru tomcat9-9.0.27/java/org/apache/catalina/webresources/LocalStrings.properties tomcat9-9.0.31/java/org/apache/catalina/webresources/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/catalina/webresources/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/webresources/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,8 @@ cache.objectMaxSizeTooBig=The value of [{0}]kB for objectMaxSize is larger than the limit of maxSize/20 so has been reduced to [{1}]kB cache.objectMaxSizeTooBigBytes=The value specified for the maximum object size to cache [{0}]kB is greater than Integer.MAX_VALUE bytes which is the maximum size that can be cached. The limit will be set to Integer.MAX_VALUE bytes. +cachedResource.invalidURL=Unable to create an instance of CachedResourceURLStreamHandler because the URL [{0}] is malformed + classpathUrlStreamHandler.notFound=Unable to load the resource [{0}] using the thread context class loader or the current class''s class loader dirResourceSet.manifestFail=Failed to read manifest from [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/catalina/webresources/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/catalina/webresources/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/catalina/webresources/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/webresources/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +abstractArchiveResourceSet.setReadOnlyFalse=基于存档的WebResourceSets 如基于jar的WebResourceSets 硬编码为只读,并且不能配置为读写 + cache.addFail=无法将位于[{0}]的资源添加到Web应用程序[{1}]的缓存中,因为在清除过期缓存条目后可用空间仍不足 - 请考虑增加缓存的最大空间。 dirResourceSet.notDirectory=基本和内部路径[{0}] {1} [{2}]指定的目录不存在。 @@ -20,9 +22,14 @@ extractingRoot.jarFailed=解压JAR文件[{0}]失败 extractingRoot.targetFailed=无法为提取的 JAR 文件创建目录 [{0}] +fileResource.getCanonicalPathFail=不能判断资源的标准路径[{0}] fileResource.getUrlFail=不能决定一个url 为资源[{0}] +jarResource.getInputStreamFail=无法获取JAR[{1}]中的资源文件[{0}]的一个InputStream + standardRoot.checkStateNotStarted=如果当前未启动资源,则可能无法访问这些资源 standardRoot.createUnknownType=无法为未知类型[{0}]创建WebResourceSet。 +standardRoot.invalidPathNormal=资源路径[{0}]已规范化为无效的[{1}] +standardRoot.lockedFile=Web应用程序[{0}]无法关闭通过以下堆栈跟踪打开的文件[{1}] standardRoot.noContext=尚未为WebResourceRoot配置上下文 standardRoot.startInvalidMain=指定的主资源集 [{0}] 无效 diff -Nru tomcat9-9.0.27/java/org/apache/catalina/webresources/StandardRoot.java tomcat9-9.0.31/java/org/apache/catalina/webresources/StandardRoot.java --- tomcat9-9.0.27/java/org/apache/catalina/webresources/StandardRoot.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/catalina/webresources/StandardRoot.java 2020-02-05 19:26:48.000000000 +0000 @@ -207,7 +207,7 @@ return getResource(path, true, false); } - private WebResource getResource(String path, boolean validate, + protected WebResource getResource(String path, boolean validate, boolean useClassLoaderResources) { if (validate) { path = validate(path); diff -Nru tomcat9-9.0.27/java/org/apache/coyote/AbstractProcessor.java tomcat9-9.0.31/java/org/apache/coyote/AbstractProcessor.java --- tomcat9-9.0.27/java/org/apache/coyote/AbstractProcessor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/AbstractProcessor.java 2020-02-05 19:26:48.000000000 +0000 @@ -98,7 +98,9 @@ * @param t The error which occurred */ protected void setErrorState(ErrorState errorState, Throwable t) { - response.setError(); + // Use the return value to avoid processing more than one async error + // in a single async cycle. + boolean setError = response.setError(); boolean blockIo = this.errorState.isIoAllowed() && !errorState.isIoAllowed(); this.errorState = this.errorState.getMostSevere(errorState); // Don't change the status code for IOException since that is almost @@ -110,17 +112,10 @@ if (t != null) { request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, t); } - if (blockIo && !ContainerThreadMarker.isContainerThread() && isAsync()) { - // The error occurred on a non-container thread during async - // processing which means not all of the necessary clean-up will - // have been completed. Dispatch to a container thread to do the - // clean-up. Need to do it this way to ensure that all the necessary - // clean-up is performed. - asyncStateMachine.asyncMustError(); - if (getLog().isDebugEnabled()) { - getLog().debug(sm.getString("abstractProcessor.nonContainerThreadError"), t); + if (blockIo && isAsync() && setError) { + if (asyncStateMachine.asyncError()) { + processSocketEvent(SocketEvent.ERROR, true); } - processSocketEvent(SocketEvent.ERROR, true); } } @@ -252,15 +247,25 @@ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED); + SocketState state; + if (getErrorState().isError()) { request.updateCounters(); - return SocketState.CLOSED; + state = SocketState.CLOSED; } else if (isAsync()) { - return SocketState.LONG; + state = SocketState.LONG; } else { request.updateCounters(); - return dispatchEndRequest(); + state = dispatchEndRequest(); + } + + if (getLog().isDebugEnabled()) { + getLog().debug("Socket: [" + socketWrapper + + "], Status in: [" + status + + "], State out: [" + state + "]"); } + + return state; } diff -Nru tomcat9-9.0.27/java/org/apache/coyote/AbstractProcessorLight.java tomcat9-9.0.31/java/org/apache/coyote/AbstractProcessorLight.java --- tomcat9-9.0.27/java/org/apache/coyote/AbstractProcessorLight.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/AbstractProcessorLight.java 2020-02-05 19:26:48.000000000 +0000 @@ -46,19 +46,18 @@ do { if (dispatches != null) { DispatchType nextDispatch = dispatches.next(); + if (getLog().isDebugEnabled()) { + getLog().debug("Processing dispatch type: [" + nextDispatch + "]"); + } state = dispatch(nextDispatch.getSocketStatus()); + if (!dispatches.hasNext()) { + state = checkForPipelinedData(state, socketWrapper); + } } else if (status == SocketEvent.DISCONNECT) { // Do nothing here, just wait for it to get recycled } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) { state = dispatch(status); - if (state == SocketState.OPEN) { - // There may be pipe-lined data to read. If the data isn't - // processed now, execution will exit this loop and call - // release() which will recycle the processor (and input - // buffer) deleting any pipe-lined data. To avoid this, - // process it now. - state = service(socketWrapper); - } + state = checkForPipelinedData(state, socketWrapper); } else if (status == SocketEvent.OPEN_WRITE) { // Extra write event likely after async, ignore state = SocketState.LONG; @@ -98,6 +97,21 @@ } + private SocketState checkForPipelinedData(SocketState inState, SocketWrapperBase socketWrapper) + throws IOException { + if (inState == SocketState.OPEN) { + // There may be pipe-lined data to read. If the data isn't + // processed now, execution will exit this loop and call + // release() which will recycle the processor (and input + // buffer) deleting any pipe-lined data. To avoid this, + // process it now. + return service(socketWrapper); + } else { + return inState; + } + } + + public void addDispatch(DispatchType dispatchType) { synchronized (dispatches) { dispatches.add(dispatchType); diff -Nru tomcat9-9.0.27/java/org/apache/coyote/AbstractProtocol.java tomcat9-9.0.31/java/org/apache/coyote/AbstractProtocol.java --- tomcat9-9.0.27/java/org/apache/coyote/AbstractProtocol.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/AbstractProtocol.java 2020-02-05 19:26:48.000000000 +0000 @@ -19,7 +19,7 @@ import java.net.InetAddress; import java.nio.ByteBuffer; import java.util.Collections; -import java.util.Map; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -391,11 +391,17 @@ public void addWaitingProcessor(Processor processor) { + if (getLog().isDebugEnabled()) { + getLog().debug(sm.getString("abstractProcotol.waitingProcerssor.add", processor)); + } waitingProcessors.add(processor); } public void removeWaitingProcessor(Processor processor) { + if (getLog().isDebugEnabled()) { + getLog().debug(sm.getString("abstractProcotol.waitingProcerssor.remove", processor)); + } waitingProcessors.remove(processor); } @@ -731,7 +737,6 @@ private final AbstractProtocol proto; private final RequestGroupInfo global = new RequestGroupInfo(); private final AtomicLong registerCount = new AtomicLong(0); - private final Map connections = new ConcurrentHashMap<>(); private final RecycledProcessors recycledProcessors = new RecycledProcessors(this); public ConnectionHandler(AbstractProtocol proto) { @@ -770,7 +775,7 @@ S socket = wrapper.getSocket(); - Processor processor = connections.get(socket); + Processor processor = (Processor) wrapper.getCurrentProcessor(); if (getLog().isDebugEnabled()) { getLog().debug(sm.getString("abstractConnectionHandler.connectionsGet", processor, socket)); @@ -805,11 +810,12 @@ // OpenSSL typically returns null whereas JSSE typically // returns "" when no protocol is negotiated if (negotiatedProtocol != null && negotiatedProtocol.length() > 0) { - UpgradeProtocol upgradeProtocol = - getProtocol().getNegotiatedProtocol(negotiatedProtocol); + UpgradeProtocol upgradeProtocol = getProtocol().getNegotiatedProtocol(negotiatedProtocol); if (upgradeProtocol != null) { - processor = upgradeProtocol.getProcessor( - wrapper, getProtocol().getAdapter()); + processor = upgradeProtocol.getProcessor(wrapper, getProtocol().getAdapter()); + if (getLog().isDebugEnabled()) { + getLog().debug(sm.getString("abstractConnectionHandler.processorCreate", processor)); + } } else if (negotiatedProtocol.equals("http/1.1")) { // Explicitly negotiated the default protocol. // Obtain a processor below. @@ -822,9 +828,8 @@ // replace the code below with the commented out // block. if (getLog().isDebugEnabled()) { - getLog().debug(sm.getString( - "abstractConnectionHandler.negotiatedProcessor.fail", - negotiatedProtocol)); + getLog().debug(sm.getString("abstractConnectionHandler.negotiatedProcessor.fail", + negotiatedProtocol)); } return SocketState.CLOSED; /* @@ -841,20 +846,22 @@ if (processor == null) { processor = recycledProcessors.pop(); if (getLog().isDebugEnabled()) { - getLog().debug(sm.getString("abstractConnectionHandler.processorPop", - processor)); + getLog().debug(sm.getString("abstractConnectionHandler.processorPop", processor)); } } if (processor == null) { processor = getProtocol().createProcessor(); register(processor); + if (getLog().isDebugEnabled()) { + getLog().debug(sm.getString("abstractConnectionHandler.processorCreate", processor)); + } } processor.setSslSupport( wrapper.getSslSupport(getProtocol().getClientCertProvider())); // Associate the processor with the connection - connections.put(socket, processor); + wrapper.setCurrentProcessor(processor); SocketState state = SocketState.CLOSED; do { @@ -873,7 +880,7 @@ wrapper, getProtocol().getAdapter()); wrapper.unRead(leftOverInput); // Associate with the processor with the connection - connections.put(socket, processor); + wrapper.setCurrentProcessor(processor); } else { if (getLog().isDebugEnabled()) { getLog().debug(sm.getString( @@ -896,7 +903,7 @@ // Mark the connection as upgraded wrapper.setUpgraded(true); // Associate with the processor with the connection - connections.put(socket, processor); + wrapper.setCurrentProcessor(processor); // Initialise the upgrade handler (which may trigger // some IO using the new protocol which is why the lines // above are necessary) @@ -934,7 +941,7 @@ } else if (state == SocketState.OPEN) { // In keep-alive but between requests. OK to recycle // processor. Continue to poll for the next request. - connections.remove(socket); + wrapper.setCurrentProcessor(null); release(processor); wrapper.registerReadInterest(); } else if (state == SocketState.SENDFILE) { @@ -960,7 +967,7 @@ // Connection closed. OK to recycle the processor. // Processors handling upgrades require additional clean-up // before release. - connections.remove(socket); + wrapper.setCurrentProcessor(null); if (processor.isUpgrade()) { UpgradeToken upgradeToken = processor.getUpgradeToken(); HttpUpgradeHandler httpUpgradeHandler = upgradeToken.getHttpUpgradeHandler(); @@ -1020,7 +1027,7 @@ // Make sure socket/processor is removed from the list of current // connections - connections.remove(socket); + wrapper.setCurrentProcessor(null); release(processor); return SocketState.CLOSED; } @@ -1040,7 +1047,15 @@ @Override public Set getOpenSockets() { - return connections.keySet(); + Set> set = proto.getEndpoint().getConnections(); + Set result = new HashSet<>(); + for (SocketWrapperBase socketWrapper : set) { + S socket = socketWrapper.getSocket(); + if (socket != null) { + result.add(socket); + } + } + return result; } @@ -1071,7 +1086,9 @@ // recycledProcessors since that pool is only for AJP or // HTTP processors recycledProcessors.push(processor); - getLog().debug("Pushed Processor [" + processor + "]"); + if (getLog().isDebugEnabled()) { + getLog().debug("Pushed Processor [" + processor + "]"); + } } } } @@ -1083,8 +1100,8 @@ */ @Override public void release(SocketWrapperBase socketWrapper) { - S socket = socketWrapper.getSocket(); - Processor processor = connections.remove(socket); + Processor processor = (Processor) socketWrapper.getCurrentProcessor(); + socketWrapper.setCurrentProcessor(null); release(processor); } @@ -1104,13 +1121,13 @@ ",name=" + getProtocol().getProtocolName() + "Request" + count); if (getLog().isDebugEnabled()) { - getLog().debug("Register " + rpName); + getLog().debug("Register [" + processor + "] as [" + rpName + "]"); } Registry.getRegistry(null, null).registerComponent(rp, rpName, null); rp.setRpName(rpName); } catch (Exception e) { - getLog().warn("Error registering request"); + getLog().warn(sm.getString("abstractProtocol.processorRegisterError"), e); } } } @@ -1129,13 +1146,13 @@ rp.setGlobalProcessor(null); ObjectName rpName = rp.getRpName(); if (getLog().isDebugEnabled()) { - getLog().debug("Unregister " + rpName); + getLog().debug("Unregister [" + rpName + "]"); } Registry.getRegistry(null, null).unregisterComponent( rpName); rp.setRpName(null); } catch (Exception e) { - getLog().warn("Error unregistering request", e); + getLog().warn(sm.getString("abstractProtocol.processorUnregisterError"), e); } } } @@ -1152,8 +1169,11 @@ * Note that even if the endpoint is resumed, there is (currently) * no API to inform the Processors of this. */ - for (Processor processor : connections.values()) { - processor.pause(); + for (SocketWrapperBase wrapper : proto.getEndpoint().getConnections()) { + Processor processor = (Processor) wrapper.getCurrentProcessor(); + if (processor != null) { + processor.pause(); + } } } } diff -Nru tomcat9-9.0.27/java/org/apache/coyote/ajp/AbstractAjpProtocol.java tomcat9-9.0.31/java/org/apache/coyote/ajp/AbstractAjpProtocol.java --- tomcat9-9.0.27/java/org/apache/coyote/ajp/AbstractAjpProtocol.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/ajp/AbstractAjpProtocol.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,6 +16,9 @@ */ package org.apache.coyote.ajp; +import java.net.InetAddress; +import java.util.regex.Pattern; + import org.apache.coyote.AbstractProtocol; import org.apache.coyote.Processor; import org.apache.coyote.UpgradeProtocol; @@ -46,6 +49,8 @@ setConnectionTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT); // AJP does not use Send File getEndpoint().setUseSendfile(false); + // AJP listens on loopback by default + getEndpoint().setAddress(InetAddress.getLoopbackAddress()); ConnectionHandler cHandler = new ConnectionHandler<>(this); setHandler(cHandler); getEndpoint().setHandler(cHandler); @@ -139,17 +144,60 @@ } - private String requiredSecret = null; + private String secret = null; + /** + * Set the secret that must be included with every request. + * + * @param secret The required secret + */ + public void setSecret(String secret) { + this.secret = secret; + } + protected String getSecret() { + return secret; + } /** * Set the required secret that must be included with every request. * * @param requiredSecret The required secret + * + * @deprecated Replaced by {@link #setSecret(String)}. + * Will be removed in Tomcat 11 onwards */ + @Deprecated public void setRequiredSecret(String requiredSecret) { - this.requiredSecret = requiredSecret; + setSecret(requiredSecret); } + /** + * @return The current secret + * + * @deprecated Replaced by {@link #getSecret()}. + * Will be removed in Tomcat 11 onwards + */ + @Deprecated protected String getRequiredSecret() { - return requiredSecret; + return getSecret(); + } + + + private boolean secretRequired = true; + public void setSecretRequired(boolean secretRequired) { + this.secretRequired = secretRequired; + } + public boolean getSecretRequired() { + return secretRequired; + } + + + private Pattern allowedRequestAttributesPattern; + public void setAllowedRequestAttributesPattern(String allowedRequestAttributesPattern) { + this.allowedRequestAttributesPattern = Pattern.compile(allowedRequestAttributesPattern); + } + public String getAllowedRequestAttributesPattern() { + return allowedRequestAttributesPattern.pattern(); + } + protected Pattern getAllowedRequestAttributesPatternInternal() { + return allowedRequestAttributesPattern; } @@ -206,4 +254,16 @@ throw new IllegalStateException(sm.getString("ajpprotocol.noUpgradeHandler", upgradeToken.getHttpUpgradeHandler().getClass().getName())); } + + + @Override + public void start() throws Exception { + if (getSecretRequired()) { + String secret = getSecret(); + if (secret == null || secret.length() == 0) { + throw new IllegalArgumentException(sm.getString("ajpprotocol.nosecret")); + } + } + super.start(); + } } diff -Nru tomcat9-9.0.27/java/org/apache/coyote/ajp/AjpProcessor.java tomcat9-9.0.31/java/org/apache/coyote/ajp/AjpProcessor.java --- tomcat9-9.0.27/java/org/apache/coyote/ajp/AjpProcessor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/ajp/AjpProcessor.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,11 @@ import java.security.NoSuchProviderException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.servlet.http.HttpServletResponse; @@ -78,6 +83,9 @@ private static final byte[] pongMessageArray; + private static final Set javaxAttributes; + + static { // Allocate the end message array AjpMessage endMessage = new AjpMessage(16); @@ -118,6 +126,14 @@ pongMessageArray = new byte[pongMessage.getLen()]; System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray, 0, pongMessage.getLen()); + + // Build the Set of javax attributes + Set s = new HashSet<>(); + s.add("javax.servlet.request.cipher_suite"); + s.add("javax.servlet.request.key_size"); + s.add("javax.servlet.request.ssl_session"); + s.add("javax.servlet.request.X509Certificate"); + javaxAttributes= Collections.unmodifiableSet(s); } @@ -313,15 +329,18 @@ this.socketWrapper = socket; boolean cping = false; - boolean keptAlive = false; + // Expected to block on the first read as there should be at least one + // AJP message to read. + boolean firstRead = true; while (!getErrorState().isError() && !protocol.isPaused()) { // Parsing the request header try { // Get first message of the request - if (!readMessage(requestHeaderMessage, !keptAlive)) { + if (!readMessage(requestHeaderMessage, firstRead)) { break; } + firstRead = false; // Processing the request so make sure the connection rather // than keep-alive timeout is used @@ -340,6 +359,9 @@ socketWrapper.write(true, pongMessageArray, 0, pongMessageArray.length); socketWrapper.flush(true); } catch (IOException e) { + if (getLog().isDebugEnabled()) { + getLog().debug("Pong message failed", e); + } setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e); } recycle(); @@ -353,7 +375,6 @@ setErrorState(ErrorState.CLOSE_CONNECTION_NOW, null); break; } - keptAlive = true; request.setStartTime(System.currentTimeMillis()); } catch (IOException e) { setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e); @@ -693,8 +714,8 @@ } // Decode extra attributes - String requiredSecret = protocol.getRequiredSecret(); - boolean secret = false; + String secret = protocol.getSecret(); + boolean secretPresentInRequest = false; byte attributeCode; while ((attributeCode = requestHeaderMessage.getByte()) != Constants.SC_A_ARE_DONE) { @@ -723,8 +744,26 @@ } } else if(n.equals(Constants.SC_A_SSL_PROTOCOL)) { request.setAttribute(SSLSupport.PROTOCOL_VERSION_KEY, v); + } else if (n.equals("JK_LB_ACTIVATION")) { + request.setAttribute(n, v); + } else if (javaxAttributes.contains(n)) { + request.setAttribute(n, v); } else { - request.setAttribute(n, v ); + // All 'known' attributes will be processed by the previous + // blocks. Any remaining attribute is an 'arbitrary' one. + Pattern pattern = protocol.getAllowedRequestAttributesPatternInternal(); + if (pattern == null) { + response.setStatus(403); + setErrorState(ErrorState.CLOSE_CLEAN, null); + } else { + Matcher m = pattern.matcher(n); + if (m.matches()) { + request.setAttribute(n, v); + } else { + response.setStatus(403); + setErrorState(ErrorState.CLOSE_CLEAN, null); + } + } } break; @@ -796,9 +835,9 @@ case Constants.SC_A_SECRET: requestHeaderMessage.getBytes(tmpMB); - if (requiredSecret != null) { - secret = true; - if (!tmpMB.equals(requiredSecret)) { + if (secret != null) { + secretPresentInRequest = true; + if (!tmpMB.equals(secret)) { response.setStatus(403); setErrorState(ErrorState.CLOSE_CLEAN, null); } @@ -814,7 +853,7 @@ } // Check if secret was submitted if required - if ((requiredSecret != null) && !secret) { + if ((secret != null) && !secretPresentInRequest) { response.setStatus(403); setErrorState(ErrorState.CLOSE_CLEAN, null); } diff -Nru tomcat9-9.0.27/java/org/apache/coyote/ajp/LocalStrings.properties tomcat9-9.0.31/java/org/apache/coyote/ajp/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/coyote/ajp/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/ajp/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -28,5 +28,6 @@ ajpprocessor.request.process=Error processing request ajpprotocol.noSSL=SSL is not supported with AJP. The SSL host configuration for [{0}] was ignored +ajpprotocol.nosecret=The AJP Connector is configured with secretRequired="true" but the secret attribute is either null or "". This combination is not valid. ajpprotocol.noUpgrade=Upgrade is not supported with AJP. The UpgradeProtocol configuration for [{0}] was ignored ajpprotocol.noUpgradeHandler=Upgrade is not supported with AJP. The HttpUpgradeHandler [{0}] can not be processed diff -Nru tomcat9-9.0.27/java/org/apache/coyote/ajp/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/coyote/ajp/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/coyote/ajp/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/ajp/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -16,6 +16,8 @@ ajpmessage.null=不能赋空值 ajpmessage.overflow=在缓冲区[{1}]位置添加[{0}]字节时发生溢出错误 +ajpprocessor.certs.fail=):证书转换失败 ajpprocessor.header.error=头部信息解析失败 +ajpprocessor.readtimeout=从Socket读取数据超时 ajpprotocol.noUpgrade=AJP 不支持升级。[{0}] 的升级协议配置被忽略。 diff -Nru tomcat9-9.0.27/java/org/apache/coyote/AsyncStateMachine.java tomcat9-9.0.31/java/org/apache/coyote/AsyncStateMachine.java --- tomcat9-9.0.27/java/org/apache/coyote/AsyncStateMachine.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/AsyncStateMachine.java 2020-02-05 19:26:48.000000000 +0000 @@ -31,108 +31,91 @@ *

      * The internal states that are used are:
      * DISPATCHED       - Standard request. Not in Async mode.
    - * STARTING         - ServletRequest.startAsync() has been called but the
    - *                    request in which that call was made has not finished
    - *                    processing.
    - * STARTED          - ServletRequest.startAsync() has been called and the
    - *                    request in which that call was made has finished
    - *                    processing.
    + * STARTING         - ServletRequest.startAsync() has been called from
    + *                    Servlet.service() but service() has not exited.
    + * STARTED          - ServletRequest.startAsync() has been called from
    + *                    Servlet.service() and service() has exited.
      * READ_WRITE_OP    - Performing an asynchronous read or write.
      * MUST_COMPLETE    - ServletRequest.startAsync() followed by complete() have
      *                    been called during a single Servlet.service() method. The
    - *                    complete() will be processed as soon as the request
    - *                    finishes.
    - * COMPLETE_PENDING - ServletRequest.startAsync() has been called and before the
    - *                    request in which that call was had finished processing,
    - *                    complete() was called for a non-container thread. The
    - *                    complete() will be processed as soon as the request
    - *                    finishes. This is different to MUST_COMPLETE because of
    - *                    differences required to avoid race conditions during error
    - *                    handling.
    + *                    complete() will be processed as soon as Servlet.service()
    + *                    exits.
    + * COMPLETE_PENDING - ServletRequest.startAsync() has been called from
    + *                    Servlet.service() but, before service() exited, complete()
    + *                    was called from another thread. The complete() will
    + *                    be processed as soon as Servlet.service() exits.
      * COMPLETING       - The call to complete() was made once the request was in
    - *                    the STARTED state. May or may not be triggered by a
    - *                    container thread - depends if start(Runnable) was used.
    + *                    the STARTED state.
      * TIMING_OUT       - The async request has timed out and is waiting for a call
    - *                    to complete(). If that isn't made, the error state will
    - *                    entered.
    + *                    to complete() or dispatch(). If that isn't made, the error
    + *                    state will be entered.
      * MUST_DISPATCH    - ServletRequest.startAsync() followed by dispatch() have
      *                    been called during a single Servlet.service() method. The
    - *                    dispatch() will be processed as soon as the request
    - *                    finishes.
    - * DISPATCH_PENDING - ServletRequest.startAsync() has been called and before the
    - *                    request in which that call was had finished processing,
    - *                    dispatch() was called for a non-container thread. The
    - *                    dispatch() will be processed as soon as the request
    - *                    finishes. This is different to MUST_DISPATCH because of
    - *                    differences required to avoid race conditions during error
    - *                    handling.
    + *                    dispatch() will be processed as soon as Servlet.service()
    + *                    exits.
    + * DISPATCH_PENDING - ServletRequest.startAsync() has been called from
    + *                    Servlet.service() but, before service() exited, dispatch()
    + *                    was called from another thread. The dispatch() will
    + *                    be processed as soon as Servlet.service() exits.
      * DISPATCHING      - The dispatch is being processed.
    - * MUST_ERROR       - ServletRequest.startAsync() has been called followed by an
    - *                    I/O error on a non-container thread. The main purpose of
    - *                    this state is to prevent additional async actions
    - *                    (complete(), dispatch() etc.) on the non-container thread.
    - *                    The container will perform the necessary error handling,
    - *                    including ensuring that the AsyncLister.onError() method
    - *                    is called.
    + * MUST_ERROR       - ServletRequest.startAsync() has been called from
    + *                    Servlet.service() but, before service() exited, an I/O
    + *                    error occured on another thread. The container will
    + *                    perform the necessary error handling when
    + *                    Servlet.service() exits.
      * ERROR            - Something went wrong.
      *
    - *                           |-----«-------------------------------«------------------------------|
    - *                           |                                                                    |
    - *                           |      error()                                                       |
    - * |-----------------»---|   |  |--«--------MUST_ERROR---------------«------------------------|   |
    - * |                    \|/ \|/\|/                                                            |   |
    - * |   |----------«-----E R R O R--«-----------------------«-------------------------------|  |   |
    - * |   |      complete() /|\/|\\ \-«--------------------------------«-------|              |  |   |
    - * |   |                  |  |  \                                           |              |  |   |
    - * |   |    |-----»-------|  |   \-----------»----------|                   |              |  |   |
    - * |   |    |                |                          |dispatch()         |              |  ^   |
    - * |   |    |                |                         \|/                  ^              |  |   |
    - * |   |    |                |          |--|timeout()   |                   |              |  |   |
    - * |   |    |     post()     |          | \|/           |     post()        |              |  |   |
    - * |   |    |    |---------- | --»DISPATCHED«---------- | --------------COMPLETING«-----|  |  |   |
    - * |   |    |    |           |   /|\/|\ |               |                | /|\ /|\      |  |  |   |
    - * |   |    |    |    |---»- | ---|  |  |startAsync()   |       timeout()|--|   |       |  |  |   |
    - * |   |    ^    ^    |      |       |  |               |                       |       |  ^  |   |
    - * |   |    |    |    |   |-- \ -----|  |   complete()  |                       |post() |  |  |   |
    - * |   |    |    |    |   |    \        |     /--»----- | ---COMPLETE_PENDING-»-|       ^  |  |   |
    - * |   |    |    |    |   |     \       |    /          |                               |  |  |   |
    - * |   |    |    |    |   ^      \      |   /           |                    complete() |  |  |   |
    - * |  \|/   |    |    |   |       \    \|/ /   post()   |                     /---»-----|  |  ^   |
    - * | MUST_COMPLETE-«- | - | --«----STARTING--»--------- | ------------|      /             |  |   |
    - * |  /|\    /|\      |   |  complete()  | \            |             |     /   error()    |  |   ^
    - * |   |      |       |   |              |  \           |             |    //---»----------|  |   |
    - * |   |      |       ^   |    dispatch()|   \          |    post()   |   //                  |   |
    - * |   |      |       |   |              |    \         |    |-----|  |  //   nct-io-error    |   |
    - * |   |      |       |   |              |     \        |    |     |  | ///---»---------------|   |
    - * |   |      |       |   |             \|/     \       |    |    \|/\| |||                       |
    - * |   |      |       |   |--«--MUST_DISPATCH-----«-----|    |--«--STARTED«---------«---------|   |
    - * |   |      |       | dispatched() /|\   |      \               / |   |        post()       |   |
    - * |   |      |       |               |    |       \             /  |   |                     |   |
    - * |   |      |       |               |    |        \           /   |   |                     |   |
    - * |   |      |       |               |    |post()  |           |   |   |                     ^   |
    - * ^   |      ^       |               |    |       \|/          |   |   |asyncOperation()     |   |
    - * |   |      |       ^               |    |  DISPATCH_PENDING  |   |   |                     |   |
    - * |   |      |       |               |    |  |post()           |   |   |                     |   |
    - * |   |      |       |               |    |  |      |----------|   |   |»-READ_WRITE_OP--»---|   |
    - * |   |      |       |               |    |  |      |  dispatch()  |            |  |  |          |
    - * |   |      |       |               |    |  |      |              |            |  |  |          |
    - * |   |      |       |post()         |    |  |      |     timeout()|            |  |  |   error()|
    - * |   |      |       |dispatched()   |   \|/\|/    \|/             |  dispatch()|  |  |-»--------|
    - * |   |      |       |---«---------- | ---DISPATCHING«-----«------ | ------«----|  |
    - * |   |      |                       |     |    ^                  |               |
    - * |   |      |                       |     |----|                  |               |
    - * |   |      |                       |    timeout()                |               |
    - * |   |      |                       |                             |               |
    - * |   |      |                       |       dispatch()           \|/              |
    - * |   |      |                       |-----------«-----------TIMING_OUT            |
    - * |   |      |                                                 |   |               |
    - * |   |      |-------«----------------------------------«------|   |               |
    - * |   |                          complete()                        |               |
    - * |   |                                                            |               |
    - * |«- | ----«-------------------«-------------------------------«--|               |
    - *     |                           error()                                          |
    - *     |                                                  complete()                |
    - *     |----------------------------------------------------------------------------|
    + *
    + * The valid state transitions are:
    + *
    + *                  post()                                        dispatched()
    + *    |-------»------------------»---------|    |-------«-----------------------«-----|
    + *    |                                    |    |                                     |
    + *    |                                    |    |        post()                       |
    + *    |               post()              \|/  \|/       dispatched()                 |
    + *    |           |-----»----------------»DISPATCHED«-------------«-------------|     |
    + *    |           |                          | /|\ |                            |     |
    + *    |           |              startAsync()|  |--|timeout()                   |     |
    + *    ^           |                          |                                  |     |
    + *    |           |        complete()        |                  dispatch()      ^     |
    + *    |           |   |--«---------------«-- | ---«--MUST_ERROR--»-----|        |     |
    + *    |           |   |                      |         /|\             |        |     |
    + *    |           ^   |                      |          |              |        |     |
    + *    |           |   |                      |    /-----|error()       |        |     |
    + *    |           |   |                      |   /                     |        ^     |
    + *    |           |  \|/  ST-complete()     \|/ /   ST-dispatch()     \|/       |     |
    + *    |    MUST_COMPLETE«--------«--------STARTING--------»---------»MUST_DISPATCH    |
    + *    |                                    / | \                                      |
    + *    |                                   /  |  \                                     |
    + *    |                    OT-complete() /   |   \    OT-dispatch()                   |
    + *    |   COMPLETE_PENDING«------«------/    |    \-------»---------»DISPATCH_PENDING |
    + *    |          |                           |                           |            |
    + *    |    post()|   timeout()         post()|   post()            post()|  timeout() |
    + *    |          |   |--|                    |  |--|                     |    |--|    |
    + *    |         \|/ \|/ |   complete()      \|/\|/ |   dispatch()       \|/  \|/ |    |
    + *    |--«-----COMPLETING«--------«----------STARTED--------»---------»DISPATCHING----|
    + *            /|\  /|\ /|\                   | /|\ \                   /|\ /|\ /|\
    + *             |    |   |                    |  \   \asyncOperation()   |   |   |
    + *             |    |   |           timeout()|   \   \                  |   |   |
    + *             |    |   |                    |    \   \                 |   |   |
    + *             |    |   |                    |     \   \                |   |   |
    + *             |    |   |                    |      \   \               |   |   |
    + *             |    |   |                    |       \   \              |   |   |
    + *             |    |   |                    |  post()\   \   dispatch()|   |   |
    + *             |    |   |   complete()       |         \ \|/            |   |   |
    + *             |    |   |---«------------«-- | --«---READ_WRITE----»----|   |   |
    + *             |    |                        |                              |   |
    + *             |    |       complete()      \|/         dispatch()          |   |
    + *             |    |------------«-------TIMING_OUT--------»----------------|   |
    + *             |                                                                |
    + *             |            complete()                     dispatch()           |
    + *             |---------------«-----------ERROR--------------»-----------------|
    + *
    + *
    + * Notes: * For clarity, the transitions to ERROR which are valid from every state apart from
    + *          STARTING are not shown.
    + *        * All transitions may happen on either the Servlet.service() thread (ST) or on any
    + *          other thread (OT) unless explicitly marked.
      * 
    */ class AsyncStateMachine { @@ -278,10 +261,12 @@ */ synchronized SocketState asyncPostProcess() { if (state == AsyncState.COMPLETE_PENDING) { - doComplete(); + clearNonBlockingListeners(); + state = AsyncState.COMPLETING; return SocketState.ASYNC_END; } else if (state == AsyncState.DISPATCH_PENDING) { - doDispatch(); + clearNonBlockingListeners(); + state = AsyncState.DISPATCHING; return SocketState.ASYNC_END; } else if (state == AsyncState.STARTING || state == AsyncState.READ_WRITE_OP) { state = AsyncState.STARTED; @@ -312,27 +297,42 @@ if (!ContainerThreadMarker.isContainerThread() && state == AsyncState.STARTING) { state = AsyncState.COMPLETE_PENDING; return false; - } else { - return doComplete(); } - } - - private synchronized boolean doComplete() { clearNonBlockingListeners(); - boolean doComplete = false; - if (state == AsyncState.STARTING || state == AsyncState.TIMING_OUT || - state == AsyncState.ERROR || state == AsyncState.READ_WRITE_OP) { + boolean triggerDispatch = false; + if (state == AsyncState.STARTING || state == AsyncState.MUST_ERROR) { + // Processing is on a container thread so no need to transfer + // processing to a new container thread state = AsyncState.MUST_COMPLETE; - } else if (state == AsyncState.STARTED || state == AsyncState.COMPLETE_PENDING) { + } else if (state == AsyncState.STARTED) { + state = AsyncState.COMPLETING; + // A dispatch to a container thread is always required. + // If on a non-container thread, need to get back onto a container + // thread to complete the processing. + // If on a container thread the current request/response are not the + // request/response associated with the AsyncContext so need a new + // container thread to process the different request/response. + triggerDispatch = true; + } else if (state == AsyncState.READ_WRITE_OP || state == AsyncState.TIMING_OUT || + state == AsyncState.ERROR) { + // Read/write operations can happen on or off a container thread but + // while in this state the call to listener that triggers the + // read/write will be in progress on a container thread. + // Processing of timeouts and errors can happen on or off a + // container thread (on is much more likely) but while in this state + // the call that triggers the timeout will be in progress on a + // container thread. + // The socket will be added to the poller when the container thread + // exits the AbstractConnectionHandler.process() method so don't do + // a dispatch here which would add it to the poller a second time. state = AsyncState.COMPLETING; - doComplete = true; } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncComplete()", state)); } - return doComplete; + return triggerDispatch; } @@ -358,45 +358,42 @@ if (!ContainerThreadMarker.isContainerThread() && state == AsyncState.STARTING) { state = AsyncState.DISPATCH_PENDING; return false; - } else { - return doDispatch(); } - } - - private synchronized boolean doDispatch() { clearNonBlockingListeners(); - boolean doDispatch = false; - if (state == AsyncState.STARTING || - state == AsyncState.TIMING_OUT || - state == AsyncState.ERROR) { - // In these three cases processing is on a container thread so no - // need to transfer processing to a new container thread + boolean triggerDispatch = false; + if (state == AsyncState.STARTING || state == AsyncState.MUST_ERROR) { + // Processing is on a container thread so no need to transfer + // processing to a new container thread state = AsyncState.MUST_DISPATCH; - } else if (state == AsyncState.STARTED || state == AsyncState.DISPATCH_PENDING) { + } else if (state == AsyncState.STARTED) { state = AsyncState.DISPATCHING; - // A dispatch is always required. + // A dispatch to a container thread is always required. // If on a non-container thread, need to get back onto a container // thread to complete the processing. // If on a container thread the current request/response are not the // request/response associated with the AsyncContext so need a new // container thread to process the different request/response. - doDispatch = true; - } else if (state == AsyncState.READ_WRITE_OP) { + triggerDispatch = true; + } else if (state == AsyncState.READ_WRITE_OP || state == AsyncState.TIMING_OUT || + state == AsyncState.ERROR) { + // Read/write operations can happen on or off a container thread but + // while in this state the call to listener that triggers the + // read/write will be in progress on a container thread. + // Processing of timeouts and errors can happen on or off a + // container thread (on is much more likely) but while in this state + // the call that triggers the timeout will be in progress on a + // container thread. + // The socket will be added to the poller when the container thread + // exits the AbstractConnectionHandler.process() method so don't do + // a dispatch here which would add it to the poller a second time. state = AsyncState.DISPATCHING; - // If on a container thread then the socket will be added to the - // poller poller when the thread exits the - // AbstractConnectionHandler.process() method so don't do a dispatch - // here which would add it to the poller a second time. - if (!ContainerThreadMarker.isContainerThread()) { - doDispatch = true; - } } else { throw new IllegalStateException( sm.getString("asyncStateMachine.invalidAsyncState", "asyncDispatch()", state)); } - return doDispatch; + return triggerDispatch; } @@ -412,36 +409,17 @@ } - synchronized void asyncMustError() { - if (state == AsyncState.STARTED) { - clearNonBlockingListeners(); + synchronized boolean asyncError() { + clearNonBlockingListeners(); + if (state == AsyncState.STARTING) { state = AsyncState.MUST_ERROR; } else { - throw new IllegalStateException( - sm.getString("asyncStateMachine.invalidAsyncState", - "asyncMustError()", state)); - } - } - - - synchronized void asyncError() { - if (state == AsyncState.STARTING || - state == AsyncState.STARTED || - state == AsyncState.DISPATCHED || - state == AsyncState.TIMING_OUT || - state == AsyncState.MUST_COMPLETE || - state == AsyncState.READ_WRITE_OP || - state == AsyncState.COMPLETING || - state == AsyncState.MUST_ERROR) { - clearNonBlockingListeners(); state = AsyncState.ERROR; - } else { - throw new IllegalStateException( - sm.getString("asyncStateMachine.invalidAsyncState", - "asyncError()", state)); } + return !ContainerThreadMarker.isContainerThread(); } + synchronized void asyncRun(Runnable runnable) { if (state == AsyncState.STARTING || state == AsyncState.STARTED || state == AsyncState.READ_WRITE_OP) { diff -Nru tomcat9-9.0.27/java/org/apache/coyote/CompressionConfig.java tomcat9-9.0.31/java/org/apache/coyote/CompressionConfig.java --- tomcat9-9.0.27/java/org/apache/coyote/CompressionConfig.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/CompressionConfig.java 2020-02-05 19:26:48.000000000 +0000 @@ -20,23 +20,33 @@ import java.io.StringReader; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Pattern; +import org.apache.juli.logging.Log; +import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.http.ResponseUtil; import org.apache.tomcat.util.http.parser.AcceptEncoding; +import org.apache.tomcat.util.http.parser.TokenList; +import org.apache.tomcat.util.res.StringManager; public class CompressionConfig { + private static final Log log = LogFactory.getLog(CompressionConfig.class); + private static final StringManager sm = StringManager.getManager(CompressionConfig.class); + private int compressionLevel = 0; private Pattern noCompressionUserAgents = null; private String compressibleMimeType = "text/html,text/xml,text/plain,text/css," + "text/javascript,application/javascript,application/json,application/xml"; private String[] compressibleMimeTypes = null; private int compressionMinSize = 2048; + private boolean noCompressionStrongETag = true; /** @@ -174,6 +184,35 @@ /** + * Determine if compression is disabled if the resource has a strong ETag. + * + * @return {@code true} if compression is disabled, otherwise {@code false} + * + * @deprecated Will be removed in Tomcat 10 where it will be hard-coded to + * {@code true} + */ + @Deprecated + public boolean getNoCompressionStrongETag() { + return noCompressionStrongETag; + } + + + /** + * Set whether compression is disabled for resources with a strong ETag. + * + * @param noCompressionStrongETag {@code true} if compression is disabled, + * otherwise {@code false} + * + * @deprecated Will be removed in Tomcat 10 where it will be hard-coded to + * {@code true} + */ + @Deprecated + public void setNoCompressionStrongETag(boolean noCompressionStrongETag) { + this.noCompressionStrongETag = noCompressionStrongETag; + } + + + /** * Determines if compression should be enabled for the given response and if * it is, sets any necessary headers to mark it as such. * @@ -193,10 +232,21 @@ // Check if content is not already compressed MessageBytes contentEncodingMB = responseHeaders.getValue("Content-Encoding"); - if (contentEncodingMB != null && - (contentEncodingMB.indexOf("gzip") != -1 || - contentEncodingMB.indexOf("br") != -1)) { - return false; + if (contentEncodingMB != null) { + // Content-Encoding values are ordered but order is not important + // for this check so use a Set rather than a List + Set tokens = new HashSet<>(); + try { + TokenList.parseTokenList(responseHeaders.values("Content-Encoding"), tokens); + } catch (IOException e) { + // Because we are using StringReader, any exception here is a + // Tomcat bug. + log.warn(sm.getString("compressionConfig.ContentEncodingParseFail"), e); + return false; + } + if (tokens.contains("gzip") || tokens.contains("br")) { + return false; + } } // If force mode, the length and MIME type checks are skipped @@ -214,6 +264,16 @@ return false; } } + + // Check if the resource has a strong ETag + if (noCompressionStrongETag) { + String eTag = responseHeaders.getHeader("ETag"); + if (eTag != null && !eTag.trim().startsWith("W/")) { + // Has an ETag that doesn't start with "W/..." so it must be a + // strong ETag + return false; + } + } // If processing reaches this far, the response might be compressed. // Therefore, set the Vary header to keep proxies happy diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/AbstractHttp11Protocol.java tomcat9-9.0.31/java/org/apache/coyote/http11/AbstractHttp11Protocol.java --- tomcat9-9.0.27/java/org/apache/coyote/http11/AbstractHttp11Protocol.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/AbstractHttp11Protocol.java 2020-02-05 19:26:48.000000000 +0000 @@ -95,6 +95,15 @@ // ------------------------------------------------ HTTP specific properties // ------------------------------------------ managed in the ProtocolHandler + private boolean useKeepAliveResponseHeader = true; + public boolean getUseKeepAliveResponseHeader() { + return useKeepAliveResponseHeader; + } + public void setUseKeepAliveResponseHeader(boolean useKeepAliveResponseHeader) { + this.useKeepAliveResponseHeader = useKeepAliveResponseHeader; + } + + private String relaxedPathChars = null; public String getRelaxedPathChars() { return relaxedPathChars; @@ -136,27 +145,56 @@ } - private boolean rejectIllegalHeaderName = true; + private boolean rejectIllegalHeader = true; /** - * If an HTTP request is received that contains an illegal header name (i.e. - * the header name is not a token) will the request be rejected (with a 400 - * response) or will the illegal header be ignored. + * If an HTTP request is received that contains an illegal header name or + * value (e.g. the header name is not a token) will the request be rejected + * (with a 400 response) or will the illegal header be ignored? * * @return {@code true} if the request will be rejected or {@code false} if * the header will be ignored */ - public boolean getRejectIllegalHeaderName() { return rejectIllegalHeaderName; } + public boolean getRejectIllegalHeader() { return rejectIllegalHeader; } + /** + * If an HTTP request is received that contains an illegal header name or + * value (e.g. the header name is not a token) should the request be + * rejected (with a 400 response) or should the illegal header be ignored? + * + * @param rejectIllegalHeader {@code true} to reject requests with illegal + * header names or values, {@code false} to + * ignore the header + */ + public void setRejectIllegalHeader(boolean rejectIllegalHeader) { + this.rejectIllegalHeader = rejectIllegalHeader; + } /** - * If an HTTP request is received that contains an illegal header name (i.e. - * the header name is not a token) should the request be rejected (with a - * 400 response) or should the illegal header be ignored. + * If an HTTP request is received that contains an illegal header name or + * value (e.g. the header name is not a token) will the request be rejected + * (with a 400 response) or will the illegal header be ignored? + * + * @return {@code true} if the request will be rejected or {@code false} if + * the header will be ignored + * + * @deprecated Now an alias for {@link #getRejectIllegalHeader()}. Will be + * removed in Tomcat 10 onwards. + */ + @Deprecated + public boolean getRejectIllegalHeaderName() { return rejectIllegalHeader; } + /** + * If an HTTP request is received that contains an illegal header name or + * value (e.g. the header name is not a token) should the request be + * rejected (with a 400 response) or should the illegal header be ignored? * * @param rejectIllegalHeaderName {@code true} to reject requests with - * illegal header names, {@code false} to - * ignore the header + * illegal header names or values, + * {@code false} to ignore the header + * + * @deprecated Now an alias for {@link #setRejectIllegalHeader(boolean)}. + * Will be removed in Tomcat 10 onwards. */ + @Deprecated public void setRejectIllegalHeaderName(boolean rejectIllegalHeaderName) { - this.rejectIllegalHeaderName = rejectIllegalHeaderName; + this.rejectIllegalHeader = rejectIllegalHeaderName; } @@ -270,6 +308,16 @@ } + @Deprecated + public boolean getNoCompressionStrongETag() { + return compressionConfig.getNoCompressionStrongETag(); + } + @Deprecated + public void setNoCompressionStrongETag(boolean noCompressionStrongETag) { + compressionConfig.setNoCompressionStrongETag(noCompressionStrongETag); + } + + public boolean useCompression(Request request, Response response) { return compressionConfig.useCompression(request, response); } @@ -479,6 +527,8 @@ } } } + + upgradeProtocol.setHttp11Protocol(this); } @Override public UpgradeProtocol getNegotiatedProtocol(String negotiatedName) { diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/Constants.java tomcat9-9.0.31/java/org/apache/coyote/http11/Constants.java --- tomcat9-9.0.27/java/org/apache/coyote/http11/Constants.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/Constants.java 2020-02-05 19:26:48.000000000 +0000 @@ -103,12 +103,26 @@ /* Various constant "strings" */ public static final String CONNECTION = "Connection"; public static final String CLOSE = "close"; + /** + * @deprecated Unused. Will be removed in Tomcat 10. + */ + @Deprecated public static final byte[] CLOSE_BYTES = ByteChunk.convertToBytes(CLOSE); + /** + * @deprecated Unused. Will be removed in Tomcat 10. + */ + @Deprecated public static final String KEEPALIVE = "keep-alive"; + public static final String KEEP_ALIVE_HEADER_VALUE_TOKEN = "keep-alive"; + /** + * @deprecated Unused. Will be removed in Tomcat 10. + */ + @Deprecated public static final byte[] KEEPALIVE_BYTES = ByteChunk.convertToBytes(KEEPALIVE); public static final String CHUNKED = "chunked"; public static final byte[] ACK_BYTES = ByteChunk.convertToBytes("HTTP/1.1 100 " + CRLF + CRLF); public static final String TRANSFERENCODING = "Transfer-Encoding"; + public static final String KEEP_ALIVE_HEADER_NAME = "Keep-Alive"; public static final byte[] _200_BYTES = ByteChunk.convertToBytes("200"); public static final byte[] _400_BYTES = ByteChunk.convertToBytes("400"); public static final byte[] _404_BYTES = ByteChunk.convertToBytes("404"); diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/filters/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/coyote/http11/filters/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/coyote/http11/filters/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/filters/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -18,6 +18,7 @@ chunkedInputFilter.error=没有数据可用由于先前的错误 chunkedInputFilter.invalidCrlfCRCR=无效的结束的行序列(CRCR) chunkedInputFilter.invalidCrlfNoCR=无效的行尾结束符序列(LF前缺少CR) +chunkedInputFilter.invalidCrlfNoData=无效的行尾序列(没有可读取的数据) chunkedInputFilter.maxExtension=超过最大扩展数 chunkedInputFilter.maxTrailer=超过最大数 diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/Http11InputBuffer.java tomcat9-9.0.31/java/org/apache/coyote/http11/Http11InputBuffer.java --- tomcat9-9.0.27/java/org/apache/coyote/http11/Http11InputBuffer.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/Http11InputBuffer.java 2020-02-05 19:26:48.000000000 +0000 @@ -28,6 +28,7 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.HeaderUtil; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.http.parser.HttpParser; import org.apache.tomcat.util.net.ApplicationBufferHandler; @@ -65,7 +66,7 @@ private final MimeHeaders headers; - private final boolean rejectIllegalHeaderName; + private final boolean rejectIllegalHeader; /** * State. @@ -151,13 +152,13 @@ // ----------------------------------------------------------- Constructors public Http11InputBuffer(Request request, int headerBufferSize, - boolean rejectIllegalHeaderName, HttpParser httpParser) { + boolean rejectIllegalHeader, HttpParser httpParser) { this.request = request; headers = request.getMimeHeaders(); this.headerBufferSize = headerBufferSize; - this.rejectIllegalHeaderName = rejectIllegalHeaderName; + this.rejectIllegalHeader = rejectIllegalHeader; this.httpParser = httpParser; filterLibrary = new InputFilter[0]; @@ -761,6 +762,8 @@ // byte chr = 0; + byte prevChr = 0; + while (headerParsePos == HeaderParsePosition.HEADER_START) { // Read new bytes if needed @@ -771,22 +774,29 @@ } } + prevChr = chr; chr = byteBuffer.get(); - if (chr == Constants.CR) { - // Skip - } else if (chr == Constants.LF) { + if (chr == Constants.CR && prevChr != Constants.CR) { + // Possible start of CRLF - process the next byte. + } else if (prevChr == Constants.CR && chr == Constants.LF) { return HeaderParseStatus.DONE; } else { - byteBuffer.position(byteBuffer.position() - 1); + if (prevChr == 0) { + // Must have only read one byte + byteBuffer.position(byteBuffer.position() - 1); + } else { + // Must have read two bytes (first was CR, second was not LF) + byteBuffer.position(byteBuffer.position() - 2); + } break; } - } if (headerParsePos == HeaderParsePosition.HEADER_START) { // Mark the current buffer position headerData.start = byteBuffer.position(); + headerData.lineStart = headerData.start; headerParsePos = HeaderParsePosition.HEADER_NAME; } @@ -877,11 +887,22 @@ } } + prevChr = chr; chr = byteBuffer.get(); if (chr == Constants.CR) { - // Skip - } else if (chr == Constants.LF) { + // Possible start of CRLF - process the next byte. + } else if (prevChr == Constants.CR && chr == Constants.LF) { eol = true; + } else if (prevChr == Constants.CR) { + // Invalid value + // Delete the header (it will be the most recent one) + headers.removeHeader(headers.size() - 1); + return skipLine(); + } else if (chr != Constants.HT && HttpParser.isControl(chr)) { + // Invalid value + // Delete the header (it will be the most recent one) + headers.removeHeader(headers.size() - 1); + return skipLine(); } else if (chr == Constants.SP || chr == Constants.HT) { byteBuffer.put(headerData.realPos, chr); headerData.realPos++; @@ -933,6 +954,9 @@ headerParsePos = HeaderParsePosition.HEADER_SKIPLINE; boolean eol = false; + byte chr = 0; + byte prevChr = 0; + // Reading bytes until the end of the line while (!eol) { @@ -944,21 +968,21 @@ } int pos = byteBuffer.position(); - byte chr = byteBuffer.get(); + prevChr = chr; + chr = byteBuffer.get(); if (chr == Constants.CR) { // Skip - } else if (chr == Constants.LF) { + } else if (prevChr == Constants.CR && chr == Constants.LF) { eol = true; } else { headerData.lastSignificantChar = pos; } } - if (rejectIllegalHeaderName || log.isDebugEnabled()) { + if (rejectIllegalHeader || log.isDebugEnabled()) { String message = sm.getString("iib.invalidheader", - new String(byteBuffer.array(), headerData.start, - headerData.lastSignificantChar - headerData.start + 1, - StandardCharsets.ISO_8859_1)); - if (rejectIllegalHeaderName) { + HeaderUtil.toPrintableString(byteBuffer.array(), headerData.lineStart, + headerData.lastSignificantChar - headerData.lineStart + 1)); + if (rejectIllegalHeader) { throw new IllegalArgumentException(message); } log.debug(message); @@ -1018,6 +1042,10 @@ private static class HeaderParseData { /** + * The first character of the header line. + */ + int lineStart = 0; + /** * When parsing header name: first character of the header.
    * When skipping broken header line: first character of the header.
    * When parsing header value: first character after ':'. @@ -1045,6 +1073,7 @@ */ MessageBytes headerValue = null; public void recycle() { + lineStart = 0; start = 0; realPos = 0; lastSignificantChar = 0; diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/Http11NioProtocol.java tomcat9-9.0.31/java/org/apache/coyote/http11/Http11NioProtocol.java --- tomcat9-9.0.27/java/org/apache/coyote/http11/Http11NioProtocol.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/Http11NioProtocol.java 2020-02-05 19:26:48.000000000 +0000 @@ -50,10 +50,21 @@ * NO-OP. * * @param count Unused + * + * @deprecated This setter will be removed in Tomcat 10. */ + @Deprecated public void setPollerThreadCount(int count) { } + /** + * Always returns 1. + * + * @return 1 + * + * @deprecated This getter will be removed in Tomcat 10. + */ + @Deprecated public int getPollerThreadCount() { return 1; } diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/Http11Processor.java tomcat9-9.0.31/java/org/apache/coyote/http11/Http11Processor.java --- tomcat9-9.0.27/java/org/apache/coyote/http11/Http11Processor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/Http11Processor.java 2020-02-05 19:26:48.000000000 +0000 @@ -19,8 +19,10 @@ import java.io.IOException; import java.io.InterruptedIOException; import java.nio.ByteBuffer; -import java.util.Enumeration; -import java.util.Locale; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.regex.Pattern; import javax.servlet.http.HttpServletResponse; @@ -46,12 +48,12 @@ import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; -import org.apache.tomcat.util.buf.Ascii; import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.FastHttpDateFormat; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.http.parser.HttpParser; +import org.apache.tomcat.util.http.parser.TokenList; import org.apache.tomcat.util.log.UserDataHelper; import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState; import org.apache.tomcat.util.net.SSLSupport; @@ -73,6 +75,7 @@ private final AbstractHttp11Protocol protocol; + /** * Input. */ @@ -153,7 +156,7 @@ protocol.getRelaxedQueryChars()); inputBuffer = new Http11InputBuffer(request, protocol.getMaxHttpHeaderSize(), - protocol.getRejectIllegalHeaderName(), httpParser); + protocol.getRejectIllegalHeader(), httpParser); request.setInputBuffer(inputBuffer); outputBuffer = new Http11OutputBuffer(response, protocol.getMaxHttpHeaderSize()); @@ -185,39 +188,6 @@ /** - * Specialized utility method: find a sequence of lower case bytes inside - * a ByteChunk. - */ - private static int findBytes(ByteChunk bc, byte[] b) { - - byte first = b[0]; - byte[] buff = bc.getBuffer(); - int start = bc.getStart(); - int end = bc.getEnd(); - - // Look for first char - int srcEnd = b.length; - - for (int i = start; i <= (end - srcEnd); i++) { - if (Ascii.toLower(buff[i]) != first) { - continue; - } - // found first char, now look for a match - int myPos = i+1; - for (int srcPos = 1; srcPos < srcEnd;) { - if (Ascii.toLower(buff[myPos++]) != b[srcPos++]) { - break; - } - if (srcPos == srcEnd) { - return i - start; // found it - } - } - } - return -1; - } - - - /** * Determine if we must drop the connection because of the HTTP status * code. Use the same list of codes as Apache/httpd. */ @@ -239,9 +209,7 @@ */ private void addInputFilter(InputFilter[] inputFilters, String encodingName) { - // Trim provided encoding name and convert to lower case since transfer - // encoding names are case insensitive. (RFC2616, section 3.6) - encodingName = encodingName.trim().toLowerCase(Locale.ENGLISH); + // Parsing trims and converts to lower case. if (encodingName.equals("identity")) { // Skip @@ -345,16 +313,7 @@ } // Has an upgrade been requested? - Enumeration connectionValues = request.getMimeHeaders().values("Connection"); - boolean foundUpgrade = false; - while (connectionValues.hasMoreElements() && !foundUpgrade) { - String connectionValue = connectionValues.nextElement(); - if (connectionValue != null) { - foundUpgrade = connectionValue.toLowerCase(Locale.ENGLISH).contains("upgrade"); - } - } - - if (foundUpgrade) { + if (isConnectionToken(request.getMimeHeaders(), "upgrade")) { // Check the protocol String requestedProtocol = request.getHeader("Upgrade"); @@ -565,7 +524,7 @@ /** * After reading the request headers, we have to setup the request filters. */ - private void prepareRequest() { + private void prepareRequest() throws IOException { http11 = true; http09 = false; @@ -603,11 +562,11 @@ // Check connection header MessageBytes connectionValueMB = headers.getValue(Constants.CONNECTION); if (connectionValueMB != null && !connectionValueMB.isNull()) { - ByteChunk connectionValueBC = connectionValueMB.getByteChunk(); - if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) { + Set tokens = new HashSet<>(); + TokenList.parseTokenList(headers.values(Constants.CONNECTION), tokens); + if (tokens.contains(Constants.CLOSE)) { keepAlive = false; - } else if (findBytes(connectionValueBC, - Constants.KEEPALIVE_BYTES) != -1) { + } else if (tokens.contains(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN)) { keepAlive = true; } } @@ -615,7 +574,7 @@ if (http11) { MessageBytes expectMB = headers.getValue("expect"); if (expectMB != null && !expectMB.isNull()) { - if (expectMB.indexOfIgnoreCase("100-continue", 0) != -1) { + if (expectMB.toString().trim().equalsIgnoreCase("100-continue")) { inputBuffer.setSwallowInput(false); request.setExpectation(true); } else { @@ -762,20 +721,17 @@ // Parse transfer-encoding header if (http11) { MessageBytes transferEncodingValueMB = headers.getValue("transfer-encoding"); - if (transferEncodingValueMB != null && !transferEncodingValueMB.isNull()) { - String transferEncodingValue = transferEncodingValueMB.toString(); - // Parse the comma separated list. "identity" codings are ignored - int startPos = 0; - int commaPos = transferEncodingValue.indexOf(','); - String encodingName = null; - while (commaPos != -1) { - encodingName = transferEncodingValue.substring(startPos, commaPos); - addInputFilter(inputFilters, encodingName); - startPos = commaPos + 1; - commaPos = transferEncodingValue.indexOf(',', startPos); + if (transferEncodingValueMB != null) { + List encodingNames = new ArrayList<>(); + if (TokenList.parseTokenList(headers.values("transfer-encoding"), encodingNames)) { + for (String encodingName : encodingNames) { + // "identity" codings are ignored + addInputFilter(inputFilters, encodingName); + } + } else { + // Invalid transfer encoding + badRequest("http11processor.request.invalidTransferEncoding"); } - encodingName = transferEncodingValue.substring(startPos); - addInputFilter(inputFilters, encodingName); } } @@ -879,7 +835,6 @@ } // Check for compression - boolean useCompression = false; if (entityBody && sendfileData == null) { useCompression = protocol.useCompression(request, response); @@ -900,7 +855,7 @@ } long contentLength = response.getContentLengthLong(); - boolean connectionClosePresent = false; + boolean connectionClosePresent = isConnectionToken(headers, Constants.CLOSE); if (http11 && response.getTrailerFields() != null) { // If trailer fields are set, always use chunking outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]); @@ -913,7 +868,6 @@ } else { // If the response code supports an entity body and we're on // HTTP 1.1 then we chunk unless we have a Connection: close header - connectionClosePresent = isConnectionClose(headers); if (http11 && entityBody && !connectionClosePresent) { outputBuffer.addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]); contentDelimitation = true; @@ -957,8 +911,36 @@ headers.addValue(Constants.CONNECTION).setString( Constants.CLOSE); } - } else if (!http11 && !getErrorState().isError()) { - headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE); + } else if (!getErrorState().isError()) { + if (!http11) { + headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN); + } + + if (protocol.getUseKeepAliveResponseHeader()) { + boolean connectionKeepAlivePresent = + isConnectionToken(request.getMimeHeaders(), Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN); + + if (connectionKeepAlivePresent) { + int keepAliveTimeout = protocol.getKeepAliveTimeout(); + + if (keepAliveTimeout > 0) { + String value = "timeout=" + keepAliveTimeout / 1000L; + headers.setValue(Constants.KEEP_ALIVE_HEADER_NAME).setString(value); + + if (http11) { + // Append if there is already a Connection header, + // else create the header + MessageBytes connectionHeaderValue = headers.getValue(Constants.CONNECTION); + if (connectionHeaderValue == null) { + headers.addValue(Constants.CONNECTION).setString(Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN); + } else { + connectionHeaderValue.setString( + connectionHeaderValue.getString() + ", " + Constants.KEEP_ALIVE_HEADER_VALUE_TOKEN); + } + } + } + } + } } // Add server header @@ -992,14 +974,18 @@ outputBuffer.commit(); } - private static boolean isConnectionClose(MimeHeaders headers) { + private static boolean isConnectionToken(MimeHeaders headers, String token) throws IOException { MessageBytes connection = headers.getValue(Constants.CONNECTION); if (connection == null) { return false; } - return connection.equals(Constants.CLOSE); + + Set tokens = new HashSet<>(); + TokenList.parseTokenList(headers.values(Constants.CONNECTION), tokens); + return tokens.contains(token); } + private void prepareSendfile(OutputFilter[] outputFilters) { String fileName = (String) request.getAttribute( org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR); diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/LocalStrings.properties tomcat9-9.0.31/java/org/apache/coyote/http11/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/coyote/http11/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -23,6 +23,7 @@ http11processor.request.finish=Error finishing request http11processor.request.inconsistentHosts=The host specified in the request line is not consistent with the host header http11processor.request.invalidScheme=The HTTP request contained an absolute URI with an invalid scheme +http11processor.request.invalidTransferEncoding=The HTTP request contained an invalid Transfer-Encoding header http11processor.request.invalidUri=The HTTP request contained an invalid URI http11processor.request.invalidUserInfo=The HTTP request contained an absolute URI with an invalid userinfo http11processor.request.multipleContentLength=The request contained multiple content-length headers diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/coyote/http11/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/coyote/http11/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,9 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +abstractHttp11Protocol.alpnConfigured=[{0}]连接器已配置为支持通过ALPN与[{1}]协商。 + http11processor.header.parse=解析 HTTP 请求 header 错误 http11processor.request.inconsistentHosts=请求行中指定的主机与主机头不一致。 +http11processor.request.invalidScheme=HTTP请求包含具有无效方案的绝对URL http11processor.request.invalidUserInfo=HTTP 请求包含带有无效 userinfo 的绝对 URI +http11processor.request.multipleContentLength=请求包含了多个content-length请求头参数 +http11processor.request.noHostHeader=http/1.1请求没有提供主机头 http11processor.request.prepare=准备请求时出错 http11processor.request.process=错误的处理请求 http11processor.response.finish=错误完成相应 @@ -30,4 +35,5 @@ iib.readtimeout=从套接字读取数据超时 iob.failedwrite=写入.失败 +iob.failedwrite.ack=无法发送HTTP 100继续响应 iob.responseheadertoolarge.error=尝试将更多数据写入响应标头,而不是缓冲区中有可用空间。 增加连接器上的maxHttpHeaderSize或将更少的数据写入响应头。 diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/upgrade/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/coyote/http11/upgrade/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/coyote/http11/upgrade/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/upgrade/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -14,9 +14,13 @@ # limitations under the License. upgrade.sis.isFinished.ise=当 ServletInputStream 不处于非阻塞模式时调用 isFinished() 是非法的(即必须首先调用 setReadListener()) +upgrade.sis.onErrorFail=对注册的readlistener的错误处理触发了这个进一步的错误,该错误被吞入 upgrade.sis.read.closed=InputStream 已被关闭 +upgrade.sis.read.ise=在非阻塞模式下调用任何read()方法而不首先通过调用isready()检查是否有可用的数据是非法的 upgrade.sis.readListener.set=在同一个upgraded连接上调用多次setReadListener()函数是非法的 +upgrade.sos.onErrorFail=对注册的WriteListener 的错误处理触发了这个进一步的错误,该错误被吞入 upgrade.sos.write.closed=输出流已被关闭 +upgrade.sos.write.ise=在非阻塞模式下调用任何写()方法都是非法的,而无需首先检查是否有可用的空间,只需调用isreadi() upgrade.sos.writeListener.null=对setWriteListener()传递null是非法的 upgradeProcessor.unexpectedState=因传入套接字状态为[{0}]而意外关闭升级连接 diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java tomcat9-9.0.31/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java --- tomcat9-9.0.27/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java 2020-02-05 19:26:48.000000000 +0000 @@ -134,6 +134,6 @@ @Override public void pause() { - // NOOP for AJP + // NOOP } } diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http2/Http2Protocol.java tomcat9-9.0.31/java/org/apache/coyote/http2/Http2Protocol.java --- tomcat9-9.0.27/java/org/apache/coyote/http2/Http2Protocol.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http2/Http2Protocol.java 2020-02-05 19:26:48.000000000 +0000 @@ -27,6 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; +import org.apache.coyote.AbstractProtocol; import org.apache.coyote.Adapter; import org.apache.coyote.CompressionConfig; import org.apache.coyote.Processor; @@ -91,6 +92,8 @@ private boolean useSendfile = true; // Compression private final CompressionConfig compressionConfig = new CompressionConfig(); + // Reference to HTTP/1.1 protocol that this instance is configured under + private AbstractProtocol http11Protocol = null; @Override public String getHttpUpgradeName(boolean isSSLEnabled) { @@ -405,7 +408,26 @@ } + @Deprecated + public boolean getNoCompressionStrongETag() { + return compressionConfig.getNoCompressionStrongETag(); + } + @Deprecated + public void setNoCompressionStrongETag(boolean noCompressionStrongETag) { + compressionConfig.setNoCompressionStrongETag(noCompressionStrongETag); + } + + public boolean useCompression(Request request, Response response) { return compressionConfig.useCompression(request, response); } + + + public AbstractProtocol getHttp11Protocol() { + return this.http11Protocol; + } + @Override + public void setHttp11Protocol(AbstractProtocol http11Protocol) { + this.http11Protocol = http11Protocol; + } } diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http2/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/coyote/http2/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/coyote/http2/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http2/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,11 +13,14 @@ # See the License for the specific language governing permissions and # limitations under the License. +abstractStream.windowSizeDec=连接[{0}],流[{1}],将流控制窗口减少[{2}]到[{3}] abstractStream.windowSizeInc=连接 [{0}], 流 [{1}], 增加流量控制窗口[{2}] 到 [{3}] +abstractStream.windowSizeTooBig=连接[{0}],流[{1}],窗口大小从[{2}]增加到[{3}],超过了允许的最大值 connectionPrefaceParser.mismatch=请求了新的远程流ID[{0}],但所有远程流都必须使用奇数标识符。 connectionSettings.debug=连接[{0}],参数类型[{1}]设置为[{2}] +connectionSettings.enablePushInvalid=连接[{0}],请求的enable push[{1}]值不是允许的值之一(零或一) connectionSettings.headerTableSizeLimit=连接 [{0}],尝试将 header 表大小设置为 [{1}],但限制为 16k connectionSettings.maxFrameSizeInvalid=连接[{0}],请求的最大帧大小[{1}]在[{2}]到[{3}]的允许范围之外 connectionSettings.unknown=连接[{0}],标识为[{1}]和值为[{2}]的未知设置被忽略 @@ -25,35 +28,49 @@ frameType.checkPayloadSize=对帧类型[{1}]来说,负载[{0}]是无效的 frameType.checkStream=无效的帧类型[{0}] +hpack.integerEncodedOverTooManyOctets=HPACK 可变长度整数编码过多的八位字节,最大值为[{0}] hpack.invalidCharacter=代码点[{1}]处的Unicode字符[{0}]无法编码,因为它超出了允许的0到255范围。 hpackdecoder.tableSizeUpdateNotAtStart=任何表大小的更新都必须在头块开始时发送。 http2Parser.error=Connection [{0}],Stream [{1}], 框架类型 [{2}], 错误 +http2Parser.headerLimitCount=连接[{0}],流[{1}],标题太多 http2Parser.headerLimitSize=连接[{0}],Stream[{1}],总的头信息尺寸太大 http2Parser.headers.wrongStream=连接[{0}], 头部信息对于流[{1}]正在进行但对于流[{2}]的一帧已经收到了。 +http2Parser.invalidBuffers=应使用两个缓冲区进行读取 http2Parser.nonZeroPadding=连接[{0}],流[{1}],非零填充 http2Parser.preface.invalid=出现无效连接 +http2Parser.processFrame.unexpectedType=需要帧类型[{0}],但收到帧类型[{1}] http2Parser.processFrameData.window=连接[{0}],客户端发送的数据比流窗口允许的多 http2Parser.processFrameHeaders.decodingDataLeft=数据在HPACK解码后依然保留 - 它本应该被消费掉 +http2Parser.processFrameHeaders.payload=连接:[{0}],流:[{1}],正在处理[{1}]大小的头文件负载 +http2Parser.processFramePriority.invalidParent=连接[{0}],流[{1}],流可能不依赖于自身 http2Parser.processFramePushPromise=请求了新的远程流ID[{0}],但所有远程流都必须使用奇数标识符\n\ \n +http2Parser.swallow.debug=连接:[{0}],流:[{1}],吞下[{2}]字节 stream.closed=连接[{0}],流[{1}],一旦关闭就无法写入流 stream.header.debug=连接[{0}],流[{1}],HTTP标头[{2}],值[{3}] stream.header.noPath=连接[{0}],流[{1}],[:path]伪标头为空 +stream.header.required=连接 [{0}], 流 [{1}], 缺少一个或多个必要的头文件 stream.header.unknownPseudoHeader=收到连接[{0}],流[{1}],未知伪标头[{2}] stream.inputBuffer.readTimeout=等待从客户端读取数据超时 stream.inputBuffer.reset=流.重置 stream.inputBuffer.signal=读线程在等待时,数据被添加到inBuffer中。 发信号通知该线程继续 stream.reprioritisation.debug=连接[{0}],流[{1}],独占[{2}],父[{3}],权重[{4}] +stream.reset.fail=连接[{0}],流[{1}],重置流失败 stream.writeTimeout=等待客户端增加流控制窗口以允许写入流数据的超时 streamProcessor.cancel=连接到[{0}],Stream [{1}], streamProcessor.error.connection=连接[{0}],Stream[{0}],处理中发生错误,对连接来说是致命的。 streamProcessor.service.error=请求处理期间出错 +streamStateMachine.debug.change=(:连接[{0}],流[{1}],状态从[{2}]更改为[{3}] + upgradeHandler.allocate.left=连接[{0}],流[{1}],[{2}]字节未分配 - 尝试分配给子项 +upgradeHandler.allocate.recipient=(:连接[{0}],流[{1}],潜在接收者[{2}],权重为[{3}] +upgradeHandler.goaway.debug=连接[{0}],离开,最后的流[{1}],错误码[{2}],调试数据[{3}] +upgradeHandler.init=连接[{0}],状态[{1}] upgradeHandler.ioerror=连接[{0}] upgradeHandler.pingFailed=对客户端发送ping 链接失败. upgradeHandler.prefaceReceived=连接[{0}],从客户端收到连接准备。 @@ -62,13 +79,19 @@ upgradeHandler.rst.debug=连接[{0}],流[{1}],错误[{2}],消息[{3}],RST(关闭流) upgradeHandler.sendPrefaceFail=连接[{0}],给客户端发送前言失败 upgradeHandler.socketCloseFailed=关闭 socket 错误 +upgradeHandler.stream.closed=流[{0}]已经关闭了一段时间 upgradeHandler.stream.even=\ 请求了新的远程流ID[{0}],但所有远程流都必须使用奇数标识符\n\ \n upgradeHandler.tooMuchOverhead=连接[{0}],开销过大,连接将关闭 upgradeHandler.upgrade=连接[{0}], HTTP/1.1 升级到流[1] +upgradeHandler.upgrade.fail=):连接[{0}],http/1.1升级失败 upgradeHandler.upgradeDispatch.entry=条目,连接[{0}],SocketStatus [{1}] upgradeHandler.upgradeDispatch.exit=退出,连接[{0}], SocketState[{1}] +upgradeHandler.windowSizeTooBig=连接[{0}],流[{1}],窗口太大 upgradeHandler.writeBody=连接 [{0}],数据流[{1}], 数据长度[{2}] upgradeHandler.writeHeaders=连接 [{0}],流 [{1}] +windowAllocationManager.notify=连接[{0}], 流[{1}], 等待类型[{2}], 通知类型[{3}] +windowAllocationManager.waitFor.ise=连接[{0}], 流[{1}], 已经准备好 + writeStateMachine.ise=处于 [{1}] 状态时调用 [{0}()] 方法是非法的 diff -Nru tomcat9-9.0.27/java/org/apache/coyote/http2/StreamProcessor.java tomcat9-9.0.31/java/org/apache/coyote/http2/StreamProcessor.java --- tomcat9-9.0.27/java/org/apache/coyote/http2/StreamProcessor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/http2/StreamProcessor.java 2020-02-05 19:26:48.000000000 +0000 @@ -71,7 +71,10 @@ try { state = process(socketWrapper, event); - if (state == SocketState.CLOSED) { + if (state == SocketState.LONG) { + handler.getProtocol().getHttp11Protocol().addWaitingProcessor(this); + } else if (state == SocketState.CLOSED) { + handler.getProtocol().getHttp11Protocol().removeWaitingProcessor(this); if (!getErrorState().isConnectionIoAllowed()) { ConnectionException ce = new ConnectionException(sm.getString( "streamProcessor.error.connection", stream.getConnectionId(), diff -Nru tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_fr.properties tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_fr.properties --- tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_fr.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_fr.properties 2020-02-05 19:26:48.000000000 +0000 @@ -28,11 +28,12 @@ abstractProcessor.hostInvalid=L''hôte [{0}] n''est pas valide abstractProcessor.httpupgrade.notsupported=La promotion (upgrade) HTTP n'est pas supporté par ce protocole abstractProcessor.noExecute=Impossible de transférer l'exécution à un thread du conteneur parce que ce processeur n'est pas associé à un SocketWrapper -abstractProcessor.nonContainerThreadError=Un erreur s'est produite hors d'un des fils d'exécution du conteneur, la connection sera immédiatement fermée abstractProcessor.pushrequest.notsupported=Le requêtes push du serveur ne sont pas supportées par ce protocole abstractProcessor.socket.ssl=Exception lors de l'obtention des attributs SSL abstractProtocol.mbeanDeregistrationFailed=Erreur lors du désenregistrement du mbean [{0}] dans le serveur [{1}] +abstractProtocol.processorRegisterError=Erreur lors de l'enregistrement du processeur de requêtes +abstractProtocol.processorUnregisterError=Erreur lors du désenregistrement du processeur de requêtes abstractProtocolHandler.asyncTimeoutError=Erreur de traitement du délai d'attente maximum en mode asynchrone abstractProtocolHandler.destroy=Destruction du gestionnaire de protocole [{0}] @@ -46,6 +47,8 @@ asyncStateMachine.invalidAsyncState=L''appel à [{0}] n''est pas valide pour une requête dans l''état Async [{1}] +compressionConfig.ContentEncodingParseFail=Echec du traitement de l'en-tête Content-Encoding en vérifiant si la compression était déjà utilisée + request.notAsync=Il n'est possible de passer en mode d'entrée-sorties non bloquantes que lors de traitements asynchrones ou après mise à niveau depuis HTTP request.nullReadListener=L'écouteur passé à setReadListener() ne peut pas être null request.readListenerSet=L'écouteur des lectures non bloquantes a déjà été défini diff -Nru tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_ja.properties tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_ja.properties --- tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_ja.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_ja.properties 2020-02-05 19:26:48.000000000 +0000 @@ -27,7 +27,6 @@ abstractProcessor.hostInvalid=ホスト名 [{0}] は不正なホスト名です。 abstractProcessor.httpupgrade.notsupported=このプロトコルは HTTP アップグレードに対応していません。 abstractProcessor.noExecute=このProcessor が現在SocketWrapperに関連付けられていないため、コンテナスレッドに処理を転送できません。 -abstractProcessor.nonContainerThreadError=非コンテナスレッドの処理中にエラーが発生しました。 すぐにコネクションが閉じられます。 abstractProcessor.pushrequest.notsupported=このプロトコルはサーバープッシュの要求に対応していません。 abstractProcessor.socket.ssl=SSL属性を取得する例外 diff -Nru tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_ko.properties tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_ko.properties --- tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_ko.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_ko.properties 2020-02-05 19:26:48.000000000 +0000 @@ -29,11 +29,12 @@ abstractProcessor.hostInvalid=호스트 [{0}]은(는) 유효하지 않습니다. abstractProcessor.httpupgrade.notsupported=HTTP 업그레이드는 이 프로토콜에 의해 지원되지 않습니다. abstractProcessor.noExecute=이 프로세서가 현재 SocketWrapper와 연관되어 있지 않기 때문에, 처리 작업을 컨테이너 쓰레드로 이관할 수 없습니다. -abstractProcessor.nonContainerThreadError=컨테이너 쓰레드가 아닌 쓰레드에서, 처리 도중 오류가 발생했습니다. 연결을 즉시 닫을 것입니다. abstractProcessor.pushrequest.notsupported=이 프로토콜은 서버 push 요청들을 지원하지 않습니다. abstractProcessor.socket.ssl=SSL 속성들을 얻으려는 중 예외 발생 abstractProtocol.mbeanDeregistrationFailed=MBean 서버 [{1}](으)로부터, [{0}](이)라는 이름의 MBean의 등록을 제거하지 못했습니다. +abstractProtocol.processorRegisterError=RequestProcessor 구성요소를 등록하는 중 오류 발생 +abstractProtocol.processorUnregisterError=RequestProcessor 구성요소를 등록 해제하는 중 오류 발생 abstractProtocolHandler.asyncTimeoutError=비동기 제한 시간 초과를 처리하는 동안 오류 발생 abstractProtocolHandler.destroy=프로토콜 핸들러 [{0}]을(를) 소멸시킵니다. @@ -47,6 +48,8 @@ asyncStateMachine.invalidAsyncState=비동기 상태가 [{1}]인 요청에 대하여, [{0}]을(를) 호출하는 것은 유효하지 않습니다. +compressionConfig.ContentEncodingParseFail=압축이 이미 사용되는지 여부를 점검하는 중, Content-Encoding 헤더를 파싱하지 못했습니다. + request.notAsync=오직 비동기 처리 또는 HTTP 업그레이드 처리 시에만, Non-blocking IO로의 전환이 유효합니다. request.nullReadListener=setReadListener()에 전달된 리스너는 널일 수 없습니다. request.readListenerSet=Non-blocking 읽기 리스너가 이미 설정되어 있습니다. diff -Nru tomcat9-9.0.27/java/org/apache/coyote/LocalStrings.properties tomcat9-9.0.31/java/org/apache/coyote/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/coyote/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -19,6 +19,7 @@ abstractConnectionHandler.negotiatedProcessor.fail=Failed to create Processor for negotiated protocol [{0}] abstractConnectionHandler.oome=Failed to complete processing of a request abstractConnectionHandler.process=Processing socket [{0}] with status [{1}] +abstractConnectionHandler.processorCreate=Created new processor [{0}] abstractConnectionHandler.processorPop=Popped processor [{0}] from cache abstractConnectionHandler.protocolexception.debug=ProtocolExceptions are normal, ignored abstractConnectionHandler.socketexception.debug=SocketExceptions are normal, ignored @@ -29,11 +30,14 @@ abstractProcessor.hostInvalid=The host [{0}] is not valid abstractProcessor.httpupgrade.notsupported=HTTP upgrade is not supported by this protocol abstractProcessor.noExecute=Unable to transfer processing to a container thread because this Processor is not currently associated with a SocketWrapper -abstractProcessor.nonContainerThreadError=An error occurred in processing while on a non-container thread. The connection will be closed immediately abstractProcessor.pushrequest.notsupported=Server push requests are not supported by this protocol abstractProcessor.socket.ssl=Exception getting SSL attributes abstractProtocol.mbeanDeregistrationFailed=Failed to deregister MBean named [{0}] from MBean server [{1}] +abstractProtocol.processorRegisterError=Error registering request processor +abstractProtocol.processorUnregisterError=Error unregistering request processor +abstractProcotol.waitingProcerssor.add=Added processor [{0}] to waiting processors +abstractProcotol.waitingProcerssor.remove=Removed processor [{0}] from waiting processors abstractProtocolHandler.asyncTimeoutError=Error processing async timeouts abstractProtocolHandler.destroy=Destroying ProtocolHandler [{0}] @@ -47,6 +51,8 @@ asyncStateMachine.invalidAsyncState=Calling [{0}] is not valid for a request with Async state [{1}] +compressionConfig.ContentEncodingParseFail=Failed to parse Content-Encoding header when checking to see if compression was already in use + request.notAsync=It is only valid to switch to non-blocking IO within async processing or HTTP upgrade processing request.nullReadListener=The listener passed to setReadListener() may not be null request.readListenerSet=The non-blocking read listener has already been set diff -Nru tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/coyote/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -15,16 +15,22 @@ abstractConnectionHandler.ioexception.debug=正常的 IOException,忽略 abstractConnectionHandler.processorPop=从缓存中弹出的处理器[{0}] +abstractConnectionHandler.socketexception.debug=(:SocketException是正常的,忽略 +abstractConnectionHandler.upgradeCreate=为套接字包装程序[{1}]创建了升级处理器[{0}] abstractProcessor.fallToDebug=注意:更多的请求解析错误将以DEBUG级别日志进行记录。 abstractProcessor.hostInvalid=[{0}] 是无效主机 abstractProcessor.httpupgrade.notsupported=此协议不支持HTTP升级(upgrade)。 abstractProcessor.socket.ssl=获取SSL属性异常 +abstractProtocol.processorRegisterError=注册请求处理器错误 +abstractProtocol.processorUnregisterError=注销请求处理器错误 + abstractProtocolHandler.asyncTimeoutError=错误的处理异步超时 abstractProtocolHandler.destroy=正在摧毁协议处理器 [{0}] abstractProtocolHandler.init=初始化协议处理器 [{0}] abstractProtocolHandler.start=开始协议处理句柄[{0}] +abstractProtocolHandler.stop=正在停止ProtocolHandler [{0}] asyncStateMachine.invalidAsyncState=调用[{0}]对于具有异步状态[{1}]的请求无效 diff -Nru tomcat9-9.0.27/java/org/apache/coyote/UpgradeProtocol.java tomcat9-9.0.31/java/org/apache/coyote/UpgradeProtocol.java --- tomcat9-9.0.27/java/org/apache/coyote/UpgradeProtocol.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/coyote/UpgradeProtocol.java 2020-02-05 19:26:48.000000000 +0000 @@ -91,4 +91,20 @@ * false */ public boolean accept(Request request); + + + /** + * Configure the HTTP/1.1 protocol that this UpgradeProcotol is nested + * under. Connections passed to this UpgradeProtocol via HTTP upgrade will + * have been initially handled by this HTTP/1.1 protocol implementation. + *

    + * The default implementation is a NO-OP. + * + * @param protocol The HTTP/1.1 protocol implementation that will initially + * handle any connections passed to this UpgradeProtocol via + * the HTTP upgrade mechanism + */ + public default void setHttp11Protocol(AbstractProtocol protocol) { + // NO-OP + } } diff -Nru tomcat9-9.0.27/java/org/apache/jasper/compiler/Compiler.java tomcat9-9.0.31/java/org/apache/jasper/compiler/Compiler.java --- tomcat9-9.0.27/java/org/apache/jasper/compiler/Compiler.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/jasper/compiler/Compiler.java 2020-02-05 19:26:48.000000000 +0000 @@ -101,9 +101,9 @@ /** * Compile the jsp file into equivalent servlet in .java file * - * @throws Exception Error generating Java source - * * @return A map of class names to JSR 045 source maps + * + * @throws Exception Error generating Java source */ protected Map generateJava() throws Exception { @@ -382,9 +382,9 @@ } try { + final Long jspLastModified = ctxt.getLastModified(ctxt.getJspFile()); Map smaps = generateJava(); File javaFile = new File(ctxt.getServletJavaFileName()); - Long jspLastModified = ctxt.getLastModified(ctxt.getJspFile()); if (!javaFile.setLastModified(jspLastModified.longValue())) { throw new JasperException(Localizer.getMessage("jsp.error.setLastModified", javaFile)); } diff -Nru tomcat9-9.0.27/java/org/apache/jasper/compiler/Generator.java tomcat9-9.0.31/java/org/apache/jasper/compiler/Generator.java --- tomcat9-9.0.27/java/org/apache/jasper/compiler/Generator.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/jasper/compiler/Generator.java 2020-02-05 19:26:48.000000000 +0000 @@ -3273,12 +3273,18 @@ } /* - * @param c The target class to which to coerce the given string @param - * s The string value @param attrName The name of the attribute whose - * value is being supplied @param propEditorClass The property editor - * for the given attribute @param isNamedAttribute true if the given - * attribute is a named attribute (that is, specified using the - * jsp:attribute standard action), and false otherwise + * @param c + * The target class to which to coerce the given string + * @param s + * The string value + * @param attrName + * The name of the attribute whose value is being supplied + * @param propEditorClass + * The property editor for the given attribute + * @param isNamedAttribute + * true if the given attribute is a named attribute (that + * is, specified using the jsp:attribute standard action), + * and false otherwise */ private String convertString(Class c, String s, String attrName, Class propEditorClass, boolean isNamedAttribute) { @@ -3695,8 +3701,8 @@ String className = tagInfo.getTagClassName(); int lastIndex = className.lastIndexOf('.'); if (lastIndex != -1) { - String pkgName = className.substring(0, lastIndex); - genPreamblePackage(pkgName); + String packageName = className.substring(0, lastIndex); + genPreamblePackage(packageName); className = className.substring(lastIndex + 1); } diff -Nru tomcat9-9.0.27/java/org/apache/jasper/JspCompilationContext.java tomcat9-9.0.31/java/org/apache/jasper/JspCompilationContext.java --- tomcat9-9.0.27/java/org/apache/jasper/JspCompilationContext.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/jasper/JspCompilationContext.java 2020-02-05 19:26:48.000000000 +0000 @@ -82,7 +82,9 @@ private volatile boolean removed = false; - private URLClassLoader jspLoader; + // volatile so changes are visible when multiple threads request a JSP file + // that has been modified + private volatile URLClassLoader jspLoader; private URL baseUrl; private Class servletClass; @@ -454,11 +456,11 @@ if (isTagFile()) { String className = tagInfo.getTagClassName(); int lastIndex = className.lastIndexOf('.'); - String pkgName = ""; + String packageName = ""; if (lastIndex != -1) { - pkgName = className.substring(0, lastIndex); + packageName = className.substring(0, lastIndex); } - return pkgName; + return packageName; } else { String dPackageName = getDerivedPackageName(); if (dPackageName.length() == 0) { @@ -767,4 +769,3 @@ return result.toString(); } } - diff -Nru tomcat9-9.0.27/java/org/apache/jasper/resources/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/jasper/resources/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/jasper/resources/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/jasper/resources/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -16,8 +16,11 @@ jasper.error.emptybodycontent.nonempty=根据 TLD,[{0}] 标签必须为空,但不是 jsp.error.action.isnottagfile=[{0}]行为只能用于标签文件 +jsp.error.action.istagfile=标签文件中不能使用[{0}]功能 jsp.error.attempt_to_clear_flushed_buffer=错误:尝试清空已刷新的缓冲区 +jsp.error.attr.quoted=应引用属性值 jsp.error.attribute.deferredmix=不能在同一属性值中同时使用 ${} 和 #{} EL 表达式 +jsp.error.attribute.duplicate=属性限定名在元素中必须是唯一的 jsp.error.attribute.noequal=期望的符号是等号 jsp.error.attribute.noescape=属性值[{0}]引用[{1}],在值内使用时必须被转义。 jsp.error.attribute.nowhitespace=JSP 规范要求一个属性名字前有空格 @@ -27,12 +30,15 @@ jsp.error.beans.noproperty=在[{1}]类型bean中找不到任何有关属性[{0}]的信息 jsp.error.beans.nullbean=尝试获取一个bean 操作在一个空对象上. jsp.error.beans.property.conversion=无法将字符串[{0}]转换为属性[{2}]的类[{1}]:[{3}] +jsp.error.beans.propertyeditor.notregistered=属性编辑器未注册到属性编辑管理器 jsp.error.cannotAddResolver=在第一次请求发生之后不能调用addELResolver jsp.error.compilation.source=加载源文件时出错[{0}] jsp.error.compiler=没有可用的Java编译器 +jsp.error.config_pagedir_encoding_mismatch=jsp属性组[{0}]中指定的页编码与page指令[{1}]中指定的页编码不同 jsp.error.corresponding.servlet=生成的servlet错误:\n jsp.error.could.not.add.taglibraries=不能增加一个或者多个tag 库. jsp.error.data.file.processing=处理文件 [{0}] 错误 +jsp.error.deferredvaluetypewithoutdeferredvalue=如果“deferredValue”的值不是“true”的话,不能指定一个值类型 jsp.error.duplicate.name.jspattribute=标准或自定义操作中指定的属性[{0}]也显示为随附的jsp:属性中name属性的值 jsp.error.el.template.deferred=#{...} 不允许出现在模板文本中 jsp.error.el_interpreter_class.instantiation=加载或实例化ELInterpreter类[{0}]失败 @@ -43,22 +49,28 @@ jsp.error.include.tag=无效的jsp:include标签 jsp.error.internal.filenotfound=内部错误:找不到文件 [{0}] jsp.error.internal.unexpectedNodeType=节点类型不一致 +jsp.error.invalid.attribute=[{0}]有一个无效属性:[{1}] jsp.error.invalid.tagdir=标签文件目录 [{0}] 不以"/WEB-INF/tags"开头 jsp.error.invalid.version=为标签 [{0}] 定义了无效的 JSP 版本号 jsp.error.ise_on_clear=当缓存大小等于0时调用clear()函数是非法的 jsp.error.jspbody.emptybody.only=标签[{}]的标签体内智能包含jsp:attribute +jsp.error.jspbody.invalidUse=JSP:主体必须是标准或自定义操作的子元素 jsp.error.jspbody.required=如果使用 jsp:attribute,则必须使用 jsp:body 为 [{0}] 指定标记正文。 +jsp.error.jspc.missingTarget=缺少目标:必须指定-webapp或-uriroot或一个或多个jsp页 jsp.error.jspelement.missing.name=XML强制性约束:属性name缺失。 jsp.error.jspoutput.conflict=&lt; jsp:output&gt;:非法使多个[{0}]出现不同的值(旧:[{1}],新:[{2}]) jsp.error.jspoutput.doctypenamesystem=<jsp:output>: 'doctype-root-element' 和 'doctype-system' 必须一起出现 +jsp.error.jspoutput.nonemptybody=<jsp:output>不能有正文 jsp.error.jsproot.version.invalid=版本号 [{0}] 无效,版本号必须是"1.2"、"2.0"、"2.1"、"2.2"、"2.3"中的一个 jsp.error.jsptext.badcontent='&lt;',当出现在&lt; jsp:text&gt;的主体中时,必须封装在CDATA中 jsp.error.lastModified=无法确定文件 [{0}] 的最后修改日期 +jsp.error.library.invalid=根据库[{0}](:[{1}],jsp页无效 jsp.error.loadclass.taghandler=无法为TAG [{1}]加载标记处理程序类[{0}] jsp.error.location=行.: [{0}], 列: [{1}] jsp.error.mandatory.attribute=[{0}]: 强制性属性 [{1}] 缺失。 jsp.error.missing_attribute=根据TLD或标记文件,标记[{1}]必须使用属性[{0}] jsp.error.negativeBufferSize=缓存大小是负数 +jsp.error.nested.jspattribute=jsp:attribute标准操作不能嵌套在另一个jsp:attribute标准操作中 jsp.error.nested.jspbody=JSP:体标准动作不能嵌套在另一个jsp:body 或者 jsp:属性标准动作中 jsp.error.nested_jsproot=嵌套的<jsp:root> jsp.error.no.scratch.dir=JSP引擎未配置scratch文件夹。\n\ @@ -70,17 +82,22 @@ jsp.error.overflow=错误:JSP缓冲区溢出 jsp.error.page.conflict.contenttype=Page指令:非法出现多次出现的''contentType''具有不同的值(old:[{0}],new:[{1}]) jsp.error.page.conflict.errorpage=页指令:不同值的多次出现“errorPage”的非法值(旧:[{0}],新:[{1}]) +jsp.error.page.conflict.language=页指令:多次出现不同值的“语言”(旧:[{0}],新:[{1}]) jsp.error.page.conflict.session=Page指令:多次出现具有不同值的''session''非法(old:[{0}],new:[{1}]) +jsp.error.page.conflict.trimdirectivewhitespaces=页面指令:违法出现多个有不同的值(旧值:[{0}],新值:[{1}])的''trimDirectiveWhitespaces'' jsp.error.page.invalid.deferredsyntaxallowedasliteral=页面指令:deferredSyntaxAllowedAsLiteral的值无效 jsp.error.page.invalid.import=网页指令:无效引用 jsp.error.page.invalid.iselignored=页面指令:忽略的无效值 jsp.error.page.invalid.session=页面提示:session值无效 jsp.error.page.multi.pageencoding=页指令不能有多次出现的页编码 +jsp.error.page.noSession=无法访问不参与任何会话的页中的会话作用域 jsp.error.page.nullThrowable=空异常 jsp.error.param.invalidUse=jsp:param 不能在jsp:include、jsp:forward或jsp:params等元素外使用 jsp.error.paramexpected=使用“name”和“value”属性期望“jsp:param”标准操作 jsp.error.params.invalidUse=参数jsp:params必须是jsp:plugin的直接孩子参数 jsp.error.parse.xml=无法解析 XML 文件 [{0}] +jsp.error.parse.xml.scripting.invalid.body=[{0}]元素的主体不能包含任何XML元素 +jsp.error.plugin.badtype=jsp:plugin中“type”属性的值非法:必须是“bean”或“applet” jsp.error.plugin.nocode=代码未定义在jsp:plugin中 jsp.error.plugin.notype=jsp:plugin中未声明type jsp.error.precompilation=无法预编译JSP[{0}] @@ -97,8 +114,12 @@ jsp.error.single.line.number=JSP文件:[{1}] 的第 [{0}] 行发生了一个错误 jsp.error.stream.close.failed=流.关闭失败 jsp.error.stream.closed=流.关闭 +jsp.error.tag.conflict.iselignored=TAG指令:多次出现不同值的“isELIgnored”(旧:[{0}],New:[{1}]) jsp.error.tag.conflict.language=标签指令:非法出现多次出现的具有不同值的“语言”(旧:[{0}],新:[{1}]) +jsp.error.tag.conflict.trimdirectivewhitespaces=标签指令:非法地多次出现具有不同值的“trimDirectiveWhitespaces”(旧值:[{0}),新值:[{1}]) +jsp.error.tag.invalid.deferredsyntaxallowedasliteral=标签指令):deferredSyntaxAllowedAsLiteral的值无效 jsp.error.tag.invalid.trimdirectivewhitespaces=TAG指令:trimDirectiveWhitespaces的值无效 +jsp.error.tagdirective.badbodycontent=标签指令中的无效的内容体[{0}] jsp.error.tagfile.nameFrom.badAttribute=属性指令(在行[{1}]中声明并且其name属性为[{0}],此name-from-attribute属性的值)必须是java.lang.String类型,是“required”而不是一个“rtexprvalue”。 jsp.error.tagfile.nameFrom.noAttribute=找不到具有值[{0}]的name属性的属性指令,该属性是name-from-attribute属性的值。 jsp.error.taglibDirective.absUriCannotBeResolved=无法在web.xml或使用此应用程序部署的jar文件中解析绝对uri:[{0}] @@ -109,6 +130,7 @@ jsp.error.text.has_subelement=&LT; JSP:文本&GT; 不得有任何子元素 jsp.error.tld.fn.invalid.signature=TLD中函数签名的语法无效。 标签库:[{0}],功能:[{1}] jsp.error.tld.mandatory.element.missing=TLD [{1}] 中强制 TLD 元素 [{0}] 不存在或为空 +jsp.error.tlv.invalid.page=):[{0}]和[{1}]的TagLibraryValidator的验证错误消息 jsp.error.unable.deleteClassFile=无法删除class文件[{0}] jsp.error.unable.load=无法加载JSP的相关类 jsp.error.unable.renameClassFile=无法重命名类文件[{0}]为[{1}] @@ -117,6 +139,7 @@ jsp.error.unbalanced.endtag=结束标签</{0}不对称 jsp.error.unknown_attribute_type=属性[{0}]的未知属性类型[{1}]。 jsp.error.unsupported.encoding=不支持的编码:[{0}] +jsp.error.variable.both.name=不能在变量指令的属性属性中同时指定给定的名称和名称 jsp.error.variable.either.name=必须在变量指令中指定 name-given 或 name-from-attribute 属性 jsp.error.xml.badStandardAction=无效、标准的action: [{0}] jsp.exception=在 [{1}] 行处理 [{0}] 时发生异常 @@ -124,6 +147,7 @@ jsp.message.jsp_added=增加JSP 为路径[{0}]为上下文[{1}]的队列 jsp.message.jsp_queue_update=在上下文[{1}]队列中更新路径为[{0}]的JSP jsp.message.jsp_removed_excess=从上下文[{1}]的队列中移除额外在路径[{0}]中JSP, +jsp.message.jsp_removed_idle=在[{2}]毫秒之后删除上下文[{1}]中路径[{0}]的空闲JSP jsp.message.jsp_unload_check=在context[{0}]中检查未加载的jsp,jsp总共:[{1}]队列长度[{2}] jsp.tldCache.noTldSummary=至少有一个JAR被扫描用于TLD但尚未包含TLD。 为此记录器启用调试日志记录,以获取已扫描但未在其中找到TLD的完整JAR列表。 在扫描期间跳过不需要的JAR可以缩短启动时间和JSP编译时间。 jsp.tldCache.tldInDir=在目录 [{0}]中找到了TLD文件。 @@ -132,6 +156,8 @@ jsp.warning.dumpSmap=警告:初始化堆内存的值无效。将使用“false”的默认值 jsp.warning.enablePooling=警告:initParam enablePooling的值无效。将使用默认值“true” jsp.warning.fork=警告:initParam的值无效。将使用“true”的默认值 +jsp.warning.noJarScanner=警告:ServletContext中没有设置org.apache.tomcat.JarScaner。回到默认的JarScaner实现。 +jsp.warning.suppressSmap=警告:suppressSmap的初始化参数无效。将使用默认值“false” jspc.delete.fail=无法删除文件 [{0}] jspc.error.fileDoesNotExist=文件参数 [{0}] 不存在 diff -Nru tomcat9-9.0.27/java/org/apache/jasper/servlet/JspServletWrapper.java tomcat9-9.0.31/java/org/apache/jasper/servlet/JspServletWrapper.java --- tomcat9-9.0.27/java/org/apache/jasper/servlet/JspServletWrapper.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/jasper/servlet/JspServletWrapper.java 2020-02-05 19:26:48.000000000 +0000 @@ -281,6 +281,7 @@ synchronized (this) { if (getReloadInternal() || tagHandlerClass == null) { tagHandlerClass = ctxt.load(); + // Volatile 'reload' forces in order write of 'tagHandlerClass' reload = false; } } @@ -326,7 +327,7 @@ } } } - target = tagHandlerClass.newInstance(); + target = tagHandlerClass.getConstructor().newInstance(); } else { target = getServlet(); } diff -Nru tomcat9-9.0.27/java/org/apache/naming/factory/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/naming/factory/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/naming/factory/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/naming/factory/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,7 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +factoryBase.factoryClassError=无法加载资源工厂类 +factoryBase.factoryCreationError=无法创建资源工厂实例 +factoryBase.instanceCreationError=无法创建资源实例 + lookupFactory.createFailed=无法创建JNDI查找工厂类实例 +lookupFactory.loadFailed=无法加载JNDI查找工厂类 lookupFactory.typeMismatch=期望JNDI引用[{0}]的类型为[{1}],但查找[{2}]返回类型为[{3}]的对象 resourceLinkFactory.nullType=引用全局资源 [{1}] 的本地资源链接 [{0}] 未指定所需的属性类型 diff -Nru tomcat9-9.0.27/java/org/apache/naming/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/naming/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/naming/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/naming/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,9 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. +contextBindings.noContextBoundToCL=没有绑定到此类加载器的命名上下文 +contextBindings.noContextBoundToThread=没有绑定到此线程的命名上下文 contextBindings.unknownContext=未知.上下文名:[{0}] namingContext.contextExpected=上下文Context未绑定名称name namingContext.failResolvingReference=解析引用时意外异常 selectorContext.methodUsingName=用[{1}]的name属性调用方法[{0}] +selectorContext.methodUsingString=使用字符串[{1}]调用方法[{0}] +selectorContext.noJavaUrl=必须通过java:url访问此上下文 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/buildutil/SignCode.java tomcat9-9.0.31/java/org/apache/tomcat/buildutil/SignCode.java --- tomcat9-9.0.27/java/org/apache/tomcat/buildutil/SignCode.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/buildutil/SignCode.java 2020-02-05 19:26:48.000000000 +0000 @@ -51,7 +51,11 @@ import org.w3c.dom.NodeList; /** - * Ant task that submits a file to the Symantec code-signing service. + * Ant task that submits a file to the Digicert (formally Symantec) code-signing + * service. The service is defined by the published + * WSDL. + * Note that while the service has migrated to a Digicert domain, the namespace + * continues to use a Symantec domain. */ public class SignCode extends Task { @@ -64,7 +68,7 @@ static { try { SIGNING_SERVICE_URL = new URL( - "https://api-appsec-cws.ws.symantec.com/webtrust/SigningService"); + "https://api-appsec.pki.digicert.com/webtrust/SigningService"); } catch (MalformedURLException e) { throw new IllegalArgumentException(e); } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/buildutil/translate/Export.java tomcat9-9.0.31/java/org/apache/tomcat/buildutil/translate/Export.java --- tomcat9-9.0.27/java/org/apache/tomcat/buildutil/translate/Export.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/buildutil/translate/Export.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,56 +0,0 @@ -/* -* 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.buildutil.translate; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.Properties; - -/** - * Generates a single properties file per language for import into a translation - * tool. - */ -public class Export { - - private static final Map translations = new HashMap<>(); - - public static void main(String... args) throws IOException { - File root = new File("."); - for (String dir : Constants.SEARCH_DIRS) { - File directory = new File(dir); - Utils.processDirectory(root, directory, translations); - } - - outputTranslations(); - } - - - private static void outputTranslations() { - - File storageDir = new File(Constants.STORAGE_DIR); - if (!storageDir.exists()) { - storageDir.mkdirs(); - } - - for (Map.Entry translationEntry : translations.entrySet()) { - Utils.export(translationEntry.getKey(), translationEntry.getValue(), storageDir); - } - } -} - diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/buildutil/translate/Utils.java tomcat9-9.0.31/java/org/apache/tomcat/buildutil/translate/Utils.java --- tomcat9-9.0.27/java/org/apache/tomcat/buildutil/translate/Utils.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/buildutil/translate/Utils.java 2020-02-05 19:26:48.000000000 +0000 @@ -78,7 +78,11 @@ static void processDirectory(File root, File dir, Map translations) throws IOException { - for (File f : dir.listFiles()) { + File[] files = dir.listFiles(); + if (files == null) { + throw new IllegalArgumentException("Not a directory [" + dir.getAbsolutePath() + "]"); + } + for (File f : files) { if (f.isDirectory()) { processDirectory(root, f, translations); } else if (f.isFile()) { diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/dbcp2/AbandonedTrace.java 2020-02-05 19:26:48.000000000 +0000 @@ -58,45 +58,6 @@ } /** - * Initializes abandoned tracing for this object. - * - * @param parent - * AbandonedTrace parent object. - */ - private void init(final AbandonedTrace parent) { - if (parent != null) { - parent.addTrace(this); - } - } - - /** - * Gets the last time this object was used in milliseconds. - * - * @return long time in milliseconds. - */ - @Override - public long getLastUsed() { - return lastUsedMillis; - } - - /** - * Sets the time this object was last used to the current time in milliseconds. - */ - protected void setLastUsed() { - lastUsedMillis = System.currentTimeMillis(); - } - - /** - * Sets the time in milliseconds this object was last used. - * - * @param lastUsedMillis - * time in milliseconds. - */ - protected void setLastUsed(final long lastUsedMillis) { - this.lastUsedMillis = lastUsedMillis; - } - - /** * Adds an object to the list of objects being traced. * * @param trace @@ -119,6 +80,16 @@ } /** + * Gets the last time this object was used in milliseconds. + * + * @return long time in milliseconds. + */ + @Override + public long getLastUsed() { + return lastUsedMillis; + } + + /** * Gets a list of objects being traced by this object. * * @return List of objects. @@ -145,6 +116,30 @@ } /** + * Initializes abandoned tracing for this object. + * + * @param parent + * AbandonedTrace parent object. + */ + private void init(final AbandonedTrace parent) { + if (parent != null) { + parent.addTrace(this); + } + } + + /** + * Removes this object the source object is tracing. + * + * @param source The object tracing + * @since 2.7.0 + */ + protected void removeThisTrace(final Object source) { + if (source instanceof AbandonedTrace) { + AbandonedTrace.class.cast(source).removeTrace(this); + } + } + + /** * Removes a child object this object is tracing. * * @param trace @@ -167,14 +162,19 @@ } /** - * Removes this object the source object is tracing. + * Sets the time this object was last used to the current time in milliseconds. + */ + protected void setLastUsed() { + lastUsedMillis = System.currentTimeMillis(); + } + + /** + * Sets the time in milliseconds this object was last used. * - * @param source The object tracing - * @since 2.7.0 + * @param lastUsedMillis + * time in milliseconds. */ - protected void removeThisTrace(final Object source) { - if (source instanceof AbandonedTrace) { - AbandonedTrace.class.cast(source).removeTrace(this); - } + protected void setLastUsed(final long lastUsedMillis) { + this.lastUsedMillis = lastUsedMillis; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/dbcp2/DelegatingConnection.java 2020-02-05 19:26:48.000000000 +0000 @@ -621,7 +621,7 @@ // Statement's when it is closed. // DBCP-288. Not all the traced objects will be statements final List traces = getTrace(); - if (traces != null && traces.isEmpty()) { + if (traces != null && !traces.isEmpty()) { final List thrownList = new ArrayList<>(); final Iterator traceIter = traces.iterator(); while (traceIter.hasNext()) { diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/dbcp2/DelegatingStatement.java 2020-02-05 19:26:48.000000000 +0000 @@ -153,18 +153,18 @@ thrownList.add(e); } } - clearTrace(); } - if (statement != null) { - try { - statement.close(); - } catch (Exception e) { - if (connection != null) { - // Does not rethrow e. - connection.handleExceptionNoThrow(e); - } - thrownList.add(e); + clearTrace(); + } + if (statement != null) { + try { + statement.close(); + } catch (Exception e) { + if (connection != null) { + // Does not rethrow e. + connection.handleExceptionNoThrow(e); } + thrownList.add(e); } } } finally { diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/BaseKeyedPooledObjectFactory.java 2020-02-05 19:26:48.000000000 +0000 @@ -21,7 +21,9 @@ *

    * All operations defined here are essentially no-op's. *

    + *

    * This class is immutable, and therefore thread-safe. + *

    * * @see KeyedPooledObjectFactory * diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/BaseObjectPool.java 2020-02-05 19:26:48.000000000 +0000 @@ -22,6 +22,7 @@ * indicating it is unsupported or throw {@link UnsupportedOperationException}. *

    * This class is intended to be thread-safe. + *

    * * @param Type of element pooled in this pool. * diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/DefaultEvictionPolicy.java 2020-02-05 19:26:48.000000000 +0000 @@ -31,7 +31,9 @@ * {@link GenericObjectPool#getSoftMinEvictableIdleTimeMillis()} / * {@link GenericKeyedObjectPool#getSoftMinEvictableIdleTimeMillis()} * + *

    * This class is immutable and thread-safe. + *

    * * @param the type of objects in the pool * diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/EvictionConfig.java 2020-02-05 19:26:48.000000000 +0000 @@ -22,6 +22,7 @@ * its own specific configuration attributes. *

    * This class is immutable and thread-safe. + *

    * * @since 2.0 */ diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/EvictionTimer.java 2020-02-05 19:26:48.000000000 +0000 @@ -120,12 +120,9 @@ public Thread newThread(final Runnable runnable) { final Thread thread = new Thread(null, runnable, "commons-pool-evictor-thread"); thread.setDaemon(true); // POOL-363 - Required for applications using Runtime.addShutdownHook(). - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Void run() { - thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader()); - return null; - } + AccessController.doPrivileged((PrivilegedAction) () -> { + thread.setContextClassLoader(EvictorThreadFactory.class.getClassLoader()); + return null; }); return thread; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/GenericKeyedObjectPool.java 2020-02-05 19:26:48.000000000 +0000 @@ -50,10 +50,12 @@ * {@link #borrowObject borrowObject} methods. Each time a new key value is * provided to one of these methods, a sub-new pool is created under the given * key to be managed by the containing GenericKeyedObjectPool. + *

    *

    * Note that the current implementation uses a ConcurrentHashMap which uses * equals() to compare keys. * This means that distinct instance keys must be distinguishable using equals. + *

    *

    * Optionally, one may configure the pool to examine and possibly evict objects * as they sit idle in the pool and to ensure that a minimum number of idle @@ -62,12 +64,15 @@ * configuring this optional feature. Eviction runs contend with client threads * for access to objects in the pool, so if they run too frequently performance * issues may result. + *

    *

    * Implementation note: To prevent possible deadlocks, care has been taken to * ensure that no call to a factory method will occur within a synchronization * block. See POOL-125 and DBCP-44 for more information. + *

    *

    * This class is intended to be thread-safe. + *

    * * @see GenericObjectPool * @@ -447,6 +452,11 @@ final ObjectDeque objectDeque = poolMap.get(key); + if (objectDeque == null) { + throw new IllegalStateException( + "No keyed pool found under the given key."); + } + final PooledObject p = objectDeque.getAllObjects().get(new IdentityWrapper<>(obj)); if (p == null) { @@ -1077,8 +1087,16 @@ final ObjectDeque objectDeque = register(key); try { - final boolean isIdle = objectDeque.getIdleObjects().remove(toDestroy); - + boolean isIdle; + synchronized(toDestroy) { + // Check idle state directly + isIdle = toDestroy.getState().equals(PooledObjectState.IDLE); + // If idle, not under eviction test, or always is true, remove instance, + // updating isIdle if instance is found in idle objects + if (isIdle || always) { + isIdle = objectDeque.getIdleObjects().remove(toDestroy); + } + } if (isIdle || always) { objectDeque.getAllObjects().remove(new IdentityWrapper<>(toDestroy.getObject())); toDestroy.invalidate(); @@ -1151,10 +1169,9 @@ */ private void deregister(final K k) { Lock lock = keyLock.readLock(); - ObjectDeque objectDeque; try { lock.lock(); - objectDeque = poolMap.get(k); + final ObjectDeque objectDeque = poolMap.get(k); final long numInterested = objectDeque.getNumInterested().decrementAndGet(); if (numInterested == 0 && objectDeque.getCreateCount().get() == 0) { // Potential to remove key diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/GenericObjectPool.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/GenericObjectPool.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/GenericObjectPool.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/GenericObjectPool.java 2020-02-05 19:26:48.000000000 +0000 @@ -38,7 +38,8 @@ *

    * When coupled with the appropriate {@link PooledObjectFactory}, * GenericObjectPool provides robust pooling functionality for - * arbitrary objects.

    + * arbitrary objects. + *

    *

    * Optionally, one may configure the pool to examine and possibly evict objects * as they sit idle in the pool and to ensure that a minimum number of idle @@ -46,7 +47,8 @@ * which runs asynchronously. Caution should be used when configuring this * optional feature. Eviction runs contend with client threads for access to * objects in the pool, so if they run too frequently performance issues may - * result.

    + * result. + *

    *

    * The pool can also be configured to detect and remove "abandoned" objects, * i.e. objects that have been checked out of the pool but neither used nor @@ -59,13 +61,16 @@ * their last use will be queried * using the getLastUsed method on that interface; otherwise * abandonment is determined by how long an object has been checked out from - * the pool.

    + * the pool. + *

    *

    * Implementation note: To prevent possible deadlocks, care has been taken to * ensure that no call to a factory method will occur within a synchronization - * block. See POOL-125 and DBCP-44 for more information.

    + * block. See POOL-125 and DBCP-44 for more information. + *

    *

    - * This class is intended to be thread-safe.

    + * This class is intended to be thread-safe. + *

    * * @see GenericKeyedObjectPool * @@ -576,6 +581,11 @@ } catch (final Exception e) { swallowException(e); } + try { + ensureIdle(1, false); + } catch (final Exception e) { + swallowException(e); + } } else { if (getLifo()) { idleObjects.addFirst(p); @@ -931,15 +941,6 @@ destroyedCount.incrementAndGet(); createCount.decrementAndGet(); } - - if (idleObjects.isEmpty() && idleObjects.hasTakeWaiters()) { - // POOL-356. - // In case there are already threads waiting on something in the pool - // (e.g. idleObjects.takeFirst(); then we need to provide them a fresh instance. - // Otherwise they will be stuck forever (or until timeout) - final PooledObject freshPooled = create(); - idleObjects.put(freshPooled); - } } @Override diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/InterruptibleReentrantLock.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/InterruptibleReentrantLock.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/InterruptibleReentrantLock.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/InterruptibleReentrantLock.java 2020-02-05 19:26:48.000000000 +0000 @@ -26,6 +26,7 @@ * class is intended for internal use only. *

    * This class is intended to be thread-safe. + *

    * * @since 2.0 */ diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/LinkedBlockingDeque.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/LinkedBlockingDeque.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/LinkedBlockingDeque.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/LinkedBlockingDeque.java 2020-02-05 19:26:48.000000000 +0000 @@ -34,6 +34,7 @@ * is equal to {@link Integer#MAX_VALUE}. Linked nodes are * dynamically created upon each insertion unless this would bring the * deque above capacity. + *

    * *

    Most operations run in constant time (ignoring time spent * blocking). Exceptions include {@link #remove(Object) remove}, @@ -41,14 +42,17 @@ * #removeLastOccurrence removeLastOccurrence}, {@link #contains * contains}, {@link #iterator iterator.remove()}, and the bulk * operations, all of which run in linear time. + *

    * *

    This class and its iterator implement all of the * optional methods of the {@link Collection} and {@link * Iterator} interfaces. + *

    * *

    This class is a member of the * * Java Collections Framework. + *

    * * @param the type of elements held in this collection * diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/SecurityManagerCallStack.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/SecurityManagerCallStack.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/SecurityManagerCallStack.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/SecurityManagerCallStack.java 2020-02-05 19:26:48.000000000 +0000 @@ -52,12 +52,7 @@ public SecurityManagerCallStack(final String messageFormat, final boolean useTimestamp) { this.messageFormat = messageFormat; this.dateFormat = useTimestamp ? new SimpleDateFormat(messageFormat) : null; - this.securityManager = AccessController.doPrivileged(new PrivilegedAction() { - @Override - public PrivateSecurityManager run() { - return new PrivateSecurityManager(); - } - }); + this.securityManager = AccessController.doPrivileged((PrivilegedAction) () -> new PrivateSecurityManager()); } @Override diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/SoftReferenceObjectPool.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/SoftReferenceObjectPool.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/impl/SoftReferenceObjectPool.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/impl/SoftReferenceObjectPool.java 2020-02-05 19:26:48.000000000 +0000 @@ -32,6 +32,7 @@ * {@link org.apache.tomcat.dbcp.pool2.ObjectPool}. *

    * This class is intended to be thread-safe. + *

    * * @param * Type of element pooled in this pool. @@ -185,6 +186,8 @@ * * @param obj * instance to return to the pool + * @throws IllegalArgumentException + * if obj is not currently part of this pool */ @Override public synchronized void returnObject(final T obj) throws Exception { diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/KeyedObjectPool.java 2020-02-05 19:26:48.000000000 +0000 @@ -17,6 +17,8 @@ package org.apache.tomcat.dbcp.pool2; import java.io.Closeable; +import java.util.Collection; +import java.util.Iterator; import java.util.NoSuchElementException; /** @@ -66,6 +68,75 @@ * @since 2.0 */ public interface KeyedObjectPool extends Closeable { + + /** + * Create an object using the {@link KeyedPooledObjectFactory factory} or + * other implementation dependent mechanism, passivate it, and then place it + * in the idle object pool. addObject is useful for + * "pre-loading" a pool with idle objects (Optional operation). + * + * @param key the key a new instance should be added to + * + * @throws Exception + * when {@link KeyedPooledObjectFactory#makeObject} fails. + * @throws IllegalStateException + * after {@link #close} has been called on this pool. + * @throws UnsupportedOperationException + * when this pool cannot add new idle objects. + */ + void addObject(K key) throws Exception, IllegalStateException, + UnsupportedOperationException; + + /** + * Calls {@link KeyedObjectPool#addObject(Object)} with each + * key in keys for count number of times. This has + * the same effect as calling {@link #addObjects(Object, int)} + * for each key in the keys collection. + * + * @param keys + * {@link Collection} of keys to add objects for. + * @param count + * the number of idle objects to add for each key. + * @throws Exception + * when {@link KeyedObjectPool#addObject(Object)} fails. + * @throws IllegalArgumentException + * when keyedPool, keys, or any value + * in keys is null. + * @see #addObjects(Object, int) + */ + default void addObjects(final Collection keys, final int count) throws Exception, IllegalArgumentException { + if (keys == null) { + throw new IllegalArgumentException(PoolUtils.MSG_NULL_KEYS); + } + final Iterator iter = keys.iterator(); + while (iter.hasNext()) { + addObjects(iter.next(), count); + } + } + + /** + * Calls {@link KeyedObjectPool#addObject(Object)} + * key count number of times. + * + * @param key + * the key to add objects for. + * @param count + * the number of idle objects to add for key. + * @throws Exception + * when {@link KeyedObjectPool#addObject(Object)} fails. + * @throws IllegalArgumentException + * when key is null. + * @since 2.8.0 + */ + default void addObjects(final K key, final int count) throws Exception, IllegalArgumentException { + if (key == null) { + throw new IllegalArgumentException(PoolUtils.MSG_NULL_KEY); + } + for (int i = 0; i < count; i++) { + addObject(key); + } + } + /** * Obtains an instance from this pool for the specified key. *

    @@ -105,75 +176,50 @@ V borrowObject(K key) throws Exception, NoSuchElementException, IllegalStateException; /** - * Return an instance to the pool. By contract, obj - * must have been obtained using - * {@link #borrowObject borrowObject} or a related method as defined in an - * implementation or sub-interface using a key that is - * equivalent to the one used to borrow the instance in the first place. - * - * @param key the key used to obtain the object - * @param obj a {@link #borrowObject borrowed} instance to be returned. + * Clears the pool, removing all pooled instances (optional operation). * - * @throws IllegalStateException - * if an attempt is made to return an object to the pool that - * is in any state other than allocated (i.e. borrowed). - * Attempting to return an object more than once or attempting - * to return an object that was never borrowed from the pool - * will trigger this exception. + * @throws UnsupportedOperationException when this implementation doesn't + * support the operation * - * @throws Exception if an instance cannot be returned to the pool + * @throws Exception if the pool cannot be cleared */ - void returnObject(K key, V obj) throws Exception; + void clear() throws Exception, UnsupportedOperationException; /** - * Invalidates an object from the pool. - *

    - * By contract, obj must have been obtained - * using {@link #borrowObject borrowObject} or a related method as defined - * in an implementation or sub-interface using a key that is - * equivalent to the one used to borrow the Object in the first - * place. - *

    - *

    - * This method should be used when an object that has been borrowed is - * determined (due to an exception or other problem) to be invalid. - *

    + * Clears the specified pool, removing all pooled instances corresponding to + * the given key (optional operation). * - * @param key the key used to obtain the object - * @param obj a {@link #borrowObject borrowed} instance to be returned. + * @param key the key to clear * - * @throws Exception if the instance cannot be invalidated + * @throws UnsupportedOperationException when this implementation doesn't + * support the operation + * + * @throws Exception if the key cannot be cleared */ - void invalidateObject(K key, V obj) throws Exception; + void clear(K key) throws Exception, UnsupportedOperationException; /** - * Create an object using the {@link KeyedPooledObjectFactory factory} or - * other implementation dependent mechanism, passivate it, and then place it - * in the idle object pool. addObject is useful for - * "pre-loading" a pool with idle objects (Optional operation). - * - * @param key the key a new instance should be added to - * - * @throws Exception - * when {@link KeyedPooledObjectFactory#makeObject} fails. - * @throws IllegalStateException - * after {@link #close} has been called on this pool. - * @throws UnsupportedOperationException - * when this pool cannot add new idle objects. + * Close this pool, and free any resources associated with it. + *

    + * Calling {@link #addObject addObject} or + * {@link #borrowObject borrowObject} after invoking this method on a pool + * will cause them to throw an {@link IllegalStateException}. + *

    + *

    + * Implementations should silently fail if not all resources can be freed. + *

    */ - void addObject(K key) throws Exception, IllegalStateException, - UnsupportedOperationException; + @Override + void close(); /** - * Returns the number of instances corresponding to the given - * key currently idle in this pool. Returns a negative value if - * this information is not available. - * - * @param key the key to query - * @return the number of instances corresponding to the given - * key currently idle in this pool. + * Returns the total number of instances currently borrowed from this pool but + * not yet returned. Returns a negative value if this information is not + * available. + * @return the total number of instances currently borrowed from this pool but + * not yet returned. */ - int getNumIdle(K key); + int getNumActive(); /** * Returns the number of instances currently borrowed from but not yet @@ -194,48 +240,55 @@ int getNumIdle(); /** - * Returns the total number of instances currently borrowed from this pool but - * not yet returned. Returns a negative value if this information is not - * available. - * @return the total number of instances currently borrowed from this pool but - * not yet returned. + * Returns the number of instances corresponding to the given + * key currently idle in this pool. Returns a negative value if + * this information is not available. + * + * @param key the key to query + * @return the number of instances corresponding to the given + * key currently idle in this pool. */ - int getNumActive(); + int getNumIdle(K key); /** - * Clears the pool, removing all pooled instances (optional operation). + * Invalidates an object from the pool. + *

    + * By contract, obj must have been obtained + * using {@link #borrowObject borrowObject} or a related method as defined + * in an implementation or sub-interface using a key that is + * equivalent to the one used to borrow the Object in the first + * place. + *

    + *

    + * This method should be used when an object that has been borrowed is + * determined (due to an exception or other problem) to be invalid. + *

    * - * @throws UnsupportedOperationException when this implementation doesn't - * support the operation + * @param key the key used to obtain the object + * @param obj a {@link #borrowObject borrowed} instance to be returned. * - * @throws Exception if the pool cannot be cleared + * @throws Exception if the instance cannot be invalidated */ - void clear() throws Exception, UnsupportedOperationException; + void invalidateObject(K key, V obj) throws Exception; /** - * Clears the specified pool, removing all pooled instances corresponding to - * the given key (optional operation). + * Return an instance to the pool. By contract, obj + * must have been obtained using + * {@link #borrowObject borrowObject} or a related method as defined in an + * implementation or sub-interface using a key that is + * equivalent to the one used to borrow the instance in the first place. * - * @param key the key to clear + * @param key the key used to obtain the object + * @param obj a {@link #borrowObject borrowed} instance to be returned. * - * @throws UnsupportedOperationException when this implementation doesn't - * support the operation + * @throws IllegalStateException + * if an attempt is made to return an object to the pool that + * is in any state other than allocated (i.e. borrowed). + * Attempting to return an object more than once or attempting + * to return an object that was never borrowed from the pool + * will trigger this exception. * - * @throws Exception if the key cannot be cleared - */ - void clear(K key) throws Exception, UnsupportedOperationException; - - /** - * Close this pool, and free any resources associated with it. - *

    - * Calling {@link #addObject addObject} or - * {@link #borrowObject borrowObject} after invoking this method on a pool - * will cause them to throw an {@link IllegalStateException}. - *

    - *

    - * Implementations should silently fail if not all resources can be freed. - *

    + * @throws Exception if an instance cannot be returned to the pool */ - @Override - void close(); + void returnObject(K key, V obj) throws Exception; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/ObjectPool.java 2020-02-05 19:26:48.000000000 +0000 @@ -60,6 +60,38 @@ public interface ObjectPool extends Closeable { /** + * Creates an object using the {@link PooledObjectFactory factory} or other + * implementation dependent mechanism, passivate it, and then place it in + * the idle object pool. addObject is useful for "pre-loading" + * a pool with idle objects. (Optional operation). + * + * @throws Exception + * when {@link PooledObjectFactory#makeObject} fails. + * @throws IllegalStateException + * after {@link #close} has been called on this pool. + * @throws UnsupportedOperationException + * when this pool cannot add new idle objects. + */ + void addObject() throws Exception, IllegalStateException, + UnsupportedOperationException; + + /** + * Calls {@link ObjectPool#addObject()} count + * number of times. + * + * @param count + * the number of idle objects to add. + * @throws Exception + * when {@link ObjectPool#addObject()} fails. + * @since 2.8.0 + */ + default void addObjects(final int count) throws Exception { + for (int i = 0; i < count; i++) { + addObject(); + } + } + + /** * Obtains an instance from this pool. *

    * Instances returned from this method will have been either newly created @@ -94,56 +126,36 @@ IllegalStateException; /** - * Returns an instance to the pool. By contract, obj - * must have been obtained using {@link #borrowObject()} or - * a related method as defined in an implementation or sub-interface. - * - * @param obj a {@link #borrowObject borrowed} instance to be returned. + * Clears any objects sitting idle in the pool, releasing any associated + * resources (optional operation). Idle objects cleared must be + * {@link PooledObjectFactory#destroyObject(PooledObject)}. * - * @throws IllegalStateException - * if an attempt is made to return an object to the pool that - * is in any state other than allocated (i.e. borrowed). - * Attempting to return an object more than once or attempting - * to return an object that was never borrowed from the pool - * will trigger this exception. + * @throws UnsupportedOperationException + * if this implementation does not support the operation * - * @throws Exception if an instance cannot be returned to the pool + * @throws Exception if the pool cannot be cleared */ - void returnObject(T obj) throws Exception; + void clear() throws Exception, UnsupportedOperationException; /** - * Invalidates an object from the pool. + * Closes this pool, and free any resources associated with it. *

    - * By contract, obj must have been obtained - * using {@link #borrowObject} or a related method as defined in an - * implementation or sub-interface. + * Calling {@link #addObject} or {@link #borrowObject} after invoking this + * method on a pool will cause them to throw an {@link IllegalStateException}. *

    *

    - * This method should be used when an object that has been borrowed is - * determined (due to an exception or other problem) to be invalid. + * Implementations should silently fail if not all resources can be freed. *

    - * - * @param obj a {@link #borrowObject borrowed} instance to be disposed. - * - * @throws Exception if the instance cannot be invalidated */ - void invalidateObject(T obj) throws Exception; + @Override + void close(); /** - * Creates an object using the {@link PooledObjectFactory factory} or other - * implementation dependent mechanism, passivate it, and then place it in - * the idle object pool. addObject is useful for "pre-loading" - * a pool with idle objects. (Optional operation). - * - * @throws Exception - * when {@link PooledObjectFactory#makeObject} fails. - * @throws IllegalStateException - * after {@link #close} has been called on this pool. - * @throws UnsupportedOperationException - * when this pool cannot add new idle objects. + * Returns the number of instances currently borrowed from this pool. Returns + * a negative value if this information is not available. + * @return the number of instances currently borrowed from this pool. */ - void addObject() throws Exception, IllegalStateException, - UnsupportedOperationException; + int getNumActive(); /** * Returns the number of instances currently idle in this pool. This may be @@ -155,34 +167,38 @@ int getNumIdle(); /** - * Returns the number of instances currently borrowed from this pool. Returns - * a negative value if this information is not available. - * @return the number of instances currently borrowed from this pool. - */ - int getNumActive(); - - /** - * Clears any objects sitting idle in the pool, releasing any associated - * resources (optional operation). Idle objects cleared must be - * {@link PooledObjectFactory#destroyObject(PooledObject)}. + * Invalidates an object from the pool. + *

    + * By contract, obj must have been obtained + * using {@link #borrowObject} or a related method as defined in an + * implementation or sub-interface. + *

    + *

    + * This method should be used when an object that has been borrowed is + * determined (due to an exception or other problem) to be invalid. + *

    * - * @throws UnsupportedOperationException - * if this implementation does not support the operation + * @param obj a {@link #borrowObject borrowed} instance to be disposed. * - * @throws Exception if the pool cannot be cleared + * @throws Exception if the instance cannot be invalidated */ - void clear() throws Exception, UnsupportedOperationException; + void invalidateObject(T obj) throws Exception; /** - * Closes this pool, and free any resources associated with it. - *

    - * Calling {@link #addObject} or {@link #borrowObject} after invoking this - * method on a pool will cause them to throw an {@link IllegalStateException}. - *

    - *

    - * Implementations should silently fail if not all resources can be freed. - *

    + * Returns an instance to the pool. By contract, obj + * must have been obtained using {@link #borrowObject()} or + * a related method as defined in an implementation or sub-interface. + * + * @param obj a {@link #borrowObject borrowed} instance to be returned. + * + * @throws IllegalStateException + * if an attempt is made to return an object to the pool that + * is in any state other than allocated (i.e. borrowed). + * Attempting to return an object more than once or attempting + * to return an object that was never borrowed from the pool + * will trigger this exception. + * + * @throws Exception if an instance cannot be returned to the pool */ - @Override - void close(); + void returnObject(T obj) throws Exception; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/PooledObject.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/PooledObject.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/PooledObject.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/PooledObject.java 2020-02-05 19:26:48.000000000 +0000 @@ -24,6 +24,7 @@ * state, for the pooled objects. *

    * Implementations of this class are required to be thread-safe. + *

    * * @param the type of object in the pool * @@ -187,7 +188,7 @@ * @param requireFullStackTrace the new configuration setting for abandoned object logging * @since 2.7.0 */ - default void setRequireFullStackTrace(boolean requireFullStackTrace) { + default void setRequireFullStackTrace(final boolean requireFullStackTrace) { // noop } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java --- tomcat9-9.0.27/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/dbcp/pool2/PoolUtils.java 2020-02-05 19:26:48.000000000 +0000 @@ -38,9 +38,9 @@ private static final String MSG_FACTOR_NEGATIVE = "factor must be positive."; private static final String MSG_MIN_IDLE = "minIdle must be non-negative."; - private static final String MSG_NULL_KEY = "key must not be null."; + static final String MSG_NULL_KEY = "key must not be null."; private static final String MSG_NULL_KEYED_POOL = "keyedPool must not be null."; - private static final String MSG_NULL_KEYS = "keys must not be null."; + static final String MSG_NULL_KEYS = "keys must not be null."; private static final String MSG_NULL_POOL = "pool must not be null."; /** @@ -220,15 +220,15 @@ * when {@link ObjectPool#addObject()} fails. * @throws IllegalArgumentException * when pool is null. + * @deprecated Use {@link ObjectPool#addObjects(int)}. */ + @Deprecated public static void prefill(final ObjectPool pool, final int count) throws Exception, IllegalArgumentException { if (pool == null) { throw new IllegalArgumentException(MSG_NULL_POOL); } - for (int i = 0; i < count; i++) { - pool.addObject(); - } + pool.addObjects(count); } /** @@ -248,19 +248,16 @@ * @throws IllegalArgumentException * when keyedPool or key is * null. + * @deprecated Use {@link KeyedObjectPool#addObjects(Object, int)}. */ + @Deprecated public static void prefill(final KeyedObjectPool keyedPool, final K key, final int count) throws Exception, IllegalArgumentException { if (keyedPool == null) { throw new IllegalArgumentException(MSG_NULL_KEYED_POOL); } - if (key == null) { - throw new IllegalArgumentException(MSG_NULL_KEY); - } - for (int i = 0; i < count; i++) { - keyedPool.addObject(key); - } + keyedPool.addObjects(key, count); } /** @@ -283,17 +280,16 @@ * when keyedPool, keys, or any value * in keys is null. * @see #prefill(KeyedObjectPool, Object, int) + * @deprecated Use {@link KeyedObjectPool#addObjects(Collection, int)}. */ + @Deprecated public static void prefill(final KeyedObjectPool keyedPool, final Collection keys, final int count) throws Exception, IllegalArgumentException { if (keys == null) { throw new IllegalArgumentException(MSG_NULL_KEYS); } - final Iterator iter = keys.iterator(); - while (iter.hasNext()) { - prefill(keyedPool, iter.next(), count); - } + keyedPool.addObjects(keys, count); } /** diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/JarScanFilter.java tomcat9-9.0.31/java/org/apache/tomcat/JarScanFilter.java --- tomcat9-9.0.27/java/org/apache/tomcat/JarScanFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/JarScanFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -28,4 +28,13 @@ * false if it should be excluded */ boolean check(JarScanType jarScanType, String jarName); + + /** + * + * @return true if all of the scans should be skipped which + * can improve startup performance. The default is false. + */ + default boolean isSkipAll() { + return false; + } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantClass.java tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantClass.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantClass.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantClass.java 2020-02-05 19:26:48.000000000 +0000 @@ -48,7 +48,7 @@ /** * @return Name index in constant pool of class name. */ - public final int getNameIndex() { + public int getNameIndex() { return name_index; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantDouble.java tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantDouble.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantDouble.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantDouble.java 2020-02-05 19:26:48.000000000 +0000 @@ -48,7 +48,7 @@ /** * @return data, i.e., 8 bytes. */ - public final double getBytes() { + public double getBytes() { return bytes; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantFloat.java tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantFloat.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantFloat.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantFloat.java 2020-02-05 19:26:48.000000000 +0000 @@ -48,7 +48,7 @@ /** * @return data, i.e., 4 bytes. */ - public final float getBytes() { + public float getBytes() { return bytes; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantInteger.java tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantInteger.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantInteger.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantInteger.java 2020-02-05 19:26:48.000000000 +0000 @@ -48,7 +48,7 @@ /** * @return data, i.e., 4 bytes. */ - public final int getBytes() { + public int getBytes() { return bytes; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantLong.java tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantLong.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/classfile/ConstantLong.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/classfile/ConstantLong.java 2020-02-05 19:26:48.000000000 +0000 @@ -48,7 +48,7 @@ /** * @return data, i.e., 8 bytes. */ - public final long getBytes() { + public long getBytes() { return bytes; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/Const.java tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/Const.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/bcel/Const.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/bcel/Const.java 2020-02-05 19:26:48.000000000 +0000 @@ -23,12 +23,14 @@ public final class Const { /** One of the access flags for fields, methods, or classes. - * @see - * Flag definitions for Fields in the Java Virtual Machine Specification (Java SE 8 Edition). - * @see - * Flag definitions for Methods in the Java Virtual Machine Specification (Java SE 8 Edition). - * @see - * Flag definitions for Classes in the Java Virtual Machine Specification (Java SE 8 Edition). + * @see + * Flag definitions for Classes in the Java Virtual Machine Specification (Java SE 9 Edition). + * @see + * Flag definitions for Fields in the Java Virtual Machine Specification (Java SE 9 Edition). + * @see + * Flag definitions for Methods in the Java Virtual Machine Specification (Java SE 9 Edition). + * @see + * Flag definitions for Inner Classes in the Java Virtual Machine Specification (Java SE 9 Edition). */ public static final short ACC_FINAL = 0x0010; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/buf/Asn1Parser.java tomcat9-9.0.31/java/org/apache/tomcat/util/buf/Asn1Parser.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/buf/Asn1Parser.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/buf/Asn1Parser.java 2020-02-05 19:26:48.000000000 +0000 @@ -83,6 +83,12 @@ } + public void parseBytes(byte[] dest) { + System.arraycopy(source, pos, dest, 0, dest.length); + pos += dest.length; + } + + private int next() { return source[pos++] & 0xFF; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/buf/Asn1Writer.java tomcat9-9.0.31/java/org/apache/tomcat/util/buf/Asn1Writer.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/buf/Asn1Writer.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/buf/Asn1Writer.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,95 @@ +/* + * 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.buf; + +public class Asn1Writer { + + public static byte[] writeSequence(byte[]... components) { + int len = 0; + for (byte[] component : components) { + len += component.length; + } + + byte[] combined = new byte[len]; + int pos = 0; + for (byte[] component : components) { + System.arraycopy(component, 0, combined, pos, component.length); + pos += component.length; + } + + return writeTag((byte) 0x30, combined); + } + + + public static byte[] writeInteger(int value) { + // How many bytes required to write the value? No more than 4 for int. + int valueSize = 1; + while ((value >> (valueSize * 8)) > 0) { + valueSize++; + } + + byte[] valueBytes = new byte[valueSize]; + int i = 0; + while (valueSize > 0) { + valueBytes[i] = (byte) (value >> (8 * (valueSize - 1))); + value = value >> 8; + valueSize--; + i++; + } + + return writeTag((byte) 0x02, valueBytes); + } + + public static byte[] writeOctetString(byte[] data) { + return writeTag((byte) 0x04, data); + } + + public static byte[] writeTag(byte tagId, byte[] data) { + int dataSize = data.length; + // How many bytes to write the length? + int lengthSize = 1; + if (dataSize >127) { + // 1 byte we have is now used to record how many bytes we need to + // record a length > 127 + // Result is lengthSize = 1 + number of bytes to record length + do { + lengthSize++; + } + while ((dataSize >> (lengthSize * 8)) > 0); + } + + // 1 for tag + lengthSize + dataSize + byte[] result = new byte[1 + lengthSize + dataSize]; + result[0] = tagId; + if (dataSize < 128) { + result[1] = (byte) dataSize; + } else { + // lengthSize is 1 + number of bytes for length + result[1] = (byte) (127 + lengthSize); + int i = lengthSize; + while (dataSize > 0) { + result[i] = (byte) (dataSize & 0xFF); + dataSize = dataSize >> 8; + i--; + } + } + + System.arraycopy(data, 0, result, 1 + lengthSize, data.length); + + return result; + } +} diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/buf/CharChunk.java tomcat9-9.0.31/java/org/apache/tomcat/util/buf/CharChunk.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/buf/CharChunk.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/buf/CharChunk.java 2020-02-05 19:26:48.000000000 +0000 @@ -165,7 +165,7 @@ // -------------------- Adding data to the buffer -------------------- - public void append(char b) throws IOException { + public void append(char c) throws IOException { makeSpace(1); int limit = getLimitInternal(); @@ -173,7 +173,7 @@ if (end >= limit) { flushBuffer(); } - buff[end++] = b; + buff[end++] = c; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/buf/CharsetCache.java tomcat9-9.0.31/java/org/apache/tomcat/util/buf/CharsetCache.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/buf/CharsetCache.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/buf/CharsetCache.java 2020-02-05 19:26:48.000000000 +0000 @@ -149,7 +149,9 @@ "29626c", "833", "cp29626c", "ibm-1140", "ibm-1141", "ibm-1142", "ibm-1143", "ibm-1144", "ibm-1145", "ibm-1146", "ibm-1147", "ibm-1148", "ibm-1149", "ibm-29626c", "ibm-858", "ibm-eucjp", "ibm1140", "ibm1141", "ibm1142", "ibm1143", "ibm1144", "ibm1145", "ibm1146", "ibm1147", "ibm1148", "ibm1149", "ibm29626c", - "ibm858", "x-ibm29626c" + "ibm858", "x-ibm29626c", + // Added from HPE JVM 1.8.0.17-hp-ux + "cp1051", "cp1386", "cshproman8", "hp-roman8", "ibm-1051", "r8", "roman8", "roman9" }; private static final Charset DUMMY_CHARSET = new DummyCharset("Dummy", null); diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/buf/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/buf/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/buf/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/buf/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,4 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +asn1Parser.lengthInvalid=无效长度 [{0}]字节报告,但是输入数据的长度是 [{1}]字节 +asn1Parser.tagMismatch=期望找到值 [{0}]但是却找到值 [{1}] + +hexUtils.fromHex.nonHex=输入只能由十六进制数字组成 + uDecoder.urlDecode.conversionError=使用编码[{1}]解码[{0}]失败 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/buf/package.html tomcat9-9.0.31/java/org/apache/tomcat/util/buf/package.html --- tomcat9-9.0.27/java/org/apache/tomcat/util/buf/package.html 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/buf/package.html 2020-02-05 19:26:48.000000000 +0000 @@ -22,7 +22,7 @@

    Encoding is a critical operation for performance. There are few tricks in this package - the C2B and -B2C converters are caching a ISReader/OSWriter and keep everything allocated to do the conversions +B2C converters are caching an ISReader/OSWriter and keep everything allocated to do the conversions in any VM without any garbage.

    diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/codec/binary/Base64.java tomcat9-9.0.31/java/org/apache/tomcat/util/codec/binary/Base64.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/codec/binary/Base64.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/codec/binary/Base64.java 2020-02-05 19:26:48.000000000 +0000 @@ -139,6 +139,10 @@ */ /** Mask used to extract 6 bits, used when encoding */ private static final int MASK_6BITS = 0x3f; + /** Mask used to extract 4 bits, used when decoding final trailing character. */ + private static final int MASK_4BITS = 0xf; + /** Mask used to extract 2 bits, used when decoding final trailing character. */ + private static final int MASK_2BITS = 0x3; // The static final fields above are used for the original static byte[] methods on Base64. // The private member fields below are used with the new streaming approach, which requires @@ -483,12 +487,12 @@ // TODO not currently tested; perhaps it is impossible? break; case 2 : // 12 bits = 8 + 4 - validateCharacter(4, context); + validateCharacter(MASK_4BITS, context); context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); break; case 3 : // 18 bits = 8 + 8 + 2 - validateCharacter(2, context); + validateCharacter(MASK_2BITS, context); context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); @@ -792,20 +796,22 @@ /** - *

    - * Validates whether the character is possible in the context of the set of possible base 64 values. - *

    + * Validates whether decoding the final trailing character is possible in the context + * of the set of possible base 64 values. + * + *

    The character is valid if the lower bits within the provided mask are zero. This + * is used to test the final trailing base-64 digit is zero in the bits that will be discarded. * - * @param numBitsToDrop number of least significant bits to check + * @param emptyBitsMask The mask of the lower bits that should be empty * @param context the context to be used * * @throws IllegalArgumentException if the bits being checked contain any non-zero value */ - private long validateCharacter(final int numBitsToDrop, final Context context) { - if ((context.ibitWorkArea & numBitsToDrop) != 0) { - throw new IllegalArgumentException( - "Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value"); + private static void validateCharacter(final int emptyBitsMask, final Context context) { + if ((context.ibitWorkArea & emptyBitsMask) != 0) { + throw new IllegalArgumentException( + "Last encoded character (before the paddings if any) is a valid base 64 alphabet but not a possible value. " + + "Expected the discarded bits to be zero."); } - return context.ibitWorkArea >> numBitsToDrop; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java tomcat9-9.0.31/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/codec/binary/BaseNCodec.java 2020-02-05 19:26:48.000000000 +0000 @@ -136,6 +136,18 @@ */ private static final int DEFAULT_BUFFER_SIZE = 128; + /** + * The maximum size buffer to allocate. + * + *

    This is set to the same size used in the JDK {@code java.util.ArrayList}:

    + *
    + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit. + *
    + */ + private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8; + /** Mask used to extract 8 bits, used in decoding bytes */ protected static final int MASK_8BITS = 0xff; @@ -165,7 +177,7 @@ private final int chunkSeparatorLength; /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} + * Note lineLength is rounded down to the nearest multiple of the encoded block size. * If chunkSeparatorLength is zero, then chunking is disabled. * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) @@ -178,7 +190,7 @@ } /** - * Note lineLength is rounded down to the nearest multiple of {@link #encodedBlockSize} + * Note lineLength is rounded down to the nearest multiple of the encoded block size. * If chunkSeparatorLength is zero, then chunking is disabled. * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) @@ -220,7 +232,7 @@ /** * Get the default buffer size. Can be overridden. * - * @return {@link #DEFAULT_BUFFER_SIZE} + * @return the default buffer size. */ protected int getDefaultBufferSize() { return DEFAULT_BUFFER_SIZE; @@ -229,18 +241,69 @@ /** * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. * @param context the context to be used + * @param minCapacity the minimum required capacity + * @return the resized byte[] buffer + * @throws OutOfMemoryError if the {@code minCapacity} is negative + */ + private static byte[] resizeBuffer(final Context context, final int minCapacity) { + // Overflow-conscious code treats the min and new capacity as unsigned. + final int oldCapacity = context.buffer.length; + int newCapacity = oldCapacity * DEFAULT_BUFFER_RESIZE_FACTOR; + if (compareUnsigned(newCapacity, minCapacity) < 0) { + newCapacity = minCapacity; + } + if (compareUnsigned(newCapacity, MAX_BUFFER_SIZE) > 0) { + newCapacity = createPositiveCapacity(minCapacity); + } + + final byte[] b = new byte[newCapacity]; + System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); + context.buffer = b; + return b; + } + + /** + * Compares two {@code int} values numerically treating the values + * as unsigned. Taken from JDK 1.8. + * + *

    TODO: Replace with JDK 1.8 Integer::compareUnsigned(int, int).

    + * + * @param x the first {@code int} to compare + * @param y the second {@code int} to compare + * @return the value {@code 0} if {@code x == y}; a value less + * than {@code 0} if {@code x < y} as unsigned values; and + * a value greater than {@code 0} if {@code x > y} as + * unsigned values */ - private byte[] resizeBuffer(final Context context) { - if (context.buffer == null) { - context.buffer = new byte[getDefaultBufferSize()]; - context.pos = 0; - context.readPos = 0; - } else { - final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; - System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); - context.buffer = b; + private static int compareUnsigned(int x, int y) { + return Integer.compare(x + Integer.MIN_VALUE, y + Integer.MIN_VALUE); + } + + /** + * Create a positive capacity at least as large the minimum required capacity. + * If the minimum capacity is negative then this throws an OutOfMemoryError as no array + * can be allocated. + * + * @param minCapacity the minimum capacity + * @return the capacity + * @throws OutOfMemoryError if the {@code minCapacity} is negative + */ + private static int createPositiveCapacity(int minCapacity) { + if (minCapacity < 0) { + // overflow + throw new OutOfMemoryError("Unable to allocate array size: " + (minCapacity & 0xffffffffL)); } - return context.buffer; + // This is called when we require buffer expansion to a very big array. + // Use the conservative maximum buffer size if possible, otherwise the biggest required. + // + // Note: In this situation JDK 1.8 java.util.ArrayList returns Integer.MAX_VALUE. + // This excludes some VMs that can exceed MAX_BUFFER_SIZE but not allocate a full + // Integer.MAX_VALUE length array. + // The result is that we may have to allocate an array of this size more than once if + // the capacity must be expanded again. + return (minCapacity > MAX_BUFFER_SIZE) ? + minCapacity : + MAX_BUFFER_SIZE; } /** @@ -251,8 +314,15 @@ * @return the buffer */ protected byte[] ensureBufferSize(final int size, final Context context){ - if ((context.buffer == null) || (context.buffer.length < context.pos + size)){ - return resizeBuffer(context); + if (context.buffer == null) { + context.buffer = new byte[getDefaultBufferSize()]; + context.pos = 0; + context.readPos = 0; + + // Overflow-conscious: + // x + y > z == x + y - z > 0 + } else if (context.pos + size - context.buffer.length > 0) { + return resizeBuffer(context, context.pos + size); } return context.buffer; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/compat/GraalCompat.java tomcat9-9.0.31/java/org/apache/tomcat/util/compat/GraalCompat.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/compat/GraalCompat.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/compat/GraalCompat.java 2020-02-05 19:26:48.000000000 +0000 @@ -18,7 +18,7 @@ import java.io.IOException; -class GraalCompat extends JreCompat { +class GraalCompat extends Jre9Compat { private static final boolean GRAAL; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/compat/JreCompat.java tomcat9-9.0.31/java/org/apache/tomcat/util/compat/JreCompat.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/compat/JreCompat.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/compat/JreCompat.java 2020-02-05 19:26:48.000000000 +0000 @@ -49,7 +49,7 @@ if (GraalCompat.isSupported()) { instance = new GraalCompat(); graalAvailable = true; - jre9Available = false; + jre9Available = Jre9Compat.isSupported(); } else if (Jre9Compat.isSupported()) { instance = new Jre9Compat(); graalAvailable = false; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/compat/LocalStrings_fr.properties tomcat9-9.0.31/java/org/apache/tomcat/util/compat/LocalStrings_fr.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/compat/LocalStrings_fr.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/compat/LocalStrings_fr.properties 2020-02-05 19:26:48.000000000 +0000 @@ -14,6 +14,8 @@ # limitations under the License. jre9Compat.invalidModuleUri=L''URI du module fournie [{0}] n''a pas pu être convertie en URL pour être traitée par le JarScanner +jre9Compat.javaPre9=Le code est considéré être exécuté sur une JVM antérieure à Java 9 car la classe n'a pas été trouvée +jre9Compat.unexpected=Impossible de créer les références vers les classes et méthodes de Java 9 jreCompat.noApplicationProtocol=Le Java Runtime utilisé ne supporte pas SSLEngine.getApplicationProtocol(). Il faut Java 9 pour utiliser cette option. jreCompat.noApplicationProtocols=L'environnement Java ne supporte pas SSLParameters.setApplicationProtocols(), cette fonctionnalité demande Java 9 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/compat/LocalStrings_ko.properties tomcat9-9.0.31/java/org/apache/tomcat/util/compat/LocalStrings_ko.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/compat/LocalStrings_ko.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/compat/LocalStrings_ko.properties 2020-02-05 19:26:48.000000000 +0000 @@ -14,6 +14,8 @@ # limitations under the License. jre9Compat.invalidModuleUri=[{0}](으)로 제공된 모듈 URI는 JarScanner가 처리할 수 있는 URL로 변환될 수 없습니다. +jre9Compat.javaPre9=Java 9 클래스가 발견되지 않습니다. Java 9 이전 버전에서 동작하고 있는 것으로 보입니다. +jre9Compat.unexpected=Java 9 클래스들과 메소드들을 참조할 수 없습니다. jreCompat.noApplicationProtocol=자바 런타임이 SSLEngine.getApplicationProtocol()을 지원하지 않습니다. 이 기능을 사용하려면 Java 9를 사용해야 합니다. jreCompat.noApplicationProtocols=자바 런타임이 SSLParameters.setApplicationProtocols()을 지원하지 않습니다. 이 기능을 사용하려면 Java 9를 사용해야 합니다. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/compat/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/compat/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/compat/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/compat/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -14,5 +14,7 @@ # limitations under the License. jre9Compat.invalidModuleUri=提供的模块URI [{0}]无法转换为JarScanner要处理的URL +jre9Compat.javaPre9=类未找到,所以假设代码运行在pre-Java 8虚拟机上 +jre9Compat.unexpected=创建对Java 9类的依赖和方法失败 jreCompat.noApplicationProtocol=Java 运行时不支持 SSLEngine.getApplicationProtocol()。要使用该功能你必须使用 Java 9。 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/descriptor/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/descriptor/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/descriptor/LocalStrings_zh_CN.properties 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/descriptor/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,16 @@ +# 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. + +digesterFactory.missingSchema=XML模型[{0}]未找到,如果XML校验功能开启了的话,这很可能终止XML校验 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/descriptor/web/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/descriptor/web/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/descriptor/web/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/descriptor/web/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -16,15 +16,25 @@ filterDef.invalidFilterName=过滤器定义中的 [{0}] 无效。 securityConstraint.uncoveredHttpOmittedMethod=对于URL模式[{0}]的安全性约束,将发现HTTP方法[{1}]。 +securityConstraint.uncoveredHttpOmittedMethodFix=添加url模式为[{0}]的安全约束以拒绝使用未覆盖的http方法[{1}]的访问 +webRuleSet.absoluteOrdering=<绝对值排序>元素在web片段xml中无效,将被忽略。 webRuleSet.nameCount=元素只能出现1次 webRuleSet.postconstruct.duplicate=class [{0}] 有重复的 post 构造方法声明 +webXml.duplicateEnvEntry=重复的env-entry 名 [{0}] webXml.duplicateFilter=重复的过滤器名称 [{0}] webXml.duplicateServletMapping=名为 [{0}]和 [{1}] 的servlet不能映射为一个url模式(url-pattern) [{2}] webXml.mergeConflictDisplayName=显示名称在多个片段中被定义,这些片段包含不同的值,包括位于[{1}]的[{0}]的片段。 +webXml.mergeConflictFilter=筛选器[{0}]在多个片段中定义不一致,包括位于[{2}]的名为[{1}]的片段 +webXml.mergeConflictOrder=片段相对顺序包含循环引用。这可以通过在web.xml中使用绝对排序来解决。 +webXml.mergeConflictServlet=Servlet[{0}]在多个片段中的定义不一致,包括位于[{2}]的名为[{1}]的片段 webXml.mergeConflictSessionCookieName=会话cookie名称在多个具有不同值的片段中定义不一致,包括位于 [{1}] 的片段 [{0}] webXml.mergeConflictSessionTimeout=会话超时以不同值的多个片段不一致地定义,这些片段包括位于[{1}]的具有名称[{0}]的片段。 webXml.mergeConflictSessionTrackingMode=会话跟踪模式在多个片段中定义不一致,包括位于[{1}]的名称为[{0}]的片段 webXml.reservedName=使用保留名称[{0}]检测到web.xml文件。 此片段将忽略name元素。 +webXml.unrecognisedPublicId=对于web.xml文件,公共ID[{0}]不匹配任何已知的公共ID‘,因此无法识别版本。 webXml.version.unknown=未知版本字符串 [{0}]。将使用默认版本。 +webXml.wrongFragmentName=在web.xml绝对排序标签上使用了错误的片段名[{0}]! + +webXmlParser.applicationPosition=出现在第 [{0}] 行 第 [{1}] 列 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/digester/Digester.java tomcat9-9.0.31/java/org/apache/tomcat/util/digester/Digester.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/digester/Digester.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/digester/Digester.java 2020-02-05 19:26:48.000000000 +0000 @@ -41,6 +41,7 @@ import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.IntrospectionUtils; +import org.apache.tomcat.util.IntrospectionUtils.PropertySource; import org.apache.tomcat.util.buf.B2CConverter; import org.apache.tomcat.util.res.StringManager; import org.apache.tomcat.util.security.PermissionCheck; @@ -55,6 +56,7 @@ import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.ext.DefaultHandler2; +import org.xml.sax.ext.EntityResolver2; import org.xml.sax.ext.Locator2; import org.xml.sax.helpers.AttributesImpl; @@ -815,12 +817,20 @@ reader.setDTDHandler(this); reader.setContentHandler(this); + EntityResolver entityResolver = getEntityResolver(); if (entityResolver == null) { - reader.setEntityResolver(this); + entityResolver = this; + } + + // Wrap the resolver so we can perform ${...} property replacement + if (entityResolver instanceof EntityResolver2) { + entityResolver = new EntityResolver2Wrapper((EntityResolver2) entityResolver, source, classLoader); } else { - reader.setEntityResolver(entityResolver); + entityResolver = new EntityResolverWrapper(entityResolver, source, classLoader); } + reader.setEntityResolver(entityResolver); + reader.setProperty("http://xml.org/sax/properties/lexical-handler", this); reader.setErrorHandler(this); @@ -1976,4 +1986,64 @@ return new StringBuilder(out); } } + + + private static class EntityResolverWrapper implements EntityResolver { + + private final EntityResolver entityResolver; + private final PropertySource[] source; + private final ClassLoader classLoader; + + public EntityResolverWrapper(EntityResolver entityResolver, PropertySource[] source, ClassLoader classLoader) { + this.entityResolver = entityResolver; + this.source = source; + this.classLoader = classLoader; + } + + @Override + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException { + publicId = replace(publicId); + systemId = replace(systemId); + return entityResolver.resolveEntity(publicId, systemId); + } + + protected String replace(String input) { + try { + return IntrospectionUtils.replaceProperties(input, null, source, classLoader); + } catch (Exception e) { + return input; + } + } + } + + + private static class EntityResolver2Wrapper extends EntityResolverWrapper implements EntityResolver2 { + + private final EntityResolver2 entityResolver2; + + public EntityResolver2Wrapper(EntityResolver2 entityResolver, PropertySource[] source, + ClassLoader classLoader) { + super(entityResolver, source, classLoader); + this.entityResolver2 = entityResolver; + } + + @Override + public InputSource getExternalSubset(String name, String baseURI) + throws SAXException, IOException { + name = replace(name); + baseURI = replace(baseURI); + return entityResolver2.getExternalSubset(name, baseURI); + } + + @Override + public InputSource resolveEntity(String name, String publicId, String baseURI, + String systemId) throws SAXException, IOException { + name = replace(name); + publicId = replace(publicId); + baseURI = replace(baseURI); + systemId = replace(systemId); + return entityResolver2.resolveEntity(name, publicId, baseURI, systemId); + } + } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/digester/LocalStrings_ja.properties tomcat9-9.0.31/java/org/apache/tomcat/util/digester/LocalStrings_ja.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/digester/LocalStrings_ja.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/digester/LocalStrings_ja.properties 2020-02-05 19:26:48.000000000 +0000 @@ -28,9 +28,9 @@ digester.noRulesFound=[{0}]と一致するルールは見つかりませんでした。 digester.parseError=行[{0}]の列[{1}]のエラー解析 digester.parseErrorFatal=行[{0}]の列[{1}]で致命的なエラーを解析します。 -digester.parseWarning=行番号 [{0}] 列番号 [{1]] の解釈中に警告が発生しました。 +digester.parseWarning=行番号 [{0}] 列番号 [{1}] の解釈中に警告が発生しました。 digester.propertySourceLoadError=プロパティソースクラス [{0}] の読み込み中に異常が発生しました。 rule.createError=オブジェクトの作成中にエラーが発生しました: [{0}] rule.noClassName=[{0}] [{1}]に指定されたクラス名がありません。 -rule.noProperty=マッチしたパターン [{0}] でプロパティ [[1}] に [{2}] を設定できません。 +rule.noProperty=マッチしたパターン [{0}] でプロパティ [{1}] に [{2}] を設定できません。 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/digester/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/digester/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/digester/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/digester/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,5 +13,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -digester.encodingInvalid=Java运行时环境无法识别 [{0}]编码,将被忽略 +digester.encodingInvalid=JRE无法识别 [{0}]编码,将被忽略 digester.failedToUpdateAttributes=属性[{0}]更新失败,旧数据为[{1}] diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/ConfigFileLoader.java tomcat9-9.0.31/java/org/apache/tomcat/util/file/ConfigFileLoader.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/ConfigFileLoader.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/ConfigFileLoader.java 2020-02-05 19:26:48.000000000 +0000 @@ -21,8 +21,6 @@ import java.io.InputStream; import java.net.URI; -import org.apache.tomcat.util.res.StringManager; - /** * This class is used to obtain {@link InputStream}s for configuration files * from a given location String. This allows greater flexibility than these @@ -30,14 +28,11 @@ */ public class ConfigFileLoader { - private static final StringManager sm = StringManager.getManager(ConfigFileLoader.class - .getPackage().getName()); - private static ConfigurationSource source; public static final ConfigurationSource getSource() { if (ConfigFileLoader.source == null) { - throw new IllegalStateException(sm.getString("configFileLoader.noConfigurationSource")); + return ConfigurationSource.DEFAULT; } return source; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/ConfigurationSource.java tomcat9-9.0.31/java/org/apache/tomcat/util/file/ConfigurationSource.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/ConfigurationSource.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/ConfigurationSource.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,10 +16,14 @@ */ package org.apache.tomcat.util.file; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URI; +import java.net.URL; /** * Abstracts configuration file storage. Allows Tomcat embedding using the regular @@ -30,6 +34,44 @@ */ public interface ConfigurationSource { + public static final ConfigurationSource DEFAULT = new ConfigurationSource() { + protected final File userDir = new File(System.getProperty("user.dir")); + protected final URI userDirUri = userDir.toURI(); + @Override + public Resource getResource(String name) throws IOException { + File f = new File(name); + if (!f.isAbsolute()) { + f = new File(userDir, name); + } + if (f.isFile()) { + return new Resource(new FileInputStream(f), f.toURI()); + } + URI uri = null; + try { + uri = getURI(name); + } catch (IllegalArgumentException e) { + throw new FileNotFoundException(name); + } + try { + URL url = uri.toURL(); + return new Resource(url.openConnection().getInputStream(), uri); + } catch (MalformedURLException e) { + throw new FileNotFoundException(name); + } + } + @Override + public URI getURI(String name) { + File f = new File(name); + if (!f.isAbsolute()) { + f = new File(userDir, name); + } + if (f.isFile()) { + return f.toURI(); + } + return userDirUri.resolve(name); + } + }; + /** * Represents a resource: a stream to the resource associated with * its URI. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_de.properties tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_de.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_de.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_de.properties 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -# 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. - -configFileLoader.noConfigurationSource=Es wurde keine Konfigurationsquelle per ConfigFileLoader.setSource gesetzt. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_fr.properties tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_fr.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_fr.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_fr.properties 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -# 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. - -configFileLoader.noConfigurationSource=Aucune source de configuration n'a été définie par ConfigFileLoader.setSource. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_ja.properties tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_ja.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_ja.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_ja.properties 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -# 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. - -configFileLoader.noConfigurationSource=ConfigFileLoader.setSourceを使用して設定ソースが設定されていません。 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_ko.properties tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_ko.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_ko.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_ko.properties 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -# 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. - -configFileLoader.noConfigurationSource=ConfigFileLoader.setSource를 사용하여, 설정 원본이 설정되지 않았습니다. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings.properties tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings.properties 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -# 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. - -configFileLoader.noConfigurationSource=No configuration source has been set using ConfigFileLoader.setSource. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/file/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/file/LocalStrings_zh_CN.properties 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -# 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. - -configFileLoader.noConfigurationSource=没有配置源时,采用ConfigFileLoader.setSource 配置 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/CookieProcessorBase.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/CookieProcessorBase.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/CookieProcessorBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/CookieProcessorBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -43,7 +43,7 @@ ANCIENT_DATE = COOKIE_DATE_FORMAT.get().format(new Date(10000)); } - private SameSiteCookies sameSiteCookies = SameSiteCookies.NONE; + private SameSiteCookies sameSiteCookies = SameSiteCookies.UNSET; public SameSiteCookies getSameSiteCookies() { return sameSiteCookies; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/disk/DiskFileItem.java 2020-02-05 19:26:48.000000000 +0000 @@ -546,7 +546,7 @@ * Removes the file contents from the temporary storage. */ @Override - protected void finalize() { + protected void finalize() throws Throwable { if (dfos == null || dfos.isInMemory()) { return; } @@ -555,6 +555,7 @@ if (outputFile != null && outputFile.exists()) { outputFile.delete(); } + super.finalize(); } /** diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/FileItemIterator.java 2020-02-05 19:26:48.000000000 +0000 @@ -17,12 +17,56 @@ package org.apache.tomcat.util.http.fileupload; import java.io.IOException; +import java.util.List; + +import org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException; +import org.apache.tomcat.util.http.fileupload.impl.SizeLimitExceededException; /** * An iterator, as returned by * {@link FileUploadBase#getItemIterator(RequestContext)}. */ public interface FileItemIterator { + /** Returns the maximum size of a single file. An {@link FileSizeLimitExceededException} + * will be thrown, if there is an uploaded file, which is exceeding this value. + * By default, this value will be copied from the {@link FileUploadBase#getFileSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setFileSizeMax(long)} on this object. + * @return The maximum size of a single, uploaded file. The value -1 indicates "unlimited". + */ + public long getFileSizeMax(); + + /** Sets the maximum size of a single file. An {@link FileSizeLimitExceededException} + * will be thrown, if there is an uploaded file, which is exceeding this value. + * By default, this value will be copied from the {@link FileUploadBase#getFileSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setFileSizeMax(long)} on this object, so + * there is no need to configure it here. + * Note:Changing this value doesn't affect files, that have already been uploaded. + * @param pFileSizeMax The maximum size of a single, uploaded file. The value -1 indicates "unlimited". + */ + public void setFileSizeMax(long pFileSizeMax); + + /** Returns the maximum size of the complete HTTP request. A {@link SizeLimitExceededException} + * will be thrown, if the HTTP request will exceed this value. + * By default, this value will be copied from the {@link FileUploadBase#getSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setSizeMax(long)} on this object. + * @return The maximum size of the complete HTTP requqest. The value -1 indicates "unlimited". + */ + public long getSizeMax(); + + /** Returns the maximum size of the complete HTTP request. A {@link SizeLimitExceededException} + * will be thrown, if the HTTP request will exceed this value. + * By default, this value will be copied from the {@link FileUploadBase#getSizeMax() + * FileUploadBase} object, however, the user may replace the default value with a + * request specific value by invoking {@link #setSizeMax(long)} on this object. + * Note: Setting the maximum size on this object will work only, if the iterator is not + * yet initialized. In other words: If the methods {@link #hasNext()}, {@link #next()} have not + * yet been invoked. + * @param pSizeMax The maximum size of the complete HTTP request. The value -1 indicates "unlimited". + */ + public void setSizeMax(long pSizeMax); /** * Returns, whether another instance of {@link FileItemStream} @@ -34,7 +78,7 @@ * @return True, if one or more additional file items * are available, otherwise false. */ - boolean hasNext() throws FileUploadException, IOException; + public boolean hasNext() throws FileUploadException, IOException; /** * Returns the next available {@link FileItemStream}. @@ -47,6 +91,7 @@ * @return FileItemStream instance, which provides * access to the next file item. */ - FileItemStream next() throws FileUploadException, IOException; + public FileItemStream next() throws FileUploadException, IOException; + public List getFileItems() throws FileUploadException, IOException; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -17,19 +17,18 @@ package org.apache.tomcat.util.http.fileupload; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.NoSuchElementException; -import org.apache.tomcat.util.http.fileupload.MultipartStream.ItemInputStream; -import org.apache.tomcat.util.http.fileupload.util.Closeable; +import org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl; +import org.apache.tomcat.util.http.fileupload.impl.FileItemStreamImpl; +import org.apache.tomcat.util.http.fileupload.impl.FileUploadIOException; +import org.apache.tomcat.util.http.fileupload.impl.IOFileUploadException; import org.apache.tomcat.util.http.fileupload.util.FileItemHeadersImpl; -import org.apache.tomcat.util.http.fileupload.util.LimitedInputStream; import org.apache.tomcat.util.http.fileupload.util.Streams; @@ -253,7 +252,7 @@ public FileItemIterator getItemIterator(RequestContext ctx) throws FileUploadException, IOException { try { - return new FileItemIteratorImpl(ctx); + return new FileItemIteratorImpl(this, ctx); } catch (FileUploadIOException e) { // unwrap encapsulated SizeException throw (FileUploadException) e.getCause(); @@ -286,7 +285,7 @@ while (iter.hasNext()) { final FileItemStream item = iter.next(); // Don't use getName() here to prevent an InvalidFileNameException. - final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name; + final String fileName = ((FileItemStreamImpl) item).getName(); FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(), item.isFormField(), fileName); items.add(fileItem); @@ -363,7 +362,7 @@ * * @return The boundary, as a byte array. */ - protected byte[] getBoundary(String contentType) { + public byte[] getBoundary(String contentType) { ParameterParser parser = new ParameterParser(); parser.setLowerCaseNames(true); // Parameter parser can handle null input @@ -387,7 +386,7 @@ * * @return The file name for the current encapsulation. */ - protected String getFileName(FileItemHeaders headers) { + public String getFileName(FileItemHeaders headers) { return getFileName(headers.getHeader(CONTENT_DISPOSITION)); } @@ -429,7 +428,7 @@ * * @return The field name for the current encapsulation. */ - protected String getFieldName(FileItemHeaders headers) { + public String getFieldName(FileItemHeaders headers) { return getFieldName(headers.getHeader(CONTENT_DISPOSITION)); } @@ -467,7 +466,7 @@ * * @return A Map containing the parsed HTTP request headers. */ - protected FileItemHeaders getParsedHeaders(String headerPart) { + public FileItemHeaders getParsedHeaders(String headerPart) { final int len = headerPart.length(); FileItemHeadersImpl headers = newFileItemHeaders(); int start = 0; @@ -549,687 +548,6 @@ } /** - * The iterator, which is returned by - * {@link FileUploadBase#getItemIterator(RequestContext)}. - */ - private class FileItemIteratorImpl implements FileItemIterator { - - /** - * Default implementation of {@link FileItemStream}. - */ - class FileItemStreamImpl implements FileItemStream { - - /** - * The file items content type. - */ - private final String contentType; - - /** - * The file items field name. - */ - private final String fieldName; - - /** - * The file items file name. - */ - private final String name; - - /** - * Whether the file item is a form field. - */ - private final boolean formField; - - /** - * The file items input stream. - */ - private final InputStream stream; - - /** - * The headers, if any. - */ - private FileItemHeaders headers; - - /** - * Creates a new instance. - * - * @param pName The items file name, or null. - * @param pFieldName The items field name. - * @param pContentType The items content type, or null. - * @param pFormField Whether the item is a form field. - * @param pContentLength The items content length, if known, or -1 - * @throws IOException Creating the file item failed. - */ - FileItemStreamImpl(String pName, String pFieldName, - String pContentType, boolean pFormField, - long pContentLength) throws IOException { - name = pName; - fieldName = pFieldName; - contentType = pContentType; - formField = pFormField; - if (fileSizeMax != -1) { // Check if limit is already exceeded - if (pContentLength != -1 - && pContentLength > fileSizeMax) { - FileSizeLimitExceededException e = - new FileSizeLimitExceededException( - String.format("The field %s exceeds its maximum permitted size of %s bytes.", - fieldName, Long.valueOf(fileSizeMax)), - pContentLength, fileSizeMax); - e.setFileName(pName); - e.setFieldName(pFieldName); - throw new FileUploadIOException(e); - } - } - // OK to construct stream now - final ItemInputStream itemStream = multi.newInputStream(); - InputStream istream = itemStream; - if (fileSizeMax != -1) { - istream = new LimitedInputStream(istream, fileSizeMax) { - @Override - protected void raiseError(long pSizeMax, long pCount) - throws IOException { - itemStream.close(true); - FileSizeLimitExceededException e = - new FileSizeLimitExceededException( - String.format("The field %s exceeds its maximum permitted size of %s bytes.", - fieldName, Long.valueOf(pSizeMax)), - pCount, pSizeMax); - e.setFieldName(fieldName); - e.setFileName(name); - throw new FileUploadIOException(e); - } - }; - } - stream = istream; - } - - /** - * Returns the items content type, or null. - * - * @return Content type, if known, or null. - */ - @Override - public String getContentType() { - return contentType; - } - - /** - * Returns the items field name. - * - * @return Field name. - */ - @Override - public String getFieldName() { - return fieldName; - } - - /** - * Returns the items file name. - * - * @return File name, if known, or null. - * @throws InvalidFileNameException The file name contains a NUL character, - * which might be an indicator of a security attack. If you intend to - * use the file name anyways, catch the exception and use - * InvalidFileNameException#getName(). - */ - @Override - public String getName() { - return Streams.checkFileName(name); - } - - /** - * Returns, whether this is a form field. - * - * @return True, if the item is a form field, - * otherwise false. - */ - @Override - public boolean isFormField() { - return formField; - } - - /** - * Returns an input stream, which may be used to - * read the items contents. - * - * @return Opened input stream. - * @throws IOException An I/O error occurred. - */ - @Override - public InputStream openStream() throws IOException { - if (((Closeable) stream).isClosed()) { - throw new FileItemStream.ItemSkippedException(); - } - return stream; - } - - /** - * Closes the file item. - * - * @throws IOException An I/O error occurred. - */ - void close() throws IOException { - stream.close(); - } - - /** - * Returns the file item headers. - * - * @return The items header object - */ - @Override - public FileItemHeaders getHeaders() { - return headers; - } - - /** - * Sets the file item headers. - * - * @param pHeaders The items header object - */ - @Override - public void setHeaders(FileItemHeaders pHeaders) { - headers = pHeaders; - } - - } - - /** - * The multi part stream to process. - */ - private final MultipartStream multi; - - /** - * The notifier, which used for triggering the - * {@link ProgressListener}. - */ - private final MultipartStream.ProgressNotifier notifier; - - /** - * The boundary, which separates the various parts. - */ - private final byte[] boundary; - - /** - * The item, which we currently process. - */ - private FileItemStreamImpl currentItem; - - /** - * The current items field name. - */ - private String currentFieldName; - - /** - * Whether we are currently skipping the preamble. - */ - private boolean skipPreamble; - - /** - * Whether the current item may still be read. - */ - private boolean itemValid; - - /** - * Whether we have seen the end of the file. - */ - private boolean eof; - - /** - * Creates a new instance. - * - * @param ctx The request context. - * @throws FileUploadException An error occurred while - * parsing the request. - * @throws IOException An I/O error occurred. - */ - FileItemIteratorImpl(RequestContext ctx) - throws FileUploadException, IOException { - if (ctx == null) { - throw new NullPointerException("ctx parameter"); - } - - String contentType = ctx.getContentType(); - if ((null == contentType) - || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) { - throw new InvalidContentTypeException(String.format( - "the request doesn't contain a %s or %s stream, content type header is %s", - MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType)); - } - - - final long requestSize = ((UploadContext) ctx).contentLength(); - - InputStream input; // N.B. this is eventually closed in MultipartStream processing - if (sizeMax >= 0) { - if (requestSize != -1 && requestSize > sizeMax) { - throw new SizeLimitExceededException(String.format( - "the request was rejected because its size (%s) exceeds the configured maximum (%s)", - Long.valueOf(requestSize), Long.valueOf(sizeMax)), - requestSize, sizeMax); - } - // N.B. this is eventually closed in MultipartStream processing - input = new LimitedInputStream(ctx.getInputStream(), sizeMax) { - @Override - protected void raiseError(long pSizeMax, long pCount) - throws IOException { - FileUploadException ex = new SizeLimitExceededException( - String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", - Long.valueOf(pCount), Long.valueOf(pSizeMax)), - pCount, pSizeMax); - throw new FileUploadIOException(ex); - } - }; - } else { - input = ctx.getInputStream(); - } - - String charEncoding = headerEncoding; - if (charEncoding == null) { - charEncoding = ctx.getCharacterEncoding(); - } - - boundary = getBoundary(contentType); - if (boundary == null) { - IOUtils.closeQuietly(input); // avoid possible resource leak - throw new FileUploadException("the request was rejected because no multipart boundary was found"); - } - - notifier = new MultipartStream.ProgressNotifier(listener, requestSize); - try { - multi = new MultipartStream(input, boundary, notifier); - } catch (IllegalArgumentException iae) { - IOUtils.closeQuietly(input); // avoid possible resource leak - throw new InvalidContentTypeException( - String.format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae); - } - multi.setHeaderEncoding(charEncoding); - - skipPreamble = true; - findNextItem(); - } - - /** - * Called for finding the next item, if any. - * - * @return True, if an next item was found, otherwise false. - * @throws IOException An I/O error occurred. - */ - private boolean findNextItem() throws IOException { - if (eof) { - return false; - } - if (currentItem != null) { - currentItem.close(); - currentItem = null; - } - for (;;) { - boolean nextPart; - if (skipPreamble) { - nextPart = multi.skipPreamble(); - } else { - nextPart = multi.readBoundary(); - } - if (!nextPart) { - if (currentFieldName == null) { - // Outer multipart terminated -> No more data - eof = true; - return false; - } - // Inner multipart terminated -> Return to parsing the outer - multi.setBoundary(boundary); - currentFieldName = null; - continue; - } - FileItemHeaders headers = getParsedHeaders(multi.readHeaders()); - if (currentFieldName == null) { - // We're parsing the outer multipart - String fieldName = getFieldName(headers); - if (fieldName != null) { - String subContentType = headers.getHeader(CONTENT_TYPE); - if (subContentType != null - && subContentType.toLowerCase(Locale.ENGLISH) - .startsWith(MULTIPART_MIXED)) { - currentFieldName = fieldName; - // Multiple files associated with this field name - byte[] subBoundary = getBoundary(subContentType); - multi.setBoundary(subBoundary); - skipPreamble = true; - continue; - } - String fileName = getFileName(headers); - currentItem = new FileItemStreamImpl(fileName, - fieldName, headers.getHeader(CONTENT_TYPE), - fileName == null, getContentLength(headers)); - currentItem.setHeaders(headers); - notifier.noteItem(); - itemValid = true; - return true; - } - } else { - String fileName = getFileName(headers); - if (fileName != null) { - currentItem = new FileItemStreamImpl(fileName, - currentFieldName, - headers.getHeader(CONTENT_TYPE), - false, getContentLength(headers)); - currentItem.setHeaders(headers); - notifier.noteItem(); - itemValid = true; - return true; - } - } - multi.discardBodyData(); - } - } - - private long getContentLength(FileItemHeaders pHeaders) { - try { - return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH)); - } catch (Exception e) { - return -1; - } - } - - /** - * Returns, whether another instance of {@link FileItemStream} - * is available. - * - * @throws FileUploadException Parsing or processing the - * file item failed. - * @throws IOException Reading the file item failed. - * @return True, if one or more additional file items - * are available, otherwise false. - */ - @Override - public boolean hasNext() throws FileUploadException, IOException { - if (eof) { - return false; - } - if (itemValid) { - return true; - } - try { - return findNextItem(); - } catch (FileUploadIOException e) { - // unwrap encapsulated SizeException - throw (FileUploadException) e.getCause(); - } - } - - /** - * Returns the next available {@link FileItemStream}. - * - * @throws java.util.NoSuchElementException No more items are - * available. Use {@link #hasNext()} to prevent this exception. - * @throws FileUploadException Parsing or processing the - * file item failed. - * @throws IOException Reading the file item failed. - * @return FileItemStream instance, which provides - * access to the next file item. - */ - @Override - public FileItemStream next() throws FileUploadException, IOException { - if (eof || (!itemValid && !hasNext())) { - throw new NoSuchElementException(); - } - itemValid = false; - return currentItem; - } - - } - - /** - * This exception is thrown for hiding an inner - * {@link FileUploadException} in an {@link IOException}. - */ - public static class FileUploadIOException extends IOException { - - private static final long serialVersionUID = -3082868232248803474L; - - public FileUploadIOException() { - super(); - } - - public FileUploadIOException(String message, Throwable cause) { - super(message, cause); - } - - public FileUploadIOException(String message) { - super(message); - } - - public FileUploadIOException(Throwable cause) { - super(cause); - } - } - - /** - * Thrown to indicate that the request is not a multipart request. - */ - public static class InvalidContentTypeException - extends FileUploadException { - - /** - * The exceptions UID, for serializing an instance. - */ - private static final long serialVersionUID = -9073026332015646668L; - - /** - * Constructs a InvalidContentTypeException with no - * detail message. - */ - public InvalidContentTypeException() { - super(); - } - - /** - * Constructs an InvalidContentTypeException with - * the specified detail message. - * - * @param message The detail message. - */ - public InvalidContentTypeException(String message) { - super(message); - } - - /** - * Constructs an InvalidContentTypeException with - * the specified detail message and cause. - * - * @param msg The detail message. - * @param cause the original cause - * - * @since 1.3.1 - */ - public InvalidContentTypeException(String msg, Throwable cause) { - super(msg, cause); - } - } - - /** - * Thrown to indicate an IOException. - */ - public static class IOFileUploadException extends FileUploadException { - - private static final long serialVersionUID = -5858565745868986701L; - - public IOFileUploadException() { - super(); - } - - public IOFileUploadException(String message, Throwable cause) { - super(message, cause); - } - - public IOFileUploadException(String message) { - super(message); - } - - public IOFileUploadException(Throwable cause) { - super(cause); - } - } - - /** - * This exception is thrown, if a requests permitted size - * is exceeded. - */ - public abstract static class SizeException extends FileUploadException { - - /** - * Serial version UID, being used, if serialized. - */ - private static final long serialVersionUID = -8776225574705254126L; - - /** - * The actual size of the request. - */ - private final long actual; - - /** - * The maximum permitted size of the request. - */ - private final long permitted; - - /** - * Creates a new instance. - * - * @param message The detail message. - * @param actual The actual number of bytes in the request. - * @param permitted The requests size limit, in bytes. - */ - protected SizeException(String message, long actual, long permitted) { - super(message); - this.actual = actual; - this.permitted = permitted; - } - - /** - * Retrieves the actual size of the request. - * - * @return The actual size of the request. - * @since 1.3 - */ - public long getActualSize() { - return actual; - } - - /** - * Retrieves the permitted size of the request. - * - * @return The permitted size of the request. - * @since 1.3 - */ - public long getPermittedSize() { - return permitted; - } - - } - - /** - * Thrown to indicate that the request size exceeds the configured maximum. - */ - public static class SizeLimitExceededException - extends SizeException { - - /** - * The exceptions UID, for serializing an instance. - */ - private static final long serialVersionUID = -2474893167098052828L; - - /** - * Constructs a SizeExceededException with - * the specified detail message, and actual and permitted sizes. - * - * @param message The detail message. - * @param actual The actual request size. - * @param permitted The maximum permitted request size. - */ - public SizeLimitExceededException(String message, long actual, - long permitted) { - super(message, actual, permitted); - } - - } - - /** - * Thrown to indicate that A files size exceeds the configured maximum. - */ - public static class FileSizeLimitExceededException - extends SizeException { - - /** - * The exceptions UID, for serializing an instance. - */ - private static final long serialVersionUID = 8150776562029630058L; - - /** - * File name of the item, which caused the exception. - */ - private String fileName; - - /** - * Field name of the item, which caused the exception. - */ - private String fieldName; - - /** - * Constructs a SizeExceededException with - * the specified detail message, and actual and permitted sizes. - * - * @param message The detail message. - * @param actual The actual request size. - * @param permitted The maximum permitted request size. - */ - public FileSizeLimitExceededException(String message, long actual, - long permitted) { - super(message, actual, permitted); - } - - /** - * Returns the file name of the item, which caused the - * exception. - * - * @return File name, if known, or null. - */ - public String getFileName() { - return fileName; - } - - /** - * Sets the file name of the item, which caused the - * exception. - * - * @param pFileName the file name of the item, which caused the exception. - */ - public void setFileName(String pFileName) { - fileName = pFileName; - } - - /** - * Returns the field name of the item, which caused the - * exception. - * - * @return Field name, if known, or null. - */ - public String getFieldName() { - return fieldName; - } - - /** - * Sets the field name of the item, which caused the - * exception. - * - * @param pFieldName the field name of the item, - * which caused the exception. - */ - public void setFieldName(String pFieldName) { - fieldName = pFieldName; - } - - } - - /** * Returns the progress listener. * * @return The progress listener, if any, or null. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileItemIteratorImpl.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileItemIteratorImpl.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileItemIteratorImpl.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileItemIteratorImpl.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,339 @@ +/* + * 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.fileupload.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.NoSuchElementException; + +import org.apache.tomcat.util.http.fileupload.FileItem; +import org.apache.tomcat.util.http.fileupload.FileItemHeaders; +import org.apache.tomcat.util.http.fileupload.FileItemIterator; +import org.apache.tomcat.util.http.fileupload.FileItemStream; +import org.apache.tomcat.util.http.fileupload.FileUploadBase; +import org.apache.tomcat.util.http.fileupload.FileUploadException; +import org.apache.tomcat.util.http.fileupload.IOUtils; +import org.apache.tomcat.util.http.fileupload.MultipartStream; +import org.apache.tomcat.util.http.fileupload.ProgressListener; +import org.apache.tomcat.util.http.fileupload.RequestContext; +import org.apache.tomcat.util.http.fileupload.UploadContext; +import org.apache.tomcat.util.http.fileupload.util.LimitedInputStream; + +/** + * The iterator, which is returned by + * {@link FileUploadBase#getItemIterator(RequestContext)}. + */ +public class FileItemIteratorImpl implements FileItemIterator { + private final FileUploadBase fileUploadBase; + private final RequestContext ctx; + private long sizeMax, fileSizeMax; + + + @Override + public long getSizeMax() { + return sizeMax; + } + + @Override + public void setSizeMax(long sizeMax) { + this.sizeMax = sizeMax; + } + + @Override + public long getFileSizeMax() { + return fileSizeMax; + } + + @Override + public void setFileSizeMax(long fileSizeMax) { + this.fileSizeMax = fileSizeMax; + } + + /** + * The multi part stream to process. + */ + private MultipartStream multiPartStream; + + /** + * The notifier, which used for triggering the + * {@link ProgressListener}. + */ + private MultipartStream.ProgressNotifier progressNotifier; + + /** + * The boundary, which separates the various parts. + */ + private byte[] multiPartBoundary; + + /** + * The item, which we currently process. + */ + private FileItemStreamImpl currentItem; + + /** + * The current items field name. + */ + private String currentFieldName; + + /** + * Whether we are currently skipping the preamble. + */ + private boolean skipPreamble; + + /** + * Whether the current item may still be read. + */ + private boolean itemValid; + + /** + * Whether we have seen the end of the file. + */ + private boolean eof; + + /** + * Creates a new instance. + * + * @param pFileUploadBase Upload instance + * @param pRequestContext The request context. + * @throws FileUploadException An error occurred while + * parsing the request. + * @throws IOException An I/O error occurred. + */ + public FileItemIteratorImpl(FileUploadBase pFileUploadBase, RequestContext pRequestContext) + throws FileUploadException, IOException { + fileUploadBase = pFileUploadBase; + sizeMax = fileUploadBase.getSizeMax(); + fileSizeMax = fileUploadBase.getFileSizeMax(); + ctx = pRequestContext; + if (ctx == null) { + throw new NullPointerException("ctx parameter"); + } + + + skipPreamble = true; + findNextItem(); + } + + protected void init(FileUploadBase fileUploadBase, @SuppressWarnings("unused") RequestContext pRequestContext) + throws FileUploadException, IOException { + String contentType = ctx.getContentType(); + if ((null == contentType) + || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(FileUploadBase.MULTIPART))) { + throw new InvalidContentTypeException( + String.format("the request doesn't contain a %s or %s stream, content type header is %s", + FileUploadBase.MULTIPART_FORM_DATA, FileUploadBase.MULTIPART_MIXED, contentType)); + } + + final long requestSize = ((UploadContext) ctx).contentLength(); + + InputStream input; // N.B. this is eventually closed in MultipartStream processing + if (sizeMax >= 0) { + if (requestSize != -1 && requestSize > sizeMax) { + throw new SizeLimitExceededException( + String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", + Long.valueOf(requestSize), Long.valueOf(sizeMax)), + requestSize, sizeMax); + } + // N.B. this is eventually closed in MultipartStream processing + input = new LimitedInputStream(ctx.getInputStream(), sizeMax) { + @Override + protected void raiseError(long pSizeMax, long pCount) + throws IOException { + FileUploadException ex = new SizeLimitExceededException( + String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", + Long.valueOf(pCount), Long.valueOf(pSizeMax)), + pCount, pSizeMax); + throw new FileUploadIOException(ex); + } + }; + } else { + input = ctx.getInputStream(); + } + + String charEncoding = fileUploadBase.getHeaderEncoding(); + if (charEncoding == null) { + charEncoding = ctx.getCharacterEncoding(); + } + + multiPartBoundary = fileUploadBase.getBoundary(contentType); + if (multiPartBoundary == null) { + IOUtils.closeQuietly(input); // avoid possible resource leak + throw new FileUploadException("the request was rejected because no multipart boundary was found"); + } + + progressNotifier = new MultipartStream.ProgressNotifier(fileUploadBase.getProgressListener(), requestSize); + try { + multiPartStream = new MultipartStream(input, multiPartBoundary, progressNotifier); + } catch (IllegalArgumentException iae) { + IOUtils.closeQuietly(input); // avoid possible resource leak + throw new InvalidContentTypeException( + String.format("The boundary specified in the %s header is too long", FileUploadBase.CONTENT_TYPE), iae); + } + multiPartStream.setHeaderEncoding(charEncoding); + } + + public MultipartStream getMultiPartStream() throws FileUploadException, IOException { + if (multiPartStream == null) { + init(fileUploadBase, ctx); + } + return multiPartStream; + } + + /** + * Called for finding the next item, if any. + * + * @return True, if an next item was found, otherwise false. + * @throws IOException An I/O error occurred. + */ + private boolean findNextItem() throws FileUploadException, IOException { + if (eof) { + return false; + } + if (currentItem != null) { + currentItem.close(); + currentItem = null; + } + final MultipartStream multi = getMultiPartStream(); + for (;;) { + boolean nextPart; + if (skipPreamble) { + nextPart = multi.skipPreamble(); + } else { + nextPart = multi.readBoundary(); + } + if (!nextPart) { + if (currentFieldName == null) { + // Outer multipart terminated -> No more data + eof = true; + return false; + } + // Inner multipart terminated -> Return to parsing the outer + multi.setBoundary(multiPartBoundary); + currentFieldName = null; + continue; + } + FileItemHeaders headers = fileUploadBase.getParsedHeaders(multi.readHeaders()); + if (currentFieldName == null) { + // We're parsing the outer multipart + String fieldName = fileUploadBase.getFieldName(headers); + if (fieldName != null) { + String subContentType = headers.getHeader(FileUploadBase.CONTENT_TYPE); + if (subContentType != null + && subContentType.toLowerCase(Locale.ENGLISH) + .startsWith(FileUploadBase.MULTIPART_MIXED)) { + currentFieldName = fieldName; + // Multiple files associated with this field name + byte[] subBoundary = fileUploadBase.getBoundary(subContentType); + multi.setBoundary(subBoundary); + skipPreamble = true; + continue; + } + String fileName = fileUploadBase.getFileName(headers); + currentItem = new FileItemStreamImpl(this, fileName, + fieldName, headers.getHeader(FileUploadBase.CONTENT_TYPE), + fileName == null, getContentLength(headers)); + currentItem.setHeaders(headers); + progressNotifier.noteItem(); + itemValid = true; + return true; + } + } else { + String fileName = fileUploadBase.getFileName(headers); + if (fileName != null) { + currentItem = new FileItemStreamImpl(this, fileName, + currentFieldName, + headers.getHeader(FileUploadBase.CONTENT_TYPE), + false, getContentLength(headers)); + currentItem.setHeaders(headers); + progressNotifier.noteItem(); + itemValid = true; + return true; + } + } + multi.discardBodyData(); + } + } + + private long getContentLength(FileItemHeaders pHeaders) { + try { + return Long.parseLong(pHeaders.getHeader(FileUploadBase.CONTENT_LENGTH)); + } catch (Exception e) { + return -1; + } + } + + /** + * Returns, whether another instance of {@link FileItemStream} + * is available. + * + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + * @return True, if one or more additional file items + * are available, otherwise false. + */ + @Override + public boolean hasNext() throws FileUploadException, IOException { + if (eof) { + return false; + } + if (itemValid) { + return true; + } + try { + return findNextItem(); + } catch (FileUploadIOException e) { + // unwrap encapsulated SizeException + throw (FileUploadException) e.getCause(); + } + } + + /** + * Returns the next available {@link FileItemStream}. + * + * @throws java.util.NoSuchElementException No more items are + * available. Use {@link #hasNext()} to prevent this exception. + * @throws FileUploadException Parsing or processing the + * file item failed. + * @throws IOException Reading the file item failed. + * @return FileItemStream instance, which provides + * access to the next file item. + */ + @Override + public FileItemStream next() throws FileUploadException, IOException { + if (eof || (!itemValid && !hasNext())) { + throw new NoSuchElementException(); + } + itemValid = false; + return currentItem; + } + + @Override + public List getFileItems() throws FileUploadException, IOException { + final List items = new ArrayList<>(); + while (hasNext()) { + final FileItemStream fis = next(); + final FileItem fi = fileUploadBase.getFileItemFactory().createItem(fis.getFieldName(), fis.getContentType(), fis.isFormField(), fis.getName()); + items.add(fi); + } + return items; + } + +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileItemStreamImpl.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileItemStreamImpl.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileItemStreamImpl.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileItemStreamImpl.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,213 @@ +/* + * 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.fileupload.impl; + +import java.io.IOException; +import java.io.InputStream; + +import org.apache.tomcat.util.http.fileupload.FileItemHeaders; +import org.apache.tomcat.util.http.fileupload.FileItemStream; +import org.apache.tomcat.util.http.fileupload.FileUploadException; +import org.apache.tomcat.util.http.fileupload.InvalidFileNameException; +import org.apache.tomcat.util.http.fileupload.MultipartStream.ItemInputStream; +import org.apache.tomcat.util.http.fileupload.util.Closeable; +import org.apache.tomcat.util.http.fileupload.util.LimitedInputStream; +import org.apache.tomcat.util.http.fileupload.util.Streams; + + +/** + * Default implementation of {@link FileItemStream}. + */ +public class FileItemStreamImpl implements FileItemStream { + private final FileItemIteratorImpl fileItemIteratorImpl; + + /** + * The file items content type. + */ + private final String contentType; + + /** + * The file items field name. + */ + private final String fieldName; + + /** + * The file items file name. + */ + final String name; + + /** + * Whether the file item is a form field. + */ + private final boolean formField; + + /** + * The file items input stream. + */ + private final InputStream stream; + + /** + * The headers, if any. + */ + private FileItemHeaders headers; + + /** + * Creates a new instance. + * @param pFileItemIterator Iterator for all files in this upload + * @param pName The items file name, or null. + * @param pFieldName The items field name. + * @param pContentType The items content type, or null. + * @param pFormField Whether the item is a form field. + * @param pContentLength The items content length, if known, or -1 + * @throws FileUploadException If an error is encountered processing the request + * @throws IOException Creating the file item failed. + */ + public FileItemStreamImpl(FileItemIteratorImpl pFileItemIterator, String pName, String pFieldName, + String pContentType, boolean pFormField, + long pContentLength) throws FileUploadException, IOException { + fileItemIteratorImpl = pFileItemIterator; + name = pName; + fieldName = pFieldName; + contentType = pContentType; + formField = pFormField; + final long fileSizeMax = fileItemIteratorImpl.getFileSizeMax(); + if (fileSizeMax != -1) { // Check if limit is already exceeded + if (pContentLength != -1 + && pContentLength > fileSizeMax) { + FileSizeLimitExceededException e = + new FileSizeLimitExceededException( + String.format("The field %s exceeds its maximum permitted size of %s bytes.", + fieldName, Long.valueOf(fileSizeMax)), + pContentLength, fileSizeMax); + e.setFileName(pName); + e.setFieldName(pFieldName); + throw new FileUploadIOException(e); + } + } + // OK to construct stream now + final ItemInputStream itemStream = fileItemIteratorImpl.getMultiPartStream().newInputStream(); + InputStream istream = itemStream; + if (fileSizeMax != -1) { + istream = new LimitedInputStream(istream, fileSizeMax) { + @Override + protected void raiseError(long pSizeMax, long pCount) + throws IOException { + itemStream.close(true); + FileSizeLimitExceededException e = + new FileSizeLimitExceededException( + String.format("The field %s exceeds its maximum permitted size of %s bytes.", + fieldName, Long.valueOf(pSizeMax)), + pCount, pSizeMax); + e.setFieldName(fieldName); + e.setFileName(name); + throw new FileUploadIOException(e); + } + }; + } + stream = istream; + } + + /** + * Returns the items content type, or null. + * + * @return Content type, if known, or null. + */ + @Override + public String getContentType() { + return contentType; + } + + /** + * Returns the items field name. + * + * @return Field name. + */ + @Override + public String getFieldName() { + return fieldName; + } + + /** + * Returns the items file name. + * + * @return File name, if known, or null. + * @throws InvalidFileNameException The file name contains a NUL character, + * which might be an indicator of a security attack. If you intend to + * use the file name anyways, catch the exception and use + * InvalidFileNameException#getName(). + */ + @Override + public String getName() { + return Streams.checkFileName(name); + } + + /** + * Returns, whether this is a form field. + * + * @return True, if the item is a form field, + * otherwise false. + */ + @Override + public boolean isFormField() { + return formField; + } + + /** + * Returns an input stream, which may be used to + * read the items contents. + * + * @return Opened input stream. + * @throws IOException An I/O error occurred. + */ + @Override + public InputStream openStream() throws IOException { + if (((Closeable) stream).isClosed()) { + throw new FileItemStream.ItemSkippedException(); + } + return stream; + } + + /** + * Closes the file item. + * + * @throws IOException An I/O error occurred. + */ + public void close() throws IOException { + stream.close(); + } + + /** + * Returns the file item headers. + * + * @return The items header object + */ + @Override + public FileItemHeaders getHeaders() { + return headers; + } + + /** + * Sets the file item headers. + * + * @param pHeaders The items header object + */ + @Override + public void setHeaders(FileItemHeaders pHeaders) { + headers = pHeaders; + } + +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileSizeLimitExceededException.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileSizeLimitExceededException.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileSizeLimitExceededException.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileSizeLimitExceededException.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,94 @@ +/* + * 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.fileupload.impl; + +/** + * Thrown to indicate that A files size exceeds the configured maximum. + */ +public class FileSizeLimitExceededException + extends SizeException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = 8150776562029630058L; + + /** + * File name of the item, which caused the exception. + */ + private String fileName; + + /** + * Field name of the item, which caused the exception. + */ + private String fieldName; + + /** + * Constructs a SizeExceededException with + * the specified detail message, and actual and permitted sizes. + * + * @param message The detail message. + * @param actual The actual request size. + * @param permitted The maximum permitted request size. + */ + public FileSizeLimitExceededException(String message, long actual, + long permitted) { + super(message, actual, permitted); + } + + /** + * Returns the file name of the item, which caused the + * exception. + * + * @return File name, if known, or null. + */ + public String getFileName() { + return fileName; + } + + /** + * Sets the file name of the item, which caused the + * exception. + * + * @param pFileName the file name of the item, which caused the exception. + */ + public void setFileName(String pFileName) { + fileName = pFileName; + } + + /** + * Returns the field name of the item, which caused the + * exception. + * + * @return Field name, if known, or null. + */ + public String getFieldName() { + return fieldName; + } + + /** + * Sets the field name of the item, which caused the + * exception. + * + * @param pFieldName the field name of the item, + * which caused the exception. + */ + public void setFieldName(String pFieldName) { + fieldName = pFieldName; + } + +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileUploadIOException.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileUploadIOException.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/FileUploadIOException.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/FileUploadIOException.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,63 @@ +/* + * 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.fileupload.impl; + +import java.io.IOException; + +import org.apache.tomcat.util.http.fileupload.FileUploadException; + +/** + * This exception is thrown for hiding an inner + * {@link FileUploadException} in an {@link IOException}. + */ +public class FileUploadIOException extends IOException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -7047616958165584154L; + + /** + * The exceptions cause; we overwrite the parent + * classes field, which is available since Java + * 1.4 only. + */ + private final FileUploadException cause; + + /** + * Creates a FileUploadIOException with the + * given cause. + * + * @param pCause The exceptions cause, if any, or null. + */ + public FileUploadIOException(FileUploadException pCause) { + // We're not doing super(pCause) cause of 1.3 compatibility. + cause = pCause; + } + + /** + * Returns the exceptions cause. + * + * @return The exceptions cause, if any, or null. + */ + @SuppressWarnings("sync-override") // Field is final + @Override + public Throwable getCause() { + return cause; + } + +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/InvalidContentTypeException.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/InvalidContentTypeException.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/InvalidContentTypeException.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/InvalidContentTypeException.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,62 @@ +/* + * 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.fileupload.impl; + +import org.apache.tomcat.util.http.fileupload.FileUploadException; + +/** + * Thrown to indicate that the request is not a multipart request. + */ +public class InvalidContentTypeException + extends FileUploadException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -9073026332015646668L; + + /** + * Constructs a InvalidContentTypeException with no + * detail message. + */ + public InvalidContentTypeException() { + super(); + } + + /** + * Constructs an InvalidContentTypeException with + * the specified detail message. + * + * @param message The detail message. + */ + public InvalidContentTypeException(String message) { + super(message); + } + + /** + * Constructs an InvalidContentTypeException with + * the specified detail message and cause. + * + * @param msg The detail message. + * @param cause the original cause + * + * @since 1.3.1 + */ + public InvalidContentTypeException(String msg, Throwable cause) { + super(msg, cause); + } +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/IOFileUploadException.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/IOFileUploadException.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/IOFileUploadException.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/IOFileUploadException.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,62 @@ +/* + * 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.fileupload.impl; + +import java.io.IOException; + +import org.apache.tomcat.util.http.fileupload.FileUploadException; + +/** + * Thrown to indicate an IOException. + */ +public class IOFileUploadException extends FileUploadException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = 1749796615868477269L; + + /** + * The exceptions cause; we overwrite the parent + * classes field, which is available since Java + * 1.4 only. + */ + private final IOException cause; + + /** + * Creates a new instance with the given cause. + * + * @param pMsg The detail message. + * @param pException The exceptions cause. + */ + public IOFileUploadException(String pMsg, IOException pException) { + super(pMsg); + cause = pException; + } + + /** + * Returns the exceptions cause. + * + * @return The exceptions cause, if any, or null. + */ + @SuppressWarnings("sync-override") // Field is final + @Override + public Throwable getCause() { + return cause; + } + +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/SizeException.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/SizeException.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/SizeException.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/SizeException.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,75 @@ +/* + * 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.fileupload.impl; + +import org.apache.tomcat.util.http.fileupload.FileUploadException; + +/** + * This exception is thrown, if a requests permitted size + * is exceeded. + */ +public abstract class SizeException extends FileUploadException { + + /** + * Serial version UID, being used, if serialized. + */ + private static final long serialVersionUID = -8776225574705254126L; + + /** + * The actual size of the request. + */ + private final long actual; + + /** + * The maximum permitted size of the request. + */ + private final long permitted; + + /** + * Creates a new instance. + * + * @param message The detail message. + * @param actual The actual number of bytes in the request. + * @param permitted The requests size limit, in bytes. + */ + protected SizeException(String message, long actual, long permitted) { + super(message); + this.actual = actual; + this.permitted = permitted; + } + + /** + * Retrieves the actual size of the request. + * + * @return The actual size of the request. + * @since 1.3 + */ + public long getActualSize() { + return actual; + } + + /** + * Retrieves the permitted size of the request. + * + * @return The permitted size of the request. + * @since 1.3 + */ + public long getPermittedSize() { + return permitted; + } + +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/SizeLimitExceededException.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/SizeLimitExceededException.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/impl/SizeLimitExceededException.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/impl/SizeLimitExceededException.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,43 @@ +/* + * 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.fileupload.impl; + +/** + * Thrown to indicate that the request size exceeds the configured maximum. + */ +public class SizeLimitExceededException + extends SizeException { + + /** + * The exceptions UID, for serializing an instance. + */ + private static final long serialVersionUID = -2474893167098052828L; + + /** + * Constructs a SizeExceededException with + * the specified detail message, and actual and permitted sizes. + * + * @param message The detail message. + * @param actual The actual request size. + * @param permitted The maximum permitted request size. + */ + public SizeLimitExceededException(String message, long actual, + long permitted) { + super(message, actual, permitted); + } + +} \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java 2020-02-05 19:26:48.000000000 +0000 @@ -22,7 +22,7 @@ import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import org.apache.tomcat.util.http.fileupload.FileUploadBase.FileUploadIOException; +import org.apache.tomcat.util.http.fileupload.impl.FileUploadIOException; import org.apache.tomcat.util.http.fileupload.util.Closeable; import org.apache.tomcat.util.http.fileupload.util.Streams; @@ -115,7 +115,7 @@ * @param pListener The listener to invoke. * @param pContentLength The expected content length. */ - ProgressNotifier(ProgressListener pListener, long pContentLength) { + public ProgressNotifier(ProgressListener pListener, long pContentLength) { listener = pListener; contentLength = pContentLength; } @@ -136,7 +136,7 @@ /** * Called to indicate, that a new file item has been detected. */ - void noteItem() { + public void noteItem() { ++items; notifyListener(); } @@ -332,7 +332,7 @@ * * @see #MultipartStream(InputStream, byte[], int, ProgressNotifier) */ - MultipartStream(InputStream input, + public MultipartStream(InputStream input, byte[] boundary, ProgressNotifier pNotifier) { this(input, boundary, DEFAULT_BUFSIZE, pNotifier); @@ -576,7 +576,7 @@ * Creates a new {@link ItemInputStream}. * @return A new instance of {@link ItemInputStream}. */ - ItemInputStream newInputStream() { + public ItemInputStream newInputStream() { return new ItemInputStream(); } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/HeaderUtil.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/HeaderUtil.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/HeaderUtil.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/HeaderUtil.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,53 @@ +/* + * 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; + +public class HeaderUtil { + + /** + * Converts an HTTP header line in byte form to a printable String. + * Bytes corresponding to visible ASCII characters will converted to those + * characters. All other bytes (0x00 to 0x1F, 0x7F to OxFF) will be + * represented in 0xNN form. + * + * @param bytes Contains an HTTP header line + * @param offset The start position of the header line in the array + * @param len The length of the HTTP header line + * + * @return A String with non-printing characters replaced by the 0xNN + * equivalent + */ + public static String toPrintableString(byte[] bytes, int offset, int len) { + StringBuilder result = new StringBuilder(); + for (int i = offset; i < offset + len; i++) { + char c = (char) (bytes[i] & 0xFF); + if (c < 0x20 || c > 0x7E) { + result.append("0x"); + result.append(Character.forDigit((c >> 4) & 0xF, 16)); + result.append(Character.forDigit((c) & 0xF, 16)); + } else { + result.append(c); + } + } + return result.toString(); + } + + + private HeaderUtil() { + // Utility class. Hide default constructor. + } +} diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/LegacyCookieProcessor.java 2020-02-05 19:26:48.000000000 +0000 @@ -326,7 +326,7 @@ SameSiteCookies sameSiteCookiesValue = getSameSiteCookies(); - if (!sameSiteCookiesValue.equals(SameSiteCookies.NONE)) { + if (!sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) { buf.append("; SameSite="); buf.append(sameSiteCookiesValue.getValue()); } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/LocalStrings.properties tomcat9-9.0.31/java/org/apache/tomcat/util/http/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -16,7 +16,7 @@ cookies.fallToDebug=\n\ \ Note: further occurrences of Cookie errors will be logged at DEBUG level. cookies.invalidCookieToken=Cookies: Invalid cookie. Value not a token or quoted value -cookies.invalidSameSiteCookies=Unknown setting [{0}], must be one of: none, lax, strict. Default value is none. +cookies.invalidSameSiteCookies=Unknown setting [{0}], must be one of: unset, none, lax, strict. Default value is unset. cookies.invalidSpecial=Cookies: Unknown Special Cookie cookies.maxCountFail=More than the maximum allowed number of cookies, [{0}], were detected. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/http/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -15,12 +15,17 @@ cookies.invalidCookieToken=Cookie:cookie无效。值不是令牌或引用值 cookies.invalidSpecial=Cookies:未知特殊的Cookie +cookies.maxCountFail=检测到超过Cookie最大允许的数量[{0}] + +headers.maxCountFail=检测到超过了允许设置的最大header 数[{0}] parameters.bytes=开始处理输入[{0}] parameters.copyFail=无法创建以调试日志记录为目的的原始参数值的副本 parameters.decodeFail.debug=字符解码失败.参数 [{0}]和值 [{1}]被忽略 +parameters.emptyChunk=忽略空参数块 parameters.fallToDebug=注:更多的参数错误将以DEBUG级别日志进行记录。 parameters.maxCountFail=检测到单个请求([{0}])的最大请求参数数(GET加POST)。 超出此限制的任何参数都被忽略。 要更改此限制,请在Connector上设置maxParameterCount属性。 parameters.maxCountFail.fallToDebug=注意:更多的错误信息只在debug级别日志中记录 +parameters.noequal=):参数从位置[{0}]开始,到位置[{1}]结束,值为[{2}],后面没有“=”字符 rfc6265CookieProcessor.invalidPath=这个cookie被指定了一个无效的路径 [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/MimeHeaders.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/MimeHeaders.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/MimeHeaders.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/MimeHeaders.java 2020-02-05 19:26:48.000000000 +0000 @@ -395,7 +395,7 @@ * reset and swap with last header * @param idx the index of the header to remove. */ - private void removeHeader(int idx) { + public void removeHeader(int idx) { MimeHeaderField mh = headers[idx]; mh.recycle(); diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/HttpParser.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/HttpParser.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/HttpParser.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/HttpParser.java 2020-02-05 19:26:48.000000000 +0000 @@ -317,6 +317,17 @@ } + public static boolean isControl(int c) { + // Fast for valid control characters, slower for some incorrect + // ones + try { + return IS_CONTROL[c]; + } catch (ArrayIndexOutOfBoundsException ex) { + return false; + } + } + + // Skip any LWS and position to read the next character. The next character // is returned as being able to 'peek()' it allows a small optimisation in // some cases. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -15,6 +15,7 @@ cookie.valueNotPresent=<不存在> +http.closingBracket=在非IPv6主机名中找到了右括号']'。 http.illegalCharacterIpv4=字符[{0}]为非法的IPv4地址。 http.illegalCharacterIpv6=字符[{0}]为非法的IPv6地址。 http.invalidCharacterDomain.afterColon=字符 [{0}] 在域名中的冒号后无效。 @@ -25,9 +26,12 @@ http.invalidCharacterDomain.atEnd=字符 [{0}] 在域名末尾无效。 http.invalidCharacterDomain.atStart=字符 [{0}] 在域名开头无效。 http.invalidHextet=hextet无效。 hextet必须包含4个或更少的十六进制字符。 +http.invalidIpv4Location=IPv6地址在无效位置包含嵌入的IPv4地址。 http.invalidLeadingZero=非零的IPv4字符可能不包含前导零。 http.invalidOctet=无效字符[{0}].IPv4字符的有效范围为0~255。 +http.invalidSegmentEndState=状态[{0}]对于段的结尾无效。 http.noClosingBracket=ipv6 地址缺失一个闭合的圆括号 +http.noOpeningBracket=IPv6地址缺少开括号( http.singleColonEnd=IPv6地址不能以单个“.”结尾。 http.singleColonStart=一个IPv6地址也许不是以单个冒号":"开头的。 http.tooManyColons=IPv6地址不能包含超过2个连续冒号字符。 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/TokenList.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/TokenList.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/TokenList.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/TokenList.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,113 @@ +/* + * 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.parser; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Locale; + +public class TokenList { + + private TokenList() { + // Utility class. Hide default constructor. + } + + + /** + * Parses an enumeration of header values of the form 1#token, forcing all + * parsed values to lower case. + * + * @param inputs The headers to parse + * @param collection The Collection (usually a list of a set) to which the + * parsed tokens should be added + * + * @return {@code} true if the header values were parsed cleanly, otherwise + * {@code false} (e.g. if a non-token value was encountered) + * + * @throws IOException If an I/O error occurs reading the header + */ + public static boolean parseTokenList(Enumeration inputs, Collection collection) throws IOException { + boolean result = true; + while (inputs.hasMoreElements()) { + String nextHeaderValue = inputs.nextElement(); + if (nextHeaderValue != null) { + if (!TokenList.parseTokenList(new StringReader(nextHeaderValue), collection)) { + result = false; + } + } + } + return result; + } + + + /** + * Parses a header of the form 1#token, forcing all parsed values to lower + * case. This is typically used when header values are case-insensitive. + * + * @param input The header to parse + * @param collection The Collection (usually a list of a set) to which the + * parsed tokens should be added + * + * @return {@code} true if the header was parsed cleanly, otherwise + * {@code false} (e.g. if a non-token value was encountered) + * + * @throws IOException If an I/O error occurs reading the header + */ + public static boolean parseTokenList(Reader input, Collection collection) throws IOException { + boolean invalid = false; + boolean valid = false; + + do { + String fieldName = HttpParser.readToken(input); + if (fieldName == null) { + // Invalid field-name, skip to the next one + invalid = true; + HttpParser.skipUntil(input, 0, ','); + continue; + } + + if (fieldName.length() == 0) { + // No more data to read + break; + } + + SkipResult skipResult = HttpParser.skipConstant(input, ","); + if (skipResult == SkipResult.EOF) { + // EOF + valid = true; + collection.add(fieldName.toLowerCase(Locale.ENGLISH)); + break; + } else if (skipResult == SkipResult.FOUND) { + valid = true; + collection.add(fieldName.toLowerCase(Locale.ENGLISH)); + continue; + } else { + // Not a token - ignore it + invalid = true; + HttpParser.skipUntil(input, 0, ','); + continue; + } + } while (true); + + // Only return true if at least one valid token was read and no invalid + // entries were found + return valid && !invalid; + } +} diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/Vary.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/Vary.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/parser/Vary.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/parser/Vary.java 2020-02-05 19:26:48.000000000 +0000 @@ -18,9 +18,12 @@ import java.io.IOException; import java.io.StringReader; -import java.util.Locale; import java.util.Set; +/** + * @deprecated Use {@link TokenList}. + */ +@Deprecated public class Vary { private Vary() { @@ -29,33 +32,6 @@ public static void parseVary(StringReader input, Set result) throws IOException { - - do { - String fieldName = HttpParser.readToken(input); - if (fieldName == null) { - // Invalid field-name, skip to the next one - HttpParser.skipUntil(input, 0, ','); - continue; - } - - if (fieldName.length() == 0) { - // No more data to read - break; - } - - SkipResult skipResult = HttpParser.skipConstant(input, ","); - if (skipResult == SkipResult.EOF) { - // EOF - result.add(fieldName.toLowerCase(Locale.ENGLISH)); - break; - } else if (skipResult == SkipResult.FOUND) { - result.add(fieldName.toLowerCase(Locale.ENGLISH)); - continue; - } else { - // Not a token - ignore it - HttpParser.skipUntil(input, 0, ','); - continue; - } - } while (true); + TokenList.parseTokenList(input, result); } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/RequestUtil.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/RequestUtil.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/RequestUtil.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/RequestUtil.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,6 +16,12 @@ */ package org.apache.tomcat.util.http; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Locale; + +import javax.servlet.http.HttpServletRequest; + public class RequestUtil { private RequestUtil() { @@ -113,4 +119,92 @@ // Return the normalized path that we have completed return normalized; } + + + public static boolean isSameOrigin(HttpServletRequest request, String origin) { + // Build scheme://host:port from request + StringBuilder target = new StringBuilder(); + String scheme = request.getScheme(); + if (scheme == null) { + return false; + } else { + scheme = scheme.toLowerCase(Locale.ENGLISH); + } + target.append(scheme); + target.append("://"); + + String host = request.getServerName(); + if (host == null) { + return false; + } + target.append(host); + + int port = request.getServerPort(); + // Origin may or may not include the (default) port. + // At this point target doesn't include a port. + if (target.length() == origin.length()) { + // origin and target can only be equal if both are using default + // ports. Therefore only append the port to the target if a + // non-default port is used. + if (("http".equals(scheme) || "ws".equals(scheme)) && port != 80 || + ("https".equals(scheme) || "wss".equals(scheme)) && port != 443) { + target.append(':'); + target.append(port); + } + } else { + // origin and target can only be equal if: + // a) origin includes an explicit default port + // b) origin is using a non-default port + // Either way, add the port to the target so it can be compared + target.append(':'); + target.append(port); + } + + + // Both scheme and host are case-insensitive but the CORS spec states + // this check should be case-sensitive + return origin.equals(target.toString()); + } + + + /** + * Checks if a given origin is valid or not. Criteria: + *
      + *
    • If an encoded character is present in origin, it's not valid.
    • + *
    • If origin is "null", it's valid.
    • + *
    • Origin should be a valid {@link URI}
    • + *
    + * + * @param origin The origin URI + * @return true if the origin was valid + * @see RFC952 + */ + public static boolean isValidOrigin(String origin) { + // Checks for encoded characters. Helps prevent CRLF injection. + if (origin.contains("%")) { + return false; + } + + // "null" is a valid origin + if ("null".equals(origin)) { + return true; + } + + // RFC6454, section 4. "If uri-scheme is file, the implementation MAY + // return an implementation-defined value.". No limits are placed on + // that value so treat all file URIs as valid origins. + if (origin.startsWith("file://")) { + return true; + } + + URI originURI; + try { + originURI = new URI(origin); + } catch (URISyntaxException e) { + return false; + } + // If scheme for URI is null, return false. Return true otherwise. + return originURI.getScheme() != null; + + } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/ResponseUtil.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/ResponseUtil.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/ResponseUtil.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/ResponseUtil.java 2020-02-05 19:26:48.000000000 +0000 @@ -27,7 +27,7 @@ import javax.servlet.http.HttpServletResponse; -import org.apache.tomcat.util.http.parser.Vary; +import org.apache.tomcat.util.http.parser.TokenList; public class ResponseUtil { @@ -81,7 +81,7 @@ for (String varyHeader : varyHeaders) { StringReader input = new StringReader(varyHeader); try { - Vary.parseVary(input, fieldNames); + TokenList.parseTokenList(input, fieldNames); } catch (IOException ioe) { // Should never happen } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/Rfc6265CookieProcessor.java 2020-02-05 19:26:48.000000000 +0000 @@ -164,7 +164,7 @@ SameSiteCookies sameSiteCookiesValue = getSameSiteCookies(); - if (!sameSiteCookiesValue.equals(SameSiteCookies.NONE)) { + if (!sameSiteCookiesValue.equals(SameSiteCookies.UNSET)) { header.append("; SameSite="); header.append(sameSiteCookiesValue.getValue()); } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/http/SameSiteCookies.java tomcat9-9.0.31/java/org/apache/tomcat/util/http/SameSiteCookies.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/http/SameSiteCookies.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/http/SameSiteCookies.java 2020-02-05 19:26:48.000000000 +0000 @@ -21,7 +21,12 @@ public enum SameSiteCookies { /** - * Don't set the SameSite cookie attribute. Cookie is always sent + * Don't set the SameSite cookie attribute. + */ + UNSET("Unset"), + + /** + * Cookie is always sent in cross-site requests. */ NONE("None"), diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/json/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/json/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/json/LocalStrings_zh_CN.properties 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/json/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,16 @@ +# 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. + +parser.expectedEOF=期望EOF,但仍有内容需要解析 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -16,6 +16,7 @@ diagnostics.threadDumpTitle=打印全部线程 diagnostics.vmInfoClassCompilation=:)class汇编 diagnostics.vmInfoClassLoading=类加载中 +diagnostics.vmInfoGarbageCollectors=垃圾收集器[{0}] diagnostics.vmInfoLogger=日志记录器(Logger)信息 diagnostics.vmInfoOs=操作系统信息 diagnostics.vmInfoRuntime=运行时信息 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/modeler/Registry.java tomcat9-9.0.31/java/org/apache/tomcat/util/modeler/Registry.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/modeler/Registry.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/modeler/Registry.java 2020-02-05 19:26:48.000000000 +0000 @@ -27,6 +27,7 @@ import java.util.Map; import javax.management.DynamicMBean; +import javax.management.InstanceNotFoundException; import javax.management.MBeanAttributeInfo; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; @@ -151,7 +152,7 @@ public static synchronized void disableRegistry() { if (registry == null) { registry = new NoDescriptorRegistry(); - } else { + } else if (!(registry instanceof NoDescriptorRegistry)) { log.warn(sm.getString("registry.noDisable")); } } @@ -233,8 +234,8 @@ * lifecycle operations. * * @param mbeans list of ObjectName on which we'll invoke the operations - * @param operation Name of the operation ( init, start, stop, etc) - * @param failFirst If false, exceptions will be ignored + * @param operation Name of the operation ( init, start, stop, etc) + * @param failFirst If false, exceptions will be ignored * @throws Exception Error invoking operation * @since 1.1 */ @@ -263,7 +264,6 @@ } } - // -------------------- ID registry -------------------- /** @@ -397,6 +397,36 @@ return null; } + /** + * Find the operation info for a method. + * + * @param oname The bean name + * @param opName The operation name + * @param argCount The number of arguments to the method + * @return the operation info for the specified operation + * @throws InstanceNotFoundException If the object name is not bound to an MBean + */ + public MBeanOperationInfo getMethodInfo(ObjectName oname, String opName, int argCount) + throws InstanceNotFoundException + { + MBeanInfo info = null; + try { + info = getMBeanServer().getMBeanInfo(oname); + } catch (InstanceNotFoundException infe) { + throw infe; + } catch (Exception e) { + log.warn(sm.getString("registry.noMetadata", oname), e); + return null; + } + MBeanOperationInfo attInfo[] = info.getOperations(); + for (int i = 0; i < attInfo.length; i++) { + if (opName.equals(attInfo[i].getName()) + && argCount == attInfo[i].getSignature().length) { + return attInfo[i]; + } + } + return null; + } /** * Unregister a component. This is just a helper that avoids exceptions by diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/AbstractEndpoint.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/AbstractEndpoint.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/AbstractEndpoint.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/AbstractEndpoint.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,7 +25,9 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -103,7 +105,10 @@ * * @return The sockets for which the handler is tracking a currently * open connection + * @deprecated Unused, will be removed in Tomcat 10, replaced + * by AbstractEndpoint.getConnections */ + @Deprecated public Set getOpenSockets(); /** @@ -183,6 +188,19 @@ private ObjectName oname = null; + /** + * Map holding all current connections keyed with the sockets. + */ + protected Map> connections = new ConcurrentHashMap<>(); + + /** + * Get a set with the current open connections. + * @return A set with the open socket wrappers + */ + public Set> getConnections() { + return new HashSet<>(connections.values()); + } + // ----------------------------------------------------------------- Properties private String defaultSSLHostConfigName = SSLHostConfig.DEFAULT_SSL_HOST_NAME; @@ -437,7 +455,7 @@ public int getAcceptorThreadPriority() { return acceptorThreadPriority; } - private int maxConnections = 10000; + private int maxConnections = 8*1024; public void setMaxConnections(int maxCon) { this.maxConnections = maxCon; LimitLatch latch = this.connectionLimitLatch; @@ -452,8 +470,7 @@ initializeConnectionLatch(); } } - - public int getMaxConnections() { return this.maxConnections; } + public int getMaxConnections() { return this.maxConnections; } /** * Return the current count of connections handled by this endpoint, if the @@ -1337,14 +1354,24 @@ /** * Close the socket when the connection has to be immediately closed when - * an error occurs while configuring the accepted socket, allocating - * a wrapper for the socket, or trying to dispatch it for processing. + * an error occurs while configuring the accepted socket or trying to + * dispatch it for processing. The wrapper associated with the socket will + * be used for the close. * @param socket The newly accepted socket */ - protected abstract void closeSocket(U socket); - - protected void destroySocket(U socket) { - closeSocket(socket); + protected void closeSocket(U socket) { + SocketWrapperBase socketWrapper = connections.get(socket); + if (socketWrapper != null) { + socketWrapper.close(); + } } + + /** + * Close the socket. This is used when the connector is not in a state + * which allows processing the socket, or if there was an error which + * prevented the allocation of the socket wrapper. + * @param socket The newly accepted socket + */ + protected abstract void destroySocket(U socket); } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/AbstractJsseEndpoint.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/AbstractJsseEndpoint.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/AbstractJsseEndpoint.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/AbstractJsseEndpoint.java 2020-02-05 19:26:48.000000000 +0000 @@ -117,19 +117,6 @@ } SSLEngine engine = sslContext.createSSLEngine(); - switch (sslHostConfig.getCertificateVerification()) { - case NONE: - engine.setNeedClientAuth(false); - engine.setWantClientAuth(false); - break; - case OPTIONAL: - case OPTIONAL_NO_CA: - engine.setWantClientAuth(true); - break; - case REQUIRED: - engine.setNeedClientAuth(true); - break; - } engine.setUseClientMode(false); engine.setEnabledCipherSuites(sslHostConfig.getEnabledCiphers()); engine.setEnabledProtocols(sslHostConfig.getEnabledProtocols()); @@ -151,7 +138,20 @@ JreCompat.getInstance().setApplicationProtocols(sslParameters, commonProtocolsArray); } } - // In case the getter returns a defensive copy + switch (sslHostConfig.getCertificateVerification()) { + case NONE: + sslParameters.setNeedClientAuth(false); + sslParameters.setWantClientAuth(false); + break; + case OPTIONAL: + case OPTIONAL_NO_CA: + sslParameters.setWantClientAuth(true); + break; + case REQUIRED: + sslParameters.setNeedClientAuth(true); + break; + } + // The getter (at least in OpenJDK and derivatives) returns a defensive copy engine.setSSLParameters(sslParameters); return engine; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/AprEndpoint.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/AprEndpoint.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/AprEndpoint.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/AprEndpoint.java 2020-02-05 19:26:48.000000000 +0000 @@ -27,12 +27,12 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import javax.net.ssl.KeyManager; @@ -109,15 +109,9 @@ protected long sslContext = 0; - private final Map connections = new ConcurrentHashMap<>(); - - // ------------------------------------------------------------ Constructor public AprEndpoint() { - // Need to override the default for maxConnections to align it with what - // was pollerSize (before the two were merged) - setMaxConnections(8 * 1024); // Asynchronous IO has significantly lower performance with APR: // - no IO vectoring // - mandatory use of direct buffers forces output buffering @@ -684,11 +678,11 @@ log.debug(sm.getString("endpoint.debug.socket", socket)); } AprSocketWrapper wrapper = new AprSocketWrapper(socket, this); + connections.put(socket, wrapper); wrapper.setKeepAliveLeft(getMaxKeepAliveRequests()); wrapper.setSecure(isSSLEnabled()); wrapper.setReadTimeout(getConnectionTimeout()); wrapper.setWriteTimeout(getConnectionTimeout()); - connections.put(socket, wrapper); getExecutor().execute(new SocketWithOptionsProcessor(wrapper)); return true; } catch (RejectedExecutionException x) { @@ -728,7 +722,7 @@ * socket should be closed */ protected boolean processSocket(long socket, SocketEvent event) { - AprSocketWrapper socketWrapper = connections.get(Long.valueOf(socket)); + SocketWrapperBase socketWrapper = connections.get(Long.valueOf(socket)); if (event == SocketEvent.OPEN_READ && socketWrapper.readOperation != null) { return socketWrapper.readOperation.process(); } else if (event == SocketEvent.OPEN_WRITE && socketWrapper.writeOperation != null) { @@ -746,43 +740,19 @@ } - @Override - protected void closeSocket(Long socket) { - closeSocket(socket.longValue()); - } - - - private void closeSocket(long socket) { - // Once this is called, the mapping from socket to wrapper will no - // longer be required. - SocketWrapperBase wrapper = connections.remove(Long.valueOf(socket)); - if (wrapper != null) { - // Cast to avoid having to catch an IOE that is never thrown. - ((AprSocketWrapper) wrapper).close(); - } + private void closeSocketInternal(long socket) { + closeSocket(Long.valueOf(socket)); } - /* - * This method should only be called if there is no chance that the socket - * is currently being used by the Poller. It is generally a bad idea to call - * this directly from a known error condition. - */ @Override protected void destroySocket(Long socket) { - destroySocket(socket.longValue()); + countDownConnection(); + destroySocketInternal(socket.longValue()); } - - /* - * This method should only be called if there is no chance that the socket - * is currently being used by the Poller. It is generally a bad idea to call - * this directly from a known error condition. - */ - private void destroySocket(long socket) { - connections.remove(Long.valueOf(socket)); + private void destroySocketInternal(long socket) { if (log.isDebugEnabled()) { - String msg = sm.getString("endpoint.debug.destroySocket", - Long.valueOf(socket)); + String msg = sm.getString("endpoint.debug.destroySocket", Long.valueOf(socket)); if (log.isTraceEnabled()) { log.trace(msg, new Exception()); } else { @@ -795,7 +765,6 @@ // are closed when calling stop() followed by start(). if (socket != 0) { Socket.destroy(socket); - countDownConnection(); } } @@ -1063,7 +1032,7 @@ * for a maximum of two events (read and write) at any one * time. * - * Therefore size is actual poller size *4. + * Therefore size is poller size *4. */ desc = new long[pollerSize * 4]; connectionCount.set(0); @@ -1115,22 +1084,24 @@ while (info != null) { // Make sure we aren't trying add the socket as well as close it addList.remove(info.socket); - // Make sure the socket isn't in the poller before we close it + // Make sure the socket isn't in the poller before we close it removeFromPoller(info.socket); // Poller isn't running at this point so use destroySocket() // directly - destroySocket(info.socket); + closeSocketInternal(info.socket); + destroySocketInternal(info.socket); info = closeList.get(); } closeList.clear(); // Close all sockets in the add queue info = addList.get(); while (info != null) { - // Make sure the socket isn't in the poller before we close it + // Make sure the socket isn't in the poller before we close it removeFromPoller(info.socket); // Poller isn't running at this point so use destroySocket() // directly - destroySocket(info.socket); + closeSocketInternal(info.socket); + destroySocketInternal(info.socket); info = addList.get(); } addList.clear(); @@ -1138,7 +1109,8 @@ int rv = Poll.pollset(aprPoller, desc); if (rv > 0) { for (int n = 0; n < rv; n++) { - destroySocket(desc[n*2+1]); + closeSocketInternal(desc[n*2+1]); + destroySocketInternal(desc[n*2+1]); } } Pool.destroy(pool); @@ -1251,7 +1223,7 @@ log.debug(sm.getString("endpoint.debug.socketTimeout", Long.valueOf(socket))); } - AprSocketWrapper socketWrapper = connections.get(Long.valueOf(socket)); + SocketWrapperBase socketWrapper = connections.get(Long.valueOf(socket)); if (socketWrapper != null) { socketWrapper.setError(new SocketTimeoutException()); if (socketWrapper.readOperation != null || socketWrapper.writeOperation != null) { @@ -1361,7 +1333,8 @@ while (info != null) { localAddList.remove(info.socket); removeFromPoller(info.socket); - destroySocket(info.socket); + closeSocketInternal(info.socket); + destroySocketInternal(info.socket); info = localCloseList.get(); } } @@ -1376,8 +1349,8 @@ Long.valueOf(info.socket))); } timeouts.remove(info.socket); - AprSocketWrapper wrapper = connections.get( - Long.valueOf(info.socket)); + AprSocketWrapper wrapper = + (AprSocketWrapper) connections.get(Long.valueOf(info.socket)); if (wrapper != null) { if (info.read() || info.write()) { wrapper.pollerFlags = wrapper.pollerFlags | @@ -1390,7 +1363,7 @@ // the poller. removeFromPoller(info.socket); if (!addToPoller(info.socket, wrapper.pollerFlags)) { - closeSocket(info.socket); + wrapper.close(); } else { timeouts.add(info.socket, System.currentTimeMillis() + @@ -1398,7 +1371,7 @@ } } else { // Should never happen. - closeSocket(info.socket); + wrapper.close(); getLog().warn(sm.getString( "endpoint.apr.pollAddInvalid", info)); } @@ -1422,8 +1395,8 @@ Long.valueOf(desc[n*2]))); } long timeout = timeouts.remove(desc[n*2+1]); - AprSocketWrapper wrapper = connections.get( - Long.valueOf(desc[n*2+1])); + AprSocketWrapper wrapper = (AprSocketWrapper) + connections.get(Long.valueOf(desc[n*2+1])); if (wrapper == null) { // Socket was closed in another thread while still in // the Poller but wasn't removed from the Poller before @@ -1448,31 +1421,31 @@ // Error probably occurred during a non-blocking read if (!processSocket(desc[n*2+1], SocketEvent.OPEN_READ)) { // Close socket and clear pool - closeSocket(desc[n*2+1]); + wrapper.close(); } } else if ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) { // Error probably occurred during a non-blocking write if (!processSocket(desc[n*2+1], SocketEvent.OPEN_WRITE)) { // Close socket and clear pool - closeSocket(desc[n*2+1]); + wrapper.close(); } } else if ((wrapper.pollerFlags & Poll.APR_POLLIN) == Poll.APR_POLLIN) { // Can't tell what was happening when the error occurred but the // socket is registered for non-blocking read so use that if (!processSocket(desc[n*2+1], SocketEvent.OPEN_READ)) { // Close socket and clear pool - closeSocket(desc[n*2+1]); + wrapper.close(); } } else if ((wrapper.pollerFlags & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) { // Can't tell what was happening when the error occurred but the // socket is registered for non-blocking write so use that if (!processSocket(desc[n*2+1], SocketEvent.OPEN_WRITE)) { // Close socket and clear pool - closeSocket(desc[n*2+1]); + wrapper.close(); } } else { // Close socket and clear pool - closeSocket(desc[n*2+1]); + wrapper.close(); } } else if (((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) || ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT)) { @@ -1481,14 +1454,14 @@ !processSocket(desc[n*2+1], SocketEvent.OPEN_READ)) { error = true; // Close socket and clear pool - closeSocket(desc[n*2+1]); + wrapper.close(); } if (!error && ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) && !processSocket(desc[n*2+1], SocketEvent.OPEN_WRITE)) { // Close socket and clear pool error = true; - closeSocket(desc[n*2+1]); + wrapper.close(); } if (!error && wrapper.pollerFlags != 0) { // If socket was registered for multiple events but @@ -1520,7 +1493,7 @@ "endpoint.apr.pollUnknownEvent", Long.valueOf(desc[n*2]))); // Close socket and clear pool - closeSocket(desc[n*2+1]); + wrapper.close(); } } } else if (rv < 0) { @@ -1689,13 +1662,13 @@ // Close any socket remaining in the add queue for (int i = (addS.size() - 1); i >= 0; i--) { SendfileData data = addS.get(i); - closeSocket(data.socket); + closeSocketInternal(data.socket); } // Close all sockets still in the poller int rv = Poll.pollset(sendfilePollset, desc); if (rv > 0) { for (int n = 0; n < rv; n++) { - closeSocket(desc[n*2+1]); + closeSocketInternal(desc[n*2+1]); } } Pool.destroy(pool); @@ -1827,7 +1800,7 @@ Integer.valueOf(rv), Error.strerror(rv))); // Can't do anything: close the socket right away - closeSocket(data.socket); + closeSocketInternal(data.socket); } } addS.clear(); @@ -1849,7 +1822,7 @@ remove(state); // Destroy file descriptor pool, which should close the file // Close the socket, as the response would be incomplete - closeSocket(state.socket); + closeSocketInternal(state.socket); continue; } // Write some data using sendfile @@ -1861,7 +1834,7 @@ remove(state); // Close the socket, as the response would be incomplete // This will close the file too. - closeSocket(state.socket); + closeSocketInternal(state.socket); continue; } @@ -1873,7 +1846,7 @@ case NONE: { // Close the socket since this is // the end of the not keep-alive request. - closeSocket(state.socket); + closeSocketInternal(state.socket); break; } case PIPELINED: { @@ -1882,7 +1855,7 @@ Socket.timeoutSet(state.socket, getConnectionTimeout() * 1000); // Process the pipelined request data if (!processSocket(state.socket, SocketEvent.OPEN_READ)) { - closeSocket(state.socket); + closeSocketInternal(state.socket); } break; } @@ -1931,7 +1904,7 @@ remove(state); // Destroy file descriptor pool, which should close the file // Close the socket, as the response would be incomplete - closeSocket(state.socket); + closeSocketInternal(state.socket); } } } @@ -1979,7 +1952,7 @@ } else { // Close socket and pool getHandler().process(socket, SocketEvent.CONNECT_FAIL); - closeSocket(socket.getSocket().longValue()); + socket.close(); socket = null; } } else { @@ -1987,21 +1960,21 @@ if (!setSocketOptions(socket)) { // Close socket and pool getHandler().process(socket, SocketEvent.CONNECT_FAIL); - closeSocket(socket.getSocket().longValue()); + socket.close(); socket = null; return; } // Process the request from this socket - Handler.SocketState state = getHandler().process(socket, - SocketEvent.OPEN_READ); + Handler.SocketState state = getHandler().process(socket, SocketEvent.OPEN_READ); if (state == Handler.SocketState.CLOSED) { // Close socket and pool - closeSocket(socket.getSocket().longValue()); + socket.close(); socket = null; } } } } + } @@ -2012,7 +1985,7 @@ * This class is the equivalent of the Worker, but will simply use in an * external Executor thread pool. */ - protected class SocketProcessor extends SocketProcessorBase { + protected class SocketProcessor extends SocketProcessorBase { public SocketProcessor(SocketWrapperBase socketWrapper, SocketEvent event) { super(socketWrapper, event); @@ -2025,7 +1998,7 @@ SocketState state = getHandler().process(socketWrapper, event); if (state == Handler.SocketState.CLOSED) { // Close socket and pool - closeSocket(socketWrapper.getSocket().longValue()); + socketWrapper.close(); } } finally { socketWrapper = null; @@ -2048,9 +2021,21 @@ // This field should only be used by Poller#run() private int pollerFlags = 0; + /* + * Used if block/non-blocking is set at the socket level. The client is + * responsible for the thread-safe use of this field via the locks provided. + */ + private volatile boolean blockingStatus = true; + private final Lock blockingStatusReadLock; + private final WriteLock blockingStatusWriteLock; + public AprSocketWrapper(Long socket, AprEndpoint endpoint) { super(socket, endpoint); + ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + this.blockingStatusReadLock = lock.readLock(); + this.blockingStatusWriteLock = lock.writeLock(); + // TODO Make the socketWriteBuffer size configurable and align the // SSL and app buffer size settings with NIO & NIO2. if (endpoint.isSSLEnabled()) { @@ -2063,6 +2048,14 @@ socketBufferHandler = new SocketBufferHandler(6 * 1500, 6 * 1500, true); } + public boolean getBlockingStatus() { return blockingStatus; } + public void setBlockingStatus(boolean blockingStatus) { + this.blockingStatus = blockingStatus; + } + public Lock getBlockingStatusReadLock() { return blockingStatusReadLock; } + public WriteLock getBlockingStatusWriteLock() { + return blockingStatusWriteLock; + } @Override public int read(boolean block, byte[] b, int off, int len) throws IOException { @@ -2242,67 +2235,15 @@ @Override protected void doClose() { if (log.isDebugEnabled()) { - log.debug("Calling [" + getEndpoint() + "].closeSocket([" + this + "])", new Exception()); + log.debug("Calling [" + getEndpoint() + "].closeSocket([" + this + "])"); } + getEndpoint().connections.remove(getSocket()); socketBufferHandler = SocketBufferHandler.EMPTY; nonBlockingWriteBuffer.clear(); - synchronized (closed) { - if (sslOutputBuffer != null) { - ByteBufferUtils.cleanDirectBuffer(sslOutputBuffer); - } - ((AprEndpoint) getEndpoint()).getPoller().close(getSocket().longValue()); - } - } - - - @Override - protected void writeBlockingDirect(ByteBuffer from) throws IOException { - if (from.isDirect()) { - super.writeBlockingDirect(from); - } else { - // The socket write buffer capacity is socket.appWriteBufSize - ByteBuffer writeBuffer = socketBufferHandler.getWriteBuffer(); - int limit = writeBuffer.capacity(); - while (from.remaining() >= limit) { - socketBufferHandler.configureWriteBufferForWrite(); - transfer(from, writeBuffer); - doWrite(true); - } - - if (from.remaining() > 0) { - socketBufferHandler.configureWriteBufferForWrite(); - transfer(from, writeBuffer); - } - } - } - - - @Override - protected void writeNonBlockingDirect(ByteBuffer from) throws IOException { - if (from.isDirect()) { - super.writeNonBlockingDirect(from); - } else { - // The socket write buffer capacity is socket.appWriteBufSize - ByteBuffer writeBuffer = socketBufferHandler.getWriteBuffer(); - int limit = writeBuffer.capacity(); - while (from.remaining() >= limit) { - socketBufferHandler.configureWriteBufferForWrite(); - transfer(from, writeBuffer); - int newPosition = writeBuffer.position() + limit; - doWrite(false); - if (writeBuffer.position() != newPosition) { - // Didn't write the whole amount of data in the last - // non-blocking write. - // Exit the loop. - return; - } - } - - if (from.remaining() > 0) { - socketBufferHandler.configureWriteBufferForWrite(); - transfer(from, writeBuffer); - } + if (sslOutputBuffer != null) { + ByteBufferUtils.cleanDirectBuffer(sslOutputBuffer); } + ((AprEndpoint) getEndpoint()).getPoller().close(getSocket().longValue()); } @@ -2410,6 +2351,9 @@ if (isClosed()) { return; } + if (log.isDebugEnabled()) { + log.debug(sm.getString("endpoint.debug.registerRead", this)); + } Poller p = ((AprEndpoint) getEndpoint()).getPoller(); if (p != null) { p.add(getSocket().longValue(), getReadTimeout(), Poll.APR_POLLIN); @@ -2425,6 +2369,9 @@ if (isClosed()) { return; } + if (log.isDebugEnabled()) { + log.debug(sm.getString("endpoint.debug.registerWrite", this)); + } ((AprEndpoint) getEndpoint()).getPoller().add( getSocket().longValue(), getWriteTimeout(), Poll.APR_POLLOUT); } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties tomcat9-9.0.31/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/jsse/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -31,4 +31,5 @@ jsseUtil.trustedCertNotChecked=The validity dates of the trusted certificate with alias [{0}] were not checked as the certificate was of an unknown type jsseUtil.trustedCertNotValid=The trusted certificate with alias [{0}] and DN [{1}] is not valid due to [{2}]. Certificates signed by this trusted certificate WILL be accepted -pemFile.noMultiPrimes=The PKCS#1 certificate is in multi-prime format and Java does not provide an API for constructing an RSA private key object from that format \ No newline at end of file +pemFile.noMultiPrimes=The PKCS#1 certificate is in multi-prime format and Java does not provide an API for constructing an RSA private key object from that format +pemFile.notValidRFC5915=The provided key file does not conform to RFC 5915 \ No newline at end of file diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/jsse/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/net/jsse/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/jsse/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/jsse/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +jsse.noDefaultProtocols=无法确定sslEnabledProtocols的默认值。设置显式值以确保连接器可以启动。 jsse.openssl.effectiveCiphers=使用的密码:[{0}] jsse.pemParseError=无法从 [{0}] 解析 key diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/jsse/PEMFile.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/jsse/PEMFile.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/jsse/PEMFile.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/jsse/PEMFile.java 2020-02-05 19:26:48.000000000 +0000 @@ -44,6 +44,7 @@ import javax.crypto.spec.PBEKeySpec; import org.apache.tomcat.util.buf.Asn1Parser; +import org.apache.tomcat.util.buf.Asn1Writer; import org.apache.tomcat.util.codec.binary.Base64; import org.apache.tomcat.util.file.ConfigFileLoader; import org.apache.tomcat.util.res.StringManager; @@ -57,6 +58,9 @@ private static final StringManager sm = StringManager.getManager(PEMFile.class); + private static final byte[] OID_EC_PUBLIC_KEY = + new byte[] { 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 }; + private String filename; private List certificates = new ArrayList<>(); private PrivateKey privateKey; @@ -105,6 +109,9 @@ case "PRIVATE KEY": privateKey = part.toPrivateKey(null, keyAlgorithm, Format.PKCS8); break; + case "EC PRIVATE KEY": + privateKey = part.toPrivateKey(null, "EC", Format.RFC5915); + break; case "ENCRYPTED PRIVATE KEY": privateKey = part.toPrivateKey(password, keyAlgorithm, Format.PKCS8); break; @@ -149,6 +156,10 @@ keySpec = new PKCS8EncodedKeySpec(decode()); break; } + case RFC5915: { + keySpec = new PKCS8EncodedKeySpec(rfc5915ToPkcs8(decode())); + break; + } } } else { EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decode()); @@ -182,13 +193,91 @@ } + /* + * RFC5915: SEQ + * INT value = 1 + * OCTET STRING len = 32 bytes + * [0] + * OID named EC + * [1] + * BIT STRING len = 520 bits + * + * PKCS8: SEQ + * INT value = 0 + * SEQ + * OID 1.2.840.10045.2.1 (EC public key) + * OID named EC + * OCTET STRING + * SEQ + * INT value = 1 + * OCTET STRING len = 32 bytes + * [1] + * BIT STRING len = 520 bits + * + */ + private byte[] rfc5915ToPkcs8(byte[] source) { + // Parse RFC 5915 format EC private key + Asn1Parser p = new Asn1Parser(source); + + // Type (sequence) + p.parseTag(0x30); + // Length + p.parseFullLength(); + + // Version + BigInteger version = p.parseInt(); + if (version.intValue() != 1) { + throw new IllegalArgumentException(sm.getString("pemFile.notValidRFC5915")); + } + + // Private key + p.parseTag(0x04); + int privateKeyLen = p.parseLength(); + byte[] privateKey = new byte[privateKeyLen]; + p.parseBytes(privateKey); + + // [0] OID + p.parseTag(0xA0); + int oidLen = p.parseLength(); + byte[] oid = new byte[oidLen]; + p.parseBytes(oid); + if (oid[0] != 0x06) { + throw new IllegalArgumentException(sm.getString("pemFile.notValidRFC5915")); + } + + // [1] Public key + p.parseTag(0xA1); + int publicKeyLen = p.parseLength(); + byte[] publicKey = new byte[publicKeyLen]; + p.parseBytes(publicKey); + if (publicKey[0] != 0x03) { + throw new IllegalArgumentException(sm.getString("pemFile.notValidRFC5915")); + } + + + // Write out PKCS#8 format + return Asn1Writer.writeSequence( + Asn1Writer.writeInteger(0), + Asn1Writer.writeSequence( + OID_EC_PUBLIC_KEY, + oid), + Asn1Writer.writeOctetString( + Asn1Writer.writeSequence( + Asn1Writer.writeInteger(1), + Asn1Writer.writeOctetString(privateKey), + Asn1Writer.writeTag((byte) 0xA1, publicKey)) + ) + ); + } + + private RSAPrivateCrtKeySpec parsePKCS1(byte[] source) { Asn1Parser p = new Asn1Parser(source); // https://en.wikipedia.org/wiki/X.690#BER_encoding // https://tools.ietf.org/html/rfc8017#page-55 - // Type + // Type (sequence) p.parseTag(0x30); // Length p.parseFullLength(); @@ -207,6 +296,7 @@ private enum Format { PKCS1, - PKCS8 + PKCS8, + RFC5915 } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings_fr.properties tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings_fr.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings_fr.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings_fr.properties 2020-02-05 19:26:48.000000000 +0000 @@ -68,6 +68,8 @@ endpoint.debug.pollerProcess=Traitement de(s) évènement(s) [{1}] pour la socket [{0}] endpoint.debug.pollerRemove=Essai d''enlever [{0}] du poller endpoint.debug.pollerRemoved=Enlevé [{0}] du poller +endpoint.debug.registerRead=Enregistrement de l’intérêt en lecture pour [{0}] +endpoint.debug.registerWrite=Enregistrement de l’intérêt en écriture pour [{0}] endpoint.debug.socket=socket [{0}] endpoint.debug.socketTimeout=Expiration [{0}] endpoint.debug.unlock.fail=Reçu une exception en essayant de déverrouiller l''accepteur sur le port [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings_ko.properties tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings_ko.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings_ko.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings_ko.properties 2020-02-05 19:26:48.000000000 +0000 @@ -68,6 +68,8 @@ endpoint.debug.pollerProcess=다음 이벤트(들)을 위해 소켓 [{0}]을(를) 처리합니다: [{1}] endpoint.debug.pollerRemove=Poller로부터 [{0}]을(를) 제거하려 시도 중 endpoint.debug.pollerRemoved=Poller로부터 [{0}]을(를) 제거했습니다. +endpoint.debug.registerRead=[{0}]을(를) 위한 readInterest를 등록했습니다. +endpoint.debug.registerWrite=[{0}]을(를) 위한 writeInterest를 등록했습니다. endpoint.debug.socket=소켓 [{0}] endpoint.debug.socketTimeout=제한 시간 초과로 처리합니다: [{0}] endpoint.debug.unlock.fail=포트 [{0}]에 대한 accept의 잠금을 풀고자 시도하는 중 예외 발생 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings.properties tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -68,6 +68,8 @@ endpoint.debug.pollerProcess=Processing socket [{0}] for event(s) [{1}] endpoint.debug.pollerRemove=Attempting to remove [{0}] from poller endpoint.debug.pollerRemoved=Removed [{0}] from poller +endpoint.debug.registerRead=Registered read interest for [{0}] +endpoint.debug.registerWrite=Registered write interest for [{0}] endpoint.debug.socket=socket [{0}] endpoint.debug.socketTimeout=Timing out [{0}] endpoint.debug.unlock.fail=Caught exception trying to unlock accept on port [{0}] diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,10 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -channel.nio.ssl.closing=频道处于正在关闭(closing)的状态 +channel.nio.ssl.appOutputNotEmpty=应用程序输出缓冲区仍包含数据。数据可能会丢失。 +channel.nio.ssl.closing=通道处于关闭状态 +channel.nio.ssl.eofDuringHandshake=握手期间EOF channel.nio.ssl.foundHttp=找到一个明文HTTP请求,它应该是一个加密的TLS连接 channel.nio.ssl.invalidCloseState=无效的关闭状态,不会发送网络数据。 channel.nio.ssl.notHandshaking=握手认证期间NOT_HANDSHAKING +channel.nio.ssl.remainingDataDuringClose=网络缓冲区中的剩余数据,无法发送SSL关闭消息,套接字仍将关闭 channel.nio.ssl.sniDefault=无法缓冲足够的数据来确定请求的SNI主机名。使用默认值 channel.nio.ssl.sniHostName=连接[{0}]中提取的SNI主机名称是[{1}] channel.nio.ssl.timeoutDuringHandshake=握手期间超时。 @@ -24,11 +27,15 @@ channel.nio.ssl.unwrapFail=无法解包数据,无效状态 [{0}] channel.nio.ssl.unwrapFailResize=由于缓冲区太小无法解包数据,无效状态 [{0}] +endpoint.alpn.negotiated=使用ALPN协商的[{0}]协议 endpoint.apr.applyConf=正将OpenSSLConfCmd应用在SSL Context上。 endpoint.apr.checkConf=检查配置OpenSSLConf endpoint.apr.errApplyConf=不能对SSL上下文应用OpenSSLConf 配置 endpoint.apr.errMakeConf=无法创建OpenSSLConf 上下文 +endpoint.apr.failSslContextMake=无法创建SSLContext。检查AprLifecycleListener中的SSLEngine 是否已启用,AprLifecycleListener是否已正确初始化,以及是否已指定有效的SSLProtocol endpoint.apr.invalidSslProtocol=为SSLProtocol属性提供了无效值[{0}] +endpoint.apr.maxConnections.running=):APR终结点在运行时不支持MaxConnections的设置。[{0}]的现有值将继续使用。 +endpoint.apr.maxConnections.unlimited=APR终结点不支持无限连接。[{0}]的现有值将继续使用。 endpoint.apr.pollAddInvalid=无效企图向一个轮询器中添加一个套接字[{0}] endpoint.apr.tooManyCertFiles=证书文件配置超过了 endpoint.debug.channelCloseFail=关闭频道失败 @@ -38,8 +45,11 @@ endpoint.err.close=抓住异常试图关闭socket endpoint.err.unexpected=处理套接字时意外错误 endpoint.getAttribute=[{0}] 是 [{1}] +endpoint.init.bind=套接字绑定失败: [{0}] [{1}] endpoint.init.notavail=APR.不可用 endpoint.invalidJmxNameSslHostCert=对于SSLHostConfigCertificate关联的主机[{0}]和证书类型[{1}],无法生成有效的XML对象名称 +endpoint.jmxRegistrationFailed=未能使用名称[{0}]注册JMX对象 +endpoint.jsse.noSslContext=):找不到主机名[{0}]的SSLContext endpoint.nio.stopLatchAwaitInterrupted=在等待轮询器停止时,该线程被中断 endpoint.nio.timeoutCme=处理超时异常. 这段代码已经被重复检查并且没有并发修改发现。如果你能重现这个错误,请提交一个tomcat bug, 提供重现步骤. endpoint.noSslHostConfig=没有找到带有hostName[{0}]的SSLHostConfig元素,以匹配连接器[{1}]的默认SSLHostConfigName @@ -65,11 +75,18 @@ jsse.ssl3=SSLv3 已显式启用。 已知该协议是不安全。 +sniExtractor.clientHelloTooBig=):ClientHello 没有出现在单个TLS记录中,因此无法提取SNI信息 + socket.apr.closed=与该链接所关联的 socket [{0}] 被关闭 +socket.closed=与此连接关联的套接字已关闭。 sslHostConfig.certificate.notype=指定了多个证书,并且至少有一个证书缺少必需的属性类型 sslHostConfig.fileNotFound=配置文件 [{0}] 不存在 +sslHostConfig.mismatch=属性[{0}]是在名为[{1}]的SSLHostConfig 上设置的,用于[{2}]配置语法,但SSLHostConfig 正与[{3}]配置语法一起使用 +sslHostConfig.opensslconf.null=(:忽略设置空OpenSSLConf 的尝试 +sslHostConfig.prefix_missing=协议[{0}]已添加到名为[{1}]的SSLHostConfig 上的协议列表中。检查是否缺少一个+/-前缀。 sslImplementation.cnfe=无法为类 [{0}] 创建SSLImplementation sslUtilBase.noneSupported=SSL引擎不支持指定的[{0}]:[{1}] +sslUtilBase.skipped=某些指定的[{0}]不受SSL引擎支持,已被跳过:[{1}] diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/Nio2Endpoint.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/Nio2Endpoint.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/Nio2Endpoint.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/Nio2Endpoint.java 2020-02-05 19:26:48.000000000 +0000 @@ -212,8 +212,8 @@ public void run() { // Then close all active connections if any remain try { - for (Nio2Channel channel : getHandler().getOpenSockets()) { - channel.getSocketWrapper().close(); + for (SocketWrapperBase wrapper : getConnections()) { + wrapper.close(); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); @@ -302,8 +302,9 @@ */ @Override protected boolean setSocketOptions(AsynchronousSocketChannel socket) { + Nio2SocketWrapper socketWrapper = null; try { - socketProperties.setProperties(socket); + // Allocate channel and wrapper Nio2Channel channel = null; if (nioChannels != null) { channel = nioChannels.pop(); @@ -319,25 +320,34 @@ channel = new Nio2Channel(bufhandler); } } - Nio2SocketWrapper socketWrapper = new Nio2SocketWrapper(channel, this); - channel.reset(socket, socketWrapper); + Nio2SocketWrapper newWrapper = new Nio2SocketWrapper(channel, this); + channel.reset(socket, newWrapper); + connections.put(socket, newWrapper); + socketWrapper = newWrapper; + + // Set socket properties + socketProperties.setProperties(socket); + socketWrapper.setReadTimeout(getConnectionTimeout()); socketWrapper.setWriteTimeout(getConnectionTimeout()); socketWrapper.setKeepAliveLeft(Nio2Endpoint.this.getMaxKeepAliveRequests()); socketWrapper.setSecure(isSSLEnabled()); - // Continue processing on another thread + // Continue processing on the same thread as the acceptor is async return processSocket(socketWrapper, SocketEvent.OPEN_READ, false); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); - log.error(sm.getString("endpoint.socketOptionsError"),t); + log.error(sm.getString("endpoint.socketOptionsError"), t); + if (socketWrapper == null) { + destroySocket(socket); + } } - // Tell to close the socket + // Tell to close the socket if needed return false; } @Override - protected void closeSocket(AsynchronousSocketChannel socket) { + protected void destroySocket(AsynchronousSocketChannel socket) { countDownConnection(); try { socket.close(); @@ -912,18 +922,16 @@ @Override protected void doClose() { if (log.isDebugEnabled()) { - log.debug("Calling [" + getEndpoint() + "].closeSocket([" + this + "])", new Exception()); + log.debug("Calling [" + getEndpoint() + "].closeSocket([" + this + "])"); } try { - synchronized (getSocket()) { - getEndpoint().countDownConnection(); - if (getSocket().isOpen()) { - getSocket().close(true); - } - if (getEndpoint().running && !getEndpoint().paused) { - if (nioChannels == null || !nioChannels.push(getSocket())) { - getSocket().free(); - } + getEndpoint().connections.remove(getSocket().getIOChannel()); + if (getSocket().isOpen()) { + getSocket().close(true); + } + if (getEndpoint().running && !getEndpoint().paused) { + if (nioChannels == null || !nioChannels.push(getSocket())) { + getSocket().free(); } } } catch (Throwable e) { @@ -1368,6 +1376,9 @@ if (readNotify) { return; } + if (log.isDebugEnabled()) { + log.debug(sm.getString("endpoint.debug.registerRead", this)); + } readInterest = true; if (readPending.tryAcquire()) { // No read pending, so do a read @@ -1395,6 +1406,9 @@ if (writeNotify) { return; } + if (log.isDebugEnabled()) { + log.debug(sm.getString("endpoint.debug.registerWrite", this)); + } writeInterest = true; if (writePending.availablePermits() == 1) { // If no write is pending, notify that writing is possible diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/NioEndpoint.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/NioEndpoint.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/NioEndpoint.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/NioEndpoint.java 2020-02-05 19:26:48.000000000 +0000 @@ -22,7 +22,6 @@ import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; -import java.net.Socket; import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.nio.channels.CancelledKeyException; @@ -136,7 +135,7 @@ public boolean getUseInheritedChannel() { return useInheritedChannel; } /** - * Priority of the poller threads. + * Priority of the poller thread. */ private int pollerThreadPriority = Thread.NORM_PRIORITY; public void setPollerThreadPriority(int pollerThreadPriority) { this.pollerThreadPriority = pollerThreadPriority; } @@ -393,13 +392,9 @@ */ @Override protected boolean setSocketOptions(SocketChannel socket) { - // Process the connection + NioSocketWrapper socketWrapper = null; try { - // Disable blocking, polling will be used - socket.configureBlocking(false); - Socket sock = socket.socket(); - socketProperties.setProperties(sock); - + // Allocate channel and wrapper NioChannel channel = null; if (nioChannels != null) { channel = nioChannels.pop(); @@ -414,10 +409,17 @@ } else { channel = new NioChannel(bufhandler); } - } else { } - NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this); - channel.reset(socket, socketWrapper); + NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this); + channel.reset(socket, newWrapper); + connections.put(socket, newWrapper); + socketWrapper = newWrapper; + + // Set socket properties + // Disable blocking, polling will be used + socket.configureBlocking(false); + socketProperties.setProperties(socket.socket()); + socketWrapper.setReadTimeout(getConnectionTimeout()); socketWrapper.setWriteTimeout(getConnectionTimeout()); socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests()); @@ -431,14 +433,17 @@ } catch (Throwable tt) { ExceptionUtils.handleThrowable(tt); } + if (socketWrapper == null) { + destroySocket(socket); + } } - // Tell to close the socket + // Tell to close the socket if needed return false; } @Override - protected void closeSocket(SocketChannel socket) { + protected void destroySocket(SocketChannel socket) { countDownConnection(); try { socket.close(); @@ -661,25 +666,23 @@ public void cancelledKey(SelectionKey sk, SocketWrapperBase socketWrapper) { try { - if (socketWrapper != null) { - socketWrapper.close(); - } + // If is important to cancel the key first, otherwise a deadlock may occur between the + // poller select and the socket channel close which would cancel the key if (sk != null) { sk.attach(null); if (sk.isValid()) { sk.cancel(); } - // The SocketChannel is also available via the SelectionKey. If - // it hasn't been closed in the block above, close it now. - if (sk.channel().isOpen()) { - sk.channel().close(); - } } } catch (Throwable e) { ExceptionUtils.handleThrowable(e); if (log.isDebugEnabled()) { log.error(sm.getString("endpoint.debug.channelCloseFail"), e); } + } finally { + if (socketWrapper != null) { + socketWrapper.close(); + } } } @@ -768,6 +771,11 @@ if (!socketWrapper.readOperation.process()) { closeSocket = true; } + } else if (socketWrapper.readBlocking) { + synchronized (socketWrapper.readLock) { + socketWrapper.readBlocking = false; + socketWrapper.readLock.notify(); + } } else if (!processSocket(socketWrapper, SocketEvent.OPEN_READ, true)) { closeSocket = true; } @@ -777,6 +785,11 @@ if (!socketWrapper.writeOperation.process()) { closeSocket = true; } + } else if (socketWrapper.writeBlocking) { + synchronized (socketWrapper.writeLock) { + socketWrapper.writeBlocking = false; + socketWrapper.writeLock.notify(); + } } else if (!processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)) { closeSocket = true; } @@ -947,24 +960,25 @@ cancelledKey(key, socketWrapper); } else if ((socketWrapper.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ || (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { - boolean isTimedOut = false; boolean readTimeout = false; boolean writeTimeout = false; // Check for read timeout if ((socketWrapper.interestOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { long delta = now - socketWrapper.getLastRead(); long timeout = socketWrapper.getReadTimeout(); - isTimedOut = timeout > 0 && delta > timeout; - readTimeout = true; + if (timeout > 0 && delta > timeout) { + readTimeout = true; + } } // Check for write timeout - if (!isTimedOut && (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { + if (!readTimeout && (socketWrapper.interestOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) { long delta = now - socketWrapper.getLastWrite(); long timeout = socketWrapper.getWriteTimeout(); - isTimedOut = timeout > 0 && delta > timeout; - writeTimeout = true; + if (timeout > 0 && delta > timeout) { + writeTimeout = true; + } } - if (isTimedOut) { + if (readTimeout || writeTimeout) { key.interestOps(0); // Avoid duplicate timeout calls socketWrapper.interestOps(0); @@ -1019,12 +1033,19 @@ private volatile long lastRead = System.currentTimeMillis(); private volatile long lastWrite = lastRead; + private final Object readLock; + private volatile boolean readBlocking = false; + private final Object writeLock; + private volatile boolean writeBlocking = false; + public NioSocketWrapper(NioChannel channel, NioEndpoint endpoint) { super(channel, endpoint); pool = endpoint.getSelectorPool(); nioChannels = endpoint.getNioChannels(); poller = endpoint.getPoller(); socketBufferHandler = channel.getBufHandler(); + readLock = (readPending == null) ? new Object() : readPending; + writeLock = (writePending == null) ? new Object() : writePending; } public Poller getPoller() { return poller; } @@ -1160,18 +1181,16 @@ @Override protected void doClose() { if (log.isDebugEnabled()) { - log.debug("Calling [" + getEndpoint() + "].closeSocket([" + this + "])", new Exception()); + log.debug("Calling [" + getEndpoint() + "].closeSocket([" + this + "])"); } try { - synchronized (getSocket()) { - getEndpoint().countDownConnection(); - if (getSocket().isOpen()) { - getSocket().close(true); - } - if (getEndpoint().running && !getEndpoint().paused) { - if (nioChannels == null || !nioChannels.push(getSocket())) { - getSocket().free(); - } + getEndpoint().connections.remove(getSocket().getIOChannel()); + if (getSocket().isOpen()) { + getSocket().close(true); + } + if (getEndpoint().running && !getEndpoint().paused) { + if (nioChannels == null || !nioChannels.push(getSocket())) { + getSocket().free(); } } } catch (Throwable e) { @@ -1209,24 +1228,37 @@ if (socket instanceof ClosedNioChannel) { throw new ClosedChannelException(); } - if (block) { - Selector selector = null; - try { - selector = pool.get(); - } catch (IOException x) { - // Ignore - } + nRead = socket.read(to); + if (nRead == -1) { + throw new EOFException(); + } + if (block && nRead == 0) { + long timeout = getReadTimeout(); try { - nRead = pool.read(to, socket, selector, getReadTimeout()); - } finally { - if (selector != null) { - pool.put(selector); + readBlocking = true; + registerReadInterest(); + synchronized (readLock) { + if (readBlocking) { + try { + if (timeout > 0) { + readLock.wait(timeout); + } else { + readLock.wait(); + } + } catch (InterruptedException e) { + // Continue ... + } + if (readBlocking) { + throw new SocketTimeoutException(); + } + } } - } - } else { - nRead = socket.read(to); - if (nRead == -1) { - throw new EOFException(); + nRead = socket.read(to); + if (nRead == -1) { + throw new EOFException(); + } + } finally { + readBlocking = false; } } return nRead; @@ -1240,36 +1272,50 @@ throw new ClosedChannelException(); } if (block) { - long writeTimeout = getWriteTimeout(); - Selector selector = null; + long timeout = getWriteTimeout(); try { - selector = pool.get(); - } catch (IOException x) { - // Ignore - } - try { - pool.write(from, socket, selector, writeTimeout); - if (block) { - // Make sure we are flushed - do { - if (socket.flush(true, selector, writeTimeout)) { - break; + int n = 0; + do { + n = socket.write(from); + if (n == -1) { + throw new EOFException(); + } + if (n == 0) { + writeBlocking = true; + registerWriteInterest(); + synchronized (writeLock) { + if (writeBlocking) { + try { + if (timeout > 0) { + writeLock.wait(timeout); + } else { + writeLock.wait(); + } + } catch (InterruptedException e) { + // Continue ... + } + if (writeBlocking) { + throw new SocketTimeoutException(); + } + } } - } while (true); - } + } + } while (from.hasRemaining()); } finally { - if (selector != null) { - pool.put(selector); - } + writeBlocking = false; } // If there is data left in the buffer the socket will be registered for // write further up the stack. This is to ensure the socket is only // registered for write once as both container and user code can trigger // write registration. } else { - if (socket.write(from) == -1) { - throw new EOFException(); - } + int n = 0; + do { + n = socket.write(from); + if (n == -1) { + throw new EOFException(); + } + } while (n > 0 && from.hasRemaining()); } updateLastWrite(); } @@ -1277,12 +1323,18 @@ @Override public void registerReadInterest() { + if (log.isDebugEnabled()) { + log.debug(sm.getString("endpoint.debug.registerRead", this)); + } getPoller().add(this, SelectionKey.OP_READ); } @Override public void registerWriteInterest() { + if (log.isDebugEnabled()) { + log.debug(sm.getString("endpoint.debug.registerWrite", this)); + } getPoller().add(this, SelectionKey.OP_WRITE); } @@ -1479,7 +1531,15 @@ } } if (doWrite) { - nBytes = getSocket().write(buffers, offset, length); + long n = 0; + do { + n = getSocket().write(buffers, offset, length); + if (n == -1) { + nBytes = n; + } else { + nBytes += n; + } + } while (n > 0); updateLastWrite(); } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/openssl/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/net/openssl/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/openssl/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/openssl/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -17,11 +17,14 @@ engine.emptyCipherSuite=空密码套件 engine.engineClosed=引擎已经关闭 engine.noSession=SSL会话ID不可用 +engine.nullCipherSuite=无加密套件 engine.openSSLError=OpenSSL 错误:[{0}] 信息: [{1}] +engine.unsupportedProtocol=不支持协议 [{0}] engine.writeToSSLFailed=写入SSL失败,返回:[{0}] openssl.X509FactoryError=获取X509工厂实例时出错 openssl.addedClientCaCert=添加了客户端 CA 证书:[{0}] +openssl.errApplyConf=无法将OpenSSLConf 应用于SSL 上下文 openssl.errMakeConf=无法创建OpenSSLConf上下文 openssl.keyManagerMissing=key管理器未找到 openssl.trustManagerMissing=没有找到.信任管理者 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/openssl/OpenSSLContext.java 2020-02-05 19:26:48.000000000 +0000 @@ -49,6 +49,7 @@ import org.apache.tomcat.util.net.AbstractEndpoint; import org.apache.tomcat.util.net.Constants; import org.apache.tomcat.util.net.SSLHostConfig; +import org.apache.tomcat.util.net.SSLHostConfig.CertificateVerification; import org.apache.tomcat.util.net.SSLHostConfigCertificate; import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type; import org.apache.tomcat.util.res.StringManager; @@ -489,7 +490,9 @@ @Override public SSLEngine createSSLEngine() { return new OpenSSLEngine(ctx, defaultProtocol, false, sessionContext, - (negotiableProtocols != null && negotiableProtocols.size() > 0), initialized); + (negotiableProtocols != null && negotiableProtocols.size() > 0), initialized, + sslHostConfig.getCertificateVerificationDepth(), + sslHostConfig.getCertificateVerification() == CertificateVerification.OPTIONAL_NO_CA); } @Override diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/openssl/OpenSSLEngine.java 2020-02-05 19:26:48.000000000 +0000 @@ -165,6 +165,8 @@ private final OpenSSLSessionContext sessionContext; private final boolean alpn; private final boolean initialized; + private final int certificateVerificationDepth; + private final boolean certificateVerificationOptionalNoCA; private String selectedProtocol = null; @@ -181,31 +183,16 @@ * {@link SSLEngine} belongs to. * @param alpn {@code true} if alpn should be used, {@code false} * otherwise - */ - OpenSSLEngine(long sslCtx, String fallbackApplicationProtocol, - boolean clientMode, OpenSSLSessionContext sessionContext, - boolean alpn) { - this(sslCtx, fallbackApplicationProtocol, clientMode, sessionContext, - alpn, false); - } - - /** - * Creates a new instance - * - * @param sslCtx an OpenSSL {@code SSL_CTX} object - * @param fallbackApplicationProtocol the fallback application protocol - * @param clientMode {@code true} if this is used for clients, {@code false} - * otherwise - * @param sessionContext the {@link OpenSSLSessionContext} this - * {@link SSLEngine} belongs to. - * @param alpn {@code true} if alpn should be used, {@code false} - * otherwise * @param initialized {@code true} if this instance gets its protocol, * cipher and client verification from the {@code SSL_CTX} {@code sslCtx} + * @param certificateVerificationDepth Certificate verification depth + * @param certificateVerificationOptionalNoCA Skip CA verification in + * optional mode */ OpenSSLEngine(long sslCtx, String fallbackApplicationProtocol, boolean clientMode, OpenSSLSessionContext sessionContext, boolean alpn, - boolean initialized) { + boolean initialized, int certificateVerificationDepth, + boolean certificateVerificationOptionalNoCA) { if (sslCtx == 0) { throw new IllegalArgumentException(sm.getString("engine.noSSLContext")); } @@ -219,6 +206,8 @@ this.sessionContext = sessionContext; this.alpn = alpn; this.initialized = initialized; + this.certificateVerificationDepth = certificateVerificationDepth; + this.certificateVerificationOptionalNoCA = certificateVerificationOptionalNoCA; } @Override @@ -1111,13 +1100,15 @@ } switch (mode) { case NONE: - SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, VERIFY_DEPTH); + SSL.setVerify(ssl, SSL.SSL_CVERIFY_NONE, certificateVerificationDepth); break; case REQUIRE: - SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRE, VERIFY_DEPTH); + SSL.setVerify(ssl, SSL.SSL_CVERIFY_REQUIRE, certificateVerificationDepth); break; case OPTIONAL: - SSL.setVerify(ssl, SSL.SSL_CVERIFY_OPTIONAL, VERIFY_DEPTH); + SSL.setVerify(ssl, + certificateVerificationOptionalNoCA ? SSL.SSL_CVERIFY_OPTIONAL_NO_CA : SSL.SSL_CVERIFY_OPTIONAL, + certificateVerificationDepth); break; } clientAuth = mode; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/SocketProperties.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/SocketProperties.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/SocketProperties.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/SocketProperties.java 2020-02-05 19:26:48.000000000 +0000 @@ -39,6 +39,7 @@ * Default is 500 * -1 is unlimited * 0 is disabled + * TODO: The default will be changed to 0 in Tomcat 10 */ protected int processorCache = 500; @@ -49,6 +50,7 @@ * -1 is unlimited * 0 is disabled * >0 the max number of objects to keep in cache. + * TODO: The default will be changed to 0 in Tomcat 10 */ protected int eventCache = 500; @@ -93,6 +95,11 @@ * this value is how many channels * -1 means unlimited cached, 0 means no cache * Default value is 500 + * TODO: The default should be changed in Tomcat 10, actually it should be + * bufferPoolSize / (appReadBufSize + appWriteBufSize), assuming the SSL + * buffers are ignored (that would be logical), and the value would be 6400. + * So the default value will be changed to a new default value like -2 to + * set a dynamic value based on bufferPoolSize in that case. */ protected int bufferPool = 500; @@ -100,6 +107,13 @@ * Buffer pool size in bytes to be cached * -1 means unlimited, 0 means no cache * Default value is 100MB (1024*1024*100 bytes) + * TODO: The default value to be used could rather be based on the + * JVM max heap, otherwise it could be a problem in some + * environments. Big servers also need to use a much higher default, + * while small cloud based ones should use 0 instead. + * Possible default value strategy: + * heap inf 1GB: 0 + * heap sup 1GB: heap / 32 */ protected int bufferPoolSize = 1024*1024*100; diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/SocketWrapperBase.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/SocketWrapperBase.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/SocketWrapperBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/SocketWrapperBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -29,9 +29,6 @@ import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -58,6 +55,7 @@ private volatile boolean upgraded = false; private boolean secure = false; private String negotiatedProtocol = null; + /* * Following cached for speed / reduced GC */ @@ -67,14 +65,8 @@ protected String remoteAddr = null; protected String remoteHost = null; protected int remotePort = -1; - /* - * Used if block/non-blocking is set at the socket level. The client is - * responsible for the thread-safe use of this field via the locks provided. - */ - private volatile boolean blockingStatus = true; - private final Lock blockingStatusReadLock; - private final WriteLock blockingStatusWriteLock; - /* + + /** * Used to record the first IOException that occurs during non-blocking * read/writes that can't be usefully propagated up the stack since there is * no user code or appropriate container code in the stack to handle it. @@ -103,17 +95,23 @@ */ protected final WriteBuffer nonBlockingWriteBuffer = new WriteBuffer(bufferedWriteSize); + /* + * Asynchronous operations. + */ protected final Semaphore readPending; protected volatile OperationState readOperation = null; protected final Semaphore writePending; protected volatile OperationState writeOperation = null; + /** + * The org.apache.coyote.Processor instance currently associated + * with the wrapper. + */ + protected Object currentProcessor = null; + public SocketWrapperBase(E socket, AbstractEndpoint endpoint) { this.socket = socket; this.endpoint = endpoint; - ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); - this.blockingStatusReadLock = lock.readLock(); - this.blockingStatusWriteLock = lock.writeLock(); if (endpoint.getUseAsyncIO() || needSemaphores()) { readPending = new Semaphore(1); writePending = new Semaphore(1); @@ -135,6 +133,14 @@ return endpoint; } + public Object getCurrentProcessor() { + return currentProcessor; + } + + public void setCurrentProcessor(Object currentProcessor) { + this.currentProcessor = currentProcessor; + } + /** * Transfers processing to a container thread. * @@ -212,13 +218,9 @@ return this.writeTimeout; } - public void setKeepAliveLeft(int keepAliveLeft) { - this.keepAliveLeft = keepAliveLeft; - } - public int decrementKeepAlive() { - return --keepAliveLeft; - } + public void setKeepAliveLeft(int keepAliveLeft) { this.keepAliveLeft = keepAliveLeft; } + public int decrementKeepAlive() { return (--keepAliveLeft); } public String getRemoteHost() { if (remoteHost == null) { @@ -268,14 +270,6 @@ } protected abstract void populateLocalPort(); - public boolean getBlockingStatus() { return blockingStatus; } - public void setBlockingStatus(boolean blockingStatus) { - this.blockingStatus = blockingStatus; - } - public Lock getBlockingStatusReadLock() { return blockingStatusReadLock; } - public WriteLock getBlockingStatusWriteLock() { - return blockingStatusWriteLock; - } public SocketBufferHandler getSocketBufferHandler() { return socketBufferHandler; } public boolean hasDataToRead() { @@ -398,8 +392,10 @@ if (log.isDebugEnabled()) { log.error(sm.getString("endpoint.debug.handlerRelease"), e); } + } finally { + getEndpoint().countDownConnection(); + doClose(); } - doClose(); } } @@ -529,14 +525,17 @@ * @throws IOException If an IO error occurs during the write */ protected void writeBlocking(byte[] buf, int off, int len) throws IOException { - socketBufferHandler.configureWriteBufferForWrite(); - int thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); - while (socketBufferHandler.getWriteBuffer().remaining() == 0) { - len = len - thisTime; - off = off + thisTime; - doWrite(true); + if (len > 0) { socketBufferHandler.configureWriteBufferForWrite(); - thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); + int thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); + len -= thisTime; + while (len > 0) { + off += thisTime; + doWrite(true); + socketBufferHandler.configureWriteBufferForWrite(); + thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); + len -= thisTime; + } } } @@ -555,53 +554,19 @@ * @throws IOException If an IO error occurs during the write */ protected void writeBlocking(ByteBuffer from) throws IOException { - if (socketBufferHandler.isWriteBufferEmpty()) { - // Socket write buffer is empty. Write the provided buffer directly - // to the network. - // TODO Shouldn't smaller writes be buffered anyway? - writeBlockingDirect(from); - } else { - // Socket write buffer has some data. + if (from.hasRemaining()) { socketBufferHandler.configureWriteBufferForWrite(); - // Put as much data as possible into the write buffer transfer(from, socketBufferHandler.getWriteBuffer()); - // If the buffer is now full, write it to the network and then write - // the remaining data directly to the network. - if (!socketBufferHandler.isWriteBufferWritable()) { + while (from.hasRemaining()) { doWrite(true); - writeBlockingDirect(from); + socketBufferHandler.configureWriteBufferForWrite(); + transfer(from, socketBufferHandler.getWriteBuffer()); } } } /** - * Writes directly to the network, bypassing the socket write buffer. - * - * @param from The ByteBuffer containing the data to be written - * - * @throws IOException If an IO error occurs during the write - */ - protected void writeBlockingDirect(ByteBuffer from) throws IOException { - // The socket write buffer capacity is socket.appWriteBufSize - // TODO This only matters when using TLS. For non-TLS connections it - // should be possible to write the ByteBuffer in a single write - int limit = socketBufferHandler.getWriteBuffer().capacity(); - int fromLimit = from.limit(); - while (from.remaining() >= limit) { - from.limit(from.position() + limit); - doWrite(true, from); - from.limit(fromLimit); - } - - if (from.remaining() > 0) { - socketBufferHandler.configureWriteBufferForWrite(); - transfer(from, socketBufferHandler.getWriteBuffer()); - } - } - - - /** * Transfers the data to the socket write buffer (writing that data to the * socket if the buffer fills up using a non-blocking write) until either * all the data has been transferred and space remains in the socket write @@ -619,11 +584,12 @@ * @throws IOException If an IO error occurs during the write */ protected void writeNonBlocking(byte[] buf, int off, int len) throws IOException { - if (nonBlockingWriteBuffer.isEmpty() && socketBufferHandler.isWriteBufferWritable()) { + if (len > 0 && nonBlockingWriteBuffer.isEmpty() + && socketBufferHandler.isWriteBufferWritable()) { socketBufferHandler.configureWriteBufferForWrite(); int thisTime = transfer(buf, off, len, socketBufferHandler.getWriteBuffer()); - len = len - thisTime; - while (!socketBufferHandler.isWriteBufferWritable()) { + len -= thisTime; + while (len > 0) { off = off + thisTime; doWrite(false); if (len > 0 && socketBufferHandler.isWriteBufferWritable()) { @@ -635,7 +601,7 @@ // else to do here. Exit the loop. break; } - len = len - thisTime; + len -= thisTime; } } @@ -664,11 +630,12 @@ protected void writeNonBlocking(ByteBuffer from) throws IOException { - if (nonBlockingWriteBuffer.isEmpty() && socketBufferHandler.isWriteBufferWritable()) { + if (from.hasRemaining() && nonBlockingWriteBuffer.isEmpty() + && socketBufferHandler.isWriteBufferWritable()) { writeNonBlockingInternal(from); } - if (from.remaining() > 0) { + if (from.hasRemaining()) { // Remaining data must be buffered nonBlockingWriteBuffer.add(from); } @@ -684,44 +651,17 @@ * @throws IOException If an IO error occurs during the write */ protected void writeNonBlockingInternal(ByteBuffer from) throws IOException { - if (socketBufferHandler.isWriteBufferEmpty()) { - writeNonBlockingDirect(from); - } else { - socketBufferHandler.configureWriteBufferForWrite(); - transfer(from, socketBufferHandler.getWriteBuffer()); - if (!socketBufferHandler.isWriteBufferWritable()) { - doWrite(false); - if (socketBufferHandler.isWriteBufferWritable()) { - writeNonBlockingDirect(from); - } - } - } - } - - - protected void writeNonBlockingDirect(ByteBuffer from) throws IOException { - // The socket write buffer capacity is socket.appWriteBufSize - // TODO This only matters when using TLS. For non-TLS connections it - // should be possible to write the ByteBuffer in a single write - int limit = socketBufferHandler.getWriteBuffer().capacity(); - int fromLimit = from.limit(); - while (from.remaining() >= limit) { - int newLimit = from.position() + limit; - from.limit(newLimit); - doWrite(false, from); - from.limit(fromLimit); - if (from.position() != newLimit) { - // Didn't write the whole amount of data in the last - // non-blocking write. - // Exit the loop. - return; + socketBufferHandler.configureWriteBufferForWrite(); + transfer(from, socketBufferHandler.getWriteBuffer()); + while (from.hasRemaining()) { + doWrite(false); + if (socketBufferHandler.isWriteBufferWritable()) { + socketBufferHandler.configureWriteBufferForWrite(); + transfer(from, socketBufferHandler.getWriteBuffer()); + } else { + break; } } - - if (from.remaining() > 0) { - socketBufferHandler.configureWriteBufferForWrite(); - transfer(from, socketBufferHandler.getWriteBuffer()); - } } @@ -1020,6 +960,7 @@ protected final CompletionHandler handler; protected final Semaphore semaphore; protected final VectoredIOCompletionHandler completion; + protected final AtomicBoolean callHandler; protected OperationState(boolean read, ByteBuffer[] buffers, int offset, int length, BlockingMode block, long timeout, TimeUnit unit, A attachment, CompletionCheck check, CompletionHandler handler, @@ -1036,6 +977,7 @@ this.handler = handler; this.semaphore = semaphore; this.completion = completion; + callHandler = (handler != null) ? new AtomicBoolean(true) : null; } protected volatile long nBytes = 0; protected volatile CompletionState state = CompletionState.PENDING; @@ -1118,7 +1060,7 @@ state.state = currentState; } state.end(); - if (completion && state.handler != null) { + if (completion && state.handler != null && state.callHandler.compareAndSet(true, false)) { state.handler.completed(Long.valueOf(state.nBytes), state.attachment); } synchronized (state) { @@ -1159,7 +1101,7 @@ state.state = state.isInline() ? CompletionState.ERROR : CompletionState.DONE; } state.end(); - if (state.handler != null) { + if (state.handler != null && state.callHandler.compareAndSet(true, false)) { state.handler.failed(exc, state.attachment); } synchronized (state) { @@ -1489,10 +1431,15 @@ try { state.wait(unit.toMillis(timeout)); if (state.state == CompletionState.PENDING) { + if (handler != null && state.callHandler.compareAndSet(true, false)) { + handler.failed(new SocketTimeoutException(), attachment); + } return CompletionState.ERROR; } } catch (InterruptedException e) { - completion.failed(new SocketTimeoutException(), state); + if (handler != null && state.callHandler.compareAndSet(true, false)) { + handler.failed(new SocketTimeoutException(), attachment); + } return CompletionState.ERROR; } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/SSLHostConfig.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/SSLHostConfig.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/SSLHostConfig.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/SSLHostConfig.java 2020-02-05 19:26:48.000000000 +0000 @@ -209,9 +209,10 @@ private void registerDefaultCertificate() { if (defaultCertificate == null) { - defaultCertificate = new SSLHostConfigCertificate( + SSLHostConfigCertificate defaultCertificate = new SSLHostConfigCertificate( this, SSLHostConfigCertificate.Type.UNDEFINED); - certificates.add(defaultCertificate); + addCertificate(defaultCertificate); + this.defaultCertificate = defaultCertificate; } } @@ -270,8 +271,11 @@ // necessary to support the old configuration attributes (Tomcat 10?). public String getCertificateKeyPassword() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateKeyPassword(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateKeyPassword(); + } } public void setCertificateKeyPassword(String certificateKeyPassword) { registerDefaultCertificate(); @@ -512,8 +516,11 @@ // necessary to support the old configuration attributes (Tomcat 10?). public String getCertificateKeyAlias() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateKeyAlias(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateKeyAlias(); + } } public void setCertificateKeyAlias(String certificateKeyAlias) { registerDefaultCertificate(); @@ -522,8 +529,11 @@ public String getCertificateKeystoreFile() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateKeystoreFile(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateKeystoreFile(); + } } public void setCertificateKeystoreFile(String certificateKeystoreFile) { registerDefaultCertificate(); @@ -532,8 +542,11 @@ public String getCertificateKeystorePassword() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateKeystorePassword(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateKeystorePassword(); + } } public void setCertificateKeystorePassword(String certificateKeystorePassword) { registerDefaultCertificate(); @@ -542,8 +555,11 @@ public String getCertificateKeystoreProvider() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateKeystoreProvider(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateKeystoreProvider(); + } } public void setCertificateKeystoreProvider(String certificateKeystoreProvider) { registerDefaultCertificate(); @@ -552,8 +568,11 @@ public String getCertificateKeystoreType() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateKeystoreType(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateKeystoreType(); + } } public void setCertificateKeystoreType(String certificateKeystoreType) { registerDefaultCertificate(); @@ -719,8 +738,11 @@ // necessary to support the old configuration attributes (Tomcat 10?). public String getCertificateChainFile() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateChainFile(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateChainFile(); + } } public void setCertificateChainFile(String certificateChainFile) { registerDefaultCertificate(); @@ -729,8 +751,11 @@ public String getCertificateFile() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateFile(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateFile(); + } } public void setCertificateFile(String certificateFile) { registerDefaultCertificate(); @@ -739,8 +764,11 @@ public String getCertificateKeyFile() { - registerDefaultCertificate(); - return defaultCertificate.getCertificateKeyFile(); + if (defaultCertificate == null) { + return null; + } else { + return defaultCertificate.getCertificateKeyFile(); + } } public void setCertificateKeyFile(String certificateKeyFile) { registerDefaultCertificate(); diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java tomcat9-9.0.31/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/net/TLSClientHelloExtractor.java 2020-02-05 19:26:48.000000000 +0000 @@ -49,11 +49,11 @@ private static final int TLS_EXTENSION_ALPN = 16; public static byte[] USE_TLS_RESPONSE = ("HTTP/1.1 400 \r\n" + - "Content-Type: text/plain;charset=ISO-8859-1\r\n" + + "Content-Type: text/plain;charset=UTF-8\r\n" + "Connection: close\r\n" + "\r\n" + "Bad Request\r\n" + - "This combination of host and port requires TLS.\r\n").getBytes(StandardCharsets.ISO_8859_1); + "This combination of host and port requires TLS.\r\n").getBytes(StandardCharsets.UTF_8); /** diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/scan/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/util/scan/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/util/scan/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/scan/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -16,6 +16,7 @@ jarScan.classPath.badEntry=class路径[{0}]不能被转化成URL jarScan.classloaderFail=在多级类加载器中扫描[{0}]失败 jarScan.classloaderJarNoScan=跳过classpath路径[{0}]下的jar包扫描。 +jarScan.classloaderJarScan=从classpath扫描JAR[{0}] jarScan.classloaderStart=在类加载器层次结构中扫描JAR jarScan.jarUrlStart=正在扫描URL [{0}] 上的JAR文件 jarScan.webinfclassesFail=无法扫描/WEB-INF/classes diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/scan/StandardJarScanFilter.java tomcat9-9.0.31/java/org/apache/tomcat/util/scan/StandardJarScanFilter.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/scan/StandardJarScanFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/scan/StandardJarScanFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -36,11 +36,13 @@ private static final String defaultScan; private static final Set defaultSkipSet = new HashSet<>(); private static final Set defaultScanSet = new HashSet<>(); + private static final boolean defaultSkipAll; static { // Initialize defaults. There are no setter methods for them. defaultSkip = System.getProperty(Constants.SKIP_JARS_PROPERTY); populateSetFromAttribute(defaultSkip, defaultSkipSet); + defaultSkipAll = defaultSkipSet.contains("*") || defaultSkipSet.contains("*.jar"); defaultScan = System.getProperty(Constants.SCAN_JARS_PROPERTY); populateSetFromAttribute(defaultScan, defaultScanSet); } @@ -132,6 +134,12 @@ } + @Override + public boolean isSkipAll() { + return defaultSkipAll; + } + + public boolean isDefaultTldScan() { return defaultTldScan; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/scan/StandardJarScanner.java tomcat9-9.0.31/java/org/apache/tomcat/util/scan/StandardJarScanner.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/scan/StandardJarScanner.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/scan/StandardJarScanner.java 2020-02-05 19:26:48.000000000 +0000 @@ -171,6 +171,10 @@ log.trace(sm.getString("jarScan.webinflibStart")); } + if (jarScanFilter.isSkipAll()) { + return; + } + Set processedURLs = new HashSet<>(); // Scan WEB-INF/lib @@ -282,6 +286,11 @@ protected void processURLs(JarScanType scanType, JarScannerCallback callback, Set processedURLs, boolean isWebapp, Deque classPathUrlsToProcess) { + + if (jarScanFilter.isSkipAll()) { + return; + } + while (!classPathUrlsToProcess.isEmpty()) { URL url = classPathUrlsToProcess.pop(); diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java tomcat9-9.0.31/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/security/ConcurrentMessageDigest.java 2020-02-05 19:26:48.000000000 +0000 @@ -68,7 +68,7 @@ } - public static byte[] digest(String algorithm, int rounds, byte[]... input) { + public static byte[] digest(String algorithm, int iterations, byte[]... input) { Queue queue = queues.get(algorithm); if (queue == null) { @@ -93,8 +93,8 @@ byte[] result = md.digest(); // Subsequent rounds - if (rounds > 1) { - for (int i = 1; i < rounds; i++) { + if (iterations > 1) { + for (int i = 1; i < iterations; i++) { md.update(result); result = md.digest(); } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/util/threads/TaskQueue.java tomcat9-9.0.31/java/org/apache/tomcat/util/threads/TaskQueue.java --- tomcat9-9.0.27/java/org/apache/tomcat/util/threads/TaskQueue.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/util/threads/TaskQueue.java 2020-02-05 19:26:48.000000000 +0000 @@ -59,12 +59,12 @@ } public boolean force(Runnable o) { - if ( parent==null || parent.isShutdown() ) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); + if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); return super.offer(o); //forces the item onto the queue, to be used if the task is rejected } public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException { - if ( parent==null || parent.isShutdown() ) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); + if (parent == null || parent.isShutdown()) throw new RejectedExecutionException(sm.getString("taskQueue.notRunning")); return super.offer(o,timeout,unit); //forces the item onto the queue, to be used if the task is rejected } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/LocalStrings_ko.properties tomcat9-9.0.31/java/org/apache/tomcat/websocket/LocalStrings_ko.properties --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/LocalStrings_ko.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/LocalStrings_ko.properties 2020-02-05 19:26:48.000000000 +0000 @@ -35,6 +35,7 @@ futureToSendHandler.timeout=[{0}] [{1}]이(가) 완료되기를 기다린 후, 작업 제한 시간을 초과했습니다. +perMessageDeflate.alreadyClosed=해당 transformer가 이미 닫혔으므로 더이상 사용될 수 없습니다. perMessageDeflate.deflateFailed=압축된 웹소켓 프레임의 압축을 풀지 못했습니다. perMessageDeflate.duplicateParameter=[{0}] 확장 파라미터가 중복 정의되어 있습니다. perMessageDeflate.invalidState=유효하지 않은 상태 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/LocalStrings.properties tomcat9-9.0.31/java/org/apache/tomcat/websocket/LocalStrings.properties --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/LocalStrings.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/LocalStrings.properties 2020-02-05 19:26:48.000000000 +0000 @@ -35,6 +35,7 @@ futureToSendHandler.timeout=Operation timed out after waiting [{0}] [{1}] to complete +perMessageDeflate.alreadyClosed=The transformer has been closed and may no longer be used perMessageDeflate.deflateFailed=Failed to decompress a compressed WebSocket frame perMessageDeflate.duplicateParameter=Duplicate definition of the [{0}] extension parameter perMessageDeflate.invalidState=Invalid state diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/websocket/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,7 +13,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +asyncChannelGroup.createFail=无法为WebSocket客户端创建专用的异步通道组,这是防止复杂类加载程序环境(如JavaEE容器)中内存泄漏所必需的 + +asyncChannelWrapperSecure.check.notOk=TLS握手返回意外状态[{0}] +asyncChannelWrapperSecure.check.unwrap=在读取期间将字节写入输出 +asyncChannelWrapperSecure.closeFail=干净的关闭通道失败 asyncChannelWrapperSecure.eof=意外的流结尾 +asyncChannelWrapperSecure.statusUnwrap=unwrap()操作后SSLEngineResult 的意外状态 + +backgroundProcessManager.processFailed=后台进程失败 caseInsensitiveKeyMap.nullKey=不允许 Key 是 Null @@ -24,13 +32,18 @@ util.notToken=一个非法的扩展参数被指定为名称[{0}]和值[{0}] util.unknownDecoderType=无法识别该解码器类型[{0}] +wsFrame.alreadySuspended=消息接收已挂起。 wsFrame.byteToLongFail=提供了太多字节([{0}]),转换成一个长的字节。 wsFrame.closed=在一个关闭的控制帧后受到了一个新的帧. wsFrame.controlFragmented=接收到分段的控制帧,但控制帧可能不被分割。 wsFrame.controlNoFin=发送一个没有设置的控制帧。控制帧不允许使用连续帧。 +wsFrame.controlPayloadTooBig=以大于125字节的最大允许值的大小[{0}]的有效载荷发送控制帧。 +wsFrame.illegalReadState=意外的读状态[{0}] +wsFrame.invalidUtf8Close=接收到一个WebSocket关闭帧,其关闭原因包含无效的UTF-8字节序列 wsFrame.notMasked=客户端帧未被屏蔽,但必须屏蔽所有客户端帧 wsFrame.partialHeaderComplete=接收到WebSocket帧. fin [{0}], rsv [{1}], OpCode [{2}], payload 长度 [{3}] wsFrame.sessionClosed=无法处理客户端数据,因为会话已被关闭 +wsFrame.textMessageTooBig=解码的文本消息对于输出缓冲区太大,终结点不支持部分消息 wsFrame.wrongRsv=对于具有opCode [{1}]的消息,客户端帧将保留位设置为[{0}],此端点不支持 wsHandshakeRequest.invalidUri=字符串 [{0}] 不能用来组成一个有效的URI @@ -51,8 +64,12 @@ wsSession.doClose=关闭 WebSocket session [{1}] wsSession.duplicateHandlerText=已配置文本消息处理器 wsSession.instanceNew=endpoint 实例注册失败 +wsSession.unknownHandler=无法添加消息处理程序[{0}],因为它是针对无法识别的类型[{1}] wsWebSocketContainer.asynchronousSocketChannelFail=无法打开与服务器的连接 +wsWebSocketContainer.failedAuthentication=无法处理http响应代码[{0}]。服务器不接受身份验证头。 +wsWebSocketContainer.invalidExtensionParameters=服务器用客户端无法支持的扩展参数响应 +wsWebSocketContainer.missingAnnotation=无法使用POJO类[{0}],因为它未添加注解@ClientEndpoint wsWebSocketContainer.missingLocationHeader=处理HTTP响应码 [{0}] 失败。响应头缺少Location wsWebSocketContainer.missingWWWAuthenticateHeader=无法处理HTTP响应代码[{0}]。 缺少WWW-Authenticate标头作为响应 wsWebSocketContainer.pathNoHost=URI中未指定主机 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/PerMessageDeflate.java tomcat9-9.0.31/java/org/apache/tomcat/websocket/PerMessageDeflate.java --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/PerMessageDeflate.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/PerMessageDeflate.java 2020-02-05 19:26:48.000000000 +0000 @@ -204,6 +204,8 @@ dest.array(), dest.arrayOffset() + dest.position(), dest.remaining()); } catch (DataFormatException e) { throw new IOException(sm.getString("perMessageDeflate.deflateFailed"), e); + } catch (NullPointerException e) { + throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e); } dest.position(dest.position() + written); @@ -229,7 +231,11 @@ } else if (written == 0) { if (fin && (isServer && !clientContextTakeover || !isServer && !serverContextTakeover)) { - inflater.reset(); + try { + inflater.reset(); + } catch (NullPointerException e) { + throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e); + } } return TransformationResult.END_OF_FRAME; } @@ -314,7 +320,7 @@ @Override - public List sendMessagePart(List uncompressedParts) { + public List sendMessagePart(List uncompressedParts) throws IOException { List allCompressedParts = new ArrayList<>(); for (MessagePart uncompressedPart : uncompressedParts) { @@ -345,10 +351,14 @@ while (deflateRequired) { ByteBuffer compressedPayload = writeBuffer; - int written = deflater.deflate(compressedPayload.array(), - compressedPayload.arrayOffset() + compressedPayload.position(), - compressedPayload.remaining(), flush); - compressedPayload.position(compressedPayload.position() + written); + try { + int written = deflater.deflate(compressedPayload.array(), + compressedPayload.arrayOffset() + compressedPayload.position(), + compressedPayload.remaining(), flush); + compressedPayload.position(compressedPayload.position() + written); + } catch (NullPointerException e) { + throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e); + } if (!uncompressedPart.isFin() && compressedPayload.hasRemaining() && deflater.needsInput()) { // This message part has been fully processed by the @@ -401,7 +411,12 @@ // - in middle of EOM bytes // - about to write EOM bytes // - more data to write - int eomBufferWritten = deflater.deflate(EOM_BUFFER, 0, EOM_BUFFER.length, Deflater.SYNC_FLUSH); + int eomBufferWritten; + try { + eomBufferWritten = deflater.deflate(EOM_BUFFER, 0, EOM_BUFFER.length, Deflater.SYNC_FLUSH); + } catch (NullPointerException e) { + throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e); + } if (eomBufferWritten < EOM_BUFFER.length) { // EOM has just been completed compressedPayload.limit(compressedPayload.limit() - EOM_BYTES.length + eomBufferWritten); @@ -447,11 +462,15 @@ } - private void startNewMessage() { + private void startNewMessage() throws IOException { firstCompressedFrameWritten = false; emptyMessage = true; if (isServer && !serverContextTakeover || !isServer && !clientContextTakeover) { - deflater.reset(); + try { + deflater.reset(); + } catch (NullPointerException e) { + throw new IOException(sm.getString("perMessageDeflate.alreadyClosed"), e); + } } } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/pojo/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/websocket/pojo/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/pojo/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/pojo/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +pojoEndpointBase.onCloseFail=):无法为类型为[{0}]的POJO调用POJO端点的onClose方法 +pojoEndpointBase.onError=在错误发生后,没有为[{0}]配置错误处理 pojoEndpointBase.onOpenFail=无法为类型为[{0}]的POJO调用POJO端点的onOpen方法 pojoEndpointServer.getPojoInstanceFail=创建类型为 [{0}] 的 POJO 实例失败 @@ -23,6 +25,11 @@ pojoMethodMapping.duplicateLastParam=用OnMessage注释的类[{1}]的方法[{0}]上存在多个布尔参数(最后的) pojoMethodMapping.duplicatePongMessageParam=使用OnMessage注释的类[{1}]的方法[{0}]上存在多个PongMessage参数 pojoMethodMapping.invalidDecoder=这个特定类型的解码器[{0}]无法被实例化 +pojoMethodMapping.methodNotPublic=注解方法[{0}]不为公共方法 pojoMethodMapping.noDecoder=在用onMessage注释的类[{1}]的方法[{0}]上找不到消息参数的解码器。 +pojoMethodMapping.onErrorNoThrowable=):类[{1}]的方法[{0}]上不存在用OnError注释的抛出参数 +pojoMethodMapping.paramWithoutAnnotation=在类[{2}]的方法[{1}]上找到了类型为[{0}]的参数,该方法没有@PathParam 注释 +pojoMethodMapping.partialObject=用onMessage注释的类[{1}]的方法[{0}]中存在无效的对象和布尔参数 +pojoMethodMapping.pongWithPayload=类[{1}]的方法[{0}]中存在无效的PongMessage 和消息参数,该方法是用onMessage注释的 pojoPathParam.wrongType=不允许将类型[{0}]作为路径参数。用@PathParam注释的参数只能是字符串、Java原语或盒装版本。 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/server/LocalStrings_zh_CN.properties tomcat9-9.0.31/java/org/apache/tomcat/websocket/server/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/server/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/server/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -14,6 +14,7 @@ # limitations under the License. serverContainer.configuratorFail=无法为[{1}]类型的POJO创建类型[{0}]的配置程序 +serverContainer.duplicatePaths=多个端点可能不能发不到同一个路径[{0}]:已经存在的端点[{1}]和新的端点[{2}] serverContainer.encoderFail=无法创建[{0}]类型的编码器 serverContainer.failedDeployment=由于以前的部署失败,不允许将WebSocket终结点部署到主机[{1}]中路径为[{0}]的Web应用程序 serverContainer.servletContextMissing=没有指定ServletContext @@ -24,3 +25,7 @@ wsFrameServer.bytesRead=将[{0}]个字节读入输入缓冲区,准备进行处理 wsFrameServer.onDataAvailable=进入方法 + +wsHttpUpgradeHandler.noPreInit=在容器调用init()之前,必须调用preinit()方法来配置WebSocket HttpUpgradeHandler。通常,这意味着创建WsHttpUpgradeHandler 实例的servlet也应该调用preinit() + +wsRemoteEndpointServer.closeFailed=无法完全关闭ServletOutputStream 连接 diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java tomcat9-9.0.31/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 2020-02-05 19:26:48.000000000 +0000 @@ -51,7 +51,6 @@ private volatile ByteBuffer[] buffers = null; private volatile long timeoutExpiry = -1; - private volatile boolean close; public WsRemoteEndpointImplServer(SocketWrapperBase socketWrapper, WsServerContainer serverContainer) { @@ -103,9 +102,6 @@ } else { wsWriteTimeout.unregister(WsRemoteEndpointImplServer.this); clearHandler(null, true); - if (close) { - close(); - } } } @Override @@ -185,9 +181,6 @@ if (complete) { wsWriteTimeout.unregister(this); clearHandler(null, useDispatch); - if (close) { - close(); - } } break; } diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/Transformation.java tomcat9-9.0.31/java/org/apache/tomcat/websocket/Transformation.java --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/Transformation.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/Transformation.java 2020-02-05 19:26:48.000000000 +0000 @@ -101,8 +101,11 @@ * @return The list of messages after this any any subsequent * transformations have been applied. The size of the returned list * may be bigger or smaller than the size of the input list + * + * @throws IOException If an error occurs during the transformation of the + * message parts */ - List sendMessagePart(List messageParts); + List sendMessagePart(List messageParts) throws IOException; /** * Clean-up any resources that were used by the transformation. diff -Nru tomcat9-9.0.27/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java tomcat9-9.0.31/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java --- tomcat9-9.0.27/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java 2020-02-05 19:26:48.000000000 +0000 @@ -338,7 +338,12 @@ intermediateMessageHandler, new EndMessageHandler(this, handler), -1)); - messageParts = transformation.sendMessagePart(messageParts); + try { + messageParts = transformation.sendMessagePart(messageParts); + } catch (IOException ioe) { + handler.onResult(new SendResult(ioe)); + return; + } // Some extensions/transformations may buffer messages so it is possible // that no message parts will be returned. If this is the case the @@ -836,7 +841,7 @@ private final ByteBuffer outputBuffer; private final boolean flushRequired; private final WsRemoteEndpointImplBase endpoint; - private int maskIndex = 0; + private volatile int maskIndex = 0; public OutputBufferSendHandler(SendHandler completion, long blockingWriteTimeoutExpiry, @@ -1253,7 +1258,7 @@ private static class BlockingSendHandler implements SendHandler { - private SendResult sendResult = null; + private volatile SendResult sendResult = null; @Override public void onResult(SendResult result) { diff -Nru tomcat9-9.0.27/MERGE.txt tomcat9-9.0.31/MERGE.txt --- tomcat9-9.0.27/MERGE.txt 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/MERGE.txt 2020-02-05 19:26:48.000000000 +0000 @@ -36,14 +36,14 @@ Sub-tree: src/main/java/org/apache/bcel The SHA1 ID for the most recent commit to be merged to Tomcat is: -4b760bb53b57b704006a0a33f7ec187b7e7f5ebc (2019-08-01) +ff6941e4491c68f6eaf270ff03c1bc1e554c7b42 (2019-12-06) Codec ----- Sub-tree: src/main/java/org/apache/commons/codec The SHA1 ID for the most recent commit to be merged to Tomcat is: -3ebef4ad92e31697fb52ca7cc71904c68654c2c8 (2019-08-01) +9637dd44fa0e2d5a6ddb45791e3cd78298842d95 (2019-12-06) Note: Only classes required for Base64 encoding/decoding. The rest are removed. FileUpload @@ -51,22 +51,22 @@ Sub-tree: src/main/java/org/apache/commons/fileupload2 The SHA1 ID for the most recent commit to be merged to Tomcat is: -9958ea2426ec5682a7c929a13372c04426ee3818 (2019-08-01) +2317552993fd5180a84083d599b8cbdb05a07bab (2019-12-06) Note: Tomcat's copy of fileupload also includes classes copied manually from Commons IO. DBCP ---- -DBCP2 +Pool2 Sub-tree -src/main/java/org/apache/commons/dbcp2 -src/main/resources/org/apache/commons/dbcp2 +src/main/java/org/apache/commons/pool2 The SHA1 ID for the most recent commit to be merged to Tomcat is: -4813b7f5456c1f4fecc4f701ac731a71f57db249 (2019-08-09) +6092f924b36061353ff92b18c88400ab3bc05327 (2019-12-06) -Pool2 +DBCP2 Sub-tree -src/main/java/org/apache/commons/pool2 +src/main/java/org/apache/commons/dbcp2 +src/main/resources/org/apache/commons/dbcp2 The SHA1 ID for the most recent commit to be merged to Tomcat is: -796e32d53cc0d870ba0db3a7faf4c5b24ff76f3f (2019-08-01) +a363906bf7a039f79c07fa3c68b082a69ae035d7 (2019-12-06) diff -Nru tomcat9-9.0.27/modules/cxf/pom.xml tomcat9-9.0.31/modules/cxf/pom.xml --- tomcat9-9.0.27/modules/cxf/pom.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/modules/cxf/pom.xml 2020-02-05 19:26:48.000000000 +0000 @@ -29,7 +29,7 @@ Apache CXF for Apache Tomcat CDI Apache CXF packaged for Apache Tomcat CDI - 3.3.3 + 3.3.4 jar diff -Nru tomcat9-9.0.27/modules/jdbc-pool/NOTICE tomcat9-9.0.31/modules/jdbc-pool/NOTICE --- tomcat9-9.0.27/modules/jdbc-pool/NOTICE 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/modules/jdbc-pool/NOTICE 2020-02-05 19:26:48.000000000 +0000 @@ -1,5 +1,5 @@ Apache Tomcat JDBC Pool -Copyright 2008-2019 The Apache Software Foundation +Copyright 2008-2020 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). diff -Nru tomcat9-9.0.27/modules/owb/pom.xml tomcat9-9.0.31/modules/owb/pom.xml --- tomcat9-9.0.27/modules/owb/pom.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/modules/owb/pom.xml 2020-02-05 19:26:48.000000000 +0000 @@ -29,14 +29,14 @@ Apache Tomcat CDI 2 support Apache Tomcat CDI 2 support using Apache OpenWebBeans - 2.0.12 + 2.0.13 jar 1.0 1.0 1.0.1 - 9.0.26 + 9.0.30 @@ -95,7 +95,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.0.0 + 3.2.1 @@ -106,7 +106,8 @@ false - + + ${mainClass} @@ -114,6 +115,14 @@ + + + + org.apache.openwebbeans + openwebbeans-maven + ${project.version} + + diff -Nru tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansContextLifecycleListener.java tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansContextLifecycleListener.java --- tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansContextLifecycleListener.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansContextLifecycleListener.java 2020-02-05 19:26:48.000000000 +0000 @@ -36,6 +36,26 @@ public class OpenWebBeansContextLifecycleListener implements LifecycleListener { /** + * Add security valve. + */ + protected boolean addSecurityValve = true; + + /** + * @return true to add the security valve + */ + public boolean getAddSecurityValve() { + return addSecurityValve; + } + + /** + * Configure if a security valve will be added + * @param addSecurityValve the addSecurityValve to set + */ + public void setAddSecurityValve(boolean addSecurityValve) { + this.addSecurityValve = addSecurityValve; + } + + /** * Start without a beans.xml file. */ protected boolean startWithoutBeansXml = true; @@ -88,15 +108,17 @@ ((Lifecycle) pipeline).addLifecycleListener(this); } } - // Add security valve - boolean securityValveFound = false; - for (Valve valve : pipeline.getValves()) { - if (valve instanceof OpenWebBeansSecurityValve) { - securityValveFound = true; + if (getAddSecurityValve()) { + // Add security valve + boolean securityValveFound = false; + for (Valve valve : pipeline.getValves()) { + if (valve instanceof OpenWebBeansSecurityValve) { + securityValveFound = true; + } + } + if (!securityValveFound) { + pipeline.addValve(new OpenWebBeansSecurityValve()); } - } - if (!securityValveFound) { - pipeline.addValve(new OpenWebBeansSecurityValve()); } } } diff -Nru tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansListener.java tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansListener.java --- tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansListener.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansListener.java 2020-02-05 19:26:48.000000000 +0000 @@ -35,11 +35,32 @@ @Override protected LifecycleListener createLifecycleListener(Context context) { OpenWebBeansContextLifecycleListener listener = new OpenWebBeansContextLifecycleListener(); + listener.setAddSecurityValve(getAddSecurityValve()); listener.setStartWithoutBeansXml(getStartWithoutBeansXml()); return listener; } /** + * Add security valve. + */ + protected boolean addSecurityValve = true; + + /** + * @return true to add the security valve + */ + public boolean getAddSecurityValve() { + return addSecurityValve; + } + + /** + * Configure if a security valve will be added + * @param addSecurityValve the addSecurityValve to set + */ + public void setAddSecurityValve(boolean addSecurityValve) { + this.addSecurityValve = addSecurityValve; + } + + /** * Start without a beans.xml file. */ protected boolean startWithoutBeansXml = true; diff -Nru tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansSecurityFilter.java tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansSecurityFilter.java --- tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansSecurityFilter.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/OpenWebBeansSecurityFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,67 @@ +/* + * 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.webbeans.web.tomcat; + +import java.io.IOException; +import java.security.Principal; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + + +/** + * Filter which sets the UserPrincipal into a ThreadLocal + * to make it injectable via a CDI Producer. This is an alternative + * to the valve to allow configuration at the webapp level as well. + * The filter name should usually be OwbSecurityFilter, mapped on + * REQUEST with *. + */ +public class OpenWebBeansSecurityFilter implements Filter { + + private static ThreadLocal principal = new ThreadLocal<>(); + + public static Principal getPrincipal() { + return principal.get(); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + try { + if (request instanceof HttpServletRequest) { + Principal p = ((HttpServletRequest) request).getUserPrincipal(); + if (p != null) { + principal.set(p); + } + } + + // continue with the request + chain.doFilter(request, response); + } finally { + if (principal.get() != null) { + principal.remove(); + } + } + } + +} diff -Nru tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/TomcatPlugin.java tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/TomcatPlugin.java --- tomcat9-9.0.27/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/TomcatPlugin.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/modules/owb/src/main/java/org/apache/webbeans/web/tomcat/TomcatPlugin.java 2020-02-05 19:26:48.000000000 +0000 @@ -47,7 +47,8 @@ /** * Security service implementation. */ - private final TomcatSecurityService securityService = new TomcatSecurityService(WebBeansContext.getInstance()); + private final TomcatSecurityService securityService = + (TomcatSecurityService) WebBeansContext.getInstance().getSecurityService(); @Override public T getSupportedService(Class serviceClass) { diff -Nru tomcat9-9.0.27/modules/owb/src/main/resources/META-INF/openwebbeans/openwebbeans.properties tomcat9-9.0.31/modules/owb/src/main/resources/META-INF/openwebbeans/openwebbeans.properties --- tomcat9-9.0.27/modules/owb/src/main/resources/META-INF/openwebbeans/openwebbeans.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/modules/owb/src/main/resources/META-INF/openwebbeans/openwebbeans.properties 2020-02-05 19:26:48.000000000 +0000 @@ -15,146 +15,6 @@ #specific language governing permissions and limitations #under the License. -org.apache.webbeans.spi.SecurityService=org.apache.webbeans.web.tomcat.TomcatSecurityService - -# Remove if using shade 3.2.2 and OpenWebBeansPropertiesTransformer -org.apache.webbeans.spi.adaptor.ELAdaptor=org.apache.webbeans.el22.EL22Adaptor -org.apache.webbeans.spi.ContainerLifecycle=org.apache.webbeans.web.lifecycle.WebContainerLifecycle -org.apache.webbeans.spi.ScannerService=org.apache.webbeans.web.scanner.WebScannerService -org.apache.webbeans.spi.ContextsService=org.apache.webbeans.web.context.WebContextsService -org.apache.webbeans.application.jsp=true -org.apache.webbeans.spi.ConversationService=org.apache.webbeans.web.context.WebConversationService -org.apache.webbeans.application.supportsConversation=true -org.apache.webbeans.spi.JNDIService=org.apache.webbeans.corespi.se.DefaultJndiService -org.apache.webbeans.spi.BeanArchiveService=org.apache.webbeans.xml.DefaultBeanArchiveService -org.apache.webbeans.spi.ApplicationBoundaryService=org.apache.webbeans.corespi.se.DefaultApplicationBoundaryService -org.apache.webbeans.spi.LoaderService=org.apache.webbeans.service.DefaultLoaderService -org.apache.webbeans.spi.deployer.useEjbMetaDataDiscoveryService=false -org.apache.webbeans.spi.InjectionPointService=org.apache.webbeans.service.DefaultInjectionPointService -org.apache.webbeans.service.DefaultInjectionPointService.implicitSupport = true -org.apache.webbeans.useBDABeansXMLScanner=false -org.apache.webbeans.proxy.mapping.javax.enterprise.context.ApplicationScoped=org.apache.webbeans.intercept.ApplicationScopedBeanInterceptorHandler -org.apache.webbeans.proxy.mapping.javax.enterprise.context.RequestScoped=org.apache.webbeans.intercept.RequestScopedBeanInterceptorHandler -org.apache.webbeans.proxy.mapping.javax.enterprise.context.SessionScoped=org.apache.webbeans.intercept.SessionScopedBeanInterceptorHandler -org.apache.webbeans.web.eagerSessionInitialisation=false - -######################### Bean Scanning ######################################################## -# A list of known JARs/paths which should not be scanned for beans -# if they don't have an explicit META-INF/beans.xml -org.apache.webbeans.scanExclusionPaths=/jre/lib, \ - /Contents/Home/, \ - /dt.jar, \ - /tools.jar, \ - /bootstrap.jar, \ - /asm, \ - /javassist, \ - /xbean-, \ - /jconsole.jar, \ - /geronimo-connector,\ - /geronimo-j2ee-,\ - /geronimo-jpa_,\ - /geronimo-javamail,\ - /geronimo-transaction,\ - /commons-, \ - /arquillian-, \ - /bsh-, \ - /shrinkwrap-, \ - /junit-, \ - /testng-, \ - /openjpa-, \ - /bcel, \ - /hamcrest, \ - /mysql-connector, \ - /testng, \ - /idea_rt, \ - /eclipse, \ - /jcommander, \ - /tomcat, \ - /catalina, \ - /jasper, \ - /jsp-api, \ - /myfaces-api, \ - /myfaces-impl, \ - /servlet-api, \ - /javax, \ - /annotation-api, \ - /el-api, \ - /mojarra, \ - /sisu-guice-, \ - /sisu-inject-, \ - /aether-, \ - /plexus-, \ - /maven-, \ - /guava-, \ - /openwebbeans-, \ - /bcprov-jdk14-, \ - /bcmail-jdk14-, \ - /bctsp-jdk14-, \ - /bcmail-jdk14-, \ - /ss_css2-, \ - /itext-, \ - /pd4ml-, \ - /xmlpull-, \ - /log4j-, \ - /slf4j-, \ - /logkit, \ - /gson-, \ - /xstream-, \ - /httpclient-, \ - /httpcore-, \ - /backport-util-concurrent-, \ - /xml-apis, \ - /xpp3_min-, \ - /bval-core, \ - /bval-jsr, \ - /hsqldb, \ - /quartz-2, \ - /jetty-, \ - /plexus-, \ - /surefire-, \ - /byte-buddy-, \ - /cglib-, \ - /okhttp-, \ - /htmlunit-, \ - /wagon-http-, \ - /wagon-provider-, \ - /wagon-file-, \ - /phantomjsdriver, \ - /error_prone_annotations-, \ - /j2objc-, \ - /xalan-, \ - /aopalliance-, \ - /owasp-, \ - /jdom2-, \ - /jfreechart-, \ - /stax-api-, \ - /jboss-logging-, \ - /barcode4j-, \ - /poi-, \ - /selenium-, \ - /graphene-, \ - /httpmime-, \ - /cssparser-, \ - /animal-sniffer-annotations-, \ - /objenesis-, \ - /xercesImpl-, \ - /neko-htmlunit-, \ - /checker-compat-qual-, \ - /awaitility-, \ - /okio-, \ - /jsr305-, \ - /guice-, \ - /jsoup- -################################################################################################ - - -######################### Bean Scanning ######################################################## -# A list of known classes which might contain final methods but should be proxyable nonetheless -# Some of those classes are from the JDK and have been proxyable in older versions. -# This setting can be overridden as jvm param via -Djavax.enterprise.inject.allowProxying.classes=... -# or an environment key with the name JAVAX_ENTERPRISE_INJECT_ALLOWPROXYING_CLASSES=... -javax.enterprise.inject.allowProxying.classes=\ - java.util.HashMap, \ - java.util.Calendar -################################################################################################ +configuration.ordinal=20 +org.apache.webbeans.spi.SecurityService=org.apache.webbeans.web.tomcat.TomcatSecurityService diff -Nru tomcat9-9.0.27/NOTICE tomcat9-9.0.31/NOTICE --- tomcat9-9.0.27/NOTICE 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/NOTICE 2020-02-05 19:26:48.000000000 +0000 @@ -1,5 +1,5 @@ Apache Tomcat -Copyright 1999-2019 The Apache Software Foundation +Copyright 1999-2020 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (https://www.apache.org/). diff -Nru tomcat9-9.0.27/RELEASE-NOTES tomcat9-9.0.31/RELEASE-NOTES --- tomcat9-9.0.27/RELEASE-NOTES 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/RELEASE-NOTES 2020-02-05 19:26:48.000000000 +0000 @@ -70,6 +70,7 @@ * catalina.jar (Tomcat Catalina implementation) * catalina-ant.jar (Tomcat Catalina Ant tasks) * catalina-ha.jar (High availability package) +* catalina-ssi.jar (Server-side Includes module) * catalina-storeconfig.jar (Generation of XML configuration from current state) * catalina-tribes.jar (Group communication) * ecj-@JDT_VERSION@.jar (Eclipse JDT Java compiler) diff -Nru tomcat9-9.0.27/res/bnd/catalina-ssi.jar.tmp.bnd tomcat9-9.0.31/res/bnd/catalina-ssi.jar.tmp.bnd --- tomcat9-9.0.27/res/bnd/catalina-ssi.jar.tmp.bnd 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/res/bnd/catalina-ssi.jar.tmp.bnd 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,19 @@ +# 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. +Bundle-Name: tomcat-ssi +Bundle-SymbolicName: org.apache.tomcat-ssi +Bundle-Version: @VERSION@ +Export-Package: \ + org.apache.catalina.ssi diff -Nru tomcat9-9.0.27/res/findbugs/filter-false-positives.xml tomcat9-9.0.31/res/findbugs/filter-false-positives.xml --- tomcat9-9.0.27/res/findbugs/filter-false-positives.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/findbugs/filter-false-positives.xml 2020-02-05 19:26:48.000000000 +0000 @@ -344,6 +344,12 @@ + + + + + + @@ -836,6 +842,18 @@ + + + + + + + + + + + + @@ -1389,6 +1407,13 @@ + + + + + + + @@ -1440,6 +1465,21 @@ + + + + + + + + + + + + + + + @@ -1552,9 +1592,17 @@ + + + + + - + + + + @@ -1606,11 +1654,23 @@ + + + + + + + + + + + + @@ -1635,23 +1695,6 @@ - - - - - - - - - - - - - - - - - @@ -1670,6 +1713,23 @@ + + + + + + + + + + + + + + + + + @@ -1688,6 +1748,12 @@ + + + + + + @@ -1777,18 +1843,18 @@ - - - - - - + + + + + + diff -Nru tomcat9-9.0.27/res/ide-support/eclipse/java-compiler-errors-warnings.txt tomcat9-9.0.31/res/ide-support/eclipse/java-compiler-errors-warnings.txt --- tomcat9-9.0.27/res/ide-support/eclipse/java-compiler-errors-warnings.txt 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/ide-support/eclipse/java-compiler-errors-warnings.txt 2020-02-05 19:26:48.000000000 +0000 @@ -18,7 +18,7 @@ # Java -> Compiler -> Errors/Warnings ======================================= -The following settings are for Oxygen (Eclipse 4.7) +The following settings are for Oxygen (Eclipse 4.13) W = Warning I = Ignore E = Error @@ -55,9 +55,13 @@ Deprecated and restricted API - Deprecated API - W ([ ] on all additional check boxes) + - Deprecated API marked for removal - W - Forbidden references - E - Discouraged reference - W +Modules + - All - W + Unnecessary code - All - W ([x] on all additional check boxes) diff -Nru tomcat9-9.0.27/res/maven/mvn.properties.default tomcat9-9.0.31/res/maven/mvn.properties.default --- tomcat9-9.0.27/res/maven/mvn.properties.default 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/maven/mvn.properties.default 2020-02-05 19:26:48.000000000 +0000 @@ -39,7 +39,7 @@ maven.asf.release.repo.repositoryId=apache.releases.https # Release version info -maven.asf.release.deploy.version=9.0.27 +maven.asf.release.deploy.version=9.0.31 #Where do we load the libraries from tomcat.lib.path=../../output/build/lib diff -Nru tomcat9-9.0.27/res/maven/mvn-pub.xml tomcat9-9.0.31/res/maven/mvn-pub.xml --- tomcat9-9.0.27/res/maven/mvn-pub.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/maven/mvn-pub.xml 2020-02-05 19:26:48.000000000 +0000 @@ -291,6 +291,10 @@ jarFileName="catalina-tribes.jar" srcJarFileName="catalina-tribes-src.jar"/> + + @@ -385,6 +389,10 @@ jarFileName="catalina-tribes.jar" srcJarFileName="catalina-tribes-src.jar"/> + + diff -Nru tomcat9-9.0.27/res/maven/tomcat-ssi.pom tomcat9-9.0.31/res/maven/tomcat-ssi.pom --- tomcat9-9.0.27/res/maven/tomcat-ssi.pom 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/res/maven/tomcat-ssi.pom 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,43 @@ + + + + 4.0.0 + org.apache.tomcat + tomcat-ssi + @MAVEN.DEPLOY.VERSION@ + Server-side Includes module + https://tomcat.apache.org/ + + + Apache License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + org.apache.tomcat + tomcat-juli + @MAVEN.DEPLOY.VERSION@ + compile + + + diff -Nru tomcat9-9.0.27/res/META-INF/jasper-el.jar/services/javax.el.ExpressionFactory tomcat9-9.0.31/res/META-INF/jasper-el.jar/services/javax.el.ExpressionFactory --- tomcat9-9.0.27/res/META-INF/jasper-el.jar/services/javax.el.ExpressionFactory 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/res/META-INF/jasper-el.jar/services/javax.el.ExpressionFactory 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,16 @@ +# 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. + +org.apache.el.ExpressionFactoryImpl \ No newline at end of file diff -Nru tomcat9-9.0.27/res/META-INF/jasper-el.jar/web-fragment.xml tomcat9-9.0.31/res/META-INF/jasper-el.jar/web-fragment.xml --- tomcat9-9.0.27/res/META-INF/jasper-el.jar/web-fragment.xml 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/res/META-INF/jasper-el.jar/web-fragment.xml 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,26 @@ + + + + org_apache_jasper_el + + \ No newline at end of file diff -Nru tomcat9-9.0.27/res/rat/rat-excludes.txt tomcat9-9.0.31/res/rat/rat-excludes.txt --- tomcat9-9.0.27/res/rat/rat-excludes.txt 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/rat/rat-excludes.txt 2020-02-05 19:26:48.000000000 +0000 @@ -55,6 +55,8 @@ - Temporary cache files used by Checkstle + - Configuration files for third party Continuous Integration systems like Travis CI + output/build/logs/* output/test-tmp/** @@ -184,3 +186,4 @@ **/*.md output/res/checkstyle/* + diff -Nru tomcat9-9.0.27/res/tomcat-maven/Dockerfile tomcat9-9.0.31/res/tomcat-maven/Dockerfile --- tomcat9-9.0.27/res/tomcat-maven/Dockerfile 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/tomcat-maven/Dockerfile 2020-02-05 19:26:48.000000000 +0000 @@ -23,6 +23,7 @@ ADD target/tomcat-maven-1.0.jar /deployments/app.jar ADD conf /deployments/conf ADD webapps /deployments/webapps +RUN chmod 777 /deployments/webapps WORKDIR /deployments diff -Nru tomcat9-9.0.27/res/tomcat-maven/pom.xml tomcat9-9.0.31/res/tomcat-maven/pom.xml --- tomcat9-9.0.27/res/tomcat-maven/pom.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/tomcat-maven/pom.xml 2020-02-05 19:26:48.000000000 +0000 @@ -29,7 +29,7 @@ UTF-8 org.apache.catalina.startup.Tomcat - 9.0.26 + 9.0.30 diff -Nru tomcat9-9.0.27/res/tomcat.nsi tomcat9-9.0.31/res/tomcat.nsi --- tomcat9-9.0.27/res/tomcat.nsi 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/res/tomcat.nsi 2020-02-05 19:26:48.000000000 +0000 @@ -53,7 +53,6 @@ Var ResetInstDir Var TomcatPortShutdown Var TomcatPortHttp -Var TomcatPortAjp Var TomcatMenuEntriesEnable Var TomcatShortcutAllUsers Var TomcatServiceName @@ -70,7 +69,6 @@ Var CtlJavaHome Var CtlTomcatPortShutdown Var CtlTomcatPortHttp -Var CtlTomcatPortAjp Var CtlTomcatServiceName Var CtlTomcatShortcutAllUsers Var CtlTomcatAdminUsername @@ -135,7 +133,6 @@ LangString TEXT_JVM_LABEL1 ${LANG_ENGLISH} "Please select the path of a Java @MIN_JAVA_VERSION@ or later JRE installed on your system." LangString TEXT_CONF_LABEL_PORT_SHUTDOWN ${LANG_ENGLISH} "Server Shutdown Port" LangString TEXT_CONF_LABEL_PORT_HTTP ${LANG_ENGLISH} "HTTP/1.1 Connector Port" - LangString TEXT_CONF_LABEL_PORT_AJP ${LANG_ENGLISH} "AJP/1.3 Connector Port" LangString TEXT_CONF_LABEL_SERVICE_NAME ${LANG_ENGLISH} "Windows Service Name" LangString TEXT_CONF_LABEL_SHORTCUT_ALL_USERS ${LANG_ENGLISH} "Create shortcuts for all users" LangString TEXT_CONF_LABEL_ADMIN ${LANG_ENGLISH} "Tomcat Administrator Login (optional)" @@ -459,7 +456,6 @@ StrCpy $JavaHome "" StrCpy $TomcatPortShutdown "-1" StrCpy $TomcatPortHttp "8080" - StrCpy $TomcatPortAjp "8009" StrCpy $TomcatMenuEntriesEnable "0" StrCpy $TomcatShortcutAllUsers "0" StrCpy $TomcatServiceDefaultName "Tomcat@VERSION_MAJOR@" @@ -477,7 +473,6 @@ ${ReadFromConfigIni} $JavaHome "JavaHome" $R2 ${ReadFromConfigIni} $TomcatPortShutdown "TomcatPortShutdown" $R2 ${ReadFromConfigIni} $TomcatPortHttp "TomcatPortHttp" $R2 - ${ReadFromConfigIni} $TomcatPortAjp "TomcatPortAjp" $R2 ${ReadFromConfigIni} $TomcatMenuEntriesEnable "TomcatMenuEntriesEnable" $R2 ${ReadFromConfigIni} $TomcatShortcutAllUsers "TomcatShortcutAllUsers" $R2 ${ReadFromConfigIni} $TomcatServiceDefaultName "TomcatServiceDefaultName" $R2 @@ -603,13 +598,6 @@ Pop $CtlTomcatPortHttp ${NSD_SetTextLimit} $CtlTomcatPortHttp 5 - ${NSD_CreateLabel} 0 36u 100u 14u "$(TEXT_CONF_LABEL_PORT_AJP)" - Pop $R0 - - ${NSD_CreateText} 150u 34u 50u 12u "$TomcatPortAjp" - Pop $CtlTomcatPortAjp - ${NSD_SetTextLimit} $CtlTomcatPortAjp 5 - ${NSD_CreateLabel} 0 57u 140u 14u "$(TEXT_CONF_LABEL_SERVICE_NAME)" Pop $R0 @@ -647,7 +635,6 @@ Function pageConfigurationLeave ${NSD_GetText} $CtlTomcatPortShutdown $TomcatPortShutdown ${NSD_GetText} $CtlTomcatPortHttp $TomcatPortHttp - ${NSD_GetText} $CtlTomcatPortAjp $TomcatPortAjp ${NSD_GetText} $CtlTomcatServiceName $TomcatServiceName ${If} $TomcatMenuEntriesEnable == "1" ${NSD_GetState} $CtlTomcatShortcutAllUsers $TomcatShortcutAllUsers @@ -670,12 +657,6 @@ Goto exit ${EndIf} - ${If} $TomcatPortAjp == "" - MessageBox MB_ICONEXCLAMATION|MB_OK 'The AJP port may not be empty' - Abort "Config not right" - Goto exit - ${EndIf} - ${If} $TomcatServiceName == "" MessageBox MB_ICONEXCLAMATION|MB_OK 'The Service Name may not be empty' Abort "Config not right" @@ -964,6 +945,27 @@ ${EndIf} ${EndIf} + ; If no 32-bit Java (JRE) found, look for 64-bit Java JDK + ${If} $1 == "" + ${AndIf} $0 != "%PROGRAMW6432%" + ReadRegStr $2 HKLM "SOFTWARE\JavaSoft\JDK" "CurrentVersion" + ReadRegStr $1 HKLM "SOFTWARE\JavaSoft\JDK\$2" "JavaHome" + ; "RuntimeLib" is not available here + + IfErrors 0 +2 + StrCpy $1 "" + ClearErrors + ${EndIf} + + ; If nothing found, try environment variable JAVA_HOME + ${If} $1 == "" + ExpandEnvStrings $1 "%JAVA_HOME%" + ${If} $1 == "%JAVA_HOME%" + StrCpy $1 "" + ${EndIf} + ClearErrors + ${EndIf} + ; Put the result in the stack Push $1 @@ -1007,12 +1009,13 @@ IfFileExists "$2" FoundJvmDll ClearErrors - ;Step three: Read defaults from registry + ;Step three: Read defaults from registry ReadRegStr $1 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment" "CurrentVersion" ReadRegStr $2 HKLM "SOFTWARE\JavaSoft\Java Runtime Environment\$1" "RuntimeLib" - IfErrors 0 FoundJvmDll + + ;not found StrCpy $2 "" FoundJvmDll: @@ -1042,7 +1045,6 @@ IfErrors SERVER_XML_LEAVELOOP ${StrRep} $R4 $R3 "8005" "$TomcatPortShutdown" ${StrRep} $R3 $R4 "8080" "$TomcatPortHttp" - ${StrRep} $R4 $R3 "8009" "$TomcatPortAjp" FileWrite $R2 $R4 Goto SERVER_XML_LOOP SERVER_XML_LEAVELOOP: @@ -1060,7 +1062,6 @@ DetailPrint 'Server shutdown listener configured on port "$TomcatPortShutdown"' DetailPrint 'HTTP/1.1 Connector configured on port "$TomcatPortHttp"' - DetailPrint 'AJP/1.3 Connector configured on port "$TomcatPortAjp"' DetailPrint "server.xml written" StrCpy $R5 '' diff -Nru tomcat9-9.0.27/test/javax/el/TestImportHandlerStandardPackages.java tomcat9-9.0.31/test/javax/el/TestImportHandlerStandardPackages.java --- tomcat9-9.0.27/test/javax/el/TestImportHandlerStandardPackages.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/javax/el/TestImportHandlerStandardPackages.java 2020-02-05 19:26:48.000000000 +0000 @@ -61,10 +61,8 @@ // 9 (and later) so it is not necessary that this is executed on // every test run. The intention is that it will catch new classes // when the tests are run on a newer JRE. - // The latest versions of the JRE where this test is known to pass are - // - OpenJDK 11.0.1 - // - OpenJDK 12 EA 26 - // - OpenJDK 13 EA 02 + // The latest version of the JRE where this test is known to pass is + // - OpenJDK 14 EA 27 if (!JreCompat.isJre9Available()) { return; } diff -Nru tomcat9-9.0.27/test/javax/servlet/http/TestHttpServlet.java tomcat9-9.0.31/test/javax/servlet/http/TestHttpServlet.java --- tomcat9-9.0.27/test/javax/servlet/http/TestHttpServlet.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/javax/servlet/http/TestHttpServlet.java 2020-02-05 19:26:48.000000000 +0000 @@ -31,6 +31,7 @@ import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap; public class TestHttpServlet extends TomcatBaseTest { @@ -92,13 +93,13 @@ tomcat.start(); - Map> resHeaders= new HashMap<>(); + Map> resHeaders= new CaseInsensitiveKeyMap<>(); String path = "http://localhost:" + getPort() + "/outer"; ByteChunk out = new ByteChunk(); int rc = getUrl(path, out, resHeaders); Assert.assertEquals(HttpServletResponse.SC_OK, rc); - String length = resHeaders.get("Content-Length").get(0); + String length = getSingleHeader("Content-Length", resHeaders); Assert.assertEquals(Long.parseLong(length), out.getLength()); out.recycle(); @@ -124,7 +125,7 @@ tomcat.start(); - Map> getHeaders = new HashMap<>(); + Map> getHeaders = new CaseInsensitiveKeyMap<>(); String path = "http://localhost:" + getPort() + "/chunking"; ByteChunk out = new ByteChunk(); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/authenticator/TestAuthenticatorBaseCorsPreflight.java tomcat9-9.0.31/test/org/apache/catalina/authenticator/TestAuthenticatorBaseCorsPreflight.java --- tomcat9-9.0.27/test/org/apache/catalina/authenticator/TestAuthenticatorBaseCorsPreflight.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/authenticator/TestAuthenticatorBaseCorsPreflight.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,177 @@ +/* + * 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.authenticator; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Assert; +import org.junit.BeforeClass; +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.Realm; +import org.apache.catalina.authenticator.AuthenticatorBase.AllowCorsPreflight; +import org.apache.catalina.filters.AddDefaultCharsetFilter; +import org.apache.catalina.filters.CorsFilter; +import org.apache.catalina.realm.NullRealm; +import org.apache.catalina.servlets.DefaultServlet; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; +import org.apache.tomcat.util.descriptor.web.LoginConfig; +import org.apache.tomcat.util.descriptor.web.SecurityCollection; +import org.apache.tomcat.util.descriptor.web.SecurityConstraint; + +@RunWith(Parameterized.class) +public class TestAuthenticatorBaseCorsPreflight extends TomcatBaseTest { + + private static final String ALLOWED_ORIGIN = "http://example.com"; + private static final String EMPTY_ORIGIN = ""; + private static final String INVALID_ORIGIN = "http://%20"; + private static final String SAME_ORIGIN = "http://localhost"; + private static final String ALLOWED_METHOD = "GET"; + private static final String BLOCKED_METHOD = "POST"; + private static final String EMPTY_METHOD = ""; + + @Parameterized.Parameters(name = "{index}: input[{0}]") + public static Collection parameters() { + List parameterSets = new ArrayList<>(); + + parameterSets.add(new Object[] { AllowCorsPreflight.NEVER, "/*", "OPTIONS", null, null, Boolean.FALSE }); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", null, null, Boolean.FALSE }); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.TRUE }); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", EMPTY_ORIGIN, ALLOWED_METHOD, Boolean.FALSE}); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", INVALID_ORIGIN, ALLOWED_METHOD, Boolean.FALSE }); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", SAME_ORIGIN, ALLOWED_METHOD, Boolean.FALSE }); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "GET", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.FALSE }); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, BLOCKED_METHOD, Boolean.FALSE }); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, EMPTY_METHOD, Boolean.FALSE}); + parameterSets.add(new Object[] { AllowCorsPreflight.ALWAYS, "/*", "OPTIONS", ALLOWED_ORIGIN, null, Boolean.FALSE}); + parameterSets.add(new Object[] { AllowCorsPreflight.FILTER, "/*", "OPTIONS", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.TRUE }); + parameterSets.add(new Object[] { AllowCorsPreflight.FILTER, "/x", "OPTIONS", ALLOWED_ORIGIN, ALLOWED_METHOD, Boolean.FALSE }); + + return parameterSets; + } + + @Parameter(0) + public AllowCorsPreflight allowCorsPreflight; + @Parameter(1) + public String filterMapping; + @Parameter(2) + public String method; + @Parameter(3) + public String origin; + @Parameter(4) + public String accessControl; + @Parameter(5) + public boolean allow; + + + @BeforeClass + public static void init() { + // So the test can set the origin header + System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); + } + + + @Test + public void test() throws Exception { + Tomcat tomcat = getTomcatInstance(); + + File appDir = new File("test/webapp"); + Context ctx = tomcat.addContext("", appDir.getAbsolutePath()); + + Tomcat.addServlet(ctx, "default", new DefaultServlet()); + ctx.addServletMappingDecoded("/", "default"); + + LoginConfig loginConfig = new LoginConfig(); + loginConfig.setAuthMethod("BASIC"); + ctx.setLoginConfig(loginConfig); + + BasicAuthenticator basicAuth = new BasicAuthenticator(); + basicAuth.setAllowCorsPreflight(allowCorsPreflight.toString()); + ctx.getPipeline().addValve(basicAuth); + + Realm realm = new NullRealm(); + ctx.setRealm(realm); + + SecurityCollection securityCollection = new SecurityCollection(); + securityCollection.addPattern("/*"); + SecurityConstraint constraint = new SecurityConstraint(); + constraint.setAuthConstraint(true); + constraint.addCollection(securityCollection); + ctx.addConstraint(constraint); + + // For code coverage + FilterDef otherFilter = new FilterDef(); + otherFilter.setFilterName("other"); + otherFilter.setFilterClass(AddDefaultCharsetFilter.class.getName()); + FilterMap otherMap = new FilterMap(); + otherMap.setFilterName("other"); + otherMap.addURLPatternDecoded("/other"); + ctx.addFilterDef(otherFilter); + ctx.addFilterMap(otherMap); + + FilterDef corsFilter = new FilterDef(); + corsFilter.setFilterName("cors"); + corsFilter.setFilterClass(CorsFilter.class.getName()); + corsFilter.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_ORIGINS, ALLOWED_ORIGIN); + corsFilter.addInitParameter(CorsFilter.PARAM_CORS_ALLOWED_METHODS, ALLOWED_METHOD); + FilterMap corsFilterMap = new FilterMap(); + corsFilterMap.setFilterName("cors"); + corsFilterMap.addURLPatternDecoded(filterMapping); + ctx.addFilterDef(corsFilter); + ctx.addFilterMap(corsFilterMap); + + tomcat.start(); + + Map> reqHead = new HashMap<>(); + if (origin != null) { + List values = new ArrayList<>(); + if (SAME_ORIGIN.equals(origin)) { + values.add(origin + ":" + getPort()); + } else { + values.add(origin); + } + reqHead.put(CorsFilter.REQUEST_HEADER_ORIGIN, values); + } + if (accessControl != null) { + List values = new ArrayList<>(); + values.add(accessControl); + reqHead.put(CorsFilter.REQUEST_HEADER_ACCESS_CONTROL_REQUEST_METHOD, values); + } + + ByteChunk out = new ByteChunk(); + int rc = methodUrl("http://localhost:" + getPort() + "/target", out, 300000, reqHead, null, method, false); + + if (allow) { + Assert.assertEquals(200, rc); + } else { + Assert.assertEquals(403, rc); + } + } +} diff -Nru tomcat9-9.0.27/test/org/apache/catalina/authenticator/TestBasicAuthParser.java tomcat9-9.0.31/test/org/apache/catalina/authenticator/TestBasicAuthParser.java --- tomcat9-9.0.27/test/org/apache/catalina/authenticator/TestBasicAuthParser.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/authenticator/TestBasicAuthParser.java 2020-02-05 19:26:48.000000000 +0000 @@ -362,26 +362,23 @@ /* * invalid base64 string tests * - * Refer to RFC2045 section 6.8. + * Refer to + * - RFC 7617 (Basic Auth) + * - RFC 4648 (base 64) */ /* - * non-trailing "=" should trigger premature termination of the - * decoder, returning a truncated string that will eventually - * result in an authentication Assert.failure. + * non-trailing "=" is illegal and will be rejected by the parser */ - @Test + @Test(expected = IllegalArgumentException.class) public void testBadBase64InlineEquals() throws Exception { final String BASE64_CRIB = "dXNlcmlkOnNlY3J=dAo="; - final String TRUNCATED_PWD = "secr"; final BasicAuthHeader AUTH_HEADER = new BasicAuthHeader(NICE_METHOD, BASE64_CRIB); + @SuppressWarnings("unused") // Exception will be thrown. BasicAuthenticator.BasicCredentials credentials = new BasicAuthenticator.BasicCredentials( AUTH_HEADER.getHeader(), StandardCharsets.UTF_8, true); - Assert.assertEquals(USER_NAME, credentials.getUsername()); - Assert.assertNotSame(PASSWORD, credentials.getPassword()); - Assert.assertEquals(TRUNCATED_PWD, credentials.getPassword()); } /* diff -Nru tomcat9-9.0.27/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java tomcat9-9.0.31/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java --- tomcat9-9.0.27/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/connector/TestCoyoteAdapterRequestFuzzing.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,7 +16,11 @@ */ package org.apache.catalina.connector; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.net.Socket; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -106,7 +110,7 @@ @Test public void doTest() throws Exception { Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setAttribute("restrictedUserAgents", "value-not-important"); + Assert.assertTrue(tomcat.getConnector().setProperty("restrictedUserAgents", "value-not-important")); File appDir = new File("test/webapp"); Context ctxt = tomcat.addContext("", appDir.getAbsolutePath()); @@ -135,6 +139,19 @@ } @Override + protected OutputStream createOutputStream(Socket socket) throws IOException { + // Override the default implementation so we can create a large + // enough buffer to hold the entire request. + // The default implementation uses the 8k buffer in the + // StreamEncoder. Since some requests are larger than this, those + // requests will be sent in several parts. If the first part is + // sufficient for Tomcat to determine the request is invalid, Tomcat + // will close the connection, causing the write of the remaining + // parts to fail which in turn causes the test to fail. + return new BufferedOutputStream(super.createOutputStream(socket), 32 * 1024); + } + + @Override public boolean isResponseBodyOK() { // Response body varies. It is the response code that is of interest // in these tests. diff -Nru tomcat9-9.0.27/test/org/apache/catalina/connector/TestInputBuffer.java tomcat9-9.0.31/test/org/apache/catalina/connector/TestInputBuffer.java --- tomcat9-9.0.27/test/org/apache/catalina/connector/TestInputBuffer.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/connector/TestInputBuffer.java 2020-02-05 19:26:48.000000000 +0000 @@ -48,7 +48,6 @@ Tomcat.addServlet(root, "Echo", new Utf8Echo()); root.addServletMappingDecoded("/test", "Echo"); - tomcat.getConnector().setProperty("soTimeout", "300000"); tomcat.start(); for (Utf8TestCase testCase : TestUtf8.TEST_CASES) { @@ -68,7 +67,7 @@ Tomcat.addServlet(root, "Bug60400Servlet", new Bug60400Servlet()); root.addServletMappingDecoded("/", "Bug60400Servlet"); - tomcat.getConnector().setProperty("appReadBufSize", "9000"); + Assert.assertTrue(tomcat.getConnector().setProperty("socket.appReadBufSize", "9000")); tomcat.start(); ByteChunk bc = new ByteChunk(); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/connector/TestKeepAliveCount.java tomcat9-9.0.31/test/org/apache/catalina/connector/TestKeepAliveCount.java --- tomcat9-9.0.27/test/org/apache/catalina/connector/TestKeepAliveCount.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/connector/TestKeepAliveCount.java 2020-02-05 19:26:48.000000000 +0000 @@ -58,9 +58,9 @@ Context root = tomcat.addContext("", TEMP_DIR); Tomcat.addServlet(root, "Simple", new SimpleServlet()); root.addServletMappingDecoded("/test", "Simple"); - tomcat.getConnector().setProperty("maxKeepAliveRequests", "5"); - tomcat.getConnector().setProperty("soTimeout", "20000"); - tomcat.getConnector().setProperty("keepAliveTimeout", "50000"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "5")); + Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000")); + Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000")); init = true; } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/connector/TestMaxConnections.java tomcat9-9.0.31/test/org/apache/catalina/connector/TestMaxConnections.java --- tomcat9-9.0.27/test/org/apache/catalina/connector/TestMaxConnections.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/connector/TestMaxConnections.java 2020-02-05 19:26:48.000000000 +0000 @@ -74,13 +74,12 @@ root.setUnloadDelay(soTimeout); Tomcat.addServlet(root, "Simple", new SimpleServlet()); root.addServletMappingDecoded("/test", "Simple"); - tomcat.getConnector().setProperty("maxKeepAliveRequests", "1"); - tomcat.getConnector().setProperty("maxThreads", "10"); - tomcat.getConnector().setProperty("soTimeout", "20000"); - tomcat.getConnector().setProperty("keepAliveTimeout", "50000"); - tomcat.getConnector().setProperty( - "maxConnections", Integer.toString(MAX_CONNECTIONS)); - tomcat.getConnector().setProperty("acceptCount", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); + Assert.assertTrue(tomcat.getConnector().setProperty("maxThreads", "10")); + Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "20000")); + Assert.assertTrue(tomcat.getConnector().setProperty("keepAliveTimeout", "50000")); + Assert.assertTrue(tomcat.getConnector().setProperty("maxConnections", Integer.toString(MAX_CONNECTIONS))); + Assert.assertTrue(tomcat.getConnector().setProperty("acceptCount", "1")); tomcat.start(); } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/connector/TestResponse.java tomcat9-9.0.31/test/org/apache/catalina/connector/TestResponse.java --- tomcat9-9.0.27/test/org/apache/catalina/connector/TestResponse.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/connector/TestResponse.java 2020-02-05 19:26:48.000000000 +0000 @@ -19,7 +19,6 @@ import java.io.IOException; import java.io.PrintWriter; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,6 +37,7 @@ import org.apache.tomcat.unittest.TesterContext; import org.apache.tomcat.unittest.TesterRequest; import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap; /** * Test case for {@link Request}. @@ -57,7 +57,7 @@ tomcat.start(); - Map> headers = new HashMap<>(); + Map> headers = new CaseInsensitiveKeyMap<>(); getUrl("http://localhost:" + getPort() + "/", new ByteChunk(), headers); // Check for headers without a name diff -Nru tomcat9-9.0.27/test/org/apache/catalina/core/TestAsyncContextImpl.java tomcat9-9.0.31/test/org/apache/catalina/core/TestAsyncContextImpl.java --- tomcat9-9.0.27/test/org/apache/catalina/core/TestAsyncContextImpl.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/core/TestAsyncContextImpl.java 2020-02-05 19:26:48.000000000 +0000 @@ -20,12 +20,13 @@ import java.io.PrintWriter; import java.net.URI; import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.LinkedHashMap; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; @@ -35,12 +36,14 @@ import javax.servlet.RequestDispatcher; import javax.servlet.Servlet; import javax.servlet.ServletException; +import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.ServletRequestWrapper; import javax.servlet.ServletResponse; import javax.servlet.ServletResponseWrapper; +import javax.servlet.WriteListener; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -53,11 +56,14 @@ import org.apache.catalina.Wrapper; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; +import org.apache.catalina.connector.TestCoyoteAdapter; +import org.apache.catalina.startup.SimpleHttpClient; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; import org.apache.catalina.valves.TesterAccessLogValve; import org.apache.tomcat.unittest.TesterContext; import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap; import org.apache.tomcat.util.descriptor.web.ErrorPage; import org.easymock.EasyMock; @@ -165,8 +171,7 @@ Tomcat tomcat = getTomcatInstance(); // Minimise pauses during test - tomcat.getConnector().setAttribute( - "connectionTimeout", Integer.valueOf(3000)); + Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "3000")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -224,14 +229,12 @@ // Call the servlet once ByteChunk bc = new ByteChunk(); - Map> headers = new HashMap<>(); + Map> headers = new CaseInsensitiveKeyMap<>(); getUrl("http://localhost:" + getPort() + "/", bc, headers); Assert.assertEquals("OK", bc.toString()); - List contentLength = headers.get("Content-Length"); - Assert.assertNotNull(contentLength); - Assert.assertEquals(1, contentLength.size()); - Assert.assertEquals("2", contentLength.get(0)); + String contentLength = getSingleHeader("Content-Length", headers); + Assert.assertEquals("2", contentLength); // Check the access log alv.validateAccessLog(1, 200, 0, REQUEST_TIME); @@ -552,19 +555,25 @@ alv.validateAccessLog(1, 200, timeoutDelay, timeoutDelay + TIMEOUT_MARGIN + REQUEST_TIME); } + + Assert.assertTrue(timeout.isAsyncStartedCorrect()); } private static class TimeoutServlet extends HttpServlet { private static final long serialVersionUID = 1L; private final Boolean completeOnTimeout; - private final String dispatchUrl; + private final transient TrackingListener trackingListener; public static final long ASYNC_TIMEOUT = 100; public TimeoutServlet(Boolean completeOnTimeout, String dispatchUrl) { this.completeOnTimeout = completeOnTimeout; - this.dispatchUrl = dispatchUrl; + if (completeOnTimeout == null) { + this.trackingListener = null; + } else { + this.trackingListener = new TrackingListener(false, completeOnTimeout.booleanValue(), dispatchUrl); + } } @Override @@ -576,13 +585,19 @@ ac.setTimeout(ASYNC_TIMEOUT); if (completeOnTimeout != null) { - ac.addListener(new TrackingListener(false, - completeOnTimeout.booleanValue(), dispatchUrl)); + ac.addListener(trackingListener); } } else { resp.getWriter().print("FAIL: Async unsupported"); } } + + public boolean isAsyncStartedCorrect() { + if (trackingListener == null) { + return true; + } + return trackingListener.isAsyncStartedCorrect(); + } } @Test @@ -667,6 +682,7 @@ count ++; } Assert.assertEquals(expectedTrack, getTrack()); + Assert.assertTrue(dispatch.isAsyncStartedCorrect()); // Check the access log alv.validateAccessLog(1, 200, 0, REQUEST_TIME); @@ -677,13 +693,15 @@ private static final long serialVersionUID = 1L; private static final String ITER_PARAM = "iter"; private static final String DISPATCH_CHECK = "check"; - private boolean addTrackingListener = false; - private boolean completeOnError = false; + private final transient TrackingListener trackingListener; public DispatchingServlet(boolean addTrackingListener, boolean completeOnError) { - this.addTrackingListener = addTrackingListener; - this.completeOnError = completeOnError; + if (addTrackingListener) { + trackingListener = new TrackingListener(completeOnError, true, null); + } else { + trackingListener = null; + } } @Override @@ -698,10 +716,8 @@ track("DispatchingServletGet-"); final int iter = Integer.parseInt(req.getParameter(ITER_PARAM)) - 1; final AsyncContext ctxt = req.startAsync(); - if (addTrackingListener) { - TrackingListener listener = - new TrackingListener(completeOnError, true, null); - ctxt.addListener(listener); + if (trackingListener != null) { + ctxt.addListener(trackingListener); } Runnable run = new Runnable() { @Override @@ -720,6 +736,13 @@ run.run(); } } + + public boolean isAsyncStartedCorrect() { + if (trackingListener == null) { + return true; + } + return trackingListener.isAsyncStartedCorrect(); + } } private static class NonAsyncServlet extends HttpServlet { @@ -824,6 +847,9 @@ private final boolean completeOnError; private final boolean completeOnTimeout; private final String dispatchUrl; + // Assumes listener is fired after container thread that initiated async + // has exited. + private boolean asyncStartedCorrect = true; public TrackingListener(boolean completeOnError, boolean completeOnTimeout, String dispatchUrl) { @@ -839,27 +865,44 @@ @Override public void onTimeout(AsyncEvent event) throws IOException { + boolean expectedAsyncStarted = true; + TestAsyncContextImpl.track("onTimeout-"); if (completeOnTimeout){ event.getAsyncContext().complete(); + expectedAsyncStarted = false; } if (dispatchUrl != null) { event.getAsyncContext().dispatch(dispatchUrl); + expectedAsyncStarted = false; } + + ServletRequest req = event.getSuppliedRequest(); + asyncStartedCorrect = (expectedAsyncStarted == req.isAsyncStarted()); } @Override public void onError(AsyncEvent event) throws IOException { + boolean expectedAsyncStarted = true; + TestAsyncContextImpl.track("onError-"); if (completeOnError) { event.getAsyncContext().complete(); + expectedAsyncStarted = false; } + + ServletRequest req = event.getSuppliedRequest(); + asyncStartedCorrect = (expectedAsyncStarted == req.isAsyncStarted()); } @Override public void onStartAsync(AsyncEvent event) throws IOException { TestAsyncContextImpl.track("onStartAsync-"); } + + public boolean isAsyncStartedCorrect() { + return asyncStartedCorrect; + } } private static class StickyTrackingListener extends TrackingListener { @@ -1010,6 +1053,7 @@ count ++; } Assert.assertEquals(expectedTrack, getTrack()); + Assert.assertTrue(dispatch.isAsyncStartedCorrect()); // Check the access log alv.validateAccessLog(1, 500, 0, REQUEST_TIME); @@ -1122,15 +1166,13 @@ tomcat.start(); // Call the servlet once - Map> headers = new LinkedHashMap<>(); + Map> headers = new CaseInsensitiveKeyMap<>(); ByteChunk bc = new ByteChunk(); int rc = getUrl("http://localhost:" + getPort() + "/", bc, headers); Assert.assertEquals(200, rc); Assert.assertEquals("OK", bc.toString()); - List testHeader = headers.get("A"); - Assert.assertNotNull(testHeader); - Assert.assertEquals(1, testHeader.size()); - Assert.assertEquals("xyz",testHeader.get(0)); + String testHeader = getSingleHeader("A", headers); + Assert.assertEquals("xyz",testHeader); // Check the access log alv.validateAccessLog(1, 200, Bug50753Servlet.THREAD_SLEEP_TIME, @@ -1838,7 +1880,8 @@ Tomcat tomcat = getTomcatInstance(); Context ctx = tomcat.addContext("", null); - Wrapper w = tomcat.addServlet("", "async", new Bug59219Servlet()); + Bug59219Servlet bug59219Servlet = new Bug59219Servlet(); + Wrapper w = tomcat.addServlet("", "async", bug59219Servlet); w.setAsyncSupported(true); ctx.addServletMappingDecoded("/async", "async"); @@ -1861,6 +1904,7 @@ private static final long serialVersionUID = 1L; + private final transient TrackingListener trackingListener = new TrackingListener(true, false, "/async"); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { @@ -1868,7 +1912,7 @@ track("doGet-"); AsyncContext ctx = req.startAsync(); ctx.setTimeout(3000); - ctx.addListener(new TrackingListener(true, false, "/async")); + ctx.addListener(trackingListener); String loopsParam = req.getParameter("loops"); Integer loopsAttr = (Integer) req.getAttribute("loops"); @@ -1887,7 +1931,6 @@ } else throw new ServletException(); } - } @Test @@ -2653,4 +2696,309 @@ } } + + + @Test + public void testAsyncIoEnd00() throws Exception { + doTestAsyncIoEnd(false, false); + } + + + @Test + public void testAsyncIoEnd01() throws Exception { + doTestAsyncIoEnd(false, true); + } + + + @Test + public void testAsyncIoEnd02() throws Exception { + doTestAsyncIoEnd(true, false); + } + + + @Test + public void testAsyncIoEnd03() throws Exception { + doTestAsyncIoEnd(true, true); + } + + + private void doTestAsyncIoEnd(boolean useThread, boolean useComplete) throws Exception { + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + AsyncIoEndServlet asyncIoEndServlet = new AsyncIoEndServlet(useThread, useComplete); + Wrapper wrapper = Tomcat.addServlet(ctx, "asyncIoEndServlet", asyncIoEndServlet); + wrapper.setAsyncSupported(true); + ctx.addServletMappingDecoded("/asyncIoEndServlet", "asyncIoEndServlet"); + + SimpleServlet simpleServlet = new SimpleServlet(); + Tomcat.addServlet(ctx, "simpleServlet", simpleServlet); + ctx.addServletMappingDecoded("/simpleServlet", "simpleServlet"); + + tomcat.start(); + + ByteChunk body = new ByteChunk(); + int rc = getUrl("http://localhost:" + getPort() + "/asyncIoEndServlet", body, null); + + Assert.assertEquals(HttpServletResponse.SC_OK, rc); + Assert.assertEquals("OK", body.toString()); + + Assert.assertFalse(asyncIoEndServlet.getInvalidStateDetected()); + } + + + private static class AsyncIoEndServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private final boolean useThread; + private final boolean useComplete; + private transient AsyncIoEndWriteListener asyncIoEndWriteListener; + + public AsyncIoEndServlet(boolean useThread, boolean useComplete) { + this.useThread = useThread; + this.useComplete = useComplete; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + if (useComplete) { + // Write expected body here + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + resp.getOutputStream().write("OK".getBytes(StandardCharsets.UTF_8)); + } + AsyncContext ac = req.startAsync(); + ServletOutputStream sos = resp.getOutputStream(); + asyncIoEndWriteListener = new AsyncIoEndWriteListener(ac, useThread, useComplete); + sos.setWriteListener(asyncIoEndWriteListener); + } + + public boolean getInvalidStateDetected() { + if (asyncIoEndWriteListener != null) { + return asyncIoEndWriteListener.getInvalidStateDetected(); + } + return false; + } + } + + + private static class AsyncIoEndWriteListener implements WriteListener { + + private final AsyncContext ac; + private final boolean useThread; + private final boolean useComplete; + private boolean invalidStateDetected = false; + + public AsyncIoEndWriteListener(AsyncContext ac, boolean useThread, + boolean useComplete) { + this.ac = ac; + this.useThread = useThread; + this.useComplete = useComplete; + } + + + @Override + public void onWritePossible() throws IOException { + if (useThread) { + (new Thread() { + @Override + public void run() { + doOnWritePossible(); + } + }).start(); + } else { + doOnWritePossible(); + } + } + + + public void doOnWritePossible() { + // Hack to avoid ISE if we try gettign the request after complete/dispatch + ServletRequest req = ac.getRequest(); + if (useComplete) { + ac.complete(); + } else { + ac.dispatch("/simpleServlet"); + } + if (req.isAsyncStarted()) { + invalidStateDetected = true; + } + } + + @Override + public void onError(Throwable throwable) { + throw new RuntimeException(throwable); + } + + + public boolean getInvalidStateDetected() { + return invalidStateDetected; + } + } + + + private static class SimpleServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + // Write expected body here + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + resp.getOutputStream().write("OK".getBytes(StandardCharsets.UTF_8)); + } + } + + + /* + * Tests an error on an async thread before the container thread that called + * startAsync() has returned to the container. + * + * Required sequence is: + * - enter Servlet's service() method + * - startAsync() + * - start async thread + * - close client connection + * - write on async thread -> I/O error + * - exit Servlet's service() method + * + * This test makes extensive use of instance fields in the Servlet that + * would normally be considered very poor practice. It is only safe in this + * test as the Servlet only processes a single request. + */ + @Test + public void testBug63816() throws Exception { + CountDownLatch doGetLatch = new CountDownLatch(1); + CountDownLatch clientCloseLatch = new CountDownLatch(1); + CountDownLatch threadCompleteLatch = new CountDownLatch(1); + + AtomicBoolean ise = new AtomicBoolean(true); + + // Setup Tomcat instance + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + Bug63816Servlet bug63816Servlet = new Bug63816Servlet(doGetLatch, clientCloseLatch, threadCompleteLatch, ise); + Wrapper wrapper = Tomcat.addServlet(ctx, "bug63816Servlet", bug63816Servlet); + wrapper.setAsyncSupported(true); + ctx.addServletMappingDecoded("/*", "bug63816Servlet"); + + tomcat.start(); + + Bug63816Client client = new Bug63816Client(); + client.setPort(getPort()); + client.setRequest(new String[] { "GET / HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF}); + client.connect(); + client.sendRequest(); + + // Wait for async to start + doGetLatch.await(); + + client.disconnect(); + + clientCloseLatch.countDown(); + + threadCompleteLatch.await(); + + Assert.assertFalse(ise.get()); + } + + + private static final class Bug63816Client extends SimpleHttpClient { + + @Override + public boolean isResponseBodyOK() { + return true; + } + } + + + private static final class Bug63816Servlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private final transient CountDownLatch doGetLatch; + private final transient CountDownLatch clientCloseLatch; + private final transient CountDownLatch threadCompleteLatch; + private final AtomicBoolean ise; + + public Bug63816Servlet(CountDownLatch doGetLatch, CountDownLatch clientCloseLatch, + CountDownLatch threadCompleteLatch, AtomicBoolean ise) { + this.doGetLatch = doGetLatch; + this.clientCloseLatch = clientCloseLatch; + this.threadCompleteLatch = threadCompleteLatch; + this.ise = ise; + } + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + + doGetLatch.countDown(); + + AsyncContext ac = req.startAsync(); + Thread t = new Bug63816Thread(ac, clientCloseLatch, threadCompleteLatch, ise); + t.start(); + + try { + threadCompleteLatch.await(); + } catch (InterruptedException e) { + // Ignore + } + } + } + + + private static final class Bug63816Thread extends Thread { + + private final AsyncContext ac; + private final CountDownLatch clientCloseLatch; + private final CountDownLatch threadCompleteLatch; + private final AtomicBoolean ise; + + public Bug63816Thread(AsyncContext ac, CountDownLatch clientCloseLatch, CountDownLatch threadCompleteLatch, + AtomicBoolean ise) { + this.ac = ac; + this.clientCloseLatch = clientCloseLatch; + this.threadCompleteLatch = threadCompleteLatch; + this.ise = ise; + } + + @Override + public void run() { + try { + // Wait for client to close connection + clientCloseLatch.await(); + + try { + ServletResponse resp = ac.getResponse(); + resp.setContentType("text/plain"); + for (int i = 0; i < 4; i++) { + resp.getWriter().write(TestCoyoteAdapter.TEXT_8K); + resp.flushBuffer(); + } + } catch (IOException e) { + // Ignore + } + ise.set(false); + } catch (InterruptedException e) { + // Ignore + } finally { + threadCompleteLatch.countDown(); + } + } + } + } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/core/TestAsyncContextStateChanges.java tomcat9-9.0.31/test/org/apache/catalina/core/TestAsyncContextStateChanges.java --- tomcat9-9.0.27/test/org/apache/catalina/core/TestAsyncContextStateChanges.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/core/TestAsyncContextStateChanges.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,378 @@ +/* + * 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.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +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.connector.TestCoyoteAdapter; +import org.apache.catalina.startup.SimpleHttpClient; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; + +/* + * Derived from a test for https://bz.apache.org/bugzilla/show_bug.cgi?id=63816 + * Expanded to cover https://bz.apache.org/bugzilla/show_bug.cgi?id=63817 and + * additional scenarios. + */ +@RunWith(Parameterized.class) +public class TestAsyncContextStateChanges extends TomcatBaseTest { + + @Parameterized.Parameters(name = "{index}: end [{0}], timing [{1}]") + public static Collection parameters() { + List parameterSets = new ArrayList<>(); + for (AsyncEnd asyncEnd : AsyncEnd.values()) { + for (EndTiming endTiming : EndTiming.values()) { + parameterSets.add(new Object[] { asyncEnd, endTiming }); + } + } + return parameterSets; + } + + @Parameter(0) + public AsyncEnd asyncEnd; + + @Parameter(1) + public EndTiming endTiming; + + private ServletRequest servletRequest = null; + private AsyncContext asyncContext = null; + private AtomicBoolean failed = new AtomicBoolean(); + private CountDownLatch servletLatch; + private CountDownLatch threadLatch; + private CountDownLatch closeLatch; + private CountDownLatch endLatch; + private boolean dispatch; + + @Test + public void testAsync() throws Exception { + dispatch = false; + servletRequest = null; + asyncContext = null; + + // Initialise tracking fields + failed.set(true); + servletLatch = new CountDownLatch(1); + threadLatch = new CountDownLatch(1); + closeLatch = new CountDownLatch(1); + endLatch = new CountDownLatch(1); + + // Setup Tomcat instance + Tomcat tomcat = getTomcatInstance(); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + AsyncServlet bug63816Servlet = new AsyncServlet(); + Wrapper wrapper = Tomcat.addServlet(ctx, "bug63816Servlet", bug63816Servlet); + wrapper.setAsyncSupported(true); + ctx.addServletMappingDecoded("/*", "bug63816Servlet"); + + tomcat.start(); + + Client client = new Client(); + client.setPort(getPort()); + client.setRequest(new String[] { "GET / HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + SimpleHttpClient.CRLF + + SimpleHttpClient.CRLF}); + client.connect(); + client.sendRequest(); + + // Wait for Servlet to start processing request + servletLatch.await(); + + if (asyncEnd.isError()) { + client.disconnect(); + closeLatch.countDown(); + try { + endLatch.await(); + } catch (InterruptedException e) { + // Ignore + } + } else { + client.setUseContentLength(true); + client.readResponse(true); + } + + Assert.assertFalse(failed.get()); + } + + + private static final class Client extends SimpleHttpClient { + + @Override + public boolean isResponseBodyOK() { + return true; + } + } + + + private final class AsyncServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + servletLatch.countDown(); + + if (dispatch) { + return; + } + + if (!asyncEnd.isError()) { + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + resp.setContentLength(2); + resp.getWriter().print("OK"); + } + + servletRequest = req; + asyncContext = req.startAsync(); + asyncContext.addListener(new Listener()); + if (!asyncEnd.isError()) { + // Use a short timeout so the test does not pause for too long + // waiting for the timeout to be triggered. + asyncContext.setTimeout(1000); + } + Thread t = new NonContainerThread(); + + switch (endTiming) { + case INLINE: { + t.run(); + break; + } + case THREAD_AFTER_EXIT: { + t.start(); + threadLatch.countDown(); + break; + } + case THREAD_BEFORE_EXIT: { + t.start(); + try { + threadLatch.await(); + } catch (InterruptedException e) { + // Ignore + } + endLatch.countDown(); + break; + } + } + } + } + + + private final class NonContainerThread extends Thread { + + @Override + public void run() { + if (endTiming == EndTiming.THREAD_AFTER_EXIT) { + try { + threadLatch.await(); + /* + * As much as I dislike it, I don't see any easy way around + * this hack. The latch above is released as the Servlet + * exits but we need to wait for the post processing to + * complete for the test to work as intended. In real-world + * applications this does mean that there is a real chance + * of an ISE. We may need to increase this delay for some CI + * systems. + */ + Thread.sleep(1000); + } catch (InterruptedException e) { + // Ignore + } + } + + // Trigger the error if necessary + if (asyncEnd.isError()) { + try { + closeLatch.await(); + } catch (InterruptedException e) { + // Ignore + } + try { + ServletResponse resp = asyncContext.getResponse(); + resp.setContentType("text/plain"); + resp.setCharacterEncoding("UTF-8"); + OutputStream os = resp.getOutputStream(); + resp.setContentType("text/plain"); + for (int i = 0; i < 16; i++) { + os.write(TestCoyoteAdapter.TEXT_8K.getBytes(StandardCharsets.UTF_8)); + } + } catch (IOException e) { + // Expected + } + } + + if (endTiming != EndTiming.THREAD_AFTER_EXIT) { + try { + switch (asyncEnd) { + case COMPLETE: + case ERROR_COMPLETE: { + asyncContext.complete(); + break; + } + case DISPATCH: + case ERROR_DISPATCH: { + dispatch = true; + asyncContext.dispatch(); + break; + } + case NONE: + case ERROR_NONE: { + break; + } + } + + // The request must stay in async mode until doGet() exists + if (servletRequest.isAsyncStarted()) { + failed.set(false); + } + } finally { + if (endTiming == EndTiming.THREAD_BEFORE_EXIT) { + threadLatch.countDown(); + } + } + } + } + } + + + private class Listener implements AsyncListener { + + @Override + public void onComplete(AsyncEvent event) throws IOException { + if (endTiming == EndTiming.INLINE) { + endLatch.countDown(); + } + } + + @Override + public void onTimeout(AsyncEvent event) throws IOException { + // Need to handle timeouts for THREAD_AFTER_EXIT in the listener to + // avoid concurrency issues. + if (endTiming == EndTiming.THREAD_AFTER_EXIT) { + switch (asyncEnd) { + case COMPLETE: { + asyncContext.complete(); + break; + } + case DISPATCH: { + dispatch = true; + asyncContext.dispatch(); + break; + } + default: + // NO-OP + } + } + if (servletRequest.isAsyncStarted() == asyncEnd.isNone()) { + failed.set(false); + } + endLatch.countDown(); + } + + @Override + public void onError(AsyncEvent event) throws IOException { + // Need to handle errors for THREAD_AFTER_EXIT in the listener to + // avoid concurrency issues. + if (endTiming == EndTiming.THREAD_AFTER_EXIT) { + switch (asyncEnd) { + case ERROR_COMPLETE: { + asyncContext.complete(); + break; + } + case ERROR_DISPATCH: { + dispatch = true; + asyncContext.dispatch(); + break; + } + default: + // NO-OP + } + if (servletRequest.isAsyncStarted() == asyncEnd.isNone()) { + failed.set(false); + } + endLatch.countDown(); + } + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException { + // NO-OP + } + } + + + public enum AsyncEnd { + + NONE ( true, false), + COMPLETE (false, false), + DISPATCH (false, false), + ERROR_NONE ( true, true), + ERROR_COMPLETE(false, true), + ERROR_DISPATCH(false, true); + + final boolean none; + final boolean error; + + private AsyncEnd(boolean none, boolean error) { + this.none = none; + this.error = error; + } + + public boolean isNone() { + return none; + } + + public boolean isError() { + return error; + } + } + + + public enum EndTiming { + INLINE, + THREAD_BEFORE_EXIT, + THREAD_AFTER_EXIT + } +} diff -Nru tomcat9-9.0.27/test/org/apache/catalina/core/TestStandardService.java tomcat9-9.0.31/test/org/apache/catalina/core/TestStandardService.java --- tomcat9-9.0.27/test/org/apache/catalina/core/TestStandardService.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/core/TestStandardService.java 2020-02-05 19:26:48.000000000 +0000 @@ -18,6 +18,7 @@ import java.net.InetAddress; +import org.junit.Assert; import org.junit.Test; import org.apache.catalina.connector.Connector; @@ -49,7 +50,7 @@ Connector c2 = new Connector("HTTP/1.1"); c2.setThrowOnFailure(throwOnFailure); - c2.setAttribute("address", ((InetAddress) connector.getAttribute("address")).getHostAddress()); + Assert.assertTrue(c2.setProperty("address", ((InetAddress) connector.getProperty("address")).getHostAddress())); c2.setPort(connector.getLocalPort()); tomcat.getService().addConnector(c2); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/core/TestSwallowAbortedUploads.java tomcat9-9.0.31/test/org/apache/catalina/core/TestSwallowAbortedUploads.java --- tomcat9-9.0.27/test/org/apache/catalina/core/TestSwallowAbortedUploads.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/core/TestSwallowAbortedUploads.java 2020-02-05 19:26:48.000000000 +0000 @@ -255,7 +255,7 @@ Connector c = tomcat.getConnector(); c.setMaxPostSize(2 * hugeSize); - c.setProperty("maxSwallowSize", Integer.toString(hugeSize)); + Assert.assertTrue(c.setProperty("maxSwallowSize", Integer.toString(hugeSize))); tomcat.start(); setPort(c.getLocalPort()); @@ -367,7 +367,7 @@ Connector c = tomcat.getConnector(); c.setMaxPostSize(2 * hugeSize); - c.setProperty("maxSwallowSize", Integer.toString(hugeSize)); + Assert.assertTrue(c.setProperty("maxSwallowSize", Integer.toString(hugeSize))); setPort(c.getLocalPort()); } @@ -431,7 +431,7 @@ // No need for target to exist. if (!limit) { - tomcat.getConnector().setAttribute("maxSwallowSize", "-1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxSwallowSize", "-1")); } tomcat.start(); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/filters/TestAddCharSetFilter.java tomcat9-9.0.31/test/org/apache/catalina/filters/TestAddCharSetFilter.java --- tomcat9-9.0.27/test/org/apache/catalina/filters/TestAddCharSetFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/filters/TestAddCharSetFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -120,9 +120,7 @@ Map> headers = new HashMap<>(); getUrl("http://localhost:" + getPort() + "/", new ByteChunk(), headers); - List ctHeaders = headers.get("Content-Type"); - Assert.assertEquals(1, ctHeaders.size()); - String ct = ctHeaders.get(0).toLowerCase(Locale.ENGLISH); + String ct = getSingleHeader("Content-Type", headers).toLowerCase(Locale.ENGLISH); Assert.assertEquals("text/plain;charset=" + expected.toLowerCase(Locale.ENGLISH), ct); } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/filters/TestCorsFilter.java tomcat9-9.0.31/test/org/apache/catalina/filters/TestCorsFilter.java --- tomcat9-9.0.27/test/org/apache/catalina/filters/TestCorsFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/filters/TestCorsFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -32,6 +32,8 @@ import org.junit.Assert; import org.junit.Test; +import org.apache.tomcat.util.http.RequestUtil; + public class TestCorsFilter { private FilterChain filterChain = new TesterFilterChain(); @@ -1425,27 +1427,27 @@ @Test public void testValidOrigin() { - Assert.assertTrue(CorsFilter.isValidOrigin("http://www.w3.org")); + Assert.assertTrue(RequestUtil.isValidOrigin("http://www.w3.org")); } @Test public void testInValidOriginCRLF() { - Assert.assertFalse(CorsFilter.isValidOrigin("http://www.w3.org\r\n")); + Assert.assertFalse(RequestUtil.isValidOrigin("http://www.w3.org\r\n")); } @Test public void testInValidOriginEncodedCRLF1() { - Assert.assertFalse(CorsFilter.isValidOrigin("http://www.w3.org%0d%0a")); + Assert.assertFalse(RequestUtil.isValidOrigin("http://www.w3.org%0d%0a")); } @Test public void testInValidOriginEncodedCRLF2() { - Assert.assertFalse(CorsFilter.isValidOrigin("http://www.w3.org%0D%0A")); + Assert.assertFalse(RequestUtil.isValidOrigin("http://www.w3.org%0D%0A")); } @Test public void testInValidOriginEncodedCRLF3() { - Assert.assertFalse(CorsFilter + Assert.assertFalse(RequestUtil .isValidOrigin("http://www.w3.org%0%0d%0ad%0%0d%0aa")); } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java tomcat9-9.0.31/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java --- tomcat9-9.0.27/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/filters/TestCsrfPreventionFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -37,7 +37,7 @@ private final HttpServletResponse wrapper = new CsrfPreventionFilter.CsrfResponseWrapper( - new NonEncodingResponse(), "TESTNONCE"); + new NonEncodingResponse(), Constants.CSRF_NONCE_SESSION_ATTR_NAME, "TESTNONCE"); @Test public void testAddNonceNoQueryNoAnchor() throws Exception { diff -Nru tomcat9-9.0.27/test/org/apache/catalina/filters/TestExpiresFilter.java tomcat9-9.0.31/test/org/apache/catalina/filters/TestExpiresFilter.java --- tomcat9-9.0.27/test/org/apache/catalina/filters/TestExpiresFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/filters/TestExpiresFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -18,10 +18,11 @@ package org.apache.catalina.filters; import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URL; +import java.util.ArrayList; import java.util.Calendar; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.StringTokenizer; import java.util.TimeZone; @@ -41,8 +42,11 @@ import org.apache.catalina.filters.ExpiresFilter.StartingPoint; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; +import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.collections.CaseInsensitiveKeyMap; import org.apache.tomcat.util.descriptor.web.FilterDef; import org.apache.tomcat.util.descriptor.web.FilterMap; +import org.apache.tomcat.util.http.FastHttpDateFormat; public class TestExpiresFilter extends TomcatBaseTest { public static final String TEMP_DIR = System.getProperty("java.io.tmpdir"); @@ -366,12 +370,13 @@ protected void validate(HttpServlet servlet, Integer expectedMaxAgeInSeconds) throws Exception { - validate(servlet, expectedMaxAgeInSeconds, HttpURLConnection.HTTP_OK); + validate(servlet, expectedMaxAgeInSeconds, HttpServletResponse.SC_OK); } protected void validate(HttpServlet servlet, Integer expectedMaxAgeInSeconds, int expectedResponseStatusCode) throws Exception { + // SETUP Tomcat tomcat = getTomcatInstance(); @@ -408,16 +413,15 @@ long timeBeforeInMillis = System.currentTimeMillis(); // TEST - HttpURLConnection httpURLConnection = (HttpURLConnection) new URL( - "http://localhost:" + tomcat.getConnector().getLocalPort() + - "/test").openConnection(); + ByteChunk bc = new ByteChunk(); + Map> responseHeaders = new HashMap<>(); + int rc = getUrl("http://localhost:" + getPort() + "/test", bc, responseHeaders); // VALIDATE - Assert.assertEquals(expectedResponseStatusCode, - httpURLConnection.getResponseCode()); + Assert.assertEquals(expectedResponseStatusCode, rc); StringBuilder msg = new StringBuilder(); - for (Entry> field : httpURLConnection.getHeaderFields().entrySet()) { + for (Entry> field : responseHeaders.entrySet()) { for (String value : field.getValue()) { msg.append((field.getKey() == null ? "" : field.getKey() + ": ") + @@ -428,7 +432,8 @@ Integer actualMaxAgeInSeconds; - String cacheControlHeader = httpURLConnection.getHeaderField("Cache-Control"); + String cacheControlHeader = getSingleHeader("Cache-Control", responseHeaders); + if (cacheControlHeader == null) { actualMaxAgeInSeconds = null; } else { @@ -459,13 +464,15 @@ Assert.assertNotNull(actualMaxAgeInSeconds); + String contentType = getSingleHeader("Content-Type", responseHeaders); + int deltaInSeconds = Math.abs(actualMaxAgeInSeconds.intValue() - expectedMaxAgeInSeconds.intValue()); Assert.assertTrue("actualMaxAgeInSeconds: " + actualMaxAgeInSeconds + ", expectedMaxAgeInSeconds: " + expectedMaxAgeInSeconds + ", request time: " + timeBeforeInMillis + " for content type " + - httpURLConnection.getContentType(), deltaInSeconds < 3); + contentType, deltaInSeconds < 3); } finally { tomcat.stop(); @@ -480,4 +487,85 @@ Assert.assertEquals(expected, actual); } + + + /* + * Tests Expires filter with: + * - per content type expires + * - no default + * - Default servlet returning 304s (without content-type) + */ + @Test + public void testBug63909() throws Exception { + + Tomcat tomcat = getTomcatInstanceTestWebapp(false, false); + Context ctxt = (Context) tomcat.getHost().findChild("/test"); + + FilterDef filterDef = new FilterDef(); + filterDef.addInitParameter("ExpiresByType text/xml;charset=utf-8", "access plus 3 minutes"); + filterDef.addInitParameter("ExpiresByType text/xml", "access plus 5 minutes"); + filterDef.addInitParameter("ExpiresByType text", "access plus 7 minutes"); + filterDef.addInitParameter("ExpiresExcludedResponseStatusCodes", ""); + + filterDef.setFilterClass(ExpiresFilter.class.getName()); + filterDef.setFilterName(ExpiresFilter.class.getName()); + + ctxt.addFilterDef(filterDef); + + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName(ExpiresFilter.class.getName()); + filterMap.addURLPatternDecoded("*"); + ctxt.addFilterMap(filterMap); + + tomcat.start(); + + ByteChunk bc = new ByteChunk(); + Map> requestHeaders = new CaseInsensitiveKeyMap<>(); + List ifModifiedSinceValues = new ArrayList<>(); + ifModifiedSinceValues.add(FastHttpDateFormat.getCurrentDate()); + requestHeaders.put("If-Modified-Since", ifModifiedSinceValues); + Map> responseHeaders = new CaseInsensitiveKeyMap<>(); + + int rc = getUrl("http://localhost:" + getPort() + "/test/bug6nnnn/bug69303.txt", bc, requestHeaders, responseHeaders); + + Assert.assertEquals(HttpServletResponse.SC_NOT_MODIFIED, rc); + + StringBuilder msg = new StringBuilder(); + for (Entry> field : responseHeaders.entrySet()) { + for (String value : field.getValue()) { + msg.append((field.getKey() == null ? "" : field.getKey() + + ": ") + + value + "\n"); + } + } + System.out.println(msg); + + Integer actualMaxAgeInSeconds; + + String cacheControlHeader = getSingleHeader("Cache-Control", responseHeaders); + + if (cacheControlHeader == null) { + actualMaxAgeInSeconds = null; + } else { + actualMaxAgeInSeconds = null; + StringTokenizer cacheControlTokenizer = new StringTokenizer( + cacheControlHeader, ","); + while (cacheControlTokenizer.hasMoreTokens() && + actualMaxAgeInSeconds == null) { + String cacheDirective = cacheControlTokenizer.nextToken(); + StringTokenizer cacheDirectiveTokenizer = new StringTokenizer( + cacheDirective, "="); + if (cacheDirectiveTokenizer.countTokens() == 2) { + String key = cacheDirectiveTokenizer.nextToken().trim(); + String value = cacheDirectiveTokenizer.nextToken().trim(); + if (key.equalsIgnoreCase("max-age")) { + actualMaxAgeInSeconds = Integer.valueOf(value); + } + } + } + } + + Assert.assertNotNull(actualMaxAgeInSeconds); + Assert.assertTrue(Math.abs(actualMaxAgeInSeconds.intValue() - 420) < 3); + } } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/mapper/TestMapper.java tomcat9-9.0.31/test/org/apache/catalina/mapper/TestMapper.java --- tomcat9-9.0.27/test/org/apache/catalina/mapper/TestMapper.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/mapper/TestMapper.java 2020-02-05 19:26:48.000000000 +0000 @@ -241,6 +241,7 @@ } @Test + @SuppressWarnings("deprecation") // contextPath public void testMap() throws Exception { MappingData mappingData = new MappingData(); MessageBytes host = MessageBytes.newInstance(); @@ -492,6 +493,7 @@ } @Test + @SuppressWarnings("deprecation") // contextPath public void testContextListConcurrencyBug56653() throws Exception { final Host host = createHost("localhost"); final Context contextRoot = createContext("ROOT"); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/mapper/TestMapperWebapps.java tomcat9-9.0.31/test/org/apache/catalina/mapper/TestMapperWebapps.java --- tomcat9-9.0.27/test/org/apache/catalina/mapper/TestMapperWebapps.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/mapper/TestMapperWebapps.java 2020-02-05 19:26:48.000000000 +0000 @@ -18,8 +18,6 @@ import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -186,14 +184,11 @@ tomcat.start(); ByteChunk bc = new ByteChunk(); - int rc = getUrl("http://localhost:" + getPort() + - "/test/welcome-files", bc, new HashMap>()); + int rc = getUrl("http://localhost:" + getPort() + "/test/welcome-files", bc, null); Assert.assertEquals(HttpServletResponse.SC_OK, rc); Assert.assertTrue(bc.toString().contains("JSP")); - rc = getUrl("http://localhost:" + getPort() + - "/test/welcome-files/sub", bc, - new HashMap>()); + rc = getUrl("http://localhost:" + getPort() + "/test/welcome-files/sub", bc, null); Assert.assertEquals(HttpServletResponse.SC_OK, rc); Assert.assertTrue(bc.toString().contains("Servlet")); } @@ -217,14 +212,11 @@ tomcat.start(); ByteChunk bc = new ByteChunk(); - int rc = getUrl("http://localhost:" + getPort() + - "/test/welcome-files", bc, new HashMap>()); + int rc = getUrl("http://localhost:" + getPort() + "/test/welcome-files", bc, null); Assert.assertEquals(HttpServletResponse.SC_OK, rc); Assert.assertTrue(bc.toString().contains("JSP")); - rc = getUrl("http://localhost:" + getPort() + - "/test/welcome-files/sub", bc, - new HashMap>()); + rc = getUrl("http://localhost:" + getPort() + "/test/welcome-files/sub", bc, null); Assert.assertEquals(HttpServletResponse.SC_NOT_FOUND, rc); } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java tomcat9-9.0.31/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java --- tomcat9-9.0.27/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java 2020-02-05 19:26:48.000000000 +0000 @@ -157,7 +157,10 @@ String servletName = NBWriteServlet.class.getName(); Tomcat.addServlet(ctx, servletName, servlet); ctx.addServletMappingDecoded("/", servletName); - tomcat.getConnector().setProperty("socket.txBufSize", "1024"); + // Note: Low values of socket.txBufSize can trigger very poor + // performance. Set it just low enough to ensure that the + // non-blocking write servlet will see isReady() == false + Assert.assertTrue(tomcat.getConnector().setProperty("socket.txBufSize", "1048576")); tomcat.start(); SocketFactory factory = SocketFactory.getDefault(); @@ -192,11 +195,15 @@ int readSinceLastPause = 0; while (read != -1) { read = is.read(buffer); + if (readSinceLastPause == 0) { + log.info("Reading data"); + } if (read > 0) { result.append(buffer, 0, read); } readSinceLastPause += read; if (readSinceLastPause > WRITE_SIZE / 16) { + log.info("Read " + readSinceLastPause + " bytes, pause 500ms"); readSinceLastPause = 0; Thread.sleep(500); } @@ -319,7 +326,10 @@ String servletName = NBWriteServlet.class.getName(); Tomcat.addServlet(ctx, servletName, servlet); ctx.addServletMappingDecoded("/", servletName); - tomcat.getConnector().setProperty("socket.txBufSize", "1024"); + // Note: Low values of socket.txBufSize can trigger very poor + // performance. Set it just low enough to ensure that the + // non-blocking write servlet will see isReady() == false + Assert.assertTrue(tomcat.getConnector().setProperty("socket.txBufSize", "1048576")); tomcat.start(); SocketFactory factory = SocketFactory.getDefault(); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/servlets/DefaultServletEncodingBaseTest.java tomcat9-9.0.31/test/org/apache/catalina/servlets/DefaultServletEncodingBaseTest.java --- tomcat9-9.0.27/test/org/apache/catalina/servlets/DefaultServletEncodingBaseTest.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/servlets/DefaultServletEncodingBaseTest.java 2020-02-05 19:26:48.000000000 +0000 @@ -200,9 +200,9 @@ int rc = getUrl(target, res, headers); Assert.assertEquals(HttpServletResponse.SC_OK, rc); - List values = headers.get("Content-Type"); - if (values != null && values.size() == 1) { - MediaType mediaType = MediaType.parseMediaType(new StringReader(values.get(0))); + 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)); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/session/FileStoreTest.java tomcat9-9.0.31/test/org/apache/catalina/session/FileStoreTest.java --- tomcat9-9.0.27/test/org/apache/catalina/session/FileStoreTest.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/session/FileStoreTest.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,101 @@ +/* + * 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.session; + +import java.io.File; +import java.io.IOException; + +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.apache.catalina.Manager; +import org.apache.tomcat.unittest.TesterContext; +import org.apache.tomcat.unittest.TesterServletContext; +import org.apache.tomcat.util.http.fileupload.FileUtils; + +public class FileStoreTest { + + private static final String SESS_TEMPPATH = "SESS_TEMP"; + private static final File dir = new File(SESS_TEMPPATH); + private static FileStore fileStore; + private static File file1 = new File(SESS_TEMPPATH + "/tmp1.session"); + private static File file2 = new File(SESS_TEMPPATH + "/tmp2.session"); + private static Manager manager = new StandardManager(); + + + @BeforeClass + public static void setup() { + TesterContext testerContext = new TesterContext(); + testerContext.setServletContext(new TesterServletContext()); + manager.setContext(testerContext); + fileStore = new FileStore(); + fileStore.setManager(manager); + } + + + @AfterClass + public static void cleanup() throws IOException { + FileUtils.cleanDirectory(dir); + FileUtils.deleteDirectory(dir); + } + + + @Before + public void beforeEachTest() throws IOException { + fileStore.setDirectory(SESS_TEMPPATH); + if (!dir.mkdir()) { + Assert.fail(); + } + if (!file1.createNewFile()) { + Assert.fail(); + } + if (!file2.createNewFile()) { + Assert.fail(); + } + } + + + @Test + public void getSize() throws Exception { + Assert.assertEquals(2, fileStore.getSize()); + } + + + @Test + public void clear() throws Exception { + fileStore.clear(); + Assert.assertEquals(0, fileStore.getSize()); + } + + + @Test + public void keys() throws Exception { + Assert.assertArrayEquals(new String[]{"tmp1", "tmp2"}, fileStore.keys()); + fileStore.clear(); + Assert.assertArrayEquals(new String[]{}, fileStore.keys()); + } + + + @Test + public void removeTest() throws Exception { + fileStore.remove("tmp1"); + Assert.assertEquals(1, fileStore.getSize()); + } +} \ No newline at end of file diff -Nru tomcat9-9.0.27/test/org/apache/catalina/startup/SimpleHttpClient.java tomcat9-9.0.31/test/org/apache/catalina/startup/SimpleHttpClient.java --- tomcat9-9.0.27/test/org/apache/catalina/startup/SimpleHttpClient.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/startup/SimpleHttpClient.java 2020-02-05 19:26:48.000000000 +0000 @@ -56,6 +56,7 @@ public static final String REDIRECT_302 = "HTTP/1.1 302 "; public static final String REDIRECT_303 = "HTTP/1.1 303 "; public static final String FAIL_400 = "HTTP/1.1 400 "; + public static final String FORBIDDEN_403 = "HTTP/1.1 403 "; public static final String FAIL_404 = "HTTP/1.1 404 "; public static final String FAIL_405 = "HTTP/1.1 405 "; public static final String TIMEOUT_408 = "HTTP/1.1 408 "; @@ -187,7 +188,7 @@ socket = new Socket(); socket.setSoTimeout(soTimeout); socket.connect(addr,connectTimeout); - OutputStream os = socket.getOutputStream(); + OutputStream os = createOutputStream(socket); writer = new OutputStreamWriter(os, encoding); InputStream is = socket.getInputStream(); Reader r = new InputStreamReader(is, encoding); @@ -197,6 +198,10 @@ connect(0,0); } + protected OutputStream createOutputStream(Socket socket) throws IOException { + return socket.getOutputStream(); + } + public void processRequest() throws IOException, InterruptedException { processRequest(true); } @@ -444,6 +449,10 @@ return responseLineStartsWith(FAIL_400); } + public boolean isResponse403() { + return responseLineStartsWith(FORBIDDEN_403); + } + public boolean isResponse404() { return responseLineStartsWith(FAIL_404); } @@ -481,4 +490,4 @@ } public abstract boolean isResponseBodyOK(); -} \ No newline at end of file +} diff -Nru tomcat9-9.0.27/test/org/apache/catalina/startup/TestWebappServiceLoader.java tomcat9-9.0.31/test/org/apache/catalina/startup/TestWebappServiceLoader.java --- tomcat9-9.0.27/test/org/apache/catalina/startup/TestWebappServiceLoader.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/startup/TestWebappServiceLoader.java 2020-02-05 19:26:48.000000000 +0000 @@ -32,6 +32,9 @@ import org.junit.Test; import org.apache.catalina.Context; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.WebResourceRoot; +import org.apache.catalina.webresources.StandardRoot; import org.apache.tomcat.unittest.TesterContext; import org.easymock.EasyMock; import org.easymock.IMocksControl; @@ -101,6 +104,8 @@ List jars = Arrays.asList("jar1.jar", "dir/"); EasyMock.expect(servletContext.getAttribute(ServletContext.ORDERED_LIBS)) .andReturn(jars); + EasyMock.expect(servletContext.getResource("/WEB-INF/classes/" + CONFIG_FILE)) + .andReturn(null); EasyMock.expect(servletContext.getResource("/WEB-INF/lib/jar1.jar")) .andReturn(url1); loader.parseConfigFile(EasyMock.isA(LinkedHashSet.class), EasyMock.eq(sci1)); @@ -181,10 +186,19 @@ private static class ExtendedTesterContext extends TesterContext { private final ServletContext servletContext; private final ClassLoader parent; + private final WebResourceRoot resources; public ExtendedTesterContext(ServletContext servletContext, ClassLoader parent) { this.servletContext = servletContext; this.parent = parent; + // Empty resources - any non-null returns will be mocked on the + // ServletContext + this.resources = new StandardRoot(this); + try { + this.resources.start(); + } catch (LifecycleException e) { + throw new IllegalStateException(e); + } } @Override @@ -202,5 +216,9 @@ return parent; } + @Override + public WebResourceRoot getResources() { + return resources; + } } } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/startup/TomcatBaseTest.java tomcat9-9.0.31/test/org/apache/catalina/startup/TomcatBaseTest.java --- tomcat9-9.0.27/test/org/apache/catalina/startup/TomcatBaseTest.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/startup/TomcatBaseTest.java 2020-02-05 19:26:48.000000000 +0000 @@ -170,12 +170,11 @@ String protocol = getProtocol(); Connector connector = new Connector(protocol); // Listen only on localhost - connector.setAttribute("address", - InetAddress.getByName("localhost").getHostAddress()); + Assert.assertTrue(connector.setProperty("address", InetAddress.getByName("localhost").getHostAddress())); // Use random free port connector.setPort(0); // Mainly set to reduce timeouts during async tests - connector.setAttribute("connectionTimeout", "3000"); + Assert.assertTrue(connector.setProperty("connectionTimeout", "3000")); tomcat.getService().addConnector(connector); tomcat.setConnector(connector); @@ -185,7 +184,6 @@ AprLifecycleListener listener = new AprLifecycleListener(); listener.setSSLRandomSeed("/dev/urandom"); server.addLifecycleListener(listener); - connector.setAttribute("pollerThreadCount", Integer.valueOf(1)); } File catalinaBase = getTemporaryDirectory(); @@ -693,8 +691,13 @@ connection.connect(); int rc = connection.getResponseCode(); if (resHead != null) { - Map> head = connection.getHeaderFields(); - resHead.putAll(head); + // Skip the entry with null key that is used for the response line + // that some Map implementations may not accept. + for (Map.Entry> entry : connection.getHeaderFields().entrySet()) { + if (entry.getKey() != null) { + resHead.put(entry.getKey(), entry.getValue()); + } + } } InputStream is; if (rc < 400) { @@ -824,6 +827,29 @@ } } + protected static String getSingleHeader(String header, Map> headers) { + // Assume headers is never null + + // Assume that either: + // a) is correct since HTTP headers are case insensitive but most Map + // implementations are case-sensitive; or + // b) CaseInsensitiveKeyMap or similar is used + List headerValues = headers.get(header); + + // Looking for a single header. No matches are OK + if (headerValues == null) { + return null; + } + + // Found a single header - return the header value + if (headerValues.size() == 1) { + return headerValues.get(0); + } + + // More than one header value is an error + throw new IllegalStateException("Found multiple headers for [" + header + "]"); + } + private static class TomcatWithFastSessionIDs extends Tomcat { @Override diff -Nru tomcat9-9.0.27/test/org/apache/catalina/tribes/test/transport/SocketNioReceive.java tomcat9-9.0.31/test/org/apache/catalina/tribes/test/transport/SocketNioReceive.java --- tomcat9-9.0.27/test/org/apache/catalina/tribes/test/transport/SocketNioReceive.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/tribes/test/transport/SocketNioReceive.java 2020-02-05 19:26:48.000000000 +0000 @@ -35,6 +35,7 @@ static DecimalFormat df = new DecimalFormat("##.00"); static double seconds = 0; + protected static final Object mutex = new Object(); public static void main(String[] args) throws Exception { Member mbr = new MemberImpl("localhost", 9999, 0); ChannelData data = new ChannelData(); diff -Nru tomcat9-9.0.27/test/org/apache/catalina/users/MemoryUserDatabaseTests.java tomcat9-9.0.31/test/org/apache/catalina/users/MemoryUserDatabaseTests.java --- tomcat9-9.0.27/test/org/apache/catalina/users/MemoryUserDatabaseTests.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/users/MemoryUserDatabaseTests.java 2020-02-05 19:26:48.000000000 +0000 @@ -19,11 +19,7 @@ import java.io.BufferedWriter; import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileWriter; -import java.io.IOException; -import java.net.URI; import java.security.Principal; import java.util.HashSet; import java.util.Iterator; @@ -34,8 +30,6 @@ import org.junit.Test; import org.apache.catalina.User; -import org.apache.tomcat.util.file.ConfigFileLoader; -import org.apache.tomcat.util.file.ConfigurationSource; public class MemoryUserDatabaseTests { private static File TEST_FILE = new File(System.getProperty("java.io.tmpdir"), "tomcat-users.xml"); @@ -58,37 +52,8 @@ + ""); } - // MemoryUserDatabase requires the use of ConfigFileLoader/ConfigurationSource - ConfigFileLoader.setSource(new ConfigurationSource() { - protected final File userDir = new File(System.getProperty("java.io.tmpdir")); - protected final URI userDirUri = userDir.toURI(); - @Override - public Resource getResource(String name) throws IOException { - File f = new File(name); - if (!f.isAbsolute()) { - f = new File(userDir, name); - } - if (f.isFile()) { - return new Resource(new FileInputStream(f), f.toURI()); - } else { - throw new FileNotFoundException(name); - } - } - @Override - public URI getURI(String name) { - File f = new File(name); - if (!f.isAbsolute()) { - f = new File(userDir, name); - } - if (f.isFile()) { - return f.toURI(); - } - return userDirUri.resolve(name); - } - }); - db = new MemoryUserDatabase(); - db.setPathname(TEST_FILE.getAbsolutePath()); + db.setPathname(TEST_FILE.toURI().toURL().toString()); db.open(); } diff -Nru tomcat9-9.0.27/test/org/apache/catalina/valves/rewrite/TestQuotedStringTokenizer.java tomcat9-9.0.31/test/org/apache/catalina/valves/rewrite/TestQuotedStringTokenizer.java --- tomcat9-9.0.27/test/org/apache/catalina/valves/rewrite/TestQuotedStringTokenizer.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/valves/rewrite/TestQuotedStringTokenizer.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,71 @@ +/* + * 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.valves.rewrite; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.hamcrest.CoreMatchers; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TestQuotedStringTokenizer { + + private String inputText; + private List tokens; + + @Parameters(name = "{index}: tokenize({0}) = {1}") + public static Collection data() { + return Arrays.asList(new Object[][] { { null, Collections.emptyList() }, { "", Collections.emptyList() }, + { " \t\r\n", Collections.emptyList() }, { "simple", Arrays.asList("simple") }, + { "more than one word", Arrays.asList("more", "than", "one", "word") }, + { "\"quoted text\"", Arrays.asList("quoted text") }, + { " mixed \t\"words with\\\"\" escapes", Arrays.asList("mixed", "words with\"", "escapes") }, + { "# comment", Collections.emptyList() }, + { "Something # and then a comment", Arrays.asList("Something") }, + { "\"Quoted with a #\" which is not a comment", + Arrays.asList("Quoted with a #", "which", "is", "not", "a", "comment") } }); + } + + public TestQuotedStringTokenizer(String inputText, List tokens) { + this.inputText = inputText; + this.tokens = tokens; + } + + @Test + public void testTokenize() { + QuotedStringTokenizer tokenizer = new QuotedStringTokenizer(inputText); + List result = new ArrayList<>(); + int count = tokens.size(); + while (tokenizer.hasMoreTokens()) { + Assert.assertThat(Integer.valueOf(tokenizer.countTokens()), CoreMatchers.is(Integer.valueOf(count))); + result.add(tokenizer.nextToken()); + count--; + } + Assert.assertThat(Integer.valueOf(tokenizer.countTokens()), CoreMatchers.is(Integer.valueOf(0))); + Assert.assertThat(tokens, CoreMatchers.is(result)); + } + +} diff -Nru tomcat9-9.0.27/test/org/apache/catalina/webresources/TestCachedResource.java tomcat9-9.0.31/test/org/apache/catalina/webresources/TestCachedResource.java --- tomcat9-9.0.27/test/org/apache/catalina/webresources/TestCachedResource.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/catalina/webresources/TestCachedResource.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,134 @@ +/* + * 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.webresources; + +import java.io.File; +import java.io.InputStream; +import java.net.JarURLConnection; +import java.net.URL; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.catalina.Context; +import org.apache.catalina.WebResourceRoot; +import org.apache.catalina.core.StandardHost; +import org.apache.catalina.startup.Tomcat; +import org.apache.catalina.startup.TomcatBaseTest; + +public class TestCachedResource extends TomcatBaseTest { + + // https://bz.apache.org/bugzilla/show_bug.cgi?id=63872 + @Test + public void testUrlFileFromDirectory() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + File docBase = new File("test/webresources/dir1"); + Context ctx = tomcat.addWebapp("/test", docBase.getAbsolutePath()); + tomcat.start(); + + WebResourceRoot root = ctx.getResources(); + + URL d1 = root.getResource("/d1").getURL(); + + URL d1f1 = new URL(d1, "d1-f1.txt"); + + try (InputStream is = d1f1.openStream()) { + Assert.assertNotNull(is); + } + } + + + // https://bz.apache.org/bugzilla/show_bug.cgi?id=63970 + @Test + public void testCachedJarUrlConnection() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + File docBase = new File("test/webresources/war-url-connection.war"); + Context ctx = tomcat.addWebapp("/test", docBase.getAbsolutePath()); + tomcat.start(); + + WebResourceRoot root = ctx.getResources(); + + // WAR contains a resources JAR so this should return a JAR URL + URL webinf = root.getResource("/index.html").getURL(); + + Assert.assertEquals("jar", webinf.getProtocol()); + JarURLConnection jarConn = null; + try { + jarConn = (JarURLConnection) webinf.openConnection(); + } catch (ClassCastException e) { + // Ignore + } + Assert.assertNotNull(jarConn); + } + + + @Test + public void testDirectoryListingsPackedWar() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + File docBase = new File("test/webresources/war-url-connection.war"); + Context ctx = tomcat.addWebapp("/test", docBase.getAbsolutePath()); + ((StandardHost) tomcat.getHost()).setUnpackWARs(false); + tomcat.start(); + + WebResourceRoot root = ctx.getResources(); + + URL d1 = root.getResource("/").getURL(); + + try (InputStream is = d1.openStream()) { + Assert.assertNotNull(is); + } + } + + + @Test + public void testDirectoryListingsWar() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + File docBase = new File("test/webresources/war-url-connection.war"); + Context ctx = tomcat.addWebapp("/test", docBase.getAbsolutePath()); + tomcat.start(); + + WebResourceRoot root = ctx.getResources(); + + URL d1 = root.getResource("/").getURL(); + + try (InputStream is = d1.openStream()) { + Assert.assertNotNull(is); + } + } + + + @Test + public void testDirectoryListingsDir() throws Exception { + + Tomcat tomcat = getTomcatInstance(); + File docBase = new File("test/webresources/dir1"); + Context ctx = tomcat.addWebapp("/test", docBase.getAbsolutePath()); + tomcat.start(); + + WebResourceRoot root = ctx.getResources(); + + URL d1 = root.getResource("/d1").getURL(); + + try (InputStream is = d1.openStream()) { + Assert.assertNotNull(is); + } + } +} diff -Nru tomcat9-9.0.27/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java tomcat9-9.0.31/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java --- tomcat9-9.0.27/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java 2020-02-05 19:26:48.000000000 +0000 @@ -33,14 +33,27 @@ import javax.servlet.http.HttpServletResponse; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.apache.catalina.Context; +import org.apache.catalina.connector.Connector; import org.apache.catalina.startup.Tomcat; import org.apache.catalina.startup.TomcatBaseTest; public class TestAbstractAjpProcessor extends TomcatBaseTest { + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + Connector c = getTomcatInstance().getConnector(); + c.setProperty("secretRequired", "false"); + c.setProperty("allowedRequestAttributesPattern", "MYATTRIBUTE.*"); + } + + @Override protected String getProtocol() { /* @@ -76,7 +89,7 @@ Map params = desc.getParams(); Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setProperty("packetSize", Integer.toString(ajpPacketSize)); + Assert.assertTrue(tomcat.getConnector().setProperty("packetSize", Integer.toString(ajpPacketSize))); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -193,13 +206,13 @@ case "REQUEST-REMOTE-USER": /* request.getRemoteUser() will not trust the AJP * info if tomcatAuthentication is set. */ - tomcat.getConnector().setProperty("tomcatAuthentication", "false"); + Assert.assertTrue(tomcat.getConnector().setProperty("tomcatAuthentication", "false")); forwardMessage.addAttribute(0x03, value); break; case "REQUEST-AUTH-TYPE": /* request.getAuthType() will not trust the AJP * info if tomcatAuthentication is set. */ - tomcat.getConnector().setProperty("tomcatAuthentication", "false"); + Assert.assertTrue(tomcat.getConnector().setProperty("tomcatAuthentication", "false")); forwardMessage.addAttribute(0x04, value); break; case "REQUEST-QUERY-STRING": @@ -490,7 +503,7 @@ @Test public void testSecret() throws Exception { Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setProperty("requiredSecret", "RIGHTSECRET"); + Assert.assertTrue(tomcat.getConnector().setProperty("requiredSecret", "RIGHTSECRET")); tomcat.start(); // No file system docBase required @@ -550,7 +563,7 @@ @Test public void testKeepAlive() throws Exception { Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setProperty("connectionTimeout", "-1"); + Assert.assertTrue(tomcat.getConnector().setProperty("connectionTimeout", "-1")); tomcat.start(); // No file system docBase required @@ -771,7 +784,7 @@ int ajpPacketSize = 16000; Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setProperty("packetSize", Integer.toString(ajpPacketSize)); + Assert.assertTrue(tomcat.getConnector().setProperty("packetSize", Integer.toString(ajpPacketSize))); // No file system docBase required Context ctx = tomcat.addContext("", null); diff -Nru tomcat9-9.0.27/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java tomcat9-9.0.31/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java --- tomcat9-9.0.27/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/http11/filters/TestChunkedInputFilter.java 2020-02-05 19:26:48.000000000 +0000 @@ -101,7 +101,7 @@ Context ctx = tomcat.addContext("", null); // Configure allowed trailer headers - tomcat.getConnector().setProperty("allowedTrailerHeaders", "x-trailer1,x-trailer2"); + Assert.assertTrue(tomcat.getConnector().setProperty("allowedTrailerHeaders", "x-trailer1,x-trailer2")); EchoHeaderServlet servlet = new EchoHeaderServlet(expectPass); Tomcat.addServlet(ctx, "servlet", servlet); @@ -170,7 +170,7 @@ ctx.addServletMappingDecoded("/", "servlet"); // Limit the size of the trailing header - tomcat.getConnector().setProperty("maxTrailerSize", "10"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxTrailerSize", "10")); tomcat.start(); String[] request = new String[]{ @@ -223,8 +223,8 @@ // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setProperty( - "maxExtensionSize", Integer.toString(EXT_SIZE_LIMIT)); + Assert.assertTrue(tomcat.getConnector().setProperty( + "maxExtensionSize", Integer.toString(EXT_SIZE_LIMIT))); // No file system docBase required Context ctx = tomcat.addContext("", null); diff -Nru tomcat9-9.0.27/test/org/apache/coyote/http11/TestHttp11InputBuffer.java tomcat9-9.0.31/test/org/apache/coyote/http11/TestHttp11InputBuffer.java --- tomcat9-9.0.27/test/org/apache/coyote/http11/TestHttp11InputBuffer.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/http11/TestHttp11InputBuffer.java 2020-02-05 19:26:48.000000000 +0000 @@ -163,13 +163,13 @@ @Test - public void testBug51557Separators() throws Exception { + public void testBug51557SeparatorsInName() throws Exception { char httpSeparators[] = new char[] { '\t', ' ', '\"', '(', ')', ',', '/', ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' }; for (char s : httpSeparators) { - doTestBug51557Char(s); + doTestBug51557CharInName(s); tearDown(); setUp(); } @@ -177,13 +177,38 @@ @Test - public void testBug51557Ctl() throws Exception { + public void testBug51557CtlInName() throws Exception { for (int i = 0; i < 31; i++) { - doTestBug51557Char((char) i); + doTestBug51557CharInName((char) i); + tearDown(); + setUp(); + } + doTestBug51557CharInName((char) 127); + } + + + @Test + public void testBug51557CtlInValue() throws Exception { + for (int i = 0; i < 31; i++) { + if (i == '\t') { + // TAB is allowed + continue; + } + doTestBug51557InvalidCharInValue((char) i); + tearDown(); + setUp(); + } + doTestBug51557InvalidCharInValue((char) 127); + } + + + @Test + public void testBug51557ObsTextInValue() throws Exception { + for (int i = 128; i < 255; i++) { + doTestBug51557ValidCharInValue((char) i); tearDown(); setUp(); } - doTestBug51557Char((char) 127); } @@ -226,7 +251,7 @@ } - private void doTestBug51557Char(char s) { + private void doTestBug51557CharInName(char s) { Bug51557Client client = new Bug51557Client("X-Bug" + s + "51557", "invalid"); @@ -236,6 +261,29 @@ Assert.assertTrue(client.isResponseBodyOK()); } + + private void doTestBug51557InvalidCharInValue(char s) { + Bug51557Client client = + new Bug51557Client("X-Bug51557-Invalid", "invalid" + s + "invalid"); + + client.doRequest(); + Assert.assertTrue("Testing [" + (int) s + "]", client.isResponse200()); + Assert.assertEquals("Testing [" + (int) s + "]", "abcd", client.getResponseBody()); + Assert.assertTrue(client.isResponseBodyOK()); + } + + + private void doTestBug51557ValidCharInValue(char s) { + Bug51557Client client = + new Bug51557Client("X-Bug51557-Valid", "valid" + s + "valid"); + + client.doRequest(); + Assert.assertTrue("Testing [" + (int) s + "]", client.isResponse200()); + Assert.assertEquals("Testing [" + (int) s + "]", "valid" + s + "validabcd", client.getResponseBody()); + Assert.assertTrue(client.isResponseBodyOK()); + } + + /** * Bug 51557 test client. */ @@ -243,12 +291,12 @@ private final String headerName; private final String headerLine; - private final boolean rejectIllegalHeaderName; + private final boolean rejectIllegalHeader; public Bug51557Client(String headerName) { this.headerName = headerName; this.headerLine = headerName; - this.rejectIllegalHeaderName = false; + this.rejectIllegalHeader = false; } public Bug51557Client(String headerName, String headerValue) { @@ -256,10 +304,10 @@ } public Bug51557Client(String headerName, String headerValue, - boolean rejectIllegalHeaderName) { + boolean rejectIllegalHeader) { this.headerName = headerName; this.headerLine = headerName + ": " + headerValue; - this.rejectIllegalHeaderName = rejectIllegalHeaderName; + this.rejectIllegalHeader = rejectIllegalHeader; } private Exception doRequest() { @@ -273,8 +321,8 @@ try { Connector connector = tomcat.getConnector(); - connector.setProperty("rejectIllegalHeaderName", - Boolean.toString(rejectIllegalHeaderName)); + Assert.assertTrue(connector.setProperty( + "rejectIllegalHeader", Boolean.toString(rejectIllegalHeader))); tomcat.start(); setPort(connector.getLocalPort()); @@ -548,7 +596,7 @@ try { Connector connector = tomcat.getConnector(); - connector.setProperty("rejectIllegalHeaderName", "false"); + Assert.assertTrue(connector.setProperty("rejectIllegalHeader", "false")); tomcat.start(); setPort(connector.getLocalPort()); diff -Nru tomcat9-9.0.27/test/org/apache/coyote/http11/TestHttp11Processor.java tomcat9-9.0.31/test/org/apache/coyote/http11/TestHttp11Processor.java --- tomcat9-9.0.27/test/org/apache/coyote/http11/TestHttp11Processor.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/http11/TestHttp11Processor.java 2020-02-05 19:26:48.000000000 +0000 @@ -24,6 +24,7 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Reader; +import java.io.StringReader; import java.io.Writer; import java.net.InetSocketAddress; import java.net.Socket; @@ -33,7 +34,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.concurrent.CountDownLatch; @@ -58,6 +58,7 @@ import org.apache.tomcat.util.buf.ByteChunk; import org.apache.tomcat.util.descriptor.web.SecurityCollection; import org.apache.tomcat.util.descriptor.web.SecurityConstraint; +import org.apache.tomcat.util.http.parser.TokenList; public class TestHttp11Processor extends TomcatBaseTest { @@ -67,7 +68,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -399,10 +400,9 @@ responseHeaders); Assert.assertEquals(HttpServletResponse.SC_OK, rc); - Assert.assertTrue(responseHeaders.containsKey("Transfer-Encoding")); - List encodings = responseHeaders.get("Transfer-Encoding"); - Assert.assertEquals(1, encodings.size()); - Assert.assertEquals("chunked", encodings.get(0)); + + String transferEncoding = getSingleHeader("Transfer-Encoding", responseHeaders); + Assert.assertEquals("chunked", transferEncoding); } @Test @@ -428,10 +428,8 @@ Assert.assertEquals(HttpServletResponse.SC_OK, rc); - Assert.assertTrue(responseHeaders.containsKey("Connection")); - List connections = responseHeaders.get("Connection"); - Assert.assertEquals(1, connections.size()); - Assert.assertEquals("close", connections.get(0)); + String connection = getSingleHeader("Connection", responseHeaders); + Assert.assertEquals("close", connection); Assert.assertFalse(responseHeaders.containsKey("Transfer-Encoding")); @@ -461,9 +459,7 @@ tomcat.start(); ByteChunk responseBody = new ByteChunk(); - Map> responseHeaders = new HashMap<>(); - int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, - responseHeaders); + int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, null); Assert.assertEquals(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, rc); if (responseBody.getLength() > 0) { @@ -484,8 +480,8 @@ @Test public void testBug55772() throws Exception { Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setProperty("processorCache", "1"); - tomcat.getConnector().setProperty("maxThreads", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("processorCache", "1")); + Assert.assertTrue(tomcat.getConnector().setProperty("maxThreads", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -575,26 +571,37 @@ tomcat.start(); - byte[] requestBody = "HelloWorld".getBytes(StandardCharsets.UTF_8); - Map> reqHeaders = null; + String request = + "POST /echo HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF; if (useExpectation) { - reqHeaders = new HashMap<>(); - List expectation = new ArrayList<>(); - expectation.add("100-continue"); - reqHeaders.put("Expect", expectation); + request += "Expect: 100-continue" + SimpleHttpClient.CRLF; + } + request += SimpleHttpClient.CRLF + + "HelloWorld"; + + Client client = new Client(tomcat.getConnector().getLocalPort()); + client.setRequest(new String[] {request}); + + client.connect(); + client.processRequest(); + + Assert.assertTrue(client.isResponse403()); + String connectionHeaderValue = null; + for (String header : client.getResponseHeaders()) { + if (header.startsWith("Connection:")) { + connectionHeaderValue = header.substring(header.indexOf(':') + 1).trim(); + break; + } } - ByteChunk responseBody = new ByteChunk(); - Map> responseHeaders = new HashMap<>(); - int rc = postUrl(requestBody, "http://localhost:" + getPort() + "/echo", - responseBody, reqHeaders, responseHeaders); - Assert.assertEquals(HttpServletResponse.SC_FORBIDDEN, rc); - List connectionHeaders = responseHeaders.get("Connection"); if (useExpectation) { + List connectionHeaders = new ArrayList<>(); + TokenList.parseTokenList(new StringReader(connectionHeaderValue), connectionHeaders); Assert.assertEquals(1, connectionHeaders.size()); - Assert.assertEquals("close", connectionHeaders.get(0).toLowerCase(Locale.ENGLISH)); + Assert.assertEquals("close", connectionHeaders.get(0)); } else { - Assert.assertNull(connectionHeaders); + Assert.assertNull(connectionHeaderValue); } } @@ -988,8 +995,8 @@ int rc = getUrl("http://localhost:" + getPort() + "/test", responseBody, responseHeaders); Assert.assertEquals(HttpServletResponse.SC_RESET_CONTENT, rc); - Assert.assertNotNull(responseHeaders.get("Content-Length")); - Assert.assertTrue("0".equals(responseHeaders.get("Content-Length").get(0))); + String contentLength = getSingleHeader("Content-Length", responseHeaders); + Assert.assertEquals("0", contentLength); Assert.assertTrue(responseBody.getLength() == 0); } @@ -1013,7 +1020,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1049,7 +1056,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1082,7 +1089,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1113,7 +1120,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1145,7 +1152,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1177,7 +1184,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1214,7 +1221,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1251,7 +1258,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1288,7 +1295,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1324,7 +1331,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1360,7 +1367,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1398,7 +1405,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1436,7 +1443,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1475,7 +1482,7 @@ // This setting means the connection will be closed at the end of the // request - tomcat.getConnector().setAttribute("maxKeepAliveRequests", "1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxKeepAliveRequests", "1")); // No file system docBase required Context ctx = tomcat.addContext("", null); @@ -1503,6 +1510,105 @@ } + @Test + public void testKeepAliveHeader01() throws Exception { + doTestKeepAliveHeader(false, 3000, 10); + } + + @Test + public void testKeepAliveHeader02() throws Exception { + doTestKeepAliveHeader(true, 5000, 1); + } + + @Test + public void testKeepAliveHeader03() throws Exception { + doTestKeepAliveHeader(true, 5000, 10); + } + + @Test + public void testKeepAliveHeader04() throws Exception { + doTestKeepAliveHeader(true, -1, 10); + } + + @Test + public void testKeepAliveHeader05() throws Exception { + doTestKeepAliveHeader(true, -1, 1); + } + + @Test + public void testKeepAliveHeader06() throws Exception { + doTestKeepAliveHeader(true, -1, -1); + } + + private void doTestKeepAliveHeader(boolean sendKeepAlive, int keepAliveTimeout, + int maxKeepAliveRequests) throws Exception { + Tomcat tomcat = getTomcatInstance(); + + tomcat.getConnector().setProperty("keepAliveTimeout", Integer.toString(keepAliveTimeout)); + tomcat.getConnector().setProperty("maxKeepAliveRequests", Integer.toString(maxKeepAliveRequests)); + + // No file system docBase required + Context ctx = tomcat.addContext("", null); + + // Add servlet + Tomcat.addServlet(ctx, "TesterServlet", new TesterServlet()); + ctx.addServletMappingDecoded("/foo", "TesterServlet"); + + tomcat.start(); + + String request = + "GET /foo HTTP/1.1" + SimpleHttpClient.CRLF + + "Host: localhost:" + getPort() + SimpleHttpClient.CRLF; + + if (sendKeepAlive) { + request += "Connection: keep-alive" + SimpleHttpClient.CRLF; + } + + request += SimpleHttpClient.CRLF; + + Client client = new Client(tomcat.getConnector().getLocalPort()); + client.setRequest(new String[] {request}); + + client.connect(); + client.processRequest(false); + + Assert.assertTrue(client.isResponse200()); + + String connectionHeaderValue = null; + String keepAliveHeaderValue = null; + for (String header : client.getResponseHeaders()) { + if (header.startsWith("Connection:")) { + connectionHeaderValue = header.substring(header.indexOf(':') + 1).trim(); + } + if (header.startsWith("Keep-Alive:")) { + keepAliveHeaderValue = header.substring(header.indexOf(':') + 1).trim(); + } + } + + if (!sendKeepAlive || keepAliveTimeout < 0 + && (maxKeepAliveRequests < 0 || maxKeepAliveRequests > 1)) { + Assert.assertNull(connectionHeaderValue); + Assert.assertNull(keepAliveHeaderValue); + } else { + List connectionHeaders = new ArrayList<>(); + TokenList.parseTokenList(new StringReader(connectionHeaderValue), connectionHeaders); + + if (sendKeepAlive && keepAliveTimeout > 0 && + (maxKeepAliveRequests < 0 || maxKeepAliveRequests > 1)) { + Assert.assertEquals(1, connectionHeaders.size()); + Assert.assertEquals("keep-alive", connectionHeaders.get(0)); + Assert.assertEquals("timeout=" + keepAliveTimeout / 1000L, keepAliveHeaderValue); + } + + if (sendKeepAlive && maxKeepAliveRequests == 1) { + Assert.assertEquals(1, connectionHeaders.size()); + Assert.assertEquals("close", connectionHeaders.get(0)); + Assert.assertNull(keepAliveHeaderValue); + } + } + } + + /** * Test servlet that prints out the values of * HttpServletRequest.getServerName() and diff -Nru tomcat9-9.0.27/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java tomcat9-9.0.31/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java --- tomcat9-9.0.27/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java 2020-02-05 19:26:48.000000000 +0000 @@ -87,7 +87,7 @@ Class upgradeHandlerClass) throws Exception { // Setup Tomcat instance Tomcat tomcat = getTomcatInstance(); - tomcat.getConnector().setProperty("useAsyncIO", "true"); + Assert.assertTrue(tomcat.getConnector().setProperty("useAsyncIO", "true")); // No file system docBase required Context ctx = tomcat.addContext("", null); diff -Nru tomcat9-9.0.27/test/org/apache/coyote/http2/TestAsyncTimeout.java tomcat9-9.0.31/test/org/apache/coyote/http2/TestAsyncTimeout.java --- tomcat9-9.0.27/test/org/apache/coyote/http2/TestAsyncTimeout.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/http2/TestAsyncTimeout.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,226 @@ +/* + * 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.io.PrintWriter; +import java.nio.ByteBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +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.Wrapper; +import org.apache.catalina.startup.Tomcat; + +public class TestAsyncTimeout extends Http2TestBase { + + @Test + public void testTimeout() throws Exception { + enableHttp2(); + + Tomcat tomcat = getTomcatInstance(); + + Context ctxt = tomcat.addContext("", null); + // This is the target of the HTTP/2 upgrade request + Tomcat.addServlet(ctxt, "simple", new SimpleServlet()); + ctxt.addServletMappingDecoded("/simple", "simple"); + + // This is the servlet that does that actual test + // This latch is used to signal that that async thread used by the test + // has ended. It isn;t essential to the test but it allows the test to + // complete without Tmcat logging an error about a still running thread. + CountDownLatch latch = new CountDownLatch(1); + Wrapper w = Tomcat.addServlet(ctxt, "async", new AsyncTimeoutServlet(latch)); + w.setAsyncSupported(true); + ctxt.addServletMappingDecoded("/async", "async"); + tomcat.start(); + + openClientConnection(); + doHttpUpgrade(); + sendClientPreface(); + validateHttp2InitialResponse(); + + // Reset connection window size after initial response + sendWindowUpdate(0, SimpleServlet.CONTENT_LENGTH); + + // Include the response body in the trace so we can check for the PASS / + // FAIL text. + output.setTraceBody(true); + + // Send request + byte[] frameHeader = new byte[9]; + ByteBuffer headersPayload = ByteBuffer.allocate(128); + buildGetRequest(frameHeader, headersPayload, null, 3, "/async"); + writeFrame(frameHeader, headersPayload); + + // Headers + parser.readFrame(true); + // Body + parser.readFrame(true); + + // Check that the expected text was received + String trace = output.getTrace(); + Assert.assertTrue(trace, trace.contains("PASS")); + + latch.await(10, TimeUnit.SECONDS); + } + + + public static class AsyncTimeoutServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + private final CountDownLatch latch; + + public AsyncTimeoutServlet(CountDownLatch latch) { + this.latch = latch; + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws IOException { + + // The idea of this test is that the timeout kicks in after 2 + // seconds and stops the async thread early rather than letting it + // complete the full 5 seconds of processing. + final AsyncContext asyncContext = request.startAsync(); + + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("text/plain"); + response.setCharacterEncoding("UTF-8"); + + // Only want to call complete() once (else we get stack traces in + // the logs so use this to track when complete() is called). + AtomicBoolean completeCalled = new AtomicBoolean(false); + Ticker ticker = new Ticker(asyncContext, completeCalled); + TimeoutListener listener = new TimeoutListener(latch, ticker, completeCalled); + asyncContext.addListener(listener); + asyncContext.setTimeout(2000); + ticker.start(); + } + } + + + private static class Ticker extends Thread { + + private final AsyncContext asyncContext; + private final AtomicBoolean completeCalled; + private volatile boolean running = true; + + public Ticker(AsyncContext asyncContext, AtomicBoolean completeCalled) { + this.asyncContext = asyncContext; + this.completeCalled = completeCalled; + } + + public void end() { + running = false; + } + + @Override + public void run() { + try { + PrintWriter pw = asyncContext.getResponse().getWriter(); + int counter = 0; + + // If the test works running will be set too false before + // counter reaches 50. + while (running && counter < 50) { + Thread.sleep(100); + counter++; + pw.print("Tick " + counter); + } + // Need to call complete() here if the test fails but complete() + // should have been called by the listener. Use the flag to make + // sure we only call complete once. + if (completeCalled.compareAndSet(false, true)) { + asyncContext.complete(); + } + } catch (IOException | InterruptedException e) { + // Ignore + } + } + } + + + private static class TimeoutListener implements AsyncListener { + + private final AtomicBoolean ended = new AtomicBoolean(false); + private final CountDownLatch latch; + private final Ticker ticker; + private final AtomicBoolean completeCalled; + + public TimeoutListener(CountDownLatch latch, Ticker ticker, AtomicBoolean completeCalled) { + this.latch = latch; + this.ticker = ticker; + this.completeCalled = completeCalled; + } + + @Override + public void onTimeout(AsyncEvent event) throws IOException { + ticker.end(); + if (ended.compareAndSet(false, true)) { + PrintWriter pw = event.getAsyncContext().getResponse().getWriter(); + pw.write("PASS"); + pw.flush(); + // If the timeout fires we should always need to call complete() + // here but use the flag to be safe. + if (completeCalled.compareAndSet(false, true)) { + event.getAsyncContext().complete(); + } + } + } + + @Override + public void onStartAsync(AsyncEvent event) throws IOException { + // NO-OP + } + + @Override + public void onError(AsyncEvent event) throws IOException { + // NO-OP + } + + @Override + public void onComplete(AsyncEvent event) throws IOException { + if (ended.compareAndSet(false, true)) { + PrintWriter pw = event.getAsyncContext().getResponse().getWriter(); + pw.write("FAIL"); + pw.flush(); + } + try { + // Wait for the async thread to end before we signal that the + // test is complete. This avoids logging an exception about a + // still running thread when the unit test shuts down. + ticker.join(); + latch.countDown(); + } catch (InterruptedException e) { + // Ignore + } + } + } +} diff -Nru tomcat9-9.0.27/test/org/apache/coyote/http2/TestHttp2InitialConnection.java tomcat9-9.0.31/test/org/apache/coyote/http2/TestHttp2InitialConnection.java --- tomcat9-9.0.27/test/org/apache/coyote/http2/TestHttp2InitialConnection.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/http2/TestHttp2InitialConnection.java 2020-02-05 19:26:48.000000000 +0000 @@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.Locale; import org.junit.Assert; import org.junit.Test; @@ -85,6 +86,10 @@ request.append("\r\n"); // Settings request.append(settings); + // Locale - Force the en Locale else the i18n on the error page changes + // the size of the response body and that triggers a failure as the test + // checks the exact response length + request.append("Accept-Language: en\r\n"); // Request terminator request.append("\r\n"); @@ -110,22 +115,37 @@ * This will vary depending on where the test is run due to: * - The length of the version string that appears once in the error * page - * - The status header uses a UTF-8 EM dash. When running in an IDE + * - The status header uses a UTF-8 EN dash. When running in an IDE * the UTF-8 properties files will be used directly rather than * after native2ascii conversion. * * Note: The status header appears twice in the error page. */ int serverInfoLength = ServerInfo.getServerInfo().getBytes().length; - StringManager sm = StringManager.getManager(ErrorReportValve.class); + StringManager sm = StringManager.getManager( + ErrorReportValve.class.getPackage().getName(), Locale.ENGLISH); + String reason = sm.getString("http." + testData.getExpectedStatus() + ".reason"); + int descriptionLength = sm.getString("http." + testData.getExpectedStatus() + ".desc") + .getBytes(StandardCharsets.UTF_8).length; int statusHeaderLength = sm - .getString("errorReportValve.statusHeader", "", "") + .getString("errorReportValve.statusHeader", + String.valueOf(testData.getExpectedStatus()), reason) + .getBytes(StandardCharsets.UTF_8).length; + int typeLabelLength = sm.getString("errorReportValve.type") + .getBytes(StandardCharsets.UTF_8).length; + int statusReportLabelLength = sm.getString("errorReportValve.statusReport") + .getBytes(StandardCharsets.UTF_8).length; + int descriptionLabelLength = sm.getString("errorReportValve.description") .getBytes(StandardCharsets.UTF_8).length; - int len = 1073 + serverInfoLength + statusHeaderLength * 2; + // 196 bytes is the static length of the pure HTML code from the ErrorReportValve + int len = 196 + org.apache.catalina.util.TomcatCSS.TOMCAT_CSS + .getBytes(StandardCharsets.UTF_8).length + + typeLabelLength + statusReportLabelLength + descriptionLabelLength + + descriptionLength + serverInfoLength + statusHeaderLength * 2; String contentLength = String.valueOf(len); return getResponseBodyFrameTrace(streamId, testData.getExpectedStatus(), "text/html;charset=utf-8", - sm.getLocale().getLanguage(), contentLength, contentLength); + "en", contentLength, contentLength); } else { Assert.fail(); // To keep the IDE happy diff -Nru tomcat9-9.0.27/test/org/apache/coyote/http2/TestHttp2Limits.java tomcat9-9.0.31/test/org/apache/coyote/http2/TestHttp2Limits.java --- tomcat9-9.0.27/test/org/apache/coyote/http2/TestHttp2Limits.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/http2/TestHttp2Limits.java 2020-02-05 19:26:48.000000000 +0000 @@ -31,9 +31,12 @@ import org.apache.catalina.connector.Connector; import org.apache.coyote.http2.HpackEncoder.State; import org.apache.tomcat.util.http.MimeHeaders; +import org.apache.tomcat.util.res.StringManager; public class TestHttp2Limits extends Http2TestBase { + private static final StringManager sm = StringManager.getManager(TestHttp2Limits.class); + @Test public void testHeaderLimits1x128() throws Exception { // Well within limits @@ -247,6 +250,12 @@ break; } case CONNECTION_RESET: { + // This message uses i18n and needs to be used in a regular + // expression (since we don't know the connection ID). Generate the + // string as a regular expression and then replace '[' and ']' with + // the escaped values. + String limitMessage = sm.getString("http2Parser.headerLimitSize", "\\d++", "3"); + limitMessage = limitMessage.replace("[", "\\[").replace("]", "\\]"); // Connection reset. Connection ID will vary so use a pattern // On some platform / Connector combinations (e.g. Windows / APR), // the TCP connection close will be processed before the client gets @@ -257,7 +266,7 @@ try { parser.readFrame(true); Assert.assertThat(output.getTrace(), RegexMatcher.matchesRegex( - "0-Goaway-\\[1\\]-\\[11\\]-\\[Connection \\[\\d++\\], Stream \\[3\\], .*")); + "0-Goaway-\\[1\\]-\\[11\\]-\\[" + limitMessage + "\\]")); } catch (IOException se) { // Expected on some platforms } @@ -490,9 +499,14 @@ // NIO2 can sometimes send window updates depending timing skipWindowSizeFrames(); - // Connection ID will vary so use a pattern + // This message uses i18n and needs to be used in a regular + // expression (since we don't know the connection ID). Generate the + // string as a regular expression and then replace '[' and ']' with + // the escaped values. + String limitMessage = sm.getString("http2Parser.headerLimitSize", "\\d++", "3"); + limitMessage = limitMessage.replace("[", "\\[").replace("]", "\\]"); Assert.assertThat(output.getTrace(), RegexMatcher.matchesRegex( - "0-Goaway-\\[3\\]-\\[11\\]-\\[Connection \\[\\d++\\], Stream \\[3\\], .*")); + "0-Goaway-\\[3\\]-\\[11\\]-\\[" + limitMessage + "\\]")); break; } } diff -Nru tomcat9-9.0.27/test/org/apache/coyote/TestCompressionConfig.java tomcat9-9.0.31/test/org/apache/coyote/TestCompressionConfig.java --- tomcat9-9.0.27/test/org/apache/coyote/TestCompressionConfig.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/TestCompressionConfig.java 2020-02-05 19:26:48.000000000 +0000 @@ -29,16 +29,24 @@ @RunWith(Parameterized.class) public class TestCompressionConfig { - @Parameterized.Parameters(name = "{index}: headers[{0}], compress[{1}]") + @Parameterized.Parameters(name = "{index}: accept-encoding[{0}], ETag [{1}], NoCompressionStrongETag[{2}], compress[{3}]") public static Collection parameters() { List parameterSets = new ArrayList<>(); - parameterSets.add(new Object[] { new String[] { }, Boolean.FALSE }); - parameterSets.add(new Object[] { new String[] { "gzip" }, Boolean.TRUE }); - parameterSets.add(new Object[] { new String[] { "xgzip" }, Boolean.FALSE }); - parameterSets.add(new Object[] { new String[] { "<>gzip" }, Boolean.FALSE }); - parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, Boolean.TRUE }); - parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, Boolean.TRUE }); + parameterSets.add(new Object[] { new String[] { }, null, Boolean.TRUE, Boolean.FALSE }); + parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.TRUE }); + parameterSets.add(new Object[] { new String[] { "xgzip" }, null, Boolean.TRUE, Boolean.FALSE }); + parameterSets.add(new Object[] { new String[] { "<>gzip" }, null, Boolean.TRUE, Boolean.FALSE }); + parameterSets.add(new Object[] { new String[] { "foo", "gzip" }, null, Boolean.TRUE, Boolean.TRUE }); + parameterSets.add(new Object[] { new String[] { "<>", "gzip" }, null, Boolean.TRUE, Boolean.TRUE }); + + parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.TRUE, Boolean.TRUE }); + parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", Boolean.TRUE, Boolean.TRUE }); + parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", Boolean.TRUE, Boolean.FALSE }); + + parameterSets.add(new Object[] { new String[] { "gzip" }, null, Boolean.FALSE, Boolean.TRUE }); + parameterSets.add(new Object[] { new String[] { "gzip" }, "W/", Boolean.FALSE, Boolean.TRUE }); + parameterSets.add(new Object[] { new String[] { "gzip" }, "XX", Boolean.FALSE, Boolean.TRUE }); return parameterSets; } @@ -46,14 +54,20 @@ @Parameter(0) public String[] headers; @Parameter(1) + public String eTag; + @Parameter(2) + public Boolean noCompressionStrongETag; + @Parameter(3) public Boolean compress; + @SuppressWarnings("deprecation") @Test public void testUseCompression() throws Exception { CompressionConfig compressionConfig = new CompressionConfig(); // Skip length and MIME type checks compressionConfig.setCompression("force"); + compressionConfig.setNoCompressionStrongETag(noCompressionStrongETag.booleanValue()); Request request = new Request(); Response response = new Response(); @@ -62,6 +76,11 @@ request.getMimeHeaders().addValue("accept-encoding").setString(header); } + if (eTag != null) { + response.getMimeHeaders().addValue("ETag").setString(eTag); + } + + Assert.assertEquals(compress, Boolean.valueOf(compressionConfig.useCompression(request, response))); } } diff -Nru tomcat9-9.0.27/test/org/apache/coyote/TestResponse.java tomcat9-9.0.31/test/org/apache/coyote/TestResponse.java --- tomcat9-9.0.27/test/org/apache/coyote/TestResponse.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/coyote/TestResponse.java 2020-02-05 19:26:48.000000000 +0000 @@ -59,10 +59,9 @@ responseHeaders); Assert.assertEquals(HttpServletResponse.SC_OK, rc); - Assert.assertTrue(responseHeaders.containsKey("Content-Type")); - List contentType = responseHeaders.get("Content-Type"); - Assert.assertEquals(1, contentType.size()); - Assert.assertEquals("text/plain;charset=uTf-8", contentType.get(0)); + + String contentType = getSingleHeader("Content-Type", responseHeaders); + Assert.assertEquals("text/plain;charset=uTf-8", contentType); } @@ -117,15 +116,14 @@ int rc = getUrl(uri.toString(), responseBody, responseHeaders); Assert.assertEquals(HttpServletResponse.SC_OK, rc); - Assert.assertTrue(responseHeaders.containsKey("Content-Type")); - List contentType = responseHeaders.get("Content-Type"); - Assert.assertEquals(1, contentType.size()); + + String contentType = getSingleHeader("Content-Type", responseHeaders); StringBuilder expected = new StringBuilder("text/plain;"); if (withSpace) { expected.append(" "); } expected.append("v=1;charset=UTF-8"); - Assert.assertEquals(expected.toString() , contentType.get(0)); + Assert.assertEquals(expected.toString() , contentType); } diff -Nru tomcat9-9.0.27/test/org/apache/jasper/compiler/TestCompiler.java tomcat9-9.0.31/test/org/apache/jasper/compiler/TestCompiler.java --- tomcat9-9.0.27/test/org/apache/jasper/compiler/TestCompiler.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/jasper/compiler/TestCompiler.java 2020-02-05 19:26:48.000000000 +0000 @@ -48,7 +48,8 @@ assertEcho(result, "OK"); // Check content type - Assert.assertTrue(headers.get("Content-Type").get(0).startsWith("text/html")); + String contentType = getSingleHeader("Content-Type", headers); + Assert.assertTrue(contentType.startsWith("text/html")); } @Test @@ -66,7 +67,8 @@ assertEcho(result, "OK"); // Check content type - Assert.assertTrue(headers.get("Content-Type").get(0).startsWith("text/plain")); + String contentType = getSingleHeader("Content-Type", headers); + Assert.assertTrue(contentType.startsWith("text/plain")); } @Test diff -Nru tomcat9-9.0.27/test/org/apache/jasper/compiler/TestGenerator.java tomcat9-9.0.31/test/org/apache/jasper/compiler/TestGenerator.java --- tomcat9-9.0.27/test/org/apache/jasper/compiler/TestGenerator.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/jasper/compiler/TestGenerator.java 2020-02-05 19:26:48.000000000 +0000 @@ -19,9 +19,6 @@ import java.io.IOException; import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.JspException; @@ -183,10 +180,7 @@ getTomcatInstanceTestWebapp(false, true); ByteChunk res = new ByteChunk(); - Map> headers = new HashMap<>(); - - getUrl("http://localhost:" + getPort() + "/test/bug49nnn/bug49799.jsp", - res, headers); + getUrl("http://localhost:" + getPort() + "/test/bug49nnn/bug49799.jsp", res, null); // Check request completed String result = res.toString(); diff -Nru tomcat9-9.0.27/test/org/apache/jasper/compiler/TestParser.java tomcat9-9.0.31/test/org/apache/jasper/compiler/TestParser.java --- tomcat9-9.0.27/test/org/apache/jasper/compiler/TestParser.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/jasper/compiler/TestParser.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,9 +16,6 @@ */ package org.apache.jasper.compiler; -import java.util.HashMap; -import java.util.List; - import org.junit.Assert; import org.junit.Test; @@ -100,8 +97,7 @@ getTomcatInstanceTestWebapp(false, true); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297NoSpace.jsp", new ByteChunk(), - new HashMap>()); + "/test/bug49nnn/bug49297NoSpace.jsp", new ByteChunk(), null); Assert.assertEquals(500, sc); } @@ -111,8 +107,7 @@ getTomcatInstanceTestWebapp(false, true); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297DuplicateAttr.jsp", new ByteChunk(), - new HashMap>()); + "/test/bug49nnn/bug49297DuplicateAttr.jsp", new ByteChunk(), null); Assert.assertEquals(500, sc); } @@ -123,8 +118,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297MultipleImport1.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297MultipleImport1.jsp", res, null); Assert.assertEquals(200, sc); assertEcho(res.toString(), "OK"); @@ -136,8 +130,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297MultipleImport2.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297MultipleImport2.jsp", res, null); Assert.assertEquals(200, sc); assertEcho(res.toString(), "OK"); @@ -149,8 +142,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297MultiplePageEncoding1.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297MultiplePageEncoding1.jsp", res, null); Assert.assertEquals(500, sc); } @@ -161,8 +153,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297MultiplePageEncoding2.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297MultiplePageEncoding2.jsp", res, null); Assert.assertEquals(500, sc); } @@ -173,8 +164,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297MultiplePageEncoding3.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297MultiplePageEncoding3.jsp", res, null); Assert.assertEquals(500, sc); } @@ -185,8 +175,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297MultiplePageEncoding4.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297MultiplePageEncoding4.jsp", res, null); Assert.assertEquals(500, sc); } @@ -197,8 +186,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297Tag.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297Tag.jsp", res, null); Assert.assertEquals(200, sc); assertEcho(res.toString(), "OK"); diff -Nru tomcat9-9.0.27/test/org/apache/jasper/compiler/TestParserNoStrictWhitespace.java tomcat9-9.0.31/test/org/apache/jasper/compiler/TestParserNoStrictWhitespace.java --- tomcat9-9.0.27/test/org/apache/jasper/compiler/TestParserNoStrictWhitespace.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/jasper/compiler/TestParserNoStrictWhitespace.java 2020-02-05 19:26:48.000000000 +0000 @@ -17,9 +17,6 @@ package org.apache.jasper.compiler; -import java.util.HashMap; -import java.util.List; - import org.junit.Assert; import org.junit.Test; @@ -110,8 +107,7 @@ ByteChunk res = new ByteChunk(); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297NoSpace.jsp", res, - new HashMap>()); + "/test/bug49nnn/bug49297NoSpace.jsp", res, null); Assert.assertEquals(200, sc); @@ -123,8 +119,7 @@ getTomcatInstanceTestWebapp(false, true); int sc = getUrl("http://localhost:" + getPort() + - "/test/bug49nnn/bug49297DuplicateAttr.jsp", new ByteChunk(), - new HashMap>()); + "/test/bug49nnn/bug49297DuplicateAttr.jsp", new ByteChunk(), null); Assert.assertEquals(500, sc); } diff -Nru tomcat9-9.0.27/test/org/apache/jasper/servlet/TestTldScanner.java tomcat9-9.0.31/test/org/apache/jasper/servlet/TestTldScanner.java --- tomcat9-9.0.27/test/org/apache/jasper/servlet/TestTldScanner.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/jasper/servlet/TestTldScanner.java 2020-02-05 19:26:48.000000000 +0000 @@ -84,7 +84,8 @@ // Check content type - Assert.assertTrue(headers.get("Content-Type").get(0).startsWith("text/html")); + String contentType = getSingleHeader("Content-Type", headers); + Assert.assertTrue(contentType.startsWith("text/html")); } diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/jni/TestSocketServer.java tomcat9-9.0.31/test/org/apache/tomcat/jni/TestSocketServer.java --- tomcat9-9.0.27/test/org/apache/tomcat/jni/TestSocketServer.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/jni/TestSocketServer.java 2020-02-05 19:26:48.000000000 +0000 @@ -24,30 +24,27 @@ import org.junit.Before; import org.junit.Test; -/** +/* * Tests for server-side sockets. + * + * While System.nanotime() is available and may have a resolution of ~100ns on + * some platforms, those same platforms do not use as precise a timer for socket + * timeouts. Therefore, a much larger error margin (100ms) is used. + * + * It is known that this larger error margin is required for Windows 10. It may + * be worth revisiting the choice of error margin once that platform is no + * longer supported. */ public class TestSocketServer extends AbstractJniTest { private static final String HOST = "localhost"; - private static final long ERROR_MARGIN; + // 100ms == 100,000,000ns + private static final long ERROR_MARGIN = 100000000; private int port = 0; private long serverSocket = 0; private long clientSocket = 0; - // Determine the resolution of System.nanoTime() so an appropriate error - // margin can be used in tests that use nanoTime() - static { - long start = System.nanoTime(); - long end = System.nanoTime(); - while (end == start) { - end = System.nanoTime(); - } - ERROR_MARGIN = 2 * (end - start); - } - - @Before public void init() throws Exception { long serverPool = Pool.create(0); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/parser/TestTokenList.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/parser/TestTokenList.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/parser/TestTokenList.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/parser/TestTokenList.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,200 @@ +/* + * 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.parser; + +import java.io.IOException; +import java.io.StringReader; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.junit.Assert; +import org.junit.Test; + +public class TestTokenList { + + @Test + public void testAll() throws IOException { + Set expected = new HashSet<>(); + expected.add("*"); + doTestVary("*", expected, true); + } + + + @Test + public void testSingle() throws IOException { + Set expected = new HashSet<>(); + expected.add("host"); + doTestVary("Host", expected, true); + } + + + @Test + public void testMultiple() throws IOException { + Set expected = new HashSet<>(); + expected.add("bar"); + expected.add("foo"); + expected.add("host"); + doTestVary("Host, Foo, Bar", expected, true); + } + + + @Test + public void testEmptyString() throws IOException { + doTestVary("", Collections.emptySet(), false); + } + + + @Test + public void testSingleInvalid() throws IOException { + doTestVary("{{{", Collections.emptySet(), false); + } + + + @Test + public void testMultipleWithInvalidStart() throws IOException { + Set expected = new HashSet<>(); + expected.add("bar"); + expected.add("foo"); + expected.add("host"); + doTestVary("{{{, Host, Foo, Bar", expected, false); + } + + + @Test + public void testMultipleWithInvalidMiddle() throws IOException { + Set expected = new HashSet<>(); + expected.add("bar"); + expected.add("foo"); + expected.add("host"); + doTestVary("Host, {{{, Foo, Bar", expected, false); + } + + + @Test + public void testMultipleWithInvalidEnd() throws IOException { + Set expected = new HashSet<>(); + expected.add("bar"); + expected.add("foo"); + expected.add("host"); + doTestVary("Host, Foo, Bar, {{{", expected, false); + } + + + @Test + public void testMultipleWithInvalidStart2() throws IOException { + Set expected = new HashSet<>(); + expected.add("bar"); + expected.add("foo"); + expected.add("host"); + doTestVary("OK {{{, Host, Foo, Bar", expected, false); + } + + + @Test + public void testMultipleWithInvalidMiddle2() throws IOException { + Set expected = new HashSet<>(); + expected.add("bar"); + expected.add("foo"); + expected.add("host"); + doTestVary("Host, OK {{{, Foo, Bar", expected, false); + } + + + @Test + public void testMultipleWithInvalidEnd2() throws IOException { + Set expected = new HashSet<>(); + expected.add("bar"); + expected.add("foo"); + expected.add("host"); + doTestVary("Host, Foo, Bar, OK {{{", expected, false); + } + + + @SuppressWarnings("deprecation") + private void doTestVary(String input, Set expectedTokens, boolean expectedResult) throws IOException { + StringReader reader = new StringReader(input); + Set tokens = new HashSet<>(); + Vary.parseVary(reader, tokens); + Assert.assertEquals(expectedTokens, tokens); + + // Can't use reset(). Parser uses marks. + reader = new StringReader(input); + tokens.clear(); + boolean result = TokenList.parseTokenList(reader, tokens); + Assert.assertEquals(expectedTokens, tokens); + Assert.assertEquals(Boolean.valueOf(expectedResult), Boolean.valueOf(result)); + } + + + @Test + public void testMultipleHeadersValidWithoutNull() throws IOException { + doTestMultipleHeadersValid(false); + } + + + @Test + public void testMultipleHeadersValidWithNull() throws IOException { + doTestMultipleHeadersValid(true); + } + + + private void doTestMultipleHeadersValid(boolean withNull) throws IOException { + Set expectedTokens = new HashSet<>(); + expectedTokens.add("bar"); + expectedTokens.add("foo"); + expectedTokens.add("foo2"); + + Set inputs = new HashSet<>(); + inputs.add("foo"); + if (withNull) { + inputs.add(null); + } + inputs.add("bar, foo2"); + + Set tokens = new HashSet<>(); + + + boolean result = TokenList.parseTokenList(Collections.enumeration(inputs), tokens); + Assert.assertEquals(expectedTokens, tokens); + Assert.assertTrue(result); + } + + + @Test + public void doTestMultipleHeadersInvalid() throws IOException { + Set expectedTokens = new HashSet<>(); + expectedTokens.add("bar"); + expectedTokens.add("bar2"); + expectedTokens.add("foo"); + expectedTokens.add("foo2"); + expectedTokens.add("foo3"); + + Set inputs = new HashSet<>(); + inputs.add("foo"); + inputs.add("bar2, }}}, foo3"); + inputs.add("bar, foo2"); + + Set tokens = new HashSet<>(); + + + boolean result = TokenList.parseTokenList(Collections.enumeration(inputs), tokens); + Assert.assertEquals(expectedTokens, tokens); + Assert.assertFalse(result); + } + +} diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/parser/TestVary.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/parser/TestVary.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/parser/TestVary.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/parser/TestVary.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,134 +0,0 @@ -/* - * 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.parser; - -import java.io.IOException; -import java.io.StringReader; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -import org.junit.Assert; -import org.junit.Test; - -public class TestVary { - - @Test - public void testAll() throws IOException { - Set expected = new HashSet<>(); - expected.add("*"); - doTestVary("*", expected); - } - - - @Test - public void testSingle() throws IOException { - Set expected = new HashSet<>(); - expected.add("host"); - doTestVary("Host", expected); - } - - - @Test - public void testMultiple() throws IOException { - Set expected = new HashSet<>(); - expected.add("bar"); - expected.add("foo"); - expected.add("host"); - doTestVary("Host, Foo, Bar", expected); - } - - - @Test - public void testEmptyString() throws IOException { - doTestVary("", Collections.emptySet()); - } - - - @Test - public void testSingleInvalid() throws IOException { - doTestVary("{{{", Collections.emptySet()); - } - - - @Test - public void testMultipleWithInvalidStart() throws IOException { - Set expected = new HashSet<>(); - expected.add("bar"); - expected.add("foo"); - expected.add("host"); - doTestVary("{{{, Host, Foo, Bar", expected); - } - - - @Test - public void testMultipleWithInvalidMiddle() throws IOException { - Set expected = new HashSet<>(); - expected.add("bar"); - expected.add("foo"); - expected.add("host"); - doTestVary("Host, {{{, Foo, Bar", expected); - } - - - @Test - public void testMultipleWithInvalidEnd() throws IOException { - Set expected = new HashSet<>(); - expected.add("bar"); - expected.add("foo"); - expected.add("host"); - doTestVary("Host, Foo, Bar, {{{", expected); - } - - - @Test - public void testMultipleWithInvalidStart2() throws IOException { - Set expected = new HashSet<>(); - expected.add("bar"); - expected.add("foo"); - expected.add("host"); - doTestVary("OK {{{, Host, Foo, Bar", expected); - } - - - @Test - public void testMultipleWithInvalidMiddle2() throws IOException { - Set expected = new HashSet<>(); - expected.add("bar"); - expected.add("foo"); - expected.add("host"); - doTestVary("Host, OK {{{, Foo, Bar", expected); - } - - - @Test - public void testMultipleWithInvalidEnd2() throws IOException { - Set expected = new HashSet<>(); - expected.add("bar"); - expected.add("foo"); - expected.add("host"); - doTestVary("Host, Foo, Bar, OK {{{", expected); - } - - - private void doTestVary(String input, Set expected) throws IOException { - StringReader reader = new StringReader(input); - Set result = new HashSet<>(); - Vary.parseVary(reader, result); - Assert.assertEquals(expected, result); - } -} diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestCookieProcessorGeneration.java 2020-02-05 19:26:48.000000000 +0000 @@ -264,12 +264,18 @@ Assert.assertEquals("foo=bar", legacy.generateHeader(cookie)); Assert.assertEquals("foo=bar", rfc6265.generateHeader(cookie)); - legacy.setSameSiteCookies("none"); - rfc6265.setSameSiteCookies("none"); + legacy.setSameSiteCookies("unset"); + rfc6265.setSameSiteCookies("unset"); Assert.assertEquals("foo=bar", legacy.generateHeader(cookie)); Assert.assertEquals("foo=bar", rfc6265.generateHeader(cookie)); + legacy.setSameSiteCookies("none"); + rfc6265.setSameSiteCookies("none"); + + Assert.assertEquals("foo=bar; SameSite=None", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar; SameSite=None", rfc6265.generateHeader(cookie)); + legacy.setSameSiteCookies("lax"); rfc6265.setSameSiteCookies("lax"); @@ -285,12 +291,18 @@ cookie.setSecure(true); cookie.setHttpOnly(true); - legacy.setSameSiteCookies("none"); - rfc6265.setSameSiteCookies("none"); + legacy.setSameSiteCookies("unset"); + rfc6265.setSameSiteCookies("unset"); Assert.assertEquals("foo=bar; Secure; HttpOnly", legacy.generateHeader(cookie)); Assert.assertEquals("foo=bar; Secure; HttpOnly", rfc6265.generateHeader(cookie)); + legacy.setSameSiteCookies("none"); + rfc6265.setSameSiteCookies("none"); + + Assert.assertEquals("foo=bar; Secure; HttpOnly; SameSite=None", legacy.generateHeader(cookie)); + Assert.assertEquals("foo=bar; Secure; HttpOnly; SameSite=None", rfc6265.generateHeader(cookie)); + legacy.setSameSiteCookies("lax"); rfc6265.setSameSiteCookies("lax"); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestHeaderUtiltoPrintableString.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestHeaderUtiltoPrintableString.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestHeaderUtiltoPrintableString.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestHeaderUtiltoPrintableString.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,83 @@ +/* + * 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.nio.charset.StandardCharsets; +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; + +@RunWith(Parameterized.class) +public class TestHeaderUtiltoPrintableString { + + @Parameterized.Parameters(name = "{index}: expected[{1}]") + public static Collection parameters() { + List parameterSets = new ArrayList<>(); + + parameterSets.add(new String[] { "", "" }); + + parameterSets.add(new String[] { "abcd", "abcd" }); + + parameterSets.add(new String[] { "\u0000abcd", "0x00abcd" }); + parameterSets.add(new String[] { "ab\u0000cd", "ab0x00cd" }); + parameterSets.add(new String[] { "abcd\u0000", "abcd0x00" }); + + parameterSets.add(new String[] { "\tabcd", "0x09abcd" }); + parameterSets.add(new String[] { "ab\tcd", "ab0x09cd" }); + parameterSets.add(new String[] { "abcd\t", "abcd0x09" }); + + parameterSets.add(new String[] { " abcd", " abcd" }); + parameterSets.add(new String[] { "ab cd", "ab cd" }); + parameterSets.add(new String[] { "abcd ", "abcd " }); + + parameterSets.add(new String[] { "~abcd", "~abcd" }); + parameterSets.add(new String[] { "ab~cd", "ab~cd" }); + parameterSets.add(new String[] { "abcd~", "abcd~" }); + + parameterSets.add(new String[] { "\u007fabcd", "0x7fabcd" }); + parameterSets.add(new String[] { "ab\u007fcd", "ab0x7fcd" }); + parameterSets.add(new String[] { "abcd\u007f", "abcd0x7f" }); + + parameterSets.add(new String[] { "\u00a3abcd", "0xa3abcd" }); + parameterSets.add(new String[] { "ab\u00a3cd", "ab0xa3cd" }); + parameterSets.add(new String[] { "abcd\u00a3", "abcd0xa3" }); + + return parameterSets; + } + + + @Parameter(0) + public String input; + @Parameter(1) + public String expected; + + + @Test + public void doTest() { + byte[] bytes = input.getBytes(StandardCharsets.ISO_8859_1); + + String result = HeaderUtil.toPrintableString(bytes, 0, bytes.length); + + Assert.assertEquals(expected, result); + } +} diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestMimeHeadersIntegration.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestMimeHeadersIntegration.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestMimeHeadersIntegration.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestMimeHeadersIntegration.java 2020-02-05 19:26:48.000000000 +0000 @@ -105,8 +105,7 @@ if (maxHeaderCount > 0) { Assert.assertEquals(maxHeaderCount, alv.arraySize); } else if (maxHeaderCount < 0) { - int maxHttpHeaderSize = ((Integer) tomcat.getConnector() - .getAttribute("maxHttpHeaderSize")).intValue(); + int maxHttpHeaderSize = ((Integer) tomcat.getConnector().getProperty("maxHttpHeaderSize")).intValue(); int headerCount = Math.min(count, maxHttpHeaderSize / header.length() + 1); int arraySize = 1; @@ -122,7 +121,7 @@ // Bumping into maxHttpHeaderSize Tomcat tomcat = getTomcatInstance(); setupHeadersTest(tomcat); - tomcat.getConnector().setProperty("maxHeaderCount", "-1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxHeaderCount", "-1")); runHeadersTest(false, tomcat, 8 * 1024, -1); } @@ -147,7 +146,7 @@ // Can change maxHeaderCount Tomcat tomcat = getTomcatInstance(); setupHeadersTest(tomcat); - tomcat.getConnector().setProperty("maxHeaderCount", "-1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxHeaderCount", "-1")); runHeadersTest(true, tomcat, 300, -1); } diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestRequestUtil.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestRequestUtil.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestRequestUtil.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestRequestUtil.java 1970-01-01 00:00:00.000000000 +0000 @@ -1,162 +0,0 @@ -/* - * 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 org.junit.Assert; -import org.junit.Test; - -public class TestRequestUtil { - - @Test - public void testNormalize01() { - doTestNormalize("//something", "/something"); - } - - @Test - public void testNormalize02() { - doTestNormalize("some//thing", "/some/thing"); - } - - @Test - public void testNormalize03() { - doTestNormalize("something//", "/something/"); - } - - @Test - public void testNormalize04() { - doTestNormalize("//", "/"); - } - - @Test - public void testNormalize05() { - doTestNormalize("//", "/"); - } - - @Test - public void testNormalize06() { - doTestNormalize("///", "/"); - } - - @Test - public void testNormalize07() { - doTestNormalize("////", "/"); - } - - @Test - public void testNormalize08() { - doTestNormalize("/.", "/"); - } - - @Test - public void testNormalize09() { - doTestNormalize("/./", "/"); - } - - @Test - public void testNormalize10() { - doTestNormalize(".", "/"); - } - - @Test - public void testNormalize11() { - doTestNormalize("/..", null); - } - - @Test - public void testNormalize12() { - doTestNormalize("/../", null); - } - - @Test - public void testNormalize13() { - doTestNormalize("..", null); - } - - @Test - public void testNormalize14() { - doTestNormalize("//..", null); - } - - @Test - public void testNormalize15() { - doTestNormalize("//../", null); - } - - @Test - public void testNormalize16() { - doTestNormalize("/./..", null); - } - - @Test - public void testNormalize17() { - doTestNormalize("/./../", null); - } - - @Test - public void testNormalize18() { - doTestNormalize("/a/../..", null); - } - - @Test - public void testNormalize19() { - doTestNormalize("/a/../../", null); - } - - @Test - public void testNormalize20() { - doTestNormalize("/a/..", "/"); - } - - @Test - public void testNormalize21() { - doTestNormalize("/a/.", "/a"); - } - - @Test - public void testNormalize22() { - doTestNormalize("/a/../", "/"); - } - - @Test - public void testNormalize23() { - doTestNormalize("/a/./", "/a/"); - } - - @Test - public void testNormalize24() { - doTestNormalize("/a/b/..", "/a"); - } - - @Test - public void testNormalize25() { - doTestNormalize("/a/b/.", "/a/b"); - } - - @Test - public void testNormalize26() { - doTestNormalize("/a/b/../", "/a/"); - } - - @Test - public void testNormalize27() { - doTestNormalize("/a/b/./", "/a/b/"); - } - - private void doTestNormalize(String input, String expected) { - Assert.assertEquals(expected,RequestUtil.normalize(input)); - } -} diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestRequestUtilNormalize.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestRequestUtilNormalize.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestRequestUtilNormalize.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestRequestUtilNormalize.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,77 @@ +/* + * 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.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; + +@RunWith(Parameterized.class) +public class TestRequestUtilNormalize { + + @Parameterized.Parameters(name = "{index}: input[{0}]") + public static Collection parameters() { + List parameterSets = new ArrayList<>(); + + parameterSets.add(new String[] { "//something", "/something" }); + parameterSets.add(new String[] { "some//thing", "/some/thing" }); + parameterSets.add(new String[] { "something//", "/something/" }); + parameterSets.add(new String[] { "//", "/" }); + parameterSets.add(new String[] { "///", "/" }); + parameterSets.add(new String[] { "////", "/" }); + parameterSets.add(new String[] { "/.", "/" }); + parameterSets.add(new String[] { "/./", "/" }); + parameterSets.add(new String[] { ".", "/" }); + parameterSets.add(new String[] { "/..", null }); + parameterSets.add(new String[] { "/../", null }); + parameterSets.add(new String[] { "..", null }); + parameterSets.add(new String[] { "//..", null }); + parameterSets.add(new String[] { "//../", null }); + parameterSets.add(new String[] { "/./..", null }); + parameterSets.add(new String[] { "/./../", null }); + parameterSets.add(new String[] { "/a/../..", null }); + parameterSets.add(new String[] { "/a/../../", null }); + parameterSets.add(new String[] { "/a/..", "/" }); + parameterSets.add(new String[] { "/a/.", "/a" }); + parameterSets.add(new String[] { "/a/../", "/" }); + parameterSets.add(new String[] { "/a/./", "/a/" }); + parameterSets.add(new String[] { "/a/b/..", "/a" }); + parameterSets.add(new String[] { "/a/b/.", "/a/b" }); + parameterSets.add(new String[] { "/a/b/../", "/a/" }); + parameterSets.add(new String[] { "/a/b/./", "/a/b/" }); + + return parameterSets; + } + + + @Parameter(0) + public String input; + @Parameter(1) + public String expected; + + + @Test + public void testNormalize() { + Assert.assertEquals(expected,RequestUtil.normalize(input)); + } +} diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestRequestUtilSameOrigin.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestRequestUtilSameOrigin.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestRequestUtilSameOrigin.java 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestRequestUtilSameOrigin.java 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,113 @@ +/* + * 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.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +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.connector.Request; + +@RunWith(Parameterized.class) +public class TestRequestUtilSameOrigin { + + @Parameterized.Parameters(name = "{index}: request[{0}], origin[{1}]") + public static Collection parameters() { + List parameterSets = new ArrayList<>(); + + TesterRequest request1 = new TesterRequest("http", "example.com", 80); + TesterRequest request2 = new TesterRequest("ws", "example.com", 80); + TesterRequest request3 = new TesterRequest("http", "example.com", 443); + TesterRequest request4 = new TesterRequest("http", "example.com", 8080); + + parameterSets.add(new Object[] { request1, "http://example.com", Boolean.TRUE }); + parameterSets.add(new Object[] { request1, "http://example.com:80", Boolean.TRUE }); + parameterSets.add(new Object[] { request1, "http://example.com:8080", Boolean.FALSE}); + + parameterSets.add(new Object[] { request2, "ws://example.com", Boolean.TRUE }); + parameterSets.add(new Object[] { request2, "ws://example.com:80", Boolean.TRUE }); + parameterSets.add(new Object[] { request2, "ws://example.com:8080", Boolean.FALSE}); + + parameterSets.add(new Object[] { request3, "http://example.com", Boolean.FALSE }); + parameterSets.add(new Object[] { request3, "http://example.com:80", Boolean.FALSE }); + parameterSets.add(new Object[] { request3, "http://example.com:443", Boolean.TRUE}); + + parameterSets.add(new Object[] { request4, "http://example.com", Boolean.FALSE }); + parameterSets.add(new Object[] { request4, "http://example.com:80", Boolean.FALSE }); + parameterSets.add(new Object[] { request4, "http://example.com:8080", Boolean.TRUE}); + + return parameterSets; + } + + + @Parameter(0) + public HttpServletRequest request; + @Parameter(1) + public String origin; + @Parameter(2) + public Boolean same; + + + @Test + public void testSameOrigin() { + Assert.assertEquals(same, Boolean.valueOf(RequestUtil.isSameOrigin(request, origin))); + } + + + private static class TesterRequest extends HttpServletRequestWrapper { + + private final String scheme; + private final String host; + private final int port; + + public TesterRequest(String scheme, String host, int port) { + super(new Request(null)); + this.scheme = scheme; + this.host = host; + this.port = port; + } + + @Override + public String getScheme() { + return scheme; + } + + @Override + public String getServerName() { + return host; + } + + @Override + public int getServerPort() { + return port; + } + + @Override + public String toString() { + return scheme + "://" + host + ":" + port; + } + } +} diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestSameSiteCookies.java tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestSameSiteCookies.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/http/TestSameSiteCookies.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/http/TestSameSiteCookies.java 2020-02-05 19:26:48.000000000 +0000 @@ -23,12 +23,25 @@ public class TestSameSiteCookies { @Test + public void testUnset() { + SameSiteCookies attribute = SameSiteCookies.UNSET; + + Assert.assertEquals("Unset", attribute.getValue()); + Assert.assertEquals(SameSiteCookies.UNSET, attribute); + + Assert.assertNotEquals(SameSiteCookies.NONE, attribute); + Assert.assertNotEquals(SameSiteCookies.LAX, attribute); + Assert.assertNotEquals(SameSiteCookies.STRICT, attribute); + } + + @Test public void testNone() { SameSiteCookies attribute = SameSiteCookies.NONE; Assert.assertEquals("None", attribute.getValue()); Assert.assertEquals(SameSiteCookies.NONE, attribute); + Assert.assertNotEquals(SameSiteCookies.UNSET, attribute); Assert.assertNotEquals(SameSiteCookies.LAX, attribute); Assert.assertNotEquals(SameSiteCookies.STRICT, attribute); } @@ -40,6 +53,7 @@ Assert.assertEquals("Lax", attribute.getValue()); Assert.assertEquals(SameSiteCookies.LAX, attribute); + Assert.assertNotEquals(SameSiteCookies.UNSET, attribute); Assert.assertNotEquals(SameSiteCookies.NONE, attribute); Assert.assertNotEquals(SameSiteCookies.STRICT, attribute); } @@ -51,12 +65,17 @@ Assert.assertEquals("Strict", attribute.getValue()); Assert.assertEquals(SameSiteCookies.STRICT, attribute); + Assert.assertNotEquals(SameSiteCookies.UNSET, attribute); Assert.assertNotEquals(SameSiteCookies.NONE, attribute); Assert.assertNotEquals(SameSiteCookies.LAX, attribute); } @Test public void testToValidAttribute() { + Assert.assertEquals(SameSiteCookies.fromString("unset"), SameSiteCookies.UNSET); + Assert.assertEquals(SameSiteCookies.fromString("Unset"), SameSiteCookies.UNSET); + Assert.assertEquals(SameSiteCookies.fromString("UNSET"), SameSiteCookies.UNSET); + Assert.assertEquals(SameSiteCookies.fromString("none"), SameSiteCookies.NONE); Assert.assertEquals(SameSiteCookies.fromString("None"), SameSiteCookies.NONE); Assert.assertEquals(SameSiteCookies.fromString("NONE"), SameSiteCookies.NONE); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java tomcat9-9.0.31/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java 2020-02-05 19:26:48.000000000 +0000 @@ -23,7 +23,6 @@ public class TesterBug50640SslImpl extends JSSEImplementation { - public static final String PROPERTY_NAME = "sslEnabledProtocols"; public static final String PROPERTY_VALUE = "magic"; diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestClientCertTls13.java tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestClientCertTls13.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestClientCertTls13.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestClientCertTls13.java 2020-02-05 19:26:48.000000000 +0000 @@ -75,9 +75,9 @@ TesterSupport.configureClientCertContext(tomcat); // Need to override some of the previous settings - tomcat.getConnector().setProperty("sslEnabledProtocols", Constants.SSL_PROTO_TLSv1_3); + Assert.assertTrue(tomcat.getConnector().setProperty("sslEnabledProtocols", Constants.SSL_PROTO_TLSv1_3)); // And add force authentication to occur on the initial handshake - tomcat.getConnector().setProperty("clientAuth", "required"); + Assert.assertTrue(tomcat.getConnector().setProperty("clientAuth", "required")); TesterSupport.configureClientSsl(); } diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestCustomSsl.java tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestCustomSsl.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestCustomSsl.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestCustomSsl.java 2020-02-05 19:26:48.000000000 +0000 @@ -32,6 +32,7 @@ import org.apache.coyote.ProtocolHandler; import org.apache.coyote.http11.AbstractHttp11JsseProtocol; import org.apache.tomcat.util.buf.ByteChunk; +import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type; import org.apache.tomcat.util.net.jsse.TesterBug50640SslImpl; import org.apache.tomcat.websocket.server.WsContextListener; @@ -59,23 +60,25 @@ Assume.assumeFalse("This test is only for JSSE based SSL connectors", connector.getProtocolHandlerClassName().contains("Apr")); - connector.setProperty("sslImplementationName", - "org.apache.tomcat.util.net.jsse.TesterBug50640SslImpl"); + SSLHostConfig sslHostConfig = new SSLHostConfig(); + SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED); + sslHostConfig.addCertificate(certificate); + connector.addSslHostConfig(sslHostConfig); + + Assert.assertTrue(connector.setProperty( + "sslImplementationName", "org.apache.tomcat.util.net.jsse.TesterBug50640SslImpl")); // This setting will break ssl configuration unless the custom // implementation is used. - connector.setProperty(TesterBug50640SslImpl.PROPERTY_NAME, - TesterBug50640SslImpl.PROPERTY_VALUE); + sslHostConfig.setProtocols(TesterBug50640SslImpl.PROPERTY_VALUE); - connector.setProperty("sslProtocol", "tls"); + sslHostConfig.setSslProtocol("tls"); - File keystoreFile = - new File(TesterSupport.LOCALHOST_RSA_JKS); - connector.setAttribute( - "keystoreFile", keystoreFile.getAbsolutePath()); + File keystoreFile = new File(TesterSupport.LOCALHOST_RSA_JKS); + certificate.setCertificateKeystoreFile(keystoreFile.getAbsolutePath()); connector.setSecure(true); - connector.setProperty("SSLEnabled", "true"); + Assert.assertTrue(connector.setProperty("SSLEnabled", "true")); File appDir = new File(getBuildDirectory(), "webapps/examples"); Context ctxt = tomcat.addWebapp( @@ -109,23 +112,25 @@ Tomcat tomcat = getTomcatInstance(); Assume.assumeTrue("SSL renegotiation has to be supported for this test", - TesterSupport.isRenegotiationSupported(getTomcatInstance())); + TesterSupport.isRenegotiationSupported(tomcat)); TesterSupport.configureClientCertContext(tomcat); + Connector connector = tomcat.getConnector(); + // Override the defaults - ProtocolHandler handler = tomcat.getConnector().getProtocolHandler(); + ProtocolHandler handler = connector.getProtocolHandler(); if (handler instanceof AbstractHttp11JsseProtocol) { - ((AbstractHttp11JsseProtocol) handler).setTruststoreFile(null); + connector.findSslHostConfigs()[0].setTruststoreFile(null); } else { // Unexpected Assert.fail("Unexpected handler type"); } if (trustType.equals(TrustType.ALL)) { - tomcat.getConnector().setAttribute("trustManagerClassName", + connector.findSslHostConfigs()[0].setTrustManagerClassName( "org.apache.tomcat.util.net.TesterSupport$TrustAllCerts"); } else if (trustType.equals(TrustType.CA)) { - tomcat.getConnector().setAttribute("trustManagerClassName", + connector.findSslHostConfigs()[0].setTrustManagerClassName( "org.apache.tomcat.util.net.TesterSupport$SequentialTrustManager"); } @@ -135,16 +140,14 @@ TesterSupport.configureClientSsl(); // Unprotected resource - ByteChunk res = - getUrl("https://localhost:" + getPort() + "/unprotected"); + ByteChunk res = getUrl("https://localhost:" + getPort() + "/unprotected"); Assert.assertEquals("OK", res.toString()); // Protected resource res.recycle(); int rc = -1; try { - rc = getUrl("https://localhost:" + getPort() + "/protected", res, - null, null); + rc = getUrl("https://localhost:" + getPort() + "/protected", res, null, null); } catch (SocketException se) { if (!trustType.equals(TrustType.NONE)) { Assert.fail(se.getMessage()); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/net/TesterSupport.java tomcat9-9.0.31/test/org/apache/tomcat/util/net/TesterSupport.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/net/TesterSupport.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/net/TesterSupport.java 2020-02-05 19:26:48.000000000 +0000 @@ -47,6 +47,8 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.junit.Assert; + import org.apache.catalina.Context; import org.apache.catalina.authenticator.SSLAuthenticator; import org.apache.catalina.connector.Connector; @@ -61,6 +63,7 @@ import org.apache.tomcat.util.descriptor.web.LoginConfig; import org.apache.tomcat.util.descriptor.web.SecurityCollection; import org.apache.tomcat.util.descriptor.web.SecurityConstraint; +import org.apache.tomcat.util.net.SSLHostConfigCertificate.Type; public final class TesterSupport { @@ -134,47 +137,39 @@ protected static void initSsl(Tomcat tomcat, String keystore, String keystorePass, String keyPass) { + Connector connector = tomcat.getConnector(); + connector.setSecure(true); + Assert.assertTrue(connector.setProperty("SSLEnabled", "true")); + + SSLHostConfig sslHostConfig = new SSLHostConfig(); + SSLHostConfigCertificate certificate = new SSLHostConfigCertificate(sslHostConfig, Type.UNDEFINED); + sslHostConfig.addCertificate(certificate); + connector.addSslHostConfig(sslHostConfig); + String protocol = tomcat.getConnector().getProtocolHandlerClassName(); if (!protocol.contains("Apr")) { - Connector connector = tomcat.getConnector(); String sslImplementation = System.getProperty("tomcat.test.sslImplementation"); if (sslImplementation != null && !"${test.sslImplementation}".equals(sslImplementation)) { StandardServer server = (StandardServer) tomcat.getServer(); AprLifecycleListener listener = new AprLifecycleListener(); listener.setSSLRandomSeed("/dev/urandom"); server.addLifecycleListener(listener); - tomcat.getConnector().setAttribute("sslImplementationName", sslImplementation); + Assert.assertTrue(connector.setProperty("sslImplementationName", sslImplementation)); } - connector.setProperty("sslProtocol", "tls"); - File keystoreFile = - new File(keystore); - connector.setAttribute("keystoreFile", - keystoreFile.getAbsolutePath()); - File truststoreFile = new File(CA_JKS); - connector.setAttribute("truststoreFile", - truststoreFile.getAbsolutePath()); + sslHostConfig.setSslProtocol("tls"); + certificate.setCertificateKeystoreFile(new File(keystore).getAbsolutePath()); + sslHostConfig.setTruststoreFile(new File(CA_JKS).getAbsolutePath()); if (keystorePass != null) { - connector.setAttribute("keystorePass", keystorePass); + certificate.setCertificateKeystorePassword(keystorePass); } if (keyPass != null) { - connector.setAttribute("keyPass", keyPass); + certificate.setCertificateKeyPassword(keyPass); } } else { - File keystoreFile = new File( - LOCALHOST_RSA_CERT_PEM); - tomcat.getConnector().setAttribute("SSLCertificateFile", - keystoreFile.getAbsolutePath()); - keystoreFile = new File( - LOCALHOST_RSA_KEY_PEM); - tomcat.getConnector().setAttribute("SSLCertificateKeyFile", - keystoreFile.getAbsolutePath()); - keystoreFile = new File( - CA_CERT_PEM); - tomcat.getConnector().setAttribute("SSLCACertificateFile", - keystoreFile.getAbsolutePath()); + certificate.setCertificateFile(new File(LOCALHOST_RSA_CERT_PEM).getAbsolutePath()); + certificate.setCertificateKeyFile(new File(LOCALHOST_RSA_KEY_PEM).getAbsolutePath()); + sslHostConfig.setCaCertificateFile(new File(CA_CERT_PEM).getAbsolutePath()); } - tomcat.getConnector().setSecure(true); - tomcat.getConnector().setProperty("SSLEnabled", "true"); } protected static KeyManager[] getUser1KeyManagers() throws Exception { @@ -263,7 +258,7 @@ * depend. Therefore, force these tests to use TLSv1.2 so that they pass * when running on TLSv1.3. */ - tomcat.getConnector().setProperty("sslEnabledProtocols", Constants.SSL_PROTO_TLSv1_2); + tomcat.getConnector().findSslHostConfigs()[0].setProtocols(Constants.SSL_PROTO_TLSv1_2); // Need a web application with a protected and unprotected URL // No file system docBase required diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestSSLHostConfigCompat.java tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestSSLHostConfigCompat.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestSSLHostConfigCompat.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestSSLHostConfigCompat.java 2020-02-05 19:26:48.000000000 +0000 @@ -322,8 +322,11 @@ connector.setPort(0); connector.setScheme("https"); connector.setSecure(true); - connector.setProperty("SSLEnabled", "true"); - connector.setProperty("sslImplementationName", sslImplementationName); + Assert.assertTrue(connector.setProperty("SSLEnabled", "true")); + if (!connector.getProtocolHandlerClassName().contains("Apr")) { + // Skip this for APR. It is not supported. + Assert.assertTrue(connector.setProperty("sslImplementationName", sslImplementationName)); + } sslHostConfig.setProtocols("TLSv1.2"); connector.addSslHostConfig(sslHostConfig); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestSsl.java tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestSsl.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestSsl.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestSsl.java 2020-02-05 19:26:48.000000000 +0000 @@ -136,28 +136,17 @@ socket.startHandshake(); - // One request should be sufficient - int requestCount = 0; - int listenerComplete = 0; - try { - while (requestCount < 10) { - requestCount++; - doRequest(os, r); - Assert.assertTrue("Checking no client issuer has been requested", - TesterSupport.getLastClientAuthRequestedIssuerCount() == 0); - if (listener.isComplete() && listenerComplete == 0) { - listenerComplete = requestCount; - } - } - } catch (AssertionError | IOException e) { - String message = "Failed on request number " + requestCount - + " after startHandshake(). " + e.getMessage(); - log.error(message, e); - Assert.fail(message); + doRequest(os, r); + // Handshake complete appears to be called asynchronously + int wait = 0; + while (wait < 5000 && !listener.isComplete()) { + wait += 50; + Thread.sleep(50); } - + Assert.assertTrue("Checking no client issuer has been requested", + TesterSupport.getLastClientAuthRequestedIssuerCount() == 0); Assert.assertTrue(listener.isComplete()); - System.out.println("Renegotiation completed after " + listenerComplete + " requests"); + System.out.println("Renegotiation completed after " + wait + " ms"); } private void doRequest(OutputStream os, Reader r) throws IOException { diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestXxxEndpoint.java tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestXxxEndpoint.java --- tomcat9-9.0.27/test/org/apache/tomcat/util/net/TestXxxEndpoint.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/util/net/TestXxxEndpoint.java 2020-02-05 19:26:48.000000000 +0000 @@ -155,7 +155,7 @@ public void testStartStopBindOnStart() throws Exception { Tomcat tomcat = getTomcatInstance(); Connector c = tomcat.getConnector(); - c.setProperty("bindOnInit", "false"); + Assert.assertTrue(c.setProperty("bindOnInit", "false")); File appDir = new File(getBuildDirectory(), "webapps/examples"); tomcat.addWebapp(null, "/examples", appDir.getAbsolutePath()); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/websocket/TestConnectionLimit.java tomcat9-9.0.31/test/org/apache/tomcat/websocket/TestConnectionLimit.java --- tomcat9-9.0.27/test/org/apache/tomcat/websocket/TestConnectionLimit.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/websocket/TestConnectionLimit.java 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,7 @@ import javax.websocket.DeploymentException; import javax.websocket.WebSocketContainer; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -50,7 +51,7 @@ Tomcat.addServlet(ctx, "default", new DefaultServlet()); ctx.addServletMappingDecoded("/", "default"); - tomcat.getConnector().setAttribute("maxConnections", "-1"); + Assert.assertTrue(tomcat.getConnector().setProperty("maxConnections", "-1")); tomcat.start(); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java tomcat9-9.0.31/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java --- tomcat9-9.0.27/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/websocket/TestPerMessageDeflate.java 2020-02-05 19:26:48.000000000 +0000 @@ -34,7 +34,7 @@ * https://bz.apache.org/bugzilla/show_bug.cgi?id=61491 */ @Test - public void testSendEmptyMessagePartWithContextTakeover() { + public void testSendEmptyMessagePartWithContextTakeover() throws IOException { // Set up the extension using defaults List parameters = Collections.emptyList(); diff -Nru tomcat9-9.0.27/test/org/apache/tomcat/websocket/TestWsWebSocketContainerWithProxy.java tomcat9-9.0.31/test/org/apache/tomcat/websocket/TestWsWebSocketContainerWithProxy.java --- tomcat9-9.0.27/test/org/apache/tomcat/websocket/TestWsWebSocketContainerWithProxy.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/test/org/apache/tomcat/websocket/TestWsWebSocketContainerWithProxy.java 2020-02-05 19:26:48.000000000 +0000 @@ -16,6 +16,7 @@ */ package org.apache.tomcat.websocket; +import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; @@ -38,7 +39,7 @@ // With httpd 2.2, AllowCONNECT requires fixed ports. From 2.4, a range // can be used. getTomcatInstance().getConnector().setPort(8080); - getTomcatInstance().getConnector().setProperty("address","0.0.0.0"); + Assert.assertTrue(getTomcatInstance().getConnector().setProperty("address","0.0.0.0")); } @Override diff -Nru tomcat9-9.0.27/test/webapp/bug6nnnn/bug69303.txt tomcat9-9.0.31/test/webapp/bug6nnnn/bug69303.txt --- tomcat9-9.0.27/test/webapp/bug6nnnn/bug69303.txt 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/test/webapp/bug6nnnn/bug69303.txt 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,18 @@ +================================================================================ + 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. +================================================================================ + +The content is not important. \ No newline at end of file diff -Nru tomcat9-9.0.27/TOMCAT-NEXT.txt tomcat9-9.0.31/TOMCAT-NEXT.txt --- tomcat9-9.0.27/TOMCAT-NEXT.txt 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/TOMCAT-NEXT.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,63 +0,0 @@ -================================================================================ - 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. -================================================================================ - -Notes of things to consider for the next major Tomcat release (10.0.x) - -Items carried over from the 9.0.x list: - -1. Remove the use of system properties to control configuration wherever - possible. - -2. Reduce instances of setters and getters for the same property existing on an - object and its parent. This may require new objects to be exposed via JMX. - -New items for 10.0.x onwards: - - 1. Remove APR connector. - - 2. Remove org.apache.tomcat.jni and replace with the minimum necessary to - interface with OpenSSL and clones. - We might want to park this one until we see what is available direct from - the JRE with project Panama. - - 3. Remove the ExtensionValidator and associated classes (assuming that the - minimum Java version is Java 9 or later). - - 4. Clean-up content-type header processing. Remove the optional space after - the ';' character (if any). Don't mutate the header if there is no charset - parameter. See BZ 62912 for some discussion and additional references. - - 5. Consider applying the delayed log file opening (until there is something to - log). See BZ 53620. - - 6. Consider including - UTF-8 - in conf/web.xml - - 7. Consider including - UTF-8 - in conf/web.xml - - 8. RFC 3986 states (section 2.2) that a %nn encoded delimiter is NOT equivalent - to the decoded form. Provide an option not to decode delimiters in %nn form. - - 9. BZ 56966. Refactor internal request timing to use System.nanoTime() - -10. BZ 63286. Make behaviour of %D and %T consistent with httpd. - -11. Refactor DefaultServlet to use Ranges in parseRanges() - diff -Nru tomcat9-9.0.27/.travis/antTest.sh tomcat9-9.0.31/.travis/antTest.sh --- tomcat9-9.0.27/.travis/antTest.sh 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/.travis/antTest.sh 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +# 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. + +# A helper script for TravisCI builds that saves the std +# out and err streams in a log file. This is needed +# because otherwise TravisCI complains that there is too +# much logging on stdout + +ant -q test 2>&1 > ant-test.log diff -Nru tomcat9-9.0.27/.travis.yml tomcat9-9.0.31/.travis.yml --- tomcat9-9.0.27/.travis.yml 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/.travis.yml 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,82 @@ +# 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. + +dist: bionic +language: java +jdk: oraclejdk8 +arch: arm64 + +addons: + apt: + packages: + - ant + - build-essential + - automake + - autoconf + - tar + - libssl-dev + - subversion + - git + - libtool-bin + +install: + - ARCH=`uname -p` + - echo $ARCH + - JDK_X64="https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u242-b08/OpenJDK8U-jdk_x64_linux_hotspot_8u242b08.tar.gz" + - JDK_ARM64="https://github.com/AdoptOpenJDK/openjdk8-binaries/releases/download/jdk8u232-b09/OpenJDK8U-jdk_aarch64_linux_hotspot_8u232b09.tar.gz" + - if test "X$ARCH" = "Xaarch64"; then JDK_URL=$JDK_ARM64; else JDK_URL=$JDK_X64; fi + - wget -q $JDK_URL && tar xzf OpenJDK*.tar.gz + - mv jdk8* jdk + - export JAVA_HOME=`pwd`/jdk + - wget -q http://mirrors.netix.net/apache/ant/binaries/apache-ant-1.10.7-bin.tar.gz && tar xzf apache-ant-*-bin.tar.gz + - export ANT_HOME=`pwd`/apache-ant-1.10.7 + - export PATH="$JAVA_HOME/bin:$ANT_HOME/bin:$PATH" + - java -version + - ant -version + - rm -rf $HOME/tmp + - export CURR_PWD=`pwd` + - svn co -q https://svn.apache.org/repos/asf/apr/apr/branches/1.6.x/ $HOME/tmp/apr + - cd $HOME/tmp/apr + - ./buildconf + - ./configure --prefix=$HOME/tmp/apr-build + - make + - make install + - export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$HOME/tmp/apr-build/lib" + - git clone -q https://github.com/apache/tomcat-native.git $HOME/tmp/tomcat-native + - cd $HOME/tmp/tomcat-native/native + - sh buildconf --with-apr=$HOME/tmp/apr + - ./configure --with-apr=$HOME/tmp/apr --with-java-home=$JAVA_HOME --with-ssl=yes --prefix=$HOME/tmp/tomcat-native-build + - make + - make install + - cd $CURR_PWD + - yes | cp build.properties.default build.properties + - echo "test.threads=16" >> build.properties + - echo "test.relaxTiming=true" >> build.properties + - echo "test.excludePerformance=true" >> build.properties + - echo "test.openssl.path=/dev/null/openssl" >> build.properties + - echo "test.apr.loc=$HOME/tmp/tomcat-native-build/lib" >> build.properties + + +script: + - ant -q clean + - travis_wait 60 "./.travis/antTest.sh" + +after_failure: + - tail -n 5000 ant-test.log + - ls -laR $HOME/tomcat-build-libs + +notifications: + email: + - dev@tomcat.apache.org diff -Nru tomcat9-9.0.27/webapps/docs/annotationapi/index.html tomcat9-9.0.31/webapps/docs/annotationapi/index.html --- tomcat9-9.0.27/webapps/docs/annotationapi/index.html 1970-01-01 00:00:00.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/annotationapi/index.html 2020-02-05 19:26:48.000000000 +0000 @@ -0,0 +1,34 @@ + + + + + + API docs + + + + +The Annotation API Javadoc is not installed by default. Download and install +the "fulldocs" package to get it. + +You can also access the javadoc online in the Tomcat + + documentation bundle. + + + diff -Nru tomcat9-9.0.27/webapps/docs/changelog.xml tomcat9-9.0.31/webapps/docs/changelog.xml --- tomcat9-9.0.27/webapps/docs/changelog.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/changelog.xml 2020-02-05 19:26:48.000000000 +0000 @@ -44,7 +44,633 @@ They eventually become mixed with the numbered issues (i.e., numbered issues do not "pop up" wrt. others). --> -
    +
    + + + + Do not store username and password as session notes during + authentication if they are not needed. (kkolinko) + + + Avoid useless environment restore when not using GSSCredential + in JNDIRealm. (remm) + + + 58577: Respect the argument-count when searching for MBean + operations to invoke via the JMXProxyServlet. (schultz) + + + 63691: Skip all jar and directory scanning when the wildcard + pattern "*" or "*.jar" is set or added to + tomcat.util.scan.StandardJarScanFilter.jarsToSkip. (isapir) + + + 64005: Correct a regression in the static resource caching + changes introduced in 9.0.28. Avoid a NullPointerException + when working with the URL provided for the root of a packed WAR. (markt) + + + 64006: Provide default configuration source based on the + current directory if none has been set, for full compatibility with + existing code. (remm) + + + 64008: Clarify/expand the Javadoc for the + Tomcat#addWebapp() and related methods. (markt) + + + Deprecate the JmxRemoteLifecycleListener as the features it + provides are now available in the remote JMX capability included with + the JRE. This listener will be removed in Tomcat 10 and may be removed + from Tomcat 9.0.x some time after 2020-12-31. (markt) + + + 64011: JNDIRealm no longer authenticates to LDAP. + (michaelo) + + + 64021: Ensure that container provided SCIs are always loaded + before application provided SCIs. Note that where both the container and + the application provide the same SCI, it is the application provided SCI + that will be used. (markt) + + + SCI definitions from JARs unpacked into WEB-INF/classes are + now handled consistently and will always be found irrespective of + whether the web application defines a JAR ordering or not. (markt) + + + 64023: Skip null-valued session attributes when deserializing + sessions. (schultz) + + + Do not throw a NullPointerException when an MBean or operation cannot + be found by the JMXProxyServlet. (schultz) + + + 64067: Allow more than one parameter when defining RewriteMaps. + (fschumacher) + + + 64074: InputStreams for directories obtained + from resource URLs now return a directory listing consistent with the + behaviour of FileURLConnection. In addition to restoring + the behaviour that was lost as a result of the introduction of + CachedResourceURLConnection, it expands the feature to + include packedWARs and to take account of resource JARs. (markt) + + + Refactor recycle facade system property into a new connector attribute + named discardFacades. (remm) + + + 64089: Add ${...} property replacement support + to XML external entity definitions. (markt) + + + Deprecate MappingData.contextPath as it is unused. (markt) + + + Fix a problem that meant that remote host, address and port information + could be missing in the access log for an HTTP/2 request where the + connection was closed unexpectely. + + + + + + + Simplify NIO blocking read and write. (remm) + + + Ensure that Servlet Asynchronous processing timeouts fire when requests + are made using HTTP/2. (markt) + + + Fix the corrupton of the TLS configuration when using the deprecated TLS + attributes on the Connector if the configuration has already been set + via the new SSLHostConfig and + SSLHostConfigCertificate elements. (markt) + + + 63966: Switch the message shown when using HTTP to connect to + an HTTPS port from ISO-8859-1 to UTF-8. (markt) + + + 64007: Cancel selection key in poller before wrapper close to + avoid possible deadlock. (remm) + + + Add support for RFC 5915 formatted, unencrypted EC key files when using + a JSSE based TLS connector. (markt) + + + Correct a regression introduced in 9.0.28 that meant invalid tokens in + the Transfer-Encoding header were ignored rather than + treated as an error. (markt) + + + Rename the HTTP Connector attribute rejectIllegalHeaderName + to rejectIllegalHeader and expand the underlying + implementation to include header values as well as names. (markt) + + + Disable (comment out in server.xml) the AJP/1.3 connector by default. + (markt) + + + Change the default bind address for the AJP/1.3 connector to be the + loopback address. (markt) + + + Rename the requiredSecret attribute of the AJP/1.3 + Connector to secret and add a new attribute + secretRequired that defaults to true. When + secretRequired is true the AJP/1.3 Connector + will not start unless the secret attribute is configured to + a non-null, non-zero length String. (markt) + + + Add a new attribute, allowedRequestAttributesPattern to + the AJP/1.3 Connector. Requests with unrecognised attributes will be + blocked with a 403. (markt) + + + + + + + Update the performance optimisation for using expressions in tags that + depend on uninitialised tag attributes with implied scope to make the + performance optimisation aware of the new public class + (java.lang.Record) added in Java 14. (markt) + + + 64097: Replace the faulty custom services lookup used for + ExpressionFactory implementations with + ServiceLoader. (markt) + + + Add a META-INF/services entry to jasper-el.jar so that the + Expression Language implementation can be discovered via the services + API. (markt) + + + + + + + 64043: Ensure that session ID changes are replicated during + form-authentication. (kfujino) + + + + + + + 64000: In the examples web application, where a Servlet + example includes ii18n support, the Locale used should be based on the + request locale and not the server locale. (markt) + + + Add additional information on securing AJP/1.3 Connectors. (markt) + + + + + + + 63995: Ensure statements are closed when a pooled JDBC + connection is passivated in Tomcat's fork of Commons DBCP2. (markt) + + + +
    +
    + + + + 63681: Introduce RealmBase#authenticate(GSSName, GSSCredential) + and friends. (michaelo) + + + 63964: Correct a regression in the static resource caching + changes introduced in 9.0.28. URLs constructed from URLs obtained from + the cache could not be used to access resources. (markt) + + + 63970: Correct a regression in the static resource caching + changes introduced in 9.0.28. Connections to URLs obtained for JAR + resources could not be cast to JarURLConnection. (markt) + + + 63937: Add a new attribute to the standard + Authenticator implementations, + allowCorsPreflight, that allows the + Authenticators to be configured to allow CORS preflight + requests to bypass authentication as required by the CORS specification. + (markt) + + + 63939: Correct the same origin check in the CORS filter. An + origin with an explicit default port is now considered to be the same as + an origin without a default port and origins are now compared in a + case-sensitive manner as required by the CORS specification. (markt) + + + 63981: Allow multiple calls to + Registry.disableRegistry() without the second and + subsequent calls triggering the logging of a warning. Based on a patch + by Andy Wilkinson. (markt) + + + 63982: CombinedRealm makes assumptions about principal implementation + (michaelo) + + + 63983: Correct a regression in the static resource caching + changes introduced in 9.0.28. A large number of file descriptors were + opened that could reach the OS limit before being released by GC. + (markt) + + + 63987: Deprecate Realm.getRoles(Principal). (michaelo) + + + Add a unit test for the session FileStore implementation + and refactor loops in FileStore to use the ForEach style. + Pull request provided by Govinda Sakhare. (markt) + + + Moved server-side include (SSI) module into a separate JAR library. (schultz) + + + Refactor FORM authentication to reduce duplicate code and to ensure that + the authenticated Principal is not cached in the session when caching is + disabled. This is the fix for CVE-2019-17563. (markt/kkolinko) + + + + + + + Fix endpoint closeSocket and destroySocket discrepancies, in particular + in the APR connector. (remm) + + + Harmonize maxConnections default value to 8192 across all connectors. + (remm) + + + 63931: Improve timeout handling for asyncIO to ensure that + blocking operations see a SocketTimeoutException if one + occurs. (remm/markt) + + + 63932: By default, do not compress content that has a strong + ETag. This behaviour is configuration for the HTTP/1.1 and HTTP/2 + connectors via the new Connector attribute + noCompressionStrongETag. (markt) + + + 63949: Fix non blocking write problems with NIO due to the + need for a write loop. (remm) + + + Simplify regular endpoint writes by removing write(Non)BlockingDirect. + All regular writes will now be buffered for a more predictable + behavior. (remm) + + + Send an exception directly to the completion handler when a timeout + exception occurs for the operation, and add a boolean to make sure the + completion handler is called only once. (remm/markt) + + + When reporting / logging invalid HTTP headers encode any non-printing + characters using the 0xNN form. (markt) + + + + + + + Ensure a couple of very unlikely concurrency issues are avoided when + writing WebSocket messages. (markt) + + + + + + + Fix the broken re-try link on the error page for the FORM authentication + example in the JSP section of the examples web application. (markt) + + + Improvements to CsrfPreventionFilter: additional logging, allow the + CSRF nonce request parameter name to be customized. + (schultz) + + + Correct the documentation for the maxConnections attribute + of the Connector in the documentation web application. + (markt) + + + Add the ability to set and display session attributes in the JSP FORM + authentication example to demonstrate session persistence across + restarts for authenticated sessions. (markt) + + + + + + + Correct the fix for 63815 (quoting the use of + CATALINA_OPTS and JAVA_OPTS when used in shell + scripts to avoid the expansion of *) as it caused various + regressions, particularly with daemon.sh. (markt) + + + Update the OWB module to Apache OpenWebBeans 2.0.13. (remm) + + + Support Java 11 in Graal Native Images with Graal 19.3+. (remm) + + + Expand the search made by the Windows installer for a suitable Java + installation to include the 64-bit JDK registry entries and the + JAVA_HOME environment variable. Pull request provided by + Alexander Norz. (markt) + + + Expand the coverage of the Korean translations provided with Apache + Tomcat. (woonsan) + + + Expand the coverage of the French translations provided with Apache + Tomcat. (remm) + + + Expand the coverage of the Chinese translations provided with Apache + Tomcat. Contributions provided by lins and 磊. (markt) + + + Update the internal fork of Apache Commons BCEL to ff6941e (2019-12-06, + 6.4.2-dev). Code clean-up only. (markt) + + + Update the internal fork of Apache Commons Codec to 9637dd4 (2019-12-06, + 1.14-SNAPSHOT). Code clean-up and a fix for CODEC-265. (markt) + + + Update the internal fork of Apache Commons FileUpload to 2317552 + (2019-12-06, 2.0-SNAPSHOT). Refactoring. (markt) + + + Update the internal fork of Apache Commons Pool 2 to 6092f92 (2019-12-06, + 2.8.0-SNAPSHOT). Clean-up and minor refactoring. (markt) + + + Update the internal fork of Apache Commons DBCP 2 to a36390 (2019-12-06, + 2.7.1-SNAPSHOT). Minor refactoring. (markt) + + + +
    +
    + + + + Refactor JMX remote RMI registry creation. This is the fix for + CVE-2019-12418. (remm) + + + Improvement to CsrfPreventionFilter: expose the latest available nonce + as a request attribute; expose the expected nonce request parameter + name as a context attribute. + (schultz) + + + + + + + 63835: Add support for Keep-Alive response header. (michaelo) + + + Correct a logic bug in the NioEndpoint timeout handling + that meant a write timeout could be handled as a read timeout. (markt) + + + + + + + Add a warning regarding potential poor performance of the HTTP and AJP + connectors if socket.txBufSize is configured with an + explicit value rather than using the JVM default. (markt) + + + + + + + Improve OWB module based using custom shade appender. (remm) + + + Add security filter in OWB module in addition to the valve for more flexibility. (remm) + + + +
    +
    + + + + Bad paths for URIs can cause exceptions on Windows due to its + path separator, so wrap using an IOException. (remm) + + + 63832: Properly mark container as FAILED when a JVM error + occurs on stop. (remm) + + + Add more details on the usage of RewriteMap + functionality in the RewriteValve. (fschumacher) + + + 63836 Ensure that references to the Host object are cleared + once the Host instance is destroyed. (markt) + + + Ensure that, when static resource caching is enabled for a web + application, all access to static files (including JSP files) goes via + the cache so that a consistent view of the static files is seen. Prior + to this change it was possible to see an updated last modified time but + the content would be that prior to the modification. (markt) + + + 63905 Clean up Tomcat CSS. (michaelo) + + + 63909: When the ExpiresFilter is used without a + default and the response is served by the Default Servlet, ensure that + the filter processes the response if the Default Servlet sets a 304 (Not + Found) status code. (markt) + + + + + + + Ensure that ServletRequest.isAsyncStarted() returns + false once AsyncContext.complete() or + AsyncContext.dispatch() has been called during + AsyncListener.onTimeout() or + AsyncListener.onError(). (markt) + + + 63816 and 63817: Correctly handle I/O errors after + asynchronous processing has been started but before the container thread + that started asynchronous processing has completed processing the + current request/response. (markt) + + + 63825: When processing the Expect and + Connection HTTP headers looking for a specific token, be + stricter in ensuring that the exact token is present. (markt) + + + 63829: Improve the check of the Content-Encoding + header when looking to see if Tomcat is serving pre-compressed content. + Ensure that only a full token is matched and that the match is case + insensitive. (markt) + + + 63864: Refactor parsing of the transfer-encoding + request header to use the shared parsing code and reduce duplication. + (markt) + + + 63865: Add Unset option to same-site cookies + and pass through None value if set by user. Patch provided + by John Kelly. (markt) + + + 63879: Remove stack trace from debug logging on socket + wrapper close. (remm) + + + Add connection tracking on the connector endpoint to remove excessive + concurrency in the protocol handler when maintaining an association + between the socket wrapper and its current processor. (remm) + + + 63894: Ensure that the configured values for + certificateVerification and + certificateVerificationDepth are correctly passed to the + OpenSSL based SSLEngine implementation. (remm/markt) + + + Improve cleanup after errors when setting socket options. (remm) + + + 63859: Do not perform a blocking read after a + CPING message is received by the AJP connector because, if + the JK Connector is configured with + ping_mode="I", the CPING message + will not always be followed by the start of a request. (markt) + + + Properly calculate all dynamic parts of the ErrorReportValve response + on the fly in + org.apache.coyote.http2.TestHttp2InitialConnection. + (michaelo) + + + + + + + 63897: Capture the timestamp of a JSP for the purposes of + modification tracking before the JSP is compiled to prevent a race + condition if the JSP is modified during compilation. Patch provided by + Karl von Randow. (markt) + + + Fix a race condition that could mean changes to a modified JSP were not + visible to end users. (markt) + + + + + + + 63913: Wrap any NullPointerExceptions throw by + the Inflater or Deflater used by the + PerMessageDeflate extension in an IOException + so that the error can be caught and handled by the WebSocket error + handling mechanism. (markt) + + + + + + + Correct the description of the default value for the server attribute in + the security How-To. (markt) + + + + + + + 63815: Quote the use of CATALINA_OPTS and + JAVA_OPTS when used in shell scripts to avoid the expansion + of *. Note that any newlines present in + CATALINA_OPTS and/or JAVA_OPTS will no longer + removed. (markt) + + + 63826: Remove commons-daemon-native.tar.gz and + tomcat-native.tar.gz from the binary zip distributions for + Windows since compiled versions of those components are already + included within the zip distributions. (markt) + + + 63838: Suppress reflexive access warnings when running the + unit tests on the command line. (markt) + + + Add missing charsets from the HPE JVM on HP-UX to pass unit tests in + org.apache.tomcat.util.buf.TestCharsetCache. (michaelo) + + + Update the CXF module to Apache CXF 3.3.4. (remm) + + + Expand the coverage and quality of the French translations provided + with Apache Tomcat. (remm) + + + Expand the coverage and quality of the Japanese translations provided + with Apache Tomcat. Patch provided by motohashi.yuki. (markt) + + + Expand the coverage and quality of the Simplified Chinese translations + provided with Apache Tomcat. Contributions provided by rpo130, Mason + Shen, leeyazhou, winsonzhao, qingshi huang, Lay, Shucheng Hou and + Yanming Zhou. (markt) + + + Expand the coverage and quality of the Brazilian Portuguese translations + provided with Apache Tomcat. Patch provided by Danielamorais. (markt) + + + +
    +
    @@ -87,7 +713,7 @@ 63781: When performing various checks related to the visibility of classes, fields an methods in the EL implementation, also - check that the containing modeul has been exported. (markt) + check that the containing module has been exported. (markt) @@ -106,12 +732,16 @@ - + Add base GraalVM documentation. (remm) - - + + Add Javadoc for the Common Annotations API implementation. (markt) - + + + Correct various typos in the comments, error messages and Javadoc. Patch + provided by 康智冬. (markt) + @@ -257,7 +887,7 @@ 63733: Remove the documentation for the "Additional - Components" since they have been remove / merged into the core + Components" since they have been removed / merged into the core Tomcat distribution for 9.0.5 onwards. (markt) @@ -306,7 +936,7 @@ When performing a silent install with the Windows Installer, ensure that - the registry entires are added to the 64-bit registry when using a + the registry entries are added to the 64-bit registry when using a 64-bit JVM. (markt) diff -Nru tomcat9-9.0.27/webapps/docs/class-loader-howto.xml tomcat9-9.0.31/webapps/docs/class-loader-howto.xml --- tomcat9-9.0.27/webapps/docs/class-loader-howto.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/class-loader-howto.xml 2020-02-05 19:26:48.000000000 +0000 @@ -135,6 +135,7 @@ container portion of Tomcat.
  • catalina-ant.jar — Tomcat Catalina Ant tasks.
  • catalina-ha.jar — High availability package.
  • +
  • catalina-ssi.jar — Server-side Includes module.
  • catalina-storeconfig.jar — Generation of XML configuration files from current state
  • catalina-tribes.jar — Group communication package.
  • diff -Nru tomcat9-9.0.27/webapps/docs/config/ajp.xml tomcat9-9.0.31/webapps/docs/config/ajp.xml --- tomcat9-9.0.27/webapps/docs/config/ajp.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/ajp.xml 2020-02-05 19:26:48.000000000 +0000 @@ -44,6 +44,13 @@ contained in the web application, and/or utilize Apache's SSL processing.

    +

    Use of the AJP protocol requires additional security considerations because + it allows greater direct manipulation of Tomcat's internal data structures + than the HTTP connectors. Particular attention should be paid to the values + used for the address, secret, + secretRequired and allowedRequestAttributesPattern + attributes.

    +

    This connector supports load balancing when used in conjunction with the jvmRoute attribute of the Engine.

    @@ -308,10 +315,26 @@

    For servers with more than one IP address, this attribute specifies which address will be used for listening on the specified - port. By default, this port will be used on all IP addresses - associated with the server. A value of 127.0.0.1 - indicates that the Connector will only listen on the loopback - interface.

    + port. By default, the loopback address will be used.

    +
    + + +

    The AJP protocol passes some information from the reverse proxy to the + AJP connector using request attributes. These attributes are:

    +
      +
    • javax.servlet.request.cipher_suite
    • +
    • javax.servlet.request.key_size
    • +
    • javax.servlet.request.ssl_session
    • +
    • javax.servlet.request.X509Certificate
    • +
    • AJP_LOCAL_ADDR
    • +
    • AJP_REMOTE_PORT
    • +
    • AJP_SSL_PROTOCOL
    • +
    • JK_LB_ACTIVATION
    • +
    +

    The AJP protocol supports the passing of arbitrary request attributes. + Requests containing arbitrary request attributes will be rejected with a + 403 response unless the entire attribute name matches this regular + expression. If not specified, the default value is null.

    @@ -376,14 +399,10 @@ falls below maxConnections at which point the server will start accepting and processing new connections again. Note that once the limit has been reached, the operating system may still accept connections - based on the acceptCount setting. The default value varies by - connector type. For NIO and NIO2 the default is 10000. - For APR/native, the default is 8192.

    -

    Note that for APR/native on Windows, the configured value will be - reduced to the highest multiple of 1024 that is less than or equal to - maxConnections. This is done for performance reasons.
    - If set to a value of -1, the maxConnections feature is disabled - and connections are not counted.

    + based on the acceptCount setting. The default value + is 8192.

    +

    For NIO/NIO2 only, setting the value to -1, will disable the + maxConnections feature and connections will not be counted.

    @@ -435,9 +454,20 @@ expected concurrent requests (synchronous and asynchronous).

    - +

    Only requests from workers with this secret keyword will be accepted. -

    + The default value is null. This attrbute must be specified + with a non-null, non-zero length value unless + secretRequired is explicitly configured to be + false.

    +
    + + +

    If this attribute is true, the AJP Connector will only + start if the secret attribute is configured with a + non-null, non-zero length value. The default value is true. + This attributue should only be set to false when the + Connector is used on a trusted network.

    @@ -505,7 +535,9 @@

    (int)The socket send buffer (SO_SNDBUF) size in bytes. JVM default - used if not set.

    + used if not set. Care should be taken if explicitly setting this value. + Very poor performance has been observed on some JVMs with values less + than ~8k.

    (bool)This is equivalent to standard attribute diff -Nru tomcat9-9.0.27/webapps/docs/config/cluster-interceptor.xml tomcat9-9.0.31/webapps/docs/config/cluster-interceptor.xml --- tomcat9-9.0.27/webapps/docs/config/cluster-interceptor.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/cluster-interceptor.xml 2020-02-05 19:26:48.000000000 +0000 @@ -205,7 +205,7 @@

    If using the TcpFailureDetector, the EncryptInterceptor must be inserted into the interceptor chain before the - TcpFailureDetector. This is becuase when validating cluster + TcpFailureDetector. This is because when validating cluster members, TcpFailureDetector writes channel data directly to the other members without using the remainder of the interceptor chain, but on the receiving side, the message still goes through the chain (in reverse). diff -Nru tomcat9-9.0.27/webapps/docs/config/cluster.xml tomcat9-9.0.31/webapps/docs/config/cluster.xml --- tomcat9-9.0.27/webapps/docs/config/cluster.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/cluster.xml 2020-02-05 19:26:48.000000000 +0000 @@ -52,6 +52,7 @@

    There are many options for providing a secure, trusted network for use by a Tomcat cluster. These include:

      +
    • EncryptInterceptor
    • private LAN
    • a Virtual Private Network (VPN)
    • IPSEC
    • diff -Nru tomcat9-9.0.27/webapps/docs/config/cookie-processor.xml tomcat9-9.0.31/webapps/docs/config/cookie-processor.xml --- tomcat9-9.0.27/webapps/docs/config/cookie-processor.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/cookie-processor.xml 2020-02-05 19:26:48.000000000 +0000 @@ -102,9 +102,12 @@

      Enables setting same-site cookie attribute.

      -

      If value is none then the same-site cookie attribute +

      If value is unset then the same-site cookie attribute won't be set. This is the default value.

      +

      If value is none then the same-site cookie attribute + will be set and the cookie will always be sent in cross-site requests.

      +

      If value is lax then the browser only sends the cookie in same-site requests and cross-site top level GET requests.

      @@ -174,9 +177,12 @@

      Enables setting same-site cookie attribute.

      -

      If value is none then the same-site cookie attribute +

      If value is unset then the same-site cookie attribute won't be set. This is the default value.

      +

      If value is none then the same-site cookie attribute + will be set and the cookie will always be sent in cross-site requests.

      +

      If value is lax then the browser only sends the cookie in same-site requests and cross-site top level GET requests.

      diff -Nru tomcat9-9.0.27/webapps/docs/config/http2.xml tomcat9-9.0.31/webapps/docs/config/http2.xml --- tomcat9-9.0.27/webapps/docs/config/http2.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/http2.xml 2020-02-05 19:26:48.000000000 +0000 @@ -179,6 +179,14 @@ means no limit. If not specified, a default of 8192 is used.

      + +

      This flag configures whether resources with a stong ETag will be + considered for compression. If true, resources with a strong + ETag will not be compressed. The default value is true.

      +

      This attribute is deprecated. It will be removed in Tomcat 10 onwards + where it will be hard-coded to true.

      +
      +

      The value is a regular expression (using java.util.regex) matching the user-agent header of HTTP clients for which diff -Nru tomcat9-9.0.27/webapps/docs/config/http.xml tomcat9-9.0.31/webapps/docs/config/http.xml --- tomcat9-9.0.27/webapps/docs/config/http.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/http.xml 2020-02-05 19:26:48.000000000 +0000 @@ -94,6 +94,17 @@ _default_ will be used.

      + +

      A boolean value which can be used to enable or disable the recycling + of the facade objects that isolate the container internal request + processing objects. If set to true the facades will be + set for garbage collection after every request, otherwise they will be + reused. This setting has no effect when the security manager is enabled. + If not specified, this attribute is set to the value of the + org.apache.catalina.connector.RECYCLE_FACADES system + property, or false if not set.

      +
      +

      Set to true if you want calls to request.getRemoteHost() to perform DNS lookups in @@ -448,14 +459,10 @@ falls below maxConnections at which point the server will start accepting and processing new connections again. Note that once the limit has been reached, the operating system may still accept connections - based on the acceptCount setting. The default value varies by - connector type. For NIO and NIO2 the default is 10000. - For APR/native, the default is 8192.

      -

      Note that for APR/native on Windows, the configured value will be - reduced to the highest multiple of 1024 that is less than or equal to - maxConnections. This is done for performance reasons.
      - If set to a value of -1, the maxConnections feature is disabled - and connections are not counted.

      + based on the acceptCount setting. The default value + is 8192.

      +

      For NIO/NIO2 only, setting the value to -1, will disable the + maxConnections feature and connections will not be counted.

      @@ -524,6 +531,14 @@ used.

      + +

      This flag configures whether resources with a stong ETag will be + considered for compression. If true, resources with a strong + ETag will not be compressed. The default value is true.

      +

      This attribute is deprecated. It will be removed in Tomcat 10 onwards + where it will be hard-coded to true.

      +
      +

      The value is a regular expression (using java.util.regex) matching the user-agent header of HTTP clients for which @@ -543,14 +558,19 @@ expected concurrent requests (synchronous and asynchronous).

      - -

      If an HTTP request is received that contains an illegal header name - (i.e. the header name is not a token) this setting determines if the + +

      If an HTTP request is received that contains an illegal header name or + value (e.g. the header name is not a token) this setting determines if the request will be rejected with a 400 response (true) or if the illegal header be ignored (false). The default value is true which will cause the request to be rejected.

      + +

      This attribute is deprecated. It will be removed in Tomcat 10 onwards. + It is now an alias for rejectIllegalHeader.

      +
      +

      The HTTP/1.1 specification requires that certain characters are %nn encoded when @@ -640,11 +660,18 @@ -

      (bool)Use this attribute to enable or disable usage of the +

      (bool) Use this attribute to enable or disable usage of the asynchronous IO API. The default value is true except when using the APR connector due to low performance.

      + +

      (bool) Use this attribute to enable or disable the addition of the + Keep-Alive HTTP response header as described in + this + Internet-Draft. The default value is true.

      +
      + @@ -662,7 +689,9 @@

      (int)The socket send buffer (SO_SNDBUF) size in bytes. JVM default - used if not set.

      + used if not set. Care should be taken if explicitly setting this value. + Very poor performance has been observed on some JVMs with values less + than ~8k.

      (bool)This is equivalent to standard attribute diff -Nru tomcat9-9.0.27/webapps/docs/config/listeners.xml tomcat9-9.0.31/webapps/docs/config/listeners.xml --- tomcat9-9.0.27/webapps/docs/config/listeners.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/listeners.xml 2020-02-05 19:26:48.000000000 +0000 @@ -504,8 +504,60 @@ + + +

      The HTTPD mod_heartmonitor Listener allows tomcat to send heart beat message to + the Apache HTTPD mod_heartmonitor module.

      + +

      The following additional attributes are supported by the HTTPD mod_heartmonitor + Listener:

      + + + +

      Port the connector that will received proxied traffic from HTTPD, default the first connector will be used

      +
      + + +

      Host it is the IP corresponding the address of the connector that will received proxied traffic, + default empty the Port will be used

      +
      + + +

      proxyURL is the URL corresponding to the Location in httpd configuration of the heartbeat Handler, + default /HeartbeatListener

      +
      + + +

      ProxyList is the list of proxies from which tomcat is going to receive requests, + formatted like "address:port,address:port" once filled the multicast logic is disable and the multi parameters are + ignored

      +
      + + +

      Group is the Multicast IP to boardcast messages to HTTPD, default 224.0.1.105

      +
      + + +

      Multiport is the Multicast port to boardcast messages to HTTPD, default 23364

      +
      + + +

      Ttl is the TTL for the boardcast messages, default 16

      +
      + +
      + +
    + +
    + +

    This listener is now deprecated as the features it provides are + now available in the remote JMX capability included with the JRE. This + listener will be removed in Tomcat 10 and may be removed from Tomcat 9 some + time after 2020-12-31.

    +

    The JMX Remote Lifecycle Listener fixes the ports used by the JMX/RMI Server making things much simpler if you need to connect visualvm or a similar tool to a remote Tomcat instance that is running @@ -656,50 +708,6 @@ - - -

    The HTTPD mod_heartmonitor Listener allows tomcat to send heart beat message to - the Apache HTTPD mod_heartmonitor module.

    - -

    The following additional attributes are supported by the HTTPD mod_heartmonitor - Listener:

    - - - -

    Port the connector that will received proxied traffic from HTTPD, default the first connector will be used

    -
    - - -

    Host it is the IP corresponding the address of the connector that will received proxied traffic, - default empty the Port will be used

    -
    - - -

    proxyURL is the URL corresponding to the Location in httpd configuration of the heartbeat Handler, - default /HeartbeatListener

    -
    - - -

    ProxyList is the list of proxies from which tomcat is going to receive requests, - formatted like "address:port,address:port" once filled the multicast logic is disable and the multi parameters are - ignored

    -
    - - -

    Group is the Multicast IP to boardcast messages to HTTPD, default 224.0.1.105

    -
    - - -

    Multiport is the Multicast port to boardcast messages to HTTPD, default 23364

    -
    - - -

    Ttl is the TTL for the boardcast messages, default 16

    -
    - -
    -
    -
    diff -Nru tomcat9-9.0.27/webapps/docs/config/valve.xml tomcat9-9.0.31/webapps/docs/config/valve.xml --- tomcat9-9.0.27/webapps/docs/config/valve.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/config/valve.xml 2020-02-05 19:26:48.000000000 +0000 @@ -1201,6 +1201,21 @@ + +

    Are requests that appear to be CORS preflight requests allowed to + bypass the authenticator as required by the CORS specification. The + allowed values are never, filter and + always. never means that a request will never + bypass authentication even if it appears to be a CORS preflight request. + filter means that a request will bypass authentication if + it appears to be a CORS preflight request; it is mapped to a web + application that has the CORS + Filter enabled; and the CORS Filter is mapped to /*. + always means that all requests that appear to be CORS + preflight requests will bypass authentication. If not set, the default + value is never.

    +
    +

    Should a session always be used once a user is authenticated? This may offer some performance benefits since the session can then be used @@ -1344,6 +1359,21 @@ + +

    Are requests that appear to be CORS preflight requests allowed to + bypass the authenticator as required by the CORS specification. The + allowed values are never, filter and + always. never means that a request will never + bypass authentication even if it appears to be a CORS preflight request. + filter means that a request will bypass authentication if + it appears to be a CORS preflight request; it is mapped to a web + application that has the CORS + Filter enabled; and the CORS Filter is mapped to /*. + always means that all requests that appear to be CORS + preflight requests will bypass authentication. If not set, the default + value is never.

    +
    +

    Should a session always be used once a user is authenticated? This may offer some performance benefits since the session can then be used @@ -1511,6 +1541,21 @@ + +

    Are requests that appear to be CORS preflight requests allowed to + bypass the authenticator as required by the CORS specification. The + allowed values are never, filter and + always. never means that a request will never + bypass authentication even if it appears to be a CORS preflight request. + filter means that a request will bypass authentication if + it appears to be a CORS preflight request; it is mapped to a web + application that has the CORS + Filter enabled; and the CORS Filter is mapped to /*. + always means that all requests that appear to be CORS + preflight requests will bypass authentication. If not set, the default + value is never.

    +
    +

    Controls if the session ID is changed if a session exists at the point where users are authenticated. This is to prevent session fixation @@ -1637,6 +1682,21 @@ + +

    Are requests that appear to be CORS preflight requests allowed to + bypass the authenticator as required by the CORS specification. The + allowed values are never, filter and + always. never means that a request will never + bypass authentication even if it appears to be a CORS preflight request. + filter means that a request will bypass authentication if + it appears to be a CORS preflight request; it is mapped to a web + application that has the CORS + Filter enabled; and the CORS Filter is mapped to /*. + always means that all requests that appear to be CORS + preflight requests will bypass authentication. If not set, the default + value is never.

    +
    +

    Should we cache authenticated Principals if the request is part of an HTTP session? If not specified, the default value of true @@ -1737,15 +1797,19 @@ - -

    A fix introduced in Java 8 update 40 ( - JDK-8048194) - onwards broke SPNEGO authentication for IE with Tomcat running on - Windows 2008 R2 servers. This option enables a work-around that allows - SPNEGO authentication to continue working. The work-around should not - impact other configurations so it is enabled by default. If necessary, - the workaround can be disabled by setting this attribute to - false.

    + +

    Are requests that appear to be CORS preflight requests allowed to + bypass the authenticator as required by the CORS specification. The + allowed values are never, filter and + always. never means that a request will never + bypass authentication even if it appears to be a CORS preflight request. + filter means that a request will bypass authentication if + it appears to be a CORS preflight request and the web application the + request maps to has the CORS + Filter enabled and mapped to /*. always + means that all requests that appear to be CORS preflight requests will + bypass authentication. If not set, the default value is + never.

    @@ -1760,6 +1824,17 @@ false will be used.

    + +

    A fix introduced in Java 8 update 40 ( + JDK-8048194) + onwards broke SPNEGO authentication for IE with Tomcat running on + Windows 2008 R2 servers. This option enables a work-around that allows + SPNEGO authentication to continue working. The work-around should not + impact other configurations so it is enabled by default. If necessary, + the workaround can be disabled by setting this attribute to + false.

    +
    +

    Should we cache authenticated Principals if the request is part of an HTTP session? If not specified, the default value of true @@ -1900,8 +1975,7 @@ status codes and/or exception types.

    NOTE: Disabling both showServerInfo and showReport will - only return the HTTP status code and remove all CSS from the default - response.

    + only return the HTTP status code.

    @@ -1922,7 +1996,7 @@

    The location of the UTF-8 encoded HTML file to return for the HTTP error code represented by nnn. For example, errorCode.404 specifies the file to return for an HTTP 404 - error. The location may be relative or absolule. If relative, it must be + error. The location may be relative or absolute. If relative, it must be relative to $CATALINA_BASE. The special value of errorCode.0 may be used to define a default error page to be used if no error page is defined for a status code. If no matching diff -Nru tomcat9-9.0.27/webapps/docs/graal.xml tomcat9-9.0.31/webapps/docs/graal.xml --- tomcat9-9.0.27/webapps/docs/graal.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/graal.xml 2020-02-05 19:26:48.000000000 +0000 @@ -35,9 +35,9 @@

    - Tomcat supports using the GraalVM Native Image tool to produce a native - binary including the container. This documentation page describes the - build process of such an image. + Tomcat supports using the GraalVM 19.3 Native Image tool to produce + a native binary including the container. This documentation page + describes the build process of such an image.

    @@ -57,7 +57,7 @@

    Download and install GraalVM. The first step is then to add the native-image tool. - export JAVA_HOME=/absolute...path...to/graalvm-ce-x.y.z + export JAVA_HOME=/absolute...path...to/graalvm-ce-javaX-x.y.z cd $JAVA_HOME/bin ./gu install native-image Download the Tomcat Maven packaging from @@ -103,7 +103,7 @@

    Run the GraalVM substrate VM using the trace agent: $JAVA_HOME/bin/java\ - -agentlib:native-image-agent=trace-output=$TOMCAT_MAVEN/target/trace-file.json\ + -agentlib:native-image-agent=config-output-dir=$TOMCAT_MAVEN/target/\ -Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties\ -jar target/tomcat-maven-1.0.jar

    @@ -117,11 +117,7 @@

    - Once that is done, the VM may be shut down. The descriptors can now be - generated from the trace file. - $JAVA_HOME/bin/native-image-configure generate\ - --trace-input=$TOMCAT_MAVEN/target/trace-file.json\ - --output-dir=$TOMCAT_MAVEN/target + The descriptors have now been generated in the agent output directory. At this point, further configuration must be made to add items that are not traced, including: base interfaces, resource bundles, BeanInfo based reflection, etc. Please refer to the Graal documentation for more @@ -139,18 +135,19 @@ --allow-incomplete-classpath --enable-https\ --initialize-at-build-time=org.eclipse.jdt,org.apache.el.parser.SimpleNode,javax.servlet.jsp.JspFactory,org.apache.jasper.servlet.JasperInitializer,org.apache.jasper.runtime.JspFactoryImpl\ -H:+JNI -H:+ReportUnsupportedElementsAtRuntime\ - -H:+ReportExceptionStackTraces -H:EnableURLProtocols=http,https,jar\ + -H:+ReportExceptionStackTraces -H:EnableURLProtocols=http,https,jar,jrt\ -H:ConfigurationFileDirectories=$TOMCAT_MAVEN/target/\ -H:ReflectionConfigurationFiles=$TOMCAT_MAVEN/tomcat-reflection.json\ -H:ResourceConfigurationFiles=$TOMCAT_MAVEN/tomcat-resource.json\ -H:JNIConfigurationFiles=$TOMCAT_MAVEN/tomcat-jni.json\ -jar $TOMCAT_MAVEN/target/tomcat-maven-1.0.jar + The additional --static parameter enables static linking of + glibc, zlib and libstd++ in the generated binary.

    Running the native image is then: - ./tomcat-maven-1.0 -Djava.library.path=$JAVA_HOME/jre/lib/amd64\ - -Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties + ./tomcat-maven-1.0 -Dcatalina.base=. -Djava.util.logging.config.file=conf/logging.properties

    @@ -159,14 +156,7 @@

    Servlets, JSPs, EL, websockets, the Tomcat container, tomcat-native, HTTP/2 - are all supported out of the box in a native image. However, EL uses - BeanInfo reflection which needs manual descriptor configuration. - To give an example, the EL expression - ${pageContext.servletContext.serverInfo} needs full - reflection information on the concrete Jasper page context class, as well - as the Catalina Servlet context implementation. Graal error messages - during runtime generally indicate which classes are missing reflection - as the BeanInfo reported for these will be empty. + are all supported out of the box in a native image.

    @@ -183,6 +173,20 @@ listeners (use of internal code that does not exist in Graal).

    +

    + Missing items for better Tomcat functionality: +

      +
    • Java serialization: Clustering and session persistence are not + functional
    • +
    • JMX: The usual monitoring and management is not usable
    • +
    • java.util.logging LogManager: Configuration through a system property + is not implemented, so standard java.util.logging must be used instead + of JULI
    • +
    • Static linking configuration: tomcat-native cannot be statically + linked
    • +
    +

    + diff -Nru tomcat9-9.0.27/webapps/docs/manager-howto.xml tomcat9-9.0.31/webapps/docs/manager-howto.xml --- tomcat9-9.0.27/webapps/docs/manager-howto.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/manager-howto.xml 2020-02-05 19:26:48.000000000 +0000 @@ -918,8 +918,6 @@ OK - Connector / Trusted Certificate information Connector[HTTP/1.1-8080] SSL is not enabled for this connector -Connector[AJP/1.3-8009] -SSL is not enabled for this connector Connector[HTTP/1.1-8443]-_default_ [ [ diff -Nru tomcat9-9.0.27/webapps/docs/monitoring.xml tomcat9-9.0.31/webapps/docs/monitoring.xml --- tomcat9-9.0.27/webapps/docs/monitoring.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/monitoring.xml 2020-02-05 19:26:48.000000000 +0000 @@ -47,26 +47,45 @@ to monitor it locally, using the same user that Tomcat runs with.

    The Oracle website includes the list of options and how to configure - JMX Remote on Java 6: - + JMX Remote on Java 8: + http://docs.oracle.com/javase/6/docs/technotes/guides/management/agent.html.

    -

    The following is a quick configuration guide for Java 6:

    +

    The following is a quick configuration guide for Java 8:

    Add the following parameters to setenv.bat script of your Tomcat (see RUNNING.txt for details).
    Note: This syntax is for Microsoft Windows. The command has to be on the same line. It is wrapped to be more readable. If Tomcat is running as a Windows service, use its configuration dialog to set java options for the service. - For un*xes remove "set " from beginning of the line. + For Linux, MacOS, etc, remove "set " from beginning of the + line.

    - +

    If you don't set com.sun.management.jmxremote.rmi.port then the +JSR 160 JMX-Adaptor will select a port at random which will may it difficult to +configure a firewall to allow access.

    +

    If you require TLS:

      -
    1. If you require authorization, add and change this: +
    2. change and add this: +
    3. +
    4. to configure the protocols and/or cipher suites use: +
    5. +
    6. to client certificate authentication use: +
    7. +
    +

    If you require authorization (it is strongly recommended that TLS is always +used with authentication):

    +
      +
    1. change and add this: @@ -81,17 +100,23 @@ Tip: The password file should be read-only and only accessible by the operating system user Tomcat is running as.
    2. +
    3. Alterantively, you can configure a JAAS login module with: +
    -

    Note: The JSR 160 JMX-Adaptor opens a second data channel - on a random port. That is a problem when you have a local firewall installed. - To fix it, configure a JmxRemoteLifecycleListener, as described - in listeners documentation. -

    + +

    If you need to specify a host name to be used in the RMI stubs sent to the +client (e.g. because the public host name that must be used to connect is not +the same as the local host name) then you can set:

    + + +

    If you need to specify a specific interface for the JMX service to bind to +then you can set:

    +
    -

    To simplify JMX usage with Ant 1.6.x, a set of tasks is provided that may +

    To simplify JMX usage with Ant, a set of tasks is provided that may be used with antlib.

    antlib: Copy your catalina-ant.jar from $CATALINA_HOME/lib to $ANT_HOME/lib.

    The following example shows the JMX Accessor usage:
    diff -Nru tomcat9-9.0.27/webapps/docs/rewrite.xml tomcat9-9.0.31/webapps/docs/rewrite.xml --- tomcat9-9.0.27/webapps/docs/rewrite.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/rewrite.xml 2020-02-05 19:26:48.000000000 +0000 @@ -17,6 +17,7 @@ --> + ]> @@ -98,7 +99,7 @@ RewriteMap expansions: These are expansions of the form ${mapname:key|default}. - See the documentation for + See the documentation for RewriteMap for more details.

  • @@ -400,10 +401,60 @@ +

    The referenced implementation of such a class – in our example rewriteMapClassName – + will be instantiated and initialized with the optional parameter – optionalParameters from above + (be careful with whitespace) – by calling setParameters(String). That instance + will then be registered under the name given as the first paramter of RewriteMap rule.

    + +

    Note: Starting with Tomcat 9 you can use more than one parameter. These have to be separated by spaces. Parameters + can be quoted with ". This enables space characters inside parameters.

    + +

    That map instance will be given the the lookup value that is configured in the corresponding RewriteRule by + calling lookup(String). Your implementation is free to return null to indicate, + that the given default should be used, or to return a replacement value.

    + +

    Say, you want to implement a rewrite map function that converts all lookup keys to uppercase. You + would start by implementing a class that implements the RewriteMap interface.

    + + + +

    Compile this class, put it into a jar and place that jar in ${CATALINA_BASE}/lib.

    + +

    Having done that, you can now define a map with the RewriteMap directive + and further on use that map in a RewriteRule.

    + +RewriteMap uc example.maps.UpperCaseMap + +RewriteRule ^/(.*)$ ${uc:$1} + + +

    With this setup a request to the url path /index.html would get routed + to /INDEX.HTML.

    @@ -509,7 +560,7 @@
  • server-variables as in rule condition test-strings (%{VARNAME})
  • -
  • mapping-function calls +
  • mapping-function calls (${mapname:key|default})
  • Back-references are identifiers of the form diff -Nru tomcat9-9.0.27/webapps/docs/security-howto.xml tomcat9-9.0.31/webapps/docs/security-howto.xml --- tomcat9-9.0.27/webapps/docs/security-howto.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/security-howto.xml 2020-02-05 19:26:48.000000000 +0000 @@ -246,18 +246,31 @@ -

    By default, an HTTP and an AJP connector are configured. Connectors - that will not be used should be removed from server.xml.

    +

    By default, a non-TLS, HTTP/1.1 connector is configured on port 8080. + Connectors that will not be used should be removed from server.xml.

    + +

    AJP Connectors should only be used on trusted networks or be + appropriately secured with a suitable secret attribute.

    + +

    AJP Connectors block forwarded requests with unknown request + attributes. Known safe and/or expected attributes may be allowed by + configuration an appropriate regular expression for the + allowedRequestAttributesPattern attribute.

    The address attribute may be used to control which IP - address the connector listens on for connections. By default, the - connector listens on all configured IP addresses.

    + address a connector listens on for connections. By default, a connector + listens on all configured IP addresses.

    The allowTrace attribute may be used to enable TRACE requests which can be useful for debugging. Due to the way some browsers handle the response from a TRACE request (which exposes the browser to an XSS attack), support for TRACE requests is disabled by default.

    +

    The discardFacades attribute set to true + will cause a new facade object to be created for each request. This + reduces the chances of a bug in an application exposing data from one + request to another.

    +

    The maxPostSize attribute controls the maximum size of a POST request that will be parsed for parameters. The parameters are cached for the duration of the request so this is limited to 2MB by @@ -286,8 +299,9 @@

    The server attribute controls the value of the Server HTTP header. The default value of this header for Tomcat 4.1.x to - .x is Apache-Coyote/1.1. This header can provide - limited information to both legitimate clients and attackers.

    + 8.0.x is Apache-Coyote/1.1. From 8.5.x onwards this header is not set by + default. This header can provide limited information to both legitimate + clients and attackers.

    The SSLEnabled, scheme and secure attributes may all be independently set. These are @@ -448,11 +462,6 @@

    -

    Setting org.apache.catalina.connector.RECYCLE_FACADES - system property to true will cause a new facade object to be - created for each request. This reduces the chances of a bug in an - application exposing data from one request to another.

    -

    The org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH and org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH diff -Nru tomcat9-9.0.27/webapps/docs/setup.xml tomcat9-9.0.31/webapps/docs/setup.xml --- tomcat9-9.0.27/webapps/docs/setup.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/setup.xml 2020-02-05 19:26:48.000000000 +0000 @@ -61,12 +61,17 @@ administration tool and its documentation).

  • Java location: The installer will provide a default JRE to use to run the service. The installer uses the registry to - determine the base path of a Java or later JRE, including the JRE - installed as part of the full JDK. When running on a 64-bit - operating system, the installer will first look for a 64-bit JRE and - only look for a 32-bit JRE if a 64-bit JRE is not found. It is not - mandatory to use the default JRE detected by the installer. Any - installed Java or later JRE (32-bit or 64-bit) may be used.
  • + determine the base path of a Java or later JRE, + including the JRE installed as part of the full JDK. When running on + a 64-bit operating system, the installer will first look for a + 64-bit JRE and only look for a 32-bit JRE if a 64-bit JRE is not + found. If a JRE cannot be found when running on a 64-bit operating + system, the installer will look for a 64-bit JDK. Finally, if a JRE + or JDK has not been found, the installer will try to use the + JAVA_HOME environment variable. It is not mandatory to + use the default JRE detected by the installer. Any installed Java + or later JRE (32-bit or 64-bit) may be + used.
  • Tray icon: When Tomcat is run as a service, there will not be any tray icon present when Tomcat is running. Note that when choosing to run Tomcat at the end of installation, the tray @@ -80,7 +85,6 @@
  • JavaHome
  • TomcatPortShutdown
  • TomcatPortHttp
  • -
  • TomcatPortAjp
  • TomcatMenuEntriesEnable
  • TomcatShortcutAllUsers
  • TomcatServiceDefaultName
  • diff -Nru tomcat9-9.0.27/webapps/docs/windows-auth-howto.xml tomcat9-9.0.31/webapps/docs/windows-auth-howto.xml --- tomcat9-9.0.27/webapps/docs/windows-auth-howto.xml 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/docs/windows-auth-howto.xml 2020-02-05 19:26:48.000000000 +0000 @@ -69,9 +69,11 @@
  • Tomcat must run as the domain account with which the SPN has been associated or as domain admin. It is NOT recommended to run Tomcat under a domain admin user.
  • -
  • The domain name (DEV.LOCAL) is not case sensitive when used in -the ktpass command, nor when used in jaas.conf
  • -
  • The domain must be specified when using the ktpass command
  • +
  • Convention is that the domain name (dev.local) is always used in +lower case. The domain name is typically not case sensitive.
  • +
  • Convention is that the Kerberos realm name (DEV.LOCAL) is always +used in upper case. The realm name is case sensitive.
  • +
  • The domain must be specified when using the ktpass command.
  • There are four components to the configuration of the built-in Tomcat support for Windows authentication. The domain controller, the server hosting @@ -80,8 +82,8 @@ component.

    The names of the three machines used in the configuration examples below are win-dc01.dev.local (the domain controller), win-tc01.dev.local (the Tomcat -instance) and win-pc01.dev.local (client). All are members of the DEV.LOCAL -domain.

    +instance) and win-pc01.dev.local (client). All are members of the +dev.local domain.

    Note: In order to use the passwords in the steps below, the domain password policy had to be relaxed. This is not recommended for production environments.

    @@ -114,14 +116,14 @@ user is test with a password of testpass.

    The above steps have been tested on a domain controller running Windows - Server 2008 R2 64-bit Standard using the Windows Server 2003 functional level + Server 2019 Standard using the Windows Server 2016 functional level for both the forest and the domain.

    -

    These steps assume that Tomcat and a Java 6 JDK/JRE have already been - installed and configured and that Tomcat is running as the tc01@DEV.LOCAL +

    These steps assume that Tomcat and a Java 8 JDK/JRE have already been + installed and configured and that Tomcat is running as the tc01@dev.local user. The steps to configure the Tomcat instance for Windows authentication are as follows:

    @@ -180,7 +182,7 @@ may be used that will simply return a Principal based on the authenticated user name that does not have any roles.

    The above steps have been tested on a Tomcat server running Windows Server - 2008 R2 64-bit Standard with an Oracle 1.6.0_24 64-bit JDK.

    + 2019 Standard with AdoptOpenJDK 8u232-b09 (64-bit).

    diff -Nru tomcat9-9.0.27/webapps/examples/jsp/security/protected/error.jsp tomcat9-9.0.31/webapps/examples/jsp/security/protected/error.jsp --- tomcat9-9.0.27/webapps/examples/jsp/security/protected/error.jsp 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/jsp/security/protected/error.jsp 2020-02-05 19:26:48.000000000 +0000 @@ -19,7 +19,7 @@ Error Page For Examples -Invalid username and/or password, please try -again. +Invalid user name and/or password, please try +again. diff -Nru tomcat9-9.0.27/webapps/examples/jsp/security/protected/index.jsp tomcat9-9.0.31/webapps/examples/jsp/security/protected/index.jsp --- tomcat9-9.0.27/webapps/examples/jsp/security/protected/index.jsp 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/jsp/security/protected/index.jsp 2020-02-05 19:26:48.000000000 +0000 @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --%> +<%@ page import="java.util.Enumeration" %> <% if (request.getParameter("logoff") != null) { session.invalidate(); @@ -72,6 +73,35 @@

    +To add some data to the authenticated session, enter it here: +
    + + + +
    +

    + +<% + String dataName = request.getParameter("dataName"); + if (dataName != null) { + session.setAttribute(dataName, request.getParameter("dataValue")); + } +%> +

    The authenticated session contains the following attributes:

    + + +<% + Enumeration names = session.getAttributeNames(); + while (names.hasMoreElements()) { + String name = names.nextElement(); +%> + +<% + } +%> +
    NameValue
    <%= name %><%= session.getAttribute(name) %>
    +

    + If you have configured this application for form-based authentication, you can log off by clicking here. diff -Nru tomcat9-9.0.27/webapps/examples/WEB-INF/classes/CookieExample.java tomcat9-9.0.31/webapps/examples/WEB-INF/classes/CookieExample.java --- tomcat9-9.0.27/webapps/examples/WEB-INF/classes/CookieExample.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/WEB-INF/classes/CookieExample.java 2020-02-05 19:26:48.000000000 +0000 @@ -39,13 +39,12 @@ private static final long serialVersionUID = 1L; - private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + ResourceBundle rb = ResourceBundle.getBundle("LocalStrings",request.getLocale()); String cookieName = request.getParameter("cookiename"); String cookieValue = request.getParameter("cookievalue"); @@ -64,7 +63,7 @@ out.println(""); out.println(""); - String title = RB.getString("cookies.title"); + String title = rb.getString("cookies.title"); out.println("" + title + ""); out.println(""); out.println(""); @@ -91,7 +90,7 @@ if (session != null) { sessionId = session.getId(); } - out.println(RB.getString("cookies.cookies") + "
    "); + out.println(rb.getString("cookies.cookies") + "
    "); for (int i = 0; i < cookies.length; i++) { Cookie cookie = cookies[i]; String cName = cookie.getName(); @@ -102,25 +101,25 @@ + "

    "); } } else { - out.println(RB.getString("cookies.no-cookies")); + out.println(rb.getString("cookies.no-cookies")); } if (aCookie != null) { out.println("

    "); - out.println(RB.getString("cookies.set") + "
    "); - out.print(RB.getString("cookies.name") + " " + out.println(rb.getString("cookies.set") + "
    "); + out.print(rb.getString("cookies.name") + " " + HTMLFilter.filter(cookieName) + "
    "); - out.print(RB.getString("cookies.value") + " " + out.print(rb.getString("cookies.value") + " " + HTMLFilter.filter(cookieValue)); } out.println("

    "); - out.println(RB.getString("cookies.make-cookie") + "
    "); + out.println(rb.getString("cookies.make-cookie") + "
    "); out.print("

    "); - out.print(RB.getString("cookies.name") + " "); + out.print(rb.getString("cookies.name") + " "); out.println("
    "); - out.print(RB.getString("cookies.value") + " "); + out.print(rb.getString("cookies.value") + " "); out.println("
    "); out.println("
    "); diff -Nru tomcat9-9.0.27/webapps/examples/WEB-INF/classes/LocalStrings_zh_CN.properties tomcat9-9.0.31/webapps/examples/WEB-INF/classes/LocalStrings_zh_CN.properties --- tomcat9-9.0.27/webapps/examples/WEB-INF/classes/LocalStrings_zh_CN.properties 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/WEB-INF/classes/LocalStrings_zh_CN.properties 2020-02-05 19:26:48.000000000 +0000 @@ -25,6 +25,7 @@ requestinfo.label.method=方法: requestinfo.label.protocol=协议: requestinfo.label.remoteaddr=远程地址: +requestinfo.label.requesturi=请求 URI (: requestinfo.title=请求信息范例 requestparams.firstname=姓: diff -Nru tomcat9-9.0.27/webapps/examples/WEB-INF/classes/RequestHeaderExample.java tomcat9-9.0.31/webapps/examples/WEB-INF/classes/RequestHeaderExample.java --- tomcat9-9.0.27/webapps/examples/WEB-INF/classes/RequestHeaderExample.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/WEB-INF/classes/RequestHeaderExample.java 2020-02-05 19:26:48.000000000 +0000 @@ -40,13 +40,13 @@ private static final long serialVersionUID = 1L; - private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + ResourceBundle rb = ResourceBundle.getBundle("LocalStrings",request.getLocale()); + response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); @@ -55,7 +55,7 @@ out.println(""); out.println(""); - String title = RB.getString("requestheader.title"); + String title = rb.getString("requestheader.title"); out.println("" + title + ""); out.println(""); out.println(""); diff -Nru tomcat9-9.0.27/webapps/examples/WEB-INF/classes/RequestInfoExample.java tomcat9-9.0.31/webapps/examples/WEB-INF/classes/RequestInfoExample.java --- tomcat9-9.0.27/webapps/examples/WEB-INF/classes/RequestInfoExample.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/WEB-INF/classes/RequestInfoExample.java 2020-02-05 19:26:48.000000000 +0000 @@ -36,13 +36,13 @@ private static final long serialVersionUID = 1L; - private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + ResourceBundle rb = ResourceBundle.getBundle("LocalStrings",request.getLocale()); + response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); @@ -51,7 +51,7 @@ out.println(""); out.println(""); - String title = RB.getString("requestinfo.title"); + String title = rb.getString("requestinfo.title"); out.println("" + title + ""); out.println(""); out.println(""); @@ -72,23 +72,23 @@ out.println("

    " + title + "

    "); out.println(""); diff -Nru tomcat9-9.0.27/webapps/examples/WEB-INF/classes/RequestParamExample.java tomcat9-9.0.31/webapps/examples/WEB-INF/classes/RequestParamExample.java --- tomcat9-9.0.27/webapps/examples/WEB-INF/classes/RequestParamExample.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/WEB-INF/classes/RequestParamExample.java 2020-02-05 19:26:48.000000000 +0000 @@ -36,13 +36,13 @@ private static final long serialVersionUID = 1L; - private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + ResourceBundle rb = ResourceBundle.getBundle("LocalStrings",request.getLocale()); + response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); @@ -51,7 +51,7 @@ out.println(""); out.println(""); - String title = RB.getString("requestparams.title"); + String title = rb.getString("requestparams.title"); out.println("" + title + ""); out.println(""); out.println(""); @@ -74,23 +74,23 @@ out.println("

    " + title + "

    "); String firstName = request.getParameter("firstname"); String lastName = request.getParameter("lastname"); - out.println(RB.getString("requestparams.params-in-req") + "
    "); + out.println(rb.getString("requestparams.params-in-req") + "
    "); if (firstName != null || lastName != null) { - out.println(RB.getString("requestparams.firstname")); + out.println(rb.getString("requestparams.firstname")); out.println(" = " + HTMLFilter.filter(firstName) + "
    "); - out.println(RB.getString("requestparams.lastname")); + out.println(rb.getString("requestparams.lastname")); out.println(" = " + HTMLFilter.filter(lastName)); } else { - out.println(RB.getString("requestparams.no-params")); + out.println(rb.getString("requestparams.no-params")); } out.println("

    "); out.print("
    "); - out.println(RB.getString("requestparams.firstname")); + out.println(rb.getString("requestparams.firstname")); out.println(""); out.println("
    "); - out.println(RB.getString("requestparams.lastname")); + out.println(rb.getString("requestparams.lastname")); out.println(""); out.println("
    "); out.println(""); diff -Nru tomcat9-9.0.27/webapps/examples/WEB-INF/classes/SessionExample.java tomcat9-9.0.31/webapps/examples/WEB-INF/classes/SessionExample.java --- tomcat9-9.0.27/webapps/examples/WEB-INF/classes/SessionExample.java 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/examples/WEB-INF/classes/SessionExample.java 2020-02-05 19:26:48.000000000 +0000 @@ -39,13 +39,13 @@ private static final long serialVersionUID = 1L; - private static final ResourceBundle RB = ResourceBundle.getBundle("LocalStrings"); - @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { + ResourceBundle rb = ResourceBundle.getBundle("LocalStrings",request.getLocale()); + response.setContentType("text/html"); response.setCharacterEncoding("UTF-8"); @@ -55,7 +55,7 @@ out.println(""); - String title = RB.getString("sessions.title"); + String title = rb.getString("sessions.title"); out.println("" + title + ""); out.println(""); out.println(""); @@ -77,11 +77,11 @@ out.println("

    " + title + "

    "); HttpSession session = request.getSession(true); - out.println(RB.getString("sessions.id") + " " + session.getId()); + out.println(rb.getString("sessions.id") + " " + session.getId()); out.println("
    "); - out.println(RB.getString("sessions.created") + " "); + out.println(rb.getString("sessions.created") + " "); out.println(new Date(session.getCreationTime()) + "
    "); - out.println(RB.getString("sessions.lastaccessed") + " "); + out.println(rb.getString("sessions.lastaccessed") + " "); out.println(new Date(session.getLastAccessedTime())); String dataName = request.getParameter("dataname"); @@ -91,7 +91,7 @@ } out.println("

    "); - out.println(RB.getString("sessions.data") + "
    "); + out.println(rb.getString("sessions.data") + "
    "); Enumeration names = session.getAttributeNames(); while (names.hasMoreElements()) { String name = names.nextElement(); @@ -105,10 +105,10 @@ out.print(response.encodeURL("SessionExample")); out.print("\" "); out.println("method=POST>"); - out.println(RB.getString("sessions.dataname")); + out.println(rb.getString("sessions.dataname")); out.println(""); out.println("
    "); - out.println(RB.getString("sessions.datavalue")); + out.println(rb.getString("sessions.datavalue")); out.println(""); out.println("
    "); out.println(""); @@ -119,10 +119,10 @@ out.print(response.encodeURL("SessionExample")); out.print("\" "); out.println("method=GET>"); - out.println(RB.getString("sessions.dataname")); + out.println(rb.getString("sessions.dataname")); out.println(""); out.println("
    "); - out.println(RB.getString("sessions.datavalue")); + out.println(rb.getString("sessions.datavalue")); out.println(""); out.println("
    "); out.println(""); diff -Nru tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/connectorCerts.jsp tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/connectorCerts.jsp --- tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/connectorCerts.jsp 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/connectorCerts.jsp 2020-02-05 19:26:48.000000000 +0000 @@ -32,7 +32,7 @@ - + Configured certificate chains per Connector diff -Nru tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/connectorCiphers.jsp tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/connectorCiphers.jsp --- tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/connectorCiphers.jsp 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/connectorCiphers.jsp 2020-02-05 19:26:48.000000000 +0000 @@ -32,7 +32,7 @@ - + Configured ciphers per Connector diff -Nru tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/connectorTrustedCerts.jsp tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/connectorTrustedCerts.jsp --- tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/connectorTrustedCerts.jsp 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/connectorTrustedCerts.jsp 2020-02-05 19:26:48.000000000 +0000 @@ -32,7 +32,7 @@ - + Trusted certificates per Connector diff -Nru tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/sessionDetail.jsp tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/sessionDetail.jsp --- tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/sessionDetail.jsp 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/sessionDetail.jsp 2020-02-05 19:26:48.000000000 +0000 @@ -51,7 +51,7 @@ - + Sessions Administration: details for <%= currentSessionId %> diff -Nru tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/sessionsList.jsp tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/sessionsList.jsp --- tomcat9-9.0.27/webapps/manager/WEB-INF/jsp/sessionsList.jsp 2019-10-07 09:50:40.000000000 +0000 +++ tomcat9-9.0.31/webapps/manager/WEB-INF/jsp/sessionsList.jsp 2020-02-05 19:26:48.000000000 +0000 @@ -42,7 +42,7 @@ - + Sessions Administration for <%= JspHelper.escapeXml(cn.getDisplayName()) %>

    "); - out.println(RB.getString("requestinfo.label.method")); + out.println(rb.getString("requestinfo.label.method")); out.println(""); out.println(HTMLFilter.filter(request.getMethod())); out.println("
    "); - out.println(RB.getString("requestinfo.label.requesturi")); + out.println(rb.getString("requestinfo.label.requesturi")); out.println(""); out.println(HTMLFilter.filter(request.getRequestURI())); out.println("
    "); - out.println(RB.getString("requestinfo.label.protocol")); + out.println(rb.getString("requestinfo.label.protocol")); out.println(""); out.println(HTMLFilter.filter(request.getProtocol())); out.println("
    "); - out.println(RB.getString("requestinfo.label.pathinfo")); + out.println(rb.getString("requestinfo.label.pathinfo")); out.println(""); out.println(HTMLFilter.filter(request.getPathInfo())); out.println("
    "); - out.println(RB.getString("requestinfo.label.remoteaddr")); + out.println(rb.getString("requestinfo.label.remoteaddr")); out.println(""); out.println(HTMLFilter.filter(request.getRemoteAddr())); out.println("