Skip to content

Commit

Permalink
Improve URL parsing
Browse files Browse the repository at this point in the history
The generated URLs are "normalized" (except for certificates and file uri, because RS still relies on HTML).

Fix URL detection of some URLs with parenthesis.
  • Loading branch information
zapek committed Jan 9, 2025
1 parent e1d4288 commit 553d807
Show file tree
Hide file tree
Showing 36 changed files with 753 additions and 272 deletions.
8 changes: 6 additions & 2 deletions common/src/main/java/io/xeres/common/id/Id.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2023 by David Gerber - https://zapek.com
* Copyright (c) 2019-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand Down Expand Up @@ -115,10 +115,14 @@ public static String toStringLowerCase(long id)
* Converts an identifier into its hexadecimal representation.
*
* @param identifier the identifier
* @return a hexadecimal lowercase representation of the identifier, without prefix
* @return a hexadecimal lowercase representation of the identifier, without prefix, or an empty string if the identifier is empty
*/
public static String toString(Identifier identifier)
{
if (identifier == null)
{
return "";
}
return toString(identifier.getBytes());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import io.xeres.ui.support.sound.SoundService.SoundType;
import io.xeres.ui.support.tray.TrayService;
import io.xeres.ui.support.uri.ChatRoomUri;
import io.xeres.ui.support.uri.ChatRoomUriFactory;
import io.xeres.ui.support.uri.UriService;
import io.xeres.ui.support.util.ImageUtils;
import io.xeres.ui.support.util.TextInputControlUtils;
Expand Down Expand Up @@ -310,7 +309,7 @@ private void createRoomTreeContextMenu()
copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT));
copyLinkItem.setOnAction(event -> {
var chatRoomInfo = ((RoomHolder) event.getSource()).getRoomInfo();
ClipboardUtils.copyTextToClipboard(ChatRoomUriFactory.generate(chatRoomInfo.getName(), chatRoomInfo.getId()));
ClipboardUtils.copyTextToClipboard(new ChatRoomUri(chatRoomInfo.getName(), chatRoomInfo.getId()).toString());
});

var xContextMenu = new XContextMenu<RoomHolder>(subscribeItem, unsubscribeItem, new SeparatorMenuItem(), copyLinkItem);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
* Copyright (c) 2024-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand All @@ -25,7 +25,7 @@
import io.xeres.ui.client.FileClient;
import io.xeres.ui.support.clipboard.ClipboardUtils;
import io.xeres.ui.support.contextmenu.XContextMenu;
import io.xeres.ui.support.uri.FileUriFactory;
import io.xeres.ui.support.uri.FileUri;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
Expand Down Expand Up @@ -173,7 +173,8 @@ private void createFilesTableViewContextMenu()
copyLinkItem.setOnAction(event -> {
if (event.getSource() instanceof FileResult file)
{
ClipboardUtils.copyTextToClipboard(FileUriFactory.generate(file.name(), file.size(), Sha1Sum.fromString(file.hash())));
var fileUri = new FileUri(file.name(), file.size(), Sha1Sum.fromString(file.hash()));
ClipboardUtils.copyTextToClipboard(fileUri.toString());
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
import io.xeres.ui.support.clipboard.ClipboardUtils;
import io.xeres.ui.support.contextmenu.XContextMenu;
import io.xeres.ui.support.uri.SearchUri;
import io.xeres.ui.support.uri.SearchUriFactory;
import io.xeres.ui.support.util.TextInputControlUtils;
import io.xeres.ui.support.util.UiUtils;
import javafx.application.Platform;
Expand All @@ -38,7 +37,6 @@
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import net.rgielen.fxweaver.core.FxmlView;
import org.apache.commons.lang3.StringUtils;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.materialdesign2.MaterialDesignL;
import org.slf4j.Logger;
Expand Down Expand Up @@ -158,7 +156,8 @@ private void createContextMenu()
copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT));
copyLinkItem.setOnAction(event -> {
var fileResultView = (FileResultView) event.getSource();
ClipboardUtils.copyTextToClipboard(SearchUriFactory.generate(StringUtils.left(fileResultView.getText(), 50), fileResultView.getText()));
var searchUri = new SearchUri(fileResultView.getText());
ClipboardUtils.copyTextToClipboard(searchUri.toString());
});

var xContextMenu = new XContextMenu<Tab>(copyLinkItem);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 by David Gerber - https://zapek.com
* Copyright (c) 2023-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand Down Expand Up @@ -42,7 +42,6 @@
import io.xeres.ui.support.markdown.MarkdownService.ParsingMode;
import io.xeres.ui.support.preference.PreferenceUtils;
import io.xeres.ui.support.uri.ForumUri;
import io.xeres.ui.support.uri.ForumUriFactory;
import io.xeres.ui.support.uri.IdentityUri;
import io.xeres.ui.support.uri.UriService;
import io.xeres.ui.support.util.UiUtils;
Expand Down Expand Up @@ -320,7 +319,8 @@ private void createForumTreeContextMenu()
copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT));
copyLinkItem.setOnAction(event -> {
var forumGroup = ((ForumGroup) event.getSource());
ClipboardUtils.copyTextToClipboard(ForumUriFactory.generate(forumGroup.getName(), forumGroup.getGxsId()));
var forumUri = new ForumUri(forumGroup.getName(), forumGroup.getGxsId(), null);
ClipboardUtils.copyTextToClipboard(forumUri.toString());
});

var xContextMenu = new XContextMenu<ForumGroup>(subscribeItem, unsubscribeItem, new SeparatorMenuItem(), copyLinkItem);
Expand Down Expand Up @@ -349,7 +349,8 @@ private void createForumMessageTableViewContextMenu()
copyLinkItem.setGraphic(new FontIcon(MaterialDesignL.LINK_VARIANT));
copyLinkItem.setOnAction(event -> {
@SuppressWarnings("unchecked") var forumMessage = ((TreeItem<ForumMessage>) event.getSource()).getValue();
ClipboardUtils.copyTextToClipboard(ForumUriFactory.generate(forumMessage.getName(), forumMessage.getGxsId(), forumMessage.getMessageId()));
var forumUri = new ForumUri(forumMessage.getName(), forumMessage.getGxsId(), forumMessage.getMessageId());
ClipboardUtils.copyTextToClipboard(forumUri.toString());
});

var xContextMenu = new XContextMenu<ForumMessage>(replyItem, new SeparatorMenuItem(), copyLinkItem);
Expand Down
9 changes: 7 additions & 2 deletions ui/src/main/java/io/xeres/ui/support/ImageCacheService.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
* Copyright (c) 2024-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand Down Expand Up @@ -63,7 +63,7 @@ public Image getImage(String url)
@Override
public void putImage(String url, Image image)
{
if (url.startsWith("data:") || !isImageCacheable(image))
if (!isUrlCacheable(url) || !isImageCacheable(image))
{
return;
}
Expand Down Expand Up @@ -144,6 +144,11 @@ private boolean isImageCacheable(Image image)
return maxSize > 0 && image.getWidth() * image.getHeight() * 4 < MAX_IMAGE_SIZE;
}

private boolean isUrlCacheable(String url)
{
return !url.startsWith("data:");
}

private static class ImageSizeSoftReference extends SoftReference<Image>
{
private final String url;
Expand Down
53 changes: 51 additions & 2 deletions ui/src/main/java/io/xeres/ui/support/contentline/ContentUri.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2023 by David Gerber - https://zapek.com
* Copyright (c) 2019-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand All @@ -21,37 +21,62 @@

import io.xeres.common.i18n.I18nUtils;
import io.xeres.ui.custom.DisclosedHyperlink;
import io.xeres.ui.support.clipboard.ClipboardUtils;
import io.xeres.ui.support.uri.UriService;
import io.xeres.ui.support.util.UiUtils;
import javafx.event.ActionEvent;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Hyperlink;
import javafx.scene.control.MenuItem;
import org.kordamp.ikonli.javafx.FontIcon;
import org.kordamp.ikonli.materialdesign2.MaterialDesignC;

import java.text.MessageFormat;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.function.Consumer;

public class ContentUri implements Content
{
private final Hyperlink node;
private static final ContextMenu contextMenu;

private final ResourceBundle bundle = I18nUtils.getBundle();
private static final ResourceBundle bundle = I18nUtils.getBundle();

static
{
var copyMenuItem = new MenuItem(bundle.getString("copy"));
copyMenuItem.setGraphic(new FontIcon(MaterialDesignC.CONTENT_COPY));
copyMenuItem.setOnAction(ContentUri::copyToClipboard);

contextMenu = new ContextMenu(copyMenuItem);
}

public ContentUri(String uri)
{
node = new Hyperlink(uri);
node.setOnAction(event -> UriService.openUri(appendMailToIfNeeded(node.getText())));
initContextMenu();
}

public ContentUri(String uri, String description)
{
node = new DisclosedHyperlink(description, uri);
node.setOnAction(event -> askBeforeOpeningIfNeeded(() -> UriService.openUri(appendMailToIfNeeded(uri))));
initContextMenu();
}

public ContentUri(String uri, String description, Consumer<String> action)
{
node = new DisclosedHyperlink(description, uri);
node.setOnAction(event -> askBeforeOpeningIfNeeded(() -> action.accept(uri)));
initContextMenu();
}

private void initContextMenu()
{
node.setOnContextMenuRequested(event -> contextMenu.show(node, event.getScreenX(), event.getScreenY()));
}

private void askBeforeOpeningIfNeeded(Runnable action)
Expand Down Expand Up @@ -82,7 +107,31 @@ public Node getNode()
}

public String getUri()
{
return getUri(node);
}

@Override
public String asText()
{
return node.getText();
}

private static String getUri(Node node)
{
return switch (node)
{
case DisclosedHyperlink disclosedHyperlink -> disclosedHyperlink.getUri();
case Hyperlink hyperlink -> hyperlink.getText();
default -> "";
};
}

private static void copyToClipboard(ActionEvent event)
{
var selectedMenuItem = (MenuItem) event.getTarget();

var popup = Objects.requireNonNull(selectedMenuItem.getParentPopup());
ClipboardUtils.copyTextToClipboard(getUri(popup.getOwnerNode()));
}
}
10 changes: 5 additions & 5 deletions ui/src/main/java/io/xeres/ui/support/markdown/UrlDetector.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023 by David Gerber - https://zapek.com
* Copyright (c) 2023-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand All @@ -19,24 +19,24 @@

