Merged keepassx-http.

This commit is contained in:
Keith Bennett 2014-03-22 12:48:43 +00:00
commit 73f91db939
114 changed files with 21277 additions and 199 deletions

View File

@ -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)

View File

@ -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);

View File

@ -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();

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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());

View File

@ -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;

View File

@ -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();
}

View File

@ -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)
};

View File

@ -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

View File

@ -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:

View File

@ -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());

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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) {

View File

@ -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)
};

View File

@ -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>

View File

@ -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()) {

View File

@ -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;

View File

@ -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 &amp;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>&amp;16x16</string>
</property>
</action>
<action name="actionToolbarIconSize22">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>&amp;22x22</string>
</property>
</action>
<action name="actionToolbarIconSize28">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>2&amp;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/>

View File

@ -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>

View File

@ -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);
}

View File

@ -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

View File

@ -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">

View File

@ -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()) {

View File

@ -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>

View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

12
src/gui/qocoa/main.cpp Normal file
View 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
View 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

View 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];
}

View 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
View 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));
}

View 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

View 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];
}

View 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();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/Qocoa">
<file>qprogressindicatorspinning_nonmac.gif</file>
</qresource>
</RCC>

View 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

View 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);
}

View 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);
}

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

View 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);
}

View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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&amp;ow a notification when credentials are requested</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="bestMatchOnly">
<property name="text">
<string>&amp;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&amp;quest for unlocking the database if it is locked</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="matchUrlScheme">
<property name="text">
<string>&amp;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 &amp;username</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="sortByTitle">
<property name="text">
<string>Sort matching entries by &amp;title</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeSharedEncryptionKeys">
<property name="text">
<string>R&amp;emove all shared encryption-keys from active database</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeStoredPermissions">
<property name="text">
<string>Re&amp;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 &amp;access to entries</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="alwaysAllowUpdate">
<property name="text">
<string>Always allow &amp;updating entries</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="searchInAllDatabases">
<property name="text">
<string>Searc&amp;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>&amp;Return also advanced string fields which start with &quot;KPH: &quot;</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
View 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
View 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
View 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
View 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
View 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
View 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

View 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})

View 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.

View 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>

View 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

View 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.

View 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

File diff suppressed because it is too large Load Diff

View 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' ]
}
]
}

View 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

File diff suppressed because it is too large Load Diff

View 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;
}

View 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;
}

View 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

View 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()
{
}

View 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

View 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();
}

View 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

View 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();
}

View 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

View 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
View 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

View 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.

File diff suppressed because it is too large Load Diff

View 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 */

View 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);
}

File diff suppressed because it is too large Load Diff

View 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;
}

View 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

View 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
View 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
View 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