paymentserver.cpp raw

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