package io.xeres.ui.support.markdown;

import io.xeres.ui.support.contentline.ContentUri;
import io.xeres.ui.support.uri.UriFactory;

import java.util.regex.Pattern;

class UrlDetector implements MarkdownDetector
{
private static final Pattern URL_PATTERN = Pattern.compile("\\b(?<u>(?:https?|ftps?)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|])|(?<e>[0-9A-Z._+\\-=]+@[0-9a-z\\-]+\\.[a-z]{2,})", Pattern.CASE_INSENSITIVE);
private static final Pattern URL_PATTERN = Pattern.compile("\\b(?<u>(?:https?|ftps?|retroshare)://[-A-Z0-9+&@#/%?=~_|!:,.;()]*[-A-Z0-9+&@#/%=~_|()])|(?<e>[0-9A-Z._+\\-=]+@[0-9a-z\\-]+\\.[a-z]{2,})", Pattern.CASE_INSENSITIVE);

@Override
public boolean isPossibly(String line)
{
return line.contains("http") || line.contains("ftp") || line.contains("@");
return line.contains("http") || line.contains("ftp") || line.contains("@") || line.contains("retroshare");
}

@Override
public void process(Context context, String line)
{
MarkdownService.processPattern(URL_PATTERN, context, line,
(s, groupName) -> context.addContent(new ContentUri(s)));
(s, groupName) -> context.addContent(UriFactory.createContent(s, "", context.getUriAction())));
}
}
39 changes: 1 addition & 38 deletions ui/src/main/java/io/xeres/ui/support/uri/AbstractUriFactory.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2019-2023 by David Gerber - https://zapek.com
* Copyright (c) 2019-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand All @@ -22,14 +22,10 @@
import io.xeres.common.id.Sha1Sum;
import io.xeres.ui.support.contentline.Content;
import io.xeres.ui.support.markdown.UriAction;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriUtils;

import java.util.Locale;

import static java.nio.charset.StandardCharsets.UTF_8;

public abstract class AbstractUriFactory
{
protected static final String PROTOCOL_RETROSHARE = "retroshare";
Expand All @@ -43,39 +39,6 @@ public String getProtocol()
return PROTOCOL_RETROSHARE;
}

protected static String buildUri(String protocol, String authority, String... args)
{
var sb = new StringBuilder(protocol);
var firstArg = true;

if (args.length % 2 != 0)
{
throw new IllegalArgumentException("Wrong number of arguments: must be name and value pairs");
}
sb.append("://");
sb.append(authority);

for (var i = 0; i < args.length; i += 2)
{
if (StringUtils.isNotBlank(args[i + 1]))
{
if (firstArg)
{
sb.append("?");
firstArg = false;
}
else
{
sb.append("&");
}
sb.append(args[i]);
sb.append("=");
sb.append(UriUtils.encodeQueryParam(args[i + 1], UTF_8));
}
}
return sb.toString();
}

protected static long getLongHexArgument(String s)
{
try
Expand Down
17 changes: 16 additions & 1 deletion ui/src/main/java/io/xeres/ui/support/uri/BoardsUri.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 by David Gerber - https://zapek.com
* Copyright (c) 2024-2025 by David Gerber - https://zapek.com
*
* This file is part of Xeres.
*
Expand All @@ -20,8 +20,23 @@
package io.xeres.ui.support.uri;

import io.xeres.common.id.GxsId;
import io.xeres.common.id.Id;
import io.xeres.common.id.MessageId;

public record BoardsUri(String name, GxsId id, MessageId messageId) implements Uri
{
static String AUTHORITY = "posted";

static String PARAMETER_NAME = "name";
static String PARAMETER_ID = "id";
static String PARAMETER_MSGID = "msgid";

@Override
public String toString()
{
return Uri.buildUri(AUTHORITY,
PARAMETER_NAME, name,
PARAMETER_ID, Id.toString(id),
PARAMETER_MSGID, Id.toString(messageId));
}
}
Loading

0 comments on commit 553d807

Please sign in to comment.