Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -295,10 +295,10 @@ protected void doHandleXhrFilePost(VaadinSession session,
StreamReceiver streamReceiver, StateNode owner, long contentLength)
throws IOException {

// These are unknown in filexhr ATM, maybe add to Accept header that
// is accessible in portlets
final String filename = "unknown";
final String mimeType = filename;
String filename = TransferUtil.extractFilenameFromXhrRequest(request);
String mimeType = TransferUtil
.extractContentTypeFromXhrRequest(request);

final InputStream stream = request.getInputStream();

boolean success = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,21 +172,8 @@ public static void handleUpload(UploadHandler handler,
handler.responseHandled(false, response);
}
} else {
// Extract filename from X-Filename header
// The filename is encoded using JavaScript's encodeURIComponent
String fileName = request.getHeader("X-Filename");

if (fileName == null || fileName.isEmpty()) {
fileName = "unknown";
} else {
// Decode the percent-encoded filename
fileName = UrlUtil.decodeURIComponent(fileName);
}

String contentType = request.getHeader("Content-Type");
if (contentType == null || contentType.isEmpty()) {
contentType = "unknown";
}
String fileName = extractFilenameFromXhrRequest(request);
String contentType = extractContentTypeFromXhrRequest(request);

UploadEvent event = new UploadEvent(request, response, session,
fileName, request.getContentLengthLong(), contentType,
Expand Down Expand Up @@ -243,6 +230,49 @@ private static boolean isMultipartContent(VaadinRequest request) {
&& contentType.toLowerCase().startsWith("multipart/");
}

/**
* Extracts the filename from an XHR upload request.
* <p>
* The filename is extracted from the X-Filename header, which is set by
* vaadin-upload. The filename is encoded using JavaScript's
* encodeURIComponent and decoded on the server using
* {@link UrlUtil#decodeURIComponent(String)} (RFC 3986).
*
* @param request
* the request to extract the filename from
* @return the decoded filename, or "unknown" if not present
*/
public static String extractFilenameFromXhrRequest(VaadinRequest request) {
String fileName = request.getHeader("X-Filename");

if (fileName == null || fileName.isEmpty()) {
return "unknown";
}

// Decode the percent-encoded filename
return UrlUtil.decodeURIComponent(fileName);
}

/**
* Extracts the content type from an XHR upload request.
* <p>
* The content type is extracted from the Content-Type header.
*
* @param request
* the request to extract the content type from
* @return the content type, or "unknown" if not present
*/
public static String extractContentTypeFromXhrRequest(
VaadinRequest request) {
String contentType = request.getHeader("Content-Type");

if (contentType == null || contentType.isEmpty()) {
return "unknown";
}

return contentType;
}

/**
* Validates upload limits for the given parts.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public class StreamReceiverHandlerTest {

private boolean isGetContentLengthLongCalled;
private String requestCharacterEncoding;
private String xFilenameHeader;

@Before
public void setup() throws Exception {
Expand Down Expand Up @@ -178,6 +179,12 @@ public String getHeader(String name) {
if ("content-length".equals(name.toLowerCase())) {
return contentLength;
}
if ("x-filename".equals(name.toLowerCase())) {
return xFilenameHeader;
}
if ("content-type".equals(name.toLowerCase())) {
return contentType;
}
return super.getHeader(name);
}

Expand Down Expand Up @@ -468,4 +475,90 @@ public void doHandleMultipartFileUpload_IOExceptionIsThrown_exceptionIsNotRethro
Mockito.verifyNoInteractions(errorHandler);
}

@Test
public void doHandleXhrFilePost_filenameFromHeader_extractedCorrectly()
throws IOException {
xFilenameHeader = "test.txt";
outputStream = new ByteArrayOutputStream();

handler.doHandleXhrFilePost(session, request, response, streamReceiver,
stateNode, 6);

ArgumentCaptor<StreamVariable.StreamingEndEvent> endEventCaptor = ArgumentCaptor
.forClass(StreamVariable.StreamingEndEvent.class);
Mockito.verify(streamVariable)
.streamingFinished(endEventCaptor.capture());
Assert.assertEquals("test.txt",
endEventCaptor.getValue().getFileName());
}

@Test
public void doHandleXhrFilePost_encodedFilename_decodedCorrectly()
throws IOException {
// encodeURIComponent("my file åäö.txt") in JavaScript
xFilenameHeader = "my%20file%20%C3%A5%C3%A4%C3%B6.txt";
outputStream = new ByteArrayOutputStream();

handler.doHandleXhrFilePost(session, request, response, streamReceiver,
stateNode, 6);

ArgumentCaptor<StreamVariable.StreamingEndEvent> endEventCaptor = ArgumentCaptor
.forClass(StreamVariable.StreamingEndEvent.class);
Mockito.verify(streamVariable)
.streamingFinished(endEventCaptor.capture());
Assert.assertEquals("my file åäö.txt",
endEventCaptor.getValue().getFileName());
}

@Test
public void doHandleXhrFilePost_contentTypeFromHeader_extractedCorrectly()
throws IOException {
xFilenameHeader = "test.txt";
contentType = "text/plain";
outputStream = new ByteArrayOutputStream();

handler.doHandleXhrFilePost(session, request, response, streamReceiver,
stateNode, 6);

ArgumentCaptor<StreamVariable.StreamingEndEvent> endEventCaptor = ArgumentCaptor
.forClass(StreamVariable.StreamingEndEvent.class);
Mockito.verify(streamVariable)
.streamingFinished(endEventCaptor.capture());
Assert.assertEquals("text/plain",
endEventCaptor.getValue().getMimeType());
}

@Test
public void doHandleXhrFilePost_missingContentTypeHeader_defaultsToUnknown()
throws IOException {
xFilenameHeader = "test.txt";
contentType = null;
outputStream = new ByteArrayOutputStream();

handler.doHandleXhrFilePost(session, request, response, streamReceiver,
stateNode, 6);

ArgumentCaptor<StreamVariable.StreamingEndEvent> endEventCaptor = ArgumentCaptor
.forClass(StreamVariable.StreamingEndEvent.class);
Mockito.verify(streamVariable)
.streamingFinished(endEventCaptor.capture());
Assert.assertEquals("unknown", endEventCaptor.getValue().getMimeType());
}

@Test
public void doHandleXhrFilePost_missingFilenameHeader_defaultsToUnknown()
throws IOException {
xFilenameHeader = null;
outputStream = new ByteArrayOutputStream();

handler.doHandleXhrFilePost(session, request, response, streamReceiver,
stateNode, 6);

ArgumentCaptor<StreamVariable.StreamingEndEvent> endEventCaptor = ArgumentCaptor
.forClass(StreamVariable.StreamingEndEvent.class);
Mockito.verify(streamVariable)
.streamingFinished(endEventCaptor.capture());
Assert.assertEquals("unknown", endEventCaptor.getValue().getFileName());
}

}
Loading