/* * eXist Open Source Native XML Database * Copyright (C) 2001-2010 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $Id$ */ package org.exist.http.servlets; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.EXistException; import org.exist.collections.Collection; import org.exist.http.BadRequestException; import org.exist.http.Descriptor; import org.exist.http.NotFoundException; import org.exist.http.RESTServer; import org.exist.security.PermissionDeniedException; import org.exist.security.Subject; import org.exist.storage.DBBroker; import org.exist.validation.XmlLibraryChecker; import org.exist.xmldb.XmldbURI; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.EOFException; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Optional; /** * Implements the REST-style interface if eXist is running within a Servlet * engine. The real work is done by class {@link org.exist.http.RESTServer}. * * @author wolf */ public class EXistServlet extends AbstractExistHttpServlet { private static final long serialVersionUID = -3563999345725645647L; private final static Logger LOG = LogManager.getLogger(EXistServlet.class); private RESTServer srvREST; public enum FeatureEnabled { FALSE, TRUE, AUTHENTICATED_USERS_ONLY } @Override public Logger getLog() { return LOG; } @Override public void init(ServletConfig config) throws ServletException { super.init(config); String useDynamicContentType = config.getInitParameter("dynamic-content-type"); if (useDynamicContentType == null) { useDynamicContentType = "no"; } final FeatureEnabled xquerySubmission = parseFeatureEnabled(config, "xquery-submission", FeatureEnabled.TRUE); final FeatureEnabled xupdateSubmission = parseFeatureEnabled(config,"xupdate-submission", FeatureEnabled.TRUE); // Instantiate REST Server srvREST = new RESTServer(getPool(), getFormEncoding(), getContainerEncoding(), useDynamicContentType.equalsIgnoreCase("yes") || useDynamicContentType.equalsIgnoreCase("true"), isInternalOnly(), xquerySubmission, xupdateSubmission); // XML lib checks.... XmlLibraryChecker.check(); } private FeatureEnabled parseFeatureEnabled(final ServletConfig config, final String paramName, final FeatureEnabled defaultValue) { final String paramValue = config.getInitParameter(paramName); if(paramValue != null) { if (paramValue.equals("disabled")) { return FeatureEnabled.FALSE; } else if (paramValue.equals("enabled")) { return FeatureEnabled.TRUE; } else if (paramValue.equals("authenticated")) { return FeatureEnabled.AUTHENTICATED_USERS_ONLY; } } return defaultValue; } /* * (non-Javadoc) * * @see * javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest * , javax.servlet.http.HttpServletResponse) */ @Override protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // first, adjust the path String path = adjustPath(request); // second, perform descriptor actions final Descriptor descriptor = Descriptor.getDescriptorSingleton(); if (descriptor != null) { // TODO: figure out a way to log PUT requests with // HttpServletRequestWrapper and // Descriptor.doLogRequestInReplayLog() // map's the path if a mapping is specified in the descriptor path = descriptor.mapPath(path); } // third, authenticate the user final Subject user = authenticate(request, response); if (user == null) { // You now get a challenge if there is no user // response.sendError(HttpServletResponse.SC_FORBIDDEN, // "Permission denied: unknown user or password"); return; } try(final DBBroker broker = getPool().get(Optional.of(user))) { final XmldbURI dbpath = XmldbURI.createInternal(path); final Collection collection = broker.getCollection(dbpath); if (collection != null) { response.sendError(400, "A PUT request is not allowed against a plain collection path."); return; } srvREST.doPut(broker, dbpath, request, response); } catch (final BadRequestException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (final PermissionDeniedException e) { // If the current user is the Default User and they do not have permission // then send a challenge request to prompt the client for a username/password. // Else return a FORBIDDEN Error if (user != null && user.equals(getDefaultUser())) { getAuthenticator().sendChallenge(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (final EXistException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (final Throwable e) { LOG.error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } } /** * @param request */ private String adjustPath(HttpServletRequest request) throws ServletException { String path = request.getPathInfo(); if (path == null) { return ""; } if (LOG.isDebugEnabled()) { LOG.debug(" In: " + path); } // path contains both required and superficial escapes, // as different user agents use different conventions; // for the sake of interoperability, remove any unnecessary escapes try { // URI.create undoes _all_ escaping, so protect slashes first URI u = URI.create("file://" + path.replaceAll("%2F", "%252F")); // URI four-argument constructor recreates all the necessary ones URI v = new URI("http", "host", u.getPath(), null).normalize(); // unprotect slashes in now normalized path path = v.getRawPath().replaceAll("%252F", "%2F"); } catch (final URISyntaxException e) { throw new ServletException(e.getMessage(), e); } // eat trailing slashes, else collections might not be found while(path.endsWith("/")) { path = path.substring(0, path.length() - 1); } // path now is in proper canonical encoded form if (LOG.isDebugEnabled()) { LOG.debug("Out: " + path); } return path; } /* * (non-Javadoc) * * @see * javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest * , javax.servlet.http.HttpServletResponse) */ @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // first, adjust the path String path = adjustPath(request); // second, perform descriptor actions final Descriptor descriptor = Descriptor.getDescriptorSingleton(); if (descriptor != null && !descriptor.requestsFiltered()) { // logs the request if specified in the descriptor descriptor.doLogRequestInReplayLog(request); // map's the path if a mapping is specified in the descriptor path = descriptor.mapPath(path); } // third, authenticate the user final Subject user = authenticate(request, response); if (user == null) { // You now get a challenge if there is no user // response.sendError(HttpServletResponse.SC_FORBIDDEN, // "Permission denied: unknown user " + "or password"); return; } // fourth, process the request try(final DBBroker broker = getPool().get(Optional.of(user))) { srvREST.doGet(broker, request, response, path); } catch (final BadRequestException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage()); } response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (final PermissionDeniedException e) { // If the current user is the Default User and they do not have permission // then send a challenge request to prompt the client for a username/password. // Else return a FORBIDDEN Error if (user != null && user.equals(getDefaultUser())) { getAuthenticator().sendChallenge(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (final NotFoundException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage()); } response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (final EXistException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (final EOFException ee) { getLog().error("GET Connection has been interrupted", ee); throw new ServletException("GET Connection has been interrupted", ee); } catch (final Throwable e) { getLog().error(e.getMessage(), e); throw new ServletException("An error occurred: " + e.getMessage(), e); } } @Override protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // first, adjust the path String path = adjustPath(request); // second, perform descriptor actions final Descriptor descriptor = Descriptor.getDescriptorSingleton(); if (descriptor != null && !descriptor.requestsFiltered()) { // logs the request if specified in the descriptor descriptor.doLogRequestInReplayLog(request); // map's the path if a mapping is specified in the descriptor path = descriptor.mapPath(path); } // third, authenticate the user final Subject user = authenticate(request, response); if (user == null) { // You now get a challenge if there is no user // response.sendError(HttpServletResponse.SC_FORBIDDEN, // "Permission denied: unknown user " + "or password"); return; } // fourth, process the request try(final DBBroker broker = getPool().get(Optional.of(user))) { srvREST.doHead(broker, request, response, path); } catch (final BadRequestException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (final PermissionDeniedException e) { // If the current user is the Default User and they do not have permission // then send a challenge request to prompt the client for a username/password. // Else return a FORBIDDEN Error if (user != null && user.equals(getDefaultUser())) { getAuthenticator().sendChallenge(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (final NotFoundException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (final EXistException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (final Throwable e) { getLog().error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } } /* * (non-Javadoc) * * @see * javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest * , javax.servlet.http.HttpServletResponse) */ @Override protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // first, adjust the path String path = adjustPath(request); // second, perform descriptor actions final Descriptor descriptor = Descriptor.getDescriptorSingleton(); if (descriptor != null) { // map's the path if a mapping is specified in the descriptor path = descriptor.mapPath(path); } // third, authenticate the user final Subject user = authenticate(request, response); if (user == null) { // You now get a challenge if there is no user // response.sendError(HttpServletResponse.SC_FORBIDDEN, // "Permission denied: unknown user " + "or password"); return; } // fourth, process the request try(final DBBroker broker = getPool().get(Optional.of(user))) { srvREST.doDelete(broker, path, request, response); } catch (final PermissionDeniedException e) { // If the current user is the Default User and they do not have permission // then send a challenge request to prompt the client for a username/password. // Else return a FORBIDDEN Error if (user != null && user.equals(getDefaultUser())) { getAuthenticator().sendChallenge(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (final NotFoundException e) { response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (final EXistException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (final Throwable e) { getLog().error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } } /* * (non-Javadoc) * * @see * javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest * , javax.servlet.http.HttpServletResponse) */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { HttpServletRequest request = null; // For POST request, If we are logging the requests we must wrap // HttpServletRequest in HttpServletRequestWrapper // otherwise we cannot access the POST parameters from the content body // of the request!!! - deliriumsky final Descriptor descriptor = Descriptor.getDescriptorSingleton(); if (descriptor != null) { if (descriptor.allowRequestLogging()) { request = new HttpServletRequestWrapper(req, getFormEncoding()); } else { request = req; } } else { request = req; } // first, adjust the path String path = request.getPathInfo(); if (path == null) { path = ""; } else { path = adjustPath(request); } // second, perform descriptor actions if (descriptor != null && !descriptor.requestsFiltered()) { // logs the request if specified in the descriptor descriptor.doLogRequestInReplayLog(request); // map's the path if a mapping is specified in the descriptor path = descriptor.mapPath(path); } // third, authenticate the user final Subject user = authenticate(request, response); if (user == null) { // You now get a challenge if there is no user // response.sendError(HttpServletResponse.SC_FORBIDDEN, // "Permission denied: unknown user " + "or password"); return; } // fourth, process the request try(final DBBroker broker = getPool().get(Optional.of(user))) { srvREST.doPost(broker, request, response, path); } catch (final PermissionDeniedException e) { // If the current user is the Default User and they do not have permission // then send a challenge request to prompt the client for a username/password. // Else return a FORBIDDEN Error if (user != null && user.equals(getDefaultUser())) { getAuthenticator().sendChallenge(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (final EXistException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (final BadRequestException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (final NotFoundException e) { if (response.isCommitted()) { throw new ServletException(e.getMessage(), e); } response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (final Throwable e) { getLog().error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } } }