Use SymmetricCipherGcrypt directly in Protocol.cpp to make it work with the latest master
revision.
This commit is contained in:
Jascha Dachtera 2014-04-18 13:51:45 +02:00
commit 65626f0da2
170 changed files with 27759 additions and 205 deletions

View File

@ -172,6 +172,8 @@ if(NOT (${GCRYPT_VERSION_STRING} VERSION_LESS "1.6.0"))
set(GCRYPT_HAS_SALSA20 1)
endif()
find_package(LibMicroHTTPD REQUIRED)
find_package(ZLIB REQUIRED)
check_cxx_source_compiles("

17
README.md Normal file
View File

@ -0,0 +1,17 @@
# KeePassX + keepasshttp + autotype
This code extends the brilliant [KeePassX](https://www.keepassx.org/) program
which accesses [KeePass](http://keepass.info/) password databases.
I have merged the latest version of the code with Francois Ferrand's
[keepassx-http](https://gitorious.org/keepassx/keepassx-http/) repository.
This adds support for the [keepasshttp](https://github.com/pfn/keepasshttp/)
protocol, enabling automatic form-filling in web browsers. This is accomplished
via a compatible browser plugin such as
[PassIFox](https://passifox.appspot.com/passifox.xpi) for Mozilla Firefox and
[chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae)
for Google Chrome.
I have also added global autotype for OSX machines and added a few other minor
tweaks and bugfixes.

View File

@ -0,0 +1,9 @@
find_path(MHD_INCLUDE_DIR microhttpd.h)
find_library(MHD_LIBRARIES microhttpd)
mark_as_advanced(MHD_LIBRARIES MHD_INCLUDE_DIR)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(LibMicroHTTPD DEFAULT_MSG MHD_LIBRARIES MHD_INCLUDE_DIR)

View File

@ -102,6 +102,14 @@ set(keepassx_SOURCES
gui/group/EditGroupWidget.cpp
gui/group/GroupModel.cpp
gui/group/GroupView.cpp
http/AccessControlDialog.cpp
http/EntryConfig.cpp
http/HttpPasswordGeneratorWidget.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 +188,13 @@ set(keepassx_MOC
gui/group/EditGroupWidget.h
gui/group/GroupModel.h
gui/group/GroupView.h
http/AccessControlDialog.h
http/EntryConfig.h
http/HttpPasswordGeneratorWidget.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 +222,9 @@ set(keepassx_FORMS
gui/entry/EditEntryWidgetHistory.ui
gui/entry/EditEntryWidgetMain.ui
gui/group/EditGroupWidgetMain.ui
http/AccessControlDialog.ui
http/HttpPasswordGeneratorWidget.ui
http/OptionDialog.ui
)
if(MINGW)
@ -221,17 +239,27 @@ 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/qjson)
add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE})
target_link_libraries(${PROGNAME}
keepassx_core
Qocoa
qjson
${MHD_LIBRARIES}
${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

@ -87,7 +87,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action)
Tools::wait(action->delayMs);
}
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
void AutoTypeExecutor::execClearField(AutoTypeClearField*)
{
// TODO: implement
}

View File

@ -9,6 +9,10 @@ if(Q_WS_X11)
endif()
endif()
if(Q_WS_MAC)
add_subdirectory(mac)
endif()
if(WITH_TESTS)
add_subdirectory(test)
endif()

View File

@ -0,0 +1,553 @@
/*
* Copyright (C) 2009-2010 Jeff Gibbons
* Copyright (C) 2005-2008 by Tarek Saidi <tarek.saidi@arcor.de>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
*
* 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)
* version 3 of the License.
*
* 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/>.
*/
#include "AutoTypeMac.h"
#include <map>
static pid_t keepassxPID2;
#define UNICODE_BUFFER_SIZE 20
static UniChar unicodeBuffer[UNICODE_BUFFER_SIZE];
static UniCharCount unicodePtr = 0;
// reusable events
static CGEventRef unicodeEvent = CGEventCreateKeyboardEvent(NULL, 0, true);
CGEventRef AutoTypePlatformMac::keyEvent =
CGEventCreateKeyboardEvent(NULL, 0, true);
bool AutoTypePlatformMac::inHotKeyEvent = false;
static const KeycodeWithMods NoKeycodeWithMods =
(KeycodeWithMods){ NoKeycode, 0 };
static uint orderedModifiers[] = {
0,
( shiftKey ) >> 8,
(controlKey ) >> 8,
( optionKey ) >> 8,
( cmdKey ) >> 8,
( shiftKey | controlKey ) >> 8,
( shiftKey | optionKey ) >> 8,
( shiftKey | cmdKey ) >> 8,
(controlKey | optionKey ) >> 8,
(controlKey | cmdKey ) >> 8,
( optionKey | cmdKey ) >> 8,
( shiftKey | controlKey | optionKey ) >> 8,
( shiftKey | controlKey | cmdKey ) >> 8,
( shiftKey | optionKey | cmdKey ) >> 8,
(controlKey | optionKey | cmdKey ) >> 8,
( shiftKey | controlKey | optionKey | cmdKey) >> 8
};
static std::map<uint,KeycodeWithMods> unicodeToKeycodeWithModsMap;
AutoTypePlatformMac::AutoTypePlatformMac() : first(true), keepassxPID(0)
{
globalKey = 0;
globalMod = 0;
inGlobalAutoType = false;
// initialize hot key handling
hotKeyRef = NULL;
hotKeyID.signature = 'kpsx';
hotKeyID.id = 1;
EventTypeSpec eventType;
eventType.eventClass = kEventClassKeyboard;
eventType.eventKind = kEventHotKeyPressed;
InstallApplicationEventHandler(&hotKeyHandler, 1, &eventType, this, NULL);
AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap();
}
QStringList AutoTypePlatformMac::windowTitles()
{
pid_t pid;
return getTargetWindowInfo(&pid, NULL);
}
WId AutoTypePlatformMac::activeWindow()
{
WId mid;
pid_t pid;
getTargetWindowInfo(&pid, &mid);
AutoTypePlatformMac::processToFront(pid);
if (first)
first = false;
return mid;
}
QString AutoTypePlatformMac::activeWindowTitle()
{
QStringList sl = getTargetWindowInfo(NULL, NULL);
return sl[0];
}
bool AutoTypePlatformMac::registerGlobalShortcut(Qt::Key key,
Qt::KeyboardModifiers modifiers)
{
if (key == 0)
return false;
//KeycodeWithMods kc = keyToKeycodeWithMods(key);
KeycodeWithMods kc = AutoTypePlatformMac::keysymToKeycodeWithMods2(static_cast<KeySym>(key));
int code = kc.keycode;
uint mod = qtToNativeModifiers(modifiers);
if (code==globalKey && mod==globalMod)
return true;
// need to unregister old before registering new
unregisterGlobalShortcut(key, modifiers);
OSStatus status = RegisterEventHotKey(code, mod, hotKeyID,
GetApplicationEventTarget(), 0, &hotKeyRef);
if (noErr == status) {
globalKey = code;
globalMod = mod;
return true;
} else {
qWarning("Error registering global shortcut: %d", status);
RegisterEventHotKey(globalKey, globalMod, hotKeyID,
GetApplicationEventTarget(), 0, &hotKeyRef);
return false;
}
}
void AutoTypePlatformMac::unregisterGlobalShortcut(Qt::Key,
Qt::KeyboardModifiers)
{
globalKey = 0;
globalMod = 0;
UnregisterEventHotKey(hotKeyRef);
}
int AutoTypePlatformMac::platformEventFilter(void* event)
{
return -1;
}
int AutoTypePlatformMac::initialTimeout()
{
first = true;
return 500;
}
AutoTypeExecutor* AutoTypePlatformMac::createExecutor()
{
return new AutoTypeExecturorMac(this);
}
void AutoTypePlatformMac::sendUnicode(KeySym keysym)
{
if (onlySendKeycodes) {
KeycodeWithMods keycodeWithMods =
AutoTypePlatformMac::keysymToKeycodeWithMods2(keysym);
if (NoKeycode == keycodeWithMods.keycode) return;
sendKeycode(keycodeWithMods);
return;
}
unicodeBuffer[unicodePtr++] = keysym;
if (UNICODE_BUFFER_SIZE == unicodePtr) flushUnicode();
}
void AutoTypePlatformMac::sendKeycode(KeycodeWithMods keycodeWithMods)
{
flushUnicode();
uint keycode = keycodeWithMods.keycode;
uint mods = keycodeWithMods.mods << 8;
uint flags = 0;
if (0 != ( shiftKey & mods)) flags |= kCGEventFlagMaskShift;
if (0 != (controlKey & mods)) flags |= kCGEventFlagMaskControl;
if (0 != ( optionKey & mods)) flags |= kCGEventFlagMaskAlternate;
if (0 != ( cmdKey & mods)) flags |= kCGEventFlagMaskCommand;
CGEventSetIntegerValueField(keyEvent, kCGKeyboardEventKeycode, keycode);
CGEventSetFlags(AutoTypePlatformMac::keyEvent, flags);
keyDownUp(AutoTypePlatformMac::keyEvent);
sleepKeyStrokeDelay();
}
KeycodeWithMods AutoTypePlatformMac::keyToKeycodeWithMods(Qt::Key key)
{
KeycodeWithMods kc;
kc.mods = 0;
switch (key) {
case Qt::Key_Tab:
kc.keycode = kVK_Tab;
break;
case Qt::Key_Enter:
kc.keycode = kVK_Return;
break;
case Qt::Key_Up:
kc.keycode = kVK_UpArrow;
break;
case Qt::Key_Down:
kc.keycode = kVK_DownArrow;
break;
case Qt::Key_Left:
kc.keycode = kVK_LeftArrow;
break;
case Qt::Key_Right:
kc.keycode = kVK_RightArrow;
break;
case Qt::Key_Insert:
kc.keycode = kVK_Help;
break;
case Qt::Key_Delete:
kc.keycode = kVK_ForwardDelete;
break;
case Qt::Key_Home:
kc.keycode = kVK_Home;
break;
case Qt::Key_End:
kc.keycode = kVK_End;
break;
case Qt::Key_PageUp:
kc.keycode = kVK_PageUp;
break;
case Qt::Key_PageDown:
kc.keycode = kVK_PageDown;
break;
case Qt::Key_Backspace:
kc.keycode = kVK_Delete;
break;
//case Qt::Key_Pause:
// return XK_Break;
case Qt::Key_CapsLock:
kc.keycode = kVK_CapsLock;
break;
case Qt::Key_Escape:
kc.keycode = kVK_Escape;
break;
case Qt::Key_Help:
kc.keycode = kVK_Help;
break;
case Qt::Key_NumLock:
kc.keycode = kVK_ANSI_KeypadClear;
break;
case Qt::Key_Print:
kc.keycode = kVK_F13;
break;
//case Qt::Key_ScrollLock:
// return XK_Scroll_Lock;
default:
if (key >= Qt::Key_F1 && key <= Qt::Key_F16)
kc.keycode = kVK_F1 + key - Qt::Key_F1;
else
kc.keycode = NoSymbol;
}
return kc;
}
// Private
QStringList AutoTypePlatformMac::getTargetWindowInfo(pid_t *pidPtr,
WId *windowNumberPtr)
{
QStringList titles;
const int maxWindowNameSize = 512;
char windowName[maxWindowNameSize];
onlySendKeycodes = false;
CFArrayRef windowInfo = CGWindowListCopyWindowInfo(
kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
0);
CFIndex windowCount = CFArrayGetCount(windowInfo);
for (CFIndex i = 0; i < windowCount; i++) {
CFDictionaryRef window = static_cast<CFDictionaryRef>
(CFArrayGetValueAtIndex(windowInfo, i));
// only want windows in layer 0
CFNumberRef windowLayerRef = static_cast<CFNumberRef>
(CFDictionaryGetValue(window, kCGWindowLayer));
int windowLayer = -1;
CFNumberGetValue(windowLayerRef, kCFNumberIntType, &windowLayer);
if (0 != windowLayer) continue;
// get the pid owning this window
CFNumberRef pidRef = static_cast<CFNumberRef>
(CFDictionaryGetValue(window, kCGWindowOwnerPID));
pid_t pid = -1;
CFNumberGetValue(pidRef, kCFNumberIntType, &pid);
// skip KeePassX windows
if (getKeepassxPID() == pid) continue;
// get window name; continue if no name
CFStringRef windowNameRef = static_cast<CFStringRef>
(CFDictionaryGetValue(window, kCGWindowName));
if (!windowNameRef) continue;
windowName[0] = 0;
if (!CFStringGetCString(windowNameRef, windowName,
maxWindowNameSize, kCFStringEncodingUTF8) ||
(0 == windowName[0])) continue;
if (NULL != pidPtr)
*pidPtr = pid;
if (NULL != windowNumberPtr) {
CGWindowID wid;
CFNumberRef windowNumberRef = static_cast<CFNumberRef>
(CFDictionaryGetValue(window, kCGWindowNumber));
CFNumberGetValue(windowNumberRef, kCGWindowIDCFNumberType, &wid);
*windowNumberPtr = wid;
return titles;
}
titles.append(QString(windowName));
}
return titles;
}
uint AutoTypePlatformMac::qtToNativeModifiers(Qt::KeyboardModifiers modifiers)
{
uint nativeModifiers = 0;
if (modifiers & Qt::ShiftModifier) {
nativeModifiers |= shiftKey;
}
if (modifiers & Qt::ControlModifier) {
nativeModifiers |= controlKey;
}
if (modifiers & Qt::AltModifier) {
nativeModifiers |= optionKey;
}
if (modifiers & Qt::MetaModifier) {
nativeModifiers |= cmdKey;
}
return nativeModifiers;
}
void AutoTypePlatformMac::flushUnicode()
{
if (0 == unicodePtr) return;
CGEventKeyboardSetUnicodeString(unicodeEvent, unicodePtr, unicodeBuffer);
keyDownUp(unicodeEvent);
unicodePtr = 0;
sleepKeyStrokeDelay();
}
void AutoTypePlatformMac::keyDownUp(CGEventRef theEvent)
{
// posting Key Down/Up events also annoyingly sets mouse location so mus
// current mouse location and set it in the event
CGEventRef eventLocation = CGEventCreate(NULL);
CGEventSetLocation(theEvent, CGEventGetLocation(eventLocation));
CFRelease(eventLocation);
CGEventSetType(theEvent, kCGEventKeyDown);
CGEventPost(kCGHIDEventTap, theEvent);
CGEventSetType(theEvent, kCGEventKeyUp);
CGEventPost(kCGHIDEventTap, theEvent);
}
void AutoTypePlatformMac::sleepTime(int msec)
{
if (msec == 0) return;
timespec timeOut, remains;
timeOut.tv_sec = msec/1000;
timeOut.tv_nsec = (msec%1000)*1000000;
nanosleep(&timeOut, &remains);
}
OSStatus AutoTypePlatformMac::hotKeyHandler(EventHandlerCallRef, EventRef,
void *userData)
{
// ignore nextHandler - should not be called
if ((inHotKeyEvent) ||
AutoTypePlatformMac::isFrontProcess(AutoTypePlatformMac::getKeepassxPID2())) return noErr;
AutoTypePlatformMac::inHotKeyEvent = true;
Q_EMIT static_cast<AutoTypePlatformMac*>(userData)->globalShortcutTriggered();
AutoTypePlatformMac::inHotKeyEvent = false;
return noErr;
}
pid_t AutoTypePlatformMac::getKeepassxPID() {
if (0 == keepassxPID) {
ProcessSerialNumber processSerialNumber;
GetCurrentProcess(&processSerialNumber);
GetProcessPID(&processSerialNumber, &keepassxPID);
}
return keepassxPID;
}
Boolean AutoTypePlatformMac::isFrontProcess(pid_t pid)
{
Boolean result;
ProcessSerialNumber pidPSN;
ProcessSerialNumber frontPSN;
OSStatus status = GetProcessForPID(pid, &pidPSN);
if (noErr != status) {
qWarning("AutoTypePlatformMac::isFrontProcess: GetProcessForPID "
"error for pid %d: %d", pid, status);
return false;
}
GetFrontProcess(&frontPSN);
SameProcess(&pidPSN, &frontPSN, &result);
return result;
}
void AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap()
{
unicodeToKeycodeWithModsMap.clear();
TISInputSourceRef inputSourceRef = TISCopyCurrentKeyboardInputSource();
if (NULL == inputSourceRef) {
qWarning("AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap: "
"inputSourceRef is NULL");
return;
}
CFDataRef unicodeKeyLayoutDataRef = static_cast<CFDataRef>
(TISGetInputSourceProperty(inputSourceRef,
kTISPropertyUnicodeKeyLayoutData));
if (NULL == unicodeKeyLayoutDataRef) {
qWarning("AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap: "
"unicodeKeyLayoutDataRef is NULL");
return;
}
const UCKeyboardLayout *unicodeKeyLayoutDataPtr =
reinterpret_cast<const UCKeyboardLayout*>
(CFDataGetBytePtr(unicodeKeyLayoutDataRef));
UInt32 deadKeyState;
UniChar unicodeString[8];
UniCharCount len;
for (int m = 0; m < 16; m++) {
uint mods = orderedModifiers[m];
for (uint keycode = 0; keycode < 0x80; keycode++) {
deadKeyState = 0;
len = 0;
OSStatus status = UCKeyTranslate(unicodeKeyLayoutDataPtr, keycode,
kUCKeyActionDown, mods, LMGetKbdType(),
kUCKeyTranslateNoDeadKeysMask, &deadKeyState,
sizeof(unicodeString), &len, unicodeString);
if (noErr != status) {
qWarning("AutoTypePlatformMac::initUnicodeToKeycodeWithModsMap: "
"UCKeyTranslate error: %d keycode 0x%02X modifiers 0x%02X",
status, keycode, mods);
continue;
}
// store if only one char and not already in store
if ((1 != len) ||
(0 < unicodeToKeycodeWithModsMap.count(unicodeString[0])))
continue;
KeycodeWithMods kc;
kc.keycode = keycode;
kc.mods = mods;
unicodeToKeycodeWithModsMap[unicodeString[0]] = kc;
}
}
}
void AutoTypePlatformMac::processToFront(pid_t pid)
{
OSStatus status;
ProcessSerialNumber processSerialNumber;
status = GetProcessForPID(pid, &processSerialNumber);
if (noErr != status) {
qWarning("AutoTypePlatformMac::processToFront: GetProcessForPID "
"error for pid %d: %d", pid, status);
return;
}
status = SetFrontProcessWithOptions(&processSerialNumber,
kSetFrontProcessFrontWindowOnly);
if (noErr != status) {
qWarning("AutoTypePlatformMac::processToFront: "
"SetFrontProcessWithOptions for pid %d: %d", pid, status);
return;
}
}
KeycodeWithMods AutoTypePlatformMac::keysymToKeycodeWithMods2(KeySym keysym)
{
return 0 == unicodeToKeycodeWithModsMap.count(keysym)
? NoKeycodeWithMods : unicodeToKeycodeWithModsMap[keysym];
}
pid_t AutoTypePlatformMac::getKeepassxPID2()
{
if (0 == keepassxPID2) {
ProcessSerialNumber processSerialNumber;
GetCurrentProcess(&processSerialNumber);
GetProcessPID(&processSerialNumber, &keepassxPID2);
}
return keepassxPID2;
}
AutoTypeExecturorMac::AutoTypeExecturorMac(AutoTypePlatformMac* platform)
: m_platform(platform)
{
}
void AutoTypeExecturorMac::execChar(AutoTypeChar* action)
{
m_platform->sendUnicode(action->character.unicode());
}
void AutoTypeExecturorMac::execKey(AutoTypeKey* action)
{
m_platform->sendKeycode(m_platform->keyToKeycodeWithMods(action->key));
}
Q_EXPORT_PLUGIN2(keepassx-autotype-mac, AutoTypePlatformMac)

View File

@ -0,0 +1,107 @@
/*
* Copyright (C) 2009-2010 Jeff Gibbons
* Copyright (C) 2005-2008 Tarek Saidi <tarek.saidi@arcor.de>
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
*
* 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)
* version 3 of the License.
*
* 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/>.
*/
#ifndef KEEPASSX_AUTOTYPEMAC_H
#define KEEPASSX_AUTOTYPEMAC_H
#include <QtCore/QtPlugin>
#include <Carbon/Carbon.h>
#include "autotype/AutoTypePlatformPlugin.h"
#include "autotype/AutoTypeAction.h"
#include "core/Global.h"
typedef quint32 KeySym;
#define NoSymbol static_cast<KeySym>(0)
#define NoKeycode static_cast<uint16>(-1)
struct KeycodeWithMods {
uint16 keycode;
uint16 mods;
};
class AutoTypePlatformMac : public QObject, public AutoTypePlatformInterface
{
Q_OBJECT
Q_INTERFACES(AutoTypePlatformInterface)
public:
AutoTypePlatformMac();
QStringList windowTitles();
WId activeWindow();
QString activeWindowTitle();
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers);
int platformEventFilter(void* event);
int initialTimeout();
AutoTypeExecutor* createExecutor();
void sendUnicode(KeySym keysym);
void sendKeycode(KeycodeWithMods keycodeWithMods);
KeycodeWithMods keyToKeycodeWithMods(Qt::Key key);
Q_SIGNALS:
void globalShortcutTriggered();
private:
QStringList getTargetWindowInfo(pid_t *pidPtr, WId *windowNumberPtr);
uint qtToNativeModifiers(Qt::KeyboardModifiers modifiers);
void flushUnicode();
void keyDownUp(CGEventRef theEvent);
void sleepTime(int msec);
inline void sleepKeyStrokeDelay(){ sleepTime(5); };
static OSStatus hotKeyHandler(EventHandlerCallRef,
EventRef, void *userData);
pid_t getKeepassxPID();
bool first;
bool onlySendKeycodes;
bool inGlobalAutoType;
int globalKey;
uint globalMod;
pid_t keepassxPID;
EventHotKeyRef hotKeyRef;
EventHotKeyID hotKeyID;
static bool inHotKeyEvent;
static CGEventRef keyEvent;
private:
static void initUnicodeToKeycodeWithModsMap();
static void processToFront(pid_t pid);
static KeycodeWithMods keysymToKeycodeWithMods2(KeySym keysym);
static Boolean isFrontProcess(pid_t pid);
static pid_t getKeepassxPID2();
};
class AutoTypeExecturorMac : public AutoTypeExecutor
{
public:
explicit AutoTypeExecturorMac(AutoTypePlatformMac* platform);
void execChar(AutoTypeChar* action);
void execKey(AutoTypeKey* action);
private:
AutoTypePlatformMac* const m_platform;
};
#endif // KEEPASSX_AUTOTYPEMAC_H

View File

@ -0,0 +1,18 @@
set(autotype_mac_SOURCES
AutoTypeMac.cpp
)
set(autotype_mac_MOC
AutoTypeMac.h
)
qt4_wrap_cpp(autotype_mac_SOURCES ${autotype_mac_MOC})
add_library(keepassx-autotype-mac MODULE ${autotype_mac_SOURCES})
if(APPLE)
set_target_properties(keepassx-autotype-mac PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit -framework Carbon")
endif()
target_link_libraries(keepassx-autotype-mac testautotype ${QT_QTCORE_LIBRARY} ${QT_QTGUI_LIBRARY})
install(TARGETS keepassx-autotype-mac
BUNDLE DESTINATION . COMPONENT Runtime
LIBRARY DESTINATION ${PLUGIN_INSTALL_DIR} COMPONENT Runtime)

View File

@ -95,6 +95,7 @@ void Config::init(const QString& fileName)
m_defaults.insert("ShowToolbar", true);
m_defaults.insert("MinimizeOnCopy", false);
m_defaults.insert("UseGroupIconOnEntryCreation", 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,22 @@ QString PasswordGenerator::generatePassword() const
return password;
}
int PasswordGenerator::getbits() const
{
QVector<PasswordGroup> groups = passwordGroups();
int bits = 0;
QVector<QChar> passwordChars;
Q_FOREACH (const PasswordGroup& group, groups) {
bits += group.size();
}
bits *= m_length;
return bits;
}
bool PasswordGenerator::isValid() const
{
if (m_classes == 0) {

View File

@ -55,6 +55,7 @@ public:
bool isValid() const;
QString generatePassword() const;
int getbits() const;
private:
QVector<PasswordGroup> passwordGroups() const;

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

@ -22,6 +22,7 @@
#include <QScopedPointer>
#include "core/Global.h"
#include "crypto/SymmetricCipher.h"
#include "crypto/SymmetricCipherBackend.h"
class SymmetricCipher
@ -47,31 +48,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,121 @@ void DatabaseTabWidget::importKeePass1Database()
dbStruct.dbWidget->switchToImportKeepass1(fileName);
}
void DatabaseTabWidget::fileChanged(const QString &fileName)
{
const bool wasEmpty = m_changedFiles.isEmpty();
m_changedFiles.insert(fileName);
bool found = false;
Q_FOREACH (QString f, m_fileWatcher->files()) {
if (f == fileName) {
found = true;
break;
}
}
if (!found) m_fileWatcher->addPath(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
|| mode == DatabaseWidget::OpenMode))
|| (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);
@ -189,7 +305,8 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
if (dbName.right(1) == "*") {
dbName.chop(1);
}
if (dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode && db->hasKey()) {
if ((dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode ||
dbStruct.dbWidget->currentMode() == DatabaseWidget::OpenMode) && db->hasKey()) {
QMessageBox::StandardButton result =
MessageBox::question(
this, tr("Close?"),
@ -231,6 +348,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
int index = databaseIndex(db);
m_fileWatcher->removePath(dbStruct.filePath);
removeTab(index);
toggleTabbar();
m_dbList.remove(db);
@ -244,6 +362,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 +384,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 +411,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 +427,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 +609,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()));
@ -510,7 +641,9 @@ bool DatabaseTabWidget::hasLockableDatabases()
i.next();
DatabaseWidget::Mode mode = i.value().dbWidget->currentMode();
if ((mode == DatabaseWidget::ViewMode || mode == DatabaseWidget::EditMode)
if ((mode == DatabaseWidget::ViewMode ||
mode == DatabaseWidget::EditMode ||
mode == DatabaseWidget::OpenMode)
&& i.value().dbWidget->dbHasKey()) {
return true;
}
@ -526,7 +659,9 @@ void DatabaseTabWidget::lockDatabases()
i.next();
DatabaseWidget::Mode mode = i.value().dbWidget->currentMode();
if ((mode == DatabaseWidget::ViewMode || mode == DatabaseWidget::EditMode)
if ((mode == DatabaseWidget::ViewMode ||
mode == DatabaseWidget::EditMode ||
mode == DatabaseWidget::OpenMode)
&& i.value().dbWidget->dbHasKey()) {
i.value().dbWidget->lock();
updateTabName(i.key());
@ -552,7 +687,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

@ -52,6 +52,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);
@ -91,8 +93,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);
@ -152,11 +155,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()));
@ -178,6 +177,9 @@ DatabaseWidget::Mode DatabaseWidget::currentMode()
else if (currentWidget() == m_unlockDatabaseWidget) {
return DatabaseWidget::LockedMode;
}
else if (currentWidget() == m_databaseOpenWidget) {
return DatabaseWidget::OpenMode;
}
else {
return DatabaseWidget::EditMode;
}
@ -658,6 +660,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);
@ -665,84 +674,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);
}
@ -757,6 +799,9 @@ void DatabaseWidget::startSearchTimer()
void DatabaseWidget::startSearch()
{
if (!isInSearchMode())
return;
if (!m_searchTimer->isActive()) {
m_searchTimer->stop();
}
@ -785,11 +830,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;
@ -54,6 +55,7 @@ public:
None,
ViewMode,
EditMode,
OpenMode,
LockedMode
};
@ -64,13 +66,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 +111,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 +137,6 @@ private Q_SLOTS:
void search();
void startSearch();
void startSearchTimer();
void showSearch();
void closeSearch();
private:
@ -152,6 +164,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

@ -38,6 +38,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
, m_database(Q_NULLPTR)
, m_defaultIconModel(new DefaultIconModel(this))
, m_customIconModel(new CustomIconModel(this))
, m_networkAccessMngr(new QNetworkAccessManager(this))
{
m_ui->setupUi(this);
@ -54,6 +55,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()
@ -90,13 +96,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());
@ -120,6 +127,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,43 @@
#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() {
//delete m_service;
}
virtual QString name() {
return QObject::tr("Http");
}
virtual QWidget * createWidget() {
OptionDialog * dlg = new OptionDialog();
QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_service, SLOT(removeSharedEncryptionKeys()));
QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_service, 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:
Service *m_service;
};
const QString MainWindow::BaseWindowTitle = "KeePassX";
MainWindow::MainWindow()
@ -41,14 +79,20 @@ MainWindow::MainWindow()
m_ui->setupUi(this);
restoreGeometry(config()->get("window/Geometry").toByteArray());
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);
@ -80,13 +124,19 @@ 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->searchField->setContentsMargins(5,0,5,0);
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);
m_ui->actionEntryClone->setShortcut(Qt::CTRL + Qt::Key_K);
m_ui->actionEntryCopyUsername->setShortcut(Qt::CTRL + Qt::Key_B);
m_ui->actionEntryCopyPassword->setShortcut(Qt::CTRL + Qt::Key_C);
m_ui->actionEntryCopyURL->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_U);
setShortcut(m_ui->actionEntryAutoType, QKeySequence::Paste, Qt::CTRL + Qt::Key_V);
m_ui->actionEntryOpenUrl->setShortcut(Qt::CTRL + Qt::Key_U);
@ -120,8 +170,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()),
@ -201,8 +249,25 @@ 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->mySpacer);
m_ui->toolBar->addWidget(m_ui->searchField);
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()
@ -255,11 +320,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);
@ -295,9 +389,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);
@ -306,6 +398,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
}
case DatabaseWidget::EditMode:
case DatabaseWidget::LockedMode:
case DatabaseWidget::OpenMode:
Q_FOREACH (QAction* action, m_ui->menuEntries->actions()) {
action->setEnabled(false);
}
@ -315,8 +408,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);
@ -337,8 +429,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);
@ -475,6 +566,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

