text: // Copyright (c) 2011-2019 The CounosH Core developers
  // Distributed under the MIT software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #if defined(HAVE_CONFIG_H)
  #include <config/counosh-config.h>
  #endif
  
  #include <qt/paymentserver.h>
  
  #include <qt/counoshunits.h>
  #include <qt/guiutil.h>
  #include <qt/optionsmodel.h>
  
  #include <chainparams.h>
  #include <interfaces/node.h>
  #include <policy/policy.h>
  #include <key_io.h>
  #include <ui_interface.h>
  #include <util/system.h>
  #include <wallet/wallet.h>
  
  #include <cstdlib>
  #include <memory>
  
  #include <QApplication>
  #include <QByteArray>
  #include <QDataStream>
  #include <QDateTime>
  #include <QDebug>
  #include <QFile>
  #include <QFileOpenEvent>
  #include <QHash>
  #include <QList>
  #include <QLocalServer>
  #include <QLocalSocket>
  #include <QStringList>
  #include <QUrlQuery>
  
  const int COUNOSH_IPC_CONNECT_TIMEOUT = 1000; // milliseconds
  const QString COUNOSH_IPC_PREFIX("counosh:");
  
  //
  // Create a name that is unique for:
  //  testnet / non-testnet
  //  data directory
  //
  static QString ipcServerName()
  {
      QString name("CounosHQt");
  
      // Append a simple hash of the datadir
      // Note that GetDataDir(true) returns a different path
      // for -testnet versus main net
      QString ddir(GUIUtil::boostPathToQString(GetDataDir(true)));
      name.append(QString::number(qHash(ddir)));
  
      return name;
  }
  
  //
  // We store payment URIs and requests received before
  // the main GUI window is up and ready to ask the user
  // to send payment.
  
  static QSet<QString> savedPaymentRequests;
  
  //
  // 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.
  //
  // Warning: ipcSendCommandLine() is called early in init,
  // so don't use "Q_EMIT message()", but "QMessageBox::"!
  //
  void PaymentServer::ipcParseCommandLine(interfaces::Node& node, int argc, char* argv[])
  {
      for (int i = 1; i < argc; i++)
      {
          QString arg(argv[i]);
          if (arg.startsWith("-"))
              continue;
  
          // If the counosh: URI contains a payment request, we are not able to detect the
          // network as that would require fetching and parsing the payment request.
          // That means clicking such an URI which contains a testnet payment request
          // will start a mainnet instance and throw a "wrong network" error.
          if (arg.startsWith(COUNOSH_IPC_PREFIX, Qt::CaseInsensitive)) // counosh: URI
          {
              if (savedPaymentRequests.contains(arg)) continue;
              savedPaymentRequests.insert(arg);
  
              SendCoinsRecipient r;
              if (GUIUtil::parseCounosHURI(arg, &r) && !r.address.isEmpty())
              {
                  auto tempChainParams = CreateChainParams(CBaseChainParams::MAIN);
  
                  if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
                      node.selectParams(CBaseChainParams::MAIN);
                  } else {
                      tempChainParams = CreateChainParams(CBaseChainParams::TESTNET);
                      if (IsValidDestinationString(r.address.toStdString(), *tempChainParams)) {
                          node.selectParams(CBaseChainParams::TESTNET);
                      }
                  }
              }
          }
      }
  }
  
  //
  // 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;
      for (const QString& r : savedPaymentRequests)
      {
          QLocalSocket* socket = new QLocalSocket();
          socket->connectToServer(ipcServerName(), QIODevice::WriteOnly);
          if (!socket->waitForConnected(COUNOSH_IPC_CONNECT_TIMEOUT))
          {
              delete socket;
              socket = nullptr;
              return false;
          }
  
          QByteArray block;
          QDataStream out(&block, QIODevice::WriteOnly);
          out.setVersion(QDataStream::Qt_4_0);
          out << r;
          out.device()->seek(0);
  
          socket->write(block);
          socket->flush();
          socket->waitForBytesWritten(COUNOSH_IPC_CONNECT_TIMEOUT);
          socket->disconnectFromServer();
  
          delete socket;
          socket = nullptr;
          fResult = true;
      }
  
      return fResult;
  }
  
  PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) :
      QObject(parent),
      saveURIs(true),
      uriServer(nullptr),
      optionsModel(nullptr)
  {
      // Install global event filter to catch QFileOpenEvents
      // on Mac: sent when you click counosh: links
      // other OSes: helpful when dealing with payment request files
      if (parent)
          parent->installEventFilter(this);
  
      QString name = ipcServerName();
  
      // Clean up old socket leftover from a crash:
      QLocalServer::removeServer(name);
  
      if (startLocalServer)
      {
          uriServer = new QLocalServer(this);
  
          if (!uriServer->listen(name)) {
              // constructor is called early in init, so don't use "Q_EMIT message()" here
              QMessageBox::critical(nullptr, tr("Payment request error"),
                  tr("Cannot start counosh: click-to-pay handler"));
          }
          else {
              connect(uriServer, &QLocalServer::newConnection, this, &PaymentServer::handleURIConnection);
          }
      }
  }
  
  PaymentServer::~PaymentServer()
  {
  }
  
  //
  // OSX-specific way of handling counosh: URIs
  //
  bool PaymentServer::eventFilter(QObject *object, QEvent *event)
  {
      if (event->type() == QEvent::FileOpen) {
          QFileOpenEvent *fileEvent = static_cast<QFileOpenEvent*>(event);
          if (!fileEvent->file().isEmpty())
              handleURIOrFile(fileEvent->file());
          else if (!fileEvent->url().isEmpty())
              handleURIOrFile(fileEvent->url().toString());
  
          return true;
      }
  
      return QObject::eventFilter(object, event);
  }
  
  void PaymentServer::uiReady()
  {
      saveURIs = false;
      for (const QString& s : savedPaymentRequests)
      {
          handleURIOrFile(s);
      }
      savedPaymentRequests.clear();
  }
  
  void PaymentServer::handleURIOrFile(const QString& s)
  {
      if (saveURIs)
      {
          savedPaymentRequests.insert(s);
          return;
      }
  
      if (s.startsWith("counosh://", Qt::CaseInsensitive))
      {
          Q_EMIT message(tr("URI handling"), tr("'counosh://' is not a valid URI. Use 'counosh:' instead."),
              CClientUIInterface::MSG_ERROR);
      }
      else if (s.startsWith(COUNOSH_IPC_PREFIX, Qt::CaseInsensitive)) // counosh: URI
      {
          QUrlQuery uri((QUrl(s)));
          // normal URI
          {
              SendCoinsRecipient recipient;
              if (GUIUtil::parseCounosHURI(s, &recipient))
              {
                  if (!IsValidDestinationString(recipient.address.toStdString())) {
                      if (uri.hasQueryItem("r")) {  // payment request
                          Q_EMIT message(tr("URI handling"),
                              tr("Cannot process payment request because BIP70 is not supported.")+
                              tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
                              tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
                              CClientUIInterface::ICON_WARNING);
                      }
                      Q_EMIT message(tr("URI handling"), tr("Invalid payment address %1").arg(recipient.address),
                          CClientUIInterface::MSG_ERROR);
                  }
                  else
                      Q_EMIT receivedPaymentRequest(recipient);
              }
              else
                  Q_EMIT message(tr("URI handling"),
                      tr("URI cannot be parsed! This can be caused by an invalid CounosH address or malformed URI parameters."),
                      CClientUIInterface::ICON_WARNING);
  
              return;
          }
      }
  
      if (QFile::exists(s)) // payment request file
      {
          Q_EMIT message(tr("Payment request file handling"),
              tr("Cannot process payment request because BIP70 is not supported.")+
              tr("Due to widespread security flaws in BIP70 it's strongly recommended that any merchant instructions to switch wallets be ignored.")+
              tr("If you are receiving this error you should request the merchant provide a BIP21 compatible URI."),
              CClientUIInterface::ICON_WARNING);
      }
  }
  
  void PaymentServer::handleURIConnection()
  {
      QLocalSocket *clientConnection = uriServer->nextPendingConnection();
  
      while (clientConnection->bytesAvailable() < (int)sizeof(quint32))
          clientConnection->waitForReadyRead();
  
      connect(clientConnection, &QLocalSocket::disconnected, clientConnection, &QLocalSocket::deleteLater);
  
      QDataStream in(clientConnection);
      in.setVersion(QDataStream::Qt_4_0);
      if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) {
          return;
      }
      QString msg;
      in >> msg;
  
      handleURIOrFile(msg);
  }
  
  void PaymentServer::setOptionsModel(OptionsModel *_optionsModel)
  {
      this->optionsModel = _optionsModel;
  }
  