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