001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ade.upload; 029 030import org.opencms.db.CmsDbSqlException; 031import org.opencms.db.CmsImportFolder; 032import org.opencms.file.CmsFile; 033import org.opencms.file.CmsObject; 034import org.opencms.file.CmsProperty; 035import org.opencms.file.CmsPropertyDefinition; 036import org.opencms.file.CmsResource; 037import org.opencms.file.CmsResourceFilter; 038import org.opencms.file.collectors.A_CmsResourceCollector; 039import org.opencms.file.collectors.I_CmsCollectorPostCreateHandler; 040import org.opencms.file.types.CmsResourceTypeFolder; 041import org.opencms.file.types.CmsResourceTypePlain; 042import org.opencms.gwt.shared.CmsUploadRestrictionInfo; 043import org.opencms.gwt.shared.I_CmsUploadConstants; 044import org.opencms.i18n.CmsMessages; 045import org.opencms.json.JSONArray; 046import org.opencms.json.JSONException; 047import org.opencms.json.JSONObject; 048import org.opencms.jsp.CmsJspBean; 049import org.opencms.loader.CmsLoaderException; 050import org.opencms.lock.CmsLockException; 051import org.opencms.main.CmsException; 052import org.opencms.main.CmsLog; 053import org.opencms.main.OpenCms; 054import org.opencms.security.CmsSecurityException; 055import org.opencms.util.CmsCollectionsGenericWrapper; 056import org.opencms.util.CmsPair; 057import org.opencms.util.CmsRequestUtil; 058import org.opencms.util.CmsStringUtil; 059import org.opencms.util.CmsUUID; 060 061import java.io.ByteArrayInputStream; 062import java.io.File; 063import java.io.IOException; 064import java.io.InputStream; 065import java.io.UnsupportedEncodingException; 066import java.net.URLDecoder; 067import java.util.ArrayList; 068import java.util.Collections; 069import java.util.HashMap; 070import java.util.List; 071import java.util.Map; 072 073import javax.servlet.http.HttpServletRequest; 074import javax.servlet.http.HttpServletResponse; 075import javax.servlet.jsp.PageContext; 076 077import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; 078import org.apache.commons.compress.archivers.zip.ZipFile; 079import org.apache.commons.fileupload.FileItem; 080import org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException; 081import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; 082import org.apache.commons.fileupload.disk.DiskFileItemFactory; 083import org.apache.commons.fileupload.servlet.ServletFileUpload; 084import org.apache.commons.lang3.StringUtils; 085import org.apache.commons.logging.Log; 086 087import com.google.common.collect.HashMultimap; 088 089/** 090 * Bean to be used in JSP scriptlet code that provides 091 * access to the upload functionality.<p> 092 * 093 * @since 8.0.0 094 */ 095public class CmsUploadBean extends CmsJspBean { 096 097 /** The default upload timeout. */ 098 public static final int DEFAULT_UPLOAD_TIMEOUT = 20000; 099 100 /** Key name for the session attribute that stores the id of the current listener. */ 101 public static final String SESSION_ATTRIBUTE_LISTENER_ID = "__CmsUploadBean.LISTENER"; 102 103 /** The log object for this class. */ 104 private static final Log LOG = CmsLog.getLog(CmsUploadBean.class); 105 106 /** A static map of all listeners. */ 107 private static Map<CmsUUID, CmsUploadListener> m_listeners = new HashMap<CmsUUID, CmsUploadListener>(); 108 109 /** The virus scanner instance to use for uploads. */ 110 private I_CmsVirusScanner m_scanner; 111 112 /** The gwt message bundle. */ 113 private CmsMessages m_bundle = org.opencms.ade.upload.Messages.get().getBundle(); 114 115 /** Signals that the start method is called. */ 116 private boolean m_called; 117 118 /** A list of the file items to upload. */ 119 private List<FileItem> m_multiPartFileItems; 120 121 /** The map of parameters read from the current request. */ 122 private Map<String, String[]> m_parameterMap; 123 124 /** The names by id of the resources that have been created successfully. */ 125 private HashMap<CmsUUID, String> m_resourcesCreated = new HashMap<CmsUUID, String>(); 126 127 /** A CMS context for the root site. */ 128 private CmsObject m_rootCms; 129 130 /** The server side upload delay. */ 131 private int m_uploadDelay; 132 133 /** The upload hook URI. */ 134 private String m_uploadHook; 135 136 private CmsUploadRestrictionInfo m_uploadRestrictionInfo; 137 138 /** The viruses found while processing the uploads, with the file names as keys. */ 139 private HashMultimap<String, String> m_viruses = HashMultimap.create(); 140 141 /** 142 * Constructor, with parameters.<p> 143 * 144 * @param context the JSP page context object 145 * @param req the JSP request 146 * @param res the JSP response 147 * 148 * @throws CmsException if something goes wrong 149 */ 150 public CmsUploadBean(PageContext context, HttpServletRequest req, HttpServletResponse res) 151 throws CmsException { 152 153 super(); 154 init(context, req, res); 155 156 m_rootCms = OpenCms.initCmsObject(getCmsObject()); 157 m_rootCms.getRequestContext().setSiteRoot(""); 158 m_uploadRestrictionInfo = OpenCms.getWorkplaceManager().getUploadRestriction().getUploadRestrictionInfo( 159 m_rootCms); 160 if (OpenCms.getWorkplaceManager().isVirusScannerEnabled()) { 161 m_scanner = OpenCms.getWorkplaceManager().getVirusScanner(); 162 } 163 } 164 165 /** 166 * Returns the listener for given CmsUUID.<p> 167 * 168 * @param listenerId the uuid 169 * 170 * @return the according listener 171 */ 172 public static CmsUploadListener getCurrentListener(CmsUUID listenerId) { 173 174 return m_listeners.get(listenerId); 175 } 176 177 /** 178 * Returns the VFS path for the given filename and folder.<p> 179 * 180 * @param cms the cms object 181 * @param fileName the filename to combine with the folder 182 * @param folder the folder to combine with the filename 183 * @param keepFileNames skip file name translation if true 184 * 185 * @return the VFS path for the given filename and folder 186 */ 187 public static String getNewResourceName(CmsObject cms, String fileName, String folder, boolean keepFileNames) { 188 189 String newResname = CmsResource.getName(fileName.replace('\\', '/')); 190 if (!keepFileNames) { 191 newResname = cms.getRequestContext().getFileTranslator().translateResource(newResname); 192 } 193 newResname = folder + newResname; 194 return newResname; 195 } 196 197 /** 198 * Sets the uploadDelay.<p> 199 * 200 * @param uploadDelay the uploadDelay to set 201 */ 202 public void setUploadDelay(int uploadDelay) { 203 204 m_uploadDelay = uploadDelay; 205 } 206 207 /** 208 * Starts the upload.<p> 209 * 210 * @return the response String (JSON) 211 */ 212 public String start() { 213 214 // ensure that this method can only be called once 215 if (m_called) { 216 throw new UnsupportedOperationException(); 217 } 218 m_called = true; 219 220 // create a upload listener 221 CmsUploadListener listener = createListener(); 222 try { 223 // try to parse the request 224 parseRequest(listener); 225 // try to create the resources on the VFS 226 createResources(listener); 227 // trigger update offline indexes, important for gallery search 228 OpenCms.getSearchManager().updateOfflineIndexes(); 229 } catch (CmsException e) { 230 // an error occurred while creating the resources on the VFS, create a special error message 231 LOG.error(e.getMessage(), e); 232 return generateResponse(Boolean.FALSE, getCreationErrorMessage(), formatStackTrace(e)); 233 } catch (CmsUploadException e) { 234 // an expected error occurred while parsing the request, the error message is already set in the exception 235 LOG.debug(e.getMessage(), e); 236 return generateResponse(Boolean.FALSE, e.getMessage(), formatStackTrace(e)); 237 } catch (Throwable e) { 238 // an unexpected error occurred while parsing the request, create a non-specific error message 239 LOG.error(e.getMessage(), e); 240 String message = m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_UNEXPECTED_0); 241 return generateResponse(Boolean.FALSE, message, formatStackTrace(e)); 242 } finally { 243 removeListener(listener.getId()); 244 } 245 // the upload was successful inform the user about success 246 return generateResponse(Boolean.TRUE, m_bundle.key(org.opencms.ade.upload.Messages.LOG_UPLOAD_SUCCESS_0), ""); 247 } 248 249 /** 250 * Creates a upload listener and puts it into the static map.<p> 251 * 252 * @return the listener 253 */ 254 private CmsUploadListener createListener() { 255 256 CmsUploadListener listener = new CmsUploadListener(getRequest().getContentLength()); 257 listener.setDelay(m_uploadDelay); 258 m_listeners.put(listener.getId(), listener); 259 getRequest().getSession().setAttribute(SESSION_ATTRIBUTE_LISTENER_ID, listener.getId()); 260 return listener; 261 } 262 263 /** 264 * Creates the resources.<p> 265 * @param listener the listener 266 * 267 * @throws CmsException if something goes wrong 268 * @throws UnsupportedEncodingException in case the encoding is not supported 269 */ 270 private void createResources(CmsUploadListener listener) throws CmsException, UnsupportedEncodingException { 271 272 CmsObject cms = getCmsObject(); 273 String[] isRootPathVals = m_parameterMap.get(I_CmsUploadConstants.UPLOAD_IS_ROOT_PATH_FIELD_NAME); 274 if ((isRootPathVals != null) && (isRootPathVals.length > 0) && Boolean.parseBoolean(isRootPathVals[0])) { 275 cms = m_rootCms; 276 } 277 // get the target folder 278 String targetFolder = getTargetFolder(cms); 279 m_uploadHook = OpenCms.getWorkplaceManager().getUploadHook(cms, targetFolder); 280 if (m_scanner != null) { 281 m_scanner.test(); 282 } 283 284 List<String> filesToUnzip = getFilesToUnzip(); 285 286 // iterate over the list of files to upload and create each single resource 287 for (FileItem fileItem : m_multiPartFileItems) { 288 if ((fileItem != null) && (!fileItem.isFormField())) { 289 // read the content of the file 290 byte[] content = fileItem.get(); 291 fileItem.delete(); 292 293 // determine the new resource name 294 String fileName = m_parameterMap.get( 295 fileItem.getFieldName() + I_CmsUploadConstants.UPLOAD_FILENAME_ENCODED_SUFFIX)[0]; 296 String originalFileName = m_parameterMap.get( 297 fileItem.getFieldName() + I_CmsUploadConstants.UPLOAD_ORIGINAL_FILENAME_ENCODED_SUFFIX)[0]; 298 List<String> viruses = scan(originalFileName, content); 299 300 fileName = URLDecoder.decode(fileName, "UTF-8"); 301 originalFileName = URLDecoder.decode(originalFileName, "UTF-8"); 302 if (viruses.size() > 0) { 303 LOG.info("Found viruses in uploaded file " + originalFileName + ": " + viruses); 304 m_viruses.putAll(originalFileName, viruses); 305 } else { 306 if (filesToUnzip.contains(CmsResource.getName(fileName.replace('\\', '/')))) { 307 308 // scan all contents of ZIP before creating anything in the VFS 309 boolean foundVirus = false; 310 try { 311 ZipFile zip = ZipFile.builder().setByteArray(content).get(); 312 for (ZipArchiveEntry entry : (Iterable<ZipArchiveEntry>)() -> zip.getEntries().asIterator()) { 313 if (entry.isDirectory() || entry.isUnixSymlink()) { 314 continue; 315 } 316 viruses = scan(originalFileName + " >> " + entry.getName(), zip, entry); 317 if (viruses.size() > 0) { 318 LOG.info("Found viruses in uploaded file " + originalFileName + ": " + viruses); 319 foundVirus = true; 320 m_viruses.putAll(originalFileName, viruses); 321 break; 322 } 323 } 324 } catch (IOException e) { 325 LOG.error(e.getLocalizedMessage(), e); 326 } 327 if (foundVirus) { 328 continue; 329 } 330 331 // import the zip 332 CmsImportFolder importZip = new CmsImportFolder(); 333 try { 334 importZip.importZip(content, targetFolder, cms, false); 335 } finally { 336 // get the created resource names 337 for (CmsResource importedResource : importZip.getImportedResources()) { 338 m_resourcesCreated.put(importedResource.getStructureId(), importedResource.getName()); 339 } 340 } 341 } else { 342 // create the resource 343 CmsResource importedResource = createSingleResource(cms, fileName, targetFolder, content); 344 if (importedResource != null) { 345 // add the name of the created resource to the list of successful created resources 346 m_resourcesCreated.put(importedResource.getStructureId(), importedResource.getName()); 347 } 348 } 349 } 350 351 if (listener.isCanceled()) { 352 throw listener.getException(); 353 } 354 } 355 } 356 357 String postCreateHandlerStr = getPostCreateHandler(); 358 if (postCreateHandlerStr != null) { 359 try { 360 CmsPair<String, String> classAndConfig = I_CmsCollectorPostCreateHandler.splitClassAndConfig( 361 postCreateHandlerStr); 362 String className = classAndConfig.getFirst(); 363 String config = classAndConfig.getSecond(); 364 I_CmsCollectorPostCreateHandler handler = A_CmsResourceCollector.getPostCreateHandler(className); 365 for (Map.Entry<CmsUUID, String> resourceEntry : m_resourcesCreated.entrySet()) { 366 try { 367 CmsUUID structureId = resourceEntry.getKey(); 368 CmsResource resource = cms.readResource(structureId, CmsResourceFilter.IGNORE_EXPIRATION); 369 if (!resource.isFolder()) { 370 handler.onCreate(cms, resource, false, config); 371 } 372 } catch (Exception e) { 373 LOG.error(e.getLocalizedMessage(), e); 374 } 375 } 376 } catch (Exception e) { 377 LOG.error(e.getLocalizedMessage(), e); 378 } 379 380 } 381 } 382 383 /** 384 * Creates a single resource and returns the new resource.<p> 385 * 386 * @param cms the CMS context to use 387 * @param fileName the name of the resource to create 388 * @param targetFolder the folder to store the new resource 389 * @param content the content of the resource to create 390 * 391 * @return the new resource 392 * 393 * @throws CmsException if something goes wrong 394 * @throws CmsLoaderException if something goes wrong 395 * @throws CmsDbSqlException if something goes wrong 396 */ 397 @SuppressWarnings("deprecation") 398 private CmsResource createSingleResource(CmsObject cms, String fileName, String targetFolder, byte[] content) 399 throws CmsException, CmsLoaderException, CmsDbSqlException { 400 401 String folderRootPath = cms.getRequestContext().addSiteRoot(targetFolder); 402 if (!m_uploadRestrictionInfo.isUploadEnabled(folderRootPath)) { 403 LOG.error("Upload not enabled for folder " + targetFolder); 404 return null; 405 } 406 407 String newResname = getNewResourceName(cms, fileName, targetFolder, isKeepFileNames()); 408 CmsResource createdResource = null; 409 410 // determine Title property value to set on new resource 411 String title = fileName; 412 if (title.lastIndexOf('.') != -1) { 413 title = title.substring(0, title.lastIndexOf('.')); 414 } 415 416 // fileName really shouldn't contain the full path, but for some reason it does sometimes when the client is 417 // running on IE7, so we eliminate anything before and including the last slash or backslash in the title 418 // before setting it as a property. 419 420 int backslashIndex = title.lastIndexOf('\\'); 421 if (backslashIndex != -1) { 422 title = title.substring(backslashIndex + 1); 423 } 424 425 int slashIndex = title.lastIndexOf('/'); 426 if (slashIndex != -1) { 427 title = title.substring(slashIndex + 1); 428 } 429 430 List<CmsProperty> properties = new ArrayList<CmsProperty>(1); 431 CmsProperty titleProp = new CmsProperty(); 432 titleProp.setName(CmsPropertyDefinition.PROPERTY_TITLE); 433 if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) { 434 titleProp.setStructureValue(title); 435 } else { 436 titleProp.setResourceValue(title); 437 } 438 properties.add(titleProp); 439 440 int plainId = OpenCms.getResourceManager().getResourceType( 441 CmsResourceTypePlain.getStaticTypeName()).getTypeId(); 442 if (!cms.existsResource(newResname, CmsResourceFilter.IGNORE_EXPIRATION)) { 443 // if the resource does not exist, create it 444 445 try { 446 // create the resource 447 int resTypeId = OpenCms.getResourceManager().getDefaultTypeForName(newResname).getTypeId(); 448 createdResource = cms.createResource(newResname, resTypeId, content, properties); 449 try { 450 cms.unlockResource(newResname); 451 } catch (CmsLockException e) { 452 LOG.info("Couldn't unlock uploaded file", e); 453 } 454 } catch (CmsSecurityException e) { 455 // in case of not enough permissions, try to create a plain text file 456 createdResource = cms.createResource(newResname, plainId, content, properties); 457 cms.unlockResource(newResname); 458 } catch (CmsDbSqlException sqlExc) { 459 // SQL error, probably the file is too large for the database settings, delete file 460 cms.lockResource(newResname); 461 cms.deleteResource(newResname, CmsResource.DELETE_PRESERVE_SIBLINGS); 462 throw sqlExc; 463 } catch (OutOfMemoryError e) { 464 // the file is to large try to clear up 465 cms.lockResource(newResname); 466 cms.deleteResource(newResname, CmsResource.DELETE_PRESERVE_SIBLINGS); 467 throw e; 468 } 469 470 } else { 471 // if the resource already exists, replace it 472 CmsResource res = cms.readResource(newResname, CmsResourceFilter.ALL); 473 boolean wasLocked = false; 474 try { 475 if (!cms.getLock(res).isOwnedBy(cms.getRequestContext().getCurrentUser())) { 476 cms.lockResource(res); 477 wasLocked = true; 478 } 479 CmsFile file = cms.readFile(res); 480 byte[] contents = file.getContents(); 481 try { 482 cms.replaceResource(newResname, res.getTypeId(), content, null); 483 createdResource = res; 484 } catch (CmsDbSqlException sqlExc) { 485 // SQL error, probably the file is too large for the database settings, restore content 486 file.setContents(contents); 487 cms.writeFile(file); 488 throw sqlExc; 489 } catch (OutOfMemoryError e) { 490 // the file is to large try to clear up 491 file.setContents(contents); 492 cms.writeFile(file); 493 throw e; 494 } 495 } finally { 496 if (wasLocked) { 497 cms.unlockResource(res); 498 } 499 } 500 } 501 return createdResource; 502 } 503 504 /** 505 * Creates the upload target folder. 506 * 507 * @param cms the CMS context 508 * @param targetFolder the upload target folder 509 * @return the new folder 510 * @throws CmsException if something goes wrong 511 */ 512 private CmsResource createTargetFolder(CmsObject cms, String targetFolder) throws CmsException { 513 514 List<String> parentFolders = new ArrayList<>(); 515 String currentFolder = targetFolder; 516 while ((currentFolder != null) && !cms.existsResource(currentFolder, CmsResourceFilter.IGNORE_EXPIRATION)) { 517 parentFolders.add(currentFolder); 518 currentFolder = CmsResource.getParentFolder(currentFolder); 519 } 520 Collections.reverse(parentFolders); 521 CmsResource lastCreated = null; 522 CmsResource firstCreated = null; 523 for (String parentFolder : parentFolders) { 524 525 CmsResource createdFolder = cms.createResource( 526 parentFolder, 527 OpenCms.getResourceManager().getResourceType(CmsResourceTypeFolder.getStaticTypeName())); 528 lastCreated = createdFolder; 529 if (firstCreated == null) { 530 firstCreated = createdFolder; 531 } 532 } 533 cms.unlockResource(firstCreated); 534 return lastCreated; 535 536 } 537 538 /** 539 * Returns the stacktrace of the given exception as String.<p> 540 * 541 * @param e the exception 542 * 543 * @return the stacktrace as String 544 */ 545 private String formatStackTrace(Throwable e) { 546 547 return StringUtils.join(CmsLog.render(e), '\n'); 548 } 549 550 /** 551 * Generates a JSON object and returns its String representation for the response.<p> 552 * 553 * @param success <code>true</code> if the upload was successful 554 * @param message the message to display 555 * @param stacktrace the stack trace in case of an error 556 * 557 * @return the the response String 558 */ 559 private String generateResponse(Boolean success, String message, String stacktrace) { 560 561 JSONObject result = new JSONObject(); 562 try { 563 result.put(I_CmsUploadConstants.KEY_SUCCESS, success); 564 result.put(I_CmsUploadConstants.KEY_MESSAGE, message); 565 result.put(I_CmsUploadConstants.KEY_STACKTRACE, stacktrace); 566 result.put(I_CmsUploadConstants.KEY_REQUEST_SIZE, getRequest().getContentLength()); 567 568 // UUIDs get converted to strings when generating the JSON text 569 result.put(I_CmsUploadConstants.KEY_UPLOADED_FILES, new JSONArray(m_resourcesCreated.keySet())); 570 571 result.put(I_CmsUploadConstants.KEY_UPLOADED_FILE_NAMES, new JSONArray(m_resourcesCreated.values())); 572 573 JSONObject virusWarnings = new JSONObject(); 574 for (String filename : m_viruses.keys()) { 575 JSONArray viruses = new JSONArray(new ArrayList<>(m_viruses.get(filename))); 576 virusWarnings.put(filename, viruses); 577 } 578 result.put(I_CmsUploadConstants.ATTR_VIRUS_WARNINGS, virusWarnings); 579 580 if (m_uploadHook != null) { 581 result.put(I_CmsUploadConstants.KEY_UPLOAD_HOOK, m_uploadHook); 582 } 583 } catch (JSONException e) { 584 LOG.error(m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_JSON_0), e); 585 } 586 return result.toString(); 587 } 588 589 /** 590 * Returns the error message if an error occurred during the creation of resources in the VFS.<p> 591 * 592 * @return the error message 593 */ 594 private String getCreationErrorMessage() { 595 596 String message = new String(); 597 if (!m_resourcesCreated.isEmpty()) { 598 // some resources have been created, tell the user which resources were created successfully 599 StringBuffer buf = new StringBuffer(64); 600 for (String name : m_resourcesCreated.values()) { 601 buf.append("<br />"); 602 buf.append(name); 603 } 604 message = m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_CREATING_1, buf.toString()); 605 } else { 606 // no resources have been created on the VFS 607 message = m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_CREATING_0); 608 } 609 return message; 610 } 611 612 /** 613 * Gets the list of file names that should be unziped.<p> 614 * 615 * @return the list of file names that should be unziped 616 * 617 * @throws UnsupportedEncodingException if something goes wrong 618 */ 619 private List<String> getFilesToUnzip() throws UnsupportedEncodingException { 620 621 if (m_parameterMap.get(I_CmsUploadConstants.UPLOAD_UNZIP_FILES_FIELD_NAME) != null) { 622 String[] filesToUnzip = m_parameterMap.get(I_CmsUploadConstants.UPLOAD_UNZIP_FILES_FIELD_NAME); 623 if (filesToUnzip != null) { 624 List<String> result = new ArrayList<String>(); 625 for (String filename : filesToUnzip) { 626 result.add(URLDecoder.decode(filename, "UTF-8")); 627 } 628 return result; 629 } 630 } 631 return Collections.emptyList(); 632 } 633 634 /** 635 * Gets the post-create handler. 636 * 637 * @return the post-create handler 638 */ 639 private String getPostCreateHandler() { 640 641 String[] values = m_parameterMap.get(I_CmsUploadConstants.POST_CREATE_HANDLER); 642 return ((values != null) && (values.length > 0)) ? values[0] : null; 643 } 644 645 /** 646 * Returns the target folder for the new resource, 647 * if the given folder does not exist root folder 648 * of the current site is returned.<p> 649 * 650 * @param cms the CMS context to use 651 * 652 * @return the target folder for the new resource 653 * 654 * @throws CmsException if something goes wrong 655 */ 656 private String getTargetFolder(CmsObject cms) throws CmsException { 657 658 // get the target folder on the vfs 659 CmsResource target = cms.readResource("/", CmsResourceFilter.IGNORE_EXPIRATION); 660 if (m_parameterMap.get(I_CmsUploadConstants.UPLOAD_TARGET_FOLDER_FIELD_NAME) != null) { 661 String targetFolder = m_parameterMap.get(I_CmsUploadConstants.UPLOAD_TARGET_FOLDER_FIELD_NAME)[0]; 662 if (!CmsStringUtil.isEmptyOrWhitespaceOnly(targetFolder)) { 663 if (cms.existsResource(targetFolder, CmsResourceFilter.IGNORE_EXPIRATION)) { 664 CmsResource tmpTarget = cms.readResource(targetFolder, CmsResourceFilter.IGNORE_EXPIRATION); 665 if (tmpTarget.isFolder()) { 666 target = tmpTarget; 667 } 668 } else { 669 target = createTargetFolder(cms, targetFolder); 670 } 671 } 672 } 673 String targetFolder = cms.getRequestContext().removeSiteRoot(target.getRootPath()); 674 if (!targetFolder.endsWith("/")) { 675 // add folder separator to currentFolder 676 targetFolder += "/"; 677 } 678 return targetFolder; 679 } 680 681 /** 682 * Returns true if file name translation should be skipped for the upload. 683 * 684 * <p>This is mainly used for the file replacement dialog. 685 * 686 * @return true if file name translation should be skipped 687 */ 688 private boolean isKeepFileNames() { 689 690 String[] values = m_parameterMap.get(I_CmsUploadConstants.KEEP_FILE_NAMES); 691 boolean result = (values != null) && (values.length > 0) && Boolean.parseBoolean(values[0]); 692 return result; 693 } 694 695 /** 696 * Parses the request.<p> 697 * 698 * Stores the file items and the request parameters in a local variable if present.<p> 699 * 700 * @param listener the upload listener 701 * 702 * @throws Exception if anything goes wrong 703 */ 704 private void parseRequest(CmsUploadListener listener) throws Exception { 705 706 // check if the request is a multipart request 707 if (!ServletFileUpload.isMultipartContent(getRequest())) { 708 // no multipart request: Abort the upload 709 throw new CmsUploadException(m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_NO_MULTIPART_0)); 710 } 711 712 // this was indeed a multipart form request, read the files 713 m_multiPartFileItems = readMultipartFileItems(listener); 714 715 // check if there were any multipart file items in the request 716 if ((m_multiPartFileItems == null) || m_multiPartFileItems.isEmpty()) { 717 // no file items found stop process 718 throw new CmsUploadException(m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_NO_FILEITEMS_0)); 719 } 720 721 // there are file items in the request, get the request parameters 722 m_parameterMap = CmsRequestUtil.readParameterMapFromMultiPart( 723 getCmsObject().getRequestContext().getEncoding(), 724 m_multiPartFileItems); 725 726 listener.setFinished(true); 727 } 728 729 /** 730 * Parses a request of the form <code>multipart/form-data</code>.<p> 731 * 732 * The result list will contain items of type <code>{@link FileItem}</code>. 733 * If the request has no file items, then <code>null</code> is returned.<p> 734 * 735 * @param listener the upload listener 736 * 737 * @return the list of <code>{@link FileItem}</code> extracted from the multipart request, 738 * or <code>null</code> if the request has no file items 739 * 740 * @throws Exception if anything goes wrong 741 */ 742 private List<FileItem> readMultipartFileItems(CmsUploadListener listener) throws Exception { 743 744 DiskFileItemFactory factory = new DiskFileItemFactory(); 745 // maximum size that will be stored in memory 746 factory.setSizeThreshold(4096); 747 // the location for saving data that is larger than the threshold 748 File temp = new File(OpenCms.getSystemInfo().getPackagesRfsPath()); 749 if (temp.exists() || temp.mkdirs()) { 750 // make sure the folder exists 751 factory.setRepository(temp); 752 } 753 754 // create a file upload servlet 755 ServletFileUpload fu = new ServletFileUpload(factory); 756 // set the listener 757 fu.setProgressListener(listener); 758 // set encoding to correctly handle special chars (e.g. in filenames) 759 fu.setHeaderEncoding(getRequest().getCharacterEncoding()); 760 // set the maximum size for a single file (value is in bytes) 761 long maxFileSizeBytes = OpenCms.getWorkplaceManager().getFileBytesMaxUploadSize(getCmsObject()); 762 if (maxFileSizeBytes > 0) { 763 fu.setFileSizeMax(maxFileSizeBytes); 764 } 765 766 // try to parse the request 767 try { 768 return CmsCollectionsGenericWrapper.list(fu.parseRequest(getRequest())); 769 } catch (SizeLimitExceededException e) { 770 // request size is larger than maximum allowed request size, throw an error 771 Integer actualSize = Integer.valueOf((int)(e.getActualSize() / 1024)); 772 Integer maxSize = Integer.valueOf((int)(e.getPermittedSize() / 1024)); 773 throw new CmsUploadException( 774 m_bundle.key(org.opencms.ade.upload.Messages.ERR_UPLOAD_REQUEST_SIZE_LIMIT_2, actualSize, maxSize), 775 e); 776 } catch (FileSizeLimitExceededException e) { 777 // file size is larger than maximum allowed file size, throw an error 778 Integer actualSize = Integer.valueOf((int)(e.getActualSize() / 1024)); 779 Integer maxSize = Integer.valueOf((int)(e.getPermittedSize() / 1024)); 780 throw new CmsUploadException( 781 m_bundle.key( 782 org.opencms.ade.upload.Messages.ERR_UPLOAD_FILE_SIZE_LIMIT_3, 783 actualSize, 784 e.getFileName(), 785 maxSize), 786 e); 787 } 788 } 789 790 /** 791 * Remove the listener active in this session. 792 * 793 * @param listenerId the id of the listener to remove 794 */ 795 private void removeListener(CmsUUID listenerId) { 796 797 getRequest().getSession().removeAttribute(SESSION_ATTRIBUTE_LISTENER_ID); 798 m_listeners.remove(listenerId); 799 } 800 801 /** 802 * Scans a byte buffer for viruses if possible. 803 * @param data the file data 804 * @return the list of detected viruses 805 */ 806 private List<String> scan(String context, byte[] data) { 807 808 if (m_scanner != null) { 809 long t1 = System.currentTimeMillis(); 810 try (InputStream stream = new ByteArrayInputStream(data)) { 811 return m_scanner.scan(stream); 812 } catch (IOException e) { 813 // shouldn't happen 814 return Collections.emptyList(); 815 } finally { 816 long t2 = System.currentTimeMillis(); 817 LOG.debug("Scanning " + context + " took " + (t2 - t1) + "ms"); 818 } 819 } else { 820 return Collections.emptyList(); 821 } 822 } 823 824 /** 825 * Scans a zip file entry for viruses if possible. 826 * @param zip the zip file 827 * @param entry the entry 828 * @return the list of detected viruses 829 */ 830 private List<String> scan(String context, ZipFile zip, ZipArchiveEntry entry) { 831 832 if (m_scanner != null) { 833 long t1 = System.currentTimeMillis(); 834 try (InputStream stream = zip.getInputStream(entry)) { 835 return m_scanner.scan(stream); 836 } catch (IOException e) { 837 // shouldn't happen, Zip file is in memory 838 return Collections.emptyList(); 839 } finally { 840 long t2 = System.currentTimeMillis(); 841 LOG.debug("Scanning " + context + " took " + (t2 - t1) + "ms"); 842 } 843 } else { 844 return Collections.emptyList(); 845 } 846 } 847 848}