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.isChrome;
025import static org.nuxeo.common.utils.UserAgentMatcher.isFirefox3;
026import static org.nuxeo.common.utils.UserAgentMatcher.isFirefox4OrMore;
027import static org.nuxeo.common.utils.UserAgentMatcher.isMSIE6or7;
028
029import java.io.UnsupportedEncodingException;
030
031/**
032 * RFC-2231 specifies how a MIME parameter value, like {@code Content-Disposition}'s {@code filename}, can be encoded to
033 * contain arbitrary character sets.
034 *
035 * @author Florent Guillaume
036 */
037public class RFC2231 {
038
039    private static final String UTF8 = "UTF-8";
040
041    private static final byte[] UNKNOWN_BYTES = { '?' };
042
043    // Utility class
044    private RFC2231() {
045    }
046
047    /**
048     * Does a simple %-escaping of the UTF-8 bytes of the value. Keep only some know safe characters.
049     *
050     * @param buf the buffer to which escaped chars are appended
051     * @param value the value to escape
052     */
053    public static void percentEscape(StringBuilder buf, String value) {
054        byte[] bytes;
055        try {
056            bytes = value.getBytes(UTF8);
057        } catch (UnsupportedEncodingException e) {
058            // cannot happen with UTF-8
059            bytes = UNKNOWN_BYTES;
060        }
061        for (byte b : bytes) {
062            if (b < '+' || b == ';' || b == ',' || b == '\\' || b > 'z') {
063                buf.append('%');
064                String s = Integer.toHexString(b & 0xff).toUpperCase();
065                if (s.length() < 2) {
066                    buf.append('0');
067                }
068                buf.append(s);
069            } else {
070                buf.append((char) b);
071            }
072        }
073    }
074
075    /**
076     * Encodes a {@code Content-Disposition} header. For some user agents the full RFC-2231 encoding won't be performed
077     * as they don't understand it.
078     *
079     * @param filename the filename
080     * @param inline {@code true} for an inline disposition, {@code false} for an attachment
081     * @param userAgent the userAgent
082     * @return a full string to set as value of a {@code Content-Disposition} header
083     */
084    public static String encodeContentDisposition(String filename, boolean inline, String userAgent) {
085        StringBuilder buf = new StringBuilder(inline ? "inline; " : "attachment; ");
086        if (userAgent == null) {
087            userAgent = "";
088        }
089        if (isFirefox3(userAgent) || isFirefox4OrMore(userAgent) || isChrome(userAgent)
090                || UserAgentMatcher.isMSIE10OrMore(userAgent)) {
091            // proper RFC2231
092            buf.append("filename*=UTF-8''");
093            percentEscape(buf, filename);
094        } else {
095            buf.append("filename=");
096            if (isMSIE6or7(userAgent)) {
097                // MSIE understands straight %-encoding
098                percentEscape(buf, filename);
099            } else {
100                // Safari (maybe others) expects direct UTF-8 encoded strings
101                buf.append(filename);
102            }
103        }
104        return buf.toString();
105    }
106
107}