Return-Path: X-Original-To: apmail-cxf-commits-archive@www.apache.org Delivered-To: apmail-cxf-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 3D84C17968 for ; Sun, 8 Feb 2015 18:56:13 +0000 (UTC) Received: (qmail 59565 invoked by uid 500); 8 Feb 2015 18:56:13 -0000 Delivered-To: apmail-cxf-commits-archive@cxf.apache.org Received: (qmail 59506 invoked by uid 500); 8 Feb 2015 18:56:13 -0000 Mailing-List: contact commits-help@cxf.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cxf.apache.org Delivered-To: mailing list commits@cxf.apache.org Received: (qmail 59495 invoked by uid 99); 8 Feb 2015 18:56:13 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Sun, 08 Feb 2015 18:56:13 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id E4A03E0120; Sun, 8 Feb 2015 18:56:12 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: sergeyb@apache.org To: commits@cxf.apache.org Message-Id: <9d7977386dd24966ac4298218252bb3e@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: cxf git commit: [CXF-6244] Support for ContentDisposition UTF8 filename parameters, patch from Mark Ford applied; This closes #52 Date: Sun, 8 Feb 2015 18:56:12 +0000 (UTC) Repository: cxf Updated Branches: refs/heads/3.0.x-fixes 9e20a49c6 -> 79a1d5ae9 [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/79a1d5ae Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/79a1d5ae Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/79a1d5ae Branch: refs/heads/3.0.x-fixes Commit: 79a1d5ae953de1d6e3e55501097a8224ab64948a Parents: 9e20a49 Author: Sergey Beryozkin Authored: Sun Feb 8 18:54:33 2015 +0000 Committer: Sergey Beryozkin Committed: Sun Feb 8 18:55:51 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/79a1d5ae/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 params = new LinkedHashMap(); - + 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 getParameters() { return Collections.unmodifiableMap(params); } - + public String toString() { return value; } http://git-wip-us.apache.org/repos/asf/cxf/blob/79a1d5ae/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/79a1d5ae/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/79a1d5ae/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 params() throws Exception { + List 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")); + } +}