001/* 002 * (C) Copyright 2006-2015 Nuxeo SAS (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Thierry Delprat 016 * Florent Guillaume 017 */ 018package org.nuxeo.ecm.platform.ui.web.download; 019 020import static org.nuxeo.ecm.core.io.download.DownloadService.NXBIGBLOB; 021import static org.nuxeo.ecm.core.io.download.DownloadService.NXBIGZIPFILE; 022import static org.nuxeo.ecm.core.io.download.DownloadService.NXDOWNLOADINFO; 023import static org.nuxeo.ecm.core.io.download.DownloadService.NXFILE; 024 025import java.io.File; 026import java.io.IOException; 027import java.net.URI; 028import java.net.URISyntaxException; 029import java.net.URLEncoder; 030import java.util.Arrays; 031 032import javax.servlet.http.HttpServlet; 033import javax.servlet.http.HttpServletRequest; 034import javax.servlet.http.HttpServletResponse; 035import javax.servlet.http.HttpSession; 036 037import org.apache.commons.lang.StringUtils; 038import org.apache.commons.lang3.tuple.Pair; 039import org.apache.commons.logging.Log; 040import org.apache.commons.logging.LogFactory; 041import org.nuxeo.ecm.core.api.Blob; 042import org.nuxeo.ecm.core.api.Blobs; 043import org.nuxeo.ecm.core.api.CoreInstance; 044import org.nuxeo.ecm.core.api.CoreSession; 045import org.nuxeo.ecm.core.api.DocumentModel; 046import org.nuxeo.ecm.core.api.DocumentRef; 047import org.nuxeo.ecm.core.api.IdRef; 048import org.nuxeo.ecm.core.api.NuxeoException; 049import org.nuxeo.ecm.core.io.download.DownloadHelper; 050import org.nuxeo.ecm.core.io.download.DownloadService; 051import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 052import org.nuxeo.runtime.api.Framework; 053import org.nuxeo.runtime.transaction.TransactionHelper; 054 055/** 056 * Simple download servlet used for big files that can not be downloaded from within the JSF context (because of 057 * buffered ResponseWrapper). 058 */ 059public class DownloadServlet extends HttpServlet { 060 061 private static final long serialVersionUID = 1L; 062 063 private static final Log log = LogFactory.getLog(DownloadServlet.class); 064 065 /** @deprecated since 7.4, use nxfile instead */ 066 @Deprecated 067 public static final String NXBIGFILE = "nxbigfile"; 068 069 @Override 070 public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { 071 try { 072 handleDownload(req, resp); 073 } catch (IOException ioe) { 074 DownloadHelper.handleClientDisconnect(ioe); 075 } 076 } 077 078 protected void handleDownload(HttpServletRequest req, HttpServletResponse resp) throws IOException { 079 String requestURI; 080 try { 081 requestURI = new URI(req.getRequestURI()).getPath(); 082 } catch (URISyntaxException e) { 083 requestURI = req.getRequestURI(); 084 } 085 // remove context 086 String context = VirtualHostHelper.getContextPath(req) + "/"; 087 if (!requestURI.startsWith(context)) { 088 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid URL syntax"); 089 return; 090 } 091 String localURI = requestURI.substring(context.length()); 092 // find what to do 093 int slash = localURI.indexOf('/'); 094 if (slash < 0) { 095 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid URL syntax"); 096 return; 097 } 098 String what = localURI.substring(0, slash); 099 String path = localURI.substring(slash + 1); 100 switch (what) { 101 case NXFILE: 102 case NXBIGFILE: 103 downloadBlob(req, resp, path, false); 104 break; 105 case NXDOWNLOADINFO: 106 // used by nxdropout.js 107 downloadBlob(req, resp, path, true); 108 break; 109 case NXBIGZIPFILE: 110 // handle the download for a big zip created in the tmp directory; 111 // the name of this zip is sent in the request 112 handleDownloadTemporaryZip(req, resp, path); 113 break; 114 case NXBIGBLOB: 115 // handle the download of a Blob referenced in HTTP Request or Session 116 handleDownloadSessionBlob(req, resp, path); 117 break; 118 default: 119 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid URL syntax"); 120 } 121 } 122 123 protected void downloadBlob(HttpServletRequest req, HttpServletResponse resp, String urlPath, boolean info) 124 throws IOException { 125 String[] parts = urlPath.split("/"); 126 if (parts.length < 2) { 127 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Invalid URL syntax"); 128 return; 129 } 130 String repositoryName = parts[0]; 131 String docId = parts[1]; 132 boolean tx = false; 133 try { 134 if (!TransactionHelper.isTransactionActive()) { 135 // Manually start and stop a transaction around repository access to be able to release transactional 136 // resources without waiting for the download that can take a long time (longer than the transaction 137 // timeout) especially if the client or the connection is slow. 138 tx = TransactionHelper.startTransaction(); 139 } 140 try (CoreSession session = CoreInstance.openCoreSession(repositoryName)) { 141 DocumentRef docRef = new IdRef(docId); 142 if (!session.exists(docRef)) { 143 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No Blob found"); 144 return; 145 } 146 DocumentModel doc = session.getDocument(docRef); 147 Pair<String, String> pair = parsePath(urlPath); 148 String xpath = pair.getLeft(); 149 String filename = pair.getRight(); 150 DownloadService downloadService = Framework.getService(DownloadService.class); 151 if (info) { 152 Blob blob = downloadService.resolveBlob(doc, xpath); 153 if (blob == null) { 154 resp.sendError(HttpServletResponse.SC_NOT_FOUND, "No blob found"); 155 return; 156 } 157 String downloadUrl = VirtualHostHelper.getBaseURL(req) 158 + downloadService.getDownloadUrl(doc, xpath, filename); 159 String result = blob.getMimeType() + ':' + URLEncoder.encode(blob.getFilename(), "UTF-8") + ':' 160 + downloadUrl; 161 resp.setContentType("text/plain"); 162 resp.getWriter().write(result); 163 resp.getWriter().flush(); 164 165 } else { 166 downloadService.downloadBlob(req, resp, doc, xpath, null, filename, "download"); 167 } 168 } 169 } catch (NuxeoException e) { 170 if (tx) { 171 TransactionHelper.setTransactionRollbackOnly(); 172 } 173 throw new IOException(e); 174 } finally { 175 if (tx) { 176 TransactionHelper.commitOrRollbackTransaction(); 177 } 178 } 179 } 180 181 // first two parts are repository name and doc id, already parsed 182 protected static Pair<String, String> parsePath(String urlPath) { 183 String[] parts = urlPath.split("/"); 184 int length = parts.length; 185 String xpath; 186 String filename; 187 if (length == 2) { 188 xpath = DownloadService.BLOBHOLDER_0; 189 filename = null; 190 } else if (length == 3) { 191 xpath = parts[2]; 192 filename = null; 193 } else { 194 xpath = StringUtils.join(Arrays.asList(parts).subList(2, length - 1), "/"); 195 filename = parts[length - 1]; 196 } 197 return Pair.of(xpath, filename); 198 } 199 200 // used by DownloadFile operation 201 protected void handleDownloadSessionBlob(HttpServletRequest req, HttpServletResponse resp, String blobId) 202 throws IOException { 203 Blob blob = (Blob) req.getAttribute(blobId); 204 if (blob != null) { 205 req.removeAttribute(blobId); 206 } else { 207 HttpSession session = req.getSession(false); 208 if (session == null) { 209 log.error("Unable to download blob " + blobId + " since the holding http session does not exist"); 210 return; 211 } 212 blob = (Blob) session.getAttribute(blobId); 213 if (blob == null) { 214 return; 215 } 216 session.removeAttribute(blobId); 217 } 218 DownloadService downloadService = Framework.getService(DownloadService.class); 219 downloadService.downloadBlob(req, resp, null, null, blob, null, "operation"); 220 } 221 222 // used by ClipboardActionsBean 223 protected void handleDownloadTemporaryZip(HttpServletRequest req, HttpServletResponse resp, String filePath) 224 throws IOException { 225 String[] pathParts = filePath.split("/"); 226 String tmpFileName = pathParts[0]; 227 File tmpZip = new File(System.getProperty("java.io.tmpdir") + "/" + tmpFileName); 228 try { 229 Blob zipBlob = Blobs.createBlob(tmpZip); 230 DownloadService downloadService = Framework.getService(DownloadService.class); 231 downloadService.downloadBlob(req, resp, null, null, zipBlob, "clipboard.zip", "clipboardZip"); 232 } finally { 233 tmpZip.delete(); 234 } 235 } 236 237}