/*
* Copyright (c) 2006-2017 DMDirc Developers
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
* OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.dmdirc.ui.core.util;
import com.dmdirc.events.StatusBarMessageEvent;
import com.dmdirc.events.UnknownURLEvent;
import com.dmdirc.interfaces.ConnectionManager;
import com.dmdirc.events.eventbus.EventBus;
import com.dmdirc.interfaces.config.AggregateConfigProvider;
import com.dmdirc.ui.StatusMessage;
import com.dmdirc.util.CommandUtils;
import java.awt.Desktop;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Date;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.dmdirc.util.LogUtils.USER_ERROR;
/** Handles URLs. */
public class URLHandler {
private static final Logger LOG = LoggerFactory.getLogger(URLHandler.class);
/** The time a browser was last launched. */
private static Date lastLaunch;
/** Event bus to fire unknown protocol errors on. */
private final EventBus eventBus;
/** Config manager. */
private final AggregateConfigProvider config;
/** Server manager to use to connect to servers. */
private final ConnectionManager connectionManager;
/** Desktop handler. */
private final Desktop desktop;
/**
* Instantiates a new URL Handler.
*
* @param eventBus Event bus to fire unknown protocol errors on.
* @param globalConfig Config to retrieve settings from
* @param connectionManager Server manager to connect to servers
*/
@Inject
public URLHandler(
final EventBus eventBus,
final AggregateConfigProvider globalConfig,
final ConnectionManager connectionManager) {
this.eventBus = eventBus;
this.config = globalConfig;
this.connectionManager = connectionManager;
this.desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
}
/**
* Launches an application for a given url.
*
* @param urlString URL to parse
*/
public void launchApp(final String urlString) {
final String sanitisedString = getSanitisedString(urlString);
URI uri;
try {
uri = new URI(sanitisedString);
if (uri.getScheme() == null) {
uri = new URI("http://" + sanitisedString);
}
} catch (URISyntaxException ex) {
LOG.info(USER_ERROR, "Unavlid URL: {}", ex.getMessage(), ex);
return;
}
launchApp(uri);
}
/**
* Sanitises the specified string so that it may be used as a {@link URI}. Sanitisation consists
* of:
*
* - replacing any pipe character with its hex escape
* - replacing any hash character in the fragment with its hex escape
*
*
* @since 0.6.5
* @param urlString The string to be sanitised
*
* @return A sanitised version of the specified string.
*/
protected static String getSanitisedString(final String urlString) {
String sanitisedString = urlString.replace("|", "%7C");
final int index = sanitisedString.indexOf('#');
if (sanitisedString.lastIndexOf('#') > index) {
sanitisedString = sanitisedString.substring(0, index + 1)
+ sanitisedString.substring(index + 1).replace("#", "%23");
}
return sanitisedString;
}
/**
* Launches an application for a given url.
*
* @param url URL to parse
*/
public void launchApp(final URL url) {
URI uri;
try {
uri = url.toURI();
if (uri.getScheme() == null) {
uri = new URI("http://" + url);
}
} catch (URISyntaxException ex) {
LOG.info(USER_ERROR, "Invalid URL: {}", ex.getMessage(), ex);
return;
}
launchApp(uri);
}
/**
* Launches an application for a given url.
*
* @param uri URL to parse
*/
public void launchApp(final URI uri) {
final Date oldLaunch = lastLaunch;
lastLaunch = new Date();
if (config.getOptionBool("browser", "uselaunchdelay") && oldLaunch != null) {
final Long diff = lastLaunch.getTime() - oldLaunch.getTime();
if (diff < config.getOptionInt("browser", "launchdelay")) {
return;
}
}
if (!config.hasOptionString("protocol", uri.getScheme().toLowerCase())) {
eventBus.publish(new UnknownURLEvent(uri));
return;
}
final String command = config.getOption("protocol", uri.getScheme().toLowerCase());
switch (command) {
case "DMDIRC":
eventBus.publishAsync(new StatusBarMessageEvent(
new StatusMessage("Connecting to: " + uri, config)));
connectionManager.connectToAddress(uri);
break;
case "BROWSER":
eventBus.publishAsync(new StatusBarMessageEvent(
new StatusMessage("Opening: " + uri, config)));
execBrowser(uri);
break;
case "MAIL":
execMail(uri);
break;
default:
eventBus.publishAsync(new StatusBarMessageEvent(
new StatusMessage("Opening: " + uri, config)));
execApp(substituteParams(uri, command));
break;
}
}
/**
* Substitutes variables into a command.
*
* @param url data url
* @param command command to be substituted
*
* @return Substituted command
*/
public static String substituteParams(final URI url, final String command) {
final String userInfo = url.getUserInfo();
String fragment = "";
String host = "";
String path = "";
String protocol = "";
String query = "";
String username = "";
String password = "";
String port = "";
String newCommand = command;
if (url.getFragment() != null) {
fragment = url.getFragment();
}
if (url.getHost() != null) {
host = url.getHost();
}
if (url.getPath() != null) {
path = url.getPath();
}
if (url.getScheme() != null) {
protocol = url.getScheme();
}
if (url.getQuery() != null) {
query = url.getQuery();
}
if (url.getPort() > 0) {
port = String.valueOf(url.getPort());
}
if (userInfo != null && !userInfo.isEmpty()) {
if (userInfo.indexOf(':') == -1) {
username = userInfo;
} else {
final int pos = userInfo.indexOf(':');
username = userInfo.substring(0, pos);
password = userInfo.substring(pos + 1);
}
}
newCommand = newCommand.replace("$url", url.toString());
newCommand = newCommand.replace("$fragment", fragment);
newCommand = newCommand.replace("$host", host);
newCommand = newCommand.replace("$path", path);
newCommand = newCommand.replace("$port", port);
newCommand = newCommand.replace("$query", query);
newCommand = newCommand.replace("$protocol", protocol);
newCommand = newCommand.replace("$username", username);
newCommand = newCommand.replace("$password", password);
return newCommand;
}
/**
* Launches an application.
*
* @param command Application and arguments
*/
private void execApp(final String command) {
try {
Runtime.getRuntime().exec(CommandUtils.parseArguments(command));
} catch (IOException ex) {
LOG.info(USER_ERROR, "Unable to run application: {}", ex.getMessage(), ex);
}
}
/**
* Opens the specified URL in the users browser.
*
* @param url URL to open
*/
private void execBrowser(final URI url) {
if (desktop != null && desktop.isSupported(Desktop.Action.BROWSE)) {
try {
desktop.browse(url);
} catch (IOException ex) {
LOG.info(USER_ERROR, "Unable to open URL: {}", ex.getMessage(), ex);
}
} else {
LOG.info(USER_ERROR, "Unable to open your browser: Your desktop enviroment is " +
"not supported, please go to the URL Handlers section of " +
"the preferences dialog and set the path to your browser manually");
}
}
/**
* Opens the specified URL in the users mail client.
*
* @param url URL to open
*/
private void execMail(final URI url) {
if (desktop != null && desktop.isSupported(Desktop.Action.MAIL)) {
try {
desktop.mail(url);
} catch (IOException ex) {
LOG.info(USER_ERROR, "Unable to open URL: {}", ex.getMessage(), ex);
}
} else {
LOG.info(USER_ERROR, "Unable to open your mail client: Your desktop enviroment is " +
"not supported, please go to the URL Handlers section of " +
"the preferences dialog and set the path to your browser manually");
}
}
}