SPARK-2268: handle file URLs better

This commit is contained in:
Sergey Ponomarev
2026-07-25 20:47:24 +03:00
parent 5d7faa3f7e
commit c54c7989c6
6 changed files with 174 additions and 95 deletions

View File

@ -180,11 +180,7 @@ public class SparkTransferManager {
// commandPanel.add(viewDownloads);
// viewDownloads.addActionListener(new ActionListener() {
// public void actionPerformed(ActionEvent e) {
// try {
// Desktop.getDesktop().browse(Downloads.getDownloadDirectory().toURI());
// } catch (IOException e1) {
// Log.error("Could not find file-browser");
// }
// BrowserLauncher.openFolder(Downloads.getDownloadDirectory());
// }
// });
}

View File

@ -15,6 +15,7 @@
*/
package org.jivesoftware.spark.util;
import org.jivesoftware.Spark;
import org.jivesoftware.spark.util.log.Log;
import java.awt.Desktop;
@ -22,39 +23,171 @@ import java.io.File;
import java.io.IOException;
import java.net.URI;
import static org.apache.commons.lang3.StringUtils.trimToNull;
@SuppressWarnings("HttpUrlsUsage")
public class BrowserLauncher {
/**
* Opens a URL, file path, Windows UNC path, or Samba share when clicked in a chat.
* Supports: http(s), ftp, file, www, smb://, \\server\share, and local file paths.
*
* @param url the URL or path to open
*/
public static void openURL(String url) {
url = trimToNull(url);
if (url == null) {
Log.error("Cannot open empty or null URL");
return;
}
try {
if (url.startsWith("http") || url.startsWith("ftp") || url.startsWith("file") || url.startsWith("www")) {
if (url.startsWith("file") && url.contains(" ")) {
url = url.replace(" ", "%20");
// Handle web protocols
if (isWebProtocol(url)) {
openWebUrl(url);
return;
}
if (url.startsWith("www")) {
url = "http://" + url;
// Handle Samba/SMB URLs
if (url.startsWith("smb://") || url.startsWith("nfs://")) {
openSambaUrl(url);
return;
}
try {
Desktop.getDesktop().browse(new URI(url));
} catch (Exception ex) {
// fallback on Linux
Runtime.getRuntime().exec("xdg-open " + url);
// Handle Windows UNC paths
if (isWindowsUncPath(url)) {
openInFileManager(url);
return;
}
} else {
if (url.startsWith("webdav://") || url.startsWith("dav://") || url.startsWith("davs://")) {
openWebDav(url);
return;
}
if (url.startsWith("sftp://") ||
url.startsWith("ftp://") || url.startsWith("ftps://")) {
openInFileManager(url);
return;
}
// Handle file:// with spaces
if (url.startsWith("file://")) {
url = url.substring(7).replace("%20", " ");
}
// Handle local file paths
File f = new File(url);
if (f.exists() && Desktop.isDesktopSupported()) {
if (f.exists()) {
openInFileManager(f.getAbsolutePath());
return;
}
// Last resort: try as URL with https prefix
openAsHttpsUrl(url);
} catch (Exception e) {
e.printStackTrace();
Log.error("Unable to open url: " + url, e);
}
}
/**
* Checks if the URL uses a web protocol (http, https, ftp, file, www).
*/
private static boolean isWebProtocol(String url) {
return url.startsWith("https://") || url.startsWith("http://") || url.startsWith("www.");
}
/**
* Checks if the path is a Windows UNC path (starts with \\ or //).
*/
private static boolean isWindowsUncPath(String path) {
return path.startsWith("\\\\") || path.startsWith("//");
}
/**
* Opens a Samba URL (smb://server/share).
* On Windows, converts to UNC path
*/
private static void openSambaUrl(String url) throws IOException {
if (Spark.isWindows()) {
// Convert smb://server/share to \\server\share
String uncPath = url.substring(6); // Remove "smb://" or "nfs://"
uncPath = "\\\\" + uncPath.replace("/", "\\");
openInFileManager(uncPath);
return;
}
openInFileManager(url);
}
private static void openWebDav(String url) throws IOException {
if (Spark.isWindows()) {
// Convert davs://server/share to https://server/share
String davUrl = url.replace("webdav://", "http://");
davUrl = davUrl.replace("dav://", "http://");
davUrl = davUrl.replace("davs://", "https://");
openInFileManager(davUrl);
return;
}
openInFileManager(url);
}
public static void openInFileManager(String path) {
File file = new File(path);
openInFileManager(file);
}
public static void openInFileManager(File file) {
try {
Desktop.getDesktop().open(file);
} catch (IOException e) {
Log.warning("Unable to open file directly, opening in edit mode: " + file + ": " + e);
try {
Desktop.getDesktop().edit(file);
} catch (IOException e2) {
Log.warning("Unable to open file directly, opening containing folder instead: " + file + ": " + e2);
try {
Desktop.getDesktop().open(f);
} catch (Exception ex) {
if (!url.startsWith("//")) {
url = "//" + url;
}
Desktop.getDesktop().browse(new URI("http:" + url));
openContainingFolder(file);
} catch (IOException e3) {
Log.error("Unable to open folder: " + file + ": " + e3);
}
}
}
}
/**
* Opens a URL with a recognized web protocol.
*/
private static void openWebUrl(String url) throws Exception {
// Handle www. prefix
if (url.startsWith("www.")) {
url = "https://" + url;
}
catch (Exception e) {
Log.error("Unable to open url " + url, e);
try {
Desktop.getDesktop().browse(new URI(url));
} catch (Exception ex) {
// Fallback for Linux systems
Runtime.getRuntime().exec(new String[]{"xdg-open", url});
}
}
/**
* Attempts to open a URL by adding https:// prefix.
*/
private static void openAsHttpsUrl(String url) throws Exception {
if (!url.startsWith("//")) {
url = "//" + url;
}
URI uri = new URI("https:" + url);
Desktop.getDesktop().browse(uri);
}
private static void openContainingFolder(File file) throws IOException {
if (Spark.isWindows()) {
Runtime.getRuntime().exec(new String[]{
"explorer.exe",
"/select," + file.getAbsolutePath()
});
return;
}
File parent = file.getParentFile();
if (parent != null) {
Desktop.getDesktop().open(parent);
} else {
throw new IOException("Unable to determine parent folder for: " + file);
}
}
@ -68,4 +201,12 @@ public class BrowserLauncher {
Log.error("Unable to open folder: " + file, ex);
}
}
public static void main(String[] args) throws InterruptedException {
// openURL("C:\\Users\\Admin\\Desktop\\avatar.jpg");
// openURL("C:\\Users\\Admin\\Desktop\\avatar.jpg");
openURL("D:\\work\\Spark\\core\\src\\main\\resources\\images\\alert.png");
// openURL("ftp://jkl.mn");
// openURL("ftp://jkl.mn");
}
}

View File

@ -18,7 +18,6 @@ package org.jivesoftware.sparkimpl.plugin.alerts;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;
@ -54,6 +53,7 @@ import org.jivesoftware.spark.ui.MessageListener;
import org.jivesoftware.spark.ui.SparkTabHandler;
import org.jivesoftware.spark.ui.rooms.ChatRoomImpl;
import org.jivesoftware.spark.ui.status.StatusBar;
import org.jivesoftware.spark.util.BrowserLauncher;
import org.jivesoftware.spark.util.ModelUtil;
import org.jivesoftware.spark.util.ResourceUtils;
import org.jivesoftware.spark.util.log.Log;
@ -440,9 +440,9 @@ public class BroadcastPlugin extends SparkTabHandler implements Plugin, StanzaLi
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
try {
Log.warning("Open the URL in default browser" + e.getURL());
Desktop.getDesktop().browse(e.getURL().toURI());
} catch (IOException | URISyntaxException ex) {
Log.error(ex.getCause());
BrowserLauncher.openURL(e.getURL().toURI().toString());
} catch (URISyntaxException ex) {
Log.error(ex);
}
}
});

View File

@ -17,7 +17,6 @@ package org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
@ -57,6 +56,7 @@ import org.jivesoftware.spark.preference.Preference;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.ContactItem;
import org.jivesoftware.spark.ui.ContactList;
import org.jivesoftware.spark.util.BrowserLauncher;
import org.jivesoftware.spark.util.ByteFormat;
import org.jivesoftware.spark.util.GraphicUtils;
import org.jivesoftware.spark.util.ResourceUtils;
@ -442,7 +442,7 @@ public class ReceiveFileTransfer extends JPanel {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
launchFile(downloadedFile);
BrowserLauncher.openInFileManager(downloadedFile);
}
}
});
@ -463,7 +463,7 @@ public class ReceiveFileTransfer extends JPanel {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
launchFile(downloadedFile);
BrowserLauncher.openInFileManager(downloadedFile);
}
}
});
@ -554,7 +554,7 @@ public class ReceiveFileTransfer extends JPanel {
@Override
public void mousePressed(MouseEvent e) {
launchFile(downloadedFile);
BrowserLauncher.openInFileManager(downloadedFile);
}
});
@ -784,41 +784,4 @@ public class ReceiveFileTransfer extends JPanel {
return uri;
}
/**
* Attempts to open the file. If no associated application can be found, or if that application fails to launch, or
* if the provided file is a directory, a file browser that shows the content of the folder in which the file
* resides is shown.
*
* @param file the file to be shown.
*/
private void launchFile(File file) {
if (!Desktop.isDesktopSupported()) {
Log.warning("Cannot launch file (not supported in this environment).");
return;
}
final Desktop desktop = Desktop.getDesktop();
try {
desktop.open(file);
} catch (IOException ex) {
try {
// Potentially trying to open on a network path that has spaces (SPARK-1350). Try again, using a URI.
desktop.browse(getFileURI(file));
return;
} catch (Exception ex1) {
// The specified file has no associated application or the associated application fails to be launched.
// Show the folder containing the file as a last-ditch effort (SPARK-2199).
if (file.isFile() && file.getParentFile() != null) {
try {
desktop.open(file.getParentFile());
return;
} catch (IOException ex2) {
// Log the original exception (see below)
}
}
}
// In case of failure, log the original exception, which is likely to be most relevant.
Log.warning("Unable to open file: " + file.getName(), ex);
}
}
}

