Merge #12079: Improve prioritisetransaction test coverage
[bitcoinplatinum.git] / src / qt / paymentserver.cpp
blob8a83b513e17066630e6ee7c295c5d24aa0c30a2f
1 // Copyright (c) 2011-2017 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.
5 #include <qt/paymentserver.h>
7 #include <qt/bitcoinunits.h>
8 #include <qt/guiutil.h>
9 #include <qt/optionsmodel.h>
11 #include <base58.h>
12 #include <chainparams.h>
13 #include <policy/policy.h>
14 #include <ui_interface.h>
15 #include <util.h>
16 #include <wallet/wallet.h>
18 #include <cstdlib>
20 #include <openssl/x509_vfy.h>
22 #include <QApplication>
23 #include <QByteArray>
24 #include <QDataStream>
25 #include <QDateTime>
26 #include <QDebug>
27 #include <QFile>
28 #include <QFileOpenEvent>
29 #include <QHash>
30 #include <QList>
31 #include <QLocalServer>
32 #include <QLocalSocket>
33 #include <QNetworkAccessManager>
34 #include <QNetworkProxy>
35 #include <QNetworkReply>
36 #include <QNetworkRequest>
37 #include <QSslCertificate>
38 #include <QSslError>
39 #include <QSslSocket>
40 #include <QStringList>
41 #include <QTextDocument>
43 #if QT_VERSION < 0x050000
44 #include <QUrl>
45 #else
46 #include <QUrlQuery>
47 #endif
49 const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
50 const QString BITCOIN_IPC_PREFIX("bitcoin:");
51 // BIP70 payment protocol messages
52 const char* BIP70_MESSAGE_PAYMENTACK = "PaymentACK";
53 const char* BIP70_MESSAGE_PAYMENTREQUEST = "PaymentRequest";
54 // BIP71 payment protocol media types
55 const char* BIP71_MIMETYPE_PAYMENT = "application/bitcoin-payment";
56 const char* BIP71_MIMETYPE_PAYMENTACK = "application/bitcoin-paymentack";
57 const char* BIP71_MIMETYPE_PAYMENTREQUEST = "application/bitcoin-paymentrequest";
59 struct X509StoreDeleter {
60 void operator()(X509_STORE* b) {
61 X509_STORE_free(b);
65 struct X509Deleter {
66 void operator()(X509* b) { X509_free(b); }
69 namespace // Anon namespace
71 std::unique_ptr<X509_STORE, X509StoreDeleter> certStore;
75 // Create a name that is unique for:
76 // testnet / non-testnet
77 // data directory
79 static QString ipcServerName()
81 QString name("BitcoinQt");
83 // Append a simple hash of the datadir
84 // Note that GetDataDir(true) returns a different path
85 // for -testnet versus main net
86 QString ddir(GUIUtil::boostPathToQString(GetDataDir(true)));
87 name.append(QString::number(qHash(ddir)));
89 return name;
93 // We store payment URIs and requests received before
94 // the main GUI window is up and ready to ask the user
95 // to send payment.
97 static QList<QString> savedPaymentRequests;
99 static void ReportInvalidCertificate(const QSslCertificate& cert)
101 #if QT_VERSION < 0x050000
102 qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
103 #else
104 qDebug() << QString("%1: Payment server found an invalid certificate: ").arg(__func__) << cert.serialNumber() << cert.subjectInfo(QSslCertificate::CommonName) << cert.subjectInfo(QSslCertificate::DistinguishedNameQualifier) << cert.subjectInfo(QSslCertificate::OrganizationalUnitName);
105 #endif
109 // Load OpenSSL's list of root certificate authorities
111 void PaymentServer::LoadRootCAs(X509_STORE* _store)
113 // Unit tests mostly use this, to pass in fake root CAs:
114 if (_store)
116 certStore.reset(_store);
117 return;
120 // Normal execution, use either -rootcertificates or system certs:
121 certStore.reset(X509_STORE_new());
123 // Note: use "-system-" default here so that users can pass -rootcertificates=""
124 // and get 'I don't like X.509 certificates, don't trust anybody' behavior:
125 QString certFile = QString::fromStdString(gArgs.GetArg("-rootcertificates", "-system-"));
127 // Empty store
128 if (certFile.isEmpty()) {
129 qDebug() << QString("PaymentServer::%1: Payment request authentication via X.509 certificates disabled.").arg(__func__);
130 return;
133 QList<QSslCertificate> certList;
135 if (certFile != "-system-") {
136 qDebug() << QString("PaymentServer::%1: Using \"%2\" as trusted root certificate.").arg(__func__).arg(certFile);
138 certList = QSslCertificate::fromPath(certFile);
139 // Use those certificates when fetching payment requests, too:
140 QSslSocket::setDefaultCaCertificates(certList);
141 } else
142 certList = QSslSocket::systemCaCertificates();
144 int nRootCerts = 0;
145 const QDateTime currentTime = QDateTime::currentDateTime();
147 for (const QSslCertificate& cert : certList) {
148 // Don't log NULL certificates
149 if (cert.isNull())
150 continue;
152 // Not yet active/valid, or expired certificate
153 if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) {
154 ReportInvalidCertificate(cert);
155 continue;
158 #if QT_VERSION >= 0x050000
159 // Blacklisted certificate
160 if (cert.isBlacklisted()) {
161 ReportInvalidCertificate(cert);
162 continue;
164 #endif
165 QByteArray certData = cert.toDer();
166 const unsigned char *data = (const unsigned char *)certData.data();
168 std::unique_ptr<X509, X509Deleter> x509(d2i_X509(0, &data, certData.size()));
169 if (x509 && X509_STORE_add_cert(certStore.get(), x509.get()))
171 // Note: X509_STORE increases the reference count to the X509 object,
172 // we still have to release our reference to it.
173 ++nRootCerts;
175 else
177 ReportInvalidCertificate(cert);
178 continue;
181 qWarning() << "PaymentServer::LoadRootCAs: Loaded " << nRootCerts << " root certificates";
183 // Project for another day:
184 // Fetch certificate revocation lists, and add them to certStore.
185 // Issues to consider:
186 // performance (start a thread to fetch in background?)
187 // privacy (fetch through tor/proxy so IP address isn't revealed)
188 // would it be easier to just use a compiled-in blacklist?
189 // or use Qt's blacklist?
190 // "certificate stapling" with server-side caching is more efficient
194 // Sending to the server is done synchronously, at startup.
195 // If the server isn't already running, startup continues,
196 // and the items in savedPaymentRequest will be handled
197 // when uiReady() is called.
199 // Warning: ipcSendCommandLine() is called early in init,
200 // so don't use "Q_EMIT message()", but "QMessageBox::"!
202 void PaymentServer::ipcParseCommandLine(int argc, char* argv[])
204 for (int i = 1; i < argc; i++)
206 QString arg(argv[i]);
207 if (arg.startsWith("-"))
208 continue;
210 // If the bitcoin: URI contains a payment request, we are not able to detect the
211 // network as that would require fetching and parsing the payment request.
212 // That means clicking such an URI which contains a testnet payment request
213 // will start a mainnet instance and throw a "wrong network" error.
214 if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
216 savedPaymentRequests.append(arg);
218 SendCoinsRecipient r;
219 if (GUIUtil::parseBitcoinURI(arg, &r) && !r.address.isEmpty())
221 auto tempChainParams = CreateChainParams(CBaseChainParams::MAIN);
223 if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
224 SelectParams(CBaseChainParams::MAIN);
225 } else {
226 tempChainParams = CreateChainParams(CBaseChainParams::TESTNET);
227 if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
228 SelectParams(CBaseChainParams::TESTNET);
233 else if (QFile::exists(arg)) // Filename
235 savedPaymentRequests.append(arg);
237 PaymentRequestPlus request;
238 if (readPaymentRequestFromFile(arg, request))
240 if (request.getDetails().network() == "main")
242 SelectParams(CBaseChainParams::MAIN);
244 else if (request.getDetails().network() == "test")
246 SelectParams(CBaseChainParams::TESTNET);
250 else
252 // Printing to debug.log is about the best we can do here, the
253 // GUI hasn't started yet so we can't pop up a message box.
254 qWarning() << "PaymentServer::ipcSendCommandLine: Payment request file does not exist: " << arg;
260 // Sending to the server is done synchronously, at startup.
261 // If the server isn't already running, startup continues,
262 // and the items in savedPaymentRequest will be handled
263 // when uiReady() is called.
265 bool PaymentServer::ipcSendCommandLine()
267 bool fResult = false;
268 for (const QString& r : savedPaymentRequests)
270 QLocalSocket* socket = new QLocalSocket();
271 socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
272 if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
274 delete socket;
275 socket = nullptr;
276 return false;
279 QByteArray block;
280 QDataStream out(&block, QIODevice::WriteOnly);
281 out.setVersion(QDataStream::Qt_4_0);
282 out << r;
283 out.device()->seek(0);
285 socket->write(block);
286 socket->flush();
287 socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
288 socket->disconnectFromServer();
290 delete socket;
291 socket = nullptr;
292 fResult = true;
295 return fResult;
298 PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
299 QObject(parent),
300 saveURIs(true),
301 uriServer(0),
302 netManager(0),
303 optionsModel(0)
305 // Verify that the version of the library that we linked against is
306 // compatible with the version of the headers we compiled against.
307 GOOGLE_PROTOBUF_VERIFY_VERSION;
309 // Install global event filter to catch QFileOpenEvents
310 // on Mac: sent when you click bitcoin: links
311 // other OSes: helpful when dealing with payment request files
312 if (parent)
313 parent->installEventFilter(this);
315 QString name = ipcServerName();
317 // Clean up old socket leftover from a crash:
318 QLocalServer::removeServer(name);
320 if (startLocalServer)
322 uriServer = new QLocalServer(this);
324 if (!uriServer->listen(name)) {
325 // constructor is called early in init, so don't use "Q_EMIT message()" here
326 QMessageBox::critical(0, tr("Payment request error"),
327 tr("Cannot start bitcoin: click-to-pay handler"));
329 else {
330 connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
331 connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString)));
336 PaymentServer::~PaymentServer()
338 google::protobuf::ShutdownProtobufLibrary();
342 // OSX-specific way of handling bitcoin: URIs and PaymentRequest mime types.
343 // Also used by paymentservertests.cpp and when opening a payment request file
344 // via "Open URI..." menu entry.
346 bool PaymentServer::eventFilter(QObject *object, QEvent *event)
348 if (event->type() == QEvent::FileOpen) {
349 QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
350 if (!fileEvent->file().isEmpty())
351 handleURIOrFile(fileEvent->file());
352 else if (!fileEvent->url().isEmpty())
353 handleURIOrFile(fileEvent->url().toString());
355 return true;
358 return QObject::eventFilter(object, event);
361 void PaymentServer::initNetManager()
363 if (!optionsModel)
364 return;
365 delete netManager;
367 // netManager is used to fetch paymentrequests given in bitcoin: URIs
368 netManager = new QNetworkAccessManager(this);
370 QNetworkProxy proxy;
372 // Query active SOCKS5 proxy
373 if (optionsModel->getProxySettings(proxy)) {
374 netManager->setProxy(proxy);
376 qDebug() << "PaymentServer::initNetManager: Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port();
378 else
379 qDebug() << "PaymentServer::initNetManager: No active proxy server found.";
381 connect(netManager, SIGNAL(finished(QNetworkReply*)),
382 this, SLOT(netRequestFinished(QNetworkReply*)));
383 connect(netManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)),
384 this, SLOT(reportSslErrors(QNetworkReply*, const QList<QSslError> &)));
387 void PaymentServer::uiReady()
389 initNetManager();
391 saveURIs = false;
392 for (const QString& s : savedPaymentRequests)
394 handleURIOrFile(s);
396 savedPaymentRequests.clear();
399 void PaymentServer::handleURIOrFile(const QString& s)
401 if (saveURIs)
403 savedPaymentRequests.append(s);
404 return;
407 if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
409 #if QT_VERSION < 0x050000
410 QUrl uri(s);
411 #else
412 QUrlQuery uri((QUrl(s)));
413 #endif
414 if (uri.hasQueryItem("r")) // payment request URI
416 QByteArray temp;
417 temp.append(uri.queryItemValue("r"));
418 QString decoded = QUrl::fromPercentEncoding(temp);
419 QUrl fetchUrl(decoded, QUrl::StrictMode);
421 if (fetchUrl.isValid())
423 qDebug() << "PaymentServer::handleURIOrFile: fetchRequest(" << fetchUrl << ")";
424 fetchRequest(fetchUrl);
426 else
428 qWarning() << "PaymentServer::handleURIOrFile: Invalid URL: " << fetchUrl;
429 Q_EMIT message(tr("URI handling"),
430 tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()),
431 CClientUIInterface::ICON_WARNING);
434 return;
436 else // normal URI
438 SendCoinsRecipient recipient;
439 if (GUIUtil::parseBitcoinURI(s, &recipient))
441 if (!IsValidDestinationString(recipient.address.toStdString())) {
442 Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
443 CClientUIInterface::MSG_ERROR);
445 else
446 Q_EMIT receivedPaymentRequest(recipient);
448 else
449 Q_EMIT message(tr("URI handling"),
450 tr("URI cannot be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
451 CClientUIInterface::ICON_WARNING);
453 return;
457 if (QFile::exists(s)) // payment request file
459 PaymentRequestPlus request;
460 SendCoinsRecipient recipient;
461 if (!readPaymentRequestFromFile(s, request))
463 Q_EMIT message(tr("Payment request file handling"),
464 tr("Payment request file cannot be read! This can be caused by an invalid payment request file."),
465 CClientUIInterface::ICON_WARNING);
467 else if (processPaymentRequest(request, recipient))
468 Q_EMIT receivedPaymentRequest(recipient);
470 return;
474 void PaymentServer::handleURIConnection()
476 QLocalSocket *clientConnection = uriServer->nextPendingConnection();
478 while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
479 clientConnection->waitForReadyRead();
481 connect(clientConnection, SIGNAL(disconnected()),
482 clientConnection, SLOT(deleteLater()));
484 QDataStream in(clientConnection);
485 in.setVersion(QDataStream::Qt_4_0);
486 if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
487 return;
489 QString msg;
490 in >> msg;
492 handleURIOrFile(msg);
496 // Warning: readPaymentRequestFromFile() is used in ipcSendCommandLine()
497 // so don't use "Q_EMIT message()", but "QMessageBox::"!
499 bool PaymentServer::readPaymentRequestFromFile(const QString& filename, PaymentRequestPlus& request)
501 QFile f(filename);
502 if (!f.open(QIODevice::ReadOnly)) {
503 qWarning() << QString("PaymentServer::%1: Failed to open %2").arg(__func__).arg(filename);
504 return false;
507 // BIP70 DoS protection
508 if (!verifySize(f.size())) {
509 return false;
512 QByteArray data = f.readAll();
514 return request.parse(data);
517 bool PaymentServer::processPaymentRequest(const PaymentRequestPlus& request, SendCoinsRecipient& recipient)
519 if (!optionsModel)
520 return false;
522 if (request.IsInitialized()) {
523 // Payment request network matches client network?
524 if (!verifyNetwork(request.getDetails())) {
525 Q_EMIT message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."),
526 CClientUIInterface::MSG_ERROR);
528 return false;
531 // Make sure any payment requests involved are still valid.
532 // This is re-checked just before sending coins in WalletModel::sendCoins().
533 if (verifyExpired(request.getDetails())) {
534 Q_EMIT message(tr("Payment request rejected"), tr("Payment request expired."),
535 CClientUIInterface::MSG_ERROR);
537 return false;
539 } else {
540 Q_EMIT message(tr("Payment request error"), tr("Payment request is not initialized."),
541 CClientUIInterface::MSG_ERROR);
543 return false;
546 recipient.paymentRequest = request;
547 recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
549 request.getMerchant(certStore.get(), recipient.authenticatedMerchant);
551 QList<std::pair<CScript, CAmount> > sendingTos = request.getPayTo();
552 QStringList addresses;
554 for (const std::pair<CScript, CAmount>& sendingTo : sendingTos) {
555 // Extract and check destination addresses
556 CTxDestination dest;
557 if (ExtractDestination(sendingTo.first, dest)) {
558 // Append destination address
559 addresses.append(QString::fromStdString(EncodeDestination(dest)));
561 else if (!recipient.authenticatedMerchant.isEmpty()) {
562 // Unauthenticated payment requests to custom bitcoin addresses are not supported
563 // (there is no good way to tell the user where they are paying in a way they'd
564 // have a chance of understanding).
565 Q_EMIT message(tr("Payment request rejected"),
566 tr("Unverified payment requests to custom payment scripts are unsupported."),
567 CClientUIInterface::MSG_ERROR);
568 return false;
571 // Bitcoin amounts are stored as (optional) uint64 in the protobuf messages (see paymentrequest.proto),
572 // but CAmount is defined as int64_t. Because of that we need to verify that amounts are in a valid range
573 // and no overflow has happened.
574 if (!verifyAmount(sendingTo.second)) {
575 Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
576 return false;
579 // Extract and check amounts
580 CTxOut txOut(sendingTo.second, sendingTo.first);
581 if (IsDust(txOut, ::dustRelayFee)) {
582 Q_EMIT message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
583 .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
584 CClientUIInterface::MSG_ERROR);
586 return false;
589 recipient.amount += sendingTo.second;
590 // Also verify that the final amount is still in a valid range after adding additional amounts.
591 if (!verifyAmount(recipient.amount)) {
592 Q_EMIT message(tr("Payment request rejected"), tr("Invalid payment request."), CClientUIInterface::MSG_ERROR);
593 return false;
596 // Store addresses and format them to fit nicely into the GUI
597 recipient.address = addresses.join("<br />");
599 if (!recipient.authenticatedMerchant.isEmpty()) {
600 qDebug() << "PaymentServer::processPaymentRequest: Secure payment request from " << recipient.authenticatedMerchant;
602 else {
603 qDebug() << "PaymentServer::processPaymentRequest: Insecure payment request to " << addresses.join(", ");
606 return true;
609 void PaymentServer::fetchRequest(const QUrl& url)
611 QNetworkRequest netRequest;
612 netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTREQUEST);
613 netRequest.setUrl(url);
614 netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
615 netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTREQUEST);
616 netManager->get(netRequest);
619 void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& recipient, QByteArray transaction)
621 const payments::PaymentDetails& details = recipient.paymentRequest.getDetails();
622 if (!details.has_payment_url())
623 return;
625 QNetworkRequest netRequest;
626 netRequest.setAttribute(QNetworkRequest::User, BIP70_MESSAGE_PAYMENTACK);
627 netRequest.setUrl(QString::fromStdString(details.payment_url()));
628 netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BIP71_MIMETYPE_PAYMENT);
629 netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
630 netRequest.setRawHeader("Accept", BIP71_MIMETYPE_PAYMENTACK);
632 payments::Payment payment;
633 payment.set_merchant_data(details.merchant_data());
634 payment.add_transactions(transaction.data(), transaction.size());
636 // Create a new refund address, or re-use:
637 QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
638 std::string strAccount = account.toStdString();
639 std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
640 if (!refundAddresses.empty()) {
641 CScript s = GetScriptForDestination(*refundAddresses.begin());
642 payments::Output* refund_to = payment.add_refund_to();
643 refund_to->set_script(&s[0], s.size());
645 else {
646 CPubKey newKey;
647 if (wallet->GetKeyFromPool(newKey)) {
648 CKeyID keyID = newKey.GetID();
649 wallet->SetAddressBook(keyID, strAccount, "refund");
651 CScript s = GetScriptForDestination(keyID);
652 payments::Output* refund_to = payment.add_refund_to();
653 refund_to->set_script(&s[0], s.size());
655 else {
656 // This should never happen, because sending coins should have
657 // just unlocked the wallet and refilled the keypool.
658 qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
662 int length = payment.ByteSize();
663 netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
664 QByteArray serData(length, '\0');
665 if (payment.SerializeToArray(serData.data(), length)) {
666 netManager->post(netRequest, serData);
668 else {
669 // This should never happen, either.
670 qWarning() << "PaymentServer::fetchPaymentACK: Error serializing payment message";
674 void PaymentServer::netRequestFinished(QNetworkReply* reply)
676 reply->deleteLater();
678 // BIP70 DoS protection
679 if (!verifySize(reply->size())) {
680 Q_EMIT message(tr("Payment request rejected"),
681 tr("Payment request %1 is too large (%2 bytes, allowed %3 bytes).")
682 .arg(reply->request().url().toString())
683 .arg(reply->size())
684 .arg(BIP70_MAX_PAYMENTREQUEST_SIZE),
685 CClientUIInterface::MSG_ERROR);
686 return;
689 if (reply->error() != QNetworkReply::NoError) {
690 QString msg = tr("Error communicating with %1: %2")
691 .arg(reply->request().url().toString())
692 .arg(reply->errorString());
694 qWarning() << "PaymentServer::netRequestFinished: " << msg;
695 Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
696 return;
699 QByteArray data = reply->readAll();
701 QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
702 if (requestType == BIP70_MESSAGE_PAYMENTREQUEST)
704 PaymentRequestPlus request;
705 SendCoinsRecipient recipient;
706 if (!request.parse(data))
708 qWarning() << "PaymentServer::netRequestFinished: Error parsing payment request";
709 Q_EMIT message(tr("Payment request error"),
710 tr("Payment request cannot be parsed!"),
711 CClientUIInterface::MSG_ERROR);
713 else if (processPaymentRequest(request, recipient))
714 Q_EMIT receivedPaymentRequest(recipient);
716 return;
718 else if (requestType == BIP70_MESSAGE_PAYMENTACK)
720 payments::PaymentACK paymentACK;
721 if (!paymentACK.ParseFromArray(data.data(), data.size()))
723 QString msg = tr("Bad response from server %1")
724 .arg(reply->request().url().toString());
726 qWarning() << "PaymentServer::netRequestFinished: " << msg;
727 Q_EMIT message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
729 else
731 Q_EMIT receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
736 void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs)
738 Q_UNUSED(reply);
740 QString errString;
741 for (const QSslError& err : errs) {
742 qWarning() << "PaymentServer::reportSslErrors: " << err;
743 errString += err.errorString() + "\n";
745 Q_EMIT message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR);
748 void PaymentServer::setOptionsModel(OptionsModel *_optionsModel)
750 this->optionsModel = _optionsModel;
753 void PaymentServer::handlePaymentACK(const QString& paymentACKMsg)
755 // currently we don't further process or store the paymentACK message
756 Q_EMIT message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL);
759 bool PaymentServer::verifyNetwork(const payments::PaymentDetails& requestDetails)
761 bool fVerified = requestDetails.network() == Params().NetworkIDString();
762 if (!fVerified) {
763 qWarning() << QString("PaymentServer::%1: Payment request network \"%2\" doesn't match client network \"%3\".")
764 .arg(__func__)
765 .arg(QString::fromStdString(requestDetails.network()))
766 .arg(QString::fromStdString(Params().NetworkIDString()));
768 return fVerified;
771 bool PaymentServer::verifyExpired(const payments::PaymentDetails& requestDetails)
773 bool fVerified = (requestDetails.has_expires() && (int64_t)requestDetails.expires() < GetTime());
774 if (fVerified) {
775 const QString requestExpires = QString::fromStdString(DateTimeStrFormat("%Y-%m-%d %H:%M:%S", (int64_t)requestDetails.expires()));
776 qWarning() << QString("PaymentServer::%1: Payment request expired \"%2\".")
777 .arg(__func__)
778 .arg(requestExpires);
780 return fVerified;
783 bool PaymentServer::verifySize(qint64 requestSize)
785 bool fVerified = (requestSize <= BIP70_MAX_PAYMENTREQUEST_SIZE);
786 if (!fVerified) {
787 qWarning() << QString("PaymentServer::%1: Payment request too large (%2 bytes, allowed %3 bytes).")
788 .arg(__func__)
789 .arg(requestSize)
790 .arg(BIP70_MAX_PAYMENTREQUEST_SIZE);
792 return fVerified;
795 bool PaymentServer::verifyAmount(const CAmount& requestAmount)
797 bool fVerified = MoneyRange(requestAmount);
798 if (!fVerified) {
799 qWarning() << QString("PaymentServer::%1: Payment request amount out of allowed range (%2, allowed 0 - %3).")
800 .arg(__func__)
801 .arg(requestAmount)
802 .arg(MAX_MONEY);
804 return fVerified;
807 X509_STORE* PaymentServer::getCertStore()
809 return certStore.get();