/*
* Copyright 2000-2016 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the 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:
* <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 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 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;
}
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);
}
}