View File

@ -17,7 +17,6 @@ package org.jivesoftware.sparkimpl.plugin.filetransfer.transfer.ui;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
@ -25,7 +24,6 @@ import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import javax.swing.*;
@ -46,11 +44,11 @@ import org.jivesoftware.spark.component.FileDragLabel;
import org.jivesoftware.spark.ui.ChatRoom;
import org.jivesoftware.spark.ui.ContactItem;
import org.jivesoftware.spark.ui.ContactList;
import org.jivesoftware.spark.util.BrowserLauncher;
import org.jivesoftware.spark.util.ByteFormat;
import org.jivesoftware.spark.util.GraphicUtils;
import org.jivesoftware.spark.util.SwingWorker;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.settings.Sizes;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
import org.jxmpp.jid.BareJid;
import org.jxmpp.jid.EntityFullJid;
@ -248,7 +246,7 @@ public class SendFileTransfer extends JPanel {
label.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
openFile(fileToSend);
BrowserLauncher.openInFileManager(fileToSend);
}
@Override
@ -263,14 +261,6 @@ public class SendFileTransfer extends JPanel {
});
}
private void openFile(File downloadedFile) {
try {
Desktop.getDesktop().open(downloadedFile);
} catch (IOException e) {
Log.error("An error occurred while trying to open downloaded file: " + downloadedFile, e);
}
}
private void updateBar(final OutgoingFileTransfer transfer, String nickname, String kBperSecond) {
FileTransfer.Status status = transfer.getStatus();
switch (status) {

View File

@ -17,12 +17,12 @@ package org.jivesoftware.sparkimpl.plugin.fileupload;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smackx.filetransfer.FileTransfer.Status;
import org.jivesoftware.smackx.filetransfer.OutgoingFileTransfer;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.component.FileDragLabel;
import org.jivesoftware.spark.ui.ContactItem;
import org.jivesoftware.spark.ui.ContactList;
import org.jivesoftware.spark.util.BrowserLauncher;
import org.jivesoftware.spark.util.ByteFormat;
import org.jivesoftware.spark.util.GraphicUtils;
import org.jivesoftware.spark.util.SwingWorker;
@ -38,10 +38,8 @@ import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
@ -49,7 +47,6 @@ import java.awt.Insets;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@ -161,7 +158,7 @@ public class FileUploadChatComponent extends JPanel {
label.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
openFile(fileToSend);
BrowserLauncher.openInFileManager(fileToSend);
}
@Override
@ -176,14 +173,6 @@ public class FileUploadChatComponent extends JPanel {
});
}
private void openFile(File downloadedFile) {
try {
Desktop.getDesktop().open(downloadedFile);
} catch (IOException e) {
Log.error("An error occurred while trying to open downloaded file: " + downloadedFile, e);
}
}
private void updateBar(final OutgoingFileTransfer transfer, String nickname, String kBperSecond) {
}