From 33fe55c3398c05e1b51acc2df8e29c3a8eef2822 Mon Sep 17 00:00:00 2001 From: Joakim Erdfelt Date: Tue, 11 Jun 2019 11:25:50 -0500 Subject: [PATCH] Issue #3708 - use StringUtil alternatives for known slow JVM impls. + StringUtil.replace() + StringUtil.replaceFirst() + StringUtil.sanitizeFileSystemPath() Change existing usages of String.replace() to either use new StringUtil.replace() or other methods elsewhere that better suit that specific need. Signed-off-by: Joakim Erdfelt --- .../jetty/ant/AntWebInfConfiguration.java | 3 +- .../fcgi/client/http/HttpSenderOverFCGI.java | 2 +- .../fcgi/server/proxy/TryFilesFilter.java | 5 +- .../org/eclipse/jetty/http/MimeTypes.java | 2 +- .../jetty/util/StringReplaceBenchmark.java | 119 +++++++++++ .../jetty/nosql/mongodb/MongoUtils.java | 22 +- .../osgi/boot/OSGiWebInfConfiguration.java | 3 +- .../eclipse/jetty/proxy/ProxyServletTest.java | 3 +- .../jetty/quickstart/AttributeNormalizer.java | 9 +- .../rewrite/handler/RedirectRegexRule.java | 10 +- .../rewrite/handler/RewriteRegexRule.java | 17 +- .../jetty/security/PropertyUserStore.java | 3 +- .../org/eclipse/jetty/server/Response.java | 4 +- .../jetty/server/handler/DefaultHandler.java | 2 +- .../jetty/servlet/ResponseHeadersTest.java | 9 +- .../jetty/servlets/CrossOriginFilter.java | 5 +- .../org/eclipse/jetty/util/StringUtil.java | 191 ++++++++++-------- .../java/org/eclipse/jetty/util/URIUtil.java | 14 +- .../jetty/util/component/Dumpable.java | 11 +- .../eclipse/jetty/util/StringUtilTest.java | 83 ++++---- .../jetty/webapp/ClasspathPattern.java | 2 +- .../jetty/webapp/WebInfConfiguration.java | 4 +- .../src/main/java/com/acme/CookieDump.java | 8 +- .../src/main/java/com/acme/Dump.java | 18 +- 24 files changed, 347 insertions(+), 202 deletions(-) create mode 100644 jetty-jmh/src/main/java/org/eclipse/jetty/util/StringReplaceBenchmark.java diff --git a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java index 6c2e9ada926..a82bfcf6478 100644 --- a/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java +++ b/jetty-ant/src/main/java/org/eclipse/jetty/ant/AntWebInfConfiguration.java @@ -28,6 +28,7 @@ import java.util.regex.Pattern; import org.apache.tools.ant.AntClassLoader; import org.eclipse.jetty.util.PatternMatcher; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.webapp.WebAppClassLoader; import org.eclipse.jetty.webapp.WebAppContext; @@ -88,7 +89,7 @@ public class AntWebInfConfiguration extends WebInfConfiguration } catch (URISyntaxException e) { - containerUris[i] = new URI(u.toString().replaceAll(" ", "%20")); + containerUris[i] = new URI(URIUtil.encodeSpaces(u.toString())); } i++; } diff --git a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java index 3a5a6b63a4d..4f836fddfe2 100644 --- a/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java +++ b/jetty-fcgi/fcgi-client/src/main/java/org/eclipse/jetty/fcgi/client/http/HttpSenderOverFCGI.java @@ -88,7 +88,7 @@ public class HttpSenderOverFCGI extends HttpSender for (HttpField field : headers) { String name = field.getName(); - String fcgiName = "HTTP_" + name.replaceAll("-", "_").toUpperCase(Locale.ENGLISH); + String fcgiName = "HTTP_" + name.replace('-', '_').toUpperCase(Locale.ENGLISH); fcgiHeaders.add(fcgiName, field.getValue()); } diff --git a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java index 3964dea1150..7a047c6c1cc 100644 --- a/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java +++ b/jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/proxy/TryFilesFilter.java @@ -24,7 +24,6 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; - import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -35,6 +34,8 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.util.StringUtil; + /** * Inspired by nginx's try_files functionality. *

