paymentserver.cpp raw
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 }
265