com.vaadin.server.FileDownloader Maven / Gradle / Ivy
/*
* Copyright (C) 2000-2024 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.server;
import java.io.IOException;
import com.vaadin.shared.extension.filedownloader.FileDownloaderState;
import com.vaadin.ui.AbstractComponent;
/**
* Extension that starts a download when the extended component is clicked. This
* is used to overcome two challenges:
*
* - 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.
* - 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.
*
*
* 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);
}
/**
* Add this extension to the target component.
*
* @param target
* the component to attach this extension to
*/
public void extend(AbstractComponent target) {
super.extend(target);
}
/**
* Add this extension to the {@code EventTrigger}.
*
* @param eventTrigger
* the trigger to attach this extension to
* @since 8.4
*/
public void extend(EventTrigger eventTrigger) {
super.extend(eventTrigger.getConnector());
getState().partInformation = eventTrigger.getPartInformation();
}
/**
* 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 the resource that is downloaded when the extended component is
* clicked.
*
* @param resource
* the resource to download
*/
public void setFileDownloadResource(Resource resource) {
setResource("dl", resource);
}
/**
* Sets whether the content type of served resources should be overridden 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.
*
* @return true
if the content type will be overridden when
* possible; false
if the original content type will be
* used.
* @see #setOverrideContentType(boolean)
*/
public boolean isOverrideContentType() {
return overrideContentType;
}
/**
* {@inheritDoc}
*
* @throws IOException
* if something goes wrong with the download or the user
* cancelled the file download process.
*/
@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;
}
VaadinSession session = getSession();
session.lock();
DownloadStream stream;
try {
Resource resource = getFileDownloadResource();
if (!(resource instanceof ConnectorResource)) {
return false;
}
stream = ((ConnectorResource) resource).getStream();
String contentDisposition = stream
.getParameter(DownloadStream.CONTENT_DISPOSITION);
if (contentDisposition == null) {
contentDisposition = "attachment; " + DownloadStream
.getContentDispositionFilename(stream.getFileName());
}
stream.setParameter(DownloadStream.CONTENT_DISPOSITION,
contentDisposition);
// Content-Type to block eager browser plug-ins from hijacking
// the file
if (isOverrideContentType()) {
stream.setContentType("application/octet-stream;charset=UTF-8");
}
} finally {
session.unlock();
}
stream.writeResponse(request, response);
return true;
}
@Override
protected FileDownloaderState getState() {
return (FileDownloaderState) super.getState();
}
@Override
protected FileDownloaderState getState(boolean markAsDirty) {
return (FileDownloaderState) super.getState(markAsDirty);
}
}