diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index b84941caf..144b228f5 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -107,7 +107,7 @@ jobs: python -m pip install --only-binary payjoin --find-links dist "payjoin==$version" python -c ' import payjoin - payjoin.Url.parse("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com") + payjoin.Uri.parse("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com") print("smoke ok") ' diff --git a/Cargo-minimal.lock b/Cargo-minimal.lock index 8f4578a21..89cddbf23 100644 --- a/Cargo-minimal.lock +++ b/Cargo-minimal.lock @@ -1792,21 +1792,14 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", + "unicode-bidi", + "unicode-normalization", ] -[[package]] -name = "idna_adapter" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfdf4f5d937a025381f5ab13624b1c5f51414bfe5c9885663226eae8d6d39560" - [[package]] name = "indexmap" version = "1.8.0" @@ -2400,7 +2393,6 @@ dependencies = [ "uniffi", "uniffi-bindgen-cs", "uniffi-dart", - "url", "yoke-derive", ] @@ -3398,9 +3390,9 @@ checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" [[package]] name = "smawk" @@ -4062,6 +4054,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ac9567e27ca9fc45bac22f987fd62547b0ac65d2e6502dfc09cdab7dbdba31f" +[[package]] +name = "unicode-bidi" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" + [[package]] name = "unicode-ident" version = "1.0.5" @@ -4299,9 +4297,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -4320,12 +4318,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - [[package]] name = "utf8parse" version = "0.2.1" diff --git a/Cargo-recent.lock b/Cargo-recent.lock index c729f9498..e92756c48 100644 --- a/Cargo-recent.lock +++ b/Cargo-recent.lock @@ -2593,7 +2593,6 @@ dependencies = [ "uniffi", "uniffi-bindgen-cs", "uniffi-dart", - "url", "yoke-derive", ] diff --git a/payjoin-ffi/Cargo.toml b/payjoin-ffi/Cargo.toml index 9c6411b3f..4797339f4 100644 --- a/payjoin-ffi/Cargo.toml +++ b/payjoin-ffi/Cargo.toml @@ -34,7 +34,6 @@ tokio = { version = "1.52.3", features = ["full"], optional = true } uniffi = { version = "0.31.2", features = ["cli"] } uniffi-bindgen-cs = { git = "https://github.com/NordSecurity/uniffi-bindgen-cs", tag = "v0.11.0+v0.31.0", optional = true } uniffi-dart = { git = "https://github.com/Uniffi-Dart/uniffi-dart.git", tag = "v0.2.1+v0.31.2", optional = true } -url = "2.5.4" yoke-derive = "=0.8.2" # Pinned transitive dependencies that appear unused to cargo-machete diff --git a/payjoin-ffi/csharp/UnitTests.cs b/payjoin-ffi/csharp/UnitTests.cs index 655286fa5..5f7aefe1f 100644 --- a/payjoin-ffi/csharp/UnitTests.cs +++ b/payjoin-ffi/csharp/UnitTests.cs @@ -5,11 +5,19 @@ namespace Payjoin.Tests; public class UriTests { + [Fact] + public void UrlUserInfoRejectedPayjoinParameter() + { + var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example@evil.com?ciao"; + var ex = Assert.ThrowsAny(() => Uri.Parse(uri)); + Assert.Contains("UserinfoNotSupported", ex.ToString()); + } + [Fact] public void UrlEncodedPayjoinParameter() { - var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - var result = Url.Parse(uri); + var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https%3A%2F%2Fexample.com%3Fciao"; + var result = Uri.Parse(uri); Assert.NotNull(result); } @@ -17,7 +25,7 @@ public void UrlEncodedPayjoinParameter() public void ValidUrl() { var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - var result = Url.Parse(uri); + var result = Uri.Parse(uri); Assert.NotNull(result); } @@ -25,7 +33,7 @@ public void ValidUrl() public void MissingAmountShouldBeOk() { var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://testnet.demo.btcpayserver.org/BTC/pj"; - var result = Url.Parse(uri); + var result = Uri.Parse(uri); Assert.NotNull(result); } @@ -37,7 +45,7 @@ public void MissingAmountShouldBeOk() public void ValidUrisWithDifferentAddressesAndEndpoints(string address, string pj) { var uri = $"{address}?amount=1&pj={pj}"; - var result = Url.Parse(uri); + var result = Uri.Parse(uri); Assert.NotNull(result); } } diff --git a/payjoin-ffi/csharp/scripts/smoke_nuget_package.sh b/payjoin-ffi/csharp/scripts/smoke_nuget_package.sh index 15ba0b52f..377dcc051 100755 --- a/payjoin-ffi/csharp/scripts/smoke_nuget_package.sh +++ b/payjoin-ffi/csharp/scripts/smoke_nuget_package.sh @@ -67,7 +67,7 @@ dotnet add "PayjoinSmoke/PayjoinSmoke.csproj" package Payjoin \ --no-restore cat >"PayjoinSmoke/Program.cs" <<'EOF' -var uri = Payjoin.Url.Parse("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"); +var uri = Payjoin.Uri.Parse("bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"); Console.WriteLine(uri.AsString()); EOF diff --git a/payjoin-ffi/dart/test/test_payjoin_unit_test.dart b/payjoin-ffi/dart/test/test_payjoin_unit_test.dart index 31e8325b3..3a16deb9d 100644 --- a/payjoin-ffi/dart/test/test_payjoin_unit_test.dart +++ b/payjoin-ffi/dart/test/test_payjoin_unit_test.dart @@ -8,29 +8,40 @@ import "utils.dart"; void main() { group('Test URIs', () { - test('Test todo url encoded', () { + test('Test url userinfo rejection', () { var uri = - "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - final result = payjoin.Url.parse(input: uri); + "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example@evil.com?ciao"; expect( - result, - isA(), - reason: "pj url should be url encoded", + () => payjoin.Uri.parse(uri: uri), + throwsA( + isA().having( + (e) => e.toString(), + 'display', + contains('UserinfoNotSupported'), + ), + ), ); }); + test('Test url encoded', () { + var uri = + "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https%3A%2F%2Fexample.com%3Fciao"; + final result = payjoin.Uri.parse(uri: uri); + expect(result, isA(), reason: "pj url encoded is not valid"); + }); + test('Test valid url', () { var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - final result = payjoin.Url.parse(input: uri); - expect(result, isA(), reason: "pj is not a valid url"); + final result = payjoin.Uri.parse(uri: uri); + expect(result, isA(), reason: "pj is not a valid url"); }); test('Test missing amount', () { var uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://testnet.demo.btcpayserver.org/BTC/pj"; - final result = payjoin.Url.parse(input: uri); - expect(result, isA(), reason: "missing amount should be ok"); + final result = payjoin.Uri.parse(uri: uri); + expect(result, isA(), reason: "missing amount should be ok"); }); test('Test valid uris', () { @@ -49,7 +60,7 @@ void main() { for (final pj in pjs) { final uri = "$address?amount=1&pj=$pj"; try { - payjoin.Url.parse(input: uri); + payjoin.Uri.parse(uri: uri); } catch (e) { fail("Failed to create a valid Uri for $uri. Error: $e"); } diff --git a/payjoin-ffi/javascript/test/unit.test.ts b/payjoin-ffi/javascript/test/unit.test.ts index 89386421e..76341f77e 100644 --- a/payjoin-ffi/javascript/test/unit.test.ts +++ b/payjoin-ffi/javascript/test/unit.test.ts @@ -44,24 +44,30 @@ const ORIGINAL_PSBT = function runUnitTests(name: string, payjoin: typeof nodejsPayjoin) { describe(`[${name}] URI tests`, () => { + test("Reject userinfo URL", () => { + const uri = + "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example@evil.com?ciao"; + assert.throws(() => payjoin.Uri.parse(uri), /UserinfoNotSupported/); + }); + test("URL encoded payjoin parameter", () => { const uri = - "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - const result = payjoin.Url.parse(uri); - assert.ok(result, "pj url should be url encoded"); + "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https%3A%2F%2Fexample.com%3Fciao"; + const result = payjoin.Uri.parse(uri); + assert.ok(result, "pj url encoded is not valid"); }); - test("valid URL", () => { + test("valid URI", () => { const uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"; - const result = payjoin.Url.parse(uri); + const result = payjoin.Uri.parse(uri); assert.ok(result, "pj is not a valid url"); }); test("missing amount should be ok", () => { const uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://testnet.demo.btcpayserver.org/BTC/pj"; - const result = payjoin.Url.parse(uri); + const result = payjoin.Uri.parse(uri); assert.ok(result, "missing amount should be ok"); }); @@ -83,7 +89,7 @@ function runUnitTests(name: string, payjoin: typeof nodejsPayjoin) { for (const pj of pjs) { const uri = `${address}?amount=1&pj=${pj}`; assert.doesNotThrow( - () => payjoin.Url.parse(uri), + () => payjoin.Uri.parse(uri), `Failed to create a valid Uri for ${uri}`, ); } diff --git a/payjoin-ffi/python/test/test_payjoin_unit_test.py b/payjoin-ffi/python/test/test_payjoin_unit_test.py index 61b5c0780..93f7e50cd 100644 --- a/payjoin-ffi/python/test/test_payjoin_unit_test.py +++ b/payjoin-ffi/python/test/test_payjoin_unit_test.py @@ -10,17 +10,22 @@ class TestURIs(unittest.TestCase): - def test_todo_url_encoded(self): - uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao" - self.assertTrue(payjoin.Url.parse(uri), "pj url should be url encoded") + def test_url_userinfo_rejection(self): + uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example@evil.com?ciao" + with self.assertRaisesRegex(payjoin.UriParseError, "UserinfoNotSupported"): + payjoin.Uri.parse(uri) + + def test_url_encoded(self): + uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https%3A%2F%2Fexample.com%3Fciao" + self.assertTrue(payjoin.Uri.parse(uri), "pj url encoded is not valid") def test_valid_url(self): uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao" - self.assertTrue(payjoin.Url.parse(uri), "pj is not a valid url") + self.assertTrue(payjoin.Uri.parse(uri), "pj is not a valid uri") def test_missing_amount(self): uri = "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?pj=https://testnet.demo.btcpayserver.org/BTC/pj" - self.assertTrue(payjoin.Url.parse(uri), "missing amount should be ok") + self.assertTrue(payjoin.Uri.parse(uri), "missing amount should be ok") def test_valid_uris(self): https = str(payjoin.example_url()) @@ -34,7 +39,7 @@ def test_valid_uris(self): for pj in [https, onion]: uri = f"{address}?amount=1&pj={pj}" try: - payjoin.Url.parse(uri) + payjoin.Uri.parse(uri) except Exception as e: self.fail(f"Failed to create a valid Uri for {uri}. Error: {e}") diff --git a/payjoin-ffi/src/uri/error.rs b/payjoin-ffi/src/uri/error.rs index 19af6378c..89d5c7baa 100644 --- a/payjoin-ffi/src/uri/error.rs +++ b/payjoin-ffi/src/uri/error.rs @@ -19,7 +19,7 @@ impl PjNotSupported { #[derive(Debug, thiserror::Error, uniffi::Object)] #[uniffi::export(Debug, Display)] #[error(transparent)] -pub struct UrlParseError(#[from] url::ParseError); +pub struct UrlParseError(#[from] payjoin::UrlParseError); #[derive(Debug, thiserror::Error, uniffi::Object)] #[uniffi::export(Debug, Display)] diff --git a/payjoin-ffi/src/uri/mod.rs b/payjoin-ffi/src/uri/mod.rs index 0e6d8c439..cbc6e72b1 100644 --- a/payjoin-ffi/src/uri/mod.rs +++ b/payjoin-ffi/src/uri/mod.rs @@ -71,22 +71,22 @@ impl PjUri { pub fn as_string(&self) -> String { self.0.clone().to_string() } } -impl From for Url { - fn from(value: url::Url) -> Self { Self(value) } +impl From for Url { + fn from(value: payjoin::Url) -> Self { Self(value) } } -impl From for url::Url { +impl From for payjoin::Url { fn from(value: Url) -> Self { value.0 } } -#[derive(Clone, Debug, uniffi::Object)] -pub struct Url(url::Url); +#[derive(Debug, uniffi::Object)] +pub struct Url(payjoin::Url); #[uniffi::export] impl Url { #[uniffi::constructor] pub fn parse(input: String) -> Result { - url::Url::parse(input.as_str()).map_err(Into::into).map(Self) + payjoin::Url::parse(input.as_str()).map_err(Into::into).map(Self) } pub fn query(&self) -> Option { self.0.query().map(|x| x.to_string()) } pub fn as_string(&self) -> String { self.0.to_string() }