This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/7.0.x by this push:
     new b40ee49  Add the necessary plumbing to allow the Windows installer to 
be signed.
b40ee49 is described below

commit b40ee494b192d6d1d8b930b3269e0954d50e7537
Author: Mark Thomas <ma...@apache.org>
AuthorDate: Thu May 23 23:10:49 2019 +0100

    Add the necessary plumbing to allow the Windows installer to be signed.
    
    The motivation for this was aligning the tomcat.nsi files across the
    currently supported versions in preparation for implementing BZ 55969.
    The changes to the installer required changes in the build script so it
    seemed reasonable to provide all of the plumbing necessary for signing.
---
 build.xml                                        | 106 +++++-
 java/org/apache/tomcat/buildutil/SignCode.java   | 435 +++++++++++++++++++++++
 java/org/apache/tomcat/util/buf/StringUtils.java | 105 ++++++
 res/checkstyle/org-import-control.xml            |   1 +
 res/tomcat.nsi                                   | 286 ++++++++-------
 5 files changed, 792 insertions(+), 141 deletions(-)

diff --git a/build.xml b/build.xml
index 92d8b92..2b1ed37 100644
--- a/build.xml
+++ b/build.xml
@@ -498,7 +498,7 @@
   <target name="echoproperties">
     <echoproperties/>
   </target>
-  
+
   <target name="build-prepare">
 
     <!-- Required so we can compile -->
@@ -2168,9 +2168,26 @@ Apache Tomcat ${version} native binaries for Win64 
AMD64/EMT64 platform.
     </copy>
   </target>
 
-  <target name="installer" description="Create Windows installer"
-      unless="skip.installer" depends="dist-static">
-    <echo message="Builds a Windows installer based on Nullsoft Installer"/>
+  <target name="-installer-pre-init">
+    <property environment="env" />
+    <condition property="wine.ok">
+      <or>
+        <os family="windows" />
+        <available file="wine" filepath="${env.PATH}" />
+        <isset property="skip.installer"/>
+      </or>
+    </condition>
+  </target>
+
+  <target name="-installer-init" depends="-installer-pre-init" 
unless="${wine.ok}">
+    <fail message="The executable wine was not found on the current path.
+Wine is required to build the Windows installer when running a release build on
+a non-Windows platform. To skip building the Windows installer, set the
+skip.installer property in build.properties" />
+  </target>
+
+  <target name="-installer-prep"
+      unless="skip.installer" depends="dist-static,-installer-init">
     <copy todir="${tomcat.dist}">
       <fileset dir="res">
         <include name="INSTALLLICENSE" />
@@ -2187,31 +2204,102 @@ Apache Tomcat ${version} native binaries for Win64 
AMD64/EMT64 platform.
     <copy file="res/tomcat.nsi" tofile="${tomcat.dist}/tomcat.nsi" 
overwrite="true" encoding="ISO-8859-1">
       <filterset refid="version.filters"/>
     </copy>
-
     <fixcrlf srcdir="${tomcat.dist}" eol="crlf"
         encoding="ISO-8859-1" fixlast="false" >
       <patternset refid="text.files" />
     </fixcrlf>
+  </target>
 
+  <target name="-installer-create-tempinstaller"
+      unless="skip.installer" depends="-installer-prep">
     <exec dir="${tomcat.dist}" executable="${nsis.exe}" osfamily="windows">
+      <arg value="/DUNINSTALLONLY" />
       <arg value="/DNSISDIR=${nsis.home}" />
       <arg value="/V2" />
       <arg value="tomcat.nsi" />
     </exec>
     <exec dir="${tomcat.dist}" executable="wine" osfamily="unix">
       <arg value="${nsis.exe}" />
+      <arg value="/DUNINSTALLONLY" />
       <arg value="/DNSISDIR=${nsis.home}" />
       <arg value="/V2" />
       <arg value="tomcat.nsi" />
     </exec>
+  </target>
+
+  <target name="-installer-create-uninstaller"
+      unless="skip.installer" depends="-installer-create-tempinstaller">
+    <!-- Execute the temporary installer to create the uninstaller -->
+    <exec dir="${tomcat.dist}" executable="${tomcat.dist}/tempinstaller.exe"
+        osfamily="windows"  />
+    <exec dir="${tomcat.dist}" executable="wine" osfamily="unix">
+      <arg value="${tomcat.dist}/tempinstaller.exe" />
+    </exec>
+
+  </target>
 
+  <target name="-installer-sign-uninstaller"
+      unless="skip.installer" depends="-installer-create-uninstaller"
+      if="${do.codesigning}">
+    <taskdef name="signcode"
+        classname="org.apache.tomcat.buildutil.SignCode"
+        classpath="${tomcat.classes}" />
+    <signcode userName="${codesigning.user}" password="${codesigning.pwd}"
+        partnerCode="${codesigning.partnercode}"
+        keyStore="${codesigning.keyStore}"
+        keyStorePassword="${codesigning.keyStorePassword}"
+        applicationName="Apache Tomcat ${version.major.minor} Uninstaller"
+        applicationversion="${version}"
+        signingService="${codesigning.service}">
+      <fileset dir="${tomcat.dist}">
+        <filename name="Uninstall.exe"/>
+      </fileset>
+    </signcode>
+  </target>
+
+  <target name="-installer" unless="skip.installer"
+      depends="-installer-sign-uninstaller">
+    <exec dir="${tomcat.dist}" executable="${nsis.exe}" osfamily="windows">
+      <arg value="/DNSISDIR=${nsis.home}" />
+      <arg value="/V2" />
+      <arg value="tomcat.nsi" />
+    </exec>
+    <exec dir="${tomcat.dist}" executable="wine" osfamily="unix">
+      <arg value="${nsis.exe}" />
+      <arg value="/DNSISDIR=${nsis.home}" />
+      <arg value="/V2" />
+      <arg value="tomcat.nsi" />
+    </exec>
     <move file="${tomcat.dist}/tomcat-installer.exe" 
