2013-11-04 16:20:43 +01:00
|
|
|
// Copyright (c) 2011-2013 The Bitcoin developers
|
2013-02-12 00:52:30 +01:00
|
|
|
// Distributed under the MIT/X11 software license, see the accompanying
|
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
2013-04-13 07:13:08 +02:00
|
|
|
#include "paymentserver.h"
|
|
|
|
|
|
|
|
#include "bitcoinunits.h"
|
|
|
|
#include "guiconstants.h"
|
|
|
|
#include "guiutil.h"
|
|
|
|
#include "optionsmodel.h"
|
|
|
|
|
|
|
|
#include "base58.h"
|
|
|
|
#include "ui_interface.h"
|
|
|
|
#include "wallet.h"
|
|
|
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
|
|
|
#include <openssl/x509.h>
|
|
|
|
#include <openssl/x509_vfy.h>
|
2013-07-23 08:52:24 +02:00
|
|
|
#include <QApplication>
|
2013-02-12 00:52:30 +01:00
|
|
|
#include <QByteArray>
|
|
|
|
#include <QDataStream>
|
2013-07-22 08:50:39 +02:00
|
|
|
#include <QDateTime>
|
2013-02-12 00:52:30 +01:00
|
|
|
#include <QDebug>
|
2013-07-22 08:50:39 +02:00
|
|
|
#include <QFile>
|
2013-02-12 00:52:30 +01:00
|
|
|
#include <QFileOpenEvent>
|
|
|
|
#include <QHash>
|
2013-07-22 08:50:39 +02:00
|
|
|
#include <QList>
|
2013-02-12 00:52:30 +01:00
|
|
|
#include <QLocalServer>
|
|
|
|
#include <QLocalSocket>
|
2013-07-22 08:50:39 +02:00
|
|
|
#include <QNetworkAccessManager>
|
|
|
|
#include <QNetworkProxy>
|
|
|
|
#include <QNetworkReply>
|
|
|
|
#include <QNetworkRequest>
|
|
|
|
#include <QSslCertificate>
|
|
|
|
#include <QSslError>
|
|
|
|
#include <QSslSocket>
|
2013-04-13 07:13:08 +02:00
|
|
|
#include <QStringList>
|
|
|
|
#include <QTextDocument>
|
|
|
|
|
2013-05-31 14:02:24 +02:00
|
|
|
#if QT_VERSION < 0x050000
|
2013-02-12 00:52:30 +01:00
|
|
|
#include <QUrl>
|
2013-07-22 08:50:39 +02:00
|
|
|
#else
|
|
|
|
#include <QUrlQuery>
|
2013-05-31 14:02:24 +02:00
|
|
|
#endif
|
2013-02-12 00:52:30 +01:00
|
|
|
|
2014-06-16 16:30:38 +02:00
|
|
|
using namespace std;
|
2013-04-13 07:13:08 +02:00
|
|
|
using namespace boost;
|
2013-07-22 08:50:39 +02:00
|
|
|
|
2013-02-12 00:52:30 +01:00
|
|
|
const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
|
|
|
|
const QString BITCOIN_IPC_PREFIX("bitcoin:");
|
2013-08-28 03:16:56 +02:00
|
|
|
const char* BITCOIN_REQUEST_MIMETYPE = "application/bitcoin-paymentrequest";
|
|
|
|
const char* BITCOIN_PAYMENTACK_MIMETYPE = "application/bitcoin-paymentack";
|
2013-10-11 14:40:08 +02:00
|
|
|
const char* BITCOIN_PAYMENTACK_CONTENTTYPE = "application/bitcoin-payment";
|
2013-02-12 00:52:30 +01:00
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
X509_STORE* PaymentServer::certStore = NULL;
|
|
|
|
void PaymentServer::freeCertStore()
|
|
|
|
{
|
|
|
|
if (PaymentServer::certStore != NULL)
|
|
|
|
{
|
|
|
|
X509_STORE_free(PaymentServer::certStore);
|
|
|
|
PaymentServer::certStore = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-12 00:52:30 +01:00
|
|
|
//
|
|
|
|
// Create a name that is unique for:
|
|
|
|
// testnet / non-testnet
|
|
|
|
// data directory
|
|
|
|
//
|
|
|
|
static QString ipcServerName()
|
|
|
|
{
|
|
|
|
QString name("BitcoinQt");
|
|
|
|
|
|
|
|
// Append a simple hash of the datadir
|
|
|
|
// Note that GetDataDir(true) returns a different path
|
|
|
|
// for -testnet versus main net
|
2013-08-31 01:11:12 +02:00
|
|
|
QString ddir(QString::fromStdString(GetDataDir(true).string()));
|
2013-02-12 00:52:30 +01:00
|
|
|
name.append(QString::number(qHash(ddir)));
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2013-08-31 01:11:12 +02:00
|
|
|
// We store payment URIs and requests received before
|
2013-02-12 00:52:30 +01:00
|
|
|
// the main GUI window is up and ready to ask the user
|
|
|
|
// to send payment.
|
2013-07-22 08:50:39 +02:00
|
|
|
|
|
|
|
static QList<QString> savedPaymentRequests;
|
|
|
|
|
|
|
|
static void ReportInvalidCertificate(const QSslCertificate& cert)
|
|
|
|
{
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "ReportInvalidCertificate : Payment server found an invalid certificate: " << cert.subjectInfo(QSslCertificate::CommonName);
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2013-08-24 15:07:17 +02:00
|
|
|
// Load OpenSSL's list of root certificate authorities
|
2013-02-12 00:52:30 +01:00
|
|
|
//
|
2013-07-22 08:50:39 +02:00
|
|
|
void PaymentServer::LoadRootCAs(X509_STORE* _store)
|
|
|
|
{
|
|
|
|
if (PaymentServer::certStore == NULL)
|
|
|
|
atexit(PaymentServer::freeCertStore);
|
|
|
|
else
|
|
|
|
freeCertStore();
|
|
|
|
|
|
|
|
// Unit tests mostly use this, to pass in fake root CAs:
|
|
|
|
if (_store)
|
|
|
|
{
|
|
|
|
PaymentServer::certStore = _store;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Normal execution, use either -rootcertificates or system certs:
|
|
|
|
PaymentServer::certStore = X509_STORE_new();
|
|
|
|
|
|
|
|
// Note: use "-system-" default here so that users can pass -rootcertificates=""
|
|
|
|
// and get 'I don't like X.509 certificates, don't trust anybody' behavior:
|
|
|
|
QString certFile = QString::fromStdString(GetArg("-rootcertificates", "-system-"));
|
|
|
|
|
|
|
|
if (certFile.isEmpty())
|
|
|
|
return; // Empty store
|
|
|
|
|
|
|
|
QList<QSslCertificate> certList;
|
|
|
|
|
|
|
|
if (certFile != "-system-")
|
|
|
|
{
|
|
|
|
certList = QSslCertificate::fromPath(certFile);
|
|
|
|
// Use those certificates when fetching payment requests, too:
|
|
|
|
QSslSocket::setDefaultCaCertificates(certList);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
certList = QSslSocket::systemCaCertificates ();
|
|
|
|
|
|
|
|
int nRootCerts = 0;
|
|
|
|
const QDateTime currentTime = QDateTime::currentDateTime();
|
|
|
|
foreach (const QSslCertificate& cert, certList)
|
|
|
|
{
|
|
|
|
if (currentTime < cert.effectiveDate() || currentTime > cert.expiryDate()) {
|
|
|
|
ReportInvalidCertificate(cert);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#if QT_VERSION >= 0x050000
|
|
|
|
if (cert.isBlacklisted()) {
|
|
|
|
ReportInvalidCertificate(cert);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
QByteArray certData = cert.toDer();
|
|
|
|
const unsigned char *data = (const unsigned char *)certData.data();
|
|
|
|
|
|
|
|
X509* x509 = d2i_X509(0, &data, certData.size());
|
2013-08-24 15:07:17 +02:00
|
|
|
if (x509 && X509_STORE_add_cert(PaymentServer::certStore, x509))
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
|
|
|
// Note: X509_STORE_free will free the X509* objects when
|
|
|
|
// the PaymentServer is destroyed
|
|
|
|
++nRootCerts;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ReportInvalidCertificate(cert);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::LoadRootCAs : Loaded " << nRootCerts << " root certificates";
|
2013-07-22 08:50:39 +02:00
|
|
|
|
|
|
|
// Project for another day:
|
|
|
|
// Fetch certificate revocation lists, and add them to certStore.
|
|
|
|
// Issues to consider:
|
|
|
|
// performance (start a thread to fetch in background?)
|
|
|
|
// privacy (fetch through tor/proxy so IP address isn't revealed)
|
|
|
|
// would it be easier to just use a compiled-in blacklist?
|
|
|
|
// or use Qt's blacklist?
|
|
|
|
// "certificate stapling" with server-side caching is more efficient
|
|
|
|
}
|
2013-02-12 00:52:30 +01:00
|
|
|
|
|
|
|
//
|
|
|
|
// Sending to the server is done synchronously, at startup.
|
|
|
|
// If the server isn't already running, startup continues,
|
|
|
|
// and the items in savedPaymentRequest will be handled
|
|
|
|
// when uiReady() is called.
|
|
|
|
//
|
2013-11-16 01:54:29 +01:00
|
|
|
// Warning: ipcSendCommandLine() is called early in init,
|
|
|
|
// so don't use "emit message()", but "QMessageBox::"!
|
|
|
|
//
|
2014-01-05 12:37:51 +01:00
|
|
|
bool PaymentServer::ipcParseCommandLine(int argc, char* argv[])
|
2013-02-12 00:52:30 +01:00
|
|
|
{
|
2013-07-22 08:50:39 +02:00
|
|
|
for (int i = 1; i < argc; i++)
|
2013-02-12 00:52:30 +01:00
|
|
|
{
|
2013-07-22 08:50:39 +02:00
|
|
|
QString arg(argv[i]);
|
|
|
|
if (arg.startsWith("-"))
|
2013-02-12 00:52:30 +01:00
|
|
|
continue;
|
2013-07-22 08:50:39 +02:00
|
|
|
|
2013-10-18 11:44:05 +02:00
|
|
|
if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
|
|
|
savedPaymentRequests.append(arg);
|
|
|
|
|
|
|
|
SendCoinsRecipient r;
|
|
|
|
if (GUIUtil::parseBitcoinURI(arg, &r))
|
|
|
|
{
|
|
|
|
CBitcoinAddress address(r.address.toStdString());
|
|
|
|
|
2014-06-19 15:10:04 +02:00
|
|
|
SelectParams(CBaseChainParams::MAIN);
|
2013-07-22 08:50:39 +02:00
|
|
|
if (!address.IsValid())
|
|
|
|
{
|
2014-06-19 15:10:04 +02:00
|
|
|
SelectParams(CBaseChainParams::TESTNET);
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (QFile::exists(arg)) // Filename
|
|
|
|
{
|
|
|
|
savedPaymentRequests.append(arg);
|
|
|
|
|
|
|
|
PaymentRequestPlus request;
|
|
|
|
if (readPaymentRequest(arg, request))
|
|
|
|
{
|
|
|
|
if (request.getDetails().network() == "main")
|
2014-06-19 15:10:04 +02:00
|
|
|
SelectParams(CBaseChainParams::MAIN);
|
2013-07-22 08:50:39 +02:00
|
|
|
else
|
2014-06-19 15:10:04 +02:00
|
|
|
SelectParams(CBaseChainParams::TESTNET);
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Printing to debug.log is about the best we can do here, the
|
|
|
|
// GUI hasn't started yet so we can't pop up a message box.
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::ipcSendCommandLine : Payment request file does not exist: " << arg;
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
2013-02-12 00:52:30 +01:00
|
|
|
}
|
2014-01-05 12:37:51 +01:00
|
|
|
return true;
|
|
|
|
}
|
2013-02-12 00:52:30 +01:00
|
|
|
|
2014-01-05 12:37:51 +01:00
|
|
|
//
|
|
|
|
// Sending to the server is done synchronously, at startup.
|
|
|
|
// If the server isn't already running, startup continues,
|
|
|
|
// and the items in savedPaymentRequest will be handled
|
|
|
|
// when uiReady() is called.
|
|
|
|
//
|
|
|
|
bool PaymentServer::ipcSendCommandLine()
|
|
|
|
{
|
|
|
|
bool fResult = false;
|
2013-07-22 08:50:39 +02:00
|
|
|
foreach (const QString& r, savedPaymentRequests)
|
2013-02-12 00:52:30 +01:00
|
|
|
{
|
|
|
|
QLocalSocket* socket = new QLocalSocket();
|
|
|
|
socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
|
|
|
|
if (!socket->waitForConnected(BITCOIN_IPC_CONNECT_TIMEOUT))
|
2013-11-14 19:21:16 +01:00
|
|
|
{
|
|
|
|
delete socket;
|
2013-02-12 00:52:30 +01:00
|
|
|
return false;
|
2013-11-14 19:21:16 +01:00
|
|
|
}
|
2013-02-12 00:52:30 +01:00
|
|
|
|
|
|
|
QByteArray block;
|
|
|
|
QDataStream out(&block, QIODevice::WriteOnly);
|
|
|
|
out.setVersion(QDataStream::Qt_4_0);
|
2013-07-22 08:50:39 +02:00
|
|
|
out << r;
|
2013-02-12 00:52:30 +01:00
|
|
|
out.device()->seek(0);
|
|
|
|
socket->write(block);
|
|
|
|
socket->flush();
|
|
|
|
|
|
|
|
socket->waitForBytesWritten(BITCOIN_IPC_CONNECT_TIMEOUT);
|
|
|
|
socket->disconnectFromServer();
|
|
|
|
delete socket;
|
|
|
|
fResult = true;
|
|
|
|
}
|
2013-10-18 11:44:05 +02:00
|
|
|
|
2013-02-12 00:52:30 +01:00
|
|
|
return fResult;
|
|
|
|
}
|
|
|
|
|
2013-10-24 16:16:39 +02:00
|
|
|
PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
|
|
|
|
QObject(parent),
|
|
|
|
saveURIs(true),
|
|
|
|
uriServer(0),
|
|
|
|
netManager(0),
|
|
|
|
optionsModel(0)
|
2013-02-12 00:52:30 +01:00
|
|
|
{
|
2013-07-22 08:50:39 +02:00
|
|
|
// Verify that the version of the library that we linked against is
|
|
|
|
// compatible with the version of the headers we compiled against.
|
|
|
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
|
|
|
|
2013-10-18 11:44:05 +02:00
|
|
|
// Install global event filter to catch QFileOpenEvents
|
|
|
|
// on Mac: sent when you click bitcoin: links
|
2013-10-22 21:27:24 +02:00
|
|
|
// other OSes: helpful when dealing with payment request files (in the future)
|
2013-07-22 08:50:39 +02:00
|
|
|
if (parent)
|
|
|
|
parent->installEventFilter(this);
|
2013-02-12 00:52:30 +01:00
|
|
|
|
|
|
|
QString name = ipcServerName();
|
|
|
|
|
|
|
|
// Clean up old socket leftover from a crash:
|
|
|
|
QLocalServer::removeServer(name);
|
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
if (startLocalServer)
|
|
|
|
{
|
|
|
|
uriServer = new QLocalServer(this);
|
2013-02-12 00:52:30 +01:00
|
|
|
|
2013-11-14 19:21:16 +01:00
|
|
|
if (!uriServer->listen(name)) {
|
|
|
|
// constructor is called early in init, so don't use "emit message()" here
|
|
|
|
QMessageBox::critical(0, tr("Payment request error"),
|
|
|
|
tr("Cannot start bitcoin: click-to-pay handler"));
|
|
|
|
}
|
2013-10-22 21:27:24 +02:00
|
|
|
else {
|
2013-07-22 08:50:39 +02:00
|
|
|
connect(uriServer, SIGNAL(newConnection()), this, SLOT(handleURIConnection()));
|
2013-10-22 21:27:24 +02:00
|
|
|
connect(this, SIGNAL(receivedPaymentACK(QString)), this, SLOT(handlePaymentACK(QString)));
|
|
|
|
}
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
2013-02-12 00:52:30 +01:00
|
|
|
}
|
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
PaymentServer::~PaymentServer()
|
|
|
|
{
|
|
|
|
google::protobuf::ShutdownProtobufLibrary();
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2013-08-31 01:11:12 +02:00
|
|
|
// OSX-specific way of handling bitcoin: URIs and
|
2013-07-22 08:50:39 +02:00
|
|
|
// PaymentRequest mime types
|
|
|
|
//
|
2013-11-14 19:21:16 +01:00
|
|
|
bool PaymentServer::eventFilter(QObject *object, QEvent *event)
|
2013-02-12 00:52:30 +01:00
|
|
|
{
|
2013-11-14 19:21:16 +01:00
|
|
|
// clicking on bitcoin: URIs creates FileOpen events on the Mac
|
2013-02-12 00:52:30 +01:00
|
|
|
if (event->type() == QEvent::FileOpen)
|
|
|
|
{
|
2013-11-14 19:21:16 +01:00
|
|
|
QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
|
2013-07-22 08:50:39 +02:00
|
|
|
if (!fileEvent->file().isEmpty())
|
|
|
|
handleURIOrFile(fileEvent->file());
|
|
|
|
else if (!fileEvent->url().isEmpty())
|
|
|
|
handleURIOrFile(fileEvent->url().toString());
|
|
|
|
|
|
|
|
return true;
|
2013-02-12 00:52:30 +01:00
|
|
|
}
|
2013-11-14 19:21:16 +01:00
|
|
|
|
|
|
|
return QObject::eventFilter(object, event);
|
2013-02-12 00:52:30 +01:00
|
|
|
}
|
|
|
|
|
2013-08-24 15:07:17 +02:00
|
|
|
void PaymentServer::initNetManager()
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
2013-08-24 15:07:17 +02:00
|
|
|
if (!optionsModel)
|
|
|
|
return;
|
2013-07-22 08:50:39 +02:00
|
|
|
if (netManager != NULL)
|
|
|
|
delete netManager;
|
|
|
|
|
2013-10-18 11:44:05 +02:00
|
|
|
// netManager is used to fetch paymentrequests given in bitcoin: URIs
|
2013-07-22 08:50:39 +02:00
|
|
|
netManager = new QNetworkAccessManager(this);
|
|
|
|
|
2013-12-20 18:47:49 +01:00
|
|
|
QNetworkProxy proxy;
|
|
|
|
|
|
|
|
// Query active proxy (fails if no SOCKS5 proxy)
|
|
|
|
if (optionsModel->getProxySettings(proxy)) {
|
|
|
|
if (proxy.type() == QNetworkProxy::Socks5Proxy) {
|
|
|
|
netManager->setProxy(proxy);
|
|
|
|
|
|
|
|
qDebug() << "PaymentServer::initNetManager : Using SOCKS5 proxy" << proxy.hostName() << ":" << proxy.port();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
qDebug() << "PaymentServer::initNetManager : No active proxy server found.";
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
2013-12-20 18:47:49 +01:00
|
|
|
else
|
|
|
|
emit message(tr("Net manager warning"),
|
|
|
|
tr("Your active proxy doesn't support SOCKS5, which is required for payment requests via proxy."),
|
|
|
|
CClientUIInterface::MSG_WARNING);
|
2013-07-22 08:50:39 +02:00
|
|
|
|
|
|
|
connect(netManager, SIGNAL(finished(QNetworkReply*)),
|
|
|
|
this, SLOT(netRequestFinished(QNetworkReply*)));
|
|
|
|
connect(netManager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> &)),
|
|
|
|
this, SLOT(reportSslErrors(QNetworkReply*, const QList<QSslError> &)));
|
|
|
|
}
|
|
|
|
|
2013-02-12 00:52:30 +01:00
|
|
|
void PaymentServer::uiReady()
|
|
|
|
{
|
2013-10-30 11:30:53 +01:00
|
|
|
initNetManager();
|
2013-07-22 08:50:39 +02:00
|
|
|
|
2013-02-12 00:52:30 +01:00
|
|
|
saveURIs = false;
|
|
|
|
foreach (const QString& s, savedPaymentRequests)
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
|
|
|
handleURIOrFile(s);
|
|
|
|
}
|
2013-02-12 00:52:30 +01:00
|
|
|
savedPaymentRequests.clear();
|
|
|
|
}
|
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
void PaymentServer::handleURIOrFile(const QString& s)
|
|
|
|
{
|
|
|
|
if (saveURIs)
|
|
|
|
{
|
|
|
|
savedPaymentRequests.append(s);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-11-14 19:21:16 +01:00
|
|
|
if (s.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
2013-04-13 07:13:08 +02:00
|
|
|
#if QT_VERSION < 0x050000
|
2013-08-31 01:11:12 +02:00
|
|
|
QUrl uri(s);
|
2013-04-13 07:13:08 +02:00
|
|
|
#else
|
|
|
|
QUrlQuery uri((QUrl(s)));
|
2013-07-22 08:50:39 +02:00
|
|
|
#endif
|
2013-12-09 10:48:14 +01:00
|
|
|
if (uri.hasQueryItem("r")) // payment request URI
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
2013-10-18 11:44:05 +02:00
|
|
|
QByteArray temp;
|
2013-12-04 04:18:09 +01:00
|
|
|
temp.append(uri.queryItemValue("r"));
|
2013-07-22 08:50:39 +02:00
|
|
|
QString decoded = QUrl::fromPercentEncoding(temp);
|
|
|
|
QUrl fetchUrl(decoded, QUrl::StrictMode);
|
|
|
|
|
|
|
|
if (fetchUrl.isValid())
|
2013-12-09 10:48:14 +01:00
|
|
|
{
|
|
|
|
qDebug() << "PaymentServer::handleURIOrFile : fetchRequest(" << fetchUrl << ")";
|
2013-07-22 08:50:39 +02:00
|
|
|
fetchRequest(fetchUrl);
|
2013-12-09 10:48:14 +01:00
|
|
|
}
|
2013-07-22 08:50:39 +02:00
|
|
|
else
|
2013-12-09 10:48:14 +01:00
|
|
|
{
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::handleURIOrFile : Invalid URL: " << fetchUrl;
|
2013-12-09 10:48:14 +01:00
|
|
|
emit message(tr("URI handling"),
|
|
|
|
tr("Payment request fetch URL is invalid: %1").arg(fetchUrl.toString()),
|
|
|
|
CClientUIInterface::ICON_WARNING);
|
|
|
|
}
|
2013-10-28 13:29:13 +01:00
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
return;
|
|
|
|
}
|
2013-12-09 10:48:14 +01:00
|
|
|
else // normal URI
|
|
|
|
{
|
|
|
|
SendCoinsRecipient recipient;
|
|
|
|
if (GUIUtil::parseBitcoinURI(s, &recipient))
|
2013-11-16 01:54:29 +01:00
|
|
|
{
|
|
|
|
CBitcoinAddress address(recipient.address.toStdString());
|
|
|
|
if (!address.IsValid()) {
|
|
|
|
emit message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
|
|
|
|
CClientUIInterface::MSG_ERROR);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
emit receivedPaymentRequest(recipient);
|
|
|
|
}
|
2013-12-09 10:48:14 +01:00
|
|
|
else
|
|
|
|
emit message(tr("URI handling"),
|
|
|
|
tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."),
|
|
|
|
CClientUIInterface::ICON_WARNING);
|
2013-07-22 08:50:39 +02:00
|
|
|
|
2013-12-09 10:48:14 +01:00
|
|
|
return;
|
|
|
|
}
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
|
2013-12-09 10:48:14 +01:00
|
|
|
if (QFile::exists(s)) // payment request file
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
|
|
|
PaymentRequestPlus request;
|
2013-10-28 13:29:13 +01:00
|
|
|
SendCoinsRecipient recipient;
|
2013-11-16 01:54:29 +01:00
|
|
|
if (!readPaymentRequest(s, request))
|
|
|
|
{
|
2013-12-09 10:48:14 +01:00
|
|
|
emit message(tr("Payment request file handling"),
|
2013-11-16 01:54:29 +01:00
|
|
|
tr("Payment request file can not be read! This can be caused by an invalid payment request file."),
|
2013-12-09 10:48:14 +01:00
|
|
|
CClientUIInterface::ICON_WARNING);
|
2013-11-16 01:54:29 +01:00
|
|
|
}
|
|
|
|
else if (processPaymentRequest(request, recipient))
|
|
|
|
emit receivedPaymentRequest(recipient);
|
2013-10-28 13:29:13 +01:00
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-12 00:52:30 +01:00
|
|
|
void PaymentServer::handleURIConnection()
|
|
|
|
{
|
|
|
|
QLocalSocket *clientConnection = uriServer->nextPendingConnection();
|
|
|
|
|
|
|
|
while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
|
|
|
|
clientConnection->waitForReadyRead();
|
|
|
|
|
|
|
|
connect(clientConnection, SIGNAL(disconnected()),
|
|
|
|
clientConnection, SLOT(deleteLater()));
|
|
|
|
|
|
|
|
QDataStream in(clientConnection);
|
|
|
|
in.setVersion(QDataStream::Qt_4_0);
|
|
|
|
if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
|
|
|
|
return;
|
|
|
|
}
|
2013-10-18 11:44:05 +02:00
|
|
|
QString msg;
|
|
|
|
in >> msg;
|
2013-02-12 00:52:30 +01:00
|
|
|
|
2013-10-18 11:44:05 +02:00
|
|
|
handleURIOrFile(msg);
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPlus& request)
|
|
|
|
{
|
|
|
|
QFile f(filename);
|
|
|
|
if (!f.open(QIODevice::ReadOnly))
|
|
|
|
{
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::readPaymentRequest : Failed to open " << filename;
|
2013-07-22 08:50:39 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f.size() > MAX_PAYMENT_REQUEST_SIZE)
|
|
|
|
{
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::readPaymentRequest : " << filename << " too large";
|
2013-07-22 08:50:39 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray data = f.readAll();
|
|
|
|
|
|
|
|
return request.parse(data);
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:29:13 +01:00
|
|
|
bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, SendCoinsRecipient& recipient)
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
2013-08-24 15:07:17 +02:00
|
|
|
if (!optionsModel)
|
|
|
|
return false;
|
|
|
|
|
2013-11-16 01:54:29 +01:00
|
|
|
if (request.IsInitialized()) {
|
|
|
|
const payments::PaymentDetails& details = request.getDetails();
|
|
|
|
|
|
|
|
// Payment request network matches client network?
|
2014-06-11 12:23:49 +02:00
|
|
|
if (details.network() != Params().NetworkIDString())
|
2013-11-16 01:54:29 +01:00
|
|
|
{
|
|
|
|
emit message(tr("Payment request rejected"), tr("Payment request network doesn't match client network."),
|
|
|
|
CClientUIInterface::MSG_ERROR);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Expired payment request?
|
|
|
|
if (details.has_expires() && (int64_t)details.expires() < GetTime())
|
|
|
|
{
|
|
|
|
emit message(tr("Payment request rejected"), tr("Payment request has expired."),
|
|
|
|
CClientUIInterface::MSG_ERROR);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
emit message(tr("Payment request error"), tr("Payment request is not initialized."),
|
|
|
|
CClientUIInterface::MSG_ERROR);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:29:13 +01:00
|
|
|
recipient.paymentRequest = request;
|
|
|
|
recipient.message = GUIUtil::HtmlEscape(request.getDetails().memo());
|
2013-10-24 16:02:39 +02:00
|
|
|
|
2013-10-28 13:29:13 +01:00
|
|
|
request.getMerchant(PaymentServer::certStore, recipient.authenticatedMerchant);
|
2013-10-24 16:02:39 +02:00
|
|
|
|
|
|
|
QList<std::pair<CScript, qint64> > sendingTos = request.getPayTo();
|
2013-10-30 11:26:44 +01:00
|
|
|
QStringList addresses;
|
2013-10-24 16:02:39 +02:00
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
foreach(const PAIRTYPE(CScript, qint64)& sendingTo, sendingTos) {
|
2013-10-24 16:02:39 +02:00
|
|
|
// Extract and check destination addresses
|
|
|
|
CTxDestination dest;
|
|
|
|
if (ExtractDestination(sendingTo.first, dest)) {
|
2013-10-30 11:26:44 +01:00
|
|
|
// Append destination address
|
|
|
|
addresses.append(QString::fromStdString(CBitcoinAddress(dest).ToString()));
|
2013-10-24 16:02:39 +02:00
|
|
|
}
|
2013-11-16 01:54:29 +01:00
|
|
|
else if (!recipient.authenticatedMerchant.isEmpty()) {
|
2013-10-24 16:02:39 +02:00
|
|
|
// Insecure payments to custom bitcoin addresses are not supported
|
|
|
|
// (there is no good way to tell the user where they are paying in a way
|
|
|
|
// they'd have a chance of understanding).
|
2013-11-16 01:54:29 +01:00
|
|
|
emit message(tr("Payment request rejected"),
|
2013-10-24 16:02:39 +02:00
|
|
|
tr("Unverified payment requests to custom payment scripts are unsupported."),
|
|
|
|
CClientUIInterface::MSG_ERROR);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract and check amounts
|
2013-07-22 08:50:39 +02:00
|
|
|
CTxOut txOut(sendingTo.second, sendingTo.first);
|
2014-04-10 20:14:18 +02:00
|
|
|
if (txOut.IsDust(CTransaction::minRelayTxFee)) {
|
2013-11-16 01:54:29 +01:00
|
|
|
emit message(tr("Payment request error"), tr("Requested payment amount of %1 is too small (considered dust).")
|
|
|
|
.arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)),
|
|
|
|
CClientUIInterface::MSG_ERROR);
|
2013-09-04 11:52:45 +02:00
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-10-28 13:29:13 +01:00
|
|
|
recipient.amount += sendingTo.second;
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
2013-10-24 16:02:39 +02:00
|
|
|
// Store addresses and format them to fit nicely into the GUI
|
|
|
|
recipient.address = addresses.join("<br />");
|
2013-07-22 08:50:39 +02:00
|
|
|
|
2013-10-24 16:02:39 +02:00
|
|
|
if (!recipient.authenticatedMerchant.isEmpty()) {
|
|
|
|
qDebug() << "PaymentServer::processPaymentRequest : Secure payment request from " << recipient.authenticatedMerchant;
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
else {
|
2013-10-24 16:02:39 +02:00
|
|
|
qDebug() << "PaymentServer::processPaymentRequest : Insecure payment request to " << addresses.join(", ");
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-08-31 01:11:12 +02:00
|
|
|
void PaymentServer::fetchRequest(const QUrl& url)
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
|
|
|
QNetworkRequest netRequest;
|
|
|
|
netRequest.setAttribute(QNetworkRequest::User, "PaymentRequest");
|
|
|
|
netRequest.setUrl(url);
|
|
|
|
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
|
2013-08-28 03:16:56 +02:00
|
|
|
netRequest.setRawHeader("Accept", BITCOIN_REQUEST_MIMETYPE);
|
2013-07-22 08:50:39 +02:00
|
|
|
netManager->get(netRequest);
|
|
|
|
}
|
|
|
|
|
2013-08-31 01:11:12 +02:00
|
|
|
void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipient, QByteArray transaction)
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
|
|
|
const payments::PaymentDetails& details = recipient.paymentRequest.getDetails();
|
|
|
|
if (!details.has_payment_url())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QNetworkRequest netRequest;
|
|
|
|
netRequest.setAttribute(QNetworkRequest::User, "PaymentACK");
|
|
|
|
netRequest.setUrl(QString::fromStdString(details.payment_url()));
|
2013-10-11 14:40:08 +02:00
|
|
|
netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BITCOIN_PAYMENTACK_CONTENTTYPE);
|
2013-07-22 08:50:39 +02:00
|
|
|
netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str());
|
2013-08-28 03:16:56 +02:00
|
|
|
netRequest.setRawHeader("Accept", BITCOIN_PAYMENTACK_MIMETYPE);
|
2013-07-22 08:50:39 +02:00
|
|
|
|
|
|
|
payments::Payment payment;
|
|
|
|
payment.set_merchant_data(details.merchant_data());
|
|
|
|
payment.add_transactions(transaction.data(), transaction.size());
|
|
|
|
|
|
|
|
// Create a new refund address, or re-use:
|
2013-10-22 21:27:24 +02:00
|
|
|
QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
|
2013-07-22 08:50:39 +02:00
|
|
|
std::string strAccount = account.toStdString();
|
|
|
|
set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
|
|
|
|
if (!refundAddresses.empty()) {
|
|
|
|
CScript s; s.SetDestination(*refundAddresses.begin());
|
|
|
|
payments::Output* refund_to = payment.add_refund_to();
|
|
|
|
refund_to->set_script(&s[0], s.size());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
CPubKey newKey;
|
2013-08-23 21:54:50 +02:00
|
|
|
if (wallet->GetKeyFromPool(newKey)) {
|
2013-12-15 13:54:07 +01:00
|
|
|
LOCK(wallet->cs_wallet); // SetAddressBook
|
2013-07-22 08:50:39 +02:00
|
|
|
CKeyID keyID = newKey.GetID();
|
|
|
|
wallet->SetAddressBook(keyID, strAccount, "refund");
|
|
|
|
|
|
|
|
CScript s; s.SetDestination(keyID);
|
|
|
|
payments::Output* refund_to = payment.add_refund_to();
|
|
|
|
refund_to->set_script(&s[0], s.size());
|
|
|
|
}
|
|
|
|
else {
|
2013-11-16 01:54:29 +01:00
|
|
|
// This should never happen, because sending coins should have
|
|
|
|
// just unlocked the wallet and refilled the keypool.
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::fetchPaymentACK : Error getting refund key, refund_to not set";
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int length = payment.ByteSize();
|
|
|
|
netRequest.setHeader(QNetworkRequest::ContentLengthHeader, length);
|
|
|
|
QByteArray serData(length, '\0');
|
|
|
|
if (payment.SerializeToArray(serData.data(), length)) {
|
|
|
|
netManager->post(netRequest, serData);
|
|
|
|
}
|
|
|
|
else {
|
2013-11-16 01:54:29 +01:00
|
|
|
// This should never happen, either.
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::fetchPaymentACK : Error serializing payment message";
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-31 01:11:12 +02:00
|
|
|
void PaymentServer::netRequestFinished(QNetworkReply* reply)
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
|
|
|
reply->deleteLater();
|
|
|
|
if (reply->error() != QNetworkReply::NoError)
|
|
|
|
{
|
2013-10-22 21:27:24 +02:00
|
|
|
QString msg = tr("Error communicating with %1: %2")
|
2013-07-22 08:50:39 +02:00
|
|
|
.arg(reply->request().url().toString())
|
|
|
|
.arg(reply->errorString());
|
2013-10-22 21:27:24 +02:00
|
|
|
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::netRequestFinished : " << msg;
|
2013-12-09 10:48:14 +01:00
|
|
|
emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
|
2013-07-22 08:50:39 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray data = reply->readAll();
|
|
|
|
|
|
|
|
QString requestType = reply->request().attribute(QNetworkRequest::User).toString();
|
|
|
|
if (requestType == "PaymentRequest")
|
|
|
|
{
|
|
|
|
PaymentRequestPlus request;
|
2013-10-28 13:29:13 +01:00
|
|
|
SendCoinsRecipient recipient;
|
2013-11-16 01:54:29 +01:00
|
|
|
if (!request.parse(data))
|
2013-12-09 10:48:14 +01:00
|
|
|
{
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::netRequestFinished : Error parsing payment request";
|
2013-12-09 10:48:14 +01:00
|
|
|
emit message(tr("Payment request error"),
|
2013-11-16 01:54:29 +01:00
|
|
|
tr("Payment request can not be parsed!"),
|
2013-12-09 10:48:14 +01:00
|
|
|
CClientUIInterface::MSG_ERROR);
|
|
|
|
}
|
2013-11-16 01:54:29 +01:00
|
|
|
else if (processPaymentRequest(request, recipient))
|
|
|
|
emit receivedPaymentRequest(recipient);
|
2013-10-22 21:27:24 +02:00
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
else if (requestType == "PaymentACK")
|
|
|
|
{
|
|
|
|
payments::PaymentACK paymentACK;
|
|
|
|
if (!paymentACK.ParseFromArray(data.data(), data.size()))
|
|
|
|
{
|
2013-10-22 21:27:24 +02:00
|
|
|
QString msg = tr("Bad response from server %1")
|
2013-07-22 08:50:39 +02:00
|
|
|
.arg(reply->request().url().toString());
|
2013-10-22 21:27:24 +02:00
|
|
|
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::netRequestFinished : " << msg;
|
2013-12-09 10:48:14 +01:00
|
|
|
emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR);
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
2013-12-09 10:48:14 +01:00
|
|
|
else
|
|
|
|
{
|
2013-10-22 21:27:24 +02:00
|
|
|
emit receivedPaymentACK(GUIUtil::HtmlEscape(paymentACK.memo()));
|
2013-07-22 08:50:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-31 01:11:12 +02:00
|
|
|
void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> &errs)
|
2013-07-22 08:50:39 +02:00
|
|
|
{
|
2013-08-31 01:11:12 +02:00
|
|
|
Q_UNUSED(reply);
|
|
|
|
|
2013-07-22 08:50:39 +02:00
|
|
|
QString errString;
|
|
|
|
foreach (const QSslError& err, errs) {
|
2014-07-01 15:21:17 +02:00
|
|
|
qWarning() << "PaymentServer::reportSslErrors : " << err;
|
2013-07-22 08:50:39 +02:00
|
|
|
errString += err.errorString() + "\n";
|
|
|
|
}
|
2013-10-18 11:44:05 +02:00
|
|
|
emit message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR);
|
2013-02-12 00:52:30 +01:00
|
|
|
}
|
2013-08-24 15:07:17 +02:00
|
|
|
|
|
|
|
void PaymentServer::setOptionsModel(OptionsModel *optionsModel)
|
|
|
|
{
|
|
|
|
this->optionsModel = optionsModel;
|
|
|
|
}
|
2013-10-22 21:27:24 +02:00
|
|
|
|
|
|
|
void PaymentServer::handlePaymentACK(const QString& paymentACKMsg)
|
|
|
|
{
|
|
|
|
// currently we don't futher process or store the paymentACK message
|
|
|
|
emit message(tr("Payment acknowledged"), paymentACKMsg, CClientUIInterface::ICON_INFORMATION | CClientUIInterface::MODAL);
|
|
|
|
}
|