LCOV - code coverage report
Current view: top level - src/qt - paymentserver.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 113 340 33.2 %
Date: 2015-10-12 22:39:14 Functions: 19 28 67.9 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // Copyright (c) 2011-2014 The Bitcoin Core developers
       2             : // Distributed under the MIT software license, see the accompanying
       3             : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
       4             : 
       5             : #include "paymentserver.h"
       6             : 
       7             : #include "bitcoinunits.h"
       8             : #include "guiutil.h"
       9             : #include "optionsmodel.h"
      10             : 
      11             : #include "base58.h"
      12             : #include "chainparams.h"
      13             : #include "main.h" // For minRelayTxFee
      14             : #include "ui_interface.h"
      15             : #include "util.h"
      16             : #include "wallet/wallet.h"
      17             : 
      18             : #include <cstdlib>
      19             : 
      20             : #include <openssl/x509_vfy.h>
      21             : 
      22             : #include <QApplication>
      23             : #include <QByteArray>
      24             : #include <QDataStream>
      25             : #include <QDateTime>
      26             : #include <QDebug>
      27             : #include <QFile>
      28             : #include <QFileOpenEvent>
      29             : #include <QHash>
      30             : #include <QList>
      31             : #include <QLocalServer>
      32             : #include <QLocalSocket>
      33             : #include <QNetworkAccessManager>
      34             : #include <QNetworkProxy>
      35             : #include <QNetworkReply>
      36             : #include <QNetworkRequest>
      37             : #include <QSslCertificate>
      38             : #include <QSslError>
      39             : #include <QSslSocket>
      40             : #include <QStringList>
      41             : #include <QTextDocument>
      42             : 
      43             : #if QT_VERSION < 0x050000
      44             : #include <QUrl>
      45             : #else
      46             : #include <QUrlQuery>
      47             : #endif
      48             : 
      49             : const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
      50           1 : const QString BITCOIN_IPC_PREFIX("bitcoin:");
      51             : // BIP70 payment protocol messages
      52             : const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
      53             : const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
      54             : // BIP71 payment protocol media types
      55             : const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment";
      56             : const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack";
      57             : const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest";
      58             : // BIP70 max payment request size in bytes (DoS protection)
      59             : const qint64 BIP70_MAX_PAYMENTREQUEST_SIZE = 50000;
      60             : 
      61             : X509_STORE* PaymentServer::certStore = NULL;
      62           1 : void PaymentServer::freeCertStore()
      63             : {
      64           3 :     if (PaymentServer::certStore != NULL)
      65             :     {
      66           3 :         X509_STORE_free(PaymentServer::certStore);
      67           3 :         PaymentServer::certStore = NULL;
      68             :     }
      69           1 : }
      70             : 
      71             : //
      72             : // Create a name that is unique for:
      73             : //  testnet / non-testnet
      74             : //  data directory
      75             : //
      76           1 : static QString ipcServerName()
      77             : {
      78             :     QString name("BitcoinQt");
      79             : 
      80             :     // Append a simple hash of the datadir
      81             :     // Note that GetDataDir(true) returns a different path
      82             :     // for -testnet versus main net
      83           3 :     QString ddir(QString::fromStdString(GetDataDir(true).string()));
      84           1 :     name.append(QString::number(qHash(ddir)));
      85             : 
      86           1 :     return name;
      87             : }
      88             : 
      89             : //
      90             : // We store payment URIs and requests received before
      91             : // the main GUI window is up and ready to ask the user
      92             : // to send payment.
      93             : 
      94           1 : static QList<QString> savedPaymentRequests;
      95             : 
      96           0 : static void ReportInvalidCertificate(const QSslCertificate& cert)
      97             : {
      98             : #if QT_VERSION < 0x050000
      99           0 :     qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
     100             : #else
     101             :     qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
     102             : #endif
     103           0 : }
     104             : 
     105             : //
     106             : // Load OpenSSL's list of root certificate authorities
     107             : //
     108           3 : void PaymentServer::LoadRootCAs(X509_STORE* _store)
     109             : {
     110           3 :     if (PaymentServer::certStore == NULL)
     111           1 :         atexit(PaymentServer::freeCertStore);
     112             :     else
     113             :         freeCertStore();
     114             : 
     115             :     // Unit tests mostly use this, to pass in fake root CAs:
     116           3 :     if (_store)
     117             :     {
     118           3 :         PaymentServer::certStore = _store;
     119           6 :         return;
     120             :     }
     121             : 
     122             :     // Normal execution, use either -rootcertificates or system certs:
     123           0 :     PaymentServer::certStore = X509_STORE_new();
     124             : 
     125             :     // Note: use "-system-" default here so that users can pass -rootcertificates=""
     126             :     // and get 'I don't like X.509 certificates, don't trust anybody' behavior:
     127           0 :     QString certFile = QString::fromStdString(GetArg("-rootcertificates", "-system-"));
     128             : 
     129             :     // Empty store
     130           0 :     if (certFile.isEmpty()) {
     131           0 :         qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__);
     132           0 :         return;
     133             :     }
     134             : 
     135           0 :     QList<QSslCertificate> certList;
     136             : 
     137           0 :     if (certFile != "-system-") {
     138           0 :             qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile);
     139             : 
     140           0 :         certList = QSslCertificate::fromPath(certFile);
     141             :         // Use those certificates when fetching payment requests, too:
     142           0 :         QSslSocket::setDefaultCaCertificates(certList);
     143             :     } else
     144           0 :         certList = QSslSocket::systemCaCertificates();
     145             : 
     146           0 :     int nRootCerts = 0;
     147           0 :     const QDateTime currentTime = QDateTime::currentDateTime();
     148             : 
     149           0 :     Q_FOREACH (const QSslCertificate& cert, certList) {
     150             :         // Don't log NULL certificates
     151           0 :         if (cert.isNull())
     152           0 :             continue;
     153             : 
     154             :         // Not yet active/valid, or expired certificate
     155           0 :         if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) {
     156           0 :             ReportInvalidCertificate(cert);
     157             :             continue;
     158             :         }
     159             : 
     160             : #if QT_VERSION >= 0x050000
     161             :         // Blacklisted certificate
     162             :         if (cert.isBlacklisted()) {
     163             :             ReportInvalidCertificate(cert);
     164             :             continue;
     165             :         }
     166             : #endif
     167           0 :         QByteArray certData = cert.toDer();
     168           0 :         const unsigned char *data = (const unsigned char *)certData.data();
     169             : 
     170           0 :         X509* x509 = d2i_X509(0, &data, certData.size());
     171           0 :         if (x509 && X509_STORE_add_cert(PaymentServer::certStore, x509))
     172             :         {
     173             :             // Note: X509_STORE_free will free the X509* objects when
     174             :             // the PaymentServer is destroyed
     175           0 :             ++nRootCerts;
     176             :         }
     177             :         else
     178             :         {
     179           0 :             ReportInvalidCertificate(cert);
     180           0 :             continue;
     181             :         }
     182           0 :     }
     183           0 :     qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates";
     184             : 
     185             :     // Project for another day:
     186             :     // Fetch certificate revocation lists, and add them to certStore.
     187             :     // Issues to consider:
     188             :     //   performance (start a thread to fetch in background?)
     189             :     //   privacy (fetch through tor/proxy so IP address isn't revealed)
     190             :     //   would it be easier to just use a compiled-in blacklist?
     191             :     //    or use Qt's blacklist?
     192             :     //   "certificate stapling" with server-side caching is more efficient
     193             : }
     194             : 
     195             : //
     196             : // Sending to the server is done synchronously, at startup.
     197             : // If the server isn't already running, startup continues,
     198             : // and the items in savedPaymentRequest will be handled
     199             : // when uiReady() is called.
     200             : //
     201             : // Warning: ipcSendCommandLine() is called early in init,
     202             : // so don't use "Q_EMIT message()", but "QMessageBox::"!
     203             : //
     204           0 : void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
     205             : {
     206           0 :     for (int i = 1; i < argc; i++)
     207             :     {
     208           0 :         QString arg(argv[i]);
     209           0 :         if (arg.startsWith("-"))
     210           0 :             continue;
     211             : 
     212             :         // If the bitcoin: URI contains a payment request, we are not able to detect the
     213             :         // network as that would require fetching and parsing the payment request.
     214             :         // That means clicking such an URI which contains a testnet payment request
     215             :         // will start a mainnet instance and throw a "wrong network" error.
     216           0 :         if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
     217             :         {
     218           0 :             savedPaymentRequests.append(arg);
     219             : 
     220           0 :             SendCoinsRecipient r;
     221           0 :             if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty())
     222             :             {
     223           0 :                 CBitcoinAddress address(r.address.toStdString());
     224             : 
     225           0 :                 if (address.IsValid(Params(CBaseChainParams::MAIN)))
     226             :                 {
     227           0 :                     SelectParams(CBaseChainParams::MAIN);
     228             :                 }
     229           0 :                 else if (address.IsValid(Params(CBaseChainParams::TESTNET)))
     230             :                 {
     231           0 :                     SelectParams(CBaseChainParams::TESTNET);
     232             :                 }
     233           0 :             }
     234             :         }
     235           0 :         else if (QFile::exists(arg)) // Filename
     236             :         {
     237           0 :             savedPaymentRequests.append(arg);
     238             : 
     239           0 :             PaymentRequestPlus request;
     240           0 :             if (readPaymentRequestFromFile(arg, request))
     241             :             {
     242           0 :                 if (request.getDetails().network() == "main")
     243             :                 {
     244           0 :                     SelectParams(CBaseChainParams::MAIN);
     245             :                 }
     246           0 :                 else if (request.getDetails().network() == "test")
     247             :                 {
     248           0 :                     SelectParams(CBaseChainParams::TESTNET);
     249             :                 }
     250           0 :             }
     251             :         }
     252             :         else
     253             :         {
     254             :             // Printing to debug.log is about the best we can do here, the
     255             :             // GUI hasn't started yet so we can't pop up a message box.
     256           0 :             qWarning() << "PaymentServer::ipcSendCommandLine: Payment request file does not exist: " << arg;
     257             :         }
     258           0 :     }
     259           0 : }
     260             : 
     261             : //
     262             : // Sending to the server is done synchronously, at startup.
     263             : // If the server isn't already running, startup continues,
     264             : // and the items in savedPaymentRequest will be handled
     265             : // when uiReady() is called.
     266             : //
     267           0 : bool PaymentServer::ipcSendCommandLine()
     268             : {
     269           0 :     bool fResult = false;
     270           0 :     Q_FOREACH (const QString& r, savedPaymentRequests)
     271             :     {
     272           0 :         QLocalSocket* socket = new QLocalSocket();
     273           0 :         socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
     274           0 :         if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
     275             :         {
     276           0 :             delete socket;
     277           0 :             socket = NULL;
     278           0 :             return false;
     279             :         }
     280             : 
     281             :         QByteArray block;
     282           0 :         QDataStream out(&block, QIODevice::WriteOnly);
     283           0 :         out.setVersion(QDataStream::Qt_4_0);
     284           0 :         out << r;
     285           0 :         out.device()->seek(0);
     286             : 
     287           0 :         socket->write(block);
     288           0 :         socket->flush();
     289           0 :         socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
     290           0 :         socket->disconnectFromServer();
     291             : 
     292           0 :         delete socket;
     293           0 :         socket = NULL;
     294           0 :         fResult = true;
     295           0 :     }
     296             : 
     297           0 :     return fResult;
     298             : }
     299             : 
     300           1 : PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
     301             :     QObject(parent),
     302             :     saveURIs(true),
     303             :     uriServer(0),
     304             :     netManager(0),
     305           1 :     optionsModel(0)
     306             : {
     307             :     // Verify that the version of the library that we linked against is
     308             :     // compatible with the version of the headers we compiled against.
     309           1 :     GOOGLE_PROTOBUF_VERIFY_VERSION;
     310             : 
     311             :     // Install global event filter to catch QFileOpenEvents
     312             :     // on Mac: sent when you click bitcoin: links
     313             :     // other OSes: helpful when dealing with payment request files
     314           1 :     if (parent)
     315           0 :         parent->installEventFilter(this);
     316             : 
     317           1 :     QString name = ipcServerName();
     318             : 
     319             :     // Clean up old socket leftover from a crash:
     320           1 :     QLocalServer::removeServer(name);
     321             : 
     322           1 :     if (startLocalServer)
     323             :     {
     324           0 :         uriServer = new QLocalServer(this);
     325             : 
     326           0 :         if (!uriServer->listen(name)) {
     327             :             // constructor is called early in init, so don't use "Q_EMIT message()" here
     328             :             QMessageBox::critical(0, tr("Payment request error"),
     329           0 :                 tr("Cannot start bitcoin: click-to-pay handler"));
     330             :         }
     331             :         else {
     332           0 :             connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
     333           0 :             connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString)));
     334             :         }
     335           1 :     }
     336           1 : }
     337             : 
     338           3 : PaymentServer::~PaymentServer()
     339             : {
     340           1 :     google::protobuf::ShutdownProtobufLibrary();
     341           2 : }
     342             : 
     343             : //
     344             : // OSX-specific way of handling bitcoin: URIs and PaymentRequest mime types.
     345             : // Also used by paymentservertests.cpp and when opening a payment request file
     346             : // via "Open URI..." menu entry.
     347             : //
     348           6 : bool PaymentServer::eventFilter(QObject *object, QEvent *event)
     349             : {
     350          12 :     if (event->type() == QEvent::FileOpen) {
     351           6 :         QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
     352           6 :         if (!fileEvent->file().isEmpty())
     353           6 :             handleURIOrFile(fileEvent->file());
     354           0 :         else if (!fileEvent->url().isEmpty())
     355           0 :             handleURIOrFile(fileEvent->url().toString());
     356             : 
     357             :         return true;
     358             :     }
     359             : 
     360           0 :     return QObject::eventFilter(object, event);
     361             : }
     362             : 
     363           1 : void PaymentServer::initNetManager()
     364             : {
     365           1 :     if (!optionsModel)
     366           0 :         return;
     367           1 :     if (netManager != NULL)
     368           0 :         delete netManager;
     369             : 
     370             :     // netManager is used to fetch paymentrequests given in bitcoin: URIs
     371           1 :     netManager = new QNetworkAccessManager(this);
     372             : 
     373           1 :     QNetworkProxy proxy;
     374             : 
     375             :     // Query active SOCKS5 proxy
     376           1 :     if (optionsModel->getProxySettings(proxy)) {
     377           0 :         netManager->setProxy(proxy);
     378             : 
     379           0 :         qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port();
     380             :     }
     381             :     else
     382           1 :         qDebug() << "PaymentServer::initNetManager: No active proxy server found.";
     383             : 
     384             :     connect(netManager, SIGNAL(finished(QNetworkReply*)),
     385           1 :             this, SLOT(netRequestFinished(QNetworkReply*)));
     386             :     connect(netManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)),
     387           1 :             this, SLOT(reportSslErrors(QNetworkReply*, const QList<QSslError> &)));
     388             : }
     389             : 
     390           1 : void PaymentServer::uiReady()
     391             : {
     392           1 :     initNetManager();
     393             : 
     394           1 :     saveURIs = false;
     395           2 :     Q_FOREACH (const QString& s, savedPaymentRequests)
     396             :     {
     397           0 :         handleURIOrFile(s);
     398             :     }
     399           1 :     savedPaymentRequests.clear();
     400           1 : }
     401             : 
     402           6 : void PaymentServer::handleURIOrFile(const QString& s)
     403             : {
     404           6 :     if (saveURIs)
     405             :     {
     406           0 :         savedPaymentRequests.append(s);
     407           0 :         return;
     408             :     }
     409             : 
     410           6 :     if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
     411             :     {
     412             : #if QT_VERSION < 0x050000
     413           0 :         QUrl uri(s);
     414             : #else
     415             :         QUrlQuery uri((QUrl(s)));
     416             : #endif
     417           0 :         if (uri.hasQueryItem("r")) // payment request URI
     418             :         {
     419             :             QByteArray temp;
     420           0 :             temp.append(uri.queryItemValue("r"));
     421           0 :             QString decoded = QUrl::fromPercentEncoding(temp);
     422           0 :             QUrl fetchUrl(decoded, QUrl::StrictMode);
     423             : 
     424           0 :             if (fetchUrl.isValid())
     425             :             {
     426           0 :                 qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")";
     427           0 :                 fetchRequest(fetchUrl);
     428             :             }
     429             :             else
     430             :             {
     431           0 :                 qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl;
     432             :                 Q_EMIT message(tr("URI handling"),
     433             :                     tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()),
     434           0 :                     CClientUIInterface::ICON_WARNING);
     435             :             }
     436             : 
     437           0 :             return;
     438             :         }
     439             :         else // normal URI
     440             :         {
     441           0 :             SendCoinsRecipient recipient;
     442           0 :             if (GUIUtil::parseBitcoinURI(s, &recipient))
     443             :             {
     444           0 :                 CBitcoinAddress address(recipient.address.toStdString());
     445           0 :                 if (!address.IsValid()) {
     446             :                     Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
     447           0 :                         CClientUIInterface::MSG_ERROR);
     448             :                 }
     449             :                 else
     450           0 :                     Q_EMIT receivedPaymentRequest(recipient);
     451             :             }
     452             :             else
     453             :                 Q_EMIT message(tr("URI handling"),
     454             :                     tr("URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
     455           0 :                     CClientUIInterface::ICON_WARNING);
     456             : 
     457           0 :             return;
     458           0 :         }
     459             :     }
     460             : 
     461           6 :     if (QFile::exists(s)) // payment request file
     462             :     {
     463           6 :         PaymentRequestPlus request;
     464          12 :         SendCoinsRecipient recipient;
     465           6 :         if (!readPaymentRequestFromFile(s, request))
     466             :         {
     467             :             Q_EMIT message(tr("Payment request file handling"),
     468             :                 tr("Payment request file cannot be read! This can be caused by an invalid payment request file."),
     469           0 :                 CClientUIInterface::ICON_WARNING);
     470             :         }
     471           6 :         else if (processPaymentRequest(request, recipient))
     472           6 :             Q_EMIT receivedPaymentRequest(recipient);
     473             : 
     474           6 :         return;
     475             :     }
     476             : }
     477             : 
     478           0 : void PaymentServer::handleURIConnection()
     479             : {
     480           0 :     QLocalSocket *clientConnection = uriServer->nextPendingConnection();
     481             : 
     482           0 :     while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
     483           0 :         clientConnection->waitForReadyRead();
     484             : 
     485             :     connect(clientConnection, SIGNAL(disconnected()),
     486           0 :             clientConnection, SLOT(deleteLater()));
     487             : 
     488           0 :     QDataStream in(clientConnection);
     489           0 :     in.setVersion(QDataStream::Qt_4_0);
     490           0 :     if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
     491           0 :         return;
     492             :     }
     493           0 :     QString msg;
     494           0 :     in >> msg;
     495             : 
     496           0 :     handleURIOrFile(msg);
     497             : }
     498             : 
     499             : //
     500             : // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
     501             : // so don't use "Q_EMIT message()", but "QMessageBox::"!
     502             : //
     503           6 : bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request)
     504             : {
     505           6 :     QFile f(filename);
     506           6 :     if (!f.open(QIODevice::ReadOnly)) {
     507           0 :         qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
     508           0 :         return false;
     509             :     }
     510             : 
     511             :     // BIP70 DoS protection
     512           6 :     if (!verifySize(f.size())) {
     513             :         return false;
     514             :     }
     515             : 
     516          12 :     QByteArray data = f.readAll();
     517             : 
     518          12 :     return request.parse(data);
     519             : }
     520             : 
     521           6 : bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient)
     522             : {
     523           6 :     if (!optionsModel)
     524             :         return false;
     525             : 
     526           6 :     if (request.IsInitialized()) {
     527             :         // Payment request network matches client network?
     528           6 :         if (!verifyNetwork(request.getDetails())) {
     529             :             Q_EMIT message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."),
     530           0 :                 CClientUIInterface::MSG_ERROR);
     531             : 
     532           0 :             return false;
     533             :         }
     534             : 
     535             :         // Make sure any payment requests involved are still valid.
     536             :         // This is re-checked just before sending coins in WalletModel::sendCoins().
     537           6 :         if (verifyExpired(request.getDetails())) {
     538             :             Q_EMIT message(tr("Payment request rejected"), tr("Payment request expired."),
     539           0 :                 CClientUIInterface::MSG_ERROR);
     540             : 
     541           0 :             return false;
     542             :         }
     543             :     } else {
     544             :         Q_EMIT message(tr("Payment request error"), tr("Payment request is not initialized."),
     545           0 :             CClientUIInterface::MSG_ERROR);
     546             : 
     547           0 :         return false;
     548             :     }
     549             : 
     550           6 :     recipient.paymentRequest = request;
     551           6 :     recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
     552             : 
     553           6 :     request.getMerchant(PaymentServer::certStore, recipient.authenticatedMerchant);
     554             : 
     555           6 :     QList<std::pair<CScript, CAmount> > sendingTos = request.getPayTo();
     556             :     QStringList addresses;
     557             : 
     558          42 :     Q_FOREACH(const PAIRTYPE(CScript, CAmount)& sendingTo, sendingTos) {
     559             :         // Extract and check destination addresses
     560             :         CTxDestination dest;
     561           6 :         if (ExtractDestination(sendingTo.first, dest)) {
     562             :             // Append destination address
     563          24 :             addresses.append(QString::fromStdString(CBitcoinAddress(dest).ToString()));
     564             :         }
     565           0 :         else if (!recipient.authenticatedMerchant.isEmpty()) {
     566             :             // Unauthenticated payment requests to custom bitcoin addresses are not supported
     567             :             // (there is no good way to tell the user where they are paying in a way they'd
     568             :             // have a chance of understanding).
     569             :             Q_EMIT message(tr("Payment request rejected"),
     570             :                 tr("Unverified payment requests to custom payment scripts are unsupported."),
     571           0 :                 CClientUIInterface::MSG_ERROR);
     572           0 :             return false;
     573             :         }
     574             : 
     575             :         // Bitcoin amounts are stored as (optional) uint64 in the protobuf messages (see paymentrequest.proto),
     576             :         // but CAmount is defined as int64_t. Because of that we need to verify that amounts are in a valid range
     577             :         // and no overflow has happened.
     578           6 :         if (!verifyAmount(sendingTo.second)) {
     579           0 :             Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
     580           0 :             return false;
     581             :         }
     582             : 
     583             :         // Extract and check amounts
     584          18 :         CTxOut txOut(sendingTo.second, sendingTo.first);
     585           6 :         if (txOut.IsDust(::minRelayTxFee)) {
     586             :             Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
     587             :                 .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
     588           0 :                 CClientUIInterface::MSG_ERROR);
     589             : 
     590           0 :             return false;
     591             :         }
     592             : 
     593           6 :         recipient.amount += sendingTo.second;
     594             :         // Also verify that the final amount is still in a valid range after adding additional amounts.
     595           6 :         if (!verifyAmount(recipient.amount)) {
     596           0 :             Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
     597           0 :             return false;
     598             :         }
     599             :     }
     600             :     // Store addresses and format them to fit nicely into the GUI
     601           6 :     recipient.address = addresses.join("<br />");
     602             : 
     603          12 :     if (!recipient.authenticatedMerchant.isEmpty()) {
     604           2 :         qDebug() << "PaymentServer::processPaymentRequest: Secure payment request from " << recipient.authenticatedMerchant;
     605             :     }
     606             :     else {
     607           4 :         qDebug() << "PaymentServer::processPaymentRequest: Insecure payment request to " << addresses.join(", ");
     608             :     }
     609             : 
     610           6 :     return true;
     611             : }
     612             : 
     613           0 : void PaymentServer::fetchRequest(const QUrl& url)
     614             : {
     615           0 :     QNetworkRequest netRequest;
     616           0 :     netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
     617           0 :     netRequest.setUrl(url);
     618           0 :     netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
     619           0 :     netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
     620           0 :     netManager->get(netRequest);
     621           0 : }
     622             : 
     623           0 : void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction)
     624             : {
     625           0 :     const payments::PaymentDetails& details = recipient.paymentRequest.getDetails();
     626           0 :     if (!details.has_payment_url())
     627           0 :         return;
     628             : 
     629           0 :     QNetworkRequest netRequest;
     630           0 :     netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
     631           0 :     netRequest.setUrl(QString::fromStdString(details.payment_url()));
     632           0 :     netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT);
     633           0 :     netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
     634           0 :     netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
     635             : 
     636           0 :     payments::Payment payment;
     637           0 :     payment.set_merchant_data(details.merchant_data());
     638           0 :     payment.add_transactions(transaction.data(), transaction.size());
     639             : 
     640             :     // Create a new refund address, or re-use:
     641           0 :     QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
     642           0 :     std::string strAccount = account.toStdString();
     643           0 :     std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
     644           0 :     if (!refundAddresses.empty()) {
     645           0 :         CScript s = GetScriptForDestination(*refundAddresses.begin());
     646           0 :         payments::Output* refund_to = payment.add_refund_to();
     647           0 :         refund_to->set_script(&s[0], s.size());
     648             :     }
     649             :     else {
     650             :         CPubKey newKey;
     651           0 :         if (wallet->GetKeyFromPool(newKey)) {
     652           0 :             CKeyID keyID = newKey.GetID();
     653           0 :             wallet->SetAddressBook(keyID, strAccount, "refund");
     654             : 
     655           0 :             CScript s = GetScriptForDestination(keyID);
     656           0 :             payments::Output* refund_to = payment.add_refund_to();
     657           0 :             refund_to->set_script(&s[0], s.size());
     658             :         }
     659             :         else {
     660             :             // This should never happen, because sending coins should have
     661             :             // just unlocked the wallet and refilled the keypool.
     662           0 :             qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
     663             :         }
     664             :     }
     665             : 
     666           0 :     int length = payment.ByteSize();
     667           0 :     netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
     668           0 :     QByteArray serData(length, '\0');
     669           0 :     if (payment.SerializeToArray(serData.data(), length)) {
     670           0 :         netManager->post(netRequest, serData);
     671             :     }
     672             :     else {
     673             :         // This should never happen, either.
     674           0 :         qWarning() << "PaymentServer::fetchPaymentACK: Error serializing payment message";
     675           0 :     }
     676             : }
     677             : 
     678           0 : void PaymentServer::netRequestFinished(QNetworkReply* reply)
     679             : {
     680           0 :     reply->deleteLater();
     681             : 
     682             :     // BIP70 DoS protection
     683           0 :     if (!verifySize(reply->size())) {
     684             :         Q_EMIT message(tr("Payment request rejected"),
     685             :             tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
     686             :                 .arg(reply->request().url().toString())
     687           0 :                 .arg(reply->size())
     688             :                 .arg(BIP70_MAX_PAYMENTREQUEST_SIZE),
     689           0 :             CClientUIInterface::MSG_ERROR);
     690           0 :         return;
     691             :     }
     692             : 
     693           0 :     if (reply->error() != QNetworkReply::NoError) {
     694             :         QString msg = tr("Error communicating with %1: %2")
     695             :             .arg(reply->request().url().toString())
     696           0 :             .arg(reply->errorString());
     697             : 
     698           0 :         qWarning() << "PaymentServer::netRequestFinished: " << msg;
     699           0 :         Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
     700           0 :         return;
     701             :     }
     702             : 
     703           0 :     QByteArray data = reply->readAll();
     704             : 
     705           0 :     QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
     706           0 :     if (requestType == BIP70_MESSAGE_PAYMENTREQUEST)
     707             :     {
     708           0 :         PaymentRequestPlus request;
     709           0 :         SendCoinsRecipient recipient;
     710           0 :         if (!request.parse(data))
     711             :         {
     712           0 :             qWarning() << "PaymentServer::netRequestFinished: Error parsing payment request";
     713             :             Q_EMIT message(tr("Payment request error"),
     714             :                 tr("Payment request cannot be parsed!"),
     715           0 :                 CClientUIInterface::MSG_ERROR);
     716             :         }
     717           0 :         else if (processPaymentRequest(request, recipient))
     718           0 :             Q_EMIT receivedPaymentRequest(recipient);
     719             : 
     720           0 :         return;
     721             :     }
     722           0 :     else if (requestType == BIP70_MESSAGE_PAYMENTACK)
     723             :     {
     724           0 :         payments::PaymentACK paymentACK;
     725           0 :         if (!paymentACK.ParseFromArray(data.data(), data.size()))
     726             :         {
     727             :             QString msg = tr("Bad response from server %1")
     728           0 :                 .arg(reply->request().url().toString());
     729             : 
     730           0 :             qWarning() << "PaymentServer::netRequestFinished: " << msg;
     731           0 :             Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
     732             :         }
     733             :         else
     734             :         {
     735           0 :             Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
     736           0 :         }
     737           0 :     }
     738             : }
     739             : 
     740           0 : void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs)
     741             : {
     742             :     Q_UNUSED(reply);
     743             : 
     744             :     QString errString;
     745           0 :     Q_FOREACH (const QSslError& err, errs) {
     746           0 :         qWarning() << "PaymentServer::reportSslErrors: " << err;
     747           0 :         errString += err.errorString() + "\n";
     748             :     }
     749           0 :     Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR);
     750           0 : }
     751             : 
     752           1 : void PaymentServer::setOptionsModel(OptionsModel *optionsModel)
     753             : {
     754           1 :     this->optionsModel = optionsModel;
     755           1 : }
     756             : 
     757           0 : void PaymentServer::handlePaymentACK(const QString& paymentACKMsg)
     758             : {
     759             :     // currently we don't further process or store the paymentACK message
     760           0 :     Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL);
     761           0 : }
     762             : 
     763           7 : bool PaymentServer::verifyNetwork(const payments::PaymentDetails& requestDetails)
     764             : {
     765          21 :     bool fVerified = requestDetails.network() == Params().NetworkIDString();
     766           7 :     if (!fVerified) {
     767             :         qWarning() << QString("PaymentServer::%1: Payment request network \"%2\" doesn't match client network \"%3\".")
     768             :             .arg(__func__)
     769           1 :             .arg(QString::fromStdString(requestDetails.network()))
     770           4 :             .arg(QString::fromStdString(Params().NetworkIDString()));
     771             :     }
     772           7 :     return fVerified;
     773             : }
     774             : 
     775           9 : bool PaymentServer::verifyExpired(const payments::PaymentDetails& requestDetails)
     776             : {
     777          18 :     bool fVerified = (requestDetails.has_expires() && (int64_t)requestDetails.expires() < GetTime());
     778           9 :     if (fVerified) {
     779           4 :         const QString requestExpires = QString::fromStdString(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires()));
     780             :         qWarning() << QString("PaymentServer::%1: Payment request expired \"%2\".")
     781             :             .arg(__func__)
     782           4 :             .arg(requestExpires);
     783             :     }
     784           9 :     return fVerified;
     785             : }
     786             : 
     787           7 : bool PaymentServer::verifySize(qint64 requestSize)
     788             : {
     789           7 :     bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE);
     790           7 :     if (!fVerified) {
     791             :         qWarning() << QString("PaymentServer::%1: Payment request too large (%2 bytes, allowed %3 bytes).")
     792             :             .arg(__func__)
     793             :             .arg(requestSize)
     794           2 :             .arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
     795             :     }
     796           7 :     return fVerified;
     797             : }
     798             : 
     799          13 : bool PaymentServer::verifyAmount(const CAmount& requestAmount)
     800             : {
     801          26 :     bool fVerified = MoneyRange(requestAmount);
     802          13 :     if (!fVerified) {
     803             :         qWarning() << QString("PaymentServer::%1: Payment request amount out of allowed range (%2, allowed 0 - %3).")
     804             :             .arg(__func__)
     805             :             .arg(requestAmount)
     806           3 :             .arg(MAX_MONEY);
     807             :     }
     808          13 :     return fVerified;
     809           3 : }

Generated by: LCOV version 1.11