From c54c7989c62ff90e02ee8097aa8fb039f9a085b5 Mon Sep 17 00:00:00 2001 From: Sergey Ponomarev Date: Sat, 25 Jul 2026 20:47:24 +0300 Subject: [PATCH] SPARK-2268: handle file URLs better --- .../filetransfer/SparkTransferManager.java | 6 +- .../spark/util/BrowserLauncher.java | 181 ++++++++++++++++-- .../plugin/alerts/BroadcastPlugin.java | 8 +- .../transfer/ui/ReceiveFileTransfer.java | 45 +---- .../transfer/ui/SendFileTransfer.java | 14 +- .../fileupload/FileUploadChatComponent.java | 15 +- 6 files changed, 174 insertions(+), 95 deletions(-) diff --git a/core/src/main/java/org/jivesoftware/spark/filetransfer/SparkTransferManager.java b/core/src/main/java/org/jivesoftware/spark/filetransfer/SparkTransferManager.java index bdbf47ab9..8bad29798 100644 --- a/core/src/main/java/org/jivesoftware/spark/filetransfer/SparkTransferManager.java +++ b/core/src/main/java/org/jivesoftware/spark/filetransfer/SparkTransferManager.java @@ -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()); // } // }); } diff --git a/core/src/main/java/org/jivesoftware/spark/util/BrowserLauncher.java b/core/src/main/java/org/jivesoftware/spark/util/BrowserLauncher.java index 0b5af0407..4aa2be514 100644 --- a/core/src/main/java/org/jivesoftware/spark/util/BrowserLauncher.java +++ b/core/src/main/java/org/jivesoftware/spark/util/BrowserLauncher.java @@ -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"); + } } diff --git a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastPlugin.java b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastPlugin.java index 641c08139..49604d67a 100644 --- a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastPlugin.java +++ b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/alerts/BroadcastPlugin.java @@ -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); } } }); diff --git a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/ReceiveFileTransfer.java b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/ReceiveFileTransfer.java index 18ace7845..f08cf2609 100644 --- a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/ReceiveFileTransfer.java +++ b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/ReceiveFileTransfer.java @@ -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); - } - } } diff --git a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/SendFileTransfer.java b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/SendFileTransfer.java index 3e4de3dd5..df89bd28e 100644 --- a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/SendFileTransfer.java +++ b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/filetransfer/transfer/ui/SendFileTransfer.java @@ -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) { diff --git a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/fileupload/FileUploadChatComponent.java b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/fileupload/FileUploadChatComponent.java index 01518299e..b061f631b 100644 --- a/core/src/main/java/org/jivesoftware/sparkimpl/plugin/fileupload/FileUploadChatComponent.java +++ b/core/src/main/java/org/jivesoftware/sparkimpl/plugin/fileupload/FileUploadChatComponent.java @@ -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) { }