@@ -132,7 +133,7 @@ public class TryFilesFilter implements Filter path += info; if (!path.startsWith("/")) path = "/" + path; - return value.replaceAll("\\$path", path); + return StringUtil.replace(value, "$path", path); } @Override diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java index 333163dd53f..3b428888e80 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/MimeTypes.java @@ -196,7 +196,7 @@ public class MimeTypes int charset=type.toString().indexOf(";charset="); if (charset>0) { - String alt=type.toString().replace(";charset=","; charset="); + String alt = StringUtil.replace(type.toString(), ";charset=", "; charset="); CACHE.put(alt,type); TYPES.put(alt,type.asBuffer()); } diff --git a/jetty-jmh/src/main/java/org/eclipse/jetty/util/StringReplaceBenchmark.java b/jetty-jmh/src/main/java/org/eclipse/jetty/util/StringReplaceBenchmark.java new file mode 100644 index 00000000000..61419271778 --- /dev/null +++ b/jetty-jmh/src/main/java/org/eclipse/jetty/util/StringReplaceBenchmark.java @@ -0,0 +1,119 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +@Fork(value = 3) +@State(Scope.Benchmark) +@Warmup(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 4, time = 1, timeUnit = TimeUnit.SECONDS) +public class StringReplaceBenchmark +{ + @Param({"3", "100", "1000"}) + int size; + + @Param({"0", "1", "3", "50"}) + int matches; + + String input; + + @Setup(Level.Trial) + public void setupTrial() throws Exception + { + String pattern = "abc"; + StringBuilder str = new StringBuilder(); + while (str.length() < size) + { + str.append(pattern); + } + + if (matches > 0) + { + int partSize = (int)((double)str.length() / (double)matches); + for (int i = 0; i < matches; i++) + { + str.insert((i * partSize), "'"); + } + } + input = str.toString(); + } + + @Benchmark + public void testJavaStringReplace_Growth(Blackhole blackhole) + { + blackhole.consume(input.replace("'", "FOOBAR")); + } + + @Benchmark + public void testJavaStringReplace_Same(Blackhole blackhole) + { + blackhole.consume(input.replace("'", "X")); + } + + @Benchmark + public void testJavaStringReplace_Reduce(Blackhole blackhole) + { + blackhole.consume(input.replace("'", "")); + } + + @Benchmark + public void testJettyStringUtilReplace_Growth(Blackhole blackhole) + { + blackhole.consume(StringUtil.replace(input, "'", "FOOBAR")); + } + + @Benchmark + public void testJettyStringUtilReplace_Same(Blackhole blackhole) + { + blackhole.consume(StringUtil.replace(input, "'", "X")); + } + + @Benchmark + public void testJettyStringUtilReplace_Reduce(Blackhole blackhole) + { + blackhole.consume(StringUtil.replace(input, "'", "")); + } + + public static void main(String[] args) throws RunnerException + { + Options opt = new OptionsBuilder() + .include(StringReplaceBenchmark.class.getSimpleName()) +// .addProfiler(GCProfiler.class) + .forks(1) + .build(); + + new Runner(opt).run(); + } +} diff --git a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoUtils.java b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoUtils.java index 9dfec359bba..a66ca44cc54 100644 --- a/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoUtils.java +++ b/jetty-nosql/src/main/java/org/eclipse/jetty/nosql/mongodb/MongoUtils.java @@ -27,10 +27,10 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; -import org.eclipse.jetty.util.ClassLoadingObjectInputStream; - import com.mongodb.BasicDBObject; import com.mongodb.DBObject; +import org.eclipse.jetty.util.ClassLoadingObjectInputStream; +import org.eclipse.jetty.util.StringUtil; /** * MongoUtils @@ -69,23 +69,23 @@ public class MongoUtils throw new IllegalStateException(valueToDecode.getClass().toString()); } } - - - + public static String decodeName(String name) { - return name.replace("%2E",".").replace("%25","%"); + String cleaned = name; + cleaned = StringUtil.replace(cleaned, "%2E", "."); + cleaned = StringUtil.replace(cleaned, "%25", "%"); + return cleaned; } - - public static String encodeName(String name) { - return name.replace("%","%25").replace(".","%2E"); + String cleaned = name; + cleaned = StringUtil.replace(cleaned, "%", "%25"); + cleaned = StringUtil.replace(cleaned, ".", "%2E"); + return cleaned; } - - public static Object encodeName(Object value) throws IOException { if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Date) diff --git a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java index 11af9ca7985..1ba0c301e91 100644 --- a/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java +++ b/jetty-osgi/jetty-osgi-boot/src/main/java/org/eclipse/jetty/osgi/boot/OSGiWebInfConfiguration.java @@ -35,6 +35,7 @@ import java.util.regex.Pattern; import org.eclipse.jetty.osgi.boot.utils.BundleFileLocatorHelperFactory; import org.eclipse.jetty.osgi.boot.utils.Util; import org.eclipse.jetty.osgi.boot.utils.internal.PackageAdminServiceTracker; +import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -339,7 +340,7 @@ public class OSGiWebInfConfiguration extends WebInfConfiguration } catch (URISyntaxException e) { - uri = new URI(url.toString().replaceAll(" ", "%20")); + uri = new URI(URIUtil.encodeSpaces(url.toString())); } String key = resourcePath.startsWith("/") ? resourcePath.substring(1) : resourcePath; resourceMap.put(key + ";" + fragment.getSymbolicName(), Resource.newResource(uri)); diff --git a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java index 8791899bb18..71afb0e0904 100644 --- a/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java +++ b/jetty-proxy/src/test/java/org/eclipse/jetty/proxy/ProxyServletTest.java @@ -43,7 +43,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import java.util.zip.GZIPOutputStream; - import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -670,7 +669,7 @@ public class ProxyServletTest // Make the request to the proxy, it should transparently forward to the server ContentResponse response = client.newRequest("localhost", proxyConnector.getLocalPort()) - .path((prefix + target).replaceAll("//", "/")) + .path((prefix + target).replace("//", "/")) .timeout(5, TimeUnit.SECONDS) .send(); assertEquals(200, response.getStatus()); diff --git a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java index c153be32d3c..522bcccf6b5 100644 --- a/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java +++ b/jetty-quickstart/src/main/java/org/eclipse/jetty/quickstart/AttributeNormalizer.java @@ -37,6 +37,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; import org.eclipse.jetty.util.resource.Resource; @@ -454,13 +455,7 @@ public class AttributeNormalizer // leftover expanded.append(str.substring(offset)); - // special case for "$$" - if (expanded.indexOf("$$") >= 0) - { - return expanded.toString().replaceAll("\\$\\$","\\$"); - } - - return expanded.toString(); + return StringUtil.replace(expanded.toString(), "$$", "$"); } private String getString(String property) diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java index 1f023bd432a..acd31fa7960 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RedirectRegexRule.java @@ -24,6 +24,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.Name; /** @@ -93,7 +94,7 @@ public class RedirectRegexRule extends RegexRule for (int g = 1; g <= matcher.groupCount(); g++) { String group = matcher.group(g); - target = target.replaceAll("\\$" + g, group); + target = StringUtil.replace(target, "$" + g, group); } target = response.encodeRedirectURL(target); @@ -110,6 +111,11 @@ public class RedirectRegexRule extends RegexRule @Override public String toString() { - return String.format("%s[%d>%s]", super.toString(), _statusCode, _location); + StringBuilder str = new StringBuilder(); + str.append(super.toString()); + str.append('[').append(_statusCode); + str.append('>').append(_location); + str.append(']'); + return str.toString(); } } diff --git a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java index 1888fdc9815..f41cbdb9032 100644 --- a/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java +++ b/jetty-rewrite/src/main/java/org/eclipse/jetty/rewrite/handler/RewriteRegexRule.java @@ -20,11 +20,11 @@ package org.eclipse.jetty.rewrite.handler; import java.io.IOException; import java.util.regex.Matcher; - import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.Name; /** @@ -96,15 +96,22 @@ public class RewriteRegexRule extends RegexRule implements Rule.ApplyURI group=""; else group = Matcher.quoteReplacement(group); - target=target.replaceAll("\\$"+g,group); - if (query!=null) - query=query.replaceAll("\\$"+g,group); + String dollarGroup = "$" + g; + target = StringUtil.replace(target, dollarGroup, group); + if (query != null) + query = StringUtil.replace(query, dollarGroup, group); } if (query!=null) { if (_queryGroup) - query=query.replace("$Q",request.getQueryString()==null?"":request.getQueryString()); + { + String replacement = ""; + if (request.getQueryString() != null) + replacement = request.getQueryString(); + + query = StringUtil.replace(query, "$Q", replacement); + } request.setAttribute("org.eclipse.jetty.rewrite.handler.RewriteRegexRule.Q",query); } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java index e43f4719d30..9d0ea1004b4 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/PropertyUserStore.java @@ -140,7 +140,8 @@ public class PropertyUserStore extends UserStore implements PathWatcher.Listener int bang_slash = uri.indexOf("!/"); if (colon < 0 || bang_slash < 0 || colon > bang_slash) throw new IllegalArgumentException("Not resolved JarFile resource: " + uri); - String entry_path = uri.substring(colon + 2).replace("!/", "__").replace('/', '_').replace('.', '_'); + + String entry_path = StringUtil.sanitizeFileSystemPath(uri.substring(colon + 2)); Path tmpDirectory = Files.createTempDirectory("users_store"); tmpDirectory.toFile().deleteOnExit(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index f5733470fb2..1809d5b1cea 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -52,12 +52,10 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; -import org.eclipse.jetty.http.Syntax; import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ErrorHandler; import org.eclipse.jetty.server.session.SessionHandler; -import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; @@ -237,7 +235,7 @@ public class Response implements HttpServletResponse if (i >= 0) { httpOnly = true; - comment = comment.replace(HTTP_ONLY_COMMENT, "").trim(); + comment = StringUtil.replace(comment.trim(), HTTP_ONLY_COMMENT, ""); if (comment.length() == 0) comment = null; } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java index 06c74a91a21..21e814f03d6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java @@ -164,7 +164,7 @@ public class DefaultHandler extends AbstractHandler { writer.append(""); } - writer.append(contextPath.replaceAll("%", "%")); + writer.append(StringUtil.replace(contextPath, "%", "%")); if (context.isRunning()) { writer.append(""); diff --git a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java index ffaaed78bf0..eb801824bea 100644 --- a/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java +++ b/jetty-servlet/src/test/java/org/eclipse/jetty/servlet/ResponseHeadersTest.java @@ -18,13 +18,9 @@ package org.eclipse.jetty.servlet; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - import java.io.IOException; import java.net.URLDecoder; import java.nio.ByteBuffer; - import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; @@ -38,6 +34,9 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + public class ResponseHeadersTest { public static class SimulateUpgradeServlet extends HttpServlet @@ -144,7 +143,7 @@ public class ResponseHeadersTest assertThat("Response Code",response.getStatus(),is(200)); assertThat("Response Header Content-Type",response.get("Content-Type"),is("text/plain;charset=UTF-8")); - String expected = actualPathInfo.replaceAll("%0A", " "); // replace OBS fold with space + String expected = actualPathInfo.replace("%0A", " "); // replace OBS fold with space expected = URLDecoder.decode(expected, "utf-8"); // decode the rest expected = expected.trim(); // trim whitespace at start/end assertThat("Response Header X-example", response.get("X-Example"), is(expected)); diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java index 692be858f1c..4c3393a90a4 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/CrossOriginFilter.java @@ -26,7 +26,6 @@ import java.util.Enumeration; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; - import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -368,8 +367,8 @@ public class CrossOriginFilter implements Filter private String parseAllowedWildcardOriginToRegex(String allowedOrigin) { - String regex = allowedOrigin.replace(".", "\\."); - return regex.replace("*", ".*"); // we want to be greedy here to match multiple subdomains, thus we use .* + String regex = StringUtil.replace(allowedOrigin, ".", "\\."); + return StringUtil.replace(regex, "*", ".*"); // we want to be greedy here to match multiple subdomains, thus we use .* } private boolean isSimpleRequest(HttpServletRequest request) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java index 9b1ef63603b..0bf5a431194 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java @@ -26,14 +26,13 @@ import java.util.List; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -/** Fast String Utilities. +/** + * Fast String Utilities. * * These string utilities provide both convenience methods and * performance improvements over most standard library versions. The * main aim of the optimizations is to avoid object creation unless * absolutely required. - * - * */ public class StringUtil { @@ -62,8 +61,7 @@ public class StringUtil CHARSETS.put("iso-8859-1",__ISO_8859_1); CHARSETS.put("iso_8859_1",__ISO_8859_1); } - - /* ------------------------------------------------------------ */ + /** Convert alternate charset names (eg utf8) to normalized * name (eg UTF-8). * @param s the charset to normalize @@ -74,8 +72,7 @@ public class StringUtil String n=CHARSETS.get(s); return (n==null)?s:n; } - - /* ------------------------------------------------------------ */ + /** Convert alternate charset names (eg utf8) to normalized * name (eg UTF-8). * @param s the charset to normalize @@ -88,9 +85,7 @@ public class StringUtil String n=CHARSETS.get(s,offset,length); return (n==null)?s.substring(offset,offset+length):n; } - - /* ------------------------------------------------------------ */ public static final char[] lowercases = { '\000','\001','\002','\003','\004','\005','\006','\007', '\010','\011','\012','\013','\014','\015','\016','\017', @@ -109,7 +104,6 @@ public class StringUtil '\160','\161','\162','\163','\164','\165','\166','\167', '\170','\171','\172','\173','\174','\175','\176','\177' }; - /* ------------------------------------------------------------ */ /** * fast lower case conversion. Only works on ascii (not unicode) * @param s the string to convert @@ -122,7 +116,6 @@ public class StringUtil char[] c = null; int i=s.length(); - // look for first conversion while (i-->0) { @@ -138,7 +131,6 @@ public class StringUtil } } } - while (i-->0) { if(c[i]<=127) @@ -148,8 +140,43 @@ public class StringUtil return c==null?s:new String(c); } + /** + * Replace all characters from input string that are known to have + * special meaning in various filesytems. + * + *

+ * This will replace all of the following characters + * with a "{@code _}" (underscore). + *

+ * + * + * @param str the raw input string + * @return the sanitized output string. + */ + public static String sanitizeFileSystemPath(String str) + { + char[] chars = str.toCharArray(); + int len = chars.length; + for (int i = 0; i < len; i++) + { + char c = chars[i]; + if ((c <= 0x1F) || // control characters + (c >= 0x7F) || // over 7-bit printable ASCII + (c == '!') || (c == '|') || (c == '.') || + (c == ':') || (c == '>') || (c == '<') || + (c == '?') || (c == '/') || (c == '\\') || + (c == '*') || (c == '"') || (c == '&')) + { + chars[i] = '_'; + } + } + return String.valueOf(chars); + } - /* ------------------------------------------------------------ */ public static boolean startsWithIgnoreCase(String s,String w) { if (w==null) @@ -174,13 +201,11 @@ public class StringUtil } return true; } - - /* ------------------------------------------------------------ */ + public static boolean endsWithIgnoreCase(String s,String w) { if (w==null) return true; - if (s==null) return false; @@ -206,8 +231,7 @@ public class StringUtil } return true; } - - /* ------------------------------------------------------------ */ + /** * returns the next index of a character from the chars string * @param s the input string to search @@ -221,10 +245,13 @@ public class StringUtil return i; return -1; } - - /* ------------------------------------------------------------ */ + /** - * replace substrings within string. + * Replace substrings within string. + *

+ * Fast replacement for {@code java.lang.String#}{@link String#replace(CharSequence, CharSequence)} + *

+ * * @param s the input string * @param sub the string to look for * @param with the string to replace with @@ -232,27 +259,56 @@ public class StringUtil */ public static String replace(String s, String sub, String with) { - int c=0; - int i=s.indexOf(sub,c); + int c = 0; + int i = s.indexOf(sub, c); if (i == -1) + { return s; - - StringBuilder buf = new StringBuilder(s.length()+with.length()); - + } + StringBuilder buf = new StringBuilder(s.length() + with.length()); do { - buf.append(s.substring(c,i)); + buf.append(s, c, i); buf.append(with); - c=i+sub.length(); - } while ((i=s.indexOf(sub,c))!=-1); - - if (c + * Fast replacement for {@code java.lang.String#}{@link String#replaceFirst(String, String)}, but without + * Regex support. + *

+ * + * @param original the original string + * @param target the target string to look for + * @param replacement the replacement string to use + * @return the replaced string + */ + public static String replaceFirst(String original, String target, String replacement) + { + int idx = original.indexOf(target); + if (idx == -1) + return original; + + int offset = 0; + int originalLen = original.length(); + StringBuilder buf = new StringBuilder(originalLen + replacement.length()); + buf.append(original, offset, idx); + offset += idx + target.length(); + buf.append(replacement); + buf.append(original, offset, originalLen); + + return buf.toString(); + } + /** Remove single or double quotes. * @param s the input string * @return the string with quotes removed @@ -263,8 +319,6 @@ public class StringUtil return QuotedStringTokenizer.unquote(s); } - - /* ------------------------------------------------------------ */ /** Append substring to StringBuilder * @param buf StringBuilder to append to * @param s String to append from @@ -288,8 +342,6 @@ public class StringUtil } } - - /* ------------------------------------------------------------ */ /** * append hex digit * @param buf the buffer to append to @@ -310,7 +362,6 @@ public class StringUtil buf.append((char)c); } - /* ------------------------------------------------------------ */ /** * Append 2 digits (zero padded) to the StringBuffer * @@ -325,8 +376,7 @@ public class StringUtil buf.append((char)(i%10+'0')); } } - - /* ------------------------------------------------------------ */ + /** * Append 2 digits (zero padded) to the StringBuilder * @@ -341,8 +391,7 @@ public class StringUtil buf.append((char)(i%10+'0')); } } - - /* ------------------------------------------------------------ */ + /** Return a non null string. * @param s String * @return The string passed in or empty string if it is null. @@ -353,8 +402,7 @@ public class StringUtil return ""; return s; } - - /* ------------------------------------------------------------ */ + public static boolean equals(String s,char[] buf, int offset, int length) { if (s.length()!=length) @@ -365,13 +413,11 @@ public class StringUtil return true; } - /* ------------------------------------------------------------ */ public static String toUTF8String(byte[] b,int offset,int length) { return new String(b,offset,length,StandardCharsets.UTF_8); } - /* ------------------------------------------------------------ */ public static String toString(byte[] b,int offset,int length,String charset) { try @@ -380,6 +426,7 @@ public class StringUtil } catch (UnsupportedEncodingException e) { + LOG.warn(e); throw new IllegalArgumentException(e); } } @@ -431,7 +478,6 @@ public class StringUtil return -1; } - /* ------------------------------------------------------------ */ /** * Test if a string is null or only has whitespace characters in it. *

@@ -494,7 +540,6 @@ public class StringUtil return str == null || str.isEmpty(); } - /* ------------------------------------------------------------ */ /** * Test if a string is not null and contains at least 1 non-whitespace characters in it. *

@@ -534,14 +579,11 @@ public class StringUtil return false; } - /* ------------------------------------------------------------ */ public static boolean isUTF8(String charset) { return __UTF8.equalsIgnoreCase(charset)||__UTF8.equalsIgnoreCase(normalizeCharset(charset)); } - - /* ------------------------------------------------------------ */ public static String printable(String name) { if (name==null) @@ -556,7 +598,7 @@ public class StringUtil return buf.toString(); } - /* ------------------------------------------------------------ */ + public static String printable(byte[] b) { StringBuilder buf = new StringBuilder(); @@ -592,13 +634,10 @@ public class StringUtil } catch(Exception e) { - LOG.warn(e); return s.getBytes(); } } - - - + /** * Converts a binary SID to a string SID * @@ -631,7 +670,6 @@ public class StringUtil // the number of subAuthorities we need to attach int subAuthorityCount = sidBytes[1]; - // attach each of the subAuthorities for (int i = 0; i < subAuthorityCount; ++i) { @@ -670,10 +708,8 @@ public class StringUtil // the revision byte sidBytes[byteCount++] = (byte)Integer.parseInt(sidTokens[1]); - // the # of sub authorities byte sidBytes[byteCount++] = (byte)subAuthorityCount; - // the certAuthority String hexStr = Long.toHexString(Long.parseLong(sidTokens[2])); @@ -681,7 +717,6 @@ public class StringUtil { hexStr = "0" + hexStr; } - // place the certAuthority 6 bytes for ( int i = 0 ; i < hexStr.length(); i = i + 2) { @@ -720,7 +755,6 @@ public class StringUtil int val = 0; boolean started = false; boolean minus = false; - for (int i = from; i < string.length(); i++) { char b = string.charAt(i); @@ -741,7 +775,6 @@ public class StringUtil else break; } - if (started) return minus?(-val):val; throw new NumberFormatException(string); @@ -759,7 +792,6 @@ public class StringUtil long val = 0; boolean started = false; boolean minus = false; - for (int i = 0; i < string.length(); i++) { char b = string.charAt(i); @@ -780,7 +812,6 @@ public class StringUtil else break; } - if (started) return minus?(-val):val; throw new NumberFormatException(string); @@ -799,12 +830,10 @@ public class StringUtil { return null; } - if (str.length() <= maxSize) { return str; } - return str.substring(0,maxSize); } @@ -817,20 +846,18 @@ public class StringUtil { if (s==null) return new String[]{}; - if (!s.startsWith("[") || !s.endsWith("]")) throw new IllegalArgumentException(); if (s.length()==2) return new String[]{}; - return csvSplit(s,1,s.length()-2); } /** - * Parse a CSV string using {@link #csvSplit(List,String, int, int)} - * @param s The string to parse - * @return An array of parsed values. - */ + * Parse a CSV string using {@link #csvSplit(List,String, int, int)} + * @param s The string to parse + * @return An array of parsed values. + */ public static String[] csvSplit(String s) { if (s==null) @@ -851,13 +878,12 @@ public class StringUtil return null; if (off<0 || len<0 || off>s.length()) throw new IllegalArgumentException(); - List list = new ArrayList<>(); csvSplit(list,s,off,len); return list.toArray(new String[list.size()]); } - enum CsvSplitState { PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA }; + enum CsvSplitState { PRE_DATA, QUOTE, SLOSH, DATA, WHITE, POST_DATA } /** Split a quoted comma separated string to a list *

Handle rfc4180-like @@ -890,7 +916,6 @@ public class StringUtil case PRE_DATA: if (Character.isWhitespace(ch)) continue; - if ('"'==ch) { state=CsvSplitState.QUOTE; @@ -902,11 +927,9 @@ public class StringUtil list.add(""); continue; } - state=CsvSplitState.DATA; out.append(ch); continue; - case DATA: if (Character.isWhitespace(ch)) { @@ -923,7 +946,6 @@ public class StringUtil state=CsvSplitState.PRE_DATA; continue; } - out.append(ch); continue; @@ -947,7 +969,6 @@ public class StringUtil out.append(ch); last=-1; continue; - case QUOTE: if ('\\'==ch) { @@ -978,13 +999,11 @@ public class StringUtil continue; } } - switch(state) { case PRE_DATA: case POST_DATA: break; - case DATA: case QUOTE: case SLOSH: @@ -1011,7 +1030,6 @@ public class StringUtil loop: for (;iThis method calls {@link String#valueOf(Object)} unless the object is null, * in which case null is returned

diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java index 50394f7c6fb..17da05adf80 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java @@ -268,7 +268,19 @@ public class URIUtil return buf; } - + + /** + * Encode a raw URI String and convert any raw spaces to + * their "%20" equivalent. + * + * @param str input raw string + * @return output with spaces converted to "%20" + */ + public static String encodeSpaces(String str) + { + return StringUtil.replace(str, " ", "%20"); + } + /* ------------------------------------------------------------ */ /** Encode a URI path. * @param path The path the encode diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java index 78229fc30ba..ee9fc9df110 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.Map; import java.util.stream.Stream; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; @@ -99,9 +100,15 @@ public interface Dumpable else if (o instanceof Map) s = String.format("%s@%x{size=%d}",o.getClass().getName(),o.hashCode(),((Map)o).size()); else if (o instanceof Dumpable) - s = ((Dumpable)o).dumpSelf().replace("\r\n","|").replace("\n","|"); + { + s = StringUtil.replace(((Dumpable)o).dumpSelf(), "\r\n", "|") + .replace('\n', '|'); + } else - s = String.valueOf(o).replace("\r\n","|").replace("\n","|"); + { + s = StringUtil.replace(String.valueOf(o), "\r\n", "|") + .replace('\n', '|'); + } if (o instanceof LifeCycle) out.append(s).append(" - ").append((AbstractLifeCycle.getState((LifeCycle)o))).append("\n"); diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java index 51d53110410..f244de11b03 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/StringUtilTest.java @@ -18,19 +18,21 @@ package org.eclipse.jetty.util; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.TimeUnit; - +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.arrayContaining; import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; public class StringUtilTest @@ -98,7 +100,36 @@ public class StringUtilTest s=" \u0690bc "; assertEquals(StringUtil.replace(s, "\u0690bc", "xyz")," xyz "); + } + public static Stream replaceFirstArgs() { + List data = new ArrayList<>(); + + // [original, target, replacement, expected] + + // no match + data.add(new String[]{ "abc", "z", "foo", "abc" }); + + // matches at start of string + data.add(new String[]{ "abc", "a", "foo", "foobc" }); + data.add(new String[]{ "abcabcabc", "a", "foo", "foobcabcabc" }); + + // matches in middle of string + data.add(new String[]{ "abc", "b", "foo", "afooc" }); + data.add(new String[]{ "abcabcabc", "b", "foo", "afoocabcabc" }); + data.add(new String[]{ "abcabcabc", "cab", "X", "abXcabc" }); + + // matches at end of string + data.add(new String[]{ "abc", "c", "foo", "abfoo" }); + + return data.stream(); + } + + @ParameterizedTest + @MethodSource(value = "replaceFirstArgs") + public void testReplaceFirst(String original, String target, String replacement, String expected) + { + assertThat(StringUtil.replaceFirst(original, target, replacement), is(expected)); } @Test @@ -165,50 +196,6 @@ public class StringUtilTest assertEquals(sid5, StringUtil.sidBytesToString(sid5Bytes)); assertEquals(sid6, StringUtil.sidBytesToString(sid6Bytes)); assertEquals(sid12, StringUtil.sidBytesToString(sid12Bytes)); - - } - - - public static void main(String[] arg) throws Exception - { - String string = "Now \u0690xxxxxxxx"; - System.err.println(string); - byte[] bytes=string.getBytes(StandardCharsets.UTF_8); - System.err.println(new String(bytes)); - System.err.println(bytes.length); - long calc=0; - Utf8StringBuffer strbuf = new Utf8StringBuffer(bytes.length); - for (int i=0;i<10;i++) - { - long s1=TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - for (int j=1000000; j-->0;) - { - calc+=new String(bytes,0,bytes.length,StandardCharsets.UTF_8).hashCode(); - } - long s2=TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - for (int j=1000000; j-->0;) - { - calc+=StringUtil.toUTF8String(bytes,0,bytes.length).hashCode(); - } - long s3=TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - for (int j=1000000; j-->0;) - { - Utf8StringBuffer buffer = new Utf8StringBuffer(bytes.length); - buffer.append(bytes,0,bytes.length); - calc+=buffer.toString().hashCode(); - } - long s4=TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - for (int j=1000000; j-->0;) - { - strbuf.reset(); - strbuf.append(bytes,0,bytes.length); - calc+=strbuf.toString().hashCode(); - } - long s5=TimeUnit.NANOSECONDS.toMillis(System.nanoTime()); - - System.err.println((s2-s1)+", "+(s3-s2)+", "+(s4-s3)+", "+(s5-s4)); - } - System.err.println(calc); } @Test diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java index ee203b35e50..1ca05559cc9 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/ClasspathPattern.java @@ -703,7 +703,7 @@ public class ClasspathPattern extends AbstractSet name=name.substring(0,name.length()-6); // Treat path elements as packages for name matching - name=name.replace("/","."); + name = name.replace('/', '.'); return combine(_packageOrNamePatterns, name, _locations, ()-> { diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java index b94f7881a69..20aec0b824d 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/WebInfConfiguration.java @@ -38,6 +38,7 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.PatternMatcher; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -210,7 +211,8 @@ public class WebInfConfiguration extends AbstractConfiguration } catch (URISyntaxException e) { - containerUris.add(new URI(u.toString().replaceAll(" ", "%20"))); + String fixedUriStr = StringUtil.replace(u.toString(), " ", "%20"); + containerUris.add(new URI(fixedUriStr)); } } } diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/CookieDump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/CookieDump.java index 48d79149dec..68c0c98c128 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/CookieDump.java +++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/CookieDump.java @@ -21,7 +21,6 @@ package com.acme; import java.io.IOException; import java.io.PrintWriter; import java.util.concurrent.TimeUnit; - import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.Cookie; @@ -29,7 +28,6 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; - /** * Test Servlet Cookies. */ @@ -117,9 +115,9 @@ public class CookieDump extends HttpServlet { if (string==null) return null; - string=string.replace("&", "&"); - string=string.replace( "<", "<"); - string=string.replace( ">", ">"); + string = string.replace("&", "&"); + string = string.replace( "<", "<"); + string = string.replace( ">", ">"); return string; } diff --git a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java index 1110e8bfeff..e2a2f575feb 100644 --- a/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java +++ b/tests/test-webapps/test-jetty-webapp/src/main/java/com/acme/Dump.java @@ -36,7 +36,6 @@ import java.util.Enumeration; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; - import javax.servlet.AsyncContext; import javax.servlet.AsyncEvent; import javax.servlet.AsyncListener; @@ -57,13 +56,14 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import javax.servlet.http.Part; - -/** +/** * Dump Servlet Request. */ @SuppressWarnings("serial") public class Dump extends HttpServlet { + /** Zero Width Space, to allow text to be wrapped at designated spots */ + private static final String ZWSP = "​"; boolean fixed; Timer _timer; @@ -647,7 +647,7 @@ public class Dump extends HttpServlet { name= (String)a.nextElement(); pout.write("\n"); - pout.write(""+name.replace("."," .")+": "); + pout.write("" + name.replace(".", ZWSP + ".") + ": "); Object value=request.getAttribute(name); if (value instanceof File) { @@ -678,7 +678,7 @@ public class Dump extends HttpServlet { name= (String)a.nextElement(); pout.write("\n"); - pout.write(""+name.replace("."," .")+": "); + pout.write("" + name.replace(".", ZWSP + ".") + ": "); pout.write(""+ toString(getServletContext().getInitParameter(name)) + ""); } @@ -689,7 +689,7 @@ public class Dump extends HttpServlet { name= (String)a.nextElement(); pout.write("\n"); - pout.write(""+name.replace("."," .")+": "); + pout.write("" + name.replace(".", ZWSP + ".") + ": "); pout.write(""+"
" + toString(getServletContext().getAttribute(name)) + "
"+""); } @@ -1055,9 +1055,9 @@ public class Dump extends HttpServlet { if (s==null) return "null"; - s=s.replaceAll("&","&"); - s=s.replaceAll("<","<"); - s=s.replaceAll(">",">"); + s = s.replace("&","&"); + s = s.replace("<","<"); + s = s.replace(">",">"); return s; } }