From 4522d94c4a6d892e0194deff468dc21d031a6801 Mon Sep 17 00:00:00 2001 From: aserkes Date: Sun, 29 Nov 2020 23:14:25 +0100 Subject: [PATCH 1/8] Add support RFC 5987 for attribute filename* in HTTP header Content-Disposition Signed-off-by: Andrii Serkes --- .../media/multipart/ContentDisposition.java | 8 +++++++ .../tests/api/ContentDispositionTest.java | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+) 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 ab9e14512f..9b81ab04ae 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,6 +20,7 @@ import java.util.Collections; import java.util.Date; import java.util.Map; +import java.util.regex.Pattern; import org.glassfish.jersey.message.internal.HttpDateFormat; import org.glassfish.jersey.message.internal.HttpHeaderReader; @@ -40,6 +41,8 @@ public class ContentDisposition { private Date modificationDate; private Date readDate; private long size; + 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!#$&+.^_`|~-])+"); protected ContentDisposition(final String type, final String fileName, final Date creationDate, final Date modificationDate, final Date readDate, final long size) { @@ -183,6 +186,11 @@ 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; + } + creationDate = createDate("creation-date"); modificationDate = createDate("modification-date"); 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 63f2cd2eee..eb8d4b34d6 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 @@ -97,6 +97,29 @@ public void testToString() { assertEquals(header, contentDisposition.toString()); } + @Test + public void testFileNameExt() { + final Date date = new Date(); + final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); + final String fileName = "test.file"; + String fileNameExt = "testExt.file"; + final String prefixHeader = contentDispositionType + ";filename=\"" + fileName + "\";" + + "creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" + + dateString + "\";size=1222" + ";name=\"testData\";" + "filename*=\""; + String header = prefixHeader + fileNameExt + "\""; + try { + ContentDisposition contentDisposition = + new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + } catch (ParseException ex) { + fail(ex.getMessage()); + } + } + protected void assertContentDisposition(final ContentDisposition contentDisposition, Date date) { assertNotNull(contentDisposition); assertEquals(contentDispositionType, contentDisposition.getType()); From 9c1437bd5e84f9778435051e0ef94e98662e0579 Mon Sep 17 00:00:00 2001 From: Andrii Serkes Date: Mon, 30 Nov 2020 12:17:36 +0100 Subject: [PATCH 2/8] Update year in Copyright comments Signed-off-by: Andrii Serkes --- .../glassfish/jersey/media/multipart/ContentDisposition.java | 2 +- .../org/glassfish/jersey/tests/api/ContentDispositionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 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 9b81ab04ae..a5701b9fca 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at 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 eb8d4b34d6..8e630446c5 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at From 3089053bbc186c6ea6ed0fecd02cc8c864b521bc Mon Sep 17 00:00:00 2001 From: Andrii Serkes Date: Sat, 5 Dec 2020 20:17:28 +0100 Subject: [PATCH 3/8] Fix problem with capital letters, add more filename patterns for tests Signed-off-by: Andrii Serkes --- .../media/multipart/ContentDisposition.java | 3 +- .../tests/api/ContentDispositionTest.java | 117 +++++++++++++++++- 2 files changed, 114 insertions(+), 6 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 a5701b9fca..5f0f3771ba 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 @@ -42,7 +42,8 @@ public class ContentDisposition { private Date readDate; private long size; 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("(UTF-8|ISO-8859-1)'([a-z]{2,8}(-[a-z0-9]+)?)?'(%[a-f0-9]{2}|[a-z0-9!#$&+.^_`|~-])+", + Pattern.CASE_INSENSITIVE); protected ContentDisposition(final String type, final String fileName, final Date creationDate, final Date modificationDate, final Date readDate, final long size) { 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 8e630446c5..dbced0288e 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 @@ -102,19 +102,126 @@ public void testFileNameExt() { final Date date = new Date(); final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); final String fileName = "test.file"; - String fileNameExt = "testExt.file"; - final String prefixHeader = contentDispositionType + ";filename=\"" + fileName + "\";" - + "creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" - + dateString + "\";size=1222" + ";name=\"testData\";" + "filename*=\""; - String header = prefixHeader + fileNameExt + "\""; + try { + //correct fileNameExt + String fileNameExt = "testExt.file"; + final String prefixHeader = contentDispositionType + ";filename=\"" + fileName + "\";" + + "creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" + + dateString + "\";size=1222" + ";name=\"testData\";" + "filename*=\""; + String header = prefixHeader + fileNameExt + "\""; ContentDisposition contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); assertEquals(fileName, contentDisposition.getFileName()); + + //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; header = prefixHeader + fileNameExt + "\""; contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); assertEquals(fileNameExt, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "UTF-8'us'fileName.txt"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "utf-8'languageTooLong'fileName.txt"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "utf-8''a"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "utf-8'lang-'a"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "ISO-8859-1'language-us'a"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "ISO-8859-1'language-us'a%a1"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "ISO-8859-1'language-us'a%z1"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "ISO-8859-1'language-us'a%"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "iso-8859-1'language-us'abc.TXT"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "ISO-8859-1'language-us'"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "ISO-8859-1'language-us'c:\\file.txt"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "ISO-8859-1'language-us'home/file.txt"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //incorrect fileNameExt + fileNameExt = "ISO-8859-1'language-us'李.txt"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileName, contentDisposition.getFileName()); + + //correct fileNameExt + fileNameExt = "ISO-8859-1'language-us'FILEname.tXt"; + header = prefixHeader + fileNameExt + "\""; + contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(fileNameExt, contentDisposition.getFileName()); + } catch (ParseException ex) { fail(ex.getMessage()); } From 109e685d99d2458f18c5629af8521095b960625c Mon Sep 17 00:00:00 2001 From: Andrii Serkes Date: Mon, 7 Dec 2020 12:42:19 +0100 Subject: [PATCH 4/8] code refactoring Signed-off-by: Andrii Serkes --- .../tests/api/ContentDispositionTest.java | 116 +++++++----------- 1 file changed, 43 insertions(+), 73 deletions(-) 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 dbced0288e..8f14b246e3 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 @@ -99,134 +99,104 @@ public void testToString() { @Test public void testFileNameExt() { - final Date date = new Date(); - final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); final String fileName = "test.file"; - try { - //correct fileNameExt + //incorrect fileNameExt - does not contain charset'' String fileNameExt = "testExt.file"; - final String prefixHeader = contentDispositionType + ";filename=\"" + fileName + "\";" - + "creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" - + dateString + "\";size=1222" + ";name=\"testData\";" + "filename*=\""; - String header = prefixHeader + fileNameExt + "\""; - ContentDisposition contentDisposition = - new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); //correct fileNameExt fileNameExt = "UTF-8'us'fileName.txt"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - too long language tag fileNameExt = "utf-8'languageTooLong'fileName.txt"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); //correct fileNameExt fileNameExt = "utf-8''a"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - language tag does not match to pattern fileNameExt = "utf-8'lang-'a"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'a"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'a%a1"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - ext-value contains an inappropriate symbol sequence (%z1) fileNameExt = "ISO-8859-1'language-us'a%z1"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - ext-value contains % without two HEXDIG fileNameExt = "ISO-8859-1'language-us'a%"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); //correct fileNameExt fileNameExt = "iso-8859-1'language-us'abc.TXT"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - no ext-value fileNameExt = "ISO-8859-1'language-us'"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - ext-value contains forbidden symbol (\) fileNameExt = "ISO-8859-1'language-us'c:\\file.txt"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - ext-value contains forbidden symbol (/) fileNameExt = "ISO-8859-1'language-us'home/file.txt"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); - //incorrect fileNameExt + //incorrect fileNameExt - ext-value contains forbidden symbol (李) fileNameExt = "ISO-8859-1'language-us'李.txt"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileName, contentDisposition.getFileName()); + assertFileNameExt(fileName, fileName, fileNameExt); //correct fileNameExt fileNameExt = "ISO-8859-1'language-us'FILEname.tXt"; - header = prefixHeader + fileNameExt + "\""; - contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); - assertEquals(fileNameExt, contentDisposition.getFileName()); + assertFileNameExt(fileNameExt, fileName, fileNameExt); } catch (ParseException ex) { fail(ex.getMessage()); } } + private void assertFileNameExt( + final String expectedFileName, + final String actualFileName, + final String actualFileNameExt + ) throws ParseException { + final Date date = new Date(); + final String dateString = HttpDateFormat.getPreferredDateFormat().format(date); + final String prefixHeader = contentDispositionType + ";filename=\"" + actualFileName + "\";" + + "creation-date=\"" + dateString + "\";modification-date=\"" + dateString + "\";read-date=\"" + + dateString + "\";size=1222" + ";name=\"testData\";" + "filename*=\""; + final String header = prefixHeader + actualFileNameExt + "\""; + final ContentDisposition contentDisposition = new ContentDisposition(HttpHeaderReader.newInstance(header), true); + assertEquals(expectedFileName, contentDisposition.getFileName()); + } + protected void assertContentDisposition(final ContentDisposition contentDisposition, Date date) { assertNotNull(contentDisposition); assertEquals(contentDispositionType, contentDisposition.getType()); From b8fac127278365f5de870fc43f6ff3b1c34a0c27 Mon Sep 17 00:00:00 2001 From: aserkes Date: Mon, 4 Jan 2021 09:42:42 +0100 Subject: [PATCH 5/8] Encode a filename parameter if it is not encoded, throw an exception if a filename parameter is not valid Signed-off-by: aserkes --- .../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 5f0f3771ba..8f0bd09bc2 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 8f14b246e3..7c3992437b 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) { From 7d4d5d588055c7fd62b5d47940c3a3daa24d1bb3 Mon Sep 17 00:00:00 2001 From: aserkes Date: Thu, 7 Jan 2021 15:12:47 +0100 Subject: [PATCH 6/8] All charsets for the filename* parameter are permitted Signed-off-by: aserkes --- .../media/multipart/ContentDisposition.java | 33 ++++++++++------- .../tests/api/ContentDispositionTest.java | 37 ++++++++++--------- 2 files changed, 40 insertions(+), 30 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 8f0bd09bc2..ec3a19d024 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 @@ -45,7 +45,7 @@ public class ContentDisposition { 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 CHARSET_REGEX = "(?<" + CHARSET_GROUP_NAME + ">[^']+)"; 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"; @@ -53,6 +53,8 @@ public class ContentDisposition { private static final Pattern FILENAME_EXT_VALUE_PATTERN = Pattern.compile(CHARSET_REGEX + "'" + LANG_REGEX + "'" + FILENAME_REGEX, Pattern.CASE_INSENSITIVE); + private static final Pattern FILENAME_VALUE_CHARS_PATTERN = + Pattern.compile("(%[a-f0-9]{2}|[a-z0-9!#$&+.^_`|~-])+", Pattern.CASE_INSENSITIVE); protected ContentDisposition(final String type, final String fileName, final Date creationDate, final Date modificationDate, final Date readDate, final long size) { @@ -206,26 +208,31 @@ private void createParameters() throws ParseException { } private String defineFileName() throws ParseException { - final String fileName = parameters.get("filename"); + 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)) { + + final String fileNameValueChars = matcher.group(FILENAME_GROUP_NAME); + if (isFilenameValueCharsEncoded(fileNameValueChars)) { return fileNameExt; + } + + 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(fileNameValueChars)) + .toString(); } 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); } } @@ -237,8 +244,8 @@ 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 boolean isFilenameValueCharsEncoded(final String parameter) { + return FILENAME_VALUE_CHARS_PATTERN.matcher(parameter).matches(); } private Date createDate(final String name) throws ParseException { 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 7c3992437b..9fa785bf92 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 @@ -114,19 +114,13 @@ public void testFileNameExt() { //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 + fileNameExt = "ISO-8859-1'language-us'abc%a1abc%a2%b1!#$&+.^_`|~-"; + assertFileNameExt(fileNameExt, fileName, fileNameExt); - //correct fileNameExt with encoding + //correct fileNameExt 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); + assertFileNameExt(fileNameExt, fileName, fileNameExt); //correct fileNameExt fileNameExt = "UTF-8'us'fileName.txt"; @@ -159,19 +153,28 @@ public void testFileNameExt() { encodedFilename = "utf-8'language-us'a%25z1"; assertFileNameExt(encodedFilename, fileName, fileNameExt); - //correct fileNameExt - fileNameExt = "UTF-8'language-us'abc%a1abc%a2%b1"; - assertFileNameExt(fileNameExt, fileName, fileNameExt); - - //incorrect fileNameExt - unsupported charset + //Incorrect fileNameExt - ext-value contains an inappropriate symbol sequence (%z1). + //Jersey won't encodes it because of the unsupported charset. try { - fileNameExt = "Windows-1251'sr-Latn-RS'a"; + fileNameExt = "windows-1251'ru-ru'a%z1"; assertFileNameExt(fileName, fileName, fileNameExt); fail("ParseException was expected to be thrown."); } catch (ParseException e) { //expected } + //correct fileNameExt + fileNameExt = "UTF-8'language-us'abc%a1abc%a2%b1"; + assertFileNameExt(fileNameExt, fileName, fileNameExt); + + //correct fileNameExt - encoded with other charset + fileNameExt = "UTF-16'language-us'abc%a1abc%a2%b1"; + assertFileNameExt(fileNameExt, fileName, fileNameExt); + + //correct fileNameExt - unsupported charset, but fileName contains only valid characters + fileNameExt = "Windows-1251'sr-Latn-RS'abc"; + assertFileNameExt(fileNameExt, fileName, fileNameExt); + //correct fileNameExt fileNameExt = "utf-8'sr-Latn-RS'a"; assertFileNameExt(fileNameExt, fileName, fileNameExt); From b523c015e32be84362493f939233ddeda0cb6f9f Mon Sep 17 00:00:00 2001 From: aserkes Date: Fri, 8 Jan 2021 14:30:20 +0100 Subject: [PATCH 7/8] Refactoring Signed-off-by: aserkes --- .../jersey/media/multipart/ContentDisposition.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 ec3a19d024..cb2ee378df 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 @@ -225,15 +225,17 @@ private String defineFileName() throws ParseException { return fileNameExt; } + final String charset = matcher.group(CHARSET_GROUP_NAME); if (matcher.group(CHARSET_GROUP_NAME).equalsIgnoreCase("UTF-8")) { - return new StringBuilder(matcher.group(CHARSET_GROUP_NAME)) + final String language = matcher.group(LANG_GROUP_NAME); + return new StringBuilder(charset) .append("'") - .append(matcher.group(LANG_GROUP_NAME) == null ? "" : matcher.group(LANG_GROUP_NAME)) + .append(language == null ? "" : language) .append("'") .append(encodeToUriFormat(fileNameValueChars)) .toString(); } else { - throw new ParseException(matcher.group(CHARSET_GROUP_NAME) + " charset is not supported", 0); + throw new ParseException(charset + " charset is not supported", 0); } } From 10ca1544160d98ecb4a1a943d562478ced07bc55 Mon Sep 17 00:00:00 2001 From: aserkes Date: Wed, 13 Jan 2021 11:10:43 +0100 Subject: [PATCH 8/8] Update year in Copyright comments Signed-off-by: aserkes --- .../glassfish/jersey/media/multipart/ContentDisposition.java | 2 +- .../org/glassfish/jersey/tests/api/ContentDispositionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 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 cb2ee378df..3d71203ef8 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2021 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at 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 9fa785bf92..8cbdcc1a50 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2021 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at