- Add the manager classes and a package renamed fileupload.
authorremm <remm@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 8 Jun 2006 15:35:56 +0000 (15:35 +0000)
committerremm <remm@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 8 Jun 2006 15:35:56 +0000 (15:35 +0000)
- I think there should be basic multipart handling in the core, although maybe the manager doesn't
  belong here (so I would remove it later on).

git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk@412780 13f79535-47bb-0310-9956-ffa450edef68

27 files changed:
java/org/apache/catalina/manager/Constants.java [new file with mode: 0644]
java/org/apache/catalina/manager/HTMLManagerServlet.java [new file with mode: 0644]
java/org/apache/catalina/manager/JMXProxyServlet.java [new file with mode: 0644]
java/org/apache/catalina/manager/LocalStrings.properties [new file with mode: 0644]
java/org/apache/catalina/manager/LocalStrings_de.properties [new file with mode: 0644]
java/org/apache/catalina/manager/LocalStrings_es.properties [new file with mode: 0644]
java/org/apache/catalina/manager/LocalStrings_fr.properties [new file with mode: 0644]
java/org/apache/catalina/manager/LocalStrings_ja.properties [new file with mode: 0644]
java/org/apache/catalina/manager/ManagerServlet.java [new file with mode: 0644]
java/org/apache/catalina/manager/StatusManagerServlet.java [new file with mode: 0644]
java/org/apache/catalina/manager/StatusTransformer.java [new file with mode: 0644]
java/org/apache/catalina/manager/host/Constants.java [new file with mode: 0644]
java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java [new file with mode: 0644]
java/org/apache/catalina/manager/host/HostManagerServlet.java [new file with mode: 0644]
java/org/apache/catalina/manager/host/LocalStrings.properties [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileItem.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileUpload.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/FileUploadException.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/MultipartStream.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java [new file with mode: 0644]
java/org/apache/tomcat/util/http/fileupload/package.html [new file with mode: 0644]

diff --git a/java/org/apache/catalina/manager/Constants.java b/java/org/apache/catalina/manager/Constants.java
new file mode 100644 (file)
index 0000000..124f7dc
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.manager";
+
+    public static final String HTML_HEADER_SECTION =
+        "<html>\n" +
+        "<head>\n" +
+        "<style>\n" +
+        org.apache.catalina.util.TomcatCSS.TOMCAT_CSS +
+        "  table {\n" +
+        "    width: 100%;\n" +
+        "  }\n" +
+        "  td.page-title {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: white;\n" +
+        "    color: black;\n" +
+        "  }\n" +
+        "  td.title {\n" +
+        "    text-align: left;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-style:italic;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #D2A41C;\n" +
+        "  }\n" +
+        "  td.header-left {\n" +
+        "    text-align: left;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #FFDC75;\n" +
+        "  }\n" +
+        "  td.header-center {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #FFDC75;\n" +
+        "  }\n" +
+        "  td.row-left {\n" +
+        "    text-align: left;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "  }\n" +
+        "  td.row-center {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "  }\n" +
+        "  td.row-right {\n" +
+        "    text-align: right;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "  }\n" +
+        "  TH {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #FFDC75;\n" +
+        "  }\n" +
+        "  TD {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "  }\n" +
+        "</style>\n";
+
+    public static final String BODY_HEADER_SECTION =
+        "<title>{0}</title>\n" +
+        "</head>\n" +
+        "\n" +
+        "<body bgcolor=\"#FFFFFF\">\n" +
+        "\n" +
+        "<table cellspacing=\"4\" width=\"100%\" border=\"0\">\n" +
+        " <tr>\n" +
+        "  <td colspan=\"2\">\n" +
+        "   <a href=\"http://www.apache.org/\">\n" +
+        "    <img border=\"0\" alt=\"The Apache Software Foundation\" align=\"left\"\n" +
+        "         src=\"{0}/images/asf-logo.gif\">\n" +
+        "   </a>\n" +
+        "   <a href=\"http://tomcat.apache.org/\">\n" +
+        "    <img border=\"0\" alt=\"The Tomcat Servlet/JSP Container\"\n" +
+        "         align=\"right\" src=\"{0}/images/tomcat.gif\">\n" +
+        "   </a>\n" +
+        "  </td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<hr size=\"1\" noshade=\"noshade\">\n" +
+        "<table cellspacing=\"4\" width=\"100%\" border=\"0\">\n" +
+        " <tr>\n" +
+        "  <td class=\"page-title\" bordercolor=\"#000000\" " +
+        "align=\"left\" nowrap>\n" +
+        "   <font size=\"+2\">{1}</font>\n" +
+        "  </td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String MESSAGE_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        " <tr>\n" +
+        "  <td class=\"row-left\" width=\"10%\">" +
+        "<small><strong>{0}</strong></small>&nbsp;</td>\n" +
+        "  <td class=\"row-left\"><pre>{1}</pre></td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String MANAGER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"4\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        " <tr>\n" +
+        "  <td class=\"row-left\"><a href=\"{1}\">{2}</a></td>\n" +
+        "  <td class=\"row-center\"><a href=\"{3}\">{4}</a></td>\n" +
+        "  <td class=\"row-center\"><a href=\"{5}\">{6}</a></td>\n" +
+        "  <td class=\"row-right\"><a href=\"{7}\">{8}</a></td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String SERVER_HEADER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"6\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"header-center\"><small>{1}</small></td>\n" +
+        " <td class=\"header-center\"><small>{2}</small></td>\n" +
+        " <td class=\"header-center\"><small>{3}</small></td>\n" +
+        " <td class=\"header-center\"><small>{4}</small></td>\n" +
+        " <td class=\"header-center\"><small>{5}</small></td>\n" +
+        " <td class=\"header-center\"><small>{6}</small></td>\n" +
+        "</tr>\n";
+
+    public static final String SERVER_ROW_SECTION =
+        "<tr>\n" +
+        " <td class=\"row-center\"><small>{0}</small></td>\n" +
+        " <td class=\"row-center\"><small>{1}</small></td>\n" +
+        " <td class=\"row-center\"><small>{2}</small></td>\n" +
+        " <td class=\"row-center\"><small>{3}</small></td>\n" +
+        " <td class=\"row-center\"><small>{4}</small></td>\n" +
+        " <td class=\"row-center\"><small>{5}</small></td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String HTML_TAIL_SECTION =
+        "<hr size=\"1\" noshade=\"noshade\">\n" +
+        "<center><font size=\"-1\" color=\"#525D76\">\n" +
+        " <em>Copyright &copy; 1999-2005, Apache Software Foundation</em>" +
+        "</font></center>\n" +
+        "\n" +
+        "</body>\n" +
+        "</html>";
+    public static final String CHARSET="utf-8";
+
+    public static final String XML_DECLARATION =
+        "<?xml version=\"1.0\" encoding=\""+CHARSET+"\"?>";
+               
+    public static final String XML_STYLE =
+        "<?xml-stylesheet type=\"text/xsl\" href=\"/manager/xform.xsl\" ?>";
+
+}
+
diff --git a/java/org/apache/catalina/manager/HTMLManagerServlet.java b/java/org/apache/catalina/manager/HTMLManagerServlet.java
new file mode 100644 (file)
index 0000000..ad84bb2
--- /dev/null
@@ -0,0 +1,691 @@
+/*
+ * Copyright 1999,2004-2005 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.tomcat.util.http.fileupload.DiskFileUpload;
+import org.apache.tomcat.util.http.fileupload.FileItem;
+
+/**
+* Servlet that enables remote management of the web applications deployed
+* within the same virtual host as this web application is.  Normally, this
+* functionality will be protected by a security constraint in the web
+* application deployment descriptor.  However, this requirement can be
+* relaxed during testing.
+* <p>
+* The difference between the <code>ManagerServlet</code> and this
+* Servlet is that this Servlet prints out a HTML interface which
+* makes it easier to administrate.
+* <p>
+* However if you use a software that parses the output of
+* <code>ManagerServlet</code you won't be able to upgrade
+* to this Servlet since the output are not in the
+* same format ar from <code>ManagerServlet</code>
+*
+* @author Bip Thelin
+* @author Malcolm Edgar
+* @author Glenn L. Nielsen
+* @version $Revision: 326772 $, $Date: 2005-10-20 03:37:02 +0200 (jeu., 20 oct. 2005) $
+* @see ManagerServlet
+*/
+
+public final class HTMLManagerServlet extends ManagerServlet {
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+
+        String path = request.getParameter("path");
+        String deployPath = request.getParameter("deployPath");
+        String deployConfig = request.getParameter("deployConfig");
+        String deployWar = request.getParameter("deployWar");
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/html; charset=" + Constants.CHARSET);
+
+        String message = "";
+        // Process the requested command
+        if (command == null || command.equals("/")) {
+        } else if (command.equals("/deploy")) {
+            message = deployInternal(deployConfig, deployPath, deployWar);
+        } else if (command.equals("/list")) {
+        } else if (command.equals("/reload")) {
+            message = reload(path);
+        } else if (command.equals("/undeploy")) {
+            message = undeploy(path);
+        } else if (command.equals("/sessions")) {
+            message = sessions(path);
+        } else if (command.equals("/start")) {
+            message = start(path);
+        } else if (command.equals("/stop")) {
+            message = stop(path);
+        } else {
+            message =
+                sm.getString("managerServlet.unknownCommand",
+                             RequestUtil.filter(command));
+        }
+
+        list(request, response, message);
+    }
+
+    /**
+     * Process a POST request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doPost(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+
+        if (command == null || !command.equals("/upload")) {
+            doGet(request,response);
+            return;
+        }
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/html; charset=" + Constants.CHARSET);
+
+        String message = "";
+
+        // Create a new file upload handler
+        DiskFileUpload upload = new DiskFileUpload();
+
+        // Get the tempdir
+        File tempdir = (File) getServletContext().getAttribute
+            ("javax.servlet.context.tempdir");
+        // Set upload parameters
+        upload.setSizeMax(-1);
+        upload.setRepositoryPath(tempdir.getCanonicalPath());
+    
+        // Parse the request
+        String basename = null;
+        String war = null;
+        FileItem warUpload = null;
+        try {
+            List items = upload.parseRequest(request);
+        
+            // Process the uploaded fields
+            Iterator iter = items.iterator();
+            while (iter.hasNext()) {
+                FileItem item = (FileItem) iter.next();
+        
+                if (!item.isFormField()) {
+                    if (item.getFieldName().equals("deployWar") &&
+                        warUpload == null) {
+                        warUpload = item;
+                    } else {
+                        item.delete();
+                    }
+                }
+            }
+            while (true) {
+                if (warUpload == null) {
+                    message = sm.getString
+                        ("htmlManagerServlet.deployUploadNoFile");
+                    break;
+                }
+                war = warUpload.getName();
+                if (!war.toLowerCase().endsWith(".war")) {
+                    message = sm.getString
+                        ("htmlManagerServlet.deployUploadNotWar",war);
+                    break;
+                }
+                // Get the filename if uploaded name includes a path
+                if (war.lastIndexOf('\\') >= 0) {
+                    war = war.substring(war.lastIndexOf('\\') + 1);
+                }
+                if (war.lastIndexOf('/') >= 0) {
+                    war = war.substring(war.lastIndexOf('/') + 1);
+                }
+                // Identify the appBase of the owning Host of this Context
+                // (if any)
+                basename = war.substring(0, war.toLowerCase().indexOf(".war"));
+                File file = new File(getAppBase(), war);
+                if (file.exists()) {
+                    message = sm.getString
+                        ("htmlManagerServlet.deployUploadWarExists",war);
+                    break;
+                }
+                String path = null;
+                if (basename.equals("ROOT")) {
+                    path = "";
+                } else {
+                    path = "/" + basename;
+                }
+
+                if (!isServiced(path)) {
+                    addServiced(path);
+                    try {
+                        warUpload.write(file);
+                        // Perform new deployment
+                        check(path);
+                    } finally {
+                        removeServiced(path);
+                    }
+                }
+                break;
+            }
+        } catch(Exception e) {
+            message = sm.getString
+                ("htmlManagerServlet.deployUploadFail", e.getMessage());
+            log(message, e);
+        } finally {
+            if (warUpload != null) {
+                warUpload.delete();
+            }
+            warUpload = null;
+        }
+
+        list(request, response, message);
+    }
+
+    /**
+     * Deploy an application for the specified path from the specified
+     * web application archive.
+     *
+     * @param config URL of the context configuration file to be deployed
+     * @param path Context path of the application to be deployed
+     * @param war URL of the web application archive to be deployed
+     * @return message String
+     */
+    protected String deployInternal(String config, String path, String war) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.deploy(printWriter, config, path, war, false);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Render a HTML list of the currently active Contexts in our virtual host,
+     * and memory and server status information.
+     *
+     * @param request The request
+     * @param response The response
+     * @param message a message to display
+     */
+    public void list(HttpServletRequest request,
+                     HttpServletResponse response,
+                     String message) throws IOException {
+
+        if (debug >= 1)
+            log("list: Listing contexts for virtual host '" +
+                host.getName() + "'");
+
+        PrintWriter writer = response.getWriter();
+
+        // HTML Header Section
+        writer.print(Constants.HTML_HEADER_SECTION);
+
+        // Body Header Section
+        Object[] args = new Object[2];
+        args[0] = request.getContextPath();
+        args[1] = sm.getString("htmlManagerServlet.title");
+        writer.print(MessageFormat.format
+                     (Constants.BODY_HEADER_SECTION, args));
+
+        // Message Section
+        args = new Object[3];
+        args[0] = sm.getString("htmlManagerServlet.messageLabel");
+        args[1] = (message == null || message.length() == 0) ? "OK" : message;
+        writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args));
+
+        // Manager Section
+        args = new Object[9];
+        args[0] = sm.getString("htmlManagerServlet.manager");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/list");
+        args[2] = sm.getString("htmlManagerServlet.list");
+        args[3] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlManagerServlet.helpHtmlManagerFile"));
+        args[4] = sm.getString("htmlManagerServlet.helpHtmlManager");
+        args[5] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlManagerServlet.helpManagerFile"));
+        args[6] = sm.getString("htmlManagerServlet.helpManager");
+        args[7] = response.encodeURL
+            (request.getContextPath() + "/status");
+        args[8] = sm.getString("statusServlet.title");
+        writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
+
+        // Apps Header Section
+        args = new Object[6];
+        args[0] = sm.getString("htmlManagerServlet.appsTitle");
+        args[1] = sm.getString("htmlManagerServlet.appsPath");
+        args[2] = sm.getString("htmlManagerServlet.appsName");
+        args[3] = sm.getString("htmlManagerServlet.appsAvailable");
+        args[4] = sm.getString("htmlManagerServlet.appsSessions");
+        args[5] = sm.getString("htmlManagerServlet.appsTasks");
+        writer.print(MessageFormat.format(APPS_HEADER_SECTION, args));
+
+        // Apps Row Section
+        // Create sorted map of deployed applications context paths.
+        Container children[] = host.findChildren();
+        String contextPaths[] = new String[children.length];
+        for (int i = 0; i < children.length; i++)
+            contextPaths[i] = children[i].getName();
+
+        TreeMap sortedContextPathsMap = new TreeMap();
+
+        for (int i = 0; i < contextPaths.length; i++) {
+            String displayPath = contextPaths[i];
+            sortedContextPathsMap.put(displayPath, contextPaths[i]);
+        }
+
+        String appsStart = sm.getString("htmlManagerServlet.appsStart");
+        String appsStop = sm.getString("htmlManagerServlet.appsStop");
+        String appsReload = sm.getString("htmlManagerServlet.appsReload");
+        String appsUndeploy = sm.getString("htmlManagerServlet.appsUndeploy");
+
+        Iterator iterator = sortedContextPathsMap.entrySet().iterator();
+        boolean isHighlighted = true;
+        String highlightColor = null;
+
+        while (iterator.hasNext()) {
+            // Bugzilla 34818, alternating row colors
+            isHighlighted = !isHighlighted;
+            if(isHighlighted) {
+                highlightColor = "#C3F3C3";
+            } else {
+                highlightColor = "#FFFFFF";
+            }
+
+            Map.Entry entry = (Map.Entry) iterator.next();
+            String displayPath = (String) entry.getKey();
+            String contextPath = (String) entry.getKey();
+            Context context = (Context) host.findChild(contextPath);
+            if (displayPath.equals("")) {
+                displayPath = "/";
+            }
+
+            if (context != null ) {
+                args = new Object[6];
+                args[0] = displayPath;
+                args[1] = context.getDisplayName();
+                if (args[1] == null) {
+                    args[1] = "&nbsp;";
+                }
+                args[2] = new Boolean(context.getAvailable());
+                args[3] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/sessions?path=" + displayPath);
+                if (context.getManager() != null) {
+                    args[4] = new Integer
+                        (context.getManager().getActiveSessions());
+                } else {
+                    args[4] = new Integer(0);
+                }
+
+                args[5] = highlightColor;
+
+                writer.print
+                    (MessageFormat.format(APPS_ROW_DETAILS_SECTION, args));
+
+                args = new Object[9];
+                args[0] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/start?path=" + displayPath);
+                args[1] = appsStart;
+                args[2] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/stop?path=" + displayPath);
+                args[3] = appsStop;
+                args[4] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/reload?path=" + displayPath);
+                args[5] = appsReload;
+                args[6] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/undeploy?path=" + displayPath);
+                args[7] = appsUndeploy;
+                
+                args[8] = highlightColor;
+
+                if (context.getPath().equals(this.context.getPath())) {
+                    writer.print(MessageFormat.format(
+                        MANAGER_APP_ROW_BUTTON_SECTION, args));
+                } else if (context.getAvailable()) {
+                    writer.print(MessageFormat.format(
+                        STARTED_APPS_ROW_BUTTON_SECTION, args));
+                } else {
+                    writer.print(MessageFormat.format(
+                        STOPPED_APPS_ROW_BUTTON_SECTION, args));
+                }
+
+            }
+        }
+
+        // Deploy Section
+        args = new Object[7];
+        args[0] = sm.getString("htmlManagerServlet.deployTitle");
+        args[1] = sm.getString("htmlManagerServlet.deployServer");
+        args[2] = response.encodeURL(request.getContextPath() + "/html/deploy");
+        args[3] = sm.getString("htmlManagerServlet.deployPath");
+        args[4] = sm.getString("htmlManagerServlet.deployConfig");
+        args[5] = sm.getString("htmlManagerServlet.deployWar");
+        args[6] = sm.getString("htmlManagerServlet.deployButton");
+        writer.print(MessageFormat.format(DEPLOY_SECTION, args));
+
+        args = new Object[4];
+        args[0] = sm.getString("htmlManagerServlet.deployUpload");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/upload");
+        args[2] = sm.getString("htmlManagerServlet.deployUploadFile");
+        args[3] = sm.getString("htmlManagerServlet.deployButton");
+        writer.print(MessageFormat.format(UPLOAD_SECTION, args));
+
+        // Server Header Section
+        args = new Object[7];
+        args[0] = sm.getString("htmlManagerServlet.serverTitle");
+        args[1] = sm.getString("htmlManagerServlet.serverVersion");
+        args[2] = sm.getString("htmlManagerServlet.serverJVMVersion");
+        args[3] = sm.getString("htmlManagerServlet.serverJVMVendor");
+        args[4] = sm.getString("htmlManagerServlet.serverOSName");
+        args[5] = sm.getString("htmlManagerServlet.serverOSVersion");
+        args[6] = sm.getString("htmlManagerServlet.serverOSArch");
+        writer.print(MessageFormat.format
+                     (Constants.SERVER_HEADER_SECTION, args));
+
+        // Server Row Section
+        args = new Object[6];
+        args[0] = ServerInfo.getServerInfo();
+        args[1] = System.getProperty("java.runtime.version");
+        args[2] = System.getProperty("java.vm.vendor");
+        args[3] = System.getProperty("os.name");
+        args[4] = System.getProperty("os.version");
+        args[5] = System.getProperty("os.arch");
+        writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));
+
+        // HTML Tail Section
+        writer.print(Constants.HTML_TAIL_SECTION);
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+    }
+
+    /**
+     * Reload the web application at the specified context path.
+     *
+     * @see ManagerServlet#reload(PrintWriter, String)
+     *
+     * @param path Context path of the application to be restarted
+     * @return message String
+     */
+    protected String reload(String path) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.reload(printWriter, path);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Undeploy the web application at the specified context path.
+     *
+     * @see ManagerServlet#undeploy(PrintWriter, String)
+     *
+     * @param path Context path of the application to be undeployd
+     * @return message String
+     */
+    protected String undeploy(String path) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.undeploy(printWriter, path);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Display session information and invoke list.
+     *
+     * @see ManagerServlet#sessions(PrintWriter, String)
+     *
+     * @param path Context path of the application to list session information
+     * @return message String
+     */
+    public String sessions(String path) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.sessions(printWriter, path);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Start the web application at the specified context path.
+     *
+     * @see ManagerServlet#start(PrintWriter, String)
+     *
+     * @param path Context path of the application to be started
+     * @return message String
+     */
+    public String start(String path) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.start(printWriter, path);
+
+        return stringWriter.toString();
+    }
+
+    /**
+     * Stop the web application at the specified context path.
+     *
+     * @see ManagerServlet#stop(PrintWriter, String)
+     *
+     * @param path Context path of the application to be stopped
+     * @return message String
+     */
+    protected String stop(String path) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.stop(printWriter, path);
+
+        return stringWriter.toString();
+    }
+
+    // ------------------------------------------------------ Private Constants
+
+    // These HTML sections are broken in relatively small sections, because of
+    // limited number of subsitutions MessageFormat can process
+    // (maximium of 10).
+
+    private static final String APPS_HEADER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"5\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"header-left\"><small>{1}</small></td>\n" +
+        " <td class=\"header-left\"><small>{2}</small></td>\n" +
+        " <td class=\"header-center\"><small>{3}</small></td>\n" +
+        " <td class=\"header-center\"><small>{4}</small></td>\n" +
+        " <td class=\"header-center\"><small>{5}</small></td>\n" +
+        "</tr>\n";
+
+    private static final String APPS_ROW_DETAILS_SECTION =
+        "<tr>\n" +
+        " <td class=\"row-left\" bgcolor=\"{5}\"><small><a href=\"{0}\">{0}</a></small></td>\n" +
+        " <td class=\"row-left\" bgcolor=\"{5}\"><small>{1}</small></td>\n" +
+        " <td class=\"row-center\" bgcolor=\"{5}\"><small>{2}</small></td>\n" +
+        " <td class=\"row-center\" bgcolor=\"{5}\"><small><a href=\"{3}\">{4}</a></small></td>\n";
+
+    private static final String MANAGER_APP_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{8}\">\n" +
+        "  <small>\n" +
+        "  &nbsp;{1}&nbsp;\n" +
+        "  &nbsp;{3}&nbsp;\n" +
+        "  &nbsp;{5}&nbsp;\n" +
+        "  &nbsp;{7}&nbsp;\n" +
+        "  </small>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String STARTED_APPS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{8}\">\n" +
+        "  <small>\n" +
+        "  &nbsp;{1}&nbsp;\n" +
+        "  &nbsp;<a href=\"{2}\" onclick=\"return(confirm('''Are you sure?'''))\">{3}</a>&nbsp;\n" +
+        "  &nbsp;<a href=\"{4}\" onclick=\"return(confirm('''Are you sure?'''))\">{5}</a>&nbsp;\n" +
+        "  &nbsp;<a href=\"{6}\" onclick=\"return(confirm('''Are you sure?'''))\">{7}</a>&nbsp;\n" +
+        "  </small>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String STOPPED_APPS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" bgcolor=\"{8}\">\n" +
+        "  <small>\n" +
+        "  &nbsp;<a href=\"{0}\" onclick=\"return(confirm('''Are you sure?'''))\">{1}</a>&nbsp;\n" +
+        "  &nbsp;{3}&nbsp;\n" +
+        "  &nbsp;{5}&nbsp;\n" +
+        "  &nbsp;<a href=\"{6}\" onclick=\"return(confirm('''Are you sure?  This will delete the application.'''))\">{7}</a>&nbsp;\n" +
+        "  </small>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String DEPLOY_SECTION =
+        "</table>\n" +
+        "<br>\n" +
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"header-left\"><small>{1}</small></td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\">\n" +
+        "<form method=\"get\" action=\"{2}\">\n" +
+        "<table cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{3}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"deployPath\" size=\"20\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{4}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"deployConfig\" size=\"20\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{5}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"deployWar\" size=\"40\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  &nbsp;\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"submit\" value=\"{6}\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "</form>\n" +
+        "</td>\n" +
+        "</tr>\n";
+
+    private static final String UPLOAD_SECTION =
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"header-left\"><small>{0}</small></td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\">\n" +
+        "<form action=\"{1}\" method=\"post\" " +
+        "enctype=\"multipart/form-data\">\n" +
+        "<table cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{2}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"file\" name=\"deployWar\" size=\"40\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  &nbsp;\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"submit\" value=\"{3}\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "</form>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+}
diff --git a/java/org/apache/catalina/manager/JMXProxyServlet.java b/java/org/apache/catalina/manager/JMXProxyServlet.java
new file mode 100644 (file)
index 0000000..7509ed8
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Iterator;
+import java.util.Set;
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.management.MBeanInfo;
+import javax.management.MBeanAttributeInfo;
+import javax.management.Attribute;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * This servlet will dump JMX attributes in a simple format
+ * and implement proxy services for modeler.
+ *
+ * @author Costin Manolache
+ */
+public class JMXProxyServlet extends HttpServlet  {
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * MBean server.
+     */
+    protected MBeanServer mBeanServer = null;
+    protected Registry registry;
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Initialize this servlet.
+     */
+    public void init() throws ServletException {
+        // Retrieve the MBean server
+        registry = Registry.getRegistry(null, null);
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException
+    {
+
+        response.setContentType("text/plain");
+
+        PrintWriter writer = response.getWriter();
+        String qryString= request.getQueryString();
+
+        if( mBeanServer==null ) {
+            writer.println("Error - No mbean server");
+            return;
+        }
+
+        String qry=request.getParameter("set");
+        if( qry!= null ) {
+            String name=request.getParameter("att");
+            String val=request.getParameter("val");
+
+            setAttribute( writer, qry, name, val );
+            return;
+        }
+        qry=request.getParameter("get");
+        if( qry!= null ) {
+            String name=request.getParameter("att");
+            getAttribute( writer, qry, name );
+            return;
+        }        
+        qry=request.getParameter("qry");
+        if( qry == null ) {
+            qry = "*:*";
+        }
+
+        listBeans( writer, qry );
+
+    }
+
+    public void getAttribute(PrintWriter writer, String onameStr, String att) {
+        try {
+            ObjectName oname = new ObjectName(onameStr);
+            Object value = mBeanServer.getAttribute(oname, att);
+            writer.println("OK - Attribute get '" + onameStr + "' - " + att + "= " + value.toString() );
+        } catch (Exception ex) {
+            writer.println("Error - " + ex.toString());
+        }
+    }
+
+    public void setAttribute( PrintWriter writer,
+                              String onameStr, String att, String val )
+    {
+        try {
+            ObjectName oname=new ObjectName( onameStr );
+            String type=registry.getType(oname, att);
+            Object valueObj=registry.convertValue(type, val );
+            mBeanServer.setAttribute( oname, new Attribute(att, valueObj));
+            writer.println("OK - Attribute set");
+        } catch( Exception ex ) {
+            writer.println("Error - " + ex.toString());
+        }
+    }
+
+    public void listBeans( PrintWriter writer, String qry )
+    {
+
+        Set names = null;
+        try {
+            names=mBeanServer.queryNames(new ObjectName(qry), null);
+            writer.println("OK - Number of results: " + names.size());
+            writer.println();
+        } catch (Exception e) {
+            writer.println("Error - " + e.toString());
+            return;
+        }
+
+        Iterator it=names.iterator();
+        while( it.hasNext()) {
+            ObjectName oname=(ObjectName)it.next();
+            writer.println( "Name: " + oname.toString());
+
+            try {
+                MBeanInfo minfo=mBeanServer.getMBeanInfo(oname);
+                // can't be null - I thinl
+                String code=minfo.getClassName();
+                if ("org.apache.commons.modeler.BaseModelMBean".equals(code)) {
+                    code=(String)mBeanServer.getAttribute(oname, "modelerType");
+                }
+                writer.println("modelerType: " + code);
+
+                MBeanAttributeInfo attrs[]=minfo.getAttributes();
+                Object value=null;
+
+                for( int i=0; i< attrs.length; i++ ) {
+                    if( ! attrs[i].isReadable() ) continue;
+                    if( ! isSupported( attrs[i].getType() )) continue;
+                    String attName=attrs[i].getName();
+                    if( attName.indexOf( "=") >=0 ||
+                            attName.indexOf( ":") >=0 ||
+                            attName.indexOf( " ") >=0 ) {
+                        continue;
+                    }
+            
+                    try {
+                        value=mBeanServer.getAttribute(oname, attName);
+                    } catch( Throwable t) {
+                        System.out.println("Error getting attribute " + oname +
+                                " " + attName + " " + t.toString());
+                        continue;
+                    }
+                    if( value==null ) continue;
+                    if( "modelerType".equals( attName)) continue;
+                    String valueString=value.toString();
+                    writer.println( attName + ": " + escape(valueString));
+                }
+            } catch (Exception e) {
+                // Ignore
+            }
+            writer.println();
+        }
+
+    }
+
+    public String escape(String value) {
+        // The only invalid char is \n
+        // We also need to keep the string short and split it with \nSPACE
+        // XXX TODO
+        int idx=value.indexOf( "\n" );
+        if( idx < 0 ) return value;
+
+        int prev=0;
+        StringBuffer sb=new StringBuffer();
+        while( idx >= 0 ) {
+            appendHead(sb, value, prev, idx-1);
+
+            sb.append( "\\n\n ");
+            prev=idx+1;
+            if( idx==value.length() -1 ) break;
+            idx=value.indexOf('\n', idx+1);
+        }
+        if( prev < value.length() )
+            appendHead( sb, value, prev, value.length());
+        return sb.toString();
+    }
+
+    private void appendHead( StringBuffer sb, String value, int start, int end) {
+        int pos=start;
+        while( end-pos > 78 ) {
+            sb.append( value.substring(pos, pos+78));
+            sb.append( "\n ");
+            pos=pos+78;
+        }
+        sb.append( value.substring(pos,end));
+    }
+
+    public boolean isSupported( String type ) {
+        return true;
+    }
+}
diff --git a/java/org/apache/catalina/manager/LocalStrings.properties b/java/org/apache/catalina/manager/LocalStrings.properties
new file mode 100644 (file)
index 0000000..feeabb1
--- /dev/null
@@ -0,0 +1,81 @@
+htmlManagerServlet.appsAvailable=Running
+htmlManagerServlet.appsName=Display Name
+htmlManagerServlet.appsPath=Path
+htmlManagerServlet.appsReload=Reload
+htmlManagerServlet.appsUndeploy=Undeploy
+htmlManagerServlet.appsSessions=Sessions
+htmlManagerServlet.appsStart=Start
+htmlManagerServlet.appsStop=Stop
+htmlManagerServlet.appsTasks=Commands
+htmlManagerServlet.appsTitle=Applications
+htmlManagerServlet.helpHtmlManager=HTML Manager Help
+htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html
+htmlManagerServlet.helpManager=Manager Help
+htmlManagerServlet.helpManagerFile=manager-howto.html
+htmlManagerServlet.deployButton=Deploy
+htmlManagerServlet.deployConfig=XML Configuration file URL:
+htmlManagerServlet.deployPath=Context Path (optional):
+htmlManagerServlet.deployServer=Deploy directory or WAR file located on server
+htmlManagerServlet.deployTitle=Deploy
+htmlManagerServlet.deployUpload=WAR file to deploy
+htmlManagerServlet.deployUploadFail=FAIL - Deploy Upload Failed, Exception: {0}
+htmlManagerServlet.deployUploadFile=Select WAR file to upload
+htmlManagerServlet.deployUploadNotWar=FAIL - File uploaded \"{0}\" must be a .war
+htmlManagerServlet.deployUploadNoFile=FAIL - File upload failed, no file
+htmlManagerServlet.deployUploadWarExists=FAIL - War file \"{0}\" already exists on server
+htmlManagerServlet.deployWar=WAR or Directory URL:
+htmlManagerServlet.list=List Applications
+htmlManagerServlet.manager=Manager
+htmlManagerServlet.messageLabel=Message:
+htmlManagerServlet.serverJVMVendor=JVM Vendor
+htmlManagerServlet.serverJVMVersion=JVM Version
+htmlManagerServlet.serverOSArch=OS Architecture
+htmlManagerServlet.serverOSName=OS Name
+htmlManagerServlet.serverOSVersion=OS Version
+htmlManagerServlet.serverTitle=Server Information
+htmlManagerServlet.serverVersion=Tomcat Version
+htmlManagerServlet.title=Tomcat Web Application Manager
+managerServlet.alreadyContext=FAIL - Application already exists at path {0}
+managerServlet.alreadyDocBase=FAIL - Directory {0} is already in use
+managerServlet.cannotInvoke=Cannot invoke manager servlet through invoker
+managerServlet.configured=OK - Deployed application from context file {0}
+managerServlet.deployed=OK - Deployed application at context path {0}
+managerServlet.deployFailed=FAIL - Failed to deploy application at context path {0}
+managerServlet.exception=FAIL - Encountered exception {0}
+managerServlet.deployed=OK - Deployed application at context path {0}
+managerServlet.invalidPath=FAIL - Invalid context path {0} was specified
+managerServlet.invalidWar=FAIL - Invalid application URL {0} was specified
+managerServlet.listed=OK - Listed applications for virtual host {0}
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=FAIL - Cannot identify application base for context path {0}
+managerServlet.noCommand=FAIL - No command was specified
+managerServlet.noContext=FAIL - No context exists for path {0}
+managerServlet.noDirectory=FAIL - Non-directory document base for path {0}
+managerServlet.noDocBase=FAIL - Cannot undeploy document base for path {0}
+managerServlet.noGlobal=FAIL - No global JNDI resources are available
+managerServlet.noReload=FAIL - Reload not supported on WAR deployed at path {0}
+managerServlet.noRename=FAIL - Cannot deploy uploaded WAR for path {0}
+managerServlet.noRole=FAIL - User does not possess role {0}
+managerServlet.noSelf=FAIL - The manager can not reload, undeploy, stop, or undeploy itself
+managerServlet.noWrapper=Container has not called setWrapper() for this servlet
+managerServlet.reloaded=OK - Reloaded application at context path {0}
+managerServlet.undeployd=OK - Undeployed application at context path {0}
+managerServlet.resourcesAll=OK - Listed global resources of all types
+managerServlet.resourcesType=OK - Listed global resources of type {0}
+managerServlet.rolesList=OK - Listed security roles
+managerServlet.saveFail=FAIL - Configuration save failed: {0}
+managerServlet.saved=OK - Server configuration saved
+managerServlet.savedContext=OK - Context {0} configuration saved
+managerServlet.sessiondefaultmax=Default maximum session inactive interval {0} minutes
+managerServlet.sessiontimeout={0} minutes:{1} sessions
+managerServlet.sessions=OK - Session information for application at context path {0}
+managerServlet.started=OK - Started application at context path {0}
+managerServlet.startFailed=FAIL - Application at context path {0} could not be started
+managerServlet.stopped=OK - Stopped application at context path {0}
+managerServlet.undeployed=OK - Undeployed application at context path {0}
+managerServlet.unknownCommand=FAIL - Unknown command {0}
+managerServlet.userDatabaseError=FAIL - Cannot resolve user database reference
+managerServlet.userDatabaseMissing=FAIL - No user database is available
+
+statusServlet.title=Server Status
+statusServlet.complete=Complete Server Status
diff --git a/java/org/apache/catalina/manager/LocalStrings_de.properties b/java/org/apache/catalina/manager/LocalStrings_de.properties
new file mode 100644 (file)
index 0000000..20ada40
--- /dev/null
@@ -0,0 +1,78 @@
+htmlManagerServlet.appsAvailable=Verfügbar
+htmlManagerServlet.appsName=Anzeigename
+htmlManagerServlet.appsPath=Kontext Pfad
+htmlManagerServlet.appsReload=Neu laden
+htmlManagerServlet.appsUndeploy=Entfernen
+htmlManagerServlet.appsSessions=Sitzungen
+htmlManagerServlet.appsStart=Start
+htmlManagerServlet.appsStop=Stop
+htmlManagerServlet.appsTasks=Kommandos
+htmlManagerServlet.appsTitle=Anwendungen
+htmlManagerServlet.helpHtmlManager=Hilfeseite HTML Manager (englisch)
+htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html
+htmlManagerServlet.helpManager=Hilfeseite Manager (englisch)
+htmlManagerServlet.helpManagerFile=manager-howto.html
+htmlManagerServlet.deployButton=Installieren
+htmlManagerServlet.deployConfig=XML Konfigurationsdatei URL:
+htmlManagerServlet.deployPath=Kontext Pfad (optional):
+htmlManagerServlet.deployServer=Verzeichnis oder WAR Datei auf Server installieren
+htmlManagerServlet.deployTitle=Installieren
+htmlManagerServlet.deployUpload=Lokale WAR Datei zur Installation hochladen
+htmlManagerServlet.deployUploadFail=FEHLER - Hochladen zur Installation fehlgeschlagen, Ausnahme: {0}
+htmlManagerServlet.deployUploadFile=WAR Datei auswählen
+htmlManagerServlet.deployUploadNotWar=FEHLER - Hochgeladene Datei \"{0}\" muss ein .war sein
+htmlManagerServlet.deployUploadNoFile=FEHLER - Hochladen fehlgeschlagen, keine Datei vorhanden
+htmlManagerServlet.deployUploadWarExists=FEHLER - WAR Datei \"{0}\" existiert bereits auf Server
+htmlManagerServlet.deployWar=WAR oder Verzeichnis URL:
+htmlManagerServlet.list=Anwendungen auflisten
+htmlManagerServlet.manager=Manager
+htmlManagerServlet.messageLabel=Nachricht:
+htmlManagerServlet.serverJVMVendor=JVM Hersteller
+htmlManagerServlet.serverJVMVersion=JVM Version
+htmlManagerServlet.serverOSArch=OS Architektur
+htmlManagerServlet.serverOSName=OS Name
+htmlManagerServlet.serverOSVersion=OS Version
+htmlManagerServlet.serverTitle=Server Informationen
+htmlManagerServlet.serverVersion=Tomcat Version
+htmlManagerServlet.title=Tomcat Webanwendungs-Manager
+managerServlet.alreadyContext=FEHLER - Anwendung existiert bereits für Kontext Pfad {0}
+managerServlet.alreadyDocBase=FEHLER - Verzeichnis {0} bereits in Benutzung
+managerServlet.cannotInvoke=Kann Manager-Servlet nicht durch Invoker aufrufen
+managerServlet.configured=OK - Anwendung von Kontext-Datei {0} installiert
+managerServlet.deployed=OK - Anwendung mit Kontext Pfad {0} installiert
+managerServlet.exception=FEHLER - Ausnahme aufgetreten {0}
+managerServlet.deployed=OK - Anwendung mit Kontext Pfad {0} installiert
+managerServlet.invalidPath=FEHLER - Ungültiger Kontext Pfad {0} angegeben
+managerServlet.invalidWar=FEHLER - Ungültige URL {0} für Anwendung angegeben
+managerServlet.listed=OK - Auflistung der Webanwendungen für virtuellen Server {0}
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=FEHLER - Kann Verzeichnis für Kontext Pfad {0} nicht finden
+managerServlet.noCommand=FEHLER - Es wurde kein Kommando angegeben
+managerServlet.noContext=FEHLER - Es existiert kein Kontext für Pfad {0}
+managerServlet.noDirectory=FEHLER - Pfad {0} ist kein Verzeichnis
+managerServlet.noDocBase=FEHLER - Kann Webanwendungs-Verzeichnis nicht entfernen für Kontext Pfad {0}
+managerServlet.noGlobal=FEHLER - Keine globalen JNDI Ressourcen verfügbar
+managerServlet.noReload=FEHLER - Neu laden nicht unterstützt für WAR mit Pfad {0}
+managerServlet.noRename=FEHLER - Kann hochgeladenes WAR mit Pfad {0} nicht installieren
+managerServlet.noRole=FEHLER - Benutzer nicht in Rolle {0}
+managerServlet.noSelf=FEHLER - Manager-Kommandos können nicht auf die Manager-Anwendung selbst angewendet werden
+managerServlet.noWrapper=Container hat setWrapper() für dieses Servlet nicht aufgerufen
+managerServlet.reloaded=OK - Anwendung mit Kontext Pfad {0} neu geladen
+managerServlet.undeployd=OK - Anwendung mit Kontext Pfad {0} entfernt
+managerServlet.resourcesAll=OK - Auflistung globaler Ressourcen (alle Typen)
+managerServlet.resourcesType=OK - Auflistung globaler Ressourcen von Typ {0}
+managerServlet.rolesList=OK - Auflistung der Sicherheits-Rollen
+managerServlet.saveFail=FEHLER - Speichern der Konfiguration fehlgeschlagen: {0}
+managerServlet.sessiondefaultmax=Voreingestellter Sitzungsablauf nach maximal {0} Minuten Inaktivität
+managerServlet.sessiontimeout={0} Minuten: {1} Sitzungen
+managerServlet.sessions=OK - Sitzungs-Informationen für Anwendung mit Kontext Pfad {0}
+managerServlet.started=OK - Anwendung mit Kontext Pfad {0} gestartet
+managerServlet.startFailed=FEHLER - Anwendung mit Kontext Pfad {0} konnte nicht gestartet werden
+managerServlet.stopped=OK - Anwendung mit Kontext Pfad {0} gestoppt
+managerServlet.undeployed=OK - Anwendung mit Kontext Pfad {0} entfernt
+managerServlet.unknownCommand=FEHLER - Unbekanntes Kommando {0}
+managerServlet.userDatabaseError=FEHLER - Kann Referenz auf Benutzerdatendank nicht auflösen
+managerServlet.userDatabaseMissing=FEHLER - Keine Benutzerdatenbank vorhanden
+
+statusServlet.title=Server Status
+statusServlet.complete=Ausführlicher Server Status
diff --git a/java/org/apache/catalina/manager/LocalStrings_es.properties b/java/org/apache/catalina/manager/LocalStrings_es.properties
new file mode 100644 (file)
index 0000000..72a2c1b
--- /dev/null
@@ -0,0 +1,80 @@
+htmlManagerServlet.appsAvailable=Ejecutándose
+htmlManagerServlet.appsName=Nombre a Mostrar
+htmlManagerServlet.appsPath=Trayectoria
+htmlManagerServlet.appsReload=Recargar
+htmlManagerServlet.appsUndeploy=Replegar
+htmlManagerServlet.appsSessions=Sesiones
+htmlManagerServlet.appsStart=Arrancar
+htmlManagerServlet.appsStop=Parar
+htmlManagerServlet.appsTasks=Comandos
+htmlManagerServlet.appsTitle=Aplicaciones
+htmlManagerServlet.helpHtmlManager=Ayuda HTML de Gestor
+htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html
+htmlManagerServlet.helpManager=Ayuda de Gestor
+htmlManagerServlet.helpManagerFile=manager-howto.html
+htmlManagerServlet.deployButton=Desplegar
+htmlManagerServlet.deployConfig=URL de archivo de Configuración XML:
+htmlManagerServlet.deployPath=Trayectoria de Contexto (opcional):
+htmlManagerServlet.deployServer=Desplegar directorio o archivo WAR localizado en servidor
+htmlManagerServlet.deployTitle=Desplegar
+htmlManagerServlet.deployUpload=Archivo WAR a desplegar
+htmlManagerServlet.deployUploadFail=FALLO - Falló Carga de Despliegue, Excepción: {0}
+htmlManagerServlet.deployUploadFile=Seleccione archivo WAR a cargar
+htmlManagerServlet.deployUploadNotWar=FALLO - El archivo cargado \"{0}\" debe de ser un .war
+htmlManagerServlet.deployUploadNoFile=FALLO - Falló la carga de archivo, no hay archivo
+htmlManagerServlet.deployUploadWarExists=FALLO - El archivo war \"{0}\" ya existe en el servidor
+htmlManagerServlet.deployWar=URL de WAR o Directorio:
+htmlManagerServlet.list=Listar Aplicaciones
+htmlManagerServlet.manager=Gestor
+htmlManagerServlet.messageLabel=Mensaje:
+htmlManagerServlet.serverJVMVendor=Vendedor JVM
+htmlManagerServlet.serverJVMVersion=Versión JVM
+htmlManagerServlet.serverOSArch=Arquitectura de SO
+htmlManagerServlet.serverOSName=Nombre de SO
+htmlManagerServlet.serverOSVersion=Versión de SO
+htmlManagerServlet.serverTitle=Información de Servidor
+htmlManagerServlet.serverVersion=Versión de Tomcat
+htmlManagerServlet.title=Gestor de Aplicaciones Web de Tomcat
+managerServlet.alreadyContext=FALLO - Ya existe la aplicación en la trayectoria {0}
+managerServlet.alreadyDocBase=FALLO - Directorio {0} ya está siendo usado
+managerServlet.cannotInvoke=No puedo invocar servlet de gestor a través de invocador
+managerServlet.configured=OK - Desplegada aplicación desde archivo de contexto {0}
+managerServlet.deployed=OK - Desplegada aplicación en trayectoria de contexto {0}
+managerServlet.exception=FALLO - Encontrada excepción {0}
+managerServlet.deployed=OK - Desplegada aplicación en trayectoria de contexto {0}
+managerServlet.invalidPath=FALLO - Se ha especificado una trayectoria inválida de contexto {0}
+managerServlet.invalidWar=FALLO - Se ha especificado una URL de aplicación inválida {0}
+managerServlet.listed=OK - Aplicaciones listadas para máquinda virutal {0}
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=FALLO - No puedo identificar aplicación base para trayectoria de contexto {0}
+managerServlet.noCommand=FALLO - No se ha especificado comando
+managerServlet.noContext=FALLO - No existe contexto para trayectoria {0}
+managerServlet.noDirectory=FALLO - Documento base No-directorio para trayectoria {0}
+managerServlet.noDocBase=FALLO - No puedo replegar documento base para trayectoria {0}
+managerServlet.noGlobal=FALLO - No hay disponibles recursos globales JNDI 
+managerServlet.noReload=FALLO - Recarga no soportada en WAR desplegado en trayectoria {0}
+managerServlet.noRename=FALLO - No pudeo desplegar WAR cargado para trayectoria {0}
+managerServlet.noRole=FALLO - El usuario no desempeña el papel de {0}
+managerServlet.noSelf=FALLO - El gestor no puede recargarse, replegarse, pararse o replegarse a sí mismo
+managerServlet.noWrapper=El Contenedor no ha llamado a setWrapper() para este servlet
+managerServlet.reloaded=OK - Recargada aplicación en trayectoria de contexto {0}
+managerServlet.undeployd=OK - Replegada aplicación en trayectoria de contexto {0}
+managerServlet.resourcesAll=OK - Listados recursos globales de todos los tipos
+managerServlet.resourcesType=OK - Listados recursos globales de tipo {0}
+managerServlet.rolesList=OK - Listados papeles de seguridad
+managerServlet.saveFail=FAIL - Fallo al guardar la configuración: {0}
+managerServlet.saved=OK - Configuración de Servidor guardada
+managerServlet.savedContext=OK - Configuración de Contexto {0} guardada
+managerServlet.sessiondefaultmax=Intervalo máximo por defecto de sesión inactiva {0} minutos
+managerServlet.sessiontimeout={0} minutos: {1} sesiones
+managerServlet.sessions=OK - Información de sesión para aplicación en trayectoria de contexto {0}
+managerServlet.started=OK - Arrancada aplicación en trayectoria de contexto {0}
+managerServlet.startFailed=FALLO - No se pudo arrancar la aplicación en trayectoria de contexto {0}
+managerServlet.stopped=OK - Parada aplicación en trayectoria de contexto {0}
+managerServlet.undeployed=OK - Replegada aplicacación en trayectoria de contexto {0}
+managerServlet.unknownCommand=FALLO - Comando desconocido {0}
+managerServlet.userDatabaseError=FALLO - No puedo resolver referencia de base de datos de usuario
+managerServlet.userDatabaseMissing=FALLO - No se encuentra disponible base de datos de usuario
+
+statusServlet.title=Estado de Servidor
+statusServlet.complete=Estado Completo de Servidor
diff --git a/java/org/apache/catalina/manager/LocalStrings_fr.properties b/java/org/apache/catalina/manager/LocalStrings_fr.properties
new file mode 100644 (file)
index 0000000..4a2e1f9
--- /dev/null
@@ -0,0 +1,64 @@
+htmlManagerServlet.appsAvailable=Fonctionnant
+htmlManagerServlet.appsName=Nom d''affichage
+htmlManagerServlet.appsPath=Chemin
+htmlManagerServlet.appsReload=Recharger
+htmlManagerServlet.appsRemove=Retirer
+htmlManagerServlet.appsSessions=Sessions
+htmlManagerServlet.appsStart=Démarrer
+htmlManagerServlet.appsStop=Arréter
+htmlManagerServlet.appsTitle=Applications
+htmlManagerServlet.installButton=Installation
+htmlManagerServlet.installConfig=URL de configuration:
+htmlManagerServlet.installPath=Chemin:
+htmlManagerServlet.installTitle=Installation
+htmlManagerServlet.installWar=URL du WAR:
+htmlManagerServlet.messageLabel=Message:
+htmlManagerServlet.serverJVMVendor=Fournisseur de la JVM
+htmlManagerServlet.serverJVMVersion=Version de la JVM
+htmlManagerServlet.serverOSArch=Architecture d''OS
+htmlManagerServlet.serverOSName=Nom d''OS
+htmlManagerServlet.serverOSVersion=Version d''OS
+htmlManagerServlet.serverTitle=Serveur
+htmlManagerServlet.serverVersion=Version de serveur
+htmlManagerServlet.title=Gestionnaire d''applications WEB Tomcat
+managerServlet.alreadyContext=ECHEC - l''application existe déjà dans le chemin {0}
+managerServlet.alreadyDocBase=ECHEC - Le répertoire {0} est déjà utilisé
+managerServlet.cannotInvoke=Impossible d''utiliser le gestionnaire de servlet au travers du délégué (invoker)
+managerServlet.configured=OK - Application configurée depuis le fichier contexte {0}
+managerServlet.deployed=OK - Application déployée pour le chemin de contexte {0}
+managerServlet.exception=ECHEC - L''exception {0} a Ã©té rencontrée
+managerServlet.installed=OK - Application installée pour le chemin de contexte {0}
+managerServlet.invalidPath=ECHEC - Un chemin de contexte invalide {0} a Ã©té spécifié
+managerServlet.invalidWar=ECHEC - Une URL d''application invalide {0} a Ã©té spécifiée
+managerServlet.listed=OK - Applications listées pour l''hôte virtuel (virtual host) {0}
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=ECHEC - Impossible d''identifier la base de l''application base pour le chemin de context {0}
+managerServlet.noCommand=ECHEC - Aucune commande n''a Ã©té spécifiée
+managerServlet.noContext=ECHEC - Aucune contexte n''existe pour le chemin {0}
+managerServlet.noDirectory=ECHEC - La base de document n''est pas un répertoire pour le chemin {0}
+managerServlet.noDocBase=ECHEC - Impossible de retirer la base de document pour le chemin {0}
+managerServlet.noGlobal=ECHEC - Aucune ressource JNDI globale n''est disponible
+managerServlet.noReload=ECHEC - Rechargement non supporté par le WAR déployé au chemin {0}
+managerServlet.noRename=ECHEC - Impossible de déployer un WAR téléchargé pour le chemin {0}
+managerServlet.noRole=ECHEC - L''utilisateur ne possède pas le rôle {0}
+managerServlet.noSelf=ECHEC - Le gestionnaire ne peut recharger, retirer, arrêter, ou se déployer lui-même
+managerServlet.noWrapper=Le conteneur n''a pas appelé "setWrapper()" pour cette servlet
+managerServlet.reloaded=OK - Application rechargée au chemin de contexte {0}
+managerServlet.removed=OK - Application retirée au chemin de contexte {0}
+managerServlet.resourcesAll=OK - Liste des ressources globales de tout type
+managerServlet.resourcesType=OK - Liste des ressources globales de type {0}
+managerServlet.rolesList=OK - Liste de rôles de securité
+managerServlet.saveFail=ECHEC - La sauvegarde de la configuration a Ã©choué: {0}
+managerServlet.sessiondefaultmax=Interval par défaut de maximum de session inactive {0} minutes
+managerServlet.sessiontimeout={0} minutes:{1} sessions
+managerServlet.sessions=OK - Information de session pour l''application au chemin de contexte {0}
+managerServlet.started=OK - Application démarrée pour le chemin de contexte {0}
+managerServlet.startFailed=ECHEC - L''application pour le chemin de contexte {0} n''a pas puêtredémarrée
+managerServlet.stopped=OK - Application arrétée pour le chemin de contexte {0}
+managerServlet.undeployed=OK - Application non-déployée pour le chemin de contexte {0}
+managerServlet.unknownCommand=ECHEC - Commande inconnue {0}
+managerServlet.userDatabaseError=ECHEC - Impossible de résoudre la base de données utilisateurs deréférence
+managerServlet.userDatabaseMissing=ECHEC - Aucune base de données utilisateurs n''est disponible
+
+statusServlet.title=Etat du serveur
+statusServlet.complete=Etat complet du serveur
diff --git a/java/org/apache/catalina/manager/LocalStrings_ja.properties b/java/org/apache/catalina/manager/LocalStrings_ja.properties
new file mode 100644 (file)
index 0000000..ac307de
--- /dev/null
@@ -0,0 +1,79 @@
+htmlManagerServlet.appsAvailable=\u5b9f\u884c\u4e2d
+htmlManagerServlet.appsName=\u8868\u793a\u540d
+htmlManagerServlet.appsPath=\u30d1\u30b9
+htmlManagerServlet.appsReload=\u518d\u30ed\u30fc\u30c9
+htmlManagerServlet.appsUndeploy=\u914d\u5099\u89e3\u9664
+htmlManagerServlet.appsSessions=\u30bb\u30c3\u30b7\u30e7\u30f3
+htmlManagerServlet.appsStart=\u8d77\u52d5
+htmlManagerServlet.appsStop=\u505c\u6b62
+htmlManagerServlet.appsTasks=\u30b3\u30de\u30f3\u30c9
+htmlManagerServlet.appsTitle=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3
+htmlManagerServlet.helpHtmlManager=HTML\u30de\u30cd\u30fc\u30b8\u30e3\u30d8\u30eb\u30d7
+htmlManagerServlet.helpHtmlManagerFile=html-manager-howto.html
+htmlManagerServlet.helpManager=\u30de\u30cd\u30fc\u30b8\u30e3\u30d8\u30eb\u30d7
+htmlManagerServlet.helpManagerFile=manager-howto.html
+htmlManagerServlet.deployButton=\u914d\u5099
+htmlManagerServlet.deployConfig=XML\u8a2d\u5b9a\u30d5\u30a1\u30a4\u30eb\u306eURL:
+htmlManagerServlet.deployPath=\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 (\u7701\u7565\u53ef):
+htmlManagerServlet.deployServer=\u30b5\u30fc\u30d0\u4e0a\u306eWAR\u30d5\u30a1\u30a4\u30eb\u53c8\u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306e\u914d\u5099
+htmlManagerServlet.deployTitle=\u914d\u5099
+htmlManagerServlet.deployUpload=WAR\u30d5\u30a1\u30a4\u30eb\u306e\u914d\u5099
+htmlManagerServlet.deployUploadFail=FAIL - \u914d\u5099\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f\u3001\u4f8b\u5916: {0}
+htmlManagerServlet.deployUploadFile=\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308bWAR\u30d5\u30a1\u30a4\u30eb\u306e\u9078\u629e
+htmlManagerServlet.deployUploadNotWar=FAIL - \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3059\u308b\u30d5\u30a1\u30a4\u30eb \"{0}\" \u306fWAR\u30d5\u30a1\u30a4\u30eb\u3067\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093
+htmlManagerServlet.deployUploadNoFile=FAIL - \u30d5\u30a1\u30a4\u30eb\u306e\u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u304c\u5931\u6557\u3057\u307e\u3057\u305f\u3001\u30d5\u30a1\u30a4\u30eb\u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+htmlManagerServlet.deployUploadWarExists=FAIL - WAR\u30d5\u30a1\u30a4\u30eb \"{0}\" \u306f\u65e2\u306b\u30b5\u30fc\u30d0\u4e0a\u306b\u5b58\u5728\u3057\u307e\u3059
+htmlManagerServlet.deployWar=WAR\u30d5\u30a1\u30a4\u30eb\u53c8\u306f\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u306eURL:
+htmlManagerServlet.list=\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u4e00\u89a7
+htmlManagerServlet.manager=\u30de\u30cd\u30fc\u30b8\u30e3
+htmlManagerServlet.messageLabel=\u30e1\u30c3\u30bb\u30fc\u30b8
+htmlManagerServlet.serverJVMVendor=JVM\u30d9\u30f3\u30c0
+htmlManagerServlet.serverJVMVersion=JVM\u30d0\u30fc\u30b8\u30e7\u30f3
+htmlManagerServlet.serverOSArch=OS\u30a2\u30fc\u30ad\u30c6\u30af\u30c1\u30e3
+htmlManagerServlet.serverOSName=OS\u540d
+htmlManagerServlet.serverOSVersion=OS\u30d0\u30fc\u30b8\u30e7\u30f3
+htmlManagerServlet.serverTitle=\u30b5\u30fc\u30d0\u60c5\u5831
+htmlManagerServlet.serverVersion=Tomcat\u30d0\u30fc\u30b8\u30e7\u30f3
+htmlManagerServlet.title=Tomcat Web\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30de\u30cd\u30fc\u30b8\u30e3
+managerServlet.alreadyContext=FAIL - \u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306f\u3001\u65e2\u306b\u30d1\u30b9 {0} \u306b\u5b58\u5728\u3057\u307e\u3059
+managerServlet.alreadyDocBase=FAIL - \u30c7\u30a3\u30ec\u30af\u30c8\u30ea {0} \u306f\u65e2\u306b\u4f7f\u7528\u3055\u308c\u3066\u3044\u307e\u3059
+managerServlet.cannotInvoke=\u30a4\u30f3\u30dc\u30fc\u30ab\u3067\u30de\u30cd\u30fc\u30b8\u30e3\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u3092\u8d77\u52d5\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f
+managerServlet.configured=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d5\u30a1\u30a4\u30eb {0} \u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3057\u305f
+managerServlet.deployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u3057\u307e\u3057\u305f
+managerServlet.exception=FAIL - \u4f8b\u5916 {0} \u304c\u767a\u751f\u3057\u307e\u3057\u305f
+managerServlet.deployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306b\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb\u3057\u307e\u3057\u305f
+managerServlet.invalidPath=FAIL - \u7121\u52b9\u306a\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f
+managerServlet.invalidWar=FAIL - \u7121\u52b9\u306a\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306eURL {0} \u304c\u6307\u5b9a\u3055\u308c\u307e\u3057\u305f
+managerServlet.listed=OK - \u30d0\u30fc\u30c1\u30e3\u30eb\u30db\u30b9\u30c8 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u4e00\u89a7\u3067\u3059
+managerServlet.listitem={0}:{1}:{2}:{3}
+managerServlet.noAppBase=FAIL - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306b\u5bfe\u3057\u3066\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30d9\u30fc\u30b9\u3092\u78ba\u8a8d\u3067\u304d\u307e\u305b\u3093
+managerServlet.noCommand=FAIL - \u30b3\u30de\u30f3\u30c9\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+managerServlet.noContext=FAIL - \u30d1\u30b9 {0} \u306e\u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u304c\u5b58\u5728\u3057\u307e\u305b\u3093
+managerServlet.noDirectory=FAIL - \u30d1\u30b9 {0} \u306b\u5bfe\u3059\u308b\u30c7\u30a3\u30ec\u30af\u30c8\u30ea\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u304c\u3042\u308a\u307e\u305b\u3093
+managerServlet.noDocBase=FAIL - \u30d1\u30b9 {0} \u306b\u5bfe\u3059\u308b\u30c9\u30ad\u30e5\u30e1\u30f3\u30c8\u30d9\u30fc\u30b9\u3092\u524a\u9664\u3067\u304d\u307e\u305b\u3093
+managerServlet.noGlobal=FAIL - \u30b0\u30ed\u30fc\u30d0\u30eb\u306aJNDI\u30ea\u30bd\u30fc\u30b9\u304c\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+managerServlet.noReload=FAIL - \u30d1\u30b9 {0} \u306b\u914d\u5099\u3055\u308c\u305fWAR\u30d5\u30a1\u30a4\u30eb\u3067\u306f\u518d\u30ed\u30fc\u30c9\u304c\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093
+managerServlet.noRename=FAIL - \u30a2\u30c3\u30d7\u30ed\u30fc\u30c9\u3055\u308c\u305fWAR\u30d5\u30a1\u30a4\u30eb\u3092\u30d1\u30b9 {0} \u306b\u914d\u5099\u3067\u304d\u307e\u305b\u3093
+managerServlet.noRole=FAIL - \u30e6\u30fc\u30b6\u306f\u30ed\u30fc\u30eb {0} \u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093
+managerServlet.noSelf=FAIL - \u30de\u30cd\u30fc\u30b8\u30e3\u81ea\u8eab\u3092\u518d\u30ed\u30fc\u30c9\u3001\u524a\u9664\u3001\u505c\u6b62\u3001\u53c8\u306f\u914d\u5099\u89e3\u9664\u3067\u304d\u307e\u305b\u3093
+managerServlet.noWrapper=\u30b3\u30f3\u30c6\u30ca\u306f\u3053\u306e\u30b5\u30fc\u30d6\u30ec\u30c3\u30c8\u306b\u5bfe\u3057\u3066\u547c\u3073\u51fa\u3055\u308c\u305fsetWrapper()\u3092\u6301\u3063\u3066\u3044\u307e\u305b\u3093
+managerServlet.reloaded=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u518d\u30ed\u30fc\u30c9\u3057\u307e\u3057\u305f
+managerServlet.undeployd=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u304b\u3089\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u89e3\u9664\u3057\u307e\u3057\u305f
+managerServlet.resourcesAll=OK - \u3059\u3079\u3066\u306e\u30bf\u30a4\u30d7\u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30ea\u30bd\u30fc\u30b9\u3092\u5217\u6319\u3057\u307e\u3057\u305f
+managerServlet.resourcesType=OK - \u30bf\u30a4\u30d7 {0} \u306e\u30b0\u30ed\u30fc\u30d0\u30eb\u30ea\u30bd\u30fc\u30b9\u3092\u5217\u6319\u3057\u307e\u3057\u305f
+managerServlet.rolesList=OK - \u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30ed\u30fc\u30eb\u3092\u5217\u6319\u3057\u307e\u3057\u305f
+managerServlet.saveFail=FAIL - \u8a2d\u5b9a\u306e\u4fdd\u5b58\u306b\u5931\u6557\u3057\u307e\u3057\u305f: {0}
+managerServlet.saved=OK - \u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f
+managerServlet.savedContext=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8 {0} \u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f
+managerServlet.sessiondefaultmax=\u30c7\u30d5\u30a9\u30eb\u30c8\u306e\u6700\u5927\u30bb\u30c3\u30b7\u30e7\u30f3\u505c\u6b62\u9593\u9694\u306f{0}\u5206\u3067\u3059
+managerServlet.sessiontimeout={0}\u5206: {1}\u30bb\u30c3\u30b7\u30e7\u30f3
+managerServlet.sessions=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u306e\u30bb\u30c3\u30b7\u30e7\u30f3\u60c5\u5831\u3067\u3059
+managerServlet.started=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u8d77\u52d5\u3057\u307e\u3057\u305f
+managerServlet.startFailed=FAIL - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u304c\u8d77\u52d5\u3067\u304d\u307e\u305b\u3093
+managerServlet.stopped=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u505c\u6b62\u3057\u307e\u3057\u305f
+managerServlet.undeployed=OK - \u30b3\u30f3\u30c6\u30ad\u30b9\u30c8\u30d1\u30b9 {0} \u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u914d\u5099\u89e3\u9664\u3057\u307e\u3057\u305f
+managerServlet.unknownCommand=FAIL - \u672a\u77e5\u306e\u30b3\u30de\u30f3\u30c9 {0} \u3067\u3059
+managerServlet.userDatabaseError=FAIL - \u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u53c2\u7167\u3092\u89e3\u6c7a\u3067\u304d\u307e\u305b\u3093
+managerServlet.userDatabaseMissing=FAIL - \u30e6\u30fc\u30b6\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u304c\u5229\u7528\u3067\u304d\u307e\u305b\u3093
+statusServlet.title=\u30b5\u30fc\u30d0\u306e\u72b6\u614b
+statusServlet.complete=\u30b5\u30fc\u30d0\u306e\u5168\u72b6\u614b
diff --git a/java/org/apache/catalina/manager/ManagerServlet.java b/java/org/apache/catalina/manager/ManagerServlet.java
new file mode 100644 (file)
index 0000000..d71ed0c
--- /dev/null
@@ -0,0 +1,1582 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager;
+
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Iterator;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import javax.naming.Binding;
+import javax.naming.InitialContext;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerServlet;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.Role;
+import org.apache.catalina.Server;
+import org.apache.catalina.ServerFactory;
+import org.apache.catalina.Session;
+import org.apache.catalina.UserDatabase;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.core.StandardServer;
+import org.apache.catalina.util.RequestUtil;
+import org.apache.catalina.util.ServerInfo;
+import org.apache.catalina.util.StringManager;
+import org.apache.tomcat.util.modeler.Registry;
+
+
+/**
+ * Servlet that enables remote management of the web applications installed
+ * within the same virtual host as this web application is.  Normally, this
+ * functionality will be protected by a security constraint in the web
+ * application deployment descriptor.  However, this requirement can be
+ * relaxed during testing.
+ * <p>
+ * This servlet examines the value returned by <code>getPathInfo()</code>
+ * and related query parameters to determine what action is being requested.
+ * The following actions and parameters (starting after the servlet path)
+ * are supported:
+ * <ul>
+ * <li><b>/deploy?config={config-url}</b> - Install and start a new
+ *     web application, based on the contents of the context configuration
+ *     file found at the specified URL.  The <code>docBase</code> attribute
+ *     of the context configuration file is used to locate the actual
+ *     WAR or directory containing the application.</li>
+ * <li><b>/deploy?config={config-url}&war={war-url}/</b> - Install and start
+ *     a new web application, based on the contents of the context
+ *     configuration file found at <code>{config-url}</code>, overriding the
+ *     <code>docBase</code> attribute with the contents of the web
+ *     application archive found at <code>{war-url}</code>.</li>
+ * <li><b>/deploy?path=/xxx&war={war-url}</b> - Install and start a new
+ *     web application attached to context path <code>/xxx</code>, based
+ *     on the contents of the web application archive found at the
+ *     specified URL.</li>
+ * <li><b>/list</b> - List the context paths of all currently installed web
+ *     applications for this virtual host.  Each context will be listed with
+ *     the following format <code>path:status:sessions</code>.
+ *     Where path is the context path.  Status is either running or stopped.
+ *     Sessions is the number of active Sessions.</li>
+ * <li><b>/reload?path=/xxx</b> - Reload the Java classes and resources for
+ *     the application at the specified path.</li>
+ * <li><b>/resources?type=xxxx</b> - Enumerate the available global JNDI
+ *     resources, optionally limited to those of the specified type
+ *     (fully qualified Java class name), if available.</li>
+ * <li><b>/roles</b> - Enumerate the available security role names and
+ *     descriptions from the user database connected to the <code>users</code>
+ *     resource reference.
+ * <li><b>/serverinfo</b> - Display system OS and JVM properties.
+ * <li><b>/sessions?path=/xxx</b> - List session information about the web
+ *     application attached to context path <code>/xxx</code> for this
+ *     virtual host.</li>
+ * <li><b>/start?path=/xxx</b> - Start the web application attached to
+ *     context path <code>/xxx</code> for this virtual host.</li>
+ * <li><b>/stop?path=/xxx</b> - Stop the web application attached to
+ *     context path <code>/xxx</code> for this virtual host.</li>
+ * <li><b>/undeploy?path=/xxx</b> - Shutdown and remove the web application
+ *     attached to context path <code>/xxx</code> for this virtual host,
+ *     and remove the underlying WAR file or document base directory.
+ *     (<em>NOTE</em> - This is only allowed if the WAR file or document
+ *     base is stored in the <code>appBase</code> directory of this host,
+ *     typically as a result of being placed there via the <code>/deploy</code>
+ *     command.</li>
+ * </ul>
+ * <p>Use <code>path=/</code> for the ROOT context.</p>
+ * <p>The syntax of the URL for a web application archive must conform to one
+ * of the following patterns to be successfully deployed:</p>
+ * <ul>
+ * <li><b>file:/absolute/path/to/a/directory</b> - You can specify the absolute
+ *     path of a directory that contains the unpacked version of a web
+ *     application.  This directory will be attached to the context path you
+ *     specify without any changes.</li>
+ * <li><b>jar:file:/absolute/path/to/a/warfile.war!/</b> - You can specify a
+ *     URL to a local web application archive file.  The syntax must conform to
+ *     the rules specified by the <code>JarURLConnection</code> class for a
+ *     reference to an entire JAR file.</li>
+ * <li><b>jar:http://hostname:port/path/to/a/warfile.war!/</b> - You can specify
+ *     a URL to a remote (HTTP-accessible) web application archive file.  The
+ *     syntax must conform to the rules specified by the
+ *     <code>JarURLConnection</code> class for a reference to an entire
+ *     JAR file.</li>
+ * </ul>
+ * <p>
+ * <b>NOTE</b> - Attempting to reload or remove the application containing
+ * this servlet itself will not succeed.  Therefore, this servlet should
+ * generally be deployed as a separate web application within the virtual host
+ * to be managed.
+ * <p>
+ * <b>NOTE</b> - For security reasons, this application will not operate
+ * when accessed via the invoker servlet.  You must explicitly map this servlet
+ * with a servlet mapping, and you will always want to protect it with
+ * appropriate security constraints as well.
+ * <p>
+ * The following servlet initialization parameters are recognized:
+ * <ul>
+ * <li><b>debug</b> - The debugging detail level that controls the amount
+ *     of information that is logged by this servlet.  Default is zero.
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Revision: 393613 $ $Date: 2006-04-12 23:08:01 +0200 (mer., 12 avr. 2006) $
+ */
+
+public class ManagerServlet
+    extends HttpServlet implements ContainerServlet {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Path where context descriptors should be deployed.
+     */
+    protected File configBase = null;
+
+
+    /**
+     * The Context container associated with our web application.
+     */
+    protected Context context = null;
+
+
+    /**
+     * The debugging detail level for this servlet.
+     */
+    protected int debug = 1;
+
+
+    /**
+     * File object representing the directory into which the deploy() command
+     * will store the WAR and context configuration files that have been
+     * uploaded.
+     */
+    protected File deployed = null;
+
+
+    /**
+     * Path used to store revisions of webapps.
+     */
+    protected File versioned = null;
+
+
+    /**
+     * Path used to store context descriptors.
+     */
+    protected File contextDescriptors = null;
+
+
+    /**
+     * The associated host.
+     */
+    protected Host host = null;
+
+    
+    /**
+     * The host appBase.
+     */
+    protected File appBase = null;
+    
+    
+    /**
+     * MBean server.
+     */
+    protected MBeanServer mBeanServer = null;
+
+
+    /**
+     * The associated deployer ObjectName.
+     */
+    protected ObjectName oname = null;
+    
+
+    /**
+     * The global JNDI <code>NamingContext</code> for this server,
+     * if available.
+     */
+    protected javax.naming.Context global = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The Wrapper container associated with this servlet.
+     */
+    protected Wrapper wrapper = null;
+
+
+    // ----------------------------------------------- ContainerServlet Methods
+
+
+    /**
+     * Return the Wrapper with which we are associated.
+     */
+    public Wrapper getWrapper() {
+
+        return (this.wrapper);
+
+    }
+
+
+    /**
+     * Set the Wrapper with which we are associated.
+     *
+     * @param wrapper The new wrapper
+     */
+    public void setWrapper(Wrapper wrapper) {
+
+        this.wrapper = wrapper;
+        if (wrapper == null) {
+            context = null;
+            host = null;
+            oname = null;
+        } else {
+            context = (Context) wrapper.getParent();
+            host = (Host) context.getParent();
+            Engine engine = (Engine) host.getParent();
+            try {
+                oname = new ObjectName(engine.getName() 
+                        + ":type=Deployer,host=" + host.getName());
+            } catch (Exception e) {
+                // ?
+            }
+        }
+
+        // Retrieve the MBean server
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+        
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize this servlet.
+     */
+    public void destroy() {
+
+        ;       // No actions necessary
+
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Verify that we were not accessed using the invoker servlet
+        if (request.getAttribute(Globals.INVOKED_ATTR) != null)
+            throw new UnavailableException
+                (sm.getString("managerServlet.cannotInvoke"));
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+        if (command == null)
+            command = request.getServletPath();
+        String config = request.getParameter("config");
+        String path = request.getParameter("path");
+        String type = request.getParameter("type");
+        String war = request.getParameter("war");
+        String tag = request.getParameter("tag");
+        boolean update = false;
+        if ((request.getParameter("update") != null) 
+            && (request.getParameter("update").equals("true"))) {
+            update = true;
+        }
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/plain; charset=" + Constants.CHARSET);
+        PrintWriter writer = response.getWriter();
+
+        // Process the requested command (note - "/deploy" is not listed here)
+        if (command == null) {
+            writer.println(sm.getString("managerServlet.noCommand"));
+        } else if (command.equals("/deploy")) {
+            if (war != null || config != null) {
+                deploy(writer, config, path, war, update);
+            } else {
+                deploy(writer, path, tag);
+            }
+        } else if (command.equals("/install")) {
+            // Deprecated
+            deploy(writer, config, path, war, false);
+        } else if (command.equals("/list")) {
+            list(writer);
+        } else if (command.equals("/reload")) {
+            reload(writer, path);
+        } else if (command.equals("/remove")) {
+            // Deprecated
+            undeploy(writer, path);
+        } else if (command.equals("/resources")) {
+            resources(writer, type);
+        } else if (command.equals("/roles")) {
+            roles(writer);
+        } else if (command.equals("/save")) {
+            save(writer, path);
+        } else if (command.equals("/serverinfo")) {
+            serverinfo(writer);
+        } else if (command.equals("/sessions")) {
+            sessions(writer, path);
+        } else if (command.equals("/start")) {
+            start(writer, path);
+        } else if (command.equals("/stop")) {
+            stop(writer, path);
+        } else if (command.equals("/undeploy")) {
+            undeploy(writer, path);
+        } else {
+            writer.println(sm.getString("managerServlet.unknownCommand",
+                                        command));
+        }
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+
+    }
+
+
+    /**
+     * Process a PUT request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doPut(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Verify that we were not accessed using the invoker servlet
+        if (request.getAttribute(Globals.INVOKED_ATTR) != null)
+            throw new UnavailableException
+                (sm.getString("managerServlet.cannotInvoke"));
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+        if (command == null)
+            command = request.getServletPath();
+        String path = request.getParameter("path");
+        String tag = request.getParameter("tag");
+        boolean update = false;
+        if ((request.getParameter("update") != null) 
+            && (request.getParameter("update").equals("true"))) {
+            update = true;
+        }
+
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/plain;charset="+Constants.CHARSET);
+        PrintWriter writer = response.getWriter();
+
+        // Process the requested command
+        if (command == null) {
+            writer.println(sm.getString("managerServlet.noCommand"));
+        } else if (command.equals("/deploy")) {
+            deploy(writer, path, tag, update, request);
+        } else {
+            writer.println(sm.getString("managerServlet.unknownCommand",
+                                        command));
+        }
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+
+    }
+
+
+    /**
+     * Initialize this servlet.
+     */
+    public void init() throws ServletException {
+
+        // Ensure that our ContainerServlet properties have been set
+        if ((wrapper == null) || (context == null))
+            throw new UnavailableException
+                (sm.getString("managerServlet.noWrapper"));
+
+        // Verify that we were not accessed using the invoker servlet
+        String servletName = getServletConfig().getServletName();
+        if (servletName == null)
+            servletName = "";
+        if (servletName.startsWith("org.apache.catalina.INVOKER."))
+            throw new UnavailableException
+                (sm.getString("managerServlet.cannotInvoke"));
+
+        // Set our properties from the initialization parameters
+        String value = null;
+        try {
+            value = getServletConfig().getInitParameter("debug");
+            debug = Integer.parseInt(value);
+        } catch (Throwable t) {
+            ;
+        }
+
+        // Acquire global JNDI resources if available
+        Server server = ServerFactory.getServer();
+        if ((server != null) && (server instanceof StandardServer)) {
+            global = ((StandardServer) server).getGlobalNamingContext();
+        }
+
+        // Calculate the directory into which we will be deploying applications
+        versioned = (File) getServletContext().getAttribute
+            ("javax.servlet.context.tempdir");
+
+        // Identify the appBase of the owning Host of this Context
+        // (if any)
+        String appBase = ((Host) context.getParent()).getAppBase();
+        deployed = new File(appBase);
+        if (!deployed.isAbsolute()) {
+            deployed = new File(System.getProperty("catalina.base"),
+                                appBase);
+        }
+        configBase = new File(System.getProperty("catalina.base"), "conf");
+        Container container = context;
+        Container host = null;
+        Container engine = null;
+        while (container != null) {
+            if (container instanceof Host)
+                host = container;
+            if (container instanceof Engine)
+                engine = container;
+            container = container.getParent();
+        }
+        if (engine != null) {
+            configBase = new File(configBase, engine.getName());
+        }
+        if (host != null) {
+            configBase = new File(configBase, host.getName());
+        }
+        // Note: The directory must exist for this to work.
+
+        // Log debugging messages as necessary
+        if (debug >= 1) {
+            log("init: Associated with Deployer '" +
+                oname + "'");
+            if (global != null) {
+                log("init: Global resources are available");
+            }
+        }
+
+    }
+
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Store server configuration.
+     * 
+     * @param path Optional context path to save
+     */
+    protected synchronized void save(PrintWriter writer, String path) {
+
+        Server server = ServerFactory.getServer();
+
+        if (!(server instanceof StandardServer)) {
+            writer.println(sm.getString("managerServlet.saveFail", server));
+            return;
+        }
+
+        if ((path == null) || path.length() == 0 || !path.startsWith("/")) {
+            try {
+                ((StandardServer) server).storeConfig();
+                writer.println(sm.getString("managerServlet.saved"));
+            } catch (Exception e) {
+                log("managerServlet.storeConfig", e);
+                writer.println(sm.getString("managerServlet.exception",
+                                            e.toString()));
+                return;
+            }
+        } else {
+            String contextPath = path;
+            if (path.equals("/")) {
+                contextPath = "";
+            }
+            Context context = (Context) host.findChild(contextPath);
+            if (context == null) {
+                writer.println(sm.getString("managerServlet.noContext", path));
+                return;
+            }
+            try {
+                ((StandardServer) server).storeContext(context);
+                writer.println(sm.getString("managerServlet.savedContext", 
+                               path));
+            } catch (Exception e) {
+                log("managerServlet.save[" + path + "]", e);
+                writer.println(sm.getString("managerServlet.exception",
+                                            e.toString()));
+                return;
+            }
+        }
+
+    }
+
+
+    /**
+     * Deploy a web application archive (included in the current request)
+     * at the specified context path.
+     *
+     * @param writer Writer to render results to
+     * @param path Context path of the application to be installed
+     * @param tag Tag to be associated with the webapp
+     * @param request Servlet request we are processing
+     */
+    protected synchronized void deploy
+        (PrintWriter writer, String path,
+         String tag, boolean update, HttpServletRequest request) {
+
+        if (debug >= 1) {
+            log("deploy: Deploying web application at '" + path + "'");
+        }
+
+        // Validate the requested context path
+        if ((path == null) || path.length() == 0 || !path.startsWith("/")) {
+            writer.println(sm.getString("managerServlet.invalidPath", path));
+            return;
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+        String basename = getDocBase(path);
+
+        // Check if app already exists, or undeploy it if updating
+        Context context = (Context) host.findChild(path);
+        if (update) {
+            if (context != null) {
+                undeploy(writer, displayPath);
+            }
+            context = (Context) host.findChild(path);
+        }
+        if (context != null) {
+            writer.println
+                (sm.getString("managerServlet.alreadyContext",
+                              displayPath));
+            return;
+        }
+
+        // Calculate the base path
+        File deployedPath = deployed;
+        if (tag != null) {
+            deployedPath = new File(versioned, tag);
+            deployedPath.mkdirs();
+        }
+
+        // Upload the web application archive to a local WAR file
+        File localWar = new File(deployedPath, basename + ".war");
+        if (debug >= 2) {
+            log("Uploading WAR file to " + localWar);
+        }
+
+        // Copy WAR to appBase
+        try {
+            if (!isServiced(path)) {
+                addServiced(path);
+                try {
+                    // Upload WAR
+                    uploadWar(request, localWar);
+                    // Copy WAR and XML to the host app base if needed
+                    if (tag != null) {
+                        deployedPath = deployed;
+                        File localWarCopy = new File(deployedPath, basename + ".war");
+                        copy(localWar, localWarCopy);
+                        localWar = localWarCopy;
+                        copy(localWar, new File(getAppBase(), basename + ".war"));
+                    }
+                    // Perform new deployment
+                    check(path);
+                } finally {
+                    removeServiced(path);
+                }
+            }
+        } catch (Exception e) {
+            log("managerServlet.check[" + displayPath + "]", e);
+            writer.println(sm.getString("managerServlet.exception",
+                                        e.toString()));
+            return;
+        }
+        
+        context = (Context) host.findChild(path);
+        if (context != null && context.getConfigured()) {
+            writer.println(sm.getString("managerServlet.deployed", displayPath));
+        } else {
+            // Something failed
+            writer.println(sm.getString("managerServlet.deployFailed", displayPath));
+        }
+        
+    }
+
+
+    /**
+     * Install an application for the specified path from the specified
+     * web application archive.
+     *
+     * @param writer Writer to render results to
+     * @param tag Revision tag to deploy from
+     * @param path Context path of the application to be installed
+     */
+    protected void deploy(PrintWriter writer, String path, String tag) {
+
+        // Validate the requested context path
+        if ((path == null) || path.length() == 0 || !path.startsWith("/")) {
+            writer.println(sm.getString("managerServlet.invalidPath", path));
+            return;
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+
+        // Calculate the base path
+        File deployedPath = versioned;
+        if (tag != null) {
+            deployedPath = new File(deployedPath, tag);
+        }
+
+        // Find the local WAR file
+        File localWar = new File(deployedPath, getDocBase(path) + ".war");
+        // Find the local context deployment file (if any)
+        File localXml = new File(configBase, getConfigFile(path) + ".xml");
+
+        // Check if app already exists, or undeploy it if updating
+        Context context = (Context) host.findChild(path);
+        if (context != null) {
+            undeploy(writer, displayPath);
+        }
+
+        // Copy WAR to appBase
+        try {
+            if (!isServiced(path)) {
+                addServiced(path);
+                try {
+                    copy(localWar, new File(getAppBase(), getDocBase(path) + ".war"));
+                    // Perform new deployment
+                    check(path);
+                } finally {
+                    removeServiced(path);
+                }
+            }
+        } catch (Exception e) {
+            log("managerServlet.check[" + displayPath + "]", e);
+            writer.println(sm.getString("managerServlet.exception",
+                                        e.toString()));
+            return;
+        }
+        
+        context = (Context) host.findChild(path);
+        if (context != null && context.getConfigured()) {
+            writer.println(sm.getString("managerServlet.deployed", displayPath));
+        } else {
+            // Something failed
+            writer.println(sm.getString("managerServlet.deployFailed", displayPath));
+        }
+        
+    }
+
+
+    /**
+     * Install an application for the specified path from the specified
+     * web application archive.
+     *
+     * @param writer Writer to render results to
+     * @param config URL of the context configuration file to be installed
+     * @param path Context path of the application to be installed
+     * @param war URL of the web application archive to be installed
+     * @param update true to override any existing webapp on the path
+     */
+    protected void deploy(PrintWriter writer, String config,
+            String path, String war, boolean update) {
+        
+        if (config != null && config.length() == 0) {
+            config = null;
+        }
+        if (war != null && war.length() == 0) {
+            war = null;
+        }
+        
+        if (debug >= 1) {
+            if (config != null && config.length() > 0) {
+                if (war != null) {
+                    log("install: Installing context configuration at '" +
+                            config + "' from '" + war + "'");
+                } else {
+                    log("install: Installing context configuration at '" +
+                            config + "'");
+                }
+            } else {
+                if (path != null && path.length() > 0) {
+                    log("install: Installing web application at '" + path +
+                            "' from '" + war + "'");
+                } else {
+                    log("install: Installing web application from '" + war + "'");
+                }
+            }
+        }
+        
+        if (path == null || path.length() == 0 || !path.startsWith("/")) {
+            writer.println(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+            return;
+        }
+        String displayPath = path;
+        if("/".equals(path)) {
+            path = "";
+        }
+        
+        // Check if app already exists, or undeploy it if updating
+        Context context = (Context) host.findChild(path);
+        if (update) {
+            if (context != null) {
+                undeploy(writer, displayPath);
+            }
+            context = (Context) host.findChild(path);
+        }
+        if (context != null) {
+            writer.println
+            (sm.getString("managerServlet.alreadyContext",
+                    displayPath));
+            return;
+        }
+        
+        if (config != null && (config.startsWith("file:"))) {
+            config = config.substring("file:".length());
+        }
+        if (war != null && (war.startsWith("file:"))) {
+            war = war.substring("file:".length());
+        }
+        
+        try {
+            if (!isServiced(path)) {
+                addServiced(path);
+                try {
+                    if (config != null) {
+                        copy(new File(config), 
+                                new File(configBase, getConfigFile(path) + ".xml"));
+                    }
+                    if (war != null) {
+                        if (war.endsWith(".war")) {
+                            copy(new File(war), 
+                                    new File(getAppBase(), getDocBase(path) + ".war"));
+                        } else {
+                            copy(new File(war), 
+                                    new File(getAppBase(), getDocBase(path)));
+                        }
+                    }
+                    // Perform new deployment
+                    check(path);
+                } finally {
+                    removeServiced(path);
+                }
+            }
+            context = (Context) host.findChild(path);
+            if (context != null && context.getConfigured()) {
+                writer.println(sm.getString("managerServlet.deployed", displayPath));
+            } else {
+                // Something failed
+                writer.println(sm.getString("managerServlet.deployFailed", displayPath));
+            }
+        } catch (Throwable t) {
+            log("ManagerServlet.install[" + displayPath + "]", t);
+            writer.println(sm.getString("managerServlet.exception",
+                    t.toString()));
+        }
+        
+    }
+
+
+    /**
+     * Render a list of the currently active Contexts in our virtual host.
+     *
+     * @param writer Writer to render to
+     */
+    protected void list(PrintWriter writer) {
+
+        if (debug >= 1)
+            log("list: Listing contexts for virtual host '" +
+                host.getName() + "'");
+
+        writer.println(sm.getString("managerServlet.listed",
+                                    host.getName()));
+        Container[] contexts = host.findChildren();
+        for (int i = 0; i < contexts.length; i++) {
+            Context context = (Context) contexts[i];
+            String displayPath = context.getPath();
+            if( displayPath.equals("") )
+                displayPath = "/";
+            if (context != null ) {
+                if (context.getAvailable()) {
+                    writer.println(sm.getString("managerServlet.listitem",
+                                                displayPath,
+                                                "running",
+                                      "" + context.getManager().findSessions().length,
+                                                context.getDocBase()));
+                } else {
+                    writer.println(sm.getString("managerServlet.listitem",
+                                                displayPath,
+                                                "stopped",
+                                                "0",
+                                                context.getDocBase()));
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Reload the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param path Context path of the application to be restarted
+     */
+    protected void reload(PrintWriter writer, String path) {
+
+        if (debug >= 1)
+            log("restart: Reloading web application at '" + path + "'");
+
+        if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
+            writer.println(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+            return;
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+
+        try {
+            Context context = (Context) host.findChild(path);
+            if (context == null) {
+                writer.println(sm.getString
+                               ("managerServlet.noContext",
+                                   RequestUtil.filter(displayPath)));
+                return;
+            }
+            // It isn't possible for the manager to reload itself
+            if (context.getPath().equals(this.context.getPath())) {
+                writer.println(sm.getString("managerServlet.noSelf"));
+                return;
+            }
+            context.reload();
+            writer.println
+                (sm.getString("managerServlet.reloaded", displayPath));
+        } catch (Throwable t) {
+            log("ManagerServlet.reload[" + displayPath + "]", t);
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Render a list of available global JNDI resources.
+     *
+     * @param type Fully qualified class name of the resource type of interest,
+     *  or <code>null</code> to list resources of all types
+     */
+    protected void resources(PrintWriter writer, String type) {
+
+        if (debug >= 1) {
+            if (type != null) {
+                log("resources:  Listing resources of type " + type);
+            } else {
+                log("resources:  Listing resources of all types");
+            }
+        }
+
+        // Is the global JNDI resources context available?
+        if (global == null) {
+            writer.println(sm.getString("managerServlet.noGlobal"));
+            return;
+        }
+
+        // Enumerate the global JNDI resources of the requested type
+        if (type != null) {
+            writer.println(sm.getString("managerServlet.resourcesType",
+                                        type));
+        } else {
+            writer.println(sm.getString("managerServlet.resourcesAll"));
+        }
+
+        Class clazz = null;
+        try {
+            if (type != null) {
+                clazz = Class.forName(type);
+            }
+        } catch (Throwable t) {
+            log("ManagerServlet.resources[" + type + "]", t);
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+            return;
+        }
+
+        printResources(writer, "", global, type, clazz);
+
+    }
+
+
+    /**
+     * List the resources of the given context.
+     */
+    protected void printResources(PrintWriter writer, String prefix,
+                                  javax.naming.Context namingContext,
+                                  String type, Class clazz) {
+
+        try {
+            NamingEnumeration items = namingContext.listBindings("");
+            while (items.hasMore()) {
+                Binding item = (Binding) items.next();
+                if (item.getObject() instanceof javax.naming.Context) {
+                    printResources
+                        (writer, prefix + item.getName() + "/",
+                         (javax.naming.Context) item.getObject(), type, clazz);
+                } else {
+                    if ((clazz != null) &&
+                        (!(clazz.isInstance(item.getObject())))) {
+                        continue;
+                    }
+                    writer.print(prefix + item.getName());
+                    writer.print(':');
+                    writer.print(item.getClassName());
+                    // Do we want a description if available?
+                    writer.println();
+                }
+            }
+        } catch (Throwable t) {
+            log("ManagerServlet.resources[" + type + "]", t);
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Render a list of security role names (and corresponding descriptions)
+     * from the <code>org.apache.catalina.UserDatabase</code> resource that is
+     * connected to the <code>users</code> resource reference.  Typically, this
+     * will be the global user database, but can be adjusted if you have
+     * different user databases for different virtual hosts.
+     *
+     * @param writer Writer to render to
+     */
+    protected void roles(PrintWriter writer) {
+
+        if (debug >= 1) {
+            log("roles:  List security roles from user database");
+        }
+
+        // Look up the UserDatabase instance we should use
+        UserDatabase database = null;
+        try {
+            InitialContext ic = new InitialContext();
+            database = (UserDatabase) ic.lookup("java:comp/env/users");
+        } catch (NamingException e) {
+            writer.println(sm.getString("managerServlet.userDatabaseError"));
+            log("java:comp/env/users", e);
+            return;
+        }
+        if (database == null) {
+            writer.println(sm.getString("managerServlet.userDatabaseMissing"));
+            return;
+        }
+
+        // Enumerate the available roles
+        writer.println(sm.getString("managerServlet.rolesList"));
+        Iterator roles = database.getRoles();
+        if (roles != null) {
+            while (roles.hasNext()) {
+                Role role = (Role) roles.next();
+                writer.print(role.getRolename());
+                writer.print(':');
+                if (role.getDescription() != null) {
+                    writer.print(role.getDescription());
+                }
+                writer.println();
+            }
+        }
+
+
+    }
+
+
+    /**
+     * Writes System OS and JVM properties.
+     * @param writer Writer to render to
+     */
+    protected void serverinfo(PrintWriter writer) {
+        if (debug >= 1)
+            log("serverinfo");
+        try {
+            StringBuffer props = new StringBuffer();
+            props.append("OK - Server info");
+            props.append("\nTomcat Version: ");
+            props.append(ServerInfo.getServerInfo());
+            props.append("\nOS Name: ");
+            props.append(System.getProperty("os.name"));
+            props.append("\nOS Version: ");
+            props.append(System.getProperty("os.version"));
+            props.append("\nOS Architecture: ");
+            props.append(System.getProperty("os.arch"));
+            props.append("\nJVM Version: ");
+            props.append(System.getProperty("java.runtime.version"));
+            props.append("\nJVM Vendor: ");
+            props.append(System.getProperty("java.vm.vendor"));
+            writer.println(props.toString());
+        } catch (Throwable t) {
+            getServletContext().log("ManagerServlet.serverinfo",t);
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+        }
+    }
+
+    /**
+     * Session information for the web application at the specified context path.
+     * Displays a profile of session MaxInactiveInterval timeouts listing number
+     * of sessions for each 10 minute timeout interval up to 10 hours.
+     *
+     * @param writer Writer to render to
+     * @param path Context path of the application to list session information for
+     */
+    protected void sessions(PrintWriter writer, String path) {
+
+        if (debug >= 1)
+            log("sessions: Session information for web application at '" + path + "'");
+
+        if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
+            writer.println(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+            return;
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+        try {
+            Context context = (Context) host.findChild(path);
+            if (context == null) {
+                writer.println(sm.getString("managerServlet.noContext",
+                                            RequestUtil.filter(displayPath)));
+                return;
+            }
+            writer.println(sm.getString("managerServlet.sessions", displayPath));
+            writer.println(sm.getString("managerServlet.sessiondefaultmax",
+                                "" + context.getManager().getMaxInactiveInterval()/60));
+            Session [] sessions = context.getManager().findSessions();
+            int [] timeout = new int[60];
+            int notimeout = 0;
+            for (int i = 0; i < sessions.length; i++) {
+                int time = sessions[i].getMaxInactiveInterval()/(10*60);
+                if (time < 0)
+                    notimeout++;
+                else if (time >= timeout.length)
+                    timeout[timeout.length-1]++;
+                else
+                    timeout[time]++;
+            }
+            if (timeout[0] > 0)
+                writer.println(sm.getString("managerServlet.sessiontimeout",
+                                            "<10", "" + timeout[0]));
+            for (int i = 1; i < timeout.length-1; i++) {
+                if (timeout[i] > 0)
+                    writer.println(sm.getString("managerServlet.sessiontimeout",
+                                     "" + (i)*10 + " - <" + (i+1)*10,
+                                                "" + timeout[i]));
+            }
+            if (timeout[timeout.length-1] > 0)
+                writer.println(sm.getString("managerServlet.sessiontimeout",
+                                            ">=" + timeout.length*10,
+                                            "" + timeout[timeout.length-1]));
+            if (notimeout > 0)
+                writer.println(sm.getString("managerServlet.sessiontimeout",
+                                            "unlimited","" + notimeout));
+        } catch (Throwable t) {
+            log("ManagerServlet.sessions[" + displayPath + "]", t);
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Start the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param path Context path of the application to be started
+     */
+    protected void start(PrintWriter writer, String path) {
+
+        if (debug >= 1)
+            log("start: Starting web application at '" + path + "'");
+
+        if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
+            writer.println(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+            return;
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+
+        try {
+            Context context = (Context) host.findChild(path);
+            if (context == null) {
+                writer.println(sm.getString("managerServlet.noContext", 
+                                            RequestUtil.filter(displayPath)));
+                return;
+            }
+            ((Lifecycle) context).start();
+            if (context.getAvailable())
+                writer.println
+                    (sm.getString("managerServlet.started", displayPath));
+            else
+                writer.println
+                    (sm.getString("managerServlet.startFailed", displayPath));
+        } catch (Throwable t) {
+            getServletContext().log
+                (sm.getString("managerServlet.startFailed", displayPath), t);
+            writer.println
+                (sm.getString("managerServlet.startFailed", displayPath));
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Stop the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param path Context path of the application to be stopped
+     */
+    protected void stop(PrintWriter writer, String path) {
+
+        if (debug >= 1)
+            log("stop: Stopping web application at '" + path + "'");
+
+        if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
+            writer.println(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+            return;
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+
+        try {
+            Context context = (Context) host.findChild(path);
+            if (context == null) {
+                writer.println(sm.getString("managerServlet.noContext", 
+                                            RequestUtil.filter(displayPath)));
+                return;
+            }
+            // It isn't possible for the manager to stop itself
+            if (context.getPath().equals(this.context.getPath())) {
+                writer.println(sm.getString("managerServlet.noSelf"));
+                return;
+            }
+            ((Lifecycle) context).stop();
+            writer.println(sm.getString("managerServlet.stopped", displayPath));
+        } catch (Throwable t) {
+            log("ManagerServlet.stop[" + displayPath + "]", t);
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+        }
+
+    }
+
+
+    /**
+     * Undeploy the web application at the specified context path.
+     *
+     * @param writer Writer to render to
+     * @param path Context path of the application to be removed
+     */
+    protected void undeploy(PrintWriter writer, String path) {
+
+        if (debug >= 1)
+            log("undeploy: Undeploying web application at '" + path + "'");
+
+        if ((path == null) || (!path.startsWith("/") && path.equals(""))) {
+            writer.println(sm.getString("managerServlet.invalidPath",
+                                        RequestUtil.filter(path)));
+            return;
+        }
+        String displayPath = path;
+        if( path.equals("/") )
+            path = "";
+
+        try {
+
+            // Validate the Context of the specified application
+            Context context = (Context) host.findChild(path);
+            if (context == null) {
+                writer.println(sm.getString("managerServlet.noContext",
+                                            RequestUtil.filter(displayPath)));
+                return;
+            }
+
+            // Identify the appBase of the owning Host of this Context (if any)
+            String appBase = null;
+            File appBaseDir = null;
+            if (context.getParent() instanceof Host) {
+                appBase = ((Host) context.getParent()).getAppBase();
+                appBaseDir = new File(appBase);
+                if (!appBaseDir.isAbsolute()) {
+                    appBaseDir = new File(System.getProperty("catalina.base"),
+                                          appBase);
+                }
+            }
+
+            if (!isServiced(path)) {
+                addServiced(path);
+                try {
+                    // Try to stop the context first to be nicer
+                    ((Lifecycle) context).stop();
+                } catch (Throwable t) {
+                    // Ignore
+                }
+                try {
+                    File war = new File(getAppBase(), getDocBase(path) + ".war");
+                    File dir = new File(getAppBase(), getDocBase(path));
+                    File xml = new File(configBase, getConfigFile(path) + ".xml");
+                    if (war.exists()) {
+                        war.delete();
+                    } else if (dir.exists()) {
+                        undeployDir(dir);
+                    } else {
+                        xml.delete();
+                    }
+                    // Perform new deployment
+                    check(path);
+                } finally {
+                    removeServiced(path);
+                }
+            }
+            writer.println(sm.getString("managerServlet.undeployed",
+                                        displayPath));
+        } catch (Throwable t) {
+            log("ManagerServlet.undeploy[" + displayPath + "]", t);
+            writer.println(sm.getString("managerServlet.exception",
+                                        t.toString()));
+        }
+
+    }
+
+
+    // -------------------------------------------------------- Support Methods
+
+
+    /**
+     * Given a context path, get the config file name.
+     */
+    protected String getConfigFile(String path) {
+        String basename = null;
+        if (path.equals("")) {
+            basename = "ROOT";
+        } else {
+            basename = path.substring(1).replace('/', '#');
+        }
+        return (basename);
+    }
+
+
+    /**
+     * Given a context path, get the config file name.
+     */
+    protected String getDocBase(String path) {
+        String basename = null;
+        if (path.equals("")) {
+            basename = "ROOT";
+        } else {
+            basename = path.substring(1);
+        }
+        return (basename);
+    }
+
+    
+    /**
+     * Return a File object representing the "application root" directory
+     * for our associated Host.
+     */
+    protected File getAppBase() {
+
+        if (appBase != null) {
+            return appBase;
+        }
+
+        File file = new File(host.getAppBase());
+        if (!file.isAbsolute())
+            file = new File(System.getProperty("catalina.base"),
+                            host.getAppBase());
+        try {
+            appBase = file.getCanonicalFile();
+        } catch (IOException e) {
+            appBase = file;
+        }
+        return (appBase);
+
+    }
+
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected void check(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "check", params, signature);
+    }
+    
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected boolean isServiced(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        Boolean result = 
+            (Boolean) mBeanServer.invoke(oname, "isServiced", params, signature);
+        return result.booleanValue();
+    }
+    
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected void addServiced(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "addServiced", params, signature);
+    }
+    
+
+    /**
+     * Invoke the check method on the deployer.
+     */
+    protected void removeServiced(String name) 
+        throws Exception {
+        String[] params = { name };
+        String[] signature = { "java.lang.String" };
+        mBeanServer.invoke(oname, "removeServiced", params, signature);
+    }
+    
+
+    /**
+     * Delete the specified directory, including all of its contents and
+     * subdirectories recursively.
+     *
+     * @param dir File object representing the directory to be deleted
+     */
+    protected void undeployDir(File dir) {
+
+        String files[] = dir.list();
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; i < files.length; i++) {
+            File file = new File(dir, files[i]);
+            if (file.isDirectory()) {
+                undeployDir(file);
+            } else {
+                file.delete();
+            }
+        }
+        dir.delete();
+
+    }
+
+
+    /**
+     * Upload the WAR file included in this request, and store it at the
+     * specified file location.
+     *
+     * @param request The servlet request we are processing
+     * @param war The file into which we should store the uploaded WAR
+     *
+     * @exception IOException if an I/O error occurs during processing
+     */
+    protected void uploadWar(HttpServletRequest request, File war)
+        throws IOException {
+
+        war.delete();
+        ServletInputStream istream = null;
+        BufferedOutputStream ostream = null;
+        try {
+            istream = request.getInputStream();
+            ostream =
+                new BufferedOutputStream(new FileOutputStream(war), 1024);
+            byte buffer[] = new byte[1024];
+            while (true) {
+                int n = istream.read(buffer);
+                if (n < 0) {
+                    break;
+                }
+                ostream.write(buffer, 0, n);
+            }
+            ostream.flush();
+            ostream.close();
+            ostream = null;
+            istream.close();
+            istream = null;
+        } catch (IOException e) {
+            war.delete();
+            throw e;
+        } finally {
+            if (ostream != null) {
+                try {
+                    ostream.close();
+                } catch (Throwable t) {
+                    ;
+                }
+                ostream = null;
+            }
+            if (istream != null) {
+                try {
+                    istream.close();
+                } catch (Throwable t) {
+                    ;
+                }
+                istream = null;
+            }
+        }
+
+    }
+
+
+    /**
+     * Copy the specified file or directory to the destination.
+     *
+     * @param src File object representing the source
+     * @param dest File object representing the destination
+     */
+    public static boolean copy(File src, File dest) {
+        boolean result = false;
+        try {
+            if( src != null &&
+                    !src.getCanonicalPath().equals(dest.getCanonicalPath()) ) {
+                result = copyInternal(src, dest, new byte[4096]);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return result;
+    }
+
+    
+    /**
+     * Copy the specified file or directory to the destination.
+     *
+     * @param src File object representing the source
+     * @param dest File object representing the destination
+     */
+    public static boolean copyInternal(File src, File dest, byte[] buf) {
+        
+        boolean result = true;
+        
+        String files[] = null;
+        if (src.isDirectory()) {
+            files = src.list();
+            result = dest.mkdir();
+        } else {
+            files = new String[1];
+            files[0] = "";
+        }
+        if (files == null) {
+            files = new String[0];
+        }
+        for (int i = 0; (i < files.length) && result; i++) {
+            File fileSrc = new File(src, files[i]);
+            File fileDest = new File(dest, files[i]);
+            if (fileSrc.isDirectory()) {
+                result = copyInternal(fileSrc, fileDest, buf);
+            } else {
+                FileInputStream is = null;
+                FileOutputStream os = null;
+                try {
+                    is = new FileInputStream(fileSrc);
+                    os = new FileOutputStream(fileDest);
+                    int len = 0;
+                    while (true) {
+                        len = is.read(buf);
+                        if (len == -1)
+                            break;
+                        os.write(buf, 0, len);
+                    }
+                } catch (IOException e) {
+                    e.printStackTrace();
+                    result = false;
+                } finally {
+                    if (is != null) {
+                        try {
+                            is.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                    if (os != null) {
+                        try {
+                            os.close();
+                        } catch (IOException e) {
+                        }
+                    }
+                }
+            }
+        }
+        return result;
+        
+    }
+    
+    
+}
diff --git a/java/org/apache/catalina/manager/StatusManagerServlet.java b/java/org/apache/catalina/manager/StatusManagerServlet.java
new file mode 100644 (file)
index 0000000..abb9050
--- /dev/null
@@ -0,0 +1,358 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager;
+
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerNotification;
+import javax.management.Notification;
+import javax.management.NotificationListener;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.util.ServerInfo;
+import org.apache.catalina.util.StringManager;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * This servlet will display a complete status of the HTTP/1.1 connector.
+ *
+ * @author Remy Maucherat
+ * @version $Revision: 303870 $ $Date: 2005-04-19 00:50:24 +0200 (mar., 19 avr. 2005) $
+ */
+
+public class StatusManagerServlet
+    extends HttpServlet implements NotificationListener {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The debugging detail level for this servlet.
+     */
+    private int debug = 0;
+
+
+    /**
+     * MBean server.
+     */
+    protected MBeanServer mBeanServer = null;
+
+
+    /**
+     * Vector of protocol handlers object names.
+     */
+    protected Vector protocolHandlers = new Vector();
+
+
+    /**
+     * Vector of thread pools object names.
+     */
+    protected Vector threadPools = new Vector();
+
+
+    /**
+     * Vector of request processors object names.
+     */
+    protected Vector requestProcessors = new Vector();
+
+
+    /**
+     * Vector of global request processors object names.
+     */
+    protected Vector globalRequestProcessors = new Vector();
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Initialize this servlet.
+     */
+    public void init() throws ServletException {
+
+        // Retrieve the MBean server
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+
+        // Set our properties from the initialization parameters
+        String value = null;
+        try {
+            value = getServletConfig().getInitParameter("debug");
+            debug = Integer.parseInt(value);
+        } catch (Throwable t) {
+            ;
+        }
+
+        try {
+
+            // Query protocol handlers
+            String onStr = "*:type=ProtocolHandler,*";
+            ObjectName objectName = new ObjectName(onStr);
+            Set set = mBeanServer.queryMBeans(objectName, null);
+            Iterator iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = (ObjectInstance) iterator.next();
+                protocolHandlers.addElement(oi.getObjectName());
+            }
+
+            // Query Thread Pools
+            onStr = "*:type=ThreadPool,*";
+            objectName = new ObjectName(onStr);
+            set = mBeanServer.queryMBeans(objectName, null);
+            iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = (ObjectInstance) iterator.next();
+                threadPools.addElement(oi.getObjectName());
+            }
+
+            // Query Global Request Processors
+            onStr = "*:type=GlobalRequestProcessor,*";
+            objectName = new ObjectName(onStr);
+            set = mBeanServer.queryMBeans(objectName, null);
+            iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = (ObjectInstance) iterator.next();
+                globalRequestProcessors.addElement(oi.getObjectName());
+            }
+
+            // Query Request Processors
+            onStr = "*:type=RequestProcessor,*";
+            objectName = new ObjectName(onStr);
+            set = mBeanServer.queryMBeans(objectName, null);
+            iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = (ObjectInstance) iterator.next();
+                requestProcessors.addElement(oi.getObjectName());
+            }
+
+            // Register with MBean server
+            onStr = "JMImplementation:type=MBeanServerDelegate";
+            objectName = new ObjectName(onStr);
+            mBeanServer.addNotificationListener(objectName, this, null, null);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+    }
+
+
+    /**
+     * Finalize this servlet.
+     */
+    public void destroy() {
+
+        ;       // No actions necessary
+
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // mode is flag for HTML or XML output
+        int mode = 0;
+        // if ?XML=true, set the mode to XML
+        if (request.getParameter("XML") != null 
+            && request.getParameter("XML").equals("true")) {
+            mode = 1;
+        }
+        StatusTransformer.setContentType(response, mode);
+
+        PrintWriter writer = response.getWriter();
+
+        boolean completeStatus = false;
+        if ((request.getPathInfo() != null) 
+            && (request.getPathInfo().equals("/all"))) {
+            completeStatus = true;
+        }
+        // use StatusTransformer to output status
+        StatusTransformer.writeHeader(writer,mode);
+
+        // Body Header Section
+        Object[] args = new Object[2];
+        args[0] = request.getContextPath();
+        if (completeStatus) {
+            args[1] = sm.getString("statusServlet.complete");
+        } else {
+            args[1] = sm.getString("statusServlet.title");
+        }
+        // use StatusTransformer to output status
+        StatusTransformer.writeBody(writer,args,mode);
+
+        // Manager Section
+        args = new Object[9];
+        args[0] = sm.getString("htmlManagerServlet.manager");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/list");
+        args[2] = sm.getString("htmlManagerServlet.list");
+        args[3] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlManagerServlet.helpHtmlManagerFile"));
+        args[4] = sm.getString("htmlManagerServlet.helpHtmlManager");
+        args[5] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlManagerServlet.helpManagerFile"));
+        args[6] = sm.getString("htmlManagerServlet.helpManager");
+        if (completeStatus) {
+            args[7] = response.encodeURL
+                (request.getContextPath() + "/status");
+            args[8] = sm.getString("statusServlet.title");
+        } else {
+            args[7] = response.encodeURL
+                (request.getContextPath() + "/status/all");
+            args[8] = sm.getString("statusServlet.complete");
+        }
+        // use StatusTransformer to output status
+        StatusTransformer.writeManager(writer,args,mode);
+
+        // Server Header Section
+        args = new Object[7];
+        args[0] = sm.getString("htmlManagerServlet.serverTitle");
+        args[1] = sm.getString("htmlManagerServlet.serverVersion");
+        args[2] = sm.getString("htmlManagerServlet.serverJVMVersion");
+        args[3] = sm.getString("htmlManagerServlet.serverJVMVendor");
+        args[4] = sm.getString("htmlManagerServlet.serverOSName");
+        args[5] = sm.getString("htmlManagerServlet.serverOSVersion");
+        args[6] = sm.getString("htmlManagerServlet.serverOSArch");
+        // use StatusTransformer to output status
+        StatusTransformer.writePageHeading(writer,args,mode);
+
+        // Server Row Section
+        args = new Object[6];
+        args[0] = ServerInfo.getServerInfo();
+        args[1] = System.getProperty("java.runtime.version");
+        args[2] = System.getProperty("java.vm.vendor");
+        args[3] = System.getProperty("os.name");
+        args[4] = System.getProperty("os.version");
+        args[5] = System.getProperty("os.arch");
+        // use StatusTransformer to output status
+        StatusTransformer.writeServerInfo(writer, args, mode);
+
+        try {
+
+            // Display operating system statistics using APR if available
+            StatusTransformer.writeOSState(writer,mode);
+
+            // Display virtual machine statistics
+            StatusTransformer.writeVMState(writer,mode);
+
+            Enumeration enumeration = threadPools.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = (ObjectName) enumeration.nextElement();
+                String name = objectName.getKeyProperty("name");
+                // use StatusTransformer to output status
+                StatusTransformer.writeConnectorState
+                    (writer, objectName,
+                     name, mBeanServer, globalRequestProcessors,
+                     requestProcessors, mode);
+            }
+
+            if ((request.getPathInfo() != null) 
+                && (request.getPathInfo().equals("/all"))) {
+                // Note: Retrieving the full status is much slower
+                // use StatusTransformer to output status
+                StatusTransformer.writeDetailedState
+                    (writer, mBeanServer, mode);
+            }
+
+        } catch (Exception e) {
+            throw new ServletException(e);
+        }
+
+        // use StatusTransformer to output status
+        StatusTransformer.writeFooter(writer, mode);
+
+    }
+
+    // ------------------------------------------- NotificationListener Methods
+
+
+    public void handleNotification(Notification notification,
+                                   java.lang.Object handback) {
+
+        if (notification instanceof MBeanServerNotification) {
+            ObjectName objectName = 
+                ((MBeanServerNotification) notification).getMBeanName();
+            if (notification.getType().equals
+                (MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
+                String type = objectName.getKeyProperty("type");
+                if (type != null) {
+                    if (type.equals("ProtocolHandler")) {
+                        protocolHandlers.addElement(objectName);
+                    } else if (type.equals("ThreadPool")) {
+                        threadPools.addElement(objectName);
+                    } else if (type.equals("GlobalRequestProcessor")) {
+                        globalRequestProcessors.addElement(objectName);
+                    } else if (type.equals("RequestProcessor")) {
+                        requestProcessors.addElement(objectName);
+                    }
+                }
+            } else if (notification.getType().equals
+                       (MBeanServerNotification.UNREGISTRATION_NOTIFICATION)) {
+                String type = objectName.getKeyProperty("type");
+                if (type != null) {
+                    if (type.equals("ProtocolHandler")) {
+                        protocolHandlers.removeElement(objectName);
+                    } else if (type.equals("ThreadPool")) {
+                        threadPools.removeElement(objectName);
+                    } else if (type.equals("GlobalRequestProcessor")) {
+                        globalRequestProcessors.removeElement(objectName);
+                    } else if (type.equals("RequestProcessor")) {
+                        requestProcessors.removeElement(objectName);
+                    }
+                }
+                String j2eeType = objectName.getKeyProperty("j2eeType");
+                if (j2eeType != null) {
+                    
+                }
+            }
+        }
+
+    }
+
+
+}
diff --git a/java/org/apache/catalina/manager/StatusTransformer.java b/java/org/apache/catalina/manager/StatusTransformer.java
new file mode 100644 (file)
index 0000000..2bdf5ba
--- /dev/null
@@ -0,0 +1,933 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Method;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.Vector;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.util.RequestUtil;
+
+/**
+ * This is a refactoring of the servlet to externalize
+ * the output into a simple class. Although we could
+ * use XSLT, that is unnecessarily complex.
+ *
+ * @author Peter Lin
+ * @version $Revision: 303967 $ $Date: 2005-06-29 19:31:56 +0200 (mer., 29 juin 2005) $
+ */
+
+public class StatusTransformer {
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    public static void setContentType(HttpServletResponse response, 
+                                      int mode) {
+        if (mode == 0){
+            response.setContentType("text/html;charset="+Constants.CHARSET);
+        } else if (mode == 1){
+            response.setContentType("text/xml;charset="+Constants.CHARSET);
+        }
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public static void writeHeader(PrintWriter writer, int mode) {
+        if (mode == 0){
+            // HTML Header Section
+            writer.print(Constants.HTML_HEADER_SECTION);
+        } else if (mode == 1){
+            writer.write(Constants.XML_DECLARATION);
+            writer.write
+                (Constants.XML_STYLE);
+            writer.write("<status>");
+        }
+    }
+
+
+    /**
+     * Write the header body. XML output doesn't bother
+     * to output this stuff, since it's just title.
+     * 
+     * @param writer The output writer
+     * @param args What to write
+     * @param mode 0 means write 
+     */
+    public static void writeBody(PrintWriter writer, Object[] args, int mode) {
+        if (mode == 0){
+            writer.print(MessageFormat.format
+                         (Constants.BODY_HEADER_SECTION, args));
+        }
+    }
+
+
+    /**
+     * Write the manager webapp information.
+     * 
+     * @param writer The output writer
+     * @param args What to write
+     * @param mode 0 means write
+     */
+    public static void writeManager(PrintWriter writer, Object[] args, 
+                                    int mode) {
+        if (mode == 0){
+            writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
+        }
+    }
+
+
+    public static void writePageHeading(PrintWriter writer, Object[] args, 
+                                        int mode) {
+        if (mode == 0){
+            writer.print(MessageFormat.format
+                         (Constants.SERVER_HEADER_SECTION, args));
+        }
+    }
+
+
+    public static void writeServerInfo(PrintWriter writer, Object[] args, 
+                                       int mode){
+        if (mode == 0){
+            writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));
+        }
+    }
+
+
+    /**
+     * 
+     */
+    public static void writeFooter(PrintWriter writer, int mode) {
+        if (mode == 0){
+            // HTML Tail Section
+            writer.print(Constants.HTML_TAIL_SECTION);
+        } else if (mode == 1){
+            writer.write("</status>");
+        }
+    }
+
+
+    /**
+     * Write the OS state. Mode 0 will generate HTML.
+     * Mode 1 will generate XML.
+     */
+    public static void writeOSState(PrintWriter writer, int mode) {
+        long[] result = new long[16];
+        boolean ok = false;
+        try {
+            String methodName = "info";
+            Class paramTypes[] = new Class[1];
+            paramTypes[0] = result.getClass();
+            Object paramValues[] = new Object[1];
+            paramValues[0] = result;
+            Method method = Class.forName("org.apache.tomcat.jni.OS")
+                .getMethod(methodName, paramTypes);
+            method.invoke(null, paramValues);
+            ok = true;
+        } catch (Throwable t) {
+            // Ignore
+        }
+        
+        if (ok) {
+            if (mode == 0){
+                writer.print("<h1>OS</h1>");
+
+                writer.print("<p>");
+                writer.print(" Physical memory: ");
+                writer.print(formatSize(new Long(result[0]), true));
+                writer.print(" Available memory: ");
+                writer.print(formatSize(new Long(result[1]), true));
+                writer.print(" Total page file: ");
+                writer.print(formatSize(new Long(result[2]), true));
+                writer.print(" Free page file: ");
+                writer.print(formatSize(new Long(result[3]), true));
+                writer.print(" Memory load: ");
+                writer.print(new Long(result[6]));
+                writer.print("<br>");
+                writer.print(" Process kernel time: ");
+                writer.print(formatTime(new Long(result[11] / 1000), true));
+                writer.print(" Process user time: ");
+                writer.print(formatTime(new Long(result[12] / 1000), true));
+                writer.print("</p>");
+            } else if (mode == 1){
+            }
+        }
+        
+    }
+    
+    
+    /**
+     * Write the VM state. Mode 0 will generate HTML.
+     * Mode 1 will generate XML.
+     */
+    public static void writeVMState(PrintWriter writer, int mode)
+        throws Exception {
+
+        if (mode == 0){
+            writer.print("<h1>JVM</h1>");
+
+            writer.print("<p>");
+            writer.print(" Free memory: ");
+            writer.print(formatSize
+                         (new Long(Runtime.getRuntime().freeMemory()), true));
+            writer.print(" Total memory: ");
+            writer.print(formatSize
+                         (new Long(Runtime.getRuntime().totalMemory()), true));
+            writer.print(" Max memory: ");
+            writer.print(formatSize
+                         (new Long(Runtime.getRuntime().maxMemory()), true));
+            writer.print("</p>");
+        } else if (mode == 1){
+            writer.write("<jvm>");
+
+            writer.write("<memory");
+            writer.write(" free='" + Runtime.getRuntime().freeMemory() + "'");
+            writer.write(" total='" + Runtime.getRuntime().totalMemory() + "'");
+            writer.write(" max='" + Runtime.getRuntime().maxMemory() + "'/>");
+
+            writer.write("</jvm>");
+        }
+
+    }
+
+
+    /**
+     * Write connector state.
+     */
+    public static void writeConnectorState(PrintWriter writer, 
+                                           ObjectName tpName, String name,
+                                           MBeanServer mBeanServer,
+                                           Vector globalRequestProcessors,
+                                           Vector requestProcessors,
+                                           int mode)
+        throws Exception {
+
+        if (mode == 0) {
+            writer.print("<h1>");
+            writer.print(name);
+            writer.print("</h1>");
+
+            writer.print("<p>");
+            writer.print(" Max threads: ");
+            writer.print(mBeanServer.getAttribute(tpName, "maxThreads"));
+            writer.print(" Min spare threads: ");
+            writer.print(mBeanServer.getAttribute(tpName, "minSpareThreads"));
+            writer.print(" Max spare threads: ");
+            writer.print(mBeanServer.getAttribute(tpName, "maxSpareThreads"));
+            writer.print(" Current thread count: ");
+            writer.print(mBeanServer.getAttribute(tpName, "currentThreadCount"));
+            writer.print(" Current thread busy: ");
+            writer.print(mBeanServer.getAttribute(tpName, "currentThreadsBusy"));
+            try {
+                Object value = mBeanServer.getAttribute(tpName, "keepAliveCount");
+                writer.print(" Keeped alive sockets count: ");
+                writer.print(value);
+            } catch (Exception e) {
+                // Ignore
+            }
+            
+            writer.print("<br>");
+
+            ObjectName grpName = null;
+
+            Enumeration enumeration = globalRequestProcessors.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = (ObjectName) enumeration.nextElement();
+                if (name.equals(objectName.getKeyProperty("name"))) {
+                    grpName = objectName;
+                }
+            }
+
+            if (grpName == null) {
+                return;
+            }
+
+            writer.print(" Max processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (grpName, "maxTime"), false));
+            writer.print(" Processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (grpName, "processingTime"), true));
+            writer.print(" Request count: ");
+            writer.print(mBeanServer.getAttribute(grpName, "requestCount"));
+            writer.print(" Error count: ");
+            writer.print(mBeanServer.getAttribute(grpName, "errorCount"));
+            writer.print(" Bytes received: ");
+            writer.print(formatSize(mBeanServer.getAttribute
+                                    (grpName, "bytesReceived"), true));
+            writer.print(" Bytes sent: ");
+            writer.print(formatSize(mBeanServer.getAttribute
+                                    (grpName, "bytesSent"), true));
+            writer.print("</p>");
+
+            writer.print("<table border=\"0\"><tr><th>Stage</th><th>Time</th><th>B Sent</th><th>B Recv</th><th>Client</th><th>VHost</th><th>Request</th></tr>");
+
+            enumeration = requestProcessors.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = (ObjectName) enumeration.nextElement();
+                if (name.equals(objectName.getKeyProperty("worker"))) {
+                    writer.print("<tr>");
+                    writeProcessorState(writer, objectName, mBeanServer, mode);
+                    writer.print("</tr>");
+                }
+            }
+
+            writer.print("</table>");
+
+            writer.print("<p>");
+            writer.print("P: Parse and prepare request S: Service F: Finishing R: Ready K: Keepalive");
+            writer.print("</p>");
+        } else if (mode == 1){
+            writer.write("<connector name='" + name + "'>");
+
+            writer.write("<threadInfo ");
+            writer.write(" maxThreads=\"" + mBeanServer.getAttribute(tpName, "maxThreads") + "\"");
+            writer.write(" minSpareThreads=\"" + mBeanServer.getAttribute(tpName, "minSpareThreads") + "\"");
+            writer.write(" maxSpareThreads=\"" + mBeanServer.getAttribute(tpName, "maxSpareThreads") + "\"");
+            writer.write(" currentThreadCount=\"" + mBeanServer.getAttribute(tpName, "currentThreadCount") + "\"");
+            writer.write(" currentThreadsBusy=\"" + mBeanServer.getAttribute(tpName, "currentThreadsBusy") + "\"");
+            writer.write(" />");
+
+            ObjectName grpName = null;
+
+            Enumeration enumeration = globalRequestProcessors.elements();
+            while (enumeration.hasMoreElements()) {
+                ObjectName objectName = (ObjectName) enumeration.nextElement();
+                if (name.equals(objectName.getKeyProperty("name"))) {
+                    grpName = objectName;
+                }
+            }
+
+            if (grpName != null) {
+
+                writer.write("<requestInfo ");
+                writer.write(" maxTime=\"" + mBeanServer.getAttribute(grpName, "maxTime") + "\"");
+                writer.write(" processingTime=\"" + mBeanServer.getAttribute(grpName, "processingTime") + "\"");
+                writer.write(" requestCount=\"" + mBeanServer.getAttribute(grpName, "requestCount") + "\"");
+                writer.write(" errorCount=\"" + mBeanServer.getAttribute(grpName, "errorCount") + "\"");
+                writer.write(" bytesReceived=\"" + mBeanServer.getAttribute(grpName, "bytesReceived") + "\"");
+                writer.write(" bytesSent=\"" + mBeanServer.getAttribute(grpName, "bytesSent") + "\"");
+                writer.write(" />");
+
+                writer.write("<workers>");
+                enumeration = requestProcessors.elements();
+                while (enumeration.hasMoreElements()) {
+                    ObjectName objectName = (ObjectName) enumeration.nextElement();
+                    if (name.equals(objectName.getKeyProperty("worker"))) {
+                        writeProcessorState(writer, objectName, mBeanServer, mode);
+                    }
+                }
+                writer.write("</workers>");
+            }
+
+            writer.write("</connector>");
+        }
+
+    }
+
+
+    /**
+     * Write processor state.
+     */
+    protected static void writeProcessorState(PrintWriter writer, 
+                                              ObjectName pName,
+                                              MBeanServer mBeanServer, 
+                                              int mode)
+        throws Exception {
+
+        Integer stageValue = 
+            (Integer) mBeanServer.getAttribute(pName, "stage");
+        int stage = stageValue.intValue();
+        boolean fullStatus = true;
+        boolean showRequest = true;
+        String stageStr = null;
+
+        switch (stage) {
+
+        case (1/*org.apache.coyote.Constants.STAGE_PARSE*/):
+            stageStr = "P";
+            fullStatus = false;
+            break;
+        case (2/*org.apache.coyote.Constants.STAGE_PREPARE*/):
+            stageStr = "P";
+            fullStatus = false;
+            break;
+        case (3/*org.apache.coyote.Constants.STAGE_SERVICE*/):
+            stageStr = "S";
+            break;
+        case (4/*org.apache.coyote.Constants.STAGE_ENDINPUT*/):
+            stageStr = "F";
+            break;
+        case (5/*org.apache.coyote.Constants.STAGE_ENDOUTPUT*/):
+            stageStr = "F";
+            break;
+        case (7/*org.apache.coyote.Constants.STAGE_ENDED*/):
+            stageStr = "R";
+            fullStatus = false;
+            break;
+        case (6/*org.apache.coyote.Constants.STAGE_KEEPALIVE*/):
+            stageStr = "K";
+            fullStatus = true;
+            showRequest = false;
+            break;
+        case (0/*org.apache.coyote.Constants.STAGE_NEW*/):
+            stageStr = "R";
+            fullStatus = false;
+            break;
+        default:
+            // Unknown stage
+            stageStr = "?";
+            fullStatus = false;
+
+        }
+
+        if (mode == 0) {
+            writer.write("<td><strong>");
+            writer.write(stageStr);
+            writer.write("</strong></td>");
+
+            if (fullStatus) {
+                writer.write("<td>");
+                writer.print(formatTime(mBeanServer.getAttribute
+                                        (pName, "requestProcessingTime"), false));
+                writer.write("</td>");
+                writer.write("<td>");
+                if (showRequest) {
+                    writer.print(formatSize(mBeanServer.getAttribute
+                                            (pName, "requestBytesSent"), false));
+                } else {
+                    writer.write("?");
+                }
+                writer.write("</td>");
+                writer.write("<td>");
+                if (showRequest) {
+                    writer.print(formatSize(mBeanServer.getAttribute
+                                            (pName, "requestBytesReceived"), 
+                                            false));
+                } else {
+                    writer.write("?");
+                }
+                writer.write("</td>");
+                writer.write("<td>");
+                writer.print(filter(mBeanServer.getAttribute
+                                    (pName, "remoteAddr")));
+                writer.write("</td>");
+                writer.write("<td nowrap>");
+                writer.write(filter(mBeanServer.getAttribute
+                                    (pName, "virtualHost")));
+                writer.write("</td>");
+                writer.write("<td nowrap>");
+                if (showRequest) {
+                    writer.write(filter(mBeanServer.getAttribute
+                                        (pName, "method")));
+                    writer.write(" ");
+                    writer.write(filter(mBeanServer.getAttribute
+                                        (pName, "currentUri")));
+                    String queryString = (String) mBeanServer.getAttribute
+                        (pName, "currentQueryString");
+                    if ((queryString != null) && (!queryString.equals(""))) {
+                        writer.write("?");
+                        writer.print(RequestUtil.filter(queryString));
+                    }
+                    writer.write(" ");
+                    writer.write(filter(mBeanServer.getAttribute
+                                        (pName, "protocol")));
+                } else {
+                    writer.write("?");
+                }
+                writer.write("</td>");
+            } else {
+                writer.write("<td>?</td><td>?</td><td>?</td><td>?</td><td>?</td><td>?</td>");
+            }
+        } else if (mode == 1){
+            writer.write("<worker ");
+            writer.write(" stage=\"" + stageStr + "\"");
+
+            if (fullStatus) {
+                writer.write(" requestProcessingTime=\"" 
+                             + mBeanServer.getAttribute
+                             (pName, "requestProcessingTime") + "\"");
+                writer.write(" requestBytesSent=\"");
+                if (showRequest) {
+                    writer.write("" + mBeanServer.getAttribute
+                                 (pName, "requestBytesSent"));
+                } else {
+                    writer.write("0");
+                }
+                writer.write("\"");
+                writer.write(" requestBytesReceived=\"");
+                if (showRequest) {
+                    writer.write("" + mBeanServer.getAttribute
+                                 (pName, "requestBytesReceived"));
+                } else {
+                    writer.write("0");
+                }
+                writer.write("\"");
+                writer.write(" remoteAddr=\"" 
+                             + filter(mBeanServer.getAttribute
+                                      (pName, "remoteAddr")) + "\"");
+                writer.write(" virtualHost=\"" 
+                             + filter(mBeanServer.getAttribute
+                                      (pName, "virtualHost")) + "\"");
+
+                if (showRequest) {
+                    writer.write(" method=\"" 
+                                 + filter(mBeanServer.getAttribute
+                                          (pName, "method")) + "\"");
+                    writer.write(" currentUri=\"" 
+                                 + filter(mBeanServer.getAttribute
+                                          (pName, "currentUri")) + "\"");
+
+                    String queryString = (String) mBeanServer.getAttribute
+                        (pName, "currentQueryString");
+                    if ((queryString != null) && (!queryString.equals(""))) {
+                        writer.write(" currentQueryString=\"" 
+                                     + RequestUtil.filter(queryString) + "\"");
+                    } else {
+                        writer.write(" currentQueryString=\"&#63;\"");
+                    }
+                    writer.write(" protocol=\"" 
+                                 + filter(mBeanServer.getAttribute
+                                          (pName, "protocol")) + "\"");
+                } else {
+                    writer.write(" method=\"&#63;\"");
+                    writer.write(" currentUri=\"&#63;\"");
+                    writer.write(" currentQueryString=\"&#63;\"");
+                    writer.write(" protocol=\"&#63;\"");
+                }
+            } else {
+                writer.write(" requestProcessingTime=\"0\"");
+                writer.write(" requestBytesSent=\"0\"");
+                writer.write(" requestBytesRecieved=\"0\"");
+                writer.write(" remoteAddr=\"&#63;\"");
+                writer.write(" virtualHost=\"&#63;\"");
+                writer.write(" method=\"&#63;\"");
+                writer.write(" currentUri=\"&#63;\"");
+                writer.write(" currentQueryString=\"&#63;\"");
+                writer.write(" protocol=\"&#63;\"");
+            }
+            writer.write(" />");
+        }
+
+    }
+
+
+    /**
+     * Write applications state.
+     */
+    public static void writeDetailedState(PrintWriter writer,
+                                          MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0){
+            ObjectName queryHosts = new ObjectName("*:j2eeType=WebModule,*");
+            Set hostsON = mBeanServer.queryNames(queryHosts, null);
+
+            // Navigation menu
+            writer.print("<h1>");
+            writer.print("Application list");
+            writer.print("</h1>");
+
+            writer.print("<p>");
+            int count = 0;
+            Iterator iterator = hostsON.iterator();
+            while (iterator.hasNext()) {
+                ObjectName contextON = (ObjectName) iterator.next();
+                String webModuleName = contextON.getKeyProperty("name");
+                if (webModuleName.startsWith("//")) {
+                    webModuleName = webModuleName.substring(2);
+                }
+                int slash = webModuleName.indexOf("/");
+                if (slash == -1) {
+                    count++;
+                    continue;
+                }
+
+                writer.print("<a href=\"#" + (count++) + ".0\">");
+                writer.print(webModuleName);
+                writer.print("</a>");
+                if (iterator.hasNext()) {
+                    writer.print("<br>");
+                }
+
+            }
+            writer.print("</p>");
+
+            // Webapp list
+            count = 0;
+            iterator = hostsON.iterator();
+            while (iterator.hasNext()) {
+                ObjectName contextON = (ObjectName) iterator.next();
+                writer.print("<a class=\"A.name\" name=\"" 
+                             + (count++) + ".0\">");
+                writeContext(writer, contextON, mBeanServer, mode);
+            }
+
+        } else if (mode == 1){
+            // for now we don't write out the Detailed state in XML
+        }
+
+    }
+
+
+    /**
+     * Write context state.
+     */
+    protected static void writeContext(PrintWriter writer, 
+                                       ObjectName objectName,
+                                       MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0){
+            String webModuleName = objectName.getKeyProperty("name");
+            String name = webModuleName;
+            if (name == null) {
+                return;
+            }
+            
+            String hostName = null;
+            String contextName = null;
+            if (name.startsWith("//")) {
+                name = name.substring(2);
+            }
+            int slash = name.indexOf("/");
+            if (slash != -1) {
+                hostName = name.substring(0, slash);
+                contextName = name.substring(slash);
+            } else {
+                return;
+            }
+
+            ObjectName queryManager = new ObjectName
+                (objectName.getDomain() + ":type=Manager,path=" + contextName 
+                 + ",host=" + hostName + ",*");
+            Set managersON = mBeanServer.queryNames(queryManager, null);
+            ObjectName managerON = null;
+            Iterator iterator2 = managersON.iterator();
+            while (iterator2.hasNext()) {
+                managerON = (ObjectName) iterator2.next();
+            }
+
+            ObjectName queryJspMonitor = new ObjectName
+                (objectName.getDomain() + ":type=JspMonitor,WebModule=" +
+                 webModuleName + ",*");
+            Set jspMonitorONs = mBeanServer.queryNames(queryJspMonitor, null);
+
+            // Special case for the root context
+            if (contextName.equals("/")) {
+                contextName = "";
+            }
+
+            writer.print("<h1>");
+            writer.print(name);
+            writer.print("</h1>");
+            writer.print("</a>");
+
+            writer.print("<p>");
+            Object startTime = mBeanServer.getAttribute(objectName,
+                                                        "startTime");
+            writer.print(" Start time: " +
+                         new Date(((Long) startTime).longValue()));
+            writer.print(" Startup time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "startupTime"), false));
+            writer.print(" TLD scan time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "tldScanTime"), false));
+            if (managerON != null) {
+                writeManager(writer, managerON, mBeanServer, mode);
+            }
+            if (jspMonitorONs != null) {
+                writeJspMonitor(writer, jspMonitorONs, mBeanServer, mode);
+            }
+            writer.print("</p>");
+
+            String onStr = objectName.getDomain() 
+                + ":j2eeType=Servlet,WebModule=" + webModuleName + ",*";
+            ObjectName servletObjectName = new ObjectName(onStr);
+            Set set = mBeanServer.queryMBeans(servletObjectName, null);
+            Iterator iterator = set.iterator();
+            while (iterator.hasNext()) {
+                ObjectInstance oi = (ObjectInstance) iterator.next();
+                writeWrapper(writer, oi.getObjectName(), mBeanServer, mode);
+            }
+
+        } else if (mode == 1){
+            // for now we don't write out the context in XML
+        }
+
+    }
+
+
+    /**
+     * Write detailed information about a manager.
+     */
+    public static void writeManager(PrintWriter writer, ObjectName objectName,
+                                    MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0) {
+            writer.print("<br>");
+            writer.print(" Active sessions: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "activeSessions"));
+            writer.print(" Session count: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "sessionCounter"));
+            writer.print(" Max active sessions: ");
+            writer.print(mBeanServer.getAttribute(objectName, "maxActive"));
+            writer.print(" Rejected session creations: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "rejectedSessions"));
+            writer.print(" Expired sessions: ");
+            writer.print(mBeanServer.getAttribute
+                         (objectName, "expiredSessions"));
+            writer.print(" Longest session alive time: ");
+            writer.print(formatSeconds(mBeanServer.getAttribute(
+                                                    objectName,
+                                                    "sessionMaxAliveTime")));
+            writer.print(" Average session alive time: ");
+            writer.print(formatSeconds(mBeanServer.getAttribute(
+                                                    objectName,
+                                                    "sessionAverageAliveTime")));
+            writer.print(" Processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "processingTime"), false));
+        } else if (mode == 1) {
+            // for now we don't write out the wrapper details
+        }
+
+    }
+
+
+    /**
+     * Write JSP monitoring information.
+     */
+    public static void writeJspMonitor(PrintWriter writer,
+                                       Set jspMonitorONs,
+                                       MBeanServer mBeanServer,
+                                       int mode)
+            throws Exception {
+
+        int jspCount = 0;
+        int jspReloadCount = 0;
+
+        Iterator iter = jspMonitorONs.iterator();
+        while (iter.hasNext()) {
+            ObjectName jspMonitorON = (ObjectName) iter.next();
+            Object obj = mBeanServer.getAttribute(jspMonitorON, "jspCount");
+            jspCount += ((Integer) obj).intValue();
+            obj = mBeanServer.getAttribute(jspMonitorON, "jspReloadCount");
+            jspReloadCount += ((Integer) obj).intValue();
+        }
+
+        if (mode == 0) {
+            writer.print("<br>");
+            writer.print(" JSPs loaded: ");
+            writer.print(jspCount);
+            writer.print(" JSPs reloaded: ");
+            writer.print(jspReloadCount);
+        } else if (mode == 1) {
+            // for now we don't write out anything
+        }
+    }
+
+
+    /**
+     * Write detailed information about a wrapper.
+     */
+    public static void writeWrapper(PrintWriter writer, ObjectName objectName,
+                                    MBeanServer mBeanServer, int mode)
+        throws Exception {
+
+        if (mode == 0) {
+            String servletName = objectName.getKeyProperty("name");
+            
+            String[] mappings = (String[]) 
+                mBeanServer.invoke(objectName, "findMappings", null, null);
+            
+            writer.print("<h2>");
+            writer.print(servletName);
+            if ((mappings != null) && (mappings.length > 0)) {
+                writer.print(" [ ");
+                for (int i = 0; i < mappings.length; i++) {
+                    writer.print(mappings[i]);
+                    if (i < mappings.length - 1) {
+                        writer.print(" , ");
+                    }
+                }
+                writer.print(" ] ");
+            }
+            writer.print("</h2>");
+            
+            writer.print("<p>");
+            writer.print(" Processing time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "processingTime"), true));
+            writer.print(" Max time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "maxTime"), false));
+            writer.print(" Request count: ");
+            writer.print(mBeanServer.getAttribute(objectName, "requestCount"));
+            writer.print(" Error count: ");
+            writer.print(mBeanServer.getAttribute(objectName, "errorCount"));
+            writer.print(" Load time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "loadTime"), false));
+            writer.print(" Classloading time: ");
+            writer.print(formatTime(mBeanServer.getAttribute
+                                    (objectName, "classLoadTime"), false));
+            writer.print("</p>");
+        } else if (mode == 1){
+            // for now we don't write out the wrapper details
+        }
+
+    }
+
+
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param obj The message string to be filtered
+     */
+    public static String filter(Object obj) {
+
+        if (obj == null)
+            return ("?");
+        String message = obj.toString();
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+        StringBuffer result = new StringBuffer(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+
+    }
+
+
+    /**
+     * Display the given size in bytes, either as KB or MB.
+     *
+     * @param mb true to display megabytes, false for kilobytes
+     */
+    public static String formatSize(Object obj, boolean mb) {
+
+        long bytes = -1L;
+
+        if (obj instanceof Long) {
+            bytes = ((Long) obj).longValue();
+        } else if (obj instanceof Integer) {
+            bytes = ((Integer) obj).intValue();
+        }
+
+        if (mb) {
+            long mbytes = bytes / (1024 * 1024);
+            long rest = 
+                ((bytes - (mbytes * (1024 * 1024))) * 100) / (1024 * 1024);
+            return (mbytes + "." + ((rest < 10) ? "0" : "") + rest + " MB");
+        } else {
+            return ((bytes / 1024) + " KB");
+        }
+
+    }
+
+
+    /**
+     * Display the given time in ms, either as ms or s.
+     *
+     * @param seconds true to display seconds, false for milliseconds
+     */
+    public static String formatTime(Object obj, boolean seconds) {
+
+        long time = -1L;
+
+        if (obj instanceof Long) {
+            time = ((Long) obj).longValue();
+        } else if (obj instanceof Integer) {
+            time = ((Integer) obj).intValue();
+        }
+
+        if (seconds) {
+            return ((((float) time ) / 1000) + " s");
+        } else {
+            return (time + " ms");
+        }
+    }
+
+
+    /**
+     * Formats the given time (given in seconds) as a string.
+     *
+     * @param obj Time object to be formatted as string
+     *
+     * @return String formatted time
+     */
+    public static String formatSeconds(Object obj) {
+
+        long time = -1L;
+
+        if (obj instanceof Long) {
+            time = ((Long) obj).longValue();
+        } else if (obj instanceof Integer) {
+            time = ((Integer) obj).intValue();
+        }
+
+        return (time + " s");
+    }
+
+}
diff --git a/java/org/apache/catalina/manager/host/Constants.java b/java/org/apache/catalina/manager/host/Constants.java
new file mode 100644 (file)
index 0000000..84b3db6
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright 1999,2004 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager.host;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.manager.host";
+
+    public static final String HTML_HEADER_SECTION =
+        "<html>\n" +
+        "<head>\n" +
+        "<style>\n" +
+        org.apache.catalina.util.TomcatCSS.TOMCAT_CSS +
+        "  table {\n" +
+        "    width: 100%;\n" +
+        "  }\n" +
+        "  td.page-title {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: white;\n" +
+        "    color: black;\n" +
+        "  }\n" +
+        "  td.title {\n" +
+        "    text-align: left;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-style:italic;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #D2A41C;\n" +
+        "  }\n" +
+        "  td.header-left {\n" +
+        "    text-align: left;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #FFDC75;\n" +
+        "  }\n" +
+        "  td.header-center {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #FFDC75;\n" +
+        "  }\n" +
+        "  td.row-left {\n" +
+        "    text-align: left;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "    background: white;\n" +
+        "  }\n" +
+        "  td.row-center {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "    background: white;\n" +
+        "  }\n" +
+        "  td.row-right {\n" +
+        "    text-align: right;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "    background: white;\n" +
+        "  }\n" +
+        "  TH {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: top;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    font-weight: bold;\n" +
+        "    background: #FFDC75;\n" +
+        "  }\n" +
+        "  TD {\n" +
+        "    text-align: center;\n" +
+        "    vertical-align: middle;\n" +
+        "    font-family:sans-serif,Tahoma,Arial;\n" +
+        "    color: black;\n" +
+        "    background: white;\n" +
+        "  }\n" +
+        "</style>\n";
+
+    public static final String BODY_HEADER_SECTION =
+        "<title>{0}</title>\n" +
+        "</head>\n" +
+        "\n" +
+        "<body bgcolor=\"#FFFFFF\">\n" +
+        "\n" +
+        "<table cellspacing=\"4\" width=\"100%\" border=\"0\">\n" +
+        " <tr>\n" +
+        "  <td colspan=\"2\">\n" +
+        "   <a href=\"http://www.apache.org/\">\n" +
+        "    <img border=\"0\" alt=\"The Apache Software Foundation\" align=\"left\"\n" +
+        "         src=\"{0}/images/asf-logo.gif\">\n" +
+        "   </a>\n" +
+        "   <a href=\"http://tomcat.apache.org/\">\n" +
+        "    <img border=\"0\" alt=\"The Tomcat Servlet/JSP Container\"\n" +
+        "         align=\"right\" src=\"{0}/images/tomcat.gif\">\n" +
+        "   </a>\n" +
+        "  </td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<hr size=\"1\" noshade=\"noshade\">\n" +
+        "<table cellspacing=\"4\" width=\"100%\" border=\"0\">\n" +
+        " <tr>\n" +
+        "  <td class=\"page-title\" bordercolor=\"#000000\" " +
+        "align=\"left\" nowrap>\n" +
+        "   <font size=\"+2\">{1}</font>\n" +
+        "  </td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String MESSAGE_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        " <tr>\n" +
+        "  <td class=\"row-left\" width=\"10%\">" +
+        "<small><strong>{0}</strong></small>&nbsp;</td>\n" +
+        "  <td class=\"row-left\"><pre>{1}</pre></td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String MANAGER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"4\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        " <tr>\n" +
+        "  <td class=\"row-left\"><a href=\"{1}\">{2}</a></td>\n" +
+        "  <td class=\"row-center\"><a href=\"{3}\">{4}</a></td>\n" +
+        "  <td class=\"row-center\"><a href=\"{5}\">{6}</a></td>\n" +
+        "  <td class=\"row-right\"><a href=\"{7}\">{8}</a></td>\n" +
+        " </tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String SERVER_HEADER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"6\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"header-center\"><small>{1}</small></td>\n" +
+        " <td class=\"header-center\"><small>{2}</small></td>\n" +
+        " <td class=\"header-center\"><small>{3}</small></td>\n" +
+        " <td class=\"header-center\"><small>{4}</small></td>\n" +
+        " <td class=\"header-center\"><small>{5}</small></td>\n" +
+        " <td class=\"header-center\"><small>{6}</small></td>\n" +
+        "</tr>\n";
+
+    public static final String SERVER_ROW_SECTION =
+        "<tr>\n" +
+        " <td class=\"row-center\"><small>{0}</small></td>\n" +
+        " <td class=\"row-center\"><small>{1}</small></td>\n" +
+        " <td class=\"row-center\"><small>{2}</small></td>\n" +
+        " <td class=\"row-center\"><small>{3}</small></td>\n" +
+        " <td class=\"row-center\"><small>{4}</small></td>\n" +
+        " <td class=\"row-center\"><small>{5}</small></td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+    public static final String HTML_TAIL_SECTION =
+        "<hr size=\"1\" noshade=\"noshade\">\n" +
+        "<center><font size=\"-1\" color=\"#525D76\">\n" +
+        " <em>Copyright &copy; 1999-2005, Apache Software Foundation</em>" +
+        "</font></center>\n" +
+        "\n" +
+        "</body>\n" +
+        "</html>";
+    public static final String CHARSET="utf-8";
+
+    // FIXME need we this?
+    public static final String XML_DECLARATION =
+        "<?xml version=\"1.0\" encoding=\""+CHARSET+"\"?>";
+               
+    public static final String XML_STYLE =
+        "<?xml-stylesheet type=\"text/xsl\" href=\"/manager/xform.xsl\" ?>";
+
+}
+
diff --git a/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java b/java/org/apache/catalina/manager/host/HTMLHostManagerServlet.java
new file mode 100644 (file)
index 0000000..c377e80
--- /dev/null
@@ -0,0 +1,478 @@
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager.host;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Host;
+import org.apache.catalina.util.ServerInfo;
+
+/**
+* Servlet that enables remote management of the virtual hosts deployed
+* on the server.  Normally, this functionality will be protected by a security
+* constraint in the web application deployment descriptor.  However, 
+* this requirement can be relaxed during testing.
+* <p>
+* The difference between the <code>HostManagerServlet</code> and this
+* Servlet is that this Servlet prints out a HTML interface which
+* makes it easier to administrate.
+* <p>
+* However if you use a software that parses the output of
+* <code>HostManagerServlet</code> you won't be able to upgrade
+* to this Servlet since the output are not in the
+* same format as from <code>HostManagerServlet</code>
+*
+* @author Bip Thelin
+* @author Malcolm Edgar
+* @author Glenn L. Nielsen
+* @author Peter Rossbach
+* @version $Revision: 384293 $, $Date: 2006-03-08 19:09:36 +0100 (mer., 08 mars 2006) $
+* @see ManagerServlet
+*/
+
+public final class HTMLHostManagerServlet extends HostManagerServlet {
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+
+        String name = request.getParameter("name");
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/html; charset=" + Constants.CHARSET);
+
+        String message = "";
+        // Process the requested command
+        if (command == null) {
+        } else if (command.equals("/add")) {
+            message = add(request, name);
+        } else if (command.equals("/remove")) {
+            message = remove(name);
+        } else if (command.equals("/list")) {
+        } else if (command.equals("/start")) {
+            message = start(name);
+        } else if (command.equals("/stop")) {
+            message = stop(name);
+        } else {
+            message =
+                sm.getString("hostManagerServlet.unknownCommand", command);
+        }
+
+        list(request, response, message);
+    }
+
+    
+    /**
+     * Add a host using the specified parameters.
+     *
+     * @param name host name
+     */
+    protected String add(HttpServletRequest request,String name) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.add(request,printWriter,name,true);
+
+        return stringWriter.toString();
+    }
+
+
+    /**
+     * Remove the specified host.
+     *
+     * @param writer Writer to render results to
+     * @param name host name
+     */
+    protected String remove(String name) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.remove(printWriter, name);
+
+        return stringWriter.toString();
+    }
+
+    
+    /**
+     * Start the host with the specified name.
+     *
+     * @param name Host name
+     */
+    protected String start(String name) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.start(printWriter, name);
+
+        return stringWriter.toString();
+    }
+
+    
+    /**
+     * Stop the host with the specified name.
+     *
+     * @param name Host name
+     */
+    protected String stop(String name) {
+
+        StringWriter stringWriter = new StringWriter();
+        PrintWriter printWriter = new PrintWriter(stringWriter);
+
+        super.stop(printWriter, name);
+
+        return stringWriter.toString();
+    }
+
+    
+    /**
+     * Render a HTML list of the currently active Contexts in our virtual host,
+     * and memory and server status information.
+     *
+     * @param request The request
+     * @param response The response
+     * @param message a message to display
+     */
+    public void list(HttpServletRequest request,
+                     HttpServletResponse response,
+                     String message) throws IOException {
+
+        PrintWriter writer = response.getWriter();
+
+        // HTML Header Section
+        writer.print(Constants.HTML_HEADER_SECTION);
+
+        // Body Header Section
+        Object[] args = new Object[2];
+        args[0] = request.getContextPath();
+        args[1] = sm.getString("htmlHostManagerServlet.title");
+        writer.print(MessageFormat.format
+                     (Constants.BODY_HEADER_SECTION, args));
+
+        // Message Section
+        args = new Object[3];
+        args[0] = sm.getString("htmlHostManagerServlet.messageLabel");
+        args[1] = (message == null || message.length() == 0) ? "OK" : message;
+        writer.print(MessageFormat.format(Constants.MESSAGE_SECTION, args));
+
+        // Manager Section
+        args = new Object[9];
+        args[0] = sm.getString("htmlHostManagerServlet.manager");
+        args[1] = response.encodeURL(request.getContextPath() + "/html/list");
+        args[2] = sm.getString("htmlHostManagerServlet.list");
+        args[3] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlHostManagerServlet.helpHtmlManagerFile"));
+        args[4] = sm.getString("htmlHostManagerServlet.helpHtmlManager");
+        args[5] = response.encodeURL
+            (request.getContextPath() + "/" +
+             sm.getString("htmlHostManagerServlet.helpManagerFile"));
+        args[6] = sm.getString("htmlHostManagerServlet.helpManager");
+        args[7] = response.encodeURL("/manager/status");
+        args[8] = sm.getString("statusServlet.title");
+        writer.print(MessageFormat.format(Constants.MANAGER_SECTION, args));
+
+         // Hosts Header Section
+        args = new Object[3];
+        args[0] = sm.getString("htmlHostManagerServlet.hostName");
+        args[1] = sm.getString("htmlHostManagerServlet.hostAliases");
+        args[2] = sm.getString("htmlHostManagerServlet.hostTasks");
+        writer.print(MessageFormat.format(HOSTS_HEADER_SECTION, args));
+
+        // Hosts Row Section
+        // Create sorted map of host names.
+        Container[] children = engine.findChildren();
+        String hostNames[] = new String[children.length];
+        for (int i = 0; i < children.length; i++)
+            hostNames[i] = children[i].getName();
+
+        TreeMap sortedHostNamesMap = new TreeMap();
+
+        for (int i = 0; i < hostNames.length; i++) {
+            String displayPath = hostNames[i];
+            sortedHostNamesMap.put(displayPath, hostNames[i]);
+        }
+
+        String hostsStart = sm.getString("htmlHostManagerServlet.hostsStart");
+        String hostsStop = sm.getString("htmlHostManagerServlet.hostsStop");
+        String hostsRemove = sm.getString("htmlHostManagerServlet.hostsRemove");
+
+        Iterator iterator = sortedHostNamesMap.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry entry = (Map.Entry) iterator.next();
+            String hostName = (String) entry.getKey();
+            Host host = (Host) engine.findChild(hostName);
+
+            if (host != null ) {
+                args = new Object[2];
+                args[0] = hostName;
+                String[] aliases = host.findAliases();
+                StringBuffer buf = new StringBuffer();
+                if (aliases.length > 0) {
+                    buf.append(aliases[0]);
+                    for (int j = 1; j < aliases.length; j++) {
+                        buf.append(", ").append(aliases[j]);
+                    }
+                }
+
+                if (buf.length() == 0) {
+                    buf.append("&nbsp;");
+                }
+
+                args[1] = buf.toString();
+                writer.print
+                    (MessageFormat.format(HOSTS_ROW_DETAILS_SECTION, args));
+
+                args = new Object[7];
+                args[0] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/start?name=" + hostName);
+                args[1] = hostsStart;
+                args[2] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/stop?name=" + hostName);
+                args[3] = hostsStop;
+                args[4] = response.encodeURL
+                    (request.getContextPath() +
+                     "/html/remove?name=" + hostName);
+                args[5] = hostsRemove;
+                args[6] = hostName;
+                if (host == this.host) {
+                    writer.print(MessageFormat.format(
+                        MANAGER_HOST_ROW_BUTTON_SECTION, args));
+                } else {
+                    writer.print(MessageFormat.format(
+                        HOSTS_ROW_BUTTON_SECTION, args));
+                }
+
+            }
+        }
+
+        // Add Section
+        args = new Object[6];
+        args[0] = sm.getString("htmlHostManagerServlet.addTitle");
+        args[1] = sm.getString("htmlHostManagerServlet.addHost");
+        args[2] = response.encodeURL(request.getContextPath() + "/html/add");
+        args[3] = sm.getString("htmlHostManagerServlet.addName");
+        args[4] = sm.getString("htmlHostManagerServlet.addAliases");
+        args[5] = sm.getString("htmlHostManagerServlet.addAppBase");
+        writer.print(MessageFormat.format(ADD_SECTION_START, args));
+        args = new Object[3];
+        args[0] = sm.getString("htmlHostManagerServlet.addAutoDeploy");
+        args[1] = "autoDeploy";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = sm.getString("htmlHostManagerServlet.addDeployOnStartup");
+        args[1] = "deployOnStartup";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = sm.getString("htmlHostManagerServlet.addDeployXML");
+        args[1] = "deployXML";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = sm.getString("htmlHostManagerServlet.addUnpackWARs");
+        args[1] = "unpackWARs";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = sm.getString("htmlHostManagerServlet.addXmlNamespaceAware");
+        args[1] = "xmlNamespaceAware";
+        args[2] = "";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        args[0] = sm.getString("htmlHostManagerServlet.addXmlValidation");
+        args[1] = "xmlValidation";
+        args[2] = "";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+
+        args[0] = sm.getString("htmlHostManagerServlet.addManager");
+        args[1] = "manager";
+        args[2] = "checked";
+        writer.print(MessageFormat.format(ADD_SECTION_BOOLEAN, args));
+        
+        args = new Object[1];
+        args[0] = sm.getString("htmlHostManagerServlet.addButton");
+        writer.print(MessageFormat.format(ADD_SECTION_END, args));
+
+        // Server Header Section
+        args = new Object[7];
+        args[0] = sm.getString("htmlHostManagerServlet.serverTitle");
+        args[1] = sm.getString("htmlHostManagerServlet.serverVersion");
+        args[2] = sm.getString("htmlHostManagerServlet.serverJVMVersion");
+        args[3] = sm.getString("htmlHostManagerServlet.serverJVMVendor");
+        args[4] = sm.getString("htmlHostManagerServlet.serverOSName");
+        args[5] = sm.getString("htmlHostManagerServlet.serverOSVersion");
+        args[6] = sm.getString("htmlHostManagerServlet.serverOSArch");
+        writer.print(MessageFormat.format
+                     (Constants.SERVER_HEADER_SECTION, args));
+
+        // Server Row Section
+        args = new Object[6];
+        args[0] = ServerInfo.getServerInfo();
+        args[1] = System.getProperty("java.runtime.version");
+        args[2] = System.getProperty("java.vm.vendor");
+        args[3] = System.getProperty("os.name");
+        args[4] = System.getProperty("os.version");
+        args[5] = System.getProperty("os.arch");
+        writer.print(MessageFormat.format(Constants.SERVER_ROW_SECTION, args));
+
+        // HTML Tail Section
+        writer.print(Constants.HTML_TAIL_SECTION);
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+    }
+
+    
+    // ------------------------------------------------------ Private Constants
+
+    // These HTML sections are broken in relatively small sections, because of
+    // limited number of subsitutions MessageFormat can process
+    // (maximium of 10).
+
+    private static final String HOSTS_HEADER_SECTION =
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"5\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"header-left\"><small>{0}</small></td>\n" +
+        " <td class=\"header-center\"><small>{1}</small></td>\n" +
+        " <td class=\"header-center\"><small>{2}</small></td>\n" +
+        "</tr>\n";
+
+    private static final String HOSTS_ROW_DETAILS_SECTION =
+        "<tr>\n" +
+        " <td class=\"row-left\"><small><a href=\"http://{0}\">{0}</a>" +
+        "</small></td>\n" +
+        " <td class=\"row-center\"><small>{1}</small></td>\n";
+
+    private static final String MANAGER_HOST_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\">\n" +
+        "  <small>\n" +
+        "  &nbsp;{1}&nbsp;\n" +
+        "  &nbsp;{3}&nbsp;\n" +
+        "  &nbsp;{5}&nbsp;\n" +
+        "  </small>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String HOSTS_ROW_BUTTON_SECTION =
+        " <td class=\"row-left\" NOWRAP>\n" +
+        "  <small>\n" +
+        "  &nbsp;<a href=\"{0}\" onclick=\"return(confirm(''{1} {6}\\n\\nAre you sure?''))\">{1}</a>&nbsp;\n" +
+        "  &nbsp;<a href=\"{2}\" onclick=\"return(confirm(''{3} {6}\\n\\nAre you sure?''))\">{3}</a>&nbsp;\n" +
+        "  &nbsp;<a href=\"{4}\" onclick=\"return(confirm(''{5} {6}\\n\\nAre you sure?''))\">{5}</a>&nbsp;\n" +
+        "  </small>\n" +
+        " </td>\n" +
+        "</tr>\n";
+
+    private static final String ADD_SECTION_START =
+        "</table>\n" +
+        "<br>\n" +
+        "<table border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"title\">{0}</td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\" class=\"header-left\"><small>{1}</small></td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td colspan=\"2\">\n" +
+        "<form method=\"get\" action=\"{2}\">\n" +
+        "<table cellspacing=\"0\" cellpadding=\"3\">\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{3}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"name\" size=\"20\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{4}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"aliases\" size=\"64\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{5}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"text\" name=\"appBase\" size=\"64\">\n" +
+        " </td>\n" +
+        "</tr>\n" ;
+    
+        private static final String ADD_SECTION_BOOLEAN =
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  <small>{0}</small>\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"checkbox\" name=\"{1}\" {2}>\n" +
+        " </td>\n" +
+        "</tr>\n" ;
+        
+        private static final String ADD_SECTION_END =
+        "<tr>\n" +
+        " <td class=\"row-right\">\n" +
+        "  &nbsp;\n" +
+        " </td>\n" +
+        " <td class=\"row-left\">\n" +
+        "  <input type=\"submit\" value=\"{0}\">\n" +
+        " </td>\n" +
+        "</tr>\n" +
+         "</table>\n" +
+        "</form>\n" +
+        "</td>\n" +
+        "</tr>\n" +
+        "</table>\n" +
+        "<br>\n" +
+        "\n";
+
+}
diff --git a/java/org/apache/catalina/manager/host/HostManagerServlet.java b/java/org/apache/catalina/manager/host/HostManagerServlet.java
new file mode 100644 (file)
index 0000000..8698147
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+ * Copyright 1999,2004-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.catalina.manager.host;
+
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.util.StringTokenizer;
+
+import javax.management.MBeanServer;
+import javax.servlet.ServletException;
+import javax.servlet.UnavailableException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.ContainerServlet;
+import org.apache.catalina.Context;
+import org.apache.catalina.Engine;
+import org.apache.catalina.Globals;
+import org.apache.catalina.Host;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.startup.HostConfig;
+import org.apache.catalina.util.StringManager;
+import org.apache.tomcat.util.modeler.Registry;
+
+
+/**
+ * Servlet that enables remote management of the virtual hosts installed
+ * on the server.  Normally, this functionality will be protected by 
+ * a security constraint in the web application deployment descriptor.  
+ * However, this requirement can be relaxed during testing.
+ * <p>
+ * This servlet examines the value returned by <code>getPathInfo()</code>
+ * and related query parameters to determine what action is being requested.
+ * The following actions and parameters (starting after the servlet path)
+ * are supported:
+ * <ul>
+ * <li><b>/add?name={host-name}&aliases={host-aliases}&manager={manager}</b> -
+ *     Create and add a new virtual host. The <code>host-name</code> attribute
+ *     indicates the name of the new host. The <code>host-aliases</code> 
+ *     attribute is a comma separated list of the host alias names. 
+ *     The <code>manager</code> attribute is a boolean value indicating if the
+ *     webapp manager will be installed in the newly created host (optional, 
+ *     false by default).</li>
+ * <li><b>/remove?name={host-name}</b> - Remove a virtual host. 
+ *     The <code>host-name</code> attribute indicates the name of the host.
+ *     </li>
+ * <li><b>/list</b> - List the virtual hosts installed on the server.
+ *     Each host will be listed with the following format 
+ *     <code>host-name#host-aliases</code>.</li>
+ * <li><b>/start?name={host-name}</b> - Start the virtual host.</li>
+ * <li><b>/stop?name={host-name}</b> - Stop the virtual host.</li>
+ * </ul>
+ * <p>
+ * <b>NOTE</b> - Attempting to stop or remove the host containing
+ * this servlet itself will not succeed.  Therefore, this servlet should
+ * generally be deployed in a separate virtual host.
+ * <p>
+ * <b>NOTE</b> - For security reasons, this application will not operate
+ * when accessed via the invoker servlet.  You must explicitly map this servlet
+ * with a servlet mapping, and you will always want to protect it with
+ * appropriate security constraints as well.
+ * <p>
+ * The following servlet initialization parameters are recognized:
+ * <ul>
+ * <li><b>debug</b> - The debugging detail level that controls the amount
+ *     of information that is logged by this servlet.  Default is zero.
+ * </ul>
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ * @version $Revision: 384293 $ $Date: 2006-03-08 19:09:36 +0100 (mer., 08 mars 2006) $
+ */
+
+public class HostManagerServlet
+    extends HttpServlet implements ContainerServlet {
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Path where context descriptors should be deployed.
+     */
+    protected File configBase = null;
+
+
+    /**
+     * The Context container associated with our web application.
+     */
+    protected Context context = null;
+
+
+    /**
+     * The debugging detail level for this servlet.
+     */
+    protected int debug = 1;
+
+
+    /**
+     * The associated host.
+     */
+    protected Host host = null;
+
+    
+    /**
+     * The associated engine.
+     */
+    protected Engine engine = null;
+
+    
+    /**
+     * MBean server.
+     */
+    protected MBeanServer mBeanServer = null;
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * The Wrapper container associated with this servlet.
+     */
+    protected Wrapper wrapper = null;
+
+
+    // ----------------------------------------------- ContainerServlet Methods
+
+
+    /**
+     * Return the Wrapper with which we are associated.
+     */
+    public Wrapper getWrapper() {
+
+        return (this.wrapper);
+
+    }
+
+
+    /**
+     * Set the Wrapper with which we are associated.
+     *
+     * @param wrapper The new wrapper
+     */
+    public void setWrapper(Wrapper wrapper) {
+
+        this.wrapper = wrapper;
+        if (wrapper == null) {
+            context = null;
+            host = null;
+            engine = null;
+        } else {
+            context = (Context) wrapper.getParent();
+            host = (Host) context.getParent();
+            engine = (Engine) host.getParent();
+        }
+
+        // Retrieve the MBean server
+        mBeanServer = Registry.getRegistry(null, null).getMBeanServer();
+        
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Finalize this servlet.
+     */
+    public void destroy() {
+
+        ;       // No actions necessary
+
+    }
+
+
+    /**
+     * Process a GET request for the specified resource.
+     *
+     * @param request The servlet request we are processing
+     * @param response The servlet response we are creating
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet-specified error occurs
+     */
+    public void doGet(HttpServletRequest request,
+                      HttpServletResponse response)
+        throws IOException, ServletException {
+
+        // Verify that we were not accessed using the invoker servlet
+        if (request.getAttribute(Globals.INVOKED_ATTR) != null)
+            throw new UnavailableException
+                (sm.getString("hostManagerServlet.cannotInvoke"));
+
+        // Identify the request parameters that we need
+        String command = request.getPathInfo();
+        if (command == null)
+            command = request.getServletPath();
+        String name = request.getParameter("name");
+  
+        // Prepare our output writer to generate the response message
+        response.setContentType("text/plain; charset=" + Constants.CHARSET);
+        PrintWriter writer = response.getWriter();
+
+        // Process the requested command
+        if (command == null) {
+            writer.println(sm.getString("hostManagerServlet.noCommand"));
+        } else if (command.equals("/add")) {
+            add(request, writer, name, false);
+        } else if (command.equals("/remove")) {
+            remove(writer, name);
+        } else if (command.equals("/list")) {
+            list(writer);
+        } else if (command.equals("/start")) {
+            start(writer, name);
+        } else if (command.equals("/stop")) {
+            stop(writer, name);
+        } else {
+            writer.println(sm.getString("hostManagerServlet.unknownCommand",
+                                        command));
+        }
+
+        // Finish up the response
+        writer.flush();
+        writer.close();
+
+    }
+
+
+    /**
+     * Add host with the given parameters.
+     *
+     * @param request The request
+     * @param writer The output writer
+     * @param name The host name
+     * @param htmlMode Flag value
+     */
+    protected void add(HttpServletRequest request, PrintWriter writer, String name, boolean htmlMode ) {
+        String aliases = request.getParameter("aliases");
+        String appBase = request.getParameter("appBase");
+        boolean manager = booleanParameter(request, "manager", false, htmlMode);
+        boolean autoDeploy = booleanParameter(request, "autoDeploy", true, htmlMode);
+        boolean deployOnStartup = booleanParameter(request, "deployOnStartup", true, htmlMode);
+        boolean deployXML = booleanParameter(request, "deployXML", true, htmlMode);
+        boolean unpackWARs = booleanParameter(request, "unpackWARs", true, htmlMode);
+        boolean xmlNamespaceAware = booleanParameter(request, "xmlNamespaceAware", false, htmlMode);
+        boolean xmlValidation = booleanParameter(request, "xmlValidation", false, htmlMode);
+        add(writer, name, aliases, appBase, manager,
+            autoDeploy,
+            deployOnStartup,
+            deployXML,                                       
+            unpackWARs,
+            xmlNamespaceAware,
+            xmlValidation);
+    }
+
+
+    /**
+     * extract boolean value from checkbox with default
+     * @param request
+     * @param parameter
+     * @param theDefault
+     * @param htmlMode
+     * @return
+     */
+    protected boolean booleanParameter(HttpServletRequest request,
+            String parameter, boolean theDefault, boolean htmlMode) {
+        String value = request.getParameter(parameter);
+        boolean booleanValue = theDefault;
+        if (value != null) {
+            if (htmlMode) {
+                if (value.equals("on")) {
+                    booleanValue = true;
+                }
+            } else if (theDefault) {
+                if (value.equals("false")) {
+                    booleanValue = false;
+                }
+            } else if (value.equals("true")) {
+                booleanValue = true;
+            }
+        } else if (htmlMode)
+            booleanValue = false;
+        return booleanValue;
+    }
+
+
+    /**
+     * Initialize this servlet.
+     */
+    public void init() throws ServletException {
+
+        // Ensure that our ContainerServlet properties have been set
+        if ((wrapper == null) || (context == null))
+            throw new UnavailableException
+                (sm.getString("hostManagerServlet.noWrapper"));
+
+        // Verify that we were not accessed using the invoker servlet
+        String servletName = getServletConfig().getServletName();
+        if (servletName == null)
+            servletName = "";
+        if (servletName.startsWith("org.apache.catalina.INVOKER."))
+            throw new UnavailableException
+                (sm.getString("hostManagerServlet.cannotInvoke"));
+
+        // Set our properties from the initialization parameters
+        String value = null;
+        try {
+            value = getServletConfig().getInitParameter("debug");
+            debug = Integer.parseInt(value);
+        } catch (Throwable t) {
+            ;
+        }
+
+    }
+
+
+
+    // -------------------------------------------------------- Private Methods
+
+
+    /**
+     * Add a host using the specified parameters.
+     *
+     * @param writer Writer to render results to
+     * @param name host name
+     * @param aliases comma separated alias list
+     * @param appBase application base for the host
+     * @param manager should the manager webapp be deployed to the new host ?
+     */
+    protected synchronized void add
+        (PrintWriter writer, String name, String aliases, String appBase, 
+         boolean manager,
+         boolean autoDeploy,
+         boolean deployOnStartup,
+         boolean deployXML,                                       
+         boolean unpackWARs,
+         boolean xmlNamespaceAware,
+         boolean xmlValidation) {
+        if (debug >= 1) {
+            log("add: Adding host '" + name + "'");
+        }
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(sm.getString("hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        // Check if host already exists
+        if (engine.findChild(name) != null) {
+            writer.println
+                (sm.getString("hostManagerServlet.alreadyHost", name));
+            return;
+        }
+
+        // Validate and create appBase
+        File appBaseFile = null;
+        if (appBase == null || appBase.length() == 0) {
+            appBase = name;
+        }
+        File file = new File(appBase);
+        if (!file.isAbsolute())
+            file = new File(System.getProperty("catalina.base"), appBase);
+        try {
+            appBaseFile = file.getCanonicalFile();
+        } catch (IOException e) {
+            appBaseFile = file;
+        }
+        if (!appBaseFile.exists()) {
+            appBaseFile.mkdirs();
+        }
+        
+        // Create base for config files
+        File configBaseFile = getConfigBase(name);
+        
+        // Copy manager.xml if requested
+        if (manager) {
+            InputStream is = null;
+            OutputStream os = null;
+            try {
+                is = getServletContext().getResourceAsStream("/manager.xml");
+                os = new FileOutputStream(new File(configBaseFile, "manager.xml"));
+                byte buffer[] = new byte[512];
+                int len = buffer.length;
+                while (true) {
+                    len = is.read(buffer);
+                    if (len == -1)
+                        break;
+                    os.write(buffer, 0, len);
+                }
+            } catch (IOException e) {
+                writer.println
+                    (sm.getString("hostManagerServlet.managerXml"));
+                return;
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                }
+                if (os != null) {
+                    try {
+                        os.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+        
+        StandardHost host = new StandardHost();
+        host.setAppBase(appBase);
+        host.setName(name);
+
+        host.addLifecycleListener(new HostConfig());
+
+        // Add host aliases
+        if ((aliases != null) && !("".equals(aliases))) {
+            StringTokenizer tok = new StringTokenizer(aliases, ",");
+            while (tok.hasMoreTokens()) {
+                host.addAlias(tok.nextToken());
+            }
+        }
+        host.setAutoDeploy(autoDeploy);
+        host.setDeployOnStartup(deployOnStartup);
+        host.setDeployXML(deployXML);
+        host.setUnpackWARs(unpackWARs);
+        host.setXmlNamespaceAware(xmlNamespaceAware);
+        host.setXmlValidation(xmlValidation);
+        
+        // Add new host
+        try {
+            engine.addChild(host);
+        } catch (Exception e) {
+            writer.println(sm.getString("hostManagerServlet.exception",
+                                        e.toString()));
+            return;
+        }
+        
+        host = (StandardHost) engine.findChild(name);
+        if (host != null) {
+            writer.println(sm.getString("hostManagerServlet.add", name));
+        } else {
+            // Something failed
+            writer.println(sm.getString("hostManagerServlet.addFailed", name));
+        }
+        
+    }
+
+
+    /**
+     * Remove the specified host.
+     *
+     * @param writer Writer to render results to
+     * @param name host name
+     */
+    protected synchronized void remove(PrintWriter writer, String name) {
+
+        if (debug >= 1) {
+            log("remove: Removing host '" + name + "'");
+        }
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(sm.getString("hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        // Check if host exists
+        if (engine.findChild(name) == null) {
+            writer.println
+                (sm.getString("hostManagerServlet.noHost", name));
+            return;
+        }
+
+        // Prevent removing our own host
+        if (engine.findChild(name) == host) {
+            writer.println
+                (sm.getString("hostManagerServlet.cannotRemoveOwnHost", name));
+            return;
+        }
+
+        // Remove host
+        // Note that the host will not get physically removed
+        try {
+            engine.removeChild(engine.findChild(name));
+        } catch (Exception e) {
+            writer.println(sm.getString("hostManagerServlet.exception",
+                                        e.toString()));
+            return;
+        }
+        
+        Host host = (StandardHost) engine.findChild(name);
+        if (host == null) {
+            writer.println(sm.getString("hostManagerServlet.remove", name));
+        } else {
+            // Something failed
+            writer.println(sm.getString("hostManagerServlet.removeFailed", name));
+        }
+        
+    }
+
+
+    /**
+     * Render a list of the currently active Contexts in our virtual host.
+     *
+     * @param writer Writer to render to
+     */
+    protected void list(PrintWriter writer) {
+
+        if (debug >= 1)
+            log("list: Listing hosts for engine '" 
+                + engine.getName() + "'");
+
+        writer.println(sm.getString("hostManagerServlet.listed",
+                                    engine.getName()));
+        Container[] hosts = engine.findChildren();
+        for (int i = 0; i < hosts.length; i++) {
+            Host host = (Host) hosts[i];
+            String name = host.getName();
+            String[] aliases = host.findAliases();
+            StringBuffer buf = new StringBuffer();
+            if (aliases.length > 0) {
+                buf.append(aliases[0]);
+                for (int j = 1; j < aliases.length; j++) {
+                    buf.append(',').append(aliases[j]);
+                }
+            }
+            writer.println(sm.getString("hostManagerServlet.listitem",
+                                        name, buf.toString()));
+        }
+    }
+
+
+    /**
+     * Start the host with the specified name.
+     *
+     * @param writer Writer to render to
+     * @param name Host name
+     */
+    protected void start(PrintWriter writer, String name) {
+
+        if (debug >= 1)
+            log("start: Starting host with name '" + name + "'");
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(sm.getString("hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        // Check if host exists
+        if (engine.findChild(name) == null) {
+            writer.println
+                (sm.getString("hostManagerServlet.noHost", name));
+            return;
+        }
+
+        // Prevent starting our own host
+        if (engine.findChild(name) == host) {
+            writer.println
+                (sm.getString("hostManagerServlet.cannotStartOwnHost", name));
+            return;
+        }
+
+        // Start host
+        try {
+            ((Lifecycle) engine.findChild(name)).start();
+            writer.println
+                (sm.getString("hostManagerServlet.started", name));
+        } catch (Throwable t) {
+            getServletContext().log
+                (sm.getString("hostManagerServlet.startFailed", name), t);
+            writer.println
+                (sm.getString("hostManagerServlet.startFailed", name));
+            writer.println(sm.getString("hostManagerServlet.exception",
+                                        t.toString()));
+            return;
+        }
+        
+    }
+
+
+    /**
+     * Start the host with the specified name.
+     *
+     * @param writer Writer to render to
+     * @param name Host name
+     */
+    protected void stop(PrintWriter writer, String name) {
+
+        if (debug >= 1)
+            log("stop: Stopping host with name '" + name + "'");
+
+        // Validate the requested host name
+        if ((name == null) || name.length() == 0) {
+            writer.println(sm.getString("hostManagerServlet.invalidHostName", name));
+            return;
+        }
+
+        // Check if host exists
+        if (engine.findChild(name) == null) {
+            writer.println
+                (sm.getString("hostManagerServlet.noHost", name));
+            return;
+        }
+
+        // Prevent starting our own host
+        if (engine.findChild(name) == host) {
+            writer.println
+                (sm.getString("hostManagerServlet.cannotStopOwnHost", name));
+            return;
+        }
+
+        // Start host
+        try {
+            ((Lifecycle) engine.findChild(name)).stop();
+            writer.println
+                (sm.getString("hostManagerServlet.stopped", name));
+        } catch (Throwable t) {
+            getServletContext().log
+                (sm.getString("hostManagerServlet.stopFailed", name), t);
+            writer.println
+                (sm.getString("hostManagerServlet.stopFailed", name));
+            writer.println(sm.getString("hostManagerServlet.exception",
+                                        t.toString()));
+            return;
+        }
+        
+    }
+
+
+    // -------------------------------------------------------- Support Methods
+
+
+    /**
+     * Get config base.
+     */
+    protected File getConfigBase(String hostName) {
+        File configBase = 
+            new File(System.getProperty("catalina.base"), "conf");
+        if (!configBase.exists()) {
+            return null;
+        }
+        if (engine != null) {
+            configBase = new File(configBase, engine.getName());
+        }
+        if (host != null) {
+            configBase = new File(configBase, hostName);
+        }
+        configBase.mkdirs();
+        return configBase;
+    }
+
+
+}
diff --git a/java/org/apache/catalina/manager/host/LocalStrings.properties b/java/org/apache/catalina/manager/host/LocalStrings.properties
new file mode 100644 (file)
index 0000000..5a665c9
--- /dev/null
@@ -0,0 +1,59 @@
+hostManagerServlet.cannotInvoke=Cannot invoke host manager servlet through invoker
+hostManagerServlet.noCommand=FAIL - No command was specified
+hostManagerServlet.unknownCommand=FAIL - Unknown command {0}
+hostManagerServlet.noWrapper=Container has not called setWrapper() for this servlet
+hostManagerServlet.invalidHostName=FAIL - Invalid host name {0} was specified
+hostManagerServlet.alreadyHost=FAIL - Host already exists with host name {0}
+hostManagerServlet.managerXml=FAIL - Couldn't install manager.xml
+hostManagerServlet.exception=FAIL - Encountered exception {0}
+hostManagerServlet.add=OK - Host {0} added
+hostManagerServlet.addFailed=FAIL - Failed to add host {0}
+hostManagerServlet.cannotRemoveOwnHost=FAIL - Cannot remove own host {0}
+hostManagerServlet.remove=OK - Removed host {0}
+hostManagerServlet.removeFailed=FAIL - Failed to remove host {0}
+hostManagerServlet.listed=OK - Listed hosts
+hostManagerServlet.listitem={0}:{1}
+hostManagerServlet.cannotStartOwnHost=FAIL - Cannot start own host {0}
+hostManagerServlet.started=OK - Host {0} started
+hostManagerServlet.startFailed=FAIL - Failed to start host {0}
+hostManagerServlet.cannotStopOwnHost=FAIL - Cannot stop own host {0}
+hostManagerServlet.stopped=OK - Host {0} stopped
+hostManagerServlet.stopFailed=FAIL - Failed to stop host {0}
+
+htmlHostManagerServlet.title=Tomcat Virtual Host Manager
+htmlHostManagerServlet.messageLabel=Message:
+htmlHostManagerServlet.manager=Host Manager
+htmlHostManagerServlet.list=List Virtual Hosts
+htmlHostManagerServlet.helpHtmlManagerFile=html-host-manager-howto.html
+htmlHostManagerServlet.helpHtmlManager=HTML Host Manager Help (Coming Soon!)
+htmlHostManagerServlet.helpManagerFile=host-manager-howto.html
+htmlHostManagerServlet.helpManager=Host Manager Help
+htmlHostManagerServlet.hostName=Host name
+htmlHostManagerServlet.hostAliases=Host aliases
+htmlHostManagerServlet.hostTasks=Commands
+htmlHostManagerServlet.hostsStart=Start
+htmlHostManagerServlet.hostsStop=Stop
+htmlHostManagerServlet.hostsRemove=Remove
+htmlHostManagerServlet.addTitle=Add Virtual Host
+htmlHostManagerServlet.addHost=Host
+htmlHostManagerServlet.addName=Name:
+htmlHostManagerServlet.addAliases=Aliases:
+htmlHostManagerServlet.addAppBase=App base:
+htmlHostManagerServlet.addManager=Manager App
+htmlHostManagerServlet.addAutoDeploy=AutoDeploy
+htmlHostManagerServlet.addDeployOnStartup=DeployOnStartup
+htmlHostManagerServlet.addDeployXML=DeployXML
+htmlHostManagerServlet.addUnpackWARs=UnpackWARs
+htmlHostManagerServlet.addXmlNamespaceAware=XmlNamespaceAware
+htmlHostManagerServlet.addXmlValidation=XmlValidation
+htmlHostManagerServlet.addButton=Add
+htmlHostManagerServlet.serverTitle=Server Information
+htmlHostManagerServlet.serverVersion=Tomcat Version
+htmlHostManagerServlet.serverJVMVersion=JVM Version
+htmlHostManagerServlet.serverJVMVendor=JVM Vendor
+htmlHostManagerServlet.serverOSName=OS Name
+htmlHostManagerServlet.serverOSVersion=OS Version
+htmlHostManagerServlet.serverOSArch=OS Architecture
+
+statusServlet.title=Server Status
+statusServlet.complete=Complete Server Status
diff --git a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItem.java
new file mode 100644 (file)
index 0000000..805b6d0
--- /dev/null
@@ -0,0 +1,607 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * <p> The default implementation of the
+ * {@link org.apache.tomcat.util.http.fileupload.FileItem FileItem} interface.
+ *
+ * <p> After retrieving an instance of this class from a {@link
+ * org.apache.tomcat.util.http.fileupload.DiskFileUpload DiskFileUpload} instance (see
+ * {@link org.apache.tomcat.util.http.fileupload.DiskFileUpload
+ * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
+ * either request all contents of file at once using {@link #get()} or
+ * request an {@link java.io.InputStream InputStream} with
+ * {@link #getInputStream()} and process the file without attempting to load
+ * it into memory, which may come handy with large files.
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@apache.org">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: DefaultFileItem.java,v 1.21 2003/06/24 05:45:15 martinc Exp $
+ */
+public class DefaultFileItem
+    implements FileItem
+{
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * Counter used in unique identifier generation.
+     */
+    private static int counter = 0;
+
+
+    /**
+     * The name of the form field as provided by the browser.
+     */
+    private String fieldName;
+
+
+    /**
+     * The content type passed by the browser, or <code>null</code> if
+     * not defined.
+     */
+    private String contentType;
+
+
+    /**
+     * Whether or not this item is a simple form field.
+     */
+    private boolean isFormField;
+
+
+    /**
+     * The original filename in the user's filesystem.
+     */
+    private String fileName;
+
+
+    /**
+     * The threshold above which uploads will be stored on disk.
+     */
+    private int sizeThreshold;
+
+
+    /**
+     * The directory in which uploaded files will be stored, if stored on disk.
+     */
+    private File repository;
+
+
+    /**
+     * Cached contents of the file.
+     */
+    private byte[] cachedContent;
+
+
+    /**
+     * Output stream for this item.
+     */
+    private DeferredFileOutputStream dfos;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs a new <code>DefaultFileItem</code> instance.
+     *
+     * @param fieldName     The name of the form field.
+     * @param contentType   The content type passed by the browser or
+     *                      <code>null</code> if not specified.
+     * @param isFormField   Whether or not this item is a plain form field, as
+     *                      opposed to a file upload.
+     * @param fileName      The original filename in the user's filesystem, or
+     *                      <code>null</code> if not specified.
+     * @param sizeThreshold The threshold, in bytes, below which items will be
+     *                      retained in memory and above which they will be
+     *                      stored as a file.
+     * @param repository    The data repository, which is the directory in
+     *                      which files will be created, should the item size
+     *                      exceed the threshold.
+     */
+    DefaultFileItem(String fieldName, String contentType, boolean isFormField,
+                    String fileName, int sizeThreshold, File repository)
+    {
+        this.fieldName = fieldName;
+        this.contentType = contentType;
+        this.isFormField = isFormField;
+        this.fileName = fileName;
+        this.sizeThreshold = sizeThreshold;
+        this.repository = repository;
+    }
+
+
+    // ------------------------------- Methods from javax.activation.DataSource
+
+
+    /**
+     * Returns an {@link java.io.InputStream InputStream} that can be
+     * used to retrieve the contents of the file.
+     *
+     * @return An {@link java.io.InputStream InputStream} that can be
+     *         used to retrieve the contents of the file.
+     *
+     * @exception IOException if an error occurs.
+     */
+    public InputStream getInputStream()
+        throws IOException
+    {
+        if (!dfos.isInMemory())
+        {
+            return new FileInputStream(dfos.getFile());
+        }
+
+        if (cachedContent == null)
+        {
+            cachedContent = dfos.getData();
+        }
+        return new ByteArrayInputStream(cachedContent);
+    }
+
+
+    /**
+     * Returns the content type passed by the browser or <code>null</code> if
+     * not defined.
+     *
+     * @return The content type passed by the browser or <code>null</code> if
+     *         not defined.
+     */
+    public String getContentType()
+    {
+        return contentType;
+    }
+
+
+    /**
+     * Returns the original filename in the client's filesystem.
+     *
+     * @return The original filename in the client's filesystem.
+     */
+    public String getName()
+    {
+        return fileName;
+    }
+
+
+    // ------------------------------------------------------- FileItem methods
+
+
+    /**
+     * Provides a hint as to whether or not the file contents will be read
+     * from memory.
+     *
+     * @return <code>true</code> if the file contents will be read
+     *         from memory; <code>false</code> otherwise.
+     */
+    public boolean isInMemory()
+    {
+        return (dfos.isInMemory());
+    }
+
+
+    /**
+     * Returns the size of the file.
+     *
+     * @return The size of the file, in bytes.
+     */
+    public long getSize()
+    {
+        if (cachedContent != null)
+        {
+            return cachedContent.length;
+        }
+        else if (dfos.isInMemory())
+        {
+            return dfos.getData().length;
+        }
+        else
+        {
+            return dfos.getFile().length();
+        }
+    }
+
+
+    /**
+     * Returns the contents of the file as an array of bytes.  If the
+     * contents of the file were not yet cached in memory, they will be
+     * loaded from the disk storage and cached.
+     *
+     * @return The contents of the file as an array of bytes.
+     */
+    public byte[] get()
+    {
+        if (dfos.isInMemory())
+        {
+            if (cachedContent == null)
+            {
+                cachedContent = dfos.getData();
+            }
+            return cachedContent;
+        }
+
+        byte[] fileData = new byte[(int) getSize()];
+        FileInputStream fis = null;
+
+        try
+        {
+            fis = new FileInputStream(dfos.getFile());
+            fis.read(fileData);
+        }
+        catch (IOException e)
+        {
+            fileData = null;
+        }
+        finally
+        {
+            if (fis != null)
+            {
+                try
+                {
+                    fis.close();
+                }
+                catch (IOException e)
+                {
+                    // ignore
+                }
+            }
+        }
+
+        return fileData;
+    }
+
+
+    /**
+     * Returns the contents of the file as a String, using the specified
+     * encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the file.
+     *
+     * @param encoding The character encoding to use.
+     *
+     * @return The contents of the file, as a string.
+     *
+     * @exception UnsupportedEncodingException if the requested character
+     *                                         encoding is not available.
+     */
+    public String getString(String encoding)
+        throws UnsupportedEncodingException
+    {
+        return new String(get(), encoding);
+    }
+
+
+    /**
+     * Returns the contents of the file as a String, using the default
+     * character encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the file.
+     *
+     * @return The contents of the file, as a string.
+     */
+    public String getString()
+    {
+        return new String(get());
+    }
+
+
+    /**
+     * A convenience method to write an uploaded item to disk. The client code
+     * is not concerned with whether or not the item is stored in memory, or on
+     * disk in a temporary location. They just want to write the uploaded item
+     * to a file.
+     * <p>
+     * This implementation first attempts to rename the uploaded item to the
+     * specified destination file, if the item was originally written to disk.
+     * Otherwise, the data will be copied to the specified file.
+     * <p>
+     * This method is only guaranteed to work <em>once</em>, the first time it
+     * is invoked for a particular item. This is because, in the event that the
+     * method renames a temporary file, that file will no longer be available
+     * to copy or rename again at a later time.
+     *
+     * @param file The <code>File</code> into which the uploaded item should
+     *             be stored.
+     *
+     * @exception Exception if an error occurs.
+     */
+    public void write(File file) throws Exception
+    {
+        if (isInMemory())
+        {
+            FileOutputStream fout = null;
+            try
+            {
+                fout = new FileOutputStream(file);
+                fout.write(get());
+            }
+            finally
+            {
+                if (fout != null)
+                {
+                    fout.close();
+                }
+            }
+        }
+        else
+        {
+            File outputFile = getStoreLocation();
+            if (outputFile != null)
+            {
+                /*
+                 * The uploaded file is being stored on disk
+                 * in a temporary location so move it to the
+                 * desired file.
+                 */
+                if (!outputFile.renameTo(file))
+                {
+                    BufferedInputStream in = null;
+                    BufferedOutputStream out = null;
+                    try
+                    {
+                        in = new BufferedInputStream(
+                            new FileInputStream(outputFile));
+                        out = new BufferedOutputStream(
+                                new FileOutputStream(file));
+                        byte[] bytes = new byte[2048];
+                        int s = 0;
+                        while ((s = in.read(bytes)) != -1)
+                        {
+                            out.write(bytes, 0, s);
+                        }
+                    }
+                    finally
+                    {
+                        try
+                        {
+                            in.close();
+                        }
+                        catch (IOException e)
+                        {
+                            // ignore
+                        }
+                        try
+                        {
+                            out.close();
+                        }
+                        catch (IOException e)
+                        {
+                            // ignore
+                        }
+                    }
+                }
+            }
+            else
+            {
+                /*
+                 * For whatever reason we cannot write the
+                 * file to disk.
+                 */
+                throw new FileUploadException(
+                    "Cannot write uploaded file to disk!");
+            }
+        }
+    }
+
+
+    /**
+     * Deletes the underlying storage for a file item, including deleting any
+     * associated temporary disk file. Although this storage will be deleted
+     * automatically when the <code>FileItem</code> instance is garbage
+     * collected, this method can be used to ensure that this is done at an
+     * earlier time, thus preserving system resources.
+     */
+    public void delete()
+    {
+        cachedContent = null;
+        File outputFile = getStoreLocation();
+        if (outputFile != null && outputFile.exists())
+        {
+            outputFile.delete();
+        }
+    }
+
+
+    /**
+     * Returns the name of the field in the multipart form corresponding to
+     * this file item.
+     *
+     * @return The name of the form field.
+     *
+     * @see #setFieldName(java.lang.String)
+     *
+     */
+    public String getFieldName()
+    {
+        return fieldName;
+    }
+
+
+    /**
+     * Sets the field name used to reference this file item.
+     *
+     * @param fieldName The name of the form field.
+     *
+     * @see #getFieldName()
+     *
+     */
+    public void setFieldName(String fieldName)
+    {
+        this.fieldName = fieldName;
+    }
+
+
+    /**
+     * Determines whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @return <code>true</code> if the instance represents a simple form
+     *         field; <code>false</code> if it represents an uploaded file.
+     *
+     * @see #setFormField(boolean)
+     *
+     */
+    public boolean isFormField()
+    {
+        return isFormField;
+    }
+
+
+    /**
+     * Specifies whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @param state <code>true</code> if the instance represents a simple form
+     *              field; <code>false</code> if it represents an uploaded file.
+     *
+     * @see #isFormField()
+     *
+     */
+    public void setFormField(boolean state)
+    {
+        isFormField = state;
+    }
+
+
+    /**
+     * Returns an {@link java.io.OutputStream OutputStream} that can
+     * be used for storing the contents of the file.
+     *
+     * @return An {@link java.io.OutputStream OutputStream} that can be used
+     *         for storing the contensts of the file.
+     *
+     * @exception IOException if an error occurs.
+     */
+    public OutputStream getOutputStream()
+        throws IOException
+    {
+        if (dfos == null)
+        {
+            File outputFile = getTempFile();
+            dfos = new DeferredFileOutputStream(sizeThreshold, outputFile);
+        }
+        return dfos;
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Returns the {@link java.io.File} object for the <code>FileItem</code>'s
+     * data's temporary location on the disk. Note that for
+     * <code>FileItem</code>s that have their data stored in memory,
+     * this method will return <code>null</code>. When handling large
+     * files, you can use {@link java.io.File#renameTo(java.io.File)} to
+     * move the file to new location without copying the data, if the
+     * source and destination locations reside within the same logical
+     * volume.
+     *
+     * @return The data file, or <code>null</code> if the data is stored in
+     *         memory.
+     */
+    public File getStoreLocation()
+    {
+        return dfos.getFile();
+    }
+
+
+    // ------------------------------------------------------ Protected methods
+
+
+    /**
+     * Removes the file contents from the temporary storage.
+     */
+    protected void finalize()
+    {
+        File outputFile = dfos.getFile();
+
+        if (outputFile != null && outputFile.exists())
+        {
+            outputFile.delete();
+        }
+    }
+
+
+    /**
+     * Creates and returns a {@link java.io.File File} representing a uniquely
+     * named temporary file in the configured repository path.
+     *
+     * @return The {@link java.io.File File} to be used for temporary storage.
+     */
+    protected File getTempFile()
+    {
+        File tempDir = repository;
+        if (tempDir == null)
+        {
+            tempDir = new File(System.getProperty("java.io.tmpdir"));
+        }
+
+        String fileName = "upload_" + getUniqueId() + ".tmp";
+
+        File f = new File(tempDir, fileName);
+        f.deleteOnExit();
+        return f;
+    }
+
+
+    // -------------------------------------------------------- Private methods
+
+
+    /**
+     * Returns an identifier that is unique within the class loader used to
+     * load this class, but does not have random-like apearance.
+     *
+     * @return A String with the non-random looking instance identifier.
+     */
+    private static String getUniqueId()
+    {
+        int current;
+        synchronized (DefaultFileItem.class)
+        {
+            current = counter++;
+        }
+        String id = Integer.toString(current);
+
+        // If you manage to get more than 100 million of ids, you'll
+        // start getting ids longer than 8 characters.
+        if (current < 100000000)
+        {
+            id = ("00000000" + id).substring(id.length());
+        }
+        return id;
+    }
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/DefaultFileItemFactory.java
new file mode 100644 (file)
index 0000000..b710a0a
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+import java.io.File;
+
+
+/**
+ * <p>The default {@link org.apache.tomcat.util.http.fileupload.FileItemFactory}
+ * implementation. This implementation creates
+ * {@link org.apache.tomcat.util.http.fileupload.FileItem} instances which keep their
+ * content either in memory, for smaller items, or in a temporary file on disk,
+ * for larger items. The size threshold, above which content will be stored on
+ * disk, is configurable, as is the directory in which temporary files will be
+ * created.</p>
+ *
+ * <p>If not otherwise configured, the default configuration values are as
+ * follows:
+ * <ul>
+ *   <li>Size threshold is 10KB.</li>
+ *   <li>Repository is the system default temp directory, as returned by
+ *       <code>System.getProperty("java.io.tmpdir")</code>.</li>
+ * </ul>
+ * </p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @version $Id: DefaultFileItemFactory.java,v 1.2 2003/05/31 22:31:08 martinc Exp $
+ */
+public class DefaultFileItemFactory implements FileItemFactory
+{
+
+    // ----------------------------------------------------- Manifest constants
+
+
+    /**
+     * The default threshold above which uploads will be stored on disk.
+     */
+    public static final int DEFAULT_SIZE_THRESHOLD = 10240;
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The directory in which uploaded files will be stored, if stored on disk.
+     */
+    private File repository;
+
+
+    /**
+     * The threshold above which uploads will be stored on disk.
+     */
+    private int sizeThreshold = DEFAULT_SIZE_THRESHOLD;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an unconfigured instance of this class. The resulting factory
+     * may be configured by calling the appropriate setter methods.
+     */
+    public DefaultFileItemFactory()
+    {
+    }
+
+
+    /**
+     * Constructs a preconfigured instance of this class.
+     *
+     * @param sizeThreshold The threshold, in bytes, below which items will be
+     *                      retained in memory and above which they will be
+     *                      stored as a file.
+     * @param repository    The data repository, which is the directory in
+     *                      which files will be created, should the item size
+     *                      exceed the threshold.
+     */
+    public DefaultFileItemFactory(int sizeThreshold, File repository)
+    {
+        this.sizeThreshold = sizeThreshold;
+        this.repository = repository;
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Returns the directory used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @return The directory in which temporary files will be located.
+     *
+     * @see #setRepository(java.io.File)
+     *
+     */
+    public File getRepository()
+    {
+        return repository;
+    }
+
+
+    /**
+     * Sets the directory used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @param repository The directory in which temporary files will be located.
+     *
+     * @see #getRepository()
+     *
+     */
+    public void setRepository(File repository)
+    {
+        this.repository = repository;
+    }
+
+
+    /**
+     * Returns the size threshold beyond which files are written directly to
+     * disk. The default value is 1024 bytes.
+     *
+     * @return The size threshold, in bytes.
+     *
+     * @see #setSizeThreshold(int)
+     */
+    public int getSizeThreshold()
+    {
+        return sizeThreshold;
+    }
+
+
+    /**
+     * Sets the size threshold beyond which files are written directly to disk.
+     *
+     * @param sizeThreshold The size threshold, in bytes.
+     *
+     * @see #getSizeThreshold()
+     *
+     */
+    public void setSizeThreshold(int sizeThreshold)
+    {
+        this.sizeThreshold = sizeThreshold;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Create a new {@link org.apache.tomcat.util.http.fileupload.DefaultFileItem}
+     * instance from the supplied parameters and the local factory
+     * configuration.
+     *
+     * @param fieldName   The name of the form field.
+     * @param contentType The content type of the form field.
+     * @param isFormField <code>true</code> if this is a plain form field;
+     *                    <code>false</code> otherwise.
+     * @param fileName    The name of the uploaded file, if any, as supplied
+     *                    by the browser or other client.
+     *
+     * @return The newly created file item.
+     */
+    public FileItem createItem(
+            String fieldName,
+            String contentType,
+            boolean isFormField,
+            String fileName
+            )
+    {
+        return new DefaultFileItem(fieldName, contentType,
+                isFormField, fileName, sizeThreshold, repository);
+    }
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java b/java/org/apache/tomcat/util/http/fileupload/DeferredFileOutputStream.java
new file mode 100644 (file)
index 0000000..79c1fe7
--- /dev/null
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * <p>An output stream which will retain data in memory until a specified
+ * threshold is reached, and only then commit it to disk. If the stream is
+ * closed before the threshold is reached, the data will not be written to
+ * disk at all.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @version $Id: DeferredFileOutputStream.java,v 1.2 2003/05/31 22:31:08 martinc Exp $
+ */
+public class DeferredFileOutputStream
+    extends ThresholdingOutputStream
+{
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The output stream to which data will be written prior to the theshold
+     * being reached.
+     */
+    private ByteArrayOutputStream memoryOutputStream;
+
+
+    /**
+     * The output stream to which data will be written after the theshold is
+     * reached.
+     */
+    private FileOutputStream diskOutputStream;
+
+
+    /**
+     * The output stream to which data will be written at any given time. This
+     * will always be one of <code>memoryOutputStream</code> or
+     * <code>diskOutputStream</code>.
+     */
+    private OutputStream currentOutputStream;
+
+
+    /**
+     * The file to which output will be directed if the threshold is exceeded.
+     */
+    private File outputFile;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an instance of this class which will trigger an event at the
+     * specified threshold, and save data to a file beyond that point.
+     *
+     * @param threshold  The number of bytes at which to trigger an event.
+     * @param outputFile The file to which data is saved beyond the threshold.
+     */
+    public DeferredFileOutputStream(int threshold, File outputFile)
+    {
+        super(threshold);
+        this.outputFile = outputFile;
+
+        memoryOutputStream = new ByteArrayOutputStream(threshold);
+        currentOutputStream = memoryOutputStream;
+    }
+
+
+    // --------------------------------------- ThresholdingOutputStream methods
+
+
+    /**
+     * Returns the current output stream. This may be memory based or disk
+     * based, depending on the current state with respect to the threshold.
+     *
+     * @return The underlying output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected OutputStream getStream() throws IOException
+    {
+        return currentOutputStream;
+    }
+
+
+    /**
+     * Switches the underlying output stream from a memory based stream to one
+     * that is backed by disk. This is the point at which we realise that too
+     * much data is being written to keep in memory, so we elect to switch to
+     * disk-based storage.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected void thresholdReached() throws IOException
+    {
+        byte[] data = memoryOutputStream.toByteArray();
+        FileOutputStream fos = new FileOutputStream(outputFile);
+        fos.write(data);
+        diskOutputStream = fos;
+        currentOutputStream = fos;
+        memoryOutputStream = null;
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Determines whether or not the data for this output stream has been
+     * retained in memory.
+     *
+     * @return <code>true</code> if the data is available in memory;
+     *         <code>false</code> otherwise.
+     */
+    public boolean isInMemory()
+    {
+        return (!isThresholdExceeded());
+    }
+
+
+    /**
+     * Returns the data for this output stream as an array of bytes, assuming
+     * that the data has been retained in memory. If the data was written to
+     * disk, this method returns <code>null</code>.
+     *
+     * @return The data for this output stream, or <code>null</code> if no such
+     *         data is available.
+     */
+    public byte[] getData()
+    {
+        if (memoryOutputStream != null)
+        {
+            return memoryOutputStream.toByteArray();
+        }
+        return null;
+    }
+
+
+    /**
+     * Returns the data for this output stream as a <code>File</code>, assuming
+     * that the data was written to disk. If the data was retained in memory,
+     * this method returns <code>null</code>.
+     *
+     * @return The file for this output stream, or <code>null</code> if no such
+     *         file exists.
+     */
+    public File getFile()
+    {
+        return outputFile;
+    }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java b/java/org/apache/tomcat/util/http/fileupload/DiskFileUpload.java
new file mode 100644 (file)
index 0000000..5b1bac9
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+import java.io.File;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * <p>High level API for processing file uploads.</p>
+ *
+ * <p>This class handles multiple files per single HTML widget, sent using
+ * <code>multipart/mixed</code> encoding type, as specified by
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
+ * #parseRequest(HttpServletRequest)} to acquire a list of {@link
+ * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
+ * widget.</p>
+ *
+ * <p>Individual parts will be stored in temporary disk storage or in memory,
+ * depending on their size, and will be available as {@link
+ * org.apache.tomcat.util.http.fileupload.FileItem}s.</p>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: DiskFileUpload.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
+ */
+public class DiskFileUpload
+    extends FileUploadBase
+ {
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The factory to use to create new form items.
+     */
+    private DefaultFileItemFactory fileItemFactory;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an instance of this class which uses the default factory to
+     * create <code>FileItem</code> instances.
+     *
+     * @see #DiskFileUpload(DefaultFileItemFactory fileItemFactory)
+     */
+    public DiskFileUpload()
+    {
+        super();
+        this.fileItemFactory = new DefaultFileItemFactory();
+    }
+
+
+    /**
+     * Constructs an instance of this class which uses the supplied factory to
+     * create <code>FileItem</code> instances.
+     *
+     * @see #DiskFileUpload()
+     */
+    public DiskFileUpload(DefaultFileItemFactory fileItemFactory)
+    {
+        super();
+        this.fileItemFactory = fileItemFactory;
+    }
+
+
+    // ----------------------------------------------------- Property accessors
+
+
+    /**
+     * Returns the factory class used when creating file items.
+     *
+     * @return The factory class for new file items.
+     */
+    public FileItemFactory getFileItemFactory()
+    {
+        return fileItemFactory;
+    }
+
+
+    /**
+     * Sets the factory class to use when creating file items. The factory must
+     * be an instance of <code>DefaultFileItemFactory</code> or a subclass
+     * thereof, or else a <code>ClassCastException</code> will be thrown.
+     *
+     * @param factory The factory class for new file items.
+     */
+    public void setFileItemFactory(FileItemFactory factory)
+    {
+        this.fileItemFactory = (DefaultFileItemFactory) factory;
+    }
+
+
+    /**
+     * Returns the size threshold beyond which files are written directly to
+     * disk.
+     *
+     * @return The size threshold, in bytes.
+     *
+     * @see #setSizeThreshold(int)
+     */
+    public int getSizeThreshold()
+    {
+        return fileItemFactory.getSizeThreshold();
+    }
+
+
+    /**
+     * Sets the size threshold beyond which files are written directly to disk.
+     *
+     * @param sizeThreshold The size threshold, in bytes.
+     *
+     * @see #getSizeThreshold()
+     */
+    public void setSizeThreshold(int sizeThreshold)
+    {
+        fileItemFactory.setSizeThreshold(sizeThreshold);
+    }
+
+
+    /**
+     * Returns the location used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @return The path to the temporary file location.
+     *
+     * @see #setRepositoryPath(String)
+     */
+    public String getRepositoryPath()
+    {
+        return fileItemFactory.getRepository().getPath();
+    }
+
+
+    /**
+     * Sets the location used to temporarily store files that are larger
+     * than the configured size threshold.
+     *
+     * @param repositoryPath The path to the temporary file location.
+     *
+     * @see #getRepositoryPath()
+     */
+    public void setRepositoryPath(String repositoryPath)
+    {
+        fileItemFactory.setRepository(new File(repositoryPath));
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream. If files are stored
+     * on disk, the path is given by <code>getRepository()</code>.
+     *
+     * @param req           The servlet request to be parsed. Must be non-null.
+     * @param sizeThreshold The max size in bytes to be stored in memory.
+     * @param sizeMax       The maximum allowed upload size, in bytes.
+     * @param path          The location where the files should be stored.
+     *
+     * @return A list of <code>FileItem</code> instances parsed from the
+     *         request, in the order that they were transmitted.
+     *
+     * @exception FileUploadException if there are problems reading/parsing
+     *                                the request or storing files.
+     */
+    public List /* FileItem */ parseRequest(HttpServletRequest req,
+                                            int sizeThreshold,
+                                            long sizeMax, String path)
+        throws FileUploadException
+    {
+        setSizeThreshold(sizeThreshold);
+        setSizeMax(sizeMax);
+        setRepositoryPath(path);
+        return parseRequest(req);
+    }
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItem.java b/java/org/apache/tomcat/util/http/fileupload/FileItem.java
new file mode 100644 (file)
index 0000000..5edecda
--- /dev/null
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * <p> This class represents a file or form item that was received within a
+ * <code>multipart/form-data</code> POST request.
+ *
+ * <p> After retrieving an instance of this class from a {@link
+ * org.apache.tomcat.util.http.fileupload.FileUpload FileUpload} instance (see
+ * {@link org.apache.tomcat.util.http.fileupload.FileUpload
+ * #parseRequest(javax.servlet.http.HttpServletRequest)}), you may
+ * either request all contents of the file at once using {@link #get()} or
+ * request an {@link java.io.InputStream InputStream} with
+ * {@link #getInputStream()} and process the file without attempting to load
+ * it into memory, which may come handy with large files.
+ *
+ * <p> While this interface does not extend
+ * <code>javax.activation.DataSource</code> per se (to avoid a seldom used
+ * dependency), several of the defined methods are specifically defined with
+ * the same signatures as methods in that interface. This allows an
+ * implementation of this interface to also implement
+ * <code>javax.activation.DataSource</code> with minimal additional work.
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:sean@informage.net">Sean Legassick</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @version $Id: FileItem.java,v 1.15 2003/06/01 17:33:24 martinc Exp $
+ */
+public interface FileItem
+    extends Serializable
+{
+
+
+    // ------------------------------- Methods from javax.activation.DataSource
+
+
+    /**
+     * Returns an {@link java.io.InputStream InputStream} that can be
+     * used to retrieve the contents of the file.
+     *
+     * @return An {@link java.io.InputStream InputStream} that can be
+     *         used to retrieve the contents of the file.
+     *
+     * @exception IOException if an error occurs.
+     */
+    InputStream getInputStream()
+        throws IOException;
+
+
+    /**
+     * Returns the content type passed by the browser or <code>null</code> if
+     * not defined.
+     *
+     * @return The content type passed by the browser or <code>null</code> if
+     *         not defined.
+     */
+    String getContentType();
+
+
+    /**
+     * Returns the original filename in the client's filesystem, as provided by
+     * the browser (or other client software). In most cases, this will be the
+     * base file name, without path information. However, some clients, such as
+     * the Opera browser, do include path information.
+     *
+     * @return The original filename in the client's filesystem.
+     */
+    String getName();
+
+
+    // ------------------------------------------------------- FileItem methods
+
+
+    /**
+     * Provides a hint as to whether or not the file contents will be read
+     * from memory.
+     *
+     * @return <code>true</code> if the file contents will be read from memory;
+     *         <code>false</code> otherwise.
+     */
+    boolean isInMemory();
+
+
+    /**
+     * Returns the size of the file item.
+     *
+     * @return The size of the file item, in bytes.
+     */
+    long getSize();
+
+
+    /**
+     * Returns the contents of the file item as an array of bytes.
+     *
+     * @return The contents of the file item as an array of bytes.
+     */
+    byte[] get();
+
+
+    /**
+     * Returns the contents of the file item as a String, using the specified
+     * encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the item.
+     *
+     * @param encoding The character encoding to use.
+     *
+     * @return The contents of the item, as a string.
+     *
+     * @exception UnsupportedEncodingException if the requested character
+     *                                         encoding is not available.
+     */
+    String getString(String encoding)
+        throws UnsupportedEncodingException;
+
+
+    /**
+     * Returns the contents of the file item as a String, using the default
+     * character encoding.  This method uses {@link #get()} to retrieve the
+     * contents of the item.
+     *
+     * @return The contents of the item, as a string.
+     */
+    String getString();
+
+
+    /**
+     * A convenience method to write an uploaded item to disk. The client code
+     * is not concerned with whether or not the item is stored in memory, or on
+     * disk in a temporary location. They just want to write the uploaded item
+     * to a file.
+     * <p>
+     * This method is not guaranteed to succeed if called more than once for
+     * the same item. This allows a particular implementation to use, for
+     * example, file renaming, where possible, rather than copying all of the
+     * underlying data, thus gaining a significant performance benefit.
+     *
+     * @param file The <code>File</code> into which the uploaded item should
+     *             be stored.
+     *
+     * @exception Exception if an error occurs.
+     */
+    void write(File file) throws Exception;
+
+
+    /**
+     * Deletes the underlying storage for a file item, including deleting any
+     * associated temporary disk file. Although this storage will be deleted
+     * automatically when the <code>FileItem</code> instance is garbage
+     * collected, this method can be used to ensure that this is done at an
+     * earlier time, thus preserving system resources.
+     */
+    void delete();
+
+
+    /**
+     * Returns the name of the field in the multipart form corresponding to
+     * this file item.
+     *
+     * @return The name of the form field.
+     */
+    String getFieldName();
+
+
+    /**
+     * Sets the field name used to reference this file item.
+     *
+     * @param name The name of the form field.
+     */
+    void setFieldName(String name);
+
+
+    /**
+     * Determines whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @return <code>true</code> if the instance represents a simple form
+     *         field; <code>false</code> if it represents an uploaded file.
+     */
+    boolean isFormField();
+
+
+    /**
+     * Specifies whether or not a <code>FileItem</code> instance represents
+     * a simple form field.
+     *
+     * @param state <code>true</code> if the instance represents a simple form
+     *              field; <code>false</code> if it represents an uploaded file.
+     */
+    void setFormField(boolean state);
+
+
+    /**
+     * Returns an {@link java.io.OutputStream OutputStream} that can
+     * be used for storing the contents of the file.
+     *
+     * @return An {@link java.io.OutputStream OutputStream} that can be used
+     *         for storing the contensts of the file.
+     *
+     * @exception IOException if an error occurs.
+     */
+    OutputStream getOutputStream() throws IOException;
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java b/java/org/apache/tomcat/util/http/fileupload/FileItemFactory.java
new file mode 100644 (file)
index 0000000..b85dbe8
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+/**
+ * <p>A factory interface for creating {@link FileItem} instances. Factories
+ * can provide their own custom configuration, over and above that provided
+ * by the default file upload implementation.</p>
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * 
+ * @version $Id: FileItemFactory.java,v 1.1 2003/04/27 17:30:06 martinc Exp $
+ */
+public interface FileItemFactory
+{
+
+    /**
+     * Create a new {@link FileItem} instance from the supplied parameters and
+     * any local factory configuration.
+     *
+     * @param fieldName   The name of the form field.
+     * @param contentType The content type of the form field.
+     * @param isFormField <code>true</code> if this is a plain form field;
+     *                    <code>false</code> otherwise.
+     * @param fileName    The name of the uploaded file, if any, as supplied
+     *                    by the browser or other client.
+     *
+     * @return The newly created file item.
+     */
+    FileItem createItem(
+            String fieldName,
+            String contentType,
+            boolean isFormField,
+            String fileName
+            );
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUpload.java b/java/org/apache/tomcat/util/http/fileupload/FileUpload.java
new file mode 100644 (file)
index 0000000..af4dfd8
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+/**
+ * <p>High level API for processing file uploads.</p>
+ *
+ * <p>This class handles multiple files per single HTML widget, sent using
+ * <code>multipart/mixed</code> encoding type, as specified by
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
+ * #parseRequest(HttpServletRequest)} to acquire a list of {@link
+ * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
+ * widget.</p>
+ *
+ * <p>How the data for individual parts is stored is determined by the factory
+ * used to create them; a given part may be in memory, on disk, or somewhere
+ * else.</p>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: FileUpload.java,v 1.23 2003/06/24 05:45:43 martinc Exp $
+ */
+public class FileUpload
+    extends FileUploadBase
+ {
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The factory to use to create new form items.
+     */
+    private FileItemFactory fileItemFactory;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an instance of this class which uses the default factory to
+     * create <code>FileItem</code> instances.
+     *
+     * @see #FileUpload(FileItemFactory)
+     */
+    public FileUpload()
+    {
+        super();
+    }
+
+
+    /**
+     * Constructs an instance of this class which uses the supplied factory to
+     * create <code>FileItem</code> instances.
+     *
+     * @see #FileUpload()
+     */
+    public FileUpload(FileItemFactory fileItemFactory)
+    {
+        super();
+        this.fileItemFactory = fileItemFactory;
+    }
+
+
+    // ----------------------------------------------------- Property accessors
+
+
+    /**
+     * Returns the factory class used when creating file items.
+     *
+     * @return The factory class for new file items.
+     */
+    public FileItemFactory getFileItemFactory()
+    {
+        return fileItemFactory;
+    }
+
+
+    /**
+     * Sets the factory class to use when creating file items.
+     *
+     * @param factory The factory class for new file items.
+     */
+    public void setFileItemFactory(FileItemFactory factory)
+    {
+        this.fileItemFactory = factory;
+    }
+
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadBase.java
new file mode 100644 (file)
index 0000000..1bfa378
--- /dev/null
@@ -0,0 +1,640 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * <p>High level API for processing file uploads.</p>
+ *
+ * <p>This class handles multiple files per single HTML widget, sent using
+ * <code>multipart/mixed</code> encoding type, as specified by
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
+ * #parseRequest(HttpServletRequest)} to acquire a list of {@link
+ * org.apache.tomcat.util.http.fileupload.FileItem}s associated with a given HTML
+ * widget.</p>
+ *
+ * <p>How the data for individual parts is stored is determined by the factory
+ * used to create them; a given part may be in memory, on disk, or somewhere
+ * else.</p>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
+ * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
+ * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: FileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
+ */
+public abstract class FileUploadBase
+{
+
+    // ---------------------------------------------------------- Class methods
+
+
+    /**
+     * Utility method that determines whether the request contains multipart
+     * content.
+     *
+     * @param req The servlet request to be evaluated. Must be non-null.
+     *
+     * @return <code>true</code> if the request is multipart;
+     *         <code>false</code> otherwise.
+     */
+    public static final boolean isMultipartContent(HttpServletRequest req)
+    {
+        String contentType = req.getHeader(CONTENT_TYPE);
+        if (contentType == null)
+        {
+            return false;
+        }
+        if (contentType.startsWith(MULTIPART))
+        {
+            return true;
+        }
+        return false;
+    }
+
+
+    // ----------------------------------------------------- Manifest constants
+
+
+    /**
+     * HTTP content type header name.
+     */
+    public static final String CONTENT_TYPE = "Content-type";
+
+
+    /**
+     * HTTP content disposition header name.
+     */
+    public static final String CONTENT_DISPOSITION = "Content-disposition";
+
+
+    /**
+     * Content-disposition value for form data.
+     */
+    public static final String FORM_DATA = "form-data";
+
+
+    /**
+     * Content-disposition value for file attachment.
+     */
+    public static final String ATTACHMENT = "attachment";
+
+
+    /**
+     * Part of HTTP content type header.
+     */
+    public static final String MULTIPART = "multipart/";
+
+
+    /**
+     * HTTP content type header for multipart forms.
+     */
+    public static final String MULTIPART_FORM_DATA = "multipart/form-data";
+
+
+    /**
+     * HTTP content type header for multiple uploads.
+     */
+    public static final String MULTIPART_MIXED = "multipart/mixed";
+
+
+    /**
+     * The maximum length of a single header line that will be parsed
+     * (1024 bytes).
+     */
+    public static final int MAX_HEADER_SIZE = 1024;
+
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The maximum size permitted for an uploaded file. A value of -1 indicates
+     * no maximum.
+     */
+    private long sizeMax = -1;
+
+
+    /**
+     * The content encoding to use when reading part headers.
+     */
+    private String headerEncoding;
+
+
+    // ----------------------------------------------------- Property accessors
+
+
+    /**
+     * Returns the factory class used when creating file items.
+     *
+     * @return The factory class for new file items.
+     */
+    public abstract FileItemFactory getFileItemFactory();
+
+
+    /**
+     * Sets the factory class to use when creating file items.
+     *
+     * @param factory The factory class for new file items.
+     */
+    public abstract void setFileItemFactory(FileItemFactory factory);
+
+
+    /**
+     * Returns the maximum allowed upload size.
+     *
+     * @return The maximum allowed size, in bytes.
+     *
+     * @see #setSizeMax(long)
+     *
+     */
+    public long getSizeMax()
+    {
+        return sizeMax;
+    }
+
+
+    /**
+     * Sets the maximum allowed upload size. If negative, there is no maximum.
+     *
+     * @param sizeMax The maximum allowed size, in bytes, or -1 for no maximum.
+     *
+     * @see #getSizeMax()
+     *
+     */
+    public void setSizeMax(long sizeMax)
+    {
+        this.sizeMax = sizeMax;
+    }
+
+
+    /**
+     * Retrieves the character encoding used when reading the headers of an
+     * individual part. When not specified, or <code>null</code>, the platform
+     * default encoding is used.
+     *
+     * @return The encoding used to read part headers.
+     */
+    public String getHeaderEncoding()
+    {
+        return headerEncoding;
+    }
+
+
+    /**
+     * Specifies the character encoding to be used when reading the headers of
+     * individual parts. When not specified, or <code>null</code>, the platform
+     * default encoding is used.
+     *
+     * @param encoding The encoding used to read part headers.
+     */
+    public void setHeaderEncoding(String encoding)
+    {
+        headerEncoding = encoding;
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
+     * compliant <code>multipart/form-data</code> stream. If files are stored
+     * on disk, the path is given by <code>getRepository()</code>.
+     *
+     * @param req The servlet request to be parsed.
+     *
+     * @return A list of <code>FileItem</code> instances parsed from the
+     *         request, in the order that they were transmitted.
+     *
+     * @exception FileUploadException if there are problems reading/parsing
+     *                                the request or storing files.
+     */
+    public List /* FileItem */ parseRequest(HttpServletRequest req)
+        throws FileUploadException
+    {
+        if (null == req)
+        {
+            throw new NullPointerException("req parameter");
+        }
+
+        ArrayList items = new ArrayList();
+        String contentType = req.getHeader(CONTENT_TYPE);
+
+        if ((null == contentType) || (!contentType.startsWith(MULTIPART)))
+        {
+            throw new InvalidContentTypeException(
+                "the request doesn't contain a "
+                + MULTIPART_FORM_DATA
+                + " or "
+                + MULTIPART_MIXED
+                + " stream, content type header is "
+                + contentType);
+        }
+        int requestSize = req.getContentLength();
+
+        if (requestSize == -1)
+        {
+            throw new UnknownSizeException(
+                "the request was rejected because it's size is unknown");
+        }
+
+        if (sizeMax >= 0 && requestSize > sizeMax)
+        {
+            throw new SizeLimitExceededException(
+                "the request was rejected because "
+                + "it's size exceeds allowed range");
+        }
+
+        try
+        {
+            int boundaryIndex = contentType.indexOf("boundary=");
+            if (boundaryIndex < 0)
+            {
+                throw new FileUploadException(
+                        "the request was rejected because "
+                        + "no multipart boundary was found");
+            }
+            byte[] boundary = contentType.substring(
+                    boundaryIndex + 9).getBytes();
+
+            InputStream input = req.getInputStream();
+
+            MultipartStream multi = new MultipartStream(input, boundary);
+            multi.setHeaderEncoding(headerEncoding);
+
+            boolean nextPart = multi.skipPreamble();
+            while (nextPart)
+            {
+                Map headers = parseHeaders(multi.readHeaders());
+                String fieldName = getFieldName(headers);
+                if (fieldName != null)
+                {
+                    String subContentType = getHeader(headers, CONTENT_TYPE);
+                    if (subContentType != null && subContentType
+                                                .startsWith(MULTIPART_MIXED))
+                    {
+                        // Multiple files.
+                        byte[] subBoundary =
+                            subContentType.substring(
+                                subContentType
+                                .indexOf("boundary=") + 9).getBytes();
+                        multi.setBoundary(subBoundary);
+                        boolean nextSubPart = multi.skipPreamble();
+                        while (nextSubPart)
+                        {
+                            headers = parseHeaders(multi.readHeaders());
+                            if (getFileName(headers) != null)
+                            {
+                                FileItem item =
+                                        createItem(headers, false);
+                                OutputStream os = item.getOutputStream();
+                                try
+                                {
+                                    multi.readBodyData(os);
+                                }
+                                finally
+                                {
+                                    os.close();
+                                }
+                                items.add(item);
+                            }
+                            else
+                            {
+                                // Ignore anything but files inside
+                                // multipart/mixed.
+                                multi.discardBodyData();
+                            }
+                            nextSubPart = multi.readBoundary();
+                        }
+                        multi.setBoundary(boundary);
+                    }
+                    else
+                    {
+                        if (getFileName(headers) != null)
+                        {
+                            // A single file.
+                            FileItem item = createItem(headers, false);
+                            OutputStream os = item.getOutputStream();
+                            try
+                            {
+                                multi.readBodyData(os);
+                            }
+                            finally
+                            {
+                                os.close();
+                            }
+                            items.add(item);
+                        }
+                        else
+                        {
+                            // A form field.
+                            FileItem item = createItem(headers, true);
+                            OutputStream os = item.getOutputStream();
+                            try
+                            {
+                                multi.readBodyData(os);
+                            }
+                            finally
+                            {
+                                os.close();
+                            }
+                            items.add(item);
+                        }
+                    }
+                }
+                else
+                {
+                    // Skip this part.
+                    multi.discardBodyData();
+                }
+                nextPart = multi.readBoundary();
+            }
+        }
+        catch (IOException e)
+        {
+            throw new FileUploadException(
+                "Processing of " + MULTIPART_FORM_DATA
+                    + " request failed. " + e.getMessage());
+        }
+
+        return items;
+    }
+
+
+    // ------------------------------------------------------ Protected methods
+
+
+    /**
+     * Retrieves the file name from the <code>Content-disposition</code>
+     * header.
+     *
+     * @param headers A <code>Map</code> containing the HTTP request headers.
+     *
+     * @return The file name for the current <code>encapsulation</code>.
+     */
+    protected String getFileName(Map /* String, String */ headers)
+    {
+        String fileName = null;
+        String cd = getHeader(headers, CONTENT_DISPOSITION);
+        if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))
+        {
+            int start = cd.indexOf("filename=\"");
+            int end = cd.indexOf('"', start + 10);
+            if (start != -1 && end != -1)
+            {
+                fileName = cd.substring(start + 10, end).trim();
+            }
+        }
+        return fileName;
+    }
+
+
+    /**
+     * Retrieves the field name from the <code>Content-disposition</code>
+     * header.
+     *
+     * @param headers A <code>Map</code> containing the HTTP request headers.
+     *
+     * @return The field name for the current <code>encapsulation</code>.
+     */
+    protected String getFieldName(Map /* String, String */ headers)
+    {
+        String fieldName = null;
+        String cd = getHeader(headers, CONTENT_DISPOSITION);
+        if (cd != null && cd.startsWith(FORM_DATA))
+        {
+            int start = cd.indexOf("name=\"");
+            int end = cd.indexOf('"', start + 6);
+            if (start != -1 && end != -1)
+            {
+                fieldName = cd.substring(start + 6, end);
+            }
+        }
+        return fieldName;
+    }
+
+
+    /**
+     * Creates a new {@link FileItem} instance.
+     *
+     * @param headers       A <code>Map</code> containing the HTTP request
+     *                      headers.
+     * @param isFormField   Whether or not this item is a form field, as
+     *                      opposed to a file.
+     *
+     * @return A newly created <code>FileItem</code> instance.
+     *
+     * @exception FileUploadException if an error occurs.
+     */
+    protected FileItem createItem(Map /* String, String */ headers,
+                                  boolean isFormField)
+        throws FileUploadException
+    {
+        return getFileItemFactory().createItem(getFieldName(headers),
+                getHeader(headers, CONTENT_TYPE),
+                isFormField,
+                getFileName(headers));
+    }
+
+
+    /**
+     * <p> Parses the <code>header-part</code> and returns as key/value
+     * pairs.
+     *
+     * <p> If there are multiple headers of the same names, the name
+     * will map to a comma-separated list containing the values.
+     *
+     * @param headerPart The <code>header-part</code> of the current
+     *                   <code>encapsulation</code>.
+     *
+     * @return A <code>Map</code> containing the parsed HTTP request headers.
+     */
+    protected Map /* String, String */ parseHeaders(String headerPart)
+    {
+        Map headers = new HashMap();
+        char buffer[] = new char[MAX_HEADER_SIZE];
+        boolean done = false;
+        int j = 0;
+        int i;
+        String header, headerName, headerValue;
+        try
+        {
+            while (!done)
+            {
+                i = 0;
+                // Copy a single line of characters into the buffer,
+                // omitting trailing CRLF.
+                while (i < 2 || buffer[i - 2] != '\r' || buffer[i - 1] != '\n')
+                {
+                    buffer[i++] = headerPart.charAt(j++);
+                }
+                header = new String(buffer, 0, i - 2);
+                if (header.equals(""))
+                {
+                    done = true;
+                }
+                else
+                {
+                    if (header.indexOf(':') == -1)
+                    {
+                        // This header line is malformed, skip it.
+                        continue;
+                    }
+                    headerName = header.substring(0, header.indexOf(':'))
+                        .trim().toLowerCase();
+                    headerValue =
+                        header.substring(header.indexOf(':') + 1).trim();
+                    if (getHeader(headers, headerName) != null)
+                    {
+                        // More that one heder of that name exists,
+                        // append to the list.
+                        headers.put(headerName,
+                                    getHeader(headers, headerName) + ','
+                                        + headerValue);
+                    }
+                    else
+                    {
+                        headers.put(headerName, headerValue);
+                    }
+                }
+            }
+        }
+        catch (IndexOutOfBoundsException e)
+        {
+            // Headers were malformed. continue with all that was
+            // parsed.
+        }
+        return headers;
+    }
+
+
+    /**
+     * Returns the header with the specified name from the supplied map. The
+     * header lookup is case-insensitive.
+     *
+     * @param headers A <code>Map</code> containing the HTTP request headers.
+     * @param name    The name of the header to return.
+     *
+     * @return The value of specified header, or a comma-separated list if
+     *         there were multiple headers of that name.
+     */
+    protected final String getHeader(Map /* String, String */ headers,
+                                     String name)
+    {
+        return (String) headers.get(name.toLowerCase());
+    }
+
+
+    /**
+     * Thrown to indicate that the request is not a multipart request.
+     */
+    public static class InvalidContentTypeException
+        extends FileUploadException
+    {
+        /**
+         * Constructs a <code>InvalidContentTypeException</code> with no
+         * detail message.
+         */
+        public InvalidContentTypeException()
+        {
+            super();
+        }
+
+        /**
+         * Constructs an <code>InvalidContentTypeException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public InvalidContentTypeException(String message)
+        {
+            super(message);
+        }
+    }
+
+
+    /**
+     * Thrown to indicate that the request size is not specified.
+     */
+    public static class UnknownSizeException
+        extends FileUploadException
+    {
+        /**
+         * Constructs a <code>UnknownSizeException</code> with no
+         * detail message.
+         */
+        public UnknownSizeException()
+        {
+            super();
+        }
+
+        /**
+         * Constructs an <code>UnknownSizeException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public UnknownSizeException(String message)
+        {
+            super(message);
+        }
+    }
+
+
+    /**
+     * Thrown to indicate that the request size exceeds the configured maximum.
+     */
+    public static class SizeLimitExceededException
+        extends FileUploadException
+    {
+        /**
+         * Constructs a <code>SizeExceededException</code> with no
+         * detail message.
+         */
+        public SizeLimitExceededException()
+        {
+            super();
+        }
+
+        /**
+         * Constructs an <code>SizeExceededException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public SizeLimitExceededException(String message)
+        {
+            super(message);
+        }
+    }
+
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java b/java/org/apache/tomcat/util/http/fileupload/FileUploadException.java
new file mode 100644 (file)
index 0000000..bbb21db
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+/**
+ * Exception for errors encountered while processing the request.
+ *
+ * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
+ * @version $Id: FileUploadException.java,v 1.7 2003/04/27 17:30:06 martinc Exp $
+ */
+public class FileUploadException
+    extends Exception
+{
+
+    /**
+     * Constructs a new <code>FileUploadException</code> without message.
+     */
+    public FileUploadException()
+    {
+    }
+
+    /**
+     * Constructs a new <code>FileUploadException</code> with specified detail
+     * message.
+     *
+     * @param msg the error message.
+     */
+    public FileUploadException(String msg)
+    {
+        super(msg);
+    }
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java b/java/org/apache/tomcat/util/http/fileupload/MultipartStream.java
new file mode 100644 (file)
index 0000000..97a22d1
--- /dev/null
@@ -0,0 +1,891 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+
+/**
+ * <p> Low level API for processing file uploads.
+ *
+ * <p> This class can be used to process data streams conforming to MIME
+ * 'multipart' format as defined in
+ * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Arbitrarily
+ * large amounts of data in the stream can be processed under constant
+ * memory usage.
+ *
+ * <p> The format of the stream is defined in the following way:<br>
+ *
+ * <code>
+ *   multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
+ *   encapsulation := delimiter body CRLF<br>
+ *   delimiter := "--" boundary CRLF<br>
+ *   close-delimiter := "--" boudary "--"<br>
+ *   preamble := &lt;ignore&gt;<br>
+ *   epilogue := &lt;ignore&gt;<br>
+ *   body := header-part CRLF body-part<br>
+ *   header-part := 1*header CRLF<br>
+ *   header := header-name ":" header-value<br>
+ *   header-name := &lt;printable ascii characters except ":"&gt;<br>
+ *   header-value := &lt;any ascii characters except CR & LF&gt;<br>
+ *   body-data := &lt;arbitrary data&gt;<br>
+ * </code>
+ *
+ * <p>Note that body-data can contain another mulipart entity.  There
+ * is limited support for single pass processing of such nested
+ * streams.  The nested stream is <strong>required</strong> to have a
+ * boundary token of the same length as the parent stream (see {@link
+ * #setBoundary(byte[])}).
+ *
+ * <p>Here is an exaple of usage of this class.<br>
+ *
+ * <pre>
+ *    try {
+ *        MultipartStream multipartStream = new MultipartStream(input,
+ *                                                              boundary);
+ *        boolean nextPart = malitPartStream.skipPreamble();
+ *        OutputStream output;
+ *        while(nextPart) {
+ *            header = chunks.readHeader();
+ *            // process headers
+ *            // create some output stream
+ *            multipartStream.readBodyPart(output);
+ *            nextPart = multipartStream.readBoundary();
+ *        }
+ *    } catch(MultipartStream.MalformedStreamException e) {
+ *          // the stream failed to follow required syntax
+ *    } catch(IOException) {
+ *          // a read or write error occurred
+ *    }
+ *
+ * </pre>
+ *
+ * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * @author Sean C. Sullivan
+ *
+ * @version $Id: MultipartStream.java,v 1.12 2003/06/01 00:18:13 martinc Exp $
+ */
+public class MultipartStream
+{
+
+    // ----------------------------------------------------- Manifest constants
+
+
+    /**
+     * The maximum length of <code>header-part</code> that will be
+     * processed (10 kilobytes = 10240 bytes.).
+     */
+    public static final int HEADER_PART_SIZE_MAX = 10240;
+
+
+    /**
+     * The default length of the buffer used for processing a request.
+     */
+    protected static final int DEFAULT_BUFSIZE = 4096;
+
+
+    /**
+     * A byte sequence that marks the end of <code>header-part</code>
+     * (<code>CRLFCRLF</code>).
+     */
+    protected static final byte[] HEADER_SEPARATOR = {0x0D, 0x0A, 0x0D, 0x0A};
+
+
+    /**
+     * A byte sequence that that follows a delimiter that will be
+     * followed by an encapsulation (<code>CRLF</code>).
+     */
+    protected static final byte[] FIELD_SEPARATOR = { 0x0D, 0x0A };
+
+
+    /**
+     * A byte sequence that that follows a delimiter of the last
+     * encapsulation in the stream (<code>--</code>).
+     */
+    protected static final byte[] STREAM_TERMINATOR = { 0x2D, 0x2D };
+
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The input stream from which data is read.
+     */
+    private InputStream input;
+
+
+    /**
+     * The length of the boundary token plus the leading <code>CRLF--</code>.
+     */
+    private int boundaryLength;
+
+
+    /**
+     * The amount of data, in bytes, that must be kept in the buffer in order
+     * to detect delimiters reliably.
+     */
+    private int keepRegion;
+
+
+    /**
+     * The byte sequence that partitions the stream.
+     */
+    private byte[] boundary;
+
+
+    /**
+     * The length of the buffer used for processing the request.
+     */
+    private int bufSize;
+
+
+    /**
+     * The buffer used for processing the request.
+     */
+    private byte[] buffer;
+
+
+    /**
+     * The index of first valid character in the buffer.
+     * <br>
+     * 0 <= head < bufSize
+     */
+    private int head;
+
+
+    /**
+     * The index of last valid characer in the buffer + 1.
+     * <br>
+     * 0 <= tail <= bufSize
+     */
+    private int tail;
+
+
+    /**
+     * The content encoding to use when reading headers.
+     */
+    private String headerEncoding;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor.
+     *
+     * @see #MultipartStream(InputStream, byte[], int)
+     * @see #MultipartStream(InputStream, byte[])
+     *
+     */
+    public MultipartStream()
+    {
+    }
+
+
+    /**
+     * <p> Constructs a <code>MultipartStream</code> with a custom size buffer.
+     *
+     * <p> Note that the buffer must be at least big enough to contain the
+     * boundary string, plus 4 characters for CR/LF and double dash, plus at
+     * least one byte of data.  Too small a buffer size setting will degrade
+     * performance.
+     *
+     * @param input    The <code>InputStream</code> to serve as a data source.
+     * @param boundary The token used for dividing the stream into
+     *                 <code>encapsulations</code>.
+     * @param bufSize  The size of the buffer to be used, in bytes.
+     *
+     *
+     * @see #MultipartStream()
+     * @see #MultipartStream(InputStream, byte[])
+     *
+     */
+    public MultipartStream(InputStream input,
+                           byte[] boundary,
+                           int bufSize)
+    {
+        this.input = input;
+        this.bufSize = bufSize;
+        this.buffer = new byte[bufSize];
+
+        // We prepend CR/LF to the boundary to chop trailng CR/LF from
+        // body-data tokens.
+        this.boundary = new byte[boundary.length + 4];
+        this.boundaryLength = boundary.length + 4;
+        this.keepRegion = boundary.length + 3;
+        this.boundary[0] = 0x0D;
+        this.boundary[1] = 0x0A;
+        this.boundary[2] = 0x2D;
+        this.boundary[3] = 0x2D;
+        System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
+
+        head = 0;
+        tail = 0;
+    }
+
+
+    /**
+     * <p> Constructs a <code>MultipartStream</code> with a default size buffer.
+     *
+     * @param input    The <code>InputStream</code> to serve as a data source.
+     * @param boundary The token used for dividing the stream into
+     *                 <code>encapsulations</code>.
+     *
+     * @exception IOException when an error occurs.
+     *
+     * @see #MultipartStream()
+     * @see #MultipartStream(InputStream, byte[], int)
+     *
+     */
+    public MultipartStream(InputStream input,
+                           byte[] boundary)
+        throws IOException
+    {
+        this(input, boundary, DEFAULT_BUFSIZE);
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Retrieves the character encoding used when reading the headers of an
+     * individual part. When not specified, or <code>null</code>, the platform
+     * default encoding is used.
+
+     *
+     * @return The encoding used to read part headers.
+     */
+    public String getHeaderEncoding()
+    {
+        return headerEncoding;
+    }
+
+
+    /**
+     * Specifies the character encoding to be used when reading the headers of
+     * individual parts. When not specified, or <code>null</code>, the platform
+     * default encoding is used.
+     *
+     * @param encoding The encoding used to read part headers.
+     */
+    public void setHeaderEncoding(String encoding)
+    {
+        headerEncoding = encoding;
+    }
+
+
+    /**
+     * Reads a byte from the <code>buffer</code>, and refills it as
+     * necessary.
+     *
+     * @return The next byte from the input stream.
+     *
+     * @exception IOException if there is no more data available.
+     */
+    public byte readByte()
+        throws IOException
+    {
+        // Buffer depleted ?
+        if (head == tail)
+        {
+            head = 0;
+            // Refill.
+            tail = input.read(buffer, head, bufSize);
+            if (tail == -1)
+            {
+                // No more data available.
+                throw new IOException("No more data is available");
+            }
+        }
+        return buffer[head++];
+    }
+
+
+    /**
+     * Skips a <code>boundary</code> token, and checks whether more
+     * <code>encapsulations</code> are contained in the stream.
+     *
+     * @return <code>true</code> if there are more encapsulations in
+     *         this stream; <code>false</code> otherwise.
+     *
+     * @exception MalformedStreamException if the stream ends unexpecetedly or
+     *                                     fails to follow required syntax.
+     */
+    public boolean readBoundary()
+        throws MalformedStreamException
+    {
+        byte[] marker = new byte[2];
+        boolean nextChunk = false;
+
+        head += boundaryLength;
+        try
+        {
+            marker[0] = readByte();
+            marker[1] = readByte();
+            if (arrayequals(marker, STREAM_TERMINATOR, 2))
+            {
+                nextChunk = false;
+            }
+            else if (arrayequals(marker, FIELD_SEPARATOR, 2))
+            {
+                nextChunk = true;
+            }
+            else
+            {
+                throw new MalformedStreamException(
+                        "Unexpected characters follow a boundary");
+            }
+        }
+        catch (IOException e)
+        {
+            throw new MalformedStreamException("Stream ended unexpectedly");
+        }
+        return nextChunk;
+    }
+
+
+    /**
+     * <p>Changes the boundary token used for partitioning the stream.
+     *
+     * <p>This method allows single pass processing of nested multipart
+     * streams.
+     *
+     * <p>The boundary token of the nested stream is <code>required</code>
+     * to be of the same length as the boundary token in parent stream.
+     *
+     * <p>Restoring the parent stream boundary token after processing of a
+     * nested stream is left to the application.
+     *
+     * @param boundary The boundary to be used for parsing of the nested
+     *                 stream.
+     *
+     * @exception IllegalBoundaryException if the <code>boundary</code>
+     *                                     has a different length than the one
+     *                                     being currently parsed.
+     */
+    public void setBoundary(byte[] boundary)
+        throws IllegalBoundaryException
+    {
+        if (boundary.length != boundaryLength - 4)
+        {
+            throw new IllegalBoundaryException(
+                    "The length of a boundary token can not be changed");
+        }
+        System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
+    }
+
+
+    /**
+     * <p>Reads the <code>header-part</code> of the current
+     * <code>encapsulation</code>.
+     *
+     * <p>Headers are returned verbatim to the input stream, including the
+     * trailing <code>CRLF</code> marker. Parsing is left to the
+     * application.
+     *
+     * <p><strong>TODO</strong> allow limiting maximum header size to
+     * protect against abuse.
+     *
+     * @return The <code>header-part</code> of the current encapsulation.
+     *
+     * @exception MalformedStreamException if the stream ends unexpecetedly.
+     */
+    public String readHeaders()
+        throws MalformedStreamException
+    {
+        int i = 0;
+        byte b[] = new byte[1];
+        // to support multi-byte characters
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        int sizeMax = HEADER_PART_SIZE_MAX;
+        int size = 0;
+        while (i < 4)
+        {
+            try
+            {
+                b[0] = readByte();
+            }
+            catch (IOException e)
+            {
+                throw new MalformedStreamException("Stream ended unexpectedly");
+            }
+            size++;
+            if (b[0] == HEADER_SEPARATOR[i])
+            {
+                i++;
+            }
+            else
+            {
+                i = 0;
+            }
+            if (size <= sizeMax)
+            {
+                baos.write(b[0]);
+            }
+        }
+
+        String headers = null;
+        if (headerEncoding != null)
+        {
+            try
+            {
+                headers = baos.toString(headerEncoding);
+            }
+            catch (UnsupportedEncodingException e)
+            {
+                // Fall back to platform default if specified encoding is not
+                // supported.
+                headers = baos.toString();
+            }
+        }
+        else
+        {
+            headers = baos.toString();
+        }
+
+        return headers;
+    }
+
+
+    /**
+     * <p>Reads <code>body-data</code> from the current
+     * <code>encapsulation</code> and writes its contents into the
+     * output <code>Stream</code>.
+     *
+     * <p>Arbitrary large amounts of data can be processed by this
+     * method using a constant size buffer. (see {@link
+     * #MultipartStream(InputStream,byte[],int) constructor}).
+     *
+     * @param output The <code>Stream</code> to write data into.
+     *
+     * @return the amount of data written.
+     *
+     * @exception MalformedStreamException if the stream ends unexpectedly.
+     * @exception IOException              if an i/o error occurs.
+     */
+    public int readBodyData(OutputStream output)
+        throws MalformedStreamException,
+               IOException
+    {
+        boolean done = false;
+        int pad;
+        int pos;
+        int bytesRead;
+        int total = 0;
+        while (!done)
+        {
+            // Is boundary token present somewere in the buffer?
+            pos = findSeparator();
+            if (pos != -1)
+            {
+                // Write the rest of the data before the boundary.
+                output.write(buffer, head, pos - head);
+                total += pos - head;
+                head = pos;
+                done = true;
+            }
+            else
+            {
+                // Determine how much data should be kept in the
+                // buffer.
+                if (tail - head > keepRegion)
+                {
+                    pad = keepRegion;
+                }
+                else
+                {
+                    pad = tail - head;
+                }
+                // Write out the data belonging to the body-data.
+                output.write(buffer, head, tail - head - pad);
+
+                // Move the data to the beging of the buffer.
+                total += tail - head - pad;
+                System.arraycopy(buffer, tail - pad, buffer, 0, pad);
+
+                // Refill buffer with new data.
+                head = 0;
+                bytesRead = input.read(buffer, pad, bufSize - pad);
+
+                // [pprrrrrrr]
+                if (bytesRead != -1)
+                {
+                    tail = pad + bytesRead;
+                }
+                else
+                {
+                    // The last pad amount is left in the buffer.
+                    // Boundary can't be in there so write out the
+                    // data you have and signal an error condition.
+                    output.write(buffer, 0, pad);
+                    output.flush();
+                    total += pad;
+                    throw new MalformedStreamException(
+                            "Stream ended unexpectedly");
+                }
+            }
+        }
+        output.flush();
+        return total;
+    }
+
+
+    /**
+     * <p> Reads <code>body-data</code> from the current
+     * <code>encapsulation</code> and discards it.
+     *
+     * <p>Use this method to skip encapsulations you don't need or don't
+     * understand.
+     *
+     * @return The amount of data discarded.
+     *
+     * @exception MalformedStreamException if the stream ends unexpectedly.
+     * @exception IOException              if an i/o error occurs.
+     */
+    public int discardBodyData()
+        throws MalformedStreamException,
+               IOException
+    {
+        boolean done = false;
+        int pad;
+        int pos;
+        int bytesRead;
+        int total = 0;
+        while (!done)
+        {
+            // Is boundary token present somewere in the buffer?
+            pos = findSeparator();
+            if (pos != -1)
+            {
+                // Write the rest of the data before the boundary.
+                total += pos - head;
+                head = pos;
+                done = true;
+            }
+            else
+            {
+                // Determine how much data should be kept in the
+                // buffer.
+                if (tail - head > keepRegion)
+                {
+                    pad = keepRegion;
+                }
+                else
+                {
+                    pad = tail - head;
+                }
+                total += tail - head - pad;
+
+                // Move the data to the beging of the buffer.
+                System.arraycopy(buffer, tail - pad, buffer, 0, pad);
+
+                // Refill buffer with new data.
+                head = 0;
+                bytesRead = input.read(buffer, pad, bufSize - pad);
+
+                // [pprrrrrrr]
+                if (bytesRead != -1)
+                {
+                    tail = pad + bytesRead;
+                }
+                else
+                {
+                    // The last pad amount is left in the buffer.
+                    // Boundary can't be in there so signal an error
+                    // condition.
+                    total += pad;
+                    throw new MalformedStreamException(
+                            "Stream ended unexpectedly");
+                }
+            }
+        }
+        return total;
+    }
+
+
+    /**
+     * Finds the beginning of the first <code>encapsulation</code>.
+     *
+     * @return <code>true</code> if an <code>encapsulation</code> was found in
+     *         the stream.
+     *
+     * @exception IOException if an i/o error occurs.
+     */
+    public boolean skipPreamble()
+        throws IOException
+    {
+        // First delimiter may be not preceeded with a CRLF.
+        System.arraycopy(boundary, 2, boundary, 0, boundary.length - 2);
+        boundaryLength = boundary.length - 2;
+        try
+        {
+            // Discard all data up to the delimiter.
+            discardBodyData();
+
+            // Read boundary - if succeded, the stream contains an
+            // encapsulation.
+            return readBoundary();
+        }
+        catch (MalformedStreamException e)
+        {
+            return false;
+        }
+        finally
+        {
+            // Restore delimiter.
+            System.arraycopy(boundary, 0, boundary, 2, boundary.length - 2);
+            boundaryLength = boundary.length;
+            boundary[0] = 0x0D;
+            boundary[1] = 0x0A;
+        }
+    }
+
+
+    /**
+     * Compares <code>count</code> first bytes in the arrays
+     * <code>a</code> and <code>b</code>.
+     *
+     * @param a     The first array to compare.
+     * @param b     The second array to compare.
+     * @param count How many bytes should be compared.
+     *
+     * @return <code>true</code> if <code>count</code> first bytes in arrays
+     *         <code>a</code> and <code>b</code> are equal.
+     */
+    public static boolean arrayequals(byte[] a,
+                                      byte[] b,
+                                      int count)
+    {
+        for (int i = 0; i < count; i++)
+        {
+            if (a[i] != b[i])
+            {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Searches for a byte of specified value in the <code>buffer</code>,
+     * starting at the specified <code>position</code>.
+     *
+     * @param value The value to find.
+     * @param pos   The starting position for searching.
+     *
+     * @return The position of byte found, counting from beginning of the
+     *         <code>buffer</code>, or <code>-1</code> if not found.
+     */
+    protected int findByte(byte value,
+                           int pos)
+    {
+        for (int i = pos; i < tail; i++)
+        {
+            if (buffer[i] == value)
+            {
+                return i;
+            }
+        }
+
+        return -1;
+    }
+
+
+    /**
+     * Searches for the <code>boundary</code> in the <code>buffer</code>
+     * region delimited by <code>head</code> and <code>tail</code>.
+     *
+     * @return The position of the boundary found, counting from the
+     *         beginning of the <code>buffer</code>, or <code>-1</code> if
+     *         not found.
+     */
+    protected int findSeparator()
+    {
+        int first;
+        int match = 0;
+        int maxpos = tail - boundaryLength;
+        for (first = head;
+             (first <= maxpos) && (match != boundaryLength);
+             first++)
+        {
+            first = findByte(boundary[0], first);
+            if (first == -1 || (first > maxpos))
+            {
+                return -1;
+            }
+            for (match = 1; match < boundaryLength; match++)
+            {
+                if (buffer[first + match] != boundary[match])
+                {
+                    break;
+                }
+            }
+        }
+        if (match == boundaryLength)
+        {
+            return first - 1;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns a string representation of this object.
+     *
+     * @return The string representation of this object.
+     */
+    public String toString()
+    {
+        StringBuffer sbTemp = new StringBuffer();
+        sbTemp.append("boundary='");
+        sbTemp.append(String.valueOf(boundary));
+        sbTemp.append("'\nbufSize=");
+        sbTemp.append(bufSize);
+        return sbTemp.toString();
+    }
+
+    /**
+     * Thrown to indicate that the input stream fails to follow the
+     * required syntax.
+     */
+    public class MalformedStreamException
+        extends IOException
+    {
+        /**
+         * Constructs a <code>MalformedStreamException</code> with no
+         * detail message.
+         */
+        public MalformedStreamException()
+        {
+            super();
+        }
+
+        /**
+         * Constructs an <code>MalformedStreamException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public MalformedStreamException(String message)
+        {
+            super(message);
+        }
+    }
+
+
+    /**
+     * Thrown upon attempt of setting an invalid boundary token.
+     */
+    public class IllegalBoundaryException
+        extends IOException
+    {
+        /**
+         * Constructs an <code>IllegalBoundaryException</code> with no
+         * detail message.
+         */
+        public IllegalBoundaryException()
+        {
+            super();
+        }
+
+        /**
+         * Constructs an <code>IllegalBoundaryException</code> with
+         * the specified detail message.
+         *
+         * @param message The detail message.
+         */
+        public IllegalBoundaryException(String message)
+        {
+            super(message);
+        }
+    }
+
+
+    // ------------------------------------------------------ Debugging methods
+
+
+    // These are the methods that were used to debug this stuff.
+    /*
+
+    // Dump data.
+    protected void dump()
+    {
+        System.out.println("01234567890");
+        byte[] temp = new byte[buffer.length];
+        for(int i=0; i<buffer.length; i++)
+        {
+            if (buffer[i] == 0x0D || buffer[i] == 0x0A)
+            {
+                temp[i] = 0x21;
+            }
+            else
+            {
+                temp[i] = buffer[i];
+            }
+        }
+        System.out.println(new String(temp));
+        int i;
+        for (i=0; i<head; i++)
+            System.out.print(" ");
+        System.out.println("h");
+        for (i=0; i<tail; i++)
+            System.out.print(" ");
+        System.out.println("t");
+        System.out.flush();
+    }
+
+    // Main routine, for testing purposes only.
+    //
+    // @param args A String[] with the command line arguments.
+    // @exception Exception, a generic exception.
+    public static void main( String[] args )
+        throws Exception
+    {
+        File boundaryFile = new File("boundary.dat");
+        int boundarySize = (int)boundaryFile.length();
+        byte[] boundary = new byte[boundarySize];
+        FileInputStream input = new FileInputStream(boundaryFile);
+        input.read(boundary,0,boundarySize);
+
+        input = new FileInputStream("multipart.dat");
+        MultipartStream chunks = new MultipartStream(input, boundary);
+
+        int i = 0;
+        String header;
+        OutputStream output;
+        boolean nextChunk = chunks.skipPreamble();
+        while (nextChunk)
+        {
+            header = chunks.readHeaders();
+            System.out.println("!"+header+"!");
+            System.out.println("wrote part"+i+".dat");
+            output = new FileOutputStream("part"+(i++)+".dat");
+            chunks.readBodyData(output);
+            nextChunk = chunks.readBoundary();
+        }
+    }
+
+    */
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java b/java/org/apache/tomcat/util/http/fileupload/ThresholdingOutputStream.java
new file mode 100644 (file)
index 0000000..f068c89
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2001-2006 The Apache Software Foundation.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.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 org.apache.tomcat.util.http.fileupload;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+/**
+ * An output stream which triggers an event when a specified number of bytes of
+ * data have been written to it. The event can be used, for example, to throw
+ * an exception if a maximum has been reached, or to switch the underlying
+ * stream type when the threshold is exceeded.
+ * <p>
+ * This class overrides all <code>OutputStream</code> methods. However, these
+ * overrides ultimately call the corresponding methods in the underlying output
+ * stream implementation.
+ * <p>
+ * NOTE: This implementation may trigger the event <em>before</em> the threshold
+ * is actually reached, since it triggers when a pending write operation would
+ * cause the threshold to be exceeded.
+ *
+ * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ *
+ * @version $Id: ThresholdingOutputStream.java,v 1.3 2003/05/31 22:31:08 martinc Exp $
+ */
+public abstract class ThresholdingOutputStream
+    extends OutputStream
+{
+
+    // ----------------------------------------------------------- Data members
+
+
+    /**
+     * The threshold at which the event will be triggered.
+     */
+    private int threshold;
+
+
+    /**
+     * The number of bytes written to the output stream.
+     */
+    private long written;
+
+
+    /**
+     * Whether or not the configured threshold has been exceeded.
+     */
+    private boolean thresholdExceeded;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Constructs an instance of this class which will trigger an event at the
+     * specified threshold.
+     *
+     * @param threshold The number of bytes at which to trigger an event.
+     */
+    public ThresholdingOutputStream(int threshold)
+    {
+        this.threshold = threshold;
+    }
+
+
+    // --------------------------------------------------- OutputStream methods
+
+
+    /**
+     * Writes the specified byte to this output stream.
+     *
+     * @param b The byte to be written.
+     *
+     * @exception IOException if an error occurs.
+     */
+    public void write(int b) throws IOException
+    {
+        checkThreshold(1);
+        getStream().write(b);
+        written++;
+    }
+
+
+    /**
+     * Writes <code>b.length</code> bytes from the specified byte array to this
+     * output stream.
+     *
+     * @param b The array of bytes to be written.
+     *
+     * @exception IOException if an error occurs.
+     */
+    public void write(byte b[]) throws IOException
+    {
+        checkThreshold(b.length);
+        getStream().write(b);
+        written += b.length;
+    }
+
+
+    /**
+     * Writes <code>len</code> bytes from the specified byte array starting at
+     * offset <code>off</code> to this output stream.
+     *
+     * @param b   The byte array from which the data will be written.
+     * @param off The start offset in the byte array.
+     * @param len The number of bytes to write.
+     *
+     * @exception IOException if an error occurs.
+     */
+    public void write(byte b[], int off, int len) throws IOException
+    {
+        checkThreshold(len);
+        getStream().write(b, off, len);
+        written += len;
+    }
+
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes to be
+     * written out.
+     *
+     * @exception IOException if an error occurs.
+     */
+    public void flush() throws IOException
+    {
+        getStream().flush();
+    }
+
+
+    /**
+     * Closes this output stream and releases any system resources associated
+     * with this stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    public void close() throws IOException
+    {
+        try
+        {
+            flush();
+        }
+        catch (IOException ignored)
+        {
+            // ignore
+        }
+        getStream().close();
+    }
+
+
+    // --------------------------------------------------------- Public methods
+
+
+    /**
+     * Returns the threshold, in bytes, at which an event will be triggered.
+     *
+     * @return The threshold point, in bytes.
+     */
+    public int getThreshold()
+    {
+        return threshold;
+    }
+
+
+    /**
+     * Returns the number of bytes that have been written to this output stream.
+     *
+     * @return The number of bytes written.
+     */
+    public long getByteCount()
+    {
+        return written;
+    }
+
+
+    /**
+     * Determines whether or not the configured threshold has been exceeded for
+     * this output stream.
+     *
+     * @return <code>true</code> if the threshold has been reached;
+     *         <code>false</code> otherwise.
+     */
+    public boolean isThresholdExceeded()
+    {
+        return (written > threshold);
+    }
+
+
+    // ------------------------------------------------------ Protected methods
+
+
+    /**
+     * Checks to see if writing the specified number of bytes would cause the
+     * configured threshold to be exceeded. If so, triggers an event to allow
+     * a concrete implementation to take action on this.
+     *
+     * @param count The number of bytes about to be written to the underlying
+     *              output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected void checkThreshold(int count) throws IOException
+    {
+        if (!thresholdExceeded && (written + count > threshold))
+        {
+            thresholdReached();
+            thresholdExceeded = true;
+        }
+    }
+
+
+    // ------------------------------------------------------- Abstract methods
+
+
+    /**
+     * Returns the underlying output stream, to which the corresponding
+     * <code>OutputStream</code> methods in this class will ultimately delegate.
+     *
+     * @return The underlying output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected abstract OutputStream getStream() throws IOException;
+
+
+    /**
+     * Indicates that the configured threshold has been reached, and that a
+     * subclass should take whatever action necessary on this event. This may
+     * include changing the underlying output stream.
+     *
+     * @exception IOException if an error occurs.
+     */
+    protected abstract void thresholdReached() throws IOException;
+}
diff --git a/java/org/apache/tomcat/util/http/fileupload/package.html b/java/org/apache/tomcat/util/http/fileupload/package.html
new file mode 100644 (file)
index 0000000..853871f
--- /dev/null
@@ -0,0 +1,66 @@
+<!-- $Id: package.html,v 1.3 2003/04/27 17:30:06 martinc Exp $ -->
+<html>
+   <head>
+      <title>Overview of the org.apache.commons.fileupload component</title>
+   </head>
+   <body>
+      <p>
+         Component for handling html file uploads as given by rfc 1867
+         <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC&nbsp;1867</a>.
+      </p>
+      <p>
+         Normal usage of the package involves
+         {@link org.apache.commons.fileupload.DiskFileUpload DiskFileUpload}
+         parsing the HttpServletRequest and returning a list of
+         {@link org.apache.commons.fileupload.FileItem FileItem}'s.
+         These <code>FileItem</code>'s provide easy access to the data
+         given in the upload.  There is also a low level api for
+         manipulating the upload data encapsulated in the
+         {@link org.apache.commons.fileupload.MultipartStream MultipartStream}
+         class.
+      </p>
+
+      <p>
+         Normal usage example:
+      </p>
+<pre>
+
+    public void doPost(HttpServletRequest req, HttpServletResponse res)
+    {
+        DiskFileUpload fu = new DiskFileUpload();
+        // maximum size before a FileUploadException will be thrown
+        fu.setSizeMax(1000000);
+        // maximum size that will be stored in memory
+        fu.setSizeThreshold(4096);
+        // the location for saving data that is larger than getSizeThreshold()
+        fu.setRepositoryPath("/tmp");
+
+        List fileItems = fu.parseRequest(req);
+        // assume we know there are two files. The first file is a small
+        // text file, the second is unknown and is written to a file on
+        // the server
+        Iterator i = fileItems.iterator();
+        String comment = ((FileItem)i.next()).getString();
+        FileItem fi = (FileItem)i.next();
+        // filename on the client
+        String fileName = fi.getName();
+        // save comment and filename to database
+        ...
+        // write the file
+        fi.write("/www/uploads/" + fileName);
+    }
+</pre>
+      <p>
+         In the example above the first file is loaded into memory as a
+         <code>String</code>. Before calling the getString method, the data
+         may have been in memory or on disk depending on its size.  The second
+         file we assume it will be large and therefore never explicitly load
+         it into memory, though if it is less than 4096 bytes it will be
+         in memory before it is written to its final location.  When writing to
+         the final location, if the data is larger than the
+         threshold, an attempt is made to rename the temporary file to
+         the given location.  If it cannot be renamed, it is streamed to the
+         new location.
+      </p>
+   </body>
+</html>