mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merged keepassx-http.
This commit is contained in:
commit
73f91db939
@ -102,6 +102,13 @@ set(keepassx_SOURCES
|
||||
gui/group/EditGroupWidget.cpp
|
||||
gui/group/GroupModel.cpp
|
||||
gui/group/GroupView.cpp
|
||||
http/AccessControlDialog.cpp
|
||||
http/EntryConfig.cpp
|
||||
http/HttpSettings.cpp
|
||||
http/OptionDialog.cpp
|
||||
http/Protocol.cpp
|
||||
http/Server.cpp
|
||||
http/Service.cpp
|
||||
keys/CompositeKey.cpp
|
||||
keys/CompositeKey_p.h
|
||||
keys/FileKey.cpp
|
||||
@ -180,6 +187,12 @@ set(keepassx_MOC
|
||||
gui/group/EditGroupWidget.h
|
||||
gui/group/GroupModel.h
|
||||
gui/group/GroupView.h
|
||||
http/AccessControlDialog.h
|
||||
http/EntryConfig.h
|
||||
http/OptionDialog.h
|
||||
http/Protocol.h
|
||||
http/Server.h
|
||||
http/Service.h
|
||||
keys/CompositeKey_p.h
|
||||
streams/HashedBlockStream.h
|
||||
streams/LayeredStream.h
|
||||
@ -207,6 +220,8 @@ set(keepassx_FORMS
|
||||
gui/entry/EditEntryWidgetHistory.ui
|
||||
gui/entry/EditEntryWidgetMain.ui
|
||||
gui/group/EditGroupWidgetMain.ui
|
||||
http/AccessControlDialog.ui
|
||||
http/OptionDialog.ui
|
||||
)
|
||||
|
||||
if(MINGW)
|
||||
@ -221,17 +236,28 @@ qt4_wrap_cpp(keepassx_SOURCES ${keepassx_MOC})
|
||||
add_library(keepassx_core STATIC ${keepassx_SOURCES})
|
||||
set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
|
||||
|
||||
add_subdirectory(gui/qocoa)
|
||||
add_subdirectory(http/qhttpserver)
|
||||
add_subdirectory(http/qjson)
|
||||
|
||||
add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE})
|
||||
target_link_libraries(${PROGNAME}
|
||||
keepassx_core
|
||||
Qocoa
|
||||
qjson
|
||||
qhttpserver
|
||||
${QT_QTCORE_LIBRARY}
|
||||
${QT_QTGUI_LIBRARY}
|
||||
${QT_QTNETWORK_LIBRARY}
|
||||
${GCRYPT_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(${PROGNAME} ${QT_QTDBUS_LIBRARY})
|
||||
endif()
|
||||
if(APPLE)
|
||||
set_target_properties(${PROGNAME} PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit")
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)
|
||||
|
||||
|
@ -94,6 +94,7 @@ void Config::init(const QString& fileName)
|
||||
m_defaults.insert("AutoSaveOnExit", false);
|
||||
m_defaults.insert("ShowToolbar", true);
|
||||
m_defaults.insert("MinimizeOnCopy", false);
|
||||
m_defaults.insert("ReloadBehavior", 0 /*always ask*/);
|
||||
m_defaults.insert("security/clearclipboard", true);
|
||||
m_defaults.insert("security/clearclipboardtimeout", 10);
|
||||
m_defaults.insert("security/lockdatabaseidle", false);
|
||||
|
@ -228,6 +228,11 @@ bool Database::verifyKey(const CompositeKey& key) const
|
||||
return (m_data.key.rawKey() == key.rawKey());
|
||||
}
|
||||
|
||||
CompositeKey Database::key() const
|
||||
{
|
||||
return m_data.key;
|
||||
}
|
||||
|
||||
void Database::createRecycleBin()
|
||||
{
|
||||
Group* recycleBin = Group::createRecycleBin();
|
||||
|
@ -99,6 +99,7 @@ public:
|
||||
void setKey(const CompositeKey& key);
|
||||
bool hasKey() const;
|
||||
bool verifyKey(const CompositeKey& key) const;
|
||||
CompositeKey key() const;
|
||||
void recycleEntry(Entry* entry);
|
||||
void recycleGroup(Group* group);
|
||||
void setEmitModified(bool value);
|
||||
|
@ -52,6 +52,11 @@ QString EntryAttributes::value(const QString& key) const
|
||||
return m_attributes.value(key);
|
||||
}
|
||||
|
||||
bool EntryAttributes::contains(const QString &key) const
|
||||
{
|
||||
return m_attributes.contains(key);
|
||||
}
|
||||
|
||||
bool EntryAttributes::isProtected(const QString& key) const
|
||||
{
|
||||
return m_protectedAttributes.contains(key);
|
||||
|
@ -34,6 +34,7 @@ public:
|
||||
QList<QString> keys() const;
|
||||
QList<QString> customKeys();
|
||||
QString value(const QString& key) const;
|
||||
bool contains(const QString& key) const;
|
||||
bool isProtected(const QString& key) const;
|
||||
void set(const QString& key, const QString& value, bool protect = false);
|
||||
void remove(const QString& key);
|
||||
|
@ -89,6 +89,12 @@ Uuid Uuid::fromBase64(const QString& str)
|
||||
return Uuid(data);
|
||||
}
|
||||
|
||||
Uuid Uuid::fromHex(const QString& str)
|
||||
{
|
||||
QByteArray data = QByteArray::fromHex(str.toAscii());
|
||||
return Uuid(data);
|
||||
}
|
||||
|
||||
uint qHash(const Uuid& key)
|
||||
{
|
||||
return qHash(key.toByteArray());
|
||||
|
@ -37,6 +37,7 @@ public:
|
||||
bool operator!=(const Uuid& other) const;
|
||||
static const int Length;
|
||||
static Uuid fromBase64(const QString& str);
|
||||
static Uuid fromHex(const QString& str);
|
||||
|
||||
private:
|
||||
QByteArray m_data;
|
||||
|
@ -21,13 +21,16 @@
|
||||
#include "crypto/SymmetricCipherGcrypt.h"
|
||||
#include "crypto/SymmetricCipherSalsa20.h"
|
||||
|
||||
SymmetricCipher::SymmetricCipher()
|
||||
: m_backend(0)
|
||||
{
|
||||
}
|
||||
|
||||
SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv)
|
||||
: m_backend(createBackend(algo, mode, direction))
|
||||
: m_backend(0)
|
||||
{
|
||||
m_backend->init();
|
||||
m_backend->setKey(key);
|
||||
m_backend->setIv(iv);
|
||||
init(algo, mode, direction, key, iv);
|
||||
}
|
||||
|
||||
SymmetricCipher::~SymmetricCipher()
|
||||
@ -56,12 +59,31 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith
|
||||
}
|
||||
}
|
||||
|
||||
void SymmetricCipher::init(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv)
|
||||
{
|
||||
Q_ASSERT(!m_backend);
|
||||
m_backend.reset(createBackend(algo, mode, direction));
|
||||
m_backend->init();
|
||||
m_backend->setKey(key);
|
||||
if (!iv.isNull())
|
||||
m_backend->setIv(iv);
|
||||
}
|
||||
|
||||
void SymmetricCipher::reset()
|
||||
{
|
||||
Q_ASSERT(m_backend);
|
||||
m_backend->reset();
|
||||
}
|
||||
|
||||
void SymmetricCipher::setIv(const QByteArray &iv)
|
||||
{
|
||||
Q_ASSERT(m_backend);
|
||||
m_backend->setIv(iv);
|
||||
}
|
||||
|
||||
int SymmetricCipher::blockSize() const
|
||||
{
|
||||
Q_ASSERT(m_backend);
|
||||
return m_backend->blockSize();
|
||||
}
|
||||
|
@ -47,31 +47,42 @@ public:
|
||||
Encrypt
|
||||
};
|
||||
|
||||
SymmetricCipher();
|
||||
SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv = QByteArray());
|
||||
~SymmetricCipher();
|
||||
|
||||
void init(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction,
|
||||
const QByteArray &key, const QByteArray &iv = QByteArray());
|
||||
bool isValid() const {
|
||||
return m_backend != 0;
|
||||
}
|
||||
|
||||
inline QByteArray process(const QByteArray& data) {
|
||||
Q_ASSERT(m_backend);
|
||||
return m_backend->process(data);
|
||||
}
|
||||
|
||||
inline void processInPlace(QByteArray& data) {
|
||||
Q_ASSERT(m_backend);
|
||||
m_backend->processInPlace(data);
|
||||
}
|
||||
|
||||
inline void processInPlace(QByteArray& data, quint64 rounds) {
|
||||
Q_ASSERT(rounds > 0);
|
||||
Q_ASSERT(m_backend);
|
||||
m_backend->processInPlace(data, rounds);
|
||||
}
|
||||
|
||||
void reset();
|
||||
void setIv(const QByteArray& iv);
|
||||
int blockSize() const;
|
||||
|
||||
private:
|
||||
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
|
||||
const QScopedPointer<SymmetricCipherBackend> m_backend;
|
||||
QScopedPointer<SymmetricCipherBackend> m_backend;
|
||||
|
||||
Q_DISABLE_COPY(SymmetricCipher)
|
||||
};
|
||||
|
@ -93,14 +93,26 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
||||
openDatabase();
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::enterKey(const CompositeKey& masterKey)
|
||||
{
|
||||
if (masterKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
openDatabase(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::openDatabase()
|
||||
{
|
||||
KeePass2Reader reader;
|
||||
CompositeKey masterKey = databaseKey();
|
||||
if (masterKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
openDatabase(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::openDatabase(const CompositeKey& masterKey)
|
||||
{
|
||||
KeePass2Reader reader;
|
||||
QFile file(m_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
// TODO: error message
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
~DatabaseOpenWidget();
|
||||
void load(const QString& filename);
|
||||
void enterKey(const QString& pw, const QString& keyFile);
|
||||
void enterKey(const CompositeKey& masterKey);
|
||||
Database* database();
|
||||
|
||||
Q_SIGNALS:
|
||||
@ -49,6 +50,7 @@ protected:
|
||||
|
||||
protected Q_SLOTS:
|
||||
virtual void openDatabase();
|
||||
void openDatabase(const CompositeKey& masterKey);
|
||||
void reject();
|
||||
|
||||
private Q_SLOTS:
|
||||
|
@ -19,6 +19,9 @@
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QTabWidget>
|
||||
#include <QtCore/QFileSystemWatcher>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
@ -45,7 +48,8 @@ DatabaseManagerStruct::DatabaseManagerStruct()
|
||||
const int DatabaseTabWidget::LastDatabasesCount = 5;
|
||||
|
||||
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
: QTabWidget(parent)
|
||||
: QTabWidget(parent),
|
||||
m_fileWatcher(new QFileSystemWatcher(this))
|
||||
{
|
||||
DragTabBar* tabBar = new DragTabBar(this);
|
||||
tabBar->setDrawBase(false);
|
||||
@ -53,6 +57,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
|
||||
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabase(int)));
|
||||
connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType()));
|
||||
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
}
|
||||
|
||||
DatabaseTabWidget::~DatabaseTabWidget()
|
||||
@ -66,16 +71,7 @@ DatabaseTabWidget::~DatabaseTabWidget()
|
||||
|
||||
void DatabaseTabWidget::toggleTabbar()
|
||||
{
|
||||
if (count() > 1) {
|
||||
if (!tabBar()->isVisible()) {
|
||||
tabBar()->show();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tabBar()->isVisible()) {
|
||||
tabBar()->hide();
|
||||
}
|
||||
}
|
||||
tabBar()->setVisible(count() > 1);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::newDatabase()
|
||||
@ -101,7 +97,7 @@ void DatabaseTabWidget::openDatabase()
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
const QString& keyFile)
|
||||
const QString& keyFile, const CompositeKey& key, int index)
|
||||
{
|
||||
QFileInfo fileInfo(fileName);
|
||||
QString canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
@ -145,12 +141,17 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
dbStruct.canonicalFilePath = canonicalFilePath;
|
||||
dbStruct.fileName = fileInfo.fileName();
|
||||
dbStruct.lastModified = fileInfo.lastModified();
|
||||
|
||||
insertDatabase(db, dbStruct);
|
||||
insertDatabase(db, dbStruct, index);
|
||||
m_fileWatcher->addPath(dbStruct.filePath);
|
||||
|
||||
updateLastDatabases(dbStruct.filePath);
|
||||
updateRecentDatabases(dbStruct.filePath);
|
||||
|
||||
if (!pw.isNull() || !keyFile.isEmpty()) {
|
||||
if (!key.isEmpty()) {
|
||||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, key);
|
||||
}
|
||||
else if (!pw.isNull() || !keyFile.isEmpty()) {
|
||||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile);
|
||||
}
|
||||
else {
|
||||
@ -177,6 +178,112 @@ void DatabaseTabWidget::importKeePass1Database()
|
||||
dbStruct.dbWidget->switchToImportKeepass1(fileName);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::fileChanged(const QString &fileName)
|
||||
{
|
||||
const bool wasEmpty = m_changedFiles.isEmpty();
|
||||
m_changedFiles.insert(fileName);
|
||||
if (wasEmpty && !m_changedFiles.isEmpty())
|
||||
QTimer::singleShot(200, this, SLOT(checkReloadDatabases()));
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::expectFileChange(const DatabaseManagerStruct& dbStruct)
|
||||
{
|
||||
if (dbStruct.filePath.isEmpty())
|
||||
return;
|
||||
m_expectedFileChanges.insert(dbStruct.filePath);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::unexpectFileChange(DatabaseManagerStruct& dbStruct)
|
||||
{
|
||||
if (dbStruct.filePath.isEmpty())
|
||||
return;
|
||||
m_expectedFileChanges.remove(dbStruct.filePath);
|
||||
dbStruct.lastModified = QFileInfo(dbStruct.filePath).lastModified();
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::checkReloadDatabases()
|
||||
{
|
||||
QSet<QString> changedFiles;
|
||||
|
||||
changedFiles = m_changedFiles.subtract(m_expectedFileChanges);
|
||||
m_changedFiles.clear();
|
||||
|
||||
if (changedFiles.isEmpty())
|
||||
return;
|
||||
|
||||
Q_FOREACH (DatabaseManagerStruct dbStruct, m_dbList) {
|
||||
QString filePath = dbStruct.filePath;
|
||||
Database * db = dbStruct.dbWidget->database();
|
||||
|
||||
if (!changedFiles.contains(filePath))
|
||||
continue;
|
||||
|
||||
QFileInfo fi(filePath);
|
||||
QDateTime lastModified = fi.lastModified();
|
||||
if (dbStruct.lastModified == lastModified)
|
||||
continue;
|
||||
|
||||
DatabaseWidget::Mode mode = dbStruct.dbWidget->currentMode();
|
||||
if (mode == DatabaseWidget::None || mode == DatabaseWidget::LockedMode || !db->hasKey())
|
||||
continue;
|
||||
|
||||
ReloadBehavior reloadBehavior = ReloadBehavior(config()->get("ReloadBehavior").toInt());
|
||||
if ( (reloadBehavior == AlwaysAsk)
|
||||
|| (reloadBehavior == ReloadUnmodified && mode == DatabaseWidget::EditMode)
|
||||
|| (reloadBehavior == ReloadUnmodified && dbStruct.modified)) {
|
||||
int res = QMessageBox::warning(this, fi.exists() ? tr("Database file changed") : tr("Database file removed"),
|
||||
tr("Do you want to discard your changes and reload?"),
|
||||
QMessageBox::Yes|QMessageBox::No);
|
||||
if (res == QMessageBox::No)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (fi.exists()) {
|
||||
//Ignore/cancel all edits
|
||||
dbStruct.dbWidget->switchToView(false);
|
||||
dbStruct.modified = false;
|
||||
|
||||
//Save current group/entry
|
||||
Uuid currentGroup;
|
||||
if (Group* group = dbStruct.dbWidget->currentGroup())
|
||||
currentGroup = group->uuid();
|
||||
Uuid currentEntry;
|
||||
if (Entry* entry = dbStruct.dbWidget->entryView()->currentEntry())
|
||||
currentEntry = entry->uuid();
|
||||
QString searchText = dbStruct.dbWidget->searchText();
|
||||
bool caseSensitive = dbStruct.dbWidget->caseSensitiveSearch();
|
||||
bool allGroups = dbStruct.dbWidget->isAllGroupsSearch();
|
||||
|
||||
//Reload updated db
|
||||
CompositeKey key = db->key();
|
||||
int tabPos = databaseIndex(db);
|
||||
closeDatabase(db);
|
||||
openDatabase(filePath, QString(), QString(), key, tabPos);
|
||||
|
||||
//Restore current group/entry
|
||||
dbStruct = indexDatabaseManagerStruct(count() - 1);
|
||||
if (dbStruct.dbWidget && dbStruct.dbWidget->currentMode() == DatabaseWidget::ViewMode) {
|
||||
Database * db = dbStruct.dbWidget->database();
|
||||
if (!currentGroup.isNull())
|
||||
if (Group* group = db->resolveGroup(currentGroup))
|
||||
dbStruct.dbWidget->groupView()->setCurrentGroup(group);
|
||||
if (!searchText.isEmpty())
|
||||
dbStruct.dbWidget->search(searchText, caseSensitive, allGroups);
|
||||
if (!currentEntry.isNull())
|
||||
if (Entry* entry = db->resolveEntry(currentEntry))
|
||||
dbStruct.dbWidget->entryView()->setCurrentEntry(entry);
|
||||
}
|
||||
} else {
|
||||
//Ignore/cancel all edits
|
||||
dbStruct.dbWidget->switchToView(false);
|
||||
dbStruct.modified = false;
|
||||
|
||||
//Close database
|
||||
closeDatabase(dbStruct.dbWidget->database());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
{
|
||||
Q_ASSERT(db);
|
||||
@ -231,6 +338,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
|
||||
|
||||
int index = databaseIndex(db);
|
||||
|
||||
m_fileWatcher->removePath(dbStruct.filePath);
|
||||
removeTab(index);
|
||||
toggleTabbar();
|
||||
m_dbList.remove(db);
|
||||
@ -244,6 +352,13 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
|
||||
|
||||
bool DatabaseTabWidget::closeAllDatabases()
|
||||
{
|
||||
QStringList reloadDatabases;
|
||||
if (config()->get("AutoReopenLastDatabases", false).toBool()) {
|
||||
for (int i = 0; i < count(); i ++)
|
||||
reloadDatabases << indexDatabaseManagerStruct(i).filePath;
|
||||
}
|
||||
config()->set("LastOpenDatabases", reloadDatabases);
|
||||
|
||||
while (!m_dbList.isEmpty()) {
|
||||
if (!closeDatabase()) {
|
||||
return false;
|
||||
@ -259,12 +374,16 @@ void DatabaseTabWidget::saveDatabase(Database* db)
|
||||
if (dbStruct.saveToFilename) {
|
||||
bool result = false;
|
||||
|
||||
expectFileChange(dbStruct);
|
||||
|
||||
QSaveFile saveFile(dbStruct.filePath);
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
m_writer.writeDatabase(&saveFile, db);
|
||||
result = saveFile.commit();
|
||||
}
|
||||
|
||||
unexpectFileChange(dbStruct);
|
||||
|
||||
if (result) {
|
||||
dbStruct.modified = false;
|
||||
updateTabName(db);
|
||||
@ -282,12 +401,12 @@ void DatabaseTabWidget::saveDatabase(Database* db)
|
||||
void DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
{
|
||||
DatabaseManagerStruct& dbStruct = m_dbList[db];
|
||||
QString oldFileName;
|
||||
QString oldFilePath;
|
||||
if (dbStruct.saveToFilename) {
|
||||
oldFileName = dbStruct.filePath;
|
||||
oldFilePath = dbStruct.filePath;
|
||||
}
|
||||
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
|
||||
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"));
|
||||
oldFilePath, tr("KeePass 2 Database").append(" (*.kdbx)"));
|
||||
if (!fileName.isEmpty()) {
|
||||
bool result = false;
|
||||
|
||||
@ -298,15 +417,18 @@ void DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
}
|
||||
|
||||
if (result) {
|
||||
m_fileWatcher->removePath(oldFilePath);
|
||||
dbStruct.modified = false;
|
||||
dbStruct.saveToFilename = true;
|
||||
QFileInfo fileInfo(fileName);
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
dbStruct.fileName = fileInfo.fileName();
|
||||
dbStruct.lastModified = fileInfo.lastModified();
|
||||
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
|
||||
updateTabName(db);
|
||||
updateLastDatabases(dbStruct.filePath);
|
||||
updateRecentDatabases(dbStruct.filePath);
|
||||
m_fileWatcher->addPath(dbStruct.filePath);
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
@ -477,14 +599,13 @@ Database* DatabaseTabWidget::databaseFromDatabaseWidget(DatabaseWidget* dbWidget
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct)
|
||||
void DatabaseTabWidget::insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct, int index)
|
||||
{
|
||||
m_dbList.insert(db, dbStruct);
|
||||
|
||||
addTab(dbStruct.dbWidget, "");
|
||||
index = insertTab(index, dbStruct.dbWidget, "");
|
||||
toggleTabbar();
|
||||
updateTabName(db);
|
||||
int index = databaseIndex(db);
|
||||
setCurrentIndex(index);
|
||||
connectDatabase(db);
|
||||
connect(dbStruct.dbWidget, SIGNAL(closeRequest()), SLOT(closeDatabaseFromSender()));
|
||||
@ -552,7 +673,7 @@ void DatabaseTabWidget::modified()
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::updateLastDatabases(const QString& filename)
|
||||
void DatabaseTabWidget::updateRecentDatabases(const QString& filename)
|
||||
{
|
||||
if (!config()->get("RememberLastDatabases").toBool()) {
|
||||
config()->set("LastDatabases", QVariant());
|
||||
|
@ -18,8 +18,10 @@
|
||||
#ifndef KEEPASSX_DATABASETABWIDGET_H
|
||||
#define KEEPASSX_DATABASETABWIDGET_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QHash>
|
||||
#include <QTabWidget>
|
||||
#include <QSet>
|
||||
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
@ -27,6 +29,7 @@
|
||||
class DatabaseWidget;
|
||||
class DatabaseOpenWidget;
|
||||
class QFile;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
struct DatabaseManagerStruct
|
||||
{
|
||||
@ -39,6 +42,7 @@ struct DatabaseManagerStruct
|
||||
bool saveToFilename;
|
||||
bool modified;
|
||||
bool readOnly;
|
||||
QDateTime lastModified;
|
||||
};
|
||||
|
||||
Q_DECLARE_TYPEINFO(DatabaseManagerStruct, Q_MOVABLE_TYPE);
|
||||
@ -51,16 +55,25 @@ public:
|
||||
explicit DatabaseTabWidget(QWidget* parent = Q_NULLPTR);
|
||||
~DatabaseTabWidget();
|
||||
void openDatabase(const QString& fileName, const QString& pw = QString(),
|
||||
const QString& keyFile = QString());
|
||||
const QString& keyFile = QString(), const CompositeKey& key = CompositeKey(),
|
||||
int index = -1);
|
||||
DatabaseWidget* currentDatabaseWidget();
|
||||
bool hasLockableDatabases();
|
||||
|
||||
static const int LastDatabasesCount;
|
||||
|
||||
enum ReloadBehavior {
|
||||
AlwaysAsk,
|
||||
ReloadUnmodified,
|
||||
IgnoreAll
|
||||
};
|
||||
|
||||
public Q_SLOTS:
|
||||
void newDatabase();
|
||||
void openDatabase();
|
||||
void importKeePass1Database();
|
||||
void fileChanged(const QString& fileName);
|
||||
void checkReloadDatabases();
|
||||
void saveDatabase(int index = -1);
|
||||
void saveDatabaseAs(int index = -1);
|
||||
bool closeDatabase(int index = -1);
|
||||
@ -93,12 +106,17 @@ private:
|
||||
Database* indexDatabase(int index);
|
||||
DatabaseManagerStruct indexDatabaseManagerStruct(int index);
|
||||
Database* databaseFromDatabaseWidget(DatabaseWidget* dbWidget);
|
||||
void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct);
|
||||
void updateLastDatabases(const QString& filename);
|
||||
void insertDatabase(Database* db, const DatabaseManagerStruct& dbStruct, int index = -1);
|
||||
void updateRecentDatabases(const QString& filename);
|
||||
void connectDatabase(Database* newDb, Database* oldDb = Q_NULLPTR);
|
||||
void expectFileChange(const DatabaseManagerStruct& dbStruct);
|
||||
void unexpectFileChange(DatabaseManagerStruct& dbStruct);
|
||||
|
||||
KeePass2Writer m_writer;
|
||||
QHash<Database*, DatabaseManagerStruct> m_dbList;
|
||||
QSet<QString> m_changedFiles;
|
||||
QSet<QString> m_expectedFileChanges;
|
||||
QFileSystemWatcher* m_fileWatcher;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASETABWIDGET_H
|
||||
|
@ -51,6 +51,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
, m_newGroup(Q_NULLPTR)
|
||||
, m_newEntry(Q_NULLPTR)
|
||||
, m_newParent(Q_NULLPTR)
|
||||
, m_searchAllGroups(false)
|
||||
, m_searchSensitivity(Qt::CaseInsensitive)
|
||||
{
|
||||
m_searchUi->setupUi(m_searchWidget);
|
||||
|
||||
@ -90,8 +92,9 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
closeAction->setIcon(closeIcon);
|
||||
m_searchUi->closeSearchButton->setDefaultAction(closeAction);
|
||||
m_searchUi->closeSearchButton->setShortcut(Qt::Key_Escape);
|
||||
int iconsize = style()->pixelMetric(QStyle::PM_SmallIconSize);
|
||||
m_searchUi->closeSearchButton->setIconSize(QSize(iconsize,iconsize));
|
||||
m_searchWidget->hide();
|
||||
m_searchUi->caseSensitiveCheckBox->setVisible(false);
|
||||
|
||||
QVBoxLayout* vLayout = new QVBoxLayout(rightHandSideWidget);
|
||||
vLayout->setMargin(0);
|
||||
@ -151,11 +154,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
connect(m_keepass1OpenWidget, SIGNAL(editFinished(bool)), SLOT(openDatabase(bool)));
|
||||
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
|
||||
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
|
||||
connect(m_searchUi->searchEdit, SIGNAL(textChanged(QString)), this, SLOT(startSearchTimer()));
|
||||
connect(m_searchUi->caseSensitiveCheckBox, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
|
||||
connect(m_searchUi->searchCurrentRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
|
||||
connect(m_searchUi->searchRootRadioButton, SIGNAL(toggled(bool)), this, SLOT(startSearch()));
|
||||
connect(m_searchUi->searchEdit, SIGNAL(returnPressed()), m_entryView, SLOT(setFocus()));
|
||||
connect(m_searchUi->searchResults, SIGNAL(linkActivated(QString)), this, SLOT(onLinkActivated(QString)));
|
||||
connect(m_searchTimer, SIGNAL(timeout()), this, SLOT(search()));
|
||||
connect(closeAction, SIGNAL(triggered()), this, SLOT(closeSearch()));
|
||||
|
||||
@ -174,7 +173,8 @@ DatabaseWidget::Mode DatabaseWidget::currentMode()
|
||||
else if (currentWidget() == m_mainWidget) {
|
||||
return DatabaseWidget::ViewMode;
|
||||
}
|
||||
else if (currentWidget() == m_unlockDatabaseWidget) {
|
||||
else if (currentWidget() == m_unlockDatabaseWidget ||
|
||||
currentWidget() == m_databaseOpenWidget) {
|
||||
return DatabaseWidget::LockedMode;
|
||||
}
|
||||
else {
|
||||
@ -626,6 +626,13 @@ void DatabaseWidget::switchToOpenDatabase(const QString& fileName, const QString
|
||||
m_databaseOpenWidget->enterKey(password, keyFile);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToOpenDatabase(const QString &fileName, const CompositeKey& masterKey)
|
||||
{
|
||||
updateFilename(fileName);
|
||||
switchToOpenDatabase(fileName);
|
||||
m_databaseOpenWidget->enterKey(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
|
||||
{
|
||||
updateFilename(fileName);
|
||||
@ -633,84 +640,117 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
|
||||
setCurrentWidget(m_keepass1OpenWidget);
|
||||
}
|
||||
|
||||
void DatabaseWidget::toggleSearch()
|
||||
void DatabaseWidget::search(const QString& searchString, bool caseSensitive, bool allGroups)
|
||||
{
|
||||
if (m_entryView->inEntryListMode()) {
|
||||
closeSearch();
|
||||
m_searchSensitivity = caseSensitive ? Qt::CaseSensitive
|
||||
: Qt::CaseInsensitive;
|
||||
m_searchAllGroups = allGroups;
|
||||
search(searchString);
|
||||
}
|
||||
|
||||
void DatabaseWidget::search(const QString& text)
|
||||
{
|
||||
if (text.isEmpty()) {
|
||||
if (m_entryView->inEntryListMode())
|
||||
closeSearch();
|
||||
}
|
||||
else if (m_entryView->inEntryListMode()) {
|
||||
m_searchText = text;
|
||||
startSearchTimer();
|
||||
}
|
||||
else {
|
||||
showSearch();
|
||||
showSearch(text);
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseWidget::caseSensitiveSearch() const
|
||||
{
|
||||
return m_searchSensitivity == Qt::CaseSensitive;
|
||||
}
|
||||
|
||||
void DatabaseWidget::setCaseSensitiveSearch(bool caseSensitive)
|
||||
{
|
||||
if (caseSensitive != caseSensitiveSearch()) {
|
||||
m_searchSensitivity = caseSensitive ? Qt::CaseSensitive
|
||||
: Qt::CaseInsensitive;
|
||||
if (m_entryView->inEntryListMode())
|
||||
startSearchTimer();
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isAllGroupsSearch() const
|
||||
{
|
||||
return m_searchAllGroups;
|
||||
}
|
||||
|
||||
bool DatabaseWidget::canChooseSearchScope() const
|
||||
{
|
||||
return currentGroup() != m_db->rootGroup();
|
||||
}
|
||||
|
||||
Group*DatabaseWidget::currentGroup() const
|
||||
{
|
||||
return m_entryView->inEntryListMode() ? m_lastGroup
|
||||
: m_groupView->currentGroup();
|
||||
}
|
||||
|
||||
void DatabaseWidget::setAllGroupsSearch(bool allGroups)
|
||||
{
|
||||
if (allGroups != isAllGroupsSearch()) {
|
||||
m_searchAllGroups = allGroups;
|
||||
if (m_entryView->inEntryListMode())
|
||||
startSearchTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::closeSearch()
|
||||
{
|
||||
Q_ASSERT(m_lastGroup);
|
||||
m_searchTimer->stop();
|
||||
m_groupView->setCurrentGroup(m_lastGroup);
|
||||
}
|
||||
|
||||
void DatabaseWidget::showSearch()
|
||||
void DatabaseWidget::showSearch(const QString & searchString)
|
||||
{
|
||||
m_searchUi->searchEdit->blockSignals(true);
|
||||
m_searchUi->searchEdit->clear();
|
||||
m_searchUi->searchEdit->blockSignals(false);
|
||||
|
||||
m_searchUi->searchCurrentRadioButton->blockSignals(true);
|
||||
m_searchUi->searchRootRadioButton->blockSignals(true);
|
||||
m_searchUi->searchRootRadioButton->setChecked(true);
|
||||
m_searchUi->searchCurrentRadioButton->blockSignals(false);
|
||||
m_searchUi->searchRootRadioButton->blockSignals(false);
|
||||
|
||||
m_searchText = searchString;
|
||||
m_lastGroup = m_groupView->currentGroup();
|
||||
|
||||
Q_ASSERT(m_lastGroup);
|
||||
|
||||
if (m_lastGroup == m_db->rootGroup()) {
|
||||
m_searchUi->optionsWidget->hide();
|
||||
m_searchUi->searchCurrentRadioButton->hide();
|
||||
m_searchUi->searchRootRadioButton->hide();
|
||||
}
|
||||
else {
|
||||
m_searchUi->optionsWidget->show();
|
||||
m_searchUi->searchCurrentRadioButton->show();
|
||||
m_searchUi->searchRootRadioButton->show();
|
||||
m_searchUi->searchCurrentRadioButton->setText(tr("Current group")
|
||||
.append(" (")
|
||||
.append(m_lastGroup->name())
|
||||
.append(")"));
|
||||
}
|
||||
m_groupView->setCurrentIndex(QModelIndex());
|
||||
|
||||
m_searchWidget->show();
|
||||
search();
|
||||
m_searchUi->searchEdit->setFocus();
|
||||
}
|
||||
|
||||
void DatabaseWidget::onLinkActivated(const QString& link)
|
||||
{
|
||||
if (link == "searchAll")
|
||||
setAllGroupsSearch(true);
|
||||
else if (link == "searchCurrent")
|
||||
setAllGroupsSearch(false);
|
||||
}
|
||||
|
||||
void DatabaseWidget::search()
|
||||
{
|
||||
Q_ASSERT(m_lastGroup);
|
||||
|
||||
Group* searchGroup;
|
||||
if (m_searchUi->searchCurrentRadioButton->isChecked()) {
|
||||
searchGroup = m_lastGroup;
|
||||
}
|
||||
else if (m_searchUi->searchRootRadioButton->isChecked()) {
|
||||
searchGroup = m_db->rootGroup();
|
||||
}
|
||||
else {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
Group* searchGroup = m_searchAllGroups ? m_db->rootGroup()
|
||||
: m_lastGroup;
|
||||
QList<Entry*> searchResult = searchGroup->search(m_searchText, m_searchSensitivity);
|
||||
|
||||
Qt::CaseSensitivity sensitivity;
|
||||
if (m_searchUi->caseSensitiveCheckBox->isChecked()) {
|
||||
sensitivity = Qt::CaseSensitive;
|
||||
QString message;
|
||||
switch(searchResult.count()) {
|
||||
case 0: message = tr("No result found"); break;
|
||||
case 1: message = tr("1 result found"); break;
|
||||
default: message = tr("%1 results found").arg(searchResult.count()); break;
|
||||
}
|
||||
else {
|
||||
sensitivity = Qt::CaseInsensitive;
|
||||
}
|
||||
QList<Entry*> searchResult = searchGroup->search(m_searchUi->searchEdit->text(), sensitivity);
|
||||
|
||||
if (searchGroup != m_db->rootGroup())
|
||||
message += tr(" in \"%1\". <a href='searchAll'>Search all groups...</a>").arg(searchGroup->name());
|
||||
else if (m_lastGroup != m_db->rootGroup())
|
||||
message += tr(". <a href='searchCurrent'>Search in \"%1\"...</a>").arg(m_lastGroup->name());
|
||||
else
|
||||
message += tr(".");
|
||||
m_searchUi->searchResults->setText(message);
|
||||
|
||||
m_entryView->setEntryList(searchResult);
|
||||
}
|
||||
@ -725,6 +765,9 @@ void DatabaseWidget::startSearchTimer()
|
||||
|
||||
void DatabaseWidget::startSearch()
|
||||
{
|
||||
if (!isInSearchMode())
|
||||
return;
|
||||
|
||||
if (!m_searchTimer->isActive()) {
|
||||
m_searchTimer->stop();
|
||||
}
|
||||
@ -753,11 +796,16 @@ bool DatabaseWidget::canDeleteCurrentGoup()
|
||||
return !isRootGroup && !isRecycleBin;
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isInSearchMode()
|
||||
bool DatabaseWidget::isInSearchMode() const
|
||||
{
|
||||
return m_entryView->inEntryListMode();
|
||||
}
|
||||
|
||||
QString DatabaseWidget::searchText() const
|
||||
{
|
||||
return m_entryView->inEntryListMode() ? m_searchText : QString();
|
||||
}
|
||||
|
||||
void DatabaseWidget::clearLastGroup(Group* group)
|
||||
{
|
||||
if (group) {
|
||||
|
@ -39,6 +39,7 @@ class KeePass1OpenWidget;
|
||||
class QFile;
|
||||
class QMenu;
|
||||
class UnlockDatabaseWidget;
|
||||
class CompositeKey;
|
||||
|
||||
namespace Ui {
|
||||
class SearchWidget;
|
||||
@ -64,13 +65,19 @@ public:
|
||||
Database* database();
|
||||
bool dbHasKey();
|
||||
bool canDeleteCurrentGoup();
|
||||
bool isInSearchMode();
|
||||
bool isInSearchMode() const;
|
||||
QString searchText() const;
|
||||
bool caseSensitiveSearch() const;
|
||||
bool isAllGroupsSearch() const;
|
||||
bool canChooseSearchScope() const;
|
||||
Group* currentGroup() const;
|
||||
int addWidget(QWidget* w);
|
||||
void setCurrentIndex(int index);
|
||||
void setCurrentWidget(QWidget* widget);
|
||||
DatabaseWidget::Mode currentMode();
|
||||
void lock();
|
||||
void updateFilename(const QString& filename);
|
||||
void search(const QString & searchString, bool caseSensitive, bool allGroups);
|
||||
|
||||
Q_SIGNALS:
|
||||
void closeRequest();
|
||||
@ -103,15 +110,20 @@ public Q_SLOTS:
|
||||
void switchToDatabaseSettings();
|
||||
void switchToOpenDatabase(const QString& fileName);
|
||||
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
||||
void switchToOpenDatabase(const QString &fileName, const CompositeKey &masterKey);
|
||||
void switchToImportKeepass1(const QString& fileName);
|
||||
void toggleSearch();
|
||||
void switchToView(bool accepted);
|
||||
void search(const QString & searchString);
|
||||
void setCaseSensitiveSearch(bool caseSensitive);
|
||||
void setAllGroupsSearch(bool allGroups);
|
||||
void emitGroupContextMenuRequested(const QPoint& pos);
|
||||
void emitEntryContextMenuRequested(const QPoint& pos);
|
||||
|
||||
private Q_SLOTS:
|
||||
void entryActivationSignalReceived(Entry* entry, EntryModel::ModelColumn column);
|
||||
void onLinkActivated(const QString& link);
|
||||
void showSearch(const QString & searchString = QString());
|
||||
void switchBackToEntryEdit();
|
||||
void switchToView(bool accepted);
|
||||
void switchToHistoryView(Entry* entry);
|
||||
void switchToEntryEdit(Entry* entry);
|
||||
void switchToEntryEdit(Entry* entry, bool create);
|
||||
@ -124,7 +136,6 @@ private Q_SLOTS:
|
||||
void search();
|
||||
void startSearch();
|
||||
void startSearchTimer();
|
||||
void showSearch();
|
||||
void closeSearch();
|
||||
|
||||
private:
|
||||
@ -151,6 +162,9 @@ private:
|
||||
QTimer* m_searchTimer;
|
||||
QWidget* widgetBeforeLock;
|
||||
QString m_filename;
|
||||
QString m_searchText;
|
||||
bool m_searchAllGroups;
|
||||
Qt::CaseSensitivity m_searchSensitivity;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASEWIDGET_H
|
||||
|
@ -37,6 +37,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
|
||||
, m_ui(new Ui::EditWidgetIcons())
|
||||
, m_defaultIconModel(new DefaultIconModel(this))
|
||||
, m_customIconModel(new CustomIconModel(this))
|
||||
, m_networkAccessMngr(new QNetworkAccessManager(this))
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
@ -53,6 +54,11 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
|
||||
this, SLOT(updateWidgetsCustomIcons(bool)));
|
||||
connect(m_ui->addButton, SIGNAL(clicked()), SLOT(addCustomIcon()));
|
||||
connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon()));
|
||||
connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon()));
|
||||
connect(m_networkAccessMngr, SIGNAL(finished(QNetworkReply*)),
|
||||
this, SLOT(onRequestFinished(QNetworkReply*)) );
|
||||
|
||||
m_ui->faviconButton->setVisible(false);
|
||||
}
|
||||
|
||||
EditWidgetIcons::~EditWidgetIcons()
|
||||
@ -89,13 +95,14 @@ IconStruct EditWidgetIcons::save()
|
||||
return iconStruct;
|
||||
}
|
||||
|
||||
void EditWidgetIcons::load(Uuid currentUuid, Database* database, IconStruct iconStruct)
|
||||
void EditWidgetIcons::load(Uuid currentUuid, Database* database, IconStruct iconStruct, const QString &url)
|
||||
{
|
||||
Q_ASSERT(database);
|
||||
Q_ASSERT(!currentUuid.isNull());
|
||||
|
||||
m_database = database;
|
||||
m_currentUuid = currentUuid;
|
||||
setUrl(url);
|
||||
|
||||
m_customIconModel->setIcons(database->metadata()->customIcons(),
|
||||
database->metadata()->customIconsOrder());
|
||||
@ -119,6 +126,76 @@ void EditWidgetIcons::load(Uuid currentUuid, Database* database, IconStruct icon
|
||||
}
|
||||
}
|
||||
|
||||
void EditWidgetIcons::setUrl(const QString &url)
|
||||
{
|
||||
m_url = url;
|
||||
m_ui->faviconButton->setVisible(!url.isEmpty());
|
||||
abortFaviconDownload();
|
||||
}
|
||||
|
||||
static QStringList getHost(QUrl url, const QString& path)
|
||||
{
|
||||
QStringList hosts;
|
||||
|
||||
QString host = url.host();
|
||||
for(;;) {
|
||||
QString s = host;
|
||||
if (url.port() >= 0)
|
||||
s += ":" + QString::number(url.port());
|
||||
hosts << s + path;
|
||||
|
||||
const int first_dot = host.indexOf( '.' );
|
||||
const int last_dot = host.lastIndexOf( '.' );
|
||||
if( ( first_dot != -1 ) && ( last_dot != -1 ) && ( first_dot != last_dot ) )
|
||||
host.remove( 0, first_dot + 1 );
|
||||
else
|
||||
break;
|
||||
}
|
||||
return hosts;
|
||||
}
|
||||
|
||||
void EditWidgetIcons::downloadFavicon()
|
||||
{
|
||||
const QStringList pathes = getHost(QUrl(m_url), "/favicon.");
|
||||
const QStringList prefixes = QStringList() << "http://" << "https://";
|
||||
const QStringList suffixes = QStringList() << "ico" << "png" << "gif" << "jpg";
|
||||
|
||||
Q_FOREACH (QString path, pathes)
|
||||
Q_FOREACH (QString prefix, prefixes)
|
||||
Q_FOREACH (QString suffix, suffixes)
|
||||
m_networkOperations << m_networkAccessMngr->get(QNetworkRequest(prefix + path + suffix));
|
||||
//TODO: progress indication
|
||||
}
|
||||
|
||||
void EditWidgetIcons::abortFaviconDownload()
|
||||
{
|
||||
Q_FOREACH (QNetworkReply *r, m_networkOperations)
|
||||
r->abort();
|
||||
}
|
||||
|
||||
void EditWidgetIcons::onRequestFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (m_networkOperations.contains(reply)) {
|
||||
m_networkOperations.remove(reply);
|
||||
|
||||
QImage image;
|
||||
if (!reply->error() && image.loadFromData(reply->readAll()) && !image.isNull()) {
|
||||
//Abort all other requests
|
||||
abortFaviconDownload();
|
||||
|
||||
//Set the image
|
||||
Uuid uuid = Uuid::random();
|
||||
m_database->metadata()->addCustomIcon(uuid, image.scaled(16, 16));
|
||||
m_customIconModel->setIcons(m_database->metadata()->customIcons(),
|
||||
m_database->metadata()->customIconsOrder());
|
||||
QModelIndex index = m_customIconModel->indexFromUuid(uuid);
|
||||
m_ui->customIconsView->setCurrentIndex(index);
|
||||
m_ui->customIconsRadio->setChecked(true);
|
||||
}
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void EditWidgetIcons::addCustomIcon()
|
||||
{
|
||||
if (m_database) {
|
||||
|
@ -19,6 +19,11 @@
|
||||
#define KEEPASSX_EDITWIDGETICONS_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QSet>
|
||||
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "core/Uuid.h"
|
||||
@ -48,9 +53,15 @@ public:
|
||||
~EditWidgetIcons();
|
||||
|
||||
IconStruct save();
|
||||
void load(Uuid currentUuid, Database* database, IconStruct iconStruct);
|
||||
void load(Uuid currentUuid, Database* database, IconStruct iconStruct, const QString &url = QString());
|
||||
|
||||
public Q_SLOTS:
|
||||
void setUrl(const QString &url);
|
||||
|
||||
private Q_SLOTS:
|
||||
void downloadFavicon();
|
||||
void abortFaviconDownload();
|
||||
void onRequestFinished(QNetworkReply *reply);
|
||||
void addCustomIcon();
|
||||
void removeCustomIcon();
|
||||
void updateWidgetsDefaultIcons(bool checked);
|
||||
@ -62,8 +73,11 @@ private:
|
||||
const QScopedPointer<Ui::EditWidgetIcons> m_ui;
|
||||
Database* m_database;
|
||||
Uuid m_currentUuid;
|
||||
QString m_url;
|
||||
DefaultIconModel* const m_defaultIconModel;
|
||||
CustomIconModel* const m_customIconModel;
|
||||
QNetworkAccessManager* const m_networkAccessMngr;
|
||||
QSet<QNetworkReply *> m_networkOperations;
|
||||
|
||||
Q_DISABLE_COPY(EditWidgetIcons)
|
||||
};
|
||||
|
@ -91,6 +91,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="faviconButton">
|
||||
<property name="text">
|
||||
<string>Download favicon</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QShortcut>
|
||||
#include <QtGui/QLineEdit>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
@ -33,6 +34,41 @@
|
||||
#include "gui/entry/EntryView.h"
|
||||
#include "gui/group/GroupView.h"
|
||||
|
||||
#include "http/Service.h"
|
||||
#include "http/HttpSettings.h"
|
||||
#include "http/OptionDialog.h"
|
||||
#include "gui/SettingsWidget.h"
|
||||
#include "gui/qocoa/qsearchfield.h"
|
||||
|
||||
class HttpPlugin: public ISettingsPage {
|
||||
public:
|
||||
HttpPlugin(DatabaseTabWidget * tabWidget): m_service(new Service(tabWidget)) {
|
||||
}
|
||||
virtual ~HttpPlugin() {
|
||||
}
|
||||
virtual QString name() {
|
||||
return QObject::tr("Http");
|
||||
}
|
||||
virtual QWidget * createWidget() {
|
||||
OptionDialog * dlg = new OptionDialog();
|
||||
QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service.data(), SLOT(removeSharedEncryptionKeys()));
|
||||
QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service.data(), SLOT(removeStoredPermissions()));
|
||||
return dlg;
|
||||
}
|
||||
virtual void loadSettings(QWidget * widget) {
|
||||
qobject_cast<OptionDialog*>(widget)->loadSettings();
|
||||
}
|
||||
virtual void saveSettings(QWidget * widget) {
|
||||
qobject_cast<OptionDialog*>(widget)->saveSettings();
|
||||
if (HttpSettings::isEnabled())
|
||||
m_service->start();
|
||||
else
|
||||
m_service->stop();
|
||||
}
|
||||
private:
|
||||
QScopedPointer<Service> m_service;
|
||||
};
|
||||
|
||||
const QString MainWindow::BaseWindowTitle = "KeePassX";
|
||||
|
||||
MainWindow::MainWindow()
|
||||
@ -40,13 +76,20 @@ MainWindow::MainWindow()
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
m_ui->settingsWidget->addSettingsPage(new HttpPlugin(m_ui->tabWidget));
|
||||
|
||||
setWindowIcon(filePath()->applicationIcon());
|
||||
QAction* toggleViewAction = m_ui->toolBar->toggleViewAction();
|
||||
toggleViewAction->setText(tr("Show toolbar"));
|
||||
m_ui->menuView->addAction(toggleViewAction);
|
||||
int toolbarIconSize = config()->get("ToolbarIconSize", 20).toInt();
|
||||
setToolbarIconSize(toolbarIconSize);
|
||||
bool showToolbar = config()->get("ShowToolbar").toBool();
|
||||
m_ui->toolBar->setVisible(showToolbar);
|
||||
connect(m_ui->toolBar, SIGNAL(visibilityChanged(bool)), this, SLOT(saveToolbarState(bool)));
|
||||
connect(m_ui->actionToolbarIconSize16, SIGNAL(triggered()), this, SLOT(setToolbarIconSize16()));
|
||||
connect(m_ui->actionToolbarIconSize22, SIGNAL(triggered()), this, SLOT(setToolbarIconSize22()));
|
||||
connect(m_ui->actionToolbarIconSize28, SIGNAL(triggered()), this, SLOT(setToolbarIconSize28()));
|
||||
|
||||
m_clearHistoryAction = new QAction("Clear history", m_ui->menuFile);
|
||||
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
|
||||
@ -78,7 +121,11 @@ MainWindow::MainWindow()
|
||||
setShortcut(m_ui->actionDatabaseClose, QKeySequence::Close, Qt::CTRL + Qt::Key_W);
|
||||
m_ui->actionLockDatabases->setShortcut(Qt::CTRL + Qt::Key_L);
|
||||
setShortcut(m_ui->actionQuit, QKeySequence::Quit, Qt::CTRL + Qt::Key_Q);
|
||||
setShortcut(m_ui->actionSearch, QKeySequence::Find, Qt::CTRL + Qt::Key_F);
|
||||
//TODO: do not register shortcut on Q_OS_MAC, if this is done automatically??
|
||||
const QKeySequence seq = !QKeySequence::keyBindings(QKeySequence::Find).isEmpty()
|
||||
? QKeySequence::Find
|
||||
: QKeySequence(Qt::CTRL + Qt::Key_F);
|
||||
connect(new QShortcut(seq, this), SIGNAL(activated()), m_ui->searchField, SLOT(setFocus()));
|
||||
m_ui->actionEntryNew->setShortcut(Qt::CTRL + Qt::Key_N);
|
||||
m_ui->actionEntryEdit->setShortcut(Qt::CTRL + Qt::Key_E);
|
||||
m_ui->actionEntryDelete->setShortcut(Qt::CTRL + Qt::Key_D);
|
||||
@ -118,8 +165,6 @@ MainWindow::MainWindow()
|
||||
|
||||
m_ui->actionAbout->setIcon(filePath()->icon("actions", "help-about"));
|
||||
|
||||
m_ui->actionSearch->setIcon(filePath()->icon("actions", "system-search"));
|
||||
|
||||
m_actionMultiplexer.connect(SIGNAL(currentModeChanged(DatabaseWidget::Mode)),
|
||||
this, SLOT(setMenuActionState(DatabaseWidget::Mode)));
|
||||
m_actionMultiplexer.connect(SIGNAL(groupChanged()),
|
||||
@ -199,8 +244,24 @@ MainWindow::MainWindow()
|
||||
|
||||
connect(m_ui->actionAbout, SIGNAL(triggered()), SLOT(showAboutDialog()));
|
||||
|
||||
m_actionMultiplexer.connect(m_ui->actionSearch, SIGNAL(triggered()),
|
||||
SLOT(toggleSearch()));
|
||||
m_ui->searchField->setPlaceholderText(tr("Type to search"));
|
||||
m_ui->searchField->setEnabled(false);
|
||||
m_ui->toolBar->addWidget(m_ui->searchPanel);
|
||||
m_actionMultiplexer.connect(m_ui->searchField, SIGNAL(textChanged(QString)),
|
||||
SLOT(search(QString)));
|
||||
QMenu* searchMenu = new QMenu(this);
|
||||
searchMenu->addAction(m_ui->actionFindCaseSensitive);
|
||||
searchMenu->addSeparator();
|
||||
searchMenu->addAction(m_ui->actionFindCurrentGroup);
|
||||
searchMenu->addAction(m_ui->actionFindRootGroup);
|
||||
m_ui->searchField->setMenu(searchMenu);
|
||||
QActionGroup* group = new QActionGroup(this);
|
||||
group->addAction(m_ui->actionFindCurrentGroup);
|
||||
group->addAction(m_ui->actionFindRootGroup);
|
||||
m_actionMultiplexer.connect(m_ui->actionFindCaseSensitive, SIGNAL(toggled(bool)),
|
||||
SLOT(setCaseSensitiveSearch(bool)));
|
||||
m_actionMultiplexer.connect(m_ui->actionFindRootGroup, SIGNAL(toggled(bool)),
|
||||
SLOT(setAllGroupsSearch(bool)));
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
@ -253,11 +314,40 @@ void MainWindow::clearLastDatabases()
|
||||
config()->set("LastDatabases", QVariant());
|
||||
}
|
||||
|
||||
void MainWindow::changeEvent(QEvent *e)
|
||||
{
|
||||
QMainWindow::changeEvent(e);
|
||||
if (e->type() == QEvent::ActivationChange) {
|
||||
if (isActiveWindow())
|
||||
m_ui->tabWidget->checkReloadDatabases();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::openDatabase(const QString& fileName, const QString& pw, const QString& keyFile)
|
||||
{
|
||||
m_ui->tabWidget->openDatabase(fileName, pw, keyFile);
|
||||
}
|
||||
|
||||
void MainWindow::updateSearchField(DatabaseWidget* dbWidget)
|
||||
{
|
||||
bool enabled = dbWidget != NULL;
|
||||
|
||||
m_ui->actionFindCaseSensitive->setChecked(enabled && dbWidget->caseSensitiveSearch());
|
||||
|
||||
m_ui->actionFindCurrentGroup->setEnabled(enabled && dbWidget->canChooseSearchScope());
|
||||
m_ui->actionFindRootGroup->setEnabled(enabled && dbWidget->canChooseSearchScope());
|
||||
if (enabled && dbWidget->isAllGroupsSearch())
|
||||
m_ui->actionFindRootGroup->setChecked(true);
|
||||
else
|
||||
m_ui->actionFindCurrentGroup->setChecked(true);
|
||||
|
||||
m_ui->searchField->setEnabled(enabled);
|
||||
if (enabled && dbWidget->isInSearchMode())
|
||||
m_ui->searchField->setText(dbWidget->searchText());
|
||||
else
|
||||
m_ui->searchField->clear();
|
||||
}
|
||||
|
||||
void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
{
|
||||
bool inDatabaseTabWidget = (m_ui->stackedWidget->currentIndex() == 0);
|
||||
@ -293,9 +383,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
m_ui->actionGroupNew->setEnabled(groupSelected);
|
||||
m_ui->actionGroupEdit->setEnabled(groupSelected);
|
||||
m_ui->actionGroupDelete->setEnabled(groupSelected && dbWidget->canDeleteCurrentGoup());
|
||||
m_ui->actionSearch->setEnabled(true);
|
||||
// TODO: get checked state from db widget
|
||||
m_ui->actionSearch->setChecked(inSearch);
|
||||
updateSearchField(dbWidget);
|
||||
m_ui->actionChangeMasterKey->setEnabled(true);
|
||||
m_ui->actionChangeDatabaseSettings->setEnabled(true);
|
||||
m_ui->actionDatabaseSave->setEnabled(true);
|
||||
@ -313,8 +401,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
}
|
||||
m_ui->menuEntryCopyAttribute->setEnabled(false);
|
||||
|
||||
m_ui->actionSearch->setEnabled(false);
|
||||
m_ui->actionSearch->setChecked(false);
|
||||
updateSearchField();
|
||||
m_ui->actionChangeMasterKey->setEnabled(false);
|
||||
m_ui->actionChangeDatabaseSettings->setEnabled(false);
|
||||
m_ui->actionDatabaseSave->setEnabled(false);
|
||||
@ -335,8 +422,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
}
|
||||
m_ui->menuEntryCopyAttribute->setEnabled(false);
|
||||
|
||||
m_ui->actionSearch->setEnabled(false);
|
||||
m_ui->actionSearch->setChecked(false);
|
||||
updateSearchField();
|
||||
m_ui->actionChangeMasterKey->setEnabled(false);
|
||||
m_ui->actionChangeDatabaseSettings->setEnabled(false);
|
||||
m_ui->actionDatabaseSave->setEnabled(false);
|
||||
@ -451,6 +537,30 @@ void MainWindow::saveToolbarState(bool value)
|
||||
config()->set("ShowToolbar", value);
|
||||
}
|
||||
|
||||
void MainWindow::setToolbarIconSize(int size)
|
||||
{
|
||||
config()->set("ToolbarIconSize", size);
|
||||
m_ui->toolBar->setIconSize(QSize(size, size));
|
||||
m_ui->actionToolbarIconSize16->setChecked(size == 16);
|
||||
m_ui->actionToolbarIconSize22->setChecked(size == 22);
|
||||
m_ui->actionToolbarIconSize28->setChecked(size == 28);
|
||||
}
|
||||
|
||||
void MainWindow::setToolbarIconSize16()
|
||||
{
|
||||
setToolbarIconSize(16);
|
||||
}
|
||||
|
||||
void MainWindow::setToolbarIconSize22()
|
||||
{
|
||||
setToolbarIconSize(22);
|
||||
}
|
||||
|
||||
void MainWindow::setToolbarIconSize28()
|
||||
{
|
||||
setToolbarIconSize(28);
|
||||
}
|
||||
|
||||
void MainWindow::setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback)
|
||||
{
|
||||
if (!QKeySequence::keyBindings(standard).isEmpty()) {
|
||||
|
@ -43,7 +43,8 @@ public Q_SLOTS:
|
||||
const QString& keyFile = QString());
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE;
|
||||
void changeEvent(QEvent *e);
|
||||
void closeEvent(QCloseEvent* event) Q_DECL_OVERRIDE;
|
||||
|
||||
private Q_SLOTS:
|
||||
void setMenuActionState(DatabaseWidget::Mode mode = DatabaseWidget::None);
|
||||
@ -61,8 +62,13 @@ private Q_SLOTS:
|
||||
void saveToolbarState(bool value);
|
||||
void rememberOpenDatabases(const QString& filePath);
|
||||
void applySettingsChanges();
|
||||
void setToolbarIconSize(int size);
|
||||
void setToolbarIconSize16();
|
||||
void setToolbarIconSize22();
|
||||
void setToolbarIconSize28();
|
||||
|
||||
private:
|
||||
void updateSearchField(DatabaseWidget* dbWidget = NULL);
|
||||
static void setShortcut(QAction* action, QKeySequence::StandardKey standard, int fallback = 0);
|
||||
|
||||
static const QString BaseWindowTitle;
|
||||
|
@ -55,6 +55,34 @@
|
||||
</widget>
|
||||
<widget class="QWidget" name="pageWelcome">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<item>
|
||||
<widget class="QWidget" name="searchPanel" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSearchField" name="searchField" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="WelcomeWidget" name="welcomeWidget" native="true"/>
|
||||
</item>
|
||||
@ -70,7 +98,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>800</width>
|
||||
<height>20</height>
|
||||
<height>24</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
@ -148,6 +176,15 @@
|
||||
<property name="title">
|
||||
<string>View</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuToolbarIconSize">
|
||||
<property name="title">
|
||||
<string>Toolbar &Icon Size</string>
|
||||
</property>
|
||||
<addaction name="actionToolbarIconSize16"/>
|
||||
<addaction name="actionToolbarIconSize22"/>
|
||||
<addaction name="actionToolbarIconSize28"/>
|
||||
</widget>
|
||||
<addaction name="menuToolbarIconSize"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
<addaction name="menuEntries"/>
|
||||
@ -177,7 +214,6 @@
|
||||
<addaction name="actionEntryCopyPassword"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionLockDatabases"/>
|
||||
<addaction name="actionSearch"/>
|
||||
</widget>
|
||||
<action name="actionQuit">
|
||||
<property name="text">
|
||||
@ -303,17 +339,6 @@
|
||||
<string>Clone entry</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSearch">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Find</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionEntryCopyUsername">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
@ -389,6 +414,54 @@
|
||||
<string>Notes</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolbarIconSize16">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&16x16</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolbarIconSize22">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&22x22</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolbarIconSize28">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2&8x28</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFindCaseSensitive">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Case Sensitive</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFindCurrentGroup">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Current Group</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFindRootGroup">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Root Group</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
@ -409,6 +482,12 @@
|
||||
<header>gui/WelcomeWidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QSearchField</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/qocoa/qsearchfield.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
@ -7,17 +7,14 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>630</width>
|
||||
<height>87</height>
|
||||
<height>34</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="LineEdit" name="searchEdit"/>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QToolButton" name="closeSearchButton">
|
||||
@ -27,72 +24,12 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Find:</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="searchResults"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QWidget" name="optionsWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="caseSensitiveCheckBox">
|
||||
<property name="text">
|
||||
<string>Case sensitive</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="searchCurrentRadioButton">
|
||||
<property name="text">
|
||||
<string>Current group</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="searchRootRadioButton">
|
||||
<property name="text">
|
||||
<string>Root group</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>255</width>
|
||||
<height>1</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>LineEdit</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>gui/LineEdit.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>closeSearchButton</tabstop>
|
||||
<tabstop>searchEdit</tabstop>
|
||||
|
@ -22,6 +22,27 @@
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
|
||||
class SettingsWidget::ExtraPage
|
||||
{
|
||||
public:
|
||||
ExtraPage(ISettingsPage* page, QWidget* widget): settingsPage(page), widget(widget)
|
||||
{}
|
||||
|
||||
void loadSettings() const
|
||||
{
|
||||
settingsPage->loadSettings(widget);
|
||||
}
|
||||
|
||||
void saveSettings() const
|
||||
{
|
||||
settingsPage->saveSettings(widget);
|
||||
}
|
||||
|
||||
private:
|
||||
QSharedPointer<ISettingsPage> settingsPage;
|
||||
QWidget* widget;
|
||||
};
|
||||
|
||||
SettingsWidget::SettingsWidget(QWidget* parent)
|
||||
: EditWidget(parent)
|
||||
, m_secWidget(new QWidget())
|
||||
@ -55,6 +76,14 @@ SettingsWidget::~SettingsWidget()
|
||||
{
|
||||
}
|
||||
|
||||
void SettingsWidget::addSettingsPage(ISettingsPage *page)
|
||||
{
|
||||
QWidget * widget = page->createWidget();
|
||||
widget->setParent(this);
|
||||
m_extraPages.append(ExtraPage(page, widget));
|
||||
add(page->name(), widget);
|
||||
}
|
||||
|
||||
void SettingsWidget::loadSettings()
|
||||
{
|
||||
m_generalUi->rememberLastDatabasesCheckBox->setChecked(config()->get("RememberLastDatabases").toBool());
|
||||
@ -64,6 +93,7 @@ void SettingsWidget::loadSettings()
|
||||
m_generalUi->autoSaveAfterEveryChangeCheckBox->setChecked(config()->get("AutoSaveAfterEveryChange").toBool());
|
||||
m_generalUi->autoSaveOnExitCheckBox->setChecked(config()->get("AutoSaveOnExit").toBool());
|
||||
m_generalUi->minimizeOnCopyCheckBox->setChecked(config()->get("MinimizeOnCopy").toBool());
|
||||
m_generalUi->reloadBehavior->setCurrentIndex(config()->get("ReloadBehavior").toInt());
|
||||
|
||||
if (autoType()->isAvailable()) {
|
||||
m_globalAutoTypeKey = static_cast<Qt::Key>(config()->get("GlobalAutoTypeKey").toInt());
|
||||
@ -81,6 +111,9 @@ void SettingsWidget::loadSettings()
|
||||
|
||||
m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool());
|
||||
|
||||
Q_FOREACH (const ExtraPage& page, m_extraPages)
|
||||
page.loadSettings();
|
||||
|
||||
setCurrentRow(0);
|
||||
}
|
||||
|
||||
@ -95,6 +128,7 @@ void SettingsWidget::saveSettings()
|
||||
m_generalUi->autoSaveAfterEveryChangeCheckBox->isChecked());
|
||||
config()->set("AutoSaveOnExit", m_generalUi->autoSaveOnExitCheckBox->isChecked());
|
||||
config()->set("MinimizeOnCopy", m_generalUi->minimizeOnCopyCheckBox->isChecked());
|
||||
config()->set("ReloadBehavior", m_generalUi->reloadBehavior->currentIndex());
|
||||
if (autoType()->isAvailable()) {
|
||||
config()->set("GlobalAutoTypeKey", m_generalUi->autoTypeShortcutWidget->key());
|
||||
config()->set("GlobalAutoTypeModifiers",
|
||||
@ -108,6 +142,9 @@ void SettingsWidget::saveSettings()
|
||||
|
||||
config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked());
|
||||
|
||||
Q_FOREACH (const ExtraPage& page, m_extraPages)
|
||||
page.saveSettings();
|
||||
|
||||
Q_EMIT editFinished(true);
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,15 @@ namespace Ui {
|
||||
class SettingsWidgetSecurity;
|
||||
}
|
||||
|
||||
class ISettingsPage {
|
||||
public:
|
||||
virtual ~ISettingsPage() {}
|
||||
virtual QString name() = 0;
|
||||
virtual QWidget * createWidget() = 0;
|
||||
virtual void loadSettings(QWidget * widget) = 0;
|
||||
virtual void saveSettings(QWidget * widget) = 0;
|
||||
};
|
||||
|
||||
class SettingsWidget : public EditWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -32,6 +41,7 @@ class SettingsWidget : public EditWidget
|
||||
public:
|
||||
explicit SettingsWidget(QWidget* parent = Q_NULLPTR);
|
||||
~SettingsWidget();
|
||||
void addSettingsPage(ISettingsPage * page);
|
||||
void loadSettings();
|
||||
|
||||
Q_SIGNALS:
|
||||
@ -49,6 +59,8 @@ private:
|
||||
const QScopedPointer<Ui::SettingsWidgetGeneral> m_generalUi;
|
||||
Qt::Key m_globalAutoTypeKey;
|
||||
Qt::KeyboardModifiers m_globalAutoTypeModifiers;
|
||||
class ExtraPage;
|
||||
QList<ExtraPage> m_extraPages;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SETTINGSWIDGET_H
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>456</width>
|
||||
<width>481</width>
|
||||
<height>185</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -14,7 +14,7 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="rememberLastDatabasesCheckBox">
|
||||
<property name="text">
|
||||
<string>Remember last databases</string>
|
||||
<string>Remember recent databases</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
@ -62,6 +62,32 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>When database files are externally modified</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="reloadBehavior">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always ask</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Reload unmodified databases</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ignore modifications</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="minimizeOnCopyCheckBox">
|
||||
<property name="text">
|
||||
|
@ -326,7 +326,8 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
|
||||
IconStruct iconStruct;
|
||||
iconStruct.uuid = entry->iconUuid();
|
||||
iconStruct.number = entry->iconNumber();
|
||||
m_iconsWidget->load(entry->uuid(), m_database, iconStruct);
|
||||
m_iconsWidget->load(entry->uuid(), m_database, iconStruct, entry->url());
|
||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
|
||||
|
||||
m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled());
|
||||
if (entry->defaultAutoTypeSequence().isEmpty()) {
|
||||
|
@ -18,13 +18,31 @@
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="AttributesListView" name="attributesView"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="attributesEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="AttributesListView" name="attributesView"/>
|
||||
<widget class="QPlainTextEdit" name="attributesEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
50
src/gui/qocoa/CMakeLists.txt
Normal file
50
src/gui/qocoa/CMakeLists.txt
Normal file
@ -0,0 +1,50 @@
|
||||
project(Qocoa)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
#find_package(Qt4 COMPONENTS QtMain QtCore QtGui REQUIRED)
|
||||
#include(UseQt4)
|
||||
|
||||
set(SOURCES
|
||||
#main.cpp
|
||||
#gallery.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
#gallery.h
|
||||
qsearchfield.h
|
||||
qbutton.h
|
||||
qprogressindicatorspinning.h
|
||||
)
|
||||
|
||||
qt4_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
|
||||
if(APPLE)
|
||||
list(APPEND SOURCES
|
||||
qsearchfield_mac.mm
|
||||
qbutton_mac.mm
|
||||
qprogressindicatorspinning_mac.mm
|
||||
)
|
||||
else()
|
||||
list(APPEND SOURCES
|
||||
qsearchfield_nonmac.cpp
|
||||
qbutton_nonmac.cpp
|
||||
qprogressindicatorspinning_nonmac.cpp
|
||||
)
|
||||
set(RESOURCES
|
||||
qsearchfield_nonmac.qrc
|
||||
qprogressindicatorspinning_nonmac.qrc
|
||||
)
|
||||
qt4_add_resources(RESOURCES_SOURCES ${RESOURCES})
|
||||
endif()
|
||||
|
||||
#add_executable(Qocoa
|
||||
# WIN32 MACOSX_BUNDLE
|
||||
# ${SOURCES} ${MOC_SOURCES} ${RESOURCES_SOURCES}
|
||||
#)
|
||||
#target_link_libraries(Qocoa ${QT_LIBRARIES})
|
||||
|
||||
add_library (Qocoa STATIC ${SOURCES} ${MOC_SOURCES} ${HEADERS} ${RESOURCES_SOURCES})
|
||||
|
||||
#if(APPLE)
|
||||
# set_target_properties(Qocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit")
|
||||
#endif()
|
19
src/gui/qocoa/LICENSE.txt
Normal file
19
src/gui/qocoa/LICENSE.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
17
src/gui/qocoa/Qocoa.pro
Normal file
17
src/gui/qocoa/Qocoa.pro
Normal file
@ -0,0 +1,17 @@
|
||||
SOURCES += main.cpp\
|
||||
gallery.cpp \
|
||||
|
||||
HEADERS += gallery.h \
|
||||
qocoa_mac.h \
|
||||
qsearchfield.h \
|
||||
qbutton.h \
|
||||
qprogressindicatorspinning.h \
|
||||
|
||||
mac {
|
||||
OBJECTIVE_SOURCES += qsearchfield_mac.mm qbutton_mac.mm qprogressindicatorspinning_mac.mm
|
||||
LIBS += -framework Foundation -framework Appkit
|
||||
QMAKE_CFLAGS += -mmacosx-version-min=10.6
|
||||
} else {
|
||||
SOURCES += qsearchfield_nonmac.cpp qbutton_nonmac.cpp qprogressindicatorspinning_nonmac.cpp
|
||||
RESOURCES += qsearchfield_nonmac.qrc qprogressindicatorspinning_nonmac.qrc
|
||||
}
|
36
src/gui/qocoa/README.md
Normal file
36
src/gui/qocoa/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Qocoa
|
||||
Qocoa is a collection of Qt wrappers for OSX's Cocoa widgets.
|
||||
|
||||
## Features
|
||||
- basic fallback to sensible Qt types on non-OSX platforms
|
||||
- shared class headers which expose no implementation details
|
||||
- typical Qt signal/slot-based API
|
||||
- trivial to import into projects (class header/implementation, [single shared global header](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h))
|
||||
|
||||
## Building
|
||||
```
|
||||
git clone git://github.com/mikemcquaid/Qocoa.git
|
||||
cd Qocoa
|
||||
qmake # or cmake .
|
||||
make
|
||||
```
|
||||
|
||||
## Status
|
||||
Qocoa classes are currently provided for NSButton, a spinning NSProgressIndicator and NSSearchField. There is a [TODO list](https://github.com/mikemcquaid/Qocoa/blob/master/TODO.md) for classes I hope to implement.
|
||||
|
||||
## Usage
|
||||
For each class you want to use copy the [`qocoa_mac.h`](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h), `$CLASS.h`, `$CLASS_mac.*` and `$CLASS_nonmac.*` files into your source tree and add them to your buildsystem. Examples are provided for [CMake](https://github.com/mikemcquaid/Qocoa/blob/master/CMakeLists.txt) and [QMake](https://github.com/mikemcquaid/Qocoa/blob/master/Qocoa.pro).
|
||||
|
||||
## Contact
|
||||
[Mike McQuaid](mailto:mike@mikemcquaid.com)
|
||||
|
||||
## License
|
||||
Qocoa is licensed under the [MIT License](http://en.wikipedia.org/wiki/MIT_License).
|
||||
The full license text is available in [LICENSE.txt](https://github.com/mikemcquaid/Qocoa/blob/master/LICENSE.txt).
|
||||
|
||||
Magnifier and EditClear icons taken from [QtCreator](http://qt-project.org/), licensed under [LGPL](http://www.gnu.org/copyleft/lesser.html).
|
||||
|
||||
Other icons are taken from the [Oxygen Project](http://www.oxygen-icons.org/) and are licensed under the [Creative Commons Attribution-ShareAlike 3.0 License](http://creativecommons.org/licenses/by-sa/3.0/).
|
||||
|
||||
## Gallery
|
||||
![Qocoa Gallery](https://github.com/mikemcquaid/Qocoa/raw/master/gallery.png)
|
13
src/gui/qocoa/TODO.md
Normal file
13
src/gui/qocoa/TODO.md
Normal file
@ -0,0 +1,13 @@
|
||||
Widgets I hope to implement (or at least investigate):
|
||||
|
||||
- NSTokenField
|
||||
- NSSegmentedControl
|
||||
- NSLevelIndicator
|
||||
- NSPathControl
|
||||
- NSSlider (Circular)
|
||||
- NSSplitView
|
||||
- NSTextFinder
|
||||
- NSOutlineView in an NSScrollView (Source List)
|
||||
- NSDrawer
|
||||
- PDFView
|
||||
- WebView
|
75
src/gui/qocoa/gallery.cpp
Normal file
75
src/gui/qocoa/gallery.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "gallery.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "qsearchfield.h"
|
||||
#include "qbutton.h"
|
||||
#include "qprogressindicatorspinning.h"
|
||||
|
||||
Gallery::Gallery(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
setWindowTitle("Qocoa Gallery");
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
QSearchField *searchField = new QSearchField(this);
|
||||
layout->addWidget(searchField);
|
||||
|
||||
QSearchField *searchFieldPlaceholder = new QSearchField(this);
|
||||
searchFieldPlaceholder->setPlaceholderText("Placeholder text");
|
||||
layout->addWidget(searchFieldPlaceholder);
|
||||
|
||||
QButton *roundedButton = new QButton(this, QButton::Rounded);
|
||||
roundedButton->setText("Button");
|
||||
layout->addWidget(roundedButton);
|
||||
|
||||
QButton *regularSquareButton = new QButton(this, QButton::RegularSquare);
|
||||
regularSquareButton->setText("Button");
|
||||
layout->addWidget(regularSquareButton);
|
||||
|
||||
QButton *disclosureButton = new QButton(this, QButton::Disclosure);
|
||||
layout->addWidget(disclosureButton);
|
||||
|
||||
QButton *shadowlessSquareButton = new QButton(this, QButton::ShadowlessSquare);
|
||||
shadowlessSquareButton->setText("Button");
|
||||
layout->addWidget(shadowlessSquareButton);
|
||||
|
||||
QButton *circularButton = new QButton(this, QButton::Circular);
|
||||
layout->addWidget(circularButton);
|
||||
|
||||
QButton *textureSquareButton = new QButton(this, QButton::TexturedSquare);
|
||||
textureSquareButton->setText("Textured Button");
|
||||
layout->addWidget(textureSquareButton);
|
||||
|
||||
QButton *helpButton = new QButton(this, QButton::HelpButton);
|
||||
layout->addWidget(helpButton);
|
||||
|
||||
QButton *smallSquareButton = new QButton(this, QButton::SmallSquare);
|
||||
smallSquareButton->setText("Gradient Button");
|
||||
layout->addWidget(smallSquareButton);
|
||||
|
||||
QButton *texturedRoundedButton = new QButton(this, QButton::TexturedRounded);
|
||||
texturedRoundedButton->setText("Round Textured");
|
||||
layout->addWidget(texturedRoundedButton);
|
||||
|
||||
QButton *roundedRectangleButton = new QButton(this, QButton::RoundRect);
|
||||
roundedRectangleButton->setText("Rounded Rect Button");
|
||||
layout->addWidget(roundedRectangleButton);
|
||||
|
||||
QButton *recessedButton = new QButton(this, QButton::Recessed);
|
||||
recessedButton->setText("Recessed Button");
|
||||
layout->addWidget(recessedButton);
|
||||
|
||||
QButton *roundedDisclosureButton = new QButton(this, QButton::RoundedDisclosure);
|
||||
layout->addWidget(roundedDisclosureButton);
|
||||
|
||||
#ifdef __MAC_10_7
|
||||
QButton *inlineButton = new QButton(this, QButton::Inline);
|
||||
inlineButton->setText("Inline Button");
|
||||
layout->addWidget(inlineButton);
|
||||
#endif
|
||||
|
||||
|
||||
QProgressIndicatorSpinning *progressIndicatorSpinning = new QProgressIndicatorSpinning(this);
|
||||
progressIndicatorSpinning->animate();
|
||||
layout->addWidget(progressIndicatorSpinning);
|
||||
}
|
14
src/gui/qocoa/gallery.h
Normal file
14
src/gui/qocoa/gallery.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef GALLERY_H
|
||||
#define GALLERY_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class Gallery : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Gallery(QWidget *parent = 0);
|
||||
};
|
||||
|
||||
#endif // WIDGET_H
|
BIN
src/gui/qocoa/gallery.png
Normal file
BIN
src/gui/qocoa/gallery.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
12
src/gui/qocoa/main.cpp
Normal file
12
src/gui/qocoa/main.cpp
Normal file
@ -0,0 +1,12 @@
|
||||
#include <QtGui/QApplication>
|
||||
#include "gallery.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication application(argc, argv);
|
||||
|
||||
Gallery gallery;
|
||||
gallery.show();
|
||||
|
||||
return application.exec();
|
||||
}
|
49
src/gui/qocoa/qbutton.h
Normal file
49
src/gui/qocoa/qbutton.h
Normal file
@ -0,0 +1,49 @@
|
||||
#ifndef QBUTTON_H
|
||||
#define QBUTTON_H
|
||||
|
||||
#include <QtGui/QWidget>
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
class QButtonPrivate;
|
||||
class QButton : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Matches NSBezelStyle
|
||||
enum BezelStyle {
|
||||
Rounded = 1,
|
||||
RegularSquare = 2,
|
||||
Disclosure = 5,
|
||||
ShadowlessSquare = 6,
|
||||
Circular = 7,
|
||||
TexturedSquare = 8,
|
||||
HelpButton = 9,
|
||||
SmallSquare = 10,
|
||||
TexturedRounded = 11,
|
||||
RoundRect = 12,
|
||||
Recessed = 13,
|
||||
RoundedDisclosure = 14,
|
||||
#ifdef __MAC_10_7
|
||||
Inline = 15
|
||||
#endif
|
||||
};
|
||||
|
||||
explicit QButton(QWidget *parent, BezelStyle bezelStyle = Rounded);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setText(const QString &text);
|
||||
void setImage(const QPixmap &image);
|
||||
void setChecked(bool checked);
|
||||
|
||||
public:
|
||||
void setCheckable(bool checkable);
|
||||
bool isChecked();
|
||||
|
||||
Q_SIGNALS:
|
||||
void clicked(bool checked = false);
|
||||
|
||||
private:
|
||||
friend class QButtonPrivate;
|
||||
QPointer<QButtonPrivate> pimpl;
|
||||
};
|
||||
#endif // QBUTTON_H
|
229
src/gui/qocoa/qbutton_mac.mm
Normal file
229
src/gui/qocoa/qbutton_mac.mm
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qbutton.h"
|
||||
|
||||
#include "qocoa_mac.h"
|
||||
|
||||
#import "Foundation/NSAutoreleasePool.h"
|
||||
#import "AppKit/NSButton.h"
|
||||
#import "AppKit/NSFont.h"
|
||||
|
||||
class QButtonPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QButtonPrivate(QButton *qButton, NSButton *nsButton, QButton::BezelStyle bezelStyle)
|
||||
: QObject(qButton), qButton(qButton), nsButton(nsButton)
|
||||
{
|
||||
switch(bezelStyle) {
|
||||
case QButton::Disclosure:
|
||||
case QButton::Circular:
|
||||
#ifdef __MAC_10_7
|
||||
case QButton::Inline:
|
||||
#endif
|
||||
case QButton::RoundedDisclosure:
|
||||
case QButton::HelpButton:
|
||||
[nsButton setTitle:@""];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
NSFont* font = 0;
|
||||
switch(bezelStyle) {
|
||||
case QButton::RoundRect:
|
||||
font = [NSFont fontWithName:@"Lucida Grande" size:12];
|
||||
break;
|
||||
|
||||
case QButton::Recessed:
|
||||
font = [NSFont fontWithName:@"Lucida Grande Bold" size:12];
|
||||
break;
|
||||
|
||||
#ifdef __MAC_10_7
|
||||
case QButton::Inline:
|
||||
font = [NSFont boldSystemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]];
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
|
||||
break;
|
||||
}
|
||||
[nsButton setFont:font];
|
||||
|
||||
switch(bezelStyle) {
|
||||
case QButton::Rounded:
|
||||
qButton->setMinimumWidth(40);
|
||||
qButton->setFixedHeight(24);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
case QButton::RegularSquare:
|
||||
case QButton::TexturedSquare:
|
||||
qButton->setMinimumSize(14, 23);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::ShadowlessSquare:
|
||||
qButton->setMinimumSize(5, 25);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::SmallSquare:
|
||||
qButton->setMinimumSize(4, 21);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::TexturedRounded:
|
||||
qButton->setMinimumSize(10, 22);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::RoundRect:
|
||||
case QButton::Recessed:
|
||||
qButton->setMinimumWidth(16);
|
||||
qButton->setFixedHeight(18);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
case QButton::Disclosure:
|
||||
qButton->setMinimumWidth(13);
|
||||
qButton->setFixedHeight(13);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
case QButton::Circular:
|
||||
qButton->setMinimumSize(16, 16);
|
||||
qButton->setMaximumHeight(40);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::HelpButton:
|
||||
case QButton::RoundedDisclosure:
|
||||
qButton->setMinimumWidth(22);
|
||||
qButton->setFixedHeight(22);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
#ifdef __MAC_10_7
|
||||
case QButton::Inline:
|
||||
qButton->setMinimumWidth(10);
|
||||
qButton->setFixedHeight(16);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
switch(bezelStyle) {
|
||||
case QButton::Recessed:
|
||||
[nsButton setButtonType:NSPushOnPushOffButton];
|
||||
case QButton::Disclosure:
|
||||
[nsButton setButtonType:NSOnOffButton];
|
||||
default:
|
||||
[nsButton setButtonType:NSMomentaryPushInButton];
|
||||
}
|
||||
|
||||
[nsButton setBezelStyle:bezelStyle];
|
||||
}
|
||||
|
||||
void clicked()
|
||||
{
|
||||
Q_EMIT qButton->clicked(qButton->isChecked());
|
||||
}
|
||||
|
||||
~QButtonPrivate() {
|
||||
[[nsButton target] release];
|
||||
[nsButton setTarget:nil];
|
||||
}
|
||||
|
||||
QButton *qButton;
|
||||
NSButton *nsButton;
|
||||
};
|
||||
|
||||
@interface QButtonTarget : NSObject
|
||||
{
|
||||
@public
|
||||
QPointer<QButtonPrivate> pimpl;
|
||||
}
|
||||
-(void)clicked;
|
||||
@end
|
||||
|
||||
@implementation QButtonTarget
|
||||
-(void)clicked {
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
pimpl->clicked();
|
||||
}
|
||||
@end
|
||||
|
||||
QButton::QButton(QWidget *parent, BezelStyle bezelStyle) : QWidget(parent)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSButton *button = [[NSButton alloc] init];
|
||||
pimpl = new QButtonPrivate(this, button, bezelStyle);
|
||||
|
||||
QButtonTarget *target = [[QButtonTarget alloc] init];
|
||||
target->pimpl = pimpl;
|
||||
[button setTarget:target];
|
||||
|
||||
[button setAction:@selector(clicked)];
|
||||
|
||||
setupLayout(button, this);
|
||||
|
||||
[button release];
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QButton::setText(const QString &text)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
[pimpl->nsButton setTitle:fromQString(text)];
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QButton::setImage(const QPixmap &image)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
[pimpl->nsButton setImage:fromQPixmap(image)];
|
||||
}
|
||||
|
||||
void QButton::setChecked(bool checked)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
[pimpl->nsButton setState:checked];
|
||||
}
|
||||
|
||||
void QButton::setCheckable(bool checkable)
|
||||
{
|
||||
const NSInteger cellMask = checkable ? NSChangeBackgroundCellMask : NSNoCellMask;
|
||||
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
[[pimpl->nsButton cell] setShowsStateBy:cellMask];
|
||||
}
|
||||
|
||||
bool QButton::isChecked()
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return false;
|
||||
|
||||
return [pimpl->nsButton state];
|
||||
}
|
89
src/gui/qocoa/qbutton_nonmac.cpp
Normal file
89
src/gui/qocoa/qbutton_nonmac.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qbutton.h"
|
||||
|
||||
#include <QtGui/QToolBar>
|
||||
#include <QtGui/QToolButton>
|
||||
#include <QtGui/QPushButton>
|
||||
#include <QtGui/QVBoxLayout>
|
||||
|
||||
class QButtonPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QButtonPrivate(QButton *button, QAbstractButton *abstractButton)
|
||||
: QObject(button), abstractButton(abstractButton) {}
|
||||
QPointer<QAbstractButton> abstractButton;
|
||||
};
|
||||
|
||||
QButton::QButton(QWidget *parent, BezelStyle) : QWidget(parent)
|
||||
{
|
||||
QAbstractButton *button = 0;
|
||||
if (qobject_cast<QToolBar*>(parent))
|
||||
button = new QToolButton(this);
|
||||
else
|
||||
button = new QPushButton(this);
|
||||
connect(button, SIGNAL(clicked()),
|
||||
this, SIGNAL(clicked()));
|
||||
pimpl = new QButtonPrivate(this, button);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
layout->addWidget(button);
|
||||
}
|
||||
|
||||
void QButton::setText(const QString &text)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
pimpl->abstractButton->setText(text);
|
||||
}
|
||||
|
||||
void QButton::setImage(const QPixmap &image)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
pimpl->abstractButton->setIcon(image);
|
||||
}
|
||||
|
||||
void QButton::setChecked(bool checked)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
pimpl->abstractButton->setChecked(checked);
|
||||
}
|
||||
|
||||
void QButton::setCheckable(bool checkable)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
pimpl->abstractButton->setCheckable(checkable);
|
||||
}
|
||||
|
||||
bool QButton::isChecked()
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return false;
|
||||
|
||||
return pimpl->abstractButton->isChecked();
|
||||
}
|
54
src/gui/qocoa/qocoa_mac.h
Normal file
54
src/gui/qocoa/qocoa_mac.h
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <Foundation/NSString.h>
|
||||
#include <QtCore/QString>
|
||||
#include <QtGui/QVBoxLayout>
|
||||
#include <QtGui/QMacCocoaViewContainer>
|
||||
|
||||
static inline NSString* fromQString(const QString &string)
|
||||
{
|
||||
const QByteArray utf8 = string.toUtf8();
|
||||
const char* cString = utf8.constData();
|
||||
return [[NSString alloc] initWithUTF8String:cString];
|
||||
}
|
||||
|
||||
static inline QString toQString(NSString *string)
|
||||
{
|
||||
if (!string)
|
||||
return QString();
|
||||
return QString::fromUtf8([string UTF8String]);
|
||||
}
|
||||
|
||||
static inline NSImage* fromQPixmap(const QPixmap &pixmap)
|
||||
{
|
||||
CGImageRef cgImage = pixmap.toMacCGImageRef();
|
||||
return [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize];
|
||||
}
|
||||
|
||||
static inline void setupLayout(void *cocoaView, QWidget *parent)
|
||||
{
|
||||
parent->setAttribute(Qt::WA_NativeWindow);
|
||||
QVBoxLayout *layout = new QVBoxLayout(parent);
|
||||
layout->setMargin(0);
|
||||
layout->addWidget(new QMacCocoaViewContainer(cocoaView, parent));
|
||||
}
|
29
src/gui/qocoa/qprogressindicatorspinning.h
Normal file
29
src/gui/qocoa/qprogressindicatorspinning.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef QPROGRESSINDICATORSPINNING_H
|
||||
#define QPROGRESSINDICATORSPINNING_H
|
||||
|
||||
#include <QtGui/QWidget>
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
class QProgressIndicatorSpinningPrivate;
|
||||
class QProgressIndicatorSpinning : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Matches NSProgressIndicatorThickness
|
||||
enum Thickness {
|
||||
Default = 14,
|
||||
Small = 10,
|
||||
Large = 18,
|
||||
Aqua = 12
|
||||
};
|
||||
|
||||
explicit QProgressIndicatorSpinning(QWidget *parent,
|
||||
Thickness thickness = Default);
|
||||
public Q_SLOTS:
|
||||
void animate(bool animate = true);
|
||||
private:
|
||||
friend class QProgressIndicatorSpinningPrivate;
|
||||
QPointer<QProgressIndicatorSpinningPrivate> pimpl;
|
||||
};
|
||||
|
||||
#endif // QPROGRESSINDICATORSPINNING_H
|
70
src/gui/qocoa/qprogressindicatorspinning_mac.mm
Normal file
70
src/gui/qocoa/qprogressindicatorspinning_mac.mm
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qprogressindicatorspinning.h"
|
||||
|
||||
#include "qocoa_mac.h"
|
||||
|
||||
#import "Foundation/NSAutoreleasePool.h"
|
||||
#import "AppKit/NSProgressIndicator.h"
|
||||
|
||||
class QProgressIndicatorSpinningPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning,
|
||||
NSProgressIndicator *nsProgressIndicator)
|
||||
: QObject(qProgressIndicatorSpinning), nsProgressIndicator(nsProgressIndicator) {}
|
||||
|
||||
NSProgressIndicator *nsProgressIndicator;
|
||||
};
|
||||
|
||||
QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent,
|
||||
Thickness thickness)
|
||||
: QWidget(parent)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSProgressIndicator *progress = [[NSProgressIndicator alloc] init];
|
||||
[progress setStyle:NSProgressIndicatorSpinningStyle];
|
||||
|
||||
pimpl = new QProgressIndicatorSpinningPrivate(this, progress);
|
||||
|
||||
setupLayout(progress, this);
|
||||
|
||||
setFixedSize(thickness, thickness);
|
||||
|
||||
[progress release];
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QProgressIndicatorSpinning::animate(bool animate)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
if (animate)
|
||||
[pimpl->nsProgressIndicator startAnimation:nil];
|
||||
else
|
||||
[pimpl->nsProgressIndicator stopAnimation:nil];
|
||||
}
|
86
src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp
Normal file
86
src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qprogressindicatorspinning.h"
|
||||
|
||||
#include <QtGui/QVBoxLayout>
|
||||
#include <QtGui/QMovie>
|
||||
#include <QtGui/QLabel>
|
||||
|
||||
class QProgressIndicatorSpinningPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning,
|
||||
QMovie *movie)
|
||||
: QObject(qProgressIndicatorSpinning), movie(movie) {}
|
||||
|
||||
QPointer<QMovie> movie;
|
||||
};
|
||||
|
||||
struct QProgressIndicatorSpinningResources
|
||||
{
|
||||
#ifndef Q_OS_MAC
|
||||
QProgressIndicatorSpinningResources() {
|
||||
Q_INIT_RESOURCE(qprogressindicatorspinning_nonmac);
|
||||
}
|
||||
~QProgressIndicatorSpinningResources() {
|
||||
Q_CLEANUP_RESOURCE(qprogressindicatorspinning_nonmac);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent,
|
||||
Thickness thickness)
|
||||
: QWidget(parent)
|
||||
{
|
||||
static QProgressIndicatorSpinningResources resources;
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
|
||||
QSize size(thickness, thickness);
|
||||
QMovie *movie = new QMovie(this);
|
||||
movie->setFileName(":/Qocoa/qprogressindicatorspinning_nonmac.gif");
|
||||
movie->setScaledSize(size);
|
||||
// Roughly match OSX speed.
|
||||
movie->setSpeed(200);
|
||||
pimpl = new QProgressIndicatorSpinningPrivate(this, movie);
|
||||
|
||||
QLabel *label = new QLabel(this);
|
||||
label->setMovie(movie);
|
||||
|
||||
layout->addWidget(label);
|
||||
setFixedSize(size);
|
||||
}
|
||||
|
||||
|
||||
void QProgressIndicatorSpinning::animate(bool animate)
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->movie);
|
||||
if (!(pimpl && pimpl->movie))
|
||||
return;
|
||||
|
||||
if (animate)
|
||||
pimpl->movie->start();
|
||||
else
|
||||
pimpl->movie->stop();
|
||||
}
|
BIN
src/gui/qocoa/qprogressindicatorspinning_nonmac.gif
Normal file
BIN
src/gui/qocoa/qprogressindicatorspinning_nonmac.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
5
src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc
Normal file
5
src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/Qocoa">
|
||||
<file>qprogressindicatorspinning_nonmac.gif</file>
|
||||
</qresource>
|
||||
</RCC>
|
48
src/gui/qocoa/qsearchfield.h
Normal file
48
src/gui/qocoa/qsearchfield.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef QSEARCHFIELD_H
|
||||
#define QSEARCHFIELD_H
|
||||
|
||||
#include <QtGui/QWidget>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtGui/QMenu>
|
||||
|
||||
class QSearchFieldPrivate;
|
||||
class QSearchField : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged USER true);
|
||||
Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText);
|
||||
|
||||
public:
|
||||
explicit QSearchField(QWidget *parent);
|
||||
|
||||
QString text() const;
|
||||
QString placeholderText() const;
|
||||
void setFocus(Qt::FocusReason);
|
||||
void setMenu(QMenu *menu);
|
||||
|
||||
public Q_SLOTS:
|
||||
void setText(const QString &text);
|
||||
void setPlaceholderText(const QString &text);
|
||||
void clear();
|
||||
void selectAll();
|
||||
void setFocus();
|
||||
|
||||
Q_SIGNALS:
|
||||
void textChanged(const QString &text);
|
||||
void editingFinished();
|
||||
void returnPressed();
|
||||
|
||||
private Q_SLOTS:
|
||||
void popupMenu();
|
||||
|
||||
protected:
|
||||
void changeEvent(QEvent*);
|
||||
void resizeEvent(QResizeEvent*);
|
||||
|
||||
private:
|
||||
friend class QSearchFieldPrivate;
|
||||
QPointer <QSearchFieldPrivate> pimpl;
|
||||
};
|
||||
|
||||
#endif // QSEARCHFIELD_H
|
257
src/gui/qocoa/qsearchfield_mac.mm
Normal file
257
src/gui/qocoa/qsearchfield_mac.mm
Normal file
@ -0,0 +1,257 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qsearchfield.h"
|
||||
|
||||
#include "qocoa_mac.h"
|
||||
|
||||
#import "Foundation/NSAutoreleasePool.h"
|
||||
#import "Foundation/NSNotification.h"
|
||||
#import "AppKit/NSSearchField.h"
|
||||
|
||||
#include <QtGui/QApplication>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
#define KEYCODE_A 0
|
||||
#define KEYCODE_X 7
|
||||
#define KEYCODE_C 8
|
||||
#define KEYCODE_V 9
|
||||
|
||||
class QSearchFieldPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QSearchFieldPrivate(QSearchField *qSearchField, NSSearchField *nsSearchField)
|
||||
: QObject(qSearchField), qSearchField(qSearchField), nsSearchField(nsSearchField) {}
|
||||
|
||||
void textDidChange(const QString &text)
|
||||
{
|
||||
if (qSearchField)
|
||||
Q_EMIT qSearchField->textChanged(text);
|
||||
}
|
||||
|
||||
void textDidEndEditing()
|
||||
{
|
||||
if (qSearchField)
|
||||
Q_EMIT qSearchField->editingFinished();
|
||||
}
|
||||
|
||||
void returnPressed()
|
||||
{
|
||||
if (qSearchField)
|
||||
Q_EMIT qSearchField->returnPressed();
|
||||
}
|
||||
|
||||
QPointer<QSearchField> qSearchField;
|
||||
NSSearchField *nsSearchField;
|
||||
};
|
||||
|
||||
@interface QSearchFieldDelegate : NSObject<NSTextFieldDelegate>
|
||||
{
|
||||
@public
|
||||
QPointer<QSearchFieldPrivate> pimpl;
|
||||
}
|
||||
-(void)controlTextDidChange:(NSNotification*)notification;
|
||||
-(void)controlTextDidEndEditing:(NSNotification*)notification;
|
||||
@end
|
||||
|
||||
@implementation QSearchFieldDelegate
|
||||
-(void)controlTextDidChange:(NSNotification*)notification {
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
pimpl->textDidChange(toQString([[notification object] stringValue]));
|
||||
}
|
||||
|
||||
-(void)controlTextDidEndEditing:(NSNotification*)notification {
|
||||
Q_UNUSED(notification);
|
||||
// No Q_ASSERT here as it is called on destruction.
|
||||
if (pimpl)
|
||||
pimpl->textDidEndEditing();
|
||||
|
||||
if ([[[notification userInfo] objectForKey:@"NSTextMovement"] intValue] == NSReturnTextMovement)
|
||||
pimpl->returnPressed();
|
||||
}
|
||||
@end
|
||||
|
||||
@interface QocoaSearchField : NSSearchField
|
||||
-(BOOL)performKeyEquivalent:(NSEvent*)event;
|
||||
@end
|
||||
|
||||
@implementation QocoaSearchField
|
||||
-(BOOL)performKeyEquivalent:(NSEvent*)event {
|
||||
if ([event type] == NSKeyDown && [event modifierFlags] & NSCommandKeyMask)
|
||||
{
|
||||
const unsigned short keyCode = [event keyCode];
|
||||
if (keyCode == KEYCODE_A)
|
||||
{
|
||||
[self performSelector:@selector(selectText:)];
|
||||
return YES;
|
||||
}
|
||||
else if (keyCode == KEYCODE_C)
|
||||
{
|
||||
QClipboard* clipboard = QApplication::clipboard();
|
||||
clipboard->setText(toQString([self stringValue]));
|
||||
return YES;
|
||||
}
|
||||
else if (keyCode == KEYCODE_V)
|
||||
{
|
||||
QClipboard* clipboard = QApplication::clipboard();
|
||||
[self setStringValue:fromQString(clipboard->text())];
|
||||
return YES;
|
||||
}
|
||||
else if (keyCode == KEYCODE_X)
|
||||
{
|
||||
QClipboard* clipboard = QApplication::clipboard();
|
||||
clipboard->setText(toQString([self stringValue]));
|
||||
[self setStringValue:@""];
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
@end
|
||||
|
||||
QSearchField::QSearchField(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSSearchField *search = [[QocoaSearchField alloc] init];
|
||||
|
||||
QSearchFieldDelegate *delegate = [[QSearchFieldDelegate alloc] init];
|
||||
pimpl = delegate->pimpl = new QSearchFieldPrivate(this, search);
|
||||
[search setDelegate:delegate];
|
||||
|
||||
setupLayout(search, this);
|
||||
|
||||
setFixedHeight(24);
|
||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
|
||||
|
||||
[search release];
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QSearchField::setMenu(QMenu *menu)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
NSMenu *nsMenu = menu->macMenu();
|
||||
[[pimpl->nsSearchField cell] setSearchMenuTemplate:nsMenu];
|
||||
}
|
||||
|
||||
void QSearchField::popupMenu()
|
||||
{
|
||||
}
|
||||
|
||||
void QSearchField::setText(const QString &text)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
[pimpl->nsSearchField setStringValue:fromQString(text)];
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QSearchField::setPlaceholderText(const QString &text)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
[[pimpl->nsSearchField cell] setPlaceholderString:fromQString(text)];
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QSearchField::clear()
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
[pimpl->nsSearchField setStringValue:@""];
|
||||
Q_EMIT textChanged(QString());
|
||||
}
|
||||
|
||||
void QSearchField::selectAll()
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
[pimpl->nsSearchField performSelector:@selector(selectText:)];
|
||||
}
|
||||
|
||||
QString QSearchField::text() const
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return QString();
|
||||
|
||||
return toQString([pimpl->nsSearchField stringValue]);
|
||||
}
|
||||
|
||||
QString QSearchField::placeholderText() const
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return QString();
|
||||
|
||||
return toQString([[pimpl->nsSearchField cell] placeholderString]);
|
||||
}
|
||||
|
||||
void QSearchField::setFocus(Qt::FocusReason)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
if ([pimpl->nsSearchField acceptsFirstResponder])
|
||||
[[pimpl->nsSearchField window] makeFirstResponder: pimpl->nsSearchField];
|
||||
}
|
||||
|
||||
void QSearchField::setFocus()
|
||||
{
|
||||
setFocus(Qt::OtherFocusReason);
|
||||
}
|
||||
|
||||
void QSearchField::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::EnabledChange) {
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
const bool enabled = isEnabled();
|
||||
[pimpl->nsSearchField setEnabled: enabled];
|
||||
}
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void QSearchField::resizeEvent(QResizeEvent *resizeEvent)
|
||||
{
|
||||
QWidget::resizeEvent(resizeEvent);
|
||||
}
|
270
src/gui/qocoa/qsearchfield_nonmac.cpp
Normal file
270
src/gui/qocoa/qsearchfield_nonmac.cpp
Normal file
@ -0,0 +1,270 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qsearchfield.h"
|
||||
|
||||
#include <QtGui/QLineEdit>
|
||||
#include <QtGui/QVBoxLayout>
|
||||
#include <QtGui/QToolButton>
|
||||
#include <QtGui/QStyle>
|
||||
#include <QtGui/QApplication>
|
||||
#include <QtGui/QDesktopWidget>
|
||||
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
class QSearchFieldPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QSearchFieldPrivate(QSearchField *searchField, QLineEdit *lineEdit, QToolButton *clearButton, QToolButton *searchButton)
|
||||
: QObject(searchField), lineEdit(lineEdit), clearButton(clearButton), searchButton(searchButton) {}
|
||||
|
||||
int lineEditFrameWidth() const {
|
||||
return lineEdit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth);
|
||||
}
|
||||
|
||||
int clearButtonPaddedWidth() const {
|
||||
return clearButton->width() + lineEditFrameWidth() * 2;
|
||||
}
|
||||
|
||||
int clearButtonPaddedHeight() const {
|
||||
return clearButton->height() + lineEditFrameWidth() * 2;
|
||||
}
|
||||
|
||||
int searchButtonPaddedWidth() const {
|
||||
return searchButton->width() + lineEditFrameWidth() * 2;
|
||||
}
|
||||
|
||||
int searchButtonPaddedHeight() const {
|
||||
return searchButton->height() + lineEditFrameWidth() * 2;
|
||||
}
|
||||
|
||||
QPointer<QLineEdit> lineEdit;
|
||||
QPointer<QToolButton> clearButton;
|
||||
QPointer<QToolButton> searchButton;
|
||||
QPointer<QMenu> searchMenu;
|
||||
};
|
||||
|
||||
struct QSearchFieldResources
|
||||
{
|
||||
#ifndef Q_OS_MAC
|
||||
QSearchFieldResources() {
|
||||
Q_INIT_RESOURCE(qsearchfield_nonmac);
|
||||
}
|
||||
~QSearchFieldResources() {
|
||||
Q_CLEANUP_RESOURCE(qsearchfield_nonmac);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
QSearchField::QSearchField(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
static QSearchFieldResources resources;
|
||||
|
||||
QLineEdit *lineEdit = new QLineEdit(this);
|
||||
connect(lineEdit, SIGNAL(textChanged(QString)),
|
||||
this, SIGNAL(textChanged(QString)));
|
||||
connect(lineEdit, SIGNAL(editingFinished()),
|
||||
this, SIGNAL(editingFinished()));
|
||||
connect(lineEdit, SIGNAL(returnPressed()),
|
||||
this, SIGNAL(returnPressed()));
|
||||
connect(lineEdit, SIGNAL(textChanged(QString)),
|
||||
this, SLOT(setText(QString)));
|
||||
|
||||
int iconsize = style()->pixelMetric(QStyle::PM_SmallIconSize);
|
||||
QToolButton *clearButton = new QToolButton(this);
|
||||
QIcon clearIcon = QIcon::fromTheme(QLatin1String("edit-clear"),
|
||||
QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_clear.png")));
|
||||
clearButton->setIcon(clearIcon);
|
||||
clearButton->setIconSize(QSize(iconsize, iconsize));
|
||||
clearButton->setFixedSize(QSize(iconsize, iconsize));
|
||||
clearButton->setStyleSheet("border: none;");
|
||||
clearButton->hide();
|
||||
connect(clearButton, SIGNAL(clicked()), this, SLOT(clear()));
|
||||
|
||||
QToolButton *searchButton = new QToolButton(this);
|
||||
QIcon searchIcon = QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_magnifier.png"));
|
||||
searchButton->setIcon(searchIcon);
|
||||
searchButton->setIconSize(QSize(iconsize, iconsize));
|
||||
searchButton->setFixedSize(QSize(iconsize, iconsize));
|
||||
searchButton->setStyleSheet("border: none;");
|
||||
searchButton->setPopupMode(QToolButton::InstantPopup);
|
||||
searchButton->setEnabled(false);
|
||||
connect(searchButton, SIGNAL(clicked()), this, SLOT(popupMenu()));
|
||||
|
||||
pimpl = new QSearchFieldPrivate(this, lineEdit, clearButton, searchButton);
|
||||
|
||||
lineEdit->setStyleSheet(QString("QLineEdit { padding-left: %1px; padding-right: %2px; } ")
|
||||
.arg(pimpl->searchButtonPaddedWidth())
|
||||
.arg(pimpl->clearButtonPaddedWidth()));
|
||||
const int width = qMax(lineEdit->minimumSizeHint().width(), pimpl->clearButtonPaddedWidth() + pimpl->searchButtonPaddedWidth());
|
||||
const int height = qMax(lineEdit->minimumSizeHint().height(),
|
||||
qMax(pimpl->clearButtonPaddedHeight(),
|
||||
pimpl->searchButtonPaddedHeight()));
|
||||
lineEdit->setMinimumSize(width, height);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
layout->setMargin(0);
|
||||
layout->addWidget(lineEdit);
|
||||
}
|
||||
|
||||
void QSearchField::setMenu(QMenu *menu)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
pimpl->searchMenu = menu;
|
||||
|
||||
QIcon searchIcon = menu ? QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_magnifier_menu.png"))
|
||||
: QIcon(QLatin1String(":/Qocoa/qsearchfield_nonmac_magnifier.png"));
|
||||
pimpl->searchButton->setIcon(searchIcon);
|
||||
pimpl->searchButton->setEnabled(isEnabled() && menu);
|
||||
}
|
||||
|
||||
void QSearchField::popupMenu()
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
if (pimpl->searchMenu) {
|
||||
const QRect screenRect = qApp->desktop()->availableGeometry(pimpl->searchButton);
|
||||
const QSize sizeHint = pimpl->searchMenu->sizeHint();
|
||||
const QRect rect = pimpl->searchButton->rect();
|
||||
const int x = pimpl->searchButton->isRightToLeft()
|
||||
? rect.right() - sizeHint.width()
|
||||
: rect.left();
|
||||
const int y = pimpl->searchButton->mapToGlobal(QPoint(0, rect.bottom())).y() + sizeHint.height() <= screenRect.height()
|
||||
? rect.bottom()
|
||||
: rect.top() - sizeHint.height();
|
||||
QPoint point = pimpl->searchButton->mapToGlobal(QPoint(x, y));
|
||||
point.rx() = qMax(screenRect.left(), qMin(point.x(), screenRect.right() - sizeHint.width()));
|
||||
point.ry() += 1;
|
||||
|
||||
pimpl->searchMenu->popup(point);
|
||||
}
|
||||
}
|
||||
|
||||
void QSearchField::changeEvent(QEvent* event)
|
||||
{
|
||||
if (event->type() == QEvent::EnabledChange) {
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
const bool enabled = isEnabled();
|
||||
pimpl->searchButton->setEnabled(enabled && pimpl->searchMenu);
|
||||
pimpl->lineEdit->setEnabled(enabled);
|
||||
pimpl->clearButton->setEnabled(enabled);
|
||||
}
|
||||
QWidget::changeEvent(event);
|
||||
}
|
||||
|
||||
void QSearchField::setText(const QString &text)
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->clearButton && pimpl->lineEdit);
|
||||
if (!(pimpl && pimpl->clearButton && pimpl->lineEdit))
|
||||
return;
|
||||
|
||||
pimpl->clearButton->setVisible(!text.isEmpty());
|
||||
|
||||
if (text != this->text())
|
||||
pimpl->lineEdit->setText(text);
|
||||
}
|
||||
|
||||
void QSearchField::setPlaceholderText(const QString &text)
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->lineEdit);
|
||||
if (!(pimpl && pimpl->lineEdit))
|
||||
return;
|
||||
|
||||
#if QT_VERSION >= 0x040700
|
||||
pimpl->lineEdit->setPlaceholderText(text);
|
||||
#endif
|
||||
}
|
||||
|
||||
void QSearchField::clear()
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->lineEdit);
|
||||
if (!(pimpl && pimpl->lineEdit))
|
||||
return;
|
||||
|
||||
pimpl->lineEdit->clear();
|
||||
}
|
||||
|
||||
void QSearchField::selectAll()
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->lineEdit);
|
||||
if (!(pimpl && pimpl->lineEdit))
|
||||
return;
|
||||
|
||||
pimpl->lineEdit->selectAll();
|
||||
}
|
||||
|
||||
QString QSearchField::text() const
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->lineEdit);
|
||||
if (!(pimpl && pimpl->lineEdit))
|
||||
return QString();
|
||||
|
||||
return pimpl->lineEdit->text();
|
||||
}
|
||||
|
||||
QString QSearchField::placeholderText() const {
|
||||
Q_ASSERT(pimpl && pimpl->lineEdit);
|
||||
if (!(pimpl && pimpl->lineEdit))
|
||||
return QString();
|
||||
|
||||
#if QT_VERSION >= 0x040700
|
||||
return pimpl->lineEdit->placeholderText();
|
||||
#else
|
||||
return QString();
|
||||
#endif
|
||||
}
|
||||
|
||||
void QSearchField::setFocus(Qt::FocusReason reason)
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->lineEdit);
|
||||
if (pimpl && pimpl->lineEdit)
|
||||
pimpl->lineEdit->setFocus(reason);
|
||||
}
|
||||
|
||||
void QSearchField::setFocus()
|
||||
{
|
||||
setFocus(Qt::OtherFocusReason);
|
||||
}
|
||||
|
||||
void QSearchField::resizeEvent(QResizeEvent *resizeEvent)
|
||||
{
|
||||
Q_ASSERT(pimpl && pimpl->clearButton && pimpl->lineEdit);
|
||||
if (!(pimpl && pimpl->clearButton && pimpl->lineEdit))
|
||||
return;
|
||||
|
||||
QWidget::resizeEvent(resizeEvent);
|
||||
const int x = width() - pimpl->clearButtonPaddedWidth();
|
||||
const int y = (height() - pimpl->clearButton->height())/2;
|
||||
pimpl->clearButton->move(x, y);
|
||||
|
||||
pimpl->searchButton->move(pimpl->lineEditFrameWidth() * 2,
|
||||
(height() - pimpl->searchButton->height())/2);
|
||||
}
|
7
src/gui/qocoa/qsearchfield_nonmac.qrc
Normal file
7
src/gui/qocoa/qsearchfield_nonmac.qrc
Normal file
@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/Qocoa">
|
||||
<file>qsearchfield_nonmac_clear.png</file>
|
||||
<file>qsearchfield_nonmac_magnifier_menu.png</file>
|
||||
<file>qsearchfield_nonmac_magnifier.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
BIN
src/gui/qocoa/qsearchfield_nonmac_clear.png
Normal file
BIN
src/gui/qocoa/qsearchfield_nonmac_clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 736 B |
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier.png
Normal file
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 300 B |
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier_menu.png
Normal file
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier_menu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 439 B |
52
src/http/AccessControlDialog.cpp
Normal file
52
src/http/AccessControlDialog.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file AccessControlDialog.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "AccessControlDialog.h"
|
||||
#include "ui_AccessControlDialog.h"
|
||||
#include "core/Entry.h"
|
||||
|
||||
AccessControlDialog::AccessControlDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AccessControlDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
|
||||
connect(ui->denyButton, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
}
|
||||
|
||||
AccessControlDialog::~AccessControlDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AccessControlDialog::setUrl(const QString &url)
|
||||
{
|
||||
ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n"
|
||||
"Please select whether you want to allow access.")).arg(QUrl(url).host()));
|
||||
}
|
||||
|
||||
void AccessControlDialog::setItems(const QList<Entry *> &items)
|
||||
{
|
||||
Q_FOREACH (Entry * entry, items)
|
||||
ui->itemsList->addItem(entry->title() + " - " + entry->username());
|
||||
}
|
||||
|
||||
bool AccessControlDialog::remember() const
|
||||
{
|
||||
return ui->rememberDecisionCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void AccessControlDialog::setRemember(bool r)
|
||||
{
|
||||
ui->rememberDecisionCheckBox->setChecked(r);
|
||||
}
|
42
src/http/AccessControlDialog.h
Normal file
42
src/http/AccessControlDialog.h
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file AccessControlDialog.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef ACCESSCONTROLDIALOG_H
|
||||
#define ACCESSCONTROLDIALOG_H
|
||||
|
||||
#include <QtGui/QDialog>
|
||||
|
||||
class Entry;
|
||||
|
||||
namespace Ui {
|
||||
class AccessControlDialog;
|
||||
}
|
||||
|
||||
class AccessControlDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AccessControlDialog(QWidget *parent = 0);
|
||||
~AccessControlDialog();
|
||||
|
||||
void setUrl(const QString & url);
|
||||
void setItems(const QList<Entry *> & items);
|
||||
bool remember() const;
|
||||
void setRemember(bool r);
|
||||
|
||||
private:
|
||||
Ui::AccessControlDialog *ui;
|
||||
};
|
||||
|
||||
#endif // ACCESSCONTROLDIALOG_H
|
69
src/http/AccessControlDialog.ui
Normal file
69
src/http/AccessControlDialog.ui
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AccessControlDialog</class>
|
||||
<widget class="QDialog" name="AccessControlDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>KeyPassX/Http: Confirm Access</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="itemsList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="rememberDecisionCheckBox">
|
||||
<property name="text">
|
||||
<string>Remember this decision</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="allowButton">
|
||||
<property name="text">
|
||||
<string>Allow</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="denyButton">
|
||||
<property name="text">
|
||||
<string>Deny</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
100
src/http/EntryConfig.cpp
Normal file
100
src/http/EntryConfig.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file EntryConfig.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "EntryConfig.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "qjson/parser.h"
|
||||
#include "qjson/qobjecthelper.h"
|
||||
#include "qjson/serializer.h"
|
||||
|
||||
static const char KEEPASSHTTP_NAME[] = "KeePassHttp Settings"; //TODO: duplicated string (also in Service.cpp)
|
||||
|
||||
EntryConfig::EntryConfig(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QStringList EntryConfig::allowedHosts() const
|
||||
{
|
||||
return m_allowedHosts.toList();
|
||||
}
|
||||
|
||||
void EntryConfig::setAllowedHosts(const QStringList &allowedHosts)
|
||||
{
|
||||
m_allowedHosts = allowedHosts.toSet();
|
||||
}
|
||||
|
||||
QStringList EntryConfig::deniedHosts() const
|
||||
{
|
||||
return m_deniedHosts.toList();
|
||||
}
|
||||
|
||||
void EntryConfig::setDeniedHosts(const QStringList &deniedHosts)
|
||||
{
|
||||
m_deniedHosts = deniedHosts.toSet();
|
||||
}
|
||||
|
||||
bool EntryConfig::isAllowed(const QString &host)
|
||||
{
|
||||
return m_allowedHosts.contains(host);
|
||||
}
|
||||
|
||||
void EntryConfig::allow(const QString &host)
|
||||
{
|
||||
m_allowedHosts.insert(host);
|
||||
m_deniedHosts.remove(host);
|
||||
}
|
||||
|
||||
bool EntryConfig::isDenied(const QString &host)
|
||||
{
|
||||
return m_deniedHosts.contains(host);
|
||||
}
|
||||
|
||||
void EntryConfig::deny(const QString &host)
|
||||
{
|
||||
m_deniedHosts.insert(host);
|
||||
m_allowedHosts.remove(host);
|
||||
}
|
||||
|
||||
QString EntryConfig::realm() const
|
||||
{
|
||||
return m_realm;
|
||||
}
|
||||
|
||||
void EntryConfig::setRealm(const QString &realm)
|
||||
{
|
||||
m_realm = realm;
|
||||
}
|
||||
|
||||
bool EntryConfig::load(const Entry *entry)
|
||||
{
|
||||
QString s = entry->attributes()->value(KEEPASSHTTP_NAME);
|
||||
if (s.isEmpty())
|
||||
return false;
|
||||
|
||||
bool isOk = false;
|
||||
QVariant v = QJson::Parser().parse(s.toUtf8(), &isOk);
|
||||
if (!isOk || !v.type() == QVariant::Map)
|
||||
return false;
|
||||
|
||||
QJson::QObjectHelper::qvariant2qobject(v.toMap(), this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void EntryConfig::save(Entry *entry)
|
||||
{
|
||||
QVariant v = QJson::QObjectHelper::qobject2qvariant(this, QJson::QObjectHelper::Flag_None);
|
||||
QByteArray json = QJson::Serializer().serialize(v);
|
||||
entry->attributes()->set(KEEPASSHTTP_NAME, json);
|
||||
}
|
54
src/http/EntryConfig.h
Normal file
54
src/http/EntryConfig.h
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file EntryConfig.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef ENTRYCONFIG_H
|
||||
#define ENTRYCONFIG_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QSet>
|
||||
|
||||
class Entry;
|
||||
|
||||
class EntryConfig : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QStringList Allow READ allowedHosts WRITE setAllowedHosts)
|
||||
Q_PROPERTY(QStringList Deny READ deniedHosts WRITE setDeniedHosts )
|
||||
Q_PROPERTY(QString Realm READ realm WRITE setRealm )
|
||||
|
||||
public:
|
||||
EntryConfig(QObject * object = 0);
|
||||
|
||||
bool load(const Entry * entry);
|
||||
void save(Entry * entry);
|
||||
bool isAllowed(const QString & host);
|
||||
void allow(const QString & host);
|
||||
bool isDenied(const QString & host);
|
||||
void deny(const QString & host);
|
||||
QString realm() const;
|
||||
void setRealm(const QString &realm);
|
||||
|
||||
private:
|
||||
QStringList allowedHosts() const;
|
||||
void setAllowedHosts(const QStringList &allowedHosts);
|
||||
QStringList deniedHosts() const;
|
||||
void setDeniedHosts(const QStringList &deniedHosts);
|
||||
|
||||
QSet<QString> m_allowedHosts;
|
||||
QSet<QString> m_deniedHosts;
|
||||
QString m_realm;
|
||||
};
|
||||
|
||||
#endif // ENTRYCONFIG_H
|
219
src/http/HttpSettings.cpp
Normal file
219
src/http/HttpSettings.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file HttpSettings.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "HttpSettings.h"
|
||||
#include "core/Config.h"
|
||||
|
||||
bool HttpSettings::isEnabled()
|
||||
{
|
||||
return config()->get("Http/Enabled", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setEnabled(bool enabled)
|
||||
{
|
||||
config()->set("Http/Enabled", enabled);
|
||||
}
|
||||
|
||||
bool HttpSettings::showNotification()
|
||||
{
|
||||
return config()->get("Http/ShowNotification", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setShowNotification(bool showNotification)
|
||||
{
|
||||
config()->set("Http/ShowNotification", showNotification);
|
||||
}
|
||||
|
||||
bool HttpSettings::bestMatchOnly()
|
||||
{
|
||||
return config()->get("Http/BestMatchOnly", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setBestMatchOnly(bool bestMatchOnly)
|
||||
{
|
||||
config()->set("Http/BestMatchOnly", bestMatchOnly);
|
||||
}
|
||||
|
||||
bool HttpSettings::unlockDatabase()
|
||||
{
|
||||
return config()->get("Http/UnlockDatabase", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setUnlockDatabase(bool unlockDatabase)
|
||||
{
|
||||
config()->set("Http/UnlockDatabase", unlockDatabase);
|
||||
}
|
||||
|
||||
bool HttpSettings::matchUrlScheme()
|
||||
{
|
||||
return config()->get("Http/MatchUrlScheme", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setMatchUrlScheme(bool matchUrlScheme)
|
||||
{
|
||||
config()->set("Http/MatchUrlScheme", matchUrlScheme);
|
||||
}
|
||||
|
||||
bool HttpSettings::sortByUsername()
|
||||
{
|
||||
return config()->get("Http/SortByUsername", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setSortByUsername(bool sortByUsername)
|
||||
{
|
||||
config()->set("Http/SortByUsername", sortByUsername);
|
||||
}
|
||||
|
||||
bool HttpSettings::sortByTitle()
|
||||
{
|
||||
return !sortByUsername();
|
||||
}
|
||||
|
||||
void HttpSettings::setSortByTitle(bool sortByUsertitle)
|
||||
{
|
||||
setSortByUsername(!sortByUsertitle);
|
||||
}
|
||||
|
||||
bool HttpSettings::alwaysAllowAccess()
|
||||
{
|
||||
return config()->get("Http/AlwaysAllowAccess", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setAlwaysAllowAccess(bool alwaysAllowAccess)
|
||||
{
|
||||
config()->set("Http/AlwaysAllowAccess", alwaysAllowAccess);
|
||||
}
|
||||
|
||||
bool HttpSettings::alwaysAllowUpdate()
|
||||
{
|
||||
return config()->get("Http/AlwaysAllowUpdate", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setAlwaysAllowUpdate(bool alwaysAllowUpdate)
|
||||
{
|
||||
config()->set("Http/AlwaysAllowAccess", alwaysAllowUpdate);
|
||||
}
|
||||
|
||||
bool HttpSettings::searchInAllDatabases()
|
||||
{
|
||||
return config()->get("Http/SearchInAllDatabases", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setSearchInAllDatabases(bool searchInAllDatabases)
|
||||
{
|
||||
config()->set("Http/SearchInAllDatabases", searchInAllDatabases);
|
||||
}
|
||||
|
||||
bool HttpSettings::supportKphFields()
|
||||
{
|
||||
return config()->get("Http/SupportKphFields", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setSupportKphFields(bool supportKphFields)
|
||||
{
|
||||
config()->set("Http/SupportKphFields", supportKphFields);
|
||||
}
|
||||
|
||||
bool HttpSettings::passwordUseNumbers()
|
||||
{
|
||||
return config()->get("Http/PasswordUseNumbers", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setPasswordUseNumbers(bool useNumbers)
|
||||
{
|
||||
config()->set("Http/PasswordUseNumbers", useNumbers);
|
||||
}
|
||||
|
||||
bool HttpSettings::passwordUseLowercase()
|
||||
{
|
||||
return config()->get("Http/PasswordUseLowercase", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setPasswordUseLowercase(bool useLowercase)
|
||||
{
|
||||
config()->set("Http/PasswordUseLowercase", useLowercase);
|
||||
}
|
||||
|
||||
bool HttpSettings::passwordUseUppercase()
|
||||
{
|
||||
return config()->get("Http/PasswordUseUppercase", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setPasswordUseUppercase(bool useUppercase)
|
||||
{
|
||||
config()->set("Http/PasswordUseUppercase", useUppercase);
|
||||
}
|
||||
|
||||
bool HttpSettings::passwordUseSpecial()
|
||||
{
|
||||
return config()->get("Http/PasswordUseSpecial", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setPasswordUseSpecial(bool useSpecial)
|
||||
{
|
||||
config()->set("Http/PasswordUseSpecial", useSpecial);
|
||||
}
|
||||
|
||||
bool HttpSettings::passwordEveryGroup()
|
||||
{
|
||||
return config()->get("Http/PasswordEveryGroup", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setPasswordEveryGroup(bool everyGroup)
|
||||
{
|
||||
config()->get("Http/PasswordEveryGroup", everyGroup);
|
||||
}
|
||||
|
||||
bool HttpSettings::passwordExcludeAlike()
|
||||
{
|
||||
return config()->get("Http/PasswordExcludeAlike", true).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setPasswordExcludeAlike(bool excludeAlike)
|
||||
{
|
||||
config()->set("Http/PasswordExcludeAlike", excludeAlike);
|
||||
}
|
||||
|
||||
int HttpSettings::passwordLength()
|
||||
{
|
||||
return config()->get("Http/PasswordLength", 20).toInt();
|
||||
}
|
||||
|
||||
void HttpSettings::setPasswordLength(int length)
|
||||
{
|
||||
config()->set("Http/PasswordLength", length);
|
||||
}
|
||||
|
||||
PasswordGenerator::CharClasses HttpSettings::passwordCharClasses()
|
||||
{
|
||||
PasswordGenerator::CharClasses classes;
|
||||
if (passwordUseLowercase())
|
||||
classes |= PasswordGenerator::LowerLetters;
|
||||
if (passwordUseUppercase())
|
||||
classes |= PasswordGenerator::UpperLetters;
|
||||
if (passwordUseNumbers())
|
||||
classes |= PasswordGenerator::Numbers;
|
||||
if (passwordUseSpecial())
|
||||
classes |= PasswordGenerator::SpecialCharacters;
|
||||
return classes;
|
||||
}
|
||||
|
||||
PasswordGenerator::GeneratorFlags HttpSettings::passwordGeneratorFlags()
|
||||
{
|
||||
PasswordGenerator::GeneratorFlags flags;
|
||||
if (passwordExcludeAlike())
|
||||
flags |= PasswordGenerator::ExcludeLookAlike;
|
||||
if (passwordEveryGroup())
|
||||
flags |= PasswordGenerator::CharFromEveryGroup;
|
||||
return flags;
|
||||
}
|
64
src/http/HttpSettings.h
Normal file
64
src/http/HttpSettings.h
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file HttpSettings.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef HTTPSETTINGS_H
|
||||
#define HTTPSETTINGS_H
|
||||
|
||||
#include "core/PasswordGenerator.h"
|
||||
|
||||
class HttpSettings
|
||||
{
|
||||
public:
|
||||
static bool isEnabled();
|
||||
static void setEnabled(bool enabled);
|
||||
|
||||
static bool showNotification(); //TODO!!
|
||||
static void setShowNotification(bool showNotification);
|
||||
static bool bestMatchOnly(); //TODO!!
|
||||
static void setBestMatchOnly(bool bestMatchOnly);
|
||||
static bool unlockDatabase(); //TODO!!
|
||||
static void setUnlockDatabase(bool unlockDatabase);
|
||||
static bool matchUrlScheme();
|
||||
static void setMatchUrlScheme(bool matchUrlScheme);
|
||||
static bool sortByUsername();
|
||||
static void setSortByUsername(bool sortByUsername = true);
|
||||
static bool sortByTitle();
|
||||
static void setSortByTitle(bool sortByUsertitle = true);
|
||||
static bool alwaysAllowAccess();
|
||||
static void setAlwaysAllowAccess(bool alwaysAllowAccess);
|
||||
static bool alwaysAllowUpdate();
|
||||
static void setAlwaysAllowUpdate(bool alwaysAllowUpdate);
|
||||
static bool searchInAllDatabases();//TODO!!
|
||||
static void setSearchInAllDatabases(bool searchInAllDatabases);
|
||||
static bool supportKphFields();
|
||||
static void setSupportKphFields(bool supportKphFields);
|
||||
|
||||
static bool passwordUseNumbers();
|
||||
static void setPasswordUseNumbers(bool useNumbers);
|
||||
static bool passwordUseLowercase();
|
||||
static void setPasswordUseLowercase(bool useLowercase);
|
||||
static bool passwordUseUppercase();
|
||||
static void setPasswordUseUppercase(bool useUppercase);
|
||||
static bool passwordUseSpecial();
|
||||
static void setPasswordUseSpecial(bool useSpecial);
|
||||
static bool passwordEveryGroup();
|
||||
static void setPasswordEveryGroup(bool everyGroup);
|
||||
static bool passwordExcludeAlike();
|
||||
static void setPasswordExcludeAlike(bool excludeAlike);
|
||||
static int passwordLength();
|
||||
static void setPasswordLength(int length);
|
||||
static PasswordGenerator::CharClasses passwordCharClasses();
|
||||
static PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
|
||||
};
|
||||
|
||||
#endif // HTTPSETTINGS_H
|
81
src/http/OptionDialog.cpp
Normal file
81
src/http/OptionDialog.cpp
Normal file
@ -0,0 +1,81 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file OptionDialog.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "OptionDialog.h"
|
||||
#include "ui_OptionDialog.h"
|
||||
#include "HttpSettings.h"
|
||||
|
||||
OptionDialog::OptionDialog(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::OptionDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys()));
|
||||
connect(ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions()));
|
||||
}
|
||||
|
||||
OptionDialog::~OptionDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void OptionDialog::loadSettings()
|
||||
{
|
||||
HttpSettings settings;
|
||||
ui->enableHttpServer->setChecked(settings.isEnabled());
|
||||
|
||||
ui->showNotification->setChecked(settings.showNotification());
|
||||
ui->bestMatchOnly->setChecked(settings.bestMatchOnly());
|
||||
ui->unlockDatabase->setChecked(settings.unlockDatabase());
|
||||
ui->matchUrlScheme->setChecked(settings.matchUrlScheme());
|
||||
if (settings.sortByUsername())
|
||||
ui->sortByUsername->setChecked(true);
|
||||
else
|
||||
ui->sortByTitle->setChecked(true);
|
||||
|
||||
ui->checkBoxLower->setChecked(settings.passwordUseLowercase());
|
||||
ui->checkBoxNumbers->setChecked(settings.passwordUseNumbers());
|
||||
ui->checkBoxUpper->setChecked(settings.passwordUseUppercase());
|
||||
ui->checkBoxSpecialChars->setChecked(settings.passwordUseSpecial());
|
||||
ui->checkBoxEnsureEvery->setChecked(settings.passwordEveryGroup());
|
||||
ui->checkBoxExcludeAlike->setChecked(settings.passwordExcludeAlike());
|
||||
ui->spinBoxLength->setValue(settings.passwordLength());
|
||||
|
||||
ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess());
|
||||
ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate());
|
||||
ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases());
|
||||
}
|
||||
|
||||
void OptionDialog::saveSettings()
|
||||
{
|
||||
HttpSettings settings;
|
||||
settings.setEnabled(ui->enableHttpServer->isChecked());
|
||||
|
||||
settings.setShowNotification(ui->showNotification->isChecked());
|
||||
settings.setBestMatchOnly(ui->bestMatchOnly->isChecked());
|
||||
settings.setUnlockDatabase(ui->unlockDatabase->isChecked());
|
||||
settings.setMatchUrlScheme(ui->matchUrlScheme->isChecked());
|
||||
settings.setSortByUsername(ui->sortByUsername->isChecked());
|
||||
|
||||
settings.setPasswordUseLowercase(ui->checkBoxLower->isChecked());
|
||||
settings.setPasswordUseNumbers(ui->checkBoxNumbers->isChecked());
|
||||
settings.setPasswordUseUppercase(ui->checkBoxUpper->isChecked());
|
||||
settings.setPasswordUseSpecial(ui->checkBoxSpecialChars->isChecked());
|
||||
settings.setPasswordEveryGroup(ui->checkBoxEnsureEvery->isChecked());
|
||||
settings.setPasswordExcludeAlike(ui->checkBoxExcludeAlike->isChecked());
|
||||
settings.setPasswordLength(ui->spinBoxLength->value());
|
||||
|
||||
settings.setAlwaysAllowAccess(ui->alwaysAllowAccess->isChecked());
|
||||
settings.setAlwaysAllowUpdate(ui->alwaysAllowUpdate->isChecked());
|
||||
settings.setSearchInAllDatabases(ui->searchInAllDatabases->isChecked());
|
||||
}
|
43
src/http/OptionDialog.h
Normal file
43
src/http/OptionDialog.h
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file OptionDialog.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef OPTIONDIALOG_H
|
||||
#define OPTIONDIALOG_H
|
||||
|
||||
#include <QtGui/QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class OptionDialog;
|
||||
}
|
||||
|
||||
class OptionDialog : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OptionDialog(QWidget *parent = 0);
|
||||
~OptionDialog();
|
||||
|
||||
public Q_SLOTS:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
Q_SIGNALS:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private:
|
||||
Ui::OptionDialog *ui;
|
||||
};
|
||||
|
||||
#endif // OPTIONDIALOG_H
|
305
src/http/OptionDialog.ui
Normal file
305
src/http/OptionDialog.ui
Normal file
@ -0,0 +1,305 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>OptionDialog</class>
|
||||
<widget class="QWidget" name="OptionDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>463</width>
|
||||
<height>354</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dialog</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableHttpServer">
|
||||
<property name="text">
|
||||
<string>Support KeypassHttp protocol
|
||||
This is required for accessing keypass database from ChromeIPass or PassIfox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="tabShape">
|
||||
<enum>QTabWidget::Rounded</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>General</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showNotification">
|
||||
<property name="text">
|
||||
<string>Sh&ow a notification when credentials are requested</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bestMatchOnly">
|
||||
<property name="text">
|
||||
<string>&Return only best matching entries for an URL instead
|
||||
of all entries for the whole domain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="unlockDatabase">
|
||||
<property name="text">
|
||||
<string>Re&quest for unlocking the database if it is locked</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="matchUrlScheme">
|
||||
<property name="text">
|
||||
<string>&Match URL schemes
|
||||
Only entries with the same scheme (http://, https://, ftp://, ...) are returned</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByUsername">
|
||||
<property name="text">
|
||||
<string>Sort matching entries by &username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByTitle">
|
||||
<property name="text">
|
||||
<string>Sort matching entries by &title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeSharedEncryptionKeys">
|
||||
<property name="text">
|
||||
<string>R&emove all shared encryption-keys from active database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeStoredPermissions">
|
||||
<property name="text">
|
||||
<string>Re&move all stored permissions from entries in active database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Password generator</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="checkBoxLower">
|
||||
<property name="text">
|
||||
<string>Lower letters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="checkBoxNumbers">
|
||||
<property name="text">
|
||||
<string>Numbers</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="checkBoxUpper">
|
||||
<property name="text">
|
||||
<string>Upper letters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="checkBoxSpecialChars">
|
||||
<property name="text">
|
||||
<string>Special characters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxEnsureEvery">
|
||||
<property name="text">
|
||||
<string>Ensure that the password contains characters from every group</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBoxExcludeAlike">
|
||||
<property name="text">
|
||||
<string>Exclude look-alike characters</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="labelLength">
|
||||
<property name="text">
|
||||
<string>Length:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="spinBoxLength">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Advanced</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="font">
|
||||
<font>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">color: rgb(255, 0, 0);</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Activate the following only, if you know what you are doing!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysAllowAccess">
|
||||
<property name="text">
|
||||
<string>Always allow &access to entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysAllowUpdate">
|
||||
<property name="text">
|
||||
<string>Always allow &updating entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchInAllDatabases">
|
||||
<property name="text">
|
||||
<string>Searc&h in all opened databases for matching entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Only the selected database has to be connected with a client!</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>30</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="supportKphFields">
|
||||
<property name="text">
|
||||
<string>&Return also advanced string fields which start with "KPH: "</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Automatic creates or updates are not supported for string fields!</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>30</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
518
src/http/Protocol.cpp
Normal file
518
src/http/Protocol.cpp
Normal file
@ -0,0 +1,518 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Response.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "Protocol.h"
|
||||
|
||||
#include <QtCore/QMetaProperty>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#include "qjson/parser.h"
|
||||
#include "qjson/qobjecthelper.h"
|
||||
#include "qjson/serializer.h"
|
||||
|
||||
#include "crypto/Random.h"
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
|
||||
namespace KeepassHttpProtocol
|
||||
{
|
||||
static const char * const STR_GET_LOGINS = "get-logins";
|
||||
static const char * const STR_GET_LOGINS_COUNT = "get-logins-count";
|
||||
static const char * const STR_GET_ALL_LOGINS = "get-all-logins";
|
||||
static const char * const STR_SET_LOGIN = "set-login";
|
||||
static const char * const STR_ASSOCIATE = "associate";
|
||||
static const char * const STR_TEST_ASSOCIATE = "test-associate";
|
||||
static const char * const STR_GENERATE_PASSWORD = "generate-password";
|
||||
static const char * const STR_VERSION = "1.7.0.0";
|
||||
|
||||
}/*namespace KeepassHttpProtocol*/
|
||||
|
||||
using namespace KeepassHttpProtocol;
|
||||
|
||||
static QHash<QString, RequestType> createStringHash()
|
||||
{
|
||||
QHash<QString, RequestType> hash;
|
||||
hash.insert(STR_GET_LOGINS, GET_LOGINS);
|
||||
hash.insert(STR_GET_LOGINS_COUNT, GET_LOGINS_COUNT);
|
||||
hash.insert(STR_GET_ALL_LOGINS, GET_ALL_LOGINS);
|
||||
hash.insert(STR_SET_LOGIN, SET_LOGIN);
|
||||
hash.insert(STR_ASSOCIATE, ASSOCIATE);
|
||||
hash.insert(STR_TEST_ASSOCIATE, TEST_ASSOCIATE);
|
||||
hash.insert(STR_GENERATE_PASSWORD,GENERATE_PASSWORD);
|
||||
return hash;
|
||||
}
|
||||
|
||||
static RequestType parseRequest(const QString &str)
|
||||
{
|
||||
static const QHash<QString, RequestType> REQUEST_STRINGS = createStringHash();
|
||||
return REQUEST_STRINGS.value(str, INVALID);
|
||||
}
|
||||
|
||||
static QByteArray decode64(QString s)
|
||||
{
|
||||
return QByteArray::fromBase64(s.toAscii());
|
||||
}
|
||||
|
||||
static QString encode64(QByteArray b)
|
||||
{
|
||||
return QString::fromAscii(b.toBase64());
|
||||
}
|
||||
|
||||
static QByteArray decrypt2(const QByteArray & data, SymmetricCipher & cipher)
|
||||
{
|
||||
//Ensure we get full blocks only
|
||||
if (data.length() <= 0 || data.length() % cipher.blockSize())
|
||||
return QByteArray();
|
||||
|
||||
//Decrypt
|
||||
cipher.reset();
|
||||
QByteArray buffer = cipher.process(data);
|
||||
|
||||
//Remove PKCS#7 padding
|
||||
buffer.chop(buffer.at(buffer.length()-1));
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static QString decrypt(const QString &data, SymmetricCipher &cipher)
|
||||
{
|
||||
return QString::fromUtf8(decrypt2(decode64(data), cipher));
|
||||
}
|
||||
|
||||
static QByteArray encrypt2(const QByteArray & data, SymmetricCipher & cipher)
|
||||
{
|
||||
//Add PKCS#7 padding
|
||||
const int blockSize = cipher.blockSize();
|
||||
const int paddingSize = blockSize - data.size() % blockSize;
|
||||
|
||||
//Encrypt
|
||||
QByteArray buffer = data + QByteArray(paddingSize, paddingSize);
|
||||
cipher.reset();
|
||||
cipher.processInPlace(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static QString encrypt(const QString & data, SymmetricCipher & cipher)
|
||||
{
|
||||
return encode64(encrypt2(data.toUtf8(), cipher));
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Request
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Request::Request(): m_requestType(INVALID)
|
||||
{
|
||||
}
|
||||
|
||||
QString Request::nonce() const
|
||||
{
|
||||
return m_nonce;
|
||||
}
|
||||
|
||||
void Request::setNonce(const QString &nonce)
|
||||
{
|
||||
m_nonce = nonce;
|
||||
}
|
||||
|
||||
QString Request::verifier() const
|
||||
{
|
||||
return m_verifier;
|
||||
}
|
||||
|
||||
void Request::setVerifier(const QString &verifier)
|
||||
{
|
||||
m_verifier = verifier;
|
||||
}
|
||||
|
||||
QString Request::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void Request::setId(const QString &id)
|
||||
{
|
||||
m_id = id;
|
||||
}
|
||||
|
||||
QString Request::key() const
|
||||
{
|
||||
return m_key;
|
||||
}
|
||||
|
||||
void Request::setKey(const QString &key)
|
||||
{
|
||||
m_key = key;
|
||||
}
|
||||
|
||||
QString Request::submitUrl() const
|
||||
{
|
||||
Q_ASSERT(m_cipher.isValid());
|
||||
return decrypt(m_submitUrl, m_cipher);
|
||||
}
|
||||
|
||||
void Request::setSubmitUrl(const QString &submitUrl)
|
||||
{
|
||||
m_submitUrl = submitUrl;
|
||||
}
|
||||
|
||||
QString Request::url() const
|
||||
{
|
||||
Q_ASSERT(m_cipher.isValid());
|
||||
return decrypt(m_url, m_cipher);
|
||||
}
|
||||
|
||||
void Request::setUrl(const QString &url)
|
||||
{
|
||||
m_url = url;
|
||||
}
|
||||
|
||||
QString Request::realm() const
|
||||
{
|
||||
Q_ASSERT(m_cipher.isValid());
|
||||
return decrypt(m_realm, m_cipher);
|
||||
}
|
||||
|
||||
void Request::setRealm(const QString &realm)
|
||||
{
|
||||
m_realm = realm;
|
||||
}
|
||||
|
||||
QString Request::login() const
|
||||
{
|
||||
Q_ASSERT(m_cipher.isValid());
|
||||
return decrypt(m_login, m_cipher);
|
||||
}
|
||||
|
||||
void Request::setLogin(const QString &login)
|
||||
{
|
||||
m_login = login;
|
||||
}
|
||||
|
||||
QString Request::uuid() const
|
||||
{
|
||||
Q_ASSERT(m_cipher.isValid());
|
||||
return decrypt(m_uuid, m_cipher);
|
||||
}
|
||||
|
||||
void Request::setUuid(const QString &uuid)
|
||||
{
|
||||
m_uuid = uuid;
|
||||
}
|
||||
|
||||
QString Request::password() const
|
||||
{
|
||||
Q_ASSERT(m_cipher.isValid());
|
||||
return decrypt(m_password, m_cipher);
|
||||
}
|
||||
|
||||
void Request::setPassword(const QString &password)
|
||||
{
|
||||
m_password = password;
|
||||
}
|
||||
|
||||
bool Request::sortSelection() const
|
||||
{
|
||||
return m_sortSelection;
|
||||
}
|
||||
|
||||
void Request::setSortSelection(bool sortSelection)
|
||||
{
|
||||
m_sortSelection = sortSelection;
|
||||
}
|
||||
|
||||
KeepassHttpProtocol::RequestType Request::requestType() const
|
||||
{
|
||||
return parseRequest(m_requestType);
|
||||
}
|
||||
|
||||
QString Request::requestTypeStr() const
|
||||
{
|
||||
return m_requestType;
|
||||
}
|
||||
|
||||
void Request::setRequestType(const QString &requestType)
|
||||
{
|
||||
m_requestType = requestType;
|
||||
}
|
||||
|
||||
bool Request::CheckVerifier(const QString &key) const
|
||||
{
|
||||
Q_ASSERT(!m_cipher.isValid());
|
||||
m_cipher.init(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt,
|
||||
decode64(key), decode64(m_nonce));
|
||||
return decrypt(m_verifier, m_cipher) == m_nonce;
|
||||
}
|
||||
|
||||
bool Request::fromJson(QString text)
|
||||
{
|
||||
bool isok = false;
|
||||
QVariant v = QJson::Parser().parse(text.toUtf8(), &isok);
|
||||
if (!isok)
|
||||
return false;
|
||||
|
||||
m_requestType.clear();
|
||||
QJson::QObjectHelper::qvariant2qobject(v.toMap(), this);
|
||||
|
||||
return requestType() != INVALID;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Response
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Response::Response(const Request &request, QString hash):
|
||||
m_requestType(request.requestTypeStr()),
|
||||
m_success(false),
|
||||
m_count(-1),
|
||||
m_version(STR_VERSION),
|
||||
m_hash(hash)
|
||||
{
|
||||
}
|
||||
|
||||
void Response::setVerifier(QString key)
|
||||
{
|
||||
Q_ASSERT(!m_cipher.isValid());
|
||||
m_cipher.init(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt, decode64(key));
|
||||
|
||||
//Generate new IV
|
||||
const QByteArray iv = randomGen()->randomArray(m_cipher.blockSize());
|
||||
m_cipher.setIv(iv);
|
||||
m_nonce = encode64(iv);
|
||||
|
||||
//Encrypt
|
||||
m_verifier = encrypt(m_nonce, m_cipher);
|
||||
}
|
||||
|
||||
QString Response::toJson()
|
||||
{
|
||||
QVariant result = QJson::QObjectHelper::qobject2qvariant(this, QJson::QObjectHelper::Flag_None);
|
||||
|
||||
QJson::Serializer s;
|
||||
s.setIndentMode(QJson::IndentCompact);
|
||||
return s.serialize(result);
|
||||
}
|
||||
|
||||
KeepassHttpProtocol::RequestType Response::requestType() const
|
||||
{
|
||||
return parseRequest(m_requestType);
|
||||
}
|
||||
|
||||
QString Response::requestTypeStr() const
|
||||
{
|
||||
return m_requestType;
|
||||
}
|
||||
|
||||
QString Response::verifier() const
|
||||
{
|
||||
return m_verifier;
|
||||
}
|
||||
|
||||
QString Response::nonce() const
|
||||
{
|
||||
return m_nonce;
|
||||
}
|
||||
|
||||
QVariant Response::count() const
|
||||
{
|
||||
return m_count < 0 ? QVariant() : QVariant(m_count);
|
||||
}
|
||||
|
||||
void Response::setCount(int count)
|
||||
{
|
||||
m_count = count;
|
||||
}
|
||||
|
||||
QVariant Response::getEntries() const
|
||||
{
|
||||
if (m_count < 0 || m_entries.isEmpty())
|
||||
return QVariant();
|
||||
|
||||
QList<QVariant> res;
|
||||
res.reserve(m_entries.size());
|
||||
Q_FOREACH (const Entry &entry, m_entries)
|
||||
res.append(QJson::QObjectHelper::qobject2qvariant(&entry, QJson::QObjectHelper::Flag_None));
|
||||
return res;
|
||||
}
|
||||
|
||||
void Response::setEntries(const QList<Entry> &entries)
|
||||
{
|
||||
Q_ASSERT(m_cipher.isValid());
|
||||
|
||||
m_count = entries.count();
|
||||
|
||||
QList<Entry> encryptedEntries;
|
||||
encryptedEntries.reserve(m_count);
|
||||
Q_FOREACH (const Entry &entry, entries) {
|
||||
Entry encryptedEntry(encrypt(entry.name(), m_cipher),
|
||||
encrypt(entry.login(), m_cipher),
|
||||
entry.password().isNull() ? QString() : encrypt(entry.password(), m_cipher),
|
||||
encrypt(entry.uuid(), m_cipher));
|
||||
Q_FOREACH (const StringField & field, entry.stringFields())
|
||||
encryptedEntry.addStringField(encrypt(field.key(), m_cipher),
|
||||
encrypt(field.value(), m_cipher));
|
||||
encryptedEntries << encryptedEntry;
|
||||
}
|
||||
m_entries = encryptedEntries;
|
||||
}
|
||||
|
||||
QString Response::hash() const
|
||||
{
|
||||
return m_hash;
|
||||
}
|
||||
|
||||
QString Response::version() const
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
QString Response::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
void Response::setId(const QString &id)
|
||||
{
|
||||
m_id = id;
|
||||
}
|
||||
|
||||
bool Response::success() const
|
||||
{
|
||||
return m_success;
|
||||
}
|
||||
|
||||
void Response::setSuccess()
|
||||
{
|
||||
m_success = true;
|
||||
}
|
||||
|
||||
QString Response::error() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
void Response::setError(const QString &error)
|
||||
{
|
||||
m_success = false;
|
||||
m_error = error;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Entry
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Entry::Entry()
|
||||
{}
|
||||
|
||||
Entry::Entry(QString name, QString login, QString password, QString uuid):
|
||||
m_login(login),
|
||||
m_password(password),
|
||||
m_uuid(uuid),
|
||||
m_name(name)
|
||||
{}
|
||||
|
||||
Entry::Entry(const Entry & other):
|
||||
QObject(),
|
||||
m_login(other.m_login),
|
||||
m_password(other.m_password),
|
||||
m_uuid(other.m_uuid),
|
||||
m_name(other.m_name),
|
||||
m_stringFields(other.m_stringFields)
|
||||
{}
|
||||
|
||||
Entry & Entry::operator=(const Entry & other)
|
||||
{
|
||||
m_login = other.m_login;
|
||||
m_password = other.m_password;
|
||||
m_uuid = other.m_uuid;
|
||||
m_name = other.m_name;
|
||||
m_stringFields = other.m_stringFields;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QString Entry::login() const
|
||||
{
|
||||
return m_login;
|
||||
}
|
||||
|
||||
QString Entry::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString Entry::uuid() const
|
||||
{
|
||||
return m_uuid;
|
||||
}
|
||||
|
||||
QString Entry::password() const
|
||||
{
|
||||
return m_password;
|
||||
}
|
||||
|
||||
QList<StringField> Entry::stringFields() const
|
||||
{
|
||||
return m_stringFields;
|
||||
}
|
||||
|
||||
void Entry::addStringField(const QString &key, const QString &value)
|
||||
{
|
||||
m_stringFields.append(StringField(key, value));
|
||||
}
|
||||
|
||||
QVariant Entry::getStringFields() const
|
||||
{
|
||||
if (m_stringFields.isEmpty())
|
||||
return QVariant();
|
||||
|
||||
QList<QVariant> res;
|
||||
res.reserve(m_stringFields.size());
|
||||
Q_FOREACH (const StringField &stringfield, m_stringFields)
|
||||
res.append(QJson::QObjectHelper::qobject2qvariant(&stringfield, QJson::QObjectHelper::Flag_None));
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// StringField
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
StringField::StringField()
|
||||
{}
|
||||
|
||||
StringField::StringField(const QString &key, const QString &value):
|
||||
m_key(key), m_value(value)
|
||||
{}
|
||||
|
||||
StringField::StringField(const StringField &other):
|
||||
QObject(NULL), m_key(other.m_key), m_value(other.m_value)
|
||||
{}
|
||||
|
||||
StringField &StringField::operator =(const StringField &other)
|
||||
{
|
||||
m_key = other.m_key;
|
||||
m_value = other.m_value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
QString StringField::key() const
|
||||
{
|
||||
return m_key;
|
||||
}
|
||||
|
||||
QString StringField::value() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
208
src/http/Protocol.h
Normal file
208
src/http/Protocol.h
Normal file
@ -0,0 +1,208 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Response.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef RESPONSE_H
|
||||
#define RESPONSE_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QVariant>
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
|
||||
namespace KeepassHttpProtocol {
|
||||
|
||||
enum RequestType {
|
||||
INVALID = -1,
|
||||
GET_LOGINS,
|
||||
GET_LOGINS_COUNT,
|
||||
GET_ALL_LOGINS,
|
||||
SET_LOGIN,
|
||||
ASSOCIATE,
|
||||
TEST_ASSOCIATE,
|
||||
GENERATE_PASSWORD
|
||||
};
|
||||
|
||||
//TODO: use QByteArray whenever possible?
|
||||
|
||||
class Request : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString RequestType READ requestTypeStr WRITE setRequestType )
|
||||
Q_PROPERTY(bool SortSelection READ sortSelection WRITE setSortSelection)
|
||||
Q_PROPERTY(QString Login READ login WRITE setLogin )
|
||||
Q_PROPERTY(QString Password READ password WRITE setPassword )
|
||||
Q_PROPERTY(QString Uuid READ uuid WRITE setUuid )
|
||||
Q_PROPERTY(QString Url READ url WRITE setUrl )
|
||||
Q_PROPERTY(QString SubmitUrl READ submitUrl WRITE setSubmitUrl )
|
||||
Q_PROPERTY(QString Key READ key WRITE setKey )
|
||||
Q_PROPERTY(QString Id READ id WRITE setId )
|
||||
Q_PROPERTY(QString Verifier READ verifier WRITE setVerifier )
|
||||
Q_PROPERTY(QString Nonce READ nonce WRITE setNonce )
|
||||
Q_PROPERTY(QString Realm READ realm WRITE setRealm )
|
||||
|
||||
public:
|
||||
Request();
|
||||
bool fromJson(QString text);
|
||||
|
||||
KeepassHttpProtocol::RequestType requestType() const;
|
||||
QString requestTypeStr() const;
|
||||
bool sortSelection() const;
|
||||
QString login() const;
|
||||
QString password() const;
|
||||
QString uuid() const;
|
||||
QString url() const;
|
||||
QString submitUrl() const;
|
||||
QString key() const;
|
||||
QString id() const;
|
||||
QString verifier() const;
|
||||
QString nonce() const;
|
||||
QString realm() const;
|
||||
bool CheckVerifier(const QString & key) const;
|
||||
|
||||
private:
|
||||
void setRequestType(const QString &requestType);
|
||||
void setSortSelection(bool sortSelection);
|
||||
void setLogin(const QString &login);
|
||||
void setPassword(const QString &password);
|
||||
void setUuid(const QString &uuid);
|
||||
void setUrl(const QString &url);
|
||||
void setSubmitUrl(const QString &submitUrl);
|
||||
void setKey(const QString &key);
|
||||
void setId(const QString &id);
|
||||
void setVerifier(const QString &verifier);
|
||||
void setNonce(const QString &nonce);
|
||||
void setRealm(const QString &realm);
|
||||
|
||||
QString m_requestType;
|
||||
bool m_sortSelection;
|
||||
QString m_login;
|
||||
QString m_password;
|
||||
QString m_uuid;
|
||||
QString m_url;
|
||||
QString m_submitUrl;
|
||||
QString m_key;
|
||||
QString m_id;
|
||||
QString m_verifier;
|
||||
QString m_nonce;
|
||||
QString m_realm;
|
||||
mutable SymmetricCipher m_cipher;
|
||||
};
|
||||
|
||||
class StringField : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString Key READ key )
|
||||
Q_PROPERTY(QString Value READ value)
|
||||
|
||||
public:
|
||||
StringField();
|
||||
StringField(const QString& key, const QString& value);
|
||||
StringField(const StringField & other);
|
||||
StringField &operator =(const StringField &other);
|
||||
|
||||
QString key() const;
|
||||
QString value() const;
|
||||
|
||||
private:
|
||||
QString m_key;
|
||||
QString m_value;
|
||||
};
|
||||
|
||||
class Entry : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString Login READ login )
|
||||
Q_PROPERTY(QString Password READ password )
|
||||
Q_PROPERTY(QString Uuid READ uuid )
|
||||
Q_PROPERTY(QString Name READ name )
|
||||
Q_PROPERTY(QVariant StringFields READ getStringFields)
|
||||
|
||||
public:
|
||||
Entry();
|
||||
Entry(QString name, QString login, QString password, QString uuid);
|
||||
Entry(const Entry & other);
|
||||
Entry &operator =(const Entry &other);
|
||||
|
||||
QString login() const;
|
||||
QString password() const;
|
||||
QString uuid() const;
|
||||
QString name() const;
|
||||
QList<StringField> stringFields() const;
|
||||
void addStringField(const QString& key, const QString& value);
|
||||
|
||||
private:
|
||||
QVariant getStringFields() const;
|
||||
|
||||
QString m_login;
|
||||
QString m_password;
|
||||
QString m_uuid;
|
||||
QString m_name;
|
||||
QList<StringField> m_stringFields;
|
||||
};
|
||||
|
||||
class Response : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString RequestType READ requestTypeStr)
|
||||
Q_PROPERTY(QString Error READ error )
|
||||
Q_PROPERTY(bool Success READ success )
|
||||
Q_PROPERTY(QString Id READ id )
|
||||
Q_PROPERTY(QString Version READ version )
|
||||
Q_PROPERTY(QString Hash READ hash )
|
||||
Q_PROPERTY(QVariant Count READ count )
|
||||
Q_PROPERTY(QVariant Entries READ getEntries )
|
||||
Q_PROPERTY(QString Nonce READ nonce )
|
||||
Q_PROPERTY(QString Verifier READ verifier )
|
||||
|
||||
public:
|
||||
Response(const Request &request, QString hash);
|
||||
|
||||
KeepassHttpProtocol::RequestType requestType() const;
|
||||
QString error() const;
|
||||
void setError(const QString &error = QString());
|
||||
bool success() const;
|
||||
void setSuccess();
|
||||
QString id() const;
|
||||
void setId(const QString &id);
|
||||
QString version() const;
|
||||
QString hash() const;
|
||||
QVariant count() const;
|
||||
void setCount(int count);
|
||||
QVariant getEntries() const;
|
||||
void setEntries(const QList<Entry> &entries);
|
||||
QString nonce() const;
|
||||
QString verifier() const;
|
||||
void setVerifier(QString key);
|
||||
|
||||
QString toJson();
|
||||
|
||||
private:
|
||||
QString requestTypeStr() const;
|
||||
|
||||
QString m_requestType;
|
||||
QString m_error;
|
||||
bool m_success;
|
||||
QString m_id;
|
||||
int m_count;
|
||||
QString m_version;
|
||||
QString m_hash;
|
||||
QList<Entry> m_entries;
|
||||
QString m_nonce;
|
||||
QString m_verifier;
|
||||
SymmetricCipher m_cipher;
|
||||
};
|
||||
|
||||
}/*namespace KeepassHttpProtocol*/
|
||||
|
||||
#endif // RESPONSE_H
|
219
src/http/Server.cpp
Normal file
219
src/http/Server.cpp
Normal file
@ -0,0 +1,219 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Server.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "Server.h"
|
||||
#include "qhttpserver/qhttpserver.h"
|
||||
#include "qhttpserver/qhttprequest.h"
|
||||
#include "qhttpserver/qhttpresponse.h"
|
||||
#include "Protocol.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtGui/QMessageBox>
|
||||
|
||||
using namespace KeepassHttpProtocol;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Request
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
RequestHandler::RequestHandler(QHttpRequest *request, QHttpResponse *response): m_request(request), m_response(response)
|
||||
{
|
||||
m_request->storeBody();
|
||||
connect(m_request, SIGNAL(end()), this, SLOT(onEnd()));
|
||||
connect(m_response, SIGNAL(done()), this, SLOT(deleteLater()));
|
||||
}
|
||||
|
||||
RequestHandler::~RequestHandler()
|
||||
{
|
||||
delete m_request;
|
||||
}
|
||||
|
||||
void RequestHandler::onEnd()
|
||||
{
|
||||
Q_EMIT requestComplete(m_request, m_response);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Request
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
Server::Server(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_httpServer(new QHttpServer(this)),
|
||||
m_started(false)
|
||||
{
|
||||
connect(m_httpServer, SIGNAL(newRequest(QHttpRequest*,QHttpResponse*)), this, SLOT(handleRequest(QHttpRequest*,QHttpResponse*)));
|
||||
}
|
||||
|
||||
void Server::start()
|
||||
{
|
||||
if (m_started)
|
||||
return;
|
||||
|
||||
static const int PORT = 19455;
|
||||
m_started = m_httpServer->listen(QHostAddress::LocalHost, PORT)
|
||||
|| m_httpServer->listen(QHostAddress::LocalHostIPv6, PORT);
|
||||
}
|
||||
|
||||
void Server::stop()
|
||||
{
|
||||
if (!m_started)
|
||||
return;
|
||||
m_httpServer->close();
|
||||
m_started = false;
|
||||
}
|
||||
|
||||
void Server::handleRequest(QHttpRequest *request, QHttpResponse *response)
|
||||
{
|
||||
RequestHandler * h = new RequestHandler(request, response);
|
||||
connect(h, SIGNAL(requestComplete(QHttpRequest*,QHttpResponse*)), this, SLOT(handleRequestComplete(QHttpRequest*,QHttpResponse*)));
|
||||
}
|
||||
|
||||
void Server::handleRequestComplete(QHttpRequest *request, QHttpResponse *response)
|
||||
{
|
||||
Request r;
|
||||
if (!isDatabaseOpened() && !openDatabase()) {
|
||||
response->writeHead(QHttpResponse::STATUS_SERVICE_UNAVAILABLE);
|
||||
response->end();
|
||||
} else if (request->header("content-type").compare("application/json", Qt::CaseInsensitive) == 0 &&
|
||||
r.fromJson(request->body())) {
|
||||
|
||||
QByteArray hash = QCryptographicHash::hash((getDatabaseRootUuid() + getDatabaseRecycleBinUuid()).toUtf8(),
|
||||
QCryptographicHash::Sha1).toHex();
|
||||
|
||||
Response protocolResp(r, QString::fromAscii(hash));
|
||||
switch(r.requestType()) {
|
||||
case INVALID: break;
|
||||
case TEST_ASSOCIATE: testAssociate(r, &protocolResp); break;
|
||||
case ASSOCIATE: associate(r, &protocolResp); break;
|
||||
case GET_LOGINS: getLogins(r, &protocolResp); break;
|
||||
case GET_LOGINS_COUNT: getLoginsCount(r, &protocolResp); break;
|
||||
case GET_ALL_LOGINS: getAllLogins(r, &protocolResp); break;
|
||||
case SET_LOGIN: setLogin(r, &protocolResp); break;
|
||||
case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break;
|
||||
}
|
||||
|
||||
QByteArray s = protocolResp.toJson().toUtf8();
|
||||
response->setHeader("Content-Type", "application/json");
|
||||
response->setHeader("Content-Length", QString::number(s.size()));
|
||||
response->writeHead(QHttpResponse::STATUS_OK);
|
||||
response->write(s);
|
||||
response->end();
|
||||
} else {
|
||||
response->writeHead(QHttpResponse::STATUS_BAD_REQUEST);
|
||||
response->end();
|
||||
}
|
||||
}
|
||||
|
||||
void Server::testAssociate(const Request& r, Response * protocolResp)
|
||||
{
|
||||
if (r.id().isEmpty())
|
||||
return; //ping
|
||||
|
||||
QString key = getKey(r.id());
|
||||
if (key.isEmpty() || !r.CheckVerifier(key))
|
||||
return;
|
||||
|
||||
protocolResp->setSuccess();
|
||||
protocolResp->setId(r.id());
|
||||
protocolResp->setVerifier(key);
|
||||
}
|
||||
|
||||
void Server::associate(const Request& r, Response * protocolResp)
|
||||
{
|
||||
if (!r.CheckVerifier(r.key()))
|
||||
return;
|
||||
|
||||
QString id = storeKey(r.key());
|
||||
if (id.isEmpty())
|
||||
return;
|
||||
|
||||
protocolResp->setSuccess();
|
||||
protocolResp->setId(id);
|
||||
protocolResp->setVerifier(r.key());
|
||||
}
|
||||
|
||||
void Server::getLogins(const Request &r, Response *protocolResp)
|
||||
{
|
||||
QString key = getKey(r.id());
|
||||
if (!r.CheckVerifier(key))
|
||||
return;
|
||||
|
||||
protocolResp->setSuccess();
|
||||
protocolResp->setId(r.id());
|
||||
protocolResp->setVerifier(key);
|
||||
QList<Entry> entries = findMatchingEntries(r.id(), r.url(), r.submitUrl(), r.realm()); //TODO: filtering, request confirmation [in db adaptation layer?]
|
||||
if (r.sortSelection()) {
|
||||
//TODO: sorting (in db adaptation layer? here?)
|
||||
}
|
||||
protocolResp->setEntries(entries);
|
||||
}
|
||||
|
||||
void Server::getLoginsCount(const Request &r, Response *protocolResp)
|
||||
{
|
||||
QString key = getKey(r.id());
|
||||
if (!r.CheckVerifier(key))
|
||||
return;
|
||||
|
||||
protocolResp->setSuccess();
|
||||
protocolResp->setId(r.id());
|
||||
protocolResp->setVerifier(key);
|
||||
protocolResp->setCount(countMatchingEntries(r.id(), r.url(), r.submitUrl(), r.realm()));
|
||||
}
|
||||
|
||||
void Server::getAllLogins(const Request &r, Response *protocolResp)
|
||||
{
|
||||
QString key = getKey(r.id());
|
||||
if (!r.CheckVerifier(key))
|
||||
return;
|
||||
|
||||
protocolResp->setSuccess();
|
||||
protocolResp->setId(r.id());
|
||||
protocolResp->setVerifier(key);
|
||||
protocolResp->setEntries(searchAllEntries(r.id())); //TODO: ensure there is no password --> change API?
|
||||
}
|
||||
|
||||
void Server::setLogin(const Request &r, Response *protocolResp)
|
||||
{
|
||||
QString key = getKey(r.id());
|
||||
if (!r.CheckVerifier(key))
|
||||
return;
|
||||
|
||||
QString uuid = r.uuid();
|
||||
if (uuid.isEmpty())
|
||||
addEntry(r.id(), r.login(), r.password(), r.url(), r.submitUrl(), r.realm());
|
||||
else
|
||||
updateEntry(r.id(), r.uuid(), r.login(), r.password(), r.url());
|
||||
|
||||
protocolResp->setSuccess();
|
||||
protocolResp->setId(r.id());
|
||||
protocolResp->setVerifier(key);
|
||||
}
|
||||
|
||||
void Server::generatePassword(const Request &r, Response *protocolResp)
|
||||
{
|
||||
QString key = getKey(r.id());
|
||||
if (!r.CheckVerifier(key))
|
||||
return;
|
||||
|
||||
QString password = generatePassword();
|
||||
|
||||
protocolResp->setSuccess();
|
||||
protocolResp->setId(r.id());
|
||||
protocolResp->setVerifier(key);
|
||||
protocolResp->setEntries(QList<Entry>() << Entry("generate-password", "generate-password", password, "generate-password"));
|
||||
|
||||
memset(password.data(), 0, password.length());
|
||||
}
|
91
src/http/Server.h
Normal file
91
src/http/Server.h
Normal file
@ -0,0 +1,91 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Server.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef SERVER_H
|
||||
#define SERVER_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QList>
|
||||
|
||||
class QHttpServer;
|
||||
class QHttpRequest;
|
||||
class QHttpResponse;
|
||||
|
||||
namespace KeepassHttpProtocol {
|
||||
|
||||
class Request;
|
||||
class Response;
|
||||
class Entry;
|
||||
|
||||
class RequestHandler: public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
RequestHandler(QHttpRequest *request, QHttpResponse *response);
|
||||
~RequestHandler();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onEnd();
|
||||
|
||||
Q_SIGNALS:
|
||||
void requestComplete(QHttpRequest *request, QHttpResponse *response);
|
||||
|
||||
private:
|
||||
QHttpRequest * m_request;
|
||||
QHttpResponse *m_response;
|
||||
};
|
||||
|
||||
class Server : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Server(QObject *parent = 0);
|
||||
|
||||
//TODO: use QByteArray?
|
||||
virtual bool isDatabaseOpened() const = 0;
|
||||
virtual bool openDatabase() = 0;
|
||||
virtual QString getDatabaseRootUuid() = 0;
|
||||
virtual QString getDatabaseRecycleBinUuid() = 0;
|
||||
virtual QString getKey(const QString &id) = 0;
|
||||
virtual QString storeKey(const QString &key) = 0;
|
||||
virtual QList<Entry> findMatchingEntries(const QString &id, const QString &url, const QString & submitUrl, const QString & realm) = 0;
|
||||
virtual int countMatchingEntries(const QString &id, const QString &url, const QString & submitUrl, const QString & realm) = 0;
|
||||
virtual QList<Entry> searchAllEntries(const QString &id) = 0;
|
||||
virtual void addEntry(const QString &id, const QString &login, const QString &password, const QString &url, const QString &submitUrl, const QString &realm) = 0;
|
||||
virtual void updateEntry(const QString &id, const QString &uuid, const QString &login, const QString &password, const QString &url) = 0;
|
||||
virtual QString generatePassword() = 0;
|
||||
|
||||
public Q_SLOTS:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleRequest(QHttpRequest * request, QHttpResponse* response);
|
||||
void handleRequestComplete(QHttpRequest * request, QHttpResponse* response);
|
||||
|
||||
private:
|
||||
void testAssociate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
void associate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
void getLogins(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
void getLoginsCount(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
void getAllLogins(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
void setLogin(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
void generatePassword(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
|
||||
QHttpServer * const m_httpServer;
|
||||
bool m_started;
|
||||
};
|
||||
|
||||
} /*namespace KeepassHttpProtocol*/
|
||||
|
||||
#endif // SERVER_H
|
571
src/http/Service.cpp
Normal file
571
src/http/Service.cpp
Normal file
@ -0,0 +1,571 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Service.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include <QtGui/QInputDialog>
|
||||
#include <QtGui/QMessageBox>
|
||||
#include <QtGui/QProgressDialog>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "Service.h"
|
||||
#include "Protocol.h"
|
||||
#include "EntryConfig.h"
|
||||
#include "AccessControlDialog.h"
|
||||
#include "HttpSettings.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Uuid.h"
|
||||
#include "core/PasswordGenerator.h"
|
||||
|
||||
static const unsigned char KEEPASSHTTP_UUID_DATA[] = {
|
||||
0x34, 0x69, 0x7a, 0x40, 0x8a, 0x5b, 0x41, 0xc0,
|
||||
0x9f, 0x36, 0x89, 0x7d, 0x62, 0x3e, 0xcb, 0x31
|
||||
};
|
||||
static const Uuid KEEPASSHTTP_UUID = Uuid(QByteArray::fromRawData(reinterpret_cast<const char *>(KEEPASSHTTP_UUID_DATA), sizeof(KEEPASSHTTP_UUID_DATA)));
|
||||
static const char KEEPASSHTTP_NAME[] = "KeePassHttp Settings";
|
||||
static const char ASSOCIATE_KEY_PREFIX[] = "AES Key: ";
|
||||
static const char KEEPASSHTTP_GROUP_NAME[] = "KeePassHttp Passwords"; //Group where new KeePassHttp password are stored
|
||||
static int KEEPASSHTTP_DEFAULT_ICON = 1;
|
||||
//private const int DEFAULT_NOTIFICATION_TIME = 5000;
|
||||
|
||||
Service::Service(DatabaseTabWidget* parent) :
|
||||
KeepassHttpProtocol::Server(parent),
|
||||
m_dbTabWidget(parent)
|
||||
{
|
||||
if (HttpSettings::isEnabled())
|
||||
start();
|
||||
}
|
||||
|
||||
Entry* Service::getConfigEntry(bool create)
|
||||
{
|
||||
if (DatabaseWidget * dbWidget = m_dbTabWidget->currentDatabaseWidget())
|
||||
if (Database * db = dbWidget->database()) {
|
||||
Entry* entry = db->resolveEntry(KEEPASSHTTP_UUID);
|
||||
if (!entry && create) {
|
||||
entry = new Entry();
|
||||
entry->setTitle(QLatin1String(KEEPASSHTTP_NAME));
|
||||
entry->setUuid(KEEPASSHTTP_UUID);
|
||||
entry->setAutoTypeEnabled(false);
|
||||
entry->setGroup(db->rootGroup());
|
||||
} else if (entry && entry->group() == db->metadata()->recycleBin()) {
|
||||
if (create)
|
||||
entry->setGroup(db->rootGroup());
|
||||
else
|
||||
entry = NULL;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool Service::isDatabaseOpened() const
|
||||
{
|
||||
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget())
|
||||
switch(dbWidget->currentMode()) {
|
||||
case DatabaseWidget::None:
|
||||
case DatabaseWidget::LockedMode:
|
||||
break;
|
||||
|
||||
case DatabaseWidget::ViewMode:
|
||||
case DatabaseWidget::EditMode:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Service::openDatabase()
|
||||
{
|
||||
if (!HttpSettings::unlockDatabase())
|
||||
return false;
|
||||
if (DatabaseWidget * dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
|
||||
switch(dbWidget->currentMode()) {
|
||||
case DatabaseWidget::None:
|
||||
case DatabaseWidget::LockedMode:
|
||||
break;
|
||||
|
||||
case DatabaseWidget::ViewMode:
|
||||
case DatabaseWidget::EditMode:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
//if (HttpSettings::showNotification()
|
||||
// && !ShowNotification(QString("%0: %1 is requesting access, click to allow or deny")
|
||||
// .arg(id).arg(submitHost.isEmpty() ? host : submithost));
|
||||
// return false;
|
||||
m_dbTabWidget->activateWindow();
|
||||
//Wait a bit for DB to be open... (w/ asynchronous reply?)
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Service::getDatabaseRootUuid()
|
||||
{
|
||||
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget())
|
||||
if (Database* db = dbWidget->database())
|
||||
if (Group* rootGroup = db->rootGroup())
|
||||
return rootGroup->uuid().toHex();
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString Service::getDatabaseRecycleBinUuid()
|
||||
{
|
||||
if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget())
|
||||
if (Database* db = dbWidget->database())
|
||||
if (Group* recycleBin = db->metadata()->recycleBin())
|
||||
return recycleBin->uuid().toHex();
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString Service::getKey(const QString &id)
|
||||
{
|
||||
if (Entry* config = getConfigEntry())
|
||||
return config->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString Service::storeKey(const QString &key)
|
||||
{
|
||||
QString id;
|
||||
if (Entry* config = getConfigEntry(true)) {
|
||||
|
||||
//ShowNotification("New key association requested")
|
||||
|
||||
do {
|
||||
bool ok;
|
||||
//Indicate who wants to associate, and request user to enter the 'name' of association key
|
||||
id = QInputDialog::getText(0, tr("KeyPassX/Http: New key association request"),
|
||||
tr("You have received an association request for the above key. If you would like to "
|
||||
"allow it access to your KeePassX database give it a unique name to identify and"
|
||||
"accept it."),
|
||||
QLineEdit::Normal, QString(), &ok);
|
||||
if (!ok || id.isEmpty())
|
||||
return QString();
|
||||
|
||||
//Warn if association key already exists
|
||||
} while(config->attributes()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id) &&
|
||||
QMessageBox::warning(0, tr("KeyPassX/Http: Overwrite existing key?"),
|
||||
tr("A shared encryption-key with the name \"%1\" already exists.\nDo you want to overwrite it?").arg(id),
|
||||
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No);
|
||||
|
||||
config->attributes()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key, true);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
bool Service::matchUrlScheme(const QString & url)
|
||||
{
|
||||
QString str = url.left(8).toLower();
|
||||
return str.startsWith("http://") ||
|
||||
str.startsWith("https://") ||
|
||||
str.startsWith("ftp://") ||
|
||||
str.startsWith("ftps://");
|
||||
}
|
||||
|
||||
bool Service::removeFirstDomain(QString & hostname)
|
||||
{
|
||||
int pos = hostname.indexOf(".");
|
||||
if (pos < 0)
|
||||
return false;
|
||||
hostname = hostname.mid(pos + 1);
|
||||
return !hostname.isEmpty();
|
||||
}
|
||||
|
||||
QList<Entry*> Service::searchEntries(Database* db, const QString& hostname)
|
||||
{
|
||||
QList<Entry*> entries;
|
||||
if (Group* rootGroup = db->rootGroup())
|
||||
Q_FOREACH (Entry* entry, rootGroup->search(hostname, Qt::CaseInsensitive)) {
|
||||
QString title = entry->title();
|
||||
QString url = entry->url();
|
||||
|
||||
//Filter to match hostname in Title and Url fields
|
||||
if ( (!title.isEmpty() && hostname.contains(title))
|
||||
|| (!url.isEmpty() && hostname.contains(url))
|
||||
|| (matchUrlScheme(title) && hostname.endsWith(QUrl(title).host()))
|
||||
|| (matchUrlScheme(url) && hostname.endsWith(QUrl(url).host())) )
|
||||
entries.append(entry);
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
QList<Entry*> Service::searchEntries(const QString& text)
|
||||
{
|
||||
//Get the list of databases to search
|
||||
QList<Database*> databases;
|
||||
if (HttpSettings::searchInAllDatabases()) {
|
||||
for (int i = 0; i < m_dbTabWidget->count(); i++)
|
||||
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i)))
|
||||
if (Database* db = dbWidget->database())
|
||||
databases << db;
|
||||
}
|
||||
else if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
|
||||
if (Database* db = dbWidget->database())
|
||||
databases << db;
|
||||
}
|
||||
|
||||
//Search entries matching the hostname
|
||||
QString hostname = QUrl(text).host();
|
||||
QList<Entry*> entries;
|
||||
do {
|
||||
Q_FOREACH (Database* db, databases)
|
||||
entries << searchEntries(db, hostname);
|
||||
} while(entries.isEmpty() && removeFirstDomain(hostname));
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
Service::Access Service::checkAccess(const Entry *entry, const QString & host, const QString & submitHost, const QString & realm)
|
||||
{
|
||||
EntryConfig config;
|
||||
if (!config.load(entry))
|
||||
return Unknown; //not configured
|
||||
if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost)))
|
||||
return Allowed; //allowed
|
||||
if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost)))
|
||||
return Denied; //denied
|
||||
if (!realm.isEmpty() && config.realm() != realm)
|
||||
return Denied;
|
||||
return Unknown; //not configured for this host
|
||||
}
|
||||
|
||||
KeepassHttpProtocol::Entry Service::prepareEntry(const Entry* entry)
|
||||
{
|
||||
KeepassHttpProtocol::Entry res(entry->title(), entry->username(), entry->password(), entry->uuid().toHex());
|
||||
if (HttpSettings::supportKphFields()) {
|
||||
const EntryAttributes * attr = entry->attributes();
|
||||
Q_FOREACH (const QString& key, attr->keys())
|
||||
if (key.startsWith(QLatin1String("KPH: ")))
|
||||
res.addStringField(key, attr->value(key));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int Service::sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const
|
||||
{
|
||||
QUrl url(entry->url());
|
||||
if (url.scheme().isEmpty())
|
||||
url.setScheme("http");
|
||||
const QString entryURL = url.toString(QUrl::StripTrailingSlash);
|
||||
const QString baseEntryURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
if (submitUrl == entryURL)
|
||||
return 100;
|
||||
if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL)
|
||||
return 90;
|
||||
if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL)
|
||||
return 80;
|
||||
if (entryURL == host)
|
||||
return 70;
|
||||
if (entryURL == baseSubmitUrl)
|
||||
return 60;
|
||||
if (entryURL.startsWith(submitUrl))
|
||||
return 50;
|
||||
if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host)
|
||||
return 40;
|
||||
if (submitUrl.startsWith(entryURL))
|
||||
return 30;
|
||||
if (submitUrl.startsWith(baseEntryURL))
|
||||
return 20;
|
||||
if (entryURL.startsWith(host))
|
||||
return 10;
|
||||
if (host.startsWith(entryURL))
|
||||
return 5;
|
||||
return 0;
|
||||
}
|
||||
|
||||
class Service::SortEntries
|
||||
{
|
||||
public:
|
||||
SortEntries(const QHash<const Entry*, int>& priorities, const QString & field):
|
||||
m_priorities(priorities), m_field(field)
|
||||
{}
|
||||
|
||||
bool operator()(const Entry* left, const Entry* right) const
|
||||
{
|
||||
int res = m_priorities.value(left) - m_priorities.value(right);
|
||||
if (res == 0)
|
||||
return QString::localeAwareCompare(left->attributes()->value(m_field), right->attributes()->value(m_field)) < 0;
|
||||
return res < 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const QHash<const Entry*, int>& m_priorities;
|
||||
const QString m_field;
|
||||
};
|
||||
|
||||
QList<KeepassHttpProtocol::Entry> Service::findMatchingEntries(const QString& /*id*/, const QString& url, const QString& submitUrl, const QString& realm)
|
||||
{
|
||||
const bool alwaysAllowAccess = HttpSettings::alwaysAllowAccess();
|
||||
const QString host = QUrl(url).host();
|
||||
const QString submitHost = QUrl(submitUrl).host();
|
||||
|
||||
//Check entries for authorization
|
||||
QList<Entry*> pwEntriesToConfirm;
|
||||
QList<Entry*> pwEntries;
|
||||
Q_FOREACH (Entry * entry, searchEntries(url)) {
|
||||
switch(checkAccess(entry, host, submitHost, realm)) {
|
||||
case Denied:
|
||||
continue;
|
||||
|
||||
case Unknown:
|
||||
if (alwaysAllowAccess)
|
||||
pwEntries.append(entry);
|
||||
else
|
||||
pwEntriesToConfirm.append(entry);
|
||||
break;
|
||||
|
||||
case Allowed:
|
||||
pwEntries.append(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//If unsure, ask user for confirmation
|
||||
//if (!pwEntriesToConfirm.isEmpty()
|
||||
// && HttpSettings::showNotification()
|
||||
// && !ShowNotification(QString("%0: %1 is requesting access, click to allow or deny")
|
||||
// .arg(id).arg(submitHost.isEmpty() ? host : submithost));
|
||||
// pwEntriesToConfirm.clear(); //timeout --> do not request confirmation
|
||||
|
||||
if (!pwEntriesToConfirm.isEmpty()) {
|
||||
|
||||
AccessControlDialog dlg;
|
||||
dlg.setUrl(url);
|
||||
dlg.setItems(pwEntriesToConfirm);
|
||||
//dlg.setRemember(); //TODO: setting!
|
||||
|
||||
int res = dlg.exec();
|
||||
if (dlg.remember()) {
|
||||
Q_FOREACH (Entry * entry, pwEntriesToConfirm) {
|
||||
EntryConfig config;
|
||||
config.load(entry);
|
||||
if (res == QDialog::Accepted) {
|
||||
config.allow(host);
|
||||
if (!submitHost.isEmpty() && host != submitHost)
|
||||
config.allow(submitHost);
|
||||
} else if (res == QDialog::Rejected) {
|
||||
config.deny(host);
|
||||
if (!submitHost.isEmpty() && host != submitHost)
|
||||
config.deny(submitHost);
|
||||
}
|
||||
if (!realm.isEmpty())
|
||||
config.setRealm(realm);
|
||||
config.save(entry);
|
||||
}
|
||||
}
|
||||
if (res == QDialog::Accepted)
|
||||
pwEntries.append(pwEntriesToConfirm);
|
||||
}
|
||||
|
||||
//Sort results
|
||||
const bool sortSelection = true;
|
||||
if (sortSelection) {
|
||||
QUrl url(submitUrl);
|
||||
if (url.scheme().isEmpty())
|
||||
url.setScheme("http");
|
||||
const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
|
||||
const QString baseSubmitURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
//Cache priorities
|
||||
QHash<const Entry *, int> priorities;
|
||||
priorities.reserve(pwEntries.size());
|
||||
Q_FOREACH (const Entry * entry, pwEntries)
|
||||
priorities.insert(entry, sortPriority(entry, host, submitUrl, baseSubmitURL));
|
||||
|
||||
//Sort by priorities
|
||||
qSort(pwEntries.begin(), pwEntries.end(), SortEntries(priorities, HttpSettings::sortByTitle() ? "Title" : "UserName"));
|
||||
}
|
||||
|
||||
//if (pwEntries.count() > 0)
|
||||
//{
|
||||
// var names = (from e in resp.Entries select e.Name).Distinct<string>();
|
||||
// var n = String.Join("\n ", names.ToArray<string>());
|
||||
// if (HttpSettings::receiveCredentialNotification())
|
||||
// ShowNotification(QString("%0: %1 is receiving credentials for:\n%2").arg(Id).arg(host).arg(n)));
|
||||
//}
|
||||
|
||||
//Fill the list
|
||||
QList<KeepassHttpProtocol::Entry> result;
|
||||
result.reserve(pwEntries.count());
|
||||
Q_FOREACH (Entry * entry, pwEntries)
|
||||
result << prepareEntry(entry);
|
||||
return result;
|
||||
}
|
||||
|
||||
int Service::countMatchingEntries(const QString &id, const QString &url, const QString &submitUrl, const QString &realm)
|
||||
{
|
||||
return searchEntries(url).count();
|
||||
}
|
||||
|
||||
QList<KeepassHttpProtocol::Entry> Service::searchAllEntries(const QString &id)
|
||||
{
|
||||
QList<KeepassHttpProtocol::Entry> result;
|
||||
if (DatabaseWidget * dbWidget = m_dbTabWidget->currentDatabaseWidget())
|
||||
if (Database * db = dbWidget->database())
|
||||
if (Group * rootGroup = db->rootGroup())
|
||||
Q_FOREACH (Entry * entry, rootGroup->entriesRecursive())
|
||||
if (!entry->url().isEmpty() || QUrl(entry->title()).isValid())
|
||||
result << KeepassHttpProtocol::Entry(entry->title(), entry->username(), QString(), entry->uuid().toHex());
|
||||
return result;
|
||||
}
|
||||
|
||||
Group * Service::findCreateAddEntryGroup()
|
||||
{
|
||||
if (DatabaseWidget * dbWidget = m_dbTabWidget->currentDatabaseWidget())
|
||||
if (Database * db = dbWidget->database())
|
||||
if (Group * rootGroup = db->rootGroup()) {
|
||||
const QString groupName = QLatin1String(KEEPASSHTTP_GROUP_NAME);//TODO: setting to decide where new keys are created
|
||||
|
||||
Q_FOREACH (const Group * g, rootGroup->groupsRecursive(true))
|
||||
if (g->name() == groupName)
|
||||
return db->resolveGroup(g->uuid());
|
||||
|
||||
Group * group;
|
||||
group = new Group();
|
||||
group->setUuid(Uuid::random());
|
||||
group->setName(groupName);
|
||||
group->setIcon(KEEPASSHTTP_DEFAULT_ICON);
|
||||
group->setParent(rootGroup);
|
||||
return group;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void Service::addEntry(const QString &id, const QString &login, const QString &password, const QString &url, const QString &submitUrl, const QString &realm)
|
||||
{
|
||||
if (Group * group = findCreateAddEntryGroup()) {
|
||||
Entry * entry = new Entry();
|
||||
entry->setUuid(Uuid::random());
|
||||
entry->setTitle(QUrl(url).host());
|
||||
entry->setUrl(url);
|
||||
entry->setIcon(KEEPASSHTTP_DEFAULT_ICON);
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->setGroup(group);
|
||||
|
||||
const QString host = QUrl(url).host();
|
||||
const QString submitHost = QUrl(submitUrl).host();
|
||||
EntryConfig config;
|
||||
config.allow(host);
|
||||
if (!submitHost.isEmpty())
|
||||
config.allow(submitHost);
|
||||
if (!realm.isEmpty())
|
||||
config.setRealm(realm);
|
||||
config.save(entry);
|
||||
}
|
||||
}
|
||||
|
||||
void Service::updateEntry(const QString &id, const QString &uuid, const QString &login, const QString &password, const QString &url)
|
||||
{
|
||||
if (DatabaseWidget * dbWidget = m_dbTabWidget->currentDatabaseWidget())
|
||||
if (Database * db = dbWidget->database())
|
||||
if (Entry * entry = db->resolveEntry(Uuid::fromHex(uuid))) {
|
||||
QString u = entry->username();
|
||||
if (u != login || entry->password() != password) {
|
||||
//ShowNotification(QString("%0: You have an entry change prompt waiting, click to activate").arg(requestId));
|
||||
if ( HttpSettings::alwaysAllowUpdate()
|
||||
|| QMessageBox::warning(0, tr("KeyPassX/Http: Update Entry"),
|
||||
tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host()).arg(u),
|
||||
QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes ) {
|
||||
entry->beginUpdate();
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->endUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Service::generatePassword()
|
||||
{
|
||||
PasswordGenerator * pwGenerator = passwordGenerator();
|
||||
return pwGenerator->generatePassword(HttpSettings::passwordLength(),
|
||||
HttpSettings::passwordCharClasses(),
|
||||
HttpSettings::passwordGeneratorFlags());
|
||||
}
|
||||
|
||||
void Service::removeSharedEncryptionKeys()
|
||||
{
|
||||
if (!isDatabaseOpened()) {
|
||||
QMessageBox::critical(0, tr("KeyPassX/Http: Database locked!"),
|
||||
tr("The active database is locked!\n"
|
||||
"Please unlock the selected database or choose another one which is unlocked."),
|
||||
QMessageBox::Ok);
|
||||
} else if (Entry* entry = getConfigEntry()) {
|
||||
QStringList keysToRemove;
|
||||
Q_FOREACH (const QString& key, entry->attributes()->keys())
|
||||
if (key.startsWith(ASSOCIATE_KEY_PREFIX))
|
||||
keysToRemove << key;
|
||||
|
||||
if(keysToRemove.count()) {
|
||||
entry->beginUpdate();
|
||||
Q_FOREACH (const QString& key, keysToRemove)
|
||||
entry->attributes()->remove(key);
|
||||
entry->endUpdate();
|
||||
|
||||
const int count = keysToRemove.count();
|
||||
QMessageBox::information(0, tr("KeyPassX/Http: Removed keys from database"),
|
||||
tr("Successfully removed %1 encryption-%2 from KeePassX/Http Settings.").arg(count).arg(count ? "keys" : "key"),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
QMessageBox::information(0, tr("KeyPassX/Http: No keys found"),
|
||||
tr("No shared encryption-keys found in KeePassHttp Settings."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
} else {
|
||||
QMessageBox::information(0, tr("KeyPassX/Http: Settings not available!"),
|
||||
tr("The active database does not contain an entry of KeePassHttp Settings."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
|
||||
void Service::removeStoredPermissions()
|
||||
{
|
||||
if (!isDatabaseOpened()) {
|
||||
QMessageBox::critical(0, tr("KeyPassX/Http: Database locked!"),
|
||||
tr("The active database is locked!\n"
|
||||
"Please unlock the selected database or choose another one which is unlocked."),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
Database * db = m_dbTabWidget->currentDatabaseWidget()->database();
|
||||
QList<Entry*> entries = db->rootGroup()->entriesRecursive();
|
||||
|
||||
QProgressDialog progress(tr("Removing stored permissions..."), tr("Abort"), 0, entries.count());
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
|
||||
uint counter = 0;
|
||||
Q_FOREACH (Entry* entry, entries) {
|
||||
if (progress.wasCanceled())
|
||||
return;
|
||||
if (entry->attributes()->contains(KEEPASSHTTP_NAME)) {
|
||||
entry->beginUpdate();
|
||||
entry->attributes()->remove(KEEPASSHTTP_NAME);
|
||||
entry->endUpdate();
|
||||
counter ++;
|
||||
}
|
||||
progress.setValue(progress.value() + 1);
|
||||
}
|
||||
progress.reset();
|
||||
|
||||
if (counter > 0) {
|
||||
QMessageBox::information(0, tr("KeyPassX/Http: Removed permissions"),
|
||||
tr("Successfully removed permissions from %1 %2.").arg(counter).arg(counter ? "entries" : "entry"),
|
||||
QMessageBox::Ok);
|
||||
} else {
|
||||
QMessageBox::information(0, tr("KeyPassX/Http: No entry with permissions found!"),
|
||||
tr("The active database does not contain an entry with permissions."),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
}
|
||||
}
|
61
src/http/Service.h
Normal file
61
src/http/Service.h
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Service.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef SERVICE_H
|
||||
#define SERVICE_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "Server.h"
|
||||
|
||||
class Service : public KeepassHttpProtocol::Server
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Service(DatabaseTabWidget* parent = 0);
|
||||
|
||||
virtual bool isDatabaseOpened() const;
|
||||
virtual bool openDatabase();
|
||||
virtual QString getDatabaseRootUuid();
|
||||
virtual QString getDatabaseRecycleBinUuid();
|
||||
virtual QString getKey(const QString& id);
|
||||
virtual QString storeKey(const QString& key);
|
||||
virtual QList<KeepassHttpProtocol::Entry> findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
virtual int countMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
virtual QList<KeepassHttpProtocol::Entry> searchAllEntries(const QString& id);
|
||||
virtual void addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
virtual void updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url);
|
||||
virtual QString generatePassword();
|
||||
|
||||
public Q_SLOTS:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private:
|
||||
enum Access { Denied, Unknown, Allowed};
|
||||
Entry* getConfigEntry(bool create = false);
|
||||
bool matchUrlScheme(const QString& url);
|
||||
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
|
||||
bool removeFirstDomain(QString& hostname);
|
||||
Group *findCreateAddEntryGroup();
|
||||
class SortEntries;
|
||||
int sortPriority(const Entry *entry, const QString &host, const QString &submitUrl, const QString &baseSubmitUrl) const;
|
||||
KeepassHttpProtocol::Entry prepareEntry(const Entry* entry);
|
||||
QList<Entry*> searchEntries(Database* db, const QString& hostname);
|
||||
QList<Entry*> searchEntries(const QString& text);
|
||||
|
||||
DatabaseTabWidget * const m_dbTabWidget;
|
||||
};
|
||||
|
||||
#endif // SERVICE_H
|
20
src/http/qhttpserver/CMakeLists.txt
Normal file
20
src/http/qhttpserver/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
set(qhttpserver_MOC_HDRS
|
||||
qhttpserver.h
|
||||
qhttpresponse.h
|
||||
qhttprequest.h
|
||||
qhttpconnection.h
|
||||
)
|
||||
|
||||
IF (NOT Qt5Core_FOUND)
|
||||
qt4_wrap_cpp(qhttpserver_MOC_SRCS ${qhttpserver_MOC_HDRS})
|
||||
ENDIF()
|
||||
|
||||
set (qhttpserver_SRCS qhttpconnection.cpp qhttprequest.cpp qhttpresponse.cpp qhttpserver.cpp
|
||||
http-parser/http_parser.c http-parser/url_parser.c)
|
||||
set (qhttpserver_HEADERS qhttpconnection.h qhttprequest.h qhttpresponse.h qhttpserver.h
|
||||
http-parser/http_parser.h)
|
||||
|
||||
INCLUDE_DIRECTORIES(http-parser)
|
||||
|
||||
add_library (qhttpserver STATIC ${qhttpserver_SRCS} ${qhttpserver_MOC_SRCS} ${qhttpserver_HEADERS})
|
19
src/http/qhttpserver/LICENSE
Normal file
19
src/http/qhttpserver/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2011-2012 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
40
src/http/qhttpserver/http-parser/AUTHORS
Normal file
40
src/http/qhttpserver/http-parser/AUTHORS
Normal file
@ -0,0 +1,40 @@
|
||||
# Authors ordered by first contribution.
|
||||
Ryan Dahl <ry@tinyclouds.org>
|
||||
Jeremy Hinegardner <jeremy@hinegardner.org>
|
||||
Sergey Shepelev <temotor@gmail.com>
|
||||
Joe Damato <ice799@gmail.com>
|
||||
tomika <tomika_nospam@freemail.hu>
|
||||
Phoenix Sol <phoenix@burninglabs.com>
|
||||
Cliff Frey <cliff@meraki.com>
|
||||
Ewen Cheslack-Postava <ewencp@cs.stanford.edu>
|
||||
Santiago Gala <sgala@apache.org>
|
||||
Tim Becker <tim.becker@syngenio.de>
|
||||
Jeff Terrace <jterrace@gmail.com>
|
||||
Ben Noordhuis <info@bnoordhuis.nl>
|
||||
Nathan Rajlich <nathan@tootallnate.net>
|
||||
Mark Nottingham <mnot@mnot.net>
|
||||
Aman Gupta <aman@tmm1.net>
|
||||
Tim Becker <tim.becker@kuriositaet.de>
|
||||
Sean Cunningham <sean.cunningham@mandiant.com>
|
||||
Peter Griess <pg@std.in>
|
||||
Salman Haq <salman.haq@asti-usa.com>
|
||||
Cliff Frey <clifffrey@gmail.com>
|
||||
Jon Kolb <jon@b0g.us>
|
||||
Fouad Mardini <f.mardini@gmail.com>
|
||||
Paul Querna <pquerna@apache.org>
|
||||
Felix Geisendörfer <felix@debuggable.com>
|
||||
koichik <koichik@improvement.jp>
|
||||
Andre Caron <andre.l.caron@gmail.com>
|
||||
Ivo Raisr <ivosh@ivosh.net>
|
||||
James McLaughlin <jamie@lacewing-project.org>
|
||||
David Gwynne <loki@animata.net>
|
||||
LE ROUX Thomas <thomas@november-eleven.fr>
|
||||
Randy Rizun <rrizun@ortivawireless.com>
|
||||
Andre Louis Caron <andre.louis.caron@usherbrooke.ca>
|
||||
Simon Zimmermann <simonz05@gmail.com>
|
||||
Erik Dubbelboer <erik@dubbelboer.com>
|
||||
Martell Malone <martellmalone@gmail.com>
|
||||
Bertrand Paquet <bpaquet@octo.com>
|
||||
BogDan Vatra <bogdan@kde.org>
|
||||
Peter Faiman <peter@thepicard.org>
|
||||
Corey Richardson <corey@octayn.net>
|
4
src/http/qhttpserver/http-parser/CONTRIBUTIONS
Normal file
4
src/http/qhttpserver/http-parser/CONTRIBUTIONS
Normal file
@ -0,0 +1,4 @@
|
||||
Contributors must agree to the Contributor License Agreement before patches
|
||||
can be accepted.
|
||||
|
||||
http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ
|
23
src/http/qhttpserver/http-parser/LICENSE-MIT
Normal file
23
src/http/qhttpserver/http-parser/LICENSE-MIT
Normal file
@ -0,0 +1,23 @@
|
||||
http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
|
||||
Igor Sysoev.
|
||||
|
||||
Additional changes are licensed under the same terms as NGINX and
|
||||
copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
178
src/http/qhttpserver/http-parser/README.md
Normal file
178
src/http/qhttpserver/http-parser/README.md
Normal file
@ -0,0 +1,178 @@
|
||||
HTTP Parser
|
||||
===========
|
||||
|
||||
This is a parser for HTTP messages written in C. It parses both requests and
|
||||
responses. The parser is designed to be used in performance HTTP
|
||||
applications. It does not make any syscalls nor allocations, it does not
|
||||
buffer data, it can be interrupted at anytime. Depending on your
|
||||
architecture, it only requires about 40 bytes of data per message
|
||||
stream (in a web server that is per connection).
|
||||
|
||||
Features:
|
||||
|
||||
* No dependencies
|
||||
* Handles persistent streams (keep-alive).
|
||||
* Decodes chunked encoding.
|
||||
* Upgrade support
|
||||
* Defends against buffer overflow attacks.
|
||||
|
||||
The parser extracts the following information from HTTP messages:
|
||||
|
||||
* Header fields and values
|
||||
* Content-Length
|
||||
* Request method
|
||||
* Response status code
|
||||
* Transfer-Encoding
|
||||
* HTTP version
|
||||
* Request URL
|
||||
* Message body
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
One `http_parser` object is used per TCP connection. Initialize the struct
|
||||
using `http_parser_init()` and set the callbacks. That might look something
|
||||
like this for a request parser:
|
||||
|
||||
http_parser_settings settings;
|
||||
settings.on_url = my_url_callback;
|
||||
settings.on_header_field = my_header_field_callback;
|
||||
/* ... */
|
||||
|
||||
http_parser *parser = malloc(sizeof(http_parser));
|
||||
http_parser_init(parser, HTTP_REQUEST);
|
||||
parser->data = my_socket;
|
||||
|
||||
When data is received on the socket execute the parser and check for errors.
|
||||
|
||||
size_t len = 80*1024, nparsed;
|
||||
char buf[len];
|
||||
ssize_t recved;
|
||||
|
||||
recved = recv(fd, buf, len, 0);
|
||||
|
||||
if (recved < 0) {
|
||||
/* Handle error. */
|
||||
}
|
||||
|
||||
/* Start up / continue the parser.
|
||||
* Note we pass recved==0 to signal that EOF has been recieved.
|
||||
*/
|
||||
nparsed = http_parser_execute(parser, &settings, buf, recved);
|
||||
|
||||
if (parser->upgrade) {
|
||||
/* handle new protocol */
|
||||
} else if (nparsed != recved) {
|
||||
/* Handle error. Usually just close the connection. */
|
||||
}
|
||||
|
||||
HTTP needs to know where the end of the stream is. For example, sometimes
|
||||
servers send responses without Content-Length and expect the client to
|
||||
consume input (for the body) until EOF. To tell http_parser about EOF, give
|
||||
`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors
|
||||
can still be encountered during an EOF, so one must still be prepared
|
||||
to receive them.
|
||||
|
||||
Scalar valued message information such as `status_code`, `method`, and the
|
||||
HTTP version are stored in the parser structure. This data is only
|
||||
temporally stored in `http_parser` and gets reset on each new message. If
|
||||
this information is needed later, copy it out of the structure during the
|
||||
`headers_complete` callback.
|
||||
|
||||
The parser decodes the transfer-encoding for both requests and responses
|
||||
transparently. That is, a chunked encoding is decoded before being sent to
|
||||
the on_body callback.
|
||||
|
||||
|
||||
The Special Problem of Upgrade
|
||||
------------------------------
|
||||
|
||||
HTTP supports upgrading the connection to a different protocol. An
|
||||
increasingly common example of this is the Web Socket protocol which sends
|
||||
a request like
|
||||
|
||||
GET /demo HTTP/1.1
|
||||
Upgrade: WebSocket
|
||||
Connection: Upgrade
|
||||
Host: example.com
|
||||
Origin: http://example.com
|
||||
WebSocket-Protocol: sample
|
||||
|
||||
followed by non-HTTP data.
|
||||
|
||||
(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more
|
||||
information the Web Socket protocol.)
|
||||
|
||||
To support this, the parser will treat this as a normal HTTP message without a
|
||||
body. Issuing both on_headers_complete and on_message_complete callbacks. However
|
||||
http_parser_execute() will stop parsing at the end of the headers and return.
|
||||
|
||||
The user is expected to check if `parser->upgrade` has been set to 1 after
|
||||
`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
|
||||
offset by the return value of `http_parser_execute()`.
|
||||
|
||||
|
||||
Callbacks
|
||||
---------
|
||||
|
||||
During the `http_parser_execute()` call, the callbacks set in
|
||||
`http_parser_settings` will be executed. The parser maintains state and
|
||||
never looks behind, so buffering the data is not necessary. If you need to
|
||||
save certain data for later usage, you can do that from the callbacks.
|
||||
|
||||
There are two types of callbacks:
|
||||
|
||||
* notification `typedef int (*http_cb) (http_parser*);`
|
||||
Callbacks: on_message_begin, on_headers_complete, on_message_complete.
|
||||
* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
|
||||
Callbacks: (requests only) on_uri,
|
||||
(common) on_header_field, on_header_value, on_body;
|
||||
|
||||
Callbacks must return 0 on success. Returning a non-zero value indicates
|
||||
error to the parser, making it exit immediately.
|
||||
|
||||
In case you parse HTTP message in chunks (i.e. `read()` request line
|
||||
from socket, parse, read half headers, parse, etc) your data callbacks
|
||||
may be called more than once. Http-parser guarantees that data pointer is only
|
||||
valid for the lifetime of callback. You can also `read()` into a heap allocated
|
||||
buffer to avoid copying memory around if this fits your application.
|
||||
|
||||
Reading headers may be a tricky task if you read/parse headers partially.
|
||||
Basically, you need to remember whether last header callback was field or value
|
||||
and apply following logic:
|
||||
|
||||
(on_header_field and on_header_value shortened to on_h_*)
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| State (prev. callback) | Callback | Description/action |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
|
||||
| | | into it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| value | on_h_field | New header started. |
|
||||
| | | Copy current name,value buffers to headers |
|
||||
| | | list and allocate new buffer for new name |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| field | on_h_field | Previous name continues. Reallocate name |
|
||||
| | | buffer and append callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| field | on_h_value | Value for current header started. Allocate |
|
||||
| | | new buffer and copy callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| value | on_h_value | Value continues. Reallocate value buffer |
|
||||
| | | and append callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
|
||||
|
||||
Parsing URLs
|
||||
------------
|
||||
|
||||
A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`.
|
||||
Users of this library may wish to use it to parse URLs constructed from
|
||||
consecutive `on_url` callbacks.
|
||||
|
||||
See examples of reading in headers:
|
||||
|
||||
* [partial example](http://gist.github.com/155877) in C
|
||||
* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C
|
||||
* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript
|
2174
src/http/qhttpserver/http-parser/http_parser.c
Normal file
2174
src/http/qhttpserver/http-parser/http_parser.c
Normal file
File diff suppressed because it is too large
Load Diff
111
src/http/qhttpserver/http-parser/http_parser.gyp
Normal file
111
src/http/qhttpserver/http-parser/http_parser.gyp
Normal file
@ -0,0 +1,111 @@
|
||||
# This file is used with the GYP meta build system.
|
||||
# http://code.google.com/p/gyp/
|
||||
# To build try this:
|
||||
# svn co http://gyp.googlecode.com/svn/trunk gyp
|
||||
# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp
|
||||
# ./out/Debug/test
|
||||
{
|
||||
'target_defaults': {
|
||||
'default_configuration': 'Debug',
|
||||
'configurations': {
|
||||
# TODO: hoist these out and put them somewhere common, because
|
||||
# RuntimeLibrary MUST MATCH across the entire project
|
||||
'Debug': {
|
||||
'defines': [ 'DEBUG', '_DEBUG' ],
|
||||
'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'RuntimeLibrary': 1, # static debug
|
||||
},
|
||||
},
|
||||
},
|
||||
'Release': {
|
||||
'defines': [ 'NDEBUG' ],
|
||||
'cflags': [ '-Wall', '-Wextra', '-O3' ],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'RuntimeLibrary': 0, # static release
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
},
|
||||
'VCLibrarianTool': {
|
||||
},
|
||||
'VCLinkerTool': {
|
||||
'GenerateDebugInformation': 'true',
|
||||
},
|
||||
},
|
||||
'conditions': [
|
||||
['OS == "win"', {
|
||||
'defines': [
|
||||
'WIN32'
|
||||
],
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'http_parser',
|
||||
'type': 'static_library',
|
||||
'include_dirs': [ '.' ],
|
||||
'direct_dependent_settings': {
|
||||
'defines': [ 'HTTP_PARSER_STRICT=0' ],
|
||||
'include_dirs': [ '.' ],
|
||||
},
|
||||
'defines': [ 'HTTP_PARSER_STRICT=0' ],
|
||||
'sources': [ './http_parser.c', ],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
# Compile as C++. http_parser.c is actually C99, but C++ is
|
||||
# close enough in this case.
|
||||
'CompileAs': 2,
|
||||
},
|
||||
},
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'http_parser_strict',
|
||||
'type': 'static_library',
|
||||
'include_dirs': [ '.' ],
|
||||
'direct_dependent_settings': {
|
||||
'defines': [ 'HTTP_PARSER_STRICT=1' ],
|
||||
'include_dirs': [ '.' ],
|
||||
},
|
||||
'defines': [ 'HTTP_PARSER_STRICT=1' ],
|
||||
'sources': [ './http_parser.c', ],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
# Compile as C++. http_parser.c is actually C99, but C++ is
|
||||
# close enough in this case.
|
||||
'CompileAs': 2,
|
||||
},
|
||||
},
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'test-nonstrict',
|
||||
'type': 'executable',
|
||||
'dependencies': [ 'http_parser' ],
|
||||
'sources': [ 'test.c' ]
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'test-strict',
|
||||
'type': 'executable',
|
||||
'dependencies': [ 'http_parser_strict' ],
|
||||
'sources': [ 'test.c' ]
|
||||
}
|
||||
]
|
||||
}
|
302
src/http/qhttpserver/http-parser/http_parser.h
Normal file
302
src/http/qhttpserver/http-parser/http_parser.h
Normal file
@ -0,0 +1,302 @@
|
||||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 0
|
||||
|
||||
#include <sys/types.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
|
||||
#include <BaseTsd.h>
|
||||
#include <stddef.h>
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
/* Maximium header size allowed */
|
||||
#define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
|
||||
|
||||
/* Callbacks should return non-zero to indicate an error. The parser will
|
||||
* then halt execution.
|
||||
*
|
||||
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
|
||||
* returning '1' from on_headers_complete will tell the parser that it
|
||||
* should not expect a body. This is used when receiving a response to a
|
||||
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
|
||||
* chunked' headers that indicate the presence of a body.
|
||||
*
|
||||
* http_data_cb does not return data chunks. It will be call arbitrarally
|
||||
* many times for each string. E.G. you might get 10 callbacks for "on_url"
|
||||
* each providing just a few characters more data.
|
||||
*/
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
|
||||
/* Request Methods */
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* webdav */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
/* subversion */ \
|
||||
XX(16, REPORT, REPORT) \
|
||||
XX(17, MKACTIVITY, MKACTIVITY) \
|
||||
XX(18, CHECKOUT, CHECKOUT) \
|
||||
XX(19, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(20, MSEARCH, M-SEARCH) \
|
||||
XX(21, NOTIFY, NOTIFY) \
|
||||
XX(22, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(24, PATCH, PATCH) \
|
||||
XX(25, PURGE, PURGE) \
|
||||
|
||||
enum http_method
|
||||
{
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
/* Flag values for http_parser.flags field */
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_TRAILING = 1 << 3
|
||||
, F_UPGRADE = 1 << 4
|
||||
, F_SKIPBODY = 1 << 5
|
||||
};
|
||||
|
||||
|
||||
/* Map for errno-related constants
|
||||
*
|
||||
* The provided argument should be a macro that takes 2 arguments.
|
||||
*/
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
/* No error */ \
|
||||
XX(OK, "success") \
|
||||
\
|
||||
/* Callback-related errors */ \
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
\
|
||||
/* Parsing-related errors */ \
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(PAUSED, "parser is paused") \
|
||||
XX(UNKNOWN, "an unknown error occurred")
|
||||
|
||||
|
||||
/* Define HPE_* values for each errno value above */
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
/* Get an http_errno value from an http_parser */
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
|
||||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned char type : 2; /* enum http_parser_type */
|
||||
unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned char state; /* enum state from http_parser.c */
|
||||
unsigned char header_state; /* enum header_state from http_parser.c */
|
||||
unsigned char index; /* index into current matcher */
|
||||
|
||||
uint32_t nread; /* # bytes read in various scenarios */
|
||||
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
|
||||
|
||||
/** READ-ONLY **/
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned short status_code; /* responses only */
|
||||
unsigned char method; /* requests only */
|
||||
unsigned char http_errno : 7;
|
||||
|
||||
/* 1 = Upgrade header was present and the parser has exited because of that.
|
||||
* 0 = No upgrade header present.
|
||||
* Should be checked when http_parser_execute() returns in addition to
|
||||
* error checking.
|
||||
*/
|
||||
unsigned char upgrade : 1;
|
||||
|
||||
/** PUBLIC **/
|
||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
/* Result structure for http_parser_parse_url().
|
||||
*
|
||||
* Callers should index into field_data[] with UF_* values iff field_set
|
||||
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
|
||||
* because we probably have padding left over), we convert any port to
|
||||
* a uint16_t.
|
||||
*/
|
||||
struct http_parser_url {
|
||||
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
|
||||
uint16_t port; /* Converted UF_PORT string */
|
||||
|
||||
struct {
|
||||
uint16_t off; /* Offset into buffer in which field starts */
|
||||
uint16_t len; /* Length of run in buffer */
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
/* If http_should_keep_alive() in the on_headers_complete or
|
||||
* on_message_complete callback returns 0, then this should be
|
||||
* the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
int http_should_keep_alive(const http_parser *parser);
|
||||
|
||||
/* Returns a string version of the HTTP method. */
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
/* Return a string name of the given error */
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
/* Return a string description of the given error */
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
/* Parse a URL; return nonzero on failure */
|
||||
int http_parser_parse_url(const char *buf, size_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
/* Pause or un-pause the parser; a nonzero value pauses */
|
||||
void http_parser_pause(http_parser *parser, int paused);
|
||||
|
||||
/* Checks if this is the final chunk of the body. */
|
||||
int http_body_is_final(const http_parser *parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
3402
src/http/qhttpserver/http-parser/test.c
Normal file
3402
src/http/qhttpserver/http-parser/test.c
Normal file
File diff suppressed because it is too large
Load Diff
44
src/http/qhttpserver/http-parser/url_parser.c
Normal file
44
src/http/qhttpserver/http-parser/url_parser.c
Normal file
@ -0,0 +1,44 @@
|
||||
#include "http_parser.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
dump_url (const char *url, const struct http_parser_url *u)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
|
||||
for (i = 0; i < UF_MAX; i++) {
|
||||
if ((u->field_set & (1 << i)) == 0) {
|
||||
printf("\tfield_data[%u]: unset\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n",
|
||||
i,
|
||||
u->field_data[i].off,
|
||||
u->field_data[i].len,
|
||||
u->field_data[i].len,
|
||||
url + u->field_data[i].off);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
if (argc != 3) {
|
||||
printf("Syntax : %s connect|get url\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
struct http_parser_url u;
|
||||
int len = strlen(argv[2]);
|
||||
int connect = strcmp("connect", argv[1]) == 0 ? 1 : 0;
|
||||
printf("Parsing %s, connect %d\n", argv[2], connect);
|
||||
|
||||
int result = http_parser_parse_url(argv[2], len, connect, &u);
|
||||
if (result != 0) {
|
||||
printf("Parse error : %d\n", result);
|
||||
return result;
|
||||
}
|
||||
printf("Parse ok, result : \n");
|
||||
dump_url(argv[2], &u);
|
||||
return 0;
|
||||
}
|
202
src/http/qhttpserver/qhttpconnection.cpp
Normal file
202
src/http/qhttpserver/qhttpconnection.cpp
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qhttpconnection.h"
|
||||
|
||||
#include <QtNetwork/QTcpSocket>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "qhttprequest.h"
|
||||
#include "qhttpresponse.h"
|
||||
|
||||
QHttpConnection::QHttpConnection(QTcpSocket *socket, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_socket(socket)
|
||||
, m_parser(0)
|
||||
, m_request(0)
|
||||
{
|
||||
qDebug() << "Got new connection" << socket->peerAddress() << socket->peerPort();
|
||||
|
||||
m_parser = (http_parser*)malloc(sizeof(http_parser));
|
||||
http_parser_init(m_parser, HTTP_REQUEST);
|
||||
|
||||
m_parserSettings.on_message_begin = MessageBegin;
|
||||
m_parserSettings.on_url = Url;
|
||||
m_parserSettings.on_header_field = HeaderField;
|
||||
m_parserSettings.on_header_value = HeaderValue;
|
||||
m_parserSettings.on_headers_complete = HeadersComplete;
|
||||
m_parserSettings.on_body = Body;
|
||||
m_parserSettings.on_message_complete = MessageComplete;
|
||||
|
||||
m_parser->data = this;
|
||||
|
||||
connect(socket, SIGNAL(readyRead()), this, SLOT(parseRequest()));
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
|
||||
}
|
||||
|
||||
QHttpConnection::~QHttpConnection()
|
||||
{
|
||||
delete m_socket;
|
||||
m_socket = 0;
|
||||
|
||||
free(m_parser);
|
||||
m_parser = 0;
|
||||
}
|
||||
|
||||
void QHttpConnection::socketDisconnected()
|
||||
{
|
||||
if(m_request) {
|
||||
if(m_request->successful()) {
|
||||
return;
|
||||
}
|
||||
m_request->setSuccessful(false);
|
||||
Q_EMIT m_request->end();
|
||||
}
|
||||
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void QHttpConnection::parseRequest()
|
||||
{
|
||||
Q_ASSERT(m_parser);
|
||||
|
||||
while(m_socket->bytesAvailable())
|
||||
{
|
||||
QByteArray arr = m_socket->readAll();
|
||||
http_parser_execute(m_parser, &m_parserSettings, arr.constData(), arr.size());
|
||||
}
|
||||
}
|
||||
|
||||
void QHttpConnection::write(const QByteArray &data)
|
||||
{
|
||||
m_socket->write(data);
|
||||
}
|
||||
|
||||
void QHttpConnection::flush()
|
||||
{
|
||||
m_socket->flush();
|
||||
}
|
||||
|
||||
/********************
|
||||
* Static Callbacks *
|
||||
*******************/
|
||||
int QHttpConnection::MessageBegin(http_parser *parser)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
theConnection->m_currentHeaders.clear();
|
||||
theConnection->m_request = new QHttpRequest(theConnection);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::HeadersComplete(http_parser *parser)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
/** set method **/
|
||||
theConnection->m_request->setMethod(static_cast<QHttpRequest::HttpMethod>(parser->method));
|
||||
|
||||
/** set version **/
|
||||
theConnection->m_request->setVersion(QString("%1.%2").arg(parser->http_major).arg(parser->http_minor));
|
||||
|
||||
// Insert last remaining header
|
||||
theConnection->m_currentHeaders[theConnection->m_currentHeaderField.toLower()] = theConnection->m_currentHeaderValue;
|
||||
theConnection->m_request->setHeaders(theConnection->m_currentHeaders);
|
||||
|
||||
/** set client information **/
|
||||
theConnection->m_request->m_remoteAddress = theConnection->m_socket->peerAddress().toString();
|
||||
theConnection->m_request->m_remotePort = theConnection->m_socket->peerPort();
|
||||
|
||||
QHttpResponse *response = new QHttpResponse(theConnection);
|
||||
if( parser->http_major < 1 || parser->http_minor < 1 )
|
||||
response->m_keepAlive = false;
|
||||
|
||||
connect(theConnection, SIGNAL(destroyed()), response, SLOT(connectionClosed()));
|
||||
|
||||
// we are good to go!
|
||||
Q_EMIT theConnection->newRequest(theConnection->m_request, response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::MessageComplete(http_parser *parser)
|
||||
{
|
||||
// TODO: do cleanup and prepare for next request
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
theConnection->m_request->setSuccessful(true);
|
||||
Q_EMIT theConnection->m_request->end();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::Url(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
QString url = QString::fromAscii(at, length);
|
||||
theConnection->m_request->setUrl(QUrl(url));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::HeaderField(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
// insert the header we parsed previously
|
||||
// into the header map
|
||||
if( !theConnection->m_currentHeaderField.isEmpty() && !theConnection->m_currentHeaderValue.isEmpty() )
|
||||
{
|
||||
// header names are always lower-cased
|
||||
theConnection->m_currentHeaders[theConnection->m_currentHeaderField.toLower()] = theConnection->m_currentHeaderValue;
|
||||
// clear header value. this sets up a nice
|
||||
// feedback loop where the next time
|
||||
// HeaderValue is called, it can simply append
|
||||
theConnection->m_currentHeaderField = QString();
|
||||
theConnection->m_currentHeaderValue = QString();
|
||||
}
|
||||
|
||||
QString fieldSuffix = QString::fromAscii(at, length);
|
||||
theConnection->m_currentHeaderField += fieldSuffix;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::HeaderValue(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
QString valueSuffix = QString::fromAscii(at, length);
|
||||
theConnection->m_currentHeaderValue += valueSuffix;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::Body(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
Q_EMIT theConnection->m_request->data(QByteArray(at, length));
|
||||
return 0;
|
||||
}
|
80
src/http/qhttpserver/qhttpconnection.h
Normal file
80
src/http/qhttpserver/qhttpconnection.h
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef Q_HTTP_CONNECTION
|
||||
#define Q_HTTP_CONNECTION
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QHash>
|
||||
|
||||
#include "http_parser.h"
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QHttpRequest;
|
||||
class QHttpResponse;
|
||||
|
||||
typedef QHash<QString, QString> HeaderHash;
|
||||
|
||||
class QHttpConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QHttpConnection(QTcpSocket *socket, QObject *parent = 0);
|
||||
virtual ~QHttpConnection();
|
||||
|
||||
void write(const QByteArray &data);
|
||||
void flush();
|
||||
|
||||
Q_SIGNALS:
|
||||
void newRequest(QHttpRequest*, QHttpResponse*);
|
||||
|
||||
private Q_SLOTS:
|
||||
void parseRequest();
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
static int MessageBegin(http_parser *parser);
|
||||
static int Url(http_parser *parser, const char *at, size_t length);
|
||||
static int HeaderField(http_parser *parser, const char *at, size_t length);
|
||||
static int HeaderValue(http_parser *parser, const char *at, size_t length);
|
||||
static int HeadersComplete(http_parser *parser);
|
||||
static int Body(http_parser *parser, const char *at, size_t length);
|
||||
static int MessageComplete(http_parser *parser);
|
||||
|
||||
private:
|
||||
QTcpSocket *m_socket;
|
||||
http_parser_settings m_parserSettings;
|
||||
http_parser *m_parser;
|
||||
|
||||
// since there can only be one request at any time
|
||||
// even with pipelining
|
||||
QHttpRequest *m_request;
|
||||
|
||||
// the ones we are reading in from the parser
|
||||
HeaderHash m_currentHeaders;
|
||||
QString m_currentHeaderField;
|
||||
QString m_currentHeaderValue;
|
||||
};
|
||||
|
||||
#endif
|
38
src/http/qhttpserver/qhttprequest.cpp
Normal file
38
src/http/qhttpserver/qhttprequest.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qhttprequest.h"
|
||||
|
||||
#include "qhttpconnection.h"
|
||||
|
||||
QHttpRequest::QHttpRequest(QHttpConnection *connection, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_connection(connection)
|
||||
, m_url("http://localhost/")
|
||||
, m_success(false)
|
||||
{
|
||||
}
|
||||
|
||||
QHttpRequest::~QHttpRequest()
|
||||
{
|
||||
}
|
||||
|
245
src/http/qhttpserver/qhttprequest.h
Normal file
245
src/http/qhttpserver/qhttprequest.h
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef Q_HTTP_REQUEST
|
||||
#define Q_HTTP_REQUEST
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QMetaEnum>
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QHttpConnection;
|
||||
|
||||
typedef QHash<QString, QString> HeaderHash;
|
||||
|
||||
/* Request Methods */
|
||||
|
||||
/*! \class QHttpRequest
|
||||
*
|
||||
* The QHttpRequest class represents the header and data
|
||||
* sent by the client.
|
||||
*
|
||||
* Header data is available immediately.
|
||||
*
|
||||
* Body data is streamed as it comes in via the data(const QByteArray&) signal.
|
||||
* As a consequence the application's request callback should ensure that it
|
||||
* connects to the data() signal before control returns back to the event loop.
|
||||
* Otherwise there is a risk of some data never being received by the
|
||||
* application.
|
||||
*
|
||||
* The class is <strong>read-only</strong> by users of %QHttpServer.
|
||||
*/
|
||||
class QHttpRequest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(HeaderHash headers READ headers);
|
||||
Q_PROPERTY(QString remoteAddress READ remoteAddress);
|
||||
Q_PROPERTY(quint16 remotePort READ remotePort);
|
||||
Q_PROPERTY(QString method READ method);
|
||||
Q_PROPERTY(QUrl url READ url);
|
||||
Q_PROPERTY(QString path READ path);
|
||||
Q_PROPERTY(QString httpVersion READ httpVersion);
|
||||
Q_ENUMS(HttpMethod);
|
||||
|
||||
public:
|
||||
virtual ~QHttpRequest();
|
||||
|
||||
/*!
|
||||
* Request Methods
|
||||
* Taken from http_parser.h -- make sure to keep synced
|
||||
*/
|
||||
enum HttpMethod {
|
||||
HTTP_DELETE = 0,
|
||||
HTTP_GET,
|
||||
HTTP_HEAD,
|
||||
HTTP_POST,
|
||||
HTTP_PUT,
|
||||
/* pathological */
|
||||
HTTP_CONNECT,
|
||||
HTTP_OPTIONS,
|
||||
HTTP_TRACE,
|
||||
/* webdav */
|
||||
HTTP_COPY,
|
||||
HTTP_LOCK,
|
||||
HTTP_MKCOL,
|
||||
HTTP_MOVE,
|
||||
HTTP_PROPFIND,
|
||||
HTTP_PROPPATCH,
|
||||
HTTP_SEARCH,
|
||||
HTTP_UNLOCK,
|
||||
/* subversion */
|
||||
HTTP_REPORT,
|
||||
HTTP_MKACTIVITY,
|
||||
HTTP_CHECKOUT,
|
||||
HTTP_MERGE,
|
||||
/* upnp */
|
||||
HTTP_MSEARCH,
|
||||
HTTP_NOTIFY,
|
||||
HTTP_SUBSCRIBE,
|
||||
HTTP_UNSUBSCRIBE,
|
||||
/* RFC-5789 */
|
||||
HTTP_PATCH,
|
||||
HTTP_PURGE
|
||||
};
|
||||
|
||||
/*!
|
||||
* Returns the method string for the request
|
||||
*/
|
||||
const QString methodString() const { return MethodToString(method()); }
|
||||
|
||||
/*!
|
||||
* The method used for the request.
|
||||
*/
|
||||
HttpMethod method() const { return m_method; };
|
||||
|
||||
/*!
|
||||
* The complete URL for the request. This
|
||||
* includes the path and query string.
|
||||
*
|
||||
*/
|
||||
const QUrl& url() const { return m_url; };
|
||||
|
||||
/*!
|
||||
* The path portion of the query URL.
|
||||
*
|
||||
* \sa url()
|
||||
*/
|
||||
const QString path() const { return m_url.path(); };
|
||||
|
||||
/*!
|
||||
* The HTTP version used by the client as a
|
||||
* 'x.x' string.
|
||||
*/
|
||||
const QString& httpVersion() const { return m_version; };
|
||||
|
||||
/*!
|
||||
* Any query string included as part of a request.
|
||||
* Usually used to send data in a GET request.
|
||||
*/
|
||||
const QString& queryString() const;
|
||||
|
||||
/*!
|
||||
* Get a hash of the headers sent by the client.
|
||||
* NOTE: All header names are <strong>lowercase</strong>
|
||||
* so that Content-Length becomes content-length and so on.
|
||||
*
|
||||
* This returns a reference! If you want to store headers
|
||||
* somewhere else, where the request may be deleted,
|
||||
* make sure you store them as a copy.
|
||||
*/
|
||||
const HeaderHash& headers() const { return m_headers; };
|
||||
|
||||
/*!
|
||||
* Get the value of a header
|
||||
*
|
||||
* \param field Name of the header field (lowercase).
|
||||
* \return Value of the header or null QString()
|
||||
*/
|
||||
QString header(const QString &field) { return m_headers[field]; };
|
||||
|
||||
/*!
|
||||
* IP Address of the client in dotted decimal format
|
||||
*/
|
||||
const QString& remoteAddress() const { return m_remoteAddress; };
|
||||
|
||||
/*!
|
||||
* Outbound connection port for the client.
|
||||
*/
|
||||
quint16 remotePort() const { return m_remotePort; };
|
||||
|
||||
/*!
|
||||
* Post data
|
||||
*/
|
||||
const QByteArray &body() const { return m_body; }
|
||||
|
||||
/*!
|
||||
* Set immediately before end has been emitted,
|
||||
* stating whether the message was properly received.
|
||||
* Defaults to false untiil the message has completed.
|
||||
*/
|
||||
bool successful() const { return m_success; }
|
||||
|
||||
/*!
|
||||
* connect to data and store all data in a QByteArray
|
||||
* accessible at body()
|
||||
*/
|
||||
void storeBody()
|
||||
{
|
||||
connect(this, SIGNAL(data(const QByteArray &)),
|
||||
this, SLOT(appendBody(const QByteArray &)),
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
/*!
|
||||
* This signal is emitted whenever body data is encountered
|
||||
* in a message.
|
||||
* This may be emitted zero or more times.
|
||||
*/
|
||||
void data(const QByteArray &);
|
||||
|
||||
/*!
|
||||
* Emitted at the end of the HTTP request.
|
||||
* No data() signals will be emitted after this.
|
||||
*/
|
||||
void end();
|
||||
|
||||
private:
|
||||
QHttpRequest(QHttpConnection *connection, QObject *parent = 0);
|
||||
|
||||
static QString MethodToString(HttpMethod method)
|
||||
{
|
||||
int index = staticMetaObject.indexOfEnumerator("HttpMethod");
|
||||
return staticMetaObject.enumerator(index).valueToKey(method);
|
||||
}
|
||||
|
||||
void setMethod(HttpMethod method) { m_method = method; }
|
||||
void setVersion(const QString &version) { m_version = version; }
|
||||
void setUrl(const QUrl &url) { m_url = url; }
|
||||
void setHeaders(const HeaderHash headers) { m_headers = headers; }
|
||||
void setSuccessful(bool success) { m_success = success; }
|
||||
|
||||
QHttpConnection *m_connection;
|
||||
HeaderHash m_headers;
|
||||
HttpMethod m_method;
|
||||
QUrl m_url;
|
||||
QString m_version;
|
||||
QString m_remoteAddress;
|
||||
quint16 m_remotePort;
|
||||
QByteArray m_body;
|
||||
bool m_success;
|
||||
|
||||
friend class QHttpConnection;
|
||||
|
||||
private Q_SLOTS:
|
||||
void appendBody(const QByteArray &body)
|
||||
{
|
||||
m_body.append(body);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
193
src/http/qhttpserver/qhttpresponse.cpp
Normal file
193
src/http/qhttpserver/qhttpresponse.cpp
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qhttpresponse.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
|
||||
#include "qhttpserver.h"
|
||||
#include "qhttpconnection.h"
|
||||
|
||||
QHttpResponse::QHttpResponse(QHttpConnection *connection)
|
||||
// TODO: parent child relation
|
||||
: QObject(0)
|
||||
, m_connection(connection)
|
||||
, m_headerWritten(false)
|
||||
, m_sentConnectionHeader(false)
|
||||
, m_sentContentLengthHeader(false)
|
||||
, m_sentTransferEncodingHeader(false)
|
||||
, m_sentDate(false)
|
||||
, m_keepAlive(true)
|
||||
, m_last(false)
|
||||
, m_useChunkedEncoding(false)
|
||||
, m_finished(false)
|
||||
{
|
||||
}
|
||||
|
||||
QHttpResponse::~QHttpResponse()
|
||||
{
|
||||
}
|
||||
|
||||
void QHttpResponse::setHeader(const QString &field, const QString &value)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_headers[field] = value;
|
||||
}
|
||||
|
||||
void QHttpResponse::writeHeader(const char *field, const QString &value)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->write(field);
|
||||
m_connection->write(": ");
|
||||
m_connection->write(value.toUtf8());
|
||||
m_connection->write("\r\n");
|
||||
}
|
||||
|
||||
void QHttpResponse::writeHeaders()
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_FOREACH(QString name, m_headers.keys())
|
||||
{
|
||||
QString value = m_headers[name];
|
||||
if( name.compare("connection", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentConnectionHeader = true;
|
||||
if( value == "close" )
|
||||
m_last = true;
|
||||
else
|
||||
m_keepAlive = true;
|
||||
}
|
||||
else if( name.compare("transfer-encoding", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentTransferEncodingHeader = true;
|
||||
if( value == "chunked" )
|
||||
m_useChunkedEncoding = true;
|
||||
}
|
||||
else if( name.compare("content-length", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentContentLengthHeader = true;
|
||||
}
|
||||
else if( name.compare("date", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentDate = true;
|
||||
}
|
||||
//TODO: Expect case
|
||||
|
||||
writeHeader(name.toAscii(), value.toAscii());
|
||||
}
|
||||
|
||||
if( !m_sentConnectionHeader )
|
||||
{
|
||||
if( m_keepAlive &&
|
||||
( m_sentContentLengthHeader || m_useChunkedEncoding ) )
|
||||
{
|
||||
writeHeader("Connection", "keep-alive");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_last = true;
|
||||
writeHeader("Connection", "close");
|
||||
}
|
||||
}
|
||||
|
||||
if( !m_sentContentLengthHeader && !m_sentTransferEncodingHeader )
|
||||
{
|
||||
if( m_useChunkedEncoding )
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
else
|
||||
m_last = true;
|
||||
}
|
||||
|
||||
if( !m_sentDate )
|
||||
{
|
||||
writeHeader("Date", QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss G'M'T"));
|
||||
}
|
||||
}
|
||||
|
||||
void QHttpResponse::writeHead(int status)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_headerWritten ) return;
|
||||
|
||||
m_connection->write(QString("HTTP/1.1 %1 %2\r\n").arg(status).arg(STATUS_CODES[status]).toAscii());
|
||||
|
||||
writeHeaders();
|
||||
|
||||
m_connection->write("\r\n");
|
||||
m_headerWritten = true;
|
||||
}
|
||||
|
||||
void QHttpResponse::write(const QByteArray &data)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( !m_headerWritten )
|
||||
{
|
||||
qDebug() << "You MUST call writeHead() before writing body data";
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->write(data);
|
||||
}
|
||||
|
||||
void QHttpResponse::write(const QString &data)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->write(data.toUtf8());
|
||||
}
|
||||
|
||||
void QHttpResponse::end(const QString &data)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
m_finished = true;
|
||||
|
||||
write(data);
|
||||
|
||||
Q_EMIT done();
|
||||
deleteLater();
|
||||
// TODO: end connection and delete ourselves
|
||||
}
|
||||
|
||||
void QHttpResponse::connectionClosed()
|
||||
{
|
||||
m_finished = true;
|
||||
deleteLater();
|
||||
}
|
174
src/http/qhttpserver/qhttpresponse.h
Normal file
174
src/http/qhttpserver/qhttpresponse.h
Normal file
@ -0,0 +1,174 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef Q_HTTP_RESPONSE
|
||||
#define Q_HTTP_RESPONSE
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QHash>
|
||||
|
||||
//
|
||||
class QTcpSocket;
|
||||
|
||||
class QHttpConnection;
|
||||
|
||||
typedef QHash<QString, QString> HeaderHash;
|
||||
|
||||
/*!
|
||||
* The QHttpResponse class handles sending
|
||||
* data back to the client in response to a request.
|
||||
*
|
||||
* The way to respond is to:
|
||||
* <ol>
|
||||
* <li>Set headers (optional).</li>
|
||||
* <li>Call writeHead() with the HTTP status code.</li>
|
||||
* <li>Call write() zero or more times.</li>
|
||||
* <li>Call end() when you are ready to end the request.</li>
|
||||
* </ol>
|
||||
*
|
||||
*/
|
||||
class QHttpResponse : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum StatusCode {
|
||||
STATUS_CONTINUE = 100,
|
||||
STATUS_SWITCH_PROTOCOLS = 101,
|
||||
STATUS_OK = 200,
|
||||
STATUS_CREATED = 201,
|
||||
STATUS_ACCEPTED = 202,
|
||||
STATUS_NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
STATUS_NO_CONTENT = 204,
|
||||
STATUS_RESET_CONTENT = 205,
|
||||
STATUS_PARTIAL_CONTENT = 206,
|
||||
STATUS_MULTIPLE_CHOICES = 300,
|
||||
STATUS_MOVED_PERMANENTLY = 301,
|
||||
STATUS_FOUND = 302,
|
||||
STATUS_SEE_OTHER = 303,
|
||||
STATUS_NOT_MODIFIED = 304,
|
||||
STATUS_USE_PROXY = 305,
|
||||
STATUS_TEMPORARY_REDIRECT = 307,
|
||||
STATUS_BAD_REQUEST = 400,
|
||||
STATUS_UNAUTHORIZED = 401,
|
||||
STATUS_PAYMENT_REQUIRED = 402,
|
||||
STATUS_FORBIDDEN = 403,
|
||||
STATUS_NOT_FOUND = 404,
|
||||
STATUS_METHOD_NOT_ALLOWED = 405,
|
||||
STATUS_NOT_ACCEPTABLE = 406,
|
||||
STATUS_PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
STATUS_REQUEST_TIMEOUT = 408,
|
||||
STATUS_CONFLICT = 409,
|
||||
STATUS_GONE = 410,
|
||||
STATUS_LENGTH_REQUIRED = 411,
|
||||
STATUS_PRECONDITION_FAILED = 412,
|
||||
STATUS_REQUEST_ENTITY_TOO_LARGE = 413,
|
||||
STATUS_REQUEST_URI_TOO_LONG = 414,
|
||||
STATUS_REQUEST_UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
STATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
||||
STATUS_EXPECTATION_FAILED = 417,
|
||||
STATUS_INTERNAL_SERVER_ERROR = 500,
|
||||
STATUS_NOT_IMPLEMENTED = 501,
|
||||
STATUS_BAD_GATEWAY = 502,
|
||||
STATUS_SERVICE_UNAVAILABLE = 503,
|
||||
STATUS_GATEWAY_TIMEOUT = 504,
|
||||
STATUS_HTTP_VERSION_NOT_SUPPORTED = 505
|
||||
};
|
||||
|
||||
virtual ~QHttpResponse();
|
||||
|
||||
public Q_SLOTS:
|
||||
/*!
|
||||
* Write the header of the response
|
||||
* using @c status as the response status
|
||||
* code. Any headers should be set before this
|
||||
* is called.
|
||||
*/
|
||||
void writeHead(int status);
|
||||
|
||||
/*!
|
||||
* Write the block of data to the client.
|
||||
*
|
||||
* \note
|
||||
* writeHead() has to be called before write(), otherwise the call will
|
||||
* fail.
|
||||
*/
|
||||
void write(const QByteArray &data);
|
||||
|
||||
/*!
|
||||
* Write a QString instead of a QByteArray.
|
||||
* \see write(const QByteArray &);
|
||||
*/
|
||||
void write(const QString &data);
|
||||
|
||||
/*!
|
||||
* End the response. Data will be flushed
|
||||
* to the underlying socket and the connection
|
||||
* itself will be closed if this is the last
|
||||
* response.
|
||||
*
|
||||
* This will emit done() and queue this object
|
||||
* for deletion. For details see \ref memorymanagement
|
||||
*/
|
||||
void end(const QString &data=QString());
|
||||
|
||||
/*!
|
||||
* Set a response header @c field to @c value
|
||||
*/
|
||||
void setHeader(const QString &field, const QString &value);
|
||||
|
||||
Q_SIGNALS:
|
||||
/*!
|
||||
* Emitted once the response is finished.
|
||||
* You should NOT interact with this object
|
||||
* after done() has been emitted as the object
|
||||
* is scheduled for deletion at any time.
|
||||
*/
|
||||
void done();
|
||||
|
||||
private:
|
||||
QHttpResponse(QHttpConnection *connection);
|
||||
|
||||
void writeHeaders();
|
||||
void writeHeader(const char *field, const QString &value);
|
||||
|
||||
QHttpConnection *m_connection;
|
||||
|
||||
bool m_headerWritten;
|
||||
HeaderHash m_headers;
|
||||
friend class QHttpConnection;
|
||||
|
||||
bool m_sentConnectionHeader;
|
||||
bool m_sentContentLengthHeader;
|
||||
bool m_sentTransferEncodingHeader;
|
||||
bool m_sentDate;
|
||||
bool m_keepAlive;
|
||||
bool m_last;
|
||||
bool m_useChunkedEncoding;
|
||||
bool m_finished;
|
||||
|
||||
private Q_SLOTS:
|
||||
void connectionClosed();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
125
src/http/qhttpserver/qhttpserver.cpp
Normal file
125
src/http/qhttpserver/qhttpserver.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qhttpserver.h"
|
||||
|
||||
#include <QtNetwork/QTcpServer>
|
||||
#include <QtNetwork/QTcpSocket>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "qhttpconnection.h"
|
||||
|
||||
QHash<int, QString> STATUS_CODES;
|
||||
|
||||
QHttpServer::QHttpServer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_tcpServer(0)
|
||||
{
|
||||
#define STATUS_CODE(num, reason) STATUS_CODES.insert(num, reason);
|
||||
// {{{
|
||||
STATUS_CODE(100, "Continue")
|
||||
STATUS_CODE(101, "Switching Protocols")
|
||||
STATUS_CODE(102, "Processing") // RFC 2518) obsoleted by RFC 4918
|
||||
STATUS_CODE(200, "OK")
|
||||
STATUS_CODE(201, "Created")
|
||||
STATUS_CODE(202, "Accepted")
|
||||
STATUS_CODE(203, "Non-Authoritative Information")
|
||||
STATUS_CODE(204, "No Content")
|
||||
STATUS_CODE(205, "Reset Content")
|
||||
STATUS_CODE(206, "Partial Content")
|
||||
STATUS_CODE(207, "Multi-Status") // RFC 4918
|
||||
STATUS_CODE(300, "Multiple Choices")
|
||||
STATUS_CODE(301, "Moved Permanently")
|
||||
STATUS_CODE(302, "Moved Temporarily")
|
||||
STATUS_CODE(303, "See Other")
|
||||
STATUS_CODE(304, "Not Modified")
|
||||
STATUS_CODE(305, "Use Proxy")
|
||||
STATUS_CODE(307, "Temporary Redirect")
|
||||
STATUS_CODE(400, "Bad Request")
|
||||
STATUS_CODE(401, "Unauthorized")
|
||||
STATUS_CODE(402, "Payment Required")
|
||||
STATUS_CODE(403, "Forbidden")
|
||||
STATUS_CODE(404, "Not Found")
|
||||
STATUS_CODE(405, "Method Not Allowed")
|
||||
STATUS_CODE(406, "Not Acceptable")
|
||||
STATUS_CODE(407, "Proxy Authentication Required")
|
||||
STATUS_CODE(408, "Request Time-out")
|
||||
STATUS_CODE(409, "Conflict")
|
||||
STATUS_CODE(410, "Gone")
|
||||
STATUS_CODE(411, "Length Required")
|
||||
STATUS_CODE(412, "Precondition Failed")
|
||||
STATUS_CODE(413, "Request Entity Too Large")
|
||||
STATUS_CODE(414, "Request-URI Too Large")
|
||||
STATUS_CODE(415, "Unsupported Media Type")
|
||||
STATUS_CODE(416, "Requested Range Not Satisfiable")
|
||||
STATUS_CODE(417, "Expectation Failed")
|
||||
STATUS_CODE(418, "I\"m a teapot") // RFC 2324
|
||||
STATUS_CODE(422, "Unprocessable Entity") // RFC 4918
|
||||
STATUS_CODE(423, "Locked") // RFC 4918
|
||||
STATUS_CODE(424, "Failed Dependency") // RFC 4918
|
||||
STATUS_CODE(425, "Unordered Collection") // RFC 4918
|
||||
STATUS_CODE(426, "Upgrade Required") // RFC 2817
|
||||
STATUS_CODE(500, "Internal Server Error")
|
||||
STATUS_CODE(501, "Not Implemented")
|
||||
STATUS_CODE(502, "Bad Gateway")
|
||||
STATUS_CODE(503, "Service Unavailable")
|
||||
STATUS_CODE(504, "Gateway Time-out")
|
||||
STATUS_CODE(505, "HTTP Version not supported")
|
||||
STATUS_CODE(506, "Variant Also Negotiates") // RFC 2295
|
||||
STATUS_CODE(507, "Insufficient Storage") // RFC 4918
|
||||
STATUS_CODE(509, "Bandwidth Limit Exceeded")
|
||||
STATUS_CODE(510, "Not Extended") // RFC 2774
|
||||
// }}}
|
||||
}
|
||||
|
||||
QHttpServer::~QHttpServer()
|
||||
{
|
||||
}
|
||||
|
||||
void QHttpServer::newConnection()
|
||||
{
|
||||
Q_ASSERT(m_tcpServer);
|
||||
while(m_tcpServer->hasPendingConnections()) {
|
||||
QHttpConnection *connection = new QHttpConnection(m_tcpServer->nextPendingConnection(), this);
|
||||
connect(connection, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)),
|
||||
this, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)));
|
||||
}
|
||||
}
|
||||
|
||||
bool QHttpServer::listen(const QHostAddress &address, quint16 port)
|
||||
{
|
||||
m_tcpServer = new QTcpServer;
|
||||
|
||||
connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection()));
|
||||
return m_tcpServer->listen(address, port);
|
||||
}
|
||||
|
||||
bool QHttpServer::listen(quint16 port)
|
||||
{
|
||||
return listen(QHostAddress::Any, port);
|
||||
}
|
||||
|
||||
void QHttpServer::close()
|
||||
{
|
||||
m_tcpServer->close();
|
||||
}
|
230
src/http/qhttpserver/qhttpserver.h
Normal file
230
src/http/qhttpserver/qhttpserver.h
Normal file
@ -0,0 +1,230 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef Q_HTTP_SERVER
|
||||
#define Q_HTTP_SERVER
|
||||
|
||||
#define QHTTPSERVER_VERSION_MAJOR 0
|
||||
#define QHTTPSERVER_VERSION_MINOR 1
|
||||
#define QHTTPSERVER_VERSION_PATCH 0
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
|
||||
class QTcpServer;
|
||||
|
||||
class QHttpRequest;
|
||||
class QHttpResponse;
|
||||
|
||||
/*!
|
||||
* A map of request or response headers
|
||||
*/
|
||||
typedef QHash<QString, QString> HeaderHash;
|
||||
|
||||
/*!
|
||||
* Maps status codes to string reason phrases
|
||||
*/
|
||||
extern QHash<int, QString> STATUS_CODES;
|
||||
|
||||
/*! \mainpage %QHttpServer Documentation
|
||||
*
|
||||
* \section introduction Introduction
|
||||
*
|
||||
* %QHttpServer is a easy to use, fast and light-weight
|
||||
* HTTP Server suitable for C++ web applications backed
|
||||
* by Qt. Since C++ web applications are pretty uncommon
|
||||
* the market for this project is pretty low.
|
||||
*
|
||||
* But integrating this with a module like QtScript
|
||||
* and using it to write JavaScript web applications is
|
||||
* a tempting possibility, and something that I want to
|
||||
* demonstrate at <a href="http://conf.kde.in">conf.kde.in 2011</a>.
|
||||
*
|
||||
* %QHttpServer uses a signal-slots based mechanism
|
||||
* for all communication, so no inheritance is required.
|
||||
* It tries to be as asynchronous as possible, to the
|
||||
* extent that request body data is also delivered as and
|
||||
* when it is received over the socket via signals. This
|
||||
* kind of programming may take some getting used to.
|
||||
*
|
||||
* %QHttpServer is backed by <a href="http://github.com/ry/http-parser">Ryan
|
||||
* Dahl's secure and fast http parser</a> which makes it streaming
|
||||
* till the lowest level.
|
||||
*
|
||||
* \section usage Usage
|
||||
*
|
||||
* Using %QHttpServer is very simple. Simply create a QHttpServer,
|
||||
* connect a slot to the newRequest() signal and use the request and
|
||||
* response objects.
|
||||
* See the QHttpServer class documentation for an example.
|
||||
*
|
||||
* \example helloworld/helloworld.cpp
|
||||
* \example helloworld/helloworld.h
|
||||
* \example greeting/greeting.cpp
|
||||
* \example greeting/greeting.h
|
||||
* \example bodydata/bodydata.cpp
|
||||
* \example bodydata/bodydata.h
|
||||
*/
|
||||
|
||||
/*! \class QHttpServer
|
||||
* The QHttpServer class forms the basis of the %QHttpServer
|
||||
* project. It is a fast, non-blocking HTTP server.
|
||||
*
|
||||
* These are the steps to create a server and respond to requests.
|
||||
*
|
||||
* <ol>
|
||||
* <li>Create an instance of QHttpServer.</li>
|
||||
* <li>Connect a slot to the newRequest(QHttpRequest*, QHttpResponse*)
|
||||
* signal.</li>
|
||||
* <li>Create a QCoreApplication to drive the server event loop.</li>
|
||||
* <li>Respond to clients by writing out to the QHttpResponse object.</li>
|
||||
* </ol>
|
||||
*
|
||||
* helloworld.cpp
|
||||
* \include helloworld/helloworld.cpp
|
||||
* helloworld.h
|
||||
* \include helloworld/helloworld.h
|
||||
*
|
||||
*/
|
||||
class QHttpServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Create a new HTTP Server
|
||||
*/
|
||||
QHttpServer(QObject *parent = 0);
|
||||
virtual ~QHttpServer();
|
||||
|
||||
/*!
|
||||
* Start the server bound to the @c address and @c port.
|
||||
* This function returns immediately!
|
||||
*
|
||||
* \param address Address on which to listen to. Default is to listen on
|
||||
* all interfaces which means the server can be accessed from anywhere.
|
||||
* \param port Port number on which the server should run.
|
||||
* \return true if the server was started successfully, false otherwise.
|
||||
* \sa listen(quint16)
|
||||
*/
|
||||
bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port=0);
|
||||
|
||||
/*!
|
||||
* Starts the server on @c port listening on all interfaces.
|
||||
*
|
||||
* \param port Port number on which the server should run.
|
||||
* \return true if the server was started successfully, false otherwise.
|
||||
* \sa listen(const QHostAddress&, quint16)
|
||||
*/
|
||||
bool listen(quint16 port);
|
||||
|
||||
/*!
|
||||
* Stop listening for connections
|
||||
*/
|
||||
void close();
|
||||
|
||||
Q_SIGNALS:
|
||||
/*!
|
||||
* This signal is emitted whenever a client
|
||||
* makes a new request to the server.
|
||||
*
|
||||
* The slot should use the @c request and @c response
|
||||
* objects to communicate with the client.
|
||||
*
|
||||
* \section memorymanagement Memory Management
|
||||
*
|
||||
* The QHttpRequest and QHttpResponse deletion policies
|
||||
* are such.
|
||||
*
|
||||
* QHttpRequest is <strong>never</strong> deleted by %QHttpServer.
|
||||
* Since it is not possible to determine till what point the application
|
||||
* may want access to its data, it is up to the application to delete it.
|
||||
* A recommended way to handle this is to create a new responder object for
|
||||
* every request and to delete the request in that object's destructor. The
|
||||
* object itself can be deleted by connecting to QHttpResponse's done()
|
||||
* slot as explained below.
|
||||
*
|
||||
* You should <strong>NOT</strong> delete the QHttpRequest object until it
|
||||
* has emitted an QHttpRequest::end() signal.
|
||||
*
|
||||
* QHttpResponse queues itself up for auto-deletion once the application
|
||||
* calls its end() method. Once the data has been flushed to the underlying
|
||||
* socket, the object will emit a QHttpResponse::done() signal before queueing itself up
|
||||
* for deletion. You should <strong>NOT</strong> interact with the response
|
||||
* object once it has emitted QHttpResponse::done() although actual deletion does not
|
||||
* happen until QHttpResponse::destroyed() is emitted.
|
||||
* QHttpResponse::done() serves as a useful way to handle memory management of the
|
||||
* application itself. For example:
|
||||
*
|
||||
* \code
|
||||
* MyApp::MyApp()
|
||||
* : QObject(0)
|
||||
* {
|
||||
* QHttpServer *s = new QHttpServer;
|
||||
* connect(s, SIGNAL(newRequest(...)), this, SLOT(handle(...)));
|
||||
* s.listen(8000);
|
||||
* }
|
||||
*
|
||||
* void MyApp::handle(QHttpRequest *request, QHttpResponse *response)
|
||||
* {
|
||||
* if( request->url() matches a route )
|
||||
* new Responder(request, response);
|
||||
* else
|
||||
* new PageNotFound(request, response);
|
||||
* }
|
||||
*
|
||||
* ...
|
||||
*
|
||||
* Responder::Responder(QHttpRequest *request, QHttpResponse *response)
|
||||
* {
|
||||
* m_request = request;
|
||||
*
|
||||
* connect(request, SIGNAL(end()), response, SLOT(end()));
|
||||
* // Once the request is complete, the response is ended.
|
||||
* // when the response ends, it deletes itself
|
||||
* // the Responder object connects to done()
|
||||
* // which will lead to it being deleted
|
||||
* // and this will delete the request.
|
||||
* // So all 3 are properly deleted.
|
||||
* connect(response, SIGNAL(done()), this, SLOT(deleteLater()));
|
||||
* response->writeHead(200);
|
||||
* response->write("Quitting soon");
|
||||
* }
|
||||
*
|
||||
* Responder::~Responder()
|
||||
* {
|
||||
* delete m_request;
|
||||
* m_request = 0;
|
||||
* }
|
||||
* \endcode
|
||||
*
|
||||
*/
|
||||
void newRequest(QHttpRequest *request, QHttpResponse *response);
|
||||
|
||||
private Q_SLOTS:
|
||||
void newConnection();
|
||||
|
||||
private:
|
||||
QTcpServer *m_tcpServer;
|
||||
};
|
||||
|
||||
#endif
|
28
src/http/qjson/CMakeLists.txt
Normal file
28
src/http/qjson/CMakeLists.txt
Normal file
@ -0,0 +1,28 @@
|
||||
# add_custom_command (OUTPUT ${qjson_SOURCE_DIR}/lib/json_parser.cc
|
||||
# PRE_BUILD
|
||||
# COMMAND bison -t -o json_parser.cc -d json_parser.yy
|
||||
# DEPENDS json_parser.yy
|
||||
# WORKING_DIRECTORY ${qjson_SOURCE_DIR}/lib/
|
||||
# )
|
||||
|
||||
# To regenerate json_scanner.cc use:
|
||||
# flex json_scanner.yy
|
||||
|
||||
set(qjson_MOC_HDRS
|
||||
parserrunnable.h
|
||||
serializerrunnable.h
|
||||
)
|
||||
|
||||
IF (NOT Qt5Core_FOUND)
|
||||
qt4_wrap_cpp(qjson_MOC_SRCS ${qjson_MOC_HDRS})
|
||||
ENDIF()
|
||||
|
||||
set (qjson_SRCS parser.cpp qobjecthelper.cpp json_scanner.cpp json_parser.cc parserrunnable.cpp serializer.cpp serializerrunnable.cpp)
|
||||
set (qjson_HEADERS parser.h parserrunnable.h qobjecthelper.h serializer.h serializerrunnable.h qjson_export.h)
|
||||
|
||||
# Required to use the intree copy of FlexLexer.h
|
||||
INCLUDE_DIRECTORIES(.)
|
||||
|
||||
add_gcc_compiler_cxxflags("-fexceptions")
|
||||
remove_definitions(-DQT_NO_KEYWORDS)
|
||||
add_library (qjson STATIC ${qjson_SRCS} ${qjson_MOC_SRCS} ${qjson_HEADERS})
|
206
src/http/qjson/FlexLexer.h
Normal file
206
src/http/qjson/FlexLexer.h
Normal file
@ -0,0 +1,206 @@
|
||||
// -*-C++-*-
|
||||
// FlexLexer.h -- define interfaces for lexical analyzer classes generated
|
||||
// by flex
|
||||
|
||||
// Copyright (c) 1993 The Regents of the University of California.
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code is derived from software contributed to Berkeley by
|
||||
// Kent Williams and Tom Epperly.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions
|
||||
// are met:
|
||||
|
||||
// 1. Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// 2. Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
|
||||
// Neither the name of the University nor the names of its contributors
|
||||
// may be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
|
||||
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
// PURPOSE.
|
||||
|
||||
// This file defines FlexLexer, an abstract class which specifies the
|
||||
// external interface provided to flex C++ lexer objects, and yyFlexLexer,
|
||||
// which defines a particular lexer class.
|
||||
//
|
||||
// If you want to create multiple lexer classes, you use the -P flag
|
||||
// to rename each yyFlexLexer to some other xxFlexLexer. You then
|
||||
// include <FlexLexer.h> in your other sources once per lexer class:
|
||||
//
|
||||
// #undef yyFlexLexer
|
||||
// #define yyFlexLexer xxFlexLexer
|
||||
// #include <FlexLexer.h>
|
||||
//
|
||||
// #undef yyFlexLexer
|
||||
// #define yyFlexLexer zzFlexLexer
|
||||
// #include <FlexLexer.h>
|
||||
// ...
|
||||
|
||||
#ifndef __FLEX_LEXER_H
|
||||
// Never included before - need to define base class.
|
||||
#define __FLEX_LEXER_H
|
||||
|
||||
#include <iostream>
|
||||
# ifndef FLEX_STD
|
||||
# define FLEX_STD std::
|
||||
# endif
|
||||
|
||||
extern "C++" {
|
||||
|
||||
struct yy_buffer_state;
|
||||
typedef int yy_state_type;
|
||||
|
||||
class FlexLexer {
|
||||
public:
|
||||
virtual ~FlexLexer() { }
|
||||
|
||||
const char* YYText() const { return yytext; }
|
||||
int YYLeng() const { return yyleng; }
|
||||
|
||||
virtual void
|
||||
yy_switch_to_buffer( struct yy_buffer_state* new_buffer ) = 0;
|
||||
virtual struct yy_buffer_state*
|
||||
yy_create_buffer( FLEX_STD istream* s, int size ) = 0;
|
||||
virtual void yy_delete_buffer( struct yy_buffer_state* b ) = 0;
|
||||
virtual void yyrestart( FLEX_STD istream* s ) = 0;
|
||||
|
||||
virtual int yylex() = 0;
|
||||
|
||||
// Call yylex with new input/output sources.
|
||||
int yylex( FLEX_STD istream* new_in, FLEX_STD ostream* new_out = 0 )
|
||||
{
|
||||
switch_streams( new_in, new_out );
|
||||
return yylex();
|
||||
}
|
||||
|
||||
// Switch to new input/output streams. A nil stream pointer
|
||||
// indicates "keep the current one".
|
||||
virtual void switch_streams( FLEX_STD istream* new_in = 0,
|
||||
FLEX_STD ostream* new_out = 0 ) = 0;
|
||||
|
||||
int lineno() const { return yylineno; }
|
||||
|
||||
int debug() const { return yy_flex_debug; }
|
||||
void set_debug( int flag ) { yy_flex_debug = flag; }
|
||||
|
||||
protected:
|
||||
char* yytext;
|
||||
int yyleng;
|
||||
int yylineno; // only maintained if you use %option yylineno
|
||||
int yy_flex_debug; // only has effect with -d or "%option debug"
|
||||
};
|
||||
|
||||
}
|
||||
#endif // FLEXLEXER_H
|
||||
|
||||
#if defined(yyFlexLexer) || ! defined(yyFlexLexerOnce)
|
||||
// Either this is the first time through (yyFlexLexerOnce not defined),
|
||||
// or this is a repeated include to define a different flavor of
|
||||
// yyFlexLexer, as discussed in the flex manual.
|
||||
#define yyFlexLexerOnce
|
||||
|
||||
extern "C++" {
|
||||
|
||||
class yyFlexLexer : public FlexLexer {
|
||||
public:
|
||||
// arg_yyin and arg_yyout default to the cin and cout, but we
|
||||
// only make that assignment when initializing in yylex().
|
||||
yyFlexLexer( FLEX_STD istream* arg_yyin = 0, FLEX_STD ostream* arg_yyout = 0 );
|
||||
|
||||
virtual ~yyFlexLexer();
|
||||
|
||||
void yy_switch_to_buffer( struct yy_buffer_state* new_buffer );
|
||||
struct yy_buffer_state* yy_create_buffer( FLEX_STD istream* s, int size );
|
||||
void yy_delete_buffer( struct yy_buffer_state* b );
|
||||
void yyrestart( FLEX_STD istream* s );
|
||||
|
||||
void yypush_buffer_state( struct yy_buffer_state* new_buffer );
|
||||
void yypop_buffer_state();
|
||||
|
||||
virtual int yylex();
|
||||
virtual void switch_streams( FLEX_STD istream* new_in, FLEX_STD ostream* new_out = 0 );
|
||||
virtual int yywrap();
|
||||
|
||||
protected:
|
||||
virtual int LexerInput( char* buf, int max_size );
|
||||
virtual void LexerOutput( const char* buf, int size );
|
||||
virtual void LexerError( const char* msg );
|
||||
|
||||
void yyunput( int c, char* buf_ptr );
|
||||
int yyinput();
|
||||
|
||||
void yy_load_buffer_state();
|
||||
void yy_init_buffer( struct yy_buffer_state* b, FLEX_STD istream* s );
|
||||
void yy_flush_buffer( struct yy_buffer_state* b );
|
||||
|
||||
int yy_start_stack_ptr;
|
||||
int yy_start_stack_depth;
|
||||
int* yy_start_stack;
|
||||
|
||||
void yy_push_state( int new_state );
|
||||
void yy_pop_state();
|
||||
int yy_top_state();
|
||||
|
||||
yy_state_type yy_get_previous_state();
|
||||
yy_state_type yy_try_NUL_trans( yy_state_type current_state );
|
||||
int yy_get_next_buffer();
|
||||
|
||||
FLEX_STD istream* yyin; // input source for default LexerInput
|
||||
FLEX_STD ostream* yyout; // output sink for default LexerOutput
|
||||
|
||||
// yy_hold_char holds the character lost when yytext is formed.
|
||||
char yy_hold_char;
|
||||
|
||||
// Number of characters read into yy_ch_buf.
|
||||
int yy_n_chars;
|
||||
|
||||
// Points to current character in buffer.
|
||||
char* yy_c_buf_p;
|
||||
|
||||
int yy_init; // whether we need to initialize
|
||||
int yy_start; // start state number
|
||||
|
||||
// Flag which is used to allow yywrap()'s to do buffer switches
|
||||
// instead of setting up a fresh yyin. A bit of a hack ...
|
||||
int yy_did_buffer_switch_on_eof;
|
||||
|
||||
|
||||
size_t yy_buffer_stack_top; /**< index of top of stack. */
|
||||
size_t yy_buffer_stack_max; /**< capacity of stack. */
|
||||
struct yy_buffer_state ** yy_buffer_stack; /**< Stack as an array. */
|
||||
void yyensure_buffer_stack(void);
|
||||
|
||||
// The following are not always needed, but may be depending
|
||||
// on use of certain flex features (like REJECT or yymore()).
|
||||
|
||||
yy_state_type yy_last_accepting_state;
|
||||
char* yy_last_accepting_cpos;
|
||||
|
||||
yy_state_type* yy_state_buf;
|
||||
yy_state_type* yy_state_ptr;
|
||||
|
||||
char* yy_full_match;
|
||||
int* yy_full_state;
|
||||
int yy_full_lp;
|
||||
|
||||
int yy_lp;
|
||||
int yy_looking_for_trail_begin;
|
||||
|
||||
int yy_more_flag;
|
||||
int yy_more_len;
|
||||
int yy_more_offset;
|
||||
int yy_prev_more_offset;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // yyFlexLexer || ! yyFlexLexerOnce
|
||||
|
89
src/http/qjson/README.license
Normal file
89
src/http/qjson/README.license
Normal file
@ -0,0 +1,89 @@
|
||||
Qjson version xxxx, Date
|
||||
|
||||
The following files are licensed under LGPL V2.1:
|
||||
------------------------------------------------
|
||||
src/json_parser.yy
|
||||
src/json_scanner.cpp
|
||||
src/json_scanner.h
|
||||
src/parser.cpp
|
||||
src/parser.h
|
||||
src/parser_p.h
|
||||
src/parserrunnable.cpp
|
||||
src/parserrunnable.h
|
||||
src/qjson_debug.h
|
||||
src/qjson_export.h
|
||||
src/qobjecthelper.cpp
|
||||
src/serializer.cpp
|
||||
src/qobjecthelper.h
|
||||
src/serializer.h
|
||||
src/serializerrunnable.cpp
|
||||
src/serializerrunnable.h
|
||||
tests/cmdline_tester/cmdline_tester.cpp
|
||||
tests/cmdline_tester/cmdlineparser.cpp
|
||||
tests/cmdline_tester/cmdlineparser.h
|
||||
tests/parser/testparser.cpp
|
||||
tests/qobjecthelper/person.h
|
||||
tests/qobjecthelper/testqobjecthelper.cpp
|
||||
tests/serializer/testserializer.cpp
|
||||
|
||||
|
||||
The following files are licensed under GPL V2 with Bison Exception:
|
||||
--------------------------------------------------------------------
|
||||
/src/json_parser.cc
|
||||
/src/stack.hh
|
||||
/src/location.hh
|
||||
/src/position.hh
|
||||
/src/json_parser.hh
|
||||
|
||||
|
||||
Copyrights:
|
||||
----------
|
||||
Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
|
||||
Copyright (C) 2009 Flavio Castelli <flavio@castelli.name> 2009 Frank Osterfeld <osterfeld@kde.org>
|
||||
Copyright (C) 2008 Flavio Castelli <flavio.castelli@gmail.com>
|
||||
Copyright (C) 2009 Till Adam <adam@kde.org>
|
||||
Copyright (C) 2009 Michael Leupold <lemma@confuego.org>
|
||||
Copyright (C) 2009 Flavio Castelli <flavio@castelli.name>
|
||||
Copyright (C) 2009 Frank Osterfeld <osterfeld@kde.org>
|
||||
Copyright (C) 2009 Pino Toscano <pino@kde.org>
|
||||
Copyright (C) 2010 Flavio Castelli <flavio@castelli.name>
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999:
|
||||
-------------------------------------------------------------
|
||||
|
||||
Checkout COPYING.lib
|
||||
|
||||
|
||||
GPL V2 with Bison Exception:
|
||||
----------------------------
|
||||
Copyright (C) 2002, 2003, 2004, 2005, 2006 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2, or (at your option)
|
||||
any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
Boston, MA 02110-1301, USA.
|
||||
|
||||
As a special exception, you may create a larger work that contains
|
||||
part or all of the Bison parser skeleton and distribute that work
|
||||
under terms of your choice, so long as that work isn't itself a
|
||||
parser generator using the skeleton or a modified version thereof
|
||||
as a parser skeleton. Alternatively, if you modify or redistribute
|
||||
the parser skeleton itself, you may (at your option) remove this
|
||||
special exception, which will cause the skeleton and the resulting
|
||||
Bison output files to be licensed under the GNU General Public
|
||||
License without this special exception.
|
||||
|
||||
This special exception was added by the Free Software Foundation in
|
||||
version 2.2 of Bison.
|
||||
|
1103
src/http/qjson/json_parser.cc
Normal file
1103
src/http/qjson/json_parser.cc
Normal file
File diff suppressed because it is too large
Load Diff
300
src/http/qjson/json_parser.hh
Normal file
300
src/http/qjson/json_parser.hh
Normal file
@ -0,0 +1,300 @@
|
||||
/* A Bison parser, made by GNU Bison 2.7. */
|
||||
|
||||
/* Skeleton interface for Bison LALR(1) parsers in C++
|
||||
|
||||
Copyright (C) 2002-2012 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
/* As a special exception, you may create a larger work that contains
|
||||
part or all of the Bison parser skeleton and distribute that work
|
||||
under terms of your choice, so long as that work isn't itself a
|
||||
parser generator using the skeleton or a modified version thereof
|
||||
as a parser skeleton. Alternatively, if you modify or redistribute
|
||||
the parser skeleton itself, you may (at your option) remove this
|
||||
special exception, which will cause the skeleton and the resulting
|
||||
Bison output files to be licensed under the GNU General Public
|
||||
License without this special exception.
|
||||
|
||||
This special exception was added by the Free Software Foundation in
|
||||
version 2.2 of Bison. */
|
||||
|
||||
/**
|
||||
** \file json_parser.hh
|
||||
** Define the yy::parser class.
|
||||
*/
|
||||
|
||||
/* C++ LALR(1) parser skeleton written by Akim Demaille. */
|
||||
|
||||
#ifndef YY_YY_JSON_PARSER_HH_INCLUDED
|
||||
# define YY_YY_JSON_PARSER_HH_INCLUDED
|
||||
|
||||
/* "%code requires" blocks. */
|
||||
/* Line 33 of lalr1.cc */
|
||||
#line 26 "json_parser.yy"
|
||||
|
||||
#include "parser_p.h"
|
||||
#include "json_scanner.h"
|
||||
#include "qjson_debug.h"
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#include <limits>
|
||||
|
||||
class JSonScanner;
|
||||
|
||||
namespace QJson {
|
||||
class Parser;
|
||||
}
|
||||
|
||||
#define YYERROR_VERBOSE 1
|
||||
|
||||
Q_DECLARE_METATYPE(QVector<QVariant>*)
|
||||
Q_DECLARE_METATYPE(QVariantMap*)
|
||||
|
||||
|
||||
/* Line 33 of lalr1.cc */
|
||||
#line 72 "json_parser.hh"
|
||||
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include "stack.hh"
|
||||
#include "location.hh"
|
||||
|
||||
/* Enabling traces. */
|
||||
#ifndef YYDEBUG
|
||||
# define YYDEBUG 1
|
||||
#endif
|
||||
|
||||
|
||||
namespace yy {
|
||||
/* Line 33 of lalr1.cc */
|
||||
#line 88 "json_parser.hh"
|
||||
|
||||
/// A Bison parser.
|
||||
class json_parser
|
||||
{
|
||||
public:
|
||||
/// Symbol semantic values.
|
||||
#ifndef YYSTYPE
|
||||
typedef int semantic_type;
|
||||
#else
|
||||
typedef YYSTYPE semantic_type;
|
||||
#endif
|
||||
/// Symbol locations.
|
||||
typedef location location_type;
|
||||
/// Tokens.
|
||||
struct token
|
||||
{
|
||||
/* Tokens. */
|
||||
enum yytokentype {
|
||||
END = 0,
|
||||
CURLY_BRACKET_OPEN = 1,
|
||||
CURLY_BRACKET_CLOSE = 2,
|
||||
SQUARE_BRACKET_OPEN = 3,
|
||||
SQUARE_BRACKET_CLOSE = 4,
|
||||
COLON = 5,
|
||||
COMMA = 6,
|
||||
NUMBER = 7,
|
||||
TRUE_VAL = 8,
|
||||
FALSE_VAL = 9,
|
||||
NULL_VAL = 10,
|
||||
STRING = 11,
|
||||
INVALID = 12
|
||||
};
|
||||
|
||||
};
|
||||
/// Token type.
|
||||
typedef token::yytokentype token_type;
|
||||
|
||||
/// Build a parser object.
|
||||
json_parser (QJson::ParserPrivate* driver_yyarg);
|
||||
virtual ~json_parser ();
|
||||
|
||||
/// Parse.
|
||||
/// \returns 0 iff parsing succeeded.
|
||||
virtual int parse ();
|
||||
|
||||
#if YYDEBUG
|
||||
/// The current debugging stream.
|
||||
std::ostream& debug_stream () const;
|
||||
/// Set the current debugging stream.
|
||||
void set_debug_stream (std::ostream &);
|
||||
|
||||
/// Type for debugging levels.
|
||||
typedef int debug_level_type;
|
||||
/// The current debugging level.
|
||||
debug_level_type debug_level () const;
|
||||
/// Set the current debugging level.
|
||||
void set_debug_level (debug_level_type l);
|
||||
#endif
|
||||
|
||||
private:
|
||||
/// Report a syntax error.
|
||||
/// \param loc where the syntax error is found.
|
||||
/// \param msg a description of the syntax error.
|
||||
virtual void error (const location_type& loc, const std::string& msg);
|
||||
|
||||
/// Generate an error message.
|
||||
/// \param state the state where the error occurred.
|
||||
/// \param tok the lookahead token.
|
||||
virtual std::string yysyntax_error_ (int yystate, int tok);
|
||||
|
||||
#if YYDEBUG
|
||||
/// \brief Report a symbol value on the debug stream.
|
||||
/// \param yytype The token type.
|
||||
/// \param yyvaluep Its semantic value.
|
||||
/// \param yylocationp Its location.
|
||||
virtual void yy_symbol_value_print_ (int yytype,
|
||||
const semantic_type* yyvaluep,
|
||||
const location_type* yylocationp);
|
||||
/// \brief Report a symbol on the debug stream.
|
||||
/// \param yytype The token type.
|
||||
/// \param yyvaluep Its semantic value.
|
||||
/// \param yylocationp Its location.
|
||||
virtual void yy_symbol_print_ (int yytype,
|
||||
const semantic_type* yyvaluep,
|
||||
const location_type* yylocationp);
|
||||
#endif
|
||||
|
||||
|
||||
/// State numbers.
|
||||
typedef int state_type;
|
||||
/// State stack type.
|
||||
typedef stack<state_type> state_stack_type;
|
||||
/// Semantic value stack type.
|
||||
typedef stack<semantic_type> semantic_stack_type;
|
||||
/// location stack type.
|
||||
typedef stack<location_type> location_stack_type;
|
||||
|
||||
/// The state stack.
|
||||
state_stack_type yystate_stack_;
|
||||
/// The semantic value stack.
|
||||
semantic_stack_type yysemantic_stack_;
|
||||
/// The location stack.
|
||||
location_stack_type yylocation_stack_;
|
||||
|
||||
/// Whether the given \c yypact_ value indicates a defaulted state.
|
||||
/// \param yyvalue the value to check
|
||||
static bool yy_pact_value_is_default_ (int yyvalue);
|
||||
|
||||
/// Whether the given \c yytable_ value indicates a syntax error.
|
||||
/// \param yyvalue the value to check
|
||||
static bool yy_table_value_is_error_ (int yyvalue);
|
||||
|
||||
/// Internal symbol numbers.
|
||||
typedef unsigned char token_number_type;
|
||||
/* Tables. */
|
||||
/// For a state, the index in \a yytable_ of its portion.
|
||||
static const signed char yypact_[];
|
||||
static const signed char yypact_ninf_;
|
||||
|
||||
/// For a state, default reduction number.
|
||||
/// Unless\a yytable_ specifies something else to do.
|
||||
/// Zero means the default is an error.
|
||||
static const unsigned char yydefact_[];
|
||||
|
||||
static const signed char yypgoto_[];
|
||||
static const signed char yydefgoto_[];
|
||||
|
||||
/// What to do in a state.
|
||||
/// \a yytable_[yypact_[s]]: what to do in state \a s.
|
||||
/// - if positive, shift that token.
|
||||
/// - if negative, reduce the rule which number is the opposite.
|
||||
/// - if zero, do what YYDEFACT says.
|
||||
static const unsigned char yytable_[];
|
||||
static const signed char yytable_ninf_;
|
||||
|
||||
static const signed char yycheck_[];
|
||||
|
||||
/// For a state, its accessing symbol.
|
||||
static const unsigned char yystos_[];
|
||||
|
||||
/// For a rule, its LHS.
|
||||
static const unsigned char yyr1_[];
|
||||
/// For a rule, its RHS length.
|
||||
static const unsigned char yyr2_[];
|
||||
|
||||
/// Convert the symbol name \a n to a form suitable for a diagnostic.
|
||||
static std::string yytnamerr_ (const char *n);
|
||||
|
||||
|
||||
/// For a symbol, its name in clear.
|
||||
static const char* const yytname_[];
|
||||
#if YYDEBUG
|
||||
/// A type to store symbol numbers and -1.
|
||||
typedef signed char rhs_number_type;
|
||||
/// A `-1'-separated list of the rules' RHS.
|
||||
static const rhs_number_type yyrhs_[];
|
||||
/// For each rule, the index of the first RHS symbol in \a yyrhs_.
|
||||
static const unsigned char yyprhs_[];
|
||||
/// For each rule, its source line number.
|
||||
static const unsigned char yyrline_[];
|
||||
/// For each scanner token number, its symbol number.
|
||||
static const unsigned short int yytoken_number_[];
|
||||
/// Report on the debug stream that the rule \a r is going to be reduced.
|
||||
virtual void yy_reduce_print_ (int r);
|
||||
/// Print the state stack on the debug stream.
|
||||
virtual void yystack_print_ ();
|
||||
|
||||
/* Debugging. */
|
||||
int yydebug_;
|
||||
std::ostream* yycdebug_;
|
||||
#endif
|
||||
|
||||
/// Convert a scanner token number \a t to a symbol number.
|
||||
token_number_type yytranslate_ (int t);
|
||||
|
||||
/// \brief Reclaim the memory associated to a symbol.
|
||||
/// \param yymsg Why this token is reclaimed.
|
||||
/// If null, do not display the symbol, just free it.
|
||||
/// \param yytype The symbol type.
|
||||
/// \param yyvaluep Its semantic value.
|
||||
/// \param yylocationp Its location.
|
||||
inline void yydestruct_ (const char* yymsg,
|
||||
int yytype,
|
||||
semantic_type* yyvaluep,
|
||||
location_type* yylocationp);
|
||||
|
||||
/// Pop \a n symbols the three stacks.
|
||||
inline void yypop_ (unsigned int n = 1);
|
||||
|
||||
/* Constants. */
|
||||
static const int yyeof_;
|
||||
/* LAST_ -- Last index in TABLE_. */
|
||||
static const int yylast_;
|
||||
static const int yynnts_;
|
||||
static const int yyempty_;
|
||||
static const int yyfinal_;
|
||||
static const int yyterror_;
|
||||
static const int yyerrcode_;
|
||||
static const int yyntokens_;
|
||||
static const unsigned int yyuser_token_number_max_;
|
||||
static const token_number_type yyundef_token_;
|
||||
|
||||
/* User arguments. */
|
||||
QJson::ParserPrivate* driver;
|
||||
};
|
||||
|
||||
} // yy
|
||||
/* Line 33 of lalr1.cc */
|
||||
#line 297 "json_parser.hh"
|
||||
|
||||
|
||||
|
||||
#endif /* !YY_YY_JSON_PARSER_HH_INCLUDED */
|
162
src/http/qjson/json_parser.yy
Normal file
162
src/http/qjson/json_parser.yy
Normal file
@ -0,0 +1,162 @@
|
||||
/* This file is part of QJSon
|
||||
*
|
||||
* Copyright (C) 2008 Flavio Castelli <flavio.castelli@gmail.com>
|
||||
* Copyright (C) 2013 Silvio Moioli <silvio@moioli.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2.1, as published by the Free Software Foundation.
|
||||
*
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library; see the file COPYING.LIB. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
%skeleton "lalr1.cc"
|
||||
%defines
|
||||
%define "parser_class_name" "json_parser"
|
||||
|
||||
%code requires{
|
||||
#include "parser_p.h"
|
||||
#include "json_scanner.h"
|
||||
#include "qjson_debug.h"
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#include <limits>
|
||||
|
||||
class JSonScanner;
|
||||
|
||||
namespace QJson {
|
||||
class Parser;
|
||||
}
|
||||
|
||||
#define YYERROR_VERBOSE 1
|
||||
|
||||
Q_DECLARE_METATYPE(QVector<QVariant>*)
|
||||
Q_DECLARE_METATYPE(QVariantMap*)
|
||||
}
|
||||
|
||||
%parse-param { QJson::ParserPrivate* driver }
|
||||
%lex-param { QJson::ParserPrivate* driver }
|
||||
|
||||
%locations
|
||||
|
||||
%debug
|
||||
%error-verbose
|
||||
|
||||
%token END 0 "end of file"
|
||||
|
||||
%token CURLY_BRACKET_OPEN 1 "{"
|
||||
%token CURLY_BRACKET_CLOSE 2 "}"
|
||||
%token SQUARE_BRACKET_OPEN 3 "["
|
||||
%token SQUARE_BRACKET_CLOSE 4 "]"
|
||||
%token COLON 5 ":"
|
||||
%token COMMA 6 ","
|
||||
|
||||
%token NUMBER 7 "number"
|
||||
%token TRUE_VAL 8 "true"
|
||||
%token FALSE_VAL 9 "false"
|
||||
%token NULL_VAL 10 "null"
|
||||
%token STRING 11 "string"
|
||||
|
||||
%token INVALID 12 "invalid"
|
||||
|
||||
// define the initial token
|
||||
%start start
|
||||
|
||||
%%
|
||||
|
||||
// grammar rules
|
||||
|
||||
start: data {
|
||||
driver->m_result = $1;
|
||||
qjsonDebug() << "json_parser - parsing finished";
|
||||
};
|
||||
|
||||
data: value { $$ = $1; }
|
||||
| error
|
||||
{
|
||||
qCritical()<< "json_parser - syntax error found, "
|
||||
<< "forcing abort, Line" << @$.begin.line << "Column" << @$.begin.column;
|
||||
YYABORT;
|
||||
}
|
||||
| END;
|
||||
|
||||
object: CURLY_BRACKET_OPEN CURLY_BRACKET_CLOSE {
|
||||
$$ = QVariant(QVariantMap());
|
||||
}
|
||||
| CURLY_BRACKET_OPEN members CURLY_BRACKET_CLOSE {
|
||||
QVariantMap* map = $2.value<QVariantMap*>();
|
||||
$$ = QVariant(*map);
|
||||
delete map;
|
||||
};
|
||||
|
||||
members: STRING COLON value {
|
||||
QVariantMap* pair = new QVariantMap();
|
||||
pair->insert($1.toString(), $3);
|
||||
$$.setValue<QVariantMap* >(pair);
|
||||
}
|
||||
| members COMMA STRING COLON value {
|
||||
$$.value<QVariantMap*>()->insert($3.toString(), $5);
|
||||
};
|
||||
|
||||
array: SQUARE_BRACKET_OPEN SQUARE_BRACKET_CLOSE {
|
||||
$$ = QVariant(QVariantList());
|
||||
}
|
||||
| SQUARE_BRACKET_OPEN values SQUARE_BRACKET_CLOSE {
|
||||
QVector<QVariant>* list = $2.value<QVector<QVariant>* >();
|
||||
$$ = QVariant(list->toList());
|
||||
delete list;
|
||||
};
|
||||
|
||||
values: value {
|
||||
QVector<QVariant>* list = new QVector<QVariant>(1);
|
||||
list->replace(0, $1);
|
||||
$$.setValue(list);
|
||||
}
|
||||
| values COMMA value {
|
||||
$$.value<QVector<QVariant>* >()->append($3);
|
||||
};
|
||||
|
||||
value: STRING
|
||||
| NUMBER
|
||||
| TRUE_VAL
|
||||
| FALSE_VAL
|
||||
| NULL_VAL
|
||||
| object
|
||||
| array;
|
||||
|
||||
%%
|
||||
|
||||
int yy::yylex(YYSTYPE *yylval, yy::location *yylloc, QJson::ParserPrivate* driver)
|
||||
{
|
||||
JSonScanner* scanner = driver->m_scanner;
|
||||
yylval->clear();
|
||||
int ret = scanner->yylex(yylval, yylloc);
|
||||
|
||||
qjsonDebug() << "json_parser::yylex - calling scanner yylval==|"
|
||||
<< yylval->toByteArray() << "|, ret==|" << QString::number(ret) << "|";
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void yy::json_parser::error (const yy::location& yyloc, const std::string& error)
|
||||
{
|
||||
/*qjsonDebug() << yyloc.begin.line;
|
||||
qjsonDebug() << yyloc.begin.column;
|
||||
qjsonDebug() << yyloc.end.line;
|
||||
qjsonDebug() << yyloc.end.column;*/
|
||||
qjsonDebug() << "json_parser::error [line" << yyloc.end.line << "] -" << error.c_str() ;
|
||||
driver->setError(QString::fromLatin1(error.c_str()), yyloc.end.line);
|
||||
}
|
4507
src/http/qjson/json_scanner.cc
Normal file
4507
src/http/qjson/json_scanner.cc
Normal file
File diff suppressed because it is too large
Load Diff
75
src/http/qjson/json_scanner.cpp
Normal file
75
src/http/qjson/json_scanner.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
/* This file is part of QJson
|
||||
*
|
||||
* Copyright (C) 2008 Flavio Castelli <flavio.castelli@gmail.com>
|
||||
* Copyright (C) 2013 Silvio Moioli <silvio@moioli.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2.1, as published by the Free Software Foundation.
|
||||
*
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library; see the file COPYING.LIB. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
#include "json_scanner.cc"
|
||||
|
||||
#include "qjson_debug.h"
|
||||
#include "json_scanner.h"
|
||||
#include "json_parser.hh"
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QRegExp>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
JSonScanner::JSonScanner(QIODevice* io)
|
||||
: m_allowSpecialNumbers(false),
|
||||
m_io (io),
|
||||
m_criticalError(false)
|
||||
{
|
||||
}
|
||||
|
||||
void JSonScanner::allowSpecialNumbers(bool allow) {
|
||||
m_allowSpecialNumbers = allow;
|
||||
}
|
||||
|
||||
int JSonScanner::yylex(YYSTYPE* yylval, yy::location *yylloc) {
|
||||
m_yylval = yylval;
|
||||
m_yylloc = yylloc;
|
||||
m_yylloc->step();
|
||||
int result = yylex();
|
||||
|
||||
if (m_criticalError) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int JSonScanner::LexerInput(char* buf, int max_size) {
|
||||
if (!m_io->isOpen()) {
|
||||
qCritical() << "JSonScanner::yylex - io device is not open";
|
||||
m_criticalError = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int readBytes = m_io->read(buf, max_size);
|
||||
if(readBytes < 0) {
|
||||
qCritical() << "JSonScanner::yylex - error while reading from io device";
|
||||
m_criticalError = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return readBytes;
|
||||
}
|
||||
|
||||
|
60
src/http/qjson/json_scanner.h
Normal file
60
src/http/qjson/json_scanner.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* This file is part of QJson
|
||||
*
|
||||
* Copyright (C) 2008 Flavio Castelli <flavio.castelli@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2.1, as published by the Free Software Foundation.
|
||||
*
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library; see the file COPYING.LIB. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef _JSON_SCANNER
|
||||
#define _JSON_SCANNER
|
||||
|
||||
#include <QtCore/QIODevice>
|
||||
#include <QtCore/QVariant>
|
||||
|
||||
#define YYSTYPE QVariant
|
||||
|
||||
// Only include FlexLexer.h if it hasn't been already included
|
||||
#if ! defined(yyFlexLexerOnce)
|
||||
#include <FlexLexer.h>
|
||||
#endif
|
||||
|
||||
#include "parser_p.h"
|
||||
|
||||
namespace yy {
|
||||
class location;
|
||||
int yylex(YYSTYPE *yylval, yy::location *yylloc, QJson::ParserPrivate* driver);
|
||||
}
|
||||
|
||||
class JSonScanner : public yyFlexLexer
|
||||
{
|
||||
public:
|
||||
explicit JSonScanner(QIODevice* io);
|
||||
void allowSpecialNumbers(bool allow);
|
||||
|
||||
int yylex(YYSTYPE* yylval, yy::location *yylloc);
|
||||
int yylex();
|
||||
int LexerInput(char* buf, int max_size);
|
||||
protected:
|
||||
bool m_allowSpecialNumbers;
|
||||
QIODevice* m_io;
|
||||
|
||||
YYSTYPE* m_yylval;
|
||||
yy::location* m_yylloc;
|
||||
bool m_criticalError;
|
||||
QString m_currentString;
|
||||
};
|
||||
|
||||
#endif
|
248
src/http/qjson/json_scanner.yy
Normal file
248
src/http/qjson/json_scanner.yy
Normal file
@ -0,0 +1,248 @@
|
||||
/* This file is part of QJson
|
||||
*
|
||||
* Copyright (C) 2013 Silvio Moioli <silvio@moioli.net>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2.1, as published by the Free Software Foundation.
|
||||
*
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library; see the file COPYING.LIB. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110yy::json_parser::token::INVALID301, USA.
|
||||
*/
|
||||
|
||||
/* Flex output settings */
|
||||
%option 8bit c++ full warn
|
||||
%option noyywrap nounistd
|
||||
%option noinput nounput noyy_push_state noyy_pop_state noyy_top_state noyy_scan_buffer noyy_scan_bytes noyy_scan_string noyyget_extra noyyset_extra noyyget_leng noyyget_text noyyget_lineno noyyset_lineno noyyget_in noyyset_in noyyget_out noyyset_out noyyget_lval noyyset_lval noyyget_lloc noyyset_lloc noyyget_debug noyyset_debug
|
||||
%option yyclass="JSonScanner"
|
||||
%option outfile="json_scanner.cc"
|
||||
|
||||
%{
|
||||
#include "json_scanner.h"
|
||||
#include "json_parser.hh"
|
||||
|
||||
#define YY_USER_INIT if(m_allowSpecialNumbers) { \
|
||||
BEGIN(ALLOW_SPECIAL_NUMBERS); \
|
||||
}
|
||||
%}
|
||||
|
||||
/* Exclusive subscanners for strings and escaped hex sequences */
|
||||
%x QUOTMARK_OPEN HEX_OPEN
|
||||
|
||||
/* Extra-JSON rules active iff m_allowSpecialNumbers is true */
|
||||
%s ALLOW_SPECIAL_NUMBERS
|
||||
|
||||
%%
|
||||
|
||||
/* Whitespace */
|
||||
[\v\f\t ]+ {
|
||||
m_yylloc->columns(yyleng);
|
||||
}
|
||||
|
||||
[\r\n]+ {
|
||||
m_yylloc->lines(yyleng);
|
||||
}
|
||||
|
||||
|
||||
/* Special values */
|
||||
true {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(true);
|
||||
return yy::json_parser::token::TRUE_VAL;
|
||||
}
|
||||
|
||||
false {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(false);
|
||||
return yy::json_parser::token::FALSE_VAL;
|
||||
}
|
||||
|
||||
null {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant();
|
||||
return yy::json_parser::token::NULL_VAL;
|
||||
}
|
||||
|
||||
|
||||
/* Numbers */
|
||||
[0-9] |
|
||||
[1-9][0-9]+ {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(strtoull(yytext, NULL, 10));
|
||||
if (errno == ERANGE) {
|
||||
qCritical() << "Number is out of range: " << yytext;
|
||||
return yy::json_parser::token::INVALID;
|
||||
}
|
||||
return yy::json_parser::token::NUMBER;
|
||||
}
|
||||
|
||||
-[0-9] |
|
||||
-[1-9][0-9]+ {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(strtoll(yytext, NULL, 10));
|
||||
if (errno == ERANGE) {
|
||||
qCritical() << "Number is out of range: " << yytext;
|
||||
return yy::json_parser::token::INVALID;
|
||||
}
|
||||
return yy::json_parser::token::NUMBER;
|
||||
}
|
||||
|
||||
-?(([0-9])|([1-9][0-9]+))(\.[0-9]+)?([Ee][+\-]?[0-9]+)? {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(strtod(yytext, NULL));
|
||||
if (errno == ERANGE) {
|
||||
qCritical() << "Number is out of range: " << yytext;
|
||||
return yy::json_parser::token::INVALID;
|
||||
}
|
||||
return yy::json_parser::token::NUMBER;
|
||||
}
|
||||
|
||||
/* Strings */
|
||||
\" {
|
||||
m_yylloc->columns(yyleng);
|
||||
BEGIN(QUOTMARK_OPEN);
|
||||
}
|
||||
|
||||
<QUOTMARK_OPEN>{
|
||||
\\\" {
|
||||
m_currentString.append(QLatin1String("\""));
|
||||
}
|
||||
|
||||
\\\\ {
|
||||
m_currentString.append(QLatin1String("\\"));
|
||||
}
|
||||
|
||||
\\\/ {
|
||||
m_currentString.append(QLatin1String("/"));
|
||||
}
|
||||
|
||||
\\b {
|
||||
m_currentString.append(QLatin1String("\b"));
|
||||
}
|
||||
|
||||
\\f {
|
||||
m_currentString.append(QLatin1String("\f"));
|
||||
}
|
||||
|
||||
\\n {
|
||||
m_currentString.append(QLatin1String("\n"));
|
||||
}
|
||||
|
||||
\\r {
|
||||
m_currentString.append(QLatin1String("\r"));
|
||||
}
|
||||
|
||||
\\t {
|
||||
m_currentString.append(QLatin1String("\t"));
|
||||
}
|
||||
|
||||
\\u {
|
||||
BEGIN(HEX_OPEN);
|
||||
}
|
||||
|
||||
[^\"\\]+ {
|
||||
m_currentString.append(QString::fromUtf8(yytext));
|
||||
}
|
||||
|
||||
\\ {
|
||||
// ignore
|
||||
}
|
||||
|
||||
\" {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(m_currentString);
|
||||
m_currentString.clear();
|
||||
BEGIN(INITIAL);
|
||||
return yy::json_parser::token::STRING;
|
||||
}
|
||||
}
|
||||
|
||||
<HEX_OPEN>{
|
||||
[0-9A-Fa-f]{4} {
|
||||
QString hexDigits = QString::fromUtf8(yytext, yyleng);
|
||||
bool ok;
|
||||
ushort hexDigit1 = hexDigits.left(2).toShort(&ok, 16);
|
||||
ushort hexDigit2 = hexDigits.right(2).toShort(&ok, 16);
|
||||
m_currentString.append(QChar(hexDigit2, hexDigit1));
|
||||
BEGIN(QUOTMARK_OPEN);
|
||||
}
|
||||
|
||||
.|\n {
|
||||
qCritical() << "Invalid hex string";
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(QLatin1String(""));
|
||||
BEGIN(QUOTMARK_OPEN);
|
||||
return yy::json_parser::token::INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* "Compound type" related tokens */
|
||||
: {
|
||||
m_yylloc->columns(yyleng);
|
||||
return yy::json_parser::token::COLON;
|
||||
}
|
||||
|
||||
, {
|
||||
m_yylloc->columns(yyleng);
|
||||
return yy::json_parser::token::COMMA;
|
||||
}
|
||||
|
||||
\[ {
|
||||
m_yylloc->columns(yyleng);
|
||||
return yy::json_parser::token::SQUARE_BRACKET_OPEN;
|
||||
}
|
||||
|
||||
\] {
|
||||
m_yylloc->columns(yyleng);
|
||||
return yy::json_parser::token::SQUARE_BRACKET_CLOSE;
|
||||
}
|
||||
|
||||
\{ {
|
||||
m_yylloc->columns(yyleng);
|
||||
return yy::json_parser::token::CURLY_BRACKET_OPEN;
|
||||
}
|
||||
|
||||
\} {
|
||||
m_yylloc->columns(yyleng);
|
||||
return yy::json_parser::token::CURLY_BRACKET_CLOSE;
|
||||
}
|
||||
|
||||
|
||||
/* Extra-JSON numbers */
|
||||
<ALLOW_SPECIAL_NUMBERS>{
|
||||
(?i:nan) {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(std::numeric_limits<double>::quiet_NaN());
|
||||
return yy::json_parser::token::NUMBER;
|
||||
}
|
||||
|
||||
[Ii]nfinity {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(std::numeric_limits<double>::infinity());
|
||||
return yy::json_parser::token::NUMBER;
|
||||
}
|
||||
|
||||
-[Ii]nfinity {
|
||||
m_yylloc->columns(yyleng);
|
||||
*m_yylval = QVariant(-std::numeric_limits<double>::infinity());
|
||||
return yy::json_parser::token::NUMBER;
|
||||
}
|
||||
}
|
||||
|
||||
/* If all else fails */
|
||||
. {
|
||||
m_yylloc->columns(yyleng);
|
||||
return yy::json_parser::token::INVALID;
|
||||
}
|
||||
|
||||
<<EOF>> return yy::json_parser::token::END;
|
181
src/http/qjson/location.hh
Normal file
181
src/http/qjson/location.hh
Normal file
@ -0,0 +1,181 @@
|
||||
/* A Bison parser, made by GNU Bison 2.7. */
|
||||
|
||||
/* Locations for Bison parsers in C++
|
||||
|
||||
Copyright (C) 2002-2007, 2009-2012 Free Software Foundation, Inc.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
/* As a special exception, you may create a larger work that contains
|
||||
part or all of the Bison parser skeleton and distribute that work
|
||||
under terms of your choice, so long as that work isn't itself a
|
||||
parser generator using the skeleton or a modified version thereof
|
||||
as a parser skeleton. Alternatively, if you modify or redistribute
|
||||
the parser skeleton itself, you may (at your option) remove this
|
||||
special exception, which will cause the skeleton and the resulting
|
||||
Bison output files to be licensed under the GNU General Public
|
||||
License without this special exception.
|
||||
|
||||
This special exception was added by the Free Software Foundation in
|
||||
version 2.2 of Bison. */
|
||||
|
||||
/**
|
||||
** \file location.hh
|
||||
** Define the yy::location class.
|
||||
*/
|
||||
|
||||
#ifndef YY_YY_LOCATION_HH_INCLUDED
|
||||
# define YY_YY_LOCATION_HH_INCLUDED
|
||||
|
||||
# include "position.hh"
|
||||
|
||||
|
||||
namespace yy {
|
||||
/* Line 166 of location.cc */
|
||||
#line 47 "location.hh"
|
||||
|
||||
/// Abstract a location.
|
||||
class location
|
||||
{
|
||||
public:
|
||||
|
||||
/// Construct a location from \a b to \a e.
|
||||
location (const position& b, const position& e)
|
||||
: begin (b)
|
||||
, end (e)
|
||||
{
|
||||
}
|
||||
|
||||
/// Construct a 0-width location in \a p.
|
||||
explicit location (const position& p = position ())
|
||||
: begin (p)
|
||||
, end (p)
|
||||
{
|
||||
}
|
||||
|
||||
/// Construct a 0-width location in \a f, \a l, \a c.
|
||||
explicit location (std::string* f,
|
||||
unsigned int l = 1u,
|
||||
unsigned int c = 1u)
|
||||
: begin (f, l, c)
|
||||
, end (f, l, c)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// Initialization.
|
||||
void initialize (std::string* f = YY_NULL,
|
||||
unsigned int l = 1u,
|
||||
unsigned int c = 1u)
|
||||
{
|
||||
begin.initialize (f, l, c);
|
||||
end = begin;
|
||||
}
|
||||
|
||||
/** \name Line and Column related manipulators
|
||||
** \{ */
|
||||
public:
|
||||
/// Reset initial location to final location.
|
||||
void step ()
|
||||
{
|
||||
begin = end;
|
||||
}
|
||||
|
||||
/// Extend the current location to the COUNT next columns.
|
||||
void columns (unsigned int count = 1)
|
||||
{
|
||||
end += count;
|
||||
}
|
||||
|
||||
/// Extend the current location to the COUNT next lines.
|
||||
void lines (unsigned int count = 1)
|
||||
{
|
||||
end.lines (count);
|
||||
}
|
||||
/** \} */
|
||||
|
||||
|
||||
public:
|
||||
/// Beginning of the located region.
|
||||
position begin;
|
||||
/// End of the located region.
|
||||
position end;
|
||||
};
|
||||
|
||||
/// Join two location objects to create a location.
|
||||
inline const location operator+ (const location& begin, const location& end)
|
||||
{
|
||||
location res = begin;
|
||||
res.end = end.end;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Add two location objects.
|
||||
inline const location operator+ (const location& begin, unsigned int width)
|
||||
{
|
||||
location res = begin;
|
||||
res.columns (width);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Add and assign a location.
|
||||
inline location& operator+= (location& res, unsigned int width)
|
||||
{
|
||||
res.columns (width);
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Compare two location objects.
|
||||
inline bool
|
||||
operator== (const location& loc1, const location& loc2)
|
||||
{
|
||||
return loc1.begin == loc2.begin && loc1.end == loc2.end;
|
||||
}
|
||||
|
||||
/// Compare two location objects.
|
||||
inline bool
|
||||
operator!= (const location& loc1, const location& loc2)
|
||||
{
|
||||
return !(loc1 == loc2);
|
||||
}
|
||||
|
||||
/** \brief Intercept output stream redirection.
|
||||
** \param ostr the destination output stream
|
||||
** \param loc a reference to the location to redirect
|
||||
**
|
||||
** Avoid duplicate information.
|
||||
*/
|
||||
template <typename YYChar>
|
||||
inline std::basic_ostream<YYChar>&
|
||||
operator<< (std::basic_ostream<YYChar>& ostr, const location& loc)
|
||||
{
|
||||
position last = loc.end - 1;
|
||||
ostr << loc.begin;
|
||||
if (last.filename
|
||||
&& (!loc.begin.filename
|
||||
|| *loc.begin.filename != *last.filename))
|
||||
ostr << '-' << last;
|
||||
else if (loc.begin.line != last.line)
|
||||
ostr << '-' << last.line << '.' << last.column;
|
||||
else if (loc.begin.column != last.column)
|
||||
ostr << '-' << last.column;
|
||||
return ostr;
|
||||
}
|
||||
|
||||
|
||||
} // yy
|
||||
/* Line 296 of location.cc */
|
||||
#line 180 "location.hh"
|
||||
|
||||
#endif /* !YY_YY_LOCATION_HH_INCLUDED */
|
125
src/http/qjson/parser.cpp
Normal file
125
src/http/qjson/parser.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
/* This file is part of QJson
|
||||
*
|
||||
* Copyright (C) 2008 Flavio Castelli <flavio.castelli@gmail.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License version 2.1, as published by the Free Software Foundation.
|
||||
*
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this library; see the file COPYING.LIB. If not, write to
|
||||
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||
* Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "parser.h"
|
||||
#include "parser_p.h"
|
||||
#include "json_parser.hh"
|
||||
#include "json_scanner.h"
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QTextStream>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
using namespace QJson;
|
||||
|
||||
ParserPrivate::ParserPrivate() :
|
||||
m_scanner(0)
|
||||
, m_negate(false)
|
||||
, m_error(false)
|
||||
, m_errorLine(0)
|
||||
, m_specialNumbersAllowed(false)
|
||||
{
|
||||
}
|
||||
|
||||
ParserPrivate::~ParserPrivate()
|
||||
{
|
||||
delete m_scanner;
|
||||
}
|
||||
|
||||
void ParserPrivate::setError(QString errorMsg, int errorLine) {
|
||||
m_error = true;
|
||||
m_errorMsg = errorMsg;
|
||||
m_errorLine = errorLine;
|
||||
}
|
||||
|
||||
Parser::Parser() :
|
||||
d(new ParserPrivate)
|
||||
{
|
||||
}
|
||||
|
||||
Parser::~Parser()
|
||||
{
|
||||
delete d;
|
||||
}
|
||||
|
||||
QVariant Parser::parse (QIODevice* io, bool* ok)
|
||||
{
|
||||
d->m_errorMsg.clear();
|
||||
delete d->m_scanner;
|
||||
d->m_scanner = 0;
|
||||
|
||||
if (!io->isOpen()) {
|
||||
if (!io->open(QIODevice::ReadOnly)) {
|
||||
if (ok != 0)
|
||||
*ok = false;
|
||||
qCritical ("Error opening device");
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
if (!io->isReadable()) {
|
||||
if (ok != 0)
|
||||
*ok = false;
|
||||
qCritical ("Device is not readable");
|
||||
io->close();
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
d->m_scanner = new JSonScanner (io);
|
||||
d->m_scanner->allowSpecialNumbers(d->m_specialNumbersAllowed);
|
||||
yy::json_parser parser(d);
|
||||
parser.parse();
|
||||
|
||||
delete d->m_scanner;
|
||||
d->m_scanner = 0;
|
||||
|
||||
if (ok != 0)
|
||||
*ok = !d->m_error;
|
||||
|
||||
io->close();
|
||||
return d->m_result;
|
||||
}
|
||||
|
||||
QVariant Parser::parse(const QByteArray& jsonString, bool* ok) {
|
||||
QBuffer buffer;
|
||||
buffer.open(QBuffer::ReadWrite);
|
||||
buffer.write(jsonString);
|
||||
buffer.seek(0);
|
||||
return parse (&buffer, ok);
|
||||
}
|
||||
|
||||
QString Parser::errorString() const
|
||||
{
|
||||
return d->m_errorMsg;
|
||||
}
|
||||
|
||||
int Parser::errorLine() const
|
||||
{
|
||||
return d->m_errorLine;
|
||||
}
|
||||
|
||||
void QJson::Parser::allowSpecialNumbers(bool allowSpecialNumbers) {
|
||||
d->m_specialNumbersAllowed = allowSpecialNumbers;
|
||||
}
|
||||
|
||||
bool Parser::specialNumbersAllowed() const {
|
||||
return d->m_specialNumbersAllowed;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user