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 */ 16 package com.squareup.okhttp; 17 18 import java.nio.charset.Charset; 19 import java.util.Locale; 20 import java.util.regex.Matcher; 21 import 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 */ 27 public 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 String charsetParameter = parameter.group(2) != null 65 ? parameter.group(2) // Value is a token. 66 : parameter.group(3); // Value is a quoted string. 67 if (charset != null && !charsetParameter.equalsIgnoreCase(charset)) { 68 throw new IllegalArgumentException("Multiple different charsets: " + string); 69 } 70 charset = charsetParameter; 71 } 72 73 return new MediaType(string, type, subtype, charset); 74 } 75 76 /** 77 * Returns the high-level media type, such as "text", "image", "audio", 78 * "video", or "application". 79 */ 80 public String type() { 81 return type; 82 } 83 84 /** 85 * Returns a specific media subtype, such as "plain" or "png", "mpeg", 86 * "mp4" or "xml". 87 */ 88 public String subtype() { 89 return subtype; 90 } 91 92 /** 93 * Returns the charset of this media type, or null if this media type doesn't 94 * specify a charset. 95 */ 96 public Charset charset() { 97 return charset != null ? Charset.forName(charset) : null; 98 } 99 100 /** 101 * Returns the charset of this media type, or {@code defaultValue} if this 102 * media type doesn't specify a charset. 103 */ 104 public Charset charset(Charset defaultValue) { 105 return charset != null ? Charset.forName(charset) : defaultValue; 106 } 107 108 /** 109 * Returns the encoded media type, like "text/plain; charset=utf-8", 110 * appropriate for use in a Content-Type header. 111 */ 112 @Override public String toString() { 113 return mediaType; 114 } 115 116 @Override public boolean equals(Object o) { 117 return o instanceof MediaType && ((MediaType) o).mediaType.equals(mediaType); 118 } 119 120 @Override public int hashCode() { 121 return mediaType.hashCode(); 122 } 123 } 124