diff options
author | Leif Åstrand <leif@vaadin.com> | 2012-10-23 15:59:54 +0300 |
---|---|---|
committer | Leif Åstrand <leif@vaadin.com> | 2012-10-23 15:59:54 +0300 |
commit | 055563a7166f4e3891929e3c8be5799789d68ae1 (patch) | |
tree | 1dcff84136dd8dc74baf7d3e4298b1dfe9663fe6 /server/src | |
parent | 7ec5bade7299a0989f7596ac346b8f0ec698d3cb (diff) | |
download | vaadin-framework-055563a7166f4e3891929e3c8be5799789d68ae1.tar.gz vaadin-framework-055563a7166f4e3891929e3c8be5799789d68ae1.zip |
FileDownloader for starting downloads with any component (#9524)
* Based on patch by Pekka Hyvönen
Change-Id: I9263078ffc624f9cabec6c25264920dfdb430efe
Diffstat (limited to 'server/src')
-rw-r--r-- | server/src/com/vaadin/server/FileDownloader.java | 127 |
1 files changed, 127 insertions, 0 deletions
diff --git a/server/src/com/vaadin/server/FileDownloader.java b/server/src/com/vaadin/server/FileDownloader.java new file mode 100644 index 0000000000..a07c0d3ed1 --- /dev/null +++ b/server/src/com/vaadin/server/FileDownloader.java @@ -0,0 +1,127 @@ +package com.vaadin.server; + +import java.io.IOException; + +import com.vaadin.ui.AbstractComponent; + +/** + * Extension that starts a download when the extended component is clicked. This + * is used to overcome two challenges: + * <ul> + * <li>Resource should be bound to a component to allow it to be garbage + * collected when there are no longer any ways of reaching the resource.</li> + * <li>Download should be started directly when the user clicks e.g. a Button + * without going through a server-side click listener to avoid triggering + * security warnings in some browsers.</li> + * </ul> + * <p> + * Please note that the download will be started in an iframe, which means that + * care should be taken to avoid serving content types that might make the + * browser attempt to show the content using a plugin instead of downloading it. + * Connector resources (e.g. {@link FileResource} and {@link ClassResource}) + * will automatically be served using a + * <code>Content-Type: application/octet-stream</code> header unless + * {@link #setOverrideContentType(boolean)} has been set to <code>false</code> + * while files served in other ways, (e.g. {@link ExternalResource} or + * {@link ThemeResource}) will not automatically get this treatment. + * </p> + * + * @author Vaadin Ltd + * @since 7.0.0 + */ +public class FileDownloader extends AbstractExtension { + + private boolean overrideContentType = true; + + /** + * Creates a new file downloader for the given resource. To use the + * downloader, you should also {@link #extend(AbstractClientConnector)} the + * component. + * + * @param resource + * the resource to download when the user clicks the extended + * component. + */ + public FileDownloader(Resource resource) { + if (resource == null) { + throw new IllegalArgumentException("resource may not be null"); + } + setResource("dl", resource); + } + + public void extend(AbstractComponent target) { + super.extend(target); + } + + /** + * Gets the resource set for download. + * + * @return the resource that will be downloaded if clicking the extended + * component + */ + public Resource getFileDownloadResource() { + return getResource("dl"); + } + + /** + * Sets whether the content type of served resources should be overriden to + * <code>application/octet-stream</code> to reduce the risk of a browser + * plugin choosing to display the resource instead of downloading it. This + * is by default set to <code>true</code>. + * <p> + * Please note that this only affects Connector resources (e.g. + * {@link FileResource} and {@link ClassResource}) but not other resource + * types (e.g. {@link ExternalResource} or {@link ThemeResource}). + * </p> + * + * @param overrideContentType + * <code>true</code> to override the content type if possible; + * <code>false</code> to use the original content type. + */ + public void setOverrideContentType(boolean overrideContentType) { + this.overrideContentType = overrideContentType; + } + + /** + * Checks whether the content type should be overridden. + * + * @see #setOverrideContentType(boolean) + * + * @return <code>true</code> if the content type will be overridden when + * possible; <code>false</code> if the original content type will be + * used. + */ + public boolean isOverrideContentType() { + return overrideContentType; + } + + @Override + public boolean handleConnectorRequest(VaadinRequest request, + VaadinResponse response, String path) throws IOException { + if (!path.matches("dl(/.*)?")) { + // Ignore if it isn't for us + return false; + } + + Resource resource = getFileDownloadResource(); + if (resource instanceof ConnectorResource) { + DownloadStream stream = ((ConnectorResource) resource).getStream(); + + if (stream.getParameter("Content-Disposition") == null) { + // Content-Disposition: attachment generally forces download + stream.setParameter("Content-Disposition", + "attachment; filename=\"" + stream.getFileName() + "\""); + } + + // Content-Type to block eager browser plug-ins from hijacking the + // file + if (isOverrideContentType()) { + stream.setContentType("application/octet-stream;charset=UTF-8"); + } + stream.writeResponse(request, response); + return true; + } else { + return false; + } + } +} |