@@ -, +, @@ variable for the UEncoder in DirContextURLConnection, as UEncoder is not thread safe. Use a immutable safeChars field for Response instances. --- java/org/apache/catalina/connector/Response.java | 4 +- .../naming/resources/DirContextURLConnection.java | 9 +- java/org/apache/tomcat/util/buf/UEncoder.java | 104 ++++++++++++++------- test/org/apache/tomcat/util/buf/TestUEncoder.java | 26 ++++++ 4 files changed, 102 insertions(+), 41 deletions(-) --- a/java/org/apache/catalina/connector/Response.java +++ a/java/org/apache/catalina/connector/Response.java @@ -51,6 +51,7 @@ import org.apache.catalina.util.RequestUtil; import org.apache.catalina.util.SessionConfig; import org.apache.tomcat.util.buf.CharChunk; import org.apache.tomcat.util.buf.UEncoder; +import org.apache.tomcat.util.buf.UEncoder.SafeCharsSet; import org.apache.tomcat.util.http.FastHttpDateFormat; import org.apache.tomcat.util.http.MimeHeaders; import org.apache.tomcat.util.http.ServerCookie; @@ -91,7 +92,6 @@ public class Response } public Response() { - urlEncoder.addSafeCharacter('/'); } @@ -282,7 +282,7 @@ public class Response /** * URL encoder. */ - protected UEncoder urlEncoder = new UEncoder(); + protected UEncoder urlEncoder = new UEncoder(SafeCharsSet.WITH_SLASH); /** --- a/java/org/apache/naming/resources/DirContextURLConnection.java +++ a/java/org/apache/naming/resources/DirContextURLConnection.java @@ -42,6 +42,7 @@ import javax.naming.directory.DirContext; import org.apache.naming.JndiPermission; import org.apache.tomcat.util.buf.UDecoder; import org.apache.tomcat.util.buf.UEncoder; +import org.apache.tomcat.util.buf.UEncoder.SafeCharsSet; import org.apache.tomcat.util.http.FastHttpDateFormat; /** @@ -57,11 +58,6 @@ import org.apache.tomcat.util.http.FastHttpDateFormat; public class DirContextURLConnection extends URLConnection { private static final UDecoder URL_DECODER = new UDecoder(); - private static final UEncoder URL_ENCODER = new UEncoder(); - - static{ - URL_ENCODER.addSafeCharacter('/'); - } // ----------------------------------------------------------- Constructors @@ -436,11 +432,12 @@ public class DirContextURLConnection extends URLConnection { try { NamingEnumeration enumeration = collection.list("/"); + UEncoder urlEncoder = new UEncoder(SafeCharsSet.WITH_SLASH); while (enumeration.hasMoreElements()) { NameClassPair ncp = enumeration.nextElement(); String s = ncp.getName(); result.addElement( - URL_ENCODER.encodeURL(s, 0, s.length()).toString()); + urlEncoder.encodeURL(s, 0, s.length()).toString()); } } catch (NamingException e) { // Unexpected exception --- a/java/org/apache/tomcat/util/buf/UEncoder.java +++ a/java/org/apache/tomcat/util/buf/UEncoder.java @@ -31,6 +31,22 @@ import java.util.BitSet; */ public final class UEncoder { + public enum SafeCharsSet { + WITH_SLASH("/"), DEFAULT(""); + private final BitSet safeChars; + + private BitSet getSafeChars() { + return this.safeChars; + } + + private SafeCharsSet(String additionalSafeChars) { + safeChars = initialSafeChars(); + for (char c : additionalSafeChars.toCharArray()) { + safeChars.set(c); + } + } + } + // Not static - the set may differ ( it's better than adding // an extra check for "/", "+", etc private BitSet safeChars=null; @@ -38,11 +54,28 @@ public final class UEncoder { private ByteChunk bb=null; private CharChunk cb=null; private CharChunk output=null; + private final boolean readOnlySafeChars; private String encoding="UTF8"; public UEncoder() { - initSafeChars(); + this.safeChars = initialSafeChars(); + readOnlySafeChars = false; + } + + /** + * Create a UEncoder with an unmodifiable safe character set. + *

+ * Calls to {@link UEncoder#addSafeCharacter(char) addSafeCharacter(char)} + * on instances created by this constructor will throw an + * {@link IllegalStateException}. + * + * @param safeCharsSet + * safe characters for this encoder + */ + public UEncoder(SafeCharsSet safeCharsSet) { + this.safeChars = safeCharsSet.getSafeChars(); + readOnlySafeChars = true; } /** @@ -54,6 +87,9 @@ public final class UEncoder { } public void addSafeCharacter( char c ) { + if (readOnlySafeChars) { + throw new IllegalStateException("UEncoders safeChararacters are read only"); + } safeChars.set( c ); } @@ -120,36 +156,38 @@ public final class UEncoder { out.append(ch); } } - - // -------------------- Internal implementation -------------------- - - private void initSafeChars() { - safeChars=new BitSet(128); - int i; - for (i = 'a'; i <= 'z'; i++) { - safeChars.set(i); - } - for (i = 'A'; i <= 'Z'; i++) { - safeChars.set(i); - } - for (i = '0'; i <= '9'; i++) { - safeChars.set(i); - } - //safe - safeChars.set('$'); - safeChars.set('-'); - safeChars.set('_'); - safeChars.set('.'); - - // Dangerous: someone may treat this as " " - // RFC1738 does allow it, it's not reserved - // safeChars.set('+'); - //extra - safeChars.set('!'); - safeChars.set('*'); - safeChars.set('\''); - safeChars.set('('); - safeChars.set(')'); - safeChars.set(','); - } + + // -------------------- Internal implementation -------------------- + + private static BitSet initialSafeChars() { + BitSet initialSafeChars = new BitSet(128); + int i; + for (i = 'a'; i <= 'z'; i++) { + initialSafeChars.set(i); + } + for (i = 'A'; i <= 'Z'; i++) { + initialSafeChars.set(i); + } + for (i = '0'; i <= '9'; i++) { + initialSafeChars.set(i); + } + // safe + initialSafeChars.set('$'); + initialSafeChars.set('-'); + initialSafeChars.set('_'); + initialSafeChars.set('.'); + + // Dangerous: someone may treat this as " " + // RFC1738 does allow it, it's not reserved + // safeChars.set('+'); + // extra + initialSafeChars.set('!'); + initialSafeChars.set('*'); + initialSafeChars.set('\''); + initialSafeChars.set('('); + initialSafeChars.set(')'); + initialSafeChars.set(','); + return initialSafeChars; + } + } --- a/test/org/apache/tomcat/util/buf/TestUEncoder.java +++ a/test/org/apache/tomcat/util/buf/TestUEncoder.java @@ -20,6 +20,9 @@ package org.apache.tomcat.util.buf; import java.io.IOException; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import org.apache.tomcat.util.buf.UEncoder.SafeCharsSet; import org.junit.Test; /** @@ -45,4 +48,27 @@ public class TestUEncoder { assertTrue(urlEncoder.encodeURL(s, 0, s.length()) .equals("%f0%90%90%81")); } + + @Test + public void testEncodeURLWithSlashInit() throws IOException { + UEncoder urlEncoder = new UEncoder(SafeCharsSet.WITH_SLASH); + + String s = "a+b/c/d+e.class"; + assertTrue(urlEncoder.encodeURL(s, 0, s.length()).equals( + "a%2bb/c/d%2be.class")); + assertTrue(urlEncoder.encodeURL(s, 2, s.length() - 2).equals( + "b/c/d%2be.cla")); + + try { + urlEncoder.addSafeCharacter('+'); + fail(); + } catch (IllegalStateException e) { + // OK + } + + s = new String(new char[] { 0xD801, 0xDC01 }); + assertTrue(urlEncoder.encodeURL(s, 0, s.length()) + .equals("%f0%90%90%81")); + } + } --