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