diff --git a/config/alfresco/public-rest-context.xml b/config/alfresco/public-rest-context.xml
index 3ae0320bf9..dd54cdc101 100644
--- a/config/alfresco/public-rest-context.xml
+++ b/config/alfresco/public-rest-context.xml
@@ -979,6 +979,7 @@
+
@@ -990,6 +991,7 @@
+
@@ -1001,6 +1003,7 @@
+
.
+ * #L%
+ */
+package org.alfresco.opencmis;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.springframework.extensions.webscripts.WebScriptResponse;
+import org.springframework.extensions.webscripts.servlet.WebScriptServletRuntime;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Wraps an OpenCMIS HttpServletResponse for specific mapping to the Alfresco implementation of OpenCMIS.
+ *
+ * @author janv
+ */
+public class CMISHttpServletResponse implements HttpServletResponse
+{
+ protected HttpServletResponse httpResp;
+
+ protected Set nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf
+
+ private final static String HDR_CONTENT_DISPOSITION = "Content-Disposition";
+
+ public CMISHttpServletResponse(WebScriptResponse res, Set nonAttachContentTypes)
+ {
+ httpResp = WebScriptServletRuntime.getHttpServletResponse(res);
+ this.nonAttachContentTypes = nonAttachContentTypes;
+ }
+
+ @Override
+ public void addCookie(Cookie cookie)
+ {
+ httpResp.addCookie(cookie);
+ }
+
+ @Override
+ public boolean containsHeader(String name)
+ {
+ return httpResp.containsHeader(name);
+ }
+
+ @Override
+ public String encodeURL(String url)
+ {
+ return httpResp.encodeURL(url);
+ }
+
+ @Override
+ public String encodeRedirectURL(String url)
+ {
+ return httpResp.encodeRedirectURL(url);
+ }
+
+ @Override
+ public String encodeUrl(String url)
+ {
+ return encodeUrl(url);
+ }
+
+ @Override
+ public String encodeRedirectUrl(String url)
+ {
+ return httpResp.encodeRedirectUrl(url);
+ }
+
+ @Override
+ public void sendError(int sc, String msg) throws IOException
+ {
+ httpResp.sendError(sc, msg);
+ }
+
+ @Override
+ public void sendError(int sc) throws IOException
+ {
+ httpResp.sendError(sc);
+ }
+
+ @Override
+ public void sendRedirect(String location) throws IOException
+ {
+ httpResp.sendRedirect(location);
+ }
+
+ @Override
+ public void setDateHeader(String name, long date)
+ {
+ httpResp.setDateHeader(name, date);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date)
+ {
+ httpResp.addDateHeader(name, date);
+ }
+
+ @Override
+ public void setHeader(String name, String value)
+ {
+ httpResp.setHeader(name, getStringHeaderValue(name, value, httpResp.getContentType()));
+ }
+
+ @Override
+ public void addHeader(String name, String value)
+ {
+ httpResp.addHeader(name, getStringHeaderValue(name, value, httpResp.getContentType()));
+ }
+
+ private String getStringHeaderValue(String name, String value, String contentType)
+ {
+ if (HDR_CONTENT_DISPOSITION.equals(name))
+ {
+ if (! nonAttachContentTypes.contains(contentType))
+ {
+ if (value.startsWith("inline"))
+ {
+ // force attachment
+ value = value.replace("inline", "attachment");
+ }
+ else if (! value.startsWith("attachment"))
+ {
+ throw new AlfrescoRuntimeException("Unexpected - attachment header could not be set: "+name+" = "+value);
+ }
+ }
+ }
+
+ return value;
+ }
+
+ @Override
+ public void setIntHeader(String name, int value)
+ {
+ httpResp.setIntHeader(name, value);
+ }
+
+ @Override
+ public void addIntHeader(String name, int value)
+ {
+ httpResp.addIntHeader(name, value);
+ }
+
+ @Override
+ public void setStatus(int sc)
+ {
+ httpResp.setStatus(sc);
+ }
+
+ @Override
+ public void setStatus(int sc, String sm)
+ {
+ httpResp.setStatus(sc, sm);
+ }
+
+ @Override
+ public int getStatus()
+ {
+ return httpResp.getStatus();
+ }
+
+ @Override
+ public String getHeader(String name)
+ {
+ return httpResp.getHeader(name);
+ }
+
+ @Override
+ public Collection getHeaders(String name)
+ {
+ return httpResp.getHeaders(name);
+ }
+
+ @Override
+ public Collection getHeaderNames()
+ {
+ return httpResp.getHeaderNames();
+ }
+
+ @Override
+ public String getCharacterEncoding()
+ {
+ return httpResp.getCharacterEncoding();
+ }
+
+ @Override
+ public String getContentType()
+ {
+ return httpResp.getContentType();
+ }
+
+ @Override
+ public ServletOutputStream getOutputStream() throws IOException
+ {
+ return httpResp.getOutputStream();
+ }
+
+ @Override
+ public PrintWriter getWriter() throws IOException
+ {
+ return httpResp.getWriter();
+ }
+
+ @Override
+ public void setCharacterEncoding(String charset)
+ {
+ httpResp.setCharacterEncoding(charset);
+ }
+
+ @Override
+ public void setContentLength(int len)
+ {
+ httpResp.setContentLength(len);
+ }
+
+ @Override
+ public void setContentType(String type)
+ {
+ httpResp.setContentType(type);
+ }
+
+ @Override
+ public void setBufferSize(int size)
+ {
+ httpResp.setBufferSize(size);
+ }
+
+ @Override
+ public int getBufferSize()
+ {
+ return httpResp.getBufferSize();
+ }
+
+ @Override
+ public void flushBuffer() throws IOException
+ {
+ httpResp.flushBuffer();
+ }
+
+ @Override
+ public void resetBuffer()
+ {
+ httpResp.resetBuffer();
+ }
+
+ @Override
+ public boolean isCommitted()
+ {
+ return httpResp.isCommitted();
+ }
+
+ @Override
+ public void reset()
+ {
+ httpResp.reset();
+ }
+
+ @Override
+ public void setLocale(Locale loc)
+ {
+ httpResp.setLocale(loc);
+ }
+
+ @Override
+ public Locale getLocale()
+ {
+ return httpResp.getLocale();
+ }
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/opencmis/CMISServletDispatcher.java b/source/java/org/alfresco/opencmis/CMISServletDispatcher.java
index 07fd479666..d803a91951 100644
--- a/source/java/org/alfresco/opencmis/CMISServletDispatcher.java
+++ b/source/java/org/alfresco/opencmis/CMISServletDispatcher.java
@@ -27,13 +27,17 @@ package org.alfresco.opencmis;
import java.io.IOException;
import java.io.InputStream;
+import java.io.PrintWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -44,10 +48,12 @@ import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRegistration;
import javax.servlet.SessionCookieConfig;
import javax.servlet.SessionTrackingMode;
import javax.servlet.descriptor.JspConfigDescriptor;
+import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
@@ -84,6 +90,8 @@ public abstract class CMISServletDispatcher implements CMISDispatcher
protected CmisVersion cmisVersion;
protected TenantAdminService tenantAdminService;
+ private Set nonAttachContentTypes = Collections.emptySet(); // pre-configured whitelist, eg. images & pdf
+
public void setTenantAdminService(TenantAdminService tenantAdminService)
{
this.tenantAdminService = tenantAdminService;
@@ -129,6 +137,11 @@ public abstract class CMISServletDispatcher implements CMISDispatcher
this.cmisVersion = CmisVersion.fromValue(cmisVersion);
}
+ public void setNonAttachContentTypes(Set nonAttachWhiteList)
+ {
+ this.nonAttachContentTypes = nonAttachWhiteList;
+ }
+
protected synchronized Descriptor getCurrentDescriptor()
{
if(this.currentDescriptor == null)
@@ -191,16 +204,22 @@ public abstract class CMISServletDispatcher implements CMISDispatcher
return httpReqWrapper;
}
+ protected CMISHttpServletResponse getHttpResponse(WebScriptResponse res)
+ {
+ CMISHttpServletResponse httpResWrapper = new CMISHttpServletResponse(res, nonAttachContentTypes);
+
+ return httpResWrapper;
+ }
+
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException
{
try
{
- HttpServletResponse httpResp = WebScriptServletRuntime.getHttpServletResponse(res);
-
- // fake a servlet request.
+ // wrap request & response
+ CMISHttpServletResponse httpResWrapper = getHttpResponse(res);
CMISHttpServletRequest httpReqWrapper = getHttpRequest(req);
- servlet.service(httpReqWrapper, httpResp);
+ servlet.service(httpReqWrapper, httpResWrapper);
}
catch(ServletException e)
{
diff --git a/source/test-java/org/alfresco/rest/api/tests/TestCMIS.java b/source/test-java/org/alfresco/rest/api/tests/TestCMIS.java
index 8ccfa469c6..91b4f92382 100644
--- a/source/test-java/org/alfresco/rest/api/tests/TestCMIS.java
+++ b/source/test-java/org/alfresco/rest/api/tests/TestCMIS.java
@@ -2343,7 +2343,100 @@ public class TestCMIS extends EnterpriseTestApi
}
assertTrue("The aspects should have P:cm:generalclassifiable", mandatoryAspects.contains("P:cm:generalclassifiable"));
}
-
+
+ @Test
+ public void testContentDisposition_MNT_17477() throws Exception
+ {
+ final TestNetwork network1 = getTestFixture().getRandomNetwork();
+
+ String username = "user" + System.currentTimeMillis();
+ PersonInfo personInfo = new PersonInfo(username, username, username, TEST_PASSWORD, null, null, null, null, null, null, null);
+ TestPerson person1 = network1.createUser(personInfo);
+ String person1Id = person1.getId();
+
+ publicApiClient.setRequestContext(new RequestContext(network1.getId(), person1Id));
+ CmisSession cmisSession = publicApiClient.createPublicApiCMISSession(Binding.browser, CMIS_VERSION_11, AlfrescoObjectFactoryImpl.class.getName());
+
+ Folder folder = (Folder)cmisSession.getObjectByPath("/Shared");
+
+ //
+ // Upload test JPG document
+ //
+ String name = GUID.generate() + ".jpg";
+
+ Map properties = new HashMap<>();
+ {
+ properties.put(PropertyIds.OBJECT_TYPE_ID, TYPE_CMIS_DOCUMENT);
+ properties.put(PropertyIds.NAME, name);
+ }
+
+ ContentStreamImpl fileContent = new ContentStreamImpl();
+ {
+ fileContent.setMimeType(MimetypeMap.MIMETYPE_IMAGE_JPEG);
+ fileContent.setStream(this.getClass().getResourceAsStream("/test.jpg"));
+ }
+
+ Document doc = folder.createDocument(properties, fileContent, VersioningState.MAJOR);
+ String docId = doc.getId();
+
+ // note: Content-Disposition can be "inline or "attachment" for content types that are white-listed (eg. specific image types & pdf)
+
+ HttpResponse response = publicApiClient.get(network1.getId()+"/public/cmis/versions/1.1/browser/root/Shared/"+name, null);
+ assertTrue(response.getHeaders().get("Content-Disposition").startsWith("inline"));
+ assertEquals(200, response.getStatusCode());
+
+ response = publicApiClient.get(network1.getId()+"/public/cmis/versions/1.1/browser/root/Shared/"+name+"?download=inline", null);
+ assertTrue(response.getHeaders().get("Content-Disposition").startsWith("inline"));
+ assertEquals(200, response.getStatusCode());
+
+ response = publicApiClient.get(network1.getId()+"/public/cmis/versions/1.1/browser/root/Shared/"+name+"?download=attachment", null);
+ assertTrue(response.getHeaders().get("Content-Disposition").startsWith("attachment"));
+ assertEquals(200, response.getStatusCode());
+
+ // note: AtomPub binding (via OpenCMIS) does not support "download" query parameter
+ response = publicApiClient.get(network1.getId()+"/public/cmis/versions/1.1/atom/content?id="+docId, null);
+ assertTrue(response.getHeaders().get("Content-Disposition").startsWith("attachment"));
+ assertEquals(200, response.getStatusCode());
+
+ //
+ // Create test HTML document
+ //
+ name = GUID.generate() + ".html";
+
+ properties = new HashMap<>();
+ {
+ properties.put(PropertyIds.OBJECT_TYPE_ID, TYPE_CMIS_DOCUMENT);
+ properties.put(PropertyIds.NAME, name);
+ }
+
+ fileContent = new ContentStreamImpl();
+ {
+ ContentWriter writer = new FileContentWriter(TempFileProvider.createTempFile(GUID.generate(), ".html"));
+ writer.putContent("Hello world");
+ ContentReader reader = writer.getReader();
+ fileContent.setMimeType(MimetypeMap.MIMETYPE_HTML);
+ fileContent.setStream(reader.getContentInputStream());
+ }
+
+ doc = folder.createDocument(properties, fileContent, VersioningState.MAJOR);
+ docId = doc.getId();
+
+ // note: Content-Disposition will always be "attachment" for content types that are not white-listed
+
+ response = publicApiClient.get(network1.getId()+"/public/cmis/versions/1.1/browser/root/Shared/"+name, null);
+ assertTrue(response.getHeaders().get("Content-Disposition").startsWith("attachment;"));
+ assertEquals(200, response.getStatusCode());
+
+ response = publicApiClient.get(network1.getId()+"/public/cmis/versions/1.1/browser/root/Shared/"+name+"?download=inline", null);
+ assertTrue(response.getHeaders().get("Content-Disposition").startsWith("attachment;"));
+ assertEquals(200, response.getStatusCode());
+
+ // note: AtomPub binding (via OpenCMIS) does not support "download" query parameter
+ response = publicApiClient.get(network1.getId()+"/public/cmis/versions/1.1/atom/content?id="+docId, null);
+ assertTrue(response.getHeaders().get("Content-Disposition").startsWith("attachment;"));
+ assertEquals(200, response.getStatusCode());
+ }
+
/**
* Test delete version on versions other than latest (most recent) version (MNT-17228)
*/