diff --git a/changes.md b/changes.md index 54c5c0cc15..e873316df9 100644 --- a/changes.md +++ b/changes.md @@ -2,11 +2,15 @@ Changes log =========== - 2.6 Release Candidate 1 (??-03-2025) + - Enhancements + - Added MultiPartRepresentation to Jetty extension to support generation and parsing. + - Added support for the "charset" parameter in HTTP BASIC challenges. Reported by Marc Lafon. + - Added MediaType constructors to help with cloning and customization needs. - Misc - - Upgrade the thymeleaf library to 3.1.3.RELEASE. - - Upgraded the Slf4j library to 5.12.0. - - Upgraded the GWT libraries to version 2.12.2. - - Upgraded the Jetty library to version 2.0.17. + - Upgraded Thymeleaf library to 3.1.3.RELEASE. + - Upgraded Slf4j library to 5.12.0. + - Upgraded GWT libraries to version 2.12.2. + - Upgraded Jetty library to version 2.0.17. - 2.6 Milestone 2 (02-03-2025) - Enhancements diff --git a/org.restlet.java/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java b/org.restlet.java/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java index 5ea7961cd6..4c74e7e91f 100644 --- a/org.restlet.java/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java +++ b/org.restlet.java/org.restlet.ext.crypto/src/test/java/org/restlet/ext/crypto/CookieAuthenticatorTestCase.java @@ -9,7 +9,6 @@ package org.restlet.ext.crypto; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.restlet.*; import org.restlet.data.CookieSetting; diff --git a/org.restlet.java/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java b/org.restlet.java/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java index c1aa98fb76..013cc4ed5d 100644 --- a/org.restlet.java/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java +++ b/org.restlet.java/org.restlet.ext.freemarker/src/test/java/org/restlet/ext/freemarker/FreeMarkerTestCase.java @@ -18,7 +18,6 @@ import java.io.FileWriter; import java.nio.file.Files; import java.util.Map; -import java.util.TreeMap; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/org.restlet.java/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java b/org.restlet.java/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java index aea896a2a8..1ec6a46c68 100644 --- a/org.restlet.java/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java +++ b/org.restlet.java/org.restlet.ext.jackson/src/test/java/org/restlet/ext/jackson/JacksonTestCase.java @@ -19,7 +19,6 @@ import java.util.Date; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; /** diff --git a/org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java b/org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java new file mode 100644 index 0000000000..0a4e1e138a --- /dev/null +++ b/org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java @@ -0,0 +1,333 @@ +/** + * Copyright 2005-2024 Qlik + *

+ * The contents of this file is subject to the terms of the Apache 2.0 open + * source license available at http://www.opensource.org/licenses/apache-2.0 + *

+ * Restlet is a registered trademark of QlikTech International AB. + */ + +package org.restlet.ext.jetty; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.MultiPart; +import org.eclipse.jetty.http.MultiPart.Part; +import org.eclipse.jetty.http.MultiPartConfig; +import org.eclipse.jetty.http.MultiPartFormData; +import org.eclipse.jetty.io.Content; +import org.eclipse.jetty.io.content.InputStreamContentSource; +import org.eclipse.jetty.util.Attributes; +import org.eclipse.jetty.util.Promise; +import org.restlet.data.MediaType; +import org.restlet.representation.InputRepresentation; +import org.restlet.representation.Representation; + +/** + * Input representation that can either parse or generate a multipart form data + * representation depending on which constructor is invoked. + * + * @author Jerome Louvel + */ +public class MultiPartRepresentation extends InputRepresentation { + + /** + * Creates a #{@link Part} object based on a {@link Representation} plus + * metadata. + * + * @param name The name of the part. + * @param fileName The client suggests file name for storing the part. + * @param partContent The part content. + * @return The Jetty #{@link Part} object created. + * @throws IOException + */ + public static Part createPart(String name, String fileName, + Representation partContent) throws IOException { + return new MultiPart.ContentSourcePart(name, fileName, HttpFields.EMPTY, + new InputStreamContentSource(partContent.getStream())); + } + + /** + * Returns the value of the first media-type parameter with "boundary" name. + * + * @param mediaType The media type that might contain a "boundary" + * parameter. + * @return The value of the first media-type parameter with "boundary" name. + */ + public static String getBoundary(MediaType mediaType) { + final String result; + + if (mediaType != null) { + result = mediaType.getParameters().getFirstValue("boundary"); + } else { + result = null; + } + + return result; + } + + /** + * Sets a boundary to an existing media type. If the original mediatype + * already has a "boundary" parameter, it will be erased. * + * + * @param mediaType The media type to update. + * @param boundary The boundary to add as a parameter. + * @return The updated media type. + */ + public static MediaType setBoundary(MediaType mediaType, String boundary) { + MediaType result = null; + + if (mediaType != null) { + if (mediaType.getParameters().getFirst("boundary") != null) { + result = new MediaType(mediaType.getParent(), "boundary", + boundary); + } else { + result = new MediaType(mediaType, "boundary", boundary); + } + } + + return result; + } + + /** + * The boundary used to separate each part for the parsed or generated form. + */ + private volatile String boundary; + + /** The wrapped multipart form data either parsed or to be generated. */ + private volatile List parts; + + /** + * Constructor that wraps multiple parts, set a random boundary, then + * GENERATES the content via {@link #getStream()} as a + * {@link MediaType#MULTIPART_FORM_DATA}. + * + * @param parts The source parts to use when generating the representation. + */ + public MultiPartRepresentation(List parts) { + this(MultiPart.generateBoundary(null, 24), parts); + } + + /** + * Constructor that wraps multiple parts, set a media type with a boundary, + * then GENERATES the content via {@link #getStream()} as a + * {@link MediaType#MULTIPART_FORM_DATA}. + * + * @param mediaType The media type to set. + * @param boundary The boundary to add as a parameter. + * @param parts The source parts to use when generating the + * representation. + */ + public MultiPartRepresentation(MediaType mediaType, String boundary, + List parts) { + super(null, setBoundary(mediaType, boundary)); + this.boundary = boundary; + this.parts = parts; + } + + /** + * Constructor that wraps multiple parts, set a random boundary, then + * GENERATES the content via {@link #getStream()} as a + * {@link MediaType#MULTIPART_FORM_DATA}. + * + * @param parts The source parts to use when generating the representation. + */ + public MultiPartRepresentation(Part... parts) { + this(Arrays.asList(parts)); + } + + /** + * Constructor that PARSES the content based on a given configuration into + * {@link #getParts()}. + * + * @param multiPartEntity The multipart entity to parse which should have a + * media type based on + * {@link MediaType#MULTIPART_FORM_DATA}, with a + * "boundary" parameter. + * @param config The multipart configuration. + * @throws IOException + */ + public MultiPartRepresentation(Representation multiPartEntity, + MultiPartConfig config) throws IOException { + this(multiPartEntity.getMediaType(), multiPartEntity.getStream(), + config); + } + + /** + * Constructor that PARSES the content based on a given configuration into + * {@link #getParts()}. Uses a default {@link MultiPartConfig}. + * + * @param multiPartEntity The multipart entity to parse which should have a + * media type based on + * {@link MediaType#MULTIPART_FORM_DATA}, with a + * "boundary" parameter. + * @param storageLocation The location where parsed files are stored for + * easier access. + * @throws IOException + */ + public MultiPartRepresentation(Representation multiPartEntity, + Path storageLocation) throws IOException { + this(multiPartEntity, new MultiPartConfig.Builder() + .location(storageLocation).build()); + } + + /** + * Constructor that PARSES the content based on a given configuration into + * {@link #getParts()}. + * + * @param mediaType The media type that should be based on + * {@link MediaType#MULTIPART_FORM_DATA}, with a + * "boundary" parameter. + * @param multiPartEntity The multipart entity to parse. + * @param config The multipart configuration. + * @throws IOException + */ + public MultiPartRepresentation(MediaType mediaType, + InputStream multiPartEntity, MultiPartConfig config) + throws IOException { + super(null, mediaType); + + if (MediaType.MULTIPART_FORM_DATA.equals(getMediaType(), true)) { + this.boundary = getMediaType().getParameters() + .getFirstValue("boundary"); + + if (this.boundary != null) { + if (multiPartEntity != null) { + Content.Source contentSource = Content.Source + .from(multiPartEntity); + Attributes.Mapped attributes = new Attributes.Mapped(); + + // Convert the request content into parts. + MultiPartFormData.onParts(contentSource, attributes, + mediaType.toString(), config, + new Promise.Invocable<>() { + @Override + public void failed(Throwable failure) { + throw new IllegalStateException( + "Unable to parse the multipart form data representation", + failure); + } + + @Override + public InvocationType getInvocationType() { + return InvocationType.BLOCKING; + } + + @Override + public void succeeded( + MultiPartFormData.Parts parts) { + // Store the resulting parts + MultiPartRepresentation.this.parts = new ArrayList<>(); + parts.iterator().forEachRemaining( + part -> MultiPartRepresentation.this.parts + .add(part)); + } + }); + } else { + throw new IllegalArgumentException( + "The multipart entity can't be null"); + } + } else { + throw new IllegalArgumentException( + "The content type must have a \"boundary\" parameter"); + } + } else { + throw new IllegalArgumentException( + "The content type must be \"multipart/form-data\" with a \"boundary\" parameter"); + } + } + + /** + * Constructor that wraps multiple parts, set a boundary, then GENERATES the + * content via {@link #getStream()} as a + * {@link MediaType#MULTIPART_FORM_DATA}. + * + * @param boundary The boundary to add as a parameter. + * @param parts The source parts to use when generating the + * representation. + */ + public MultiPartRepresentation(String boundary, List parts) { + this(MediaType.MULTIPART_FORM_DATA, boundary, parts); + } + + /** + * Constructor that wraps multiple parts, set a boundary, then GENERATES the + * content via {@link #getStream()} as a + * {@link MediaType#MULTIPART_FORM_DATA}. + * + * @param parts The source parts to use when generating the representation. + */ + public MultiPartRepresentation(String boundary, Part... parts) { + this(boundary, Arrays.asList(parts)); + } + + /** + * Returns the boundary used to separate each part for the parsed or + * generated form. + * + * @return The boundary used to separate each part for the parsed or + * generated form. + */ + public String getBoundary() { + return boundary; + } + + /** + * Returns the wrapped multipart form data either parsed or to be generated. + * + * @return The wrapped multipart form data either parsed or to be generated. + */ + public List getParts() { + return parts; + } + + /** + * Returns an input stream that generates the multipart form data + * serialization for the wrapped {@link #getParts()} object. The "boundary" + * must be non-null when invoking this method. + * + * @return An input stream that generates the multipart form data. + */ + @Override + public InputStream getStream() throws IOException { + if (getBoundary() == null) { + throw new IllegalArgumentException("The boundary can't be null"); + } + + MultiPartFormData.ContentSource content = new MultiPartFormData.ContentSource( + getBoundary()); + + for (Part part : this.parts) { + content.addPart(part); + } + + content.close(); + setStream(null); + return Content.Source.asInputStream(content); + } + + /** + * Sets the boundary used to separate each part for the parsed or generated + * form. It will also update the {@link MediaType}'s "boundary" attribute. + * + * @param boundary The boundary used to separate each part for the parsed or + * generated form. + */ + public void setBoundary(String boundary) { + this.boundary = boundary; + + if (getMediaType() == null) { + setMediaType(new MediaType(MediaType.MULTIPART_FORM_DATA, + "boundary", boundary)); + } else { + setMediaType(setBoundary(getMediaType(), boundary)); + } + } + +} diff --git a/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/MultiPartFormTestCase.java b/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/MultiPartFormTestCase.java deleted file mode 100644 index f2f12ccba0..0000000000 --- a/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/MultiPartFormTestCase.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright 2005-2024 Qlik - * - * The contents of this file is subject to the terms of the Apache 2.0 open - * source license available at http://www.opensource.org/licenses/apache-2.0 - * - * Restlet is a registered trademark of QlikTech International AB. - */ - -package org.restlet.ext.jetty; - -import org.junit.jupiter.api.Test; - -/** - * Test case for the {@link FormDataSet} class in multipart mode. - * - * @author Jerome Louvel - */ -public class MultiPartFormTestCase { - - /* - TODO restore test about support of multi part representations. - - - @Test - public void testWrite() throws IOException { - - // considered as a simple field entry - Representation textFile = new EmptyRepresentation(); - textFile.setMediaType(MediaType.TEXT_PLAIN); - - // considered as a file - Representation textFile2 = new StringRepresentation("test", - MediaType.TEXT_PLAIN); - textFile2.setDisposition(new Disposition()); - textFile2.getDisposition().setFilename("test.txt"); - - // considered as a file - Representation file = new EmptyRepresentation(); - file.setMediaType(MediaType.APPLICATION_OCTET_STREAM); - - String boundary = "-----------------------------1294919323195"; - String boundaryBis = "--" + boundary; - String expected; - - FormDataSet form = new FormDataSet(boundary); - form.getEntries().add(new FormData("number", "5555555555")); - form.getEntries().add(new FormData("clip", "rickroll")); - form.getEntries().add(new FormData("upload_file", file)); - form.getEntries().add(new FormData("upload_textfile", textFile)); - form.getEntries().add(new FormData("upload_textfile2", textFile2)); - form.getEntries().add(new FormData("tos", "agree")); - - expected = boundaryBis - + "\r\n" - + "Content-Disposition: form-data; name=\"number\"\r\n" - + "\r\n" - + "5555555555\r\n" - + boundaryBis - + "\r\n" - + "Content-Disposition: form-data; name=\"clip\"\r\n" - + "\r\n" - + "rickroll\r\n" - + boundaryBis - + "\r\n" - + "Content-Disposition: form-data; name=\"upload_file\"; filename=\"\"\r\n" - + "Content-Type: application/octet-stream\r\n" - + "\r\n" - + "\r\n" - + boundaryBis - + "\r\n" - + "Content-Disposition: form-data; name=\"upload_textfile\"\r\n" - + "\r\n" - + "\r\n" - + boundaryBis - + "\r\n" - + "Content-Disposition: form-data; name=\"upload_textfile2\"; filename=\"test.txt\"\r\n" - + "Content-Type: text/plain; charset=UTF-8\r\n" + "\r\n" - + "test" + "\r\n" + boundaryBis + "\r\n" - + "Content-Disposition: form-data; name=\"tos\"\r\n" + "\r\n" - + "agree\r\n" + boundaryBis + "--\r\n"; - assertEquals(expected, form.getText()); - } - */ - - /** - * Tests the multipart content-type. - */ - @Test - public void testContentType() { - /* -TODO restore test of Form class - FormDataSet form = null; - - form = new FormDataSet(); - form.setMultipart(true); - assertTrue(form.getMediaType().equals(MediaType.MULTIPART_FORM_DATA, - true)); - - form = new FormDataSet("test"); - assertTrue(form.isMultipart()); - assertTrue(form.getMediaType().equals(MediaType.MULTIPART_FORM_DATA, - true)); - assertEquals( - form.getMediaType().getParameters().getFirstValue("boundary"), - "test"); - form = new FormDataSet(); - - form.setMultipartBoundary("test2"); - assertTrue(form.isMultipart()); - assertTrue(form.getMediaType().equals(MediaType.MULTIPART_FORM_DATA, - true)); - assertEquals( - form.getMediaType().getParameters().getFirstValue("boundary"), - "test2"); - - */ - } -} diff --git a/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/MultiPartRepresentationTestCase.java b/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/MultiPartRepresentationTestCase.java new file mode 100644 index 0000000000..70a6760edd --- /dev/null +++ b/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/MultiPartRepresentationTestCase.java @@ -0,0 +1,124 @@ +/** + * Copyright 2005-2024 Qlik + * + * The contents of this file is subject to the terms of the Apache 2.0 open + * source license available at http://www.opensource.org/licenses/apache-2.0 + * + * Restlet is a registered trademark of QlikTech International AB. + */ + +package org.restlet.ext.jetty; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jetty.client.StringRequestContent; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.MultiPart; +import org.eclipse.jetty.http.MultiPart.Part; +import org.junit.jupiter.api.Test; +import org.restlet.data.MediaType; +import org.restlet.representation.StringRepresentation; + +/** + * Test case for the {@link MultiPartRepresentation} class in multipart mode. + * + * @author Jerome Louvel + */ +public class MultiPartRepresentationTestCase { + + @Test + public void testWriteFromParts() throws IOException { + Path textFilePath = Files.createTempFile("multiPart", ""); + Files.write(textFilePath, "this is the content of the file" + .getBytes(StandardCharsets.UTF_8)); + MultiPart.PathPart filePart = new MultiPart.PathPart("icon", "text.txt", + HttpFields.EMPTY, textFilePath); + + MultiPart.ContentSourcePart contentSourcePart = new MultiPart.ContentSourcePart( + "field", null, HttpFields.EMPTY, + new StringRequestContent("foo")); + + final String boundary = "-----------------------------1294919323195"; + + MultiPartRepresentation rep = new MultiPartRepresentation( + contentSourcePart, filePart); + rep.setBoundary(boundary); + + final String expected = """ + --%s\r + Content-Disposition: form-data; name="field"\r + \r + foo\r + --%s\r + Content-Disposition: form-data; name="icon"; filename="text.txt"\r + \r + this is the content of the file\r + --%s--\r + """ + .replace("%s", boundary); + assertEquals(expected, rep.getText()); + } + + /** + * Tests the multipart content-type. + */ + @Test + public void testContentType() { + MultiPart.ContentSourcePart contentSourcePart = new MultiPart.ContentSourcePart( + "field", null, HttpFields.EMPTY, + new StringRequestContent("foo")); + MultiPartRepresentation rep = new MultiPartRepresentation( + "myInitialBoundary", contentSourcePart); + rep.setBoundary("myActualBoundary"); + assertEquals("multipart/form-data; boundary=myActualBoundary", + rep.getMediaType().toString()); + } + + @Test + public void testParseIntoParts() throws IOException { + final String boundary = "-----------------------------1294919323195"; + final String multipartEntityContent = """ + --%s\r + Content-Disposition: form-data; name="field"\r + \r + foo\r + --%s\r + Content-Disposition: form-data; name="icon"; filename="text.txt"\r + \r + this is the content of the file\r + --%s--\r + """ + .replace("%s", boundary); + + StringRepresentation multipartEntity = new StringRepresentation( + multipartEntityContent); + multipartEntity.setMediaType( + MediaType.valueOf("multipart/form-data; boundary=" + boundary)); + Path tempDir = Files + .createTempDirectory("multipartRepresentationTestCase"); + MultiPartRepresentation rep = new MultiPartRepresentation( + multipartEntity, tempDir); + + Part part1 = rep.getParts().get(0); + assertEquals("field", part1.getName()); + assertNull(part1.getFileName()); + assertEquals(3, part1.getLength()); + assertEquals("foo", part1.getContentAsString(null)); + + Part part2 = rep.getParts().get(1); + assertEquals("icon", part2.getName()); + assertEquals("text.txt", part2.getFileName()); + assertEquals(31, part2.getLength()); + assertEquals("this is the content of the file", + part2.getContentAsString(null)); + + } + +} diff --git a/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/connectors/SslGetTestCase.java b/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/connectors/SslGetTestCase.java index 0ee1b8d7a7..43d56eab66 100644 --- a/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/connectors/SslGetTestCase.java +++ b/org.restlet.java/org.restlet.ext.jetty/src/test/java/org/restlet/ext/jetty/connectors/SslGetTestCase.java @@ -16,7 +16,6 @@ import org.restlet.representation.Variant; import org.restlet.resource.ServerResource; import org.restlet.routing.Router; -import org.restlet.util.Series; import static java.lang.String.format; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/org.restlet.java/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java b/org.restlet.java/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java index b16db8c473..e8566469a4 100644 --- a/org.restlet.java/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java +++ b/org.restlet.java/org.restlet.ext.spring/src/test/java/org/restlet/ext/spring/SpringBeanRouterTestCase.java @@ -10,7 +10,6 @@ package org.restlet.ext.spring; import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.restlet.Request; diff --git a/org.restlet.java/org.restlet/src/main/java/org/restlet/data/MediaType.java b/org.restlet.java/org.restlet/src/main/java/org/restlet/data/MediaType.java index ed6117c703..0e2126df3a 100644 --- a/org.restlet.java/org.restlet/src/main/java/org/restlet/data/MediaType.java +++ b/org.restlet.java/org.restlet/src/main/java/org/restlet/data/MediaType.java @@ -30,854 +30,1011 @@ */ public final class MediaType extends Metadata { - /** - * Illegal ASCII characters as defined in RFC 1521.
- * Keep the underscore for the ordering - * - * @see RFC 1521 - */ - private static final String _TSPECIALS = "()<>@,;:/[]?=\\\""; + /** + * Illegal ASCII characters as defined in RFC 1521.
+ * Keep the underscore for the ordering + * + * @see RFC 1521 + */ + private static final String _TSPECIALS = "()<>@,;:/[]?=\\\""; - /** - * The known media types registered with {@link #register(String, String)}, - * retrievable using {@link #valueOf(String)}.
- * Keep the underscore for the ordering. - */ - private static volatile Map _types = null; + /** + * The known media types registered with {@link #register(String, String)}, + * retrievable using {@link #valueOf(String)}.
+ * Keep the underscore for the ordering. + */ + private static volatile Map _types = null; - public static final MediaType ALL = register("*/*", "All media"); + public static final MediaType ALL = register("*/*", "All media"); - public static final MediaType APPLICATION_ALL = register("application/*", "All application documents"); + public static final MediaType APPLICATION_ALL = register("application/*", + "All application documents"); - public static final MediaType APPLICATION_ALL_JSON = register("application/*+json", - "All application/*+json documents"); + public static final MediaType APPLICATION_ALL_JSON = register( + "application/*+json", "All application/*+json documents"); - public static final MediaType APPLICATION_ALL_XML = register("application/*+xml", - "All application/*+xml documents"); + public static final MediaType APPLICATION_ALL_XML = register( + "application/*+xml", "All application/*+xml documents"); - public static final MediaType APPLICATION_ATOM = register("application/atom+xml", "Atom document"); + public static final MediaType APPLICATION_ATOM = register( + "application/atom+xml", "Atom document"); - public static final MediaType APPLICATION_ATOMPUB_CATEGORY = register("application/atomcat+xml", - "Atom category document"); + public static final MediaType APPLICATION_ATOMPUB_CATEGORY = register( + "application/atomcat+xml", "Atom category document"); - public static final MediaType APPLICATION_ATOMPUB_SERVICE = register("application/atomsvc+xml", - "Atom service document"); + public static final MediaType APPLICATION_ATOMPUB_SERVICE = register( + "application/atomsvc+xml", "Atom service document"); - public static final MediaType APPLICATION_CAB = register("application/vnd.ms-cab-compressed", - "Microsoft Cabinet archive"); + public static final MediaType APPLICATION_CAB = register( + "application/vnd.ms-cab-compressed", "Microsoft Cabinet archive"); - public static final MediaType APPLICATION_COMPRESS = register("application/x-compress", "Compressed file"); + public static final MediaType APPLICATION_COMPRESS = register( + "application/x-compress", "Compressed file"); - public static final MediaType APPLICATION_ECORE = register("application/x-ecore+xmi+xml", "EMOF ECore metamodel"); + public static final MediaType APPLICATION_ECORE = register( + "application/x-ecore+xmi+xml", "EMOF ECore metamodel"); - public static final MediaType APPLICATION_EXCEL = register("application/vnd.ms-excel", "Microsoft Excel document"); + public static final MediaType APPLICATION_EXCEL = register( + "application/vnd.ms-excel", "Microsoft Excel document"); - public static final MediaType APPLICATION_FLASH = register("application/x-shockwave-flash", - "Shockwave Flash object"); + public static final MediaType APPLICATION_FLASH = register( + "application/x-shockwave-flash", "Shockwave Flash object"); - public static final MediaType APPLICATION_GNU_TAR = register("application/x-gtar", "GNU Tar archive"); + public static final MediaType APPLICATION_GNU_TAR = register( + "application/x-gtar", "GNU Tar archive"); - public static final MediaType APPLICATION_GNU_ZIP = register("application/x-gzip", "GNU Zip archive"); + public static final MediaType APPLICATION_GNU_ZIP = register( + "application/x-gzip", "GNU Zip archive"); - public static final MediaType APPLICATION_HTTP_COOKIES = register("application/x-http-cookies", "HTTP cookies"); + public static final MediaType APPLICATION_HTTP_COOKIES = register( + "application/x-http-cookies", "HTTP cookies"); - public static final MediaType APPLICATION_JAVA = register("application/java", "Java class"); + public static final MediaType APPLICATION_JAVA = register( + "application/java", "Java class"); - public static final MediaType APPLICATION_JAVA_ARCHIVE = register("application/java-archive", "Java archive"); + public static final MediaType APPLICATION_JAVA_ARCHIVE = register( + "application/java-archive", "Java archive"); - public static final MediaType APPLICATION_JAVA_OBJECT = register("application/x-java-serialized-object", - "Java serialized object"); + public static final MediaType APPLICATION_JAVA_OBJECT = register( + "application/x-java-serialized-object", "Java serialized object"); - public static final MediaType APPLICATION_JAVA_OBJECT_GWT = register("text/x-gwt-rpc", - "Java serialized object (using GWT-RPC encoder)"); + public static final MediaType APPLICATION_JAVA_OBJECT_GWT = register( + "text/x-gwt-rpc", "Java serialized object (using GWT-RPC encoder)"); - public static final MediaType APPLICATION_JAVA_OBJECT_XML = register("text/x-gwt-rpc+xml", - "Java serialized object (using JavaBeans XML encoder)"); + public static final MediaType APPLICATION_JAVA_OBJECT_XML = register( + "text/x-gwt-rpc+xml", + "Java serialized object (using JavaBeans XML encoder)"); - public static final MediaType APPLICATION_JAVASCRIPT = register("application/x-javascript", "Javascript document"); + public static final MediaType APPLICATION_JAVASCRIPT = register( + "application/x-javascript", "Javascript document"); - public static final MediaType APPLICATION_JNLP = register("application/x-java-jnlp-file", "JNLP"); + public static final MediaType APPLICATION_JNLP = register( + "application/x-java-jnlp-file", "JNLP"); - public static final MediaType APPLICATION_JSON = register("application/json", - "JavaScript Object Notation document"); + public static final MediaType APPLICATION_JSON = register( + "application/json", "JavaScript Object Notation document"); - public static final MediaType APPLICATION_JSON_ACTIVITY = register("application/activity+json", - "Activity Streams JSON document"); + public static final MediaType APPLICATION_JSON_ACTIVITY = register( + "application/activity+json", "Activity Streams JSON document"); - public static final MediaType APPLICATION_JSON_PATCH = register("application/json-patch", "JSON patch document"); + public static final MediaType APPLICATION_JSON_PATCH = register( + "application/json-patch", "JSON patch document"); - public static final MediaType APPLICATION_JSON_SMILE = register("application/x-json-smile", - "JavaScript Object Notation smile document"); + public static final MediaType APPLICATION_JSON_SMILE = register( + "application/x-json-smile", + "JavaScript Object Notation smile document"); - public static final MediaType APPLICATION_KML = register("application/vnd.google-earth.kml+xml", - "Google Earth/Maps KML document"); + public static final MediaType APPLICATION_KML = register( + "application/vnd.google-earth.kml+xml", + "Google Earth/Maps KML document"); - public static final MediaType APPLICATION_KMZ = register("application/vnd.google-earth.kmz", - "Google Earth/Maps KMZ document"); + public static final MediaType APPLICATION_KMZ = register( + "application/vnd.google-earth.kmz", + "Google Earth/Maps KMZ document"); - public static final MediaType APPLICATION_LATEX = register("application/x-latex", "LaTeX"); + public static final MediaType APPLICATION_LATEX = register( + "application/x-latex", "LaTeX"); - public static final MediaType APPLICATION_MAC_BINHEX40 = register("application/mac-binhex40", "Mac binhex40"); + public static final MediaType APPLICATION_MAC_BINHEX40 = register( + "application/mac-binhex40", "Mac binhex40"); - public static final MediaType APPLICATION_MATHML = register("application/mathml+xml", "MathML XML document"); + public static final MediaType APPLICATION_MATHML = register( + "application/mathml+xml", "MathML XML document"); - public static final MediaType APPLICATION_MSML = register("application/msml+xml", "Media Server Markup Language"); + public static final MediaType APPLICATION_MSML = register( + "application/msml+xml", "Media Server Markup Language"); - public static final MediaType APPLICATION_MSOFFICE_DOCM = register( - "application/vnd.ms-word.document.macroEnabled.12", "Office Word 2007 macro-enabled document"); + public static final MediaType APPLICATION_MSOFFICE_DOCM = register( + "application/vnd.ms-word.document.macroEnabled.12", + "Office Word 2007 macro-enabled document"); - public static final MediaType APPLICATION_MSOFFICE_DOCX = register( - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "Microsoft Office Word 2007 document"); + public static final MediaType APPLICATION_MSOFFICE_DOCX = register( + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "Microsoft Office Word 2007 document"); - public static final MediaType APPLICATION_MSOFFICE_DOTM = register( - "application/vnd.ms-word.template.macroEnabled.12", "Office Word 2007 macro-enabled document template"); + public static final MediaType APPLICATION_MSOFFICE_DOTM = register( + "application/vnd.ms-word.template.macroEnabled.12", + "Office Word 2007 macro-enabled document template"); - public static final MediaType APPLICATION_MSOFFICE_DOTX = register( - "application/vnd.openxmlformats-officedocument.wordprocessingml.template", "Office Word 2007 template"); + public static final MediaType APPLICATION_MSOFFICE_DOTX = register( + "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + "Office Word 2007 template"); - public static final MediaType APPLICATION_MSOFFICE_ONETOC = register("application/onenote", - "Microsoft Office OneNote 2007 TOC"); + public static final MediaType APPLICATION_MSOFFICE_ONETOC = register( + "application/onenote", "Microsoft Office OneNote 2007 TOC"); - public static final MediaType APPLICATION_MSOFFICE_ONETOC2 = register("application/onenote", - "Office OneNote 2007 TOC"); + public static final MediaType APPLICATION_MSOFFICE_ONETOC2 = register( + "application/onenote", "Office OneNote 2007 TOC"); - public static final MediaType APPLICATION_MSOFFICE_POTM = register( - "application/vnd.ms-powerpoint.template.macroEnabled.12", - "Office PowerPoint 2007 macro-enabled presentation template"); + public static final MediaType APPLICATION_MSOFFICE_POTM = register( + "application/vnd.ms-powerpoint.template.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled presentation template"); - public static final MediaType APPLICATION_MSOFFICE_POTX = register( - "application/vnd.openxmlformats-officedocument.presentationml.template", "Office PowerPoint 2007 template"); + public static final MediaType APPLICATION_MSOFFICE_POTX = register( + "application/vnd.openxmlformats-officedocument.presentationml.template", + "Office PowerPoint 2007 template"); - public static final MediaType APPLICATION_MSOFFICE_PPAM = register( - "application/vnd.ms-powerpoint.addin.macroEnabled.12", "Office PowerPoint 2007 add-in"); + public static final MediaType APPLICATION_MSOFFICE_PPAM = register( + "application/vnd.ms-powerpoint.addin.macroEnabled.12", + "Office PowerPoint 2007 add-in"); - public static final MediaType APPLICATION_MSOFFICE_PPSM = register( - "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", - "Office PowerPoint 2007 macro-enabled slide show"); + public static final MediaType APPLICATION_MSOFFICE_PPSM = register( + "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled slide show"); - public static final MediaType APPLICATION_MSOFFICE_PPSX = register( - "application/vnd.openxmlformats-officedocument.presentationml.slideshow", - "Office PowerPoint 2007 slide show"); + public static final MediaType APPLICATION_MSOFFICE_PPSX = register( + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + "Office PowerPoint 2007 slide show"); - public static final MediaType APPLICATION_MSOFFICE_PPTM = register( - "application/vnd.ms-powerpoint.presentation.macroEnabled.12", - "Office PowerPoint 2007 macro-enabled presentation"); + public static final MediaType APPLICATION_MSOFFICE_PPTM = register( + "application/vnd.ms-powerpoint.presentation.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled presentation"); - public static final MediaType APPLICATION_MSOFFICE_PPTX = register( - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "Microsoft Office PowerPoint 2007 presentation"); + public static final MediaType APPLICATION_MSOFFICE_PPTX = register( + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "Microsoft Office PowerPoint 2007 presentation"); - public static final MediaType APPLICATION_MSOFFICE_SLDM = register( - "application/vnd.ms-powerpoint.slide.macroEnabled.12", "Office PowerPoint 2007 macro-enabled slide"); + public static final MediaType APPLICATION_MSOFFICE_SLDM = register( + "application/vnd.ms-powerpoint.slide.macroEnabled.12", + "Office PowerPoint 2007 macro-enabled slide"); - public static final MediaType APPLICATION_MSOFFICE_SLDX = register( - "application/vnd.openxmlformats-officedocument.presentationml.slide", "Office PowerPoint 2007 slide"); + public static final MediaType APPLICATION_MSOFFICE_SLDX = register( + "application/vnd.openxmlformats-officedocument.presentationml.slide", + "Office PowerPoint 2007 slide"); - public static final MediaType APPLICATION_MSOFFICE_XLAM = register("application/vnd.ms-excel.addin.macroEnabled.12", - "Office Excel 2007 add-in"); + public static final MediaType APPLICATION_MSOFFICE_XLAM = register( + "application/vnd.ms-excel.addin.macroEnabled.12", + "Office Excel 2007 add-in"); - public static final MediaType APPLICATION_MSOFFICE_XLSB = register( - "application/vnd.ms-excel.sheet.binary.macroEnabled.12", "Office Excel 2007 binary workbook"); + public static final MediaType APPLICATION_MSOFFICE_XLSB = register( + "application/vnd.ms-excel.sheet.binary.macroEnabled.12", + "Office Excel 2007 binary workbook"); - public static final MediaType APPLICATION_MSOFFICE_XLSM = register("application/vnd.ms-excel.sheet.macroEnabled.12", - "Office Excel 2007 macro-enabled workbook"); + public static final MediaType APPLICATION_MSOFFICE_XLSM = register( + "application/vnd.ms-excel.sheet.macroEnabled.12", + "Office Excel 2007 macro-enabled workbook"); - public static final MediaType APPLICATION_MSOFFICE_XLSX = register( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "Microsoft Office Excel 2007 workbook"); + public static final MediaType APPLICATION_MSOFFICE_XLSX = register( + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "Microsoft Office Excel 2007 workbook"); - public static final MediaType APPLICATION_MSOFFICE_XLTM = register( - "application/vnd.ms-excel.template.macroEnabled.12", "Office Excel 2007 macro-enabled workbook template"); + public static final MediaType APPLICATION_MSOFFICE_XLTM = register( + "application/vnd.ms-excel.template.macroEnabled.12", + "Office Excel 2007 macro-enabled workbook template"); - public static final MediaType APPLICATION_MSOFFICE_XLTX = register( - "application/vnd.openxmlformats-officedocument.spreadsheetml.template", "Office Excel 2007 template"); + public static final MediaType APPLICATION_MSOFFICE_XLTX = register( + "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + "Office Excel 2007 template"); - public static final MediaType APPLICATION_OCTET_STREAM = register("application/octet-stream", "Raw octet stream"); + public static final MediaType APPLICATION_OCTET_STREAM = register( + "application/octet-stream", "Raw octet stream"); - public static final MediaType APPLICATION_OPENOFFICE_ODB = register("application/vnd.oasis.opendocument.database", - "OpenDocument Database"); + public static final MediaType APPLICATION_OPENOFFICE_ODB = register( + "application/vnd.oasis.opendocument.database", + "OpenDocument Database"); - public static final MediaType APPLICATION_OPENOFFICE_ODC = register("application/vnd.oasis.opendocument.chart", - "OpenDocument Chart"); + public static final MediaType APPLICATION_OPENOFFICE_ODC = register( + "application/vnd.oasis.opendocument.chart", "OpenDocument Chart"); - public static final MediaType APPLICATION_OPENOFFICE_ODF = register("application/vnd.oasis.opendocument.formula", - "OpenDocument Formula"); + public static final MediaType APPLICATION_OPENOFFICE_ODF = register( + "application/vnd.oasis.opendocument.formula", + "OpenDocument Formula"); - public static final MediaType APPLICATION_OPENOFFICE_ODG = register("application/vnd.oasis.opendocument.graphics", - "OpenDocument Drawing"); + public static final MediaType APPLICATION_OPENOFFICE_ODG = register( + "application/vnd.oasis.opendocument.graphics", + "OpenDocument Drawing"); - public static final MediaType APPLICATION_OPENOFFICE_ODI = register("application/vnd.oasis.opendocument.image", - "OpenDocument Image "); + public static final MediaType APPLICATION_OPENOFFICE_ODI = register( + "application/vnd.oasis.opendocument.image", "OpenDocument Image "); - public static final MediaType APPLICATION_OPENOFFICE_ODM = register( - "application/vnd.oasis.opendocument.text-master", "OpenDocument Master Document"); + public static final MediaType APPLICATION_OPENOFFICE_ODM = register( + "application/vnd.oasis.opendocument.text-master", + "OpenDocument Master Document"); - public static final MediaType APPLICATION_OPENOFFICE_ODP = register( - "application/vnd.oasis.opendocument.presentation", "OpenDocument Presentation "); + public static final MediaType APPLICATION_OPENOFFICE_ODP = register( + "application/vnd.oasis.opendocument.presentation", + "OpenDocument Presentation "); - public static final MediaType APPLICATION_OPENOFFICE_ODS = register( - "application/vnd.oasis.opendocument.spreadsheet", "OpenDocument Spreadsheet"); + public static final MediaType APPLICATION_OPENOFFICE_ODS = register( + "application/vnd.oasis.opendocument.spreadsheet", + "OpenDocument Spreadsheet"); - public static final MediaType APPLICATION_OPENOFFICE_ODT = register("application/vnd.oasis.opendocument.text ", - "OpenDocument Text"); + public static final MediaType APPLICATION_OPENOFFICE_ODT = register( + "application/vnd.oasis.opendocument.text ", "OpenDocument Text"); - public static final MediaType APPLICATION_OPENOFFICE_OTG = register( - "application/vnd.oasis.opendocument.graphics-template", "OpenDocument Drawing Template"); + public static final MediaType APPLICATION_OPENOFFICE_OTG = register( + "application/vnd.oasis.opendocument.graphics-template", + "OpenDocument Drawing Template"); - public static final MediaType APPLICATION_OPENOFFICE_OTH = register("application/vnd.oasis.opendocument.text-web", - "HTML Document Template"); + public static final MediaType APPLICATION_OPENOFFICE_OTH = register( + "application/vnd.oasis.opendocument.text-web", + "HTML Document Template"); - public static final MediaType APPLICATION_OPENOFFICE_OTP = register( - "application/vnd.oasis.opendocument.presentation-template", "OpenDocument Presentation Template"); + public static final MediaType APPLICATION_OPENOFFICE_OTP = register( + "application/vnd.oasis.opendocument.presentation-template", + "OpenDocument Presentation Template"); - public static final MediaType APPLICATION_OPENOFFICE_OTS = register( - "application/vnd.oasis.opendocument.spreadsheet-template", "OpenDocument Spreadsheet Template"); + public static final MediaType APPLICATION_OPENOFFICE_OTS = register( + "application/vnd.oasis.opendocument.spreadsheet-template", + "OpenDocument Spreadsheet Template"); - public static final MediaType APPLICATION_OPENOFFICE_OTT = register( - "application/vnd.oasis.opendocument.text-template", "OpenDocument Text Template"); + public static final MediaType APPLICATION_OPENOFFICE_OTT = register( + "application/vnd.oasis.opendocument.text-template", + "OpenDocument Text Template"); - public static final MediaType APPLICATION_OPENOFFICE_OXT = register("application/vnd.openofficeorg.extension", - "OpenOffice.org extension"); + public static final MediaType APPLICATION_OPENOFFICE_OXT = register( + "application/vnd.openofficeorg.extension", + "OpenOffice.org extension"); - public static final MediaType APPLICATION_PDF = register("application/pdf", "Adobe PDF document"); + public static final MediaType APPLICATION_PDF = register("application/pdf", + "Adobe PDF document"); - public static final MediaType APPLICATION_POSTSCRIPT = register("application/postscript", "Postscript document"); + public static final MediaType APPLICATION_POSTSCRIPT = register( + "application/postscript", "Postscript document"); - public static final MediaType APPLICATION_POWERPOINT = register("application/vnd.ms-powerpoint", - "Microsoft Powerpoint document"); + public static final MediaType APPLICATION_POWERPOINT = register( + "application/vnd.ms-powerpoint", "Microsoft Powerpoint document"); - public static final MediaType APPLICATION_PROJECT = register("application/vnd.ms-project", - "Microsoft Project document"); + public static final MediaType APPLICATION_PROJECT = register( + "application/vnd.ms-project", "Microsoft Project document"); - public static final MediaType APPLICATION_RDF_TRIG = register("application/x-trig", - "Plain text serialized Resource Description Framework document"); + public static final MediaType APPLICATION_RDF_TRIG = register( + "application/x-trig", + "Plain text serialized Resource Description Framework document"); - public static final MediaType APPLICATION_RDF_TRIX = register("application/trix", - "Simple XML serialized Resource Description Framework document"); + public static final MediaType APPLICATION_RDF_TRIX = register( + "application/trix", + "Simple XML serialized Resource Description Framework document"); - public static final MediaType APPLICATION_RDF_XML = register("application/rdf+xml", - "Normalized XML serialized Resource Description Framework document"); + public static final MediaType APPLICATION_RDF_XML = register( + "application/rdf+xml", + "Normalized XML serialized Resource Description Framework document"); - public static final MediaType APPLICATION_RELAXNG_COMPACT = register("application/relax-ng-compact-syntax", - "Relax NG Schema document, Compact syntax"); + public static final MediaType APPLICATION_RELAXNG_COMPACT = register( + "application/relax-ng-compact-syntax", + "Relax NG Schema document, Compact syntax"); - public static final MediaType APPLICATION_RELAXNG_XML = register("application/x-relax-ng+xml", - "Relax NG Schema document, XML syntax"); + public static final MediaType APPLICATION_RELAXNG_XML = register( + "application/x-relax-ng+xml", + "Relax NG Schema document, XML syntax"); - public static final MediaType APPLICATION_RSS = register("application/rss+xml", - "Really Simple Syndication document"); + public static final MediaType APPLICATION_RSS = register( + "application/rss+xml", "Really Simple Syndication document"); - public static final MediaType APPLICATION_RTF = register("application/rtf", "Rich Text Format document"); + public static final MediaType APPLICATION_RTF = register("application/rtf", + "Rich Text Format document"); - public static final MediaType APPLICATION_SDP = register("application/sdp", "Session Description Protocol"); + public static final MediaType APPLICATION_SDP = register("application/sdp", + "Session Description Protocol"); - public static final MediaType APPLICATION_SPARQL_RESULTS_JSON = register("application/sparql-results+json", - "SPARQL Query Results JSON document"); + public static final MediaType APPLICATION_SPARQL_RESULTS_JSON = register( + "application/sparql-results+json", + "SPARQL Query Results JSON document"); - public static final MediaType APPLICATION_SPARQL_RESULTS_XML = register("application/sparql-results+xml", - "SPARQL Query Results XML document"); + public static final MediaType APPLICATION_SPARQL_RESULTS_XML = register( + "application/sparql-results+xml", + "SPARQL Query Results XML document"); - public static final MediaType APPLICATION_SPSS_SAV = register("application/x-spss-sav", "SPSS Data"); + public static final MediaType APPLICATION_SPSS_SAV = register( + "application/x-spss-sav", "SPSS Data"); - public static final MediaType APPLICATION_SPSS_SPS = register("application/x-spss-sps", "SPSS Script Syntax"); + public static final MediaType APPLICATION_SPSS_SPS = register( + "application/x-spss-sps", "SPSS Script Syntax"); - public static final MediaType APPLICATION_STATA_STA = register("application/x-stata", "Stata data file"); + public static final MediaType APPLICATION_STATA_STA = register( + "application/x-stata", "Stata data file"); - public static final MediaType APPLICATION_STUFFIT = register("application/x-stuffit", "Stuffit archive"); + public static final MediaType APPLICATION_STUFFIT = register( + "application/x-stuffit", "Stuffit archive"); - public static final MediaType APPLICATION_TAR = register("application/x-tar", "Tar archive"); + public static final MediaType APPLICATION_TAR = register( + "application/x-tar", "Tar archive"); - public static final MediaType APPLICATION_TEX = register("application/x-tex", "Tex file"); + public static final MediaType APPLICATION_TEX = register( + "application/x-tex", "Tex file"); - public static final MediaType APPLICATION_TROFF_MAN = register("application/x-troff-man", "LaTeX"); + public static final MediaType APPLICATION_TROFF_MAN = register( + "application/x-troff-man", "LaTeX"); - public static final MediaType APPLICATION_VOICEXML = register("application/voicexml+xml", "VoiceXML"); + public static final MediaType APPLICATION_VOICEXML = register( + "application/voicexml+xml", "VoiceXML"); - public static final MediaType APPLICATION_W3C_SCHEMA = register("application/x-xsd+xml", "W3C XML Schema document"); + public static final MediaType APPLICATION_W3C_SCHEMA = register( + "application/x-xsd+xml", "W3C XML Schema document"); - public static final MediaType APPLICATION_W3C_XSLT = register("application/xslt+xml", "W3C XSLT Stylesheet"); + public static final MediaType APPLICATION_W3C_XSLT = register( + "application/xslt+xml", "W3C XSLT Stylesheet"); - public static final MediaType APPLICATION_WADL = register("application/vnd.sun.wadl+xml", - "Web Application Description Language document"); + public static final MediaType APPLICATION_WADL = register( + "application/vnd.sun.wadl+xml", + "Web Application Description Language document"); - public static final MediaType APPLICATION_WORD = register("application/msword", "Microsoft Word document"); + public static final MediaType APPLICATION_WORD = register( + "application/msword", "Microsoft Word document"); - public static final MediaType APPLICATION_WWW_FORM = register("application/x-www-form-urlencoded", - "Web form (URL encoded)"); + public static final MediaType APPLICATION_WWW_FORM = register( + "application/x-www-form-urlencoded", "Web form (URL encoded)"); - public static final MediaType APPLICATION_XHTML = register("application/xhtml+xml", "XHTML document"); + public static final MediaType APPLICATION_XHTML = register( + "application/xhtml+xml", "XHTML document"); - public static final MediaType APPLICATION_XMI = register("application/xmi+xml", "XMI document"); + public static final MediaType APPLICATION_XMI = register( + "application/xmi+xml", "XMI document"); - public static final MediaType APPLICATION_XML = register("application/xml", "XML document"); + public static final MediaType APPLICATION_XML = register("application/xml", + "XML document"); - public static final MediaType APPLICATION_XML_DTD = register("application/xml-dtd", "XML DTD"); + public static final MediaType APPLICATION_XML_DTD = register( + "application/xml-dtd", "XML DTD"); - public static final MediaType APPLICATION_XQUERY = register("application/xquery", "XQuery document"); + public static final MediaType APPLICATION_XQUERY = register( + "application/xquery", "XQuery document"); - public static final MediaType APPLICATION_XUL = register("application/vnd.mozilla.xul+xml", "XUL document"); + public static final MediaType APPLICATION_XUL = register( + "application/vnd.mozilla.xul+xml", "XUL document"); - public static final MediaType APPLICATION_YAML = register("application/x-yaml", "YAML document"); + public static final MediaType APPLICATION_YAML = register( + "application/x-yaml", "YAML document"); - public static final MediaType APPLICATION_ZIP = register("application/zip", "Zip archive"); + public static final MediaType APPLICATION_ZIP = register("application/zip", + "Zip archive"); - public static final MediaType AUDIO_ALL = register("audio/*", "All audios"); + public static final MediaType AUDIO_ALL = register("audio/*", "All audios"); - public static final MediaType AUDIO_BASIC = register("audio/basic", "AU audio"); + public static final MediaType AUDIO_BASIC = register("audio/basic", + "AU audio"); - public static final MediaType AUDIO_MIDI = register("audio/midi", "MIDI audio"); + public static final MediaType AUDIO_MIDI = register("audio/midi", + "MIDI audio"); - public static final MediaType AUDIO_MPEG = register("audio/mpeg", "MPEG audio (MP3)"); + public static final MediaType AUDIO_MPEG = register("audio/mpeg", + "MPEG audio (MP3)"); - public static final MediaType AUDIO_REAL = register("audio/x-pn-realaudio", "Real audio"); + public static final MediaType AUDIO_REAL = register("audio/x-pn-realaudio", + "Real audio"); - public static final MediaType AUDIO_WAV = register("audio/x-wav", "Waveform audio"); + public static final MediaType AUDIO_WAV = register("audio/x-wav", + "Waveform audio"); - public static final MediaType IMAGE_ALL = register("image/*", "All images"); + public static final MediaType IMAGE_ALL = register("image/*", "All images"); - public static final MediaType IMAGE_BMP = register("image/bmp", "Windows bitmap"); + public static final MediaType IMAGE_BMP = register("image/bmp", + "Windows bitmap"); - public static final MediaType IMAGE_GIF = register("image/gif", "GIF image"); + public static final MediaType IMAGE_GIF = register("image/gif", + "GIF image"); - public static final MediaType IMAGE_ICON = register("image/x-icon", "Windows icon (Favicon)"); + public static final MediaType IMAGE_ICON = register("image/x-icon", + "Windows icon (Favicon)"); - public static final MediaType IMAGE_JPEG = register("image/jpeg", "JPEG image"); + public static final MediaType IMAGE_JPEG = register("image/jpeg", + "JPEG image"); - public static final MediaType IMAGE_PNG = register("image/png", "PNG image"); + public static final MediaType IMAGE_PNG = register("image/png", + "PNG image"); - public static final MediaType IMAGE_SVG = register("image/svg+xml", "Scalable Vector Graphics"); + public static final MediaType IMAGE_SVG = register("image/svg+xml", + "Scalable Vector Graphics"); - public static final MediaType IMAGE_TIFF = register("image/tiff", "TIFF image"); + public static final MediaType IMAGE_TIFF = register("image/tiff", + "TIFF image"); - public static final MediaType MESSAGE_ALL = register("message/*", "All messages"); + public static final MediaType MESSAGE_ALL = register("message/*", + "All messages"); - public static final MediaType MESSAGE_HTTP = register("message/http", "HTTP message"); + public static final MediaType MESSAGE_HTTP = register("message/http", + "HTTP message"); - public static final MediaType MODEL_ALL = register("model/*", "All models"); + public static final MediaType MODEL_ALL = register("model/*", "All models"); - public static final MediaType MODEL_VRML = register("model/vrml", "VRML"); + public static final MediaType MODEL_VRML = register("model/vrml", "VRML"); - public static final MediaType MULTIPART_ALL = register("multipart/*", "All multipart data"); + public static final MediaType MULTIPART_ALL = register("multipart/*", + "All multipart data"); - public static final MediaType MULTIPART_FORM_DATA = register("multipart/form-data", "Multipart form data"); + public static final MediaType MULTIPART_FORM_DATA = register( + "multipart/form-data", "Multipart form data"); - public static final MediaType TEXT_ALL = register("text/*", "All texts"); + public static final MediaType TEXT_ALL = register("text/*", "All texts"); - public static final MediaType TEXT_CALENDAR = register("text/calendar", "iCalendar event"); + public static final MediaType TEXT_CALENDAR = register("text/calendar", + "iCalendar event"); - public static final MediaType TEXT_CSS = register("text/css", "CSS stylesheet"); + public static final MediaType TEXT_CSS = register("text/css", + "CSS stylesheet"); - public static final MediaType TEXT_CSV = register("text/csv", "Comma-separated Values"); + public static final MediaType TEXT_CSV = register("text/csv", + "Comma-separated Values"); - public static final MediaType TEXT_DAT = register("text/x-fixed-field", "Fixed-width Values"); + public static final MediaType TEXT_DAT = register("text/x-fixed-field", + "Fixed-width Values"); - public static final MediaType TEXT_HTML = register("text/html", "HTML document"); + public static final MediaType TEXT_HTML = register("text/html", + "HTML document"); - public static final MediaType TEXT_J2ME_APP_DESCRIPTOR = register("text/vnd.sun.j2me.app-descriptor", - "J2ME Application Descriptor"); + public static final MediaType TEXT_J2ME_APP_DESCRIPTOR = register( + "text/vnd.sun.j2me.app-descriptor", "J2ME Application Descriptor"); - public static final MediaType TEXT_JAVASCRIPT = register("text/javascript", "Javascript document"); + public static final MediaType TEXT_JAVASCRIPT = register("text/javascript", + "Javascript document"); - public static final MediaType TEXT_PLAIN = register("text/plain", "Plain text"); + public static final MediaType TEXT_PLAIN = register("text/plain", + "Plain text"); - public static final MediaType TEXT_RDF_N3 = register("text/n3", - "N3 serialized Resource Description Framework document"); + public static final MediaType TEXT_RDF_N3 = register("text/n3", + "N3 serialized Resource Description Framework document"); - public static final MediaType TEXT_RDF_NTRIPLES = register("text/n-triples", - "N-Triples serialized Resource Description Framework document"); + public static final MediaType TEXT_RDF_NTRIPLES = register("text/n-triples", + "N-Triples serialized Resource Description Framework document"); - public static final MediaType TEXT_TSV = register("text/tab-separated-values", "Tab-separated Values"); + public static final MediaType TEXT_TSV = register( + "text/tab-separated-values", "Tab-separated Values"); - public static final MediaType TEXT_TURTLE = register("text/turtle", - "Plain text serialized Resource Description Framework document"); + public static final MediaType TEXT_TURTLE = register("text/turtle", + "Plain text serialized Resource Description Framework document"); - public static final MediaType TEXT_URI_LIST = register("text/uri-list", "List of URIs"); + public static final MediaType TEXT_URI_LIST = register("text/uri-list", + "List of URIs"); - public static final MediaType TEXT_VCARD = register("text/x-vcard", "vCard"); + public static final MediaType TEXT_VCARD = register("text/x-vcard", + "vCard"); - public static final MediaType TEXT_XML = register("text/xml", "XML text"); + public static final MediaType TEXT_XML = register("text/xml", "XML text"); - public static final MediaType TEXT_YAML = register("text/x-yaml", "YAML document"); + public static final MediaType TEXT_YAML = register("text/x-yaml", + "YAML document"); - public static final MediaType VIDEO_ALL = register("video/*", "All videos"); + public static final MediaType VIDEO_ALL = register("video/*", "All videos"); - public static final MediaType VIDEO_AVI = register("video/x-msvideo", "AVI video"); + public static final MediaType VIDEO_AVI = register("video/x-msvideo", + "AVI video"); - public static final MediaType VIDEO_MP4 = register("video/mp4", "MPEG-4 video"); + public static final MediaType VIDEO_MP4 = register("video/mp4", + "MPEG-4 video"); - public static final MediaType VIDEO_MPEG = register("video/mpeg", "MPEG video"); + public static final MediaType VIDEO_MPEG = register("video/mpeg", + "MPEG video"); - public static final MediaType VIDEO_QUICKTIME = register("video/quicktime", "Quicktime video"); + public static final MediaType VIDEO_QUICKTIME = register("video/quicktime", + "Quicktime video"); - public static final MediaType VIDEO_WMV = register("video/x-ms-wmv", "Windows movie"); - - /** - * Returns the first of the most specific media type of the given array of - * {@link MediaType}s. - *

- * Examples: - *

- * - * @param mediaTypes An array of media types. - * @return The most concrete MediaType. - * @throws IllegalArgumentException If the array is null or empty. - */ - public static MediaType getMostSpecific(MediaType... mediaTypes) throws IllegalArgumentException { - if ((mediaTypes == null) || (mediaTypes.length == 0)) { - throw new IllegalArgumentException("You must give at least one MediaType"); - } - - if (mediaTypes.length == 1) { - return mediaTypes[0]; - } - - MediaType mostSpecific = mediaTypes[0]; - - for (int i = 1; i < mediaTypes.length; i++) { - MediaType mediaType = mediaTypes[i]; - - if (mediaType != null) { - if (mediaType.getMainType().equals("*")) { - continue; - } - - if (mostSpecific.getMainType().equals("*")) { - mostSpecific = mediaType; - continue; - } - - if (mostSpecific.getSubType().contains("*")) { - mostSpecific = mediaType; - continue; - } - } - } - - return mostSpecific; - } - - /** - * Returns the known media types map. - * - * @return the known media types map. - */ - private static Map getTypes() { - if (_types == null) { - _types = new HashMap(); - } - return _types; - } - - /** - * Normalizes the specified token. - * - * @param token Token to normalize. - * @return The normalized token. - * @throws IllegalArgumentException if token is not legal. - */ - private static String normalizeToken(String token) { - int length; - char c; - - // Makes sure we're not dealing with a "*" token. - token = token.trim(); - if (token.isEmpty() || "*".equals(token)) - return "*"; - - // Makes sure the token is RFC compliant. - length = token.length(); - for (int i = 0; i < length; i++) { - c = token.charAt(i); - if (c <= 32 || c >= 127 || _TSPECIALS.indexOf(c) != -1) - throw new IllegalArgumentException("Illegal token: " + token); - } - - return token; - } - - /** - * Normalizes the specified media type. - * - * @param name The name of the type to normalize. - * @param parameters The parameters of the type to normalize. - * @return The normalized type. - */ - private static String normalizeType(String name, Series parameters) { - int slashIndex; - int colonIndex; - String mainType; - String subType; - StringBuilder params = null; - - // Ignore null names (backward compatibility). - if (name == null) - return null; - - // Check presence of parameters - if ((colonIndex = name.indexOf(';')) != -1) { - params = new StringBuilder(name.substring(colonIndex)); - name = name.substring(0, colonIndex); - } - - // No main / sub separator, assumes name/*. - if ((slashIndex = name.indexOf('/')) == -1) { - mainType = normalizeToken(name); - subType = "*"; - } else { - // Normalizes the main and sub types. - mainType = normalizeToken(name.substring(0, slashIndex)); - subType = normalizeToken(name.substring(slashIndex + 1)); - } - - // Merge parameters taken from the name and the method argument. - if (parameters != null && !parameters.isEmpty()) { - try { - if (params == null) { - params = new StringBuilder(); - } - HeaderWriter hw = new HeaderWriter() { - @Override - public HeaderWriter append(Parameter value) { - return appendExtension(value); - } - }; - for (int i = 0; i < parameters.size(); i++) { - Parameter p = parameters.get(i); - hw.appendParameterSeparator(); - hw.appendSpace(); - hw.append(p); - } - params.append(hw.toString()); - hw.close(); - } catch (IOException e) { - Context.getCurrentLogger().log(Level.INFO, "Unable to parse the media type parameter", e); - } - } - - return (params == null) ? mainType + '/' + subType : mainType + '/' + subType + params.toString(); - } - - /** - * Register a media type as a known type that can later be retrieved using - * {@link #valueOf(String)}. If the type already exists, the existing type is - * returned, otherwise a new instance is created. - * - * @param name The name. - * @param description The description. - * @return The registered media type - */ - public static synchronized MediaType register(String name, String description) { - - if (!getTypes().containsKey(name)) { - final MediaType type = new MediaType(name, description); - getTypes().put(name, type); - } - - return getTypes().get(name); - } - - /** - * Returns the media type associated to a name. If an existing constant exists - * then it is returned, otherwise a new instance is created. - * - * @param name The name. - * @return The associated media type. - */ - public static MediaType valueOf(String name) { - MediaType result = null; - - if (!StringUtils.isNullOrEmpty(name)) { - result = getTypes().get(name); - if (result == null) { - result = new MediaType(name); - } - } - - return result; - } - - /** The list of parameters. */ - private volatile Series parameters; - - /** - * Constructor. - * - * @param name The name. - */ - public MediaType(String name) { - this(name, null, "Media type or range of media types"); - } - - /** - * Constructor. - * - * @param name The name. - * @param parameters The list of parameters. - */ - public MediaType(String name, Series parameters) { - this(name, parameters, "Media type or range of media types"); - } - - /** - * Constructor. - * - * @param name The name. - * @param parameters The list of parameters. - * @param description The description. - */ - @SuppressWarnings("unchecked") - public MediaType(String name, Series parameters, String description) { - super(normalizeType(name, parameters), description); - - if (parameters != null) { - this.parameters = (Series) Series.unmodifiableSeries(parameters); - } - } - - /** - * Constructor. - * - * @param name The name. - * @param description The description. - */ - public MediaType(String name, String description) { - this(name, null, description); - } - - /** {@inheritDoc} */ - @Override - public boolean equals(Object obj) { - return equals(obj, false); - } - - /** - * Test the equality of two media types, with the possibility to ignore the - * parameters. - * - * @param obj The object to compare to. - * @param ignoreParameters Indicates if parameters should be ignored during - * comparison. - * @return True if both media types are equal. - */ - public boolean equals(Object obj, boolean ignoreParameters) { - boolean result = (obj == this); - - // if obj == this no need to go further - if (!result) { - // if obj isn't a mediatype or is null don't evaluate further - if (obj instanceof MediaType) { - final MediaType that = (MediaType) obj; - if (getMainType().equals(that.getMainType()) && getSubType().equals(that.getSubType())) { - result = ignoreParameters || getParameters().equals(that.getParameters()); - } - } - } - - return result; - } - - /** - * Returns the main type. - * - * @return The main type. - */ - public String getMainType() { - String result = null; - - if (getName() != null) { - int index = getName().indexOf('/'); - - // Some clients appear to use name types without subtypes - if (index == -1) { - index = getName().indexOf(';'); - } - - if (index == -1) { - result = getName(); - } else { - result = getName().substring(0, index); - } - } - - return result; - } - - /** - * Returns the unmodifiable list of parameters corresponding to subtype - * modifiers. Creates a new instance if no one has been set. - * - * @return The list of parameters. - */ - @SuppressWarnings("unchecked") - public Series getParameters() { - // Lazy initialization with double-check. - Series p = this.parameters; - if (p == null) { - synchronized (this) { - p = this.parameters; - if (p == null) { - Series params = null; - - if (getName() != null) { - int index = getName().indexOf(';'); - - if (index != -1) { - params = new Form(getName().substring(index + 1).trim(), ';'); - } - } - - if (params == null) { - params = new Series(Parameter.class); - } - - this.parameters = p = (Series) Series.unmodifiableSeries(params); - } - } - } - return p; - } - - /** - * {@inheritDoc}
- * In case the media type has parameters, this method returns the concatenation - * of the main type and the subtype. If the subtype is not equal to "*", it - * returns the concatenation of the main type and "*". Otherwise, it returns - * either the {@link #ALL} media type if it is already the {@link #ALL} media - * type, or null. - */ - @Override - public MediaType getParent() { - MediaType result = null; - - if (getParameters().size() > 0) { - result = MediaType.valueOf(getMainType() + "/" + getSubType()); - } else { - if (getSubType().equals("*")) { - result = equals(ALL) ? null : ALL; - } else { - result = MediaType.valueOf(getMainType() + "/*"); - } - } - - return result; - } - - /** - * Returns the sub-type. - * - * @return The sub-type. - */ - public String getSubType() { - String result = null; - - if (getName() != null) { - final int slash = getName().indexOf('/'); - - if (slash == -1) { - // No subtype found, assume that all subtypes are accepted - result = "*"; - } else { - final int separator = getName().indexOf(';'); - if (separator == -1) { - result = getName().substring(slash + 1); - } else { - result = getName().substring(slash + 1, separator); - } - } - } - - return result; - } - - /** {@inheritDoc} */ - @Override - public int hashCode() { - return SystemUtils.hashCode(super.hashCode(), getParameters()); - } - - /** - * Indicates if a given media type is included in the current one @see - * {@link #includes(Metadata, boolean)}. It ignores the parameters. - * - * @param included The media type to test for inclusion. - * @return True if the given media type is included in the current one. - * @see #isCompatible(Metadata) - */ - @Override - public boolean includes(Metadata included) { - return includes(included, true); - } - - /** - * Indicates if a given media type is included in the current one @see - * {@link #includes(Metadata, boolean)}. The test is true if both types are - * equal or if the given media type is within the range of the current one. For - * example, ALL includes all media types. Parameters are ignored for this - * comparison. A null media type is considered as included into the current one. - * It ignores the parameters. - *

- * Examples: - *

    - *
  • TEXT_ALL.includes(TEXT_PLAIN) returns true
  • - *
  • TEXT_PLAIN.includes(TEXT_ALL) returns false
  • - *
- * - * @param included The media type to test for inclusion. - * @return True if the given media type is included in the current one. - * @see #isCompatible(Metadata) - */ - public boolean includes(Metadata included, boolean ignoreParameters) { - boolean result = equals(ALL) || equals(included); - - if (!result && (included instanceof MediaType)) { - MediaType includedMediaType = (MediaType) included; - - if (getMainType().equals(includedMediaType.getMainType())) { - // Both media types are different - if (getSubType().equals(includedMediaType.getSubType())) { - if (ignoreParameters) { - result = true; - } else { - // Check parameters: - // Media type A includes media type B if for each param - // name/value pair in A, B contains the same name/value. - result = true; - for (int i = 0; result && i < getParameters().size(); i++) { - Parameter param = getParameters().get(i); - Parameter includedParam = includedMediaType.getParameters().getFirst(param.getName()); - - // If there was no param with the same name, or the - // param with the same name had a different value, - // then no match. - result = (includedParam != null && param.getValue().equals(includedParam.getValue())); - } - } - } else if (getSubType().equals("*")) { - result = true; - } else if (getSubType().startsWith("*+") - && includedMediaType.getSubType().endsWith(getSubType().substring(2))) { - result = true; - } - } - } - - return result; - } - - /** - * Checks if the current media type is concrete. A media type is concrete if - * neither the main type nor the sub-type are equal to "*". - * - * @return True if this media type is concrete. - */ - public boolean isConcrete() { - return !getName().contains("*"); - } + public static final MediaType VIDEO_WMV = register("video/x-ms-wmv", + "Windows movie"); + + /** + * Returns the first of the most specific media type of the given array of + * {@link MediaType}s. + *

+ * Examples: + *

    + *
  • "text/plain" is more specific than "text/*" or "image/*"
  • + *
  • "text/html" is same specific as "application/pdf" or "image/jpg"
  • + *
  • "text/*" is same specific than "application/*" or "image/*"
  • + *
  • "*/*" is the most unspecific MediaType
  • + *
+ * + * @param mediaTypes An array of media types. + * @return The most concrete MediaType. + * @throws IllegalArgumentException If the array is null or empty. + */ + public static MediaType getMostSpecific(MediaType... mediaTypes) + throws IllegalArgumentException { + if ((mediaTypes == null) || (mediaTypes.length == 0)) { + throw new IllegalArgumentException( + "You must give at least one MediaType"); + } + + if (mediaTypes.length == 1) { + return mediaTypes[0]; + } + + MediaType mostSpecific = mediaTypes[0]; + + for (int i = 1; i < mediaTypes.length; i++) { + MediaType mediaType = mediaTypes[i]; + + if (mediaType != null) { + if (mediaType.getMainType().equals("*")) { + continue; + } + + if (mostSpecific.getMainType().equals("*")) { + mostSpecific = mediaType; + continue; + } + + if (mostSpecific.getSubType().contains("*")) { + mostSpecific = mediaType; + continue; + } + } + } + + return mostSpecific; + } + + /** + * Returns the known media types map. + * + * @return the known media types map. + */ + private static Map getTypes() { + if (_types == null) { + _types = new HashMap(); + } + return _types; + } + + /** + * Normalizes the specified token. + * + * @param token Token to normalize. + * @return The normalized token. + * @throws IllegalArgumentException if token is not legal. + */ + private static String normalizeToken(String token) { + int length; + char c; + + // Makes sure we're not dealing with a "*" token. + token = token.trim(); + if (token.isEmpty() || "*".equals(token)) + return "*"; + + // Makes sure the token is RFC compliant. + length = token.length(); + for (int i = 0; i < length; i++) { + c = token.charAt(i); + if (c <= 32 || c >= 127 || _TSPECIALS.indexOf(c) != -1) + throw new IllegalArgumentException("Illegal token: " + token); + } + + return token; + } + + /** + * Normalizes the specified media type. + * + * @param name The name of the type to normalize. + * @param parameters The parameters of the type to normalize. + * @return The normalized type. + */ + private static String normalizeType(String name, + Series parameters) { + int slashIndex; + int colonIndex; + String mainType; + String subType; + StringBuilder params = null; + + // Ignore null names (backward compatibility). + if (name == null) + return null; + + // Check presence of parameters + if ((colonIndex = name.indexOf(';')) != -1) { + params = new StringBuilder(name.substring(colonIndex)); + name = name.substring(0, colonIndex); + } + + // No main / sub separator, assumes name/*. + if ((slashIndex = name.indexOf('/')) == -1) { + mainType = normalizeToken(name); + subType = "*"; + } else { + // Normalizes the main and sub types. + mainType = normalizeToken(name.substring(0, slashIndex)); + subType = normalizeToken(name.substring(slashIndex + 1)); + } + + // Merge parameters taken from the name and the method argument. + if (parameters != null && !parameters.isEmpty()) { + try { + if (params == null) { + params = new StringBuilder(); + } + HeaderWriter hw = new HeaderWriter() { + @Override + public HeaderWriter append(Parameter value) { + return appendExtension(value); + } + }; + for (int i = 0; i < parameters.size(); i++) { + Parameter p = parameters.get(i); + hw.appendParameterSeparator(); + hw.appendSpace(); + hw.append(p); + } + params.append(hw.toString()); + hw.close(); + } catch (IOException e) { + Context.getCurrentLogger().log(Level.INFO, + "Unable to parse the media type parameter", e); + } + } + + return (params == null) ? mainType + '/' + subType + : mainType + '/' + subType + params.toString(); + } + + /** + * Register a media type as a known type that can later be retrieved using + * {@link #valueOf(String)}. If the type already exists, the existing type + * is returned, otherwise a new instance is created. + * + * @param name The name. + * @param description The description. + * @return The registered media type + */ + public static synchronized MediaType register(String name, + String description) { + + if (!getTypes().containsKey(name)) { + final MediaType type = new MediaType(name, description); + getTypes().put(name, type); + } + + return getTypes().get(name); + } + + /** + * Returns the media type associated to a name. If an existing constant + * exists then it is returned, otherwise a new instance is created. + * + * @param name The name. + * @return The associated media type. + */ + public static MediaType valueOf(String name) { + MediaType result = null; + + if (!StringUtils.isNullOrEmpty(name)) { + result = getTypes().get(name); + if (result == null) { + result = new MediaType(name); + } + } + + return result; + } + + /** The list of parameters. */ + private volatile Series parameters; + + /** + * Constructor that clones an original media type. + * + * @param original The original media type to clone. + * @param paramName The name of the unique parameter to set. + * @param paramValue The value of the unique parameter to set. + */ + public MediaType(MediaType original, String paramName, String paramValue) { + this(original, new Parameter(paramName, paramValue)); + } + + /** + * Constructor that clones an original media type. + * + * @param original The original media type to clone. + * @param parameter The unique parameter to set. + */ + public MediaType(MediaType original, Parameter parameter) { + this(original, parameter == null ? null : parameter.createSeries()); + } + + /** + * Constructor that clones an original media type by extracting its parent + * media type then adding a new set of parameters. + * + * @param original The original media type to clone. + * @param parameters The list of parameters to set. + */ + public MediaType(MediaType original, Series parameters) { + this((original == null) ? null : original.getName(), parameters, + (original == null) ? null : original.getDescription()); + } + + /** + * Constructor. + * + * @param name The name. + */ + public MediaType(String name) { + this(name, null, "Media type or range of media types"); + } + + /** + * Constructor. + * + * @param name The name. + * @param parameters The list of parameters. + */ + public MediaType(String name, Series parameters) { + this(name, parameters, "Media type or range of media types"); + } + + /** + * Constructor. + * + * @param name The name. + * @param parameters The list of parameters. + * @param description The description. + */ + @SuppressWarnings("unchecked") + public MediaType(String name, Series parameters, + String description) { + super(normalizeType(name, parameters), description); + + if (parameters != null) { + this.parameters = (Series) Series + .unmodifiableSeries(parameters); + } + } + + /** + * Constructor. + * + * @param name The name. + * @param description The description. + */ + public MediaType(String name, String description) { + this(name, null, description); + } + + /** {@inheritDoc} */ + @Override + public boolean equals(Object obj) { + return equals(obj, false); + } + + /** + * Test the equality of two media types, with the possibility to ignore the + * parameters. + * + * @param obj The object to compare to. + * @param ignoreParameters Indicates if parameters should be ignored during + * comparison. + * @return True if both media types are equal. + */ + public boolean equals(Object obj, boolean ignoreParameters) { + boolean result = (obj == this); + + // if obj == this no need to go further + if (!result) { + // if obj isn't a mediatype or is null don't evaluate further + if (obj instanceof MediaType) { + final MediaType that = (MediaType) obj; + if (getMainType().equals(that.getMainType()) + && getSubType().equals(that.getSubType())) { + result = ignoreParameters + || getParameters().equals(that.getParameters()); + } + } + } + + return result; + } + + /** + * Returns the main type. + * + * @return The main type. + */ + public String getMainType() { + String result = null; + + if (getName() != null) { + int index = getName().indexOf('/'); + + // Some clients appear to use name types without subtypes + if (index == -1) { + index = getName().indexOf(';'); + } + + if (index == -1) { + result = getName(); + } else { + result = getName().substring(0, index); + } + } + + return result; + } + + /** + * Returns the unmodifiable list of parameters corresponding to subtype + * modifiers. Creates a new instance if no one has been set. + * + * @return The list of parameters. + */ + @SuppressWarnings("unchecked") + public Series getParameters() { + // Lazy initialization with double-check. + Series p = this.parameters; + if (p == null) { + synchronized (this) { + p = this.parameters; + if (p == null) { + Series params = null; + + if (getName() != null) { + int index = getName().indexOf(';'); + + if (index != -1) { + params = new Form( + getName().substring(index + 1).trim(), ';'); + } + } + + if (params == null) { + params = new Series(Parameter.class); + } + + this.parameters = p = (Series) Series + .unmodifiableSeries(params); + } + } + } + return p; + } + + /** + * {@inheritDoc}
+ * In case the media type has parameters, this method returns the + * concatenation of the main type and the subtype. If the subtype is not + * equal to "*", it returns the concatenation of the main type and "*". + * Otherwise, it returns either the {@link #ALL} media type if it is already + * the {@link #ALL} media type, or null. + */ + @Override + public MediaType getParent() { + MediaType result = null; + + if (getParameters().size() > 0) { + result = MediaType.valueOf(getMainType() + "/" + getSubType()); + } else { + if (getSubType().equals("*")) { + result = equals(ALL) ? null : ALL; + } else { + result = MediaType.valueOf(getMainType() + "/*"); + } + } + + return result; + } + + /** + * Returns the sub-type. + * + * @return The sub-type. + */ + public String getSubType() { + String result = null; + + if (getName() != null) { + final int slash = getName().indexOf('/'); + + if (slash == -1) { + // No subtype found, assume that all subtypes are accepted + result = "*"; + } else { + final int separator = getName().indexOf(';'); + if (separator == -1) { + result = getName().substring(slash + 1); + } else { + result = getName().substring(slash + 1, separator); + } + } + } + + return result; + } + + /** {@inheritDoc} */ + @Override + public int hashCode() { + return SystemUtils.hashCode(super.hashCode(), getParameters()); + } + + /** + * Indicates if a given media type is included in the current one @see + * {@link #includes(Metadata, boolean)}. It ignores the parameters. + * + * @param included The media type to test for inclusion. + * @return True if the given media type is included in the current one. + * @see #isCompatible(Metadata) + */ + @Override + public boolean includes(Metadata included) { + return includes(included, true); + } + + /** + * Indicates if a given media type is included in the current one @see + * {@link #includes(Metadata, boolean)}. The test is true if both types are + * equal or if the given media type is within the range of the current one. + * For example, ALL includes all media types. Parameters are ignored for + * this comparison. A null media type is considered as included into the + * current one. It ignores the parameters. + *

+ * Examples: + *

    + *
  • TEXT_ALL.includes(TEXT_PLAIN) returns true
  • + *
  • TEXT_PLAIN.includes(TEXT_ALL) returns false
  • + *
+ * + * @param included The media type to test for inclusion. + * @return True if the given media type is included in the current one. + * @see #isCompatible(Metadata) + */ + public boolean includes(Metadata included, boolean ignoreParameters) { + boolean result = equals(ALL) || equals(included); + + if (!result && (included instanceof MediaType)) { + MediaType includedMediaType = (MediaType) included; + + if (getMainType().equals(includedMediaType.getMainType())) { + // Both media types are different + if (getSubType().equals(includedMediaType.getSubType())) { + if (ignoreParameters) { + result = true; + } else { + // Check parameters: + // Media type A includes media type B if for each param + // name/value pair in A, B contains the same name/value. + result = true; + for (int i = 0; result + && i < getParameters().size(); i++) { + Parameter param = getParameters().get(i); + Parameter includedParam = includedMediaType + .getParameters().getFirst(param.getName()); + + // If there was no param with the same name, or the + // param with the same name had a different value, + // then no match. + result = (includedParam != null && param.getValue() + .equals(includedParam.getValue())); + } + } + } else if (getSubType().equals("*")) { + result = true; + } else if (getSubType().startsWith("*+") && includedMediaType + .getSubType().endsWith(getSubType().substring(2))) { + result = true; + } + } + } + + return result; + } + + /** + * Checks if the current media type is concrete. A media type is concrete if + * neither the main type nor the sub-type are equal to "*". + * + * @return True if this media type is concrete. + */ + public boolean isConcrete() { + return !getName().contains("*"); + } } diff --git a/org.restlet.java/org.restlet/src/main/java/org/restlet/data/Parameter.java b/org.restlet.java/org.restlet/src/main/java/org/restlet/data/Parameter.java index dcf616e011..743c35942f 100644 --- a/org.restlet.java/org.restlet/src/main/java/org/restlet/data/Parameter.java +++ b/org.restlet.java/org.restlet/src/main/java/org/restlet/data/Parameter.java @@ -11,6 +11,7 @@ import org.restlet.engine.util.SystemUtils; import org.restlet.util.NamedValue; +import org.restlet.util.Series; import java.io.IOException; import java.util.Objects; @@ -29,6 +30,19 @@ public class Parameter implements Comparable, NamedValue { /** The second object. */ private volatile String value; + /** + * Creates a series that includes the current parameter as the initial + * entry. + * + * @return A series that includes the current parameter as the initial + * entry. + */ + public Series createSeries() { + Series result = new Form(); + result.add(this); + return result; + } + /** * Creates a parameter. * diff --git a/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java b/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java index aa0932e138..8d8737fec5 100644 --- a/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java +++ b/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/header/ContentType.java @@ -11,6 +11,7 @@ import org.restlet.data.CharacterSet; import org.restlet.data.MediaType; +import org.restlet.data.Parameter; import org.restlet.representation.Representation; import java.io.IOException; @@ -50,15 +51,29 @@ public static MediaType readMediaType(String contentType) { * @return The HTTP "Content-Type" header. */ public static String writeHeader(MediaType mediaType, CharacterSet characterSet) { - String result = mediaType.toString(); + StringBuilder result = new StringBuilder(mediaType.toString()); // Specify the character set parameter if required + // TODO I wonder if the given parameter "characterSet" overrides the mediaType's charset'? + /* if ((mediaType.getParameters().getFirstValue("charset") == null) && (characterSet != null)) { result = result + "; charset=" + characterSet.getName(); } + */ - return result; - + for (Parameter param : mediaType.getParameters()) { + if (param == null) { + continue; + } + if (characterSet != null && param.getName().equals("charset")) { + // TODO I wonder if the given parameter "characterSet" overrides the mediaType's charset'? + result.append("; ").append(param.getName()).append("=").append(characterSet); + } else { + result.append("; ").append(param.getName()).append("=").append(param.getValue()); + } + } + + return result.toString(); } /** diff --git a/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java b/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java index 74466ab480..4a884e54ca 100644 --- a/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java +++ b/org.restlet.java/org.restlet/src/main/java/org/restlet/engine/security/HttpBasicHelper.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Base64; +import java.util.Objects; import java.util.logging.Level; /** @@ -41,12 +42,23 @@ public HttpBasicHelper() { @Override public void formatRequest(ChallengeWriter cw, ChallengeRequest challenge, Response response, Series
httpHeaders) throws IOException { - if (challenge.getRealm() != null) { - cw.appendQuotedChallengeParameter("realm", challenge.getRealm()); + String realm = challenge.getRealm(); + String charset = challenge.getParameters().getFirstValue("charset"); + + if (realm != null) { + cw.appendQuotedChallengeParameter("realm", realm); } else { getLogger() .warning("The realm directive is required for all authentication schemes that issue a challenge."); } + + if (charset != null) { + if ("UTF-8".equalsIgnoreCase(charset)) { + cw.appendQuotedChallengeParameter("charset", "UTF-8"); + } else { + getLogger().warning("The \"charset\" parameter must be \"UTF-8\" per RFC 7617."); + } + } } @Override @@ -56,12 +68,25 @@ public void formatResponse(ChallengeWriter cw, ChallengeResponse challenge, Requ if (challenge == null) { throw new RuntimeException("No challenge provided, unable to encode credentials"); } else { + String charset = challenge.getParameters().getFirstValue("charset"); + + if (charset != null) { + if ("UTF-8".equalsIgnoreCase(charset)) { + charset = "UTF-8"; + } else { + getLogger().warning( + "The \"charset\" parameter must be \"UTF-8\" per RFC 7617. Using \"ISO-8859-1\" instead."); + charset = "ISO-8859-1"; + } + } else { + charset = "ISO-8859-1"; + } + CharArrayWriter credentials = new CharArrayWriter(); credentials.write(challenge.getIdentifier()); credentials.write(":"); credentials.write(challenge.getSecret()); - cw.append(Base64.getEncoder() - .encodeToString(IoUtils.toByteArray(credentials.toCharArray(), "ISO-8859-1"))); + cw.append(Base64.getEncoder().encodeToString(IoUtils.toByteArray(credentials.toCharArray(), charset))); } } catch (UnsupportedEncodingException e) { throw new RuntimeException("Unsupported encoding, unable to encode credentials"); @@ -105,14 +130,29 @@ public void parseRequest(ChallengeRequest challenge, Response response, Series httpHeaders) { - try { - byte[] credentialsEncoded = Base64.getDecoder().decode(challenge.getRawValue()); + if (challenge.getRawValue() == null) { + getLogger().info("Cannot decode credentials: " + challenge.getRawValue()); + return; + } - if (credentialsEncoded == null) { - getLogger().info("Cannot decode credentials: " + challenge.getRawValue()); + try { + String charset = challenge.getParameters().getFirstValue("charset"); + + if (charset != null) { + if ("UTF-8".equalsIgnoreCase(charset)) { + charset = "UTF-8"; + } else { + getLogger().warning( + "The \"charset\" parameter must be \"UTF-8\" per RFC 7617. Using \"ISO-8859-1\" instead."); + charset = "ISO-8859-1"; + } + } else { + charset = "ISO-8859-1"; } - String credentials = new String(credentialsEncoded, "ISO-8859-1"); + byte[] credentialsEncoded = Base64.getDecoder().decode(challenge.getRawValue()); + + String credentials = new String(credentialsEncoded, charset); int separator = credentials.indexOf(':'); if (separator == -1) { diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java index 275d81a463..273d81cf9d 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/data/FileClientTestCase.java @@ -11,10 +11,8 @@ import org.junit.jupiter.api.Test; import org.restlet.engine.Engine; -import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.resource.ClientResource; -import org.restlet.resource.ResourceException; import java.io.File; import java.io.IOException; diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java index f29848cfef..4e71bb98dd 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/data/RiapConnectorsTestCase.java @@ -11,8 +11,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.restlet.*; @@ -24,7 +22,6 @@ import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; /** * Unit test case for the RIAP Internal routing protocol. diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java index 7e622ffd32..c116b96517 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/header/HeaderTestCase.java @@ -9,18 +9,15 @@ package org.restlet.engine.header; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.restlet.data.ClientInfo; import org.restlet.data.Encoding; import org.restlet.data.Header; import org.restlet.data.MediaType; import org.restlet.engine.util.DateUtils; -import org.restlet.representation.Representation; import java.io.IOException; import java.util.ArrayList; -import java.util.Base64; import java.util.Date; import java.util.List; diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java index f1003fd7dc..e62f890faf 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/engine/util/AlphaNumericComparatorTestCase.java @@ -14,7 +14,6 @@ import org.restlet.resource.Directory; import java.util.ArrayList; -import java.util.Collections; import java.util.LinkedList; import java.util.List; diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java index c4b0a7725f..3badf2983b 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractAnnotatedResourceTestCase.java @@ -13,7 +13,6 @@ import org.junit.jupiter.api.BeforeEach; import org.restlet.engine.Engine; import org.restlet.representation.ObjectRepresentation; -import org.restlet.resource.ClientResource; /** * Test the annotated resources, client and server sides. diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java index b859f17e04..4035a8512c 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/AbstractGenericAnnotatedServerResource.java @@ -9,9 +9,6 @@ package org.restlet.resource; -import org.restlet.resource.Post; -import org.restlet.resource.ServerResource; - public abstract class AbstractGenericAnnotatedServerResource extends ServerResource { @Post diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException01.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException01.java index f9e79d5749..620bf91493 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException01.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException01.java @@ -11,8 +11,6 @@ import java.util.Date; -import org.restlet.resource.Status; - @Status(value = 400, serialize = false) public class MyException01 extends Throwable { diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException02.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException02.java index 8b431b9b3b..5ff4e2055d 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException02.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyException02.java @@ -9,8 +9,6 @@ package org.restlet.resource; -import org.restlet.resource.Status; - @Status(value = 400) public class MyException02 extends Throwable { diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource02.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource02.java index edeef7f103..786b164cb4 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource02.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource02.java @@ -12,8 +12,6 @@ import org.restlet.data.MediaType; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; -import org.restlet.resource.Get; -import org.restlet.resource.ServerResource; public class MyResource02 extends ServerResource { diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource04.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource04.java index cadd4de7ec..2c0240b31f 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource04.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource04.java @@ -9,9 +9,6 @@ package org.restlet.resource; -import org.restlet.resource.Get; -import org.restlet.resource.ServerResource; - public class MyResource04 extends ServerResource { @Get("xml") diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource05.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource05.java index d87341832b..a036ccef83 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource05.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource05.java @@ -9,9 +9,6 @@ package org.restlet.resource; -import org.restlet.resource.Post; -import org.restlet.resource.ServerResource; - public class MyResource05 extends ServerResource { @Post("txt:xml") diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource06.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource06.java index c777fc8f79..206681092a 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource06.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource06.java @@ -11,9 +11,6 @@ import java.io.IOException; -import org.restlet.resource.Post; -import org.restlet.resource.ServerResource; - public class MyResource06 extends ServerResource { @Post("txt:xml") diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource07.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource07.java index c927c7114b..f38a0f867a 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource07.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource07.java @@ -9,9 +9,6 @@ package org.restlet.resource; -import org.restlet.resource.Post; -import org.restlet.resource.ServerResource; - public class MyResource07 extends ServerResource { @Post("json:xml") diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource08.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource08.java index 350d0d729d..cb0eb69e41 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource08.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource08.java @@ -9,9 +9,6 @@ package org.restlet.resource; -import org.restlet.resource.Post; -import org.restlet.resource.ServerResource; - public class MyResource08 extends ServerResource { @Post("xml|json:xml|json") diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource12.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource12.java index 8d8574a1b4..9ab5705ec1 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource12.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource12.java @@ -10,8 +10,6 @@ package org.restlet.resource; import org.restlet.data.Form; -import org.restlet.resource.Get; -import org.restlet.resource.Put; /** * Sample annotated interface. diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource17.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource17.java index 85381289bb..2294da3ab3 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource17.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/resource/MyResource17.java @@ -9,8 +9,6 @@ package org.restlet.resource; -import org.restlet.resource.Post; - public interface MyResource17 { @Post diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java index 8c723b9cbb..e88b04ce5c 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/routing/AbstractFilterTestCase.java @@ -9,7 +9,6 @@ package org.restlet.routing; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.restlet.Request; import org.restlet.Response; diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java index 6d886907c4..328c2ab542 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/security/HttpBasicTestCase.java @@ -9,15 +9,32 @@ package org.restlet.security; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.restlet.*; -import org.restlet.data.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.params.provider.Arguments.arguments; import java.util.Arrays; +import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.assertEquals; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.restlet.Application; +import org.restlet.Client; +import org.restlet.Component; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.Restlet; +import org.restlet.Server; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Protocol; +import org.restlet.data.Status; /** * Restlet unit tests for HTTP Basic authentication client/server. @@ -27,244 +44,145 @@ */ public class HttpBasicTestCase { - public static class AuthenticatedRestlet extends Restlet { - @Override - public void handle(Request request, Response response) { - response.setEntity(AUTHENTICATED_MSG, MediaType.TEXT_PLAIN); - } - } - - public static class TestVerifier extends MapVerifier { - public TestVerifier() { - getLocalSecrets().put(SHORT_USERNAME, SHORT_PASSWORD.toCharArray()); - getLocalSecrets().put(LONG_USERNAME, LONG_PASSWORD.toCharArray()); - } - - @Override - public int verify(String identifier, char[] inputSecret) { - // NOTE: Allocating Strings are not really secure treatment of passwords - String almostSecret = new String(inputSecret); - - try { - return super.verify(identifier, inputSecret); - } finally { - // Clear secret from memory as soon as possible (This is better - // treatment, but useless due to our almostSecret - // copy) - Arrays.fill(inputSecret, '\000'); - } - } - } - - public static final String AUTHENTICATED_MSG = "You are authenticated"; - - public static final String LONG_PASSWORD = "thisLongPasswordIsExtremelySecure"; - - public static final String LONG_USERNAME = "aVeryLongUsernameIsIndeedRequiredForThisTest"; - - public static final String SHORT_PASSWORD = "pw15"; - - public static final String SHORT_USERNAME = "user13"; - - public static final String WRONG_USERNAME = "wrongUser"; - - private ChallengeAuthenticator authenticator; - - private Component component; - - private String uri; - - private MapVerifier verifier; - - @Test - public void guardLong() { - assertEquals( - Verifier.RESULT_VALID, - this.verifier.verify(LONG_USERNAME, LONG_PASSWORD.toCharArray()), - "Didn't authenticate short user/pwd" - ); - } - - @Test - public void guardLongWrong() { - assertEquals( - Verifier.RESULT_INVALID, - this.verifier.verify(LONG_USERNAME, SHORT_PASSWORD.toCharArray()), - "Authenticated long username with wrong password" - ); - } - - // Test our guard.checkSecret() stand-alone - @Test - public void guardShort() { - assertEquals( - Verifier.RESULT_VALID, - this.verifier.verify(SHORT_USERNAME, SHORT_PASSWORD.toCharArray()), - "Didn't authenticate short user/pwd" - ); - } - - @Test - public void guardShortWrong() { - assertEquals( - Verifier.RESULT_INVALID, - this.verifier.verify(SHORT_USERNAME, LONG_PASSWORD.toCharArray()), - "Authenticated short username with wrong password" - ); - } - - @Test - public void guardWrongUser() { - assertEquals( - Verifier.RESULT_INVALID, - this.verifier.verify(WRONG_USERNAME, SHORT_PASSWORD.toCharArray()), - "Authenticated wrong username" - ); - } - - public void HttpBasicLong() throws Exception { - Request request = new Request(Method.GET, this.uri); - Client client = new Client(Protocol.HTTP); - - ChallengeResponse authentication = new ChallengeResponse( - ChallengeScheme.HTTP_BASIC, LONG_USERNAME, LONG_PASSWORD); - request.setChallengeResponse(authentication); - - final Response response = client.handle(request); - assertEquals( - Status.SUCCESS_OK, response.getStatus(), - "Long username did not return 200 OK" - ); - assertEquals(AUTHENTICATED_MSG, response.getEntity().getText()); - - client.stop(); - } - - public void HttpBasicLongWrong() throws Exception { - final Request request = new Request(Method.GET, this.uri); - final Client client = new Client(Protocol.HTTP); - - final ChallengeResponse authentication = new ChallengeResponse( - ChallengeScheme.HTTP_BASIC, LONG_USERNAME, SHORT_PASSWORD); - request.setChallengeResponse(authentication); - - final Response response = client.handle(request); - - assertEquals( - Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus(), - "Long username w/wrong pw did not throw 403" - ); - - client.stop(); - } - - // Test various HTTP Basic auth connections - public void HttpBasicNone() throws Exception { - final Request request = new Request(Method.GET, this.uri); - final Client client = new Client(Protocol.HTTP); - final Response response = client.handle(request); - assertEquals( - Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus(), - "No user did not throw 401" - ); - client.stop(); - } - - public void HttpBasicShort() throws Exception { - final Request request = new Request(Method.GET, this.uri); - final Client client = new Client(Protocol.HTTP); - - final ChallengeResponse authentication = new ChallengeResponse( - ChallengeScheme.HTTP_BASIC, SHORT_USERNAME, SHORT_PASSWORD); - request.setChallengeResponse(authentication); - - final Response response = client.handle(request); - assertEquals( - Status.SUCCESS_OK, response.getStatus(), - "Short username did not return 200 OK" - ); - assertEquals(AUTHENTICATED_MSG, response.getEntity().getText()); - - client.stop(); - } - - public void HttpBasicShortWrong() throws Exception { - final Request request = new Request(Method.GET, this.uri); - final Client client = new Client(Protocol.HTTP); - - final ChallengeResponse authentication = new ChallengeResponse( - ChallengeScheme.HTTP_BASIC, SHORT_USERNAME, LONG_PASSWORD); - request.setChallengeResponse(authentication); - - final Response response = client.handle(request); - - assertEquals( - Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus(), - "Short username did not throw 401" - ); - - client.stop(); - } - - public void HttpBasicWrongUser() throws Exception { - final Request request = new Request(Method.GET, this.uri); - final Client client = new Client(Protocol.HTTP); - - final ChallengeResponse authentication = new ChallengeResponse( - ChallengeScheme.HTTP_BASIC, WRONG_USERNAME, SHORT_PASSWORD); - request.setChallengeResponse(authentication); - - final Response response = client.handle(request); - - assertEquals( - Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus(), - "Wrong username did not throw 401" - ); - - client.stop(); - } - - @BeforeEach - public void makeServer() throws Exception { - this.component = new Component(); - final Server server = this.component.getServers().add(Protocol.HTTP, 0); - - final Application application = new Application() { - @Override - public Restlet createInboundRoot() { - HttpBasicTestCase.this.verifier = new TestVerifier(); - HttpBasicTestCase.this.authenticator = new ChallengeAuthenticator( - getContext(), ChallengeScheme.HTTP_BASIC, - HttpBasicTestCase.class.getSimpleName()); - HttpBasicTestCase.this.authenticator - .setVerifier(HttpBasicTestCase.this.verifier); - HttpBasicTestCase.this.authenticator - .setNext(new AuthenticatedRestlet()); - return HttpBasicTestCase.this.authenticator; - } - }; - - this.component.getDefaultHost().attach(application); - this.component.start(); - this.uri = "http://localhost:" + server.getActualPort() + "/"; - } - - @AfterEach - public void stopServer() throws Exception { - if (this.component.isStarted()) { - this.component.stop(); - } - this.component = null; - } - - @Test - public void testHttpBasic() throws Exception { - HttpBasicWrongUser(); - HttpBasicShort(); - HttpBasicShortWrong(); - HttpBasicNone(); - HttpBasicLong(); - HttpBasicLongWrong(); - } + public static class AuthenticatedRestlet extends Restlet { + @Override + public void handle(Request request, Response response) { + response.setEntity(AUTHENTICATED_MSG, MediaType.TEXT_PLAIN); + } + } + + public static class TestVerifier extends MapVerifier { + public TestVerifier() { + getLocalSecrets().put(SHORT_USERNAME, SHORT_PASSWORD.toCharArray()); + getLocalSecrets().put(LONG_USERNAME, LONG_PASSWORD.toCharArray()); + } + + @Override + public int verify(String identifier, char[] inputSecret) { + // NOTE: Allocating Strings are not really secure treatment of passwords + String almostSecret = new String(inputSecret); + + try { + return super.verify(identifier, inputSecret); + } finally { + // Clear secret from memory as soon as possible (This is better + // treatment, but useless due to our almostSecret copy) + Arrays.fill(inputSecret, '\000'); + } + } + } + + public static class BlockerVerifier implements Verifier { + @Override + public int verify(Request request, Response response) { + return RESULT_INVALID; + } + } + + public static final String AUTHENTICATED_MSG = "You are authenticated"; + + public static final String LONG_PASSWORD = "thisLongPasswordIsExtremelySecure"; + + public static final String LONG_USERNAME = "aVeryLongUsernameIsIndeedRequiredForThisTest"; + + public static final String SHORT_PASSWORD = "pw15"; + + public static final String SHORT_USERNAME = "user13"; + + public static final String WRONG_USERNAME = "wrongUser"; + + static Stream invalidCredentials() { + return Stream.of(arguments(LONG_USERNAME, SHORT_PASSWORD), arguments(SHORT_USERNAME, LONG_PASSWORD), + arguments(WRONG_USERNAME, SHORT_PASSWORD)); + } + + static Stream validCredentials() { + return Stream.of(arguments(LONG_USERNAME, LONG_PASSWORD), arguments(SHORT_USERNAME, SHORT_PASSWORD)); + } + + @Nested + class TestMapVerifier { + + private final MapVerifier verifier = new TestVerifier(); + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#invalidCredentials") + void testInvalidCredentials(final String login, final String password) { + assertEquals(Verifier.RESULT_INVALID, this.verifier.verify(login, password.toCharArray())); + } + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#validCredentials") + void testValidCredentials(final String login, final String password) { + assertEquals(Verifier.RESULT_VALID, this.verifier.verify(login, password.toCharArray())); + } + + } + + @Nested + class TestHttpBasicServer { + private Component component; + private Request request; + private Client client; + + @Test + public void HttpBasicNone() throws Exception { + final Response response = client.handle(request); + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + } + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#invalidCredentials") + void testInvalidCredentials(final String login, final String password) throws Exception { + ChallengeResponse authentication = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, login, password); + request.setChallengeResponse(authentication); + + final Response response = client.handle(request); + assertEquals(Status.CLIENT_ERROR_UNAUTHORIZED, response.getStatus()); + } + + @ParameterizedTest + @MethodSource("org.restlet.security.HttpBasicTestCase#validCredentials") + void testValidCredentials(final String login, final String password) throws Exception { + ChallengeResponse authentication = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, login, password); + request.setChallengeResponse(authentication); + + final Response response = client.handle(request); + assertEquals(Status.SUCCESS_OK, response.getStatus()); + assertEquals(AUTHENTICATED_MSG, response.getEntity().getText()); + } + + @BeforeEach + public void makeServer() throws Exception { + final String REALM = HttpBasicTestCase.class.getSimpleName(); + this.component = new Component(); + final Server server = this.component.getServers().add(Protocol.HTTP, 0); + + final Application application = new Application() { + @Override + public Restlet createInboundRoot() { + ChallengeAuthenticator authenticator = new ChallengeAuthenticator(getContext(), + ChallengeScheme.HTTP_BASIC, REALM); + authenticator.setVerifier(new TestVerifier()); + authenticator.setNext(new AuthenticatedRestlet()); + return authenticator; + } + }; + + this.component.getDefaultHost().attach(application); + this.component.start(); + request = new Request(Method.GET, "http://localhost:" + server.getActualPort()); + client = new Client(Protocol.HTTP); + } + + @AfterEach + public void cleanup() throws Exception { + client.stop(); + if (this.component.isStarted()) { + this.component.stop(); + } + this.component = null; + } + } } diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SaasApplication.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SaasApplication.java index 94468f2440..75c7fcd094 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SaasApplication.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SaasApplication.java @@ -14,7 +14,6 @@ import org.restlet.Restlet; import org.restlet.data.ChallengeScheme; import org.restlet.routing.Router; -import org.restlet.security.*; /** * Sample SAAS application with a Basic authenticator guarding a hello world diff --git a/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java b/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java index 21baf2661f..ca3e83df39 100644 --- a/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java +++ b/org.restlet.java/org.restlet/src/test/java/org/restlet/security/SecurityTestCase.java @@ -12,7 +12,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.restlet.Component; import org.restlet.data.ChallengeResponse; import org.restlet.data.Status; import org.restlet.engine.Engine;