tofile="${tomcat.release}/v${version}/bin/${final.name}.exe" />
     <hashAndSign file="${tomcat.release}/v${version}/bin/${final.name}.exe" />
   </target>
 
+  <target name="installer-sign"
+      description="Builds and optionally signs the Windows installer"
+      depends="-installer" if="${do.codesigning}" >
+    <taskdef name="signcode"
+        classname="org.apache.tomcat.buildutil.SignCode"
+        classpath="${tomcat.classes}" />
+    <signcode userName="${codesigning.user}" password="${codesigning.pwd}"
+              partnerCode="${codesigning.partnercode}"
+              keyStore="${codesigning.keyStore}"
+              keyStorePassword="${codesigning.keyStorePassword}"
+              applicationName="Apache Tomcat ${version.major.minor}"
+              applicationversion="${version}"
+              signingService="${codesigning.service}">
+      <fileset dir="${tomcat.release}">
+        <filename name="v${version}/bin/${final.name}.exe"/>
+      </fileset>
+    </signcode>
+    <!-- .exe has changed so need to redo checksums and OpenPGP signature -->
+    <delete file="${tomcat.release}/v${version}/bin/${final.name}.exe.asc" />
+    <delete file="${tomcat.release}/v${version}/bin/${final.name}.exe.sha512" 
/>
+    <hashAndSign file="${tomcat.release}/v${version}/bin/${final.name}.exe" />
+  </target>
+
   <target name="release"
-    
depends="clean,release-init,dist-deployer,installer,package-zip,package-winzip,package-tgz,package-deployer-zip,package-deployer-tgz,javadoc,package-docs-tgz,package-src-zip,package-src-tgz,package-src-jar"
-    description="Create a Tomcat 7 packaged distribution">
+    
depends="clean,release-init,dist-deployer,installer-sign,package-zip,package-winzip,package-tgz,package-deployer-zip,package-deployer-tgz,javadoc,package-docs-tgz,package-src-zip,package-src-tgz,package-src-jar"
+    description="Create a Tomcat packaged distribution">
 
     <copy file="KEYS"
          todir="${tomcat.release}/v${version}"/>
@@ -2680,6 +2768,7 @@ Apache Tomcat ${version} native binaries for Win64 
AMD64/EMT64 platform.
       <param name="checksum.algorithm" value="${jdt.checksum.algorithm}"/>
       <param name="checksum.value" value="${jdt.checksum.value}"/>
     </antcall>
+
   </target>
 
   <target name="download-test-compile"
@@ -2800,7 +2889,6 @@ Apache Tomcat ${version} native binaries for Win64 
AMD64/EMT64 platform.
 
   </target>
 
-
   <!-- =============== Utility Targets to support downloads ================ 
-->
 
   <target name="proxyflags">
@@ -3055,6 +3143,8 @@ Apache Tomcat ${version} native binaries for Win64 
AMD64/EMT64 platform.
 
   <!-- ============================ IDE Support ============================ 
-->
 
+  <!-- ============================ Eclipse ================================ 
-->
+
   <target name="ide-eclipse"
           depends="download-compile, extras-webservices-prepare, 
download-test-compile"
           description="Prepares the source tree to be built in Eclipse">
