1 /* 2 * ==================================================================== 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * ==================================================================== 20 * 21 * This software consists of voluntary contributions made by many 22 * individuals on behalf of the Apache Software Foundation. For more 23 * information on the Apache Software Foundation, please see 24 * <http://www.apache.org/>. 25 * 26 */ 27 28 package ch.boye.httpclientandroidlib.entity; 29 30 import java.io.Serializable; 31 import java.nio.charset.Charset; 32 import java.nio.charset.UnsupportedCharsetException; 33 import java.util.Locale; 34 35 import ch.boye.httpclientandroidlib.Consts; 36 import ch.boye.httpclientandroidlib.Header; 37 import ch.boye.httpclientandroidlib.HeaderElement; 38 import ch.boye.httpclientandroidlib.HttpEntity; 39 import ch.boye.httpclientandroidlib.NameValuePair; 40 import ch.boye.httpclientandroidlib.ParseException; 41 import ch.boye.httpclientandroidlib.annotation.Immutable; 42 import ch.boye.httpclientandroidlib.message.BasicHeaderValueFormatter; 43 import ch.boye.httpclientandroidlib.message.BasicHeaderValueParser; 44 import ch.boye.httpclientandroidlib.message.ParserCursor; 45 import ch.boye.httpclientandroidlib.util.Args; 46 import ch.boye.httpclientandroidlib.util.CharArrayBuffer; 47 import ch.boye.httpclientandroidlib.util.TextUtils; 48 49 /** 50 * Content type information consisting of a MIME type and an optional charset. 51 * <p/> 52 * This class makes no attempts to verify validity of the MIME type. 53 * The input parameters of the {@link #create(String, String)} method, however, may not 54 * contain characters <">, <;>, <,> reserved by the HTTP specification. 55 * 56 * @since 4.2 57 */ 58 @Immutable 59 public final class ContentType implements Serializable { 60 61 private static final long serialVersionUID = -7768694718232371896L; 62 63 // constants 64 public static final ContentType APPLICATION_ATOM_XML = create( 65 "application/atom+xml", Consts.ISO_8859_1); 66 public static final ContentType APPLICATION_FORM_URLENCODED = create( 67 "application/x-www-form-urlencoded", Consts.ISO_8859_1); 68 public static final ContentType APPLICATION_JSON = create( 69 "application/json", Consts.UTF_8); 70 public static final ContentType APPLICATION_OCTET_STREAM = create( 71 "application/octet-stream", (Charset) null); 72 public static final ContentType APPLICATION_SVG_XML = create( 73 "application/svg+xml", Consts.ISO_8859_1); 74 public static final ContentType APPLICATION_XHTML_XML = create( 75 "application/xhtml+xml", Consts.ISO_8859_1); 76 public static final ContentType APPLICATION_XML = create( 77 "application/xml", Consts.ISO_8859_1); 78 public static final ContentType MULTIPART_FORM_DATA = create( 79 "multipart/form-data", Consts.ISO_8859_1); 80 public static final ContentType TEXT_HTML = create( 81 "text/html", Consts.ISO_8859_1); 82 public static final ContentType TEXT_PLAIN = create( 83 "text/plain", Consts.ISO_8859_1); 84 public static final ContentType TEXT_XML = create( 85 "text/xml", Consts.ISO_8859_1); 86 public static final ContentType WILDCARD = create( 87 "*/*", (Charset) null); 88 89 // defaults 90 public static final ContentType DEFAULT_TEXT = TEXT_PLAIN; 91 public static final ContentType DEFAULT_BINARY = APPLICATION_OCTET_STREAM; 92 93 private final String mimeType; 94 private final Charset charset; 95 private final NameValuePair[] params; 96 ContentType( final String mimeType, final Charset charset)97 ContentType( 98 final String mimeType, 99 final Charset charset) { 100 this.mimeType = mimeType; 101 this.charset = charset; 102 this.params = null; 103 } 104 ContentType( final String mimeType, final NameValuePair[] params)105 ContentType( 106 final String mimeType, 107 final NameValuePair[] params) throws UnsupportedCharsetException { 108 this.mimeType = mimeType; 109 this.params = params; 110 final String s = getParameter("charset"); 111 this.charset = !TextUtils.isBlank(s) ? Charset.forName(s) : null; 112 } 113 getMimeType()114 public String getMimeType() { 115 return this.mimeType; 116 } 117 getCharset()118 public Charset getCharset() { 119 return this.charset; 120 } 121 122 /** 123 * @since 4.3 124 */ getParameter(final String name)125 public String getParameter(final String name) { 126 Args.notEmpty(name, "Parameter name"); 127 if (this.params == null) { 128 return null; 129 } 130 for (final NameValuePair param: this.params) { 131 if (param.getName().equalsIgnoreCase(name)) { 132 return param.getValue(); 133 } 134 } 135 return null; 136 } 137 138 /** 139 * Generates textual representation of this content type which can be used as the value 140 * of a <code>Content-Type</code> header. 141 */ 142 @Override toString()143 public String toString() { 144 final CharArrayBuffer buf = new CharArrayBuffer(64); 145 buf.append(this.mimeType); 146 if (this.params != null) { 147 buf.append("; "); 148 BasicHeaderValueFormatter.INSTANCE.formatParameters(buf, this.params, false); 149 } else if (this.charset != null) { 150 buf.append("; charset="); 151 buf.append(this.charset.name()); 152 } 153 return buf.toString(); 154 } 155 valid(final String s)156 private static boolean valid(final String s) { 157 for (int i = 0; i < s.length(); i++) { 158 final char ch = s.charAt(i); 159 if (ch == '"' || ch == ',' || ch == ';') { 160 return false; 161 } 162 } 163 return true; 164 } 165 166 /** 167 * Creates a new instance of {@link ContentType}. 168 * 169 * @param mimeType MIME type. It may not be <code>null</code> or empty. It may not contain 170 * characters <">, <;>, <,> reserved by the HTTP specification. 171 * @param charset charset. 172 * @return content type 173 */ create(final String mimeType, final Charset charset)174 public static ContentType create(final String mimeType, final Charset charset) { 175 final String type = Args.notBlank(mimeType, "MIME type").toLowerCase(Locale.US); 176 Args.check(valid(type), "MIME type may not contain reserved characters"); 177 return new ContentType(type, charset); 178 } 179 180 /** 181 * Creates a new instance of {@link ContentType} without a charset. 182 * 183 * @param mimeType MIME type. It may not be <code>null</code> or empty. It may not contain 184 * characters <">, <;>, <,> reserved by the HTTP specification. 185 * @return content type 186 */ create(final String mimeType)187 public static ContentType create(final String mimeType) { 188 return new ContentType(mimeType, (Charset) null); 189 } 190 191 /** 192 * Creates a new instance of {@link ContentType}. 193 * 194 * @param mimeType MIME type. It may not be <code>null</code> or empty. It may not contain 195 * characters <">, <;>, <,> reserved by the HTTP specification. 196 * @param charset charset. It may not contain characters <">, <;>, <,> reserved by the HTTP 197 * specification. This parameter is optional. 198 * @return content type 199 * @throws UnsupportedCharsetException Thrown when the named charset is not available in 200 * this instance of the Java virtual machine 201 */ create( final String mimeType, final String charset)202 public static ContentType create( 203 final String mimeType, final String charset) throws UnsupportedCharsetException { 204 return create(mimeType, !TextUtils.isBlank(charset) ? Charset.forName(charset) : null); 205 } 206 create(final HeaderElement helem)207 private static ContentType create(final HeaderElement helem) { 208 final String mimeType = helem.getName(); 209 final NameValuePair[] params = helem.getParameters(); 210 return new ContentType(mimeType, params != null && params.length > 0 ? params : null); 211 } 212 213 /** 214 * Parses textual representation of <code>Content-Type</code> value. 215 * 216 * @param s text 217 * @return content type 218 * @throws ParseException if the given text does not represent a valid 219 * <code>Content-Type</code> value. 220 * @throws UnsupportedCharsetException Thrown when the named charset is not available in 221 * this instance of the Java virtual machine 222 */ parse( final String s)223 public static ContentType parse( 224 final String s) throws ParseException, UnsupportedCharsetException { 225 Args.notNull(s, "Content type"); 226 final CharArrayBuffer buf = new CharArrayBuffer(s.length()); 227 buf.append(s); 228 final ParserCursor cursor = new ParserCursor(0, s.length()); 229 final HeaderElement[] elements = BasicHeaderValueParser.INSTANCE.parseElements(buf, cursor); 230 if (elements.length > 0) { 231 return create(elements[0]); 232 } else { 233 throw new ParseException("Invalid content type: " + s); 234 } 235 } 236 237 /** 238 * Extracts <code>Content-Type</code> value from {@link HttpEntity} exactly as 239 * specified by the <code>Content-Type</code> header of the entity. Returns <code>null</code> 240 * if not specified. 241 * 242 * @param entity HTTP entity 243 * @return content type 244 * @throws ParseException if the given text does not represent a valid 245 * <code>Content-Type</code> value. 246 * @throws UnsupportedCharsetException Thrown when the named charset is not available in 247 * this instance of the Java virtual machine 248 */ get( final HttpEntity entity)249 public static ContentType get( 250 final HttpEntity entity) throws ParseException, UnsupportedCharsetException { 251 if (entity == null) { 252 return null; 253 } 254 final Header header = entity.getContentType(); 255 if (header != null) { 256 final HeaderElement[] elements = header.getElements(); 257 if (elements.length > 0) { 258 return create(elements[0]); 259 } 260 } 261 return null; 262 } 263 264 /** 265 * Extracts <code>Content-Type</code> value from {@link HttpEntity} or returns the default value 266 * {@link #DEFAULT_TEXT} if not explicitly specified. 267 * 268 * @param entity HTTP entity 269 * @return content type 270 * @throws ParseException if the given text does not represent a valid 271 * <code>Content-Type</code> value. 272 * @throws UnsupportedCharsetException Thrown when the named charset is not available in 273 * this instance of the Java virtual machine 274 */ getOrDefault( final HttpEntity entity)275 public static ContentType getOrDefault( 276 final HttpEntity entity) throws ParseException, UnsupportedCharsetException { 277 final ContentType contentType = get(entity); 278 return contentType != null ? contentType : DEFAULT_TEXT; 279 } 280 281 /** 282 * Creates a new instance with this MIME type and the given Charset. 283 * 284 * @param charset charset 285 * @return a new instance with this MIME type and the given Charset. 286 * @since 4.3 287 */ withCharset(final Charset charset)288 public ContentType withCharset(final Charset charset) { 289 return create(this.getMimeType(), charset); 290 } 291 292 /** 293 * Creates a new instance with this MIME type and the given Charset name. 294 * 295 * @param charset name 296 * @return a new instance with this MIME type and the given Charset name. 297 * @throws UnsupportedCharsetException Thrown when the named charset is not available in 298 * this instance of the Java virtual machine 299 * @since 4.3 300 */ withCharset(final String charset)301 public ContentType withCharset(final String charset) { 302 return create(this.getMimeType(), charset); 303 } 304 305 } 306