mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Merge branch 'master' of https://github.com/keepassx/keepassx
Use SymmetricCipherGcrypt directly in Protocol.cpp to make it work with the latest master revision.
This commit is contained in:
commit
65626f0da2
@ -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
17
README.md
Normal 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.
|
||||
|
9
cmake/FindLibMicroHTTPD.cmake
Normal file
9
cmake/FindLibMicroHTTPD.cmake
Normal 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)
|
@ -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)
|
||||
|
||||
|
@ -87,7 +87,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action)
|
||||
Tools::wait(action->delayMs);
|
||||
}
|
||||
|
||||
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
|
||||
void AutoTypeExecutor::execClearField(AutoTypeClearField*)
|
||||
{
|
||||
// TODO: implement
|
||||
}
|
||||
|
@ -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()
|
||||
|
553
src/autotype/mac/AutoTypeMac.cpp
Normal file
553
src/autotype/mac/AutoTypeMac.cpp
Normal 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)
|
107
src/autotype/mac/AutoTypeMac.h
Normal file
107
src/autotype/mac/AutoTypeMac.h
Normal 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
|
18
src/autotype/mac/CMakeLists.txt
Normal file
18
src/autotype/mac/CMakeLists.txt
Normal 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)
|
@ -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);
|
||||
|
@ -228,6 +228,11 @@ bool Database::verifyKey(const CompositeKey& key) const
|
||||
return (m_data.key.rawKey() == key.rawKey());
|
||||
}
|
||||
|
||||
CompositeKey Database::key() const
|
||||
{
|
||||
return m_data.key;
|
||||
}
|
||||
|
||||
void Database::createRecycleBin()
|
||||
{
|
||||
Group* recycleBin = Group::createRecycleBin();
|
||||
|
@ -99,6 +99,7 @@ public:
|
||||
void setKey(const CompositeKey& key);
|
||||
bool hasKey() const;
|
||||
bool verifyKey(const CompositeKey& key) const;
|
||||
CompositeKey key() const;
|
||||
void recycleEntry(Entry* entry);
|
||||
void recycleGroup(Group* group);
|
||||
void setEmitModified(bool value);
|
||||
|
@ -52,6 +52,11 @@ QString EntryAttributes::value(const QString& key) const
|
||||
return m_attributes.value(key);
|
||||
}
|
||||
|
||||
bool EntryAttributes::contains(const QString &key) const
|
||||
{
|
||||
return m_attributes.contains(key);
|
||||
}
|
||||
|
||||
bool EntryAttributes::isProtected(const QString& key) const
|
||||
{
|
||||
return m_protectedAttributes.contains(key);
|
||||
|
@ -34,6 +34,7 @@ public:
|
||||
QList<QString> keys() const;
|
||||
QList<QString> customKeys();
|
||||
QString value(const QString& key) const;
|
||||
bool contains(const QString& key) const;
|
||||
bool isProtected(const QString& key) const;
|
||||
void set(const QString& key, const QString& value, bool protect = false);
|
||||
void remove(const QString& key);
|
||||
|
@ -89,6 +89,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) {
|
||||
|
@ -55,6 +55,7 @@ public:
|
||||
bool isValid() const;
|
||||
|
||||
QString generatePassword() const;
|
||||
int getbits() const;
|
||||
|
||||
private:
|
||||
QVector<PasswordGroup> passwordGroups() const;
|
||||
|
@ -89,6 +89,12 @@ Uuid Uuid::fromBase64(const QString& str)
|
||||
return Uuid(data);
|
||||
}
|
||||
|
||||
Uuid Uuid::fromHex(const QString& str)
|
||||
{
|
||||
QByteArray data = QByteArray::fromHex(str.toAscii());
|
||||
return Uuid(data);
|
||||
}
|
||||
|
||||
uint qHash(const Uuid& key)
|
||||
{
|
||||
return qHash(key.toByteArray());
|
||||
|
@ -37,6 +37,7 @@ public:
|
||||
bool operator!=(const Uuid& other) const;
|
||||
static const int Length;
|
||||
static Uuid fromBase64(const QString& str);
|
||||
static Uuid fromHex(const QString& str);
|
||||
|
||||
private:
|
||||
QByteArray m_data;
|
||||
|
@ -63,4 +63,4 @@ void SymmetricCipher::reset()
|
||||
int SymmetricCipher::blockSize() const
|
||||
{
|
||||
return m_backend->blockSize();
|
||||
}
|
||||
}
|
@ -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)
|
||||
};
|
||||
|
@ -93,14 +93,26 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
||||
openDatabase();
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::enterKey(const CompositeKey& masterKey)
|
||||
{
|
||||
if (masterKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
openDatabase(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::openDatabase()
|
||||
{
|
||||
KeePass2Reader reader;
|
||||
CompositeKey masterKey = databaseKey();
|
||||
if (masterKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
openDatabase(masterKey);
|
||||
}
|
||||
|
||||
void DatabaseOpenWidget::openDatabase(const CompositeKey& masterKey)
|
||||
{
|
||||
KeePass2Reader reader;
|
||||
QFile file(m_filename);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
// TODO: error message
|
||||
|
@ -39,6 +39,7 @@ public:
|
||||
~DatabaseOpenWidget();
|
||||
void load(const QString& filename);
|
||||
void enterKey(const QString& pw, const QString& keyFile);
|
||||
void enterKey(const CompositeKey& masterKey);
|
||||
Database* database();
|
||||
|
||||
Q_SIGNALS:
|
||||
@ -49,6 +50,7 @@ protected:
|
||||
|
||||
protected Q_SLOTS:
|
||||
virtual void openDatabase();
|
||||
void openDatabase(const CompositeKey& masterKey);
|
||||
void reject();
|
||||
|
||||
private Q_SLOTS:
|
||||
|
@ -19,6 +19,9 @@
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QTabWidget>
|
||||
#include <QtCore/QFileSystemWatcher>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
@ -45,7 +48,8 @@ DatabaseManagerStruct::DatabaseManagerStruct()
|
||||
const int DatabaseTabWidget::LastDatabasesCount = 5;
|
||||
|
||||
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
: QTabWidget(parent)
|
||||
: QTabWidget(parent),
|
||||
m_fileWatcher(new QFileSystemWatcher(this))
|
||||
{
|
||||
DragTabBar* tabBar = new DragTabBar(this);
|
||||
tabBar->setDrawBase(false);
|
||||
@ -53,6 +57,7 @@ DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
|
||||
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabase(int)));
|
||||
connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType()));
|
||||
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString)));
|
||||
}
|
||||
|
||||
DatabaseTabWidget::~DatabaseTabWidget()
|
||||
@ -66,16 +71,7 @@ DatabaseTabWidget::~DatabaseTabWidget()
|
||||
|
||||
void DatabaseTabWidget::toggleTabbar()
|
||||
{
|
||||
if (count() > 1) {
|
||||
if (!tabBar()->isVisible()) {
|
||||
tabBar()->show();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tabBar()->isVisible()) {
|
||||
tabBar()->hide();
|
||||
}
|
||||
}
|
||||
tabBar()->setVisible(count() > 1);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::newDatabase()
|
||||
@ -101,7 +97,7 @@ void DatabaseTabWidget::openDatabase()
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
const QString& keyFile)
|
||||
const QString& keyFile, const CompositeKey& key, int index)
|
||||
{
|
||||
QFileInfo fileInfo(fileName);
|
||||
QString canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
@ -145,12 +141,17 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
dbStruct.canonicalFilePath = canonicalFilePath;
|
||||
dbStruct.fileName = fileInfo.fileName();
|
||||
dbStruct.lastModified = fileInfo.lastModified();
|
||||
|
||||
insertDatabase(db, dbStruct);
|
||||
insertDatabase(db, dbStruct, index);
|
||||
m_fileWatcher->addPath(dbStruct.filePath);
|
||||
|
||||
updateLastDatabases(dbStruct.filePath);
|
||||
updateRecentDatabases(dbStruct.filePath);
|
||||
|
||||
if (!pw.isNull() || !keyFile.isEmpty()) {
|
||||
if (!key.isEmpty()) {
|
||||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, key);
|
||||
}
|
||||
else if (!pw.isNull() || !keyFile.isEmpty()) {
|
||||
dbStruct.dbWidget->switchToOpenDatabase(dbStruct.filePath, pw, keyFile);
|
||||
}
|
||||
else {
|
||||
@ -177,6 +178,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());
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -19,6 +19,11 @@
|
||||
#define KEEPASSX_EDITWIDGETICONS_H
|
||||
|
||||
#include <QWidget>
|
||||
#include <QSet>
|
||||
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "core/Uuid.h"
|
||||
@ -48,9 +53,15 @@ public:
|
||||
~EditWidgetIcons();
|
||||
|
||||
IconStruct save();
|
||||
void load(Uuid currentUuid, Database* database, IconStruct iconStruct);
|
||||
void load(Uuid currentUuid, Database* database, IconStruct iconStruct, const QString &url = QString());
|
||||
|
||||
public Q_SLOTS:
|
||||
void setUrl(const QString &url);
|
||||
|
||||
private Q_SLOTS:
|
||||
void downloadFavicon();
|
||||
void abortFaviconDownload();
|
||||
void onRequestFinished(QNetworkReply *reply);
|
||||
void addCustomIcon();
|
||||
void removeCustomIcon();
|
||||
void updateWidgetsDefaultIcons(bool checked);
|
||||
@ -62,8 +73,11 @@ private:
|
||||
const QScopedPointer<Ui::EditWidgetIcons> m_ui;
|
||||
Database* m_database;
|
||||
Uuid m_currentUuid;
|
||||
QString m_url;
|
||||
DefaultIconModel* const m_defaultIconModel;
|
||||
CustomIconModel* const m_customIconModel;
|
||||
QNetworkAccessManager* const m_networkAccessMngr;
|
||||
QSet<QNetworkReply *> m_networkOperations;
|
||||
|
||||
Q_DISABLE_COPY(EditWidgetIcons)
|
||||
};
|
||||
|
@ -91,6 +91,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="faviconButton">
|
||||
<property name="text">
|
||||
<string>Download favicon</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QShortcut>
|
||||
#include <QtGui/QLineEdit>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
@ -33,6 +34,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()) {
|
||||
|
@ -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;
|
||||
|
@ -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 &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>&16x16</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolbarIconSize22">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&22x22</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionToolbarIconSize28">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2&8x28</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFindCaseSensitive">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Case Sensitive</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFindCurrentGroup">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Current Group</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFindRootGroup">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Root Group</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
@ -409,6 +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/>
|
||||
|
@ -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/>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -25,6 +25,15 @@ namespace Ui {
|
||||
class SettingsWidgetSecurity;
|
||||
}
|
||||
|
||||
class ISettingsPage {
|
||||
public:
|
||||
virtual ~ISettingsPage() {}
|
||||
virtual QString name() = 0;
|
||||
virtual QWidget * createWidget() = 0;
|
||||
virtual void loadSettings(QWidget * widget) = 0;
|
||||
virtual void saveSettings(QWidget * widget) = 0;
|
||||
};
|
||||
|
||||
class SettingsWidget : public EditWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -32,6 +41,7 @@ class SettingsWidget : public EditWidget
|
||||
public:
|
||||
explicit SettingsWidget(QWidget* parent = Q_NULLPTR);
|
||||
~SettingsWidget();
|
||||
void addSettingsPage(ISettingsPage * page);
|
||||
void loadSettings();
|
||||
|
||||
Q_SIGNALS:
|
||||
@ -49,6 +59,8 @@ private:
|
||||
const QScopedPointer<Ui::SettingsWidgetGeneral> m_generalUi;
|
||||
Qt::Key m_globalAutoTypeKey;
|
||||
Qt::KeyboardModifiers m_globalAutoTypeModifiers;
|
||||
class ExtraPage;
|
||||
QList<ExtraPage> m_extraPages;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SETTINGSWIDGET_H
|
||||
|
@ -6,7 +6,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>456</width>
|
||||
<width>481</width>
|
||||
<height>185</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -14,7 +14,7 @@
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="rememberLastDatabasesCheckBox">
|
||||
<property name="text">
|
||||
<string>Remember last databases</string>
|
||||
<string>Remember recent databases</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
@ -62,6 +62,32 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>When database files are externally modified</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QComboBox" name="reloadBehavior">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always ask</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Reload unmodified databases</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Ignore modifications</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QCheckBox" name="minimizeOnCopyCheckBox">
|
||||
<property name="text">
|
||||
|
@ -326,7 +326,8 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
|
||||
IconStruct iconStruct;
|
||||
iconStruct.uuid = entry->iconUuid();
|
||||
iconStruct.number = entry->iconNumber();
|
||||
m_iconsWidget->load(entry->uuid(), m_database, iconStruct);
|
||||
m_iconsWidget->load(entry->uuid(), m_database, iconStruct, entry->url());
|
||||
connect(m_mainUi->urlEdit, SIGNAL(textChanged(QString)), m_iconsWidget, SLOT(setUrl(QString)));
|
||||
|
||||
m_autoTypeUi->enableButton->setChecked(entry->autoTypeEnabled());
|
||||
if (entry->defaultAutoTypeSequence().isEmpty()) {
|
||||
|
@ -18,13 +18,31 @@
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="AttributesListView" name="attributesView"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="attributesEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="margin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="AttributesListView" name="attributesView"/>
|
||||
<widget class="QPlainTextEdit" name="attributesEdit">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -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
2
src/gui/qocoa/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
cmake/
|
||||
qmake/
|
50
src/gui/qocoa/CMakeLists.txt
Normal file
50
src/gui/qocoa/CMakeLists.txt
Normal file
@ -0,0 +1,50 @@
|
||||
project(Qocoa)
|
||||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
#find_package(Qt4 COMPONENTS QtMain QtCore QtGui REQUIRED)
|
||||
#include(UseQt4)
|
||||
|
||||
set(SOURCES
|
||||
#main.cpp
|
||||
#gallery.cpp
|
||||
)
|
||||
|
||||
set(HEADERS
|
||||
#gallery.h
|
||||
qsearchfield.h
|
||||
qbutton.h
|
||||
qprogressindicatorspinning.h
|
||||
)
|
||||
|
||||
qt4_wrap_cpp(MOC_SOURCES ${HEADERS})
|
||||
|
||||
if(APPLE)
|
||||
list(APPEND SOURCES
|
||||
qsearchfield_mac.mm
|
||||
qbutton_mac.mm
|
||||
qprogressindicatorspinning_mac.mm
|
||||
)
|
||||
else()
|
||||
list(APPEND SOURCES
|
||||
qsearchfield_nonmac.cpp
|
||||
qbutton_nonmac.cpp
|
||||
qprogressindicatorspinning_nonmac.cpp
|
||||
)
|
||||
set(RESOURCES
|
||||
qsearchfield_nonmac.qrc
|
||||
qprogressindicatorspinning_nonmac.qrc
|
||||
)
|
||||
qt4_add_resources(RESOURCES_SOURCES ${RESOURCES})
|
||||
endif()
|
||||
|
||||
#add_executable(Qocoa
|
||||
# WIN32 MACOSX_BUNDLE
|
||||
# ${SOURCES} ${MOC_SOURCES} ${RESOURCES_SOURCES}
|
||||
#)
|
||||
#target_link_libraries(Qocoa ${QT_LIBRARIES})
|
||||
|
||||
add_library (Qocoa STATIC ${SOURCES} ${MOC_SOURCES} ${HEADERS} ${RESOURCES_SOURCES})
|
||||
|
||||
#if(APPLE)
|
||||
# set_target_properties(Qocoa PROPERTIES LINK_FLAGS "-framework Foundation -framework AppKit")
|
||||
#endif()
|
19
src/gui/qocoa/LICENSE.txt
Normal file
19
src/gui/qocoa/LICENSE.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
17
src/gui/qocoa/Qocoa.pro
Normal file
17
src/gui/qocoa/Qocoa.pro
Normal file
@ -0,0 +1,17 @@
|
||||
SOURCES += main.cpp\
|
||||
gallery.cpp \
|
||||
|
||||
HEADERS += gallery.h \
|
||||
qocoa_mac.h \
|
||||
qsearchfield.h \
|
||||
qbutton.h \
|
||||
qprogressindicatorspinning.h \
|
||||
|
||||
mac {
|
||||
OBJECTIVE_SOURCES += qsearchfield_mac.mm qbutton_mac.mm qprogressindicatorspinning_mac.mm
|
||||
LIBS += -framework Foundation -framework Appkit
|
||||
QMAKE_CFLAGS += -mmacosx-version-min=10.6
|
||||
} else {
|
||||
SOURCES += qsearchfield_nonmac.cpp qbutton_nonmac.cpp qprogressindicatorspinning_nonmac.cpp
|
||||
RESOURCES += qsearchfield_nonmac.qrc qprogressindicatorspinning_nonmac.qrc
|
||||
}
|
36
src/gui/qocoa/README.md
Normal file
36
src/gui/qocoa/README.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Qocoa
|
||||
Qocoa is a collection of Qt wrappers for OSX's Cocoa widgets.
|
||||
|
||||
## Features
|
||||
- basic fallback to sensible Qt types on non-OSX platforms
|
||||
- shared class headers which expose no implementation details
|
||||
- typical Qt signal/slot-based API
|
||||
- trivial to import into projects (class header/implementation, [single shared global header](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h))
|
||||
|
||||
## Building
|
||||
```
|
||||
git clone git://github.com/mikemcquaid/Qocoa.git
|
||||
cd Qocoa
|
||||
qmake # or cmake .
|
||||
make
|
||||
```
|
||||
|
||||
## Status
|
||||
Qocoa classes are currently provided for NSButton, a spinning NSProgressIndicator and NSSearchField. There is a [TODO list](https://github.com/mikemcquaid/Qocoa/blob/master/TODO.md) for classes I hope to implement.
|
||||
|
||||
## Usage
|
||||
For each class you want to use copy the [`qocoa_mac.h`](https://github.com/mikemcquaid/Qocoa/blob/master/qocoa_mac.h), `$CLASS.h`, `$CLASS_mac.*` and `$CLASS_nonmac.*` files into your source tree and add them to your buildsystem. Examples are provided for [CMake](https://github.com/mikemcquaid/Qocoa/blob/master/CMakeLists.txt) and [QMake](https://github.com/mikemcquaid/Qocoa/blob/master/Qocoa.pro).
|
||||
|
||||
## Contact
|
||||
[Mike McQuaid](mailto:mike@mikemcquaid.com)
|
||||
|
||||
## License
|
||||
Qocoa is licensed under the [MIT License](http://en.wikipedia.org/wiki/MIT_License).
|
||||
The full license text is available in [LICENSE.txt](https://github.com/mikemcquaid/Qocoa/blob/master/LICENSE.txt).
|
||||
|
||||
Magnifier and EditClear icons taken from [QtCreator](http://qt-project.org/) 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
13
src/gui/qocoa/TODO.md
Normal file
@ -0,0 +1,13 @@
|
||||
Widgets I hope to implement (or at least investigate):
|
||||
|
||||
- NSTokenField
|
||||
- NSSegmentedControl
|
||||
- NSLevelIndicator
|
||||
- NSPathControl
|
||||
- NSSlider (Circular)
|
||||
- NSSplitView
|
||||
- NSTextFinder
|
||||
- NSOutlineView in an NSScrollView (Source List)
|
||||
- NSDrawer
|
||||
- PDFView
|
||||
- WebView
|
75
src/gui/qocoa/gallery.cpp
Normal file
75
src/gui/qocoa/gallery.cpp
Normal file
@ -0,0 +1,75 @@
|
||||
#include "gallery.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "qsearchfield.h"
|
||||
#include "qbutton.h"
|
||||
#include "qprogressindicatorspinning.h"
|
||||
|
||||
Gallery::Gallery(QWidget *parent) : QWidget(parent)
|
||||
{
|
||||
setWindowTitle("Qocoa Gallery");
|
||||
QVBoxLayout *layout = new QVBoxLayout(this);
|
||||
|
||||
QSearchField *searchField = new QSearchField(this);
|
||||
layout->addWidget(searchField);
|
||||
|
||||
QSearchField *searchFieldPlaceholder = new QSearchField(this);
|
||||
searchFieldPlaceholder->setPlaceholderText("Placeholder text");
|
||||
layout->addWidget(searchFieldPlaceholder);
|
||||
|
||||
QButton *roundedButton = new QButton(this, QButton::Rounded);
|
||||
roundedButton->setText("Button");
|
||||
layout->addWidget(roundedButton);
|
||||
|
||||
QButton *regularSquareButton = new QButton(this, QButton::RegularSquare);
|
||||
regularSquareButton->setText("Button");
|
||||
layout->addWidget(regularSquareButton);
|
||||
|
||||
QButton *disclosureButton = new QButton(this, QButton::Disclosure);
|
||||
layout->addWidget(disclosureButton);
|
||||
|
||||
QButton *shadowlessSquareButton = new QButton(this, QButton::ShadowlessSquare);
|
||||
shadowlessSquareButton->setText("Button");
|
||||
layout->addWidget(shadowlessSquareButton);
|
||||
|
||||
QButton *circularButton = new QButton(this, QButton::Circular);
|
||||
layout->addWidget(circularButton);
|
||||
|
||||
QButton *textureSquareButton = new QButton(this, QButton::TexturedSquare);
|
||||
textureSquareButton->setText("Textured Button");
|
||||
layout->addWidget(textureSquareButton);
|
||||
|
||||
QButton *helpButton = new QButton(this, QButton::HelpButton);
|
||||
layout->addWidget(helpButton);
|
||||
|
||||
QButton *smallSquareButton = new QButton(this, QButton::SmallSquare);
|
||||
smallSquareButton->setText("Gradient Button");
|
||||
layout->addWidget(smallSquareButton);
|
||||
|
||||
QButton *texturedRoundedButton = new QButton(this, QButton::TexturedRounded);
|
||||
texturedRoundedButton->setText("Round Textured");
|
||||
layout->addWidget(texturedRoundedButton);
|
||||
|
||||
QButton *roundedRectangleButton = new QButton(this, QButton::RoundRect);
|
||||
roundedRectangleButton->setText("Rounded Rect Button");
|
||||
layout->addWidget(roundedRectangleButton);
|
||||
|
||||
QButton *recessedButton = new QButton(this, QButton::Recessed);
|
||||
recessedButton->setText("Recessed Button");
|
||||
layout->addWidget(recessedButton);
|
||||
|
||||
QButton *roundedDisclosureButton = new QButton(this, QButton::RoundedDisclosure);
|
||||
layout->addWidget(roundedDisclosureButton);
|
||||
|
||||
#ifdef __MAC_10_7
|
||||
QButton *inlineButton = new QButton(this, QButton::Inline);
|
||||
inlineButton->setText("Inline Button");
|
||||
layout->addWidget(inlineButton);
|
||||
#endif
|
||||
|
||||
|
||||
QProgressIndicatorSpinning *progressIndicatorSpinning = new QProgressIndicatorSpinning(this);
|
||||
progressIndicatorSpinning->animate();
|
||||
layout->addWidget(progressIndicatorSpinning);
|
||||
}
|
14
src/gui/qocoa/gallery.h
Normal file
14
src/gui/qocoa/gallery.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef GALLERY_H
|
||||
#define GALLERY_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class Gallery : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Gallery(QWidget *parent = 0);
|
||||
};
|
||||
|
||||
#endif // WIDGET_H
|
BIN
src/gui/qocoa/gallery.png
Normal file
BIN
src/gui/qocoa/gallery.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
13
src/gui/qocoa/main.cpp
Normal file
13
src/gui/qocoa/main.cpp
Normal 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
49
src/gui/qocoa/qbutton.h
Normal 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
|
229
src/gui/qocoa/qbutton_mac.mm
Normal file
229
src/gui/qocoa/qbutton_mac.mm
Normal file
@ -0,0 +1,229 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qbutton.h"
|
||||
|
||||
#include "qocoa_mac.h"
|
||||
|
||||
#import "Foundation/NSAutoreleasePool.h"
|
||||
#import "AppKit/NSButton.h"
|
||||
#import "AppKit/NSFont.h"
|
||||
|
||||
class QButtonPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QButtonPrivate(QButton *qButton, NSButton *nsButton, QButton::BezelStyle bezelStyle)
|
||||
: QObject(qButton), qButton(qButton), nsButton(nsButton)
|
||||
{
|
||||
switch(bezelStyle) {
|
||||
case QButton::Disclosure:
|
||||
case QButton::Circular:
|
||||
#ifdef __MAC_10_7
|
||||
case QButton::Inline:
|
||||
#endif
|
||||
case QButton::RoundedDisclosure:
|
||||
case QButton::HelpButton:
|
||||
[nsButton setTitle:@""];
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
NSFont* font = 0;
|
||||
switch(bezelStyle) {
|
||||
case QButton::RoundRect:
|
||||
font = [NSFont fontWithName:@"Lucida Grande" size:12];
|
||||
break;
|
||||
|
||||
case QButton::Recessed:
|
||||
font = [NSFont fontWithName:@"Lucida Grande Bold" size:12];
|
||||
break;
|
||||
|
||||
#ifdef __MAC_10_7
|
||||
case QButton::Inline:
|
||||
font = [NSFont boldSystemFontOfSize:[NSFont systemFontSizeForControlSize:NSSmallControlSize]];
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
font = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
|
||||
break;
|
||||
}
|
||||
[nsButton setFont:font];
|
||||
|
||||
switch(bezelStyle) {
|
||||
case QButton::Rounded:
|
||||
qButton->setMinimumWidth(40);
|
||||
qButton->setFixedHeight(24);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
case QButton::RegularSquare:
|
||||
case QButton::TexturedSquare:
|
||||
qButton->setMinimumSize(14, 23);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::ShadowlessSquare:
|
||||
qButton->setMinimumSize(5, 25);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::SmallSquare:
|
||||
qButton->setMinimumSize(4, 21);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::TexturedRounded:
|
||||
qButton->setMinimumSize(10, 22);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::RoundRect:
|
||||
case QButton::Recessed:
|
||||
qButton->setMinimumWidth(16);
|
||||
qButton->setFixedHeight(18);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
case QButton::Disclosure:
|
||||
qButton->setMinimumWidth(13);
|
||||
qButton->setFixedHeight(13);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
case QButton::Circular:
|
||||
qButton->setMinimumSize(16, 16);
|
||||
qButton->setMaximumHeight(40);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
||||
break;
|
||||
case QButton::HelpButton:
|
||||
case QButton::RoundedDisclosure:
|
||||
qButton->setMinimumWidth(22);
|
||||
qButton->setFixedHeight(22);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
#ifdef __MAC_10_7
|
||||
case QButton::Inline:
|
||||
qButton->setMinimumWidth(10);
|
||||
qButton->setFixedHeight(16);
|
||||
qButton->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
switch(bezelStyle) {
|
||||
case QButton::Recessed:
|
||||
[nsButton setButtonType:NSPushOnPushOffButton];
|
||||
case QButton::Disclosure:
|
||||
[nsButton setButtonType:NSOnOffButton];
|
||||
default:
|
||||
[nsButton setButtonType:NSMomentaryPushInButton];
|
||||
}
|
||||
|
||||
[nsButton setBezelStyle:bezelStyle];
|
||||
}
|
||||
|
||||
void clicked()
|
||||
{
|
||||
Q_EMIT qButton->clicked(qButton->isChecked());
|
||||
}
|
||||
|
||||
~QButtonPrivate() {
|
||||
[[nsButton target] release];
|
||||
[nsButton setTarget:nil];
|
||||
}
|
||||
|
||||
QButton *qButton;
|
||||
NSButton *nsButton;
|
||||
};
|
||||
|
||||
@interface QButtonTarget : NSObject
|
||||
{
|
||||
@public
|
||||
QPointer<QButtonPrivate> pimpl;
|
||||
}
|
||||
-(void)clicked;
|
||||
@end
|
||||
|
||||
@implementation QButtonTarget
|
||||
-(void)clicked {
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
pimpl->clicked();
|
||||
}
|
||||
@end
|
||||
|
||||
QButton::QButton(QWidget *parent, BezelStyle bezelStyle) : QWidget(parent)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSButton *button = [[NSButton alloc] init];
|
||||
pimpl = new QButtonPrivate(this, button, bezelStyle);
|
||||
|
||||
QButtonTarget *target = [[QButtonTarget alloc] init];
|
||||
target->pimpl = pimpl;
|
||||
[button setTarget:target];
|
||||
|
||||
[button setAction:@selector(clicked)];
|
||||
|
||||
setupLayout(button, this);
|
||||
|
||||
[button release];
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QButton::setText(const QString &text)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
[pimpl->nsButton setTitle:fromQString(text)];
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QButton::setImage(const QPixmap &image)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
[pimpl->nsButton setImage:fromQPixmap(image)];
|
||||
}
|
||||
|
||||
void QButton::setChecked(bool checked)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
[pimpl->nsButton setState:checked];
|
||||
}
|
||||
|
||||
void QButton::setCheckable(bool checkable)
|
||||
{
|
||||
const NSInteger cellMask = checkable ? NSChangeBackgroundCellMask : NSNoCellMask;
|
||||
|
||||
Q_ASSERT(pimpl);
|
||||
if (pimpl)
|
||||
[[pimpl->nsButton cell] setShowsStateBy:cellMask];
|
||||
}
|
||||
|
||||
bool QButton::isChecked()
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return false;
|
||||
|
||||
return [pimpl->nsButton state];
|
||||
}
|
89
src/gui/qocoa/qbutton_nonmac.cpp
Normal file
89
src/gui/qocoa/qbutton_nonmac.cpp
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qbutton.h"
|
||||
|
||||
#include <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
63
src/gui/qocoa/qocoa_mac.h
Normal 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));
|
||||
}
|
29
src/gui/qocoa/qprogressindicatorspinning.h
Normal file
29
src/gui/qocoa/qprogressindicatorspinning.h
Normal file
@ -0,0 +1,29 @@
|
||||
#ifndef QPROGRESSINDICATORSPINNING_H
|
||||
#define QPROGRESSINDICATORSPINNING_H
|
||||
|
||||
#include <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
|
70
src/gui/qocoa/qprogressindicatorspinning_mac.mm
Normal file
70
src/gui/qocoa/qprogressindicatorspinning_mac.mm
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright (C) 2011 by Mike McQuaid
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qprogressindicatorspinning.h"
|
||||
|
||||
#include "qocoa_mac.h"
|
||||
|
||||
#import "Foundation/NSAutoreleasePool.h"
|
||||
#import "AppKit/NSProgressIndicator.h"
|
||||
|
||||
class QProgressIndicatorSpinningPrivate : public QObject
|
||||
{
|
||||
public:
|
||||
QProgressIndicatorSpinningPrivate(QProgressIndicatorSpinning *qProgressIndicatorSpinning,
|
||||
NSProgressIndicator *nsProgressIndicator)
|
||||
: QObject(qProgressIndicatorSpinning), nsProgressIndicator(nsProgressIndicator) {}
|
||||
|
||||
NSProgressIndicator *nsProgressIndicator;
|
||||
};
|
||||
|
||||
QProgressIndicatorSpinning::QProgressIndicatorSpinning(QWidget *parent,
|
||||
Thickness thickness)
|
||||
: QWidget(parent)
|
||||
{
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
NSProgressIndicator *progress = [[NSProgressIndicator alloc] init];
|
||||
[progress setStyle:NSProgressIndicatorSpinningStyle];
|
||||
|
||||
pimpl = new QProgressIndicatorSpinningPrivate(this, progress);
|
||||
|
||||
setupLayout(progress, this);
|
||||
|
||||
setFixedSize(thickness, thickness);
|
||||
|
||||
[progress release];
|
||||
|
||||
[pool drain];
|
||||
}
|
||||
|
||||
void QProgressIndicatorSpinning::animate(bool animate)
|
||||
{
|
||||
Q_ASSERT(pimpl);
|
||||
if (!pimpl)
|
||||
return;
|
||||
|
||||
if (animate)
|
||||
[pimpl->nsProgressIndicator startAnimation:nil];
|
||||
else
|
||||
[pimpl->nsProgressIndicator stopAnimation:nil];
|
||||
}
|
72
src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp
Normal file
72
src/gui/qocoa/qprogressindicatorspinning_nonmac.cpp
Normal 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();
|
||||
}
|
BIN
src/gui/qocoa/qprogressindicatorspinning_nonmac.gif
Normal file
BIN
src/gui/qocoa/qprogressindicatorspinning_nonmac.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
5
src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc
Normal file
5
src/gui/qocoa/qprogressindicatorspinning_nonmac.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/Qocoa">
|
||||
<file>qprogressindicatorspinning_nonmac.gif</file>
|
||||
</qresource>
|
||||
</RCC>
|
48
src/gui/qocoa/qsearchfield.h
Normal file
48
src/gui/qocoa/qsearchfield.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef QSEARCHFIELD_H
|
||||
#define QSEARCHFIELD_H
|
||||
|
||||
#include <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
|
262
src/gui/qocoa/qsearchfield_mac.mm
Normal file
262
src/gui/qocoa/qsearchfield_mac.mm
Normal 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);
|
||||
}
|
256
src/gui/qocoa/qsearchfield_nonmac.cpp
Normal file
256
src/gui/qocoa/qsearchfield_nonmac.cpp
Normal 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);
|
||||
}
|
7
src/gui/qocoa/qsearchfield_nonmac.qrc
Normal file
7
src/gui/qocoa/qsearchfield_nonmac.qrc
Normal file
@ -0,0 +1,7 @@
|
||||
<RCC>
|
||||
<qresource prefix="/Qocoa">
|
||||
<file>qsearchfield_nonmac_clear.png</file>
|
||||
<file>qsearchfield_nonmac_magnifier_menu.png</file>
|
||||
<file>qsearchfield_nonmac_magnifier.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
BIN
src/gui/qocoa/qsearchfield_nonmac_clear.png
Normal file
BIN
src/gui/qocoa/qsearchfield_nonmac_clear.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 736 B |
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier.png
Normal file
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 300 B |
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier_menu.png
Normal file
BIN
src/gui/qocoa/qsearchfield_nonmac_magnifier_menu.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 439 B |
52
src/http/AccessControlDialog.cpp
Normal file
52
src/http/AccessControlDialog.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file AccessControlDialog.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "AccessControlDialog.h"
|
||||
#include "ui_AccessControlDialog.h"
|
||||
#include "core/Entry.h"
|
||||
|
||||
AccessControlDialog::AccessControlDialog(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::AccessControlDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
connect(ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
|
||||
connect(ui->denyButton, SIGNAL(clicked()), this, SLOT(reject()));
|
||||
}
|
||||
|
||||
AccessControlDialog::~AccessControlDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void AccessControlDialog::setUrl(const QString &url)
|
||||
{
|
||||
ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n"
|
||||
"Please select whether you want to allow access.")).arg(QUrl(url).host()));
|
||||
}
|
||||
|
||||
void AccessControlDialog::setItems(const QList<Entry *> &items)
|
||||
{
|
||||
Q_FOREACH (Entry * entry, items)
|
||||
ui->itemsList->addItem(entry->title() + " - " + entry->username());
|
||||
}
|
||||
|
||||
bool AccessControlDialog::remember() const
|
||||
{
|
||||
return ui->rememberDecisionCheckBox->isChecked();
|
||||
}
|
||||
|
||||
void AccessControlDialog::setRemember(bool r)
|
||||
{
|
||||
ui->rememberDecisionCheckBox->setChecked(r);
|
||||
}
|
42
src/http/AccessControlDialog.h
Normal file
42
src/http/AccessControlDialog.h
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file AccessControlDialog.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef ACCESSCONTROLDIALOG_H
|
||||
#define ACCESSCONTROLDIALOG_H
|
||||
|
||||
#include <QtGui/QDialog>
|
||||
|
||||
class Entry;
|
||||
|
||||
namespace Ui {
|
||||
class AccessControlDialog;
|
||||
}
|
||||
|
||||
class AccessControlDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit AccessControlDialog(QWidget *parent = 0);
|
||||
~AccessControlDialog();
|
||||
|
||||
void setUrl(const QString & url);
|
||||
void setItems(const QList<Entry *> & items);
|
||||
bool remember() const;
|
||||
void setRemember(bool r);
|
||||
|
||||
private:
|
||||
Ui::AccessControlDialog *ui;
|
||||
};
|
||||
|
||||
#endif // ACCESSCONTROLDIALOG_H
|
69
src/http/AccessControlDialog.ui
Normal file
69
src/http/AccessControlDialog.ui
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>AccessControlDialog</class>
|
||||
<widget class="QDialog" name="AccessControlDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>221</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>KeyPassX/Http: Confirm Access</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListWidget" name="itemsList"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="rememberDecisionCheckBox">
|
||||
<property name="text">
|
||||
<string>Remember this decision</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="allowButton">
|
||||
<property name="text">
|
||||
<string>Allow</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="denyButton">
|
||||
<property name="text">
|
||||
<string>Deny</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
100
src/http/EntryConfig.cpp
Normal file
100
src/http/EntryConfig.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file EntryConfig.cpp
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "EntryConfig.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/EntryAttributes.h"
|
||||
#include "qjson/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
54
src/http/EntryConfig.h
Normal file
@ -0,0 +1,54 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file EntryConfig.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef ENTRYCONFIG_H
|
||||
#define ENTRYCONFIG_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QSet>
|
||||
|
||||
class Entry;
|
||||
|
||||
class EntryConfig : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QStringList Allow READ allowedHosts WRITE setAllowedHosts)
|
||||
Q_PROPERTY(QStringList Deny READ deniedHosts WRITE setDeniedHosts )
|
||||
Q_PROPERTY(QString Realm READ realm WRITE setRealm )
|
||||
|
||||
public:
|
||||
EntryConfig(QObject * object = 0);
|
||||
|
||||
bool load(const Entry * entry);
|
||||
void save(Entry * entry);
|
||||
bool isAllowed(const QString & host);
|
||||
void allow(const QString & host);
|
||||
bool isDenied(const QString & host);
|
||||
void deny(const QString & host);
|
||||
QString realm() const;
|
||||
void setRealm(const QString &realm);
|
||||
|
||||
private:
|
||||
QStringList allowedHosts() const;
|
||||
void setAllowedHosts(const QStringList &allowedHosts);
|
||||
QStringList deniedHosts() const;
|
||||
void setDeniedHosts(const QStringList &deniedHosts);
|
||||
|
||||
QSet<QString> m_allowedHosts;
|
||||
QSet<QString> m_deniedHosts;
|
||||
QString m_realm;
|
||||
};
|
||||
|
||||
#endif // ENTRYCONFIG_H
|
148
src/http/HttpPasswordGeneratorWidget.cpp
Normal file
148
src/http/HttpPasswordGeneratorWidget.cpp
Normal 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());
|
||||
}
|
60
src/http/HttpPasswordGeneratorWidget.h
Normal file
60
src/http/HttpPasswordGeneratorWidget.h
Normal 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
|
227
src/http/HttpPasswordGeneratorWidget.ui
Normal file
227
src/http/HttpPasswordGeneratorWidget.ui
Normal 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>/*_& ...</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
236
src/http/HttpSettings.cpp
Normal 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
69
src/http/HttpSettings.h
Normal 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
85
src/http/OptionDialog.cpp
Normal 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
43
src/http/OptionDialog.h
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file OptionDialog.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef OPTIONDIALOG_H
|
||||
#define OPTIONDIALOG_H
|
||||
|
||||
#include <QtGui/QWidget>
|
||||
|
||||
namespace Ui {
|
||||
class OptionDialog;
|
||||
}
|
||||
|
||||
class OptionDialog : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OptionDialog(QWidget *parent = 0);
|
||||
~OptionDialog();
|
||||
|
||||
public Q_SLOTS:
|
||||
void loadSettings();
|
||||
void saveSettings();
|
||||
|
||||
Q_SIGNALS:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private:
|
||||
Ui::OptionDialog *ui;
|
||||
};
|
||||
|
||||
#endif // OPTIONDIALOG_H
|
237
src/http/OptionDialog.ui
Normal file
237
src/http/OptionDialog.ui
Normal 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&ow a notification when credentials are requested</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="bestMatchOnly">
|
||||
<property name="text">
|
||||
<string>&Return only best matching entries for an URL instead
|
||||
of all entries for the whole domain</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="unlockDatabase">
|
||||
<property name="text">
|
||||
<string>Re&quest for unlocking the database if it is locked</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="matchUrlScheme">
|
||||
<property name="text">
|
||||
<string>&Match URL schemes
|
||||
Only entries with the same scheme (http://, https://, ftp://, ...) are returned</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByUsername">
|
||||
<property name="text">
|
||||
<string>Sort matching entries by &username</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="sortByTitle">
|
||||
<property name="text">
|
||||
<string>Sort matching entries by &title</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeSharedEncryptionKeys">
|
||||
<property name="text">
|
||||
<string>R&emove all shared encryption-keys from active database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="removeStoredPermissions">
|
||||
<property name="text">
|
||||
<string>Re&move all stored permissions from entries in active database</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_3">
|
||||
<attribute name="title">
|
||||
<string>Password generator</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<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 &access to entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="alwaysAllowUpdate">
|
||||
<property name="text">
|
||||
<string>Always allow &updating entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="searchInAllDatabases">
|
||||
<property name="text">
|
||||
<string>Searc&h in all opened databases for matching entries</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Only the selected database has to be connected with a client!</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>30</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="supportKphFields">
|
||||
<property name="text">
|
||||
<string>&Return also advanced string fields which start with "KPH: "</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Automatic creates or updates are not supported for string fields!</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>30</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<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
525
src/http/Protocol.cpp
Normal 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
208
src/http/Protocol.h
Normal file
@ -0,0 +1,208 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Response.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef RESPONSE_H
|
||||
#define RESPONSE_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QVariant>
|
||||
#include "crypto/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
327
src/http/Server.cpp
Normal 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
93
src/http/Server.h
Normal 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
573
src/http/Service.cpp
Normal 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
61
src/http/Service.h
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
***************************************************************************
|
||||
* @file Service.h
|
||||
*
|
||||
* @brief
|
||||
*
|
||||
* Copyright (C) 2013
|
||||
*
|
||||
* @author Francois Ferrand
|
||||
* @date 4/2013
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef SERVICE_H
|
||||
#define SERVICE_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "Server.h"
|
||||
|
||||
class Service : public KeepassHttpProtocol::Server
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Service(DatabaseTabWidget* parent = 0);
|
||||
|
||||
virtual bool isDatabaseOpened() const;
|
||||
virtual bool openDatabase();
|
||||
virtual QString getDatabaseRootUuid();
|
||||
virtual QString getDatabaseRecycleBinUuid();
|
||||
virtual QString getKey(const QString& id);
|
||||
virtual QString storeKey(const QString& key);
|
||||
virtual QList<KeepassHttpProtocol::Entry> findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
virtual int countMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
virtual QList<KeepassHttpProtocol::Entry> searchAllEntries(const QString& id);
|
||||
virtual void addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm);
|
||||
virtual void updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url);
|
||||
virtual QString generatePassword();
|
||||
|
||||
public Q_SLOTS:
|
||||
void removeSharedEncryptionKeys();
|
||||
void removeStoredPermissions();
|
||||
|
||||
private:
|
||||
enum Access { Denied, Unknown, Allowed};
|
||||
Entry* getConfigEntry(bool create = false);
|
||||
bool matchUrlScheme(const QString& url);
|
||||
Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
|
||||
bool removeFirstDomain(QString& hostname);
|
||||
Group *findCreateAddEntryGroup();
|
||||
class SortEntries;
|
||||
int sortPriority(const Entry *entry, const QString &host, const QString &submitUrl, const QString &baseSubmitUrl) const;
|
||||
KeepassHttpProtocol::Entry prepareEntry(const Entry* entry);
|
||||
QList<Entry*> searchEntries(Database* db, const QString& hostname);
|
||||
QList<Entry*> searchEntries(const QString& text);
|
||||
|
||||
DatabaseTabWidget * const m_dbTabWidget;
|
||||
};
|
||||
|
||||
#endif // SERVICE_H
|
20
src/http/qhttpserver/CMakeLists.txt
Normal file
20
src/http/qhttpserver/CMakeLists.txt
Normal file
@ -0,0 +1,20 @@
|
||||
|
||||
set(qhttpserver_MOC_HDRS
|
||||
qhttpserver.h
|
||||
qhttpresponse.h
|
||||
qhttprequest.h
|
||||
qhttpconnection.h
|
||||
)
|
||||
|
||||
IF (NOT Qt5Core_FOUND)
|
||||
qt4_wrap_cpp(qhttpserver_MOC_SRCS ${qhttpserver_MOC_HDRS})
|
||||
ENDIF()
|
||||
|
||||
set (qhttpserver_SRCS qhttpconnection.cpp qhttprequest.cpp qhttpresponse.cpp qhttpserver.cpp
|
||||
http-parser/http_parser.c http-parser/url_parser.c)
|
||||
set (qhttpserver_HEADERS qhttpconnection.h qhttprequest.h qhttpresponse.h qhttpserver.h
|
||||
http-parser/http_parser.h)
|
||||
|
||||
INCLUDE_DIRECTORIES(http-parser)
|
||||
|
||||
add_library (qhttpserver STATIC ${qhttpserver_SRCS} ${qhttpserver_MOC_SRCS} ${qhttpserver_HEADERS})
|
19
src/http/qhttpserver/LICENSE
Normal file
19
src/http/qhttpserver/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2011-2012 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
40
src/http/qhttpserver/http-parser/AUTHORS
Normal file
40
src/http/qhttpserver/http-parser/AUTHORS
Normal file
@ -0,0 +1,40 @@
|
||||
# Authors ordered by first contribution.
|
||||
Ryan Dahl <ry@tinyclouds.org>
|
||||
Jeremy Hinegardner <jeremy@hinegardner.org>
|
||||
Sergey Shepelev <temotor@gmail.com>
|
||||
Joe Damato <ice799@gmail.com>
|
||||
tomika <tomika_nospam@freemail.hu>
|
||||
Phoenix Sol <phoenix@burninglabs.com>
|
||||
Cliff Frey <cliff@meraki.com>
|
||||
Ewen Cheslack-Postava <ewencp@cs.stanford.edu>
|
||||
Santiago Gala <sgala@apache.org>
|
||||
Tim Becker <tim.becker@syngenio.de>
|
||||
Jeff Terrace <jterrace@gmail.com>
|
||||
Ben Noordhuis <info@bnoordhuis.nl>
|
||||
Nathan Rajlich <nathan@tootallnate.net>
|
||||
Mark Nottingham <mnot@mnot.net>
|
||||
Aman Gupta <aman@tmm1.net>
|
||||
Tim Becker <tim.becker@kuriositaet.de>
|
||||
Sean Cunningham <sean.cunningham@mandiant.com>
|
||||
Peter Griess <pg@std.in>
|
||||
Salman Haq <salman.haq@asti-usa.com>
|
||||
Cliff Frey <clifffrey@gmail.com>
|
||||
Jon Kolb <jon@b0g.us>
|
||||
Fouad Mardini <f.mardini@gmail.com>
|
||||
Paul Querna <pquerna@apache.org>
|
||||
Felix Geisendörfer <felix@debuggable.com>
|
||||
koichik <koichik@improvement.jp>
|
||||
Andre Caron <andre.l.caron@gmail.com>
|
||||
Ivo Raisr <ivosh@ivosh.net>
|
||||
James McLaughlin <jamie@lacewing-project.org>
|
||||
David Gwynne <loki@animata.net>
|
||||
LE ROUX Thomas <thomas@november-eleven.fr>
|
||||
Randy Rizun <rrizun@ortivawireless.com>
|
||||
Andre Louis Caron <andre.louis.caron@usherbrooke.ca>
|
||||
Simon Zimmermann <simonz05@gmail.com>
|
||||
Erik Dubbelboer <erik@dubbelboer.com>
|
||||
Martell Malone <martellmalone@gmail.com>
|
||||
Bertrand Paquet <bpaquet@octo.com>
|
||||
BogDan Vatra <bogdan@kde.org>
|
||||
Peter Faiman <peter@thepicard.org>
|
||||
Corey Richardson <corey@octayn.net>
|
4
src/http/qhttpserver/http-parser/CONTRIBUTIONS
Normal file
4
src/http/qhttpserver/http-parser/CONTRIBUTIONS
Normal file
@ -0,0 +1,4 @@
|
||||
Contributors must agree to the Contributor License Agreement before patches
|
||||
can be accepted.
|
||||
|
||||
http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ
|
23
src/http/qhttpserver/http-parser/LICENSE-MIT
Normal file
23
src/http/qhttpserver/http-parser/LICENSE-MIT
Normal file
@ -0,0 +1,23 @@
|
||||
http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
|
||||
Igor Sysoev.
|
||||
|
||||
Additional changes are licensed under the same terms as NGINX and
|
||||
copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
178
src/http/qhttpserver/http-parser/README.md
Normal file
178
src/http/qhttpserver/http-parser/README.md
Normal file
@ -0,0 +1,178 @@
|
||||
HTTP Parser
|
||||
===========
|
||||
|
||||
This is a parser for HTTP messages written in C. It parses both requests and
|
||||
responses. The parser is designed to be used in performance HTTP
|
||||
applications. It does not make any syscalls nor allocations, it does not
|
||||
buffer data, it can be interrupted at anytime. Depending on your
|
||||
architecture, it only requires about 40 bytes of data per message
|
||||
stream (in a web server that is per connection).
|
||||
|
||||
Features:
|
||||
|
||||
* No dependencies
|
||||
* Handles persistent streams (keep-alive).
|
||||
* Decodes chunked encoding.
|
||||
* Upgrade support
|
||||
* Defends against buffer overflow attacks.
|
||||
|
||||
The parser extracts the following information from HTTP messages:
|
||||
|
||||
* Header fields and values
|
||||
* Content-Length
|
||||
* Request method
|
||||
* Response status code
|
||||
* Transfer-Encoding
|
||||
* HTTP version
|
||||
* Request URL
|
||||
* Message body
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
One `http_parser` object is used per TCP connection. Initialize the struct
|
||||
using `http_parser_init()` and set the callbacks. That might look something
|
||||
like this for a request parser:
|
||||
|
||||
http_parser_settings settings;
|
||||
settings.on_url = my_url_callback;
|
||||
settings.on_header_field = my_header_field_callback;
|
||||
/* ... */
|
||||
|
||||
http_parser *parser = malloc(sizeof(http_parser));
|
||||
http_parser_init(parser, HTTP_REQUEST);
|
||||
parser->data = my_socket;
|
||||
|
||||
When data is received on the socket execute the parser and check for errors.
|
||||
|
||||
size_t len = 80*1024, nparsed;
|
||||
char buf[len];
|
||||
ssize_t recved;
|
||||
|
||||
recved = recv(fd, buf, len, 0);
|
||||
|
||||
if (recved < 0) {
|
||||
/* Handle error. */
|
||||
}
|
||||
|
||||
/* Start up / continue the parser.
|
||||
* Note we pass recved==0 to signal that EOF has been recieved.
|
||||
*/
|
||||
nparsed = http_parser_execute(parser, &settings, buf, recved);
|
||||
|
||||
if (parser->upgrade) {
|
||||
/* handle new protocol */
|
||||
} else if (nparsed != recved) {
|
||||
/* Handle error. Usually just close the connection. */
|
||||
}
|
||||
|
||||
HTTP needs to know where the end of the stream is. For example, sometimes
|
||||
servers send responses without Content-Length and expect the client to
|
||||
consume input (for the body) until EOF. To tell http_parser about EOF, give
|
||||
`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors
|
||||
can still be encountered during an EOF, so one must still be prepared
|
||||
to receive them.
|
||||
|
||||
Scalar valued message information such as `status_code`, `method`, and the
|
||||
HTTP version are stored in the parser structure. This data is only
|
||||
temporally stored in `http_parser` and gets reset on each new message. If
|
||||
this information is needed later, copy it out of the structure during the
|
||||
`headers_complete` callback.
|
||||
|
||||
The parser decodes the transfer-encoding for both requests and responses
|
||||
transparently. That is, a chunked encoding is decoded before being sent to
|
||||
the on_body callback.
|
||||
|
||||
|
||||
The Special Problem of Upgrade
|
||||
------------------------------
|
||||
|
||||
HTTP supports upgrading the connection to a different protocol. An
|
||||
increasingly common example of this is the Web Socket protocol which sends
|
||||
a request like
|
||||
|
||||
GET /demo HTTP/1.1
|
||||
Upgrade: WebSocket
|
||||
Connection: Upgrade
|
||||
Host: example.com
|
||||
Origin: http://example.com
|
||||
WebSocket-Protocol: sample
|
||||
|
||||
followed by non-HTTP data.
|
||||
|
||||
(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more
|
||||
information the Web Socket protocol.)
|
||||
|
||||
To support this, the parser will treat this as a normal HTTP message without a
|
||||
body. Issuing both on_headers_complete and on_message_complete callbacks. However
|
||||
http_parser_execute() will stop parsing at the end of the headers and return.
|
||||
|
||||
The user is expected to check if `parser->upgrade` has been set to 1 after
|
||||
`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
|
||||
offset by the return value of `http_parser_execute()`.
|
||||
|
||||
|
||||
Callbacks
|
||||
---------
|
||||
|
||||
During the `http_parser_execute()` call, the callbacks set in
|
||||
`http_parser_settings` will be executed. The parser maintains state and
|
||||
never looks behind, so buffering the data is not necessary. If you need to
|
||||
save certain data for later usage, you can do that from the callbacks.
|
||||
|
||||
There are two types of callbacks:
|
||||
|
||||
* notification `typedef int (*http_cb) (http_parser*);`
|
||||
Callbacks: on_message_begin, on_headers_complete, on_message_complete.
|
||||
* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
|
||||
Callbacks: (requests only) on_uri,
|
||||
(common) on_header_field, on_header_value, on_body;
|
||||
|
||||
Callbacks must return 0 on success. Returning a non-zero value indicates
|
||||
error to the parser, making it exit immediately.
|
||||
|
||||
In case you parse HTTP message in chunks (i.e. `read()` request line
|
||||
from socket, parse, read half headers, parse, etc) your data callbacks
|
||||
may be called more than once. Http-parser guarantees that data pointer is only
|
||||
valid for the lifetime of callback. You can also `read()` into a heap allocated
|
||||
buffer to avoid copying memory around if this fits your application.
|
||||
|
||||
Reading headers may be a tricky task if you read/parse headers partially.
|
||||
Basically, you need to remember whether last header callback was field or value
|
||||
and apply following logic:
|
||||
|
||||
(on_header_field and on_header_value shortened to on_h_*)
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| State (prev. callback) | Callback | Description/action |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
|
||||
| | | into it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| value | on_h_field | New header started. |
|
||||
| | | Copy current name,value buffers to headers |
|
||||
| | | list and allocate new buffer for new name |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| field | on_h_field | Previous name continues. Reallocate name |
|
||||
| | | buffer and append callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| field | on_h_value | Value for current header started. Allocate |
|
||||
| | | new buffer and copy callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| value | on_h_value | Value continues. Reallocate value buffer |
|
||||
| | | and append callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
|
||||
|
||||
Parsing URLs
|
||||
------------
|
||||
|
||||
A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`.
|
||||
Users of this library may wish to use it to parse URLs constructed from
|
||||
consecutive `on_url` callbacks.
|
||||
|
||||
See examples of reading in headers:
|
||||
|
||||
* [partial example](http://gist.github.com/155877) in C
|
||||
* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C
|
||||
* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript
|
2174
src/http/qhttpserver/http-parser/http_parser.c
Normal file
2174
src/http/qhttpserver/http-parser/http_parser.c
Normal file
File diff suppressed because it is too large
Load Diff
111
src/http/qhttpserver/http-parser/http_parser.gyp
Normal file
111
src/http/qhttpserver/http-parser/http_parser.gyp
Normal file
@ -0,0 +1,111 @@
|
||||
# This file is used with the GYP meta build system.
|
||||
# http://code.google.com/p/gyp/
|
||||
# To build try this:
|
||||
# svn co http://gyp.googlecode.com/svn/trunk gyp
|
||||
# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp
|
||||
# ./out/Debug/test
|
||||
{
|
||||
'target_defaults': {
|
||||
'default_configuration': 'Debug',
|
||||
'configurations': {
|
||||
# TODO: hoist these out and put them somewhere common, because
|
||||
# RuntimeLibrary MUST MATCH across the entire project
|
||||
'Debug': {
|
||||
'defines': [ 'DEBUG', '_DEBUG' ],
|
||||
'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'RuntimeLibrary': 1, # static debug
|
||||
},
|
||||
},
|
||||
},
|
||||
'Release': {
|
||||
'defines': [ 'NDEBUG' ],
|
||||
'cflags': [ '-Wall', '-Wextra', '-O3' ],
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'RuntimeLibrary': 0, # static release
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
},
|
||||
'VCLibrarianTool': {
|
||||
},
|
||||
'VCLinkerTool': {
|
||||
'GenerateDebugInformation': 'true',
|
||||
},
|
||||
},
|
||||
'conditions': [
|
||||
['OS == "win"', {
|
||||
'defines': [
|
||||
'WIN32'
|
||||
],
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'http_parser',
|
||||
'type': 'static_library',
|
||||
'include_dirs': [ '.' ],
|
||||
'direct_dependent_settings': {
|
||||
'defines': [ 'HTTP_PARSER_STRICT=0' ],
|
||||
'include_dirs': [ '.' ],
|
||||
},
|
||||
'defines': [ 'HTTP_PARSER_STRICT=0' ],
|
||||
'sources': [ './http_parser.c', ],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
# Compile as C++. http_parser.c is actually C99, but C++ is
|
||||
# close enough in this case.
|
||||
'CompileAs': 2,
|
||||
},
|
||||
},
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'http_parser_strict',
|
||||
'type': 'static_library',
|
||||
'include_dirs': [ '.' ],
|
||||
'direct_dependent_settings': {
|
||||
'defines': [ 'HTTP_PARSER_STRICT=1' ],
|
||||
'include_dirs': [ '.' ],
|
||||
},
|
||||
'defines': [ 'HTTP_PARSER_STRICT=1' ],
|
||||
'sources': [ './http_parser.c', ],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
# Compile as C++. http_parser.c is actually C99, but C++ is
|
||||
# close enough in this case.
|
||||
'CompileAs': 2,
|
||||
},
|
||||
},
|
||||
}]
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'test-nonstrict',
|
||||
'type': 'executable',
|
||||
'dependencies': [ 'http_parser' ],
|
||||
'sources': [ 'test.c' ]
|
||||
},
|
||||
|
||||
{
|
||||
'target_name': 'test-strict',
|
||||
'type': 'executable',
|
||||
'dependencies': [ 'http_parser_strict' ],
|
||||
'sources': [ 'test.c' ]
|
||||
}
|
||||
]
|
||||
}
|
302
src/http/qhttpserver/http-parser/http_parser.h
Normal file
302
src/http/qhttpserver/http-parser/http_parser.h
Normal file
@ -0,0 +1,302 @@
|
||||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 0
|
||||
|
||||
#include <sys/types.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
|
||||
#include <BaseTsd.h>
|
||||
#include <stddef.h>
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
/* Maximium header size allowed */
|
||||
#define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
|
||||
|
||||
/* Callbacks should return non-zero to indicate an error. The parser will
|
||||
* then halt execution.
|
||||
*
|
||||
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
|
||||
* returning '1' from on_headers_complete will tell the parser that it
|
||||
* should not expect a body. This is used when receiving a response to a
|
||||
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
|
||||
* chunked' headers that indicate the presence of a body.
|
||||
*
|
||||
* http_data_cb does not return data chunks. It will be call arbitrarally
|
||||
* many times for each string. E.G. you might get 10 callbacks for "on_url"
|
||||
* each providing just a few characters more data.
|
||||
*/
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
|
||||
/* Request Methods */
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* webdav */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
/* subversion */ \
|
||||
XX(16, REPORT, REPORT) \
|
||||
XX(17, MKACTIVITY, MKACTIVITY) \
|
||||
XX(18, CHECKOUT, CHECKOUT) \
|
||||
XX(19, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(20, MSEARCH, M-SEARCH) \
|
||||
XX(21, NOTIFY, NOTIFY) \
|
||||
XX(22, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(24, PATCH, PATCH) \
|
||||
XX(25, PURGE, PURGE) \
|
||||
|
||||
enum http_method
|
||||
{
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
/* Flag values for http_parser.flags field */
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_TRAILING = 1 << 3
|
||||
, F_UPGRADE = 1 << 4
|
||||
, F_SKIPBODY = 1 << 5
|
||||
};
|
||||
|
||||
|
||||
/* Map for errno-related constants
|
||||
*
|
||||
* The provided argument should be a macro that takes 2 arguments.
|
||||
*/
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
/* No error */ \
|
||||
XX(OK, "success") \
|
||||
\
|
||||
/* Callback-related errors */ \
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
\
|
||||
/* Parsing-related errors */ \
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(PAUSED, "parser is paused") \
|
||||
XX(UNKNOWN, "an unknown error occurred")
|
||||
|
||||
|
||||
/* Define HPE_* values for each errno value above */
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
/* Get an http_errno value from an http_parser */
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
|
||||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned char type : 2; /* enum http_parser_type */
|
||||
unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned char state; /* enum state from http_parser.c */
|
||||
unsigned char header_state; /* enum header_state from http_parser.c */
|
||||
unsigned char index; /* index into current matcher */
|
||||
|
||||
uint32_t nread; /* # bytes read in various scenarios */
|
||||
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
|
||||
|
||||
/** READ-ONLY **/
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned short status_code; /* responses only */
|
||||
unsigned char method; /* requests only */
|
||||
unsigned char http_errno : 7;
|
||||
|
||||
/* 1 = Upgrade header was present and the parser has exited because of that.
|
||||
* 0 = No upgrade header present.
|
||||
* Should be checked when http_parser_execute() returns in addition to
|
||||
* error checking.
|
||||
*/
|
||||
unsigned char upgrade : 1;
|
||||
|
||||
/** PUBLIC **/
|
||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
/* Result structure for http_parser_parse_url().
|
||||
*
|
||||
* Callers should index into field_data[] with UF_* values iff field_set
|
||||
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
|
||||
* because we probably have padding left over), we convert any port to
|
||||
* a uint16_t.
|
||||
*/
|
||||
struct http_parser_url {
|
||||
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
|
||||
uint16_t port; /* Converted UF_PORT string */
|
||||
|
||||
struct {
|
||||
uint16_t off; /* Offset into buffer in which field starts */
|
||||
uint16_t len; /* Length of run in buffer */
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
/* If http_should_keep_alive() in the on_headers_complete or
|
||||
* on_message_complete callback returns 0, then this should be
|
||||
* the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
int http_should_keep_alive(const http_parser *parser);
|
||||
|
||||
/* Returns a string version of the HTTP method. */
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
/* Return a string name of the given error */
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
/* Return a string description of the given error */
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
/* Parse a URL; return nonzero on failure */
|
||||
int http_parser_parse_url(const char *buf, size_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
/* Pause or un-pause the parser; a nonzero value pauses */
|
||||
void http_parser_pause(http_parser *parser, int paused);
|
||||
|
||||
/* Checks if this is the final chunk of the body. */
|
||||
int http_body_is_final(const http_parser *parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
3402
src/http/qhttpserver/http-parser/test.c
Normal file
3402
src/http/qhttpserver/http-parser/test.c
Normal file
File diff suppressed because it is too large
Load Diff
44
src/http/qhttpserver/http-parser/url_parser.c
Normal file
44
src/http/qhttpserver/http-parser/url_parser.c
Normal file
@ -0,0 +1,44 @@
|
||||
#include "http_parser.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
void
|
||||
dump_url (const char *url, const struct http_parser_url *u)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port);
|
||||
for (i = 0; i < UF_MAX; i++) {
|
||||
if ((u->field_set & (1 << i)) == 0) {
|
||||
printf("\tfield_data[%u]: unset\n", i);
|
||||
continue;
|
||||
}
|
||||
|
||||
printf("\tfield_data[%u]: off: %u len: %u part: \"%.*s\n",
|
||||
i,
|
||||
u->field_data[i].off,
|
||||
u->field_data[i].len,
|
||||
u->field_data[i].len,
|
||||
url + u->field_data[i].off);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char ** argv) {
|
||||
if (argc != 3) {
|
||||
printf("Syntax : %s connect|get url\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
struct http_parser_url u;
|
||||
int len = strlen(argv[2]);
|
||||
int connect = strcmp("connect", argv[1]) == 0 ? 1 : 0;
|
||||
printf("Parsing %s, connect %d\n", argv[2], connect);
|
||||
|
||||
int result = http_parser_parse_url(argv[2], len, connect, &u);
|
||||
if (result != 0) {
|
||||
printf("Parse error : %d\n", result);
|
||||
return result;
|
||||
}
|
||||
printf("Parse ok, result : \n");
|
||||
dump_url(argv[2], &u);
|
||||
return 0;
|
||||
}
|
202
src/http/qhttpserver/qhttpconnection.cpp
Normal file
202
src/http/qhttpserver/qhttpconnection.cpp
Normal file
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qhttpconnection.h"
|
||||
|
||||
#include <QtNetwork/QTcpSocket>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "qhttprequest.h"
|
||||
#include "qhttpresponse.h"
|
||||
|
||||
QHttpConnection::QHttpConnection(QTcpSocket *socket, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_socket(socket)
|
||||
, m_parser(0)
|
||||
, m_request(0)
|
||||
{
|
||||
qDebug() << "Got new connection" << socket->peerAddress() << socket->peerPort();
|
||||
|
||||
m_parser = (http_parser*)malloc(sizeof(http_parser));
|
||||
http_parser_init(m_parser, HTTP_REQUEST);
|
||||
|
||||
m_parserSettings.on_message_begin = MessageBegin;
|
||||
m_parserSettings.on_url = Url;
|
||||
m_parserSettings.on_header_field = HeaderField;
|
||||
m_parserSettings.on_header_value = HeaderValue;
|
||||
m_parserSettings.on_headers_complete = HeadersComplete;
|
||||
m_parserSettings.on_body = Body;
|
||||
m_parserSettings.on_message_complete = MessageComplete;
|
||||
|
||||
m_parser->data = this;
|
||||
|
||||
connect(socket, SIGNAL(readyRead()), this, SLOT(parseRequest()));
|
||||
connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected()));
|
||||
}
|
||||
|
||||
QHttpConnection::~QHttpConnection()
|
||||
{
|
||||
delete m_socket;
|
||||
m_socket = 0;
|
||||
|
||||
free(m_parser);
|
||||
m_parser = 0;
|
||||
}
|
||||
|
||||
void QHttpConnection::socketDisconnected()
|
||||
{
|
||||
if(m_request) {
|
||||
if(m_request->successful()) {
|
||||
return;
|
||||
}
|
||||
m_request->setSuccessful(false);
|
||||
Q_EMIT m_request->end();
|
||||
}
|
||||
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void QHttpConnection::parseRequest()
|
||||
{
|
||||
Q_ASSERT(m_parser);
|
||||
|
||||
while(m_socket->bytesAvailable())
|
||||
{
|
||||
QByteArray arr = m_socket->readAll();
|
||||
http_parser_execute(m_parser, &m_parserSettings, arr.constData(), arr.size());
|
||||
}
|
||||
}
|
||||
|
||||
void QHttpConnection::write(const QByteArray &data)
|
||||
{
|
||||
m_socket->write(data);
|
||||
}
|
||||
|
||||
void QHttpConnection::flush()
|
||||
{
|
||||
m_socket->flush();
|
||||
}
|
||||
|
||||
/********************
|
||||
* Static Callbacks *
|
||||
*******************/
|
||||
int QHttpConnection::MessageBegin(http_parser *parser)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
theConnection->m_currentHeaders.clear();
|
||||
theConnection->m_request = new QHttpRequest(theConnection);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::HeadersComplete(http_parser *parser)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
/** set method **/
|
||||
theConnection->m_request->setMethod(static_cast<QHttpRequest::HttpMethod>(parser->method));
|
||||
|
||||
/** set version **/
|
||||
theConnection->m_request->setVersion(QString("%1.%2").arg(parser->http_major).arg(parser->http_minor));
|
||||
|
||||
// Insert last remaining header
|
||||
theConnection->m_currentHeaders[theConnection->m_currentHeaderField.toLower()] = theConnection->m_currentHeaderValue;
|
||||
theConnection->m_request->setHeaders(theConnection->m_currentHeaders);
|
||||
|
||||
/** set client information **/
|
||||
theConnection->m_request->m_remoteAddress = theConnection->m_socket->peerAddress().toString();
|
||||
theConnection->m_request->m_remotePort = theConnection->m_socket->peerPort();
|
||||
|
||||
QHttpResponse *response = new QHttpResponse(theConnection);
|
||||
if( parser->http_major < 1 || parser->http_minor < 1 )
|
||||
response->m_keepAlive = false;
|
||||
|
||||
connect(theConnection, SIGNAL(destroyed()), response, SLOT(connectionClosed()));
|
||||
|
||||
// we are good to go!
|
||||
Q_EMIT theConnection->newRequest(theConnection->m_request, response);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::MessageComplete(http_parser *parser)
|
||||
{
|
||||
// TODO: do cleanup and prepare for next request
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
theConnection->m_request->setSuccessful(true);
|
||||
Q_EMIT theConnection->m_request->end();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::Url(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
QString url = QString::fromAscii(at, length);
|
||||
theConnection->m_request->setUrl(QUrl(url));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::HeaderField(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
// insert the header we parsed previously
|
||||
// into the header map
|
||||
if( !theConnection->m_currentHeaderField.isEmpty() && !theConnection->m_currentHeaderValue.isEmpty() )
|
||||
{
|
||||
// header names are always lower-cased
|
||||
theConnection->m_currentHeaders[theConnection->m_currentHeaderField.toLower()] = theConnection->m_currentHeaderValue;
|
||||
// clear header value. this sets up a nice
|
||||
// feedback loop where the next time
|
||||
// HeaderValue is called, it can simply append
|
||||
theConnection->m_currentHeaderField = QString();
|
||||
theConnection->m_currentHeaderValue = QString();
|
||||
}
|
||||
|
||||
QString fieldSuffix = QString::fromAscii(at, length);
|
||||
theConnection->m_currentHeaderField += fieldSuffix;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::HeaderValue(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
QString valueSuffix = QString::fromAscii(at, length);
|
||||
theConnection->m_currentHeaderValue += valueSuffix;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int QHttpConnection::Body(http_parser *parser, const char *at, size_t length)
|
||||
{
|
||||
QHttpConnection *theConnection = (QHttpConnection *)parser->data;
|
||||
Q_ASSERT(theConnection->m_request);
|
||||
|
||||
Q_EMIT theConnection->m_request->data(QByteArray(at, length));
|
||||
return 0;
|
||||
}
|
80
src/http/qhttpserver/qhttpconnection.h
Normal file
80
src/http/qhttpserver/qhttpconnection.h
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef Q_HTTP_CONNECTION
|
||||
#define Q_HTTP_CONNECTION
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QHash>
|
||||
|
||||
#include "http_parser.h"
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QHttpRequest;
|
||||
class QHttpResponse;
|
||||
|
||||
typedef QHash<QString, QString> HeaderHash;
|
||||
|
||||
class QHttpConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QHttpConnection(QTcpSocket *socket, QObject *parent = 0);
|
||||
virtual ~QHttpConnection();
|
||||
|
||||
void write(const QByteArray &data);
|
||||
void flush();
|
||||
|
||||
Q_SIGNALS:
|
||||
void newRequest(QHttpRequest*, QHttpResponse*);
|
||||
|
||||
private Q_SLOTS:
|
||||
void parseRequest();
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
static int MessageBegin(http_parser *parser);
|
||||
static int Url(http_parser *parser, const char *at, size_t length);
|
||||
static int HeaderField(http_parser *parser, const char *at, size_t length);
|
||||
static int HeaderValue(http_parser *parser, const char *at, size_t length);
|
||||
static int HeadersComplete(http_parser *parser);
|
||||
static int Body(http_parser *parser, const char *at, size_t length);
|
||||
static int MessageComplete(http_parser *parser);
|
||||
|
||||
private:
|
||||
QTcpSocket *m_socket;
|
||||
http_parser_settings m_parserSettings;
|
||||
http_parser *m_parser;
|
||||
|
||||
// since there can only be one request at any time
|
||||
// even with pipelining
|
||||
QHttpRequest *m_request;
|
||||
|
||||
// the ones we are reading in from the parser
|
||||
HeaderHash m_currentHeaders;
|
||||
QString m_currentHeaderField;
|
||||
QString m_currentHeaderValue;
|
||||
};
|
||||
|
||||
#endif
|
38
src/http/qhttpserver/qhttprequest.cpp
Normal file
38
src/http/qhttpserver/qhttprequest.cpp
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qhttprequest.h"
|
||||
|
||||
#include "qhttpconnection.h"
|
||||
|
||||
QHttpRequest::QHttpRequest(QHttpConnection *connection, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_connection(connection)
|
||||
, m_url("http://localhost/")
|
||||
, m_success(false)
|
||||
{
|
||||
}
|
||||
|
||||
QHttpRequest::~QHttpRequest()
|
||||
{
|
||||
}
|
||||
|
245
src/http/qhttpserver/qhttprequest.h
Normal file
245
src/http/qhttpserver/qhttprequest.h
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef Q_HTTP_REQUEST
|
||||
#define Q_HTTP_REQUEST
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QMetaEnum>
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
class QTcpSocket;
|
||||
|
||||
class QHttpConnection;
|
||||
|
||||
typedef QHash<QString, QString> HeaderHash;
|
||||
|
||||
/* Request Methods */
|
||||
|
||||
/*! \class QHttpRequest
|
||||
*
|
||||
* The QHttpRequest class represents the header and data
|
||||
* sent by the client.
|
||||
*
|
||||
* Header data is available immediately.
|
||||
*
|
||||
* Body data is streamed as it comes in via the data(const QByteArray&) signal.
|
||||
* As a consequence the application's request callback should ensure that it
|
||||
* connects to the data() signal before control returns back to the event loop.
|
||||
* Otherwise there is a risk of some data never being received by the
|
||||
* application.
|
||||
*
|
||||
* The class is <strong>read-only</strong> by users of %QHttpServer.
|
||||
*/
|
||||
class QHttpRequest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(HeaderHash headers READ headers);
|
||||
Q_PROPERTY(QString remoteAddress READ remoteAddress);
|
||||
Q_PROPERTY(quint16 remotePort READ remotePort);
|
||||
Q_PROPERTY(QString method READ method);
|
||||
Q_PROPERTY(QUrl url READ url);
|
||||
Q_PROPERTY(QString path READ path);
|
||||
Q_PROPERTY(QString httpVersion READ httpVersion);
|
||||
Q_ENUMS(HttpMethod);
|
||||
|
||||
public:
|
||||
virtual ~QHttpRequest();
|
||||
|
||||
/*!
|
||||
* Request Methods
|
||||
* Taken from http_parser.h -- make sure to keep synced
|
||||
*/
|
||||
enum HttpMethod {
|
||||
HTTP_DELETE = 0,
|
||||
HTTP_GET,
|
||||
HTTP_HEAD,
|
||||
HTTP_POST,
|
||||
HTTP_PUT,
|
||||
/* pathological */
|
||||
HTTP_CONNECT,
|
||||
HTTP_OPTIONS,
|
||||
HTTP_TRACE,
|
||||
/* webdav */
|
||||
HTTP_COPY,
|
||||
HTTP_LOCK,
|
||||
HTTP_MKCOL,
|
||||
HTTP_MOVE,
|
||||
HTTP_PROPFIND,
|
||||
HTTP_PROPPATCH,
|
||||
HTTP_SEARCH,
|
||||
HTTP_UNLOCK,
|
||||
/* subversion */
|
||||
HTTP_REPORT,
|
||||
HTTP_MKACTIVITY,
|
||||
HTTP_CHECKOUT,
|
||||
HTTP_MERGE,
|
||||
/* upnp */
|
||||
HTTP_MSEARCH,
|
||||
HTTP_NOTIFY,
|
||||
HTTP_SUBSCRIBE,
|
||||
HTTP_UNSUBSCRIBE,
|
||||
/* RFC-5789 */
|
||||
HTTP_PATCH,
|
||||
HTTP_PURGE
|
||||
};
|
||||
|
||||
/*!
|
||||
* Returns the method string for the request
|
||||
*/
|
||||
const QString methodString() const { return MethodToString(method()); }
|
||||
|
||||
/*!
|
||||
* The method used for the request.
|
||||
*/
|
||||
HttpMethod method() const { return m_method; };
|
||||
|
||||
/*!
|
||||
* The complete URL for the request. This
|
||||
* includes the path and query string.
|
||||
*
|
||||
*/
|
||||
const QUrl& url() const { return m_url; };
|
||||
|
||||
/*!
|
||||
* The path portion of the query URL.
|
||||
*
|
||||
* \sa url()
|
||||
*/
|
||||
const QString path() const { return m_url.path(); };
|
||||
|
||||
/*!
|
||||
* The HTTP version used by the client as a
|
||||
* 'x.x' string.
|
||||
*/
|
||||
const QString& httpVersion() const { return m_version; };
|
||||
|
||||
/*!
|
||||
* Any query string included as part of a request.
|
||||
* Usually used to send data in a GET request.
|
||||
*/
|
||||
const QString& queryString() const;
|
||||
|
||||
/*!
|
||||
* Get a hash of the headers sent by the client.
|
||||
* NOTE: All header names are <strong>lowercase</strong>
|
||||
* so that Content-Length becomes content-length and so on.
|
||||
*
|
||||
* This returns a reference! If you want to store headers
|
||||
* somewhere else, where the request may be deleted,
|
||||
* make sure you store them as a copy.
|
||||
*/
|
||||
const HeaderHash& headers() const { return m_headers; };
|
||||
|
||||
/*!
|
||||
* Get the value of a header
|
||||
*
|
||||
* \param field Name of the header field (lowercase).
|
||||
* \return Value of the header or null QString()
|
||||
*/
|
||||
QString header(const QString &field) { return m_headers[field]; };
|
||||
|
||||
/*!
|
||||
* IP Address of the client in dotted decimal format
|
||||
*/
|
||||
const QString& remoteAddress() const { return m_remoteAddress; };
|
||||
|
||||
/*!
|
||||
* Outbound connection port for the client.
|
||||
*/
|
||||
quint16 remotePort() const { return m_remotePort; };
|
||||
|
||||
/*!
|
||||
* Post data
|
||||
*/
|
||||
const QByteArray &body() const { return m_body; }
|
||||
|
||||
/*!
|
||||
* Set immediately before end has been emitted,
|
||||
* stating whether the message was properly received.
|
||||
* Defaults to false untiil the message has completed.
|
||||
*/
|
||||
bool successful() const { return m_success; }
|
||||
|
||||
/*!
|
||||
* connect to data and store all data in a QByteArray
|
||||
* accessible at body()
|
||||
*/
|
||||
void storeBody()
|
||||
{
|
||||
connect(this, SIGNAL(data(const QByteArray &)),
|
||||
this, SLOT(appendBody(const QByteArray &)),
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
Q_SIGNALS:
|
||||
/*!
|
||||
* This signal is emitted whenever body data is encountered
|
||||
* in a message.
|
||||
* This may be emitted zero or more times.
|
||||
*/
|
||||
void data(const QByteArray &);
|
||||
|
||||
/*!
|
||||
* Emitted at the end of the HTTP request.
|
||||
* No data() signals will be emitted after this.
|
||||
*/
|
||||
void end();
|
||||
|
||||
private:
|
||||
QHttpRequest(QHttpConnection *connection, QObject *parent = 0);
|
||||
|
||||
static QString MethodToString(HttpMethod method)
|
||||
{
|
||||
int index = staticMetaObject.indexOfEnumerator("HttpMethod");
|
||||
return staticMetaObject.enumerator(index).valueToKey(method);
|
||||
}
|
||||
|
||||
void setMethod(HttpMethod method) { m_method = method; }
|
||||
void setVersion(const QString &version) { m_version = version; }
|
||||
void setUrl(const QUrl &url) { m_url = url; }
|
||||
void setHeaders(const HeaderHash headers) { m_headers = headers; }
|
||||
void setSuccessful(bool success) { m_success = success; }
|
||||
|
||||
QHttpConnection *m_connection;
|
||||
HeaderHash m_headers;
|
||||
HttpMethod m_method;
|
||||
QUrl m_url;
|
||||
QString m_version;
|
||||
QString m_remoteAddress;
|
||||
quint16 m_remotePort;
|
||||
QByteArray m_body;
|
||||
bool m_success;
|
||||
|
||||
friend class QHttpConnection;
|
||||
|
||||
private Q_SLOTS:
|
||||
void appendBody(const QByteArray &body)
|
||||
{
|
||||
m_body.append(body);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
193
src/http/qhttpserver/qhttpresponse.cpp
Normal file
193
src/http/qhttpserver/qhttpresponse.cpp
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* Copyright 2011 Nikhil Marathe <nsm.nikhil@gmail.com>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "qhttpresponse.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
|
||||
#include "qhttpserver.h"
|
||||
#include "qhttpconnection.h"
|
||||
|
||||
QHttpResponse::QHttpResponse(QHttpConnection *connection)
|
||||
// TODO: parent child relation
|
||||
: QObject(0)
|
||||
, m_connection(connection)
|
||||
, m_headerWritten(false)
|
||||
, m_sentConnectionHeader(false)
|
||||
, m_sentContentLengthHeader(false)
|
||||
, m_sentTransferEncodingHeader(false)
|
||||
, m_sentDate(false)
|
||||
, m_keepAlive(true)
|
||||
, m_last(false)
|
||||
, m_useChunkedEncoding(false)
|
||||
, m_finished(false)
|
||||
{
|
||||
}
|
||||
|
||||
QHttpResponse::~QHttpResponse()
|
||||
{
|
||||
}
|
||||
|
||||
void QHttpResponse::setHeader(const QString &field, const QString &value)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_headers[field] = value;
|
||||
}
|
||||
|
||||
void QHttpResponse::writeHeader(const char *field, const QString &value)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->write(field);
|
||||
m_connection->write(": ");
|
||||
m_connection->write(value.toUtf8());
|
||||
m_connection->write("\r\n");
|
||||
}
|
||||
|
||||
void QHttpResponse::writeHeaders()
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
Q_FOREACH(QString name, m_headers.keys())
|
||||
{
|
||||
QString value = m_headers[name];
|
||||
if( name.compare("connection", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentConnectionHeader = true;
|
||||
if( value == "close" )
|
||||
m_last = true;
|
||||
else
|
||||
m_keepAlive = true;
|
||||
}
|
||||
else if( name.compare("transfer-encoding", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentTransferEncodingHeader = true;
|
||||
if( value == "chunked" )
|
||||
m_useChunkedEncoding = true;
|
||||
}
|
||||
else if( name.compare("content-length", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentContentLengthHeader = true;
|
||||
}
|
||||
else if( name.compare("date", Qt::CaseInsensitive) == 0 )
|
||||
{
|
||||
m_sentDate = true;
|
||||
}
|
||||
//TODO: Expect case
|
||||
|
||||
writeHeader(name.toAscii(), value.toAscii());
|
||||
}
|
||||
|
||||
if( !m_sentConnectionHeader )
|
||||
{
|
||||
if( m_keepAlive &&
|
||||
( m_sentContentLengthHeader || m_useChunkedEncoding ) )
|
||||
{
|
||||
writeHeader("Connection", "keep-alive");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_last = true;
|
||||
writeHeader("Connection", "close");
|
||||
}
|
||||
}
|
||||
|
||||
if( !m_sentContentLengthHeader && !m_sentTransferEncodingHeader )
|
||||
{
|
||||
if( m_useChunkedEncoding )
|
||||
writeHeader("Transfer-Encoding", "chunked");
|
||||
else
|
||||
m_last = true;
|
||||
}
|
||||
|
||||
if( !m_sentDate )
|
||||
{
|
||||
writeHeader("Date", QDateTime::currentDateTimeUtc().toString("ddd, dd MMM yyyy hh:mm:ss G'M'T"));
|
||||
}
|
||||
}
|
||||
|
||||
void QHttpResponse::writeHead(int status)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_headerWritten ) return;
|
||||
|
||||
m_connection->write(QString("HTTP/1.1 %1 %2\r\n").arg(status).arg(STATUS_CODES[status]).toAscii());
|
||||
|
||||
writeHeaders();
|
||||
|
||||
m_connection->write("\r\n");
|
||||
m_headerWritten = true;
|
||||
}
|
||||
|
||||
void QHttpResponse::write(const QByteArray &data)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( !m_headerWritten )
|
||||
{
|
||||
qDebug() << "You MUST call writeHead() before writing body data";
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->write(data);
|
||||
}
|
||||
|
||||
void QHttpResponse::write(const QString &data)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->write(data.toUtf8());
|
||||
}
|
||||
|
||||
void QHttpResponse::end(const QString &data)
|
||||
{
|
||||
if(m_finished) {
|
||||
return;
|
||||
}
|
||||
m_finished = true;
|
||||
|
||||
write(data);
|
||||
|
||||
Q_EMIT done();
|
||||
deleteLater();
|
||||
// TODO: end connection and delete ourselves
|
||||
}
|
||||
|
||||
void QHttpResponse::connectionClosed()
|
||||
{
|
||||
m_finished = true;
|
||||
deleteLater();
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user