001/* 002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and contributors. 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}