@ -70,7 +70,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 +148,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 +186,17 @@
<addaction name="actionEntryCopyPassword"/>
<addaction name="separator"/>
<addaction name="actionLockDatabases"/>
<addaction name="actionSearch"/>
<widget class="QWidget" name="mySpacer">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
<widget class="QWidget" name="searchPanel">
<widget class="QSearchField" name="searchField" native="true"/>
</widget>
</widget>
<action name="actionQuit">
<property name="text">
@ -303,17 +322,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 +397,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 +465,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>16</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,78 +24,14 @@
</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>
<tabstop>caseSensitiveCheckBox</tabstop>
<tabstop>searchCurrentRadioButton</tabstop>
<tabstop>searchRootRadioButton</tabstop>
</tabstops>
<resources/>
<connections/>

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())
@ -57,6 +78,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());
@ -67,6 +96,7 @@ void SettingsWidget::loadSettings()
m_generalUi->autoSaveOnExitCheckBox->setChecked(config()->get("AutoSaveOnExit").toBool());
m_generalUi->minimizeOnCopyCheckBox->setChecked(config()->get("MinimizeOnCopy").toBool());
m_generalUi->useGroupIconOnEntryCreationCheckBox->setChecked(config()->get("UseGroupIconOnEntryCreation").toBool());
m_generalUi->reloadBehavior->setCurrentIndex(config()->get("ReloadBehavior").toInt());
if (autoType()->isAvailable()) {
m_globalAutoTypeKey = static_cast<Qt::Key>(config()->get("GlobalAutoTypeKey").toInt());
@ -85,7 +115,8 @@ void SettingsWidget::loadSettings()
m_secUi->passwordCleartextCheckBox->setChecked(config()->get("security/passwordscleartext").toBool());
m_secUi->autoTypeAskCheckBox->setChecked(config()->get("security/autotypeask").toBool());
Q_FOREACH (const ExtraPage& page, m_extraPages)
page.loadSettings();
setCurrentRow(0);
}
@ -102,6 +133,7 @@ void SettingsWidget::saveSettings()
config()->set("MinimizeOnCopy", m_generalUi->minimizeOnCopyCheckBox->isChecked());
config()->set("UseGroupIconOnEntryCreation",
m_generalUi->useGroupIconOnEntryCreationCheckBox->isChecked());
config()->set("ReloadBehavior", m_generalUi->reloadBehavior->currentIndex());
if (autoType()->isAvailable()) {
config()->set("GlobalAutoTypeKey", m_generalUi->autoTypeShortcutWidget->key());
config()->set("GlobalAutoTypeModifiers",
@ -116,6 +148,8 @@ void SettingsWidget::saveSettings()
config()->set("security/passwordscleartext", m_secUi->passwordCleartextCheckBox->isChecked());
config()->set("security/autotypeask", m_secUi->autoTypeAskCheckBox->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

@ -41,7 +41,7 @@ Entry* EntryModel::entryFromIndex(const QModelIndex& index) const
QModelIndex EntryModel::indexFromEntry(Entry* entry) const
{
int row = m_entries.indexOf(entry);
Q_ASSERT(row != -1);
//Q_ASSERT(row != -1);
return index(row, 1);
}

2
src/gui/qocoa/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
cmake/
qmake/

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/) and are licensed under the [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

13
src/gui/qocoa/main.cpp Normal file
View File

@ -0,0 +1,13 @@
#include <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 <QWidget>
#include <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 <QToolBar>
#include <QToolButton>
#include <QPushButton>
#include <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();
}

63
src/gui/qocoa/qocoa_mac.h Normal file
View File

@ -0,0 +1,63 @@
/*
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 <AppKit/NSImage.h>
#include <Foundation/NSString.h>
#include <QString>
#include <QVBoxLayout>
#include <QMacCocoaViewContainer>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <qmacfunctions.h>
#endif
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)
{
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
CGImageRef cgImage = pixmap.toMacCGImageRef();
#else
CGImageRef cgImage = QtMac::toCGImageRef(pixmap);
#endif
return [[NSImage alloc] initWithCGImage:cgImage size:NSZeroSize];
}
static inline void setupLayout(NSView *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 <QWidget>
#include <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,72 @@
/*
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 <QVBoxLayout>
#include <QMovie>
#include <QLabel>
class QProgressIndicatorSpinningPrivate : public QObject
{
public:
QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning,
QMovie *movie)
: QObject(qProgressIndicatorSpinning), movie(movie) {}
QPointer<QMovie> movie;
};
QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent,
Thickness thickness)
: QWidget(parent)
{
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 <QWidget>
#include <QPointer>
#include <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,262 @@
/*
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 <QApplication>
#include <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 NO;
}
else if (keyCode == KEYCODE_C)
{
QClipboard* clipboard = QApplication::clipboard();
clipboard->setText(toQString([self stringValue]));
return NO;
}
else if (keyCode == KEYCODE_V)
{
QClipboard* clipboard = QApplication::clipboard();
[self setStringValue:fromQString(clipboard->text())];
return NO;
}
else if (keyCode == KEYCODE_X)
{
QClipboard* clipboard = QApplication::clipboard();
clipboard->setText(toQString([self stringValue]));
[self setStringValue:@""];
return NO;
}
}
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;
#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
NSMenu *nsMenu = menu->macMenu();
#else
NSMenu *nsMenu = menu->toNSMenu();
#endif
[[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,256 @@
/*
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 <QLineEdit>
#include <QVBoxLayout>
#include <QToolButton>
#include <QStyle>
#include <QApplication>
#include <QDesktopWidget>
#include <QDir>
#include <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;
};
QSearchField::QSearchField(QWidget *parent) : QWidget(parent)
{
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/src/parser.h"
#include "qjson/src/qobjecthelper.h"
#include "qjson/src/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

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
*
* 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)
* version 3 of the License.
*
* 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/>.
*/
#include "HttpPasswordGeneratorWidget.h"
#include "ui_HttpPasswordGeneratorWidget.h"
#include <QLineEdit>
#include "core/Config.h"
#include "core/PasswordGenerator.h"
#include "core/FilePath.h"
HttpPasswordGeneratorWidget::HttpPasswordGeneratorWidget(QWidget* parent)
: QWidget(parent)
, m_updatingSpinBox(false)
, m_generator(new PasswordGenerator())
, m_ui(new Ui::HttpPasswordGeneratorWidget())
{
m_ui->setupUi(this);
connect(m_ui->buttonApply, SIGNAL(clicked()), SLOT(saveSettings()));
connect(m_ui->sliderLength, SIGNAL(valueChanged(int)), SLOT(sliderMoved()));
connect(m_ui->spinBoxLength, SIGNAL(valueChanged(int)), SLOT(spinBoxChanged()));
connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator()));
m_ui->buttonApply->setEnabled(true);
loadSettings();
reset();
}
HttpPasswordGeneratorWidget::~HttpPasswordGeneratorWidget()
{
}
void HttpPasswordGeneratorWidget::loadSettings()
{
m_ui->checkBoxLower->setChecked(config()->get("Http/generator/LowerCase", true).toBool());
m_ui->checkBoxUpper->setChecked(config()->get("Http/generator/UpperCase", true).toBool());
m_ui->checkBoxNumbers->setChecked(config()->get("Http/generator/Numbers", true).toBool());
m_ui->checkBoxSpecialChars->setChecked(config()->get("Http/generator/SpecialChars", false).toBool());
m_ui->checkBoxExcludeAlike->setChecked(config()->get("Http/generator/ExcludeAlike", true).toBool());
m_ui->checkBoxEnsureEvery->setChecked(config()->get("Http/generator/EnsureEvery", true).toBool());
m_ui->spinBoxLength->setValue(config()->get("Http/generator/Length", 16).toInt());
}
void HttpPasswordGeneratorWidget::saveSettings()
{
config()->set("Http/generator/LowerCase", m_ui->checkBoxLower->isChecked());
config()->set("Http/generator/UpperCase", m_ui->checkBoxUpper->isChecked());
config()->set("Http/generator/Numbers", m_ui->checkBoxNumbers->isChecked());
config()->set("Http/generator/SpecialChars", m_ui->checkBoxSpecialChars->isChecked());
config()->set("Http/generator/ExcludeAlike", m_ui->checkBoxExcludeAlike->isChecked());
config()->set("Http/generator/EnsureEvery", m_ui->checkBoxEnsureEvery->isChecked());
config()->set("Http/generator/Length", m_ui->spinBoxLength->value());
}
void HttpPasswordGeneratorWidget::reset()
{
updateGenerator();
}
void HttpPasswordGeneratorWidget::sliderMoved()
{
if (m_updatingSpinBox) {
return;
}
m_ui->spinBoxLength->setValue(m_ui->sliderLength->value());
updateGenerator();
}
void HttpPasswordGeneratorWidget::spinBoxChanged()
{
// Interlock so that we don't update twice - this causes issues as the spinbox can go higher than slider
m_updatingSpinBox = true;
m_ui->sliderLength->setValue(m_ui->spinBoxLength->value());
m_updatingSpinBox = false;
updateGenerator();
}
PasswordGenerator::CharClasses HttpPasswordGeneratorWidget::charClasses()
{
PasswordGenerator::CharClasses classes;
if (m_ui->checkBoxLower->isChecked()) {
classes |= PasswordGenerator::LowerLetters;
}
if (m_ui->checkBoxUpper->isChecked()) {
classes |= PasswordGenerator::UpperLetters;
}
if (m_ui->checkBoxNumbers->isChecked()) {
classes |= PasswordGenerator::Numbers;
}
if (m_ui->checkBoxSpecialChars->isChecked()) {
classes |= PasswordGenerator::SpecialCharacters;
}
return classes;
}
PasswordGenerator::GeneratorFlags HttpPasswordGeneratorWidget::generatorFlags()
{
PasswordGenerator::GeneratorFlags flags;
if (m_ui->checkBoxExcludeAlike->isChecked()) {
flags |= PasswordGenerator::ExcludeLookAlike;
}
if (m_ui->checkBoxEnsureEvery->isChecked()) {
flags |= PasswordGenerator::CharFromEveryGroup;
}
return flags;
}
void HttpPasswordGeneratorWidget::updateGenerator()
{
m_generator->setLength(m_ui->spinBoxLength->value());
m_generator->setCharClasses(charClasses());
m_generator->setFlags(generatorFlags());
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (C) 2013 Felix Geyer <debfx@fobos.de>
*
* 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)
* version 3 of the License.
*
* 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/>.
*/
#ifndef KEEPASSX_PASSWORDGENERATORWIDGET_H
#define KEEPASSX_PASSWORDGENERATORWIDGET_H
#include <QWidget>
#include <QComboBox>
#include "core/Global.h"
#include "core/PasswordGenerator.h"
namespace Ui {
class HttpPasswordGeneratorWidget;
}
class PasswordGenerator;
class HttpPasswordGeneratorWidget : public QWidget
{
Q_OBJECT
public:
explicit HttpPasswordGeneratorWidget(QWidget* parent = Q_NULLPTR);
~HttpPasswordGeneratorWidget();
void loadSettings();
void reset();
private Q_SLOTS:
void saveSettings();
void sliderMoved();
void spinBoxChanged();
void updateGenerator();
private:
bool m_updatingSpinBox;
PasswordGenerator::CharClasses charClasses();
PasswordGenerator::GeneratorFlags generatorFlags();
const QScopedPointer<PasswordGenerator> m_generator;
const QScopedPointer<Ui::HttpPasswordGeneratorWidget> m_ui;
};
#endif // KEEPASSX_PASSWORDGENERATORWIDGET_H

