You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by se...@apache.org on 2015/02/08 19:54:50 UTC
cxf git commit: [CXF-6244] Support for ContentDisposition UTF8
filename parameters, patch from Mark Ford applied; This closes #52
Repository: cxf
Updated Branches:
refs/heads/master 941392bc1 -> 5f125f514
[CXF-6244] Support for ContentDisposition UTF8 filename parameters, patch from Mark Ford applied; This closes #52
Project: http://git-wip-us.apache.org/repos/asf/cxf/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/5f125f51
Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/5f125f51
Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/5f125f51
Branch: refs/heads/master
Commit: 5f125f514d00451b3644b25b0ecfd506664c958f
Parents: 941392b
Author: Sergey Beryozkin <sb...@talend.com>
Authored: Sun Feb 8 18:54:33 2015 +0000
Committer: Sergey Beryozkin <sb...@talend.com>
Committed: Sun Feb 8 18:54:33 2015 +0000
----------------------------------------------------------------------
.../cxf/attachment/ContentDisposition.java | 67 ++++++++++++---
.../org/apache/cxf/attachment/Rfc5987Util.java | 88 ++++++++++++++++++++
.../cxf/attachment/AttachmentUtilTest.java | 10 ++-
.../apache/cxf/attachment/Rfc5987UtilTest.java | 56 +++++++++++++
4 files changed, 206 insertions(+), 15 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cxf/blob/5f125f51/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java b/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
index c1ce0e7..1fdc4ee 100644
--- a/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
+++ b/core/src/main/java/org/apache/cxf/attachment/ContentDisposition.java
@@ -19,6 +19,7 @@
package org.apache.cxf.attachment;
+import java.io.UnsupportedEncodingException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -26,45 +27,85 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class ContentDisposition {
- private static final String CD_HEADER_PARAMS_EXPRESSION =
- "(([\\w]+( )?=( )?\"[^\"]+\")|([\\w]+( )?=( )?[^;]+))";
+ private static final String CD_HEADER_PARAMS_EXPRESSION =
+ "(([\\w]+( )?\\*?=( )?\"[^\"]+\")|([\\w]+( )?\\*?=( )?[^;]+))";
private static final Pattern CD_HEADER_PARAMS_PATTERN =
- Pattern.compile(CD_HEADER_PARAMS_EXPRESSION);
+ Pattern.compile(CD_HEADER_PARAMS_EXPRESSION);
+
+ private static final String CD_HEADER_EXT_PARAMS_EXPRESSION =
+ "(UTF-8|ISO-8859-1)''((?:%[0-9a-f]{2}|\\S)+)";
+ private static final Pattern CD_HEADER_EXT_PARAMS_PATTERN =
+ Pattern.compile(CD_HEADER_EXT_PARAMS_EXPRESSION);
+
private String value;
private String type;
private Map<String, String> params = new LinkedHashMap<String, String>();
-
+
public ContentDisposition(String value) {
this.value = value;
-
+
String tempValue = value;
-
+
int index = tempValue.indexOf(';');
if (index > 0 && !(tempValue.indexOf('=') < index)) {
type = tempValue.substring(0, index).trim();
tempValue = tempValue.substring(index + 1);
}
-
+
+ String extendedFilename = null;
Matcher m = CD_HEADER_PARAMS_PATTERN.matcher(tempValue);
while (m.find()) {
String[] pair = m.group().trim().split("=");
- params.put(pair[0].trim(),
- pair.length == 2 ? pair[1].trim().replace("\"", "") : "");
+ String paramName = pair[0].trim();
+ String paramValue = pair.length == 2 ? pair[1].trim().replace("\"", "") : "";
+ // filename* looks like the only CD param that is human readable
+ // and worthy of the extended encoding support. Other parameters
+ // can be supported if needed, see the complete list below
+ /*
+ http://www.iana.org/assignments/cont-disp/cont-disp.xhtml#cont-disp-2
+
+ filename name to be used when creating file [RFC2183]
+ creation-date date when content was created [RFC2183]
+ modification-date date when content was last modified [RFC2183]
+ read-date date when content was last read [RFC2183]
+ size approximate size of content in octets [RFC2183]
+ name original field name in form [RFC2388]
+ voice type or use of audio content [RFC2421]
+ handling whether or not processing is required [RFC3204]
+ */
+ if ("filename*".equals(paramName)) {
+ // try to decode the value if it matches the spec
+ try {
+ Matcher matcher = CD_HEADER_EXT_PARAMS_PATTERN.matcher(paramValue);
+ if (matcher.matches()) {
+ String encodingScheme = matcher.group(1);
+ String encodedValue = matcher.group(2);
+ paramValue = Rfc5987Util.decode(encodedValue, encodingScheme);
+ extendedFilename = paramValue;
+ }
+ } catch (UnsupportedEncodingException e) {
+ // would be odd not to support UTF-8 or 8859-1
+ }
+ }
+ params.put(paramName, paramValue);
+ }
+ if (extendedFilename != null) {
+ params.put("filename", extendedFilename);
}
}
-
+
public String getType() {
return type;
}
-
+
public String getParameter(String name) {
return params.get(name);
}
-
+
public Map<String, String> getParameters() {
return Collections.unmodifiableMap(params);
}
-
+
public String toString() {
return value;
}
http://git-wip-us.apache.org/repos/asf/cxf/blob/5f125f51/core/src/main/java/org/apache/cxf/attachment/Rfc5987Util.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/cxf/attachment/Rfc5987Util.java b/core/src/main/java/org/apache/cxf/attachment/Rfc5987Util.java
new file mode 100644
index 0000000..8da87f7
--- /dev/null
+++ b/core/src/main/java/org/apache/cxf/attachment/Rfc5987Util.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.attachment;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Utility for encoding and decoding values according to RFC 5987. Assumes the
+ * caller already knows the encoding scheme for the value.
+ */
+public final class Rfc5987Util {
+
+ private static final Pattern ENCODED_VALUE_PATTERN = Pattern.compile("%[0-9a-f]{2}|\\S",
+ Pattern.CASE_INSENSITIVE);
+
+ private Rfc5987Util() {
+
+ }
+
+ public static String encode(final String s) throws UnsupportedEncodingException {
+ return encode(s, "UTF-8");
+ }
+
+ // http://stackoverflow.com/questions/11302361/ (continued next line)
+ // handling-filename-parameters-with-spaces-via-rfc-5987-results-in-in-filenam
+ public static String encode(final String s, String encoding) throws UnsupportedEncodingException {
+ final byte[] rawBytes = s.getBytes(encoding);
+ final int len = rawBytes.length;
+ final StringBuilder sb = new StringBuilder(len << 1);
+ final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f'};
+ final byte[] attributeChars = {'!', '#', '$', '&', '+', '-', '.', '0',
+ '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
+ 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
+ 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '^', '_', '`', 'a',
+ 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
+ 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '|',
+ '~'};
+ for (final byte b : rawBytes) {
+ if (Arrays.binarySearch(attributeChars, b) >= 0) {
+ sb.append((char) b);
+ } else {
+ sb.append('%');
+ sb.append(digits[0x0f & (b >>> 4)]);
+ sb.append(digits[b & 0x0f]);
+ }
+ }
+
+ return sb.toString();
+ }
+
+ public static String decode(String s, String encoding)
+ throws UnsupportedEncodingException {
+ Matcher matcher = ENCODED_VALUE_PATTERN.matcher(s);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ while (matcher.find()) {
+ String matched = matcher.group();
+ if (matched.startsWith("%")) {
+ Integer value = Integer.parseInt(matched.substring(1), 16);
+ bos.write(value);
+ } else {
+ bos.write(matched.charAt(0));
+ }
+ }
+
+ return new String(bos.toByteArray(), encoding);
+ }
+}
http://git-wip-us.apache.org/repos/asf/cxf/blob/5f125f51/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java b/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
index 932dcfe..6eeedd4 100644
--- a/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
+++ b/core/src/test/java/org/apache/cxf/attachment/AttachmentUtilTest.java
@@ -70,7 +70,13 @@ public class AttachmentUtilTest extends Assert {
assertEquals("a;txt",
AttachmentUtil.getContentDispositionFileName("name=\"a\";filename=\"a;txt\""));
}
-
-
+
+ @Test
+ public void testContendDispositionFileNameKanjiChars() {
+ assertEquals("世界ーファイル.txt",
+ AttachmentUtil.getContentDispositionFileName(
+ "filename*=UTF-8''%e4%b8%96%e7%95%8c%e3%83%bc%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab.txt"));
+ }
+
}
http://git-wip-us.apache.org/repos/asf/cxf/blob/5f125f51/core/src/test/java/org/apache/cxf/attachment/Rfc5987UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/cxf/attachment/Rfc5987UtilTest.java b/core/src/test/java/org/apache/cxf/attachment/Rfc5987UtilTest.java
new file mode 100644
index 0000000..17f04f05
--- /dev/null
+++ b/core/src/test/java/org/apache/cxf/attachment/Rfc5987UtilTest.java
@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.attachment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class Rfc5987UtilTest {
+ private final String input;
+ private final String expected;
+
+ public Rfc5987UtilTest(String input, String expected) {
+ this.input = input;
+ this.expected = expected;
+ }
+
+ @Parameterized.Parameters
+ public static List<Object[]> params() throws Exception {
+ List<Object[]> params = new ArrayList<>();
+ params.add(new Object[] {"foo-ä-€.html", "foo-%c3%a4-%e2%82%ac.html"});
+ params.add(new Object[]{"世界ーファイル 2.jpg",
+ "%e4%b8%96%e7%95%8c%e3%83%bc%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab%202.jpg"});
+ params.add(new Object[]{"foo.jpg", "foo.jpg"});
+ return params;
+ }
+
+ @Test
+ public void test() throws Exception {
+ assertEquals(expected, Rfc5987Util.encode(input, "UTF-8"));
+
+ assertEquals(input, Rfc5987Util.decode(expected, "UTF-8"));
+ }
+}