/*******************************************************************************
* Copyright (c) 2009, 2016 Red Hat Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Alexander Kurtakov - initial API and implementation
*******************************************************************************/
package org.eclipse.linuxtools.internal.man.parser;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.linuxtools.internal.man.preferences.PreferenceConstants;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
/**
* Parser for the man executable output.
*/
public class ManParser {
private static final int DEFAULT_SSH_PORT = 22;
/**
* Gets the list of paths returned when one runs "man -w" with no other
* parameters. This is the list of directories that is searched by man for
* man pages.
*
* @return the list of paths in which man searches for man pages in same
* order that man would return them
*/
public static List<Path> getManPaths() {
// Build param list
List<String> params = new ArrayList<>();
params.add(getManExecutable());
params.add("-w"); //$NON-NLS-1$
List<Path> manPaths = new ArrayList<>();
ProcessBuilder builder = new ProcessBuilder(params);
try (InputStream stdout = builder.start().getInputStream()) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int x;
while ((x = stdout.read()) != -1) {
bos.write(x);
}
for (String path : bos.toString().trim()
.split(File.pathSeparator)) {
manPaths.add(Paths.get(path));
}
} catch (IOException e) {
Bundle bundle = FrameworkUtil.getBundle(ManParser.class);
Status status = new Status(IStatus.ERROR, e.getMessage(),
bundle.getSymbolicName());
Platform.getLog(bundle).log(status);
}
return manPaths;
}
/**
* Opens a manual page and returns an input stream from which to read it.
*
* @param page
* the name of the man page to open
* @param html
* true to open the given man page as an HTML document, false to
* open it as a plain text document suitable for display in a
* terminal
* @param sections
* a string array of manual sections in which to look for the
* given man page
* @return a new input stream, the caller is responsible for closing it
*/
public InputStream getManPage(String page, boolean html,
String... sections) {
StringBuilder sectionParam = new StringBuilder();
for (String section : sections) {
if (sectionParam.length() > 0) {
sectionParam.append(':');
}
sectionParam.append(section);
}
// Build param list
List<String> params = new ArrayList<>();
params.add(getManExecutable());
if (page != null && !page.isEmpty() && sectionParam.length() > 0) {
params.add("-S"); //$NON-NLS-1$
params.add(sectionParam.toString());
}
if (html) {
params.add("-Thtml"); //$NON-NLS-1$
}
params.add(page);
ProcessBuilder builder = new ProcessBuilder(params);
InputStream stdout = null;
try {
Process process = builder.start();
stdout = process.getInputStream();
} catch (IOException e) {
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
Status status = new Status(IStatus.ERROR, e.getMessage(),
bundle.getSymbolicName());
Platform.getLog(bundle).log(status);
}
return stdout;
}
/**
* Returns the raw representation of the man executable for a given man page
* i.e. `man ls`.
*
* @param manPage
* The man page to fetch.
* @return Raw output of the man command.
*/
public StringBuilder getRawManPage(String manPage) {
StringBuilder sb = new StringBuilder();
try (InputStream manContent = getManPage(manPage, false);
BufferedReader reader = manContent != null
? new BufferedReader(new InputStreamReader(manContent))
: null) {
if (reader != null) {
sb.append(reader.lines().collect(Collectors.joining("\n"))); //$NON-NLS-1$
}
} catch (IOException e) {
Bundle bundle = FrameworkUtil.getBundle(this.getClass());
Status status = new Status(IStatus.ERROR, e.getMessage(),
bundle.getSymbolicName());
Platform.getLog(bundle).log(status);
}
return sb;
}
/**
* Returns the raw representation of the man page of an executable on a
* remote machine.
*
* @param manPage
* The man page to fetch.
* @param user
* The name of the user to access the man page as.
* @param host
* The name of host where the man page is to be fetched from.
* @param password
* The user's login password.
* @return Raw output of the man command.
*/
public StringBuilder getRemoteRawManPage(String manPage, String user,
String host, String password) {
final StringBuilder sb = new StringBuilder();
OutputStream out = new OutputStream() {
@Override
public void write(int b) throws IOException {
sb.append((char) b);
}
};
try {
execRemoteAndWait(new String[] { getManExecutable(), manPage }, out,
out, user, host, password);
} catch (JSchException e) {
sb.setLength(0);
sb.append(Messages.ManParser_RemoteAccessError);
}
return sb;
}
private static Channel execRemoteAndWait(String[] args, OutputStream out,
OutputStream err, String user, String host, String password)
throws JSchException {
Channel channel = execRemote(args, out, err, user, host, password);
while (!channel.isClosed()) {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
// Thread was interrupted just return.
return channel;
}
}
return channel;
}
private static Channel execRemote(String[] args, OutputStream out,
OutputStream err, String user, String host, String password)
throws JSchException {
JSch jsch = new JSch();
Session session = jsch.getSession(user, host, DEFAULT_SSH_PORT);
session.setPassword(password);
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no"); //$NON-NLS-1$//$NON-NLS-2$
session.setConfig(config);
session.connect();
StringBuilder command = new StringBuilder();
for (int i = 0; i < args.length; i++) {
command.append(args[i] + ' ');
}
ChannelExec channel = (ChannelExec) session.openChannel("exec"); //$NON-NLS-1$
channel.setPty(true);
channel.setCommand(command.toString());
channel.setInputStream(null, true);
channel.setOutputStream(out, true);
channel.setExtOutputStream(err, true);
channel.connect();
return channel;
}
private static String getManExecutable() {
IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(
FrameworkUtil.getBundle(ManParser.class).getSymbolicName());
return prefs.get(PreferenceConstants.P_PATH,
PreferenceConstants.P_PATH_DEFAULT);
}
}