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