1/* 2 * Copyright (C) 2013 Square, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16package com.squareup.okhttp; 17 18import java.nio.charset.Charset; 19import java.util.Locale; 20import java.util.regex.Matcher; 21import java.util.regex.Pattern; 22 23/** 24 * An <a href="http://tools.ietf.org/html/rfc2045">RFC 2045</a> Media Type, 25 * appropriate to describe the content type of an HTTP request or response body. 26 */ 27public final class MediaType { 28 private static final String TOKEN = "([a-zA-Z0-9-!#$%&'*+.^_`{|}~]+)"; 29 private static final String QUOTED = "\"([^\"]*)\""; 30 private static final Pattern TYPE_SUBTYPE = Pattern.compile(TOKEN + "/" + TOKEN); 31 private static final Pattern PARAMETER = Pattern.compile( 32 ";\\s*" + TOKEN + "=(?:" + TOKEN + "|" + QUOTED + ")"); 33 34 private final String mediaType; 35 private final String type; 36 private final String subtype; 37 private final String charset; 38 39 private MediaType(String mediaType, String type, String subtype, String charset) { 40 this.mediaType = mediaType; 41 this.type = type; 42 this.subtype = subtype; 43 this.charset = charset; 44 } 45 46 /** 47 * Returns a media type for {@code string}, or null if {@code string} is not a 48 * well-formed media type. 49 */ 50 public static MediaType parse(String string) { 51 Matcher typeSubtype = TYPE_SUBTYPE.matcher(string); 52 if (!typeSubtype.lookingAt()) return null; 53 String type = typeSubtype.group(1).toLowerCase(Locale.US); 54 String subtype = typeSubtype.group(2).toLowerCase(Locale.US); 55 56 String charset = null; 57 Matcher parameter = PARAMETER.matcher(string); 58 for (int s = typeSubtype.end(); s < string.length(); s = parameter.end()) { 59 parameter.region(s, string.length()); 60 if (!parameter.lookingAt()) return null; // This is not a well-formed media type. 61 62 String name = parameter.group(1); 63 if (name == null || !name.equalsIgnoreCase("charset")) continue; 64 if (charset != null) throw new IllegalArgumentException("Multiple charsets: " + string); 65 charset = parameter.group(2) != null 66 ? parameter.group(2) // Value is a token. 67 : parameter.group(3); // Value is a quoted string. 68 } 69 70 return new MediaType(string, type, subtype, charset); 71 } 72 73 /** 74 * Returns the high-level media type, such as "text", "image", "audio", 75 * "video", or "application". 76 */ 77 public String type() { 78 return type; 79 } 80 81 /** 82 * Returns a specific media subtype, such as "plain" or "png", "mpeg", 83 * "mp4" or "xml". 84 */ 85 public String subtype() { 86 return subtype; 87 } 88 89 /** 90 * Returns the charset of this media type, or null if this media type doesn't 91 * specify a charset. 92 */ 93 public Charset charset() { 94 return charset != null ? Charset.forName(charset) : null; 95 } 96 97 /** 98 * Returns the charset of this media type, or {@code defaultValue} if this 99 * media type doesn't specify a charset. 100 */ 101 public Charset charset(Charset defaultValue) { 102 return charset != null ? Charset.forName(charset) : defaultValue; 103 } 104 105 /** 106 * Returns the encoded media type, like "text/plain; charset=utf-8", 107 * appropriate for use in a Content-Type header. 108 */ 109 @Override public String toString() { 110 return mediaType; 111 } 112 113 @Override public boolean equals(Object o) { 114 return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType); 115 } 116 117 @Override public int hashCode() { 118 return mediaType.hashCode(); 119 } 120} 121