/*
* Copyright 2009-2012 by KNURT Systeme (http://www.knurt.de)
*
* Licensed under the Creative Commons License Attribution-NonCommercial-ShareAlike 3.0 Unported;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://creativecommons.org/licenses/by-nc-sa/3.0/
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.knurt.fam.core.model.config;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.activation.MimetypesFileTypeMap;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.web.servlet.ModelAndView;
import de.knurt.fam.connector.FamConnector;
import de.knurt.fam.core.aspects.logging.FamLog;
import de.knurt.fam.core.util.bu.FileOfUser;
import de.knurt.fam.template.model.TemplateResource;
import de.knurt.fam.template.util.TemplateConfig;
/**
* control file uploads. use default system settings for max memory size and
* temp directory.
*
* @see DiskFileItemFactory
*
* @author Daniel Oltmanns
* @since 1.8.0 (04/17/2012)
*/
public class FileUploadController {
private TemplateResource tr;
private HttpServletResponse response;
private FileItemFactory factory;
private MimetypesFileTypeMap mftm;
private String error = null;
public static String[] ACCEPT_FILE_TYPES;
public static long MAX_FILE_SIZE, MIN_FILE_SIZE;
public static int MAX_NUMBER_OF_FILES, MAX_FILE_SIZE_SUM;
public FileUploadController(TemplateResource tr, HttpServletResponse response) {
reinitOptions();
this.tr = tr;
this.response = response;
this.factory = new DiskFileItemFactory();
this.mftm = new MimetypesFileTypeMap();
this.uploadDir = new FileOfUser(tr.getAuthUser()).getUploadDir();
}
/**
* re-init the configuration for file uploads (sizes, suffixes et cetera)
*/
public static void reinitOptions() {
try {
ACCEPT_FILE_TYPES = FamConnector.getGlobalPropertyAsList("fileupload.accepted_file_types");
MAX_FILE_SIZE = Integer.parseInt(FamConnector.getGlobalProperty("fileupload.max_single_file_size"));
MIN_FILE_SIZE = Integer.parseInt(FamConnector.getGlobalProperty("fileupload.min_single_file_size"));
MAX_NUMBER_OF_FILES = Integer.parseInt(FamConnector.getGlobalProperty("fileupload.max_total_number_of_files"));
MAX_FILE_SIZE_SUM = Integer.parseInt(FamConnector.getGlobalProperty("fileupload.max_total_file_size"));
} catch (Exception e) {
FamLog.exception("error in global configuration", e, 201204241532l);
}
}
public File[] getExistingFileNames() {
File[] result = new File[0];
File upload_dir = this.uploadDir;
if(upload_dir == null) {
FamLog.error("no upload dir configured or present", 201207101224l);
} else {
result = upload_dir.listFiles();
}
return result;
}
/**
* return the size of all files in bytes
*
* @see File#length()
* @return the size of all files in bytes
*/
public long getSizeOfAllFiles() {
File[] files = this.getExistingFileNames();
long result = 0;
for (File file : files) {
result += file.length();
}
return result;
}
private String getNewFilename(String filename) {
String suffix = filename.replaceFirst(".*\\.", "");
String result = filename.substring(0, filename.length() - suffix.length() - 1).replaceAll("[^a-zA-Z0-9]", "_");
while (result != result.replaceAll("__", "_"))
result = result.replaceAll("__", "_");
result += "." + suffix.toLowerCase();
// check if this one exists
boolean exists = false;
for (File e : this.getExistingFileNames()) {
if (e.getName().equalsIgnoreCase(result)) {
exists = true;
break;
}
}
if (exists) {
result = result.substring(0, result.length() - suffix.length() - 1) + "_copy." + suffix;
return this.getNewFilename(result);
} else {
return result;
}
}
private File getExistingFile(String filename) {
File result = null;
File[] candidates = this.getExistingFileNames();
for (File candidate : candidates) {
if (filename.equals(candidate.getName())) {
result = candidate;
}
}
return result;
}
public ModelAndView getModelAndView() {
ModelAndView result = null;
if (tr.getFilename().equalsIgnoreCase("get") && tr.getRequest().getMethod().equalsIgnoreCase("GET") && tr.getSuffix().equalsIgnoreCase("html")) {
// initial call for the fileupload page (<iframe
// src="get-fileupload.html" ...)
tr.setTemplateFile("page_fileupload.html");
result = TemplateConfig.me().getResourceController().handleGetRequests(tr, response, tr.getRequest());
} else if (tr.getFilename().equalsIgnoreCase("put") && tr.getRequest().getMethod().equalsIgnoreCase("GET") && tr.getSuffix().equalsIgnoreCase("json")) {
// requesting existing files
JSONArray json = new JSONArray();
File[] existings = this.getExistingFileNames();
for (File existing : existings) {
json.put(this.getJSONObject(existing));
}
this.write(json);
} else if (tr.getFilename().equalsIgnoreCase("delete") && (tr.getRequest().getMethod().equalsIgnoreCase("POST") || tr.getRequest().getMethod().equalsIgnoreCase("DELETE")) && tr.getSuffix().equalsIgnoreCase("json")) {
// ↖ delete
boolean succ = false;
File fileToDelete = this.getExistingFile(tr.getRequest().getParameter("file"));
if (fileToDelete != null) {
succ = fileToDelete.delete();
}
this.write(succ ? "true" : "false");
} else if (tr.getFilename().equalsIgnoreCase("download") && tr.getRequest().getMethod().equalsIgnoreCase("GET") && tr.getSuffix().equalsIgnoreCase("json")) {
File fileToDownload = this.getExistingFile(tr.getRequest().getParameter("file"));
if (fileToDownload != null) {
// ↘ force "save as" in browser
response.setHeader("Content-Disposition", "attachment; filename=" + fileToDownload.getName());
// ↘ it is a pdf
response.setContentType(mftm.getContentType(fileToDownload));
ServletOutputStream outputStream = null;
FileInputStream inputStream = null;
try {
outputStream = response.getOutputStream();
inputStream = new FileInputStream(fileToDownload);
int nob = IOUtils.copy(inputStream, outputStream);
if (nob < 1)
FamLog.error("fail to download: " + fileToDownload.getAbsolutePath(), 201204181310l);
} catch (IOException e) {
FamLog.exception(e, 201204181302l);
} finally {
IOUtils.closeQuietly(inputStream);
IOUtils.closeQuietly(outputStream);
}
}
} else if (tr.getFilename().equalsIgnoreCase("put") && tr.getRequest().getMethod().equalsIgnoreCase("POST") && tr.getSuffix().equalsIgnoreCase("json") && ServletFileUpload.isMultipartContent(tr.getRequest())) {
// ↖ insert
JSONObject json_result = new JSONObject();
// Parse the request
try {
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
@SuppressWarnings("unchecked")
List<FileItem> items = upload.parseRequest(tr.getRequest());
this.setError(items);
if (this.error == null) {
// Process the uploaded items
for (FileItem item : items) {
if (!item.isFormField()) {
File uploadedFile = null;
File upload_dir = this.uploadDir;
if (upload_dir != null) {
uploadedFile = new File(upload_dir.getAbsolutePath() + File.separator + this.getNewFilename(item.getName()));
}
try {
item.write(uploadedFile);
json_result = this.getJSONObject(uploadedFile);
} catch (Exception e) {
FamLog.exception(e, 201204170945l);
json_result.put("error", "unknown");
}
}
}
} else { // ← error
json_result.put("error", this.error);
}
} catch (FileUploadException e) {
FamLog.exception(e, 201204170928l);
try {
json_result.put("error", "unknown");
} catch (JSONException e1) {
FamLog.exception(e1, 201204171037l);
}
} catch (JSONException e) {
FamLog.exception(e, 201204171036l);
}
JSONArray json_wrapper = new JSONArray();
json_wrapper.put(json_result);
this.write(json_wrapper);
}
return result;
}
private JSONObject getJSONObject(File file) {
JSONObject result = new JSONObject();
try {
result.put("name", file.getName());
result.put("size", file.length());
result.put("type", mftm.getContentType(file));
result.put("url", String.format("download-fileupload.json?file=%s", file.getName()));
result.put("delete_url", String.format("delete-fileupload.json?file=%s", file.getName()));
result.put("delete_type", "DELETE");
} catch (JSONException e) {
FamLog.exception(e, 201204171115l);
}
return result;
}
private void setError(List<FileItem> items) {
long size_sum = 0;
if (items.size() > this.getExistingFileNames().length + MAX_NUMBER_OF_FILES) {
this.error = "maxNumberOfFiles";
} else {
for (FileItem item : items) {
size_sum += item.getSize();
if (item.getSize() > MAX_FILE_SIZE) {
this.error = "maxFileSize";
break;
}
if (item.getSize() < MIN_FILE_SIZE) {
this.error = "minFileSize";
break;
}
boolean acceptedFileType = false;
for (String suffix : ACCEPT_FILE_TYPES) {
if (item.getName().endsWith(suffix)) {
acceptedFileType = true;
break;
}
}
if (!acceptedFileType) {
this.error = "acceptFileTypes";
}
}
}
if (this.error == null && this.getSizeOfAllFiles() + size_sum > MAX_FILE_SIZE_SUM) {
this.error = "maxFileSize";
}
}
private void write(String output) {
PrintWriter pw = null;
try {
response.setContentType("application/json");
pw = response.getWriter();
IOUtils.write(output, pw);
} catch (IOException ex) {
FamLog.exception(ex, 201204171241l);
} finally {
IOUtils.closeQuietly(pw);
}
}
private void write(JSONArray json) {
this.write(json.toString());
}
private File uploadDir = null;
}