-
Notifications
You must be signed in to change notification settings - Fork 278
2.6 multipart jetty #1456
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
2.6 multipart jetty #1456
Changes from 17 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
b0625f3
Added MultiPartFormDataRepresentation
jlouvel 966ec59
Added support for the "charset" parameter in HTTP BASIC challenges
jlouvel e019a2b
Removed unused imports
jlouvel 27c7bc2
Update changes.md
jlouvel f30c57a
Update MultiPartFormDataRepresentation.java
jlouvel b24ec67
HttpBasic test refacto
d7179ed
HttpBasicHelper: fix potential NPE
a554344
HttpBasicHelper: fix potential NPE
fef1ec9
Added tests cases for multipart
9f6bca1
Added logic to duplicate MediaType along with the addition of parameter
jlouvel e737f79
Enhanced MultiPartFormDataRepresentation
jlouvel 427bf76
Update MultiPartFormDataRepresentation.java
jlouvel 54a00b6
Fixed boundary setting issue and adjusted test case
jlouvel 114b44c
Renamed MultiPartFormDataRep to MultiPartRep
jlouvel 7417cab
Update MultiPartRepresentation.java
jlouvel 2f46349
Fixed multipart parsing logic
jlouvel 49a3cef
Update changes.md
jlouvel 309bd23
Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restl…
jlouvel fd08ce7
Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restl…
jlouvel 9995a20
Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restl…
jlouvel 57e3a57
Update org.restlet.java/org.restlet.ext.jetty/src/main/java/org/restl…
jlouvel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
333 changes: 333 additions & 0 deletions
333
...va/org.restlet.ext.jetty/src/main/java/org/restlet/ext/jetty/MultiPartRepresentation.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,333 @@ | ||
| /** | ||
| * Copyright 2005-2024 Qlik | ||
| * <p> | ||
| * 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 | ||
| * <p> | ||
| * 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 suggest 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 mediatype parameter with "boundary" name. | ||
jlouvel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * | ||
| * @param mediaType The media type that might contain a "boundary" | ||
| * parameter. | ||
| * @return The value of the first mediatype parameter with "boundary" name. | ||
jlouvel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| */ | ||
| 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<Part> 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<Part> 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<Part> 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<Part> 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<Part> 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. | ||
jlouvel marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| * | ||
| * @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)); | ||
| } | ||
| } | ||
|
|
||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.