aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/dbTypes.ts16
-rw-r--r--src/headless/taler-wallet-cli.ts53
-rw-r--r--src/util/taleruri-test.ts55
-rw-r--r--src/util/taleruri.ts81
-rw-r--r--src/wallet-impl/pay.ts23
-rw-r--r--src/wallet-impl/pending.ts3
-rw-r--r--src/walletTypes.ts1
-rw-r--r--src/webex/wxBackend.ts114
8 files changed, 200 insertions, 146 deletions
diff --git a/src/dbTypes.ts b/src/dbTypes.ts
index 3625740e2..553040614 100644
--- a/src/dbTypes.ts
+++ b/src/dbTypes.ts
@@ -699,11 +699,11 @@ export class ProposalDownload {
*/
@Checkable.Class()
export class ProposalRecord {
- /**
- * URL where the proposal was downloaded.
- */
@Checkable.String()
- url: string;
+ orderId: string;
+
+ @Checkable.String()
+ merchantBaseUrl: string;
/**
* Downloaded data from the merchant.
@@ -970,7 +970,6 @@ export interface WireFee {
sig: string;
}
-
/**
* Record that stores status information about one purchase, starting from when
* the customer accepts a proposal. Includes refund status if applicable.
@@ -1058,7 +1057,7 @@ export interface PurchaseRecord {
*/
lastRefundStatusError: OperationError | undefined;
- /**
+ /**
* Retry information for querying the refund status with the merchant.
*/
refundApplyRetryInfo: RetryInfo;
@@ -1242,7 +1241,10 @@ export namespace Stores {
constructor() {
super("proposals", { keyPath: "proposalId" });
}
- urlIndex = new Index<string, ProposalRecord>(this, "urlIndex", "url");
+ urlAndOrderIdIndex = new Index<string, ProposalRecord>(this, "urlIndex", [
+ "merchantBaseUrl",
+ "orderId",
+ ]);
}
class PurchasesStore extends Store<PurchaseRecord> {
diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts
index 2073c2573..4cec984d5 100644
--- a/src/headless/taler-wallet-cli.ts
+++ b/src/headless/taler-wallet-cli.ts
@@ -28,6 +28,7 @@ import * as Amounts from "../util/amounts";
import { decodeCrock } from "../crypto/talerCrypto";
import { OperationFailedAndReportedError } from "../wallet-impl/errors";
import { Bank } from "./bank";
+import { classifyTalerUri, TalerUriType } from "../util/taleruri";
const logger = new Logger("taler-wallet-cli.ts");
@@ -212,25 +213,39 @@ walletCli
.action(async args => {
await withWallet(args, async wallet => {
const uri: string = args.handleUri.uri;
- if (uri.startsWith("taler://pay/")) {
- await doPay(wallet, uri, { alwaysYes: args.handleUri.autoYes });
- } else if (uri.startsWith("taler://tip/")) {
- const res = await wallet.getTipStatus(uri);
- console.log("tip status", res);
- await wallet.acceptTip(res.tipId);
- } else if (uri.startsWith("taler://refund/")) {
- await wallet.applyRefund(uri);
- } else if (uri.startsWith("taler://withdraw/")) {
- const withdrawInfo = await wallet.getWithdrawalInfo(uri);
- const selectedExchange = withdrawInfo.suggestedExchange;
- if (!selectedExchange) {
- console.error("no suggested exchange!");
- process.exit(1);
- return;
- }
- const res = await wallet.acceptWithdrawal(uri, selectedExchange);
- await wallet.processReserve(res.reservePub);
+ const uriType = classifyTalerUri(uri);
+ switch (uriType) {
+ case TalerUriType.TalerPay:
+ await doPay(wallet, uri, { alwaysYes: args.handleUri.autoYes });
+ break;
+ case TalerUriType.TalerTip:
+ {
+ const res = await wallet.getTipStatus(uri);
+ console.log("tip status", res);
+ await wallet.acceptTip(res.tipId);
+ }
+ break;
+ case TalerUriType.TalerRefund:
+ await wallet.applyRefund(uri);
+ break;
+ case TalerUriType.TalerWithdraw:
+ {
+ const withdrawInfo = await wallet.getWithdrawalInfo(uri);
+ const selectedExchange = withdrawInfo.suggestedExchange;
+ if (!selectedExchange) {
+ console.error("no suggested exchange!");
+ process.exit(1);
+ return;
+ }
+ const res = await wallet.acceptWithdrawal(uri, selectedExchange);
+ await wallet.processReserve(res.reservePub);
+ }
+ break;
+ default:
+ console.log(`URI type (${uriType}) not handled`);
+ break;
}
+ return;
});
});
@@ -445,7 +460,7 @@ testCli
.requiredOption("bank", ["-b", "--bank"], clk.STRING, {
default: "https://bank.test.taler.net/",
})
- .action(async (args) => {
+ .action(async args => {
const b = new Bank(args.genWithdrawUri.bank);
const user = await b.registerRandomUser();
const url = await b.generateWithdrawUri(user, args.genWithdrawUri.amount);
diff --git a/src/util/taleruri-test.ts b/src/util/taleruri-test.ts
index c687a6717..de4a90697 100644
--- a/src/util/taleruri-test.ts
+++ b/src/util/taleruri-test.ts
@@ -22,23 +22,6 @@ import {
parseTipUri,
} from "./taleruri";
-test("taler pay url parsing: http(s)", t => {
- const url1 = "https://example.com/bar?spam=eggs";
- const r1 = parsePayUri(url1);
- if (!r1) {
- t.fail();
- return;
- }
- t.is(r1.downloadUrl, url1);
- t.is(r1.sessionId, undefined);
- const url2 = "http://example.com/bar?spam=eggs";
- const r2 = parsePayUri(url2);
- if (!r2) {
- t.fail();
- return;
- }
-});
-
test("taler pay url parsing: wrong scheme", t => {
const url1 = "talerfoo://";
const r1 = parsePayUri(url1);
@@ -56,7 +39,7 @@ test("taler pay url parsing: defaults", t => {
t.fail();
return;
}
- t.is(r1.downloadUrl, "https://example.com/public/proposal?order_id=myorder");
+ t.is(r1.merchantBaseUrl, "https://example.com/public/");
t.is(r1.sessionId, undefined);
const url2 = "taler://pay/example.com/-/-/myorder/mysession";
@@ -65,7 +48,7 @@ test("taler pay url parsing: defaults", t => {
t.fail();
return;
}
- t.is(r2.downloadUrl, "https://example.com/public/proposal?order_id=myorder");
+ t.is(r2.merchantBaseUrl, "https://example.com/public/");
t.is(r2.sessionId, "mysession");
});
@@ -76,7 +59,7 @@ test("taler pay url parsing: trailing parts", t => {
t.fail();
return;
}
- t.is(r1.downloadUrl, "https://example.com/public/proposal?order_id=myorder");
+ t.is(r1.merchantBaseUrl, "https://example.com/public/");
t.is(r1.sessionId, "mysession");
});
@@ -87,10 +70,8 @@ test("taler pay url parsing: instance", t => {
t.fail();
return;
}
- t.is(
- r1.downloadUrl,
- "https://example.com/public/instances/myinst/proposal?order_id=myorder",
- );
+ t.is(r1.merchantBaseUrl, "https://example.com/public/instances/myinst/");
+ t.is(r1.orderId, "myorder");
});
test("taler pay url parsing: path prefix and instance", t => {
@@ -100,10 +81,7 @@ test("taler pay url parsing: path prefix and instance", t => {
t.fail();
return;
}
- t.is(
- r1.downloadUrl,
- "https://example.com/mypfx/instances/myinst/proposal?order_id=myorder",
- );
+ t.is(r1.merchantBaseUrl, "https://example.com/mypfx/instances/myinst/");
});
test("taler pay url parsing: complex path prefix", t => {
@@ -113,10 +91,9 @@ test("taler pay url parsing: complex path prefix", t => {
t.fail();
return;
}
- t.is(
- r1.downloadUrl,
- "https://example.com/mypfx/public/proposal?order_id=myorder",
- );
+ t.is(r1.merchantBaseUrl, "https://example.com/mypfx/public/");
+ t.is(r1.orderId, "myorder");
+ t.is(r1.sessionId, undefined);
});
test("taler pay url parsing: complex path prefix and instance", t => {
@@ -126,10 +103,8 @@ test("taler pay url parsing: complex path prefix and instance", t => {
t.fail();
return;
}
- t.is(
- r1.downloadUrl,
- "https://example.com/mypfx/public/instances/foo/proposal?order_id=myorder",
- );
+ t.is(r1.merchantBaseUrl, "https://example.com/mypfx/public/instances/foo/");
+ t.is(r1.orderId, "myorder");
});
test("taler pay url parsing: non-https #1", t => {
@@ -138,8 +113,9 @@ test("taler pay url parsing: non-https #1", t => {
if (!r1) {
t.fail();
return;
- }
- t.is(r1.downloadUrl, "http://example.com/public/proposal?order_id=myorder");
+ }
+ t.is(r1.merchantBaseUrl, "http://example.com/public/");
+ t.is(r1.orderId, "myorder")
});
test("taler pay url parsing: non-https #2", t => {
@@ -149,7 +125,8 @@ test("taler pay url parsing: non-https #2", t => {
t.fail();
return;
}
- t.is(r1.downloadUrl, "https://example.com/public/proposal?order_id=myorder");
+ t.is(r1.merchantBaseUrl, "https://example.com/public/");
+ t.is(r1.orderId, "myorder");
});
test("taler withdraw uri parsing", t => {
diff --git a/src/util/taleruri.ts b/src/util/taleruri.ts
index 50886a916..f34b82a59 100644
--- a/src/util/taleruri.ts
+++ b/src/util/taleruri.ts
@@ -15,7 +15,8 @@
*/
export interface PayUriResult {
- downloadUrl: string;
+ merchantBaseUrl: string;
+ orderId: string;
sessionId?: string;
}
@@ -36,7 +37,7 @@ export interface TipUriResult {
export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
const pfx = "taler://withdraw/";
- if (!s.startsWith(pfx)) {
+ if (!s.toLowerCase().startsWith(pfx)) {
return undefined;
}
@@ -44,6 +45,20 @@ export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
let [host, path, withdrawId] = rest.split("/");
+ if (!host) {
+ return undefined;
+ }
+
+ host = host.toLowerCase();
+
+ if (!path) {
+ return undefined;
+ }
+
+ if (!withdrawId) {
+ return undefined;
+ }
+
if (path === "-") {
path = "api/withdraw-operation";
}
@@ -53,15 +68,45 @@ export function parseWithdrawUri(s: string): WithdrawUriResult | undefined {
};
}
-export function parsePayUri(s: string): PayUriResult | undefined {
- if (s.startsWith("https://") || s.startsWith("http://")) {
- return {
- downloadUrl: s,
- sessionId: undefined,
- };
+export const enum TalerUriType {
+ TalerPay = "taler-pay",
+ TalerWithdraw = "taler-withdraw",
+ TalerTip = "taler-tip",
+ TalerRefund = "taler-refund",
+ TalerNotifyReserve = "taler-notify-reserve",
+ Unknown = "unknown",
+}
+
+export function classifyTalerUri(s: string): TalerUriType {
+ const sl = s.toLowerCase();
+ if (sl.startsWith("taler://pay/")) {
+ return TalerUriType.TalerPay;
}
+ if (sl.startsWith("taler://tip/")) {
+ return TalerUriType.TalerTip;
+ }
+ if (sl.startsWith("taler://refund/")) {
+ return TalerUriType.TalerRefund;
+ }
+ if (sl.startsWith("taler://withdraw/")) {
+ return TalerUriType.TalerWithdraw;
+ }
+ if (sl.startsWith("taler://notify-reserve/")) {
+ return TalerUriType.TalerWithdraw;
+ }
+ return TalerUriType.Unknown;
+
+}
+
+export function getOrderDownloadUrl(merchantBaseUrl: string, orderId: string) {
+ const u = new URL("proposal", merchantBaseUrl);
+ u.searchParams.set("order_id", orderId);
+ return u.href
+}
+
+export function parsePayUri(s: string): PayUriResult | undefined {
const pfx = "taler://pay/";
- if (!s.startsWith(pfx)) {
+ if (!s.toLowerCase().startsWith(pfx)) {
return undefined;
}
@@ -75,6 +120,8 @@ export function parsePayUri(s: string): PayUriResult | undefined {
return undefined;
}
+ host = host.toLowerCase();
+
if (!maybePath) {
return undefined;
}
@@ -99,21 +146,21 @@ export function parsePayUri(s: string): PayUriResult | undefined {
protocol = "http";
}
- const downloadUrl =
+ const merchantBaseUrl =
`${protocol}://${host}/` +
decodeURIComponent(maybePath) +
- maybeInstancePath +
- `proposal?order_id=${orderId}`;
+ maybeInstancePath;
return {
- downloadUrl,
+ merchantBaseUrl,
+ orderId,
sessionId: maybeSessionid,
};
}
export function parseTipUri(s: string): TipUriResult | undefined {
const pfx = "taler://tip/";
- if (!s.startsWith(pfx)) {
+ if (!s.toLowerCase().startsWith(pfx)) {
return undefined;
}
@@ -125,6 +172,8 @@ export function parseTipUri(s: string): TipUriResult | undefined {
return undefined;
}
+ host = host.toLowerCase();
+
if (!maybePath) {
return undefined;
}
@@ -155,7 +204,7 @@ export function parseTipUri(s: string): TipUriResult | undefined {
export function parseRefundUri(s: string): RefundUriResult | undefined {
const pfx = "taler://refund/";
- if (!s.startsWith(pfx)) {
+ if (!s.toLowerCase().startsWith(pfx)) {
return undefined;
}
@@ -167,6 +216,8 @@ export function parseRefundUri(s: string): RefundUriResult | undefined {
return undefined;
}
+ host = host.toLowerCase();
+
if (!maybePath) {
return undefined;
}
diff --git a/src/wallet-impl/pay.ts b/src/wallet-impl/pay.ts
index 4933098cd..7076f905d 100644
--- a/src/wallet-impl/pay.ts
+++ b/src/wallet-impl/pay.ts
@@ -65,7 +65,7 @@ import {
} from "../util/helpers";
import { Logger } from "../util/logging";
import { InternalWalletState } from "./state";
-import { parsePayUri, parseRefundUri } from "../util/taleruri";
+import { parsePayUri, parseRefundUri, getOrderDownloadUrl } from "../util/taleruri";
import { getTotalRefreshCost, refresh } from "./refresh";
import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
import { guardOperationException } from "./errors";
@@ -557,9 +557,10 @@ async function processDownloadProposalImpl(
if (proposal.proposalStatus != ProposalStatus.DOWNLOADING) {
return;
}
- const parsed_url = new URL(proposal.url);
- parsed_url.searchParams.set("nonce", proposal.noncePub);
- const urlWithNonce = parsed_url.href;
+
+ const parsedUrl = new URL(getOrderDownloadUrl(proposal.merchantBaseUrl, proposal.orderId));
+ parsedUrl.searchParams.set("nonce", proposal.noncePub);
+ const urlWithNonce = parsedUrl.href;
console.log("downloading contract from '" + urlWithNonce + "'");
let resp;
try {
@@ -629,13 +630,14 @@ async function processDownloadProposalImpl(
*/
async function startDownloadProposal(
ws: InternalWalletState,
- url: string,
+ merchantBaseUrl: string,
+ orderId: string,
sessionId?: string,
): Promise<string> {
const oldProposal = await oneShotGetIndexed(
ws.db,
- Stores.proposals.urlIndex,
- url,
+ Stores.proposals.urlAndOrderIdIndex,
+ [merchantBaseUrl, orderId],
);
if (oldProposal) {
await processDownloadProposal(ws, oldProposal.proposalId);
@@ -650,8 +652,8 @@ async function startDownloadProposal(
noncePriv: priv,
noncePub: pub,
timestamp: getTimestampNow(),
- url,
- downloadSessionId: sessionId,
+ merchantBaseUrl,
+ orderId,
proposalId: proposalId,
proposalStatus: ProposalStatus.DOWNLOADING,
repurchaseProposalId: undefined,
@@ -763,7 +765,8 @@ export async function preparePay(
let proposalId = await startDownloadProposal(
ws,
- uriResult.downloadUrl,
+ uriResult.merchantBaseUrl,
+ uriResult.orderId,
uriResult.sessionId,
);
diff --git a/src/wallet-impl/pending.ts b/src/wallet-impl/pending.ts
index 02f8d9ef9..5fb9ef5ce 100644
--- a/src/wallet-impl/pending.ts
+++ b/src/wallet-impl/pending.ts
@@ -312,7 +312,8 @@ async function gatherProposalPending(
resp.pendingOperations.push({
type: "proposal-download",
givesLifeness: true,
- merchantBaseUrl: proposal.download?.contractTerms.merchant_base_url || "",
+ merchantBaseUrl: proposal.merchantBaseUrl,
+ orderId: proposal.orderId,
proposalId: proposal.proposalId,
proposalTimestamp: proposal.timestamp,
lastError: proposal.lastError,
diff --git a/src/walletTypes.ts b/src/walletTypes.ts
index 407871666..e2be26b03 100644
--- a/src/walletTypes.ts
+++ b/src/walletTypes.ts
@@ -741,6 +741,7 @@ export interface PendingProposalDownloadOperation {
merchantBaseUrl: string;
proposalTimestamp: Timestamp;
proposalId: string;
+ orderId: string;
lastError?: OperationError;
retryInfo: RetryInfo;
}
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 4363890eb..27141247e 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -42,6 +42,7 @@ import Port = chrome.runtime.Port;
import MessageSender = chrome.runtime.MessageSender;
import { BrowserCryptoWorkerFactory } from "../crypto/workers/cryptoApi";
import { OpenedPromise, openPromise } from "../util/promiseUtils";
+import { classifyTalerUri, TalerUriType } from "../util/taleruri";
const NeedsWallet = Symbol("NeedsWallet");
@@ -257,7 +258,11 @@ async function handleMessage(
await walletInit.promise;
} catch (e) {
errors.push("Error during wallet initialization: " + e);
- if (currentDatabase === undefined && outdatedDbVersion === undefined && isFirefox()) {
+ if (
+ currentDatabase === undefined &&
+ outdatedDbVersion === undefined &&
+ isFirefox()
+ ) {
firefoxIdbProblem = true;
}
}
@@ -435,7 +440,7 @@ async function reinitWallet() {
http,
new BrowserCryptoWorkerFactory(),
);
- wallet.runRetryLoop().catch((e) => {
+ wallet.runRetryLoop().catch(e => {
console.log("error during wallet retry loop", e);
});
// Useful for debugging in the background page.
@@ -601,61 +606,60 @@ export async function wxMain() {
for (let header of details.responseHeaders || []) {
if (header.name.toLowerCase() === "taler") {
const talerUri = header.value || "";
- if (!talerUri.startsWith("taler://")) {
- console.warn(
- "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
- );
- break;
- }
- if (talerUri.startsWith("taler://withdraw/")) {
- return makeSyncWalletRedirect(
- "withdraw.html",
- details.tabId,
- details.url,
- {
- talerWithdrawUri: talerUri,
- },
- );
- } else if (talerUri.startsWith("taler://pay/")) {
- return makeSyncWalletRedirect(
- "pay.html",
- details.tabId,
- details.url,
- {
- talerPayUri: talerUri,
- },
- );
- } else if (talerUri.startsWith("taler://tip/")) {
- return makeSyncWalletRedirect(
- "tip.html",
- details.tabId,
- details.url,
- {
- talerTipUri: talerUri,
- },
- );
- } else if (talerUri.startsWith("taler://refund/")) {
- return makeSyncWalletRedirect(
- "refund.html",
- details.tabId,
- details.url,
- {
- talerRefundUri: talerUri,
- },
- );
- } else if (talerUri.startsWith("taler://notify-reserve/")) {
- Promise.resolve().then(() => {
- const w = currentWallet;
- if (!w) {
- return;
- }
- w.handleNotifyReserve();
- });
+ const uriType = classifyTalerUri(talerUri);
+ switch (uriType) {
+ case TalerUriType.TalerWithdraw:
+ return makeSyncWalletRedirect(
+ "withdraw.html",
+ details.tabId,
+ details.url,
+ {
+ talerWithdrawUri: talerUri,
+ },
+ );
+ case TalerUriType.TalerPay:
+ return makeSyncWalletRedirect(
+ "pay.html",
+ details.tabId,
+ details.url,
+ {
+ talerPayUri: talerUri,
+ },
+ );
+ case TalerUriType.TalerTip:
+ return makeSyncWalletRedirect(
+ "tip.html",
+ details.tabId,
+ details.url,
+ {
+ talerTipUri: talerUri,
+ },
+ );
+ case TalerUriType.TalerRefund:
+ return makeSyncWalletRedirect(
+ "refund.html",
+ details.tabId,
+ details.url,
+ {
+ talerRefundUri: talerUri,
+ },
+ );
+ case TalerUriType.TalerNotifyReserve:
+ Promise.resolve().then(() => {
+ const w = currentWallet;
+ if (!w) {
+ return;
+ }
+ w.handleNotifyReserve();
+ });
+ break;
- } else {
- console.warn("Unknown action in taler:// URI, ignoring.");
+ default:
+ console.warn(
+ "Response with HTTP 402 has Taler header, but header value is not a taler:// URI.",
+ );
+ break;
}
- break;
}
}
}