You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@chemistry.apache.org by fg...@apache.org on 2011/02/10 15:37:18 UTC
svn commit: r1069413 - in /incubator/chemistry/opencmis/trunk:
chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/
chemistry-opencmis-commons/chemistry-opencmis-commons-i...
Author: fguillaume
Date: Thu Feb 10 14:37:17 2011
New Revision: 1069413
URL: http://svn.apache.org/viewvc?rev=1069413&view=rev
Log:
CMIS-301: pass filename for setContentStream PUT using Content-Disposition header
Added:
incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java (with props)
incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java (with props)
Modified:
incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/AbstractAtomPubService.java
incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/HttpUtils.java
incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/ObjectServiceImpl.java
incubator/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/atompub/ObjectService.java
incubator/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-fit/src/test/java/org/apache/chemistry/opencmis/fit/runtime/AbstractWriteObjectIT.java
Modified: incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/AbstractAtomPubService.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/AbstractAtomPubService.java?rev=1069413&r1=1069412&r2=1069413&view=diff
==============================================================================
--- incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/AbstractAtomPubService.java (original)
+++ incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/AbstractAtomPubService.java Thu Feb 10 14:37:17 2011
@@ -504,8 +504,16 @@ public class AbstractAtomPubService {
* result.
*/
protected HttpUtils.Response put(UrlBuilder url, String contentType, HttpUtils.Output writer) {
+ return put(url, contentType, null, writer);
+ }
+
+ /**
+ * Performs a PUT on an URL, checks the response code and returns the
+ * result.
+ */
+ protected HttpUtils.Response put(UrlBuilder url, String contentType, Map<String, String> headers, HttpUtils.Output writer) {
// make the call
- HttpUtils.Response resp = HttpUtils.invokePUT(url, contentType, writer, session);
+ HttpUtils.Response resp = HttpUtils.invokePUT(url, contentType, headers, writer, session);
// check response code
if ((resp.getResponseCode() < 200) || (resp.getResponseCode() > 299)) {
Modified: incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/HttpUtils.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/HttpUtils.java?rev=1069413&r1=1069412&r2=1069413&view=diff
==============================================================================
--- incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/HttpUtils.java (original)
+++ incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/HttpUtils.java Thu Feb 10 14:37:17 2011
@@ -57,27 +57,28 @@ public class HttpUtils {
}
public static Response invokeGET(UrlBuilder url, Session session) {
- return invoke(url, "GET", null, null, session, null, null);
+ return invoke(url, "GET", null, null, null, session, null, null);
}
public static Response invokeGET(UrlBuilder url, Session session, BigInteger offset, BigInteger length) {
- return invoke(url, "GET", null, null, session, offset, length);
+ return invoke(url, "GET", null, null, null, session, offset, length);
}
public static Response invokePOST(UrlBuilder url, String contentType, Output writer, Session session) {
- return invoke(url, "POST", contentType, writer, session, null, null);
+ return invoke(url, "POST", contentType, null, writer, session, null, null);
}
- public static Response invokePUT(UrlBuilder url, String contentType, Output writer, Session session) {
- return invoke(url, "PUT", contentType, writer, session, null, null);
+ public static Response invokePUT(UrlBuilder url, String contentType, Map<String, String> headers, Output writer, Session session) {
+ return invoke(url, "PUT", contentType, headers, writer, session, null, null);
}
public static Response invokeDELETE(UrlBuilder url, Session session) {
- return invoke(url, "DELETE", null, null, session, null, null);
+ return invoke(url, "DELETE", null, null, null, session, null, null);
}
- private static Response invoke(UrlBuilder url, String method, String contentType, Output writer, Session session,
- BigInteger offset, BigInteger length) {
+ private static Response invoke(UrlBuilder url, String method,
+ String contentType, Map<String, String> headers, Output writer,
+ Session session, BigInteger offset, BigInteger length) {
try {
// log before connect
if (log.isDebugEnabled()) {
@@ -95,6 +96,12 @@ public class HttpUtils {
if (contentType != null) {
conn.setRequestProperty("Content-Type", contentType);
}
+ // set other headers
+ if (headers != null) {
+ for (Map.Entry<String, String> header : headers.entrySet()) {
+ conn.setRequestProperty(header.getKey(), header.getValue());
+ }
+ }
// authenticate
AbstractAuthenticationProvider authProvider = CmisBindingsHelper.getAuthenticationProvider(session);
Modified: incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/ObjectServiceImpl.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/ObjectServiceImpl.java?rev=1069413&r1=1069412&r2=1069413&view=diff
==============================================================================
--- incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/ObjectServiceImpl.java (original)
+++ incubator/chemistry/opencmis/trunk/chemistry-opencmis-client/chemistry-opencmis-client-bindings/src/main/java/org/apache/chemistry/opencmis/client/bindings/spi/atompub/ObjectServiceImpl.java Thu Feb 10 14:37:17 2011
@@ -26,6 +26,7 @@ import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import org.apache.chemistry.opencmis.client.bindings.spi.Session;
import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomAllowableActions;
@@ -51,6 +52,7 @@ import org.apache.chemistry.opencmis.com
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
import org.apache.chemistry.opencmis.commons.impl.Constants;
+import org.apache.chemistry.opencmis.commons.impl.MimeHelper;
import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
@@ -523,8 +525,15 @@ public class ObjectServiceImpl extends A
final InputStream stream = contentStream.getStream();
+ // Content-Disposition header for the filename
+ Map<String, String> headers = Collections.singletonMap(
+ MimeHelper.CONTENT_DISPOSITION,
+ MimeHelper.encodeContentDisposition(
+ MimeHelper.DISPOSITION_ATTACHMENT,
+ contentStream.getFileName()));
+
// send content
- HttpUtils.Response resp = HttpUtils.invokePUT(url, contentStream.getMimeType(), new HttpUtils.Output() {
+ HttpUtils.Response resp = put(url, contentStream.getMimeType(), headers, new HttpUtils.Output() {
public void write(OutputStream out) throws Exception {
int b;
byte[] buffer = new byte[4096];
@@ -535,9 +544,9 @@ public class ObjectServiceImpl extends A
stream.close();
}
- }, getSession());
+ });
- // check response code
+ // check response code further
if ((resp.getResponseCode() != 200) && (resp.getResponseCode() != 201) && (resp.getResponseCode() != 204)) {
throw convertStatusCode(resp.getResponseCode(), resp.getResponseMessage(), resp.getErrorContent(), null);
}
Added: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java?rev=1069413&view=auto
==============================================================================
--- incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java (added)
+++ incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java Thu Feb 10 14:37:17 2011
@@ -0,0 +1,560 @@
+/*
+ * 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.
+ *
+ * Contributors:
+ * Original contributors from geronimo-javamail_1.4_spec-1.7.1
+ * Florent Guillaume
+ */
+package org.apache.chemistry.opencmis.commons.impl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * MIME helper class.
+ */
+public class MimeHelper {
+
+ public static final String CONTENT_DISPOSITION = "Content-Disposition";
+
+ public static final String DISPOSITION_ATTACHMENT = "attachment";
+
+ public static final String DISPOSITION_FILENAME = "filename";
+
+ // RFC 2045
+ private static final String MIME_SPECIALS = "()<>@,;:\\\"/[]?=" + "\t ";
+
+ private static final String RFC2231_SPECIALS = "*'%" + MIME_SPECIALS;
+
+ private static final String WHITE = " \t\n\r";
+
+ private static final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
+
+ private static final byte[] HEX_DECODE = new byte[0x80];
+ static {
+ for (int i = 0; i < HEX_DIGITS.length; i++) {
+ HEX_DECODE[HEX_DIGITS[i]] = (byte) i;
+ }
+ }
+
+ private MimeHelper() {
+ }
+
+ /**
+ * Encodes a value per RFC 2231.
+ * <p>
+ * This is used to pass non-ASCII parameters to MIME parameter lists.
+ * <p>
+ * This implementation always uses UTF-8 and no language.
+ * <p>
+ * See <a href="http://tools.ietf.org/html/rfc2231">RFC 2231</a> for
+ * details.
+ *
+ * @param value the value to encode
+ * @param buf the buffer to fill
+ * @return {@code true} if an encoding was needed, or {@code false} if no
+ * encoding was actually needed
+ */
+ protected static boolean encodeRFC2231value(String value, StringBuilder buf) {
+ String charset = "UTF-8";
+ buf.append(charset);
+ buf.append("''"); // no language
+ byte[] bytes;
+ try {
+ bytes = value.getBytes(charset);
+ } catch (UnsupportedEncodingException e) {
+ return true;
+ }
+ boolean encoded = false;
+ for (int i = 0; i < bytes.length; i++) {
+ int ch = bytes[i] & 0xff;
+ if (ch <= 32 || ch >= 127 || RFC2231_SPECIALS.indexOf(ch) != -1) {
+ buf.append('%');
+ buf.append(HEX_DIGITS[ch >> 4]);
+ buf.append(HEX_DIGITS[ch & 0xf]);
+ encoded = true;
+ } else {
+ buf.append((char) ch);
+ }
+ }
+ return encoded;
+ }
+
+ /**
+ * Encodes a MIME parameter per RFC 2231.
+ * <p>
+ * This implementation always uses UTF-8 and no language.
+ * <p>
+ * See <a href="http://tools.ietf.org/html/rfc2231">RFC 2231</a> for
+ * details.
+ *
+ * @param value the string to encode
+ * @return the encoded string
+ */
+ protected static String encodeRFC2231(String key, String value) {
+ StringBuilder buf = new StringBuilder();
+ boolean encoded = encodeRFC2231value(value, buf);
+ if (encoded) {
+ return "; " + key + "*=" + buf.toString();
+ } else {
+ return "; " + key + "=" + value;
+ }
+ }
+
+ /**
+ * Encodes the Content-Disposition header value according to RFC 2183 and
+ * RFC 2231.
+ * <p>
+ * See <a href="http://tools.ietf.org/html/rfc2231">RFC 2231</a> for
+ * details.
+ *
+ * @param disposition the disposition
+ * @param filename the file name
+ * @return the encoded header value
+ */
+ public static String encodeContentDisposition(String disposition,
+ String filename) {
+ if (disposition == null) {
+ disposition = DISPOSITION_ATTACHMENT;
+ }
+ return disposition + encodeRFC2231(DISPOSITION_FILENAME, filename);
+ }
+
+ /**
+ * Decodes a filename from the Content-Disposition header value according to
+ * RFC 2183 and RFC 2231.
+ * <p>
+ * See <a href="http://tools.ietf.org/html/rfc2231">RFC 2231</a> for
+ * details.
+ *
+ * @param value the header value to decode
+ * @return the filename
+ */
+ public static String decodeContentDispositionFilename(String value) {
+ Map<String, String> params = new HashMap<String, String>();
+ decodeContentDisposition(value, params);
+ return params.get(DISPOSITION_FILENAME);
+ }
+
+ /**
+ * Decodes the Content-Disposition header value according to RFC 2183 and
+ * RFC 2231.
+ * <p>
+ * Does not deal with continuation lines.
+ * <p>
+ * See <a href="http://tools.ietf.org/html/rfc2231">RFC 2231</a> for
+ * details.
+ *
+ * @param value the header value to decode
+ * @param params the map of parameters to fill
+ * @return the disposition
+ *
+ */
+ public static String decodeContentDisposition(String value,
+ Map<String, String> params) {
+ try {
+ HeaderTokenizer tokenizer = new HeaderTokenizer(value);
+ // get the first token, which must be an ATOM
+ Token token = tokenizer.next();
+ if (token.getType() != Token.ATOM) {
+ return null;
+ }
+ String disposition = token.getValue();
+ // value ignored in this method
+
+ // the remainder is the parameters
+ String remainder = tokenizer.getRemainder();
+ if (remainder != null) {
+ getParameters(remainder, params);
+ }
+ return disposition;
+ } catch (ParseException e) {
+ return null;
+ }
+ }
+
+ protected static class ParseException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public ParseException() {
+ super();
+ }
+
+ public ParseException(String message) {
+ super(message);
+ }
+ }
+
+ /*
+ * From geronimo-javamail_1.4_spec-1.7.1. Token
+ */
+ protected static class Token {
+ // Constant values from J2SE 1.4 API Docs (Constant values)
+ public static final int ATOM = -1;
+
+ public static final int COMMENT = -3;
+
+ public static final int EOF = -4;
+
+ public static final int QUOTEDSTRING = -2;
+
+ private int _type;
+
+ private String _value;
+
+ public Token(int type, String value) {
+ _type = type;
+ _value = value;
+ }
+
+ public int getType() {
+ return _type;
+ }
+
+ public String getValue() {
+ return _value;
+ }
+ }
+
+ /*
+ * Tweaked from geronimo-javamail_1.4_spec-1.7.1. HeaderTokenizer
+ */
+ protected static class HeaderTokenizer {
+
+ private static final Token EOF = new Token(Token.EOF, null);
+
+ private final String header;
+
+ private final String delimiters;
+
+ private final boolean skipComments;
+
+ private int pos;
+
+ public HeaderTokenizer(String header) {
+ this(header, MIME_SPECIALS, true);
+ }
+
+ protected HeaderTokenizer(String header, String delimiters,
+ boolean skipComments) {
+ this.header = header;
+ this.delimiters = delimiters;
+ this.skipComments = skipComments;
+ }
+
+ public String getRemainder() {
+ return header.substring(pos);
+ }
+
+ public Token next() throws ParseException {
+ return readToken();
+ }
+
+ /**
+ * Read an ATOM token from the parsed header.
+ *
+ * @return A token containing the value of the atom token.
+ */
+ private Token readAtomicToken() {
+ // skip to next delimiter
+ int start = pos;
+ while (++pos < header.length()) {
+ // break on the first non-atom character.
+ char ch = header.charAt(pos);
+ if (delimiters.indexOf(header.charAt(pos)) != -1 || ch < 32
+ || ch >= 127) {
+ break;
+ }
+ }
+ return new Token(Token.ATOM, header.substring(start, pos));
+ }
+
+ /**
+ * Read the next token from the header.
+ *
+ * @return The next token from the header. White space is skipped, and
+ * comment tokens are also skipped if indicated.
+ */
+ private Token readToken() throws ParseException {
+ if (pos >= header.length()) {
+ return EOF;
+ } else {
+ char c = header.charAt(pos);
+ // comment token...read and skip over this
+ if (c == '(') {
+ Token comment = readComment();
+ if (skipComments) {
+ return readToken();
+ } else {
+ return comment;
+ }
+ // quoted literal
+ } else if (c == '\"') {
+ return readQuotedString();
+ // white space, eat this and find a real token.
+ } else if (WHITE.indexOf(c) != -1) {
+ eatWhiteSpace();
+ return readToken();
+ // either a CTL or special. These characters have a
+ // self-defining token type.
+ } else if (c < 32 || c >= 127 || delimiters.indexOf(c) != -1) {
+ pos++;
+ return new Token((int) c, String.valueOf(c));
+ } else {
+ // start of an atom, parse it off.
+ return readAtomicToken();
+ }
+ }
+ }
+
+ /**
+ * Extract a substring from the header string and apply any
+ * escaping/folding rules to the string.
+ *
+ * @param start The starting offset in the header.
+ * @param end The header end offset + 1.
+ * @return The processed string value.
+ */
+ private String getEscapedValue(int start, int end)
+ throws ParseException {
+ StringBuffer value = new StringBuffer();
+ for (int i = start; i < end; i++) {
+ char ch = header.charAt(i);
+ // is this an escape character?
+ if (ch == '\\') {
+ i++;
+ if (i == end) {
+ throw new ParseException("Invalid escape character");
+ }
+ value.append(header.charAt(i));
+ }
+ // line breaks are ignored, except for naked '\n' characters,
+ // which are consider
+ // parts of linear whitespace.
+ else if (ch == '\r') {
+ // see if this is a CRLF sequence, and skip the second if it
+ // is.
+ if (i < end - 1 && header.charAt(i + 1) == '\n') {
+ i++;
+ }
+ } else {
+ // just append the ch value.
+ value.append(ch);
+ }
+ }
+ return value.toString();
+ }
+
+ /**
+ * Read a comment from the header, applying nesting and escape rules to
+ * the content.
+ *
+ * @return A comment token with the token value.
+ */
+ private Token readComment() throws ParseException {
+ int start = pos + 1;
+ int nesting = 1;
+ boolean requiresEscaping = false;
+ // skip to end of comment/string
+ while (++pos < header.length()) {
+ char ch = header.charAt(pos);
+ if (ch == ')') {
+ nesting--;
+ if (nesting == 0) {
+ break;
+ }
+ } else if (ch == '(') {
+ nesting++;
+ } else if (ch == '\\') {
+ pos++;
+ requiresEscaping = true;
+ }
+ // we need to process line breaks also
+ else if (ch == '\r') {
+ requiresEscaping = true;
+ }
+ }
+ if (nesting != 0) {
+ throw new ParseException("Unbalanced comments");
+ }
+ String value;
+ if (requiresEscaping) {
+ value = getEscapedValue(start, pos);
+ } else {
+ value = header.substring(start, pos++);
+ }
+ return new Token(Token.COMMENT, value);
+ }
+
+ /**
+ * Parse out a quoted string from the header, applying escaping rules to
+ * the value.
+ *
+ * @return The QUOTEDSTRING token with the value.
+ * @exception ParseException
+ */
+ private Token readQuotedString() throws ParseException {
+ int start = pos + 1;
+ boolean requiresEscaping = false;
+ // skip to end of comment/string
+ while (++pos < header.length()) {
+ char ch = header.charAt(pos);
+ if (ch == '"') {
+ String value;
+ if (requiresEscaping) {
+ value = getEscapedValue(start, pos++);
+ } else {
+ value = header.substring(start, pos++);
+ }
+ return new Token(Token.QUOTEDSTRING, value);
+ } else if (ch == '\\') {
+ pos++;
+ requiresEscaping = true;
+ }
+ // we need to process line breaks also
+ else if (ch == '\r') {
+ requiresEscaping = true;
+ }
+ }
+ throw new ParseException("Missing '\"'");
+ }
+
+ /**
+ * Skip white space in the token string.
+ */
+ private void eatWhiteSpace() {
+ // skip to end of whitespace
+ while (++pos < header.length()
+ && WHITE.indexOf(header.charAt(pos)) != -1)
+ ;
+ }
+ }
+
+ /*
+ * Tweaked from geronimo-javamail_1.4_spec-1.7.1. ParameterList
+ */
+ protected static Map<String, String> getParameters(String list,
+ Map<String, String> params) throws ParseException {
+ HeaderTokenizer tokenizer = new HeaderTokenizer(list);
+ while (true) {
+ Token token = tokenizer.next();
+ switch (token.getType()) {
+ case Token.EOF:
+ // the EOF token terminates parsing.
+ return params;
+
+ case ';':
+ // each new parameter is separated by a semicolon, including
+ // the first, which separates
+ // the parameters from the main part of the header.
+ // the next token needs to be a parameter name
+ token = tokenizer.next();
+ // allow a trailing semicolon on the parameters.
+ if (token.getType() == Token.EOF) {
+ return params;
+ }
+
+ if (token.getType() != Token.ATOM) {
+ throw new ParseException("Invalid parameter name: "
+ + token.getValue());
+ }
+
+ // get the parameter name as a lower case version for better
+ // mapping.
+ String name = token.getValue().toLowerCase();
+
+ token = tokenizer.next();
+
+ // parameters are name=value, so we must have the "=" here.
+ if (token.getType() != '=') {
+ throw new ParseException("Missing '='");
+ }
+
+ // now the value, which may be an atom or a literal
+ token = tokenizer.next();
+
+ if (token.getType() != Token.ATOM
+ && token.getType() != Token.QUOTEDSTRING) {
+ throw new ParseException("Invalid parameter value: "
+ + token.getValue());
+ }
+
+ String value = token.getValue();
+
+ // we might have to do some additional decoding. A name that
+ // ends with "*" is marked as being encoded, so if requested, we
+ // decode the value.
+ if (name.endsWith("*")) {
+ name = name.substring(0, name.length() - 1);
+ value = decodeRFC2231value(value);
+ }
+ params.put(name, value);
+ break;
+ default:
+ throw new ParseException("Missing ';'");
+ }
+ }
+ }
+
+ protected static String decodeRFC2231value(String value) {
+ int q1 = value.indexOf('\'');
+ if (q1 == -1) {
+ // missing charset
+ return value;
+ }
+ String mimeCharset = value.substring(0, q1);
+ int q2 = value.indexOf('\'', q1 + 1);
+ if (q2 == -1) {
+ // missing language
+ return value;
+ }
+ byte[] bytes = fromHex(value.substring(q2 + 1));
+ try {
+ return new String(bytes, getJavaCharset(mimeCharset));
+ } catch (UnsupportedEncodingException e) {
+ // incorrect encoding
+ return value;
+ }
+ }
+
+ protected static byte[] fromHex(String data) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ for (int i = 0; i < data.length();) {
+ char c = data.charAt(i++);
+ if (c == '%') {
+ if (i > data.length() - 2) {
+ break; // unterminated sequence
+ }
+ byte b1 = HEX_DECODE[data.charAt(i++) & 0x7f];
+ byte b2 = HEX_DECODE[data.charAt(i++) & 0x7f];
+ out.write((b1 << 4) | b2);
+ } else {
+ out.write((byte) c);
+ }
+ }
+ return out.toByteArray();
+ }
+
+ protected static String getJavaCharset(String mimeCharset) {
+ // good enough for standard values
+ return mimeCharset;
+ }
+
+}
Propchange: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java
------------------------------------------------------------------------------
svn:keywords = Id
Propchange: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/main/java/org/apache/chemistry/opencmis/commons/impl/MimeHelper.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java?rev=1069413&view=auto
==============================================================================
--- incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java (added)
+++ incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java Thu Feb 10 14:37:17 2011
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ *
+ * Contributors:
+ * Florent Guillaume
+ */
+package org.apache.chemistry.opencmis.commons.impl.misc;
+
+import static org.apache.chemistry.opencmis.commons.impl.MimeHelper.decodeContentDisposition;
+import static org.apache.chemistry.opencmis.commons.impl.MimeHelper.decodeContentDispositionFilename;
+import static org.apache.chemistry.opencmis.commons.impl.MimeHelper.encodeContentDisposition;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+public class MimeHelperTest extends TestCase {
+
+ public void testEncodeContentDisposition() {
+ assertEquals("inline; filename=foo.bar",
+ encodeContentDisposition("inline", "foo.bar"));
+ assertEquals("attachment; filename=foo.bar",
+ encodeContentDisposition(null, "foo.bar"));
+ assertEquals("attachment; filename*=UTF-8''caf%C3%A9.pdf",
+ encodeContentDisposition(null, "caf\u00e9.pdf"));
+ assertEquals(
+ "attachment; filename*=UTF-8''%20%27%2A%25%20abc%20%C2%81%C2%82%0D%0A%09",
+ encodeContentDisposition(null, " '*% abc \u0081\u0082\r\n\t"));
+ }
+
+ public void testDecodeContentDisposition() {
+ Map<String, String> params = new HashMap<String, String>();
+ Map<String, String> expected = new HashMap<String, String>();
+
+ assertEquals("attachment",
+ decodeContentDisposition("attachment; a=b; c=d", params));
+ expected.put("a", "b");
+ expected.put("c", "d");
+ assertEquals(expected, params);
+ params.clear();
+ expected.clear();
+
+ assertEquals(
+ "inline",
+ decodeContentDisposition(
+ " inline ; a = \"b b\" (this is a comment) ; c =d;",
+ params));
+ expected.put("a", "b b");
+ expected.put("c", "d");
+ assertEquals(expected, params);
+ params.clear();
+ expected.clear();
+
+ assertEquals(
+ "inline",
+ decodeContentDisposition(
+ "inline; modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"",
+ params));
+ assertEquals(Collections.singletonMap("modification-date",
+ "Wed, 12 Feb 1997 16:29:51 -0500"), params);
+ params.clear();
+ }
+
+ public void testDecodeContentDispositionFilename() {
+ assertNull(decodeContentDispositionFilename("attachment; a=b; c=d;"));
+ assertNull(decodeContentDispositionFilename("inline"));
+ assertNull(decodeContentDispositionFilename("inline; modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\""));
+ assertEquals(
+ "foo.bar",
+ decodeContentDispositionFilename("attachment; filename=foo.bar"));
+ assertEquals(
+ "foo.bar",
+ decodeContentDispositionFilename("attachment; filename = \"foo.bar\""));
+ assertEquals(
+ "foo.bar",
+ decodeContentDispositionFilename(" guess ; filename = (this is rfc822 a comment) \"foo.bar\""));
+ assertEquals(
+ "caf\u00e9.pdf",
+ decodeContentDispositionFilename("foo; filename*=UTF-8''caf%C3%A9.pdf"));
+ assertEquals(
+ "caf\u00e9.pdf",
+ decodeContentDispositionFilename("bar; filename*=ISO-8859-1''caf%E9.pdf"));
+ }
+
+}
Propchange: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java
------------------------------------------------------------------------------
svn:keywords = Id
Propchange: incubator/chemistry/opencmis/trunk/chemistry-opencmis-commons/chemistry-opencmis-commons-impl/src/test/java/org/apache/chemistry/opencmis/commons/impl/misc/MimeHelperTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: incubator/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/atompub/ObjectService.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/atompub/ObjectService.java?rev=1069413&r1=1069412&r2=1069413&view=diff
==============================================================================
--- incubator/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/atompub/ObjectService.java (original)
+++ incubator/chemistry/opencmis/trunk/chemistry-opencmis-server/chemistry-opencmis-server-bindings/src/main/java/org/apache/chemistry/opencmis/server/impl/atompub/ObjectService.java Thu Feb 10 14:37:17 2011
@@ -51,6 +51,7 @@ import org.apache.chemistry.opencmis.com
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.impl.Constants;
+import org.apache.chemistry.opencmis.commons.impl.MimeHelper;
import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
@@ -213,6 +214,10 @@ public final class ObjectService {
} catch (NumberFormatException e) {
}
}
+ String contentDisposition = request.getHeader(MimeHelper.CONTENT_DISPOSITION);
+ if (contentDisposition != null) {
+ contentStream.setFileName(MimeHelper.decodeContentDispositionFilename(contentDisposition));
+ }
// execute
Holder<String> objectIdHolder = new Holder<String>(objectId);
Modified: incubator/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-fit/src/test/java/org/apache/chemistry/opencmis/fit/runtime/AbstractWriteObjectIT.java
URL: http://svn.apache.org/viewvc/incubator/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-fit/src/test/java/org/apache/chemistry/opencmis/fit/runtime/AbstractWriteObjectIT.java?rev=1069413&r1=1069412&r2=1069413&view=diff
==============================================================================
--- incubator/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-fit/src/test/java/org/apache/chemistry/opencmis/fit/runtime/AbstractWriteObjectIT.java (original)
+++ incubator/chemistry/opencmis/trunk/chemistry-opencmis-test/chemistry-opencmis-test-fit/src/test/java/org/apache/chemistry/opencmis/fit/runtime/AbstractWriteObjectIT.java Thu Feb 10 14:37:17 2011
@@ -20,11 +20,8 @@ package org.apache.chemistry.opencmis.fi
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
@@ -33,15 +30,12 @@ import java.util.UUID;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.ObjectId;
-import org.apache.chemistry.opencmis.client.api.OperationContext;
import org.apache.chemistry.opencmis.client.api.Property;
-import org.apache.chemistry.opencmis.client.api.TransientDocument;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisNotSupportedException;
-import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.junit.Ignore;
import org.junit.Test;
@@ -301,9 +295,10 @@ public abstract class AbstractWriteObjec
assertNotNull(doc);
doc.setContentStream(contentStream, true);
- // Assert.assertEquals(buf1.length, doc.getContentStreamLength());
- // Assert.assertEquals(mimetype, doc.getContentStreamMimeType());
- // Assert.assertEquals(filename, doc.getContentStreamFileName());
+ doc.refresh();
+ assertEquals(buf1.length, doc.getContentStreamLength());
+ assertEquals(mimetype, doc.getContentStreamMimeType());
+ assertEquals(filename, doc.getContentStreamFileName());
String content2 = this.getContentAsString(doc.getContentStream());
assertEquals(content1, content2);
}
@@ -337,9 +332,10 @@ public abstract class AbstractWriteObjec
assertNotNull(doc);
doc.setContentStream(contentStream, false);
- // Assert.assertEquals(buf1.length, doc.getContentStreamLength());
- // Assert.assertEquals(mimetype, doc.getContentStreamMimeType());
- // Assert.assertEquals(filename, doc.getContentStreamFileName());
+ doc.refresh();
+ assertEquals(buf1.length, doc.getContentStreamLength());
+ assertEquals(mimetype, doc.getContentStreamMimeType());
+ assertEquals(filename, doc.getContentStreamFileName());
String content2 = this.getContentAsString(doc.getContentStream());
assertEquals(content1, content2);
}
@@ -381,9 +377,10 @@ public abstract class AbstractWriteObjec
assertNotNull(doc);
doc.setContentStream(contentStream2, true);
- // Assert.assertEquals(buf1.length, doc.getContentStreamLength());
- // Assert.assertEquals(mimetype, doc.getContentStreamMimeType());
- // Assert.assertEquals(filename, doc.getContentStreamFileName());
+ doc.refresh();
+ assertEquals(buf2.length, doc.getContentStreamLength());
+ assertEquals(mimetype, doc.getContentStreamMimeType());
+ assertEquals(filename2, doc.getContentStreamFileName());
String content3 = this.getContentAsString(doc.getContentStream());
assertEquals(content2, content3);
}
@@ -426,9 +423,10 @@ public abstract class AbstractWriteObjec
assertNotNull(doc);
doc.setContentStream(contentStream2, false);
- // Assert.assertEquals(buf1.length, doc.getContentStreamLength());
- // Assert.assertEquals(mimetype, doc.getContentStreamMimeType());
- // Assert.assertEquals(filename, doc.getContentStreamFileName());
+ doc.refresh();
+ assertEquals(buf2.length, doc.getContentStreamLength());
+ assertEquals(mimetype, doc.getContentStreamMimeType());
+ assertEquals(filename2, doc.getContentStreamFileName());
String content3 = this.getContentAsString(doc.getContentStream());
assertEquals(content2, content3);
}