Vaadin 7.7.13 StreamResource filename with '#' breaks download

We are on Vaadin 7.7.13, and our code for creating a StreamResource sets the filename parameter to a value like “Yozons #1234.pdf” as this was the file name uploaded into our system. For downloading, when we create the StreamResource, the resulting connector URL is invalid and results in a 404. If the ‘#’ is removed from the file name, it works normally.


Below is our Tomcat access log showing that once the ‘#’ was found in the filename, it just stopped the URL prematurely and thus gets a 404:

0:0:0:0:0:0:0:1 - - [13/Mar/2018:19:28:39 -0500]
“GET /open-eSignFormsVaadin/ui/APP/global/0/legacy/0/Yozons%20 HTTP/1.1” 404 16049


Below is the same access log after renaming the file to remove the ‘#’. This then works with a valid URL:

0:0:0:0:0:0:0:1 - - [13/Mar/2018:19:38:36 -0500]
“GET /open-eSignFormsVaadin/ui/APP/global/0/legacy/0/Yozons%201234.pdf HTTP/1.1” 200 84639

This is actually correct behaviour on the browser side. Everything after an anchor (#) is
not
part of the resource requested but only of information to the browser.

Related: https://www.w3.org/TR/html4/struct/links.html#h-12.2.3

The issue isn’t the browser, it’s the StreamResource that doesn’t encode the filename correctly. I do not create the URL, just the filename for the StreamResource (that is, I do not specify any of the URL ‘/APP/globa/0/legacy/0/’ either).

If I do a traditional download all using UTF-8 encoding, and set the content-disposition header filename=“Yozons #123.pdf” there is no issue whatsoever. I can download the file with the ‘#’ symbold from my vaadin app via other schemes without issue, just when I try to use a StreamResource.

It’s only that StreamResource is not generating a useful URL when the filename contains a ‘#’ symbol, which is not really weird as they are uploading and downloading files, and file names are not restricted to un-encoded URL characters. Nowhere in the documention does it suggest when I specify the filename param to StreamResource I should be specially encoding it, as StreamResource already encoded things like the space (%20), which I didn’t do.

No response from Vaadin on this? Is there a reason why a ‘#’ in the filename is not being escaped as necessar for the StreamResource generated URL? It does seem to URL encode spaces, so it’s unclear why it’s not encoding the other characters as the ‘filename’ param is just a file name and not something pre-set for being used in an URL.

The forum is mostly community-driven with some Vaadiners helping outside their regular worktime and this is a rather deep technical question.

Now, I checked the sources for the related classes
https://github.com/vaadin/framework/blob/7.7/server/src/main/java/com/vaadin/server/DownloadStream.java
https://github.com/vaadin/framework/blob/7.7/server/src/main/java/com/vaadin/server/StreamResource.java
https://github.com/vaadin/framework/blob/7.7/server/src/main/java/com/vaadin/util/EncodeUtil.java

and I don’t see any special treatment of ‘#’.

It might be an issue with Tomcat. See
https://wiki.apache.org/tomcat/FAQ/CharacterEncoding#Q2

If that doesn’t help, please file a bug under
https://github.com/vaadin/framework/issues

Best,
Ronny

Created issue https://github.com/vaadin/framework/issues/10747

Hi David,
seems like the problem is that ConnectorResource registered via GlobalResourceHandler
does not URL encode the filename.

Here is the relevant part of code

public void register(Resource resource, ClientConnector ownerConnector) {
    if (resource instanceof ConnectorResource) {
        if (!(ownerConnector instanceof LegacyComponent)) {
            throw new IllegalArgumentException(
                    "A normal ConnectorResource can only be registered for legacy components.");
        }
        ConnectorResource connectorResource = (ConnectorResource) resource;
        if (!legacyResourceKeys.containsKey(resource)) {
            String uri = LEGACY_TYPE + '/'
                    + Integer.toString(nextLegacyId++);
            String filename = connectorResource.getFilename();
            if (filename != null && !filename.isEmpty()) {
                uri += '/' + filename;
            }
            legacyResourceKeys.put(connectorResource, uri);
            legacyResources.put(uri, connectorResource);
            registerResourceUsage(connectorResource, ownerConnector);
        }
    }
}

As you can see filename is taken as is.
By contrast in ResourceReference when using non global handled ConnectorResource
the following code is executed

private static String getConnectorResourceBase(String filename,
        ClientConnector connector) {
    String uri = ApplicationConstants.APP_PROTOCOL_PREFIX
            + ApplicationConstants.APP_PATH + '/'
            + ConnectorResource.CONNECTOR_PATH + '/'
            + connector.getUI().getUIId() + '/' + connector.getConnectorId()
            + '/' + encodeFileName(filename);
    return uri;
}

And here filename is encoded.

Will post this also on github issue.

As a temporarily workaround I think you can override StreamResource.getFileName to return ResourceReference.encodeFileName(super.getFilename()).

Best regards
Marco

I’ll give it a try. But the generated link is partially encoded as the space was turned to %20, unless that URL encoding is separate/distinct from this initial filename encoding.

I didn’t try the override, but I did try URL encoding the filename passed into the StreamResource, and it resulted in a link like: /open-eSignFormsVaadin/ui/APP/global/0/legacy/0/Yozons+%231234.pdf, but it also failed with a 404:

GET /open-eSignFormsVaadin/ui/APP/global/0/legacy/0/Yozons+%231234.pdf HTTP/1.1" 404 16036

Whereas my servlet based file downloader has no issue:

GET /open-eSignFormsVaadin/filedownload/fileUpload/Yozons+%231234.pdf?tfid=cba5eda7-d4b7-49c7-973c-0f8320227d33&puc=eeGQt2QRAdQVHlvXhm4w&tdid=ab502856-cf61-46d4-96b0-3f4675320bc3 HTTP/1.1" 200 84547

Based on Vaadin’s ResourceReference, I have this code now to pre-encode the filename. Note that the “%23” that is the encoded ‘#’ has to be stripped to make it download rather than get the 404 error.

public static String urlEncodeForVaadinStreamResourceFilename(String filename) {
	filename = urlEncode(filename);
	filename = filename.replace("%2F", "/").replace("%5C", "\\").replace("%23", "");
	return filename;
}