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.\n"
239  "Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.\n"
240  "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.\n"
262  "Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.\n"
263  "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 }
policy.h
GUIUtil::boostPathToQString
QString boostPathToQString(const fs::path &path)
Convert OS specific boost path to QString through UTF-8.
Definition: guiutil.cpp:640
GetDataDir
const fs::path & GetDataDir(bool fNetSpecific)
Definition: system.cpp:770
PaymentServer::optionsModel
OptionsModel * optionsModel
Definition: paymentserver.h:106
wallet.h
key_io.h
PaymentServer::saveURIs
bool saveURIs
Definition: paymentserver.h:104
PaymentServer::message
void message(const QString &title, const QString &message, unsigned int style)
BITCOIN_IPC_PREFIX
const QString BITCOIN_IPC_PREFIX("bitcoin:")
CClientUIInterface::ICON_WARNING
@ ICON_WARNING
Definition: ui_interface.h:31
PaymentServer::~PaymentServer
~PaymentServer()
Definition: paymentserver.cpp:182
PaymentServer::setOptionsModel
void setOptionsModel(OptionsModel *optionsModel)
Definition: paymentserver.cpp:288
CBaseChainParams::TESTNET
static const std::string TESTNET
Definition: chainparamsbase.h:23
PaymentServer::handleURIConnection
void handleURIConnection()
Definition: paymentserver.cpp:268
GUIUtil::parseBitcoinURI
bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
Definition: guiutil.cpp:122
PaymentServer::ipcParseCommandLine
static void ipcParseCommandLine(int argc, char *argv[])
Definition: paymentserver.cpp:76
bitcoin-config.h
chainparams.h
PaymentServer::uiReady
void uiReady()
Definition: paymentserver.cpp:204
PaymentServer::ipcSendCommandLine
static bool ipcSendCommandLine()
Definition: paymentserver.cpp:117
SendCoinsRecipient
Definition: sendcoinsrecipient.h:19
OptionsModel
Interface from Qt to configuration data structure for Bitcoin client.
Definition: optionsmodel.h:39
ipcServerName
static QString ipcServerName()
Definition: paymentserver.cpp:47
PaymentServer::receivedPaymentRequest
void receivedPaymentRequest(SendCoinsRecipient)
node.h
guiutil.h
SelectParams
void SelectParams(const std::string &network)
Sets the params returned by Params() to those for the given chain name.
Definition: chainparams.cpp:542
savedPaymentRequests
static QSet< QString > savedPaymentRequests
Definition: paymentserver.cpp:65
CBaseChainParams::MAIN
static const std::string MAIN
Chain name strings.
Definition: chainparamsbase.h:22
name
const char * name
Definition: rest.cpp:42
ui_interface.h
system.h
CClientUIInterface::MSG_ERROR
@ MSG_ERROR
Definition: ui_interface.h:68
SendCoinsRecipient::address
QString address
Definition: sendcoinsrecipient.h:31
gArgs
ArgsManager gArgs
Definition: system.cpp:79
bitcoinunits.h
IsValidDestinationString
bool IsValidDestinationString(const std::string &str, const CChainParams &params)
Definition: key_io.cpp:257
PaymentServer::uriServer
QLocalServer * uriServer
Definition: paymentserver.h:105
PaymentServer::handleURIOrFile
void handleURIOrFile(const QString &s)
Definition: paymentserver.cpp:214
optionsmodel.h
BITCOIN_IPC_CONNECT_TIMEOUT
const int BITCOIN_IPC_CONNECT_TIMEOUT
Definition: paymentserver.cpp:39
PaymentServer::eventFilter
bool eventFilter(QObject *object, QEvent *event) override
Definition: paymentserver.cpp:189
PaymentServer::PaymentServer
PaymentServer(QObject *parent, bool startLocalServer=true)
Definition: paymentserver.cpp:150
CreateChainParams
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.
Definition: chainparams.cpp:528
paymentserver.h