gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: new taler:// URI syntax


From: gnunet
Subject: [taler-wallet-core] branch master updated: new taler:// URI syntax
Date: Mon, 27 Jul 2020 13:40:02 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new ae111663 new taler:// URI syntax
ae111663 is described below

commit ae111663f412ad7bee9029110e3ab1594ec14576
Author: Florian Dold <florian.dold@gmail.com>
AuthorDate: Mon Jul 27 17:09:52 2020 +0530

    new taler:// URI syntax
---
 src/operations/tip.ts      |   6 +-
 src/operations/withdraw.ts |   8 +-
 src/util/taleruri-test.ts  | 101 ++++++---------------
 src/util/taleruri.ts       | 222 +++++++++++++++++++--------------------------
 4 files changed, 132 insertions(+), 205 deletions(-)

diff --git a/src/operations/tip.ts b/src/operations/tip.ts
index d121b1cb..17f7ee90 100644
--- a/src/operations/tip.ts
+++ b/src/operations/tip.ts
@@ -66,9 +66,11 @@ export async function getTipStatus(
 
   const amount = Amounts.parseOrThrow(tipPickupStatus.amount);
 
+  const merchantOrigin = new URL(res.merchantBaseUrl).origin;
+
   let tipRecord = await ws.db.get(Stores.tips, [
     res.merchantTipId,
-    res.merchantOrigin,
+    merchantOrigin,
   ]);
 
   if (!tipRecord) {
@@ -117,7 +119,7 @@ export async function getTipStatus(
     amountLeft: Amounts.parseOrThrow(tipPickupStatus.amount_left),
     exchangeUrl: tipPickupStatus.exchange_url,
     nextUrl: tipPickupStatus.extra.next_url,
-    merchantOrigin: res.merchantOrigin,
+    merchantOrigin: merchantOrigin,
     merchantTipId: res.merchantTipId,
     expirationTimestamp: tipPickupStatus.stamp_expire,
     timestamp: tipPickupStatus.stamp_created,
diff --git a/src/operations/withdraw.ts b/src/operations/withdraw.ts
index f7879dfe..9f6804b2 100644
--- a/src/operations/withdraw.ts
+++ b/src/operations/withdraw.ts
@@ -141,7 +141,11 @@ export async function getBankWithdrawalInfo(
   if (!uriResult) {
     throw Error(`can't parse URL ${talerWithdrawUri}`);
   }
-  const resp = await ws.http.get(uriResult.statusUrl);
+  const reqUrl = new URL(
+    `api/withdraw-operations/${uriResult.withdrawalOperationId}`,
+    uriResult.bankIntegrationApiBaseUrl,
+  );
+  const resp = await ws.http.get(reqUrl.href);
   const status = await readSuccessResponseJsonOrThrow(
     resp,
     codecForWithdrawOperationStatusResponse(),
@@ -150,7 +154,7 @@ export async function getBankWithdrawalInfo(
   return {
     amount: Amounts.parseOrThrow(status.amount),
     confirmTransferUrl: status.confirm_transfer_url,
-    extractedStatusUrl: uriResult.statusUrl,
+    extractedStatusUrl: uriResult.bankIntegrationApiBaseUrl,
     selectionDone: status.selection_done,
     senderWire: status.sender_wire,
     suggestedExchange: status.suggested_exchange,
diff --git a/src/util/taleruri-test.ts b/src/util/taleruri-test.ts
index 1510880c..40a30bf7 100644
--- a/src/util/taleruri-test.ts
+++ b/src/util/taleruri-test.ts
@@ -33,136 +33,93 @@ test("taler pay url parsing: wrong scheme", (t) => {
 });
 
 test("taler pay url parsing: defaults", (t) => {
-  const url1 = "taler://pay/example.com/-/-/myorder";
+  const url1 = "taler://pay/example.com/myorder/";
   const r1 = parsePayUri(url1);
   if (!r1) {
     t.fail();
     return;
   }
-  t.is(r1.merchantBaseUrl, "https://example.com/public/";);
-  t.is(r1.sessionId, undefined);
+  t.is(r1.merchantBaseUrl, "https://example.com/";);
+  t.is(r1.sessionId, "");
 
-  const url2 = "taler://pay/example.com/-/-/myorder/mysession";
+  const url2 = "taler://pay/example.com/myorder/mysession";
   const r2 = parsePayUri(url2);
   if (!r2) {
     t.fail();
     return;
   }
-  t.is(r2.merchantBaseUrl, "https://example.com/public/";);
+  t.is(r2.merchantBaseUrl, "https://example.com/";);
   t.is(r2.sessionId, "mysession");
 });
 
-test("taler pay url parsing: trailing parts", (t) => {
-  const url1 = "taler://pay/example.com/-/-/myorder/mysession/spam/eggs";
-  const r1 = parsePayUri(url1);
-  if (!r1) {
-    t.fail();
-    return;
-  }
-  t.is(r1.merchantBaseUrl, "https://example.com/public/";);
-  t.is(r1.sessionId, "mysession");
-});
-
 test("taler pay url parsing: instance", (t) => {
-  const url1 = "taler://pay/example.com/-/myinst/myorder";
+  const url1 = "taler://pay/example.com/instances/myinst/myorder/";
   const r1 = parsePayUri(url1);
   if (!r1) {
     t.fail();
     return;
   }
-  t.is(r1.merchantBaseUrl, "https://example.com/public/instances/myinst/";);
+  t.is(r1.merchantBaseUrl, "https://example.com/instances/myinst/";);
   t.is(r1.orderId, "myorder");
 });
 
-test("taler pay url parsing: path prefix and instance", (t) => {
-  const url1 = "taler://pay/example.com/mypfx/myinst/myorder";
-  const r1 = parsePayUri(url1);
-  if (!r1) {
-    t.fail();
-    return;
-  }
-  t.is(r1.merchantBaseUrl, "https://example.com/mypfx/instances/myinst/";);
-});
-
-test("taler pay url parsing: complex path prefix", (t) => {
-  const url1 = "taler://pay/example.com/mypfx%2Fpublic/-/myorder";
-  const r1 = parsePayUri(url1);
-  if (!r1) {
-    t.fail();
-    return;
-  }
-  t.is(r1.merchantBaseUrl, "https://example.com/mypfx/public/";);
-  t.is(r1.orderId, "myorder");
-  t.is(r1.sessionId, undefined);
-});
-
-test("taler pay uri parsing: complex path prefix and instance", (t) => {
-  const url1 = "taler://pay/example.com/mypfx%2Fpublic/foo/myorder";
-  const r1 = parsePayUri(url1);
-  if (!r1) {
-    t.fail();
-    return;
-  }
-  t.is(r1.merchantBaseUrl, "https://example.com/mypfx/public/instances/foo/";);
-  t.is(r1.orderId, "myorder");
-});
 
 test("taler refund uri parsing: non-https #1", (t) => {
-  const url1 = "taler://refund/example.com/-/-/myorder?insecure=1";
+  const url1 = "taler+http://refund/example.com/myorder";;
   const r1 = parseRefundUri(url1);
   if (!r1) {
     t.fail();
     return;
   }
-  t.is(r1.merchantBaseUrl, "http://example.com/public/";);
+  t.is(r1.merchantBaseUrl, "http://example.com/";);
   t.is(r1.orderId, "myorder");
 });
 
-test("taler pay uri parsing: non-https #1", (t) => {
-  const url1 = "taler://pay/example.com/-/-/myorder?insecure=1";
+test("taler pay uri parsing: non-https", (t) => {
+  const url1 = "taler+http://pay/example.com/myorder/";;
   const r1 = parsePayUri(url1);
   if (!r1) {
     t.fail();
     return;
   }
-  t.is(r1.merchantBaseUrl, "http://example.com/public/";);
+  t.is(r1.merchantBaseUrl, "http://example.com/";);
   t.is(r1.orderId, "myorder");
 });
 
-test("taler pay url parsing: non-https #2", (t) => {
-  const url1 = "taler://pay/example.com/-/-/myorder?insecure=2";
+test("taler pay uri parsing: missing session component", (t) => {
+  const url1 = "taler+http://pay/example.com/myorder";;
   const r1 = parsePayUri(url1);
-  if (!r1) {
+  if (r1) {
     t.fail();
     return;
   }
-  t.is(r1.merchantBaseUrl, "https://example.com/public/";);
-  t.is(r1.orderId, "myorder");
+  t.pass();
 });
 
 test("taler withdraw uri parsing", (t) => {
-  const url1 = "taler://withdraw/bank.example.com/-/12345";
+  const url1 = "taler://withdraw/bank.example.com/12345";
   const r1 = parseWithdrawUri(url1);
   if (!r1) {
     t.fail();
     return;
   }
-  t.is(r1.statusUrl, "https://bank.example.com/api/withdraw-operation/12345";);
+  t.is(r1.withdrawalOperationId, "12345");
+  t.is(r1.bankIntegrationApiBaseUrl, "https://bank.example.com/";);
 });
 
 test("taler refund uri parsing", (t) => {
-  const url1 = "taler://refund/merchant.example.com/-/-/1234";
+  const url1 = "taler://refund/merchant.example.com/1234";
   const r1 = parseRefundUri(url1);
   if (!r1) {
     t.fail();
     return;
   }
-  t.is(r1.merchantBaseUrl, "https://merchant.example.com/public/";);
+  t.is(r1.merchantBaseUrl, "https://merchant.example.com/";);
   t.is(r1.orderId, "1234");
 });
 
 test("taler refund uri parsing with instance", (t) => {
-  const url1 = "taler://refund/merchant.example.com/-/myinst/1234";
+  const url1 = "taler://refund/merchant.example.com/instances/myinst/1234";
   const r1 = parseRefundUri(url1);
   if (!r1) {
     t.fail();
@@ -171,22 +128,22 @@ test("taler refund uri parsing with instance", (t) => {
   t.is(r1.orderId, "1234");
   t.is(
     r1.merchantBaseUrl,
-    "https://merchant.example.com/public/instances/myinst/";,
+    "https://merchant.example.com/instances/myinst/";,
   );
 });
 
 test("taler tip pickup uri", (t) => {
-  const url1 = "taler://tip/merchant.example.com/-/-/tipid";
+  const url1 = "taler://tip/merchant.example.com/tipid";
   const r1 = parseTipUri(url1);
   if (!r1) {
     t.fail();
     return;
   }
-  t.is(r1.merchantBaseUrl, "https://merchant.example.com/public/";);
+  t.is(r1.merchantBaseUrl, "https://merchant.example.com/";);
 });
 
 test("taler tip pickup uri with instance", (t) => {
-  const url1 = "taler://tip/merchant.example.com/-/tipm/tipid";
+  const url1 = "taler://tip/merchant.example.com/instances/tipm/tipid";
   const r1 = parseTipUri(url1);
   if (!r1) {
     t.fail();
@@ -194,13 +151,13 @@ test("taler tip pickup uri with instance", (t) => {
   }
   t.is(
     r1.merchantBaseUrl,
-    "https://merchant.example.com/public/instances/tipm/";,
+    "https://merchant.example.com/instances/tipm/";,
   );
   t.is(r1.merchantTipId, "tipid");
 });
 
 test("taler tip pickup uri with instance and prefix", (t) => {
-  const url1 = "taler://tip/merchant.example.com/my%2fpfx/tipm/tipid";
+  const url1 = "taler://tip/merchant.example.com/my/pfx/tipm/tipid";
   const r1 = parseTipUri(url1);
   if (!r1) {
     t.fail();
@@ -208,7 +165,7 @@ test("taler tip pickup uri with instance and prefix", (t) 
=> {
   }
   t.is(
     r1.merchantBaseUrl,
-    "https://merchant.example.com/my/pfx/instances/tipm/";,
+    "https://merchant.example.com/my/pfx/tipm/";,
   );
   t.is(r1.merchantTipId, "tipid");
 });
diff --git a/src/util/taleruri.ts b/src/util/taleruri.ts
index 73280b6c..7e64dd4c 100644
--- a/src/util/taleruri.ts
+++ b/src/util/taleruri.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
+ (C) 2019-2020 Taler Systems S.A.
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -17,11 +17,12 @@
 export interface PayUriResult {
   merchantBaseUrl: string;
   orderId: string;
-  sessionId?: string;
+  sessionId: string;
 }
 
 export interface WithdrawUriResult {
-  statusUrl: string;
+  bankIntegrationApiBaseUrl: string;
+  withdrawalOperationId: string;
 }
 
 export interface RefundUriResult {
@@ -31,10 +32,13 @@ export interface RefundUriResult {
 
 export interface TipUriResult {
   merchantTipId: string;
-  merchantOrigin: string;
   merchantBaseUrl: string;
 }
 
+/**
+ * Parse a taler[+http]://withdraw URI.
+ * Return undefined if not passed a valid URI.
+ */
 export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
   const pfx = "taler://withdraw/";
   if (!s.toLowerCase().startsWith(pfx)) {
@@ -42,29 +46,20 @@ export function parseWithdrawUri(s: string): 
WithdrawUriResult | undefined {
   }
 
   const rest = s.substring(pfx.length);
+  const parts = rest.split("/");
 
-  let [host, path, withdrawId] = rest.split("/");
-
-  if (!host) {
-    return undefined;
-  }
-
-  host = host.toLowerCase();
-
-  if (!path) {
+  if (parts.length < 2) {
     return undefined;
   }
 
-  if (!withdrawId) {
-    return undefined;
-  }
-
-  if (path === "-") {
-    path = "api/withdraw-operation";
-  }
+  const host = parts[0].toLowerCase();
+  const pathSegments = parts.slice(1, parts.length - 1);
+  const withdrawId = parts[parts.length - 1];
+  const p = [host, ...pathSegments].join("/");
 
   return {
-    statusUrl: `https://${host}/${path}/${withdrawId}`,
+    bankIntegrationApiBaseUrl: `https://${p}/`,
+    withdrawalOperationId: withdrawId,
   };
 }
 
@@ -77,17 +72,29 @@ export const enum TalerUriType {
   Unknown = "unknown",
 }
 
+/**
+ * Classify a taler:// URI.
+ */
 export function classifyTalerUri(s: string): TalerUriType {
   const sl = s.toLowerCase();
   if (sl.startsWith("taler://pay/")) {
     return TalerUriType.TalerPay;
   }
+  if (sl.startsWith("taler+http://pay/";)) {
+    return TalerUriType.TalerPay;
+  }
   if (sl.startsWith("taler://tip/")) {
     return TalerUriType.TalerTip;
   }
+  if (sl.startsWith("taler+http://tip/";)) {
+    return TalerUriType.TalerTip;
+  }
   if (sl.startsWith("taler://refund/")) {
     return TalerUriType.TalerRefund;
   }
+  if (sl.startsWith("taler+http://refund/";)) {
+    return TalerUriType.TalerRefund;
+  }
   if (sl.startsWith("taler://withdraw/")) {
     return TalerUriType.TalerWithdraw;
   }
@@ -97,146 +104,103 @@ export function classifyTalerUri(s: string): 
TalerUriType {
   return TalerUriType.Unknown;
 }
 
-export function parsePayUri(s: string): PayUriResult | undefined {
-  const pfx = "taler://pay/";
-  if (!s.toLowerCase().startsWith(pfx)) {
-    return undefined;
-  }
-
-  const [path, search] = s.slice(pfx.length).split("?");
+interface TalerUriProtoInfo {
+  innerProto: "http" | "https"; 
+  rest: string;
+}
 
-  let [host, maybePath, maybeInstance, orderId, maybeSessionid] = path.split(
-    "/",
-  );
 
-  if (!host) {
+function parseProtoInfo(s: string, action: string): TalerUriProtoInfo | 
undefined {
+  const pfxPlain = `taler://${action}/`;
+  const pfxHttp = `taler+http://${action}/`;
+  if (s.toLowerCase().startsWith(pfxPlain)) {
+    return {
+      innerProto: "https",
+      rest: s.substring(pfxPlain.length),
+    }
+  } else if (s.toLowerCase().startsWith(pfxHttp)) {
+    return {
+      innerProto: "http",
+      rest: s.substring(pfxHttp.length),
+    }
+  } else {
     return undefined;
   }
+}
 
-  host = host.toLowerCase();
-
-  if (!maybePath) {
+/**
+ * Parse a taler[+http]://pay URI.
+ * Return undefined if not passed a valid URI.
+ */
+export function parsePayUri(s: string): PayUriResult | undefined {
+  const pi = parseProtoInfo(s, "pay");
+  if (!pi) {
     return undefined;
   }
-
-  if (!orderId) {
+  const c = pi?.rest.split("?");
+  const parts = c[0].split("/");
+  if (parts.length < 3) {
     return undefined;
   }
-
-  if (maybePath === "-") {
-    maybePath = "";
-  } else {
-    maybePath = decodeURIComponent(maybePath) + "/";
-  }
-  let maybeInstancePath = "";
-  if (maybeInstance !== "-") {
-    maybeInstancePath = `instances/${maybeInstance}/`;
-  }
-
-  let protocol = "https";
-  const searchParams = new URLSearchParams(search);
-  if (searchParams.get("insecure") === "1") {
-    protocol = "http";
-  }
-
-  const merchantBaseUrl =
-    `${protocol}://${host}/` +
-    decodeURIComponent(maybePath) +
-    maybeInstancePath;
+  const host = parts[0].toLowerCase();
+  const sessionId = parts[parts.length - 1];
+  const orderId = parts[parts.length - 2];
+  const pathSegments = parts.slice(1, parts.length - 2);
+  const p = [host, ...pathSegments].join("/");
+  const merchantBaseUrl = `${pi.innerProto}://${p}/`;
 
   return {
     merchantBaseUrl,
     orderId,
-    sessionId: maybeSessionid,
+    sessionId: sessionId,
   };
 }
 
+/**
+ * Parse a taler[+http]://tip URI.
+ * Return undefined if not passed a valid URI.
+ */
 export function parseTipUri(s: string): TipUriResult | undefined {
-  const pfx = "taler://tip/";
-  if (!s.toLowerCase().startsWith(pfx)) {
+  const pi = parseProtoInfo(s, "tip");
+  if (!pi) {
     return undefined;
   }
-
-  const path = s.slice(pfx.length);
-
-  let [host, maybePath, maybeInstance, tipId] = path.split("/");
-
-  if (!host) {
-    return undefined;
-  }
-
-  host = host.toLowerCase();
-
-  if (!maybePath) {
-    return undefined;
-  }
-
-  if (!tipId) {
+  const c = pi?.rest.split("?");
+  const parts = c[0].split("/");
+  if (parts.length < 2) {
     return undefined;
   }
-
-  if (maybePath === "-") {
-    maybePath = "public/";
-  } else {
-    maybePath = decodeURIComponent(maybePath) + "/";
-  }
-  let maybeInstancePath = "";
-  if (maybeInstance !== "-") {
-    maybeInstancePath = `instances/${maybeInstance}/`;
-  }
-
-  const merchantBaseUrl = `https://${host}/${maybePath}${maybeInstancePath}`;
+  const host = parts[0].toLowerCase();
+  const tipId = parts[parts.length - 1];
+  const pathSegments = parts.slice(1, parts.length - 1);
+  const p = [host, ...pathSegments].join("/");
+  const merchantBaseUrl = `${pi.innerProto}://${p}/`;
 
   return {
-    merchantTipId: tipId,
-    merchantOrigin: new URL(merchantBaseUrl).origin,
     merchantBaseUrl,
+    merchantTipId: tipId,
   };
 }
 
+/**
+ * Parse a taler[+http]://refund URI.
+ * Return undefined if not passed a valid URI.
+ */
 export function parseRefundUri(s: string): RefundUriResult | undefined {
-  const pfx = "taler://refund/";
-
-  if (!s.toLowerCase().startsWith(pfx)) {
-    return undefined;
-  }
-
-  const [path, search] = s.slice(pfx.length).split("?");
-
-  let [host, maybePath, maybeInstance, orderId] = path.split("/");
-
-  if (!host) {
-    return undefined;
-  }
-
-  host = host.toLowerCase();
-
-  if (!maybePath) {
+  const pi = parseProtoInfo(s, "refund");
+  if (!pi) {
     return undefined;
   }
-
-  if (!orderId) {
+  const c = pi?.rest.split("?");
+  const parts = c[0].split("/");
+  if (parts.length < 2) {
     return undefined;
   }
-
-  if (maybePath === "-") {
-    maybePath = "";
-  } else {
-    maybePath = decodeURIComponent(maybePath) + "/";
-  }
-  let maybeInstancePath = "";
-  if (maybeInstance !== "-") {
-    maybeInstancePath = `instances/${maybeInstance}/`;
-  }
-
-  let protocol = "https";
-  const searchParams = new URLSearchParams(search);
-  if (searchParams.get("insecure") === "1") {
-    protocol = "http";
-  }
-
-  const merchantBaseUrl =
-    `${protocol}://${host}/` + maybePath + maybeInstancePath;
+  const host = parts[0].toLowerCase();
+  const orderId = parts[parts.length - 1];
+  const pathSegments = parts.slice(1, parts.length - 1);
+  const p = [host, ...pathSegments].join("/");
+  const merchantBaseUrl = `${pi.innerProto}://${p}/`;
 
   return {
     merchantBaseUrl,

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]