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 import java.util.concurrent.atomic.AtomicReference;
41
42 import org.apache.hc.core5.annotation.Contract;
43 import org.apache.hc.core5.annotation.ThreadingBehavior;
44 import org.apache.hc.core5.http.message.BasicNameValuePair;
45 import org.apache.hc.core5.http.message.MessageSupport;
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.UTF_8);
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.UTF_8);
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
265
266
267
268 public String getParameter(final String name) {
269 Args.notEmpty(name, "Parameter name");
270 if (this.params == null) {
271 return null;
272 }
273 for (final NameValuePair param: this.params) {
274 if (param.getName().equalsIgnoreCase(name)) {
275 return param.getValue();
276 }
277 }
278 return null;
279 }
280
281
282
283
284
285 @Override
286 public String toString() {
287 final CharArrayBuffer buf = new CharArrayBuffer(64);
288 buf.append(this.mimeType);
289 if (this.params != null) {
290 buf.append("; ");
291 MessageSupport.formatParameters(buf, this.params);
292 } else if (this.charset != null) {
293 buf.append("; charset=");
294 buf.append(this.charset.name());
295 }
296 return buf.toString();
297 }
298
299 private static boolean valid(final String s) {
300 for (int i = 0; i < s.length(); i++) {
301 final char ch = s.charAt(i);
302 if (ch == '"' || ch == ',' || ch == ';') {
303 return false;
304 }
305 }
306 return true;
307 }
308
309
310
311
312
313
314
315
316
317 public static ContentType create(final String mimeType, final Charset charset) {
318 final String normalizedMimeType = TextUtils.toLowerCase(Args.notBlank(mimeType, "MIME type"));
319 Args.check(valid(normalizedMimeType), "MIME type may not contain reserved characters");
320 return new ContentType(normalizedMimeType, charset);
321 }
322
323
324
325
326
327
328
329
330 public static ContentType create(final String mimeType) {
331 return create(mimeType, (Charset) null);
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345 public static ContentType create(
346 final String mimeType, final String charset) throws UnsupportedCharsetException {
347 return create(mimeType, !TextUtils.isBlank(charset) ? Charset.forName(charset) : null);
348 }
349
350 private static ContentType create(final HeaderElement helem, final boolean strict) {
351 final String mimeType = helem.getName();
352 if (TextUtils.isBlank(mimeType)) {
353 return null;
354 }
355 return create(helem.getName(), helem.getParameters(), strict);
356 }
357
358 private static ContentType create(final String mimeType, final NameValuePair[] params, final boolean strict) {
359 Charset charset = null;
360 if (params != null) {
361 for (final NameValuePair param : params) {
362 if (param.getName().equalsIgnoreCase(CHARSET)) {
363 final String s = param.getValue();
364 if (!TextUtils.isBlank(s)) {
365 try {
366 charset = Charset.forName(s);
367 } catch (final UnsupportedCharsetException ex) {
368 if (strict) {
369 throw ex;
370 }
371 }
372 }
373 break;
374 }
375 }
376 }
377 return new ContentType(mimeType, charset, params != null && params.length > 0 ? params : null);
378 }
379
380
381
382
383
384
385
386
387
388
389
390
391 public static ContentType create(
392 final String mimeType, final NameValuePair... params) throws UnsupportedCharsetException {
393 final String type = TextUtils.toLowerCase(Args.notBlank(mimeType, "MIME type"));
394 Args.check(valid(type), "MIME type may not contain reserved characters");
395 return create(mimeType, params != null ? params.clone() : null, true);
396 }
397
398
399
400
401
402
403
404
405
406 public static ContentType parse(final CharSequence s) throws UnsupportedCharsetException {
407 return parse(s, true);
408 }
409
410
411
412
413
414
415
416
417
418 public static ContentType parseLenient(final CharSequence s) throws UnsupportedCharsetException {
419 return parse(s, false);
420 }
421
422 private static ContentType parse(final CharSequence s, final boolean strict) throws UnsupportedCharsetException {
423 if (TextUtils.isBlank(s)) {
424 return null;
425 }
426 final ParserCursor cursor = new ParserCursor(0, s.length());
427 final AtomicReference<HeaderElement> firstElementRef = new AtomicReference<>();
428 MessageSupport.parseElements(s, cursor, e -> firstElementRef.compareAndSet(null, e));
429 final HeaderElement element = firstElementRef.get();
430 if (element != null) {
431 return create(element, strict);
432 }
433 return null;
434 }
435
436
437
438
439
440
441
442
443
444
445
446 @Deprecated
447 public static ContentType getByMimeType(final String mimeType) {
448 if (mimeType == null) {
449 return null;
450 }
451 return CONTENT_TYPE_MAP.get(mimeType);
452 }
453
454
455
456
457
458
459
460
461
462 public static Charset getCharset(final ContentType contentType, final Charset defaultCharset) {
463 return contentType != null ? contentType.getCharset(defaultCharset) : defaultCharset;
464 }
465
466
467
468
469
470
471
472
473 public ContentType withCharset(final Charset charset) {
474 return create(this.getMimeType(), charset);
475 }
476
477
478
479
480
481
482
483
484
485
486 public ContentType withCharset(final String charset) {
487 return create(this.getMimeType(), charset);
488 }
489
490
491
492
493
494
495
496
497
498
499 public ContentType withParameters(
500 final NameValuePair... params) throws UnsupportedCharsetException {
501 if (params.length == 0) {
502 return this;
503 }
504 final Map<String, String> paramMap = new LinkedHashMap<>();
505 if (this.params != null) {
506 for (final NameValuePair param: this.params) {
507 paramMap.put(param.getName(), param.getValue());
508 }
509 }
510 for (final NameValuePair param: params) {
511 paramMap.put(param.getName(), param.getValue());
512 }
513 final List<NameValuePair> newParams = new ArrayList<>(paramMap.size() + 1);
514 if (this.charset != null && !paramMap.containsKey(CHARSET)) {
515 newParams.add(new BasicNameValuePair(CHARSET, this.charset.name()));
516 }
517 for (final Map.Entry<String, String> entry: paramMap.entrySet()) {
518 newParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
519 }
520 return create(this.getMimeType(), newParams.toArray(EMPTY_NAME_VALUE_PAIR_ARRAY), true);
521 }
522
523 public boolean isSameMimeType(final ContentType contentType) {
524 return contentType != null && mimeType.equalsIgnoreCase(contentType.getMimeType());
525 }
526
527 }