1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http;
29
30 import java.io.Serializable;
31 import java.nio.charset.Charset;
32 import java.nio.charset.StandardCharsets;
33 import java.nio.charset.UnsupportedCharsetException;
34 import java.util.ArrayList;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.LinkedHashMap;
38 import java.util.List;
39 import java.util.Map;
40
41 import org.apache.hc.core5.annotation.Contract;
42 import org.apache.hc.core5.annotation.ThreadingBehavior;
43 import org.apache.hc.core5.http.message.BasicHeaderValueFormatter;
44 import org.apache.hc.core5.http.message.BasicHeaderValueParser;
45 import org.apache.hc.core5.http.message.BasicNameValuePair;
46 import org.apache.hc.core5.http.message.ParserCursor;
47 import org.apache.hc.core5.util.Args;
48 import org.apache.hc.core5.util.CharArrayBuffer;
49 import org.apache.hc.core5.util.TextUtils;
50
51
52
53
54
55
56
57
58
59
60 @Contract(threading = ThreadingBehavior.IMMUTABLE)
61 public final class ContentType implements Serializable {
62
63 private static final long serialVersionUID = -7768694718232371896L;
64
65
66
67
68 private static final String CHARSET = "charset";
69
70
71 public static final ContentType APPLICATION_ATOM_XML = create(
72 "application/atom+xml", StandardCharsets.UTF_8);
73 public static final ContentType APPLICATION_FORM_URLENCODED = create(
74 "application/x-www-form-urlencoded", StandardCharsets.ISO_8859_1);
75 public static final ContentType APPLICATION_JSON = create(
76 "application/json", StandardCharsets.UTF_8);
77
78
79
80
81
82 public static final ContentType APPLICATION_NDJSON = create(
83 "application/x-ndjson", StandardCharsets.UTF_8);
84
85 public static final ContentType APPLICATION_OCTET_STREAM = create(
86 "application/octet-stream", (Charset) null);
87
88
89
90
91 public static final ContentType APPLICATION_PDF = create(
92 "application/pdf", StandardCharsets.UTF_8);
93
94 public static final ContentType APPLICATION_SOAP_XML = create(
95 "application/soap+xml", StandardCharsets.UTF_8);
96 public static final ContentType APPLICATION_SVG_XML = create(
97 "application/svg+xml", StandardCharsets.UTF_8);
98 public static final ContentType APPLICATION_XHTML_XML = create(
99 "application/xhtml+xml", StandardCharsets.UTF_8);
100 public static final ContentType APPLICATION_XML = create(
101 "application/xml", StandardCharsets.UTF_8);
102
103
104
105
106
107 public static final ContentType APPLICATION_PROBLEM_JSON = create(
108 "application/problem+json", StandardCharsets.UTF_8);
109
110
111
112
113
114 public static final ContentType APPLICATION_PROBLEM_XML = create(
115 "application/problem+xml", StandardCharsets.UTF_8);
116
117
118
119
120
121 public static final ContentType APPLICATION_RSS_XML = create(
122 "application/rss+xml", StandardCharsets.UTF_8);
123
124 public static final ContentType IMAGE_BMP = create(
125 "image/bmp");
126 public static final ContentType IMAGE_GIF = create(
127 "image/gif");
128 public static final ContentType IMAGE_JPEG = create(
129 "image/jpeg");
130 public static final ContentType IMAGE_PNG = create(
131 "image/png");
132 public static final ContentType IMAGE_SVG = create(
133 "image/svg+xml");
134 public static final ContentType IMAGE_TIFF = create(
135 "image/tiff");
136 public static final ContentType IMAGE_WEBP = create(
137 "image/webp");
138 public static final ContentType MULTIPART_FORM_DATA = create(
139 "multipart/form-data", StandardCharsets.ISO_8859_1);
140
141
142
143
144
145 public static final ContentType MULTIPART_MIXED = create(
146 "multipart/mixed", StandardCharsets.ISO_8859_1);
147
148
149
150
151
152 public static final ContentType MULTIPART_RELATED = create(
153 "multipart/related", StandardCharsets.ISO_8859_1);
154
155 public static final ContentType TEXT_HTML = create(
156 "text/html", StandardCharsets.ISO_8859_1);
157
158
159
160
161
162 public static final ContentType TEXT_MARKDOWN = create(
163 "text/markdown", StandardCharsets.UTF_8);
164
165 public static final ContentType TEXT_PLAIN = create(
166 "text/plain", StandardCharsets.ISO_8859_1);
167 public static final ContentType TEXT_XML = create(
168 "text/xml", StandardCharsets.UTF_8);
169
170
171
172
173
174 public static final ContentType TEXT_EVENT_STREAM = create(
175 "text/event-stream", StandardCharsets.UTF_8);
176
177 public static final ContentType WILDCARD = create(
178 "*/*", (Charset) null);
179
180
181
182
183 private static final NameValuePair[] EMPTY_NAME_VALUE_PAIR_ARRAY = {};
184
185
186
187
188 @Deprecated
189 private static final Map<String, ContentType> CONTENT_TYPE_MAP;
190 static {
191
192 final ContentType[] contentTypes = {
193 APPLICATION_ATOM_XML,
194 APPLICATION_FORM_URLENCODED,
195 APPLICATION_JSON,
196 APPLICATION_SVG_XML,
197 APPLICATION_XHTML_XML,
198 APPLICATION_XML,
199 IMAGE_BMP,
200 IMAGE_GIF,
201 IMAGE_JPEG,
202 IMAGE_PNG,
203 IMAGE_SVG,
204 IMAGE_TIFF,
205 IMAGE_WEBP,
206 MULTIPART_FORM_DATA,
207 TEXT_HTML,
208 TEXT_PLAIN,
209 TEXT_XML };
210 final HashMap<String, ContentType> map = new HashMap<>();
211 for (final ContentType contentType: contentTypes) {
212 map.put(contentType.getMimeType(), contentType);
213 }
214 CONTENT_TYPE_MAP = Collections.unmodifiableMap(map);
215 }
216
217
218 public static final ContentType DEFAULT_TEXT = TEXT_PLAIN;
219 public static final ContentType DEFAULT_BINARY = APPLICATION_OCTET_STREAM;
220
221 private final String mimeType;
222 private final Charset charset;
223 private final NameValuePair[] params;
224
225 ContentType(
226 final String mimeType,
227 final Charset charset) {
228 this.mimeType = mimeType;
229 this.charset = charset;
230 this.params = null;
231 }
232
233 ContentType(
234 final String mimeType,
235 final Charset charset,
236 final NameValuePair[] params) {
237 this.mimeType = mimeType;
238 this.charset = charset;
239 this.params = params;
240 }
241
242 public String getMimeType() {
243 return this.mimeType;
244 }
245
246 public Charset getCharset() {
247 return this.charset;
248 }
249
250
251
252
253
254
255
256
257 public Charset getCharset(final Charset defaultCharset) {
258 return this.charset != null ? this.charset : defaultCharset;
259 }
260
261
262
263
264 public String getParameter(final String name) {
265 Args.notEmpty(name, "Parameter name");
266 if (this.params == null) {
267 return null;
268 }
269 for (final NameValuePair param: this.params) {
270 if (param.getName().equalsIgnoreCase(name)) {
271 return param.getValue();
272 }
273 }
274 return null;
275 }
276
277
278
279
280
281 @Override
282 public String toString() {
283 final CharArrayBuffer buf = new CharArrayBuffer(64);
284 buf.append(this.mimeType);
285 if (this.params != null) {
286 buf.append("; ");
287 BasicHeaderValueFormatter.INSTANCE.formatParameters(buf, this.params, false);
288 } else if (this.charset != null) {
289 buf.append("; charset=");
290 buf.append(this.charset.name());
291 }
292 return buf.toString();
293 }
294
295 private static boolean valid(final String s) {
296 for (int i = 0; i < s.length(); i++) {
297 final char ch = s.charAt(i);
298 if (ch == '"' || ch == ',' || ch == ';') {
299 return false;
300 }
301 }
302 return true;
303 }
304
305
306
307
308
309
310
311
312
313 public static ContentType create(final String mimeType, final Charset charset) {
314 final String normalizedMimeType = TextUtils.toLowerCase(Args.notBlank(mimeType, "MIME type"));
315 Args.check(valid(normalizedMimeType), "MIME type may not contain reserved characters");
316 return new ContentType(normalizedMimeType, charset);
317 }
318
319
320
321
322
323
324
325
326 public static ContentType create(final String mimeType) {
327 return create(mimeType, (Charset) null);
328 }
329
330
331
332
333
334
335
336
337
338
339
340
341 public static ContentType create(
342 final String mimeType, final String charset) throws UnsupportedCharsetException {
343 return create(mimeType, !TextUtils.isBlank(charset) ? Charset.forName(charset) : null);
344 }
345
346 private static ContentType create(final HeaderElement helem, final boolean strict) {
347 final String mimeType = helem.getName();
348 if (TextUtils.isBlank(mimeType)) {
349 return null;
350 }
351 return create(helem.getName(), helem.getParameters(), strict);
352 }
353
354 private static ContentType create(final String mimeType, final NameValuePair[] params, final boolean strict) {
355 Charset charset = null;
356 if (params != null) {
357 for (final NameValuePair param : params) {
358 if (param.getName().equalsIgnoreCase(CHARSET)) {
359 final String s = param.getValue();
360 if (!TextUtils.isBlank(s)) {
361 try {
362 charset = Charset.forName(s);
363 } catch (final UnsupportedCharsetException ex) {
364 if (strict) {
365 throw ex;
366 }
367 }
368 }
369 break;
370 }
371 }
372 }
373 return new ContentType(mimeType, charset, params != null && params.length > 0 ? params : null);
374 }
375
376
377
378
379
380
381
382
383
384
385
386 public static ContentType create(
387 final String mimeType, final NameValuePair... params) throws UnsupportedCharsetException {
388 final String type = TextUtils.toLowerCase(Args.notBlank(mimeType, "MIME type"));
389 Args.check(valid(type), "MIME type may not contain reserved characters");
390 return create(mimeType, params, true);
391 }
392
393
394
395
396
397
398
399
400
401 public static ContentType parse(final CharSequence s) throws UnsupportedCharsetException {
402 return parse(s, true);
403 }
404
405
406
407
408
409
410
411
412
413 public static ContentType parseLenient(final CharSequence s) throws UnsupportedCharsetException {
414 return parse(s, false);
415 }
416
417 private static ContentType parse(final CharSequence s, final boolean strict) throws UnsupportedCharsetException {
418 if (TextUtils.isBlank(s)) {
419 return null;
420 }
421 final ParserCursor cursor = new ParserCursor(0, s.length());
422 final HeaderElement[] elements = BasicHeaderValueParser.INSTANCE.parseElements(s, cursor);
423 if (elements.length > 0) {
424 return create(elements[0], strict);
425 }
426 return null;
427 }
428
429
430
431
432
433
434
435
436
437
438
439 @Deprecated
440 public static ContentType getByMimeType(final String mimeType) {
441 if (mimeType == null) {
442 return null;
443 }
444 return CONTENT_TYPE_MAP.get(mimeType);
445 }
446
447
448
449
450
451
452
453
454
455 public static Charset getCharset(final ContentType contentType, final Charset defaultCharset) {
456 return contentType != null ? contentType.getCharset(defaultCharset) : defaultCharset;
457 }
458
459
460
461
462
463
464
465
466 public ContentType withCharset(final Charset charset) {
467 return create(this.getMimeType(), charset);
468 }
469
470
471
472
473
474
475
476
477
478
479 public ContentType withCharset(final String charset) {
480 return create(this.getMimeType(), charset);
481 }
482
483
484
485
486
487
488
489
490 public ContentType withParameters(
491 final NameValuePair... params) throws UnsupportedCharsetException {
492 if (params.length == 0) {
493 return this;
494 }
495 final Map<String, String> paramMap = new LinkedHashMap<>();
496 if (this.params != null) {
497 for (final NameValuePair param: this.params) {
498 paramMap.put(param.getName(), param.getValue());
499 }
500 }
501 for (final NameValuePair param: params) {
502 paramMap.put(param.getName(), param.getValue());
503 }
504 final List<NameValuePair> newParams = new ArrayList<>(paramMap.size() + 1);
505 if (this.charset != null && !paramMap.containsKey(CHARSET)) {
506 newParams.add(new BasicNameValuePair(CHARSET, this.charset.name()));
507 }
508 for (final Map.Entry<String, String> entry: paramMap.entrySet()) {
509 newParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
510 }
511 return create(this.getMimeType(), newParams.toArray(EMPTY_NAME_VALUE_PAIR_ARRAY), true);
512 }
513
514 public boolean isSameMimeType(final ContentType contentType) {
515 return contentType != null && mimeType.equalsIgnoreCase(contentType.getMimeType());
516 }
517
518 }