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