Skip to content

Commit e22ebc8

Browse files
committed
Copy/merge in the FileManager and MultipartConfiguration changes from 0.3.5.maintenance.
1 parent e2a6e69 commit e22ebc8

18 files changed

+932
-46
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright (c) 2025, FusionAuth, All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package io.fusionauth.http;
17+
18+
/**
19+
* Thrown when a request exceeds the total configured maximum size.
20+
*
21+
* @author Daniel DeGroff
22+
*/
23+
public class ContentTooLargeException extends HTTPProcessingException {
24+
public long maximumRequestSize;
25+
26+
public ContentTooLargeException(long maximumRequestSize, String detailedMessage) {
27+
super(413, "Content Too Large", detailedMessage);
28+
this.maximumRequestSize = maximumRequestSize;
29+
}
30+
}

src/main/java/io/fusionauth/http/Cookie.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public Cookie(Cookie other) {
7474
return;
7575
}
7676

77+
this.attributes.putAll(other.attributes);
7778
this.domain = other.domain;
7879
this.expires = other.expires;
7980
this.httpOnly = other.httpOnly;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2025, FusionAuth, All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package io.fusionauth.http;
17+
18+
/**
19+
* A base HTTP processing exception that is able to suggest a status code to return the client.
20+
*/
21+
public abstract class HTTPProcessingException extends RuntimeException {
22+
protected final int status;
23+
24+
protected final String statusMessage;
25+
26+
protected HTTPProcessingException(int status, String statusMessage) {
27+
this.status = status;
28+
this.statusMessage = statusMessage;
29+
}
30+
31+
protected HTTPProcessingException(int status, String statusMessage, String detailedMessage) {
32+
super(detailedMessage);
33+
this.status = status;
34+
this.statusMessage = statusMessage;
35+
}
36+
37+
public int getStatus() {
38+
return status;
39+
}
40+
41+
public String getStatusMessage() {
42+
return statusMessage;
43+
}
44+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Copyright (c) 2025, FusionAuth, All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package io.fusionauth.http;
17+
18+
/**
19+
* Thrown when a multipart request cannot be parsed because a processor was not specified.
20+
*
21+
* @author Daniel DeGroff
22+
*/
23+
public class UnprocessableContentException extends HTTPProcessingException {
24+
public UnprocessableContentException(String message) {
25+
super(422, "Unprocessable Content", message);
26+
}
27+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright (c) 2025, FusionAuth, All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package io.fusionauth.http.io;
17+
18+
import java.io.IOException;
19+
import java.nio.file.Files;
20+
import java.nio.file.Path;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
24+
/**
25+
* Manage file creation for multipart streams.
26+
*
27+
* @author Daniel DeGroff
28+
*/
29+
public class DefaultMultipartFileManager implements MultipartFileManager {
30+
private final String optionalPrefix;
31+
32+
private final String optionalSuffix;
33+
34+
private final Path tempDir;
35+
36+
private final List<Path> tempFiles = new ArrayList<>(0);
37+
38+
public DefaultMultipartFileManager(Path tempDir, String optionalPrefix, String optionalSuffix) {
39+
this.tempDir = tempDir;
40+
this.optionalPrefix = optionalPrefix;
41+
this.optionalSuffix = optionalSuffix;
42+
}
43+
44+
public Path createTemporaryFile() throws IOException {
45+
Path tempFile = Files.createTempFile(tempDir, optionalPrefix, optionalSuffix);
46+
tempFiles.add(tempFile);
47+
return tempFile;
48+
}
49+
50+
public List<Path> getTemporaryFiles() {
51+
return List.copyOf(tempFiles);
52+
}
53+
}
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* Copyright (c) 2025, FusionAuth, All Rights Reserved
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing,
11+
* software distributed under the License is distributed on an
12+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13+
* either express or implied. See the License for the specific
14+
* language governing permissions and limitations under the License.
15+
*/
16+
package io.fusionauth.http.io;
17+
18+
import java.util.Objects;
19+
20+
/**
21+
* Provides configuration to control the behavior of the {@link MultipartStream} parser, specifically around file uploads.
22+
*
23+
* @author Daniel DeGroff
24+
*/
25+
@SuppressWarnings("UnusedReturnValue")
26+
public class MultipartConfiguration {
27+
private boolean deleteTemporaryFiles = true;
28+
29+
private MultipartFileUploadPolicy fileUploadPolicy = MultipartFileUploadPolicy.Reject;
30+
31+
private long maxFileSize = 1024 * 1024; // 1 Megabyte
32+
33+
private long maxRequestSize = 10 * 1024 * 1024; // 10 Megabyte
34+
35+
private int multipartBufferSize = 8 * 1024; // 8 Kilobyte
36+
37+
private String temporaryFileLocation = System.getProperty("java.io.tmpdir");
38+
39+
private String temporaryFilenamePrefix = "java-http";
40+
41+
private String temporaryFilenameSuffix = "file-upload";
42+
43+
public MultipartConfiguration() {
44+
}
45+
46+
public MultipartConfiguration(MultipartConfiguration other) {
47+
this.deleteTemporaryFiles = other.deleteTemporaryFiles;
48+
this.fileUploadPolicy = other.fileUploadPolicy;
49+
this.maxFileSize = other.maxFileSize;
50+
this.maxRequestSize = other.maxRequestSize;
51+
this.multipartBufferSize = other.multipartBufferSize;
52+
this.temporaryFileLocation = other.temporaryFileLocation;
53+
this.temporaryFilenamePrefix = other.temporaryFilenamePrefix;
54+
this.temporaryFilenameSuffix = other.temporaryFilenameSuffix;
55+
}
56+
57+
public boolean deleteTemporaryFiles() {
58+
return deleteTemporaryFiles;
59+
}
60+
61+
@Override
62+
public boolean equals(Object o) {
63+
if (!(o instanceof MultipartConfiguration that)) {
64+
return false;
65+
}
66+
return deleteTemporaryFiles == that.deleteTemporaryFiles &&
67+
fileUploadPolicy == that.fileUploadPolicy &&
68+
maxFileSize == that.maxFileSize &&
69+
maxRequestSize == that.maxRequestSize &&
70+
multipartBufferSize == that.multipartBufferSize &&
71+
Objects.equals(temporaryFileLocation, that.temporaryFileLocation) &&
72+
Objects.equals(temporaryFilenamePrefix, that.temporaryFilenamePrefix) &&
73+
Objects.equals(temporaryFilenameSuffix, that.temporaryFilenameSuffix);
74+
}
75+
76+
public MultipartFileUploadPolicy getFileUploadPolicy() {
77+
return fileUploadPolicy;
78+
}
79+
80+
public long getMaxFileSize() {
81+
return maxFileSize;
82+
}
83+
84+
public long getMaxRequestSize() {
85+
return maxRequestSize;
86+
}
87+
88+
public int getMultipartBufferSize() {
89+
return multipartBufferSize;
90+
}
91+
92+
public String getTemporaryFileLocation() {
93+
return temporaryFileLocation;
94+
}
95+
96+
public String getTemporaryFilenamePrefix() {
97+
return temporaryFilenamePrefix;
98+
}
99+
100+
public String getTemporaryFilenameSuffix() {
101+
return temporaryFilenameSuffix;
102+
}
103+
104+
@Override
105+
public int hashCode() {
106+
return Objects.hash(
107+
deleteTemporaryFiles,
108+
fileUploadPolicy,
109+
maxFileSize,
110+
maxRequestSize,
111+
multipartBufferSize,
112+
temporaryFileLocation,
113+
temporaryFilenamePrefix,
114+
temporaryFilenameSuffix);
115+
}
116+
117+
public boolean isDeleteTemporaryFiles() {
118+
return deleteTemporaryFiles;
119+
}
120+
121+
/**
122+
* Setting this to <code>true</code> will cause the server to delete all temporary files created while processing a multipart stream after
123+
* the request handler has been invoked.
124+
* <p>
125+
* If you set this to <code>false</code> the request handler will need to manage cleanup of these temporary files.
126+
*
127+
* @param deleteTemporaryFiles controls if temporary files are deleted by the server.
128+
* @return This.
129+
*/
130+
public MultipartConfiguration withDeleteTemporaryFiles(boolean deleteTemporaryFiles) {
131+
this.deleteTemporaryFiles = deleteTemporaryFiles;
132+
return this;
133+
}
134+
135+
/**
136+
* This is the file upload policy for the HTTP server.
137+
*
138+
* @param fileUploadPolicy the file upload policy. Cannot be null.
139+
* @return This.
140+
*/
141+
public MultipartConfiguration withFileUploadPolicy(MultipartFileUploadPolicy fileUploadPolicy) {
142+
Objects.requireNonNull(fileUploadPolicy, "You cannot set the fileUploadPolicy to null");
143+
this.fileUploadPolicy = fileUploadPolicy;
144+
return this;
145+
}
146+
147+
/**
148+
* This is the maximum size for each file found within a multipart stream which may contain one to many files.
149+
*
150+
* @param maxFileSize the maximum file size in bytes
151+
* @return This.
152+
*/
153+
public MultipartConfiguration withMaxFileSize(long maxFileSize) {
154+
this.maxFileSize = maxFileSize;
155+
return this;
156+
}
157+
158+
/**
159+
* This is the maximum size of the request payload in bytes when reading a multipart stream.
160+
*
161+
* @param maxRequestSize the maximum request size in bytes
162+
* @return This.
163+
*/
164+
public MultipartConfiguration withMaxRequestSize(long maxRequestSize) {
165+
if (maxRequestSize < maxFileSize) {
166+
// In practice the maxRequestSize should be more than just one byte larger than maxFileSize, but I am not going to require any specific amount.
167+
throw new IllegalArgumentException("The maximum request size must be greater than the maxFileSize");
168+
}
169+
170+
this.maxRequestSize = maxRequestSize;
171+
return this;
172+
}
173+
174+
/**
175+
* @param multipartBufferSize the size of the buffer used to parse a multipart stream.
176+
* @return This.
177+
*/
178+
public MultipartConfiguration withMultipartBufferSize(int multipartBufferSize) {
179+
if (multipartBufferSize <= 0) {
180+
throw new IllegalArgumentException("The multipart buffer size must be greater than 0");
181+
}
182+
183+
this.multipartBufferSize = multipartBufferSize;
184+
return this;
185+
}
186+
187+
/**
188+
* A temporary file location used for creating temporary files.
189+
* <p>
190+
* The specific behavior of creating temporary files will be dependant upon the {@link MultipartFileManager} implementation.
191+
*
192+
* @param temporaryFileLocation the temporary file location. Cannot be <code>null</code>.
193+
* @return This.
194+
*/
195+
public MultipartConfiguration withTemporaryFileLocation(String temporaryFileLocation) {
196+
Objects.requireNonNull(temporaryFileLocation, "You cannot set the temporaryFileLocation to null");
197+
this.temporaryFileLocation = temporaryFileLocation;
198+
return this;
199+
}
200+
201+
/**
202+
* An optional filename prefix used for naming temporary files.
203+
* <p>
204+
* This parameter may be set to <code>null</code>. When set to <code>null</code> a system default such as '.tmp' may be used when naming a
205+
* temporary file depending upon the {@link MultipartFileManager} implementation.
206+
*
207+
* @param temporaryFilenamePrefix an optional filename prefix to be used when creating temporary files.
208+
* @return This.
209+
*/
210+
public MultipartConfiguration withTemporaryFilenamePrefix(String temporaryFilenamePrefix) {
211+
this.temporaryFilenamePrefix = temporaryFilenamePrefix;
212+
return this;
213+
}
214+
215+
/**
216+
* An optional filename suffix used for naming temporary files.
217+
* <p>
218+
* This parameter may be set to <code>null</code>. The specific file naming with or without this optional suffix may be dependant upon the
219+
* {@link MultipartFileManager} implementation. file depending upon the {@link MultipartFileManager} implementation.
220+
*
221+
* @param temporaryFilenameSuffix an optional filename suffix to be used when creating temporary files.
222+
* @return This.
223+
*/
224+
public MultipartConfiguration withTemporaryFilenameSuffix(String temporaryFilenameSuffix) {
225+
this.temporaryFilenameSuffix = temporaryFilenameSuffix;
226+
return this;
227+
}
228+
}

0 commit comments

Comments
 (0)