View File

@ -0,0 +1,227 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>HttpPasswordGeneratorWidget</class>
<widget class="QWidget" name="HttpPasswordGeneratorWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>434</width>
<height>250</height>
</rect>
</property>
<property name="windowTitle">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="0">
<widget class="QLabel" name="labelLength">
<property name="text">
<string>Length:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QSlider" name="sliderLength">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>64</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickPosition">
<enum>QSlider::TicksBelow</enum>
</property>
<property name="tickInterval">
<number>8</number>
</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>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Character Types</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QToolButton" name="checkBoxUpper">
<property name="toolTip">
<string>Upper Case Letters</string>
</property>
<property name="text">
<string>A-Z</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">optionButtons</string>
</attribute>
</widget>
</item>
<item>
<widget class="QToolButton" name="checkBoxLower">
<property name="toolTip">
<string>Lower Case Letters</string>
</property>
<property name="text">
<string>a-z</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">optionButtons</string>
</attribute>
</widget>
</item>
<item>
<widget class="QToolButton" name="checkBoxNumbers">
<property name="toolTip">
<string>Numbers</string>
</property>
<property name="text">
<string>0-9</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">optionButtons</string>
</attribute>
</widget>
</item>
<item>
<widget class="QToolButton" name="checkBoxSpecialChars">
<property name="toolTip">
<string>Special Characters</string>
</property>
<property name="text">
<string>/*_&amp; ...</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<attribute name="buttonGroup">
<string notr="true">optionButtons</string>
</attribute>
</widget>
</item>
<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>
</layout>
</item>
<item>
<widget class="QCheckBox" name="checkBoxExcludeAlike">
<property name="text">
<string>Exclude look-alike characters</string>
</property>
<attribute name="buttonGroup">
<string notr="true">optionButtons</string>
</attribute>
</widget>
</item>
<item>
<widget class="QCheckBox" name="checkBoxEnsureEvery">
<property name="text">
<string>Ensure that the password contains characters from every group</string>
</property>
<attribute name="buttonGroup">
<string notr="true">optionButtons</string>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<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>
<item>
<widget class="QPushButton" name="buttonApply">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Accept</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PasswordComboBox</class>
<extends>QComboBox</extends>
<header location="global">gui/PasswordComboBox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>sliderLength</tabstop>
<tabstop>spinBoxLength</tabstop>
<tabstop>checkBoxUpper</tabstop>
<tabstop>checkBoxLower</tabstop>
<tabstop>checkBoxNumbers</tabstop>
<tabstop>checkBoxSpecialChars</tabstop>
<tabstop>checkBoxExcludeAlike</tabstop>
<tabstop>checkBoxEnsureEvery</tabstop>
<tabstop>buttonApply</tabstop>
</tabstops>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="optionButtons">
<property name="exclusive">
<bool>false</bool>
</property>
</buttongroup>
</buttongroups>
</ui>

236
src/http/HttpSettings.cpp Normal file
View File

@ -0,0 +1,236 @@
/**
***************************************************************************
* @file HttpSettings.cpp
*
* @brief
*
* Copyright (C) 2013
*
* @author Francois Ferrand
* @date 4/2013
***************************************************************************
*/
#include "HttpSettings.h"
#include "core/Config.h"
PasswordGenerator HttpSettings::m_generator;
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/generator/Numbers", true).toBool();
}
void HttpSettings::setPasswordUseNumbers(bool useNumbers)
{
config()->set("Http/generator/Numbers", useNumbers);
}
bool HttpSettings::passwordUseLowercase()
{
return config()->get("Http/generator/LowerCase", true).toBool();
}
void HttpSettings::setPasswordUseLowercase(bool useLowercase)
{
config()->set("Http/generator/LowerCase", useLowercase);
}
bool HttpSettings::passwordUseUppercase()
{
return config()->get("Http/generator/UpperCase", true).toBool();
}
void HttpSettings::setPasswordUseUppercase(bool useUppercase)
{
config()->set("Http/generator/UpperCase", useUppercase);
}
bool HttpSettings::passwordUseSpecial()
{
return config()->get("Http/generator/SpecialChars", false).toBool();
}
void HttpSettings::setPasswordUseSpecial(bool useSpecial)
{
config()->set("Http/generator/SpecialChars", useSpecial);
}
bool HttpSettings::passwordEveryGroup()
{
return config()->get("Http/generator/EnsureEvery", true).toBool();
}
void HttpSettings::setPasswordEveryGroup(bool everyGroup)
{
config()->get("Http/generator/EnsureEvery", everyGroup);
}
bool HttpSettings::passwordExcludeAlike()
{
return config()->get("Http/generator/ExcludeAlike", true).toBool();
}
void HttpSettings::setPasswordExcludeAlike(bool excludeAlike)
{
config()->set("Http/generator/ExcludeAlike", excludeAlike);
}
int HttpSettings::passwordLength()
{
return config()->get("Http/generator/Length", 20).toInt();
}
void HttpSettings::setPasswordLength(int length)
{
config()->set("Http/generator/Length", length);
m_generator.setLength(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;
}
QString HttpSettings::generatePassword()
{
m_generator.setLength(passwordLength());
m_generator.setCharClasses(passwordCharClasses());
m_generator.setFlags(passwordGeneratorFlags());
return m_generator.generatePassword();
}
int HttpSettings::getbits()
{
return m_generator.getbits();
}

69
src/http/HttpSettings.h Normal file
View File

@ -0,0 +1,69 @@
/**
***************************************************************************
* @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();
static QString generatePassword();
static int getbits();
private:
static PasswordGenerator m_generator;
};
#endif // HTTPSETTINGS_H

85
src/http/OptionDialog.cpp Normal file
View File

@ -0,0 +1,85 @@
/**
***************************************************************************
* @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

237
src/http/OptionDialog.ui Normal file
View File

@ -0,0 +1,237 @@
<?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>
<widget class="HttpPasswordGeneratorWidget" name="passwordGenerator" native="true"/>
</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>
<customwidgets>
<customwidget>
<class>HttpPasswordGeneratorWidget</class>
<extends>QWidget</extends>
<header>http/HttpPasswordGeneratorWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PasswordEdit</class>
<extends>QLineEdit</extends>
<header>gui/PasswordEdit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

525
src/http/Protocol.cpp Normal file
View File

@ -0,0 +1,525 @@
/**
***************************************************************************
* @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/src/parser.h"
#include "qjson/src/qobjecthelper.h"
#include "qjson/src/serializer.h"
#include "crypto/Random.h"
#include "crypto/SymmetricCipher.h"
#include "crypto/SymmetricCipherGcrypt.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.8.3.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, SymmetricCipherGcrypt & 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, SymmetricCipherGcrypt & cipher)
{
return QString::fromUtf8(decrypt2(decode64(data), cipher));
}
static QByteArray encrypt2(const QByteArray & data, SymmetricCipherGcrypt & 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, SymmetricCipherGcrypt & cipher)
{
return encode64(encrypt2(data.toUtf8(), cipher));
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Request
////////////////////////////////////////////////////////////////////////////////////////////////////
Request::Request():
m_requestType(INVALID),
m_cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt)
{
}
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.setKey(decode64(key));
m_cipher.setIv(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),
m_cipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt)
{
}
void Response::setVerifier(QString key)
{
Q_ASSERT(!m_cipher.isValid());
m_cipher.setKey(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/SymmetricCipherGcrypt.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 SymmetricCipherGcrypt 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;
mutable SymmetricCipherGcrypt m_cipher;
};
}/*namespace KeepassHttpProtocol*/
#endif // RESPONSE_H

327
src/http/Server.cpp Normal file
View File

@ -0,0 +1,327 @@
/**
***************************************************************************
* @file Server.cpp
*
* @brief
*
* Copyright (C) 2013
*
* @author Francois Ferrand
* @date 4/2013
***************************************************************************
*/
#include "Server.h"
#include <microhttpd.h>
#include "Protocol.h"
#include "HttpSettings.h"
#include "crypto/Crypto.h"
#include <QtCore/QHash>
#include <QtCore/QCryptographicHash>
#include <QtGui/QMessageBox>
#include <QEventLoop>
using namespace KeepassHttpProtocol;
using namespace KeepassHttpProtocol;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Request
////////////////////////////////////////////////////////////////////////////////////////////////////
Server::Server(QObject *parent) :
QObject(parent),
m_started(false)
{
connect(this, SIGNAL(emitRequest(const QByteArray, QByteArray*)),
this, SLOT(handleRequest(const QByteArray, QByteArray*)));
connect(this, SIGNAL(emitOpenDatabase(bool*)),
this, SLOT(handleOpenDatabase(bool*)));
}
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);
}
int Server::send_response(struct MHD_Connection *connection, const char *page)
{
int ret;
struct MHD_Response *response;
response = MHD_create_response_from_buffer(
strlen(page), static_cast<void*>(const_cast<char*>(page)),
MHD_RESPMEM_PERSISTENT);
if (!response) return MHD_NO;
MHD_add_response_header (response, "Content-Type", "application/json");
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
MHD_destroy_response (response);
return ret;
}
int Server::send_unavailable(struct MHD_Connection *connection)
{
int ret;
struct MHD_Response *response;
response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
if (!response) return MHD_NO;
ret = MHD_queue_response (connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
MHD_destroy_response (response);
return ret;
}
void Server::generatePassword(const Request &r, Response *protocolResp)
{
QString key = getKey(r.id());
if (!r.CheckVerifier(key))
return;
QString password = generatePassword();
QString bits = QString::number(HttpSettings::getbits());
protocolResp->setSuccess();
protocolResp->setId(r.id());
protocolResp->setVerifier(key);
protocolResp->setEntries(QList<Entry>() << Entry("generate-password", bits, password, "generate-password"));
memset(password.data(), 0, password.length());
}
int Server::request_handler_wrapper(void *me, struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **con_cls)
{
Server *myself = static_cast<Server*>(me);
if (myself)
return myself->request_handler(connection, url, method, version,
upload_data, upload_data_size, con_cls);
else
return MHD_NO;
}
void Server::handleRequest(const QByteArray in, QByteArray *out)
{
*out = QByteArray();
Request r;
if (!r.fromJson(in))
return;
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 GENERATE_PASSWORD: generatePassword(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;
}
*out = protocolResp.toJson().toUtf8();
Q_EMIT donewrk();
}
void Server::handleOpenDatabase(bool *success)
{
*success = openDatabase();
Q_EMIT donewrk();
}
int Server::request_handler(struct MHD_Connection *connection,
const char *, const char *method, const char *,
const char *upload_data, size_t *upload_data_size, void **con_cls)
{
struct Server::connection_info_struct *con_info =
static_cast<struct Server::connection_info_struct*>(*con_cls);
if (!isDatabaseOpened()) {
bool success;
QEventLoop loop1;
loop1.connect(this, SIGNAL(donewrk()), SLOT(quit()));
Q_EMIT emitOpenDatabase(&success);
loop1.exec();
if (!success)
return send_unavailable(connection);
}
if (con_info == NULL) {
*con_cls = calloc(1, sizeof(*con_info));
return MHD_YES;
}
if (strcmp (method, MHD_HTTP_METHOD_POST) != 0)
return MHD_NO;
if (*upload_data_size == 0) {
if (con_info && con_info->response)
return send_response(connection, con_info->response);
else
return MHD_NO;
}
QString type = MHD_lookup_connection_value(connection,
MHD_HEADER_KIND, "Content-Type");
if (!type.contains("application/json", Qt::CaseInsensitive))
return MHD_NO;
// Now process the POST request
QByteArray post = QByteArray(upload_data, *upload_data_size);
QByteArray s;
QEventLoop loop;
loop.connect(this, SIGNAL(donewrk()), SLOT(quit()));
Q_EMIT emitRequest(post, &s);
loop.exec();
if (s.size() == 0)
return MHD_NO;
con_info->response = static_cast<char*>(calloc(1, s.size()+1));
memcpy(con_info->response, s.data(), s.size());
*upload_data_size = 0;
return MHD_YES;
}
void Server::request_completed(void *, struct MHD_Connection *,
void **con_cls, enum MHD_RequestTerminationCode)
{
struct Server::connection_info_struct *con_info =
static_cast<struct Server::connection_info_struct*>(*con_cls);
if (con_info == NULL)
return;
if (con_info->response) free(con_info->response);
free(con_info);
*con_cls = NULL;
}
void Server::start(void)
{
if (m_started) return;
static const int PORT = 19455;
daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL,
&this->request_handler_wrapper, this,
MHD_OPTION_NOTIFY_COMPLETED,
this->request_completed, NULL,
MHD_OPTION_END);
m_started = true;
}
void Server::stop(void)
{
if (!m_started)
return;
MHD_stop_daemon(daemon);
m_started = false;
}

