emailQt: Initial commit
This commit is contained in:
commit
92158f9f84
32
CMakeLists.txt
Normal file
32
CMakeLists.txt
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.27)
|
||||||
|
project(emailQt)
|
||||||
|
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG -fsanitize=address,undefined")
|
||||||
|
set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fsanitize=address,undefined")
|
||||||
|
|
||||||
|
find_package(Qt6 COMPONENTS Core Gui Widgets REQUIRED)
|
||||||
|
find_package(Qt6Keychain CONFIG REQUIRED)
|
||||||
|
|
||||||
|
add_executable(emailQt main.cpp
|
||||||
|
EmailClient.hpp
|
||||||
|
EmailClient.cpp
|
||||||
|
CurlHandler.hpp
|
||||||
|
CurlHandler.cpp
|
||||||
|
ParametersProvider.cpp
|
||||||
|
ParametersProvider.hpp
|
||||||
|
Email.cpp
|
||||||
|
Email.hpp
|
||||||
|
EmailDetails.cpp
|
||||||
|
EmailDetails.hpp)
|
||||||
|
|
||||||
|
target_link_libraries(emailQt PRIVATE Qt6::Core
|
||||||
|
Qt6::Gui
|
||||||
|
Qt6::Widgets
|
||||||
|
curl
|
||||||
|
Qt6Keychain::Qt6Keychain)
|
101
CurlHandler.cpp
Executable file
101
CurlHandler.cpp
Executable file
@ -0,0 +1,101 @@
|
|||||||
|
#include "CurlHandler.hpp"
|
||||||
|
|
||||||
|
CurlHandler::CurlHandler()
|
||||||
|
: initialRes{}, initialCurl(curl_easy_init(), curl_easy_cleanup) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CurlHandler::~CurlHandler() {
|
||||||
|
for (auto& t : v)
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlHandler::configure(const ParametersProvider::settings &sett) {
|
||||||
|
url.append("imaps://");
|
||||||
|
url += sett.imapS;
|
||||||
|
url += "/INBOX?ALL";
|
||||||
|
setts = sett;
|
||||||
|
|
||||||
|
setupCurl(initialCurl, initialChunk, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> *CurlHandler::fetch() {
|
||||||
|
initialRes = curl_easy_perform(initialCurl.get());
|
||||||
|
if (initialRes != CURLE_OK) {
|
||||||
|
return &decodedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We prepare to query the server.
|
||||||
|
url.erase(url.end() - 4, url.end());
|
||||||
|
url += "/;MAILINDEX=";
|
||||||
|
|
||||||
|
std::string temp;
|
||||||
|
std::stringstream s(initialChunk);
|
||||||
|
|
||||||
|
size_t count = 0;
|
||||||
|
while (getline(s, temp, ' ')) {
|
||||||
|
// SELECT and *
|
||||||
|
if (count < 2) {
|
||||||
|
count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
messagesId.emplace_back(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We will query the server number by number.
|
||||||
|
// The messagesId vector contains all Email GUIDs.
|
||||||
|
v.reserve(messagesId.size());
|
||||||
|
decodedMessages.resize(messagesId.size());
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
for (auto i{messagesId.rbegin()}; i != messagesId.rbegin() + 5; i++)
|
||||||
|
v.emplace_back(&CurlHandler::query, this, std::ref(*i), count++);
|
||||||
|
|
||||||
|
return &decodedMessages;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlHandler::query(std::string &c, size_t count) {
|
||||||
|
std::shared_ptr<CURL> localCurl(curl_easy_init(), curl_easy_cleanup);
|
||||||
|
CURLcode localCode;
|
||||||
|
c.erase(std::remove(c.begin(), c.end(), '\r'), c.end());
|
||||||
|
c.erase(std::remove(c.begin(), c.end(), '\n'), c.end());
|
||||||
|
setupCurl(localCurl, decodedMessages[count], url + c);
|
||||||
|
|
||||||
|
localCode = curl_easy_perform(localCurl.get());
|
||||||
|
if (localCode != CURLE_OK) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
emit threadFinished(count, std::stoi(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CurlHandler::setupCurl(std::shared_ptr<CURL> ptr, std::string &m, std::string const &ur) const {
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_USERNAME, setts.userS.c_str());
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_PASSWORD, setts.passS.c_str());
|
||||||
|
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_PORT, setts.port);
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_TIMEOUT, curlTimeoutSeconds);
|
||||||
|
|
||||||
|
// The callback function will receive the string ref as its userdata
|
||||||
|
// and therefore save the data there.
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_USE_SSL, (long) CURLUSESSL_ALL);
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_WRITEDATA, (void *) &m);
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_WRITEFUNCTION, cb);
|
||||||
|
|
||||||
|
// Necessary on some servers
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_USERAGENT, "libcurl-agent/1.0");
|
||||||
|
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_URL, ur.c_str());
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_VERBOSE, 1L);
|
||||||
|
#else
|
||||||
|
curl_easy_setopt(ptr.get(), CURLOPT_VERBOSE, 0L);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cb(char *data, size_t size, size_t numberOfMembers, void *userdata) {
|
||||||
|
size_t realSize = size * numberOfMembers;
|
||||||
|
auto *mem = (std::string *) userdata;
|
||||||
|
mem->append(data, realSize);
|
||||||
|
return realSize;
|
||||||
|
}
|
49
CurlHandler.hpp
Executable file
49
CurlHandler.hpp
Executable file
@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
#include "ParametersProvider.hpp"
|
||||||
|
|
||||||
|
constexpr long curlTimeoutSeconds{5};
|
||||||
|
|
||||||
|
// View RFC 3501 and RFC 2192 for more details
|
||||||
|
|
||||||
|
size_t cb(char *data, size_t size, size_t numberOfMembers, void *userdata);
|
||||||
|
|
||||||
|
class CurlHandler : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
CurlHandler();
|
||||||
|
~CurlHandler();
|
||||||
|
|
||||||
|
void configure(const ParametersProvider::settings &sett);
|
||||||
|
std::vector<std::string> *fetch();
|
||||||
|
|
||||||
|
void query(std::string &c, size_t count);
|
||||||
|
void setupCurl(std::shared_ptr<CURL> ptr, std::string &m, const std::string &ur) const;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void threadFinished(size_t threadId, int uid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string url;
|
||||||
|
std::shared_ptr<CURL> initialCurl;
|
||||||
|
CURLcode initialRes;
|
||||||
|
std::string initialChunk;
|
||||||
|
|
||||||
|
std::vector<std::string> decodedMessages;
|
||||||
|
std::vector<std::string> messagesId;
|
||||||
|
std::vector<std::thread> v;
|
||||||
|
|
||||||
|
ParametersProvider::settings setts;
|
||||||
|
};
|
47
Email.cpp
Normal file
47
Email.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#include "Email.hpp"
|
||||||
|
|
||||||
|
Email::Email(const std::string &str, size_t uid)
|
||||||
|
: QListWidgetItem(nullptr, QListWidgetItem::UserType), uid{uid} {
|
||||||
|
setString(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
Email::Email(Email const &e)
|
||||||
|
: QListWidgetItem(e), uid{e.uid}, titleS(e.titleS), messageS(e.messageS) {
|
||||||
|
setString(titleS);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Email::setString(const std::string &str) {
|
||||||
|
std::stringstream ss(str);
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
while (std::getline(ss, line)) {
|
||||||
|
if (line.starts_with("Subject:"))
|
||||||
|
titleS = line.substr(9);
|
||||||
|
else if (line.starts_with("Content-Type:"))
|
||||||
|
parseEmailBody(ss, line);
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(QString::fromStdString(titleS));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Email::title() const {
|
||||||
|
return titleS;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &Email::message() const {
|
||||||
|
return messageS;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Email::operator<(QListWidgetItem const &other) const {
|
||||||
|
auto a{dynamic_cast<const Email &>(other)};
|
||||||
|
return this->uid < a.uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Email::parseEmailBody(std::stringstream &ss, std::string &line) {
|
||||||
|
if (!line.contains("text/plain"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (std::getline(ss, line) && !(line.starts_with("--"))) {
|
||||||
|
messageS.append(line);
|
||||||
|
}
|
||||||
|
}
|
28
Email.hpp
Normal file
28
Email.hpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <QListWidgetItem>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class Email : public QListWidgetItem {
|
||||||
|
public:
|
||||||
|
explicit Email(const std::string &str, size_t uid);
|
||||||
|
Email(const Email &e);
|
||||||
|
~Email() = default;
|
||||||
|
|
||||||
|
void setString(const std::string &str);
|
||||||
|
[[nodiscard]] const std::string &title() const;
|
||||||
|
[[nodiscard]] const std::string &message() const;
|
||||||
|
|
||||||
|
bool operator<(const QListWidgetItem &other) const;
|
||||||
|
|
||||||
|
void parseEmailBody(std::stringstream &ss, std::string &line);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string titleS;
|
||||||
|
std::string messageS;
|
||||||
|
size_t uid;
|
||||||
|
};
|
44
EmailClient.cpp
Executable file
44
EmailClient.cpp
Executable file
@ -0,0 +1,44 @@
|
|||||||
|
#include "EmailClient.hpp"
|
||||||
|
|
||||||
|
EmailClient::EmailClient(QWidget *parent)
|
||||||
|
: QMainWindow(parent) {
|
||||||
|
connect(&provider, &ParametersProvider::done, this, &EmailClient::parametersDone);
|
||||||
|
|
||||||
|
auto *layout{new QVBoxLayout};
|
||||||
|
response = new QListWidget;
|
||||||
|
|
||||||
|
layout->addWidget(response);
|
||||||
|
|
||||||
|
auto *centralWidget{new QWidget(this)};
|
||||||
|
centralWidget->setLayout(layout);
|
||||||
|
setCentralWidget(centralWidget);
|
||||||
|
|
||||||
|
setWindowTitle("emailQt");
|
||||||
|
|
||||||
|
connect(response, &QListWidget::itemActivated, this, &EmailClient::itemActivated);
|
||||||
|
connect(&handler, &CurlHandler::threadFinished, this, &EmailClient::updateList);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmailClient::start() {
|
||||||
|
provider.show();
|
||||||
|
provider.setFocusInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmailClient::parametersDone() {
|
||||||
|
handler.configure(provider.getSettings());
|
||||||
|
|
||||||
|
emailsString = handler.fetch();
|
||||||
|
|
||||||
|
showMaximized();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmailClient::itemActivated(QListWidgetItem *item) {
|
||||||
|
auto *p = dynamic_cast<Email *>(item);
|
||||||
|
det.setMail(*p);
|
||||||
|
det.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmailClient::updateList(size_t threadId, int uid) {
|
||||||
|
response->addItem(new Email((*emailsString)[threadId], uid));
|
||||||
|
response->sortItems(Qt::DescendingOrder);
|
||||||
|
}
|
43
EmailClient.hpp
Executable file
43
EmailClient.hpp
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QList>
|
||||||
|
#include <QMainWindow>
|
||||||
|
#include <QString>
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "CurlHandler.hpp"
|
||||||
|
#include "Email.hpp"
|
||||||
|
#include "EmailDetails.hpp"
|
||||||
|
#include "ParametersProvider.hpp"
|
||||||
|
|
||||||
|
class EmailClient : public QMainWindow {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit EmailClient(QWidget *parent = nullptr);
|
||||||
|
~EmailClient() = default;
|
||||||
|
|
||||||
|
void start();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void parametersDone();
|
||||||
|
void itemActivated(QListWidgetItem *item);
|
||||||
|
void updateList(size_t threadId, int uid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
ParametersProvider provider;
|
||||||
|
CurlHandler handler;
|
||||||
|
QListWidget *response;
|
||||||
|
|
||||||
|
std::vector<std::string> *emailsString{};
|
||||||
|
QList<QString> emailTitles;
|
||||||
|
EmailDetails det;
|
||||||
|
};
|
18
EmailDetails.cpp
Normal file
18
EmailDetails.cpp
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#include "EmailDetails.hpp"
|
||||||
|
|
||||||
|
EmailDetails::EmailDetails(QWidget *parent)
|
||||||
|
: QWidget(parent) {
|
||||||
|
t = new QTextEdit;
|
||||||
|
|
||||||
|
auto *lay = new QVBoxLayout;
|
||||||
|
|
||||||
|
lay->addWidget(t);
|
||||||
|
setLayout(lay);
|
||||||
|
|
||||||
|
resize(800, 600);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EmailDetails::setMail(const Email &m) {
|
||||||
|
setWindowTitle(QString::fromStdString(m.title()));
|
||||||
|
t->setPlainText(QString::fromStdString(m.message()));
|
||||||
|
}
|
19
EmailDetails.hpp
Normal file
19
EmailDetails.hpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "Email.hpp"
|
||||||
|
|
||||||
|
class EmailDetails : public QWidget {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit EmailDetails(QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
void setMail(const Email &m);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QTextEdit *t;
|
||||||
|
};
|
57
ParametersProvider.cpp
Normal file
57
ParametersProvider.cpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
#include "ParametersProvider.hpp"
|
||||||
|
|
||||||
|
ParametersProvider::ParametersProvider(QWidget *parent)
|
||||||
|
: QDialog(parent) {
|
||||||
|
auto *layout = new QVBoxLayout;
|
||||||
|
|
||||||
|
auto *user = new QLabel("Nom d'utilisateur", this);
|
||||||
|
userField = new QLineEdit;
|
||||||
|
|
||||||
|
auto *pass = new QLabel("Mot de passe", this);
|
||||||
|
passField = new QLineEdit;
|
||||||
|
passField->setEchoMode(QLineEdit::Password);
|
||||||
|
|
||||||
|
auto *imap = new QLabel("Serveur IMAP", this);
|
||||||
|
imapField = new QLineEdit("imap.gmail.com");
|
||||||
|
|
||||||
|
auto *port = new QLabel("Port", this);
|
||||||
|
portField = new QLineEdit("993");
|
||||||
|
portField->setValidator(new QIntValidator(1, 1000, this));
|
||||||
|
|
||||||
|
auto *startButton = new QPushButton("OK", this);
|
||||||
|
|
||||||
|
layout->addWidget(user);
|
||||||
|
layout->addWidget(userField);
|
||||||
|
layout->addWidget(pass);
|
||||||
|
layout->addWidget(passField);
|
||||||
|
layout->addWidget(imap);
|
||||||
|
layout->addWidget(imapField);
|
||||||
|
layout->addWidget(port);
|
||||||
|
layout->addWidget(portField);
|
||||||
|
layout->addStretch(1);
|
||||||
|
layout->addWidget(startButton);
|
||||||
|
|
||||||
|
this->setLayout(layout);
|
||||||
|
|
||||||
|
resize(300, 450);
|
||||||
|
|
||||||
|
connect(startButton, &QPushButton::clicked, this, &ParametersProvider::start);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParametersProvider::start() {
|
||||||
|
sett.port = portField->text().toInt();
|
||||||
|
sett.userS = userField->text().toStdString();
|
||||||
|
sett.passS = passField->text().toStdString();
|
||||||
|
sett.imapS = imapField->text().toStdString();
|
||||||
|
close();
|
||||||
|
emit done();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParametersProvider::setFocusInternal() {
|
||||||
|
activateWindow();
|
||||||
|
userField->setFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const ParametersProvider::settings &ParametersProvider::getSettings() {
|
||||||
|
return sett;
|
||||||
|
}
|
32
ParametersProvider.hpp
Normal file
32
ParametersProvider.hpp
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QIntValidator>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
|
class ParametersProvider : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct settings {
|
||||||
|
std::string userS, passS, imapS;
|
||||||
|
int port;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ParametersProvider(QWidget *parent = nullptr);
|
||||||
|
~ParametersProvider() = default;
|
||||||
|
|
||||||
|
void start();
|
||||||
|
void setFocusInternal();
|
||||||
|
const settings &getSettings();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void done();
|
||||||
|
|
||||||
|
private:
|
||||||
|
settings sett;
|
||||||
|
QLineEdit *userField, *passField, *imapField, *portField;
|
||||||
|
};
|
10
README.md
Normal file
10
README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# emailQt - A small & simple Qt email client
|
||||||
|
|
||||||
|
![](demo.mkv)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
If you are using gmail, you need to enable 2FA and then generate an application password.
|
||||||
|
This can be done at https://myaccount.google.com/security.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
You will need a C++23 compiler, cmake, Qt6, libcurl and [QtKeychain](https://github.com/frankosterfeld/qtkeychain).
|
Loading…
Reference in New Issue
Block a user