/*******************************************************************************
* Copyright (c) 2010, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.orion.internal.server.servlets.file;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileInfo;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.URIUtil;
import org.eclipse.orion.internal.server.servlets.ServletResourceHandler;
import org.eclipse.orion.internal.server.servlets.Slug;
import org.eclipse.orion.server.core.LogHelper;
import org.eclipse.orion.server.core.OrionConfiguration;
import org.eclipse.orion.server.core.ProtocolConstants;
import org.eclipse.orion.server.core.ServerStatus;
import org.eclipse.orion.server.servlets.OrionServlet;
import org.eclipse.osgi.util.NLS;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Handles HTTP requests against directories for eclipse web protocol version
* 1.0.
*/
public class DirectoryHandlerV1 extends ServletResourceHandler<IFileStore> {
static final int CREATE_COPY = 0x1;
static final int CREATE_MOVE = 0x2;
static final int CREATE_NO_OVERWRITE = 0x4;
private final ServletResourceHandler<IStatus> statusHandler;
public DirectoryHandlerV1(ServletResourceHandler<IStatus> statusHandler) {
this.statusHandler = statusHandler;
}
private boolean handleGet(HttpServletRequest request, HttpServletResponse response, IFileStore dir) throws IOException, CoreException {
URI location = getURI(request);
JSONObject result = ServletFileStoreHandler.toJSON(dir, dir.fetchInfo(EFS.NONE, null), location);
String depthString = request.getParameter(ProtocolConstants.PARM_DEPTH);
int depth = 0;
if (depthString != null) {
try {
depth = Integer.parseInt(depthString);
} catch (NumberFormatException e) {
// ignore
}
}
encodeChildren(dir, location, result, depth);
OrionServlet.writeJSONResponse(request, response, result);
return true;
}
private void encodeChildren(IFileStore dir, URI location, JSONObject result, int depth) throws CoreException {
if (depth <= 0)
return;
JSONArray children = new JSONArray();
//more efficient to ask for child information in bulk for certain file systems
IFileInfo[] childInfos = dir.childInfos(EFS.NONE, null);
for (IFileInfo childInfo : childInfos) {
IFileStore childStore = dir.getChild(childInfo.getName());
String name = childInfo.getName();
if (childInfo.isDirectory())
name += "/"; //$NON-NLS-1$
URI childLocation = URIUtil.append(location, name);
JSONObject childResult = ServletFileStoreHandler.toJSON(childStore, childInfo, childLocation);
if (childInfo.isDirectory())
encodeChildren(childStore, childLocation, childResult, depth - 1);
children.put(childResult);
}
try {
result.put(ProtocolConstants.KEY_CHILDREN, children);
} catch (JSONException e) {
// cannot happen
throw new RuntimeException(e);
}
}
private boolean handlePost(HttpServletRequest request, HttpServletResponse response, IFileStore dir) throws JSONException, CoreException, ServletException, IOException {
//setup and precondition checks
JSONObject requestObject = OrionServlet.readJSONRequest(request);
String name = computeName(request, requestObject);
if (name.length() == 0)
return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "File name not specified.", null));
IFileStore toCreate = dir.getChild(name);
if (!name.equals(toCreate.getName()) || name.contains(":"))
return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Bad file name: " + name, null));
int options = getCreateOptions(request);
boolean destinationExists = toCreate.fetchInfo(EFS.NONE, null).exists();
if (!validateOptions(request, response, toCreate, destinationExists, options))
return true;
//perform the operation
if (performPost(request, response, requestObject, toCreate, options)) {
//write the response
URI location = URIUtil.append(getURI(request), name);
JSONObject result = ServletFileStoreHandler.toJSON(toCreate, toCreate.fetchInfo(EFS.NONE, null), location);
result.append("FileEncoding", System.getProperty("file.encoding"));
OrionServlet.writeJSONResponse(request, response, result);
response.setHeader(ProtocolConstants.HEADER_LOCATION, ServletResourceHandler.resovleOrionURI(request, location).toString());
//response code should indicate if a new resource was actually created or not
response.setStatus(destinationExists ? HttpServletResponse.SC_OK : HttpServletResponse.SC_CREATED);
}
return true;
}
/**
* Performs the actual modification corresponding to a POST request. All preconditions
* are assumed to be satisfied.
* @return <code>true</code> if the operation was successful, and <code>false</code> otherwise.
*/
private boolean performPost(HttpServletRequest request, HttpServletResponse response, JSONObject requestObject, IFileStore toCreate, int options) throws CoreException, IOException, ServletException {
boolean isCopy = (options & CREATE_COPY) != 0;
boolean isMove = (options & CREATE_MOVE) != 0;
try {
if (isCopy || isMove)
return performCopyMove(request, response, requestObject, toCreate, isCopy, options);
if (requestObject.optBoolean(ProtocolConstants.KEY_DIRECTORY))
toCreate.mkdir(EFS.NONE, null);
else
toCreate.openOutputStream(EFS.NONE, null).close();
} catch (CoreException e) {
IStatus status = e.getStatus();
if (status != null && status.getCode() == EFS.ERROR_WRITE) {
// Sanitize message, as it might contain the filepath.
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Failed to create: " + toCreate.getName(), null));
return false;
}
throw e;
}
return true;
}
/**
* Perform a copy or move as specified by the request.
* @return <code>true</code> if the operation was successful, and <code>false</code> otherwise.
*/
private boolean performCopyMove(HttpServletRequest request, HttpServletResponse response, JSONObject requestObject, IFileStore toCreate, boolean isCopy, int options) throws ServletException, CoreException {
String locationString = requestObject.optString(ProtocolConstants.KEY_LOCATION, null);
if (locationString == null) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Copy or move request must specify source location", null));
return false;
}
try {
IFileStore source = resolveSourceLocation(request, locationString);
if (source == null) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, NLS.bind("Source does not exist: ", locationString), null));
return false;
} else if (source.isParentOf(toCreate)) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "The destination cannot be a descendent of the source location", null));
return false;
}
boolean allowOverwrite = (options & CREATE_NO_OVERWRITE) == 0;
int efsOptions = allowOverwrite ? EFS.OVERWRITE : EFS.NONE;
try {
if (isCopy) {
source.copy(toCreate, efsOptions, null);
} else {
source.move(toCreate, efsOptions, null);
// Path format is /file/workspaceId/projectId/[location to folder]
Path path = new Path(locationString);
if (path.segmentCount() == 3 && path.segment(0).equals("file")) {
// The folder is a project, remove the metadata
OrionConfiguration.getMetaStore().deleteProject(path.segment(1), source.getName());
}
}
} catch (CoreException e) {
if (!source.fetchInfo(EFS.NONE, null).exists()) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_NOT_FOUND, NLS.bind("Source does not exist: ", locationString), e));
return false;
}
if (e.getStatus().getCode() == EFS.ERROR_EXISTS) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_PRECONDITION_FAILED, "A file or folder with the same name already exists at this location", null));
return false;
}
//just rethrow if we can't do something more specific
throw e;
}
} catch (URISyntaxException e) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, NLS.bind("Bad source location in request: ", locationString), e));
return false;
}
return true;
}
/**
* Computes the name of the resource to be created by a POST operation. Returns
* an empty string if the name was not specified.
*/
private String computeName(HttpServletRequest request, JSONObject requestObject) {
String name = Slug.decode(request.getHeader(ProtocolConstants.HEADER_SLUG));
//next comes the source location for a copy/move
if (name == null || name.length() == 0) {
String location = requestObject.optString(ProtocolConstants.KEY_LOCATION);
int lastSlash = location.lastIndexOf('/');
if (lastSlash >= 0)
name = location.substring(lastSlash + 1);
}
//finally use the name attribute from the request body
if (name == null || name.length() == 0)
name = requestObject.optString(ProtocolConstants.KEY_NAME);
return name;
}
/**
* Asserts that request options are valid. If options are not valid then this method handles the request response and return false. If the options
* are valid this method return true.
*/
private boolean validateOptions(HttpServletRequest request, HttpServletResponse response, IFileStore toCreate, boolean destinationExists, int options) throws ServletException {
//operation cannot be both copy and move
int copyMove = CREATE_COPY | CREATE_MOVE;
if ((options & copyMove) == copyMove) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Syntax error in request", null));
return false;
}
//if overwrite is disallowed make sure destination does not exist yet
boolean noOverwrite = (options & CREATE_NO_OVERWRITE) != 0;
//for copy/move case, let the implementation check for overwrite because pre-validating is complicated
if ((options & copyMove) == 0 && noOverwrite && destinationExists) {
statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_PRECONDITION_FAILED, "A file or folder with the same name already exists at this location", null));
return false;
}
return true;
}
/**
* Returns a bit-mask of create options as specified by the request.
*/
private int getCreateOptions(HttpServletRequest request) {
int result = 0;
String optionString = request.getHeader(ProtocolConstants.HEADER_CREATE_OPTIONS);
if (optionString != null) {
for (String option : optionString.split(",")) { //$NON-NLS-1$
if (ProtocolConstants.OPTION_COPY.equalsIgnoreCase(option))
result |= CREATE_COPY;
else if (ProtocolConstants.OPTION_MOVE.equalsIgnoreCase(option))
result |= CREATE_MOVE;
else if (ProtocolConstants.OPTION_NO_OVERWRITE.equalsIgnoreCase(option))
result |= CREATE_NO_OVERWRITE;
}
}
return result;
}
private boolean handleDelete(HttpServletRequest request, HttpServletResponse response, IFileStore dir) throws JSONException, CoreException, ServletException, IOException {
dir.delete(EFS.NONE, null);
return true;
}
private boolean handlePut(HttpServletRequest request, HttpServletResponse response, IFileStore dir) throws JSONException, IOException, CoreException {
IFileInfo info = ServletFileStoreHandler.fromJSON(request);
dir.putInfo(info, EFS.NONE, null);
return true;
}
@Override
public boolean handleRequest(HttpServletRequest request, HttpServletResponse response, IFileStore dir) throws ServletException {
try {
switch (getMethod(request)) {
case GET :
return handleGet(request, response, dir);
case PUT :
return handlePut(request, response, dir);
case POST :
return handlePost(request, response, dir);
case DELETE :
return handleDelete(request, response, dir);
default :
return false;
}
} catch (JSONException e) {
return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_BAD_REQUEST, "Syntax error in request", e));
} catch (CoreException e) {
//core exception messages are designed for end user consumption, so use message directly
return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage(), e));
} catch (Exception e) {
if (handleAuthFailure(request, response, e))
return true;
//the exception message is probably not appropriate for end user consumption
LogHelper.log(e);
return statusHandler.handleRequest(request, response, new ServerStatus(IStatus.ERROR, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "An unknown failure occurred. Consult your server log or contact your system administrator.", e));
}
}
}