LCOV - code coverage report
Current view: top level - src/qt - paymentserver.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 2 132 1.5 %
Date: 2020-09-26 01:30:44 Functions: 2 15 13.3 %

          Line data    Source code
       1             : // Copyright (c) 2011-2019 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             : #if defined(HAVE_CONFIG_H)
       6             : #include <config/bitcoin-config.h>
       7             : #endif
       8             : 
       9             : #include <qt/paymentserver.h>
      10             : 
      11             : #include <qt/bitcoinunits.h>
      12             : #include <qt/guiutil.h>
      13             : #include <qt/optionsmodel.h>
      14             : 
      15             : #include <chainparams.h>
      16             : #include <interfaces/node.h>
      17             : #include <key_io.h>
      18             : #include <node/ui_interface.h>
      19             : #include <policy/policy.h>
      20             : #include <util/system.h>
      21             : #include <wallet/wallet.h>
      22             : 
      23             : #include <cstdlib>
      24             : #include <memory>
      25             : 
      26             : #include <QApplication>
      27             : #include <QByteArray>
      28             : #include <QDataStream>
      29             : #include <QDateTime>
      30             : #include <QDebug>
      31             : #include <QFile>
      32             : #include <QFileOpenEvent>
      33             : #include <QHash>
      34             : #include <QList>
      35             : #include <QLocalServer>
      36             : #include <QLocalSocket>
      37             : #include <QStringList>
      38             : #include <QUrlQuery>
      39             : 
      40             : const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
      41           1 : const QString BITCOIN_IPC_PREFIX("bitcoin:");
      42             : 
      43             : //
      44             : // Create a name that is unique for:
      45             : //  testnet / non-testnet
      46             : //  data directory
      47             : //
      48           0 : static QString ipcServerName()
      49             : {
      50           0 :     QString name("BitcoinQt");
      51             : 
      52             :     // Append a simple hash of the datadir
      53             :     // Note that GetDataDir(true) returns a different path
      54             :     // for -testnet versus main net
      55           0 :     QString ddir(GUIUtil::boostPathToQString(GetDataDir(true)));
      56           0 :     name.append(QString::number(qHash(ddir)));
      57             : 
      58             :     return name;
      59           0 : }
      60             : 
      61             : //
      62             : // We store payment URIs and requests received before
      63             : // the main GUI window is up and ready to ask the user
      64             : // to send payment.
      65             : 
      66           1 : static QSet<QString> savedPaymentRequests;
      67             : 
      68             : //
      69             : // Sending to the server is done synchronously, at startup.
      70             : // If the server isn't already running, startup continues,
      71             : // and the items in savedPaymentRequest will be handled
      72             : // when uiReady() is called.
      73             : //
      74             : // Warning: ipcSendCommandLine() is called early in init,
      75             : // so don't use "Q_EMIT message()", but "QMessageBox::"!
      76             : //
      77           0 : void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
      78             : {
      79           0 :     for (int i = 1; i < argc; i++)
      80             :     {
      81           0 :         QString arg(argv[i]);
      82           0 :         if (arg.startsWith("-"))
      83           0 :             continue;
      84             : 
      85             :         // If the bitcoin: URI contains a payment request, we are not able to detect the
      86             :         // network as that would require fetching and parsing the payment request.
      87             :         // That means clicking such an URI which contains a testnet payment request
      88             :         // will start a mainnet instance and throw a "wrong network" error.
      89           0 :         if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
      90             :         {
      91           0 :             if (savedPaymentRequests.contains(arg)) continue;
      92           0 :             savedPaymentRequests.insert(arg);
      93             : 
      94           0 :             SendCoinsRecipient r;
      95           0 :             if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty())
      96             :             {
      97           0 :                 auto tempChainParams = CreateChainParams(CBaseChainParams::MAIN);
      98             : 
      99           0 :                 if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
     100           0 :                     SelectParams(CBaseChainParams::MAIN);
     101             :                 } else {
     102           0 :                     tempChainParams = CreateChainParams(CBaseChainParams::TESTNET);
     103           0 :                     if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
     104           0 :                         SelectParams(CBaseChainParams::TESTNET);
     105             :                     }
     106             :                 }
     107           0 :             }
     108           0 :         }
     109           0 :     }
     110           0 : }
     111             : 
     112             : //
     113             : // Sending to the server is done synchronously, at startup.
     114             : // If the server isn't already running, startup continues,
     115             : // and the items in savedPaymentRequest will be handled
     116             : // when uiReady() is called.
     117             : //
     118           0 : bool PaymentServer::ipcSendCommandLine()
     119             : {
     120             :     bool fResult = false;
     121           0 :     for (const QString& r : savedPaymentRequests)
     122             :     {
     123           0 :         QLocalSocket* socket = new QLocalSocket();
     124           0 :         socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
     125           0 :         if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
     126             :         {
     127           0 :             delete socket;
     128             :             socket = nullptr;
     129           0 :             return false;
     130             :         }
     131             : 
     132           0 :         QByteArray block;
     133           0 :         QDataStream out(&block, QIODevice::WriteOnly);
     134           0 :         out.setVersion(QDataStream::Qt_4_0);
     135           0 :         out << r;
     136           0 :         out.device()->seek(0);
     137             : 
     138           0 :         socket->write(block);
     139           0 :         socket->flush();
     140           0 :         socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
     141           0 :         socket->disconnectFromServer();
     142             : 
     143           0 :         delete socket;
     144             :         socket = nullptr;
     145             :         fResult = true;
     146           0 :     }
     147             : 
     148           0 :     return fResult;
     149           0 : }
     150             : 
     151           0 : PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
     152           0 :     QObject(parent),
     153           0 :     saveURIs(true),
     154           0 :     uriServer(nullptr),
     155           0 :     optionsModel(nullptr)
     156           0 : {
     157             :     // Install global event filter to catch QFileOpenEvents
     158             :     // on Mac: sent when you click bitcoin: links
     159             :     // other OSes: helpful when dealing with payment request files
     160           0 :     if (parent)
     161           0 :         parent->installEventFilter(this);
     162             : 
     163           0 :     QString name = ipcServerName();
     164             : 
     165             :     // Clean up old socket leftover from a crash:
     166           0 :     QLocalServer::removeServer(name);
     167             : 
     168           0 :     if (startLocalServer)
     169             :     {
     170           0 :         uriServer = new QLocalServer(this);
     171             : 
     172           0 :         if (!uriServer->listen(name)) {
     173             :             // constructor is called early in init, so don't use "Q_EMIT message()" here
     174           0 :             QMessageBox::critical(nullptr, tr("Payment request error"),
     175           0 :                 tr("Cannot start bitcoin: click-to-pay handler"));
     176           0 :         }
     177             :         else {
     178           0 :             connect(uriServer, &QLocalServer::newConnection, this, &PaymentServer::handleURIConnection);
     179             :         }
     180             :     }
     181           0 : }
     182             : 
     183           0 : PaymentServer::~PaymentServer()
     184           0 : {
     185           0 : }
     186             : 
     187             : //
     188             : // OSX-specific way of handling bitcoin: URIs
     189             : //
     190           0 : bool PaymentServer::eventFilter(QObject *object, QEvent *event)
     191             : {
     192           0 :     if (event->type() == QEvent::FileOpen) {
     193           0 :         QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
     194           0 :         if (!fileEvent->file().isEmpty())
     195           0 :             handleURIOrFile(fileEvent->file());
     196           0 :         else if (!fileEvent->url().isEmpty())
     197           0 :             handleURIOrFile(fileEvent->url().toString());
     198             : 
     199             :         return true;
     200           0 :     }
     201             : 
     202           0 :     return QObject::eventFilter(object, event);
     203           0 : }
     204             : 
     205           0 : void PaymentServer::uiReady()
     206             : {
     207           0 :     saveURIs = false;
     208           0 :     for (const QString& s : savedPaymentRequests)
     209             :     {
     210           0 :         handleURIOrFile(s);
     211             :     }
     212           0 :     savedPaymentRequests.clear();
     213           0 : }
     214             : 
     215           0 : void PaymentServer::handleURIOrFile(const QString& s)
     216             : {
     217           0 :     if (saveURIs)
     218             :     {
     219           0 :         savedPaymentRequests.insert(s);
     220           0 :         return;
     221             :     }
     222             : 
     223           0 :     if (s.startsWith("bitcoin://", Qt::CaseInsensitive))
     224             :     {
     225           0 :         Q_EMIT message(tr("URI handling"), tr("'bitcoin://' is not a valid URI. Use 'bitcoin:' instead."),
     226             :             CClientUIInterface::MSG_ERROR);
     227           0 :     }
     228           0 :     else if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
     229             :     {
     230           0 :         QUrlQuery uri((QUrl(s)));
     231             :         // normal URI
     232             :         {
     233           0 :             SendCoinsRecipient recipient;
     234           0 :             if (GUIUtil::parseBitcoinURI(s, &recipient))
     235             :             {
     236           0 :                 if (!IsValidDestinationString(recipient.address.toStdString())) {
     237           0 :                     if (uri.hasQueryItem("r")) {  // payment request
     238           0 :                         Q_EMIT message(tr("URI handling"),
     239           0 :                             tr("Cannot process payment request because BIP70 is not supported.")+
     240           0 :                             tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
     241           0 :                             tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
     242             :                             CClientUIInterface::ICON_WARNING);
     243           0 :                     }
     244           0 :                     Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
     245             :                         CClientUIInterface::MSG_ERROR);
     246           0 :                 }
     247             :                 else
     248           0 :                     Q_EMIT receivedPaymentRequest(recipient);
     249             :             }
     250             :             else
     251           0 :                 Q_EMIT message(tr("URI handling"),
     252           0 :                     tr("URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
     253             :                     CClientUIInterface::ICON_WARNING);
     254             : 
     255             :             return;
     256           0 :         }
     257           0 :     }
     258             : 
     259           0 :     if (QFile::exists(s)) // payment request file
     260             :     {
     261           0 :         Q_EMIT message(tr("Payment request file handling"),
     262           0 :             tr("Cannot process payment request because BIP70 is not supported.")+
     263           0 :             tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
     264           0 :             tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
     265             :             CClientUIInterface::ICON_WARNING);
     266           0 :     }
     267           0 : }
     268             : 
     269           0 : void PaymentServer::handleURIConnection()
     270             : {
     271           0 :     QLocalSocket *clientConnection = uriServer->nextPendingConnection();
     272             : 
     273           0 :     while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
     274           0 :         clientConnection->waitForReadyRead();
     275             : 
     276           0 :     connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater);
     277             : 
     278           0 :     QDataStream in(clientConnection);
     279           0 :     in.setVersion(QDataStream::Qt_4_0);
     280           0 :     if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
     281           0 :         return;
     282             :     }
     283           0 :     QString msg;
     284           0 :     in >> msg;
     285             : 
     286           0 :     handleURIOrFile(msg);
     287           0 : }
     288             : 
     289           0 : void PaymentServer::setOptionsModel(OptionsModel *_optionsModel)
     290             : {
     291           0 :     this->optionsModel = _optionsModel;
     292           0 : }

Generated by: LCOV version 1.15