diff --git a/ext/qtkeychain/CMakeLists.txt b/ext/qtkeychain/CMakeLists.txt new file mode 100644 index 0000000..7736c60 --- /dev/null +++ b/ext/qtkeychain/CMakeLists.txt @@ -0,0 +1,195 @@ +cmake_minimum_required(VERSION 2.8) +project(qtkeychain) + +### + +set(QTKEYCHAIN_VERSION 0.5.90) +set(QTKEYCHAIN_SOVERSION 0) + +### + +set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${PROJECT_SOURCE_DIR}/cmake/Modules") +include(GNUInstallDirs) + +option(BUILD_WITH_QT4 "Build qtkeychain with Qt4 no matter if Qt5 was found" OFF) + + +if( NOT BUILD_WITH_QT4 ) + # try Qt5 first, and prefer that if found + find_package(Qt5Core QUIET) +endif() + +if (Qt5Core_FOUND) + set(QTKEYCHAIN_VERSION_INFIX 5) + if(UNIX AND NOT APPLE) + find_package(Qt5DBus REQUIRED) + include_directories(${Qt5DBus_INCLUDE_DIRS}) + set(QTDBUS_LIBRARIES ${Qt5DBus_LIBRARIES}) + macro(qt_add_dbus_interface) + qt5_add_dbus_interface(${ARGN}) + endmacro() + endif() + find_package(Qt5LinguistTools REQUIRED) + macro(qt_add_translation) + qt5_add_translation(${ARGN}) + endmacro(qt_add_translation) + macro(qt_create_translation) + qt5_create_translation(${ARGN}) + endmacro(qt_create_translation) + macro(qt_wrap_cpp) + qt5_wrap_cpp(${ARGN}) + endmacro() + + set(QTCORE_LIBRARIES ${Qt5Core_LIBRARIES}) + include_directories(${Qt5Core_INCLUDE_DIRS}) + + if (Qt5_POSITION_INDEPENDENT_CODE) + if (CMAKE_VERSION VERSION_LESS 2.8.9) # TODO remove once we increase the cmake requirement + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC") + else() + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() + endif() +else() + set(QTKEYCHAIN_VERSION_INFIX "") + if(UNIX AND NOT APPLE) + find_package(Qt4 COMPONENTS QtCore QtDBus REQUIRED) + set(QTDBUS_LIBRARIES ${QT_QTDBUS_LIBRARY}) + macro(qt_add_dbus_interface) + qt4_add_dbus_interface(${ARGN}) + endmacro() + else() + find_package(Qt4 COMPONENTS QtCore REQUIRED) + endif() + include_directories(${QT_INCLUDES}) + set(QTCORE_LIBRARIES ${QT_QTCORE_LIBRARY}) + macro(qt_add_translation) + qt4_add_translation(${ARGN}) + endmacro(qt_add_translation) + macro(qt_create_translation) + qt4_create_translation(${ARGN}) + endmacro(qt_create_translation) + macro(qt_wrap_cpp) + qt4_wrap_cpp(${ARGN}) + endmacro() +endif() + + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +list(APPEND qtkeychain_LIBRARIES ${QTCORE_LIBRARIES}) +set(qtkeychain_SOURCES + keychain.cpp +) + +ADD_DEFINITIONS( -Wall ) + +if(WIN32) + list(APPEND qtkeychain_SOURCES keychain_win.cpp) + list(APPEND qtkeychain_LIBRARIES crypt32) + #FIXME: mingw bug; otherwise getting undefined refs to RtlSecureZeroMemory there + if(MINGW) + add_definitions( -O2 ) + endif() +endif() + +if(APPLE) + list(APPEND qtkeychain_SOURCES keychain_mac.cpp) + + find_library(COREFOUNDATION_LIBRARY CoreFoundation) + list(APPEND qtkeychain_LIBRARIES ${COREFOUNDATION_LIBRARY}) + + find_library(SECURITY_LIBRARY Security) + list(APPEND qtkeychain_LIBRARIES ${SECURITY_LIBRARY}) +endif() + +if(UNIX AND NOT APPLE) + list(APPEND qtkeychain_SOURCES keychain_unix.cpp gnomekeyring.cpp) + qt_add_dbus_interface(qtkeychain_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/org.kde.KWallet.xml kwallet_interface KWalletInterface) + list(APPEND qtkeychain_LIBRARIES ${QTDBUS_LIBRARIES}) +endif() + +QT_WRAP_CPP(qtkeychain_MOC_OUTFILES keychain.h keychain_p.h) + +set(qtkeychain_TR_FILES + translations/qtkeychain_de.ts + translations/qtkeychain_ro.ts +) + +file(GLOB qtkeychain_TR_SOURCES *.cpp *.h *.ui) +qt_create_translation(qtkeychain_MESSAGES ${qtkeychain_TR_SOURCES} ${qtkeychain_TR_FILES}) +qt_add_translation(qtkeychain_QM_FILES ${qtkeychain_TR_FILES}) +add_custom_target(messages DEPENDS ${qtkeychain_MESSAGES}) +add_custom_target(translations DEPENDS ${qtkeychain_QM_FILES}) + +if(NOT QT_TRANSLATIONS_DIR) + # If this directory is missing, we are in a Qt5 environment. + # Extract the qmake executable location + get_target_property(QT5_QMAKE_EXECUTABLE Qt5::qmake IMPORTED_LOCATION) + # Ask Qt5 where to put the translations + execute_process( COMMAND ${QT5_QMAKE_EXECUTABLE} -query QT_INSTALL_TRANSLATIONS + OUTPUT_VARIABLE qt_translations_dir OUTPUT_STRIP_TRAILING_WHITESPACE ) + # make sure we have / and not \ as qmake gives on windows + FILE(TO_CMAKE_PATH "${qt_translations_dir}" qt_translations_dir) + SET(QT_TRANSLATIONS_DIR ${qt_translations_dir} CACHE PATH "The + location of the Qt translations" FORCE) +endif() + +install(FILES ${qtkeychain_QM_FILES} + DESTINATION ${QT_TRANSLATIONS_DIR}) + +set(QTKEYCHAIN_TARGET_NAME qt${QTKEYCHAIN_VERSION_INFIX}keychain) +if(NOT QTKEYCHAIN_STATIC) + add_library(${QTKEYCHAIN_TARGET_NAME} SHARED ${qtkeychain_SOURCES} ${qtkeychain_MOC_OUTFILES} ${qtkeychain_QM_FILES}) + set_target_properties(${QTKEYCHAIN_TARGET_NAME} PROPERTIES COMPILE_DEFINITIONS QKEYCHAIN_BUILD_QKEYCHAIN_LIB) + target_link_libraries(${QTKEYCHAIN_TARGET_NAME} ${qtkeychain_LIBRARIES}) +else() + add_library(${QTKEYCHAIN_TARGET_NAME} STATIC ${qtkeychain_SOURCES} ${qtkeychain_MOC_OUTFILES} ${qtkeychain_QM_FILES}) + set_target_properties(${QTKEYCHAIN_TARGET_NAME} PROPERTIES COMPILE_DEFINITIONS QKEYCHAIN_STATICLIB) +endif() + +set_target_properties(${QTKEYCHAIN_TARGET_NAME} PROPERTIES + VERSION ${QTKEYCHAIN_VERSION} + SOVERSION ${QTKEYCHAIN_SOVERSION} + MACOSX_RPATH 1 + INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}" +) + +install(FILES keychain.h qkeychain_export.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/qt${QTKEYCHAIN_VERSION_INFIX}keychain/ +) + +install(TARGETS ${QTKEYCHAIN_TARGET_NAME} + EXPORT Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +add_executable( testclient testclient.cpp ) +target_link_libraries( testclient ${QTKEYCHAIN_TARGET_NAME}) + + +### +### CMake config file +### + +export(TARGETS ${QTKEYCHAIN_TARGET_NAME} FILE "${PROJECT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends.cmake") +export(PACKAGE Qt${QTKEYCHAIN_VERSION_INFIX}Keychain) + +configure_file(QtKeychainBuildTreeSettings.cmake.in + "${PROJECT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainBuildTreeSettings.cmake" @ONLY) +configure_file(QtKeychainConfig.cmake.in + "${PROJECT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfig.cmake" @ONLY) +configure_file(QtKeychainConfigVersion.cmake.in + "${PROJECT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake" @ONLY) + +install(EXPORT Qt${QTKEYCHAIN_VERSION_INFIX}KeychainLibraryDepends + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Qt${QTKEYCHAIN_VERSION_INFIX}Keychain" +) + +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/Qt${QTKEYCHAIN_VERSION_INFIX}KeychainConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Qt${QTKEYCHAIN_VERSION_INFIX}Keychain +) + diff --git a/ext/qtkeychain/COPYING b/ext/qtkeychain/COPYING new file mode 100644 index 0000000..cca2a5c --- /dev/null +++ b/ext/qtkeychain/COPYING @@ -0,0 +1,20 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/ext/qtkeychain/ChangeLog b/ext/qtkeychain/ChangeLog new file mode 100644 index 0000000..f89dc19 --- /dev/null +++ b/ext/qtkeychain/ChangeLog @@ -0,0 +1,23 @@ +ChangeLog +========= + +version 0.5.0 (release 2015-05-04) + * Added support for KWallet5 (KDE5/KF) + +version 0.4.0 (release 2014-09-01) + * KWallet: Handle case where no wallet exists yet (Liviu Cristian Mirea Ghiban ) + * Improved desktop environment detection at runtime (Daniel Molkentin ) + +version 0.3.0 (release 2014-03-13) + * Gnome Keyring supported added (Francois Ferrand ) + * Improved Qt 5 support + * KWallet: Distinguish empty passwords from non-existing entries + * KWallet: Do not use hardcoded wallet name + * German translation (Daniel Molkentin ) + * Romanian translation (Arthur Țițeică ) + +version 0.2.0: no official release + +version 0.1.0 (release 2013-01-16) + * Initial release + diff --git a/ext/qtkeychain/QtKeychainBuildTreeSettings.cmake.in b/ext/qtkeychain/QtKeychainBuildTreeSettings.cmake.in new file mode 100644 index 0000000..3f9e9f6 --- /dev/null +++ b/ext/qtkeychain/QtKeychainBuildTreeSettings.cmake.in @@ -0,0 +1,4 @@ +set(QTKEYCHAIN_INCLUDE_DIRS + "@PROJECT_SOURCE_DIR@" + "@PROJECT_BINARY_DIR@" +) diff --git a/ext/qtkeychain/QtKeychainConfig.cmake.in b/ext/qtkeychain/QtKeychainConfig.cmake.in new file mode 100644 index 0000000..74c1356 --- /dev/null +++ b/ext/qtkeychain/QtKeychainConfig.cmake.in @@ -0,0 +1,21 @@ +# - Config file for the QtKeychain package +# It defines the following variables +# QTKEYCHAIN_INCLUDE_DIRS - include directories for QtKeychain +# QTKEYCHAIN_LIBRARIES - libraries to link against + +# Compute paths +get_filename_component(QTKEYCHAIN_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) +if(EXISTS "${QTKEYCHAIN_CMAKE_DIR}/CMakeCache.txt") + # In build tree + include("${QTKEYCHAIN_CMAKE_DIR}/Qt@QTKEYCHAIN_VERSION_INFIX@KeychainBuildTreeSettings.cmake") +else() + set(QTKEYCHAIN_INCLUDE_DIRS "@CMAKE_INSTALL_FULL_INCLUDEDIR@") +endif() + +# Our library dependencies (contains definitions for IMPORTED targets) +include("${QTKEYCHAIN_CMAKE_DIR}/Qt@QTKEYCHAIN_VERSION_INFIX@KeychainLibraryDepends.cmake") + +# These are IMPORTED targets created by FooBarLibraryDepends.cmake +set(QTKEYCHAIN_LIBRARIES "@QTKEYCHAIN_TARGET_NAME@") + +set(QTKEYCHAIN_FOUND TRUE) diff --git a/ext/qtkeychain/QtKeychainConfigVersion.cmake.in b/ext/qtkeychain/QtKeychainConfigVersion.cmake.in new file mode 100644 index 0000000..fba821a --- /dev/null +++ b/ext/qtkeychain/QtKeychainConfigVersion.cmake.in @@ -0,0 +1,11 @@ +set(PACKAGE_VERSION "@QTKEYCHAIN_VERSION@") + +# Check whether the requested PACKAGE_FIND_VERSION is compatible +if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_COMPATIBLE FALSE) +else() + set(PACKAGE_VERSION_COMPATIBLE TRUE) + if ("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") + set(PACKAGE_VERSION_EXACT TRUE) + endif() +endif() diff --git a/ext/qtkeychain/ReadMe.markdown b/ext/qtkeychain/ReadMe.markdown new file mode 100644 index 0000000..e89adb7 --- /dev/null +++ b/ext/qtkeychain/ReadMe.markdown @@ -0,0 +1,15 @@ +QtKeychain +========== + +QtKeychain is a Qt API to store passwords and other secret data securely. How the data is stored depends on the platform: + + * **Mac OS X:** Passwords are stored in the OS X Keychain. + + * **Linux/Unix:** If running, GNOME Keyring is used, otherwise +qtkeychain tries to use KWallet (via D-Bus), if available. + + * **Windows:** Windows does not provide a service for secure storage. QtKeychain uses the Windows API function [CryptProtectData](http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261%28v=vs.85%29.aspx "CryptProtectData function") to encrypt the password with the user's logon credentials. The encrypted data is then persisted via QSettings. + +In unsupported environments QtKeychain will report an error. It will not store any data unencrypted unless explicitly requested (setInsecureFallback( true )). + +**License:** QtKeychain is available under the [Modified BSD License](http://www.gnu.org/licenses/license-list.html#ModifiedBSD). See the file COPYING for details. diff --git a/ext/qtkeychain/ReadMe.txt b/ext/qtkeychain/ReadMe.txt new file mode 100644 index 0000000..e89adb7 --- /dev/null +++ b/ext/qtkeychain/ReadMe.txt @@ -0,0 +1,15 @@ +QtKeychain +========== + +QtKeychain is a Qt API to store passwords and other secret data securely. How the data is stored depends on the platform: + + * **Mac OS X:** Passwords are stored in the OS X Keychain. + + * **Linux/Unix:** If running, GNOME Keyring is used, otherwise +qtkeychain tries to use KWallet (via D-Bus), if available. + + * **Windows:** Windows does not provide a service for secure storage. QtKeychain uses the Windows API function [CryptProtectData](http://msdn.microsoft.com/en-us/library/windows/desktop/aa380261%28v=vs.85%29.aspx "CryptProtectData function") to encrypt the password with the user's logon credentials. The encrypted data is then persisted via QSettings. + +In unsupported environments QtKeychain will report an error. It will not store any data unencrypted unless explicitly requested (setInsecureFallback( true )). + +**License:** QtKeychain is available under the [Modified BSD License](http://www.gnu.org/licenses/license-list.html#ModifiedBSD). See the file COPYING for details. diff --git a/ext/qtkeychain/cmake/Modules/GNUInstallDirs.cmake b/ext/qtkeychain/cmake/Modules/GNUInstallDirs.cmake new file mode 100644 index 0000000..0302e4b --- /dev/null +++ b/ext/qtkeychain/cmake/Modules/GNUInstallDirs.cmake @@ -0,0 +1,188 @@ +# - Define GNU standard installation directories +# Provides install directory variables as defined for GNU software: +# http://www.gnu.org/prep/standards/html_node/Directory-Variables.html +# Inclusion of this module defines the following variables: +# CMAKE_INSTALL_ - destination for files of a given type +# CMAKE_INSTALL_FULL_ - corresponding absolute path +# where is one of: +# BINDIR - user executables (bin) +# SBINDIR - system admin executables (sbin) +# LIBEXECDIR - program executables (libexec) +# SYSCONFDIR - read-only single-machine data (etc) +# SHAREDSTATEDIR - modifiable architecture-independent data (com) +# LOCALSTATEDIR - modifiable single-machine data (var) +# LIBDIR - object code libraries (lib or lib64 or lib/ on Debian) +# INCLUDEDIR - C header files (include) +# OLDINCLUDEDIR - C header files for non-gcc (/usr/include) +# DATAROOTDIR - read-only architecture-independent data root (share) +# DATADIR - read-only architecture-independent data (DATAROOTDIR) +# INFODIR - info documentation (DATAROOTDIR/info) +# LOCALEDIR - locale-dependent data (DATAROOTDIR/locale) +# MANDIR - man documentation (DATAROOTDIR/man) +# DOCDIR - documentation root (DATAROOTDIR/doc/PROJECT_NAME) +# Each CMAKE_INSTALL_ value may be passed to the DESTINATION options of +# install() commands for the corresponding file type. If the includer does +# not define a value the above-shown default will be used and the value will +# appear in the cache for editing by the user. +# Each CMAKE_INSTALL_FULL_ value contains an absolute path constructed +# from the corresponding destination by prepending (if necessary) the value +# of CMAKE_INSTALL_PREFIX. + +#============================================================================= +# Copyright 2011 Nikita Krupen'ko +# Copyright 2011 Kitware, Inc. +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# (To distribute this file outside of CMake, substitute the full +# License text for the above reference.) + +# Installation directories +# +if(NOT DEFINED CMAKE_INSTALL_BINDIR) + set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_SBINDIR) + set(CMAKE_INSTALL_SBINDIR "sbin" CACHE PATH "system admin executables (sbin)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_LIBEXECDIR) + set(CMAKE_INSTALL_LIBEXECDIR "libexec" CACHE PATH "program executables (libexec)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_SYSCONFDIR) + set(CMAKE_INSTALL_SYSCONFDIR "etc" CACHE PATH "read-only single-machine data (etc)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_SHAREDSTATEDIR) + set(CMAKE_INSTALL_SHAREDSTATEDIR "com" CACHE PATH "modifiable architecture-independent data (com)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_LOCALSTATEDIR) + set(CMAKE_INSTALL_LOCALSTATEDIR "var" CACHE PATH "modifiable single-machine data (var)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_LIBDIR) + set(_LIBDIR_DEFAULT "lib") + # Override this default 'lib' with 'lib64' iff: + # - we are on Linux system but NOT cross-compiling + # - we are NOT on debian + # - we are on a 64 bits system + # reason is: amd64 ABI: http://www.x86-64.org/documentation/abi.pdf + # For Debian with multiarch, use 'lib/${CMAKE_LIBRARY_ARCHITECTURE}' if + # CMAKE_LIBRARY_ARCHITECTURE is set (which contains e.g. "i386-linux-gnu" + # See http://wiki.debian.org/Multiarch + if(CMAKE_SYSTEM_NAME MATCHES "Linux" + AND NOT CMAKE_CROSSCOMPILING) + if (EXISTS "/etc/debian_version") # is this a debian system ? + if(CMAKE_LIBRARY_ARCHITECTURE) + set(_LIBDIR_DEFAULT "lib/${CMAKE_LIBRARY_ARCHITECTURE}") + endif() + else() # not debian, rely on CMAKE_SIZEOF_VOID_P: + if(NOT DEFINED CMAKE_SIZEOF_VOID_P) + message(AUTHOR_WARNING + "Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. " + "Please enable at least one language before including GNUInstallDirs.") + else() + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(_LIBDIR_DEFAULT "lib64") + endif() + endif() + endif() + endif() + set(CMAKE_INSTALL_LIBDIR "${_LIBDIR_DEFAULT}" CACHE PATH "object code libraries (${_LIBDIR_DEFAULT})") +endif() + +if(NOT DEFINED CMAKE_INSTALL_INCLUDEDIR) + set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE PATH "C header files (include)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_OLDINCLUDEDIR) + set(CMAKE_INSTALL_OLDINCLUDEDIR "/usr/include" CACHE PATH "C header files for non-gcc (/usr/include)") +endif() + +if(NOT DEFINED CMAKE_INSTALL_DATAROOTDIR) + set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE PATH "read-only architecture-independent data root (share)") +endif() + +#----------------------------------------------------------------------------- +# Values whose defaults are relative to DATAROOTDIR. Store empty values in +# the cache and store the defaults in local variables if the cache values are +# not set explicitly. This auto-updates the defaults as DATAROOTDIR changes. + +if(NOT CMAKE_INSTALL_DATADIR) + set(CMAKE_INSTALL_DATADIR "" CACHE PATH "read-only architecture-independent data (DATAROOTDIR)") + set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}") +endif() + +if(NOT CMAKE_INSTALL_INFODIR) + set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (DATAROOTDIR/info)") + set(CMAKE_INSTALL_INFODIR "${CMAKE_INSTALL_DATAROOTDIR}/info") +endif() + +if(NOT CMAKE_INSTALL_LOCALEDIR) + set(CMAKE_INSTALL_LOCALEDIR "" CACHE PATH "locale-dependent data (DATAROOTDIR/locale)") + set(CMAKE_INSTALL_LOCALEDIR "${CMAKE_INSTALL_DATAROOTDIR}/locale") +endif() + +if(NOT CMAKE_INSTALL_MANDIR) + set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (DATAROOTDIR/man)") + set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_DATAROOTDIR}/man") +endif() + +if(NOT CMAKE_INSTALL_DOCDIR) + set(CMAKE_INSTALL_DOCDIR "" CACHE PATH "documentation root (DATAROOTDIR/doc/PROJECT_NAME)") + set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") +endif() + +#----------------------------------------------------------------------------- + +mark_as_advanced( + CMAKE_INSTALL_BINDIR + CMAKE_INSTALL_SBINDIR + CMAKE_INSTALL_LIBEXECDIR + CMAKE_INSTALL_SYSCONFDIR + CMAKE_INSTALL_SHAREDSTATEDIR + CMAKE_INSTALL_LOCALSTATEDIR + CMAKE_INSTALL_LIBDIR + CMAKE_INSTALL_INCLUDEDIR + CMAKE_INSTALL_OLDINCLUDEDIR + CMAKE_INSTALL_DATAROOTDIR + CMAKE_INSTALL_DATADIR + CMAKE_INSTALL_INFODIR + CMAKE_INSTALL_LOCALEDIR + CMAKE_INSTALL_MANDIR + CMAKE_INSTALL_DOCDIR + ) + +# Result directories +# +foreach(dir + BINDIR + SBINDIR + LIBEXECDIR + SYSCONFDIR + SHAREDSTATEDIR + LOCALSTATEDIR + LIBDIR + INCLUDEDIR + OLDINCLUDEDIR + DATAROOTDIR + DATADIR + INFODIR + LOCALEDIR + MANDIR + DOCDIR + ) + if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}}) + set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}") + else() + set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}") + endif() +endforeach() diff --git a/ext/qtkeychain/gnomekeyring.cpp b/ext/qtkeychain/gnomekeyring.cpp new file mode 100644 index 0000000..9cef00f --- /dev/null +++ b/ext/qtkeychain/gnomekeyring.cpp @@ -0,0 +1,71 @@ +#include "gnomekeyring_p.h" + +const char* GnomeKeyring::GNOME_KEYRING_DEFAULT = NULL; + +bool GnomeKeyring::isAvailable() +{ + const GnomeKeyring& keyring = instance(); + return keyring.isLoaded() && + keyring.NETWORK_PASSWORD && + keyring.is_available && + keyring.find_password && + keyring.store_password && + keyring.delete_password && + keyring.is_available(); +} + +GnomeKeyring::gpointer GnomeKeyring::store_network_password( const gchar* keyring, const gchar* display_name, + const gchar* user, const gchar* server, const gchar* password, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data ) +{ + if ( !isAvailable() ) + return 0; + return instance().store_password( instance().NETWORK_PASSWORD, + keyring, display_name, password, callback, data, destroy_data, + "user", user, "server", server, static_cast(0) ); +} + +GnomeKeyring::gpointer GnomeKeyring::find_network_password( const gchar* user, const gchar* server, + OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data ) +{ + if ( !isAvailable() ) + return 0; + return instance().find_password( instance().NETWORK_PASSWORD, + callback, data, destroy_data, + "user", user, "server", server, static_cast(0) ); +} + +GnomeKeyring::gpointer GnomeKeyring::delete_network_password( const gchar* user, + const gchar* server, + OperationDoneCallback callback, + gpointer data, + GDestroyNotify destroy_data ) +{ + if ( !isAvailable() ) + return 0; + return instance().delete_password( instance().NETWORK_PASSWORD, + callback, data, destroy_data, + "user", user, "server", server, static_cast(0) ); +} + +GnomeKeyring::GnomeKeyring() + : QLibrary("gnome-keyring", 0) +{ + static const PasswordSchema schema = { + ITEM_NETWORK_PASSWORD, + {{ "user", ATTRIBUTE_TYPE_STRING }, + { "server", ATTRIBUTE_TYPE_STRING }, + { 0, static_cast( 0 ) }} + }; + + NETWORK_PASSWORD = &schema; + is_available = reinterpret_cast( resolve( "gnome_keyring_is_available" ) ); + find_password = reinterpret_cast( resolve( "gnome_keyring_find_password" ) ); + store_password = reinterpret_cast( resolve( "gnome_keyring_store_password" ) ); + delete_password = reinterpret_cast( resolve( "gnome_keyring_delete_password" ) ); +} + +GnomeKeyring& GnomeKeyring::instance() { + static GnomeKeyring keyring; + return keyring; +} diff --git a/ext/qtkeychain/gnomekeyring_p.h b/ext/qtkeychain/gnomekeyring_p.h new file mode 100644 index 0000000..6d150ba --- /dev/null +++ b/ext/qtkeychain/gnomekeyring_p.h @@ -0,0 +1,88 @@ +#ifndef QTKEYCHAIN_GNOME_P_H +#define QTKEYCHAIN_GNOME_P_H + +#include + +class GnomeKeyring : private QLibrary { +public: + enum Result { + RESULT_OK, + RESULT_DENIED, + RESULT_NO_KEYRING_DAEMON, + RESULT_ALREADY_UNLOCKED, + RESULT_NO_SUCH_KEYRING, + RESULT_BAD_ARGUMENTS, + RESULT_IO_ERROR, + RESULT_CANCELLED, + RESULT_KEYRING_ALREADY_EXISTS, + RESULT_NO_MATCH + }; + + enum ItemType { + ITEM_GENERIC_SECRET = 0, + ITEM_NETWORK_PASSWORD, + ITEM_NOTE, + ITEM_CHAINED_KEYRING_PASSWORD, + ITEM_ENCRYPTION_KEY_PASSWORD, + ITEM_PK_STORAGE = 0x100 + }; + + enum AttributeType { + ATTRIBUTE_TYPE_STRING, + ATTRIBUTE_TYPE_UINT32 + }; + + typedef char gchar; + typedef void* gpointer; + typedef bool gboolean; + typedef struct { + ItemType item_type; + struct { + const gchar* name; + AttributeType type; + } attributes[32]; + } PasswordSchema; + + typedef void ( *OperationGetStringCallback )( Result result, const char* string, gpointer data ); + typedef void ( *OperationDoneCallback )( Result result, gpointer data ); + typedef void ( *GDestroyNotify )( gpointer data ); + + static const char* GNOME_KEYRING_DEFAULT; + + static bool isAvailable(); + + static gpointer store_network_password( const gchar* keyring, const gchar* display_name, + const gchar* user, const gchar* server, const gchar* password, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data ); + + static gpointer find_network_password( const gchar* user, const gchar* server, + OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data ); + + static gpointer delete_network_password( const gchar* user, const gchar* server, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data ); +private: + GnomeKeyring(); + + static GnomeKeyring& instance(); + + const PasswordSchema* NETWORK_PASSWORD; + typedef gboolean ( is_available_fn )( void ); + typedef gpointer ( store_password_fn )( const PasswordSchema* schema, const gchar* keyring, + const gchar* display_name, const gchar* password, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + typedef gpointer ( find_password_fn )( const PasswordSchema* schema, + OperationGetStringCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + typedef gpointer ( delete_password_fn )( const PasswordSchema* schema, + OperationDoneCallback callback, gpointer data, GDestroyNotify destroy_data, + ... ); + + is_available_fn* is_available; + find_password_fn* find_password; + store_password_fn* store_password; + delete_password_fn* delete_password; +}; + + +#endif diff --git a/ext/qtkeychain/keychain.cpp b/ext/qtkeychain/keychain.cpp new file mode 100644 index 0000000..7687f9d --- /dev/null +++ b/ext/qtkeychain/keychain.cpp @@ -0,0 +1,229 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain.h" +#include "keychain_p.h" + +using namespace QKeychain; + +Job::Job( const QString& service, QObject *parent ) + : QObject( parent ) + , d ( new JobPrivate( service ) ) { +} + +Job::~Job() { + delete d; +} + +QString Job::service() const { + return d->service; +} + +QSettings* Job::settings() const { + return d->settings; +} + +void Job::setSettings( QSettings* settings ) { + d->settings = settings; +} + +void Job::start() { + QMetaObject::invokeMethod( this, "doStart", Qt::QueuedConnection ); +} + +bool Job::autoDelete() const { + return d->autoDelete; +} + +void Job::setAutoDelete( bool autoDelete ) { + d->autoDelete = autoDelete; +} + +bool Job::insecureFallback() const { + return d->insecureFallback; +} + +void Job::setInsecureFallback( bool insecureFallback ) { + d->insecureFallback = insecureFallback; +} + +void Job::emitFinished() { + emit finished( this ); + if ( d->autoDelete ) + deleteLater(); +} + +void Job::emitFinishedWithError( Error error, const QString& errorString ) { + d->error = error; + d->errorString = errorString; + emitFinished(); +} + +Error Job::error() const { + return d->error; +} + +QString Job::errorString() const { + return d->errorString; +} + +void Job::setError( Error error ) { + d->error = error; +} + +void Job::setErrorString( const QString& errorString ) { + d->errorString = errorString; +} + +ReadPasswordJob::ReadPasswordJob( const QString& service, QObject* parent ) + : Job( service, parent ) + , d( new ReadPasswordJobPrivate( this ) ) +{} + +ReadPasswordJob::~ReadPasswordJob() { + delete d; +} + +QString ReadPasswordJob::textData() const { + return QString::fromUtf8( d->data ); +} + +QByteArray ReadPasswordJob::binaryData() const { + return d->data; +} + +QString ReadPasswordJob::key() const { + return d->key; +} + +void ReadPasswordJob::setKey( const QString& key ) { + d->key = key; +} + +void ReadPasswordJob::doStart() { + JobExecutor::instance()->enqueue( this ); +} + +WritePasswordJob::WritePasswordJob( const QString& service, QObject* parent ) + : Job( service, parent ) + , d( new WritePasswordJobPrivate( this ) ) { +} + +WritePasswordJob::~WritePasswordJob() { + delete d; +} + +QString WritePasswordJob::key() const { + return d->key; +} + +void WritePasswordJob::setKey( const QString& key ) { + d->key = key; +} + +void WritePasswordJob::setBinaryData( const QByteArray& data ) { + d->binaryData = data; + d->mode = WritePasswordJobPrivate::Binary; +} + +void WritePasswordJob::setTextData( const QString& data ) { + d->textData = data; + d->mode = WritePasswordJobPrivate::Text; +} + +void WritePasswordJob::doStart() { + JobExecutor::instance()->enqueue( this ); +} + +DeletePasswordJob::DeletePasswordJob( const QString& service, QObject* parent ) + : Job( service, parent ) + , d( new DeletePasswordJobPrivate( this ) ) { +} + +DeletePasswordJob::~DeletePasswordJob() { + delete d; +} + +void DeletePasswordJob::doStart() { + //Internally, to delete a password we just execute a write job with no data set (null byte array). + //In all current implementations, this deletes the entry so this is sufficient + WritePasswordJob* job = new WritePasswordJob( service(), this ); + connect( job, SIGNAL(finished(QKeychain::Job*)), d, SLOT(jobFinished(QKeychain::Job*)) ); + job->setInsecureFallback(true); + job->setSettings(settings()); + job->setKey( d->key ); + job->doStart(); +} + +QString DeletePasswordJob::key() const { + return d->key; +} + +void DeletePasswordJob::setKey( const QString& key ) { + d->key = key; +} + +void DeletePasswordJobPrivate::jobFinished( Job* job ) { + q->setError( job->error() ); + q->setErrorString( job->errorString() ); + q->emitFinished(); +} + +JobExecutor::JobExecutor() + : QObject( 0 ) + , m_runningJob( 0 ) +{ +} + +void JobExecutor::enqueue( Job* job ) { + m_queue.append( job ); + startNextIfNoneRunning(); +} + +void JobExecutor::startNextIfNoneRunning() { + if ( m_queue.isEmpty() || m_runningJob ) + return; + QPointer next; + while ( !next && !m_queue.isEmpty() ) { + next = m_queue.first(); + m_queue.pop_front(); + } + if ( next ) { + connect( next, SIGNAL(finished(QKeychain::Job*)), this, SLOT(jobFinished(QKeychain::Job*)) ); + connect( next, SIGNAL(destroyed(QObject*)), this, SLOT(jobDestroyed(QObject*)) ); + m_runningJob = next; + if ( ReadPasswordJob* rpj = qobject_cast( m_runningJob ) ) + rpj->d->scheduledStart(); + else if ( WritePasswordJob* wpj = qobject_cast( m_runningJob) ) + wpj->d->scheduledStart(); + } +} + +void JobExecutor::jobDestroyed( QObject* object ) { + Q_UNUSED( object ) // for release mode + Q_ASSERT( object == m_runningJob ); + m_runningJob->disconnect( this ); + m_runningJob = 0; + startNextIfNoneRunning(); +} + +void JobExecutor::jobFinished( Job* job ) { + Q_UNUSED( job ) // for release mode + Q_ASSERT( job == m_runningJob ); + m_runningJob->disconnect( this ); + m_runningJob = 0; + startNextIfNoneRunning(); +} + +JobExecutor* JobExecutor::s_instance = 0; + +JobExecutor* JobExecutor::instance() { + if ( !s_instance ) + s_instance = new JobExecutor; + return s_instance; +} diff --git a/ext/qtkeychain/keychain.h b/ext/qtkeychain/keychain.h new file mode 100644 index 0000000..6ed5e95 --- /dev/null +++ b/ext/qtkeychain/keychain.h @@ -0,0 +1,145 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#ifndef KEYCHAIN_H +#define KEYCHAIN_H + +#include "qkeychain_export.h" + +#include +#include + +class QSettings; + +#define QTKEYCHAIN_VERSION 0x000100 + +namespace QKeychain { + +/** + * Error codes + */ +enum Error { + NoError=0, /**< No error occurred, operation was successful */ + EntryNotFound, /**< For the given key no data was found */ + CouldNotDeleteEntry, /**< Could not delete existing secret data */ + AccessDeniedByUser, /**< User denied access to keychain */ + AccessDenied, /**< Access denied for other reasons */ + NoBackendAvailable, /**< No platform-specific keychain service available */ + NotImplemented, /**< Not implemented on platform */ + OtherError /**< Something else went wrong (errorString() might provide details) */ +}; + +class JobExecutor; +class JobPrivate; + +class QKEYCHAIN_EXPORT Job : public QObject { + Q_OBJECT +public: + explicit Job( const QString& service, QObject* parent=0 ); + ~Job(); + + QSettings* settings() const; + void setSettings( QSettings* settings ); + + void start(); + + QString service() const; + + Error error() const; + QString errorString() const; + + bool autoDelete() const; + void setAutoDelete( bool autoDelete ); + + bool insecureFallback() const; + void setInsecureFallback( bool insecureFallback ); + +Q_SIGNALS: + void finished( QKeychain::Job* ); + +protected: + Q_INVOKABLE virtual void doStart() = 0; + + void setError( Error error ); + void setErrorString( const QString& errorString ); + void emitFinished(); + void emitFinishedWithError(Error, const QString& errorString); + +private: + JobPrivate* const d; +}; + +class ReadPasswordJobPrivate; + +class QKEYCHAIN_EXPORT ReadPasswordJob : public Job { + Q_OBJECT +public: + explicit ReadPasswordJob( const QString& service, QObject* parent=0 ); + ~ReadPasswordJob(); + + QString key() const; + void setKey( const QString& key ); + + QByteArray binaryData() const; + QString textData() const; + +protected: + void doStart(); + +private: + friend class QKeychain::ReadPasswordJobPrivate; + friend class QKeychain::JobExecutor; + ReadPasswordJobPrivate* const d; +}; + +class WritePasswordJobPrivate; + +class QKEYCHAIN_EXPORT WritePasswordJob : public Job { + Q_OBJECT +public: + explicit WritePasswordJob( const QString& service, QObject* parent=0 ); + ~WritePasswordJob(); + + QString key() const; + void setKey( const QString& key ); + + void setBinaryData( const QByteArray& data ); + void setTextData( const QString& data ); + +protected: + void doStart(); + +private: + friend class QKeychain::JobExecutor; + friend class QKeychain::WritePasswordJobPrivate; + friend class DeletePasswordJob; + WritePasswordJobPrivate* const d; +}; + +class DeletePasswordJobPrivate; + +class QKEYCHAIN_EXPORT DeletePasswordJob : public Job { + Q_OBJECT +public: + explicit DeletePasswordJob( const QString& service, QObject* parent=0 ); + ~DeletePasswordJob(); + + QString key() const; + void setKey( const QString& key ); + +protected: + void doStart(); + +private: + friend class QKeychain::DeletePasswordJobPrivate; + DeletePasswordJobPrivate* const d; +}; + +} // namespace QtKeychain + +#endif diff --git a/ext/qtkeychain/keychain_mac.cpp b/ext/qtkeychain/keychain_mac.cpp new file mode 100644 index 0000000..8a058cf --- /dev/null +++ b/ext/qtkeychain/keychain_mac.cpp @@ -0,0 +1,161 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain_p.h" + +#include +#include +#include + +using namespace QKeychain; + +template +struct Releaser { + explicit Releaser( const T& v ) : value( v ) {} + ~Releaser() { + CFRelease( value ); + } + + const T value; +}; + +static QString strForStatus( OSStatus os ) { + const Releaser str( SecCopyErrorMessageString( os, 0 ) ); + const char * const buf = CFStringGetCStringPtr( str.value, kCFStringEncodingUTF8 ); + if ( !buf ) + return QObject::tr( "%1 (OSStatus %2)" ) + .arg( "OSX Keychain Error" ).arg( os ); + return QObject::tr( "%1 (OSStatus %2)" ) + .arg( QString::fromUtf8( buf, strlen( buf ) ) ).arg( os ); +} + +static OSStatus readPw( QByteArray* pw, + const QString& service, + const QString& account, + SecKeychainItemRef* ref ) { + Q_ASSERT( pw ); + pw->clear(); + const QByteArray serviceData = service.toUtf8(); + const QByteArray accountData = account.toUtf8(); + + void* data = 0; + UInt32 len = 0; + + const OSStatus ret = SecKeychainFindGenericPassword( NULL, // default keychain + serviceData.size(), + serviceData.constData(), + accountData.size(), + accountData.constData(), + &len, + &data, + ref ); + if ( ret == noErr ) { + *pw = QByteArray( reinterpret_cast( data ), len ); + const OSStatus ret2 = SecKeychainItemFreeContent ( 0, data ); + if ( ret2 != noErr ) + qWarning() << "Could not free item content: " << strForStatus( ret2 ); + } + return ret; +} + +void ReadPasswordJobPrivate::scheduledStart() +{ + QString errorString; + Error error = NoError; + const OSStatus ret = readPw( &data, q->service(), q->key(), 0 ); + + switch ( ret ) { + case noErr: + break; + case errSecItemNotFound: + errorString = tr("Password not found"); + error = EntryNotFound; + break; + default: + errorString = strForStatus( ret ); + error = OtherError; + break; + } + q->emitFinishedWithError( error, errorString ); +} + + +static QKeychain::Error deleteEntryImpl( const QString& service, const QString& account, QString* err ) { + SecKeychainItemRef ref; + QByteArray pw; + const OSStatus ret1 = readPw( &pw, service, account, &ref ); + if ( ret1 == errSecItemNotFound ) + return NoError; // No item stored, we're done + if ( ret1 != noErr ) { + *err = strForStatus( ret1 ); + //TODO map error code, set errstr + return OtherError; + } + const Releaser releaser( ref ); + + const OSStatus ret2 = SecKeychainItemDelete( ref ); + + if ( ret2 == noErr ) + return NoError; + //TODO map error code + *err = strForStatus( ret2 ); + return CouldNotDeleteEntry; +} + +static QKeychain::Error writeEntryImpl( const QString& service, + const QString& account, + const QByteArray& data, + QString* err ) { + Q_ASSERT( err ); + err->clear(); + const QByteArray serviceData = service.toUtf8(); + const QByteArray accountData = account.toUtf8(); + const OSStatus ret = SecKeychainAddGenericPassword( NULL, //default keychain + serviceData.size(), + serviceData.constData(), + accountData.size(), + accountData.constData(), + data.size(), + data.constData(), + NULL //item reference + ); + if ( ret != noErr ) { + switch ( ret ) { + case errSecDuplicateItem: + { + Error derr = deleteEntryImpl( service, account, err ); + if ( derr != NoError ) + return CouldNotDeleteEntry; + else + return writeEntryImpl( service, account, data, err ); + } + default: + *err = strForStatus( ret ); + return OtherError; + } + } + + return NoError; +} + +void WritePasswordJobPrivate::scheduledStart() +{ + QString errorString; + Error error = NoError; + + if ( mode == Delete ) { + const Error derr = deleteEntryImpl( q->service(), key, &errorString ); + if ( derr != NoError ) + error = CouldNotDeleteEntry; + q->emitFinishedWithError( error, errorString ); + return; + } + const QByteArray data = mode == Text ? textData.toUtf8() : binaryData; + error = writeEntryImpl( q->service(), key, data, &errorString ); + q->emitFinishedWithError( error, errorString ); +} diff --git a/ext/qtkeychain/keychain_p.h b/ext/qtkeychain/keychain_p.h new file mode 100644 index 0000000..09c9b25 --- /dev/null +++ b/ext/qtkeychain/keychain_p.h @@ -0,0 +1,163 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#ifndef KEYCHAIN_P_H +#define KEYCHAIN_P_H + +#include +#include +#include +#include +#include + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + +#include + +#include "kwallet_interface.h" +#else + +class QDBusPendingCallWatcher; + +#endif + +#include "keychain.h" + +namespace QKeychain { + +class JobExecutor; + +class JobPrivate : public QObject { + Q_OBJECT +public: + JobPrivate( const QString& service_ ) + : error( NoError ) + , service( service_ ) + , autoDelete( true ) + , insecureFallback( false ) {} + + QKeychain::Error error; + QString errorString; + QString service; + bool autoDelete; + bool insecureFallback; + QPointer settings; +}; + +class ReadPasswordJobPrivate : public QObject { + Q_OBJECT +public: + explicit ReadPasswordJobPrivate( ReadPasswordJob* qq ) : q( qq ), walletHandle( 0 ), dataType( Text ) {} + void scheduledStart(); + + ReadPasswordJob* const q; + QByteArray data; + QString key; + int walletHandle; + enum DataType { + Binary, + Text + }; + DataType dataType; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + org::kde::KWallet* iface; + static void gnomeKeyring_cb( int result, const char* string, ReadPasswordJobPrivate* data ); + friend class QKeychain::JobExecutor; + void fallbackOnError(const QDBusError& err); + +private Q_SLOTS: + void kwalletWalletFound( QDBusPendingCallWatcher* watcher ); + void kwalletOpenFinished( QDBusPendingCallWatcher* watcher ); + void kwalletEntryTypeFinished( QDBusPendingCallWatcher* watcher ); + void kwalletReadFinished( QDBusPendingCallWatcher* watcher ); +#else //moc's too dumb to respect above macros, so just define empty slot implementations +private Q_SLOTS: + void kwalletWalletFound( QDBusPendingCallWatcher* ) {} + void kwalletOpenFinished( QDBusPendingCallWatcher* ) {} + void kwalletEntryTypeFinished( QDBusPendingCallWatcher* ) {} + void kwalletReadFinished( QDBusPendingCallWatcher* ) {} +#endif + +}; + +class WritePasswordJobPrivate : public QObject { + Q_OBJECT +public: + explicit WritePasswordJobPrivate( WritePasswordJob* qq ) : q( qq ), mode( Delete ) {} + void scheduledStart(); + + enum Mode { + Delete, + Text, + Binary + }; + + static QString modeToString(Mode m); + static Mode stringToMode(const QString& s); + + WritePasswordJob* const q; + Mode mode; + QString key; + QByteArray binaryData; + QString textData; + +#if defined(Q_OS_UNIX) && !defined(Q_OS_DARWIN) + org::kde::KWallet* iface; + static void gnomeKeyring_cb( int result, WritePasswordJobPrivate* self ); + friend class QKeychain::JobExecutor; + void fallbackOnError(const QDBusError& err); + +private Q_SLOTS: + void kwalletWalletFound( QDBusPendingCallWatcher* watcher ); + void kwalletOpenFinished( QDBusPendingCallWatcher* watcher ); + void kwalletWriteFinished( QDBusPendingCallWatcher* watcher ); +#else +private Q_SLOTS: + void kwalletWalletFound( QDBusPendingCallWatcher* ) {} + void kwalletOpenFinished( QDBusPendingCallWatcher* ) {} + void kwalletWriteFinished( QDBusPendingCallWatcher* ) {} +#endif +}; + +class DeletePasswordJobPrivate : public QObject { + Q_OBJECT +public: + explicit DeletePasswordJobPrivate( DeletePasswordJob* qq ) : q( qq ) {} + void doStart(); + DeletePasswordJob* const q; + QString key; +private Q_SLOTS: + void jobFinished( QKeychain::Job* ); +}; + +class JobExecutor : public QObject { + Q_OBJECT +public: + + static JobExecutor* instance(); + + void enqueue( Job* job ); + +private: + explicit JobExecutor(); + void startNextIfNoneRunning(); + +private Q_SLOTS: + void jobFinished( QKeychain::Job* ); + void jobDestroyed( QObject* object ); + +private: + static JobExecutor* s_instance; + Job* m_runningJob; + QVector > m_queue; +}; + +} + +#endif // KEYCHAIN_P_H diff --git a/ext/qtkeychain/keychain_unix.cpp b/ext/qtkeychain/keychain_unix.cpp new file mode 100644 index 0000000..48f4240 --- /dev/null +++ b/ext/qtkeychain/keychain_unix.cpp @@ -0,0 +1,510 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain_p.h" +#include "gnomekeyring_p.h" + +#include + +#include + +using namespace QKeychain; + +static QString typeKey( const QString& key ) +{ + return QString::fromLatin1( "%1/type" ).arg( key ); +} + +static QString dataKey( const QString& key ) +{ + return QString::fromLatin1( "%1/data" ).arg( key ); +} + +enum KeyringBackend { + Backend_GnomeKeyring, + Backend_Kwallet4, + Backend_Kwallet5 +}; + +enum DesktopEnvironment { + DesktopEnv_Gnome, + DesktopEnv_Kde4, + DesktopEnv_Plasma5, + DesktopEnv_Unity, + DesktopEnv_Xfce, + DesktopEnv_Other +}; + +// the following detection algorithm is derived from chromium, +// licensed under BSD, see base/nix/xdg_util.cc + +static DesktopEnvironment getKdeVersion() { + QString value = qgetenv("KDE_SESSION_VERSION"); + if ( value == "5" ) { + return DesktopEnv_Plasma5; + } else if (value == "4" ) { + return DesktopEnv_Kde4; + } else { + // most likely KDE3 + return DesktopEnv_Other; + } +} + +static DesktopEnvironment detectDesktopEnvironment() { + QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); + if ( xdgCurrentDesktop == "GNOME" ) { + return DesktopEnv_Gnome; + } else if ( xdgCurrentDesktop == "Unity" ) { + return DesktopEnv_Unity; + } else if ( xdgCurrentDesktop == "KDE" ) { + return getKdeVersion(); + } + + QByteArray desktopSession = qgetenv("DESKTOP_SESSION"); + if ( desktopSession == "gnome" ) { + return DesktopEnv_Gnome; + } else if ( desktopSession == "kde" ) { + return getKdeVersion(); + } else if ( desktopSession == "kde4" ) { + return DesktopEnv_Kde4; + } else if ( desktopSession.contains("xfce") || desktopSession == "xubuntu" ) { + return DesktopEnv_Xfce; + } + + if ( !qgetenv("GNOME_DESKTOP_SESSION_ID").isEmpty() ) { + return DesktopEnv_Gnome; + } else if ( !qgetenv("KDE_FULL_SESSION").isEmpty() ) { + return getKdeVersion(); + } + + return DesktopEnv_Other; +} + +static KeyringBackend detectKeyringBackend() +{ + switch (detectDesktopEnvironment()) { + case DesktopEnv_Kde4: + return Backend_Kwallet4; + break; + case DesktopEnv_Plasma5: + return Backend_Kwallet5; + break; + // fall through + case DesktopEnv_Gnome: + case DesktopEnv_Unity: + case DesktopEnv_Xfce: + case DesktopEnv_Other: + default: + if ( GnomeKeyring::isAvailable() ) { + return Backend_GnomeKeyring; + } else { + return Backend_Kwallet4; + } + } + +} + +static KeyringBackend getKeyringBackend() +{ + static KeyringBackend backend = detectKeyringBackend(); + return backend; +} + +static void kwalletReadPasswordScheduledStartImpl(const char * service, const char * path, ReadPasswordJobPrivate * priv) { + if ( QDBusConnection::sessionBus().isConnected() ) + { + priv->iface = new org::kde::KWallet( QLatin1String(service), QLatin1String(path), QDBusConnection::sessionBus(), priv ); + const QDBusPendingReply reply = priv->iface->networkWallet(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, priv ); + priv->connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), priv, SLOT(kwalletWalletFound(QDBusPendingCallWatcher*)) ); + } + else + { + // D-Bus is not reachable so none can tell us something about KWalletd + QDBusError err( QDBusError::NoServer, ReadPasswordJobPrivate::tr("D-Bus is not running") ); + priv->fallbackOnError( err ); + } +} + +void ReadPasswordJobPrivate::scheduledStart() { + switch ( getKeyringBackend() ) { + case Backend_GnomeKeyring: + if ( !GnomeKeyring::find_network_password( key.toUtf8().constData(), q->service().toUtf8().constData(), + reinterpret_cast( &ReadPasswordJobPrivate::gnomeKeyring_cb ), + this, 0 ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + break; + + case Backend_Kwallet4: + kwalletReadPasswordScheduledStartImpl("org.kde.kwalletd", "/modules/kwalletd", this); + break; + case Backend_Kwallet5: + kwalletReadPasswordScheduledStartImpl("org.kde.kwalletd5", "/modules/kwalletd5", this); + break; + } +} + +void ReadPasswordJobPrivate::kwalletWalletFound(QDBusPendingCallWatcher *watcher) +{ + watcher->deleteLater(); + const QDBusPendingReply reply = *watcher; + const QDBusPendingReply pendingReply = iface->open( reply.value(), 0, q->service() ); + QDBusPendingCallWatcher* pendingWatcher = new QDBusPendingCallWatcher( pendingReply, this ); + connect( pendingWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) ); +} + +static QPair mapGnomeKeyringError( int result ) +{ + Q_ASSERT( result != GnomeKeyring::RESULT_OK ); + + switch ( result ) { + case GnomeKeyring::RESULT_DENIED: + return qMakePair( AccessDenied, QObject::tr("Access to keychain denied") ); + case GnomeKeyring::RESULT_NO_KEYRING_DAEMON: + return qMakePair( NoBackendAvailable, QObject::tr("No keyring daemon") ); + case GnomeKeyring::RESULT_ALREADY_UNLOCKED: + return qMakePair( OtherError, QObject::tr("Already unlocked") ); + case GnomeKeyring::RESULT_NO_SUCH_KEYRING: + return qMakePair( OtherError, QObject::tr("No such keyring") ); + case GnomeKeyring::RESULT_BAD_ARGUMENTS: + return qMakePair( OtherError, QObject::tr("Bad arguments") ); + case GnomeKeyring::RESULT_IO_ERROR: + return qMakePair( OtherError, QObject::tr("I/O error") ); + case GnomeKeyring::RESULT_CANCELLED: + return qMakePair( OtherError, QObject::tr("Cancelled") ); + case GnomeKeyring::RESULT_KEYRING_ALREADY_EXISTS: + return qMakePair( OtherError, QObject::tr("Keyring already exists") ); + case GnomeKeyring::RESULT_NO_MATCH: + return qMakePair( EntryNotFound, QObject::tr("No match") ); + default: + break; + } + + return qMakePair( OtherError, QObject::tr("Unknown error") ); +} + +void ReadPasswordJobPrivate::gnomeKeyring_cb( int result, const char* string, ReadPasswordJobPrivate* self ) +{ + if ( result == GnomeKeyring::RESULT_OK ) { + if ( self->dataType == ReadPasswordJobPrivate::Text ) + self->data = string; + else + self->data = QByteArray::fromBase64( string ); + self->q->emitFinished(); + } else { + const QPair errorResult = mapGnomeKeyringError( result ); + self->q->emitFinishedWithError( errorResult.first, errorResult.second ); + } +} + +void ReadPasswordJobPrivate::fallbackOnError(const QDBusError& err ) +{ + QScopedPointer local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.data(); + + if ( q->insecureFallback() && actual->contains( dataKey( key ) ) ) { + + const WritePasswordJobPrivate::Mode mode = WritePasswordJobPrivate::stringToMode( actual->value( typeKey( key ) ).toString() ); + if (mode == WritePasswordJobPrivate::Binary) + dataType = Binary; + else + dataType = Text; + data = actual->value( dataKey( key ) ).toByteArray(); + + q->emitFinished(); + } else { + if ( err.type() == QDBusError::ServiceUnknown ) //KWalletd not running + q->emitFinishedWithError( NoBackendAvailable, tr("No keychain service available") ); + else + q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + } +} + +void ReadPasswordJobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + const QDBusPendingReply reply = *watcher; + + QScopedPointer local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.data(); + + if ( reply.isError() ) { + fallbackOnError( reply.error() ); + return; + } + + if ( actual->contains( dataKey( key ) ) ) { + // We previously stored data in the insecure QSettings, but now have KWallet available. + // Do the migration + + data = actual->value( dataKey( key ) ).toByteArray(); + const WritePasswordJobPrivate::Mode mode = WritePasswordJobPrivate::stringToMode( actual->value( typeKey( key ) ).toString() ); + actual->remove( key ); + + q->emitFinished(); + + + WritePasswordJob* j = new WritePasswordJob( q->service(), 0 ); + j->setSettings( q->settings() ); + j->setKey( key ); + j->setAutoDelete( true ); + if ( mode == WritePasswordJobPrivate::Binary ) + j->setBinaryData( data ); + else if ( mode == WritePasswordJobPrivate::Text ) + j->setTextData( QString::fromUtf8( data ) ); + else + Q_ASSERT( false ); + + j->start(); + + return; + } + + walletHandle = reply.value(); + + if ( walletHandle < 0 ) { + q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") ); + return; + } + + const QDBusPendingReply nextReply = iface->entryType( walletHandle, q->service(), key, q->service() ); + QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this ); + connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletEntryTypeFinished(QDBusPendingCallWatcher*)) ); +} + +//Must be in sync with KWallet::EntryType (kwallet.h) +enum KWalletEntryType { + Unknown=0, + Password, + Stream, + Map +}; + +void ReadPasswordJobPrivate::kwalletEntryTypeFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + if ( watcher->isError() ) { + const QDBusError err = watcher->error(); + q->emitFinishedWithError( OtherError, tr("Could not determine data type: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + return; + } + + const QDBusPendingReply reply = *watcher; + const int value = reply.value(); + + switch ( value ) { + case Unknown: + q->emitFinishedWithError( EntryNotFound, tr("Entry not found") ); + return; + case Password: + dataType = Text; + break; + case Stream: + dataType = Binary; + break; + case Map: + q->emitFinishedWithError( EntryNotFound, tr("Unsupported entry type 'Map'") ); + return; + default: + q->emitFinishedWithError( OtherError, tr("Unknown kwallet entry type '%1'").arg( value ) ); + return; + } + + const QDBusPendingCall nextReply = dataType == Text + ? QDBusPendingCall( iface->readPassword( walletHandle, q->service(), key, q->service() ) ) + : QDBusPendingCall( iface->readEntry( walletHandle, q->service(), key, q->service() ) ); + QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this ); + connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletReadFinished(QDBusPendingCallWatcher*)) ); +} + +void ReadPasswordJobPrivate::kwalletReadFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + if ( watcher->isError() ) { + const QDBusError err = watcher->error(); + q->emitFinishedWithError( OtherError, tr("Could not read password: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + return; + } + + if ( dataType == Binary ) { + QDBusPendingReply reply = *watcher; + data = reply.value(); + } else { + QDBusPendingReply reply = *watcher; + data = reply.value().toUtf8(); + } + q->emitFinished(); +} + +static void kwalletWritePasswordScheduledStart( const char * service, const char * path, WritePasswordJobPrivate * priv ) { + if ( QDBusConnection::sessionBus().isConnected() ) + { + priv->iface = new org::kde::KWallet( QLatin1String(service), QLatin1String(path), QDBusConnection::sessionBus(), priv ); + const QDBusPendingReply reply = priv->iface->networkWallet(); + QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher( reply, priv ); + priv->connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), priv, SLOT(kwalletWalletFound(QDBusPendingCallWatcher*)) ); + } + else + { + // D-Bus is not reachable so none can tell us something about KWalletd + QDBusError err( QDBusError::NoServer, WritePasswordJobPrivate::tr("D-Bus is not running") ); + priv->fallbackOnError( err ); + } +} + +void WritePasswordJobPrivate::scheduledStart() { + switch ( getKeyringBackend() ) { + case Backend_GnomeKeyring: + if ( mode == WritePasswordJobPrivate::Delete ) { + if ( !GnomeKeyring::delete_network_password( key.toUtf8().constData(), q->service().toUtf8().constData(), + reinterpret_cast( &WritePasswordJobPrivate::gnomeKeyring_cb ), + this, 0 ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } else { + QByteArray password = mode == WritePasswordJobPrivate::Text ? textData.toUtf8() : binaryData.toBase64(); + QByteArray service = q->service().toUtf8(); + if ( !GnomeKeyring::store_network_password( GnomeKeyring::GNOME_KEYRING_DEFAULT, service.constData(), + key.toUtf8().constData(), service.constData(), password.constData(), + reinterpret_cast( &WritePasswordJobPrivate::gnomeKeyring_cb ), + this, 0 ) ) + q->emitFinishedWithError( OtherError, tr("Unknown error") ); + } + break; + + case Backend_Kwallet4: + kwalletWritePasswordScheduledStart("org.kde.kwalletd", "/modules/kwalletd", this); + break; + case Backend_Kwallet5: + kwalletWritePasswordScheduledStart("org.kde.kwalletd5", "/modules/kwalletd5", this); + break; + } +} + +QString WritePasswordJobPrivate::modeToString(Mode m) +{ + switch (m) { + case Delete: + return QLatin1String("Delete"); + case Text: + return QLatin1String("Text"); + case Binary: + return QLatin1String("Binary"); + } + + Q_ASSERT_X(false, Q_FUNC_INFO, "Unhandled Mode value"); + return QString(); +} + +WritePasswordJobPrivate::Mode WritePasswordJobPrivate::stringToMode(const QString& s) +{ + if (s == QLatin1String("Delete") || s == QLatin1String("0")) + return Delete; + if (s == QLatin1String("Text") || s == QLatin1String("1")) + return Text; + if (s == QLatin1String("Binary") || s == QLatin1String("2")) + return Binary; + + qCritical("Unexpected mode string '%s'", qPrintable(s)); + + return Text; +} + +void WritePasswordJobPrivate::fallbackOnError(const QDBusError &err) +{ + QScopedPointer local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.data(); + + if ( !q->insecureFallback() ) { + q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + return; + } + + if ( mode == Delete ) { + actual->remove( key ); + actual->sync(); + + q->emitFinished(); + return; + } + + actual->setValue( QString::fromLatin1( "%1/type" ).arg( key ), mode ); + if ( mode == Text ) + actual->setValue( QString::fromLatin1( "%1/data" ).arg( key ), textData.toUtf8() ); + else if ( mode == Binary ) + actual->setValue( QString::fromLatin1( "%1/data" ).arg( key ), binaryData ); + actual->sync(); + + q->emitFinished(); +} + +void WritePasswordJobPrivate::gnomeKeyring_cb( int result, WritePasswordJobPrivate* self ) +{ + if ( result == GnomeKeyring::RESULT_OK ) { + self->q->emitFinished(); + } else { + const QPair errorResult = mapGnomeKeyringError( result ); + self->q->emitFinishedWithError( errorResult.first, errorResult.second ); + } +} + +void WritePasswordJobPrivate::kwalletWalletFound(QDBusPendingCallWatcher *watcher) +{ + watcher->deleteLater(); + const QDBusPendingReply reply = *watcher; + const QDBusPendingReply pendingReply = iface->open( reply.value(), 0, q->service() ); + QDBusPendingCallWatcher* pendingWatcher = new QDBusPendingCallWatcher( pendingReply, this ); + connect( pendingWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletOpenFinished(QDBusPendingCallWatcher*)) ); +} + +void WritePasswordJobPrivate::kwalletOpenFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + + QScopedPointer local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.data(); + + if ( reply.isError() ) { + fallbackOnError( reply.error() ); + return; + } + + if ( actual->contains( key ) ) + { + // If we had previously written to QSettings, but we now have a kwallet available, migrate and delete old insecure data + actual->remove( key ); + actual->sync(); + } + + const int handle = reply.value(); + + if ( handle < 0 ) { + q->emitFinishedWithError( AccessDenied, tr("Access to keychain denied") ); + return; + } + + QDBusPendingReply nextReply; + + if ( !textData.isEmpty() ) + nextReply = iface->writePassword( handle, q->service(), key, textData, q->service() ); + else if ( !binaryData.isEmpty() ) + nextReply = iface->writeEntry( handle, q->service(), key, binaryData, q->service() ); + else + nextReply = iface->removeEntry( handle, q->service(), key, q->service() ); + + QDBusPendingCallWatcher* nextWatcher = new QDBusPendingCallWatcher( nextReply, this ); + connect( nextWatcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, SLOT(kwalletWriteFinished(QDBusPendingCallWatcher*)) ); +} + +void WritePasswordJobPrivate::kwalletWriteFinished( QDBusPendingCallWatcher* watcher ) { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + if ( reply.isError() ) { + const QDBusError err = reply.error(); + q->emitFinishedWithError( OtherError, tr("Could not open wallet: %1; %2").arg( QDBusError::errorString( err.type() ), err.message() ) ); + return; + } + + q->emitFinished(); +} diff --git a/ext/qtkeychain/keychain_win.cpp b/ext/qtkeychain/keychain_win.cpp new file mode 100644 index 0000000..e2df4b8 --- /dev/null +++ b/ext/qtkeychain/keychain_win.cpp @@ -0,0 +1,107 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include "keychain_p.h" + +#include + +#include +#include + +#include + +using namespace QKeychain; + +void ReadPasswordJobPrivate::scheduledStart() { + //Use settings member if there, create local settings object if not + std::auto_ptr local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.get(); + + QByteArray encrypted = actual->value( key ).toByteArray(); + if ( encrypted.isNull() ) { + q->emitFinishedWithError( EntryNotFound, tr("Entry not found") ); + return; + } + + DATA_BLOB blob_in, blob_out; + + blob_in.pbData = reinterpret_cast( encrypted.data() ); + blob_in.cbData = encrypted.size(); + + const BOOL ret = CryptUnprotectData( &blob_in, + NULL, + NULL, + NULL, + NULL, + 0, + &blob_out ); + if ( !ret ) { + q->emitFinishedWithError( OtherError, tr("Could not decrypt data") ); + return; + } + + data = QByteArray( reinterpret_cast( blob_out.pbData ), blob_out.cbData ); + SecureZeroMemory( blob_out.pbData, blob_out.cbData ); + LocalFree( blob_out.pbData ); + + q->emitFinished(); +} + +void WritePasswordJobPrivate::scheduledStart() { + if ( mode == Delete ) { + //Use settings member if there, create local settings object if not + std::auto_ptr local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.get(); + actual->remove( key ); + actual->sync(); + if ( actual->status() != QSettings::NoError ) { + const QString err = actual->status() == QSettings::AccessError + ? tr("Could not delete encrypted data from settings: access error") + : tr("Could not delete encrypted data from settings: format error"); + q->emitFinishedWithError( OtherError, err ); + } else { + q->emitFinished(); + } + return; + } + + QByteArray data = mode == Binary ? binaryData : textData.toUtf8(); + DATA_BLOB blob_in, blob_out; + blob_in.pbData = reinterpret_cast( data.data() ); + blob_in.cbData = data.size(); + const BOOL res = CryptProtectData( &blob_in, + L"QKeychain-encrypted data", + NULL, + NULL, + NULL, + 0, + &blob_out ); + if ( !res ) { + q->emitFinishedWithError( OtherError, tr("Encryption failed") ); //TODO more details available? + return; + } + + const QByteArray encrypted( reinterpret_cast( blob_out.pbData ), blob_out.cbData ); + LocalFree( blob_out.pbData ); + + //Use settings member if there, create local settings object if not + std::auto_ptr local( !q->settings() ? new QSettings( q->service() ) : 0 ); + QSettings* actual = q->settings() ? q->settings() : local.get(); + actual->setValue( key, encrypted ); + actual->sync(); + if ( actual->status() != QSettings::NoError ) { + + const QString errorString = actual->status() == QSettings::AccessError + ? tr("Could not store encrypted data in settings: access error") + : tr("Could not store encrypted data in settings: format error"); + q->emitFinishedWithError( OtherError, errorString ); + return; + } + + q->emitFinished(); +} diff --git a/ext/qtkeychain/org.kde.KWallet.xml b/ext/qtkeychain/org.kde.KWallet.xml new file mode 100644 index 0000000..548c2f8 --- /dev/null +++ b/ext/qtkeychain/org.kde.KWallet.xml @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/qtkeychain/qkeychain_export.h b/ext/qtkeychain/qkeychain_export.h new file mode 100644 index 0000000..1bb1669 --- /dev/null +++ b/ext/qtkeychain/qkeychain_export.h @@ -0,0 +1,17 @@ +#ifndef QKEYCHAIN_EXPORT_H +#define QKEYCHAIN_EXPORT_H + +#include + +# ifdef QKEYCHAIN_STATICLIB +# undef QKEYCHAIN_SHAREDLIB +# define QKEYCHAIN_EXPORT +# else +# ifdef QKEYCHAIN_BUILD_QKEYCHAIN_LIB +# define QKEYCHAIN_EXPORT Q_DECL_EXPORT +# else +# define QKEYCHAIN_EXPORT Q_DECL_IMPORT +# endif +# endif + +#endif diff --git a/ext/qtkeychain/testclient.cpp b/ext/qtkeychain/testclient.cpp new file mode 100644 index 0000000..a9d9bad --- /dev/null +++ b/ext/qtkeychain/testclient.cpp @@ -0,0 +1,98 @@ +/****************************************************************************** + * Copyright (C) 2011-2015 Frank Osterfeld * + * * + * 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. For licensing and distribution * + * details, check the accompanying file 'COPYING'. * + *****************************************************************************/ +#include +#include + +#include "keychain.h" +#include + +using namespace QKeychain; + +static int printUsage() { + std::cerr << "testclient store " << std::endl; + std::cerr << "testclient restore " << std::endl; + std::cerr << "testclient delete " << std::endl; + return 1; +} + +int main( int argc, char** argv ) { + QCoreApplication app( argc, argv ); + const QStringList args = app.arguments(); + if ( args.count() < 2 ) + return printUsage(); + + QStringList::ConstIterator it = args.constBegin(); + ++it; + + if ( *it == QLatin1String("store") ) { + if ( ++it == args.constEnd() ) + return printUsage(); + const QString acc = *it; + if ( ++it == args.constEnd() ) + return printUsage(); + const QString pass = *it; + if ( ++it != args.constEnd() ) + return printUsage(); + WritePasswordJob job( QLatin1String("qtkeychain-testclient") ); + job.setAutoDelete( false ); + job.setKey( acc ); + job.setTextData( pass ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + if ( job.error() ) { + std::cerr << "Storing password failed: " << qPrintable(job.errorString()) << std::endl; + return 1; + } + std::cout << "Password stored successfully" << std::endl; + } else if ( *it == QLatin1String("restore") ) { + if ( ++it == args.constEnd() ) + return printUsage(); + const QString acc = *it; + if ( ++it != args.constEnd() ) + return printUsage(); + ReadPasswordJob job( QLatin1String("qtkeychain-testclient") ); + job.setAutoDelete( false ); + job.setKey( acc ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + + const QString pw = job.textData(); + if ( job.error() ) { + std::cerr << "Restoring password failed: " << qPrintable(job.errorString()) << std::endl; + return 1; + } + std::cout << qPrintable(pw) << std::endl; + } else if ( *it == QLatin1String("delete") ) { + if ( ++it == args.constEnd() ) + return printUsage(); + const QString acc = *it; + if ( ++it != args.constEnd() ) + return printUsage(); + DeletePasswordJob job( QLatin1String("qtkeychain-testclient") ); + job.setAutoDelete( false ); + job.setKey( acc ); + QEventLoop loop; + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.start(); + loop.exec(); + + if ( job.error() ) { + std::cerr << "Deleting password failed: " << qPrintable(job.errorString()) << std::endl; + return 1; + } + std::cout << "Password deleted successfully" << std::endl; + } else { + return printUsage(); + } +} + diff --git a/ext/qtkeychain/translations/qtkeychain_de.ts b/ext/qtkeychain/translations/qtkeychain_de.ts new file mode 100644 index 0000000..cd8aad0 --- /dev/null +++ b/ext/qtkeychain/translations/qtkeychain_de.ts @@ -0,0 +1,177 @@ + + + + + QKeychain::ReadPasswordJobPrivate + + + Unknown error + Unbekannter Fehler + + + + D-Bus is not running + + + + + No keychain service available + Kein Schlüsselbund-Dienst verfügbar + + + + Could not open wallet: %1; %2 + Konnte Brieftasche nicht öffnen: %1; %2 + + + + Access to keychain denied + Zugriff auf Schlüsselbund verweigert + + + + Could not determine data type: %1; %2 + Datentyp kann nicht ermittelt werden: %1: %2 + + + + Unsupported entry type 'Map' + + + + + Unknown kwallet entry type '%1' + + + + + Could not read password: %1; %2 + Passwort konnte nicht ausgelesen werden: %1; %2 + + + + Password not found + Passwort nicht gefunden + + + + + Entry not found + Eintrag nicht gefunden + + + + Could not decrypt data + Kann Daten nicht entschlüsseln + + + + QKeychain::WritePasswordJobPrivate + + + + Unknown error + Unbekannter Fehler + + + + D-Bus is not running + + + + + + Could not open wallet: %1; %2 + Konnte Brieftasche nicht öffnen: %1; %2 + + + + Access to keychain denied + Zugriff auf Schlüsselbund verweigert + + + + Could not delete encrypted data from settings: access error + Kann verschlüsselte Daten nicht aus den Einstellungen entfernen: Zugriffsfehler + + + + Could not delete encrypted data from settings: format error + Kann verschlüsselte Daten nicht aus den Einstellungen entfernen: Formatfehler + + + + Encryption failed + Verschlüsselung fehlgeschlagen + + + + Could not store encrypted data in settings: access error + Kann verschlüsselte Daten nicht in den Einstellungen speichern: Zugriffsfehler + + + + Could not store encrypted data in settings: format error + Kann verschlüsselte Daten nicht in den Einstellungen speichern: Formatfehler + + + + QObject + + + Access to keychain denied + Zugriff auf Schlüsselbund verweigert + + + + No keyring daemon + Kein Schlüsselbund-Dienst + + + + Already unlocked + Bereits entsperrt + + + + No such keyring + Kein solcher Schlüsselbund + + + + Bad arguments + Ungültige Argumente + + + + I/O error + Ein-/Ausgabe-Fehler + + + + Cancelled + Abgebrochen + + + + Keyring already exists + Schlüsselbund existiert bereits + + + + No match + Kein Treffer + + + + Unknown error + Unbekannter Fehler + + + + + %1 (OSStatus %2) + + + + diff --git a/ext/qtkeychain/translations/qtkeychain_ro.ts b/ext/qtkeychain/translations/qtkeychain_ro.ts new file mode 100644 index 0000000..9fa3494 --- /dev/null +++ b/ext/qtkeychain/translations/qtkeychain_ro.ts @@ -0,0 +1,178 @@ + + + + + QKeychain::ReadPasswordJobPrivate + + + Unknown error + Eroare necunoscută + + + + D-Bus is not running + D-Bus nu rulează + + + + No keychain service available + Nu există niciun serviciu de chei disponibil + Kein Schlüsselbund-Dienst verfügbar + + + + Could not open wallet: %1; %2 + Nu se poate deschide portofelul: %1; %2 + + + + Access to keychain denied + Acces interzis la serviciul de chei + + + + Could not determine data type: %1; %2 + Nu se poate stabili tipul de date: %1: %2 + + + + Unsupported entry type 'Map' + Tip de înregistrare nesuportat 'Map' + + + + Unknown kwallet entry type '%1' + Tip de înregistrare kwallet necunoscut '%1' + + + + Could not read password: %1; %2 + Nu se poate citi parola: %1; %2 + + + + Password not found + Parola nu a fost găsită + + + + + Entry not found + Înregistrarea nu a fost găsită + + + + Could not decrypt data + Nu se poate decripta data + + + + QKeychain::WritePasswordJobPrivate + + + + Unknown error + Eroare necunoscută + + + + D-Bus is not running + D-Bus nu rulează + + + + + Could not open wallet: %1; %2 + Nu se poate deschide portofelul: %1; %2 + + + + Access to keychain denied + Acces interzis la serviciul de chei + + + + Could not delete encrypted data from settings: access error + Nu se pot șterge datele criptate din setări: eroare de acces + + + + Could not delete encrypted data from settings: format error + Nu se pot șterge datele criptate din setări: eroare de format + + + + Encryption failed + Criptarea a eșuat + + + + Could not store encrypted data in settings: access error + Nu se pot stoca datele criptate în setări: eroare de acces + + + + Could not store encrypted data in settings: format error + Nu se pot stoca datele criptate în setări: eroare de format + + + + QObject + + + Access to keychain denied + Acces interzis la serviciul de chei + + + + No keyring daemon + Niciun demon pentru inelul de chei + + + + Already unlocked + Deja deblocat + + + + No such keyring + Nu există astfel de inel de chei + + + + Bad arguments + Argumente greșite + + + + I/O error + Eroare de I/E + + + + Cancelled + Anulat + + + + Keyring already exists + Inelul de chei deja există + + + + No match + Nicio potrivire + + + + Unknown error + Eroare necunoscută + + + + + %1 (OSStatus %2) + %1 (OSStatus %2) + + + diff --git a/fuel.pro b/fuel.pro index f606f86..40d3c05 100644 --- a/fuel.pro +++ b/fuel.pro @@ -58,7 +58,8 @@ SOURCES += src/main.cpp\ src/Fossil.cpp \ src/Workspace.cpp \ src/SearchBox.cpp \ - src/Settings.cpp + src/Settings.cpp \ + src/RemoteDialog.cpp HEADERS += src/MainWindow.h \ src/CommitDialog.h \ @@ -75,7 +76,8 @@ HEADERS += src/MainWindow.h \ src/Fossil.h \ src/Workspace.h \ src/SearchBox.h \ - src/Settings.h + src/Settings.h \ + src/RemoteDialog.h FORMS += ui/MainWindow.ui \ ui/CommitDialog.ui \ @@ -84,11 +86,40 @@ FORMS += ui/MainWindow.ui \ ui/FslSettingsDialog.ui \ ui/CloneDialog.ui \ ui/BrowserWidget.ui \ - ui/RevisionDialog.ui + ui/RevisionDialog.ui \ + ui/RemoteDialog.ui RESOURCES += \ rsrc/resources.qrc +# QtKeychain +SOURCES += ext/qtkeychain/keychain.cpp + +HEADERS += ext/qtkeychain/keychain.h \ + ext/qtkeychain/keychain_p.h \ + ext/qtkeychain/qkeychain_export.h + +unix:!macx { + QT += dbus + + SOURCES += ext/qtkeychain/keychain_unix.cpp \ + ext/qtkeychain/gnomekeyring.cpp + + HEADERS += ext/qtkeychain/gnomekeyring_p.h + DBUS_INTERFACES += ext/qtkeychain/org.kde.KWallet.xml +} + +macx { + SOURCES += ext/qtkeychain/keychain_mac.cpp +} + +win32 { + SOURCES += ext/qtkeychain/keychain_win.cpp +} + + + + CODECFORTR = UTF-8 TRANSLATIONS += \ diff --git a/manifest b/manifest index 404c4e0..95853c3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merged\snew\sworkspace -D 2015-05-26T18:03:05.443 +C Merged\sremote-management +D 2015-05-29T19:26:31.501 F .travis.yml 77966888a81c4ceee1fcc79bce842c9667ad8a35 F debian/changelog eb4304dfcb6bb66850ec740838090eb50ce1249b F debian/compat b6abd567fa79cbe0196d093a067271361dc6ca8b @@ -15,7 +15,29 @@ F dist/win/fuel.iss ef3558dbba409eb194938b930377fc9ee27d319e F doc/Building.txt 17b43fa23da764b5d1b828cc48c5a95e612bbd8f F doc/Changes.txt b03302545e4a6c0b16a30d623a7627f8aef65ef6 F doc/License.txt 4cc77b90af91e615a64ae04893fdffa7939db84c -F fuel.pro b010c4ee3093112003a9d27045927efce5985dab +F ext/qtkeychain/CMakeLists.txt fc1afa05034f2765ba243ce758a7e9d6b6efe2d6 +F ext/qtkeychain/COPYING d0f83c8198fdd5464d2373015b7b64ce7cae607e +F ext/qtkeychain/ChangeLog 1703279e17036995806ba1719033d14840a8a7e2 +F ext/qtkeychain/QtKeychainBuildTreeSettings.cmake.in a50c3b646181124f15b946c3297f13012e959341 +F ext/qtkeychain/QtKeychainConfig.cmake.in ac7c87e54854a06c51e00f833f21f8323d1e6884 +F ext/qtkeychain/QtKeychainConfigVersion.cmake.in 3b650037d5775f28802c0471afe2cf6dbe51084e +F ext/qtkeychain/ReadMe.markdown 65fe7f400600aa98a9a7fa5c3fc842ad8699cc43 +F ext/qtkeychain/ReadMe.txt 65fe7f400600aa98a9a7fa5c3fc842ad8699cc43 +F ext/qtkeychain/cmake/Modules/GNUInstallDirs.cmake 7a2ccf81f25546e93a6f48c792cdebaae51857e9 +F ext/qtkeychain/gnomekeyring.cpp 7fa97bd4ffb7c9df00d25e56cd9173d34109afe6 +F ext/qtkeychain/gnomekeyring_p.h 7f6acaf6d00b36bd08f5f31ba9136efa969e9875 +F ext/qtkeychain/keychain.cpp 427cbda7c6a76de995b1f1b4caa700cd06a9d19a +F ext/qtkeychain/keychain.h f084c671b481af6ac7ce00bf641055a3cfc9cf9b +F ext/qtkeychain/keychain_mac.cpp a028f6fc5e40f9ab88c94ebe30b8b0ae417f2f34 +F ext/qtkeychain/keychain_p.h 36f4caee2cbdbde971a1105ab388681ad2924665 +F ext/qtkeychain/keychain_unix.cpp 8e657da9acd9e86b2fdec19dc40f1afa4a1c5191 +F ext/qtkeychain/keychain_win.cpp e52877828703650219c1c674e618c7211f588d0d +F ext/qtkeychain/org.kde.KWallet.xml f3729fda9f8fa8031a6f69415dcc29455c3c9ae6 +F ext/qtkeychain/qkeychain_export.h d756528188ef9bf3c4461ecc80048f06c362b54b +F ext/qtkeychain/testclient.cpp cb1290a9584b627306a7bfdf1c02a8bbae503f5f +F ext/qtkeychain/translations/qtkeychain_de.ts 0a70c8205c066c30ed8172f0670942de1fc5eede +F ext/qtkeychain/translations/qtkeychain_ro.ts f16939382fd1a047b0692426bc82847347f14b32 +F fuel.pro d67b78b5257d5d39bf4d0c2a4b9fbba1dbdd7005 F intl/convert.bat 4222ae403418381452b843929d15259ea9850ab1 x F intl/convert.sh 2ca2179ff53e727f241925b75e19182607883c45 x F intl/de_DE.ts e2faceab920ac60c97bbc6fba038e261d51fc741 @@ -196,26 +218,28 @@ F src/FileActionDialog.cpp fcaebf9986f789b3440d5390b3458ad5f86fe0c8 F src/FileActionDialog.h 15db1650b3a13d70bc338371e4c033c66e3b79ce F src/FileTableView.cpp 5ddf8c391c9a3ac449ec61fb1db837b577afeec2 F src/FileTableView.h 03e56d87c2d46411b9762b87f4d301619aaf18df -F src/Fossil.cpp 0d4c50327a61c48506d2d45e28cd6f71f1697ea2 -F src/Fossil.h 31765ef57e20a860914372d56c024033b30aa765 -F src/FslSettingsDialog.cpp f5a34a70ecb0560d2b6eea6bf27e42048548aedd +F src/Fossil.cpp 2b03aeec20149a1b89f10118058fac8ca33425fc +F src/Fossil.h e706992b331385660d57df6a27e5418342c14e19 +F src/FslSettingsDialog.cpp 2531d3709f0eab66651671e3edead2ca720d07d5 F src/FslSettingsDialog.h dfe2a61884a55a74cbb9206b6f6b482b979725e7 F src/LoggedProcess.cpp 2a1e5c94bc1e57c8984563e66c210e43a14dc60c F src/LoggedProcess.h 85df7c635c807a5a0e8c4763f17a0752aaff7261 -F src/MainWindow.cpp 4cbfa1fdf3092b97649711388576230e4808d50e -F src/MainWindow.h a848462f21423b5c1e0c218cab1805c308299607 +F src/MainWindow.cpp bba716022fd683843abc07be47fdca3e15391e1f +F src/MainWindow.h 1ecfb255c8e013cf4e2d07bfd485ed41d26c97c7 +F src/RemoteDialog.cpp 7f4272117080260c31c748e2ada3e33adc024826 +F src/RemoteDialog.h 5e0438c2bd7c79b1bb44bfbd58c2181b544a9e5d F src/RevisionDialog.cpp 51065c65a07c118dd1a7363da4a55a135d1c6c9c F src/RevisionDialog.h b718c3009342eaabad39c8a11a253a4e4fef7a73 F src/SearchBox.cpp d4209c575baa9933e1ce5ed376e785b289a145ba F src/SearchBox.h 0c78d3a68136dab3e0e71b83ae36f22bd2688ab2 -F src/Settings.cpp 6ab826273b9693bfcd65f0f59b550ae2aa3577f1 -F src/Settings.h 1ff8bb71e19949150e8caa4f5e5f13f8810e496b +F src/Settings.cpp 7a674604caa9d9f5ffb6b73d95745bde09525389 +F src/Settings.h 883ac5c0f38a6ac93b400b7b96447b017ee50c06 F src/SettingsDialog.cpp 25be4c351dd21ea9132321944f42dc0bc22fb128 F src/SettingsDialog.h b324dfd77ca3ad24fd83588aaf79a7e4c291e716 -F src/Utils.cpp c48e3316f3a0a5d735e8d4953710500358985e32 -F src/Utils.h c2a28611bd77fb35ea3dcf65fb60ed585f68aa3c -F src/Workspace.cpp f68a4ca05d1b7c5c345fbd89527691813593c663 -F src/Workspace.h d6649a3ae1cd0fbad55237030313e85530417271 +F src/Utils.cpp 09ba0fc6d6d293ebbd2f7c7283286b68d3cb04fb +F src/Utils.h e22c5d86722e3987894fa06bdee3d57597ff425e +F src/Workspace.cpp dd2dfb259fd917bd8434b5f00d438fdd9643242f +F src/Workspace.h 7ae2e63196433ae34864d182e49e3a2f0726fb78 F src/main.cpp d8c65ea5e54102e4989fef9fd8cfd4f13ef8a8f0 F tools/git-push.sh 62cc58434cae5b7bcd6bd9d4cce8b08739f31cd7 x F tools/pack.sh d7f38a498c4e9327fecd6a6e5ac27be270d43008 x @@ -223,12 +247,13 @@ F ui/BrowserWidget.ui 994ad9ea0e9f5815d6b1a27acc2f6f39164c507f F ui/CloneDialog.ui 4886e7d4f258ea8b852b5eefc860396e35145712 F ui/CommitDialog.ui aea77347eef82b6b591f31fb058a1bb96193c728 F ui/FileActionDialog.ui 89bb4dc2d0b8adcd41adcb11ec65f2028a09a12d -F ui/FslSettingsDialog.ui 042717833d8efea905b4fc380bad580be809717d -F ui/MainWindow.ui 2d36b1ba9886356802f2cddf12c4cabb5ee1acde +F ui/FslSettingsDialog.ui eb3d4cb764cab90b01e82922237d8c42d6ce1749 +F ui/MainWindow.ui 5857b45ed96fb027b6159e44742e9afaa3e89cfb +F ui/RemoteDialog.ui 95a4750d972ed8c49bb10b95db91ff16cfe2dd0b F ui/RevisionDialog.ui 27c3b98c665fec014a50cbf3352c0627f75e68cd F ui/SettingsDialog.ui 4c480cd595a32664d01c85bf74845c4282fc0068 -P e2ba8f0aba679a1c52d36daf3041cd139bd49579 7ebac37b3255e6721889c070b7327a96c63b6c97 -R 8f806c4029c039de40ed9c8c7d5c5dca -T +closed 7ebac37b3255e6721889c070b7327a96c63b6c97 +P 322729110f48bfb8c007dd611d47e411b389044a 1f10dd85e5d7e95539baaea140623673b19f12f2 +R ddb33050a72347b140cbfa284e3483f6 +T +closed 1f10dd85e5d7e95539baaea140623673b19f12f2 U kostas -Z 0150f2a9d78e86830f0a9f5b26438204 +Z a7be8a80a6ffb699e0de683c517f0113 diff --git a/manifest.uuid b/manifest.uuid index 0442ec3..5545d6d 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -322729110f48bfb8c007dd611d47e411b389044a \ No newline at end of file +4c246e5290d2cb3b1a90127fe953bffbbf77ea53 \ No newline at end of file diff --git a/src/Fossil.cpp b/src/Fossil.cpp index 148502e..76ff9c2 100644 --- a/src/Fossil.cpp +++ b/src/Fossil.cpp @@ -70,6 +70,10 @@ RepoStatus Fossil::getRepoStatus() } } + defaultRemoteUrl.clear(); + if(run_ok) + getRemoteUrl(defaultRemoteUrl); + return run_ok ? REPO_OK : REPO_NOT_FOUND; } @@ -128,15 +132,51 @@ bool Fossil::status(QStringList &result) } //------------------------------------------------------------------------------ -bool Fossil::pushRepository() +bool Fossil::pushRepository(const QUrl &url) { - return runFossil(QStringList() << "push"); + QStringList params; + params << "push"; + + int runFlags=RUNFLAGS_NONE; + + if(!url.isEmpty()) + { + params << url.toString(); + params << "--once"; + + QStringList log_params = params; + log_params[1] = url.toDisplayString(); + log_params.push_front("fossil"); + + runFlags = RUNFLAGS_SILENT_INPUT; + log(">"+log_params.join(" ")+"
", true); + } + + return runFossil(params, 0, runFlags); } //------------------------------------------------------------------------------ -bool Fossil::pullRepository() +bool Fossil::pullRepository(const QUrl &url) { - return runFossil(QStringList() << "pull"); + QStringList params; + params << "pull"; + + int runFlags=RUNFLAGS_NONE; + + if(!url.isEmpty()) + { + params << url.toString(); + params << "--once"; + + QStringList log_params = params; + log_params[1] = url.toDisplayString(); + log_params.push_front("fossil"); + + runFlags = RUNFLAGS_SILENT_INPUT; + log(">"+log_params.join(" ")+"
", true); + } + + return runFossil(params, 0, runFlags); } //------------------------------------------------------------------------------ @@ -361,20 +401,25 @@ bool Fossil::setFossilSetting(const QString& name, const QString& value, bool gl } //------------------------------------------------------------------------------ -bool Fossil::setRemoteUrl(const QString& url) +bool Fossil::setRemoteUrl(const QUrl& url) { - QString u = url; + QString u = url.toString(QUrl::FullyEncoded); if(url.isEmpty()) u = "off"; // Run as silent to avoid displaying credentials in the log - // FIXME: maybe use a QUrl instead - return runFossil(QStringList() << "remote-url" << u, 0, RUNFLAGS_SILENT_INPUT); + bool ok = runFossil(QStringList() << "remote-url" << u, 0, RUNFLAGS_SILENT_INPUT); + + // Retrieve default url + if(ok) + getRemoteUrl(defaultRemoteUrl); + + return ok; } //------------------------------------------------------------------------------ -bool Fossil::getRemoteUrl(QString& url) +bool Fossil::getRemoteUrl(QUrl& url) { url.clear(); @@ -382,8 +427,15 @@ bool Fossil::getRemoteUrl(QString& url) if(!runFossil(QStringList() << "remote-url", &out, RUNFLAGS_SILENT_ALL)) return false; + QString url_str; if(out.length()>0) - url = out[0].trimmed(); + url_str = out[0].trimmed(); + + if(url_str == "off") + url.clear(); + else + url.setUrl(url_str); + return true; } diff --git a/src/Fossil.h b/src/Fossil.h index 1925f3d..c2c75ac 100644 --- a/src/Fossil.h +++ b/src/Fossil.h @@ -4,7 +4,7 @@ class QStringList; #include #include -#include +#include #include "LoggedProcess.h" #include "Utils.h" @@ -81,8 +81,8 @@ public: bool openRepository(const QString &repositoryPath, const QString& workspacePath); bool newRepository(const QString &repositoryPath); bool closeRepository(); - bool pushRepository(); - bool pullRepository(); + bool pushRepository(const QUrl& url); + bool pullRepository(const QUrl& url); bool cloneRepository(const QString &repository, const QUrl &url, const QUrl &proxyUrl); bool undoRepository(QStringList& result, bool explainOnly); bool updateRepository(QStringList& result, const QString& revision, bool explainOnly); @@ -103,8 +103,8 @@ public: bool renameFile(const QString& beforePath, const QString& afterPath, bool renameLocal); bool getFossilSettings(QStringList& result); bool setFossilSetting(const QString &name, const QString &value, bool global); - bool setRemoteUrl(const QString &url); - bool getRemoteUrl(QString &url); + bool setRemoteUrl(const QUrl& url); + bool getRemoteUrl(QUrl &url); bool stashNew(const QStringList& fileList, const QString& name, bool revert); bool stashList(stashmap_t &stashes); @@ -128,6 +128,8 @@ public: const QString &getUIHttpPort() const { return fossilUIPort; } QString getUIHttpAddress() const; + const QUrl &getDefaultRemoteUrl() const { return defaultRemoteUrl; } + private: void log(const QString &text, bool isHTML=false) { @@ -144,6 +146,7 @@ private: QString repositoryFile; QString projectName; QString currentRevision; + QUrl defaultRemoteUrl; QStringList currentTags; LoggedProcess fossilUI; QString fossilUIPort; diff --git a/src/FslSettingsDialog.cpp b/src/FslSettingsDialog.cpp index 62de753..890b63c 100644 --- a/src/FslSettingsDialog.cpp +++ b/src/FslSettingsDialog.cpp @@ -20,7 +20,6 @@ FslSettingsDialog::FslSettingsDialog(QWidget *parent, Settings &_settings) : ui->lineProxy->setText(settings->GetFossilValue(FOSSIL_SETTING_PROXY_URL).toString()); // Repository Settings - ui->lineRemoteURL->setText(settings->GetFossilValue(FOSSIL_SETTING_REMOTE_URL).toString()); ui->lineIgnore->setText(settings->GetFossilValue(FOSSIL_SETTING_IGNORE_GLOB).toString()); ui->lineIgnoreCRNL->setText(settings->GetFossilValue(FOSSIL_SETTING_CRNL_GLOB).toString()); } @@ -47,7 +46,6 @@ void FslSettingsDialog::on_buttonBox_accepted() settings->SetFossilValue(FOSSIL_SETTING_GMERGE_CMD, ui->lineGMergeCommand->text()); settings->SetFossilValue(FOSSIL_SETTING_PROXY_URL, ui->lineProxy->text()); - settings->SetFossilValue(FOSSIL_SETTING_REMOTE_URL, ui->lineRemoteURL->text()); settings->SetFossilValue(FOSSIL_SETTING_IGNORE_GLOB, ui->lineIgnore->text()); settings->SetFossilValue(FOSSIL_SETTING_CRNL_GLOB, ui->lineIgnoreCRNL->text()); diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 09896a2..14b74f5 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -19,6 +19,7 @@ #include "FileActionDialog.h" #include "CloneDialog.h" #include "RevisionDialog.h" +#include "RemoteDialog.h" #include "Utils.h" #define REVISION_LATEST "Latest revision" @@ -58,7 +59,7 @@ struct WorkspaceItem TYPE_TAGS, TYPE_TAG, TYPE_REMOTES, - TYPE_SETTINGS + TYPE_REMOTE, }; WorkspaceItem() @@ -174,6 +175,16 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP menuBranches->addAction(ui->actionMergeBranch); menuBranches->addAction(ui->actionUpdate); + // RemotesMenu + menuRemotes = new QMenu(this); + menuRemotes->addAction(ui->actionPushRemote); + menuRemotes->addAction(ui->actionPullRemote); + menuRemotes->addAction(separator); + menuRemotes->addAction(ui->actionAddRemote); + menuRemotes->addAction(ui->actionDeleteRemote); + menuRemotes->addAction(ui->actionEditRemote); + menuRemotes->addAction(ui->actionSetDefaultRemote); + // Recent Workspaces // Locate a sequence of two separator actions in file menu QList file_actions = ui->menuFile->actions(); @@ -207,7 +218,6 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP ui->statusBar->insertPermanentWidget(1, lblTags); lblTags->setVisible(true); - // Construct ProgressBar progressBar = new QProgressBar(); progressBar->setMinimum(0); @@ -244,7 +254,6 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP searchShortcut->setEnabled(true); connect(searchShortcut, SIGNAL(activated()), this, SLOT(onSearch())); - // Create SearchBox searchBox = new SearchBox(this); searchBox->setPlaceholderText(tr("Find (%0)").arg(searchShortcut->key().toString())); @@ -277,6 +286,7 @@ MainWindow::MainWindow(Settings &_settings, QWidget *parent, QString *workspaceP MainWindow::~MainWindow() { stopUI(); + getWorkspace().storeWorkspace(*settings.GetStore()); updateSettings(); delete ui; @@ -291,24 +301,14 @@ const QString &MainWindow::getCurrentWorkspace() //----------------------------------------------------------------------------- void MainWindow::setCurrentWorkspace(const QString &workspace) { - if(workspace.isEmpty()) - { - fossil().setCurrentWorkspace(""); - return; - } - - QString new_workspace = QFileInfo(workspace).absoluteFilePath(); - - fossil().setCurrentWorkspace(new_workspace); - - addWorkspace(new_workspace); - - if(!QDir::setCurrent(new_workspace)) - QMessageBox::critical(this, tr("Error"), tr("Could not change current directory to '%0'").arg(new_workspace), QMessageBox::Ok ); + if(!getWorkspace().switchWorkspace(workspace, *settings.GetStore())) + QMessageBox::critical(this, tr("Error"), tr("Could not change current directory to '%0'").arg(workspace), QMessageBox::Ok ); + else + addWorkspaceHistory(fossil().getCurrentWorkspace()); } //------------------------------------------------------------------------------ -void MainWindow::addWorkspace(const QString &dir) +void MainWindow::addWorkspaceHistory(const QString &dir) { if(dir.isEmpty()) return; @@ -773,7 +773,6 @@ void MainWindow::updateWorkspaceView() { const QString &tag_name = it.key(); - QStandardItem *tag = new QStandardItem(getInternalIcon(":icons/icon-item-tag"), tag_name); tag->setData(WorkspaceItem(WorkspaceItem::TYPE_TAG, tag_name), ROLE_WORKSPACE_ITEM); @@ -787,7 +786,6 @@ void MainWindow::updateWorkspaceView() tags->appendRow(tag); } - // FIXME: Unique Icon name // Stashes QStandardItem *stashes = new QStandardItem(getInternalIcon(":icons/icon-action-repo-open"), tr("Stashes")); stashes->setData(WorkspaceItem(WorkspaceItem::TYPE_STASHES, ""), ROLE_WORKSPACE_ITEM); @@ -800,21 +798,26 @@ void MainWindow::updateWorkspaceView() stashes->appendRow(stash); } -#if 0 // Remotes QStandardItem *remotes = new QStandardItem(getInternalIcon(":icons/icon-item-remote"), tr("Remotes")); remotes->setData(WorkspaceItem(WorkspaceItem::TYPE_REMOTES, ""), ROLE_WORKSPACE_ITEM); remotes->setEditable(false); getWorkspace().getTreeModel().appendRow(remotes); -#endif + for(remote_map_t::const_iterator it=getWorkspace().getRemotes().begin(); it!=getWorkspace().getRemotes().end(); ++it) + { + QStandardItem *remote_item = new QStandardItem(getInternalIcon(":icons/icon-item-remote"), it->name); + remote_item->setData(WorkspaceItem(WorkspaceItem::TYPE_REMOTE, it->url.toString()), ROLE_WORKSPACE_ITEM); + remote_item->setToolTip(it->url.toDisplayString()); -#if 0 // Unimplemented for now - // Settings - QStandardItem *settings = new QStandardItem(getInternalIcon(":icons/icon-action-settings"), tr("Settings")); - settings->setData(WorkspaceItem(WorkspaceItem::TYPE_SETTINGS, ""), ROLE_WORKSPACE_ITEM); - settings->setEditable(false); - getWorkspace().getTreeModel().appendRow(settings); -#endif + // Mark the default url as bold + if(it->isDefault) + { + QFont font = remote_item->font(); + font.setBold(true); + remote_item->setFont(font); + } + remotes->appendRow(remote_item); + } // Expand previously selected nodes name_map.clear(); @@ -956,6 +959,7 @@ void MainWindow::on_actionClearLog_triggered() void MainWindow::applySettings() { QSettings *store = settings.GetStore(); + QString active_workspace; int num_wks = store->beginReadArray("Workspaces"); for(int i=0; icontains("Active") && store->value("Active").toBool()) - setCurrentWorkspace(wk); + active_workspace = wk; } store->endArray(); @@ -1025,7 +1029,10 @@ void MainWindow::applySettings() ui->actionViewAsFolders->setChecked(!store->value("ViewAsList").toBool()); viewMode = store->value("ViewAsList").toBool()? VIEWMODE_LIST : VIEWMODE_TREE; } - //ui->workspaceTreeView->setVisible(viewMode == VIEWMODE_TREE); + + // Set the workspace after loading the settings, since it may trigger a remote info storage + if(!active_workspace.isEmpty()) + setCurrentWorkspace(active_workspace); } //------------------------------------------------------------------------------ @@ -1233,9 +1240,26 @@ void MainWindow::getSelectionStashes(QStringList &stashNames) QString name = mi.model()->data(mi, Qt::DisplayRole).toString(); stashNames.append(name); } - } +//------------------------------------------------------------------------------ +void MainWindow::getSelectionRemotes(QStringList &remoteUrls) +{ + QModelIndexList selection = ui->workspaceTreeView->selectionModel()->selectedIndexes(); + + foreach(const QModelIndex &mi, selection) + { + QVariant data = mi.model()->data(mi, ROLE_WORKSPACE_ITEM); + Q_ASSERT(data.isValid()); + WorkspaceItem tv = data.value(); + + if(tv.Type != WorkspaceItem::TYPE_REMOTE) + continue; + + QString url = tv.Value; + remoteUrls.append(url); + } +} //------------------------------------------------------------------------------ bool MainWindow::diffFile(const QString &repoFile) { @@ -1332,34 +1356,6 @@ void MainWindow::on_actionOpenFile_triggered() } } -//------------------------------------------------------------------------------ -void MainWindow::on_actionPush_triggered() -{ - QString remote_url = settings.GetFossilValue(FOSSIL_SETTING_REMOTE_URL).toString(); - - if(remote_url.isEmpty() || remote_url == "off") - { - QMessageBox::critical(this, tr("Error"), tr("A remote repository has not been specified.\nUse the preferences window to set the remote repostory location"), QMessageBox::Ok ); - return; - } - - fossil().pushRepository(); -} - -//------------------------------------------------------------------------------ -void MainWindow::on_actionPull_triggered() -{ - QString remote_url = settings.GetFossilValue(FOSSIL_SETTING_REMOTE_URL).toString(); - - if(remote_url.isEmpty() || remote_url == "off") - { - QMessageBox::critical(this, tr("Error"), tr("A remote repository has not been specified.\nUse the preferences window to set the remote repostory location"), QMessageBox::Ok ); - return; - } - - fossil().pullRepository(); -} - //------------------------------------------------------------------------------ void MainWindow::on_actionCommit_triggered() { @@ -1608,17 +1604,6 @@ void MainWindow::loadFossilSettings() const QString &name = it.key(); Settings::Setting::SettingType type = it->Type; - // Command types we issue directly on fossil - - if(name == FOSSIL_SETTING_REMOTE_URL) - { - // Retrieve existing url - QString url; - if(fossil().getRemoteUrl(url)) - it.value().Value = url; - continue; - } - Q_ASSERT(type == Settings::Setting::TYPE_FOSSIL_GLOBAL || type == Settings::Setting::TYPE_FOSSIL_LOCAL); // Otherwise it must be a fossil setting @@ -1664,15 +1649,6 @@ void MainWindow::on_actionFossilSettings_triggered() const QString &name = it.key(); Settings::Setting::SettingType type = it.value().Type; - // Command types we issue directly on fossil - // FIXME: major uglyness with settings management - if(name == FOSSIL_SETTING_REMOTE_URL) - { - // Run as silent to avoid displaying credentials in the log - fossil().setRemoteUrl(it.value().Value.toString()); - continue; - } - Q_ASSERT(type == Settings::Setting::TYPE_FOSSIL_GLOBAL || type == Settings::Setting::TYPE_FOSSIL_LOCAL); QString value = it.value().Value.toString(); @@ -1788,13 +1764,16 @@ void MainWindow::on_workspaceTreeView_doubleClicked(const QModelIndex &index) Q_ASSERT(data.isValid()); WorkspaceItem tv = data.value(); - if(tv.Type!=WorkspaceItem::TYPE_FOLDER && tv.Type!=WorkspaceItem::TYPE_WORKSPACE) - return; - - QString target = getCurrentWorkspace() + PATH_SEPARATOR + tv.Value; - - QUrl url = QUrl::fromLocalFile(target); - QDesktopServices::openUrl(url); + if(tv.Type==WorkspaceItem::TYPE_FOLDER || tv.Type==WorkspaceItem::TYPE_WORKSPACE) + { + QString target = getCurrentWorkspace() + PATH_SEPARATOR + tv.Value; + QUrl url = QUrl::fromLocalFile(target); + QDesktopServices::openUrl(url); + } + else if(tv.Type==WorkspaceItem::TYPE_REMOTE) + { + on_actionEditRemote_triggered(); + } } //------------------------------------------------------------------------------ @@ -2187,6 +2166,8 @@ void MainWindow::on_workspaceTreeView_customContextMenuRequested(const QPoint &) menu = menuTags; else if (tv.Type == WorkspaceItem::TYPE_BRANCH || tv.Type == WorkspaceItem::TYPE_BRANCHES) menu = menuBranches; + else if (tv.Type == WorkspaceItem::TYPE_REMOTE || tv.Type == WorkspaceItem::TYPE_REMOTES) + menu = menuRemotes; if(menu) { @@ -2477,3 +2458,173 @@ void MainWindow::onSearch() searchBox->setFocus(); } +//------------------------------------------------------------------------------ +void MainWindow::on_actionEditRemote_triggered() +{ + QStringList remotes; + getSelectionRemotes(remotes); + if(remotes.empty()) + return; + + QUrl old_url(remotes.first()); + + QString name; + Remote *remote = getWorkspace().findRemote(old_url); + if(remote) + name = remote->name; + + bool exists = KeychainGet(this, old_url); + + QUrl new_url = old_url; + if(!RemoteDialog::run(this, new_url, name)) + return; + + if(!new_url.isLocalFile()) + { + if(exists) + KeychainDelete(this, new_url); + + if(!KeychainSet(this, new_url)) + QMessageBox::critical(this, tr("Error"), tr("Could not store information to keychain."), QMessageBox::Ok ); + } + + // Remove password + new_url.setPassword(""); + old_url.setPassword(""); + // Url changed? + if(new_url != old_url) + { + getWorkspace().removeRemote(old_url); + getWorkspace().addRemote(new_url, name); + } + else // Just data changed + remote->name = name; + + updateWorkspaceView(); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionPushRemote_triggered() +{ + QStringList remotes; + getSelectionRemotes(remotes); + if(remotes.empty()) + return; + + QUrl url(remotes.first()); + + // Retrieve password from keychain + if(!url.isLocalFile()) + KeychainGet(this, url); + + fossil().pushRepository(url); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionPullRemote_triggered() +{ + QStringList remotes; + getSelectionRemotes(remotes); + if(remotes.empty()) + return; + + QUrl url(remotes.first()); + + // Retrieve password from keychain + if(!url.isLocalFile()) + KeychainGet(this, url); + + fossil().pullRepository(url); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionPush_triggered() +{ + QUrl url = getWorkspace().getRemoteDefault(); + + if(url.isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("A default remote repository has not been specified."), QMessageBox::Ok ); + return; + } + + // Retrieve password from keychain + if(!url.isLocalFile()) + KeychainGet(this, url); + + fossil().pushRepository(url); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionPull_triggered() +{ + QUrl url = getWorkspace().getRemoteDefault(); + + if(url.isEmpty()) + { + QMessageBox::critical(this, tr("Error"), tr("A default remote repository has not been specified."), QMessageBox::Ok ); + return; + } + + // Retrieve password from keychain + if(!url.isLocalFile()) + KeychainGet(this, url); + + fossil().pullRepository(url); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionSetDefaultRemote_triggered() +{ + QStringList remotes; + getSelectionRemotes(remotes); + if(remotes.empty()) + return; + + QUrl url(remotes.first()); + + getWorkspace().setRemoteDefault(url); + updateWorkspaceView(); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionAddRemote_triggered() +{ + QUrl url; + QString name; + if(!RemoteDialog::run(this, url, name)) + return; + + if(!url.isLocalFile()) + { + KeychainDelete(this, url); + + if(!KeychainSet(this, url)) + QMessageBox::critical(this, tr("Error"), tr("Could not store information to keychain."), QMessageBox::Ok ); + } + + url.setPassword(""); + + getWorkspace().addRemote(url, name); + updateWorkspaceView(); +} + +//------------------------------------------------------------------------------ +void MainWindow::on_actionDeleteRemote_triggered() +{ + QStringList remotes; + getSelectionRemotes(remotes); + if(remotes.empty()) + return; + + QUrl url(remotes.first()); + + Remote *remote = getWorkspace().findRemote(url); + Q_ASSERT(remote); + + if(QMessageBox::Yes != DialogQuery(this, tr("Delete Remote"), tr("Are you sure want to delete the remote '%0' ?").arg(remote->name))) + return; + + getWorkspace().removeRemote(url); + updateWorkspaceView(); +} diff --git a/src/MainWindow.h b/src/MainWindow.h index dc7eb64..cfbf27b 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -40,11 +40,12 @@ private: void getDirViewSelection(QStringList &filenames, int includeMask=WorkspaceFile::TYPE_ALL, bool allIfEmpty=false); void getSelectionStashes(QStringList &stashNames); void getSelectionPaths(stringset_t &paths); + void getSelectionRemotes(QStringList& remoteUrls); void getAllFilenames(QStringList &filenames, int includeMask=WorkspaceFile::TYPE_ALL); bool startUI(); void stopUI(); void enableActions(bool on); - void addWorkspace(const QString &dir); + void addWorkspaceHistory(const QString &dir); void rebuildRecent(); bool openWorkspace(const QString &path); void loadFossilSettings(); @@ -89,6 +90,8 @@ private slots: void on_actionOpenFile_triggered(); void on_actionPush_triggered(); void on_actionPull_triggered(); + void on_actionPushRemote_triggered(); + void on_actionPullRemote_triggered(); void on_actionCommit_triggered(); void on_actionAdd_triggered(); void on_actionDelete_triggered(); @@ -124,6 +127,10 @@ private slots: void on_actionDeleteTag_triggered(); void on_actionCreateBranch_triggered(); void on_actionMergeBranch_triggered(); + void on_actionEditRemote_triggered(); + void on_actionSetDefaultRemote_triggered(); + void on_actionAddRemote_triggered(); + void on_actionDeleteRemote_triggered(); private: class MainWinUICallback : public UICallback @@ -171,6 +178,7 @@ private: QMenu *menuStashes; QMenu *menuTags; QMenu *menuBranches; + QMenu *menuRemotes; bool operationAborted; stringset_t selectedDirs; // The directory selected in the tree diff --git a/src/RemoteDialog.cpp b/src/RemoteDialog.cpp new file mode 100644 index 0000000..8afdb00 --- /dev/null +++ b/src/RemoteDialog.cpp @@ -0,0 +1,95 @@ +#include "RemoteDialog.h" +#include "ui_RemoteDialog.h" +#include +#include +#include +#include +#include +#include "Utils.h" + +//----------------------------------------------------------------------------- +RemoteDialog::RemoteDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::RemoteDialog) +{ + ui->setupUi(this); +} + +//----------------------------------------------------------------------------- +RemoteDialog::~RemoteDialog() +{ + delete ui; +} + +//----------------------------------------------------------------------------- +bool RemoteDialog::run(QWidget *parent, QUrl &url, QString &name) +{ + RemoteDialog dlg(parent); + + // Set URL components + if(!url.isEmpty()) + { + QString url_no_credentials = url.toString(QUrl::PrettyDecoded|QUrl::RemoveUserInfo); + dlg.ui->lineURL->setText(url_no_credentials); + dlg.ui->lineUserName->setText(url.userName()); + dlg.ui->linePassword->setText(url.password()); + dlg.ui->lineName->setText(name); + } + + if(dlg.exec() != QDialog::Accepted) + return false; + + QString urltext = dlg.ui->lineURL->text(); + + url = QUrl::fromUserInput(urltext); + if(url.isEmpty() || !url.isValid()) + { + QMessageBox::critical(parent, tr("Error"), tr("Invalid URL."), QMessageBox::Ok ); + return false; + } + + if(!dlg.ui->lineUserName->text().trimmed().isEmpty()) + url.setUserName(dlg.ui->lineUserName->text()); + + if(!dlg.ui->linePassword->text().trimmed().isEmpty()) + url.setPassword(dlg.ui->linePassword->text()); + + name =dlg.ui->lineName->text().trimmed(); + if(name.isEmpty()) + name = url.toString(QUrl::PrettyDecoded|QUrl::RemoveUserInfo); + + return true; +} + +//----------------------------------------------------------------------------- +void RemoteDialog::GetRepositoryPath(QString &pathResult) +{ + QString filter(tr("Fossil Repository") + QString(" (*." FOSSIL_EXT ")")); + + pathResult = QFileDialog::getSaveFileName( + this, + tr("Select Fossil Repository"), + QDir::toNativeSeparators(pathResult), + filter, + &filter, + QFileDialog::DontConfirmOverwrite); +} + +//----------------------------------------------------------------------------- +void RemoteDialog::on_btnSelectSourceRepo_clicked() +{ + QString path = ui->lineURL->text(); + GetRepositoryPath(path); + + if(path.trimmed().isEmpty()) + return; + + if(!QFile::exists(path)) + { + QMessageBox::critical(this, tr("Error"), tr("Invalid Repository File."), QMessageBox::Ok); + return; + } + + ui->lineURL->setText(QDir::toNativeSeparators(path)); +} + diff --git a/src/RemoteDialog.h b/src/RemoteDialog.h new file mode 100644 index 0000000..afd30e9 --- /dev/null +++ b/src/RemoteDialog.h @@ -0,0 +1,29 @@ +#ifndef REMOTEDIALOG_H +#define REMOTEDIALOG_H + +#include + +namespace Ui { +class RemoteDialog; +} + +class RemoteDialog : public QDialog +{ + Q_OBJECT + +public: + explicit RemoteDialog(QWidget *parent = 0); + ~RemoteDialog(); + + static bool run(QWidget *parent, class QUrl &url, QString &name); + +private slots: + void on_btnSelectSourceRepo_clicked(); + +private: + void GetRepositoryPath(QString &pathResult); + + Ui::RemoteDialog *ui; +}; + +#endif // REMOTEDIALOG_H diff --git a/src/Settings.cpp b/src/Settings.cpp index 95ce3cf..1a88187 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -17,7 +17,6 @@ Settings::Settings(bool portableMode) : store(0) Mappings.insert(FOSSIL_SETTING_IGNORE_GLOB, Setting("", Setting::TYPE_FOSSIL_LOCAL)); Mappings.insert(FOSSIL_SETTING_CRNL_GLOB, Setting("", Setting::TYPE_FOSSIL_LOCAL)); - Mappings.insert(FOSSIL_SETTING_REMOTE_URL, Setting("off", Setting::TYPE_FOSSIL_COMMAND)); // Go into portable mode when explicitly requested or if a config file exists next to the executable QString ini_path = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + QDir::separator() + QCoreApplication::applicationName() + ".ini"); diff --git a/src/Settings.h b/src/Settings.h index f0535ee..3153868 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -16,7 +16,6 @@ #define FOSSIL_SETTING_PROXY_URL "proxy" #define FOSSIL_SETTING_IGNORE_GLOB "ignore-glob" #define FOSSIL_SETTING_CRNL_GLOB "crnl-glob" -#define FOSSIL_SETTING_REMOTE_URL "remote-url" #define FOSSIL_SETTING_HTTP_PORT "http-port" @@ -35,8 +34,7 @@ struct Settings enum SettingType { TYPE_FOSSIL_GLOBAL, - TYPE_FOSSIL_LOCAL, - TYPE_FOSSIL_COMMAND + TYPE_FOSSIL_LOCAL }; Setting(QVariant value, SettingType type) : Value(value), Type(type) diff --git a/src/Utils.cpp b/src/Utils.cpp index 4dedf39..e6777b9 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -2,6 +2,9 @@ #include #include #include +#include +#include "ext/qtkeychain/keychain.h" +#include /////////////////////////////////////////////////////////////////////////////// QMessageBox::StandardButton DialogQuery(QWidget *parent, const QString &title, const QString &query, QMessageBox::StandardButtons buttons) @@ -388,4 +391,66 @@ void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItemModel Q_ASSERT(item); BuildNameToModelIndex(map, *item); } -} \ No newline at end of file +} + +//------------------------------------------------------------------------------ +bool KeychainSet(QObject *parent, const QUrl &url) +{ + QEventLoop loop(parent); + QKeychain::WritePasswordJob job(url.toString(QUrl::PrettyDecoded|QUrl::RemoveUserInfo)); + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); + job.setAutoDelete( false ); + job.setInsecureFallback(false); + job.setKey(url.userName()); + job.setTextData(url.password()); + job.start(); + loop.exec(); + return job.error() == QKeychain::NoError; +} + +//------------------------------------------------------------------------------ +bool KeychainGet(QObject *parent, QUrl &url) +{ + QEventLoop loop(parent); + QKeychain::ReadPasswordJob job(url.toString(QUrl::PrettyDecoded|QUrl::RemoveUserInfo)); + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit())); + job.setAutoDelete( false ); + job.setInsecureFallback(false); + job.setAutoDelete( false ); + job.setKey(url.userName()); + job.start(); + loop.exec(); + + if(job.error() != QKeychain::NoError) + return false; + + url.setUserName(job.key()); + url.setPassword(job.textData()); + return true; +} + +//------------------------------------------------------------------------------ +bool KeychainDelete(QObject* parent, const QUrl& url) +{ + QEventLoop loop(parent); + QKeychain::DeletePasswordJob job(url.toString(QUrl::PrettyDecoded|QUrl::RemoveUserInfo)); + job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit())); + job.setAutoDelete( false ); + job.setInsecureFallback(false); + job.setAutoDelete( false ); + job.setKey(url.userName()); + job.start(); + loop.exec(); + + return job.error() == QKeychain::NoError; +} + +//------------------------------------------------------------------------------ +QString HashString(const QString& str) +{ + QCryptographicHash hash(QCryptographicHash::Sha1); + const QByteArray ba(str.toUtf8()); + hash.addData(ba.data(), ba.size()); + QString str_out(hash.result().toHex()); + return str_out; +} diff --git a/src/Utils.h b/src/Utils.h index 0e86eae..e14789d 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -22,6 +22,10 @@ typedef QMap name_modelindex_map_t; void GetStandardItemTextRecursive(QString &name, const QStandardItem &item, const QChar &separator='/'); void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItem &item); void BuildNameToModelIndex(name_modelindex_map_t &map, const QStandardItemModel &model); +bool KeychainSet(QObject* parent, const QUrl& url); +bool KeychainGet(QObject* parent, QUrl& url); +bool KeychainDelete(QObject* parent, const QUrl& url); +QString HashString(const QString &str); typedef QMap QStringMap; diff --git a/src/Workspace.cpp b/src/Workspace.cpp index fb5c467..764a1f3 100644 --- a/src/Workspace.cpp +++ b/src/Workspace.cpp @@ -2,10 +2,15 @@ #include #include "Utils.h" +//----------------------------------------------------------------------------- +Workspace::Workspace() +{ +} //----------------------------------------------------------------------------- Workspace::~Workspace() { clearState(); + remotes.clear(); } //------------------------------------------------------------------------------ @@ -23,6 +28,93 @@ void Workspace::clearState() isIntegrated = false; } +//------------------------------------------------------------------------------ +void Workspace::storeWorkspace(QSettings &store) +{ + QString workspace = fossil().getCurrentWorkspace(); + if(workspace.isEmpty()) + return; + + store.beginGroup("Remotes"); + QString workspace_hash = HashString(QDir::toNativeSeparators(workspace)); + + store.beginWriteArray(workspace_hash); + int index = 0; + for(remote_map_t::iterator it=remotes.begin(); it!=remotes.end(); ++it, ++index) + { + store.setArrayIndex(index); + store.setValue("Name", it->name); + QUrl url = it->url; + url.setPassword(""); + store.setValue("Url", url); + if(it->isDefault) + store.setValue("Default", it->isDefault); + else + store.remove("Default"); + } + store.endArray(); + store.endGroup(); + +} + +//------------------------------------------------------------------------------ +bool Workspace::switchWorkspace(const QString& workspace, QSettings &store) +{ + // Save Remotes + storeWorkspace(store); + clearState(); + remotes.clear(); + + fossil().setCurrentWorkspace(""); + if(workspace.isEmpty()) + return true; + + QString new_workspace = QFileInfo(workspace).absoluteFilePath(); + + if(!QDir::setCurrent(new_workspace)) + return false; + + fossil().setCurrentWorkspace(new_workspace); + + // Load Remotes + QString workspace_hash = HashString(QDir::toNativeSeparators(new_workspace)); + + QString gr = store.group(); + + store.beginGroup("Remotes"); + gr = store.group(); + int num_remotes = store.beginReadArray(workspace_hash); + for(int i=0; i 0; +} + +//------------------------------------------------------------------------------ +bool Workspace::setRemoteDefault(const QUrl& url) +{ + Q_ASSERT(url.password().isEmpty()); + + bool found = false; + for(remote_map_t::iterator it=remotes.begin(); it!=remotes.end(); ++it) + { + if(it->url == url) + { + it->isDefault = true; + found = true; + } + else + it->isDefault = false; + } + return found; +} + +//------------------------------------------------------------------------------ +const QUrl & Workspace::getRemoteDefault() const +{ + return fossil().getDefaultRemoteUrl(); +} + +//------------------------------------------------------------------------------ +Remote * Workspace::findRemote(const QUrl& url) +{ + remote_map_t::iterator it = remotes.find(url); + if(it!=remotes.end()) + return &(*it); + return NULL; +} + diff --git a/src/Workspace.h b/src/Workspace.h index 68dfa2c..fb58735 100644 --- a/src/Workspace.h +++ b/src/Workspace.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "Utils.h" #include "Fossil.h" @@ -99,13 +100,31 @@ private: typedef QSet stringset_t; +class Remote +{ +public: + Remote(const QString &_name, const QUrl &_url, bool _isDefault=false) + : name(_name), url(_url), isDefault(_isDefault) + { + } + + QString name; + QUrl url; + bool isDefault; + +}; + +typedef QMap remote_map_t; + + ////////////////////////////////////////////////////////////////////////// // Workspace ////////////////////////////////////////////////////////////////////////// - class Workspace { public: + Workspace(); + ~Workspace(); typedef QList filelist_t; @@ -116,7 +135,7 @@ public: Fossil & fossil() { return bridge; } const Fossil & fossil() const { return bridge; } - static bool scanDirectory(QFileInfoList &entries, const QString& dirPath, const QString &baseDir, const QString ignoreSpec, const bool& abort, UICallback &uiCallback); + bool switchWorkspace(const QString &workspace, QSettings &store); void scanWorkspace(bool scanLocal, bool scanIgnored, bool scanModified, bool scanUnchanged, const QString &ignoreGlob, UICallback &uiCallback, bool &operationAborted); QStandardItemModel &getFileModel() { return repoFileModel; } @@ -129,6 +148,19 @@ public: QStringList &getBranches() { return branchList; } bool otherChanges() const { return isIntegrated; } + // Remotes + const remote_map_t &getRemotes() const { return remotes; } + bool addRemote(const QUrl &url, const QString &name); + bool removeRemote(const QUrl &url); + bool setRemoteDefault(const QUrl& url); + const QUrl &getRemoteDefault() const; + Remote * findRemote(const QUrl& url); + + + void storeWorkspace(QSettings &store); +private: + static bool scanDirectory(QFileInfoList &entries, const QString& dirPath, const QString &baseDir, const QString ignoreSpec, const bool& abort, UICallback &uiCallback); + private: Fossil bridge; filemap_t workspaceFiles; @@ -136,6 +168,7 @@ private: stashmap_t stashMap; QStringList branchList; QStringMap tags; + remote_map_t remotes; bool isIntegrated; QStandardItemModel repoFileModel; diff --git a/ui/FslSettingsDialog.ui b/ui/FslSettingsDialog.ui index db9ea78..e1ca55b 100644 --- a/ui/FslSettingsDialog.ui +++ b/ui/FslSettingsDialog.ui @@ -10,7 +10,7 @@ 0 0 457 - 266 + 235 @@ -165,19 +165,6 @@ - - - - - 0 - 0 - - - - A comma separated list of glob-style file patterns to exclude from Fossil's CR/NL consistency checking - - - @@ -191,8 +178,8 @@ - - + + 0 @@ -200,7 +187,7 @@ - A comma separated list of glob-style file/path patterns ignored in Fossil file operations + A comma separated list of glob-style file patterns to exclude from Fossil's CR/NL consistency checking @@ -217,8 +204,8 @@ - - + + 0 @@ -226,22 +213,7 @@ - The remote url used to push/pull changes. -URL style user names and passwords are also supported. -For example http://username:password@server.com/fossil - - - - - - - - 100 - 0 - - - - Remote Url + A comma separated list of glob-style file/path patterns ignored in Fossil file operations diff --git a/ui/MainWindow.ui b/ui/MainWindow.ui index 7774bde..0155000 100644 --- a/ui/MainWindow.ui +++ b/ui/MainWindow.ui @@ -493,7 +493,7 @@ &Push - Push changes to the remote repository + Push changes to the default remote repository Push changes to the remote repository @@ -511,7 +511,7 @@ Pu&ll - Pull changes from the remote repository + Pull changes from the default remote repository Pull changes from the remote repository @@ -520,6 +520,36 @@ Ctrl+L + + + + :/icons/icon-action-push:/icons/icon-action-push + + + &Push to Remote + + + Push changes to a remote repository + + + Push changes to a remote repository + + + + + + :/icons/icon-action-pull:/icons/icon-action-pull + + + Pu&ll from Remote + + + Pull changes from a remote repository + + + Pull changes from a remote repository + + @@ -978,6 +1008,35 @@ F&ossil Settings + + + Edit Remote + + + Edit Remote URL + + + + + Set Remote as Default + + + Makes the selected remote + + + + + Add Remote + + + Adds a Remote Url + + + + + Delete Remote + + diff --git a/ui/RemoteDialog.ui b/ui/RemoteDialog.ui new file mode 100644 index 0000000..2ca91d2 --- /dev/null +++ b/ui/RemoteDialog.ui @@ -0,0 +1,165 @@ + + + RemoteDialog + + + Qt::WindowModal + + + + 0 + 0 + 478 + 189 + + + + Remote Repository + + + true + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + URL + + + + + + + + + The URL of the source repository + + + + + + + + 0 + 0 + + + + + 24 + 24 + + + + ... + + + + + + + + + User Name + + + + + + + The user name used to access the remote repository. Leave blank if not required + + + + + + + Password + + + + + + + The password used to access the remote repository. Leave blank if not required + + + QLineEdit::Password + + + + + + + The password used to access the remote repository. Leave blank if not required + + + + + + + Name + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + lineURL + btnSelectSourceRepo + lineUserName + linePassword + + + + + buttonBox + accepted() + RemoteDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RemoteDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +