mirror of
https://github.com/qTox/qTox.git
synced 2024-03-22 14:00:36 +08:00
Merge branch 'toxuri'
This commit is contained in:
commit
e1b301eb83
10
qtox.pro
10
qtox.pro
|
@ -149,7 +149,10 @@ HEADERS += src/widget/form/addfriendform.h \
|
||||||
src/widget/form/setpassworddialog.h \
|
src/widget/form/setpassworddialog.h \
|
||||||
src/widget/form/tabcompleter.h \
|
src/widget/form/tabcompleter.h \
|
||||||
src/video/videoframe.h \
|
src/video/videoframe.h \
|
||||||
src/misc/flowlayout.h
|
src/misc/flowlayout.h \
|
||||||
|
src/ipc.h \
|
||||||
|
src/widget/toxuri.h \
|
||||||
|
src/toxdns.h
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
src/widget/form/addfriendform.cpp \
|
src/widget/form/addfriendform.cpp \
|
||||||
|
@ -208,4 +211,7 @@ SOURCES += \
|
||||||
src/video/netvideosource.cpp \
|
src/video/netvideosource.cpp \
|
||||||
src/widget/form/tabcompleter.cpp \
|
src/widget/form/tabcompleter.cpp \
|
||||||
src/video/videoframe.cpp \
|
src/video/videoframe.cpp \
|
||||||
src/misc/flowlayout.cpp
|
src/misc/flowlayout.cpp \
|
||||||
|
src/widget/toxuri.cpp \
|
||||||
|
src/toxdns.cpp \
|
||||||
|
src/ipc.cpp
|
||||||
|
|
12
src/core.cpp
12
src/core.cpp
|
@ -47,7 +47,7 @@ QList<ToxFile> Core::fileSendQueue;
|
||||||
QList<ToxFile> Core::fileRecvQueue;
|
QList<ToxFile> Core::fileRecvQueue;
|
||||||
|
|
||||||
Core::Core(Camera* cam, QThread *coreThread, QString loadPath) :
|
Core::Core(Camera* cam, QThread *coreThread, QString loadPath) :
|
||||||
tox(nullptr), camera(cam), loadPath(loadPath)
|
tox(nullptr), camera(cam), loadPath(loadPath), ready{false}
|
||||||
{
|
{
|
||||||
qDebug() << "Core: loading Tox from" << loadPath;
|
qDebug() << "Core: loading Tox from" << loadPath;
|
||||||
|
|
||||||
|
@ -297,6 +297,8 @@ void Core::start()
|
||||||
else
|
else
|
||||||
qDebug() << "Core: Error loading self avatar";
|
qDebug() << "Core: Error loading self avatar";
|
||||||
|
|
||||||
|
ready = true;
|
||||||
|
|
||||||
process(); // starts its own timer
|
process(); // starts its own timer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1353,8 +1355,9 @@ void Core::switchConfiguration(const QString& profile)
|
||||||
saveConfiguration();
|
saveConfiguration();
|
||||||
clearPassword(ptMain);
|
clearPassword(ptMain);
|
||||||
clearPassword(ptHistory);
|
clearPassword(ptHistory);
|
||||||
|
|
||||||
|
ready = false;
|
||||||
toxTimer->stop();
|
toxTimer->stop();
|
||||||
|
|
||||||
Widget::getInstance()->setEnabledThreadsafe(false);
|
Widget::getInstance()->setEnabledThreadsafe(false);
|
||||||
if (tox) {
|
if (tox) {
|
||||||
toxav_kill(toxav);
|
toxav_kill(toxav);
|
||||||
|
@ -1747,3 +1750,8 @@ QString Core::getPeerName(const ToxID& id) const
|
||||||
delete[] cname;
|
delete[] cname;
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Core::isReady()
|
||||||
|
{
|
||||||
|
return ready;
|
||||||
|
}
|
||||||
|
|
|
@ -69,6 +69,7 @@ public:
|
||||||
|
|
||||||
bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
|
bool anyActiveCalls(); ///< true is any calls are currently active (note: a call about to start is not yet active)
|
||||||
bool isPasswordSet(PasswordType passtype);
|
bool isPasswordSet(PasswordType passtype);
|
||||||
|
bool isReady(); ///< Most of the API shouldn't be used until Core is ready, call start() first
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void start(); ///< Initializes the core, must be called before anything else
|
void start(); ///< Initializes the core, must be called before anything else
|
||||||
|
@ -260,6 +261,7 @@ private:
|
||||||
static QList<ToxFile> fileSendQueue, fileRecvQueue;
|
static QList<ToxFile> fileSendQueue, fileRecvQueue;
|
||||||
static ToxCall calls[];
|
static ToxCall calls[];
|
||||||
QMutex fileSendMutex;
|
QMutex fileSendMutex;
|
||||||
|
bool ready;
|
||||||
|
|
||||||
uint8_t* pwsaltedkeys[PasswordType::ptCounter]; // use the pw's hash as the "pw"
|
uint8_t* pwsaltedkeys[PasswordType::ptCounter]; // use the pw's hash as the "pw"
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
#include "src/corestructs.h"
|
#include "src/corestructs.h"
|
||||||
#include "src/core.h"
|
#include "src/core.h"
|
||||||
|
#include <tox/tox.h>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
|
#define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE
|
||||||
|
|
||||||
ToxFile::ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction)
|
ToxFile::ToxFile(int FileNum, int FriendId, QByteArray FileName, QString FilePath, FileDirection Direction)
|
||||||
: fileNum(FileNum), friendId(FriendId), fileName{FileName}, filePath{FilePath}, file{new QFile(filePath)},
|
: fileNum(FileNum), friendId(FriendId), fileName{FileName}, filePath{FilePath}, file{new QFile(filePath)},
|
||||||
|
@ -48,3 +52,9 @@ bool ToxID::isMine() const
|
||||||
{
|
{
|
||||||
return *this == Core::getInstance()->getSelfId();
|
return *this == Core::getInstance()->getSelfId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ToxID::isToxId(const QString& value)
|
||||||
|
{
|
||||||
|
const QRegularExpression hexRegExp("^[A-Fa-f0-9]+$");
|
||||||
|
return value.length() == TOX_ID_LENGTH && value.contains(hexRegExp);
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,8 @@ struct ToxID
|
||||||
QString checkSum;
|
QString checkSum;
|
||||||
|
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
ToxID static fromString(QString id);
|
static ToxID fromString(QString id);
|
||||||
|
static bool isToxId(const QString& id);
|
||||||
|
|
||||||
bool operator==(const ToxID& other) const;
|
bool operator==(const ToxID& other) const;
|
||||||
bool operator!=(const ToxID& other) const;
|
bool operator!=(const ToxID& other) const;
|
||||||
|
|
254
src/ipc.cpp
Normal file
254
src/ipc.cpp
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
#include "src/ipc.h"
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
IPC::IPC() :
|
||||||
|
globalMemory{"qtox"}
|
||||||
|
{
|
||||||
|
qRegisterMetaType<IPCEventHandler>("IPCEventHandler");
|
||||||
|
|
||||||
|
ownerTimer.setInterval(EVENT_TIMER_MS);
|
||||||
|
ownerTimer.setSingleShot(true);
|
||||||
|
connect(&ownerTimer, &QTimer::timeout, this, &IPC::processEvents);
|
||||||
|
|
||||||
|
// The first started instance gets to manage the shared memory by taking ownership
|
||||||
|
// Every time it processes events it updates the global shared timestamp
|
||||||
|
// If the timestamp isn't updated, that's a timeout and someone else can take ownership
|
||||||
|
// This is a safety measure, in case one of the clients crashes
|
||||||
|
// If the owner exits normally, it can set the timestamp to 0 first to immediately give ownership
|
||||||
|
// We keep one shared page, starting with the 64bit ID of the current owner and last timestamp
|
||||||
|
// then various events to be processed by the owner with a 16bit size then data each
|
||||||
|
// Each event is in its own chunk of data, the last chunk is followed by a chunk of size 0
|
||||||
|
|
||||||
|
qsrand(time(0));
|
||||||
|
globalId = ((uint64_t)qrand()) * ((uint64_t)qrand()) * ((uint64_t)qrand());
|
||||||
|
qDebug() << "IPC: Our global ID is "<<globalId;
|
||||||
|
if (globalMemory.create(MEMORY_SIZE))
|
||||||
|
{
|
||||||
|
qDebug() << "IPC: Creating the global shared memory and taking ownership";
|
||||||
|
if (globalMemory.lock())
|
||||||
|
{
|
||||||
|
*(uint64_t*)globalMemory.data() = globalId;
|
||||||
|
updateGlobalTimestamp();
|
||||||
|
globalMemory.unlock();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "IPC: Couldn't lock to take ownership";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (globalMemory.attach())
|
||||||
|
{
|
||||||
|
qDebug() << "IPC: Attaching to the global shared memory";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qDebug() << "IPC: Failed to attach to the global shared memory, giving up";
|
||||||
|
return; // We won't be able to do any IPC without being attached, let's get outta here
|
||||||
|
}
|
||||||
|
|
||||||
|
ownerTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
IPC::~IPC()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPC::isCurrentOwner()
|
||||||
|
{
|
||||||
|
if (globalMemory.lock())
|
||||||
|
{
|
||||||
|
bool isOwner = ((*(uint64_t*)globalMemory.data()) == globalId);
|
||||||
|
globalMemory.unlock();
|
||||||
|
return isOwner;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "IPC:isCurrentOwner failed to lock, returning false";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::registerEventHandler(IPCEventHandler handler)
|
||||||
|
{
|
||||||
|
eventHandlers += handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::processEvents()
|
||||||
|
{
|
||||||
|
if (globalMemory.lock())
|
||||||
|
{
|
||||||
|
lastSeenTimestamp = getGlobalTimestamp();
|
||||||
|
|
||||||
|
// Only the owner processes events. But if the previous owner's dead, we can take ownership now
|
||||||
|
if (*(uint64_t*)globalMemory.data() != globalId)
|
||||||
|
{
|
||||||
|
if (difftime(time(0), getGlobalTimestamp()) >= OWNERSHIP_TIMEOUT_S)
|
||||||
|
{
|
||||||
|
qDebug() << "IPC: Previous owner timed out, taking ownership";
|
||||||
|
*(uint64_t*)globalMemory.data() = globalId;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
goto unlockAndRestartTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're the owner, let's process those events
|
||||||
|
forever {
|
||||||
|
QByteArray eventData = fetchEvent();
|
||||||
|
if (eventData.isEmpty())
|
||||||
|
break;
|
||||||
|
|
||||||
|
qDebug() << "IPC: Processing event: "<<eventData;
|
||||||
|
for (const IPCEventHandler& handler : eventHandlers)
|
||||||
|
runEventHandler(handler, eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGlobalTimestamp();
|
||||||
|
goto unlockAndRestartTimer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "IPC:processEvents failed to lock";
|
||||||
|
goto restartTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Centralized cleanup. Always restart the timer, unlock only if we locked successfully.
|
||||||
|
unlockAndRestartTimer:
|
||||||
|
globalMemory.unlock();
|
||||||
|
restartTimer:
|
||||||
|
ownerTimer.start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t IPC::postEvent(const QByteArray& data)
|
||||||
|
{
|
||||||
|
int dataSize = data.size();
|
||||||
|
if (dataSize >= 65535)
|
||||||
|
{
|
||||||
|
qWarning() << "IPC: sendEvent: Too much data for a single chunk, giving up";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (globalMemory.lock())
|
||||||
|
{
|
||||||
|
// Check that we have enough room for that new chunk
|
||||||
|
char* nextChunk = getFirstFreeChunk();
|
||||||
|
if (nextChunk == nullptr
|
||||||
|
|| nextChunk + 2 + dataSize + 2 - (char*)globalMemory.data() >= MEMORY_SIZE)
|
||||||
|
{
|
||||||
|
qWarning() << "IPC: sendEvent: Not enough memory left, giving up";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commit the new chunk to shared memory
|
||||||
|
*(uint16_t*)nextChunk = dataSize;
|
||||||
|
memcpy(nextChunk+2, data.data(), dataSize);
|
||||||
|
*(uint16_t*)(nextChunk+2+dataSize) = 0;
|
||||||
|
|
||||||
|
globalMemory.unlock();
|
||||||
|
qDebug() << "IPC: Posted event: "<<data;
|
||||||
|
return time(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qWarning() << "IPC: sendEvent failed to lock, giving up";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* IPC::getFirstFreeChunk()
|
||||||
|
{
|
||||||
|
char* ptr = (char*)globalMemory.data() + MEMORY_HEADER_SIZE;
|
||||||
|
|
||||||
|
forever
|
||||||
|
{
|
||||||
|
uint16_t chunkSize = *(uint16_t*)ptr;
|
||||||
|
|
||||||
|
if (!chunkSize)
|
||||||
|
return ptr;
|
||||||
|
|
||||||
|
if ((ptr + chunkSize + 2) - (char*)globalMemory.data() >= MEMORY_SIZE)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
ptr += chunkSize;
|
||||||
|
}
|
||||||
|
return nullptr; // Never reached
|
||||||
|
}
|
||||||
|
|
||||||
|
char* IPC::getLastUsedChunk()
|
||||||
|
{
|
||||||
|
char* ptr = (char*)globalMemory.data() + MEMORY_HEADER_SIZE;
|
||||||
|
char* lastPtr = nullptr;
|
||||||
|
|
||||||
|
forever
|
||||||
|
{
|
||||||
|
uint16_t chunkSize = *(uint16_t*)ptr;
|
||||||
|
|
||||||
|
if (!chunkSize)
|
||||||
|
return lastPtr;
|
||||||
|
|
||||||
|
if ((ptr + chunkSize + 2) - (char*)globalMemory.data() > MEMORY_SIZE)
|
||||||
|
return lastPtr;
|
||||||
|
|
||||||
|
lastPtr = ptr;
|
||||||
|
ptr += chunkSize;
|
||||||
|
}
|
||||||
|
return nullptr; // Never reached
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray IPC::fetchEvent()
|
||||||
|
{
|
||||||
|
QByteArray eventData;
|
||||||
|
|
||||||
|
// Get a pointer to the last chunk
|
||||||
|
char* nextChunk = getLastUsedChunk();
|
||||||
|
if (nextChunk == nullptr)
|
||||||
|
return eventData;
|
||||||
|
|
||||||
|
// Read that chunk and remove it from memory
|
||||||
|
uint16_t dataSize = *(uint16_t*)nextChunk;
|
||||||
|
*(uint16_t*)nextChunk = 0;
|
||||||
|
eventData.resize(dataSize);
|
||||||
|
memcpy(eventData.data(), nextChunk+2, dataSize);
|
||||||
|
|
||||||
|
return eventData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::updateGlobalTimestamp()
|
||||||
|
{
|
||||||
|
*(time_t*)((char*)globalMemory.data()+sizeof(globalId)) = time(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
time_t IPC::getGlobalTimestamp()
|
||||||
|
{
|
||||||
|
return *(time_t*)((char*)globalMemory.data()+sizeof(globalId));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPC::isEventProcessed(time_t postTime)
|
||||||
|
{
|
||||||
|
return (difftime(lastSeenTimestamp, postTime) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::waitUntilProcessed(time_t postTime)
|
||||||
|
{
|
||||||
|
while (difftime(lastSeenTimestamp, postTime) <= 0)
|
||||||
|
qApp->processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IPC::runEventHandler(IPCEventHandler handler, const QByteArray& arg)
|
||||||
|
{
|
||||||
|
if (QThread::currentThread() != qApp->thread())
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "runEventHandler", Qt::BlockingQueuedConnection,
|
||||||
|
Q_ARG(IPCEventHandler, handler), Q_ARG(const QByteArray&, arg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
handler(arg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
67
src/ipc.h
Normal file
67
src/ipc.h
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#ifndef IPC_H
|
||||||
|
#define IPC_H
|
||||||
|
|
||||||
|
#include <QSharedMemory>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QVector>
|
||||||
|
#include <functional>
|
||||||
|
#include <ctime>
|
||||||
|
|
||||||
|
/// Handles an IPC event, must filter out and ignore events it doesn't recognize
|
||||||
|
using IPCEventHandler = std::function<void (const QByteArray&)>;
|
||||||
|
|
||||||
|
/// Class used for inter-process communication with other qTox instances
|
||||||
|
/// IPC event handlers will be called from the GUI thread after its event loop starts
|
||||||
|
class IPC : public QThread
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
IPC();
|
||||||
|
~IPC();
|
||||||
|
/// Posts an event to the global shared memory, returns the time at wich it was posted
|
||||||
|
time_t postEvent(const QByteArray& data);
|
||||||
|
bool isCurrentOwner(); ///< Returns whether we're responsible for event processing of the global shared memory
|
||||||
|
void registerEventHandler(IPCEventHandler handler); ///< Registers a function to be called whenever an event is received
|
||||||
|
bool isEventProcessed(time_t postTime); ///< Returns wether a previously posted event was already processed
|
||||||
|
void waitUntilProcessed(time_t postTime); ///< Blocks until a previously posted event is processed
|
||||||
|
|
||||||
|
protected slots:
|
||||||
|
void processEvents();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Runs an IPC event handler from the main (GUI) thread, will block until the handler returns
|
||||||
|
Q_INVOKABLE void runEventHandler(IPCEventHandler handler, const QByteArray& arg);
|
||||||
|
/// Assumes that the memory IS LOCKED
|
||||||
|
/// Returns a pointer to the first free chunk of shared memory or a nullptr on error
|
||||||
|
char* getFirstFreeChunk();
|
||||||
|
/// Assumes that the memory IS LOCKED
|
||||||
|
/// Returns a pointer to the last used chunk of shared memory or a nullptr on error
|
||||||
|
char* getLastUsedChunk();
|
||||||
|
/// Assumes that the memory IS LOCKED
|
||||||
|
/// Removes the last event from the shared memory and returns its data, or an empty object on error
|
||||||
|
QByteArray fetchEvent();
|
||||||
|
/// Assumes that the memory IS LOCKED
|
||||||
|
/// Updates the global shared timestamp
|
||||||
|
void updateGlobalTimestamp();
|
||||||
|
/// Assumes that the memory IS LOCKED
|
||||||
|
/// Returns the global shared timestamp
|
||||||
|
time_t getGlobalTimestamp();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSharedMemory globalMemory;
|
||||||
|
uint64_t globalId;
|
||||||
|
QTimer ownerTimer;
|
||||||
|
QVector<IPCEventHandler> eventHandlers;
|
||||||
|
time_t lastSeenTimestamp;
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
static const int MEMORY_SIZE = 4096;
|
||||||
|
static const int MEMORY_HEADER_SIZE = sizeof(globalId) + sizeof(time_t);
|
||||||
|
static const int EVENT_TIMER_MS = 1000;
|
||||||
|
static const int OWNERSHIP_TIMEOUT_S = 5;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IPC_H
|
31
src/main.cpp
31
src/main.cpp
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
#include "widget/widget.h"
|
#include "widget/widget.h"
|
||||||
#include "misc/settings.h"
|
#include "misc/settings.h"
|
||||||
|
#include "src/ipc.h"
|
||||||
|
#include "src/widget/toxuri.h"
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
@ -77,8 +79,35 @@ int main(int argc, char *argv[])
|
||||||
// Install Unicode 6.1 supporting font
|
// Install Unicode 6.1 supporting font
|
||||||
QFontDatabase::addApplicationFont("://DejaVuSans.ttf");
|
QFontDatabase::addApplicationFont("://DejaVuSans.ttf");
|
||||||
|
|
||||||
Widget* w = Widget::getInstance();
|
// Inter-process communication
|
||||||
|
IPC ipc;
|
||||||
|
ipc.registerEventHandler(&toxURIEventHandler);
|
||||||
|
|
||||||
|
// Process arguments
|
||||||
|
if (argc >= 2)
|
||||||
|
{
|
||||||
|
QString firstParam(argv[1]);
|
||||||
|
// Tox URIs. If there's already another qTox instance running, we ask it to handle the URI and we exit
|
||||||
|
// Otherwise we start a new qTox instance and process it ourselves
|
||||||
|
if (firstParam.startsWith("tox:"))
|
||||||
|
{
|
||||||
|
if (ipc.isCurrentOwner()) // Don't bother sending an event if we're going to process it ourselves
|
||||||
|
{
|
||||||
|
handleToxURI(firstParam.toUtf8());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
time_t event = ipc.postEvent(firstParam.toUtf8());
|
||||||
|
ipc.waitUntilProcessed(event);
|
||||||
|
// If someone else processed it, we're done here, no need to actually start qTox
|
||||||
|
if (!ipc.isCurrentOwner())
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run
|
||||||
|
Widget* w = Widget::getInstance();
|
||||||
int errorcode = a.exec();
|
int errorcode = a.exec();
|
||||||
|
|
||||||
delete w;
|
delete w;
|
||||||
|
|
244
src/toxdns.cpp
Normal file
244
src/toxdns.cpp
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
#include "src/toxdns.h"
|
||||||
|
#include "src/misc/cdata.h"
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <tox/tox.h>
|
||||||
|
#include <tox/toxdns.h>
|
||||||
|
|
||||||
|
#define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE
|
||||||
|
|
||||||
|
const ToxDNS::tox3_server ToxDNS::pinnedServers[]
|
||||||
|
{
|
||||||
|
{"toxme.se", (uint8_t[32]){0x5D, 0x72, 0xC5, 0x17, 0xDF, 0x6A, 0xEC, 0x54, 0xF1, 0xE9, 0x77, 0xA6, 0xB6, 0xF2, 0x59, 0x14,
|
||||||
|
0xEA, 0x4C, 0xF7, 0x27, 0x7A, 0x85, 0x02, 0x7C, 0xD9, 0xF5, 0x19, 0x6D, 0xF1, 0x7E, 0x0B, 0x13}},
|
||||||
|
{"utox.org", (uint8_t[32]){0xD3, 0x15, 0x4F, 0x65, 0xD2, 0x8A, 0x5B, 0x41, 0xA0, 0x5D, 0x4A, 0xC7, 0xE4, 0xB3, 0x9C, 0x6B,
|
||||||
|
0x1C, 0x23, 0x3C, 0xC8, 0x57, 0xFB, 0x36, 0x5C, 0x56, 0xE8, 0x39, 0x27, 0x37, 0x46, 0x2A, 0x12}}
|
||||||
|
};
|
||||||
|
|
||||||
|
void ToxDNS::showWarning(const QString &message)
|
||||||
|
{
|
||||||
|
QMessageBox warning;
|
||||||
|
warning.setWindowTitle("Tox");
|
||||||
|
warning.setText(message);
|
||||||
|
warning.setIcon(QMessageBox::Warning);
|
||||||
|
warning.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ToxDNS::fetchLastTextRecord(const QString& record, bool silent)
|
||||||
|
{
|
||||||
|
QByteArray result;
|
||||||
|
|
||||||
|
QDnsLookup dns;
|
||||||
|
dns.setType(QDnsLookup::TXT);
|
||||||
|
dns.setName(record);
|
||||||
|
dns.lookup();
|
||||||
|
|
||||||
|
int timeout;
|
||||||
|
for (timeout = 0; timeout<30 && !dns.isFinished(); ++timeout)
|
||||||
|
{
|
||||||
|
qApp->processEvents();
|
||||||
|
QThread::msleep(100);
|
||||||
|
}
|
||||||
|
if (timeout >= 30) {
|
||||||
|
dns.abort();
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("The connection timed out","The DNS gives the Tox ID associated to toxme.se addresses"));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dns.error() == QDnsLookup::NotFoundError) {
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("This address does not exist","The DNS gives the Tox ID associated to toxme.se addresses"));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (dns.error() != QDnsLookup::NoError) {
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("Error while looking up DNS","The DNS gives the Tox ID associated to toxme.se addresses"));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QList<QDnsTextRecord> textRecords = dns.textRecords();
|
||||||
|
if (textRecords.isEmpty()) {
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("No text record found", "Error with the DNS"));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QList<QByteArray> textRecordValues = textRecords.last().values();
|
||||||
|
if (textRecordValues.length() != 1) {
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("Unexpected number of values in text record", "Error with the DNS"));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = textRecordValues.first();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ToxDNS::queryTox1(const QString& record, bool silent)
|
||||||
|
{
|
||||||
|
QString realRecord = record, toxId;
|
||||||
|
realRecord.replace("@", "._tox.");
|
||||||
|
const QString entry = fetchLastTextRecord(realRecord, silent);
|
||||||
|
if (entry.isEmpty())
|
||||||
|
return toxId;
|
||||||
|
|
||||||
|
// Check toxdns protocol version
|
||||||
|
int verx = entry.indexOf("v=");
|
||||||
|
if (verx) {
|
||||||
|
verx += 2;
|
||||||
|
int verend = entry.indexOf(';', verx);
|
||||||
|
if (verend)
|
||||||
|
{
|
||||||
|
QString ver = entry.mid(verx, verend-verx);
|
||||||
|
if (ver != "tox1")
|
||||||
|
{
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("The version of Tox DNS used by this server is not supported", "Error with the DNS"));
|
||||||
|
return toxId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the tox id
|
||||||
|
int idx = entry.indexOf("id=");
|
||||||
|
if (idx < 0) {
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("The DNS lookup does not contain any Tox ID", "Error with the DNS"));
|
||||||
|
return toxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 3;
|
||||||
|
if (entry.length() < idx + static_cast<int>(TOX_ID_LENGTH)) {
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS"));
|
||||||
|
return toxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
toxId = entry.mid(idx, TOX_ID_LENGTH);
|
||||||
|
if (!ToxID::isToxId(toxId)) {
|
||||||
|
if (!silent)
|
||||||
|
showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS"));
|
||||||
|
return toxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return toxId;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ToxDNS::queryTox3(const tox3_server& server, const QString &record, bool silent)
|
||||||
|
{
|
||||||
|
QByteArray nameData = record.left(record.indexOf('@')).toUtf8(), id, realRecord;
|
||||||
|
QString entry, toxIdStr;
|
||||||
|
int toxIdSize, idx, verx, dns_string_len;
|
||||||
|
const int dns_string_maxlen = 128;
|
||||||
|
|
||||||
|
void* tox_dns3 = tox_dns3_new(server.pubkey);
|
||||||
|
if (!tox_dns3)
|
||||||
|
{
|
||||||
|
qWarning() << "queryTox3: failed to create a tox_dns3 object for "<<server.name<<", using tox1 as a fallback";
|
||||||
|
goto fallbackOnTox1;
|
||||||
|
}
|
||||||
|
uint32_t request_id;
|
||||||
|
uint8_t dns_string[dns_string_maxlen];
|
||||||
|
dns_string_len = tox_generate_dns3_string(tox_dns3, dns_string, dns_string_maxlen, &request_id,
|
||||||
|
(uint8_t*)nameData.data(), nameData.size());
|
||||||
|
|
||||||
|
if (dns_string_len < 0) // We can always fallback on tox1 if toxdns3 fails
|
||||||
|
{
|
||||||
|
qWarning() << "queryTox3: failed to generate dns3 string for "<<server.name<<", using tox1 as a fallback";
|
||||||
|
goto fallbackOnTox1;
|
||||||
|
}
|
||||||
|
|
||||||
|
realRecord = '_'+QByteArray((char*)dns_string, dns_string_len)+"._tox."+server.name;
|
||||||
|
entry = fetchLastTextRecord(realRecord, silent);
|
||||||
|
if (entry.isEmpty())
|
||||||
|
{
|
||||||
|
qWarning() << "queryTox3: Server "<<server.name<<" returned no record, using tox1 as a fallback";
|
||||||
|
goto fallbackOnTox1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check toxdns protocol version
|
||||||
|
verx = entry.indexOf("v=");
|
||||||
|
if (verx!=-1) {
|
||||||
|
verx += 2;
|
||||||
|
int verend = entry.indexOf(';', verx);
|
||||||
|
if (verend!=-1)
|
||||||
|
{
|
||||||
|
QString ver = entry.mid(verx, verend-verx);
|
||||||
|
if (ver != "tox3")
|
||||||
|
{
|
||||||
|
qWarning() << "queryTox3: Server "<<server.name<<" returned a bad version ("<<ver<<"), using tox1 as a fallback";
|
||||||
|
goto fallbackOnTox1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get and decrypt the tox id
|
||||||
|
idx = entry.indexOf("id=");
|
||||||
|
if (idx < 0) {
|
||||||
|
qWarning() << "queryTox3: Server "<<server.name<<" returned an empty id, using tox1 as a fallback";
|
||||||
|
goto fallbackOnTox1;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 3;
|
||||||
|
id = entry.mid(idx).toUtf8();
|
||||||
|
uint8_t toxId[TOX_FRIEND_ADDRESS_SIZE];
|
||||||
|
toxIdSize = tox_decrypt_dns3_TXT(tox_dns3, toxId, (uint8_t*)id.data(), id.size(), request_id);
|
||||||
|
if (toxIdSize < 0) // We can always fallback on tox1 if toxdns3 fails
|
||||||
|
{
|
||||||
|
qWarning() << "queryTox3: failed to decrypt dns3 reply for "<<server.name<<", using tox1 as a fallback";
|
||||||
|
goto fallbackOnTox1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tox_dns3_kill(tox_dns3);
|
||||||
|
toxIdStr = CFriendAddress::toString(toxId);
|
||||||
|
return toxIdStr;
|
||||||
|
|
||||||
|
// Centralized error handling, fallback on tox1 queries
|
||||||
|
fallbackOnTox1:
|
||||||
|
if (tox_dns3)
|
||||||
|
tox_dns3_kill(tox_dns3);
|
||||||
|
queryTox1(record, silent);
|
||||||
|
return toxIdStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ToxID ToxDNS::resolveToxAddress(const QString &address, bool silent)
|
||||||
|
{
|
||||||
|
ToxID toxId;
|
||||||
|
|
||||||
|
if (address.isEmpty()) {
|
||||||
|
return toxId;
|
||||||
|
} else if (ToxID::isToxId(address)) {
|
||||||
|
toxId = ToxID::fromString(address);
|
||||||
|
return toxId;
|
||||||
|
} else {
|
||||||
|
// If we're querying one of our pinned server, do a tox3 request directly
|
||||||
|
QString servname = address.mid(address.indexOf('@')+1);
|
||||||
|
for (const ToxDNS::tox3_server& pin : ToxDNS::pinnedServers)
|
||||||
|
{
|
||||||
|
if (servname == pin.name)
|
||||||
|
{
|
||||||
|
toxId = ToxID::fromString(queryTox3(pin, address, silent));
|
||||||
|
return toxId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise try tox3 if we can get a pubkey or fallback to tox1
|
||||||
|
QByteArray pubkey = fetchLastTextRecord("_tox."+servname, true);
|
||||||
|
if (!pubkey.isEmpty())
|
||||||
|
{
|
||||||
|
QByteArray servnameData = servname.toUtf8();
|
||||||
|
ToxDNS::tox3_server server;
|
||||||
|
server.name = servnameData.data();
|
||||||
|
server.pubkey = (uint8_t*)pubkey.data();
|
||||||
|
toxId = ToxID::fromString(queryTox3(server, address, silent));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toxId = ToxID::fromString(queryTox1(address, silent));
|
||||||
|
}
|
||||||
|
return toxId;
|
||||||
|
}
|
||||||
|
}
|
42
src/toxdns.h
Normal file
42
src/toxdns.h
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef QTOXDNS_H
|
||||||
|
#define QTOXDNS_H
|
||||||
|
|
||||||
|
#include "src/corestructs.h"
|
||||||
|
#include <QDnsLookup>
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
/// Handles tox1 and tox3 DNS queries
|
||||||
|
class ToxDNS : public QObject
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct tox3_server ///< Represents a tox3 server
|
||||||
|
{
|
||||||
|
tox3_server()=default;
|
||||||
|
tox3_server(const char* _name, uint8_t _pk[32]):name{_name},pubkey{_pk}{}
|
||||||
|
|
||||||
|
const char* name; ///< Hostname of the server, e.g. toxme.se
|
||||||
|
uint8_t* pubkey; ///< Public key of the tox3 server, usually 256bit long
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// Tries to map a text string to a ToxID struct, will query Tox DNS records if necessary
|
||||||
|
static ToxID resolveToxAddress(const QString& address, bool silent=true);
|
||||||
|
|
||||||
|
static QString queryTox1(const QString& record, bool silent=true); ///< Record should look like user@domain.tld
|
||||||
|
static QString queryTox3(const tox3_server& server, const QString& record, bool silent=true); ///< Record should look like user@domain.tld, may fallback on queryTox1
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static void showWarning(const QString& message);
|
||||||
|
ToxDNS()=default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Try to fetch the first entry of the given TXT record
|
||||||
|
/// Returns an empty object on failure. May block for up to ~3s
|
||||||
|
/// May display message boxes on error if silent if false
|
||||||
|
static QByteArray fetchLastTextRecord(const QString& record, bool silent=true);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static const tox3_server pinnedServers[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QTOXDNS_H
|
|
@ -18,29 +18,14 @@
|
||||||
|
|
||||||
#include <QFont>
|
#include <QFont>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
#include <QThread>
|
|
||||||
#include <tox/tox.h>
|
#include <tox/tox.h>
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include "src/core.h"
|
#include "src/core.h"
|
||||||
#include "src/misc/cdata.h"
|
#include "src/misc/cdata.h"
|
||||||
#include "tox/toxdns.h"
|
#include "src/toxdns.h"
|
||||||
|
|
||||||
#include <QDebug>
|
AddFriendForm::AddFriendForm()
|
||||||
|
|
||||||
#define TOX_ID_LENGTH 2*TOX_FRIEND_ADDRESS_SIZE
|
|
||||||
|
|
||||||
const AddFriendForm::tox3_server AddFriendForm::pinnedServers[]
|
|
||||||
{
|
{
|
||||||
{"toxme.se", (uint8_t[32]){0x5D, 0x72, 0xC5, 0x17, 0xDF, 0x6A, 0xEC, 0x54, 0xF1, 0xE9, 0x77, 0xA6, 0xB6, 0xF2, 0x59, 0x14,
|
|
||||||
0xEA, 0x4C, 0xF7, 0x27, 0x7A, 0x85, 0x02, 0x7C, 0xD9, 0xF5, 0x19, 0x6D, 0xF1, 0x7E, 0x0B, 0x13}},
|
|
||||||
{"utox.org", (uint8_t[32]){0xD3, 0x15, 0x4F, 0x65, 0xD2, 0x8A, 0x5B, 0x41, 0xA0, 0x5D, 0x4A, 0xC7, 0xE4, 0xB3, 0x9C, 0x6B,
|
|
||||||
0x1C, 0x23, 0x3C, 0xC8, 0x57, 0xFB, 0x36, 0x5C, 0x56, 0xE8, 0x39, 0x27, 0x37, 0x46, 0x2A, 0x12}}
|
|
||||||
};
|
|
||||||
|
|
||||||
AddFriendForm::AddFriendForm() : dns(this)
|
|
||||||
{
|
|
||||||
dns.setType(QDnsLookup::TXT);
|
|
||||||
|
|
||||||
main = new QWidget(), head = new QWidget();
|
main = new QWidget(), head = new QWidget();
|
||||||
QFont bold;
|
QFont bold;
|
||||||
bold.setBold(true);
|
bold.setBold(true);
|
||||||
|
@ -79,10 +64,10 @@ void AddFriendForm::show(Ui::MainWindow &ui)
|
||||||
head->show();
|
head->show();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AddFriendForm::isToxId(const QString &value) const
|
QString AddFriendForm::getMessage() const
|
||||||
{
|
{
|
||||||
const QRegularExpression hexRegExp("^[A-Fa-f0-9]+$");
|
const QString msg = message.toPlainText();
|
||||||
return value.length() == TOX_ID_LENGTH && value.contains(hexRegExp);
|
return !msg.isEmpty() ? msg : message.placeholderText();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddFriendForm::showWarning(const QString &message) const
|
void AddFriendForm::showWarning(const QString &message) const
|
||||||
|
@ -94,19 +79,13 @@ void AddFriendForm::showWarning(const QString &message) const
|
||||||
warning.exec();
|
warning.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AddFriendForm::getMessage() const
|
|
||||||
{
|
|
||||||
const QString msg = message.toPlainText();
|
|
||||||
return !msg.isEmpty() ? msg : message.placeholderText();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddFriendForm::onSendTriggered()
|
void AddFriendForm::onSendTriggered()
|
||||||
{
|
{
|
||||||
QString id = toxId.text().trimmed();
|
QString id = toxId.text().trimmed();
|
||||||
|
|
||||||
if (id.isEmpty()) {
|
if (id.isEmpty()) {
|
||||||
showWarning(tr("Please fill in a valid Tox ID","Tox ID of the friend you're sending a friend request to"));
|
showWarning(tr("Please fill in a valid Tox ID","Tox ID of the friend you're sending a friend request to"));
|
||||||
} else if (isToxId(id)) {
|
} else if (ToxID::isToxId(id)) {
|
||||||
if (id.toUpper() == Core::getInstance()->getSelfId().toString().toUpper())
|
if (id.toUpper() == Core::getInstance()->getSelfId().toString().toUpper())
|
||||||
showWarning(tr("You can't add yourself as a friend!","When trying to add your own Tox ID as friend"));
|
showWarning(tr("You can't add yourself as a friend!","When trying to add your own Tox ID as friend"));
|
||||||
else
|
else
|
||||||
|
@ -114,208 +93,16 @@ void AddFriendForm::onSendTriggered()
|
||||||
this->toxId.clear();
|
this->toxId.clear();
|
||||||
this->message.clear();
|
this->message.clear();
|
||||||
} else {
|
} else {
|
||||||
// If we're querying one of our pinned server, do a tox3 request directly
|
ToxID toxId = ToxDNS::resolveToxAddress(id, true);
|
||||||
QString servname = id.mid(id.indexOf('@')+1);
|
|
||||||
for (const AddFriendForm::tox3_server& pin : pinnedServers)
|
if (toxId.toString().isEmpty())
|
||||||
{
|
{
|
||||||
if (servname == pin.name)
|
showWarning(tr("This Tox ID does not exist","DNS error"));
|
||||||
{
|
return;
|
||||||
queryTox3(pin, id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise try tox3 if we can get a pubkey or fallback to tox1
|
emit friendRequested(toxId.toString(), getMessage());
|
||||||
QByteArray pubkey = fetchLastTextRecord("_tox."+servname);
|
this->toxId.clear();
|
||||||
if (!pubkey.isEmpty())
|
this->message.clear();
|
||||||
{
|
|
||||||
QByteArray servnameData = servname.toUtf8();
|
|
||||||
AddFriendForm::tox3_server server;
|
|
||||||
server.name = servnameData.data();
|
|
||||||
server.pubkey = (uint8_t*)pubkey.data();
|
|
||||||
queryTox3(server, id);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
queryTox1(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray AddFriendForm::fetchLastTextRecord(const QString& record, bool silent)
|
|
||||||
{
|
|
||||||
QByteArray result;
|
|
||||||
|
|
||||||
dns.setName(record);
|
|
||||||
dns.lookup();
|
|
||||||
|
|
||||||
int timeout;
|
|
||||||
for (timeout = 0; timeout<30 && !dns.isFinished(); ++timeout)
|
|
||||||
{
|
|
||||||
qApp->processEvents();
|
|
||||||
QThread::msleep(100);
|
|
||||||
}
|
|
||||||
if (timeout >= 30) {
|
|
||||||
dns.abort();
|
|
||||||
if (!silent)
|
|
||||||
showWarning(tr("The connection timed out","The DNS gives the Tox ID associated to toxme.se addresses"));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dns.error() == QDnsLookup::NotFoundError) {
|
|
||||||
if (!silent)
|
|
||||||
showWarning(tr("This address does not exist","The DNS gives the Tox ID associated to toxme.se addresses"));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else if (dns.error() != QDnsLookup::NoError) {
|
|
||||||
if (!silent)
|
|
||||||
showWarning(tr("Error while looking up DNS","The DNS gives the Tox ID associated to toxme.se addresses"));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QList<QDnsTextRecord> textRecords = dns.textRecords();
|
|
||||||
if (textRecords.isEmpty()) {
|
|
||||||
if (!silent)
|
|
||||||
showWarning(tr("No text record found", "Error with the DNS"));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QList<QByteArray> textRecordValues = textRecords.last().values();
|
|
||||||
if (textRecordValues.length() != 1) {
|
|
||||||
if (!silent)
|
|
||||||
showWarning(tr("Unexpected number of values in text record", "Error with the DNS"));
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = textRecordValues.first();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddFriendForm::queryTox1(const QString& record)
|
|
||||||
{
|
|
||||||
QString realRecord = record;
|
|
||||||
realRecord.replace("@", "._tox.");
|
|
||||||
const QString entry = fetchLastTextRecord(realRecord, false);
|
|
||||||
if (entry.isEmpty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Check toxdns protocol version
|
|
||||||
int verx = entry.indexOf("v=");
|
|
||||||
if (verx) {
|
|
||||||
verx += 2;
|
|
||||||
int verend = entry.indexOf(';', verx);
|
|
||||||
if (verend)
|
|
||||||
{
|
|
||||||
QString ver = entry.mid(verx, verend-verx);
|
|
||||||
if (ver != "tox1")
|
|
||||||
{
|
|
||||||
showWarning(tr("The version of Tox DNS used by this server is not supported", "Error with the DNS"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the tox id
|
|
||||||
int idx = entry.indexOf("id=");
|
|
||||||
if (idx < 0) {
|
|
||||||
showWarning(tr("The DNS lookup does not contain any Tox ID", "Error with the DNS"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
idx += 3;
|
|
||||||
if (entry.length() < idx + static_cast<int>(TOX_ID_LENGTH)) {
|
|
||||||
showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString friendAdress = entry.mid(idx, TOX_ID_LENGTH);
|
|
||||||
if (!isToxId(friendAdress)) {
|
|
||||||
showWarning(tr("The DNS lookup does not contain a valid Tox ID", "Error with the DNS"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// finally we got it
|
|
||||||
emit friendRequested(friendAdress, getMessage());
|
|
||||||
this->toxId.clear();
|
|
||||||
this->message.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void AddFriendForm::queryTox3(const tox3_server& server, QString &record)
|
|
||||||
{
|
|
||||||
QByteArray nameData = record.left(record.indexOf('@')).toUtf8(), id, realRecord;
|
|
||||||
QString entry, toxIdStr;
|
|
||||||
int toxIdSize, idx, verx, dns_string_len;
|
|
||||||
const int dns_string_maxlen = 128;
|
|
||||||
|
|
||||||
void* tox_dns3 = tox_dns3_new(server.pubkey);
|
|
||||||
if (!tox_dns3)
|
|
||||||
{
|
|
||||||
qWarning() << "queryTox3: failed to create a tox_dns3 object for "<<server.name<<", using tox1 as a fallback";
|
|
||||||
goto fallbackOnTox1;
|
|
||||||
}
|
|
||||||
uint32_t request_id;
|
|
||||||
uint8_t dns_string[dns_string_maxlen];
|
|
||||||
dns_string_len = tox_generate_dns3_string(tox_dns3, dns_string, dns_string_maxlen, &request_id,
|
|
||||||
(uint8_t*)nameData.data(), nameData.size());
|
|
||||||
|
|
||||||
if (dns_string_len < 0) // We can always fallback on tox1 if toxdns3 fails
|
|
||||||
{
|
|
||||||
qWarning() << "queryTox3: failed to generate dns3 string for "<<server.name<<", using tox1 as a fallback";
|
|
||||||
goto fallbackOnTox1;
|
|
||||||
}
|
|
||||||
|
|
||||||
realRecord = '_'+QByteArray((char*)dns_string, dns_string_len)+"._tox."+server.name;
|
|
||||||
entry = fetchLastTextRecord(realRecord, false);
|
|
||||||
if (entry.isEmpty())
|
|
||||||
{
|
|
||||||
qWarning() << "queryTox3: Server "<<server.name<<" returned no record, using tox1 as a fallback";
|
|
||||||
goto fallbackOnTox1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check toxdns protocol version
|
|
||||||
verx = entry.indexOf("v=");
|
|
||||||
if (verx!=-1) {
|
|
||||||
verx += 2;
|
|
||||||
int verend = entry.indexOf(';', verx);
|
|
||||||
if (verend!=-1)
|
|
||||||
{
|
|
||||||
QString ver = entry.mid(verx, verend-verx);
|
|
||||||
if (ver != "tox3")
|
|
||||||
{
|
|
||||||
qWarning() << "queryTox3: Server "<<server.name<<" returned a bad version ("<<ver<<"), using tox1 as a fallback";
|
|
||||||
goto fallbackOnTox1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get and decrypt the tox id
|
|
||||||
idx = entry.indexOf("id=");
|
|
||||||
if (idx < 0) {
|
|
||||||
qWarning() << "queryTox3: Server "<<server.name<<" returned an empty id, using tox1 as a fallback";
|
|
||||||
goto fallbackOnTox1;
|
|
||||||
}
|
|
||||||
|
|
||||||
idx += 3;
|
|
||||||
id = entry.mid(idx).toUtf8();
|
|
||||||
uint8_t toxId[TOX_FRIEND_ADDRESS_SIZE];
|
|
||||||
toxIdSize = tox_decrypt_dns3_TXT(tox_dns3, toxId, (uint8_t*)id.data(), id.size(), request_id);
|
|
||||||
if (toxIdSize < 0) // We can always fallback on tox1 if toxdns3 fails
|
|
||||||
{
|
|
||||||
qWarning() << "queryTox3: failed to decrypt dns3 reply for "<<server.name<<", using tox1 as a fallback";
|
|
||||||
goto fallbackOnTox1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tox_dns3_kill(tox_dns3);
|
|
||||||
toxIdStr = CFriendAddress::toString(toxId);
|
|
||||||
emit friendRequested(toxIdStr, getMessage());
|
|
||||||
this->toxId.clear();
|
|
||||||
this->message.clear();
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Centralized error handling, fallback on tox1 queries
|
|
||||||
fallbackOnTox1:
|
|
||||||
if (tox_dns3)
|
|
||||||
tox_dns3_kill(tox_dns3);
|
|
||||||
queryTox1(record);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
|
@ -22,7 +22,6 @@
|
||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QTextEdit>
|
#include <QTextEdit>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QDnsLookup>
|
|
||||||
|
|
||||||
namespace Ui {class MainWindow;}
|
namespace Ui {class MainWindow;}
|
||||||
|
|
||||||
|
@ -34,34 +33,17 @@ public:
|
||||||
~AddFriendForm();
|
~AddFriendForm();
|
||||||
|
|
||||||
void show(Ui::MainWindow &ui);
|
void show(Ui::MainWindow &ui);
|
||||||
bool isToxId(const QString& value) const;
|
|
||||||
void showWarning(const QString& message) const;
|
|
||||||
QString getMessage() const;
|
QString getMessage() const;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void friendRequested(const QString& friendAddress, const QString& message);
|
void friendRequested(const QString& friendAddress, const QString& message);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void showWarning(const QString& message) const;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void onSendTriggered();
|
void onSendTriggered();
|
||||||
|
|
||||||
private:
|
|
||||||
struct tox3_server
|
|
||||||
{
|
|
||||||
tox3_server()=default;
|
|
||||||
tox3_server(const char* _name, uint8_t _pk[32]):name{_name},pubkey{_pk}{}
|
|
||||||
|
|
||||||
const char* name; ///< Hostname of the server, e.g. toxme.se
|
|
||||||
uint8_t* pubkey; ///< Public key of the tox3 server, usually 256bit long
|
|
||||||
};
|
|
||||||
|
|
||||||
private:
|
|
||||||
void queryTox1(const QString& record); ///< Record should look like user@domain.tld
|
|
||||||
void queryTox3(const tox3_server& server, QString& record); ///< Record should look like user@domain.tld, may fallback on queryTox1
|
|
||||||
/// Try to fetch the first entry of the given TXT record
|
|
||||||
/// Returns an empty object on failure. May block for up to ~3s
|
|
||||||
/// May display message boxes on error if silent if false
|
|
||||||
QByteArray fetchLastTextRecord(const QString& record, bool silent=true);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QLabel headLabel, toxIdLabel, messageLabel;
|
QLabel headLabel, toxIdLabel, messageLabel;
|
||||||
QPushButton sendButton;
|
QPushButton sendButton;
|
||||||
|
@ -69,10 +51,6 @@ private:
|
||||||
QTextEdit message;
|
QTextEdit message;
|
||||||
QVBoxLayout layout, headLayout;
|
QVBoxLayout layout, headLayout;
|
||||||
QWidget *head, *main;
|
QWidget *head, *main;
|
||||||
|
|
||||||
/** will be used for dns discovery if necessary */
|
|
||||||
QDnsLookup dns;
|
|
||||||
static const tox3_server pinnedServers[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ADDFRIENDFORM_H
|
#endif // ADDFRIENDFORM_H
|
||||||
|
|
92
src/widget/toxuri.cpp
Normal file
92
src/widget/toxuri.cpp
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
#include "src/widget/toxuri.h"
|
||||||
|
#include "src/toxdns.h"
|
||||||
|
#include "src/widget/tool/friendrequestdialog.h"
|
||||||
|
#include "src/core.h"
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPlainTextEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
void toxURIEventHandler(const QByteArray& eventData)
|
||||||
|
{
|
||||||
|
if (!eventData.startsWith("tox:"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
handleToxURI(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleToxURI(const QString &toxURI)
|
||||||
|
{
|
||||||
|
Core* core = Core::getInstance();
|
||||||
|
|
||||||
|
while (!core)
|
||||||
|
{
|
||||||
|
core = Core::getInstance();
|
||||||
|
qApp->processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!core->isReady())
|
||||||
|
{
|
||||||
|
qApp->processEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString toxaddr = toxURI.mid(4);
|
||||||
|
QString toxid = ToxDNS::resolveToxAddress(toxaddr, true).toString();
|
||||||
|
|
||||||
|
if (toxid.isEmpty())
|
||||||
|
{
|
||||||
|
QMessageBox::warning(0, "qTox", toxaddr+" is not a valid Tox address.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ToxURIDialog dialog(0, toxaddr, QObject::tr("Tox me maybe?","Default message in Tox URI friend requests. Write something appropriate!"));
|
||||||
|
if (dialog.exec() == QDialog::Accepted)
|
||||||
|
Core::getInstance()->requestFriendship(toxid, dialog.getRequestMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToxURIDialog::ToxURIDialog(QWidget *parent, const QString &userId, const QString &message) :
|
||||||
|
QDialog(parent)
|
||||||
|
{
|
||||||
|
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||||
|
setWindowTitle(tr("Add a friend","Title of the window to add a friend through Tox URI"));
|
||||||
|
|
||||||
|
QLabel *friendsLabel = new QLabel(tr("Do you want to add %1 as a friend ?").arg(userId), this);
|
||||||
|
QLabel *userIdLabel = new QLabel(tr("User ID:"), this);
|
||||||
|
QLineEdit *userIdEdit = new QLineEdit(userId, this);
|
||||||
|
userIdEdit->setCursorPosition(0);
|
||||||
|
userIdEdit->setReadOnly(true);
|
||||||
|
QLabel *messageLabel = new QLabel(tr("Friend request message:"), this);
|
||||||
|
messageEdit = new QPlainTextEdit(message, this);
|
||||||
|
|
||||||
|
QDialogButtonBox *buttonBox = new QDialogButtonBox(Qt::Horizontal, this);
|
||||||
|
|
||||||
|
buttonBox->addButton(tr("Send","Send a friend request"), QDialogButtonBox::AcceptRole);
|
||||||
|
buttonBox->addButton(tr("Cancel","Don't send a friend request"), QDialogButtonBox::RejectRole);
|
||||||
|
|
||||||
|
connect(buttonBox, &QDialogButtonBox::accepted, this, &FriendRequestDialog::accept);
|
||||||
|
connect(buttonBox, &QDialogButtonBox::rejected, this, &FriendRequestDialog::reject);
|
||||||
|
|
||||||
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
layout->addWidget(friendsLabel);
|
||||||
|
layout->addSpacing(12);
|
||||||
|
layout->addWidget(userIdLabel);
|
||||||
|
layout->addWidget(userIdEdit);
|
||||||
|
layout->addWidget(messageLabel);
|
||||||
|
layout->addWidget(messageEdit);
|
||||||
|
layout->addWidget(buttonBox);
|
||||||
|
|
||||||
|
resize(300, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ToxURIDialog::getRequestMessage()
|
||||||
|
{
|
||||||
|
return messageEdit->toPlainText();
|
||||||
|
}
|
25
src/widget/toxuri.h
Normal file
25
src/widget/toxuri.h
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#ifndef TOXURI_H
|
||||||
|
#define TOXURI_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
/// Shows a dialog asking whether or not to add this tox address as a friend
|
||||||
|
/// Will wait until the core is ready first
|
||||||
|
void handleToxURI(const QString& toxURI);
|
||||||
|
|
||||||
|
// Internals
|
||||||
|
class QByteArray;
|
||||||
|
class QPlainTextEdit;
|
||||||
|
void toxURIEventHandler(const QByteArray& eventData);
|
||||||
|
class ToxURIDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit ToxURIDialog(QWidget *parent, const QString &userId, const QString &message);
|
||||||
|
QString getRequestMessage();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPlainTextEdit *messageEdit;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // TOXURI_H
|
|
@ -62,7 +62,6 @@ Widget::Widget(QWidget *parent)
|
||||||
|
|
||||||
void Widget::init()
|
void Widget::init()
|
||||||
{
|
{
|
||||||
|
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
if (QSystemTrayIcon::isSystemTrayAvailable() == true)
|
if (QSystemTrayIcon::isSystemTrayAvailable() == true)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user