Skip to content

Commit 7a51e5b

Browse files
authored
feat: add UploadHanlder interface (#21352)
Add upload handler callback when xhr upload happens. closes #21232 closes #21233
1 parent 1832b4c commit 7a51e5b

File tree

10 files changed

+1213
-28
lines changed

10 files changed

+1213
-28
lines changed

flow-server/src/main/java/com/vaadin/flow/server/Constants.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,23 @@ public final class Constants implements Serializable {
408408
*/
409409
public static final String ATTRIBUTE_HEIGHT_FULL = "data-height-full";
410410

411+
/**
412+
* maximum allowed size of a complete request for multipart stream upload
413+
* requests.
414+
*/
415+
public static final long DEFAULT_REQUEST_SIZE_MAX = -1;
416+
417+
/**
418+
* maximum allowed size of a single uploaded file for multipart stream
419+
* upload requests.
420+
*/
421+
public static final long DEFAULT_FILE_SIZE_MAX = -1;
422+
423+
/**
424+
* maximum number of files allowed per multipart stream upload requests.
425+
*/
426+
public static final long DEFAULT_FILE_COUNT_MAX = 10000;
427+
411428
private Constants() {
412429
// prevent instantiation constants class only
413430
}

flow-server/src/main/java/com/vaadin/flow/server/communication/StreamReceiverHandler.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
import javax.naming.SizeLimitExceededException;
1919

20+
import static com.vaadin.flow.server.Constants.DEFAULT_FILE_COUNT_MAX;
21+
import static com.vaadin.flow.server.Constants.DEFAULT_FILE_SIZE_MAX;
22+
import static com.vaadin.flow.server.Constants.DEFAULT_REQUEST_SIZE_MAX;
2023
import static java.nio.charset.StandardCharsets.UTF_8;
2124

2225
import java.io.BufferedWriter;
@@ -77,16 +80,10 @@ public class StreamReceiverHandler implements Serializable {
7780

7881
private static final int MAX_UPLOAD_BUFFER_SIZE = 4 * 1024;
7982

80-
static final long DEFAULT_SIZE_MAX = -1;
81-
82-
static final long DEFAULT_FILE_SIZE_MAX = -1;
83-
84-
static final long DEFAULT_FILE_COUNT_MAX = 10000;
85-
8683
/* Minimum interval which will be used for streaming progress events. */
8784
public static final int DEFAULT_STREAMING_PROGRESS_EVENT_INTERVAL_MS = 500;
8885

89-
private long requestSizeMax = DEFAULT_SIZE_MAX;
86+
private long requestSizeMax = DEFAULT_REQUEST_SIZE_MAX;
9087

9188
private long fileSizeMax = DEFAULT_FILE_SIZE_MAX;
9289

flow-server/src/main/java/com/vaadin/flow/server/communication/StreamRequestHandler.java

Lines changed: 87 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import java.io.IOException;
1919
import java.net.URI;
2020
import java.net.URISyntaxException;
21+
import java.nio.charset.StandardCharsets;
22+
import java.security.MessageDigest;
2123
import java.util.Optional;
2224

2325
import org.slf4j.Logger;
@@ -28,18 +30,22 @@
2830
import com.vaadin.flow.internal.StateNode;
2931
import com.vaadin.flow.internal.UrlUtil;
3032
import com.vaadin.flow.server.AbstractStreamResource;
33+
import com.vaadin.flow.server.ErrorEvent;
3134
import com.vaadin.flow.server.HttpStatusCode;
3235
import com.vaadin.flow.server.RequestHandler;
3336
import com.vaadin.flow.server.StreamReceiver;
3437
import com.vaadin.flow.server.StreamResource;
3538
import com.vaadin.flow.server.StreamResourceRegistry;
39+
import com.vaadin.flow.server.UploadException;
3640
import com.vaadin.flow.server.VaadinRequest;
3741
import com.vaadin.flow.server.VaadinResponse;
3842
import com.vaadin.flow.server.VaadinSession;
43+
import com.vaadin.flow.server.frontend.FrontendUtils;
44+
import com.vaadin.flow.server.streams.UploadHandler;
3945

40-
import static com.vaadin.flow.server.communication.StreamReceiverHandler.DEFAULT_FILE_COUNT_MAX;
41-
import static com.vaadin.flow.server.communication.StreamReceiverHandler.DEFAULT_FILE_SIZE_MAX;
42-
import static com.vaadin.flow.server.communication.StreamReceiverHandler.DEFAULT_SIZE_MAX;
46+
import static com.vaadin.flow.server.Constants.DEFAULT_FILE_COUNT_MAX;
47+
import static com.vaadin.flow.server.Constants.DEFAULT_FILE_SIZE_MAX;
48+
import static com.vaadin.flow.server.Constants.DEFAULT_REQUEST_SIZE_MAX;
4349

4450
/**
4551
* Handles {@link StreamResource} and {@link StreamReceiver} instances
@@ -109,48 +115,108 @@ public boolean handleRequest(VaadinSession session, VaadinRequest request,
109115

110116
AbstractStreamResource resource = abstractStreamResource.get();
111117
if (resource instanceof StreamResourceRegistry.ElementStreamResource elementRequest) {
112-
Element owner = elementRequest.getOwner();
113-
StateNode node = owner.getNode();
114-
115-
if ((node.isInert()
116-
&& !elementRequest.getElementRequestHandler().allowInert())
117-
|| !node.isAttached() || !node.isEnabled()) {
118-
response.sendError(HttpStatusCode.FORBIDDEN.getCode(),
119-
"Resource not available");
120-
return true;
121-
} else {
122-
elementRequest.getElementRequestHandler().handleRequest(request,
123-
response, session, elementRequest.getOwner());
124-
}
118+
callElementResourceHandler(session, request, response,
119+
elementRequest, pathInfo);
125120
} else if (resource instanceof StreamResource) {
126121
resourceHandler.handleRequest(session, request, response,
127122
(StreamResource) resource);
128123
} else if (resource instanceof StreamReceiver streamReceiver) {
129-
String[] parts = parsePath(pathInfo);
124+
PathData parts = parsePath(pathInfo);
130125

131126
receiverHandler.handleRequest(session, request, response,
132-
streamReceiver, parts[0], parts[1]);
127+
streamReceiver, parts.UIid, parts.securityKey);
133128
} else {
134129
getLogger().warn("Received unknown stream resource.");
135130
}
136131
return true;
137132
}
138133

134+
private void callElementResourceHandler(VaadinSession session,
135+
VaadinRequest request, VaadinResponse response,
136+
StreamResourceRegistry.ElementStreamResource elementRequest,
137+
String pathInfo) throws IOException {
138+
Element owner = elementRequest.getOwner();
139+
StateNode node = owner.getNode();
140+
141+
if ((node.isInert()
142+
&& !elementRequest.getElementRequestHandler().allowInert())
143+
|| !node.isAttached() || !node.isEnabled()) {
144+
response.sendError(HttpStatusCode.FORBIDDEN.getCode(),
145+
"Resource not available");
146+
return;
147+
}
148+
149+
if (elementRequest
150+
.getElementRequestHandler() instanceof UploadHandler) {
151+
// Validate upload security key. Else respond with
152+
// FORBIDDEN.
153+
PathData parts = parsePath(pathInfo);
154+
session.lock();
155+
try {
156+
String secKey = elementRequest.getId();
157+
if (secKey == null || !MessageDigest.isEqual(
158+
secKey.getBytes(StandardCharsets.UTF_8),
159+
parts.securityKey.getBytes(StandardCharsets.UTF_8))) {
160+
LoggerFactory.getLogger(StreamRequestHandler.class).warn(
161+
"Received incoming stream with faulty security key.");
162+
response.sendError(HttpStatusCode.FORBIDDEN.getCode(),
163+
"Resource not available");
164+
return;
165+
}
166+
167+
// Set current UI to upload url ui.
168+
UI ui = session.getUIById(Integer.parseInt(parts.UIid));
169+
UI.setCurrent(ui);
170+
171+
if (node == null) {
172+
session.getErrorHandler()
173+
.error(new ErrorEvent(new UploadException(
174+
"File upload ignored because the node for the upload owner component was not found")));
175+
response.sendError(HttpStatusCode.FORBIDDEN.getCode(),
176+
"Resource not available");
177+
return;
178+
}
179+
if (!node.isAttached()) {
180+
session.getErrorHandler()
181+
.error(new ErrorEvent(new UploadException(
182+
"Warning: file upload ignored for "
183+
+ node.getId()
184+
+ " because the component was disabled")));
185+
response.sendError(HttpStatusCode.FORBIDDEN.getCode(),
186+
"Resource not available");
187+
return;
188+
}
189+
} finally {
190+
session.unlock();
191+
}
192+
}
193+
194+
elementRequest.getElementRequestHandler().handleRequest(request,
195+
response, session, elementRequest.getOwner());
196+
}
197+
198+
private record PathData(String UIid, String securityKey, String fileName) {
199+
}
200+
139201
/**
140202
* Parse the pathInfo for id data.
141203
* <p>
142204
* URI pattern: VAADIN/dynamic/resource/[UIID]/[SECKEY]/[NAME]
143205
*
144206
* @see #generateURI
145207
*/
146-
private String[] parsePath(String pathInfo) {
208+
private PathData parsePath(String pathInfo) {
147209
// strip away part until the data we are interested starts
148210
int startOfData = pathInfo.indexOf(DYN_RES_PREFIX)
149211
+ DYN_RES_PREFIX.length();
150212

151213
String uppUri = pathInfo.substring(startOfData);
152214
// [0] UIid, [1] security key, [2] name
153-
return uppUri.split("/", 3);
215+
String[] split = uppUri.split("/", 3);
216+
if (split.length == 3) {
217+
return new PathData(split[0], split[1], split[2]);
218+
}
219+
return new PathData(split[0], split[1], "");
154220
}
155221

156222
/**
@@ -195,7 +261,7 @@ private static Optional<URI> getPathUri(String path) {
195261
* @return maximum request size for upload
196262
*/
197263
protected long getRequestSizeMax() {
198-
return DEFAULT_SIZE_MAX;
264+
return DEFAULT_REQUEST_SIZE_MAX;
199265
}
200266

201267
/**

0 commit comments

Comments
 (0)