001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 *
019 * $Id$
020 */
021
022package org.nuxeo.common.utils;
023
024import static org.nuxeo.common.utils.UserAgentMatcher.isSafari5;
025import static org.nuxeo.common.utils.UserAgentMatcher.isMSIE6or7;
026
027import java.io.UnsupportedEncodingException;
028
029/**
030 * RFC-2231 specifies how a MIME parameter value, like {@code Content-Disposition}'s {@code filename}, can be encoded to
031 * contain arbitrary character sets.
032 *
033 * @author Florent Guillaume
034 */
035public class RFC2231 {
036
037    private static final String UTF8 = "UTF-8";
038
039    private static final byte[] UNKNOWN_BYTES = { '?' };
040
041    // Utility class
042    private RFC2231() {
043    }
044
045    /**
046     * Does a simple %-escaping of the UTF-8 bytes of the value. Keep only some know safe characters.
047     *
048     * @param buf the buffer to which escaped chars are appended
049     * @param value the value to escape
050     */
051    public static void percentEscape(StringBuilder buf, String value) {
052        byte[] bytes;
053        try {
054            bytes = value.getBytes(UTF8);
055        } catch (UnsupportedEncodingException e) {
056            // cannot happen with UTF-8
057            bytes = UNKNOWN_BYTES;
058        }
059        for (byte b : bytes) {
060            if (b < '+' || b == ';' || b == ',' || b == '\\' || b > 'z') {
061                buf.append('%');
062                String s = Integer.toHexString(b & 0xff).toUpperCase();
063                if (s.length() < 2) {
064                    buf.append('0');
065                }
066                buf.append(s);
067            } else {
068                buf.append((char) b);
069            }
070        }
071    }
072
073    /**
074     * Encodes a {@code Content-Disposition} header. For some user agents the full RFC-2231 encoding won't be performed
075     * as they don't understand it.
076     *
077     * @param filename the filename
078     * @param inline {@code true} for an inline disposition, {@code false} for an attachment
079     * @param userAgent the userAgent
080     * @return a full string to set as value of a {@code Content-Disposition} header
081     */
082    public static String encodeContentDisposition(String filename, boolean inline, String userAgent) {
083        StringBuilder buf = new StringBuilder(inline ? "inline; " : "attachment; ");
084        if (userAgent == null) {
085            userAgent = "";
086        }
087        if (isMSIE6or7(userAgent)) {
088            // MSIE understands straight %-encoding
089            buf.append("filename=");
090            percentEscape(buf, filename);
091        } else {
092            // proper RFC2231
093            buf.append("filename*=UTF-8''");
094            percentEscape(buf, filename);
095        }
096        return buf.toString();
097    }
098
099}