diff --git a/java/org/apache/tomcat/buildutil/SignCode.java 
b/java/org/apache/tomcat/buildutil/SignCode.java
new file mode 100644
index 0000000..20b6bed
--- /dev/null
+++ b/java/org/apache/tomcat/buildutil/SignCode.java
@@ -0,0 +1,435 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You 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.buildutil;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import javax.xml.soap.MessageFactory;
+import javax.xml.soap.SOAPBody;
+import javax.xml.soap.SOAPConnection;
+import javax.xml.soap.SOAPConnectionFactory;
+import javax.xml.soap.SOAPConstants;
+import javax.xml.soap.SOAPElement;
+import javax.xml.soap.SOAPEnvelope;
+import javax.xml.soap.SOAPException;
+import javax.xml.soap.SOAPMessage;
+import javax.xml.soap.SOAPPart;
+
+import org.apache.tomcat.util.buf.StringUtils;
+import org.apache.tomcat.util.codec.binary.Base64;
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.DirectoryScanner;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.types.FileSet;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+/**
+ * Ant task that submits a file to the Symantec code-signing service.
+ */
+public class SignCode extends Task {
+
+    private static final URL SIGNING_SERVICE_URL;
+
+    private static final String NS = "cod";
+
+    private static final MessageFactory SOAP_MSG_FACTORY;
+
+    static {
+        try {
+            SIGNING_SERVICE_URL = new URL(
+                    
"https://api-appsec-cws.ws.symantec.com/webtrust/SigningService";);
+        } catch (MalformedURLException e) {
+            throw new IllegalArgumentException(e);
+        }
+        try {
+            SOAP_MSG_FACTORY = 
MessageFactory.newInstance(SOAPConstants.SOAP_1_1_PROTOCOL);
+        } catch (SOAPException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private final List<FileSet> filesets = new ArrayList<FileSet>();
+    private String userName;
+    private String password;
+    private String partnerCode;
+    private String keyStore;
+    private String keyStorePassword;
+    private String applicationName;
+    private String applicationVersion;
+    private String signingService;
+    private boolean debug;
+
+    public void addFileset(FileSet fileset) {
+        filesets.add(fileset);
+    }
+
+
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
+
+    public void setPartnerCode(String partnerCode) {
+        this.partnerCode = partnerCode;
+    }
+
+
+    public void setKeyStore(String keyStore) {
+        this.keyStore = keyStore;
+    }
+
+
+    public void setKeyStorePassword(String keyStorePassword) {
+        this.keyStorePassword = keyStorePassword;
+    }
+
+
+    public void setApplicationName(String applicationName) {
+        this.applicationName = applicationName;
+    }
+
+
+    public void setApplicationVersion(String applicationVersion) {
+        this.applicationVersion = applicationVersion;
+    }
+
+
+    public void setSigningService(String signingService) {
+        this.signingService = signingService;
+    }
+
+
+    public void setDebug(String debug) {
+        this.debug = Boolean.parseBoolean(debug);
+    }
+
+
+    @Override
+    public void execute() throws BuildException {
+
+        List<File> filesToSign = new ArrayList<File>();
+
+        // Process the filesets and populate the list of files that need to be
+        // signed.
+        for (FileSet fileset : filesets) {
+            DirectoryScanner ds = fileset.getDirectoryScanner(getProject());
+            File basedir = ds.getBasedir();
+            String[] files = ds.getIncludedFiles();
+            if (files.length > 0) {
+                for (int i = 0; i < files.length; i++) {
+                    File file = new File(basedir, files[i]);
+                    filesToSign.add(file);
+                }
+            }
+        }
+
+        // Set up the TLS client
+        System.setProperty("javax.net.ssl.keyStore", keyStore);
+        System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword);
+
+        try {
+            String signingSetID = makeSigningRequest(filesToSign);
+            downloadSignedFiles(filesToSign, signingSetID);
+        } catch (SOAPException e) {
+            throw new BuildException(e);
+        } catch (IOException e) {
+            throw new BuildException(e);
+        }
+    }
+
+
+    private String makeSigningRequest(List<File> filesToSign) throws 
SOAPException, IOException {
+        log("Constructing the code signing request");
+
+        SOAPMessage message = SOAP_MSG_FACTORY.createMessage();
+        SOAPBody body = populateEnvelope(message, NS);
+
+        SOAPElement requestSigning = body.addChildElement("requestSigning", 
NS);
+        SOAPElement requestSigningRequest =
+                requestSigning.addChildElement("requestSigningRequest", NS);
+
+        addCredentials(requestSigningRequest, this.userName, this.password, 
this.partnerCode);
+
+        SOAPElement applicationName =
+                requestSigningRequest.addChildElement("applicationName", NS);
+        applicationName.addTextNode(this.applicationName);
+
+        SOAPElement applicationVersion =
+                requestSigningRequest.addChildElement("applicationVersion", 
NS);
+        applicationVersion.addTextNode(this.applicationVersion);
+
+        SOAPElement signingServiceName =
+                requestSigningRequest.addChildElement("signingServiceName", 
NS);
+        signingServiceName.addTextNode(this.signingService);
+
+        List<String> fileNames = getFileNames(filesToSign);
+
+        SOAPElement commaDelimitedFileNames =
+                
requestSigningRequest.addChildElement("commaDelimitedFileNames", NS);
+        commaDelimitedFileNames.addTextNode(StringUtils.join(fileNames));
+
+        SOAPElement application =
+                requestSigningRequest.addChildElement("application", NS);
+        application.addTextNode(getApplicationString(fileNames, filesToSign));
+
+        // Send the message
+        SOAPConnectionFactory soapConnectionFactory = 
SOAPConnectionFactory.newInstance();
+        SOAPConnection connection = soapConnectionFactory.createConnection();
+
+        log("Sending singing request to server and waiting for response");
+        SOAPMessage response = connection.call(message, SIGNING_SERVICE_URL);
+
+        if (debug) {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream(2 * 1024);
+            response.writeTo(baos);
+            log(baos.toString("UTF-8"));
+        }
+
+        log("Processing response");
+        SOAPElement responseBody = response.getSOAPBody();
+
+        // Should come back signed
+        NodeList bodyNodes = responseBody.getChildNodes();
+        NodeList requestSigningResponseNodes = 
bodyNodes.item(0).getChildNodes();
+        NodeList returnNodes = 
requestSigningResponseNodes.item(0).getChildNodes();
+
+        String signingSetID = null;
+        String signingSetStatus = null;
+
+        for (int i = 0; i < returnNodes.getLength(); i++) {
+            Node returnNode = returnNodes.item(i);
+            if (returnNode.getLocalName().equals("signingSetID")) {
+                signingSetID = returnNode.getTextContent();
+            } else if (returnNode.getLocalName().equals("signingSetStatus")) {
+                signingSetStatus = returnNode.getTextContent();
+            }
+        }
+
+        if (!signingService.contains("TEST") && 
!"SIGNED".equals(signingSetStatus) ||
+                signingService.contains("TEST") && 
!"INITIALIZED".equals(signingSetStatus) ) {
+            throw new BuildException("Signing failed. Status was: " + 
signingSetStatus);
+        }
+
+        return signingSetID;
+    }
+
+
+    private void downloadSignedFiles(List<File> filesToSign, String id)
+            throws SOAPException, IOException {
+
+        log("Downloading signed files. The signing set ID is: " + id);
+
+        SOAPMessage message = SOAP_MSG_FACTORY.createMessage();
+        SOAPBody body = populateEnvelope(message, NS);
+
+        SOAPElement getSigningSetDetails = 
body.addChildElement("getSigningSetDetails", NS);
+        SOAPElement getSigningSetDetailsRequest =
+                
getSigningSetDetails.addChildElement("getSigningSetDetailsRequest", NS);
+
+        addCredentials(getSigningSetDetailsRequest, this.userName, 
this.password, this.partnerCode);
+
+        SOAPElement signingSetID =
+                getSigningSetDetailsRequest.addChildElement("signingSetID", 
NS);
+        signingSetID.addTextNode(id);
+
+        SOAPElement returnApplication =
+                
getSigningSetDetailsRequest.addChildElement("returnApplication", NS);
+        returnApplication.addTextNode("true");
+
+        // Send the message
+        SOAPConnectionFactory soapConnectionFactory = 
SOAPConnectionFactory.newInstance();
+        SOAPConnection connection = soapConnectionFactory.createConnection();
+
+        log("Requesting signed files from server and waiting for response");
+        SOAPMessage response = connection.call(message, SIGNING_SERVICE_URL);
+
+        log("Processing response");
+        SOAPElement responseBody = response.getSOAPBody();
+
+        // Check for success
+
+        // Extract the signed file(s) from the ZIP
+        NodeList bodyNodes = responseBody.getChildNodes();
+        NodeList getSigningSetDetailsResponseNodes = 
bodyNodes.item(0).getChildNodes();
+        NodeList returnNodes = 
getSigningSetDetailsResponseNodes.item(0).getChildNodes();
+
+        String result = null;
+        String data = null;
+
+        for (int i = 0; i < returnNodes.getLength(); i++) {
+            Node returnNode = returnNodes.item(i);
+            if (returnNode.getLocalName().equals("result")) {
+                result = returnNode.getChildNodes().item(0).getTextContent();
+            } else if (returnNode.getLocalName().equals("signingSet")) {
+                data = returnNode.getChildNodes().item(1).getTextContent();
+            }
+        }
+
+        if (!"0".equals(result)) {
+            throw new BuildException("Download failed. Result code was: " + 
result);
+        }
+
+        extractFilesFromApplicationString(data, filesToSign);
+    }
+
+
+    private static SOAPBody populateEnvelope(SOAPMessage message, String 
namespace)
+            throws SOAPException {
+        SOAPPart soapPart = message.getSOAPPart();
+        SOAPEnvelope envelope = soapPart.getEnvelope();
+        envelope.addNamespaceDeclaration(
+                "soapenv","http://schemas.xmlsoap.org/soap/envelope/";);
+        envelope.addNamespaceDeclaration(
+                
namespace,"http://api.ws.symantec.com/webtrust/codesigningservice";);
+        return envelope.getBody();
+    }
+
+
+    private static void addCredentials(SOAPElement requestSigningRequest,
+            String user, String pwd, String code) throws SOAPException {
+        SOAPElement authToken = 
requestSigningRequest.addChildElement("authToken", NS);
+        SOAPElement userName = authToken.addChildElement("userName", NS);
+        userName.addTextNode(user);
+        SOAPElement password = authToken.addChildElement("password", NS);
+        password.addTextNode(pwd);
+        SOAPElement partnerCode = authToken.addChildElement("partnerCode", NS);
+        partnerCode.addTextNode(code);
+    }
+
+
+    /**
+     * Signing service requires unique files names. Since files will be 
returned
+     * in order, use dummy names that we know are unique but retain the file
+     * extension since the signing service appears to use it to figure out what
+     * to sign and how to sign it.
+     */
+    private static List<String> getFileNames(List<File> filesToSign) {
+        List<String> result = new ArrayList<String>(filesToSign.size());
+
+        for (int i = 0; i < filesToSign.size(); i++) {
+            File f = filesToSign.get(i);
+            String fileName = f.getName();
+            int extIndex = fileName.lastIndexOf('.');
+            String newName;
+            if (extIndex < 0) {
+                newName = Integer.toString(i);
+            } else {
+                newName = Integer.toString(i) + fileName.substring(extIndex);
+            }
+            result.add(newName);
+        }
+        return result;
+    }
+
+
+    /**
+     * Zips the files, base 64 encodes the resulting zip and then returns the
+     * string. It would be far more efficient to stream this directly to the
+     * signing server but the files that need to be signed are relatively small
+     * and this simpler to write.
+     *
+     * @param fileNames Modified names of files
+     * @param files     Files to be signed
+     */
+    private static String getApplicationString(List<String> fileNames, 
List<File> files)
+            throws IOException {
+        // 16 MB should be more than enough for Tomcat
+        // TODO: Refactoring this entire class so it uses streaming rather than
+        //       buffering the entire set of files in memory would make it more
+        //       widely useful.
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(16 * 1024 * 
1024);
+        ZipOutputStream zos = null;
+        try {
+            zos = new ZipOutputStream(baos);
+            byte[] buf = new byte[32 * 1024];
+            for (int i = 0; i < files.size(); i++) {
+                FileInputStream fis = null;
+                try {
+                    fis = new FileInputStream(files.get(i));
+                    ZipEntry zipEntry = new ZipEntry(fileNames.get(i));
+                    zos.putNextEntry(zipEntry);
+                    int numRead;
+                    while ( (numRead = fis.read(buf)) >= 0) {
+                        zos.write(buf, 0, numRead);
+                    }
+                } finally {
+                    if (fis != null) {
+                        fis.close();
+                    }
+                }
+            }
+        } finally {
+            if (zos != null) {
+                zos.close();
+            }
+        }
+
+        return Base64.encodeBase64String(baos.toByteArray());
+    }
+
+
+    /**
+     * Removes base64 encoding, unzips the files and writes the new files over
+     * the top of the old ones.
+     */
+    private static void extractFilesFromApplicationString(String data, 
List<File> files)
+            throws IOException {
+        ByteArrayInputStream bais = new 
ByteArrayInputStream(Base64.decodeBase64(data));
+        ZipInputStream zis = null;
+        try {
+            zis = new ZipInputStream(bais);
+            byte[] buf = new byte[32 * 1024];
+            for (int i = 0; i < files.size(); i ++) {
+                FileOutputStream fos = null;
+                try {
+                    fos = new FileOutputStream(files.get(i));
+                    zis.getNextEntry();
+                    int numRead;
+                    while ( (numRead = zis.read(buf)) >= 0) {
+                        fos.write(buf, 0 , numRead);
+                    }
+                } finally {
+                    if (fos != null) {
+                        fos.close();
+                    }
+                }
+            }
+        } finally {
+            if (zis != null) {
+                zis.close();
+            }
+        }
+    }
+}
diff --git a/java/org/apache/tomcat/util/buf/StringUtils.java 
b/java/org/apache/tomcat/util/buf/StringUtils.java
new file mode 100644
index 0000000..9e2f9ab
--- /dev/null
+++ b/java/org/apache/tomcat/util/buf/StringUtils.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.buf;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+/**
+ * Utility methods to build a separated list from a given set (not
+ * java.util.Set) of inputs and return that list as a string or append it to an
+ * existing StringBuilder. If the given set is null or empty, an empty string
+ * will be returned.
+ */
+public final class StringUtils {
+
+    private static final String EMPTY_STRING = "";
+
+    private StringUtils() {
+        // Utility class
+    }
+
+
+    public static String join(String[] array) {
+        if (array == null) {
+            return EMPTY_STRING;
+        }
+        return join(Arrays.asList(array));
+    }
+
+
+    public static void join(String[] array, char separator, StringBuilder sb) {
+        if (array == null) {
+            return;
+        }
+        join(Arrays.asList(array), separator, sb);
+    }
+
+
+    public static String join(Collection<String> collection) {
+        return join(collection, ',');
+    }
+
+
+    public static String join(Collection<String> collection, char separator) {
+        // Shortcut
+        if (collection == null || collection.isEmpty()) {
+            return EMPTY_STRING;
+        }
+
+        StringBuilder result = new StringBuilder();
+        join(collection, separator, result);
+        return result.toString();
+    }
+
+
+    public static void join(Iterable<String> iterable, char separator, 
StringBuilder sb) {
+        join(iterable, separator,
+                new Function<String>() {@Override public String apply(String 
t) { return t; }}, sb);
+    }
+
+
+    public static <T> void join(T[] array, char separator, Function<T> 
function,
+            StringBuilder sb) {
+        if (array == null) {
+            return;
+        }
+        join(Arrays.asList(array), separator, function, sb);
+    }
+
+
+    public static <T> void join(Iterable<T> iterable, char separator, 
Function<T> function,
+            StringBuilder sb) {
+        if (iterable == null) {
+            return;
+        }
+        boolean first = true;
+        for (T value : iterable) {
+            if (first) {
+                first = false;
+            } else {
+                sb.append(separator);
+            }
+            sb.append(function.apply(value));
+        }
+    }
+
+
+    public interface Function<T> {
+        public String apply(T t);
+    }
+}
diff --git a/res/checkstyle/org-import-control.xml 
b/res/checkstyle/org-import-control.xml
index 271c46c..cbff820 100644
--- a/res/checkstyle/org-import-control.xml
+++ b/res/checkstyle/org-import-control.xml
@@ -120,6 +120,7 @@
     <allow pkg="javax.servlet"/>
     <subpackage name="buildutil">
       <allow pkg="org.apache.tools.ant"/>
+      <allow pkg="org.apache.tomcat.util"/>
     </subpackage>
     <subpackage name="dbcp">
       <allow pkg="org.apache.juli"/>
diff --git a/res/tomcat.nsi b/res/tomcat.nsi
index be0421f..fb15582 100644
--- a/res/tomcat.nsi
+++ b/res/tomcat.nsi
@@ -15,8 +15,11 @@
 
 ; Tomcat script for Nullsoft Installer
 
-  ;General
+!ifdef UNINSTALLONLY
+  OutFile "tempinstaller.exe"
+!else
   OutFile tomcat-installer.exe
+!endif
 
   ;Compression options
   CRCCheck on
@@ -110,9 +113,11 @@ Var ServiceInstallLog
   Page custom CheckUserType
   !insertmacro MUI_PAGE_FINISH
 
-  ;Uninstall Page order
-  !insertmacro MUI_UNPAGE_CONFIRM
-  !insertmacro MUI_UNPAGE_INSTFILES
+  !ifdef UNINSTALLONLY
+    ;Uninstall Page order
+    !insertmacro MUI_UNPAGE_CONFIRM
+    !insertmacro MUI_UNPAGE_INSTFILES
+  !endif
 
   ;Language
   !insertmacro MUI_LANGUAGE English
@@ -339,7 +344,11 @@ Section -post
     Call createShortcuts
   ${EndIf}
 
-  WriteUninstaller "$INSTDIR\Uninstall.exe"
+  !ifndef UNINSTALLONLY
+    SetOutPath $INSTDIR
+    ; this packages the signed uninstaller
+    File Uninstall.exe
+  !endif
 
   WriteRegStr HKLM "SOFTWARE\Apache Software 
Foundation\Tomcat\@VERSION_MAJOR_MINOR@\$TomcatServiceName" "InstallPath" 
$INSTDIR
   WriteRegStr HKLM "SOFTWARE\Apache Software 
Foundation\Tomcat\@VERSION_MAJOR_MINOR@\$TomcatServiceName" "Version" @VERSION@
@@ -377,6 +386,14 @@ Function ReadFromConfigIni
 FunctionEnd
 
 Function .onInit
+  !ifdef UNINSTALLONLY
+    ; If UNINSTALLONLY is defined, then we aren't supposed to do anything 
except write out
+    ; the installer.  This is better than processing a command line option as 
it means
+    ; this entire code path is not present in the final (real) installer.
+    WriteUninstaller "$EXEDIR\Uninstall.exe"
+    Quit
+  !endif
+
   ${GetParameters} $R0
   ClearErrors
 
@@ -1158,138 +1175,141 @@ FunctionEnd
 ;--------------------------------
 ;Uninstaller Section
 
-Section Uninstall
-
-  ${If} $TomcatServiceName == ""
-    MessageBox MB_ICONSTOP|MB_OK \
-        "No service name specified to uninstall. This will be provided 
automatically if you uninstall via \
-         Add/Remove Programs or the shortcut on the Start menu. Alternatively, 
call the installer from \
-         the command line with -ServiceName=$\"<name of service>$\"."
-    Quit
-  ${EndIf}
-
-  Delete "$INSTDIR\Uninstall.exe"
+!ifdef UNINSTALLONLY
+  Section Uninstall
 
-  ; Stop Tomcat service monitor if running
-  DetailPrint "Stopping $TomcatServiceName service monitor"
-  nsExec::ExecToLog '"$INSTDIR\bin\$TomcatServiceManagerFileName" 
//MQ//$TomcatServiceName'
-  ; Delete Tomcat service
-  DetailPrint "Uninstalling $TomcatServiceName service"
-  nsExec::ExecToLog '"$INSTDIR\bin\$TomcatServiceFileName" 
//DS//$TomcatServiceName --LogPath "$INSTDIR\logs"'
-  ClearErrors
+    ${If} $TomcatServiceName == ""
+      MessageBox MB_ICONSTOP|MB_OK \
+          "No service name specified to uninstall. This will be provided 
automatically if you uninstall via \
+           Add/Remove Programs or the shortcut on the Start menu. 
Alternatively, call the installer from \
+           the command line with -ServiceName=$\"<name of service>$\"."
+      Quit
+    ${EndIf}
 
-  ; Don't know if 32-bit or 64-bit registry was used so, for now, remove both
-  SetRegView 32
-  DeleteRegKey HKLM 
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Apache Tomcat 
@VERSION_MAJOR_MINOR@ $TomcatServiceName"
-  DeleteRegKey HKLM "SOFTWARE\Apache Software 
Foundation\Tomcat\@VERSION_MAJOR_MINOR@\$TomcatServiceName"
-  DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
-  DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
-  SetRegView 64
-  DeleteRegKey HKLM 
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Apache Tomcat 
@VERSION_MAJOR_MINOR@ $TomcatServiceName"
-  DeleteRegKey HKLM "SOFTWARE\Apache Software 
Foundation\Tomcat\@VERSION_MAJOR_MINOR@\$TomcatServiceName"
-  DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
-  DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
-
-  ; Don't know if short-cuts were created for all users, one user or not at 
all so, for now, remove both
-  SetShellVarContext all
-  RMDir /r "$SMPROGRAMS\Apache Tomcat @VERSION_MAJOR_MINOR@ $TomcatServiceName"
-  SetShellVarContext current
-  RMDir /r "$SMPROGRAMS\Apache Tomcat @VERSION_MAJOR_MINOR@ $TomcatServiceName"
-
-  ; Before files are removed using recursive deletes, remove any symbolic
-  ; links in the installation directory and the directory structure below it
-  ; to ensure the recursive deletes don't result in any nasty surprises.
-  Push "$INSTDIR"
-  Call un.RemoveSymlinks
-
-  Delete "$INSTDIR\tomcat.ico"
-  Delete "$INSTDIR\LICENSE"
-  Delete "$INSTDIR\NOTICE"
-  RMDir /r "$INSTDIR\bin"
-  RMDir /r "$INSTDIR\lib"
-  Delete "$INSTDIR\conf\*.dtd"
-  RMDir "$INSTDIR\logs"
-  RMDir /r "$INSTDIR\webapps\docs"
-  RMDir /r "$INSTDIR\webapps\examples"
-  RMDir /r "$INSTDIR\work"
-  RMDir /r "$INSTDIR\temp"
-  RMDir "$INSTDIR"
-
-  IfSilent Removed 0
-
-  ; if $INSTDIR was removed, skip these next ones
-  IfFileExists "$INSTDIR" 0 Removed
-    MessageBox MB_YESNO|MB_ICONQUESTION \
-      "Remove all files in your Apache Tomcat @VERSION_MAJOR_MINOR@ 
$TomcatServiceName directory? (If you have anything  \
- you created that you want to keep, click No)" IDNO Removed
-    ; these would be skipped if the user hits no
-    RMDir /r "$INSTDIR\webapps"
-    RMDir /r "$INSTDIR\logs"
-    RMDir /r "$INSTDIR\conf"
-    Delete "$INSTDIR\*.*"
-    RMDir /r "$INSTDIR"
-    Sleep 500
-    IfFileExists "$INSTDIR" 0 Removed
-      MessageBox MB_OK|MB_ICONEXCLAMATION \
-                 "Note: $INSTDIR could not be removed."
-  Removed:
+    Delete "$INSTDIR\Uninstall.exe"
 
-SectionEnd
+    ; Stop Tomcat service monitor if running
+    DetailPrint "Stopping $TomcatServiceName service monitor"
+    nsExec::ExecToLog '"$INSTDIR\bin\$TomcatServiceManagerFileName" 
//MQ//$TomcatServiceName'
+    ; Delete Tomcat service
+    DetailPrint "Uninstalling $TomcatServiceName service"
+    nsExec::ExecToLog '"$INSTDIR\bin\$TomcatServiceFileName" 
//DS//$TomcatServiceName --LogPath "$INSTDIR\logs"'
+    ClearErrors
 
+    ; Don't know if 32-bit or 64-bit registry was used so, for now, remove both
+    SetRegView 32
+    DeleteRegKey HKLM 
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Apache Tomcat 
@VERSION_MAJOR_MINOR@ $TomcatServiceName"
+    DeleteRegKey HKLM "SOFTWARE\Apache Software 
Foundation\Tomcat\@VERSION_MAJOR_MINOR@\$TomcatServiceName"
+    DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
+    DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
+    SetRegView 64
+    DeleteRegKey HKLM 
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Apache Tomcat 
@VERSION_MAJOR_MINOR@ $TomcatServiceName"
+    DeleteRegKey HKLM "SOFTWARE\Apache Software 
Foundation\Tomcat\@VERSION_MAJOR_MINOR@\$TomcatServiceName"
+    DeleteRegValue HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
+    DeleteRegValue HKCU "Software\Microsoft\Windows\CurrentVersion\Run" 
"ApacheTomcatMonitor@VERSION_MAJOR_MINOR@_$TomcatServiceName"
 
-; =================
-; uninstall init function
-;
-; Read the command line parameter and set up the service name variables so the
-; uninstaller knows which service it is working with
-; =================
-Function un.onInit
-  ${GetParameters} $R0
-  ${GetOptions} $R0 "-ServiceName=" $R1
-  StrCpy $TomcatServiceName $R1
-  StrCpy $TomcatServiceFileName $R1.exe
-  StrCpy $TomcatServiceManagerFileName $R1w.exe
-FunctionEnd
+    ; Don't know if short-cuts were created for all users, one user or not at 
all so, for now, remove both
+    SetShellVarContext all
+    RMDir /r "$SMPROGRAMS\Apache Tomcat @VERSION_MAJOR_MINOR@ 
$TomcatServiceName"
+    SetShellVarContext current
+    RMDir /r "$SMPROGRAMS\Apache Tomcat @VERSION_MAJOR_MINOR@ 
$TomcatServiceName"
+
+    ; Before files are removed using recursive deletes, remove any symbolic
+    ; links in the installation directory and the directory structure below it
+    ; to ensure the recursive deletes don't result in any nasty surprises.
+    Push "$INSTDIR"
+    Call un.RemoveSymlinks
+
+    Delete "$INSTDIR\tomcat.ico"
+    Delete "$INSTDIR\LICENSE"
+    Delete "$INSTDIR\NOTICE"
+    RMDir /r "$INSTDIR\bin"
+    RMDir /r "$INSTDIR\lib"
+    Delete "$INSTDIR\conf\*.dtd"
+    RMDir "$INSTDIR\logs"
+    RMDir /r "$INSTDIR\webapps\docs"
+    RMDir /r "$INSTDIR\webapps\examples"
+    RMDir /r "$INSTDIR\work"
+    RMDir /r "$INSTDIR\temp"
+    RMDir "$INSTDIR"
+
+    IfSilent Removed 0
+
+    ; if $INSTDIR was removed, skip these next ones
+    IfFileExists "$INSTDIR" 0 Removed
+      MessageBox MB_YESNO|MB_ICONQUESTION \
+        "Remove all files in your Apache Tomcat @VERSION_MAJOR_MINOR@ 
$TomcatServiceName directory? (If you have anything  \
+   you created that you want to keep, click No)" IDNO Removed
+      ; these would be skipped if the user hits no
+      RMDir /r "$INSTDIR\webapps"
+      RMDir /r "$INSTDIR\logs"
+      RMDir /r "$INSTDIR\conf"
+      Delete "$INSTDIR\*.*"
+      RMDir /r "$INSTDIR"
+      Sleep 500
+      IfFileExists "$INSTDIR" 0 Removed
+        MessageBox MB_OK|MB_ICONEXCLAMATION \
+                   "Note: $INSTDIR could not be removed."
+    Removed:
+
+  SectionEnd
+
+  ; =================
+  ; uninstall init function
+  ;
+  ; Read the command line parameter and set up the service name variables so 
the
+  ; uninstaller knows which service it is working with
+  ; =================
+  Function un.onInit
+    ${GetParameters} $R0
+    ${GetOptions} $R0 "-ServiceName=" $R1
+    StrCpy $TomcatServiceName $R1
+    StrCpy $TomcatServiceFileName $R1.exe
+    StrCpy $TomcatServiceManagerFileName $R1w.exe
+  FunctionEnd
+
+  ; =================
+  ; Removes symbolic links from the path found on top of the stack.
+  ; The path is removed from the stack as a result of calling this function.
+  ; =================
+  Function un.RemoveSymlinks
+    Pop $0
+    ${GetFileAttributes} "$0" "REPARSE_POINT" $3
+    ; DetailPrint "Processing directory [$0] [$3]"
+    FindFirst $1 $2 $0\*.*
+    ; DetailPrint "Search [$1] found [$2]"
+    StrCmp $3 "1" RemoveSymlinks-delete
+  RemoveSymlinks-loop:
+    ; DetailPrint "Search [$1] processing [$0\$2]"
+    StrCmp $2 "" RemoveSymlinks-exit
+    StrCmp $2 "." RemoveSymlinks-skip
+    StrCmp $2 ".." RemoveSymlinks-skip
+    IfFileExists $0\$2\*.* RemoveSymlinks-directory
+  RemoveSymlinks-skip:
+    ; DetailPrint "Search [$1] ignoring file [$0\$2]"
+    FindNext $1 $2
+    StrCmp $2 "" RemoveSymlinks-exit
+    goto RemoveSymlinks-loop
+  RemoveSymlinks-directory:
+    ; DetailPrint "Search [$1] found directory [$0\$2]"
+    Push $0
+    Push $1
+    Push $0\$2
+    Call un.RemoveSymlinks
+    Pop $1
+    Pop $0
+    ; DetailPrint "Search [$1] restored for [$0]"
+    FindNext $1 $2
+    goto RemoveSymlinks-loop
+  RemoveSymlinks-delete:
+    ; DetailPrint "Deleting symlink [$0]"
+    SetFileAttributes "$0" "NORMAL"
+    System::Call "kernel32::RemoveDirectoryW(w `$0`) i.n"
+  RemoveSymlinks-exit:
+    ; DetailPrint "Search [$1] closed"
+   FindClose $1
+  FunctionEnd
+
+!endif
 
-; =================
-; Removes symbolic links from the path found on top of the stack.
-; The path is removed from the stack as a result of calling this function.
-; =================
-Function un.RemoveSymlinks
-  Pop $0
-  ${GetFileAttributes} "$0" "REPARSE_POINT" $3
-  ; DetailPrint "Processing directory [$0] [$3]"
-  FindFirst $1 $2 $0\*.*
-  ; DetailPrint "Search [$1] found [$2]"
-  StrCmp $3 "1" RemoveSymlinks-delete
-RemoveSymlinks-loop:
-  ; DetailPrint "Search [$1] processing [$0\$2]"
-  StrCmp $2 "" RemoveSymlinks-exit
-  StrCmp $2 "." RemoveSymlinks-skip
-  StrCmp $2 ".." RemoveSymlinks-skip
-  IfFileExists $0\$2\*.* RemoveSymlinks-directory
-RemoveSymlinks-skip:
-  ; DetailPrint "Search [$1] ignoring file [$0\$2]"
-  FindNext $1 $2
-  StrCmp $2 "" RemoveSymlinks-exit
-  goto RemoveSymlinks-loop
-RemoveSymlinks-directory:
-  ; DetailPrint "Search [$1] found directory [$0\$2]"
-  Push $0
-  Push $1
-  Push $0\$2
-  Call un.RemoveSymlinks
-  Pop $1
-  Pop $0
-  ; DetailPrint "Search [$1] restored for [$0]"
-  FindNext $1 $2
-  goto RemoveSymlinks-loop
-RemoveSymlinks-delete:
-  ; DetailPrint "Deleting symlink [$0]"
-  SetFileAttributes "$0" "NORMAL"
-  System::Call "kernel32::RemoveDirectoryW(w `$0`) i.n"
-RemoveSymlinks-exit:
-  ; DetailPrint "Search [$1] closed"
- FindClose $1
-FunctionEnd
 ;eof


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org
For additional commands, e-mail: dev-h...@tomcat.apache.org

Reply via email to