mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
svn: Implemented basic svn+ssh authentication using terminal (under svn.use.terminal registry key)
This commit is contained in:
@@ -304,6 +304,7 @@ testng_sm_runner=false
|
||||
show.flex.debug.design.view=false
|
||||
git.new.log=true
|
||||
|
||||
svn.use.terminal=false
|
||||
|
||||
completion.enable.relevant.method.chain.suggestions=false
|
||||
ide.mac.message.sheets.java.emulation=false
|
||||
|
||||
@@ -136,6 +136,12 @@ public class CommandExecutor {
|
||||
return outputAdapter.getOutput().getStderr();
|
||||
}
|
||||
|
||||
// TODO: Carefully here - do not modify command from threads other than the one started command execution
|
||||
@NotNull
|
||||
public Command getCommand() {
|
||||
return myCommand;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for process termination
|
||||
* @param timeout
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.jetbrains.idea.svn.commandLine;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -170,8 +171,19 @@ public class CommandRuntime {
|
||||
|
||||
@NotNull
|
||||
private CommandExecutor newExecutor(@NotNull Command command) {
|
||||
command.putIfNotPresent("--non-interactive");
|
||||
return new CommandExecutor(exePath, command);
|
||||
final CommandExecutor executor;
|
||||
|
||||
if (!Registry.is("svn.use.terminal")) {
|
||||
command.putIfNotPresent("--non-interactive");
|
||||
executor = new CommandExecutor(exePath, command);
|
||||
}
|
||||
else {
|
||||
command.put("--force-interactive");
|
||||
executor = new TerminalExecutor(exePath, command);
|
||||
((TerminalExecutor)executor).addInteractiveListener(new TerminalSshModule(executor, myAuthCallback));
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.idea.svn.commandLine;
|
||||
|
||||
import com.intellij.openapi.util.Key;
|
||||
|
||||
/**
|
||||
* @author Konstantin Kolosovsky.
|
||||
*/
|
||||
public interface InteractiveCommandListener {
|
||||
|
||||
boolean handlePrompt(String line, Key outputType);
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.idea.svn.commandLine;
|
||||
|
||||
import com.intellij.execution.CommandLineUtil;
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.process.OSProcessHandler;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.pty4j.PtyProcess;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Konstantin Kolosovsky.
|
||||
*/
|
||||
public class TerminalExecutor extends CommandExecutor {
|
||||
|
||||
// max available value is 480
|
||||
// if greater value is provided than the default value of 80 will be assumed
|
||||
// this could provide unnecessary line breaks and thus could break parsing logic
|
||||
private static final int TERMINAL_WINDOW_MAX_COLUMNS = 480;
|
||||
|
||||
static {
|
||||
if (SystemInfo.isWindows) {
|
||||
System.setProperty("win.pty.cols", String.valueOf(TERMINAL_WINDOW_MAX_COLUMNS));
|
||||
}
|
||||
}
|
||||
|
||||
private final List<InteractiveCommandListener> myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList();
|
||||
|
||||
public TerminalExecutor(@NotNull @NonNls String exePath, @NotNull Command command) {
|
||||
super(exePath, command);
|
||||
}
|
||||
|
||||
public void addInteractiveListener(@NotNull InteractiveCommandListener listener) {
|
||||
myInteractiveListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startHandlingStreams() {
|
||||
for (InteractiveCommandListener listener : myInteractiveListeners) {
|
||||
((TerminalProcessHandler)myHandler).addInteractiveListener(listener);
|
||||
}
|
||||
|
||||
super.startHandlingStreams();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected OSProcessHandler createProcessHandler() {
|
||||
return new TerminalProcessHandler(myProcess);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected Process createProcess() throws ExecutionException {
|
||||
List<String> parameters =
|
||||
escapeArguments(CommandLineUtil.toCommandLine(myCommandLine.getExePath(), myCommandLine.getParametersList().getList()));
|
||||
|
||||
try {
|
||||
return PtyProcess
|
||||
.exec(ArrayUtil.toStringArray(parameters), myCommandLine.getEnvironment(), myCommandLine.getWorkDirectory().getAbsolutePath());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new ExecutionException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> escapeArguments(List<String> collection) {
|
||||
// TODO: Add additional checks like in java.lang.ProcessImpl constructor
|
||||
return ContainerUtil.map(collection, new Function<String, String>() {
|
||||
@Override
|
||||
public String fun(String s) {
|
||||
return s.contains(" ") ? "\"" + s + "\"" : s;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.idea.svn.commandLine;
|
||||
|
||||
import com.intellij.execution.process.CapturingProcessAdapter;
|
||||
import com.intellij.execution.process.OSProcessHandler;
|
||||
import com.intellij.execution.process.ProcessEvent;
|
||||
import com.intellij.execution.process.ProcessOutputTypes;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Konstantin Kolosovsky.
|
||||
*/
|
||||
public class TerminalProcessHandler extends OSProcessHandler {
|
||||
|
||||
// see http://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
private static final String NON_CSI_ESCAPE_CODE = "\u001B.[@-_]";
|
||||
private static final String CSI_ESCAPE_CODE = "\u001B\\[(.*?)[@-~]";
|
||||
|
||||
private final List<InteractiveCommandListener> myInteractiveListeners = ContainerUtil.createLockFreeCopyOnWriteList();
|
||||
private final CapturingProcessAdapter terminalOutputCapturer = new CapturingProcessAdapter();
|
||||
|
||||
private final StringBuilder outputLine = new StringBuilder();
|
||||
private final StringBuilder errorLine = new StringBuilder();
|
||||
|
||||
public TerminalProcessHandler(@NotNull Process process) {
|
||||
super(process);
|
||||
}
|
||||
|
||||
public void addInteractiveListener(@NotNull InteractiveCommandListener listener) {
|
||||
myInteractiveListeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void notifyTextAvailable(String text, Key outputType) {
|
||||
terminalOutputCapturer.onTextAvailable(new ProcessEvent(this, text), outputType);
|
||||
|
||||
// filter terminal escape codes - they are presented in the output for windows platform
|
||||
String filteredText = text.replaceAll(CSI_ESCAPE_CODE, "").replaceAll(NON_CSI_ESCAPE_CODE, "");
|
||||
// trim leading '\r' symbols - as they break xml parsing logic
|
||||
filteredText = StringUtil.trimLeading(filteredText);
|
||||
|
||||
if (!StringUtil.isEmpty(filteredText)) {
|
||||
StringBuilder lastLine = getLastLineFor(outputType);
|
||||
String currentLine = lastLine.append(filteredText).toString();
|
||||
lastLine.setLength(0);
|
||||
|
||||
// check if current line presents some interactive output
|
||||
boolean handled = false;
|
||||
for (InteractiveCommandListener listener : myInteractiveListeners) {
|
||||
handled |= listener.handlePrompt(currentLine, outputType);
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
notify(currentLine, outputType, lastLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notify(@NotNull String text, @NotNull Key outputType, @NotNull StringBuilder lastLine) {
|
||||
// for windows platform output is assumed in format suitable for terminal emulator
|
||||
// for instance, same text could be returned twice with '\r' symbol in between (so in emulator output we'll still see correct
|
||||
// text without duplication)
|
||||
// because of this we manually process '\r' occurrences to get correct output
|
||||
text = removeAllBeforeCaretReturn(text);
|
||||
|
||||
// text is not more than one line - either one line or part of the line
|
||||
if (StringUtil.endsWith(text, "\n")) {
|
||||
// we have full line - notify listeners
|
||||
super.notifyTextAvailable(text, outputType);
|
||||
}
|
||||
else {
|
||||
// save line part to lastLine
|
||||
lastLine.append(text);
|
||||
}
|
||||
}
|
||||
|
||||
private static String removeAllBeforeCaretReturn(@NotNull String line) {
|
||||
int caretReturn = line.lastIndexOf("\r");
|
||||
|
||||
while (caretReturn >= 0) {
|
||||
if (caretReturn + 1 < line.length() && line.charAt(caretReturn + 1) != '\n') {
|
||||
// next symbol is not '\n' - we should not treat text before found caret return symbol
|
||||
line = line.substring(caretReturn + 1);
|
||||
break;
|
||||
}
|
||||
caretReturn = line.lastIndexOf("\r", caretReturn - 1);
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private StringBuilder getLastLineFor(Key outputType) {
|
||||
if (ProcessOutputTypes.STDERR.equals(outputType)) {
|
||||
return errorLine;
|
||||
}
|
||||
else if (ProcessOutputTypes.STDOUT.equals(outputType)) {
|
||||
return outputLine;
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Unknown process output type " + outputType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2000-2013 JetBrains s.r.o.
|
||||
*
|
||||
* 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.jetbrains.idea.svn.commandLine;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
|
||||
import org.tmatesoft.svn.core.auth.SVNAuthentication;
|
||||
import org.tmatesoft.svn.core.auth.SVNSSHAuthentication;
|
||||
|
||||
/**
|
||||
* @author Konstantin Kolosovsky.
|
||||
*/
|
||||
public class TerminalSshModule extends LineCommandAdapter implements CommandRuntimeModule, InteractiveCommandListener {
|
||||
|
||||
private static final Logger LOG = Logger.getInstance(TerminalSshModule.class);
|
||||
|
||||
@NotNull private final CommandExecutor myExecutor;
|
||||
@NotNull private final AuthenticationCallback myAuthCallback;
|
||||
|
||||
// TODO: Do not accept executor here and make it as command runtime module
|
||||
public TerminalSshModule(@NotNull CommandExecutor executor, @NotNull AuthenticationCallback authCallback) {
|
||||
myExecutor = executor;
|
||||
myAuthCallback = authCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(@NotNull Command command) throws SvnBindException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handlePrompt(String line, Key outputType) {
|
||||
boolean result = false;
|
||||
|
||||
if (line.toLowerCase().contains("enter passphrase for key")) {
|
||||
result = handlePassphrase();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean handlePassphrase() {
|
||||
SVNAuthentication authentication =
|
||||
myAuthCallback.requestCredentials(myExecutor.getCommand().getRepositoryUrl(), ISVNAuthenticationManager.SSH);
|
||||
|
||||
if (authentication != null && authentication instanceof SVNSSHAuthentication) {
|
||||
try {
|
||||
myExecutor.write(String.format("%s\n", ((SVNSSHAuthentication)authentication).getPassphrase()));
|
||||
return true;
|
||||
}
|
||||
catch (SvnBindException e) {
|
||||
// TODO: handle this more carefully
|
||||
LOG.info(e);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -76,6 +76,9 @@
|
||||
</SOURCES>
|
||||
</library>
|
||||
</orderEntry>
|
||||
<orderEntry type="library" name="jna" level="project" />
|
||||
<orderEntry type="library" name="pty4j" level="project" />
|
||||
<orderEntry type="library" name="purejavacomm" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user