Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #10251: 13.0.6 FileDownload allow disabling of no-store #11349

Merged
merged 1 commit into from
Jan 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/13_0_0/components/filedownload.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ stream the binary data. FileDownload presents an easier way to do the same.
| value | null | StreamedContent | A streamed content instance.
| contentDisposition | attachment | String | Specifies display mode on non-AJAX downloads.
| monitorKey | null | String | Optional key to support monitoring multiple filedownloads on same page.
| store | false | Boolean | Controls the `no-store` attribute on the cache control header. Default is to include `no-store`.

## Getting started with FileDownload
A user command action is required to trigger the file-download process.
Expand Down Expand Up @@ -73,6 +74,10 @@ On regular (non-AJAX) downloads, by default, content is displayed as an `attachm
another alternative is the `inline` mode, in this case browser will try to open the file internally without a prompt.
Note that content disposition is not part of the http standard, although it is widely implemented.

## Browser Caching
Most of the time you want to leave store="false" which is the default to not cache the results. However, in the case of PDF Downloads there have
been issues with some browser like Chrome/Edge. See: https://github.com/primefaces/primefaces/issues/10251

## Monitor Status
When fileDownload is used without AJAX, ajaxStatus cannot apply. Still PrimeFaces provides a feature
to monitor file downloads via client side `monitorDownload(startFunction, endFunction)` method.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,18 @@ public class FileDownloadActionListener implements ActionListener, StateHolder {

private ValueExpression monitorKey;

private ValueExpression store;

public FileDownloadActionListener() {
ResourceUtils.addComponentResource(FacesContext.getCurrentInstance(), "filedownload/filedownload.js");
}

public FileDownloadActionListener(ValueExpression value, ValueExpression contentDisposition, ValueExpression monitorKey) {
public FileDownloadActionListener(ValueExpression value, ValueExpression contentDisposition, ValueExpression monitorKey, ValueExpression store) {
this();
this.value = value;
this.contentDisposition = contentDisposition;
this.monitorKey = monitorKey;
this.store = store;
}

@Override
Expand Down Expand Up @@ -101,7 +104,9 @@ protected void regularDownload(FacesContext context, StreamedContent content) {
? "/"
: externalContext.getRequestContextPath()); // Always add cookies to context root; see #3108
ResourceUtils.addResponseCookie(context, monitorKeyCookieName, "true", cookieOptions);
ResourceUtils.addNoCacheControl(externalContext);

Boolean store = this.store != null ? (Boolean) this.store.getValue(context.getELContext()) : Boolean.FALSE;
ResourceUtils.addNoCacheControl(externalContext, store);

if (content.getContentLength() != null) {
// we can't use externalContext.setResponseContentLength as our contentLength is a long
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ public class FileDownloadTagHandler extends TagHandler {
private final TagAttribute value;
private final TagAttribute contentDisposition;
private final TagAttribute monitorKey;
private final TagAttribute store;

public FileDownloadTagHandler(TagConfig tagConfig) {
super(tagConfig);
value = getRequiredAttribute("value");
contentDisposition = getAttribute("contentDisposition");
monitorKey = getAttribute("monitorKey");
store = getAttribute("store");
}

@Override
Expand All @@ -54,15 +56,19 @@ public void apply(FaceletContext faceletContext, UIComponent parent) throws IOEx
ValueExpression valueVE = value.getValueExpression(faceletContext, Object.class);
ValueExpression contentDispositionVE = null;
ValueExpression monitorKeyVE = null;
ValueExpression storeVE = null;

if (contentDisposition != null) {
contentDispositionVE = contentDisposition.getValueExpression(faceletContext, String.class);
}
if (monitorKey != null) {
monitorKeyVE = monitorKey.getValueExpression(faceletContext, String.class);
}
if (store != null) {
storeVE = store.getValueExpression(faceletContext, Boolean.class);
}

ActionSource actionSource = (ActionSource) parent;
actionSource.addActionListener(new FileDownloadActionListener(valueVE, contentDispositionVE, monitorKeyVE));
actionSource.addActionListener(new FileDownloadActionListener(valueVE, contentDispositionVE, monitorKeyVE, storeVE));
}
}
18 changes: 17 additions & 1 deletion primefaces/src/main/java/org/primefaces/util/ResourceUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,23 @@ public static byte[] toByteArray(InputStream is) {
* @see <a href="https://github.com/primefaces/primefaces/issues/6359">FileDownload: configure Cache-Control</a>
*/
public static void addNoCacheControl(ExternalContext externalContext) {
externalContext.setResponseHeader("Cache-Control", "no-cache, no-store, must-revalidate");
addNoCacheControl(externalContext, false);
}

/**
* Adds no cache pragma to the response to ensure it is not cached. Dynamic downloads should always add this
* to prevent caching and for GDPR.
*
* @param externalContext the ExternalContext we add the pragma to
* @param store used to add no-store or exclude it if false
* @see <a href="https://github.com/primefaces/primefaces/issues/6359">FileDownload: configure Cache-Control</a>
*/
public static void addNoCacheControl(ExternalContext externalContext, boolean store) {
String cacheControl = "no-cache, no-store, must-revalidate";
if (store) {
cacheControl = "no-cache, must-revalidate";
}
externalContext.setResponseHeader("Cache-Control", cacheControl);
externalContext.setResponseHeader("Pragma", "no-cache");
externalContext.setResponseHeader("Expires", "0");
}
Expand Down
18 changes: 13 additions & 5 deletions primefaces/src/main/resources/META-INF/primefaces-p.taglib.xml
Original file line number Diff line number Diff line change
Expand Up @@ -229,31 +229,39 @@
</tag>

<tag>
<description>The legacy way to present dynamic binary data to the client is to write a servlet or a filter and stream the binary data.
FileDownload presents an easier way to do the same.
<description><![CDATA[The legacy way to present dynamic binary data to the client is to write a servlet or a filter and stream the binary data.
FileDownload presents an easier way to do the same.]]>
</description>
<tag-name>fileDownload</tag-name>
<handler-class>
org.primefaces.component.filedownload.FileDownloadTagHandler
</handler-class>
<attribute>
<description>A streamed content instance.</description>
<description><![CDATA[A streamed content instance.]]></description>
<name>value</name>
<required>true</required>
<type>java.lang.Object</type>
</attribute>
<attribute>
<description>Specifies display mode (non-ajax), valid values are "attachment" (default) and "inline".</description>
<description><![CDATA[Specifies display mode (non-ajax), valid values are "attachment" (default) and "inline".]]></description>
<name>contentDisposition</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>Defines setting cookie key for monitorDownload method on client side.</description>
<description><![CDATA[Defines setting cookie key for monitorDownload method on client side.]]></description>
<name>monitorKey</name>
<required>false</required>
<type>java.lang.String</type>
</attribute>
<attribute>
<description>
<![CDATA[Controls the 'no-store' attribute on the cache control header. Default false is to include 'no-store'.]]>
</description>
<name>store</name>
<required>false</required>
<type>java.lang.Boolean</type>
</attribute>
</tag>

<tag>
Expand Down