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"));
+    }
+}