From 83d048ed93217c0f9554f215a065ffc808e2b53e Mon Sep 17 00:00:00 2001 From: aserkes Date: Mon, 4 Jan 2021 09:42:42 +0100 Subject: [PATCH] Encode a filename parameter if it is not encoded, throw an exception if a filename parameter is not valid --- .../media/multipart/ContentDisposition.java | 54 +++++++- .../tests/api/ContentDispositionTest.java | 128 +++++++++++------- 2 files changed, 129 insertions(+), 53 deletions(-) diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java index 5f0f3771ba1..8f0bd09bc25 100644 --- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java +++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/ContentDisposition.java @@ -20,10 +20,12 @@ import java.util.Collections; import java.util.Date; import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.glassfish.jersey.message.internal.HttpDateFormat; import org.glassfish.jersey.message.internal.HttpHeaderReader; +import org.glassfish.jersey.uri.UriComponent; /** * A content disposition header. @@ -41,8 +43,15 @@ public class ContentDisposition { private Date modificationDate; private Date readDate; private long size; + + private static final String CHARSET_GROUP_NAME = "charset"; + private static final String CHARSET_REGEX = "(?<" + CHARSET_GROUP_NAME + ">UTF-8|ISO-8859-1)"; + private static final String LANG_GROUP_NAME = "lang"; + private static final String LANG_REGEX = "(?<" + LANG_GROUP_NAME + ">[a-z]{2,8}(-[a-z0-9-]+)?)?"; + private static final String FILENAME_GROUP_NAME = "filename"; + private static final String FILENAME_REGEX = "(?<" + FILENAME_GROUP_NAME + ">.+)"; private static final Pattern FILENAME_EXT_VALUE_PATTERN = - Pattern.compile("(UTF-8|ISO-8859-1)'([a-z]{2,8}(-[a-z0-9]+)?)?'(%[a-f0-9]{2}|[a-z0-9!#$&+.^_`|~-])+", + Pattern.compile(CHARSET_REGEX + "'" + LANG_REGEX + "'" + FILENAME_REGEX, Pattern.CASE_INSENSITIVE); protected ContentDisposition(final String type, final String fileName, final Date creationDate, @@ -185,12 +194,7 @@ protected void addLongParameter(final StringBuilder sb, final String name, final } private void createParameters() throws ParseException { - fileName = parameters.get("filename"); - - String fileNameExt = parameters.get("filename*"); - if (fileNameExt != null && FILENAME_EXT_VALUE_PATTERN.matcher(fileNameExt).matches()) { - fileName = fileNameExt; - } + fileName = defineFileName(); creationDate = createDate("creation-date"); @@ -201,6 +205,42 @@ private void createParameters() throws ParseException { size = createLong("size"); } + private String defineFileName() throws ParseException { + final String fileName = parameters.get("filename"); + + final String fileNameExt = parameters.get("filename*"); + if (fileNameExt == null) { + return fileName; + } + + final Matcher matcher = FILENAME_EXT_VALUE_PATTERN.matcher(fileNameExt); + if (matcher.matches()) { + if (isEncodedInUriFormat(fileNameExt)) { + return fileNameExt; + } else { + if (matcher.group(CHARSET_GROUP_NAME).equalsIgnoreCase("UTF-8")) { + return new StringBuilder(matcher.group(CHARSET_GROUP_NAME)) + .append("'") + .append(matcher.group(LANG_GROUP_NAME) == null ? "" : matcher.group(LANG_GROUP_NAME)) + .append("'") + .append(encodeToUriFormat(matcher.group(FILENAME_GROUP_NAME))) + .toString(); + } + throw new ParseException(matcher.group(CHARSET_GROUP_NAME) + " charset is not supported", 0); + } + } + + throw new ParseException(fileNameExt + " - unsupported filename parameter", 0); + } + + private String encodeToUriFormat(final String parameter) { + return UriComponent.contextualEncode(parameter, UriComponent.Type.UNRESERVED); + } + + private boolean isEncodedInUriFormat(final String parameter) { + return UriComponent.valid(parameter, UriComponent.Type.UNRESERVED); + } + private Date createDate(final String name) throws ParseException { final String value = parameters.get(name); if (value == null) { diff --git a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java index 8f14b246e39..7c3992437ba 100644 --- a/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java +++ b/tests/e2e/src/test/java/org/glassfish/jersey/tests/api/ContentDispositionTest.java @@ -24,10 +24,12 @@ import org.glassfish.jersey.message.internal.HttpHeaderReader; import org.junit.Test; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; + /** * @author Imran@SmartITEngineering.Com */ @@ -100,81 +102,115 @@ public void testToString() { @Test public void testFileNameExt() { final String fileName = "test.file"; + String fileNameExt; + String encodedFilename; try { //incorrect fileNameExt - does not contain charset'' - String fileNameExt = "testExt.file"; - assertFileNameExt(fileName, fileName, fileNameExt); - - //correct fileNameExt - fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; - assertFileNameExt(fileNameExt, fileName, fileNameExt); + try { + fileNameExt = "testExt.file"; + assertFileNameExt(fileName, fileName, fileNameExt); + fail("ParseException was expected to be thrown."); + } catch (ParseException e) { + //expected + } + + //correct fileNameExt, but unsupported charset (support only UTF-8) + try { + fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; + assertFileNameExt(fileNameExt, fileName, fileNameExt); + fail("ParseException was expected to be thrown."); + } catch (ParseException e) { + //expected + } + + //correct fileNameExt with encoding + fileNameExt = "UTF-8'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; + encodedFilename = "UTF-8'language-us'abc%a1abc%a2%b1%21%23%24%26%2B.%5E_%60%7C~-"; + assertFileNameExt(encodedFilename, fileName, fileNameExt); //correct fileNameExt fileNameExt = "UTF-8'us'fileName.txt"; assertFileNameExt(fileNameExt, fileName, fileNameExt); //incorrect fileNameExt - too long language tag - fileNameExt = "utf-8'languageTooLong'fileName.txt"; - assertFileNameExt(fileName, fileName, fileNameExt); + try { + fileNameExt = "utf-8'languageTooLong'fileName.txt"; + assertFileNameExt(fileName, fileName, fileNameExt); + fail("ParseException was expected to be thrown."); + } catch (ParseException e) { + //expected + } //correct fileNameExt fileNameExt = "utf-8''a"; assertFileNameExt(fileNameExt, fileName, fileNameExt); //incorrect fileNameExt - language tag does not match to pattern - fileNameExt = "utf-8'lang-'a"; - assertFileNameExt(fileName, fileName, fileNameExt); + try { + fileNameExt = "utf-8'lang-'a"; + assertFileNameExt(fileName, fileName, fileNameExt); + fail("ParseException was expected to be thrown."); + } catch (ParseException e) { + //expected + } + + //incorrect fileNameExt - ext-value contains an inappropriate symbol sequence (%z1). Jersey encodes it. + fileNameExt = "utf-8'language-us'a%z1"; + encodedFilename = "utf-8'language-us'a%25z1"; + assertFileNameExt(encodedFilename, fileName, fileNameExt); //correct fileNameExt - fileNameExt = "ISO-8859-1'language-us'a"; + fileNameExt = "UTF-8'language-us'abc%a1abc%a2%b1"; assertFileNameExt(fileNameExt, fileName, fileNameExt); - //correct fileNameExt - fileNameExt = "ISO-8859-1'language-us'a%a1"; - assertFileNameExt(fileNameExt, fileName, fileNameExt); - - //incorrect fileNameExt - ext-value contains an inappropriate symbol sequence (%z1) - fileNameExt = "ISO-8859-1'language-us'a%z1"; - assertFileNameExt(fileName, fileName, fileNameExt); + //incorrect fileNameExt - unsupported charset + try { + fileNameExt = "Windows-1251'sr-Latn-RS'a"; + assertFileNameExt(fileName, fileName, fileNameExt); + fail("ParseException was expected to be thrown."); + } catch (ParseException e) { + //expected + } //correct fileNameExt - fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1"; + fileNameExt = "utf-8'sr-Latn-RS'a"; assertFileNameExt(fileNameExt, fileName, fileNameExt); - //incorrect fileNameExt - ext-value contains % without two HEXDIG - fileNameExt = "ISO-8859-1'language-us'a%"; - assertFileNameExt(fileName, fileName, fileNameExt); + //incorrect fileNameExt - ext-value contains % without two HEXDIG. Jersey encodes it. + fileNameExt = "utf-8'language-us'a%"; + encodedFilename = "utf-8'language-us'a%25"; + assertFileNameExt(encodedFilename, fileName, fileNameExt); //correct fileNameExt - fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; - assertFileNameExt(fileNameExt, fileName, fileNameExt); - - //correct fileNameExt - fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; - assertFileNameExt(fileNameExt, fileName, fileNameExt); - - //correct fileNameExt - fileNameExt = "iso-8859-1'language-us'abc.TXT"; + fileNameExt = "UTF-8'language-us'abc.TXT"; assertFileNameExt(fileNameExt, fileName, fileNameExt); //incorrect fileNameExt - no ext-value - fileNameExt = "ISO-8859-1'language-us'"; - assertFileNameExt(fileName, fileName, fileNameExt); - - //incorrect fileNameExt - ext-value contains forbidden symbol (\) - fileNameExt = "ISO-8859-1'language-us'c:\\file.txt"; - assertFileNameExt(fileName, fileName, fileNameExt); - - //incorrect fileNameExt - ext-value contains forbidden symbol (/) - fileNameExt = "ISO-8859-1'language-us'home/file.txt"; - assertFileNameExt(fileName, fileName, fileNameExt); - - //incorrect fileNameExt - ext-value contains forbidden symbol (李) - fileNameExt = "ISO-8859-1'language-us'李.txt"; - assertFileNameExt(fileName, fileName, fileNameExt); + try { + fileNameExt = "utf-8'language-us'"; + assertFileNameExt(fileName, fileName, fileNameExt); + fail("ParseException was expected to be thrown."); + } catch (ParseException e) { + //expected + } + + //incorrect fileNameExt - ext-value contains forbidden symbol (\). Jersey encodes it. + fileNameExt = "utf-8'language-us'c:\\\\file.txt"; + encodedFilename = "utf-8'language-us'c%3A%5Cfile.txt"; + assertFileNameExt(encodedFilename, fileName, fileNameExt); + + //incorrect fileNameExt - ext-value contains forbidden symbol (/). Jersey encodes it. + fileNameExt = "utf-8'language-us'home/file.txt"; + encodedFilename = "utf-8'language-us'home%2Ffile.txt"; + assertFileNameExt(encodedFilename, fileName, fileNameExt); + + //incorrect fileNameExt - ext-value contains forbidden symbol (李). Jersey encodes it. + fileNameExt = "utf-8'language-us'李.txt"; + encodedFilename = "utf-8'language-us'%E6%9D%8E.txt"; + assertFileNameExt(encodedFilename, fileName, fileNameExt); //correct fileNameExt - fileNameExt = "ISO-8859-1'language-us'FILEname.tXt"; + fileNameExt = "utf-8'language-us'FILEname.tXt"; assertFileNameExt(fileNameExt, fileName, fileNameExt); } catch (ParseException ex) {