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}