/**
* Copyright (C) 2010-2017 Structr GmbH
*
* This file is part of Structr <http://structr.org>.
*
* Structr is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* Structr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Structr. If not, see <http://www.gnu.org/licenses/>.
*/
package org.structr.web.servlet;
import com.google.common.io.Files;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.lang.StringUtils;
import org.apache.pdfbox.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.structr.api.config.Settings;
import org.structr.common.AccessMode;
import org.structr.common.SecurityContext;
import org.structr.common.error.FrameworkException;
import org.structr.core.app.StructrApp;
import org.structr.core.auth.exception.AuthenticationException;
import org.structr.core.graph.Tx;
import org.structr.rest.service.HttpServiceServlet;
import org.structr.rest.service.StructrHttpServiceConfig;
import org.structr.schema.SchemaHelper;
import org.structr.web.auth.UiAuthenticator;
import org.structr.web.maintenance.DeployCommand;
//~--- classes ----------------------------------------------------------------
/**
* Endpoint for deployment file upload
*/
public class DeploymentServlet extends HttpServlet implements HttpServiceServlet {
private static final Logger logger = LoggerFactory.getLogger(DeploymentServlet.class.getName());
private static final int MEGABYTE = 1024 * 1024;
private static final int MEMORY_THRESHOLD = 10 * MEGABYTE; // above 10 MB, files are stored on disk
// non-static fields
private ServletFileUpload uploader = null;
private File filesDir = null;
private final StructrHttpServiceConfig config = new StructrHttpServiceConfig();
public DeploymentServlet() {
}
//~--- methods --------------------------------------------------------
@Override
public StructrHttpServiceConfig getConfig() {
return config;
}
@Override
public void init() {
try (final Tx tx = StructrApp.getInstance().tx()) {
DiskFileItemFactory fileFactory = new DiskFileItemFactory();
fileFactory.setSizeThreshold(MEMORY_THRESHOLD);
filesDir = new File(Settings.TmpPath.getValue()); // new File(Services.getInstance().getTmpPath());
if (!filesDir.exists()) {
filesDir.mkdir();
}
fileFactory.setRepository(filesDir);
uploader = new ServletFileUpload(fileFactory);
tx.success();
} catch (FrameworkException t) {
logger.warn("", t);
}
}
@Override
public void destroy() {
}
@Override
protected void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException {
try (final Tx tx = StructrApp.getInstance().tx()) {
if (!ServletFileUpload.isMultipartContent(request)) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.getOutputStream().write("ERROR (400): Request does not contain multipart content.\n".getBytes("UTF-8"));
return;
}
final SecurityContext securityContext;
try {
securityContext = getConfig().getAuthenticator().initializeAndExamineRequest(request, response);
} catch (AuthenticationException ae) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().write("ERROR (401): Invalid user or password.\n".getBytes("UTF-8"));
return;
}
if (securityContext.getUser(false) == null && !Settings.DeploymentAllowAnonymousUploads.getValue()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getOutputStream().write("ERROR (401): Anonymous uploads forbidden.\n".getBytes("UTF-8"));
return;
}
// Ensure access mode is frontend
securityContext.setAccessMode(AccessMode.Frontend);
request.setCharacterEncoding("UTF-8");
// Important: Set character encoding before calling response.getWriter() !!, see Servlet Spec 5.4
response.setCharacterEncoding("UTF-8");
// don't continue on redirects
if (response.getStatus() == 302) {
return;
}
final String pathInfo = request.getPathInfo();
String type = null;
if (StringUtils.isNotBlank(pathInfo)) {
type = SchemaHelper.normalizeEntityName(StringUtils.stripStart(pathInfo.trim(), "/"));
}
uploader.setFileSizeMax(MEGABYTE * Settings.DeploymentMaxFileSize.getValue());
uploader.setSizeMax(MEGABYTE * Settings.DeploymentMaxRequestSize.getValue());
response.setContentType("text/html");
final List<FileItem> fileItemsList = uploader.parseRequest(request);
final Iterator<FileItem> fileItemsIterator = fileItemsList.iterator();
final Map<String, Object> params = new HashMap<>();
while (fileItemsIterator.hasNext()) {
final FileItem item = fileItemsIterator.next();
try {
final String directoryPath = "/tmp/" + UUID.randomUUID();
final String filePath = directoryPath + ".zip";
File file = new File(filePath);
Files.write(IOUtils.toByteArray(item.getInputStream()), file);
unzip(file, directoryPath);
DeployCommand deployCommand = StructrApp.getInstance(securityContext).command(DeployCommand.class);
final Map<String, Object> attributes = new HashMap<>();
attributes.put("source", directoryPath + "/" + StringUtils.substringBeforeLast(item.getName(), "."));
deployCommand.execute(attributes);
file.deleteOnExit();
File dir = new File(directoryPath);
dir.deleteOnExit();
} catch (IOException ex) {
logger.warn("Could not upload file", ex);
}
}
tx.success();
} catch (FrameworkException | IOException | FileUploadException t) {
logger.error("Exception while processing request", t);
UiAuthenticator.writeInternalServerError(response);
}
}
/**
* Unzip given file to given output directory.
*
* @param file
* @param outputDir
* @throws IOException
*/
private void unzip(final File file, final String outputDir) throws IOException {
try (final ZipFile zipFile = new ZipFile(file)) {
final Enumeration<? extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
final ZipEntry entry = entries.nextElement();
final File targetFile = new File(outputDir, entry.getName());
if (entry.isDirectory()) {
targetFile.mkdirs();
} else {
targetFile.getParentFile().mkdirs();
InputStream in = zipFile.getInputStream(entry);
try (OutputStream out = new FileOutputStream(targetFile)) {
IOUtils.copy(in, out);
IOUtils.closeQuietly(in);
}
}
}
}
}
}