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