diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -128,6 +128,7 @@ private: static bool readPaymentRequestFromFile(const QString &filename, PaymentRequestPlus &request); + bool handleURI(const QString &scheme, const QString &s); bool processPaymentRequest(const PaymentRequestPlus &request, SendCoinsRecipient &recipient); void fetchRequest(const QUrl &url); diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -201,6 +201,35 @@ // "certificate stapling" with server-side caching is more efficient } +static std::string ipcParseURI(const QString &arg, const CChainParams ¶ms, + bool useCashAddr) { + const QString scheme = GUIUtil::bitcoinURIScheme(params, useCashAddr); + if (!arg.startsWith(scheme + ":", Qt::CaseInsensitive)) { + return {}; + } + + SendCoinsRecipient r; + if (!GUIUtil::parseBitcoinURI(scheme, arg, &r)) { + return {}; + } + + return r.address.toStdString(); +} + +static bool ipcCanParseCashAddrURI(const QString &arg, + const std::string &network) { + const CChainParams ¶ms(Params(network)); + std::string addr = ipcParseURI(arg, params, true); + return IsValidDestinationString(addr, params); +} + +static bool ipcCanParseLegacyURI(const QString &arg, + const std::string &network) { + const CChainParams ¶ms(Params(network)); + std::string addr = ipcParseURI(arg, params, false); + return IsValidDestinationString(addr, params); +} + // // Sending to the server is done synchronously, at startup. // If the server isn't already running, startup continues, and the items in @@ -210,54 +239,69 @@ // message()", but "QMessageBox::"! // void PaymentServer::ipcParseCommandLine(int argc, char *argv[]) { + std::array networks = {&CBaseChainParams::MAIN, + &CBaseChainParams::TESTNET, + &CBaseChainParams::REGTEST}; + + const std::string *chosenNetwork = nullptr; + for (int i = 1; i < argc; i++) { QString arg(argv[i]); if (arg.startsWith("-")) { continue; } - QString scheme = GUIUtil::bitcoinURIScheme(Params(), false); - // If the bitcoincash: URI contains a payment request, we are not able - // to detect the network as that would require fetching and parsing the - // payment request. That means clicking such an URI which contains a - // testnet payment request will start a mainnet instance and throw a - // "wrong network" error. - if (arg.startsWith(scheme + ":", - Qt::CaseInsensitive)) // bitcoincash: URI - { - savedPaymentRequests.append(arg); - - SendCoinsRecipient r; - if (GUIUtil::parseBitcoinURI(scheme, arg, &r) && - !r.address.isEmpty()) { - if (IsValidDestinationString(r.address.toStdString(), - Params(CBaseChainParams::MAIN))) { - SelectParams(CBaseChainParams::MAIN); - } else if (IsValidDestinationString( - r.address.toStdString(), - Params(CBaseChainParams::TESTNET))) { - SelectParams(CBaseChainParams::TESTNET); - } + const std::string *itemNetwork = nullptr; + + // Try to parse as a URI + for (auto net : networks) { + if (ipcCanParseCashAddrURI(arg, *net)) { + itemNetwork = net; + break; } - } else if (QFile::exists(arg)) { - // Filename - savedPaymentRequests.append(arg); + if (ipcCanParseLegacyURI(arg, *net)) { + itemNetwork = net; + break; + } + } + + if (!itemNetwork && QFile::exists(arg)) { + // Filename PaymentRequestPlus request; if (readPaymentRequestFromFile(arg, request)) { - if (request.getDetails().network() == "main") { - SelectParams(CBaseChainParams::MAIN); - } else if (request.getDetails().network() == "test") { - SelectParams(CBaseChainParams::TESTNET); + for (auto net : networks) { + if (*net == request.getDetails().network()) { + itemNetwork = net; + } } } - } else { + } + + if (itemNetwork == nullptr) { // Printing to debug.log is about the best we can do here, the GUI // hasn't started yet so we can't pop up a message box. qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " - "file does not exist: " + "file or URI does not exist or is invalid: " << arg; + continue; + } + + if (chosenNetwork && chosenNetwork != itemNetwork) { + qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " + "from network " + << QString(itemNetwork->c_str()) + << " does not match already chosen network " + << QString(chosenNetwork->c_str()); + continue; } + + savedPaymentRequests.append(arg); + chosenNetwork = itemNetwork; + } + + if (chosenNetwork) { + SelectParams(*chosenNetwork); } } @@ -397,64 +441,77 @@ savedPaymentRequests.clear(); } -void PaymentServer::handleURIOrFile(const QString &s) { - if (saveURIs) { - savedPaymentRequests.append(s); - return; +bool PaymentServer::handleURI(const QString &scheme, const QString &s) { + if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) { + return false; } - // bitcoincash: URI - QString scheme = GUIUtil::bitcoinURIScheme(Params(), false); - if (s.startsWith(scheme + ":", Qt::CaseInsensitive)) { #if QT_VERSION < 0x050000 - QUrl uri(s); + QUrl uri(s); #else - QUrlQuery uri((QUrl(s))); + QUrlQuery uri((QUrl(s))); #endif - if (uri.hasQueryItem("r")) { - // payment request URI - QByteArray temp; - temp.append(uri.queryItemValue("r")); - QString decoded = QUrl::fromPercentEncoding(temp); - QUrl fetchUrl(decoded, QUrl::StrictMode); - - if (fetchUrl.isValid()) { - qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" - << fetchUrl << ")"; - fetchRequest(fetchUrl); - } else { - qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " - << fetchUrl; - Q_EMIT message(tr("URI handling"), - tr("Payment request fetch URL is invalid: %1") - .arg(fetchUrl.toString()), - CClientUIInterface::ICON_WARNING); - } - - return; + if (uri.hasQueryItem("r")) { + // payment request URI + QByteArray temp; + temp.append(uri.queryItemValue("r")); + QString decoded = QUrl::fromPercentEncoding(temp); + QUrl fetchUrl(decoded, QUrl::StrictMode); + + if (fetchUrl.isValid()) { + qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" + << fetchUrl << ")"; + fetchRequest(fetchUrl); } else { - // normal URI - SendCoinsRecipient recipient; - if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) { - if (!IsValidDestinationString( - recipient.address.toStdString())) { - Q_EMIT message( - tr("URI handling"), - tr("Invalid payment address %1").arg(recipient.address), - CClientUIInterface::MSG_ERROR); - } else { - Q_EMIT receivedPaymentRequest(recipient); - } - } else { - Q_EMIT message( - tr("URI handling"), - tr("URI cannot be parsed! This can be caused by an invalid " - "Bitcoin address or malformed URI parameters."), - CClientUIInterface::ICON_WARNING); - } + qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " + << fetchUrl; + Q_EMIT message(tr("URI handling"), + tr("Payment request fetch URL is invalid: %1") + .arg(fetchUrl.toString()), + CClientUIInterface::ICON_WARNING); + } - return; + return true; + } + + // normal URI + SendCoinsRecipient recipient; + if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) { + if (!IsValidDestinationString(recipient.address.toStdString())) { + Q_EMIT message( + tr("URI handling"), + tr("Invalid payment address %1").arg(recipient.address), + CClientUIInterface::MSG_ERROR); + } else { + Q_EMIT receivedPaymentRequest(recipient); } + } else { + Q_EMIT message( + tr("URI handling"), + tr("URI cannot be parsed! This can be caused by an invalid " + "Bitcoin address or malformed URI parameters."), + CClientUIInterface::ICON_WARNING); + } + + return true; +} + +void PaymentServer::handleURIOrFile(const QString &s) { + if (saveURIs) { + savedPaymentRequests.append(s); + return; + } + + // bitcoincash: CashAddr URI + QString schemeCash = GUIUtil::bitcoinURIScheme(Params(), true); + if (handleURI(schemeCash, s)) { + return; + } + + // bitcoincash: Legacy URI + QString schemeLegacy = GUIUtil::bitcoinURIScheme(Params(), false); + if (handleURI(schemeLegacy, s)) { + return; } // payment request file