refactor secure url check for additional call sites

This commit is contained in:
Craig Raw 2026-05-30 11:33:51 +02:00
parent e96aa0d3f8
commit 7d0699faa0
4 changed files with 95 additions and 2 deletions

View File

@ -14,6 +14,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
@ -93,6 +94,15 @@ public class Utils {
return ((st > 0) || (len < bytes.length)) ? Arrays.copyOfRange(bytes, st, len) : bytes;
}
public static boolean isSecureUrl(URI uri) {
if(uri == null || uri.getScheme() == null || uri.getHost() == null) {
return false;
}
String scheme = uri.getScheme().toLowerCase(Locale.ROOT);
return "https".equals(scheme) || ("http".equals(scheme) && uri.getHost().toLowerCase(Locale.ROOT).endsWith(".onion"));
}
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {

View File

@ -1,6 +1,7 @@
package com.sparrowwallet.drongo.uri;
import com.sparrowwallet.drongo.Network;
import com.sparrowwallet.drongo.Utils;
import com.sparrowwallet.drongo.address.Address;
import com.sparrowwallet.drongo.address.InvalidAddressException;
import com.sparrowwallet.drongo.silentpayments.SilentPayment;
@ -275,10 +276,10 @@ public class BitcoinURI {
if(payjoinUrl != null) {
try {
URI uri = new URI(payjoinUrl);
if(uri.getScheme().equals("https") || uri.getHost().endsWith(".onion")) {
if(Utils.isSecureUrl(uri)) {
return uri;
} else {
log.error("Insecure payjoin URL provided, must be https or .onion: " + payjoinUrl);
log.error("Insecure payjoin URL provided, must be https or http .onion: " + payjoinUrl);
}
} catch(URISyntaxException e) {
log.error("Invalid payjoin URL provided", e);

View File

@ -0,0 +1,48 @@
package com.sparrowwallet.drongo;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.net.URI;
public class UtilsTest {
@Test
public void isSecureUrlAcceptsHttps() throws Exception {
Assertions.assertTrue(Utils.isSecureUrl(new URI("https://example.com/callback")));
}
@Test
public void isSecureUrlAcceptsUppercaseHttps() throws Exception {
Assertions.assertTrue(Utils.isSecureUrl(new URI("HTTPS://example.com/callback")));
}
@Test
public void isSecureUrlAcceptsHttpOnion() throws Exception {
Assertions.assertTrue(Utils.isSecureUrl(new URI("http://abcdefghijklmnopqrstuvwxyzabcdefghijklmnop.onion/callback")));
}
@Test
public void isSecureUrlRejectsHttpClearnet() throws Exception {
Assertions.assertFalse(Utils.isSecureUrl(new URI("http://example.com/callback")));
}
@Test
public void isSecureUrlRejectsNonHttpOnion() throws Exception {
Assertions.assertFalse(Utils.isSecureUrl(new URI("ftp://abcdefghijklmnopqrstuvwxyzabcdefghijklmnop.onion/callback")));
}
@Test
public void isSecureUrlRejectsOpaqueHttps() throws Exception {
Assertions.assertFalse(Utils.isSecureUrl(new URI("https:opaque")));
}
@Test
public void isSecureUrlRejectsSchemeless() throws Exception {
Assertions.assertFalse(Utils.isSecureUrl(new URI("callback")));
}
@Test
public void isSecureUrlRejectsNull() {
Assertions.assertFalse(Utils.isSecureUrl(null));
}
}

View File

@ -3,9 +3,13 @@ package com.sparrowwallet.drongo.uri;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
public class BitcoinUriTest {
private static final String ADDRESS = "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4";
@Test
public void testSamourai() throws BitcoinURIParseException {
String uri = "bitcoin:BC1QT4NRM47695YWDG9N30N68JARMXRJNKFMR36994?amount=0,001";
@ -14,4 +18,34 @@ public class BitcoinUriTest {
Assertions.assertEquals("BC1QT4NRM47695YWDG9N30N68JARMXRJNKFMR36994".toLowerCase(Locale.ROOT), bitcoinURI.getAddress().toString());
Assertions.assertEquals(Long.valueOf(100000), bitcoinURI.getAmount());
}
@Test
public void acceptsHttpsPayjoinUrl() throws BitcoinURIParseException {
BitcoinURI bitcoinURI = payjoinUri("https://example.com/payjoin");
Assertions.assertNotNull(bitcoinURI.getPayjoinUrl());
Assertions.assertEquals("https://example.com/payjoin", bitcoinURI.getPayjoinUrl().toString());
}
@Test
public void acceptsHttpOnionPayjoinUrl() throws BitcoinURIParseException {
BitcoinURI bitcoinURI = payjoinUri("http://abcdefghijklmnopqrstuvwxyzabcdefghijklmnop.onion/payjoin");
Assertions.assertNotNull(bitcoinURI.getPayjoinUrl());
Assertions.assertEquals("http://abcdefghijklmnopqrstuvwxyzabcdefghijklmnop.onion/payjoin", bitcoinURI.getPayjoinUrl().toString());
}
@Test
public void rejectsNonHttpOnionPayjoinUrl() throws BitcoinURIParseException {
BitcoinURI bitcoinURI = payjoinUri("file://abcdefghijklmnopqrstuvwxyzabcdefghijklmnop.onion/payjoin");
Assertions.assertNull(bitcoinURI.getPayjoinUrl());
}
@Test
public void rejectsMalformedPayjoinUrl() throws BitcoinURIParseException {
BitcoinURI bitcoinURI = payjoinUri("payjoin");
Assertions.assertNull(bitcoinURI.getPayjoinUrl());
}
private static BitcoinURI payjoinUri(String payjoinUrl) throws BitcoinURIParseException {
return new BitcoinURI("bitcoin:" + ADDRESS + "?pj=" + URLEncoder.encode(payjoinUrl, StandardCharsets.UTF_8));
}
}