001: /**
002: * Copyright (c) 2003-2007, David A. Czarnecki
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * Redistributions of source code must retain the above copyright notice, this list of conditions and the
009: * following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
011: * following disclaimer in the documentation and/or other materials provided with the distribution.
012: * Neither the name of "David A. Czarnecki" and "blojsom" nor the names of its contributors may be used to
013: * endorse or promote products derived from this software without specific prior written permission.
014: * Products derived from this software may not be called "blojsom", nor may "blojsom" appear in their name,
015: * without prior written permission of David A. Czarnecki.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
018: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
019: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
021: * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
022: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
025: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026: * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
029: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030: */package org.blojsom.plugin.admin;
031:
032: import org.apache.commons.fileupload.FileItem;
033: import org.apache.commons.fileupload.FileUploadException;
034: import org.apache.commons.fileupload.disk.DiskFileItemFactory;
035: import org.apache.commons.fileupload.servlet.ServletFileUpload;
036: import org.apache.commons.logging.Log;
037: import org.apache.commons.logging.LogFactory;
038: import org.blojsom.blog.Blog;
039: import org.blojsom.blog.Entry;
040: import org.blojsom.plugin.PluginException;
041: import org.blojsom.util.BlojsomConstants;
042: import org.blojsom.util.BlojsomUtils;
043:
044: import javax.servlet.http.HttpServletRequest;
045: import javax.servlet.http.HttpServletResponse;
046: import java.io.File;
047: import java.util.*;
048:
049: /**
050: * FileUploadPlugin
051: *
052: * @author David Czarnecki
053: * @version $Id: FileUploadPlugin.java,v 1.3 2007/01/17 02:35:05 czarneckid Exp $
054: * @since blojsom 3.0
055: */
056: public class FileUploadPlugin extends BaseAdminPlugin {
057:
058: private Log _logger = LogFactory.getLog(FileUploadPlugin.class);
059:
060: // Localization constants
061: private static final String FAILED_PERMISSION_KEY = "file.upload.failed.permission.text";
062: private static final String FAILED_RESOURCE_KEY = "file.upload.failed.resource.text";
063: private static final String UNKNOWN_ERROR_KEY = "file.upload.unknown.error.text";
064: private static final String SUCCESSFUL_UPLOAD_KEY = "successful.upload.text";
065: private static final String INVALID_EXTENSION_KEY = "invalid.upload.extension.text";
066: private static final String INVALID_TYPE_KEY = "invalid.upload.type.text";
067: private static final String DELETED_FILES_KEY = "deleted.files.text";
068: private static final String UPLOAD_LIMIT_KEY = "upload.limit.exceeded.text";
069:
070: private static final String RESOURCES_DIRECTORY_IP = "resources-directory";
071: private static final String TEMPORARY_DIRECTORY_IP = "temporary-directory";
072: private static final String DEFAULT_TEMPORARY_DIRECTORY = "/tmp";
073: private static final String MAXIMUM_UPLOAD_SIZE_IP = "maximum-upload-size";
074: private static final long DEFAULT_MAXIMUM_UPLOAD_SIZE = 100000;
075: private static final String MAXIMUM_MEMORY_SIZE_IP = "maximum-memory-size";
076: private static final int DEFAULT_MAXIMUM_MEMORY_SIZE = 50000;
077: private static final String ACCEPTED_FILE_TYPES_IP = "accepted-file-types";
078: private static final String[] DEFAULT_ACCEPTED_FILE_TYPES = {
079: "image/jpeg", "image/gif", "image/png" };
080: private static final String INVALID_FILE_EXTENSIONS_IP = "invalid-file-extensions";
081: private static final String[] DEFAULT_INVALID_FILE_EXTENSIONS = {
082: ".jsp", ".jspf", ".jspi", ".jspx", ".php", ".cgi" };
083: private static final String UPLOAD_QUOTA_ENABLED_IP = "upload-quota-enabled";
084: private static final String UPLOAD_QUOTA_LIMIT_IP = "upload-quota-limit";
085: private static final long DEFAULT_UPLOAD_QUOTA_LIMIT = 10485760;
086:
087: // Pages
088: private static final String FILE_UPLOAD_PAGE = "/org/blojsom/plugin/admin/templates/admin-file-upload";
089:
090: // Constants
091: private static final String PLUGIN_ADMIN_FILE_UPLOAD_FILES = "PLUGIN_ADMIN_FILE_UPLOAD_FILES";
092: private static final String ACCEPTED_FILE_TYPES = "ACCEPTED_FILE_TYPES";
093: private static final String INVALID_FILE_EXTENSIONS = "INVALID_FILE_EXTENSIONS";
094:
095: // Actions
096: private static final String UPLOAD_FILE_ACTION = "upload-file";
097: private static final String DELETE_UPLOAD_FILES = "delete-upload-files";
098:
099: // Form items
100: private static final String FILE_TO_DELETE = "file-to-delete";
101:
102: // Permissions
103: private static final String FILE_UPLOAD_PERMISSION = "file_upload_permission";
104:
105: private String _temporaryDirectory;
106: private long _maximumUploadSize;
107: private int _maximumMemorySize;
108: private Map _acceptedFileTypes;
109: private String _resourcesDirectory;
110: private String[] _invalidFileExtensions;
111: private boolean _uploadQuotaEnabled;
112: private long _uploadQuotaLimit;
113: private Properties _fileUploadProperties;
114:
115: /**
116: * Default constructor.
117: */
118: public FileUploadPlugin() {
119: }
120:
121: /**
122: * Set the file upload properties
123: *
124: * @param fileUploadProperties File upload properties
125: */
126: public void setFileUploadProperties(Properties fileUploadProperties) {
127: _fileUploadProperties = fileUploadProperties;
128: }
129:
130: /**
131: * Initialize this plugin. This method only called when the plugin is instantiated.
132: *
133: * @throws org.blojsom.plugin.PluginException
134: * If there is an error initializing the plugin
135: */
136: public void init() throws PluginException {
137: super .init();
138:
139: _temporaryDirectory = _fileUploadProperties
140: .getProperty(TEMPORARY_DIRECTORY_IP);
141: if (BlojsomUtils.checkNullOrBlank(_temporaryDirectory)) {
142: _temporaryDirectory = DEFAULT_TEMPORARY_DIRECTORY;
143: }
144: _logger.debug("Using temporary directory: "
145: + _temporaryDirectory);
146:
147: try {
148: _maximumUploadSize = Long.parseLong(_fileUploadProperties
149: .getProperty(MAXIMUM_UPLOAD_SIZE_IP));
150: } catch (NumberFormatException e) {
151: _maximumUploadSize = DEFAULT_MAXIMUM_UPLOAD_SIZE;
152: }
153: _logger.debug("Using maximum upload size: "
154: + _maximumUploadSize);
155:
156: try {
157: _maximumMemorySize = Integer.parseInt(_fileUploadProperties
158: .getProperty(MAXIMUM_MEMORY_SIZE_IP));
159: } catch (NumberFormatException e) {
160: _maximumMemorySize = DEFAULT_MAXIMUM_MEMORY_SIZE;
161: }
162: _logger.debug("Using maximum memory size: "
163: + _maximumMemorySize);
164:
165: String acceptedFileTypes = _fileUploadProperties
166: .getProperty(ACCEPTED_FILE_TYPES_IP);
167: String[] parsedListOfTypes;
168: if (BlojsomUtils.checkNullOrBlank(acceptedFileTypes)) {
169: parsedListOfTypes = DEFAULT_ACCEPTED_FILE_TYPES;
170: } else {
171: parsedListOfTypes = BlojsomUtils
172: .parseCommaList(acceptedFileTypes);
173: }
174: _acceptedFileTypes = new HashMap(parsedListOfTypes.length);
175: for (int i = 0; i < parsedListOfTypes.length; i++) {
176: String type = parsedListOfTypes[i];
177: _acceptedFileTypes.put(type, type);
178: }
179: _logger.debug("Using accepted file types: "
180: + BlojsomUtils
181: .arrayOfStringsToString(parsedListOfTypes));
182:
183: _resourcesDirectory = _fileUploadProperties
184: .getProperty(RESOURCES_DIRECTORY_IP);
185: if (BlojsomUtils.checkNullOrBlank(_resourcesDirectory)) {
186: _resourcesDirectory = BlojsomConstants.DEFAULT_RESOURCES_DIRECTORY;
187: }
188:
189: _resourcesDirectory = BlojsomUtils
190: .checkStartingAndEndingSlash(_resourcesDirectory);
191: _logger.debug("Using resources directory: "
192: + _resourcesDirectory);
193:
194: String invalidFileExtensionsProperty = _fileUploadProperties
195: .getProperty(INVALID_FILE_EXTENSIONS_IP);
196: if (BlojsomUtils
197: .checkNullOrBlank(invalidFileExtensionsProperty)) {
198: _invalidFileExtensions = DEFAULT_INVALID_FILE_EXTENSIONS;
199: } else {
200: _invalidFileExtensions = BlojsomUtils
201: .parseCommaList(invalidFileExtensionsProperty);
202: }
203: _logger.debug("Using invalid file extensions: "
204: + invalidFileExtensionsProperty);
205:
206: _uploadQuotaEnabled = Boolean.valueOf(
207: _fileUploadProperties
208: .getProperty(UPLOAD_QUOTA_ENABLED_IP))
209: .booleanValue();
210: if (_uploadQuotaEnabled) {
211: String uploadQuotaLimit = _fileUploadProperties
212: .getProperty(UPLOAD_QUOTA_LIMIT_IP);
213: if (BlojsomUtils.checkNullOrBlank(uploadQuotaLimit)) {
214: _uploadQuotaLimit = DEFAULT_UPLOAD_QUOTA_LIMIT;
215: } else {
216: try {
217: _uploadQuotaLimit = Long
218: .parseLong(uploadQuotaLimit);
219: if (_uploadQuotaLimit <= 0) {
220: _uploadQuotaLimit = DEFAULT_UPLOAD_QUOTA_LIMIT;
221: }
222: } catch (NumberFormatException e) {
223: _uploadQuotaLimit = DEFAULT_UPLOAD_QUOTA_LIMIT;
224: }
225: }
226:
227: _logger.debug("Upload limit enabled. Quota is : "
228: + _uploadQuotaLimit + " bytes");
229: }
230: }
231:
232: /**
233: * Return the size of a directory by getting the size of all its files and all of its directories files
234: *
235: * @param directory Directory whose size should be retrieved
236: * @return Size of files in a directory or -1 if the original argument was not a directory
237: */
238: protected long getDirectorySize(File directory) {
239: if (!directory.isDirectory()) {
240: return -1;
241: }
242:
243: long totalSize = 0;
244:
245: File[] files = directory.listFiles();
246: for (int i = 0; i < files.length; i++) {
247: File file = files[i];
248:
249: if (file.isDirectory()) {
250: totalSize += getDirectorySize(file);
251: } else {
252: totalSize += file.length();
253: }
254: }
255:
256: return totalSize;
257: }
258:
259: /**
260: * Process the blog entries
261: *
262: * @param httpServletRequest Request
263: * @param httpServletResponse Response
264: * @param blog {@link Blog} instance
265: * @param context Context
266: * @param entries Blog entries retrieved for the particular request
267: * @return Modified set of blog entries
268: * @throws PluginException If there is an error processing the blog entries
269: */
270: public Entry[] process(HttpServletRequest httpServletRequest,
271: HttpServletResponse httpServletResponse, Blog blog,
272: Map context, Entry[] entries) throws PluginException {
273: if (!authenticateUser(httpServletRequest, httpServletResponse,
274: context, blog)) {
275: httpServletRequest.setAttribute(
276: BlojsomConstants.PAGE_PARAM, ADMIN_LOGIN_PAGE);
277:
278: return entries;
279: }
280:
281: String username = getUsernameFromSession(httpServletRequest,
282: blog);
283: if (!checkPermission(blog, null, username,
284: FILE_UPLOAD_PERMISSION)) {
285: httpServletRequest.setAttribute(
286: BlojsomConstants.PAGE_PARAM,
287: ADMIN_ADMINISTRATION_PAGE);
288: addOperationResultMessage(context, getAdminResource(
289: FAILED_PERMISSION_KEY, FAILED_PERMISSION_KEY, blog
290: .getBlogAdministrationLocale()));
291:
292: return entries;
293: }
294:
295: File resourceDirectory = new File(_servletConfig
296: .getServletContext().getRealPath("/")
297: + _resourcesDirectory + blog.getBlogId() + "/");
298:
299: String action = BlojsomUtils.getRequestValue(ACTION_PARAM,
300: httpServletRequest);
301: if (BlojsomUtils.checkNullOrBlank(action)) {
302: _logger.debug("User did not request edit action");
303:
304: httpServletRequest.setAttribute(
305: BlojsomConstants.PAGE_PARAM,
306: ADMIN_ADMINISTRATION_PAGE);
307: } else if (PAGE_ACTION.equals(action)) {
308: _logger.debug("User requested file upload page");
309:
310: httpServletRequest.setAttribute(
311: BlojsomConstants.PAGE_PARAM, FILE_UPLOAD_PAGE);
312: } else if (UPLOAD_FILE_ACTION.equals(action)) {
313: _logger.debug("User requested file upload action");
314:
315: // Create a factory for disk-based file items
316: DiskFileItemFactory factory = new DiskFileItemFactory();
317:
318: // Set factory constraints
319: factory.setSizeThreshold(_maximumMemorySize);
320: factory.setRepository(new File(_temporaryDirectory));
321:
322: ServletFileUpload upload = new ServletFileUpload(factory);
323:
324: // Set overall request size constraint
325: upload.setSizeMax(_maximumUploadSize);
326:
327: try {
328: List items = upload.parseRequest(httpServletRequest);
329: Iterator itemsIterator = items.iterator();
330: while (itemsIterator.hasNext()) {
331: FileItem item = (FileItem) itemsIterator.next();
332:
333: // Check for the file upload form item
334: if (!item.isFormField()) {
335: String itemNameWithoutPath = BlojsomUtils
336: .getFilenameFromPath(item.getName());
337:
338: _logger.debug("Found file item: "
339: + itemNameWithoutPath + " of type: "
340: + item.getContentType());
341:
342: // Is it one of the accepted file types?
343: String fileType = item.getContentType();
344: boolean isAcceptedFileType = _acceptedFileTypes
345: .containsKey(fileType);
346:
347: String extension = BlojsomUtils
348: .getFileExtension(itemNameWithoutPath);
349: boolean isAcceptedFileExtension = true;
350: for (int i = 0; i < _invalidFileExtensions.length; i++) {
351: String invalidFileExtension = _invalidFileExtensions[i];
352: if (itemNameWithoutPath
353: .indexOf(invalidFileExtension) != -1) {
354: isAcceptedFileExtension = false;
355: break;
356: }
357: }
358:
359: if (_uploadQuotaEnabled) {
360: boolean overQuota = true;
361: long currentLimit = getDirectorySize(resourceDirectory);
362: if ((currentLimit != -1)
363: && ((currentLimit + item.getSize() < _uploadQuotaLimit))) {
364: overQuota = false;
365: }
366:
367: if (overQuota) {
368: _logger
369: .error("Upload quota exceeded trying to upload file: "
370: + itemNameWithoutPath);
371: addOperationResultMessage(
372: context,
373: formatAdminResource(
374: UPLOAD_LIMIT_KEY,
375: UPLOAD_LIMIT_KEY,
376: blog
377: .getBlogAdministrationLocale(),
378: new Object[] {
379: itemNameWithoutPath,
380: new Long(
381: _uploadQuotaLimit) }));
382:
383: break;
384: }
385: }
386:
387: // If so, upload the file to the resources directory
388: if (isAcceptedFileType
389: && isAcceptedFileExtension) {
390: if (!resourceDirectory.exists()) {
391: if (!resourceDirectory.mkdirs()) {
392: _logger
393: .error("Unable to create resource directory for user: "
394: + resourceDirectory
395: .toString());
396: addOperationResultMessage(
397: context,
398: getAdminResource(
399: FAILED_RESOURCE_KEY,
400: FAILED_RESOURCE_KEY,
401: blog
402: .getBlogAdministrationLocale()));
403: return entries;
404: }
405: }
406:
407: File resourceFile = new File(
408: resourceDirectory,
409: itemNameWithoutPath);
410: try {
411: item.write(resourceFile);
412: } catch (Exception e) {
413: _logger.error(e);
414: addOperationResultMessage(
415: context,
416: formatAdminResource(
417: UNKNOWN_ERROR_KEY,
418: UNKNOWN_ERROR_KEY,
419: blog
420: .getBlogAdministrationLocale(),
421: new Object[] { e
422: .getMessage() }));
423: }
424:
425: String resourceURL = blog.getBlogBaseURL()
426: + _resourcesDirectory
427: + blog.getBlogId() + "/"
428: + itemNameWithoutPath;
429:
430: _logger
431: .debug("Successfully uploaded resource file: "
432: + resourceFile.toString());
433: addOperationResultMessage(
434: context,
435: formatAdminResource(
436: SUCCESSFUL_UPLOAD_KEY,
437: SUCCESSFUL_UPLOAD_KEY,
438: blog
439: .getBlogAdministrationLocale(),
440: new Object[] {
441: itemNameWithoutPath,
442: resourceURL,
443: itemNameWithoutPath }));
444: } else {
445: if (!isAcceptedFileExtension) {
446: _logger
447: .error("Upload file does not have an accepted extension: "
448: + extension);
449: addOperationResultMessage(
450: context,
451: formatAdminResource(
452: INVALID_EXTENSION_KEY,
453: INVALID_EXTENSION_KEY,
454: blog
455: .getBlogAdministrationLocale(),
456: new Object[] { extension }));
457: } else {
458: _logger
459: .error("Upload file is not an accepted type: "
460: + itemNameWithoutPath
461: + " of type: "
462: + item.getContentType());
463: addOperationResultMessage(
464: context,
465: formatAdminResource(
466: INVALID_TYPE_KEY,
467: INVALID_TYPE_KEY,
468: blog
469: .getBlogAdministrationLocale(),
470: new Object[] { item
471: .getContentType() }));
472: }
473: }
474: }
475: }
476: } catch (FileUploadException e) {
477: _logger.error(e);
478: addOperationResultMessage(context, formatAdminResource(
479: UNKNOWN_ERROR_KEY, UNKNOWN_ERROR_KEY, blog
480: .getBlogAdministrationLocale(),
481: new Object[] { e.getMessage() }));
482: }
483:
484: httpServletRequest.setAttribute(
485: BlojsomConstants.PAGE_PARAM, FILE_UPLOAD_PAGE);
486: } else if (DELETE_UPLOAD_FILES.equals(action)) {
487: String[] filesToDelete = httpServletRequest
488: .getParameterValues(FILE_TO_DELETE);
489: int actualFilesDeleted = 0;
490: if (filesToDelete != null && filesToDelete.length > 0) {
491: File deletedFile;
492: for (int i = 0; i < filesToDelete.length; i++) {
493: String fileToDelete = filesToDelete[i];
494: deletedFile = new File(resourceDirectory,
495: fileToDelete);
496: if (!deletedFile.delete()) {
497: _logger
498: .debug("Unable to delete resource file: "
499: + deletedFile.toString());
500: } else {
501: actualFilesDeleted += 1;
502: }
503: }
504:
505: addOperationResultMessage(context,
506: formatAdminResource(DELETED_FILES_KEY,
507: DELETED_FILES_KEY, blog
508: .getBlogAdministrationLocale(),
509: new Object[] { new Integer(
510: actualFilesDeleted) }));
511: }
512:
513: httpServletRequest.setAttribute(
514: BlojsomConstants.PAGE_PARAM, FILE_UPLOAD_PAGE);
515: }
516:
517: // Create a list of files in the user's resource directory
518: Map resourceFilesMap = null;
519: if (resourceDirectory.exists()) {
520: File[] resourceFiles = resourceDirectory.listFiles();
521:
522: if (resourceFiles != null) {
523: resourceFilesMap = new HashMap(resourceFiles.length);
524: for (int i = 0; i < resourceFiles.length; i++) {
525: File resourceFile = resourceFiles[i];
526: resourceFilesMap.put(resourceFile.getName(),
527: resourceFile.getName());
528: }
529: }
530: } else {
531: resourceFilesMap = new HashMap();
532: }
533:
534: resourceFilesMap = new TreeMap(resourceFilesMap);
535: context.put(PLUGIN_ADMIN_FILE_UPLOAD_FILES, resourceFilesMap);
536: context.put(ACCEPTED_FILE_TYPES,
537: new TreeMap(_acceptedFileTypes));
538: context.put(INVALID_FILE_EXTENSIONS, new TreeMap(BlojsomUtils
539: .listToMap(BlojsomUtils
540: .arrayToList(_invalidFileExtensions))));
541:
542: return entries;
543: }
544: }
|