001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.scm.provider.svn.svnexe.command;
020
021import java.io.File;
022import java.io.FileOutputStream;
023import java.io.IOException;
024import java.io.PrintStream;
025import java.util.List;
026
027import org.apache.commons.lang3.StringUtils;
028import org.apache.maven.scm.provider.svn.repository.SvnScmProviderRepository;
029import org.apache.maven.scm.provider.svn.util.SvnUtil;
030import org.codehaus.plexus.util.Os;
031import org.codehaus.plexus.util.cli.CommandLineException;
032import org.codehaus.plexus.util.cli.CommandLineUtils;
033import org.codehaus.plexus.util.cli.Commandline;
034import org.codehaus.plexus.util.cli.StreamConsumer;
035import org.slf4j.Logger;
036import org.slf4j.LoggerFactory;
037
038/**
039 * Command line construction utility.
040 *
041 * @author Brett Porter
042 * @author Olivier Lamy
043 *
044 */
045public final class SvnCommandLineUtils {
046    private static final Logger LOGGER = LoggerFactory.getLogger(SvnCommandLineUtils.class);
047
048    private SvnCommandLineUtils() {}
049
050    public static void addTarget(Commandline cl, List<File> files) throws IOException {
051        if (files == null || files.isEmpty()) {
052            return;
053        }
054
055        StringBuilder sb = new StringBuilder();
056        String ls = System.getProperty("line.separator");
057        for (File f : files) {
058            sb.append(f.getPath().replace('\\', '/'));
059            sb.append(ls);
060        }
061
062        File targets = File.createTempFile("maven-scm-", "-targets");
063        PrintStream out = new PrintStream(new FileOutputStream(targets));
064        out.print(sb);
065        out.flush();
066        out.close();
067
068        cl.createArg().setValue("--targets");
069        cl.createArg().setValue(targets.getAbsolutePath());
070
071        targets.deleteOnExit();
072    }
073
074    public static Commandline getBaseSvnCommandLine(File workingDirectory, SvnScmProviderRepository repository) {
075        Commandline cl = new Commandline();
076
077        cl.setExecutable("svn");
078        try {
079            cl.addSystemEnvironment();
080            cl.addEnvironment("LC_MESSAGES", "C");
081        } catch (Exception e) {
082            // Do nothing
083        }
084
085        if (workingDirectory != null) {
086            cl.setWorkingDirectory(workingDirectory.getAbsolutePath());
087        }
088
089        if (!StringUtils.isEmpty(System.getProperty("maven.scm.svn.config_directory"))) {
090            cl.createArg().setValue("--config-dir");
091            cl.createArg().setValue(System.getProperty("maven.scm.svn.config_directory"));
092        } else if (!StringUtils.isEmpty(SvnUtil.getSettings().getConfigDirectory())) {
093            cl.createArg().setValue("--config-dir");
094            cl.createArg().setValue(SvnUtil.getSettings().getConfigDirectory());
095        }
096
097        boolean hasAuthInfo = false;
098        if (repository != null && !StringUtils.isEmpty(repository.getUser())) {
099            hasAuthInfo = true;
100            cl.createArg().setValue("--username");
101            cl.createArg().setValue(repository.getUser());
102        }
103
104        if (repository != null && !StringUtils.isEmpty(repository.getPassword())) {
105            hasAuthInfo = true;
106            cl.createArg().setValue("--password");
107            cl.createArg().setValue(repository.getPassword());
108        }
109
110        // [by Lenik] don't overwrite existing auth cache by default.
111        if (hasAuthInfo && !SvnUtil.getSettings().isUseAuthCache()) {
112            cl.createArg().setValue("--no-auth-cache");
113        }
114
115        if (SvnUtil.getSettings().isUseNonInteractive()) {
116            cl.createArg().setValue("--non-interactive");
117        }
118
119        if (SvnUtil.getSettings().isTrustServerCert()) {
120            cl.createArg().setValue("--trust-server-cert");
121        }
122
123        return cl;
124    }
125
126    public static int execute(Commandline cl, StreamConsumer consumer, CommandLineUtils.StringStreamConsumer stderr)
127            throws CommandLineException {
128        // SCM-482: force English resource bundle
129        cl.addEnvironment("LC_MESSAGES", "en");
130
131        int exitCode = CommandLineUtils.executeCommandLine(cl, consumer, stderr);
132
133        exitCode = checkIfCleanUpIsNeeded(exitCode, cl, consumer, stderr);
134
135        return exitCode;
136    }
137
138    public static int execute(
139            Commandline cl, CommandLineUtils.StringStreamConsumer stdout, CommandLineUtils.StringStreamConsumer stderr)
140            throws CommandLineException {
141        int exitCode = CommandLineUtils.executeCommandLine(cl, stdout, stderr);
142
143        exitCode = checkIfCleanUpIsNeeded(exitCode, cl, stdout, stderr);
144
145        return exitCode;
146    }
147
148    private static int checkIfCleanUpIsNeeded(
149            int exitCode, Commandline cl, StreamConsumer consumer, CommandLineUtils.StringStreamConsumer stderr)
150            throws CommandLineException {
151        if (exitCode != 0
152                && stderr.getOutput() != null
153                && stderr.getOutput().indexOf("'svn cleanup'") > 0
154                && stderr.getOutput().indexOf("'svn help cleanup'") > 0) {
155            if (LOGGER.isInfoEnabled()) {
156                LOGGER.info("Svn command failed due to some locks in working copy. We try to run a 'svn cleanup'.");
157            }
158
159            if (executeCleanUp(cl.getWorkingDirectory(), consumer, stderr) == 0) {
160                exitCode = CommandLineUtils.executeCommandLine(cl, consumer, stderr);
161            }
162        }
163        return exitCode;
164    }
165
166    public static int executeCleanUp(File workinDirectory, StreamConsumer stdout, StreamConsumer stderr)
167            throws CommandLineException {
168        Commandline cl = new Commandline();
169
170        cl.setExecutable("svn");
171
172        cl.setWorkingDirectory(workinDirectory.getAbsolutePath());
173
174        if (LOGGER.isInfoEnabled()) {
175            LOGGER.info("Executing: " + SvnCommandLineUtils.cryptPassword(cl));
176
177            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
178                LOGGER.info("Working directory: " + cl.getWorkingDirectory().getAbsolutePath());
179            }
180        }
181
182        return CommandLineUtils.executeCommandLine(cl, stdout, stderr);
183    }
184
185    public static String cryptPassword(Commandline cl) {
186        String clString = cl.toString();
187
188        final String passwordOpt = "--password";
189        String quoteChar;
190        String escapedQuoteChar;
191        String cryptedPassword;
192
193        int pos = clString.indexOf(passwordOpt);
194
195        if (pos > 0) {
196            if (Os.isFamily(Os.FAMILY_WINDOWS)) {
197                quoteChar = "\"";
198                escapedQuoteChar = "\"\"";
199                cryptedPassword = "*****";
200            } else {
201                quoteChar = "'";
202                escapedQuoteChar = "'\"'\"'";
203                cryptedPassword = "'*****'";
204            }
205
206            // Move pointer after password option
207            pos += passwordOpt.length();
208
209            // Skip quote after password option
210            if (clString.substring(pos, pos + 1).equals(quoteChar)) {
211                pos++;
212            }
213
214            // Skip space after password option
215            pos++;
216
217            String beforePassword = clString.substring(0, pos);
218            String afterPassword = clString.substring(pos);
219
220            if (afterPassword.startsWith(quoteChar)) {
221                pos = 1;
222                while (afterPassword.indexOf(escapedQuoteChar, pos) != -1) {
223                    pos = afterPassword.indexOf(escapedQuoteChar, pos) + escapedQuoteChar.length();
224                }
225                afterPassword = afterPassword.substring(afterPassword.indexOf(quoteChar, pos) + quoteChar.length());
226            } else {
227                // We assume that the password arg ist not the last one on the arg list
228                afterPassword = afterPassword.substring(afterPassword.indexOf(' '));
229            }
230
231            clString = beforePassword + cryptedPassword + afterPassword;
232        }
233
234        return clString;
235    }
236}