/ src / qt / paymentserver.cpp
paymentserver.cpp
  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  }