Changeset View
Changeset View
Standalone View
Standalone View
src/qt/paymentserver.cpp
// Copyright (c) 2011-2016 The Bitcoin Core developers | // Copyright (c) 2011-2016 The Bitcoin Core developers | ||||
// Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
#if defined(HAVE_CONFIG_H) | |||||
#include <config/bitcoin-config.h> | |||||
#endif | |||||
#include <qt/paymentserver.h> | #include <qt/paymentserver.h> | ||||
#include <cashaddrenc.h> | #include <cashaddrenc.h> | ||||
#include <chainparams.h> | #include <chainparams.h> | ||||
#include <interfaces/node.h> | #include <interfaces/node.h> | ||||
#include <key_io.h> | #include <key_io.h> | ||||
#include <policy/policy.h> | #include <policy/policy.h> | ||||
#include <qt/bitcoinunits.h> | #include <qt/bitcoinunits.h> | ||||
Show All 26 Lines | |||||
#include <QStringList> | #include <QStringList> | ||||
#include <QTextDocument> | #include <QTextDocument> | ||||
#include <QUrlQuery> | #include <QUrlQuery> | ||||
#include <cstdlib> | #include <cstdlib> | ||||
#include <memory> | #include <memory> | ||||
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds | const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds | ||||
#ifdef ENABLE_BIP70 | |||||
// BIP70 payment protocol messages | // BIP70 payment protocol messages | ||||
const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; | const char *BIP70_MESSAGE_PAYMENTACK = "PaymentACK"; | ||||
const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; | const char *BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest"; | ||||
// BIP71 payment protocol media types | // BIP71 payment protocol media types | ||||
const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment"; | const char *BIP71_MIMETYPE_PAYMENT = "application/bitcoincash-payment"; | ||||
const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack"; | const char *BIP71_MIMETYPE_PAYMENTACK = "application/bitcoincash-paymentack"; | ||||
const char *BIP71_MIMETYPE_PAYMENTREQUEST = | const char *BIP71_MIMETYPE_PAYMENTREQUEST = | ||||
"application/bitcoincash-paymentrequest"; | "application/bitcoincash-paymentrequest"; | ||||
#endif | |||||
// | // | ||||
// Create a name that is unique for: | // Create a name that is unique for: | ||||
// testnet / non-testnet | // testnet / non-testnet | ||||
// data directory | // data directory | ||||
// | // | ||||
static QString ipcServerName() { | static QString ipcServerName() { | ||||
QString name("BitcoinQt"); | QString name("BitcoinQt"); | ||||
▲ Show 20 Lines • Show All 74 Lines • ▼ Show 20 Lines | for (int i = 1; i < argc; i++) { | ||||
} | } | ||||
if (ipcCanParseLegacyURI(arg, *net)) { | if (ipcCanParseLegacyURI(arg, *net)) { | ||||
itemNetwork = net; | itemNetwork = net; | ||||
break; | break; | ||||
} | } | ||||
} | } | ||||
#ifdef ENABLE_BIP70 | |||||
if (!itemNetwork && QFile::exists(arg)) { | if (!itemNetwork && QFile::exists(arg)) { | ||||
// Filename | // Filename | ||||
PaymentRequestPlus request; | PaymentRequestPlus request; | ||||
if (readPaymentRequestFromFile(arg, request)) { | if (readPaymentRequestFromFile(arg, request)) { | ||||
for (auto net : networks) { | for (auto net : networks) { | ||||
if (*net == request.getDetails().network()) { | if (*net == request.getDetails().network()) { | ||||
itemNetwork = net; | itemNetwork = net; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (itemNetwork == nullptr) { | if (itemNetwork == nullptr) { | ||||
Fabien: I'm not sure this check should be excluded. For example if someone opens a bitcoin core uri… | |||||
// Printing to debug.log is about the best we can do here, the GUI | // 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. | // hasn't started yet so we can't pop up a message box. | ||||
qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " | qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " | ||||
"file or URI does not exist or is invalid: " | "file or URI does not exist or is invalid: " | ||||
<< arg; | << arg; | ||||
continue; | continue; | ||||
} | } | ||||
if (chosenNetwork && chosenNetwork != itemNetwork) { | if (chosenNetwork && chosenNetwork != itemNetwork) { | ||||
qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " | qWarning() << "PaymentServer::ipcSendCommandLine: Payment request " | ||||
"from network " | "from network " | ||||
<< QString(itemNetwork->c_str()) | << QString(itemNetwork->c_str()) | ||||
<< " does not match already chosen network " | << " does not match already chosen network " | ||||
<< QString(chosenNetwork->c_str()); | << QString(chosenNetwork->c_str()); | ||||
continue; | continue; | ||||
} | } | ||||
savedPaymentRequests.append(arg); | savedPaymentRequests.append(arg); | ||||
#endif | |||||
chosenNetwork = itemNetwork; | chosenNetwork = itemNetwork; | ||||
} | } | ||||
if (chosenNetwork) { | if (chosenNetwork) { | ||||
node.selectParams(*chosenNetwork); | node.selectParams(*chosenNetwork); | ||||
} | } | ||||
} | } | ||||
Show All 28 Lines | for (const QString &r : savedPaymentRequests) { | ||||
socket = nullptr; | socket = nullptr; | ||||
fResult = true; | fResult = true; | ||||
} | } | ||||
return fResult; | return fResult; | ||||
} | } | ||||
PaymentServer::PaymentServer(QObject *parent, bool startLocalServer) | PaymentServer::PaymentServer(QObject *parent, bool startLocalServer) | ||||
: QObject(parent), saveURIs(true), uriServer(0), optionsModel(0), | : QObject(parent), saveURIs(true), uriServer(0), optionsModel(0) | ||||
netManager(0) { | #ifdef ENABLE_BIP70 | ||||
, | |||||
netManager(0) | |||||
FabienUnsubmitted Not Done Inline ActionsI suppose this is from clang-format, any chance this can be made better ? Fabien: I suppose this is from clang-format, any chance this can be made better ? | |||||
jasonbcoxAuthorUnsubmitted Done Inline Actionsyes, clang-format did this. I've turned off formatting for these few lines. it's a little easier to read this way. Although it doesn't strictly match Core better, it looks to be more inline with Core's code, so should make merge conflicts easier to resolve. Let me know what you think. jasonbcox: yes, clang-format did this. I've turned off formatting for these few lines. it's a little… | |||||
#endif | |||||
{ | |||||
#ifdef ENABLE_BIP70 | |||||
// Verify that the version of the library that we linked against is | // Verify that the version of the library that we linked against is | ||||
// compatible with the version of the headers we compiled against. | // compatible with the version of the headers we compiled against. | ||||
GOOGLE_PROTOBUF_VERIFY_VERSION; | GOOGLE_PROTOBUF_VERIFY_VERSION; | ||||
#endif | |||||
// Install global event filter to catch QFileOpenEvents | // Install global event filter to catch QFileOpenEvents | ||||
// on Mac: sent when you click bitcoincash: links | // on Mac: sent when you click bitcoincash: links | ||||
// other OSes: helpful when dealing with payment request files | // other OSes: helpful when dealing with payment request files | ||||
if (parent) { | if (parent) { | ||||
parent->installEventFilter(this); | parent->installEventFilter(this); | ||||
} | } | ||||
QString name = ipcServerName(); | QString name = ipcServerName(); | ||||
// Clean up old socket leftover from a crash: | // Clean up old socket leftover from a crash: | ||||
QLocalServer::removeServer(name); | QLocalServer::removeServer(name); | ||||
if (startLocalServer) { | if (startLocalServer) { | ||||
uriServer = new QLocalServer(this); | uriServer = new QLocalServer(this); | ||||
if (!uriServer->listen(name)) { | if (!uriServer->listen(name)) { | ||||
// constructor is called early in init, so don't use "Q_EMIT | // constructor is called early in init, so don't use "Q_EMIT | ||||
// message()" here | // message()" here | ||||
QMessageBox::critical(0, tr("Payment request error"), | QMessageBox::critical(0, tr("Payment request error"), | ||||
tr("Cannot start click-to-pay handler")); | tr("Cannot start click-to-pay handler")); | ||||
} else { | } else { | ||||
connect(uriServer, &QLocalServer::newConnection, this, | connect(uriServer, &QLocalServer::newConnection, this, | ||||
&PaymentServer::handleURIConnection); | &PaymentServer::handleURIConnection); | ||||
#ifdef ENABLE_BIP70 | |||||
connect(this, &PaymentServer::receivedPaymentACK, this, | connect(this, &PaymentServer::receivedPaymentACK, this, | ||||
&PaymentServer::handlePaymentACK); | &PaymentServer::handlePaymentACK); | ||||
#endif | |||||
} | } | ||||
} | } | ||||
} | } | ||||
PaymentServer::~PaymentServer() { | PaymentServer::~PaymentServer() { | ||||
#ifdef ENABLE_BIP70 | |||||
google::protobuf::ShutdownProtobufLibrary(); | google::protobuf::ShutdownProtobufLibrary(); | ||||
#endif | |||||
} | } | ||||
// | // | ||||
// OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types. | // OSX-specific way of handling bitcoincash: URIs and PaymentRequest mime types. | ||||
// Also used by paymentservertests.cpp and when opening a payment request file | // Also used by paymentservertests.cpp and when opening a payment request file | ||||
// via "Open URI..." menu entry. | // via "Open URI..." menu entry. | ||||
// | // | ||||
bool PaymentServer::eventFilter(QObject *object, QEvent *event) { | bool PaymentServer::eventFilter(QObject *object, QEvent *event) { | ||||
if (event->type() == QEvent::FileOpen) { | if (event->type() == QEvent::FileOpen) { | ||||
QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent *>(event); | QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent *>(event); | ||||
if (!fileEvent->file().isEmpty()) { | if (!fileEvent->file().isEmpty()) { | ||||
handleURIOrFile(fileEvent->file()); | handleURIOrFile(fileEvent->file()); | ||||
} else if (!fileEvent->url().isEmpty()) { | } else if (!fileEvent->url().isEmpty()) { | ||||
handleURIOrFile(fileEvent->url().toString()); | handleURIOrFile(fileEvent->url().toString()); | ||||
} | } | ||||
return true; | return true; | ||||
} | } | ||||
return QObject::eventFilter(object, event); | return QObject::eventFilter(object, event); | ||||
} | } | ||||
void PaymentServer::uiReady() { | void PaymentServer::uiReady() { | ||||
#ifdef ENABLE_BIP70 | |||||
initNetManager(); | initNetManager(); | ||||
#endif | |||||
saveURIs = false; | saveURIs = false; | ||||
for (const QString &s : savedPaymentRequests) { | for (const QString &s : savedPaymentRequests) { | ||||
handleURIOrFile(s); | handleURIOrFile(s); | ||||
} | } | ||||
savedPaymentRequests.clear(); | savedPaymentRequests.clear(); | ||||
} | } | ||||
bool PaymentServer::handleURI(const CChainParams ¶ms, const QString &s) { | bool PaymentServer::handleURI(const CChainParams ¶ms, const QString &s) { | ||||
const QString scheme = QString::fromStdString(params.CashAddrPrefix()); | const QString scheme = QString::fromStdString(params.CashAddrPrefix()); | ||||
if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) { | if (!s.startsWith(scheme + ":", Qt::CaseInsensitive)) { | ||||
return false; | return false; | ||||
} | } | ||||
QUrlQuery uri((QUrl(s))); | QUrlQuery uri((QUrl(s))); | ||||
if (uri.hasQueryItem("r")) { | |||||
// payment request URI | // payment request URI | ||||
if (uri.hasQueryItem("r")) { | |||||
#ifdef ENABLE_BIP70 | |||||
QByteArray temp; | QByteArray temp; | ||||
temp.append(uri.queryItemValue("r")); | temp.append(uri.queryItemValue("r")); | ||||
QString decoded = QUrl::fromPercentEncoding(temp); | QString decoded = QUrl::fromPercentEncoding(temp); | ||||
QUrl fetchUrl(decoded, QUrl::StrictMode); | QUrl fetchUrl(decoded, QUrl::StrictMode); | ||||
if (fetchUrl.isValid()) { | if (fetchUrl.isValid()) { | ||||
qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" | qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" | ||||
<< fetchUrl << ")"; | << fetchUrl << ")"; | ||||
fetchRequest(fetchUrl); | fetchRequest(fetchUrl); | ||||
} else { | } else { | ||||
qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " | qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " | ||||
<< fetchUrl; | << fetchUrl; | ||||
Q_EMIT message(tr("URI handling"), | Q_EMIT message(tr("URI handling"), | ||||
tr("Payment request fetch URL is invalid: %1") | tr("Payment request fetch URL is invalid: %1") | ||||
.arg(fetchUrl.toString()), | .arg(fetchUrl.toString()), | ||||
CClientUIInterface::ICON_WARNING); | CClientUIInterface::ICON_WARNING); | ||||
} | } | ||||
#else | |||||
Q_EMIT message(tr("URI handling"), | |||||
tr("Cannot process payment request because BIP70 " | |||||
"support was not compiled in."), | |||||
CClientUIInterface::ICON_WARNING); | |||||
#endif | |||||
return true; | return true; | ||||
} | } | ||||
// normal URI | // normal URI | ||||
SendCoinsRecipient recipient; | SendCoinsRecipient recipient; | ||||
if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) { | if (GUIUtil::parseBitcoinURI(scheme, s, &recipient)) { | ||||
if (!IsValidDestinationString(recipient.address.toStdString(), | if (!IsValidDestinationString(recipient.address.toStdString(), | ||||
params)) { | params)) { | ||||
Show All 21 Lines | if (saveURIs) { | ||||
return; | return; | ||||
} | } | ||||
// bitcoincash: CashAddr URI | // bitcoincash: CashAddr URI | ||||
if (handleURI(Params(), s)) { | if (handleURI(Params(), s)) { | ||||
return; | return; | ||||
} | } | ||||
#ifdef ENABLE_BIP70 | |||||
// payment request file | // payment request file | ||||
if (QFile::exists(s)) { | if (QFile::exists(s)) { | ||||
PaymentRequestPlus request; | PaymentRequestPlus request; | ||||
SendCoinsRecipient recipient; | SendCoinsRecipient recipient; | ||||
if (!readPaymentRequestFromFile(s, request)) { | if (!readPaymentRequestFromFile(s, request)) { | ||||
Q_EMIT message(tr("Payment request file handling"), | Q_EMIT message(tr("Payment request file handling"), | ||||
tr("Payment request file cannot be read! This can " | tr("Payment request file cannot be read! This can " | ||||
"be caused by an invalid payment request file."), | "be caused by an invalid payment request file."), | ||||
CClientUIInterface::ICON_WARNING); | CClientUIInterface::ICON_WARNING); | ||||
} else if (processPaymentRequest(request, recipient)) { | } else if (processPaymentRequest(request, recipient)) { | ||||
Q_EMIT receivedPaymentRequest(recipient); | Q_EMIT receivedPaymentRequest(recipient); | ||||
} | } | ||||
return; | return; | ||||
} | } | ||||
#endif | |||||
} | } | ||||
void PaymentServer::handleURIConnection() { | void PaymentServer::handleURIConnection() { | ||||
QLocalSocket *clientConnection = uriServer->nextPendingConnection(); | QLocalSocket *clientConnection = uriServer->nextPendingConnection(); | ||||
while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) { | while (clientConnection->bytesAvailable() < (int)sizeof(quint32)) { | ||||
clientConnection->waitForReadyRead(); | clientConnection->waitForReadyRead(); | ||||
} | } | ||||
Show All 11 Lines | void PaymentServer::handleURIConnection() { | ||||
handleURIOrFile(msg); | handleURIOrFile(msg); | ||||
} | } | ||||
void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) { | void PaymentServer::setOptionsModel(OptionsModel *_optionsModel) { | ||||
this->optionsModel = _optionsModel; | this->optionsModel = _optionsModel; | ||||
} | } | ||||
#ifdef ENABLE_BIP70 | |||||
struct X509StoreDeleter { | struct X509StoreDeleter { | ||||
void operator()(X509_STORE *b) { X509_STORE_free(b); } | void operator()(X509_STORE *b) { X509_STORE_free(b); } | ||||
}; | }; | ||||
struct X509Deleter { | struct X509Deleter { | ||||
void operator()(X509 *b) { X509_free(b); } | void operator()(X509 *b) { X509_free(b); } | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 471 Lines • ▼ Show 20 Lines | if (!fVerified) { | ||||
.arg(MAX_MONEY / SATOSHI); | .arg(MAX_MONEY / SATOSHI); | ||||
} | } | ||||
return fVerified; | return fVerified; | ||||
} | } | ||||
X509_STORE *PaymentServer::getCertStore() { | X509_STORE *PaymentServer::getCertStore() { | ||||
return certStore.get(); | return certStore.get(); | ||||
} | } | ||||
#endif |
I'm not sure this check should be excluded. For example if someone opens a bitcoin core uri (bitcoin://) this will fail silently.