From 055563a7166f4e3891929e3c8be5799789d68ae1 Mon Sep 17 00:00:00 2001 From: Leif Åstrand Date: Tue, 23 Oct 2012 15:59:54 +0300 Subject: FileDownloader for starting downloads with any component (#9524) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Based on patch by Pekka Hyvönen Change-Id: I9263078ffc624f9cabec6c25264920dfdb430efe --- server/src/com/vaadin/server/FileDownloader.java | 127 +++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 server/src/com/vaadin/server/FileDownloader.java (limited to 'server/src') 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: + * + *

+ * 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 + * Content-Type: application/octet-stream header unless + * {@link #setOverrideContentType(boolean)} has been set to false + * while files served in other ways, (e.g. {@link ExternalResource} or + * {@link ThemeResource}) will not automatically get this treatment. + *

+ * + * @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 + * application/octet-stream to reduce the risk of a browser + * plugin choosing to display the resource instead of downloading it. This + * is by default set to true. + *

+ * 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}). + *

+ * + * @param overrideContentType + * true to override the content type if possible; + * false 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 true if the content type will be overridden when + * possible; false 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; + } + } +} -- cgit v1.2.3