93
src/http/Server.h Normal file
View File

@ -0,0 +1,93 @@
/**
***************************************************************************
* @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>
#include <microhttpd.h>
namespace KeepassHttpProtocol {
class Request;
class Response;
class Entry;
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(const QByteArray in, QByteArray *out);
void handleOpenDatabase(bool *success);
Q_SIGNALS:
void emitRequest(const QByteArray in, QByteArray *out);
void emitOpenDatabase(bool *success);
void donewrk();
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);
static int request_handler_wrapper(void *me,
struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **con_cls);
static void request_completed(void *, struct MHD_Connection *,
void **con_cls, enum MHD_RequestTerminationCode);
int request_handler(struct MHD_Connection *connection,
const char *, const char *method, const char *,
const char *upload_data, size_t *upload_data_size, void **con_cls);
int send_response(struct MHD_Connection *connection, const char *page);
int send_unavailable(struct MHD_Connection *connection);
bool m_started;
struct MHD_Daemon *daemon;
struct connection_info_struct {
char *response;
};
};
} /*namespace KeepassHttpProtocol*/
#endif // SERVER_H

573
src/http/Service.cpp Normal file
View File

@ -0,0 +1,573 @@
/**
***************************************************************************
* @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:
case DatabaseWidget::OpenMode:
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:
case DatabaseWidget::OpenMode:
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.\n"
"If you would like to allow it access "
"to your KeePassX database\n"
"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 &, const QString &url, const QString &, const QString &)
{
return searchEntries(url).count();
}
QList<KeepassHttpProtocol::Entry> Service::searchAllEntries(const QString &)
{
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 &, 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 &, 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()
{
return HttpSettings::generatePassword();
}
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

Some files were not shown because too many files have changed in this diff Show More