Skip to content

Commit

Permalink
Document Multipart Configuration.
Browse files Browse the repository at this point in the history
Allow to limit the number of parts in the multipart entity.

Signed-off-by: jansupol <jan.supol@oracle.com>
  • Loading branch information
jansupol committed May 7, 2024
1 parent d32c63e commit 0333559
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 8 deletions.
76 changes: 76 additions & 0 deletions docs/src/main/docbook/appendix-properties.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2175,4 +2175,80 @@
</tgroup>
</table>
</section>
<section xml:id="appendix-properties-multipart">
<title>Multipart configuration properties</title>

<para>
List of multipart configuration properties that can be found in &jersey.media.multipart.MultiPartProperties; class.
</para>

<table>
<title>
List of multipart configuration properties settable in the
&jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE; configuration file.
</title>
<tgroup cols="3">
<thead>
<row>
<entry>Constant</entry>
<entry>Value</entry>
<entry>Description</entry>
</row>
</thead>
<tbody>
<row>
<entry>&jersey.media.multipart.MultiPartProperties.BUFFER_THRESHOLD;</entry>
<entry><literal>jersey.config.multipart.bufferThreshold</literal></entry>
<entry>
<para>
Name of the resource property for the threshold size (in bytes) above which a
body part entity will be buffered to disk instead of being held in memory.
</para>
<para>
The default value is &jersey.message.MessageProperties.IO_DEFAULT_BUFFER_SIZE;
</para>
</entry>
</row>
<row>
<entry>&jersey.media.multipart.MultiPartProperties.MAX_PARTS;</entry>
<entry><literal>jersey.config.multipart.maxParts</literal></entry>
<entry>
<para>
Limit the maximum number of parts the multipart entity can have. If the limit is over,
the error response status <literal>413 - REQUEST_ENTITY_TOO_LARGE</literal>
is returned.
</para>
<para>
By default, the number is unlimited.
</para>
</entry>
</row>
<row>
<entry>&jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE;</entry>
<entry><literal>jersey-multipart-config.properties</literal></entry>
<entry>
<para>
Name of a properties resource that (if found in the classpath
for this application) will be used to configure the settings returned
by our getter methods.
</para>
</entry>
</row>
<row>
<entry>&jersey.media.multipart.MultiPartProperties.TEMP_DIRECTORY;</entry>
<entry><literal>jersey.config.multipart.tempDir</literal></entry>
<entry>
<para>
Name of the resource property for the directory to store temporary files containing body parts
of multipart message that extends allowed memory threshold.
</para>
<para>
The default value is not set (will be taken from <literal>java.io.tmpdir</literal> system property).
</para>
</entry>
</row>
</tbody>
</tgroup>
</table>
</section>
</appendix>
7 changes: 6 additions & 1 deletion docs/src/main/docbook/jersey.ent
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,12 @@
<!ENTITY jersey.media.multipart.FormDataParam "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/FormDataParam.html'>@FormDataParam</link>" >
<!ENTITY jersey.media.multipart.MultiPart "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPart.html'>MultiPart</link>" >
<!ENTITY jersey.media.multipart.MultiPartFeature "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartFeature.html'>MultiPartFeature</link>" >
<!ENTITY jersey.media.multipart.StreamDataBodyPart "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/file/StreamDataBodyPart.html'>StreamDataBodyPart</link>" >
<!ENTITY jersey.media.multipart.MultiPartProperties "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html'>MultiPartProperties</link>" >
<!ENTITY jersey.media.multipart.MultiPartProperties.BUFFER_THRESHOLD "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#BUFFER_THRESHOLD'>MultiPartProperties.BUFFER_THRESHOLD</link>">
<!ENTITY jersey.media.multipart.MultiPartProperties.MAX_PARTS "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#MAX_PARTS'>MultiPartProperties.MAX_PARTS</link>">
<!ENTITY jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#MULTI_PART_CONFIG_RESOURCE'>MultiPartProperties.MULTI_PART_CONFIG_RESOURCE</link>">
<!ENTITY jersey.media.multipart.MultiPartProperties.TEMP_DIRECTORY "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#TEMP_DIRECTORY'>MultiPartProperties.TEMP_DIRECTORY</link>">
<!ENTITY jersey.media.multipart.StreamDataBodyPart "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/file/StreamDataBodyPart.html'>StreamDataBodyPart</link>">
<!ENTITY jersey.message.MessageBodyWorkers "<link xlink:href='&jersey.javadoc.uri.prefix;/message/MessageBodyWorkers.html'>MessageBodyWorkers</link>">
<!ENTITY jersey.message.MessageProperties "<link xlink:href='&jersey.javadoc.uri.prefix;/message/MessageProperties.html'>MessageProperties</link>">
<!ENTITY jersey.message.MessageProperties.DEFLATE_WITHOUT_ZLIB "<link xlink:href='&jersey.javadoc.uri.prefix;/message/MessageProperties.html#DEFLATE_WITHOUT_ZLIB'>MessageProperties.DEFLATE_WITHOUT_ZLIB</link>">
Expand Down
21 changes: 20 additions & 1 deletion docs/src/main/docbook/media.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" standalone="no"?>
<!--
Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
This program and the accompanying materials are made available under the
terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -1889,5 +1889,24 @@ public String postForm(
</tip>
</section>
</section>
<section xml:id="multipart.configuration">
<title>Properties for configuring the Multipart</title>
<para>
There are multiple options that can be used when configuring
the multipart. See &jersey.media.multipart.MultiPartProperties; or <xref linkend="appendix-properties-multipart"/>
for the possibilities.
</para>
<para>
The options can set in a configuration file specified by the
&jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE; property.
That is the standard Java properties file.
</para>
<para>
Or the options can be set programmatically,
by registering <literal>ContextResolver&lt;MultiPartProperties&gt;</literal>. For instance:
</para>
<programlisting language="java">ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.register(new MultiPartProperties().bufferThreshold(65535).maxParts(2).resolver());</programlisting>
</section>
</section>
</chapter>
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand All @@ -23,6 +23,7 @@
import javax.ws.rs.ext.ContextResolver;

import org.glassfish.jersey.internal.util.PropertiesClass;
import org.glassfish.jersey.message.internal.ReaderWriter;

/**
* Injectable JavaBean containing the configuration parameters for
Expand All @@ -38,20 +39,25 @@ public class MultiPartProperties {
/**
* Default threshold size for buffer.
*/
public static final int DEFAULT_BUFFER_THRESHOLD = 4096;
public static final int DEFAULT_BUFFER_THRESHOLD = ReaderWriter.BUFFER_SIZE;

/**
* <p>
* Name of a properties resource that (if found in the classpath
* for this application) will be used to configure the settings returned
* by our getter methods.
* </p>
* <p>
* The resource name is {@code jersey-multipart-config.properties}.
* </p>
*/
public static final String MULTI_PART_CONFIG_RESOURCE = "jersey-multipart-config.properties";

/**
* Name of the resource property for the threshold size (in bytes) above which a body part entity will be
* buffered to disk instead of being held in memory.
*
* The default value is {@value #DEFAULT_BUFFER_THRESHOLD}.
* The default value is {@link #DEFAULT_BUFFER_THRESHOLD}.
*/
public static final String BUFFER_THRESHOLD = "jersey.config.multipart.bufferThreshold";

Expand All @@ -60,9 +66,21 @@ public class MultiPartProperties {
*/
public static final int BUFFER_THRESHOLD_MEMORY_ONLY = -1;

/**
* <p>
* Limit the maximum number of parts the multipart entity can have. If the limit is over,
* the error response status {@link javax.ws.rs.core.Response.Status#REQUEST_ENTITY_TOO_LARGE} is returned.
* </p>
* <p>
* By default, the number is unlimited.
* </p>
* @since 2.44
*/
public static final String MAX_PARTS = "jersey.config.multipart.maxParts";

/**
* Name of the resource property for the directory to store temporary files containing body parts of multipart message that
* extends allowed memory threshold..
* extends allowed memory threshold.
*
* The default value is not set (will be taken from {@code java.io.tmpdir} system property).
*/
Expand All @@ -79,6 +97,11 @@ public class MultiPartProperties {
*/
private String tempDir = null;

/**
* Maximum number of entity parts allowed.
*/
private int maxParts = Integer.MAX_VALUE;

/**
* Load and customize (if necessary) the configuration values for the
* {@code jersey-multipart} injection binder.
Expand Down Expand Up @@ -113,6 +136,15 @@ public String getTempDir() {
return tempDir;
}

/**
* Return maximum number of entity parts allowed.
* @return maximum number of parts.
* @since 2.44
*/
public int getMaxParts() {
return maxParts;
}

/**
* Set the size (in bytes) of the entity of an incoming {@link BodyPart} before it will be buffered to disk.
*
Expand All @@ -138,6 +170,17 @@ public MultiPartProperties tempDir(final String path) {
return this;
}

/**
* Set the maximum number of received parts of a multipart entity.
* @param maxParts The maximum number of entity parts.
* @return {@code MultiPartProperties} instance.
* @since 2.44
*/
public MultiPartProperties maxParts(int maxParts) {
this.maxParts = maxParts;
return this;
}

/**
* Configure the values returned by this instance's getters based on
* the contents of a properties resource, if it exists on the classpath
Expand Down Expand Up @@ -169,6 +212,9 @@ private void configure() {
if (props.containsKey(TEMP_DIRECTORY)) {
this.tempDir = props.getProperty(TEMP_DIRECTORY);
}
if (props.contains(MAX_PARTS)) {
this.maxParts = Integer.parseInt(props.getProperty(MAX_PARTS));
}
} catch (final IOException e) {
throw new IllegalArgumentException(e);
} finally {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
Expand Down Expand Up @@ -28,6 +28,7 @@
import java.util.logging.Logger;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.ClientErrorException;
import javax.ws.rs.ConstrainedTo;
import javax.ws.rs.Consumes;
import javax.ws.rs.RuntimeType;
Expand All @@ -36,6 +37,7 @@
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ContextResolver;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Providers;
Expand Down Expand Up @@ -79,6 +81,7 @@ public class MultiPartReaderClientSide implements MessageBodyReader<MultiPart> {
*/
private Provider<MessageBodyWorkers> messageBodyWorkers;
private final MIMEConfig mimeConfig;
private final int maxParts;

/**
* Accepts constructor injection of the configuration parameters for this
Expand All @@ -98,6 +101,8 @@ public MultiPartReaderClientSide(@Context final Providers providers,
properties = new MultiPartProperties();
}

maxParts = properties.getMaxParts();

this.messageBodyWorkers = messageBodyWorkers;
mimeConfig = createMimeConfig(properties);
}
Expand Down Expand Up @@ -205,7 +210,12 @@ protected MultiPart readMultiPart(final Class<MultiPart> type,
fileNameFix = userAgent != null && userAgent.contains(" MSIE ");
}

for (final MIMEPart mimePart : getMimeParts(mimeMessage)) {
final List<MIMEPart> mimeParts = getMimeParts(mimeMessage);
if (mimeParts.size() > maxParts) {
throw new ClientErrorException(Response.Status.REQUEST_ENTITY_TOO_LARGE);
}

for (final MIMEPart mimePart : mimeParts) {
final BodyPart bodyPart = formData ? new FormDataBodyPart(fileNameFix) : new BodyPart();

// Configure providers.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/

package org.glassfish.jersey.media.multipart.internal;

import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.media.multipart.MultiPartProperties;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

public class RestrictionsTest extends JerseyTest {
@Path("/")
public static class RestrictionsTestResource {
@POST
@Path("max.parts")
public String postMaxPart(@FormDataParam("part1") String part1, @FormDataParam("part2") String part2) {
return part1 + part2;
}
}

@Override
protected Application configure() {
return new ResourceConfig(RestrictionsTestResource.class)
.register(MultiPartFeature.class)
.register(new MultiPartProperties().maxParts(2).resolver());
}

@Override
protected void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
}

@Test
public void testPassNumberOfParts() {
FormDataMultiPart multiPart = new FormDataMultiPart();
multiPart.field("part1", "he", MediaType.TEXT_PLAIN_TYPE);
multiPart.field("part2", "llo", MediaType.TEXT_PLAIN_TYPE);
try (Response r = target("max.parts").request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE))) {
Assertions.assertEquals(200, r.getStatus());
Assertions.assertEquals("hello", r.readEntity(String.class));
}
}

@Test
public void testFailsNumberOfParts() {
FormDataMultiPart multiPart = new FormDataMultiPart();
multiPart.field("part1", "he", MediaType.TEXT_PLAIN_TYPE);
multiPart.field("part2", "llo", MediaType.TEXT_PLAIN_TYPE);
multiPart.field("part3", "!", MediaType.TEXT_PLAIN_TYPE);
try (Response r = target("max.parts").request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE))) {
Assertions.assertEquals(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(), r.getStatus());
}
}
}

0 comments on commit 0333559

Please sign in to comment.