001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.http.classic.header; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020import static org.apache.juneau.commons.utils.StringUtils.*; 021 022import java.util.function.*; 023 024import org.apache.juneau.commons.collections.*; 025import org.apache.juneau.commons.http.*; 026import org.apache.juneau.http.*; 027 028/** 029 * Represents a parsed <l>Content-Disposition</l> HTTP request header. 030 * 031 * <p> 032 * In a regular HTTP response, the Content-Disposition response header is a header indicating if the content is expected 033 * to be displayed inline in the browser, that is, as a Web page or as part of a Web page, or as an attachment, that is 034 * downloaded and saved locally. 035 * 036 * <h5 class='figure'>Example</h5> 037 * <p class='bcode'> 038 * Content-Disposition: form-data; name="fieldName"; filename="filename.jpg" 039 * </p> 040 * 041 * <h5 class='topic'>RFC2616 Specification</h5> 042 * 043 * The Expect request-header field is used to indicate that particular server behaviors are required by the client. 044 * <p class='bcode'> 045 * content-disposition = "Content-Disposition" ":" 046 * disposition-type *( ";" disposition-parm ) 047 * disposition-type = "attachment" | disp-extension-token 048 * disposition-parm = filename-parm | disp-extension-parm 049 * filename-parm = "filename" "=" quoted-string 050 * disp-extension-token = token 051 * disp-extension-parm = token "=" ( token | quoted-string ) 052 * </p> 053 * 054 * <h5 class='section'>See Also:</h5><ul> 055 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 056 * <li class='extlink'><a class="doclink" href="https://www.w3.org/Protocols/rfc2616/rfc2616.html">Hypertext Transfer Protocol -- HTTP/1.1</a> 057 * </ul> 058 * 059 * @serial exclude 060 */ 061@Header("Content-Disposition") 062public class ContentDisposition extends BasicStringRangesHeader { 063 private static final long serialVersionUID = 1L; 064 private static final String NAME = "Content-Disposition"; 065 066 private static final Cache<String,ContentDisposition> CACHE = Cache.of(String.class, ContentDisposition.class).build(); 067 068 /** 069 * Creates a <l>Content-Disposition</l> header for a response attachment (browser download / save-as). 070 * 071 * <p> 072 * Produces <code>attachment; filename="..."</code> with the filename escaped for use inside an HTTP quoted-string 073 * ({@code \} and {@code "} backslash-escaped). CR and LF are rejected to reduce response-splitting risk. 074 * </p> 075 * 076 * <p> 077 * For non-ASCII filenames, RFC 5987 {@code filename*} is not set; callers needing full Unicode support may build 078 * a header value manually or extend this API later. 079 * </p> 080 * 081 * @param filename Suggested download name. Must not be <jk>null</jk>, blank, or contain CR/LF. 082 * @return A non-<jk>null</jk> header. 083 * @throws IllegalArgumentException If {@code filename} is null, blank, or contains line breaks. 084 */ 085 public static ContentDisposition attachment(String filename) { 086 if (filename == null || isBlank(filename)) 087 throw iaex("Attachment filename must not be null or blank."); 088 for (var i = 0; i < filename.length(); i++) { 089 var c = filename.charAt(i); 090 if (c == '\r' || c == '\n') 091 throw iaex("Attachment filename must not contain CR or LF characters."); 092 } 093 var escaped = escapeFilenameForQuotedString(filename); 094 return ContentDisposition.of("attachment; filename=\"" + escaped + "\""); 095 } 096 097 private static String escapeFilenameForQuotedString(String filename) { 098 var sb = new StringBuilder(filename.length() + 8); 099 for (var i = 0; i < filename.length(); i++) { 100 var c = filename.charAt(i); 101 if (c == '\\' || c == '"') 102 sb.append('\\'); 103 sb.append(c); 104 } 105 return sb.toString(); 106 } 107 108 /** 109 * Static creator. 110 * 111 * @param value 112 * The header value. 113 * <br>Must be parsable by {@link StringRanges#of(String)}. 114 * <br>Can be <jk>null</jk>. 115 * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>. 116 */ 117 public static ContentDisposition of(String value) { 118 return value == null ? null : CACHE.get(value, () -> new ContentDisposition(value)); 119 } 120 121 /** 122 * Static creator. 123 * 124 * @param value 125 * The header value. 126 * <br>Can be <jk>null</jk>. 127 * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>. 128 */ 129 public static ContentDisposition of(StringRanges value) { 130 return value == null ? null : new ContentDisposition(value); 131 } 132 133 /** 134 * Static creator with delayed value. 135 * 136 * <p> 137 * Header value is re-evaluated on each call to {@link #getValue()}. 138 * 139 * @param value 140 * The supplier of the header value. 141 * <br>Can be <jk>null</jk>. 142 * @return A new header bean, or <jk>null</jk> if the value is <jk>null</jk>. 143 */ 144 public static ContentDisposition of(Supplier<StringRanges> value) { 145 return value == null ? null : new ContentDisposition(value); 146 } 147 148 /** 149 * Constructor. 150 * 151 * @param value 152 * The header value. 153 * <br>Must be parsable by {@link StringRanges#of(String)}. 154 * <br>Can be <jk>null</jk> to create a header with no value. 155 */ 156 public ContentDisposition(String value) { 157 super(NAME, value); 158 } 159 160 /** 161 * Constructor. 162 * 163 * @param value 164 * The header value. 165 * <br>Can be <jk>null</jk> to create a header with no value. 166 */ 167 public ContentDisposition(StringRanges value) { 168 super(NAME, value); 169 } 170 171 /** 172 * Constructor with delayed value. 173 * 174 * <p> 175 * Header value is re-evaluated on each call to {@link #getValue()}. 176 * 177 * @param value 178 * The supplier of the header value. 179 * <br>Can be <jk>null</jk> to create a header with no value. 180 */ 181 public ContentDisposition(Supplier<StringRanges> value) { 182 super(NAME, value); 183 } 184}