diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java index 36392aa0deb..b2ac85b3051 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java @@ -211,7 +211,7 @@ public class MultiPartFilter implements Filter else if(tl.startsWith("name=")) name=value(t); else if(tl.startsWith("filename=")) - filename=value(t); + filename=filenameValue(t); } // Check disposition @@ -416,6 +416,34 @@ public class MultiPartFilter implements Filter String value = nameEqualsValue.substring(idx+1).trim(); return QuotedStringTokenizer.unquoteOnly(value); } + + + /* ------------------------------------------------------------ */ + private String filenameValue(String nameEqualsValue) + { + int idx = nameEqualsValue.indexOf('='); + String value = nameEqualsValue.substring(idx+1).trim(); + + if (value.matches(".??[a-z,A-Z]\\:\\\\[^\\\\].*")) + { + //incorrectly escaped IE filenames that have the whole path + //we just strip any leading & trailing quotes and leave it as is + char first=value.charAt(0); + if (first=='"' || first=='\'') + value=value.substring(1); + char last=value.charAt(value.length()-1); + if (last=='"' || last=='\'') + value = value.substring(0,value.length()-1); + + return value; + } + else + //unquote the string, but allow any backslashes that don't + //form a valid escape sequence to remain as many browsers + //even on *nix systems will not escape a filename containing + //backslashes + return QuotedStringTokenizer.unquoteOnly(value, true); + } /* ------------------------------------------------------------------------------- */ /** diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java index e7f771d0cef..19e144c1fc4 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java @@ -224,7 +224,7 @@ public class MultipartFilterTest response.parse(tester.getResponses(request.generate())); - System.out.printf("Content: [%s]%n", response.getContent()); + //System.out.printf("Content: [%s]%n", response.getContent()); assertThat(response.getMethod(), nullValue()); assertThat(response.getStatus(), is(HttpServletResponse.SC_OK)); @@ -232,7 +232,80 @@ public class MultipartFilterTest assertThat(response.getContent(), containsString("Filename [Taken on Aug 22 \\ 2012.jpg]")); assertThat(response.getContent(), containsString("How now brown cow.")); } + + @Test + public void testBadlyEncodedMSFilename() throws Exception + { + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + // test GET + request.setMethod("POST"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/dump"); + + String boundary="XyXyXy"; + request.setHeader("Content-Type","multipart/form-data; boundary="+boundary); + + + String content = "--" + boundary + "\r\n"+ + "Content-Disposition: form-data; name=\"fileup\"; filename=\"c:\\this\\really\\is\\some\\path\\to\\a\\file.txt\"\r\n"+ + "Content-Type: application/octet-stream\r\n\r\n"+ + "How now brown cow."+ + "\r\n--" + boundary + "--\r\n\r\n"; + + request.setContent(content); + + response.parse(tester.getResponses(request.generate())); + + //System.out.printf("Content: [%s]%n", response.getContent()); + + assertThat(response.getMethod(), nullValue()); + assertThat(response.getStatus(), is(HttpServletResponse.SC_OK)); + + assertThat(response.getContent(), containsString("Filename [c:\\this\\really\\is\\some\\path\\to\\a\\file.txt]")); + assertThat(response.getContent(), containsString("How now brown cow.")); + } + + @Test + public void testCorrectlyEncodedMSFilename() throws Exception + { + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + + // test GET + request.setMethod("POST"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/dump"); + + String boundary="XyXyXy"; + request.setHeader("Content-Type","multipart/form-data; boundary="+boundary); + + + String content = "--" + boundary + "\r\n"+ + "Content-Disposition: form-data; name=\"fileup\"; filename=\"c:\\\\this\\\\really\\\\is\\\\some\\\\path\\\\to\\\\a\\\\file.txt\"\r\n"+ + "Content-Type: application/octet-stream\r\n\r\n"+ + "How now brown cow."+ + "\r\n--" + boundary + "--\r\n\r\n"; + + request.setContent(content); + + response.parse(tester.getResponses(request.generate())); + + //System.out.printf("Content: [%s]%n", response.getContent()); + + assertThat(response.getMethod(), nullValue()); + assertThat(response.getStatus(), is(HttpServletResponse.SC_OK)); + + assertThat(response.getContent(), containsString("Filename [c:\\this\\really\\is\\some\\path\\to\\a\\file.txt]")); + assertThat(response.getContent(), containsString("How now brown cow.")); + } + + /* * Test multipart with parts encoded in base64 (RFC1521 section 5) */ diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java index 1dc416ec88a..54ee74fe339 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java @@ -409,12 +409,21 @@ public class QuotedStringTokenizer } } + + /* ------------------------------------------------------------ */ + public static String unquoteOnly(String s) + { + return unquoteOnly(s, false); + } + + /* ------------------------------------------------------------ */ /** Unquote a string, NOT converting unicode sequences * @param s The string to unquote. + * @param lenient if true, will leave in backslashes that aren't valid escapes * @return quoted string */ - public static String unquoteOnly(String s) + public static String unquoteOnly(String s, boolean lenient) { if (s==null) return null; @@ -435,7 +444,7 @@ public class QuotedStringTokenizer if (escape) { escape=false; - if (!isValidEscaping(c)) + if (lenient && !isValidEscaping(c)) { b.append('\\'); } @@ -453,13 +462,19 @@ public class QuotedStringTokenizer return b.toString(); } - + + /* ------------------------------------------------------------ */ + public static String unquote(String s) + { + return unquote(s,false); + } + /* ------------------------------------------------------------ */ /** Unquote a string. * @param s The string to unquote. * @return quoted string */ - public static String unquote(String s) + public static String unquote(String s, boolean lenient) { if (s==null) return null; @@ -516,7 +531,7 @@ public class QuotedStringTokenizer ); break; default: - if (!isValidEscaping(c)) + if (lenient && !isValidEscaping(c)) { b.append('\\'); } @@ -536,6 +551,13 @@ public class QuotedStringTokenizer return b.toString(); } + + /* ------------------------------------------------------------ */ + /** Check that char c (which is preceded by a backslash) is a valid + * escape sequence. + * @param c + * @return + */ private static boolean isValidEscaping(char c) { return ((c == 'n') || (c == 'r') || (c == 't') ||