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