/* * eXist Open Source Native XML Database * Copyright (C) 2001-2008 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.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.http.SOAPServer; import org.exist.http.urlrewrite.XQueryURLRewrite; import org.exist.security.PermissionDeniedException; import org.exist.security.SecurityManager; import org.exist.security.User; import org.exist.security.XmldbPrincipal; import org.exist.storage.BrokerPool; import org.exist.storage.DBBroker; import org.exist.util.Configuration; import org.exist.util.DatabaseConfigurationException; import org.exist.validation.XmlLibraryChecker; import org.exist.xmldb.XmldbURI; import org.exist.xquery.Constants; import org.xmldb.api.DatabaseManager; import org.xmldb.api.base.Database; import org.xmldb.api.base.XMLDBException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.security.Principal; /** * 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 HttpServlet { private String formEncoding = null; public final static String DEFAULT_ENCODING = "UTF-8"; protected final static Logger LOG = Logger.getLogger(EXistServlet.class); private BrokerPool pool = null; private String defaultUsername = SecurityManager.GUEST_USER; private String defaultPassword = SecurityManager.GUEST_USER; private boolean internalOnly = false; private RESTServer srvREST; private SOAPServer srvSOAP; private Authenticator authenticator; private User defaultUser; /* (non-Javadoc) * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig) */ public void init(ServletConfig config) throws ServletException { super.init(config); // Configure BrokerPool try { if (BrokerPool.isConfigured()) { LOG.info("Database already started. Skipping configuration ..."); } else { String confFile = config.getInitParameter("configuration"); String dbHome = config.getInitParameter("basedir"); String start = config.getInitParameter("start"); if (confFile == null) confFile = "conf.xml"; dbHome = (dbHome == null) ? config.getServletContext().getRealPath( ".") : config.getServletContext().getRealPath(dbHome); LOG.info("EXistServlet: exist.home=" + dbHome); File f = new File(dbHome + File.separator + confFile); LOG.info("reading configuration from " + f.getAbsolutePath()); if (!f.canRead()) throw new ServletException("configuration file " + confFile + " not found or not readable"); Configuration configuration = new Configuration(confFile, dbHome); if (start != null && start.equals("true")) startup(configuration); } pool = BrokerPool.getInstance(); String option = config.getInitParameter("use-default-user"); boolean useDefaultUser = true; if (option != null) { useDefaultUser = option.trim().equals("true"); } if (useDefaultUser) { option = config.getInitParameter("user"); if (option != null) defaultUsername = option; option = config.getInitParameter("password"); if (option != null) defaultPassword = option; defaultUser = getDefaultUser(); if (defaultUser!=null) { LOG.info("Using default user "+defaultUsername+" for all unauthorized requests."); } else { LOG.error("Default user "+defaultUsername+" cannot be found. A BASIC AUTH challenge will be the default."); } } else { LOG.info("No default user. All requires must be authorized or will result in a BASIC AUTH challenge."); defaultUser = null; } authenticator = new BasicAuthenticator(pool); } catch (EXistException e) { throw new ServletException("No database instance available"); } catch (DatabaseConfigurationException e) { throw new ServletException("Unable to configure database instance: " + e.getMessage(), e); } //get form and container encoding's formEncoding = config.getInitParameter("form-encoding"); if(formEncoding == null) formEncoding = DEFAULT_ENCODING; String containerEncoding = config.getInitParameter("container-encoding"); if(containerEncoding == null) containerEncoding = DEFAULT_ENCODING; String useDynamicContentType = config.getInitParameter("dynamic-content-type"); if (useDynamicContentType == null) useDynamicContentType = "no"; String param = config.getInitParameter("hidden"); if (param != null) internalOnly = Boolean.valueOf(param); //Instantiate REST Server srvREST = new RESTServer(pool, formEncoding, containerEncoding, useDynamicContentType.equalsIgnoreCase("yes") || useDynamicContentType.equalsIgnoreCase("true"), internalOnly); //Instantiate SOAP Server srvSOAP = new SOAPServer(formEncoding, containerEncoding); //XML lib checks.... XmlLibraryChecker.check(); } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //first, adjust the path String path = adjustPath(request); //second, perform descriptor actions 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 User 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; } DBBroker broker = null; File tempFile = null; try { XmldbURI dbpath = XmldbURI.create(path); broker = pool.get(user); Collection collection = broker.getCollection(dbpath); if (collection != null) { response.sendError(400,"A PUT request is not allowed against a plain collection path."); return; } //fourth, process the request ServletInputStream is = request.getInputStream(); int len = request.getContentLength(); // put may send a lot of data, so save it // to a temporary file first. tempFile = File.createTempFile("existSRV", ".tmp"); FileOutputStream fos = new FileOutputStream(tempFile); BufferedOutputStream os = new BufferedOutputStream(fos); byte[] buffer = new byte[4096]; int count, l = 0; if (len > 0) { do { count = is.read(buffer); if (count > 0) os.write(buffer, 0, count); l += count; } while (l < len); } os.close(); srvREST.doPut(broker, tempFile, dbpath, request, response); } catch (BadRequestException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (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.equals(defaultUser)) { authenticator.sendChallenge(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (EXistException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (Throwable e){ LOG.error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } finally { if (broker!=null) { pool.release(broker); } if (tempFile!=null) { tempFile.delete(); } } } /** * @param request * @return */ private String adjustPath(HttpServletRequest request) { String path = request.getPathInfo(); if(path==null){ path=""; } int p = path.lastIndexOf(';'); if (p != Constants.STRING_NOT_FOUND) path = path.substring(0, p); return path; } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //first, adjust the path String path = adjustPath(request); //second, perform descriptor actions 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 User 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 DBBroker broker = null; try { broker = pool.get(user); //Route the request if(path.indexOf(SOAPServer.WEBSERVICE_MODULE_EXTENSION) > -1) { //SOAP Server srvSOAP.doGet(broker, request, response, path); } else { //REST Server srvREST.doGet(broker, request, response, path); } } catch (BadRequestException e) { if (response.isCommitted()) throw new ServletException(e.getMessage()); response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (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.equals(defaultUser)) { authenticator.sendChallenge(request, response); } else { response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (NotFoundException e) { if (response.isCommitted()) throw new ServletException(e.getMessage()); response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (EXistException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(),e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (Throwable e){ LOG.error(e.getMessage(), e); throw new ServletException("An error occurred: " + e.getMessage(), e); } finally { pool.release(broker); } } protected void doHead(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //first, adjust the path String path = adjustPath(request); //second, perform descriptor actions 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 User 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 DBBroker broker = null; try { broker = pool.get(user); srvREST.doHead(broker, request, response, path); } catch (BadRequestException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (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.equals(defaultUser)) { authenticator.sendChallenge(request, response); } else { response .sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (NotFoundException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (EXistException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (Throwable e){ LOG.error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } finally { pool.release(broker); } } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //first, adjust the path String path = adjustPath(request); //second, perform descriptor actions 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 User 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 DBBroker broker = null; try { broker = pool.get(user); srvREST.doDelete(broker, XmldbURI.create(path), response); } catch (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.equals(defaultUser)) { authenticator.sendChallenge(request, response); } else { response .sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (NotFoundException e) { response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (EXistException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage()); } catch (Throwable e){ LOG.error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } finally { pool.release(broker); } } /* (non-Javadoc) * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) */ 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 Descriptor descriptor = Descriptor.getDescriptorSingleton(); if(descriptor != null) { if(descriptor.allowRequestLogging()) { request = new HttpServletRequestWrapper(req, formEncoding); } 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 User 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 DBBroker broker = null; try { broker = pool.get(user); //Route the request if(path.indexOf(SOAPServer.WEBSERVICE_MODULE_EXTENSION) > -1) { //SOAP Server srvSOAP.doPost(broker, request, response, path); } else { //REST Server srvREST.doPost(broker, request, response, path); } } catch (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.equals(defaultUser)) { authenticator.sendChallenge(request, response); } else { response .sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage()); } } catch (EXistException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e .getMessage()); } catch (BadRequestException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); } catch (NotFoundException e) { if (response.isCommitted()) throw new ServletException(e.getMessage(), e); response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage()); } catch (Throwable e){ LOG.error(e); throw new ServletException("An unknown error occurred: " + e.getMessage(), e); } finally { pool.release(broker); } } /* (non-Javadoc) * @see javax.servlet.GenericServlet#destroy() */ public void destroy() { super.destroy(); BrokerPool.stopAll(false); } private User authenticate(HttpServletRequest request,HttpServletResponse response) throws java.io.IOException { if (internalOnly && request.getAttribute(XQueryURLRewrite.RQ_ATTR) == null) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return null; } // First try to validate the principal if passed from the Servlet engine Principal principal = request.getUserPrincipal(); if(principal instanceof XmldbPrincipal){ String username = ((XmldbPrincipal)principal).getName(); String password = ((XmldbPrincipal)principal).getPassword(); LOG.info("Validating Principle: " + principal.getName()); User user = pool.getSecurityManager().getUser(username); if (user != null){ if (password.equalsIgnoreCase(user.getPassword())){ LOG.info("Valid User: " + user.getName()); return user; }else{ LOG.info( "Password invalid for user: " + username ); } LOG.info("User not found: " + principal.getName()); } } //Secondly try basic authentication String auth = request.getHeader("Authorization"); if(auth == null && defaultUser!=null) { return defaultUser; } return authenticator.authenticate(request,response); /* byte[] c = Base64.decode(auth.substring(6).getBytes()); String s = new String(c); int p = s.indexOf(':'); if (p == Constants.STRING_NOT_FOUND) { return null; } String username = s.substring(0, p); String password = s.substring(p + 1); User user = pool.getSecurityManager().getUser(username); if (user == null) return null; if (!user.validate(password)) return null; return user; */ } private User getDefaultUser() { if (defaultUsername != null) { User user = pool.getSecurityManager().getUser(defaultUsername); if (user != null) { if (!user.validate(defaultPassword)) return null; } return user; } return null; } private void startup(Configuration configuration) throws ServletException { if ( configuration == null ) throw new ServletException( "database has not been " + "configured" ); LOG.info("configuring eXist instance"); try { if ( !BrokerPool.isConfigured() ) BrokerPool.configure( 1, 5, configuration ); } catch ( EXistException e ) { throw new ServletException( e.getMessage(), e ); } catch (DatabaseConfigurationException e) { throw new ServletException( e.getMessage(), e ); } try { LOG.info("registering XMLDB driver"); Class clazz = Class.forName("org.exist.xmldb.DatabaseImpl"); Database database = (Database)clazz.newInstance(); DatabaseManager.registerDatabase(database); } catch (ClassNotFoundException e) { LOG.info("ERROR", e); } catch (InstantiationException e) { LOG.info("ERROR", e); } catch (IllegalAccessException e) { LOG.info("ERROR", e); } catch (XMLDBException e) { LOG.info("ERROR", e); } } }