Bitcoin Core  0.19.99
P2P Digital Currency
paymentserver.cpp
Go to the documentation of this file.
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)
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 <policy/policy.h>
18 #include <key_io.h>
19 #include <ui_interface.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 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 static QString ipcServerName()
49 {
50  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  QString ddir(GUIUtil::boostPathToQString(GetDataDir(true)));
56  name.append(QString::number(qHash(ddir)));
57 
58  return name;
59 }
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 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 void PaymentServer::ipcParseCommandLine(interfaces::Node& node, int argc, char* argv[])
78 {
79  for (int i = 1; i < argc; i++)
80  {
81  QString arg(argv[i]);
82  if (arg.startsWith("-"))
83  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  if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
90  {
91  if (savedPaymentRequests.contains(arg)) continue;
92  savedPaymentRequests.insert(arg);
93 
95  if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty())
96  {
97  auto tempChainParams = CreateChainParams(CBaseChainParams::MAIN);
98 
99  if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
101  } else {
102  tempChainParams = CreateChainParams(CBaseChainParams::TESTNET);
103  if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
105  }
106  }
107  }
108  }
109  }
110 }
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 //
119 {
120  bool fResult = false;
121  for (const QString& r : savedPaymentRequests)
122  {
123  QLocalSocket* socket = new QLocalSocket();
124  socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
125  if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
126  {
127  delete socket;
128  socket = nullptr;
129  return false;
130  }
131 
132  QByteArray block;
133  QDataStream out(&block, QIODevice::WriteOnly);
134  out.setVersion(QDataStream::Qt_4_0);
135  out << r;
136  out.device()->seek(0);
137 
138  socket->write(block);
139  socket->flush();
140  socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
141  socket->disconnectFromServer();
142 
143  delete socket;
144  socket = nullptr;
145  fResult = true;
146  }
147 
148  return fResult;
149 }
150 
151 PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
152  QObject(parent),
153  saveURIs(true),
154  uriServer(nullptr),
155  optionsModel(nullptr)
156 {
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  if (parent)
161  parent->installEventFilter(this);
162 
163  QString name = ipcServerName();
164 
165  // Clean up old socket leftover from a crash:
166  QLocalServer::removeServer(name);
167 
168  if (startLocalServer)
169  {
170  uriServer = new QLocalServer(this);
171 
172  if (!uriServer->listen(name)) {
173  // constructor is called early in init, so don't use "Q_EMIT message()" here
174  QMessageBox::critical(nullptr, tr("Payment request error"),
175  tr("Cannot start bitcoin: click-to-pay handler"));
176  }
177  else {
178  connect(uriServer, &QLocalServer::newConnection, this, &PaymentServer::handleURIConnection);
179  }
180  }
181 }
182 
184 {
185 }
186 
187 //
188 // OSX-specific way of handling bitcoin: URIs
189 //
190 bool PaymentServer::eventFilter(QObject *object, QEvent *event)
191 {
192  if (event->type() == QEvent::FileOpen) {
193  QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
194  if (!fileEvent->file().isEmpty())
195  handleURIOrFile(fileEvent->file());
196  else if (!fileEvent->url().isEmpty())
197  handleURIOrFile(fileEvent->url().toString());
198 
199  return true;
200  }
201 
202  return QObject::eventFilter(object, event);
203 }
204 
206 {
207  saveURIs = false;
208  for (const QString& s : savedPaymentRequests)
209  {
210  handleURIOrFile(s);
211  }
212  savedPaymentRequests.clear();
213 }
214 
215 void PaymentServer::handleURIOrFile(const QString& s)
216 {
217  if (saveURIs)
218  {
219  savedPaymentRequests.insert(s);
220  return;
221  }
222 
223  if (s.startsWith("bitcoin://", Qt::CaseInsensitive))
224  {
225  Q_EMIT message(tr("URI handling"), tr("'bitcoin://' is not a valid URI. Use 'bitcoin:' instead."),
227  }
228  else if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
229  {
230  QUrlQuery uri((QUrl(s)));
231  // normal URI
232  {
233  SendCoinsRecipient recipient;
234  if (GUIUtil::parseBitcoinURI(s, &recipient))
235  {
236  if (!IsValidDestinationString(recipient.address.toStdString())) {
237  if (uri.hasQueryItem("r")) { // payment request
238  Q_EMIT message(tr("URI handling"),
239  tr("Cannot process payment request because BIP70 is not supported.")+
240  tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
241  tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
243  }
244  Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
246  }
247  else
248  Q_EMIT receivedPaymentRequest(recipient);
249  }
250  else
251  Q_EMIT message(tr("URI handling"),
252  tr("URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
254 
255  return;
256  }
257  }
258 
259  if (QFile::exists(s)) // payment request file
260  {
261  Q_EMIT message(tr("Payment request file handling"),
262  tr("Cannot process payment request because BIP70 is not supported.")+
263  tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
264  tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
266  }
267 }
268 
270 {
271  QLocalSocket *clientConnection = uriServer->nextPendingConnection();
272 
273  while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
274  clientConnection->waitForReadyRead();
275 
276  connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater);
277 
278  QDataStream in(clientConnection);
279  in.setVersion(QDataStream::Qt_4_0);
280  if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
281  return;
282  }
283  QString msg;
284  in >> msg;
285 
286  handleURIOrFile(msg);
287 }
288 
290 {
291  this->optionsModel = _optionsModel;
292 }
std::unique_ptr< const CChainParams > CreateChainParams(const std::string &chain)
Creates and returns a std::unique_ptr<CChainParams> of the chosen chain.
void message(const QString &title, const QString &message, unsigned int style)
virtual void selectParams(const std::string &network)=0
Choose network parameters.
void setOptionsModel(OptionsModel *optionsModel)
bool IsValidDestinationString(const std::string &str, const CChainParams &params)
Definition: key_io.cpp:220
static void ipcParseCommandLine(interfaces::Node &node, int argc, char *argv[])
bool eventFilter(QObject *object, QEvent *event)
void receivedPaymentRequest(SendCoinsRecipient)
bool parseBitcoinURI(const QUrl &uri, SendCoinsRecipient *out)
Definition: guiutil.cpp:117
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:40
const fs::path & GetDataDir(bool fNetSpecific)
Definition: system.cpp:612
PaymentServer(QObject *parent, bool startLocalServer=true)
Interface from Qt to configuration data structure for Bitcoin client.
Definition: optionsmodel.h:36
const QString BITCOIN_IPC_PREFIX("bitcoin:")
static QString ipcServerName()
void handleURIConnection()
static const std::string TESTNET
OptionsModel * optionsModel
QString boostPathToQString(const fs::path &path)
Definition: guiutil.cpp:715
Top-level interface for a bitcoin node (bitcoind process).
Definition: node.h:39