mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
Compare commits
186 Commits
2.0-alpha6
...
2.0-beta1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d3fb58cf5 | ||
|
|
df5da2fcef | ||
|
|
1226d1dbd5 | ||
|
|
607007f94f | ||
|
|
6327eaf587 | ||
|
|
b1fd99f4c4 | ||
|
|
d1331053c8 | ||
|
|
c6fe0da569 | ||
|
|
b9fe2c1bf9 | ||
|
|
3efc8f457a | ||
|
|
65e8732eeb | ||
|
|
e82015d419 | ||
|
|
fa0fe6d33d | ||
|
|
8be135adf9 | ||
|
|
d04927ce7f | ||
|
|
6889cc2f20 | ||
|
|
8325b20d36 | ||
|
|
826cd472c8 | ||
|
|
8ecab15c33 | ||
|
|
c9520214e2 | ||
|
|
6d1ca363af | ||
|
|
2170794d9c | ||
|
|
719ac64851 | ||
|
|
d3a7e0dee9 | ||
|
|
240919335f | ||
|
|
ceb6a0383e | ||
|
|
0185b112e1 | ||
|
|
af84261eb6 | ||
|
|
274f86fd04 | ||
|
|
bcc3108c3d | ||
|
|
5f1b286630 | ||
|
|
8ad48d6774 | ||
|
|
721bec9794 | ||
|
|
c6105a08ab | ||
|
|
154f1673e9 | ||
|
|
577609b3e3 | ||
|
|
f22069bb11 | ||
|
|
280a1aceb9 | ||
|
|
bcd3de1180 | ||
|
|
f4361dd4d5 | ||
|
|
fc43aa1717 | ||
|
|
d553698b20 | ||
|
|
7db9c78855 | ||
|
|
7a2c02f0df | ||
|
|
c535736853 | ||
|
|
a8bf6a9782 | ||
|
|
0458dad6dc | ||
|
|
68373730bf | ||
|
|
5d9039ea89 | ||
|
|
7e1faadd11 | ||
|
|
fceb93061d | ||
|
|
c9d007fcdf | ||
|
|
eeb940c0dc | ||
|
|
05b5446e94 | ||
|
|
b45437d502 | ||
|
|
a599787a25 | ||
|
|
58061af959 | ||
|
|
8bf1bb0517 | ||
|
|
d26cff520f | ||
|
|
04aa10cee7 | ||
|
|
ade684d501 | ||
|
|
4362c3ea38 | ||
|
|
e0d4b4b625 | ||
|
|
cfffdae573 | ||
|
|
f6243675c9 | ||
|
|
a762cef0a9 | ||
|
|
a7f4e2d0cd | ||
|
|
ae013c2196 | ||
|
|
eefe844dcd | ||
|
|
94111c3662 | ||
|
|
3fca61dc24 | ||
|
|
e41bf008e9 | ||
|
|
6c9c0fd5c5 | ||
|
|
f3d956ceed | ||
|
|
b9c9c56059 | ||
|
|
bed58cde84 | ||
|
|
855d79e28f | ||
|
|
a044467d10 | ||
|
|
ecb2e337ef | ||
|
|
2dde18b179 | ||
|
|
6411b9bd66 | ||
|
|
cf0bc32b27 | ||
|
|
93ab7eb058 | ||
|
|
bd3ae05fcf | ||
|
|
b055d524e8 | ||
|
|
9e051e835b | ||
|
|
3ab1072e9e | ||
|
|
00df73ced0 | ||
|
|
940a5026c1 | ||
|
|
2631277184 | ||
|
|
b86b640860 | ||
|
|
2dfc740782 | ||
|
|
e4985f4ff7 | ||
|
|
22f579a59e | ||
|
|
c9d12e93c2 | ||
|
|
778f01bcf1 | ||
|
|
ccb7a4c96d | ||
|
|
33650c4a04 | ||
|
|
019cf9684c | ||
|
|
835c411d12 | ||
|
|
e4758c1984 | ||
|
|
eb22f0a2d8 | ||
|
|
3ea0592b53 | ||
|
|
876a75b572 | ||
|
|
c39898dad9 | ||
|
|
07a3d7a696 | ||
|
|
7f412fbd7f | ||
|
|
2adc64939f | ||
|
|
71d39865b3 | ||
|
|
226c061c01 | ||
|
|
dd2fbebb81 | ||
|
|
889c742a33 | ||
|
|
5cc3334325 | ||
|
|
e58be44523 | ||
|
|
34a7321786 | ||
|
|
07e4fbacd4 | ||
|
|
8fd69e084e | ||
|
|
dd79105baa | ||
|
|
b1c3814972 | ||
|
|
57107ea560 | ||
|
|
4b3a82592c | ||
|
|
6ecb8690f2 | ||
|
|
1c365b8417 | ||
|
|
315df0b8a8 | ||
|
|
87468b648b | ||
|
|
4cdb9a645d | ||
|
|
870d7355ca | ||
|
|
f1aa6aca26 | ||
|
|
72b59d541a | ||
|
|
0e8aa0bc6c | ||
|
|
3a0648cf25 | ||
|
|
2e76385cae | ||
|
|
867d14f7aa | ||
|
|
0d6117bf4c | ||
|
|
b417bf9187 | ||
|
|
7137990a21 | ||
|
|
916ab99d62 | ||
|
|
5a31e055cf | ||
|
|
28694ae687 | ||
|
|
becd3a0019 | ||
|
|
8cc1e6008e | ||
|
|
910788c038 | ||
|
|
9391de74c7 | ||
|
|
c806f9ebf4 | ||
|
|
e776de8eeb | ||
|
|
a25b28ffee | ||
|
|
0e75e6ff03 | ||
|
|
76da4a6cd4 | ||
|
|
8a4100adbd | ||
|
|
584f4b50bf | ||
|
|
9ac01c930d | ||
|
|
d874f58a39 | ||
|
|
05de45dadb | ||
|
|
4ab887c773 | ||
|
|
552ca7bf71 | ||
|
|
2d8ba2b394 | ||
|
|
a6d44034a4 | ||
|
|
77af79498c | ||
|
|
ea3375490c | ||
|
|
204cd8d971 | ||
|
|
c2940a8f18 | ||
|
|
4f60df029d | ||
|
|
819cfd459a | ||
|
|
c90ac914bb | ||
|
|
8bf4826003 | ||
|
|
e361b0dd81 | ||
|
|
ce7e01a1b1 | ||
|
|
cda5e990ac | ||
|
|
50cbd80925 | ||
|
|
75d3e6261b | ||
|
|
bf39d0b1be | ||
|
|
147cd4ed7b | ||
|
|
9363d23e09 | ||
|
|
b718e9d8f2 | ||
|
|
d6c30b0886 | ||
|
|
7c7f0b93ae | ||
|
|
e263c475c9 | ||
|
|
c917096d3c | ||
|
|
5de62a5ef4 | ||
|
|
7893a2e84d | ||
|
|
ad26d962dc | ||
|
|
967a9f0195 | ||
|
|
6c663a19bf | ||
|
|
b194c29166 | ||
|
|
0b9167c78b | ||
|
|
63ae460a80 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
src/version.h.cmake export-subst
|
||||
19
.travis.yml
Normal file
19
.travis.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
compiler:
|
||||
- gcc
|
||||
- clang
|
||||
language: cpp
|
||||
install:
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq update; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get -qq install cmake libqt4-dev libgcrypt11-dev zlib1g-dev libxtst-dev; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew update; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install cmake qt libgcrypt; fi
|
||||
before_script: mkdir build && pushd build
|
||||
script:
|
||||
- cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_GUI_TESTS=ON ..
|
||||
- make
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then make test ARGS+="-E testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then xvfb-run -a --server-args="-screen 0 800x600x24" make test ARGS+="-R testgui --output-on-failure"; fi
|
||||
- if [ "$TRAVIS_OS_NAME" = "osx" ]; then make test; fi
|
||||
8
.tx/config
Normal file
8
.tx/config
Normal file
@@ -0,0 +1,8 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[keepassx.keepassx_ents]
|
||||
source_file = share/translations/keepassx_en.ts
|
||||
file_filter = share/translations/keepassx_<lang>.ts
|
||||
source_lang = en
|
||||
type = QT
|
||||
16
CHANGELOG
16
CHANGELOG
@@ -1,4 +1,18 @@
|
||||
2.0 Alpha 6 (2014-04-06)
|
||||
2.0 Beta 1 (2015-07-18)
|
||||
=========================
|
||||
|
||||
- Remember entry column sizes [#159]
|
||||
- Add translations
|
||||
- Support opening attachments directly
|
||||
- Support cmd:// URLs [#244]
|
||||
- Protect opened databases with a file lock [#18]
|
||||
- Export to csv files [#57]
|
||||
- Add optional tray icon [#153]
|
||||
- Allow setting the default auto-type sequence for groups [#175]
|
||||
- Make the kdbx parser more lenient
|
||||
- Remove --password command line option [#285]
|
||||
|
||||
2.0 Alpha 6 (2014-04-12)
|
||||
=========================
|
||||
|
||||
- Add option to lock databases after user inactivity [#62]
|
||||
|
||||
@@ -32,10 +32,10 @@ include(CheckCXXSourceCompiles)
|
||||
option(WITH_TESTS "Enable building of unit tests" ON)
|
||||
option(WITH_GUI_TESTS "Enable building of GUI tests" OFF)
|
||||
option(WITH_LTO "Enable Link Time Optimization (LTO)" OFF)
|
||||
option(WITH_CXX11 "Build with the C++ 11 standard" OFF)
|
||||
option(WITH_CXX11 "Build with the C++ 11 standard" ON)
|
||||
|
||||
set(KEEPASSX_VERSION "2.0 alpha 6")
|
||||
set(KEEPASSX_VERSION_NUM "1.9.85")
|
||||
set(KEEPASSX_VERSION "2.0 beta 1")
|
||||
set(KEEPASSX_VERSION_NUM "1.9.91")
|
||||
|
||||
if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(CMAKE_COMPILER_IS_CLANG 1)
|
||||
@@ -151,7 +151,7 @@ else()
|
||||
|
||||
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
|
||||
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassx")
|
||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATAROOTDIR}/keepassx")
|
||||
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassx")
|
||||
endif()
|
||||
|
||||
if(WITH_TESTS)
|
||||
@@ -159,12 +159,12 @@ if(WITH_TESTS)
|
||||
endif(WITH_TESTS)
|
||||
|
||||
set(QT_REQUIRED_MODULES QtCore QtGui QtTest)
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(QT_REQUIRED_MODULES ${QT_REQUIRED_MODULES} QtDBus)
|
||||
endif()
|
||||
|
||||
find_package(Qt4 4.6.0 REQUIRED ${QT_REQUIRED_MODULES})
|
||||
include(${QT_USE_FILE})
|
||||
# Debian sets the the build type to None for package builds.
|
||||
# Make sure we don't enable asserts there.
|
||||
set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG)
|
||||
|
||||
find_package(Gcrypt REQUIRED)
|
||||
if(NOT (${GCRYPT_VERSION_STRING} VERSION_LESS "1.6.0"))
|
||||
|
||||
4
COPYING
4
COPYING
@@ -183,3 +183,7 @@ Files: src/streams/qtiocompressor.*
|
||||
tests/modeltest.*
|
||||
Copyright: 2009-2012, Nokia Corporation and/or its subsidiary(-ies)
|
||||
License: LGPL-2.1 or GPL-3
|
||||
|
||||
Files: cmake/GetGitRevisionDescription.cmake*
|
||||
Copyright: 2009-2010, Iowa State University
|
||||
License: Boost-1.0
|
||||
|
||||
2
INSTALL
2
INSTALL
@@ -2,7 +2,7 @@ Building:
|
||||
=========
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. [CMAKE PARAMETERS]
|
||||
cmake [CMAKE PARAMETERS] ..
|
||||
make [-jX]
|
||||
|
||||
Common cmake parameters:
|
||||
|
||||
23
LICENSE.BOOST-1.0
Normal file
23
LICENSE.BOOST-1.0
Normal file
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
129
README.md
Normal file
129
README.md
Normal file
@@ -0,0 +1,129 @@
|
||||
# KeePassX
|
||||
|
||||
## About
|
||||
|
||||
KeePassX is an application for people with extremely high demands on secure personal data management.
|
||||
It has a light interface, is cross platform and published under the terms of the GNU General Public License.
|
||||
|
||||
KeePassX saves many different information e.g. user names, passwords, urls, attachments and comments in one single database.
|
||||
For a better management user-defined titles and icons can be specified for each single entry.
|
||||
Furthermore the entries are sorted in groups, which are customizable as well. The integrated search function allows to search in a single group or the complete database.
|
||||
KeePassX offers a little utility for secure password generation. The password generator is very customizable, fast and easy to use.
|
||||
Especially someone who generates passwords frequently will appreciate this feature.
|
||||
|
||||
The complete database is always encrypted with the AES (aka Rijndael) encryption algorithm using a 256 bit key.
|
||||
Therefore the saved information can be considered as quite safe. KeePassX uses a database format that is compatible with [KeePass Password Safe](http://keepass.info/).
|
||||
This makes the use of that application even more favorable.
|
||||
|
||||
## Install
|
||||
|
||||
KeePassX can be downloaded and installed using an assortment of installers available on the main [KeePassX website](http://www.keepassx.org).
|
||||
KeePassX can also be installed from the official repositories of many Linux repositories.
|
||||
If you wish to build KeePassX from source, rather than rely on the pre-compiled binaries, you may wish to read up on the _From Source_ section.
|
||||
|
||||
### Debian
|
||||
|
||||
To install KeePassX from the Debian repository:
|
||||
|
||||
```bash
|
||||
sudo apt-get install keepassx
|
||||
```
|
||||
|
||||
### Red Hat
|
||||
|
||||
Install KeePassX from the Red Hat (or CentOS) repository:
|
||||
|
||||
```bash
|
||||
sudo yum install keepassx
|
||||
```
|
||||
|
||||
### Windows / Mac OS X
|
||||
|
||||
Download the installer from the KeePassX [download](https://www.keepassx.org/downloads) page.
|
||||
Once downloaded, double click on the file to execute the installer.
|
||||
|
||||
### From Source
|
||||
|
||||
#### Build Dependencies
|
||||
|
||||
The following tools must exist within your PATH:
|
||||
|
||||
* make
|
||||
* cmake (>= 2.6.4)
|
||||
* g++ or clang++
|
||||
|
||||
The following libraries are required:
|
||||
|
||||
* Qt 4 (>= 4.6)
|
||||
* libgcrypt
|
||||
* zlib
|
||||
* libxtst (optional for auto-type on X11)
|
||||
|
||||
On Debian you can install them with:
|
||||
|
||||
```bash
|
||||
sudo apt-get install build-essential cmake libqt4-dev libgcrypt11-dev zlib1g-dev
|
||||
```
|
||||
|
||||
#### Build Steps
|
||||
|
||||
To compile from source:
|
||||
|
||||
```bash
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
make [-jX]
|
||||
```
|
||||
|
||||
You will have the compiled KeePassX binary inside the `./build/src/` directory.
|
||||
|
||||
To install this binary execute the following:
|
||||
|
||||
```bash
|
||||
sudo make install
|
||||
```
|
||||
|
||||
More detailed instructions available in the INSTALL file.
|
||||
|
||||
## Contribute
|
||||
|
||||
Coordination of work between developers is handled through the [KeePassX development](https://www.keepassx.org/dev/) site.
|
||||
Requests for enhancements, or reports of bugs encountered, can also be reported through the KeePassX development site.
|
||||
However, members of the open-source community are encouraged to submit pull requests directly through GitHub.
|
||||
|
||||
### Clone Repository
|
||||
|
||||
Clone the repository to a suitable location where you can extend and build this project.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keepassx/keepassx.git
|
||||
```
|
||||
|
||||
**Note:** This will clone the entire contents of the repository at the HEAD revision.
|
||||
|
||||
To update the project from within the project's folder you can run the following command:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
### Feature Requests
|
||||
|
||||
We're always looking for suggestions to improve our application. If you have a suggestion for improving an existing feature,
|
||||
or would like to suggest a completely new feature for KeePassX, please file a ticket on the [KeePassX development](https://www.keepassx.org/dev/) site.
|
||||
|
||||
### Bug Reports
|
||||
|
||||
Our software isn't always perfect, but we strive to always improve our work. You may file bug reports on the [KeePassX development](https://www.keepassx.org/dev/) site.
|
||||
|
||||
### Pull Requests
|
||||
|
||||
Along with our desire to hear your feedback and suggestions, we're also interested in accepting direct assistance in the form of code.
|
||||
|
||||
Issue merge requests against our [GitHub repository](https://github.com/keepassx/keepassx).
|
||||
|
||||
### Translations
|
||||
|
||||
Translations are managed on [Transifex](https://www.transifex.com/projects/p/keepassx/) which offers a web interface.
|
||||
Please join an existing language team or request a new one if there is none.
|
||||
130
cmake/GetGitRevisionDescription.cmake
Normal file
130
cmake/GetGitRevisionDescription.cmake
Normal file
@@ -0,0 +1,130 @@
|
||||
# - Returns a version string from Git
|
||||
#
|
||||
# These functions force a re-configure on each git commit so that you can
|
||||
# trust the values of the variables in your build system.
|
||||
#
|
||||
# get_git_head_revision(<refspecvar> <hashvar> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the refspec and sha hash of the current head revision
|
||||
#
|
||||
# git_describe(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe on the source tree, and adjusting
|
||||
# the output so that it tests false if an error occurs.
|
||||
#
|
||||
# git_get_exact_tag(<var> [<additional arguments to git describe> ...])
|
||||
#
|
||||
# Returns the results of git describe --exact-match on the source tree,
|
||||
# and adjusting the output so that it tests false if there was no exact
|
||||
# matching tag.
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE.BOOST-1.0 or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
if(__get_git_revision_description)
|
||||
return()
|
||||
endif()
|
||||
set(__get_git_revision_description YES)
|
||||
|
||||
# We must run the following at "include" time, not at function call time,
|
||||
# to find the path to this module rather than the path to a calling list file
|
||||
get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH)
|
||||
|
||||
function(get_git_head_revision _refspecvar _hashvar)
|
||||
set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories
|
||||
set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}")
|
||||
get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH)
|
||||
if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT)
|
||||
# We have reached the root directory, we are not in git
|
||||
set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
set(GIT_DIR "${GIT_PARENT_DIR}/.git")
|
||||
endwhile()
|
||||
# check if this is a submodule
|
||||
if(NOT IS_DIRECTORY ${GIT_DIR})
|
||||
file(READ ${GIT_DIR} submodule)
|
||||
string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule})
|
||||
get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH)
|
||||
get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE)
|
||||
endif()
|
||||
set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data")
|
||||
if(NOT EXISTS "${GIT_DATA}")
|
||||
file(MAKE_DIRECTORY "${GIT_DATA}")
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${GIT_DIR}/HEAD")
|
||||
return()
|
||||
endif()
|
||||
set(HEAD_FILE "${GIT_DATA}/HEAD")
|
||||
configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY)
|
||||
|
||||
configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in"
|
||||
"${GIT_DATA}/grabRef.cmake"
|
||||
@ONLY)
|
||||
include("${GIT_DATA}/grabRef.cmake")
|
||||
|
||||
set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE)
|
||||
set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_describe _var)
|
||||
if(NOT GIT_FOUND)
|
||||
find_package(Git QUIET)
|
||||
endif()
|
||||
get_git_head_revision(refspec hash)
|
||||
if(NOT GIT_FOUND)
|
||||
set(${_var} "GIT-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
if(NOT hash)
|
||||
set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# TODO sanitize
|
||||
#if((${ARGN}" MATCHES "&&") OR
|
||||
# (ARGN MATCHES "||") OR
|
||||
# (ARGN MATCHES "\\;"))
|
||||
# message("Please report the following error to the project!")
|
||||
# message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}")
|
||||
#endif()
|
||||
|
||||
#message(STATUS "Arguments to execute_process: ${ARGN}")
|
||||
|
||||
execute_process(COMMAND
|
||||
"${GIT_EXECUTABLE}"
|
||||
describe
|
||||
${hash}
|
||||
${ARGN}
|
||||
WORKING_DIRECTORY
|
||||
"${CMAKE_SOURCE_DIR}"
|
||||
RESULT_VARIABLE
|
||||
res
|
||||
OUTPUT_VARIABLE
|
||||
out
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||
if(NOT res EQUAL 0)
|
||||
set(out "${out}-${res}-NOTFOUND")
|
||||
endif()
|
||||
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(git_get_exact_tag _var)
|
||||
git_describe(out --exact-match ${ARGN})
|
||||
set(${_var} "${out}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
41
cmake/GetGitRevisionDescription.cmake.in
Normal file
41
cmake/GetGitRevisionDescription.cmake.in
Normal file
@@ -0,0 +1,41 @@
|
||||
#
|
||||
# Internal file for GetGitRevisionDescription.cmake
|
||||
#
|
||||
# Requires CMake 2.6 or newer (uses the 'function' command)
|
||||
#
|
||||
# Original Author:
|
||||
# 2009-2010 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
|
||||
# http://academic.cleardefinition.com
|
||||
# Iowa State University HCI Graduate Program/VRAC
|
||||
#
|
||||
# Copyright Iowa State University 2009-2010.
|
||||
# Distributed under the Boost Software License, Version 1.0.
|
||||
# (See accompanying file LICENSE.BOOST-1.0 or copy at
|
||||
# http://www.boost.org/LICENSE_1_0.txt)
|
||||
|
||||
set(HEAD_HASH)
|
||||
|
||||
file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024)
|
||||
|
||||
string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS)
|
||||
if(HEAD_CONTENTS MATCHES "ref")
|
||||
# named branch
|
||||
string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}")
|
||||
if(EXISTS "@GIT_DIR@/${HEAD_REF}")
|
||||
configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
else()
|
||||
configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY)
|
||||
file(READ "@GIT_DATA@/packed-refs" PACKED_REFS)
|
||||
if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}")
|
||||
set(HEAD_HASH "${CMAKE_MATCH_1}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
# detached HEAD
|
||||
configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY)
|
||||
endif()
|
||||
|
||||
if(NOT HEAD_HASH)
|
||||
file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024)
|
||||
string(STRIP "${HEAD_HASH}" HEAD_HASH)
|
||||
endif()
|
||||
@@ -13,17 +13,19 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
add_subdirectory(translations)
|
||||
|
||||
file(GLOB DATABASE_ICONS icons/database/*.png)
|
||||
|
||||
install(FILES ${DATABASE_ICONS} DESTINATION ${DATA_INSTALL_DIR}/icons/database)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(DIRECTORY icons/application/ DESTINATION share/icons/hicolor
|
||||
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
|
||||
FILES_MATCHING PATTERN "keepassx.png" PATTERN "keepassx.svgz")
|
||||
install(DIRECTORY icons/application/ DESTINATION share/icons/hicolor
|
||||
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
|
||||
FILES_MATCHING PATTERN "application-x-keepassx.png" PATTERN "application-x-keepassx.svgz")
|
||||
install(FILES linux/keepassx.desktop DESTINATION share/applications)
|
||||
install(FILES linux/keepassx.xml DESTINATION share/mime/packages)
|
||||
install(FILES linux/keepassx.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
install(FILES linux/keepassx.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
if(APPLE)
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
[Desktop Entry]
|
||||
Name=KeePassX
|
||||
GenericName=Cross Platform Password Manager
|
||||
GenericName=Password Manager
|
||||
GenericName[de]=Passwortverwaltung
|
||||
GenericName[es]=Gestor de contraseñas multiplataforma
|
||||
GenericName[es]=Gestor de contraseñas
|
||||
GenericName[fr]=Gestionnaire de mot de passe
|
||||
GenericName[ru]=менеджер паролей
|
||||
Exec=keepassx %f
|
||||
Icon=keepassx
|
||||
Terminal=false
|
||||
|
||||
26
share/translations/CMakeLists.txt
Normal file
26
share/translations/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
# Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 or (at your option)
|
||||
# version 3 of the License.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
file(GLOB TRANSLATION_FILES *.ts)
|
||||
get_filename_component(TRANSLATION_EN_ABS keepassx_en.ts ABSOLUTE)
|
||||
list(REMOVE_ITEM TRANSLATION_FILES keepassx_en.ts)
|
||||
list(REMOVE_ITEM TRANSLATION_FILES ${TRANSLATION_EN_ABS})
|
||||
message(STATUS ${TRANSLATION_FILES})
|
||||
|
||||
qt4_add_translation(QM_FILES ${TRANSLATION_FILES})
|
||||
|
||||
install(FILES ${QM_FILES} DESTINATION ${DATA_INSTALL_DIR}/translations)
|
||||
add_custom_target(translations DEPENDS ${QM_FILES})
|
||||
add_dependencies(${PROGNAME} translations)
|
||||
1205
share/translations/keepassx_cs.ts
Normal file
1205
share/translations/keepassx_cs.ts
Normal file
File diff suppressed because it is too large
Load Diff
1202
share/translations/keepassx_da.ts
Normal file
1202
share/translations/keepassx_da.ts
Normal file
File diff suppressed because it is too large
Load Diff
1204
share/translations/keepassx_de.ts
Normal file
1204
share/translations/keepassx_de.ts
Normal file
File diff suppressed because it is too large
Load Diff
1286
share/translations/keepassx_en.ts
Normal file
1286
share/translations/keepassx_en.ts
Normal file
File diff suppressed because it is too large
Load Diff
41
share/translations/keepassx_en_plurals.ts
Normal file
41
share/translations/keepassx_en_plurals.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.0" language="en_US">
|
||||
<context>
|
||||
<name>DatabaseWidget</name>
|
||||
<message numerus="yes">
|
||||
<source>Do you really want to move %n entry(s) to the recycle bin?</source>
|
||||
<translation>
|
||||
<numerusform>Do you really want to move %n entry to the recycle bin?</numerusform>
|
||||
<numerusform>Do you really want to move %n entries to the recycle bin?</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditEntryWidget</name>
|
||||
<message numerus="yes">
|
||||
<source>%n week(s)</source>
|
||||
<translation>
|
||||
<numerusform>%n week</numerusform>
|
||||
<numerusform>%n weeks</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
<message numerus="yes">
|
||||
<source>%n month(s)</source>
|
||||
<translation>
|
||||
<numerusform>%n month</numerusform>
|
||||
<numerusform>%n months</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditWidgetIcons</name>
|
||||
<message numerus="yes">
|
||||
<source>Can't delete icon. Still used by %n item(s).</source>
|
||||
<translation>
|
||||
<numerusform>Can't delete icon. Still used by %n item.</numerusform>
|
||||
<numerusform>Can't delete icon. Still used by %n items.</numerusform>
|
||||
</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
1205
share/translations/keepassx_es.ts
Normal file
1205
share/translations/keepassx_es.ts
Normal file
File diff suppressed because it is too large
Load Diff
1205
share/translations/keepassx_fr.ts
Normal file
1205
share/translations/keepassx_fr.ts
Normal file
File diff suppressed because it is too large
Load Diff
1205
share/translations/keepassx_id.ts
Normal file
1205
share/translations/keepassx_id.ts
Normal file
File diff suppressed because it is too large
Load Diff
1260
share/translations/keepassx_it.ts
Normal file
1260
share/translations/keepassx_it.ts
Normal file
File diff suppressed because it is too large
Load Diff
1260
share/translations/keepassx_ja.ts
Normal file
1260
share/translations/keepassx_ja.ts
Normal file
File diff suppressed because it is too large
Load Diff
1205
share/translations/keepassx_nl_NL.ts
Normal file
1205
share/translations/keepassx_nl_NL.ts
Normal file
File diff suppressed because it is too large
Load Diff
1264
share/translations/keepassx_pt_PT.ts
Normal file
1264
share/translations/keepassx_pt_PT.ts
Normal file
File diff suppressed because it is too large
Load Diff
1205
share/translations/keepassx_ru.ts
Normal file
1205
share/translations/keepassx_ru.ts
Normal file
File diff suppressed because it is too large
Load Diff
1205
share/translations/keepassx_sv.ts
Normal file
1205
share/translations/keepassx_sv.ts
Normal file
File diff suppressed because it is too large
Load Diff
1265
share/translations/keepassx_zh_CN.ts
Normal file
1265
share/translations/keepassx_zh_CN.ts
Normal file
File diff suppressed because it is too large
Load Diff
1203
share/translations/keepassx_zh_TW.ts
Normal file
1203
share/translations/keepassx_zh_TW.ts
Normal file
File diff suppressed because it is too large
Load Diff
12
share/translations/update.sh
Executable file
12
share/translations/update.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
BASEDIR=$(dirname $0)
|
||||
|
||||
cd $BASEDIR/../..
|
||||
|
||||
echo Updating source file
|
||||
lupdate -no-ui-lines -disable-heuristic similartext -locations none -no-obsolete src -ts share/translations/keepassx_en.ts
|
||||
lupdate -no-ui-lines -disable-heuristic similartext -locations none -pluralonly src -ts share/translations/keepassx_en_plurals.ts
|
||||
|
||||
echo Pulling translations from Transifex
|
||||
tx pull -a --minimum-perc=80
|
||||
@@ -17,6 +17,17 @@ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
configure_file(config-keepassx.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-keepassx.h)
|
||||
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REFSPEC GIT_HEAD)
|
||||
git_describe(GIT_DESCRIBE --long)
|
||||
|
||||
if (NOT GIT_HEAD OR NOT GIT_DESCRIBE)
|
||||
set(GIT_HEAD "")
|
||||
set(GIT_DESCRIBE "")
|
||||
endif()
|
||||
|
||||
configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY)
|
||||
|
||||
set(keepassx_SOURCES
|
||||
autotype/AutoType.cpp
|
||||
autotype/AutoTypeAction.cpp
|
||||
@@ -35,6 +46,7 @@ set(keepassx_SOURCES
|
||||
core/Entry.cpp
|
||||
core/EntryAttachments.cpp
|
||||
core/EntryAttributes.cpp
|
||||
core/EntrySearcher.cpp
|
||||
core/FilePath.cpp
|
||||
core/Global.h
|
||||
core/Group.cpp
|
||||
@@ -42,12 +54,15 @@ set(keepassx_SOURCES
|
||||
core/ListDeleter.h
|
||||
core/Metadata.cpp
|
||||
core/PasswordGenerator.cpp
|
||||
core/qlockfile.cpp
|
||||
core/qsavefile.cpp
|
||||
core/qsavefile_p.h
|
||||
core/SignalMultiplexer.cpp
|
||||
core/TimeDelta.cpp
|
||||
core/TimeInfo.cpp
|
||||
core/ToDbExporter.cpp
|
||||
core/Tools.cpp
|
||||
core/Translator.cpp
|
||||
core/Uuid.cpp
|
||||
core/qcommandlineoption.cpp
|
||||
core/qcommandlineparser.cpp
|
||||
@@ -57,6 +72,7 @@ set(keepassx_SOURCES
|
||||
crypto/SymmetricCipher.cpp
|
||||
crypto/SymmetricCipherBackend.h
|
||||
crypto/SymmetricCipherGcrypt.cpp
|
||||
format/CsvExporter.cpp
|
||||
format/KeePass1.h
|
||||
format/KeePass1Reader.cpp
|
||||
format/KeePass2.h
|
||||
@@ -73,6 +89,7 @@ set(keepassx_SOURCES
|
||||
gui/DatabaseSettingsWidget.cpp
|
||||
gui/DatabaseTabWidget.cpp
|
||||
gui/DatabaseWidget.cpp
|
||||
gui/DatabaseWidgetStateSync.cpp
|
||||
gui/DialogyWidget.cpp
|
||||
gui/DragTabBar.cpp
|
||||
gui/EditWidget.cpp
|
||||
@@ -126,6 +143,18 @@ if(NOT GCRYPT_HAS_SALSA20)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
set(keepassx_SOURCES
|
||||
${keepassx_SOURCES}
|
||||
core/qlockfile_unix.cpp
|
||||
)
|
||||
elseif(MINGW)
|
||||
set(keepassx_SOURCES
|
||||
${keepassx_SOURCES}
|
||||
core/qlockfile_win.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
set(keepassx_SOURCES_MAINEXE
|
||||
main.cpp
|
||||
)
|
||||
@@ -154,6 +183,7 @@ set(keepassx_MOC
|
||||
gui/DatabaseSettingsWidget.h
|
||||
gui/DatabaseTabWidget.h
|
||||
gui/DatabaseWidget.h
|
||||
gui/DatabaseWidgetStateSync.h
|
||||
gui/DialogyWidget.h
|
||||
gui/DragTabBar.h
|
||||
gui/EditWidget.h
|
||||
@@ -229,10 +259,6 @@ target_link_libraries(${PROGNAME}
|
||||
${GCRYPT_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(${PROGNAME} ${QT_QTDBUS_LIBRARY})
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)
|
||||
|
||||
if(APPLE)
|
||||
|
||||
@@ -23,12 +23,14 @@
|
||||
#include "autotype/AutoTypePlatformPlugin.h"
|
||||
#include "autotype/AutoTypeSelectDialog.h"
|
||||
#include "autotype/WildcardMatcher.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Entry.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/ListDeleter.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
AutoType* AutoType::m_instance = Q_NULLPTR;
|
||||
|
||||
@@ -146,6 +148,8 @@ void AutoType::performAutoType(const Entry* entry, QWidget* hideWindow, const QS
|
||||
window = m_plugin->activeWindow();
|
||||
}
|
||||
|
||||
QCoreApplication::processEvents(QEventLoop::AllEvents, 10);
|
||||
|
||||
Q_FOREACH (AutoTypeAction* action, actions) {
|
||||
if (m_plugin->activeWindow() != window) {
|
||||
qWarning("Active window changed, interrupting auto-type.");
|
||||
@@ -188,8 +192,12 @@ void AutoType::performGlobalAutoType(const QList<Database*>& dbList)
|
||||
|
||||
if (entryList.isEmpty()) {
|
||||
m_inAutoType = false;
|
||||
QString message = tr("Couldn't find an entry that matches the window title:");
|
||||
message.append("\n\n");
|
||||
message.append(windowTitle);
|
||||
MessageBox::information(Q_NULLPTR, tr("Auto-Type - KeePassX"), message);
|
||||
}
|
||||
else if (entryList.size() == 1) {
|
||||
else if ((entryList.size() == 1) && !config()->get("security/autotypeask").toBool()) {
|
||||
m_inAutoType = false;
|
||||
performAutoType(entryList.first(), Q_NULLPTR, sequenceHash[entryList.first()]);
|
||||
}
|
||||
@@ -469,9 +477,17 @@ QList<AutoTypeAction*> AutoType::createActionFromTemplate(const QString& tmpl, c
|
||||
QString resolved = entry->resolvePlaceholders(placeholder);
|
||||
if (placeholder != resolved) {
|
||||
Q_FOREACH (const QChar& ch, resolved) {
|
||||
if (ch == '\n') {
|
||||
list.append(new AutoTypeKey(Qt::Key_Enter));
|
||||
}
|
||||
else if (ch == '\t') {
|
||||
list.append(new AutoTypeKey(Qt::Key_Tab));
|
||||
}
|
||||
else {
|
||||
list.append(new AutoTypeChar(ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
@@ -499,6 +515,12 @@ QString AutoType::autoTypeSequence(const Entry* entry, const QString& windowTitl
|
||||
}
|
||||
}
|
||||
|
||||
if (!match && config()->get("AutoTypeEntryTitleMatch").toBool() && !entry->title().isEmpty()
|
||||
&& windowTitle.contains(entry->title(), Qt::CaseInsensitive)) {
|
||||
sequence = entry->defaultAutoTypeSequence();
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -89,5 +89,7 @@ void AutoTypeExecutor::execDelay(AutoTypeDelay* action)
|
||||
|
||||
void AutoTypeExecutor::execClearField(AutoTypeClearField* action)
|
||||
{
|
||||
Q_UNUSED(action);
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ AutoTypePlatformX11::AutoTypePlatformX11()
|
||||
m_classBlacklist << "desktop_window" << "gnome-panel"; // Gnome
|
||||
m_classBlacklist << "kdesktop" << "kicker"; // KDE 3
|
||||
m_classBlacklist << "Plasma"; // KDE 4
|
||||
m_classBlacklist << "plasmashell"; // KDE 5
|
||||
m_classBlacklist << "xfdesktop" << "xfce4-panel"; // Xfce 4
|
||||
|
||||
m_currentGlobalKey = static_cast<Qt::Key>(0);
|
||||
@@ -58,7 +59,9 @@ AutoTypePlatformX11::AutoTypePlatformX11()
|
||||
void AutoTypePlatformX11::unload()
|
||||
{
|
||||
// Restore the KeyboardMapping to its original state.
|
||||
if (m_currentRemapKeysym != NoSymbol) {
|
||||
AddKeysym(NoSymbol);
|
||||
}
|
||||
|
||||
if (m_keysymTable) {
|
||||
XFree(m_keysymTable);
|
||||
@@ -209,23 +212,26 @@ QString AutoTypePlatformX11::windowTitle(Window window, bool useBlacklist)
|
||||
unsigned long after;
|
||||
unsigned char* data = Q_NULLPTR;
|
||||
|
||||
// the window manager spec says we should read _NET_WM_NAME first, then fall back to WM_NAME
|
||||
|
||||
int retVal = XGetWindowProperty(m_dpy, window, m_atomNetWmName, 0, 1000, false, m_atomUtf8String,
|
||||
&type, &format, &nitems, &after, &data);
|
||||
|
||||
if (retVal != 0 && data) {
|
||||
if ((retVal == 0) && data) {
|
||||
title = QString::fromUtf8(reinterpret_cast<char*>(data));
|
||||
}
|
||||
else {
|
||||
XTextProperty textProp;
|
||||
retVal = XGetTextProperty(m_dpy, window, &textProp, m_atomWmName);
|
||||
if (retVal != 0 && textProp.value) {
|
||||
if ((retVal != 0) && textProp.value) {
|
||||
char** textList = Q_NULLPTR;
|
||||
int count;
|
||||
|
||||
if (textProp.encoding == m_atomUtf8String) {
|
||||
title = QString::fromUtf8(reinterpret_cast<char*>(textProp.value));
|
||||
}
|
||||
else if (XmbTextPropertyToTextList(m_dpy, &textProp, &textList, &count) == 0 && textList && count > 0) {
|
||||
else if ((XmbTextPropertyToTextList(m_dpy, &textProp, &textList, &count) == 0)
|
||||
&& textList && (count > 0)) {
|
||||
title = QString::fromLocal8Bit(textList[0]);
|
||||
}
|
||||
else if (textProp.encoding == m_atomString) {
|
||||
@@ -430,9 +436,21 @@ void AutoTypePlatformX11::updateKeymap()
|
||||
int mod_index, mod_key;
|
||||
XModifierKeymap *modifiers;
|
||||
|
||||
/* read keyboard map */
|
||||
if (m_xkb != NULL) XkbFreeKeyboard(m_xkb, XkbAllComponentsMask, True);
|
||||
m_xkb = XkbGetKeyboard (m_dpy, XkbCompatMapMask | XkbGeometryMask, XkbUseCoreKbd);
|
||||
|
||||
XDeviceInfo* devices;
|
||||
int num_devices;
|
||||
XID keyboard_id = XkbUseCoreKbd;
|
||||
devices = XListInputDevices(m_dpy, &num_devices);
|
||||
|
||||
for (int i = 0; i < num_devices; i++) {
|
||||
if (QString(devices[i].name) == "Virtual core XTEST keyboard") {
|
||||
keyboard_id = devices[i].id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_xkb = XkbGetKeyboard(m_dpy, XkbCompatMapMask | XkbGeometryMask, keyboard_id);
|
||||
|
||||
XDisplayKeycodes(m_dpy, &m_minKeycode, &m_maxKeycode);
|
||||
if (m_keysymTable != NULL) XFree(m_keysymTable);
|
||||
@@ -466,6 +484,14 @@ void AutoTypePlatformX11::updateKeymap()
|
||||
}
|
||||
}
|
||||
XFreeModifiermap(modifiers);
|
||||
|
||||
/* Xlib needs some time until the mapping is distributed to
|
||||
all clients */
|
||||
// TODO: we should probably only sleep while in the middle of typing something
|
||||
timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 30 * 1000 * 1000;
|
||||
nanosleep(&ts, Q_NULLPTR);
|
||||
}
|
||||
|
||||
bool AutoTypePlatformX11::isRemapKeycodeValid()
|
||||
@@ -532,13 +558,6 @@ int AutoTypePlatformX11::AddKeysym(KeySym keysym)
|
||||
XFlush(m_dpy);
|
||||
updateKeymap();
|
||||
|
||||
/* Xlib needs some time until the mapping is distributed to
|
||||
all clients */
|
||||
timespec ts;
|
||||
ts.tv_sec = 0;
|
||||
ts.tv_nsec = 10 * 1000 * 1000;
|
||||
nanosleep(&ts, Q_NULLPTR);
|
||||
|
||||
return m_remapKeycode;
|
||||
}
|
||||
|
||||
@@ -636,7 +655,7 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
|
||||
int keycode;
|
||||
|
||||
if (keysym == NoSymbol) {
|
||||
qWarning("No such key: keysym=0x%lX", static_cast<long>(keysym));
|
||||
qWarning("No such key: keysym=0x%lX", keysym);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -655,23 +674,56 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
|
||||
|
||||
Window root, child;
|
||||
int root_x, root_y, x, y;
|
||||
unsigned int mask;
|
||||
unsigned int saved_mask;
|
||||
unsigned int wanted_mask = 0;
|
||||
unsigned int original_mask;
|
||||
|
||||
XQueryPointer(m_dpy, event.root, &root, &child, &root_x, &root_y, &x, &y, &mask);
|
||||
saved_mask = mask;
|
||||
XQueryPointer(m_dpy, event.root, &root, &child, &root_x, &root_y, &x, &y, &original_mask);
|
||||
|
||||
/* determine keycode and mask for the given keysym */
|
||||
keycode = GetKeycode(keysym, &mask);
|
||||
keycode = GetKeycode(keysym, &wanted_mask);
|
||||
if (keycode < 8 || keycode > 255) {
|
||||
qWarning("Unable to get valid keycode for key: keysym=0x%lX", static_cast<long>(keysym));
|
||||
qWarning("Unable to get valid keycode for key: keysym=0x%lX", keysym);
|
||||
return;
|
||||
}
|
||||
|
||||
/* release all modifiers */
|
||||
SendModifier(&event, mask, KeyRelease);
|
||||
event.state = original_mask;
|
||||
|
||||
SendModifier(&event, mask, KeyPress);
|
||||
// modifiers that need to be pressed but aren't
|
||||
unsigned int press_mask = wanted_mask & ~original_mask;
|
||||
|
||||
// modifiers that are pressed but maybe shouldn't
|
||||
unsigned int release_check_mask = original_mask & ~wanted_mask;
|
||||
|
||||
// modifiers we need to release before sending the keycode
|
||||
unsigned int release_mask = 0;
|
||||
|
||||
// check every release_check_mask individually if it affects the keysym we would generate
|
||||
// if it doesn't we probably don't need to release it
|
||||
for (int mod_index = ShiftMapIndex; mod_index <= Mod5MapIndex; mod_index ++) {
|
||||
if (release_check_mask & (1 << mod_index)) {
|
||||
unsigned int mods_rtrn;
|
||||
KeySym keysym_rtrn;
|
||||
XkbTranslateKeyCode(m_xkb, keycode, wanted_mask | (1 << mod_index), &mods_rtrn, &keysym_rtrn);
|
||||
|
||||
if (keysym_rtrn != keysym) {
|
||||
release_mask |= (1 << mod_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finally check if the combination of pressed modifiers that we chose to ignore affects the keysym
|
||||
unsigned int mods_rtrn;
|
||||
KeySym keysym_rtrn;
|
||||
XkbTranslateKeyCode(m_xkb, keycode, wanted_mask | (release_check_mask & ~release_mask), &mods_rtrn, &keysym_rtrn);
|
||||
if (keysym_rtrn != keysym) {
|
||||
// oh well, release all the modifiers we don't want
|
||||
release_mask = release_check_mask;
|
||||
}
|
||||
|
||||
/* release all modifiers */
|
||||
SendModifier(&event, release_mask, KeyRelease);
|
||||
|
||||
SendModifier(&event, press_mask, KeyPress);
|
||||
|
||||
/* press and release key */
|
||||
event.keycode = keycode;
|
||||
@@ -679,10 +731,10 @@ void AutoTypePlatformX11::SendKeyPressedEvent(KeySym keysym)
|
||||
SendEvent(&event, KeyRelease);
|
||||
|
||||
/* release the modifiers */
|
||||
SendModifier(&event, mask, KeyRelease);
|
||||
SendModifier(&event, press_mask, KeyRelease);
|
||||
|
||||
/* restore the old keyboard mask */
|
||||
SendModifier(&event, saved_mask, KeyPress);
|
||||
SendModifier(&event, release_mask, KeyPress);
|
||||
}
|
||||
|
||||
int AutoTypePlatformX11::MyErrorHandler(Display* my_dpy, XErrorEvent* event)
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
#define KEEPASSX_VERSION "${KEEPASSX_VERSION}"
|
||||
|
||||
#define KEEPASSX_SOURCE_DIR "${CMAKE_SOURCE_DIR}"
|
||||
#define KEEPASSX_BINARY_DIR "${CMAKE_BINARY_DIR}"
|
||||
|
||||
#define KEEPASSX_PREFIX_DIR "${CMAKE_INSTALL_PREFIX}"
|
||||
#define KEEPASSX_PLUGIN_DIR "${PLUGIN_INSTALL_DIR}"
|
||||
#define KEEPASSX_DATA_DIR "${DATA_INSTALL_DIR}"
|
||||
|
||||
#cmakedefine HAVE_PR_SET_DUMPABLE 1
|
||||
#cmakedefine HAVE_RLIMIT_CORE 1
|
||||
|
||||
@@ -71,7 +71,8 @@ Config::Config(QObject* parent)
|
||||
userPath += "/keepassx/";
|
||||
#else
|
||||
userPath = QDir::fromNativeSeparators(QDesktopServices::storageLocation(QDesktopServices::DataLocation));
|
||||
// storageLocation() appends the application name ("/keepassx/") to the end
|
||||
// storageLocation() appends the application name ("/keepassx") to the end
|
||||
userPath += "/";
|
||||
#endif
|
||||
|
||||
userPath += "keepassx2.ini";
|
||||
@@ -88,17 +89,23 @@ void Config::init(const QString& fileName)
|
||||
m_settings.reset(new QSettings(fileName, QSettings::IniFormat));
|
||||
|
||||
m_defaults.insert("RememberLastDatabases", true);
|
||||
m_defaults.insert("RememberLastKeyFiles", true);
|
||||
m_defaults.insert("OpenPreviousDatabasesOnStartup", true);
|
||||
m_defaults.insert("ModifiedOnExpandedStateChanges", true);
|
||||
m_defaults.insert("AutoSaveAfterEveryChange", false);
|
||||
m_defaults.insert("AutoSaveOnExit", false);
|
||||
m_defaults.insert("ShowToolbar", true);
|
||||
m_defaults.insert("MinimizeOnCopy", false);
|
||||
m_defaults.insert("UseGroupIconOnEntryCreation", false);
|
||||
m_defaults.insert("AutoTypeEntryTitleMatch", true);
|
||||
m_defaults.insert("security/clearclipboard", true);
|
||||
m_defaults.insert("security/clearclipboardtimeout", 10);
|
||||
m_defaults.insert("security/lockdatabaseidle", false);
|
||||
m_defaults.insert("security/lockdatabaseidlesec", 10);
|
||||
m_defaults.insert("security/passwordscleartext", false);
|
||||
m_defaults.insert("security/autotypeask", true);
|
||||
m_defaults.insert("GUI/Language", "system");
|
||||
m_defaults.insert("GUI/ShowTrayIcon", false);
|
||||
m_defaults.insert("GUI/MinimizeToTray", false);
|
||||
}
|
||||
|
||||
Config* Config::instance()
|
||||
@@ -110,7 +117,7 @@ Config* Config::instance()
|
||||
return m_instance;
|
||||
}
|
||||
|
||||
void Config::createConfigFromFile(QString file)
|
||||
void Config::createConfigFromFile(const QString& file)
|
||||
{
|
||||
Q_ASSERT(!m_instance);
|
||||
m_instance = new Config(file, qApp);
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
void set(const QString& key, const QVariant& value);
|
||||
|
||||
static Config* instance();
|
||||
static void createConfigFromFile(QString file);
|
||||
static void createConfigFromFile(const QString& file);
|
||||
static void createTempFileInstance();
|
||||
|
||||
private:
|
||||
|
||||
@@ -37,7 +37,7 @@ Database::Database()
|
||||
{
|
||||
m_data.cipher = KeePass2::CIPHER_AES;
|
||||
m_data.compressionAlgo = CompressionGZip;
|
||||
m_data.transformRounds = 50000;
|
||||
m_data.transformRounds = 100000;
|
||||
m_data.hasKey = false;
|
||||
|
||||
setRootGroup(new Group());
|
||||
@@ -188,32 +188,51 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo)
|
||||
m_data.compressionAlgo = algo;
|
||||
}
|
||||
|
||||
void Database::setTransformRounds(quint64 rounds)
|
||||
bool Database::setTransformRounds(quint64 rounds)
|
||||
{
|
||||
if (m_data.transformRounds != rounds) {
|
||||
quint64 oldRounds = m_data.transformRounds;
|
||||
|
||||
m_data.transformRounds = rounds;
|
||||
|
||||
if (m_data.hasKey) {
|
||||
setKey(m_data.key);
|
||||
if (!setKey(m_data.key)) {
|
||||
m_data.transformRounds = oldRounds;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed,
|
||||
bool updateChangedTime)
|
||||
{
|
||||
bool ok;
|
||||
QString errorString;
|
||||
|
||||
QByteArray transformedMasterKey =
|
||||
key.transform(transformSeed, transformRounds(), &ok, &errorString);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_data.key = key;
|
||||
m_data.transformSeed = transformSeed;
|
||||
m_data.transformedMasterKey = key.transform(transformSeed, transformRounds());
|
||||
m_data.transformedMasterKey = transformedMasterKey;
|
||||
m_data.hasKey = true;
|
||||
if (updateChangedTime) {
|
||||
m_metadata->setMasterKeyChanged(Tools::currentDateTimeUtc());
|
||||
}
|
||||
Q_EMIT modifiedImmediate();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Database::setKey(const CompositeKey& key)
|
||||
bool Database::setKey(const CompositeKey& key)
|
||||
{
|
||||
setKey(key, randomGen()->randomArray(32));
|
||||
return setKey(key, randomGen()->randomArray(32));
|
||||
}
|
||||
|
||||
bool Database::hasKey() const
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
* Sets group as the root group and takes ownership of it.
|
||||
* Warning: Be careful when calling this method as it doesn't
|
||||
* emit any notifications so e.g. models aren't updated.
|
||||
* The caller is responsible for cleaning up the pervious
|
||||
* The caller is responsible for cleaning up the previous
|
||||
root group.
|
||||
*/
|
||||
void setRootGroup(Group* group);
|
||||
@@ -90,13 +90,14 @@ public:
|
||||
|
||||
void setCipher(const Uuid& cipher);
|
||||
void setCompressionAlgo(Database::CompressionAlgorithm algo);
|
||||
void setTransformRounds(quint64 rounds);
|
||||
void setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime = true);
|
||||
bool setTransformRounds(quint64 rounds);
|
||||
bool setKey(const CompositeKey& key, const QByteArray& transformSeed,
|
||||
bool updateChangedTime = true);
|
||||
|
||||
/**
|
||||
* Sets the database key and generates a random transform seed.
|
||||
*/
|
||||
void setKey(const CompositeKey& key);
|
||||
bool setKey(const CompositeKey& key);
|
||||
bool hasKey() const;
|
||||
bool verifyKey(const CompositeKey& key) const;
|
||||
void recycleEntry(Entry* entry);
|
||||
|
||||
@@ -579,25 +579,6 @@ const Database* Entry::database() const
|
||||
}
|
||||
}
|
||||
|
||||
bool Entry::match(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts);
|
||||
Q_FOREACH (const QString& word, wordList) {
|
||||
if (!wordMatch(word, caseSensitivity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Entry::wordMatch(const QString& word, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
return title().contains(word, caseSensitivity) ||
|
||||
username().contains(word, caseSensitivity) ||
|
||||
url().contains(word, caseSensitivity) ||
|
||||
notes().contains(word, caseSensitivity);
|
||||
}
|
||||
|
||||
QString Entry::resolvePlaceholders(const QString& str) const
|
||||
{
|
||||
QString result = str;
|
||||
|
||||
@@ -141,7 +141,6 @@ public:
|
||||
void setGroup(Group* group);
|
||||
|
||||
void setUpdateTimeinfo(bool value);
|
||||
bool match(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity);
|
||||
|
||||
Q_SIGNALS:
|
||||
/**
|
||||
@@ -157,7 +156,6 @@ private Q_SLOTS:
|
||||
void updateModifiedSinceBegin();
|
||||
|
||||
private:
|
||||
bool wordMatch(const QString& word, Qt::CaseSensitivity caseSensitivity);
|
||||
const Database* database() const;
|
||||
template <class T> bool set(T& property, const T& value);
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ QList<QString> EntryAttachments::keys() const
|
||||
return m_attachments.keys();
|
||||
}
|
||||
|
||||
bool EntryAttachments::hasKey(const QString& key) const
|
||||
{
|
||||
return m_attachments.keys().contains(key);
|
||||
}
|
||||
|
||||
QList<QByteArray> EntryAttachments::values() const
|
||||
{
|
||||
return m_attachments.values();
|
||||
|
||||
@@ -30,6 +30,7 @@ class EntryAttachments : public QObject
|
||||
public:
|
||||
explicit EntryAttachments(QObject* parent = Q_NULLPTR);
|
||||
QList<QString> keys() const;
|
||||
bool hasKey(const QString& key) const;
|
||||
QList<QByteArray> values() const;
|
||||
QByteArray value(const QString& key) const;
|
||||
void set(const QString& key, const QByteArray& value);
|
||||
|
||||
@@ -36,6 +36,11 @@ QList<QString> EntryAttributes::keys() const
|
||||
return m_attributes.keys();
|
||||
}
|
||||
|
||||
bool EntryAttributes::hasKey(const QString& key) const
|
||||
{
|
||||
return m_attributes.keys().contains(key);
|
||||
}
|
||||
|
||||
QList<QString> EntryAttributes::customKeys()
|
||||
{
|
||||
QList<QString> customKeys;
|
||||
|
||||
@@ -32,6 +32,7 @@ class EntryAttributes : public QObject
|
||||
public:
|
||||
explicit EntryAttributes(QObject* parent = Q_NULLPTR);
|
||||
QList<QString> keys() const;
|
||||
bool hasKey(const QString& key) const;
|
||||
QList<QString> customKeys();
|
||||
QString value(const QString& key) const;
|
||||
bool isProtected(const QString& key) const;
|
||||
|
||||
65
src/core/EntrySearcher.cpp
Normal file
65
src/core/EntrySearcher.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EntrySearcher.h"
|
||||
|
||||
#include "core/Group.h"
|
||||
|
||||
QList<Entry*> EntrySearcher::search(const QString &searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
if (!group->resolveSearchingEnabled()) {
|
||||
return QList<Entry*>();
|
||||
}
|
||||
|
||||
return searchEntries(searchTerm, group, caseSensitivity);
|
||||
}
|
||||
|
||||
QList<Entry*> EntrySearcher::searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
QList<Entry*> searchResult;
|
||||
|
||||
Q_FOREACH (Entry* entry, group->entries()) {
|
||||
searchResult.append(matchEntry(searchTerm, entry, caseSensitivity));
|
||||
}
|
||||
Q_FOREACH (Group* childGroup, group->children()) {
|
||||
if (childGroup->searchingEnabled() != Group::Disable) {
|
||||
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
|
||||
}
|
||||
}
|
||||
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
QList<Entry*> EntrySearcher::matchEntry(const QString& searchTerm, Entry* entry, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts);
|
||||
Q_FOREACH (const QString& word, wordList) {
|
||||
if (!wordMatch(word, entry, caseSensitivity)) {
|
||||
return QList<Entry*>();
|
||||
}
|
||||
}
|
||||
|
||||
return QList<Entry*>() << entry;
|
||||
}
|
||||
|
||||
bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
return entry->title().contains(word, caseSensitivity) ||
|
||||
entry->username().contains(word, caseSensitivity) ||
|
||||
entry->url().contains(word, caseSensitivity) ||
|
||||
entry->notes().contains(word, caseSensitivity);
|
||||
}
|
||||
37
src/core/EntrySearcher.h
Normal file
37
src/core/EntrySearcher.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Florian Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_ENTRYSEARCHER_H
|
||||
#define KEEPASSX_ENTRYSEARCHER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
|
||||
class Group;
|
||||
class Entry;
|
||||
|
||||
class EntrySearcher
|
||||
{
|
||||
public:
|
||||
QList<Entry*> search(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
|
||||
private:
|
||||
QList<Entry*> searchEntries(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
|
||||
QList<Entry*> matchEntry(const QString& searchTerm, Entry* entry, Qt::CaseSensitivity caseSensitivity);
|
||||
bool wordMatch(const QString &word, Entry *entry, Qt::CaseSensitivity caseSensitivity);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ENTRYSEARCHER_H
|
||||
14
src/core/Exporter.h
Normal file
14
src/core/Exporter.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#ifndef KEEPASSX_EXPORTER_H
|
||||
#define KEEPASSX_EXPORTER_H
|
||||
|
||||
class Database;
|
||||
class Group;
|
||||
|
||||
class Exporter
|
||||
{
|
||||
public:
|
||||
virtual Database* exportGroup(Group* group) = 0;
|
||||
virtual ~Exporter() {}
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_EXPORTER_H
|
||||
@@ -49,13 +49,20 @@ QString FilePath::pluginPath(const QString& name)
|
||||
|
||||
pluginPaths << QCoreApplication::applicationDirPath();
|
||||
|
||||
QString systemPluginDir = KEEPASSX_PLUGIN_DIR;
|
||||
if (systemPluginDir != ".") {
|
||||
if (!QDir(systemPluginDir).isAbsolute()) {
|
||||
systemPluginDir = QCoreApplication::applicationDirPath() + "/../" + systemPluginDir;
|
||||
systemPluginDir = QDir(systemPluginDir).canonicalPath();
|
||||
QString configuredPluginDir = KEEPASSX_PLUGIN_DIR;
|
||||
if (configuredPluginDir != ".") {
|
||||
if (QDir(configuredPluginDir).isAbsolute()) {
|
||||
pluginPaths << configuredPluginDir;
|
||||
}
|
||||
else {
|
||||
QString relativePluginDir = QString("%1/../%2")
|
||||
.arg(QCoreApplication::applicationDirPath(), configuredPluginDir);
|
||||
pluginPaths << QDir(relativePluginDir).canonicalPath();
|
||||
|
||||
QString absolutePluginDir = QString("%1/%2")
|
||||
.arg(KEEPASSX_PREFIX_DIR, configuredPluginDir);
|
||||
pluginPaths << QDir(absolutePluginDir).canonicalPath();
|
||||
}
|
||||
pluginPaths << systemPluginDir;
|
||||
}
|
||||
|
||||
QStringList dirFilter;
|
||||
@@ -164,6 +171,10 @@ QIcon FilePath::onOffIcon(const QString& category, const QString& name)
|
||||
|
||||
FilePath::FilePath()
|
||||
{
|
||||
const QString appDirPath = QCoreApplication::applicationDirPath();
|
||||
bool isDataDirAbsolute = QDir::isAbsolutePath(KEEPASSX_DATA_DIR);
|
||||
Q_UNUSED(isDataDirAbsolute);
|
||||
|
||||
if (false) {
|
||||
}
|
||||
#ifdef QT_DEBUG
|
||||
@@ -171,15 +182,19 @@ FilePath::FilePath()
|
||||
}
|
||||
#endif
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/../share/keepassx")) {
|
||||
else if (isDataDirAbsolute && testSetDir(KEEPASSX_DATA_DIR)) {
|
||||
}
|
||||
else if (!isDataDirAbsolute && testSetDir(QString("%1/../%2").arg(appDirPath, KEEPASSX_DATA_DIR))) {
|
||||
}
|
||||
else if (!isDataDirAbsolute && testSetDir(QString("%1/%2").arg(KEEPASSX_PREFIX_DIR, KEEPASSX_DATA_DIR))) {
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_MAC
|
||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/../Resources")) {
|
||||
else if (testSetDir(appDirPath + "/../Resources")) {
|
||||
}
|
||||
#endif
|
||||
#ifdef Q_OS_WIN
|
||||
else if (testSetDir(QCoreApplication::applicationDirPath() + "/share")) {
|
||||
else if (testSetDir(appDirPath + "/share")) {
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -248,11 +248,9 @@ void Group::setExpanded(bool expanded)
|
||||
if (m_data.isExpanded != expanded) {
|
||||
m_data.isExpanded = expanded;
|
||||
updateTimeinfo();
|
||||
if (config()->get("ModifiedOnExpandedStateChanges").toBool()) {
|
||||
Q_EMIT modified();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Group::setDefaultAutoTypeSequence(const QString& sequence)
|
||||
{
|
||||
@@ -436,6 +434,20 @@ QList<const Group*> Group::groupsRecursive(bool includeSelf) const
|
||||
groupList.append(this);
|
||||
}
|
||||
|
||||
Q_FOREACH (const Group* group, m_children) {
|
||||
groupList.append(group->groupsRecursive(true));
|
||||
}
|
||||
|
||||
return groupList;
|
||||
}
|
||||
|
||||
QList<Group*> Group::groupsRecursive(bool includeSelf)
|
||||
{
|
||||
QList<Group*> groupList;
|
||||
if (includeSelf) {
|
||||
groupList.append(this);
|
||||
}
|
||||
|
||||
Q_FOREACH (Group* group, m_children) {
|
||||
groupList.append(group->groupsRecursive(true));
|
||||
}
|
||||
@@ -500,22 +512,6 @@ void Group::copyDataFrom(const Group* other)
|
||||
m_lastTopVisibleEntry = other->m_lastTopVisibleEntry;
|
||||
}
|
||||
|
||||
Database* Group::exportToDb()
|
||||
{
|
||||
Q_ASSERT(database());
|
||||
|
||||
Database* db = new Database();
|
||||
Group* clonedGroup = clone(Entry::CloneNewUuid | Entry::CloneIncludeHistory);
|
||||
clonedGroup->setParent(db->rootGroup());
|
||||
|
||||
QSet<Uuid> customIcons = customIconsRecursive();
|
||||
db->metadata()->copyCustomIcons(customIcons, database()->metadata());
|
||||
|
||||
db->copyAttributesFrom(database());
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
void Group::addEntry(Entry* entry)
|
||||
{
|
||||
Q_ASSERT(entry);
|
||||
@@ -612,24 +608,7 @@ void Group::recCreateDelObjects()
|
||||
}
|
||||
}
|
||||
|
||||
QList<Entry*> Group::search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity,
|
||||
bool resolveInherit)
|
||||
{
|
||||
QList<Entry*> searchResult;
|
||||
if (includeInSearch(resolveInherit)) {
|
||||
Q_FOREACH (Entry* entry, m_entries) {
|
||||
if (entry->match(searchTerm, caseSensitivity)) {
|
||||
searchResult.append(entry);
|
||||
}
|
||||
}
|
||||
Q_FOREACH (Group* group, m_children) {
|
||||
searchResult.append(group->search(searchTerm, caseSensitivity, false));
|
||||
}
|
||||
}
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
bool Group::includeInSearch(bool resolveInherit)
|
||||
bool Group::resolveSearchingEnabled() const
|
||||
{
|
||||
switch (m_data.searchingEnabled) {
|
||||
case Inherit:
|
||||
@@ -637,12 +616,27 @@ bool Group::includeInSearch(bool resolveInherit)
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (resolveInherit) {
|
||||
return m_parent->includeInSearch(true);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
return m_parent->resolveSearchingEnabled();
|
||||
}
|
||||
case Enable:
|
||||
return true;
|
||||
case Disable:
|
||||
return false;
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Group::resolveAutoTypeEnabled() const
|
||||
{
|
||||
switch (m_data.autoTypeEnabled) {
|
||||
case Inherit:
|
||||
if (!m_parent) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return m_parent->resolveAutoTypeEnabled();
|
||||
}
|
||||
case Enable:
|
||||
return true;
|
||||
|
||||
@@ -65,6 +65,8 @@ public:
|
||||
QString defaultAutoTypeSequence() const;
|
||||
Group::TriState autoTypeEnabled() const;
|
||||
Group::TriState searchingEnabled() const;
|
||||
bool resolveSearchingEnabled() const;
|
||||
bool resolveAutoTypeEnabled() const;
|
||||
Entry* lastTopVisibleEntry() const;
|
||||
bool isExpired() const;
|
||||
|
||||
@@ -99,6 +101,7 @@ public:
|
||||
const QList<Entry*>& entries() const;
|
||||
QList<Entry*> entriesRecursive(bool includeHistoryItems = false) const;
|
||||
QList<const Group*> groupsRecursive(bool includeSelf) const;
|
||||
QList<Group*> groupsRecursive(bool includeSelf);
|
||||
QSet<Uuid> customIconsRecursive() const;
|
||||
/**
|
||||
* Creates a duplicate of this group including all child entries and groups.
|
||||
@@ -109,10 +112,6 @@ public:
|
||||
*/
|
||||
Group* clone(Entry::CloneFlags entryFlags = Entry::CloneNewUuid | Entry::CloneResetTimeInfo) const;
|
||||
void copyDataFrom(const Group* other);
|
||||
Database* exportToDb();
|
||||
|
||||
QList<Entry*> search(const QString& searchTerm, Qt::CaseSensitivity caseSensitivity,
|
||||
bool resolveInherit = true);
|
||||
|
||||
Q_SIGNALS:
|
||||
void dataChanged(Group* group);
|
||||
@@ -147,7 +146,6 @@ private:
|
||||
void cleanupParent();
|
||||
void recCreateDelObjects();
|
||||
void updateTimeinfo();
|
||||
bool includeInSearch(bool resolveInherit);
|
||||
|
||||
QPointer<Database> m_db;
|
||||
Uuid m_uuid;
|
||||
|
||||
@@ -67,7 +67,14 @@ bool InactivityTimer::eventFilter(QObject* watched, QEvent* event)
|
||||
|
||||
void InactivityTimer::timeout()
|
||||
{
|
||||
// make sure we don't emit the signal a second time while it's still processed
|
||||
if (!m_emitMutx.tryLock()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_active && !m_timer->isActive()) {
|
||||
Q_EMIT inactivityDetected();
|
||||
}
|
||||
|
||||
m_emitMutx.unlock();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#ifndef KEEPASSX_INACTIVITYTIMER_H
|
||||
#define KEEPASSX_INACTIVITYTIMER_H
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
|
||||
#include "core/Global.h"
|
||||
@@ -46,6 +47,7 @@ private Q_SLOTS:
|
||||
private:
|
||||
QTimer* m_timer;
|
||||
bool m_active;
|
||||
QMutex m_emitMutx;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_INACTIVITYTIMER_H
|
||||
|
||||
@@ -343,6 +343,22 @@ void Metadata::addCustomIcon(const Uuid& uuid, const QImage& icon)
|
||||
Q_EMIT modified();
|
||||
}
|
||||
|
||||
void Metadata::addCustomIconScaled(const Uuid& uuid, const QImage& icon)
|
||||
{
|
||||
QImage iconScaled;
|
||||
|
||||
// scale down to 128x128 if icon is larger
|
||||
if (icon.width() > 128 || icon.height() > 128) {
|
||||
iconScaled = icon.scaled(QSize(128, 128), Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
}
|
||||
else {
|
||||
iconScaled = icon;
|
||||
}
|
||||
|
||||
addCustomIcon(uuid, iconScaled);
|
||||
}
|
||||
|
||||
void Metadata::removeCustomIcon(const Uuid& uuid)
|
||||
{
|
||||
Q_ASSERT(!uuid.isNull());
|
||||
|
||||
@@ -115,6 +115,7 @@ public:
|
||||
void setProtectNotes(bool value);
|
||||
// void setAutoEnableVisualHiding(bool value);
|
||||
void addCustomIcon(const Uuid& uuid, const QImage& icon);
|
||||
void addCustomIconScaled(const Uuid& uuid, const QImage& icon);
|
||||
void removeCustomIcon(const Uuid& uuid);
|
||||
void copyCustomIcons(const QSet<Uuid>& iconList, const Metadata* otherMetadata);
|
||||
void setRecycleBinEnabled(bool value);
|
||||
|
||||
39
src/core/ToDbExporter.cpp
Normal file
39
src/core/ToDbExporter.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ToDbExporter.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
|
||||
Database* ToDbExporter::exportGroup(Group* group)
|
||||
{
|
||||
Database* oldDb = group->database();
|
||||
Q_ASSERT(oldDb);
|
||||
|
||||
Database* db = new Database();
|
||||
Group* clonedGroup = group->clone(Entry::CloneNewUuid | Entry::CloneIncludeHistory);
|
||||
clonedGroup->setParent(db->rootGroup());
|
||||
|
||||
QSet<Uuid> customIcons = group->customIconsRecursive();
|
||||
db->metadata()->copyCustomIcons(customIcons, oldDb->metadata());
|
||||
|
||||
db->copyAttributesFrom(oldDb);
|
||||
|
||||
return db;
|
||||
}
|
||||
33
src/core/ToDbExporter.h
Normal file
33
src/core/ToDbExporter.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
* Copyright (C) 2014 Florian Geyer <blueice@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_TODBEXPORTER_H
|
||||
#define KEEPASSX_TODBEXPORTER_H
|
||||
|
||||
#include "core/Exporter.h"
|
||||
|
||||
class Database;
|
||||
class Group;
|
||||
|
||||
class ToDbExporter : Exporter
|
||||
{
|
||||
public:
|
||||
Database* exportGroup(Group* group);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TODBEXPORTER_H
|
||||
@@ -39,10 +39,12 @@
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
#include <sys/prctl.h>
|
||||
#elif defined(HAVE_RLIMIT_CORE)
|
||||
#include <sys/resource.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
@@ -222,21 +224,23 @@ QString platform()
|
||||
|
||||
void disableCoreDumps()
|
||||
{
|
||||
bool success = false;
|
||||
// default to true
|
||||
// there is no point in printing a warning if this is not implemented on the platform
|
||||
bool success = true;
|
||||
|
||||
// prefer PR_SET_DUMPABLE since that also prevents ptrace
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
success = (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||
#elif defined(HAVE_RLIMIT_CORE)
|
||||
#if defined(HAVE_RLIMIT_CORE)
|
||||
struct rlimit limit;
|
||||
limit.rlim_cur = 0;
|
||||
limit.rlim_max = 0;
|
||||
success = (setrlimit(RLIMIT_CORE, &limit) == 0);
|
||||
success = success && (setrlimit(RLIMIT_CORE, &limit) == 0);
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_PR_SET_DUMPABLE)
|
||||
success = success && (prctl(PR_SET_DUMPABLE, 0) == 0);
|
||||
#endif
|
||||
|
||||
// Mac OS X
|
||||
#ifdef HAVE_PT_DENY_ATTACH
|
||||
// make sure setrlimit() and ptrace() succeeded
|
||||
success = success && (ptrace(PT_DENY_ATTACH, 0, 0, 0) == 0);
|
||||
#endif
|
||||
|
||||
|
||||
127
src/core/Translator.cpp
Normal file
127
src/core/Translator.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Translator.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QLibraryInfo>
|
||||
#include <QLocale>
|
||||
#include <QRegExp>
|
||||
#include <QTranslator>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/FilePath.h"
|
||||
|
||||
void Translator::installTranslator()
|
||||
{
|
||||
QString language = config()->get("GUI/Language").toString();
|
||||
if (language == "system" || language.isEmpty()) {
|
||||
language = QLocale::system().name();
|
||||
}
|
||||
|
||||
if (!installTranslator(language)) {
|
||||
// English fallback still needs translations for plurals
|
||||
if (!installTranslator("en_plurals")) {
|
||||
qWarning("Couldn't load translations.");
|
||||
}
|
||||
}
|
||||
|
||||
installQtTranslator(language);
|
||||
|
||||
availableLanguages();
|
||||
}
|
||||
|
||||
QList<QPair<QString, QString> > Translator::availableLanguages()
|
||||
{
|
||||
QStringList paths;
|
||||
#ifdef QT_DEBUG
|
||||
paths.append(QString("%1/share/translations").arg(KEEPASSX_BINARY_DIR));
|
||||
#endif
|
||||
paths.append(filePath()->dataPath("translations"));
|
||||
|
||||
QList<QPair<QString, QString> > languages;
|
||||
languages.append(QPair<QString, QString>("system", "System default"));
|
||||
|
||||
QRegExp regExp("keepassx_([a-zA-Z_]+)\\.qm", Qt::CaseInsensitive, QRegExp::RegExp2);
|
||||
Q_FOREACH (const QString& path, paths) {
|
||||
Q_FOREACH (const QString& filename, QDir(path).entryList()) {
|
||||
if (regExp.exactMatch(filename)) {
|
||||
QString langcode = regExp.cap(1);
|
||||
if (langcode == "en_plurals") {
|
||||
langcode = "en";
|
||||
}
|
||||
|
||||
QLocale locale(langcode);
|
||||
QString languageStr = QLocale::languageToString(locale.language());
|
||||
QString countryStr;
|
||||
if (langcode.contains("_")) {
|
||||
countryStr = QString(" (%1)").arg(QLocale::countryToString(locale.country()));
|
||||
}
|
||||
|
||||
QPair<QString, QString> language(langcode, languageStr + countryStr);
|
||||
languages.append(language);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return languages;
|
||||
}
|
||||
|
||||
bool Translator::installTranslator(const QString& language)
|
||||
{
|
||||
QStringList paths;
|
||||
#ifdef QT_DEBUG
|
||||
paths.append(QString("%1/share/translations").arg(KEEPASSX_BINARY_DIR));
|
||||
#endif
|
||||
paths.append(filePath()->dataPath("translations"));
|
||||
|
||||
Q_FOREACH (const QString& path, paths) {
|
||||
if (installTranslator(language, path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Translator::installTranslator(const QString& language, const QString& path)
|
||||
{
|
||||
QTranslator* translator = new QTranslator(qApp);
|
||||
if (translator->load(QString("keepassx_").append(language), path)) {
|
||||
QCoreApplication::installTranslator(translator);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
delete translator;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Translator::installQtTranslator(const QString& language)
|
||||
{
|
||||
QTranslator* qtTranslator = new QTranslator(qApp);
|
||||
if (qtTranslator->load(QString("%1/qt_%2").arg(QLibraryInfo::location(QLibraryInfo::TranslationsPath), language))) {
|
||||
QCoreApplication::installTranslator(qtTranslator);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
delete qtTranslator;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
36
src/core/Translator.h
Normal file
36
src/core/Translator.h
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_TRANSLATOR_H
|
||||
#define KEEPASSX_TRANSLATOR_H
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
|
||||
class Translator
|
||||
{
|
||||
public:
|
||||
static void installTranslator();
|
||||
static QList<QPair<QString, QString> > availableLanguages();
|
||||
|
||||
private:
|
||||
static bool installTranslator(const QString& language);
|
||||
static bool installTranslator(const QString& language, const QString& path);
|
||||
static bool installQtTranslator(const QString& language);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_TRANSLATOR_H
|
||||
344
src/core/qlockfile.cpp
Normal file
344
src/core/qlockfile.cpp
Normal file
@@ -0,0 +1,344 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qlockfile.h"
|
||||
#include "qlockfile_p.h"
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
# include <QElapsedTimer>
|
||||
#else
|
||||
# include <QTime>
|
||||
#endif
|
||||
#include <QDateTime>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
/*!
|
||||
\class QLockFile
|
||||
\inmodule QtCore
|
||||
\brief The QLockFile class provides locking between processes using a file.
|
||||
\since 5.1
|
||||
|
||||
A lock file can be used to prevent multiple processes from accessing concurrently
|
||||
the same resource. For instance, a configuration file on disk, or a socket, a port,
|
||||
a region of shared memory...
|
||||
|
||||
Serialization is only guaranteed if all processes that access the shared resource
|
||||
use QLockFile, with the same file path.
|
||||
|
||||
QLockFile supports two use cases:
|
||||
to protect a resource for a short-term operation (e.g. verifying if a configuration
|
||||
file has changed before saving new settings), and for long-lived protection of a
|
||||
resource (e.g. a document opened by a user in an editor) for an indefinite amount of time.
|
||||
|
||||
When protecting for a short-term operation, it is acceptable to call lock() and wait
|
||||
until any running operation finishes.
|
||||
When protecting a resource over a long time, however, the application should always
|
||||
call setStaleLockTime(0) and then tryLock() with a short timeout, in order to
|
||||
warn the user that the resource is locked.
|
||||
|
||||
If the process holding the lock crashes, the lock file stays on disk and can prevent
|
||||
any other process from accessing the shared resource, ever. For this reason, QLockFile
|
||||
tries to detect such a "stale" lock file, based on the process ID written into the file,
|
||||
and (in case that process ID got reused meanwhile), on the last modification time of
|
||||
the lock file (30s by default, for the use case of a short-lived operation).
|
||||
If the lock file is found to be stale, it will be deleted.
|
||||
|
||||
For the use case of protecting a resource over a long time, you should therefore call
|
||||
setStaleLockTime(0), and when tryLock() returns LockFailedError, inform the user
|
||||
that the document is locked, possibly using getLockInfo() for more details.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\enum QLockFile::LockError
|
||||
|
||||
This enum describes the result of the last call to lock() or tryLock().
|
||||
|
||||
\value NoError The lock was acquired successfully.
|
||||
\value LockFailedError The lock could not be acquired because another process holds it.
|
||||
\value PermissionError The lock file could not be created, for lack of permissions
|
||||
in the parent directory.
|
||||
\value UnknownError Another error happened, for instance a full partition
|
||||
prevented writing out the lock file.
|
||||
*/
|
||||
|
||||
/*!
|
||||
Constructs a new lock file object.
|
||||
The object is created in an unlocked state.
|
||||
When calling lock() or tryLock(), a lock file named \a fileName will be created,
|
||||
if it doesn't already exist.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
QLockFile::QLockFile(const QString &fileName)
|
||||
: d_ptr(new QLockFilePrivate(fileName))
|
||||
{
|
||||
}
|
||||
|
||||
/*!
|
||||
Destroys the lock file object.
|
||||
If the lock was acquired, this will release the lock, by deleting the lock file.
|
||||
*/
|
||||
QLockFile::~QLockFile()
|
||||
{
|
||||
unlock();
|
||||
}
|
||||
|
||||
/*!
|
||||
Sets \a staleLockTime to be the time in milliseconds after which
|
||||
a lock file is considered stale.
|
||||
The default value is 30000, i.e. 30 seconds.
|
||||
If your application typically keeps the file locked for more than 30 seconds
|
||||
(for instance while saving megabytes of data for 2 minutes), you should set
|
||||
a bigger value using setStaleLockTime().
|
||||
|
||||
The value of \a staleLockTime is used by lock() and tryLock() in order
|
||||
to determine when an existing lock file is considered stale, i.e. left over
|
||||
by a crashed process. This is useful for the case where the PID got reused
|
||||
meanwhile, so the only way to detect a stale lock file is by the fact that
|
||||
it has been around for a long time.
|
||||
|
||||
\sa staleLockTime()
|
||||
*/
|
||||
void QLockFile::setStaleLockTime(int staleLockTime)
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
d->staleLockTime = staleLockTime;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the time in milliseconds after which
|
||||
a lock file is considered stale.
|
||||
|
||||
\sa setStaleLockTime()
|
||||
*/
|
||||
int QLockFile::staleLockTime() const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->staleLockTime;
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns \c true if the lock was acquired by this QLockFile instance,
|
||||
otherwise returns \c false.
|
||||
|
||||
\sa lock(), unlock(), tryLock()
|
||||
*/
|
||||
bool QLockFile::isLocked() const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->isLocked;
|
||||
}
|
||||
|
||||
/*!
|
||||
Creates the lock file.
|
||||
|
||||
If another process (or another thread) has created the lock file already,
|
||||
this function will block until that process (or thread) releases it.
|
||||
|
||||
Calling this function multiple times on the same lock from the same
|
||||
thread without unlocking first is not allowed. This function will
|
||||
\e dead-lock when the file is locked recursively.
|
||||
|
||||
Returns \c true if the lock was acquired, false if it could not be acquired
|
||||
due to an unrecoverable error, such as no permissions in the parent directory.
|
||||
|
||||
\sa unlock(), tryLock()
|
||||
*/
|
||||
bool QLockFile::lock()
|
||||
{
|
||||
return tryLock(-1);
|
||||
}
|
||||
|
||||
/*!
|
||||
Attempts to create the lock file. This function returns \c true if the
|
||||
lock was obtained; otherwise it returns \c false. If another process (or
|
||||
another thread) has created the lock file already, this function will
|
||||
wait for at most \a timeout milliseconds for the lock file to become
|
||||
available.
|
||||
|
||||
Note: Passing a negative number as the \a timeout is equivalent to
|
||||
calling lock(), i.e. this function will wait forever until the lock
|
||||
file can be locked if \a timeout is negative.
|
||||
|
||||
If the lock was obtained, it must be released with unlock()
|
||||
before another process (or thread) can successfully lock it.
|
||||
|
||||
Calling this function multiple times on the same lock from the same
|
||||
thread without unlocking first is not allowed, this function will
|
||||
\e always return false when attempting to lock the file recursively.
|
||||
|
||||
\sa lock(), unlock()
|
||||
*/
|
||||
bool QLockFile::tryLock(int timeout)
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 7, 0)
|
||||
QElapsedTimer timer;
|
||||
#else
|
||||
QTime timer;
|
||||
#endif
|
||||
if (timeout > 0)
|
||||
timer.start();
|
||||
int sleepTime = 100;
|
||||
Q_FOREVER {
|
||||
d->lockError = d->tryLock_sys();
|
||||
switch (d->lockError) {
|
||||
case NoError:
|
||||
d->isLocked = true;
|
||||
return true;
|
||||
case PermissionError:
|
||||
case UnknownError:
|
||||
return false;
|
||||
case LockFailedError:
|
||||
if (!d->isLocked && d->isApparentlyStale()) {
|
||||
// Stale lock from another thread/process
|
||||
// Ensure two processes don't remove it at the same time
|
||||
QLockFile rmlock(d->fileName + QLatin1String(".rmlock"));
|
||||
if (rmlock.tryLock()) {
|
||||
if (d->isApparentlyStale() && d->removeStaleLock())
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (timeout == 0 || (timeout > 0 && (timer.elapsed() > timeout)))
|
||||
return false;
|
||||
QLockFileThread::msleep(sleepTime);
|
||||
if (sleepTime < 5 * 1000)
|
||||
sleepTime *= 2;
|
||||
}
|
||||
// not reached
|
||||
return false;
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn void QLockFile::unlock()
|
||||
Releases the lock, by deleting the lock file.
|
||||
|
||||
Calling unlock() without locking the file first, does nothing.
|
||||
|
||||
\sa lock(), tryLock()
|
||||
*/
|
||||
|
||||
/*!
|
||||
Retrieves information about the current owner of the lock file.
|
||||
|
||||
If tryLock() returns \c false, and error() returns LockFailedError,
|
||||
this function can be called to find out more information about the existing
|
||||
lock file:
|
||||
\list
|
||||
\li the PID of the application (returned in \a pid)
|
||||
\li the \a hostname it's running on (useful in case of networked filesystems),
|
||||
\li the name of the application which created it (returned in \a appname),
|
||||
\endlist
|
||||
|
||||
Note that tryLock() automatically deleted the file if there is no
|
||||
running application with this PID, so LockFailedError can only happen if there is
|
||||
an application with this PID (it could be unrelated though).
|
||||
|
||||
This can be used to inform users about the existing lock file and give them
|
||||
the choice to delete it. After removing the file using removeStaleLockFile(),
|
||||
the application can call tryLock() again.
|
||||
|
||||
This function returns \c true if the information could be successfully retrieved, false
|
||||
if the lock file doesn't exist or doesn't contain the expected data.
|
||||
This can happen if the lock file was deleted between the time where tryLock() failed
|
||||
and the call to this function. Simply call tryLock() again if this happens.
|
||||
*/
|
||||
bool QLockFile::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->getLockInfo(pid, hostname, appname);
|
||||
}
|
||||
|
||||
bool QLockFilePrivate::getLockInfo(qint64 *pid, QString *hostname, QString *appname) const
|
||||
{
|
||||
QFile reader(fileName);
|
||||
if (!reader.open(QIODevice::ReadOnly))
|
||||
return false;
|
||||
|
||||
QByteArray pidLine = reader.readLine();
|
||||
pidLine.chop(1);
|
||||
QByteArray appNameLine = reader.readLine();
|
||||
appNameLine.chop(1);
|
||||
QByteArray hostNameLine = reader.readLine();
|
||||
hostNameLine.chop(1);
|
||||
if (pidLine.isEmpty())
|
||||
return false;
|
||||
|
||||
qint64 thePid = pidLine.toLongLong();
|
||||
if (pid)
|
||||
*pid = thePid;
|
||||
if (appname)
|
||||
*appname = QString::fromUtf8(appNameLine);
|
||||
if (hostname)
|
||||
*hostname = QString::fromUtf8(hostNameLine);
|
||||
return thePid > 0;
|
||||
}
|
||||
|
||||
/*!
|
||||
Attempts to forcefully remove an existing lock file.
|
||||
|
||||
Calling this is not recommended when protecting a short-lived operation: QLockFile
|
||||
already takes care of removing lock files after they are older than staleLockTime().
|
||||
|
||||
This method should only be called when protecting a resource for a long time, i.e.
|
||||
with staleLockTime(0), and after tryLock() returned LockFailedError, and the user
|
||||
agreed on removing the lock file.
|
||||
|
||||
Returns \c true on success, false if the lock file couldn't be removed. This happens
|
||||
on Windows, when the application owning the lock is still running.
|
||||
*/
|
||||
bool QLockFile::removeStaleLockFile()
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
if (d->isLocked) {
|
||||
qWarning("removeStaleLockFile can only be called when not holding the lock");
|
||||
return false;
|
||||
}
|
||||
return d->removeStaleLock();
|
||||
}
|
||||
|
||||
/*!
|
||||
Returns the lock file error status.
|
||||
|
||||
If tryLock() returns \c false, this function can be called to find out
|
||||
the reason why the locking failed.
|
||||
*/
|
||||
QLockFile::LockError QLockFile::error() const
|
||||
{
|
||||
Q_D(const QLockFile);
|
||||
return d->lockError;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
79
src/core/qlockfile.h
Normal file
79
src/core/qlockfile.h
Normal file
@@ -0,0 +1,79 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QLOCKFILE_H
|
||||
#define QLOCKFILE_H
|
||||
|
||||
#include <QString>
|
||||
#include <QScopedPointer>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QLockFilePrivate;
|
||||
|
||||
class QLockFile
|
||||
{
|
||||
public:
|
||||
QLockFile(const QString &fileName);
|
||||
~QLockFile();
|
||||
|
||||
bool lock();
|
||||
bool tryLock(int timeout = 0);
|
||||
void unlock();
|
||||
|
||||
void setStaleLockTime(int);
|
||||
int staleLockTime() const;
|
||||
|
||||
bool isLocked() const;
|
||||
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
|
||||
bool removeStaleLockFile();
|
||||
|
||||
enum LockError {
|
||||
NoError = 0,
|
||||
LockFailedError = 1,
|
||||
PermissionError = 2,
|
||||
UnknownError = 3
|
||||
};
|
||||
LockError error() const;
|
||||
|
||||
protected:
|
||||
QScopedPointer<QLockFilePrivate> d_ptr;
|
||||
|
||||
private:
|
||||
Q_DECLARE_PRIVATE(QLockFile)
|
||||
Q_DISABLE_COPY(QLockFile)
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif // QLOCKFILE_H
|
||||
104
src/core/qlockfile_p.h
Normal file
104
src/core/qlockfile_p.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef QLOCKFILE_P_H
|
||||
#define QLOCKFILE_P_H
|
||||
|
||||
//
|
||||
// W A R N I N G
|
||||
// -------------
|
||||
//
|
||||
// This file is not part of the Qt API. It exists purely as an
|
||||
// implementation detail. This header file may change from version to
|
||||
// version without notice, or even be removed.
|
||||
//
|
||||
// We mean it.
|
||||
//
|
||||
|
||||
#include "qlockfile.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QThread>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <qt_windows.h>
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QLockFileThread : public QThread
|
||||
{
|
||||
public:
|
||||
static void msleep(unsigned long msecs) { QThread::msleep(msecs); }
|
||||
};
|
||||
|
||||
class QLockFilePrivate
|
||||
{
|
||||
public:
|
||||
QLockFilePrivate(const QString &fn)
|
||||
: fileName(fn),
|
||||
#ifdef Q_OS_WIN
|
||||
fileHandle(INVALID_HANDLE_VALUE),
|
||||
#else
|
||||
fileHandle(-1),
|
||||
#endif
|
||||
staleLockTime(30 * 1000), // 30 seconds
|
||||
lockError(QLockFile::NoError),
|
||||
isLocked(false)
|
||||
{
|
||||
}
|
||||
QLockFile::LockError tryLock_sys();
|
||||
bool removeStaleLock();
|
||||
bool getLockInfo(qint64 *pid, QString *hostname, QString *appname) const;
|
||||
// Returns \c true if the lock belongs to dead PID, or is old.
|
||||
// The attempt to delete it will tell us if it was really stale or not, though.
|
||||
bool isApparentlyStale() const;
|
||||
|
||||
#ifdef Q_OS_UNIX
|
||||
static int checkFcntlWorksAfterFlock();
|
||||
#endif
|
||||
|
||||
QString fileName;
|
||||
#ifdef Q_OS_WIN
|
||||
Qt::HANDLE fileHandle;
|
||||
#else
|
||||
int fileHandle;
|
||||
#endif
|
||||
int staleLockTime; // "int milliseconds" is big enough for 24 days
|
||||
QLockFile::LockError lockError;
|
||||
bool isLocked;
|
||||
};
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
||||
#endif /* QLOCKFILE_P_H */
|
||||
199
src/core/qlockfile_unix.cpp
Normal file
199
src/core/qlockfile_unix.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#include "qlockfile_p.h"
|
||||
|
||||
#include <QTemporaryFile>
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
|
||||
#include <sys/file.h> // flock
|
||||
#include <sys/types.h> // kill
|
||||
#include <signal.h> // kill
|
||||
#include <unistd.h>
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
#define EINTR_LOOP(var, cmd) \
|
||||
do { \
|
||||
var = cmd; \
|
||||
} while (var == -1 && errno == EINTR)
|
||||
|
||||
// don't call QT_OPEN or ::open
|
||||
// call qt_safe_open
|
||||
static inline int qt_safe_open(const char *pathname, int flags, mode_t mode = 0777)
|
||||
{
|
||||
#ifdef O_CLOEXEC
|
||||
flags |= O_CLOEXEC;
|
||||
#endif
|
||||
int fd;
|
||||
EINTR_LOOP(fd, ::open(pathname, flags, mode));
|
||||
|
||||
// unknown flags are ignored, so we have no way of verifying if
|
||||
// O_CLOEXEC was accepted
|
||||
if (fd != -1)
|
||||
::fcntl(fd, F_SETFD, FD_CLOEXEC);
|
||||
return fd;
|
||||
}
|
||||
|
||||
static inline qint64 qt_safe_write(int fd, const void *data, qint64 len)
|
||||
{
|
||||
qint64 ret = 0;
|
||||
EINTR_LOOP(ret, ::write(fd, data, len));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static QString localHostName() // from QHostInfo::localHostName()
|
||||
{
|
||||
char hostName[512];
|
||||
if (gethostname(hostName, sizeof(hostName)) == -1)
|
||||
return QString();
|
||||
hostName[sizeof(hostName) - 1] = '\0';
|
||||
return QString::fromLocal8Bit(hostName);
|
||||
}
|
||||
|
||||
// ### merge into qt_safe_write?
|
||||
static qint64 qt_write_loop(int fd, const char *data, qint64 len)
|
||||
{
|
||||
qint64 pos = 0;
|
||||
while (pos < len) {
|
||||
const qint64 ret = qt_safe_write(fd, data + pos, len - pos);
|
||||
if (ret == -1) // e.g. partition full
|
||||
return pos;
|
||||
pos += ret;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
static bool setNativeLocks(int fd)
|
||||
{
|
||||
#if defined(LOCK_EX) && defined(LOCK_NB)
|
||||
if (flock(fd, LOCK_EX | LOCK_NB) == -1) // other threads, and other processes on a local fs
|
||||
return false;
|
||||
#endif
|
||||
struct flock flockData;
|
||||
flockData.l_type = F_WRLCK;
|
||||
flockData.l_whence = SEEK_SET;
|
||||
flockData.l_start = 0;
|
||||
flockData.l_len = 0; // 0 = entire file
|
||||
flockData.l_pid = getpid();
|
||||
if (fcntl(fd, F_SETLK, &flockData) == -1) // for networked filesystems
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
QLockFile::LockError QLockFilePrivate::tryLock_sys()
|
||||
{
|
||||
// Assemble data, to write in a single call to write
|
||||
// (otherwise we'd have to check every write call)
|
||||
// Use operator% from the fast builder to avoid multiple memory allocations.
|
||||
QByteArray fileData = QByteArray::number(QCoreApplication::applicationPid()) + '\n'
|
||||
+ qAppName().toUtf8() + '\n'
|
||||
+ localHostName().toUtf8() + '\n';
|
||||
|
||||
const QByteArray lockFileName = QFile::encodeName(fileName);
|
||||
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY | O_CREAT | O_EXCL, 0644);
|
||||
if (fd < 0) {
|
||||
switch (errno) {
|
||||
case EEXIST:
|
||||
return QLockFile::LockFailedError;
|
||||
case EACCES:
|
||||
case EROFS:
|
||||
return QLockFile::PermissionError;
|
||||
default:
|
||||
return QLockFile::UnknownError;
|
||||
}
|
||||
}
|
||||
// Ensure nobody else can delete the file while we have it
|
||||
if (!setNativeLocks(fd))
|
||||
qWarning() << "setNativeLocks failed:" << strerror(errno);
|
||||
|
||||
if (qt_write_loop(fd, fileData.constData(), fileData.size()) < fileData.size()) {
|
||||
close(fd);
|
||||
if (!QFile::remove(fileName))
|
||||
qWarning("QLockFile: Could not remove our own lock file %s.", qPrintable(fileName));
|
||||
return QLockFile::UnknownError; // partition full
|
||||
}
|
||||
|
||||
// We hold the lock, continue.
|
||||
fileHandle = fd;
|
||||
|
||||
return QLockFile::NoError;
|
||||
}
|
||||
|
||||
bool QLockFilePrivate::removeStaleLock()
|
||||
{
|
||||
const QByteArray lockFileName = QFile::encodeName(fileName);
|
||||
const int fd = qt_safe_open(lockFileName.constData(), O_WRONLY, 0644);
|
||||
if (fd < 0) // gone already?
|
||||
return false;
|
||||
bool success = setNativeLocks(fd) && (::unlink(lockFileName) == 0);
|
||||
close(fd);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool QLockFilePrivate::isApparentlyStale() const
|
||||
{
|
||||
qint64 pid;
|
||||
QString hostname, appname;
|
||||
if (!getLockInfo(&pid, &hostname, &appname))
|
||||
return false;
|
||||
if (hostname.isEmpty() || hostname == localHostName()) {
|
||||
if (::kill(pid, 0) == -1 && errno == ESRCH)
|
||||
return true; // PID doesn't exist anymore
|
||||
}
|
||||
const qint64 age = QFileInfo(fileName).lastModified().secsTo(QDateTime::currentDateTime()) * 1000;
|
||||
return staleLockTime > 0 && age > staleLockTime;
|
||||
}
|
||||
|
||||
void QLockFile::unlock()
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
if (!d->isLocked)
|
||||
return;
|
||||
close(d->fileHandle);
|
||||
d->fileHandle = -1;
|
||||
if (!QFile::remove(d->fileName)) {
|
||||
qWarning() << "Could not remove our own lock file" << d->fileName << "maybe permissions changed meanwhile?";
|
||||
// This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
|
||||
}
|
||||
QFile::remove(d->fileName);
|
||||
d->lockError = QLockFile::NoError;
|
||||
d->isLocked = false;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
178
src/core/qlockfile_win.cpp
Normal file
178
src/core/qlockfile_win.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 David Faure <faure+bluesystems@kde.org>
|
||||
** Contact: http://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the QtCore module of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:LGPL21$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see http://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at http://www.qt.io/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 or version 3 as published by the Free
|
||||
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
|
||||
** LICENSE.LGPLv3 included in the packaging of this file. Please review the
|
||||
** following information to ensure the GNU Lesser General Public License
|
||||
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
|
||||
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** As a special exception, The Qt Company gives you certain additional
|
||||
** rights. These rights are described in The Qt Company LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef _UNICODE
|
||||
#define _UNICODE
|
||||
#endif
|
||||
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
|
||||
#include "qlockfile_p.h"
|
||||
|
||||
#include <qt_windows.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
static inline QByteArray localHostName()
|
||||
{
|
||||
return qgetenv("COMPUTERNAME");
|
||||
}
|
||||
|
||||
static inline bool fileExists(const wchar_t *fileName)
|
||||
{
|
||||
WIN32_FILE_ATTRIBUTE_DATA data;
|
||||
return GetFileAttributesEx(fileName, GetFileExInfoStandard, &data);
|
||||
}
|
||||
|
||||
QLockFile::LockError QLockFilePrivate::tryLock_sys()
|
||||
{
|
||||
const ushort* nativePath = QDir::toNativeSeparators(fileName).utf16();
|
||||
// When writing, allow others to read.
|
||||
// When reading, QFile will allow others to read and write, all good.
|
||||
// Adding FILE_SHARE_DELETE would allow forceful deletion of stale files,
|
||||
// but Windows doesn't allow recreating it while this handle is open anyway,
|
||||
// so this would only create confusion (can't lock, but no lock file to read from).
|
||||
const DWORD dwShareMode = FILE_SHARE_READ;
|
||||
#ifndef Q_OS_WINRT
|
||||
SECURITY_ATTRIBUTES securityAtts = { sizeof(SECURITY_ATTRIBUTES), NULL, FALSE };
|
||||
HANDLE fh = CreateFile((const wchar_t*)nativePath,
|
||||
GENERIC_WRITE,
|
||||
dwShareMode,
|
||||
&securityAtts,
|
||||
CREATE_NEW, // error if already exists
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
NULL);
|
||||
#else // !Q_OS_WINRT
|
||||
HANDLE fh = CreateFile2((const wchar_t*)nativePath,
|
||||
GENERIC_WRITE,
|
||||
dwShareMode,
|
||||
CREATE_NEW, // error if already exists
|
||||
NULL);
|
||||
#endif // Q_OS_WINRT
|
||||
if (fh == INVALID_HANDLE_VALUE) {
|
||||
const DWORD lastError = GetLastError();
|
||||
switch (lastError) {
|
||||
case ERROR_SHARING_VIOLATION:
|
||||
case ERROR_ALREADY_EXISTS:
|
||||
case ERROR_FILE_EXISTS:
|
||||
return QLockFile::LockFailedError;
|
||||
case ERROR_ACCESS_DENIED:
|
||||
// readonly file, or file still in use by another process.
|
||||
// Assume the latter if the file exists, since we don't create it readonly.
|
||||
return fileExists((const wchar_t*)nativePath)
|
||||
? QLockFile::LockFailedError
|
||||
: QLockFile::PermissionError;
|
||||
default:
|
||||
qWarning() << "Got unexpected locking error" << lastError;
|
||||
return QLockFile::UnknownError;
|
||||
}
|
||||
}
|
||||
|
||||
// We hold the lock, continue.
|
||||
fileHandle = fh;
|
||||
// Assemble data, to write in a single call to write
|
||||
// (otherwise we'd have to check every write call)
|
||||
QByteArray fileData;
|
||||
fileData += QByteArray::number(QCoreApplication::applicationPid());
|
||||
fileData += '\n';
|
||||
fileData += QCoreApplication::applicationName().toUtf8();
|
||||
fileData += '\n';
|
||||
fileData += localHostName();
|
||||
fileData += '\n';
|
||||
DWORD bytesWritten = 0;
|
||||
QLockFile::LockError error = QLockFile::NoError;
|
||||
if (!WriteFile(fh, fileData.constData(), fileData.size(), &bytesWritten, NULL) || !FlushFileBuffers(fh))
|
||||
error = QLockFile::UnknownError; // partition full
|
||||
return error;
|
||||
}
|
||||
|
||||
bool QLockFilePrivate::removeStaleLock()
|
||||
{
|
||||
// QFile::remove fails on Windows if the other process is still using the file, so it's not stale.
|
||||
return QFile::remove(fileName);
|
||||
}
|
||||
|
||||
bool QLockFilePrivate::isApparentlyStale() const
|
||||
{
|
||||
qint64 pid;
|
||||
QString hostname, appname;
|
||||
if (!getLockInfo(&pid, &hostname, &appname))
|
||||
return false;
|
||||
|
||||
// On WinRT there seems to be no way of obtaining information about other
|
||||
// processes due to sandboxing
|
||||
#ifndef Q_OS_WINRT
|
||||
if (hostname == QString::fromLocal8Bit(localHostName())) {
|
||||
HANDLE procHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
|
||||
if (!procHandle)
|
||||
return true;
|
||||
// We got a handle but check if process is still alive
|
||||
DWORD dwR = ::WaitForSingleObject(procHandle, 0);
|
||||
::CloseHandle(procHandle);
|
||||
if (dwR == WAIT_TIMEOUT)
|
||||
return true;
|
||||
}
|
||||
#endif // !Q_OS_WINRT
|
||||
const qint64 age = QFileInfo(fileName).lastModified().msecsTo(QDateTime::currentDateTime());
|
||||
return staleLockTime > 0 && age > staleLockTime;
|
||||
}
|
||||
|
||||
void QLockFile::unlock()
|
||||
{
|
||||
Q_D(QLockFile);
|
||||
if (!d->isLocked)
|
||||
return;
|
||||
CloseHandle(d->fileHandle);
|
||||
int attempts = 0;
|
||||
static const int maxAttempts = 500; // 500ms
|
||||
while (!QFile::remove(d->fileName) && ++attempts < maxAttempts) {
|
||||
// Someone is reading the lock file right now (on Windows this prevents deleting it).
|
||||
QLockFileThread::msleep(1);
|
||||
}
|
||||
if (attempts == maxAttempts) {
|
||||
qWarning() << "Could not remove our own lock file" << d->fileName << ". Either other users of the lock file are reading it constantly for 500 ms, or we (no longer) have permissions to delete the file";
|
||||
// This is bad because other users of this lock file will now have to wait for the stale-lock-timeout...
|
||||
}
|
||||
d->lockError = QLockFile::NoError;
|
||||
d->isLocked = false;
|
||||
}
|
||||
|
||||
QT_END_NAMESPACE
|
||||
@@ -46,6 +46,12 @@
|
||||
#include <QFileInfo>
|
||||
#include <QTemporaryFile>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <windows.h>
|
||||
#else
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
QSaveFilePrivate::QSaveFilePrivate()
|
||||
: tempFile(0), error(QFile::NoError)
|
||||
{
|
||||
@@ -279,8 +285,19 @@ bool QSaveFile::commit()
|
||||
Q_D(QSaveFile);
|
||||
if (!d->tempFile)
|
||||
return false;
|
||||
Q_ASSERT(isOpen());
|
||||
QIODevice::close(); // flush and close
|
||||
if (!isOpen()) {
|
||||
qWarning("QSaveFile::commit: File (%s) is not open", qPrintable(fileName()));
|
||||
return false;
|
||||
}
|
||||
flush();
|
||||
#ifdef Q_OS_WIN
|
||||
FlushFileBuffers(reinterpret_cast<HANDLE>(handle()));
|
||||
#elif defined(_POSIX_SYNCHRONIZED_IO) && _POSIX_SYNCHRONIZED_IO > 0
|
||||
fdatasync(d->tempFile->handle());
|
||||
#else
|
||||
fsync(d->tempFile->handle());
|
||||
#endif
|
||||
QIODevice::close();
|
||||
if (d->error != QFile::NoError) {
|
||||
d->tempFile->remove();
|
||||
unsetError();
|
||||
|
||||
@@ -21,7 +21,12 @@
|
||||
|
||||
#include <gcrypt.h>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/SymmetricCipher.h"
|
||||
|
||||
bool Crypto::m_initalized(false);
|
||||
QString Crypto::m_errorStr;
|
||||
|
||||
#if !defined(GCRYPT_VERSION_NUMBER) || (GCRYPT_VERSION_NUMBER < 0x010600)
|
||||
static int gcry_qt_mutex_init(void** p_sys)
|
||||
@@ -64,11 +69,11 @@ Crypto::Crypto()
|
||||
{
|
||||
}
|
||||
|
||||
void Crypto::init()
|
||||
bool Crypto::init()
|
||||
{
|
||||
if (m_initalized) {
|
||||
qWarning("Crypto::init: already initalized");
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
// libgcrypt >= 1.6 doesn't allow custom thread callbacks anymore.
|
||||
@@ -78,7 +83,19 @@ void Crypto::init()
|
||||
gcry_check_version(0);
|
||||
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
|
||||
|
||||
if (!checkAlgorithms()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// has to be set before testing Crypto classes
|
||||
m_initalized = true;
|
||||
|
||||
if (!selfTest()) {
|
||||
m_initalized = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::initalized()
|
||||
@@ -86,7 +103,222 @@ bool Crypto::initalized()
|
||||
return m_initalized;
|
||||
}
|
||||
|
||||
bool Crypto::selfTest()
|
||||
QString Crypto::errorString()
|
||||
{
|
||||
return m_errorStr;
|
||||
}
|
||||
|
||||
bool Crypto::backendSelfTest()
|
||||
{
|
||||
return (gcry_control(GCRYCTL_SELFTEST) == 0);
|
||||
}
|
||||
|
||||
bool Crypto::checkAlgorithms()
|
||||
{
|
||||
if (gcry_cipher_algo_info(GCRY_CIPHER_AES256, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) {
|
||||
m_errorStr = "GCRY_CIPHER_AES256 not found.";
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
if (gcry_cipher_algo_info(GCRY_CIPHER_TWOFISH, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) {
|
||||
m_errorStr = "GCRY_CIPHER_TWOFISH not found.";
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
#ifdef GCRYPT_HAS_SALSA20
|
||||
if (gcry_cipher_algo_info(GCRY_CIPHER_SALSA20, GCRYCTL_TEST_ALGO, Q_NULLPTR, Q_NULLPTR) != 0) {
|
||||
m_errorStr = "GCRY_CIPHER_SALSA20 not found.";
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) {
|
||||
m_errorStr = "GCRY_MD_SHA256 not found.";
|
||||
qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::selfTest()
|
||||
{
|
||||
return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20();
|
||||
}
|
||||
|
||||
void Crypto::raiseError(const QString& str)
|
||||
{
|
||||
m_errorStr = str;
|
||||
qWarning("Crypto::selfTest: %s", qPrintable(m_errorStr));
|
||||
}
|
||||
|
||||
bool Crypto::testSha256()
|
||||
{
|
||||
QByteArray sha256Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
|
||||
CryptoHash::Sha256);
|
||||
|
||||
if (sha256Test != QByteArray::fromHex("248D6A61D20638B8E5C026930C3E6039A33CE45964FF2167F6ECEDD419DB06C1")) {
|
||||
raiseError("SHA-256 mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testAes256Cbc()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("f58c4c04d6e5f1ba779eabfb5f7bfbd6");
|
||||
cipherText.append(QByteArray::fromHex("9cfc4e967edb808d679f777bc6702c7d"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
if (!aes256Encrypt.init(key, iv)) {
|
||||
raiseError(aes256Encrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray encryptedText = aes256Encrypt.process(plainText, &ok);
|
||||
if (!ok) {
|
||||
raiseError(aes256Encrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
if (encryptedText != cipherText) {
|
||||
raiseError("AES-256 CBC encryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!aes256Descrypt.init(key, iv)) {
|
||||
raiseError(aes256Descrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok);
|
||||
if (!ok) {
|
||||
raiseError(aes256Descrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
if (decryptedText != plainText) {
|
||||
raiseError("AES-256 CBC decryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testAes256Ecb()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
|
||||
QByteArray iv = QByteArray::fromHex("00000000000000000000000000000000");
|
||||
QByteArray plainText = QByteArray::fromHex("00112233445566778899AABBCCDDEEFF");
|
||||
plainText.append(QByteArray::fromHex("00112233445566778899AABBCCDDEEFF"));
|
||||
QByteArray cipherText = QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089");
|
||||
cipherText.append(QByteArray::fromHex("8EA2B7CA516745BFEAFC49904B496089"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher aes256Encrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt);
|
||||
if (!aes256Encrypt.init(key, iv)) {
|
||||
raiseError(aes256Encrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray encryptedText = aes256Encrypt.process(plainText, &ok);
|
||||
if (!ok) {
|
||||
raiseError(aes256Encrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
if (encryptedText != cipherText) {
|
||||
raiseError("AES-256 ECB encryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SymmetricCipher aes256Descrypt(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Decrypt);
|
||||
if (!aes256Descrypt.init(key, iv)) {
|
||||
raiseError(aes256Descrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray decryptedText = aes256Descrypt.process(cipherText, &ok);
|
||||
if (!ok) {
|
||||
raiseError(aes256Descrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
if (decryptedText != plainText) {
|
||||
raiseError("AES-256 ECB decryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testTwofish()
|
||||
{
|
||||
QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
|
||||
QByteArray iv = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f");
|
||||
QByteArray plainText = QByteArray::fromHex("6bc1bee22e409f96e93d7e117393172a");
|
||||
plainText.append(QByteArray::fromHex("ae2d8a571e03ac9c9eb76fac45af8e51"));
|
||||
QByteArray cipherText = QByteArray::fromHex("e0227c3cc80f3cb1b2ed847cc6f57d3c");
|
||||
cipherText.append(QByteArray::fromHex("657b1e7960b30fb7c8d62e72ae37c3a0"));
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher twofishEncrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
|
||||
if (!twofishEncrypt.init(key, iv)) {
|
||||
raiseError(twofishEncrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray encryptedText = twofishEncrypt.process(plainText, &ok);
|
||||
if (!ok) {
|
||||
raiseError(twofishEncrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
if (encryptedText != cipherText) {
|
||||
raiseError("Twofish encryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
SymmetricCipher twofishDecrypt(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!twofishDecrypt.init(key, iv)) {
|
||||
raiseError(twofishEncrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray decryptedText = twofishDecrypt.process(cipherText, &ok);
|
||||
if (!ok) {
|
||||
raiseError(twofishDecrypt.errorString());
|
||||
return false;
|
||||
}
|
||||
if (decryptedText != plainText) {
|
||||
raiseError("Twofish encryption mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Crypto::testSalsa20()
|
||||
{
|
||||
QByteArray salsa20Key = QByteArray::fromHex("F3F4F5F6F7F8F9FAFBFCFDFEFF000102030405060708090A0B0C0D0E0F101112");
|
||||
QByteArray salsa20iv = QByteArray::fromHex("0000000000000000");
|
||||
QByteArray salsa20Plain = QByteArray::fromHex("00000000000000000000000000000000");
|
||||
QByteArray salsa20Cipher = QByteArray::fromHex("B4C0AFA503BE7FC29A62058166D56F8F");
|
||||
bool ok;
|
||||
|
||||
SymmetricCipher salsa20Stream(SymmetricCipher::Salsa20, SymmetricCipher::Stream,
|
||||
SymmetricCipher::Encrypt);
|
||||
if (!salsa20Stream.init(salsa20Key, salsa20iv)) {
|
||||
raiseError(salsa20Stream.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray salsaProcessed = salsa20Stream.process(salsa20Plain, &ok);
|
||||
if (!ok) {
|
||||
raiseError(salsa20Stream.errorString());
|
||||
return false;
|
||||
}
|
||||
if (salsaProcessed != salsa20Cipher) {
|
||||
raiseError("Salsa20 stream cipher mismatch.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18,18 +18,31 @@
|
||||
#ifndef KEEPASSX_CRYPTO_H
|
||||
#define KEEPASSX_CRYPTO_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "core/Global.h"
|
||||
|
||||
class Crypto
|
||||
{
|
||||
public:
|
||||
static void init();
|
||||
static bool init();
|
||||
static bool initalized();
|
||||
static bool selfTest();
|
||||
static bool backendSelfTest();
|
||||
static QString errorString();
|
||||
|
||||
private:
|
||||
Crypto();
|
||||
static bool checkAlgorithms();
|
||||
static bool selfTest();
|
||||
static void raiseError(const QString& str);
|
||||
static bool testSha256();
|
||||
static bool testAes256Cbc();
|
||||
static bool testAes256Ecb();
|
||||
static bool testTwofish();
|
||||
static bool testSalsa20();
|
||||
|
||||
static bool m_initalized;
|
||||
static QString m_errorStr;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_CRYPTO_H
|
||||
|
||||
@@ -49,6 +49,7 @@ CryptoHash::CryptoHash(CryptoHash::Algorithm algo)
|
||||
|
||||
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0);
|
||||
Q_ASSERT(error == 0); // TODO: error handling
|
||||
Q_UNUSED(error);
|
||||
|
||||
d->hashLen = gcry_md_get_algo_dlen(algoGcrypt);
|
||||
}
|
||||
|
||||
@@ -22,17 +22,39 @@
|
||||
#include "crypto/SymmetricCipherSalsa20.h"
|
||||
|
||||
SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv)
|
||||
SymmetricCipher::Direction direction)
|
||||
: m_backend(createBackend(algo, mode, direction))
|
||||
, m_initialized(false)
|
||||
{
|
||||
m_backend->setKey(key);
|
||||
m_backend->setIv(iv);
|
||||
}
|
||||
|
||||
SymmetricCipher::~SymmetricCipher()
|
||||
{
|
||||
}
|
||||
|
||||
bool SymmetricCipher::init(const QByteArray& key, const QByteArray& iv)
|
||||
{
|
||||
if (!m_backend->init()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_backend->setKey(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_backend->setIv(iv)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymmetricCipher::isInitalized() const
|
||||
{
|
||||
return m_initialized;
|
||||
}
|
||||
|
||||
SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction)
|
||||
{
|
||||
@@ -55,12 +77,17 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith
|
||||
}
|
||||
}
|
||||
|
||||
void SymmetricCipher::reset()
|
||||
bool SymmetricCipher::reset()
|
||||
{
|
||||
m_backend->reset();
|
||||
return m_backend->reset();
|
||||
}
|
||||
|
||||
int SymmetricCipher::blockSize() const
|
||||
{
|
||||
return m_backend->blockSize();
|
||||
}
|
||||
|
||||
QString SymmetricCipher::errorString() const
|
||||
{
|
||||
return m_backend->errorString();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "crypto/SymmetricCipherBackend.h"
|
||||
@@ -48,30 +49,35 @@ public:
|
||||
};
|
||||
|
||||
SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction, const QByteArray& key, const QByteArray& iv);
|
||||
SymmetricCipher::Direction direction);
|
||||
~SymmetricCipher();
|
||||
|
||||
inline QByteArray process(const QByteArray& data) {
|
||||
return m_backend->process(data);
|
||||
bool init(const QByteArray& key, const QByteArray& iv);
|
||||
bool isInitalized() const;
|
||||
|
||||
inline QByteArray process(const QByteArray& data, bool* ok) {
|
||||
return m_backend->process(data, ok);
|
||||
}
|
||||
|
||||
inline void processInPlace(QByteArray& data) {
|
||||
m_backend->processInPlace(data);
|
||||
inline bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT {
|
||||
return m_backend->processInPlace(data);
|
||||
}
|
||||
|
||||
inline void processInPlace(QByteArray& data, quint64 rounds) {
|
||||
inline bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT {
|
||||
Q_ASSERT(rounds > 0);
|
||||
m_backend->processInPlace(data, rounds);
|
||||
return m_backend->processInPlace(data, rounds);
|
||||
}
|
||||
|
||||
void reset();
|
||||
bool reset();
|
||||
int blockSize() const;
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
|
||||
const QScopedPointer<SymmetricCipherBackend> m_backend;
|
||||
bool m_initialized;
|
||||
|
||||
Q_DISABLE_COPY(SymmetricCipher)
|
||||
};
|
||||
|
||||
@@ -24,15 +24,18 @@ class SymmetricCipherBackend
|
||||
{
|
||||
public:
|
||||
virtual ~SymmetricCipherBackend() {}
|
||||
virtual void setKey(const QByteArray& key) = 0;
|
||||
virtual void setIv(const QByteArray& iv) = 0;
|
||||
virtual bool init() = 0;
|
||||
virtual bool setKey(const QByteArray& key) = 0;
|
||||
virtual bool setIv(const QByteArray& iv) = 0;
|
||||
|
||||
virtual QByteArray process(const QByteArray& data) = 0;
|
||||
virtual void processInPlace(QByteArray& data) = 0;
|
||||
virtual void processInPlace(QByteArray& data, quint64 rounds) = 0;
|
||||
virtual QByteArray process(const QByteArray& data, bool* ok) = 0;
|
||||
virtual bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT = 0;
|
||||
virtual bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT = 0;
|
||||
|
||||
virtual void reset() = 0;
|
||||
virtual bool reset() = 0;
|
||||
virtual int blockSize() const = 0;
|
||||
|
||||
virtual QString errorString() const = 0;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H
|
||||
|
||||
@@ -22,22 +22,12 @@
|
||||
|
||||
SymmetricCipherGcrypt::SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction)
|
||||
: m_algo(gcryptAlgo(algo))
|
||||
: m_ctx(Q_NULLPTR)
|
||||
, m_algo(gcryptAlgo(algo))
|
||||
, m_mode(gcryptMode(mode))
|
||||
, m_direction(direction)
|
||||
, m_blockSize(-1)
|
||||
{
|
||||
Q_ASSERT(Crypto::initalized());
|
||||
|
||||
gcry_error_t error;
|
||||
|
||||
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
|
||||
Q_ASSERT(error == 0); // TODO: real error checking
|
||||
|
||||
size_t blockSizeT;
|
||||
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT);
|
||||
Q_ASSERT(error == 0);
|
||||
m_blockSize = blockSizeT;
|
||||
}
|
||||
|
||||
SymmetricCipherGcrypt::~SymmetricCipherGcrypt()
|
||||
@@ -83,21 +73,65 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
|
||||
}
|
||||
}
|
||||
|
||||
void SymmetricCipherGcrypt::setKey(const QByteArray& key)
|
||||
void SymmetricCipherGcrypt::setErrorString(gcry_error_t err)
|
||||
{
|
||||
const char* gcryptError = gcry_strerror(err);
|
||||
const char* gcryptErrorSource = gcry_strsource(err);
|
||||
|
||||
m_errorString = QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource),
|
||||
QString::fromLocal8Bit(gcryptError));
|
||||
}
|
||||
|
||||
bool SymmetricCipherGcrypt::init()
|
||||
{
|
||||
Q_ASSERT(Crypto::initalized());
|
||||
|
||||
gcry_error_t error;
|
||||
|
||||
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t blockSizeT;
|
||||
error = gcry_cipher_algo_info(m_algo, GCRYCTL_GET_BLKLEN, Q_NULLPTR, &blockSizeT);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_blockSize = blockSizeT;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
|
||||
{
|
||||
m_key = key;
|
||||
gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size());
|
||||
Q_ASSERT(error == 0);
|
||||
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SymmetricCipherGcrypt::setIv(const QByteArray& iv)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
|
||||
{
|
||||
m_iv = iv;
|
||||
gcry_error_t error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
||||
Q_ASSERT(error == 0);
|
||||
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray SymmetricCipherGcrypt::process(const QByteArray& data)
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
|
||||
{
|
||||
// TODO: check block size
|
||||
|
||||
@@ -113,12 +147,16 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data)
|
||||
error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size());
|
||||
}
|
||||
|
||||
Q_ASSERT(error == 0);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
*ok = false;
|
||||
}
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||
{
|
||||
// TODO: check block size
|
||||
|
||||
@@ -131,10 +169,15 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data)
|
||||
error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), Q_NULLPTR, 0);
|
||||
}
|
||||
|
||||
Q_ASSERT(error == 0);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
||||
{
|
||||
// TODO: check block size
|
||||
|
||||
@@ -146,28 +189,52 @@ void SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
|
||||
if (m_direction == SymmetricCipher::Decrypt) {
|
||||
for (quint64 i = 0; i != rounds; ++i) {
|
||||
error = gcry_cipher_decrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
|
||||
Q_ASSERT(error == 0);
|
||||
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (quint64 i = 0; i != rounds; ++i) {
|
||||
error = gcry_cipher_encrypt(m_ctx, rawData, size, Q_NULLPTR, 0);
|
||||
Q_ASSERT(error == 0);
|
||||
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SymmetricCipherGcrypt::reset()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymmetricCipherGcrypt::reset()
|
||||
{
|
||||
gcry_error_t error;
|
||||
|
||||
error = gcry_cipher_reset(m_ctx);
|
||||
Q_ASSERT(error == 0);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
|
||||
Q_ASSERT(error == 0);
|
||||
if (error != 0) {
|
||||
setErrorString(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int SymmetricCipherGcrypt::blockSize() const
|
||||
{
|
||||
return m_blockSize;
|
||||
}
|
||||
|
||||
QString SymmetricCipherGcrypt::errorString() const
|
||||
{
|
||||
return m_errorString;
|
||||
}
|
||||
|
||||
@@ -29,19 +29,24 @@ public:
|
||||
SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
~SymmetricCipherGcrypt();
|
||||
void setKey(const QByteArray& key);
|
||||
void setIv(const QByteArray& iv);
|
||||
|
||||
QByteArray process(const QByteArray& data);
|
||||
void processInPlace(QByteArray& data);
|
||||
void processInPlace(QByteArray& data, quint64 rounds);
|
||||
bool init();
|
||||
bool setKey(const QByteArray& key);
|
||||
bool setIv(const QByteArray& iv);
|
||||
|
||||
void reset();
|
||||
QByteArray process(const QByteArray& data, bool* ok);
|
||||
bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT;
|
||||
bool processInPlace(QByteArray& data, quint64 rounds) Q_REQUIRED_RESULT;
|
||||
|
||||
bool reset();
|
||||
int blockSize() const;
|
||||
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
|
||||
static int gcryptMode(SymmetricCipher::Mode mode);
|
||||
void setErrorString(gcry_error_t err);
|
||||
|
||||
gcry_cipher_hd_t m_ctx;
|
||||
const int m_algo;
|
||||
@@ -50,6 +55,7 @@ private:
|
||||
QByteArray m_key;
|
||||
QByteArray m_iv;
|
||||
int m_blockSize;
|
||||
QString m_errorString;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H
|
||||
|
||||
@@ -33,23 +33,32 @@ SymmetricCipherSalsa20::~SymmetricCipherSalsa20()
|
||||
{
|
||||
}
|
||||
|
||||
void SymmetricCipherSalsa20::setKey(const QByteArray& key)
|
||||
bool SymmetricCipherSalsa20::init()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SymmetricCipherSalsa20::setKey(const QByteArray& key)
|
||||
{
|
||||
Q_ASSERT((key.size() == 16) || (key.size() == 32));
|
||||
|
||||
m_key = key;
|
||||
ECRYPT_keysetup(&m_ctx, reinterpret_cast<const u8*>(m_key.constData()), m_key.size()*8, 64);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SymmetricCipherSalsa20::setIv(const QByteArray& iv)
|
||||
bool SymmetricCipherSalsa20::setIv(const QByteArray& iv)
|
||||
{
|
||||
Q_ASSERT(iv.size() == 8);
|
||||
|
||||
m_iv = iv;
|
||||
ECRYPT_ivsetup(&m_ctx, reinterpret_cast<const u8*>(m_iv.constData()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray SymmetricCipherSalsa20::process(const QByteArray& data)
|
||||
QByteArray SymmetricCipherSalsa20::process(const QByteArray& data, bool* ok)
|
||||
{
|
||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||
|
||||
@@ -59,18 +68,21 @@ QByteArray SymmetricCipherSalsa20::process(const QByteArray& data)
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
reinterpret_cast<u8*>(result.data()), data.size());
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void SymmetricCipherSalsa20::processInPlace(QByteArray& data)
|
||||
bool SymmetricCipherSalsa20::processInPlace(QByteArray& data)
|
||||
{
|
||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
reinterpret_cast<u8*>(data.data()), data.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
||||
bool SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
||||
{
|
||||
Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0));
|
||||
|
||||
@@ -78,14 +90,23 @@ void SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds)
|
||||
ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast<const u8*>(data.constData()),
|
||||
reinterpret_cast<u8*>(data.data()), data.size());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SymmetricCipherSalsa20::reset()
|
||||
bool SymmetricCipherSalsa20::reset()
|
||||
{
|
||||
ECRYPT_ivsetup(&m_ctx, reinterpret_cast<const u8*>(m_iv.constData()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int SymmetricCipherSalsa20::blockSize() const
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
|
||||
QString SymmetricCipherSalsa20::errorString() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -28,19 +28,22 @@ public:
|
||||
SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode,
|
||||
SymmetricCipher::Direction direction);
|
||||
~SymmetricCipherSalsa20();
|
||||
bool init();
|
||||
void setAlgorithm(SymmetricCipher::Algorithm algo);
|
||||
void setMode(SymmetricCipher::Mode mode);
|
||||
void setDirection(SymmetricCipher::Direction direction);
|
||||
void setKey(const QByteArray& key);
|
||||
void setIv(const QByteArray& iv);
|
||||
bool setKey(const QByteArray& key);
|
||||
bool setIv(const QByteArray& iv);
|
||||
|
||||
QByteArray process(const QByteArray& data);
|
||||
void processInPlace(QByteArray& data);
|
||||
void processInPlace(QByteArray& data, quint64 rounds);
|
||||
QByteArray process(const QByteArray& data, bool* ok);
|
||||
bool processInPlace(QByteArray& data);
|
||||
bool processInPlace(QByteArray& data, quint64 rounds);
|
||||
|
||||
void reset();
|
||||
bool reset();
|
||||
int blockSize() const;
|
||||
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
ECRYPT_ctx m_ctx;
|
||||
QByteArray m_key;
|
||||
|
||||
@@ -9,50 +9,13 @@
|
||||
|
||||
/* Guess the endianness of the target architecture. */
|
||||
|
||||
/*
|
||||
* The LITTLE endian machines:
|
||||
*/
|
||||
#if defined(__ultrix) /* Older MIPS */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif defined(__alpha) /* Alpha */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif defined(i386) /* x86 (gcc) */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif defined(__i386) /* x86 (gcc) */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif defined(__x86_64) /* x86_64 (gcc) */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif defined(_M_IX86) /* x86 (MSC, Borland) */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif defined(_MSC_VER) /* x86 (surely MSC) */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif defined(__INTEL_COMPILER) /* x86 (surely Intel compiler icl.exe) */
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#include <QtGlobal>
|
||||
|
||||
/*
|
||||
* The BIG endian machines:
|
||||
*/
|
||||
#elif defined(__sparc) /* Newer Sparc's */
|
||||
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
|
||||
#define ECRYPT_LITTLE_ENDIAN
|
||||
#elif Q_BYTE_ORDER == Q_BIG_ENDIAN
|
||||
#define ECRYPT_BIG_ENDIAN
|
||||
#elif defined(__powerpc__) /* PowerPC */
|
||||
#define ECRYPT_BIG_ENDIAN
|
||||
#elif defined(__ppc__) /* PowerPC */
|
||||
#define ECRYPT_BIG_ENDIAN
|
||||
#elif defined(__hppa) /* HP-PA */
|
||||
#define ECRYPT_BIG_ENDIAN
|
||||
|
||||
/*
|
||||
* Finally machines with UNKNOWN endianness:
|
||||
*/
|
||||
#elif defined (_AIX) /* RS6000 */
|
||||
#define ECRYPT_UNKNOWN
|
||||
#elif defined(__aux) /* 68K */
|
||||
#define ECRYPT_UNKNOWN
|
||||
#elif defined(__dgux) /* 88K (but P6 in latest boxes) */
|
||||
#define ECRYPT_UNKNOWN
|
||||
#elif defined(__sgi) /* Newer MIPS */
|
||||
#define ECRYPT_UNKNOWN
|
||||
#else /* Any other processor */
|
||||
#else
|
||||
#define ECRYPT_UNKNOWN
|
||||
#endif
|
||||
|
||||
|
||||
103
src/format/CsvExporter.cpp
Normal file
103
src/format/CsvExporter.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Florian Geyer <blueice@fobos.de>
|
||||
* Copyright (C) 2015 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "CsvExporter.h"
|
||||
|
||||
#include <QFile>
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
|
||||
bool CsvExporter::exportDatabase(const QString& filename, const Database* db)
|
||||
{
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
m_error = file.errorString();
|
||||
return false;
|
||||
}
|
||||
return exportDatabase(&file, db);
|
||||
}
|
||||
|
||||
bool CsvExporter::exportDatabase(QIODevice* device, const Database* db)
|
||||
{
|
||||
QString header;
|
||||
addColumn(header, "Group");
|
||||
addColumn(header, "Title");
|
||||
addColumn(header, "Username");
|
||||
addColumn(header, "Password");
|
||||
addColumn(header, "URL");
|
||||
addColumn(header, "Notes");
|
||||
header.append("\n");
|
||||
|
||||
if (device->write(header.toUtf8()) == -1) {
|
||||
m_error = device->errorString();
|
||||
return false;
|
||||
}
|
||||
|
||||
return writeGroup(device, db->rootGroup());
|
||||
}
|
||||
|
||||
QString CsvExporter::errorString() const
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
bool CsvExporter::writeGroup(QIODevice* device, const Group* group, QString groupPath)
|
||||
{
|
||||
if (!groupPath.isEmpty()) {
|
||||
groupPath.append("/");
|
||||
}
|
||||
groupPath.append(group->name());
|
||||
|
||||
Q_FOREACH (const Entry* entry, group->entries()) {
|
||||
QString line;
|
||||
|
||||
addColumn(line, groupPath);
|
||||
addColumn(line, entry->title());
|
||||
addColumn(line, entry->username());
|
||||
addColumn(line, entry->password());
|
||||
addColumn(line, entry->url());
|
||||
addColumn(line, entry->notes());
|
||||
|
||||
line.append("\n");
|
||||
|
||||
if (device->write(line.toUtf8()) == -1) {
|
||||
m_error = device->errorString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Q_FOREACH (const Group* child, group->children()) {
|
||||
if (!writeGroup(device, child, groupPath)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CsvExporter::addColumn(QString& str, const QString& column)
|
||||
{
|
||||
if (!str.isEmpty()) {
|
||||
str.append(",");
|
||||
}
|
||||
|
||||
str.append("\"");
|
||||
str.append(QString(column).replace("\"", "\"\""));
|
||||
str.append("\"");
|
||||
}
|
||||
42
src/format/CsvExporter.h
Normal file
42
src/format/CsvExporter.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Florian Geyer <blueice@fobos.de>
|
||||
* Copyright (C) 2015 Felix Geyer <debfx@fobos.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSX_CSVEXPORTER_H
|
||||
#define KEEPASSX_CSVEXPORTER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class Database;
|
||||
class Group;
|
||||
class QIODevice;
|
||||
|
||||
class CsvExporter
|
||||
{
|
||||
public:
|
||||
bool exportDatabase(const QString& filename, const Database* db);
|
||||
bool exportDatabase(QIODevice* device, const Database* db);
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
bool writeGroup(QIODevice* device, const Group* group, QString groupPath = QString());
|
||||
void addColumn(QString& str, const QString& column);
|
||||
|
||||
QString m_error;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_CSVEXPORTER_H
|
||||
@@ -49,7 +49,12 @@ private:
|
||||
|
||||
|
||||
KeePass1Reader::KeePass1Reader()
|
||||
: m_error(false)
|
||||
: m_db(Q_NULLPTR)
|
||||
, m_tmpParent(Q_NULLPTR)
|
||||
, m_device(Q_NULLPTR)
|
||||
, m_encryptionFlags(0)
|
||||
, m_transformRounds(0)
|
||||
, m_error(false)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -154,14 +159,16 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
||||
raiseError("Invalid number of transform rounds");
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
m_db->setTransformRounds(m_transformRounds);
|
||||
if (!m_db->setTransformRounds(m_transformRounds)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
qint64 contentPos = m_device->pos();
|
||||
|
||||
QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
|
||||
|
||||
if (!cipherStream) {
|
||||
raiseError("Unable to create cipher stream");
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
@@ -234,7 +241,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|
||||
key.addKey(newFileKey);
|
||||
}
|
||||
|
||||
db->setKey(key);
|
||||
if (!db->setKey(key)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
return db.take();
|
||||
}
|
||||
@@ -326,16 +336,26 @@ SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const Q
|
||||
}
|
||||
|
||||
QByteArray finalKey = key(passwordData, keyfileData);
|
||||
if (finalKey.isEmpty()) {
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
if (m_encryptionFlags & KeePass1::Rijndael) {
|
||||
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Aes256,
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
|
||||
}
|
||||
else {
|
||||
cipherStream.reset(new SymmetricCipherStream(m_device, SymmetricCipher::Twofish,
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt, finalKey, m_encryptionIV));
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
|
||||
}
|
||||
|
||||
cipherStream->open(QIODevice::ReadOnly);
|
||||
if (!cipherStream->init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream->errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
if (!cipherStream->open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream->errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
bool success = verifyKey(cipherStream.data());
|
||||
|
||||
@@ -372,9 +392,18 @@ QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& key
|
||||
key.setPassword(password);
|
||||
key.setKeyfileData(keyfileData);
|
||||
|
||||
bool ok;
|
||||
QString errorString;
|
||||
QByteArray transformedKey = key.transform(m_transformSeed, m_transformRounds, &ok, &errorString);
|
||||
|
||||
if (!ok) {
|
||||
raiseError(errorString);
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(key.transform(m_transformSeed, m_transformRounds));
|
||||
hash.addData(transformedKey);
|
||||
return hash.result();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,14 +20,19 @@
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "format/KeePass2.h"
|
||||
|
||||
KeePass2RandomStream::KeePass2RandomStream(const QByteArray& key)
|
||||
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt,
|
||||
CryptoHash::hash(key, CryptoHash::Sha256), KeePass2::INNER_STREAM_SALSA20_IV)
|
||||
KeePass2RandomStream::KeePass2RandomStream()
|
||||
: m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt)
|
||||
, m_offset(0)
|
||||
{
|
||||
}
|
||||
|
||||
QByteArray KeePass2RandomStream::randomBytes(int size)
|
||||
bool KeePass2RandomStream::init(const QByteArray& key)
|
||||
{
|
||||
return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256),
|
||||
KeePass2::INNER_STREAM_SALSA20_IV);
|
||||
}
|
||||
|
||||
QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok)
|
||||
{
|
||||
QByteArray result;
|
||||
|
||||
@@ -35,7 +40,10 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
|
||||
|
||||
while (bytesRemaining > 0) {
|
||||
if (m_buffer.size() == m_offset) {
|
||||
loadBlock();
|
||||
if (!loadBlock()) {
|
||||
*ok = false;
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
int bytesToCopy = qMin(bytesRemaining, m_buffer.size() - m_offset);
|
||||
@@ -44,12 +52,20 @@ QByteArray KeePass2RandomStream::randomBytes(int size)
|
||||
bytesRemaining -= bytesToCopy;
|
||||
}
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray KeePass2RandomStream::process(const QByteArray& data)
|
||||
QByteArray KeePass2RandomStream::process(const QByteArray& data, bool* ok)
|
||||
{
|
||||
QByteArray randomData = randomBytes(data.size());
|
||||
bool randomBytesOk;
|
||||
|
||||
QByteArray randomData = randomBytes(data.size(), &randomBytesOk);
|
||||
if (!randomBytesOk) {
|
||||
*ok = false;
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray result;
|
||||
result.resize(data.size());
|
||||
|
||||
@@ -57,23 +73,39 @@ QByteArray KeePass2RandomStream::process(const QByteArray& data)
|
||||
result[i] = data[i] ^ randomData[i];
|
||||
}
|
||||
|
||||
*ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
void KeePass2RandomStream::processInPlace(QByteArray& data)
|
||||
bool KeePass2RandomStream::processInPlace(QByteArray& data)
|
||||
{
|
||||
QByteArray randomData = randomBytes(data.size());
|
||||
bool ok;
|
||||
QByteArray randomData = randomBytes(data.size(), &ok);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < data.size(); i++) {
|
||||
data[i] = data[i] ^ randomData[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void KeePass2RandomStream::loadBlock()
|
||||
QString KeePass2RandomStream::errorString() const
|
||||
{
|
||||
return m_cipher.errorString();
|
||||
}
|
||||
|
||||
bool KeePass2RandomStream::loadBlock()
|
||||
{
|
||||
Q_ASSERT(m_offset == m_buffer.size());
|
||||
|
||||
m_buffer.fill('\0', m_cipher.blockSize());
|
||||
m_cipher.processInPlace(m_buffer);
|
||||
m_offset = 0;
|
||||
if (!m_cipher.processInPlace(m_buffer)) {
|
||||
return false;
|
||||
}
|
||||
m_offset = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -25,13 +25,15 @@
|
||||
class KeePass2RandomStream
|
||||
{
|
||||
public:
|
||||
explicit KeePass2RandomStream(const QByteArray& key);
|
||||
QByteArray randomBytes(int size);
|
||||
QByteArray process(const QByteArray& data);
|
||||
void processInPlace(QByteArray& data);
|
||||
KeePass2RandomStream();
|
||||
bool init(const QByteArray& key);
|
||||
QByteArray randomBytes(int size, bool* ok);
|
||||
QByteArray process(const QByteArray& data, bool* ok);
|
||||
bool processInPlace(QByteArray& data) Q_REQUIRED_RESULT;
|
||||
QString errorString() const;
|
||||
|
||||
private:
|
||||
void loadBlock();
|
||||
bool loadBlock();
|
||||
|
||||
SymmetricCipher m_cipher;
|
||||
QByteArray m_buffer;
|
||||
|
||||
@@ -33,8 +33,12 @@
|
||||
#include "streams/SymmetricCipherStream.h"
|
||||
|
||||
KeePass2Reader::KeePass2Reader()
|
||||
: m_error(false)
|
||||
: m_device(Q_NULLPTR)
|
||||
, m_headerStream(Q_NULLPTR)
|
||||
, m_error(false)
|
||||
, m_headerEnd(false)
|
||||
, m_saveXml(false)
|
||||
, m_db(Q_NULLPTR)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -96,16 +100,26 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
m_db->setKey(key, m_transformSeed, false);
|
||||
if (!m_db->setKey(key, m_transformSeed, false)) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
CryptoHash hash(CryptoHash::Sha256);
|
||||
hash.addData(m_masterSeed);
|
||||
hash.addData(m_db->transformedMasterKey());
|
||||
QByteArray finalKey = hash.result();
|
||||
|
||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Decrypt, finalKey, m_encryptionIV);
|
||||
cipherStream.open(QIODevice::ReadOnly);
|
||||
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256,
|
||||
SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
|
||||
if (!cipherStream.init(finalKey, m_encryptionIV)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
if (!cipherStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
QByteArray realStart = cipherStream.read(32);
|
||||
|
||||
@@ -115,7 +129,10 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
||||
}
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
hashedStream.open(QIODevice::ReadOnly);
|
||||
if (!hashedStream.open(QIODevice::ReadOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
QIODevice* xmlDevice;
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
@@ -126,11 +143,18 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
|
||||
else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
ioCompressor->open(QIODevice::ReadOnly);
|
||||
if (!ioCompressor->open(QIODevice::ReadOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
xmlDevice = ioCompressor.data();
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(m_protectedStreamKey);
|
||||
KeePass2RandomStream randomStream;
|
||||
if (!randomStream.init(m_protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
QScopedPointer<QBuffer> buffer;
|
||||
|
||||
@@ -340,7 +364,9 @@ void KeePass2Reader::setTansformRounds(const QByteArray& data)
|
||||
raiseError("Invalid transform rounds size");
|
||||
}
|
||||
else {
|
||||
m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER));
|
||||
if (!m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER))) {
|
||||
raiseError(tr("Unable to calculate master key"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,13 +88,20 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
CHECK_RETURN(writeData(header.data()));
|
||||
|
||||
SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc,
|
||||
SymmetricCipher::Encrypt, finalKey, encryptionIV);
|
||||
cipherStream.open(QIODevice::WriteOnly);
|
||||
SymmetricCipher::Encrypt);
|
||||
cipherStream.init(finalKey, encryptionIV);
|
||||
if (!cipherStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(cipherStream.errorString());
|
||||
return;
|
||||
}
|
||||
m_device = &cipherStream;
|
||||
CHECK_RETURN(writeData(startBytes));
|
||||
|
||||
HashedBlockStream hashedStream(&cipherStream);
|
||||
hashedStream.open(QIODevice::WriteOnly);
|
||||
if (!hashedStream.open(QIODevice::WriteOnly)) {
|
||||
raiseError(hashedStream.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
QScopedPointer<QtIOCompressor> ioCompressor;
|
||||
|
||||
@@ -104,14 +111,25 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
else {
|
||||
ioCompressor.reset(new QtIOCompressor(&hashedStream));
|
||||
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
|
||||
ioCompressor->open(QIODevice::WriteOnly);
|
||||
if (!ioCompressor->open(QIODevice::WriteOnly)) {
|
||||
raiseError(ioCompressor->errorString());
|
||||
return;
|
||||
}
|
||||
m_device = ioCompressor.data();
|
||||
}
|
||||
|
||||
KeePass2RandomStream randomStream(protectedStreamKey);
|
||||
KeePass2RandomStream randomStream;
|
||||
if (!randomStream.init(protectedStreamKey)) {
|
||||
raiseError(randomStream.errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
KeePass2XmlWriter xmlWriter;
|
||||
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
|
||||
|
||||
if (xmlWriter.hasError()) {
|
||||
raiseError(xmlWriter.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
bool KeePass2Writer::writeData(const QByteArray& data)
|
||||
|
||||
@@ -34,10 +34,17 @@ KeePass2XmlReader::KeePass2XmlReader()
|
||||
: m_randomStream(Q_NULLPTR)
|
||||
, m_db(Q_NULLPTR)
|
||||
, m_meta(Q_NULLPTR)
|
||||
, m_tmpParent(Q_NULLPTR)
|
||||
, m_error(false)
|
||||
, m_strictMode(false)
|
||||
{
|
||||
}
|
||||
|
||||
void KeePass2XmlReader::setStrictMode(bool strictMode)
|
||||
{
|
||||
m_strictMode = strictMode;
|
||||
}
|
||||
|
||||
void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
|
||||
{
|
||||
m_error = false;
|
||||
@@ -350,7 +357,7 @@ void KeePass2XmlReader::parseIcon()
|
||||
while (!m_xml.error() && m_xml.readNextStartElement()) {
|
||||
if (m_xml.name() == "UUID") {
|
||||
uuid = readUuid();
|
||||
uuidSet = true;
|
||||
uuidSet = !uuid.isNull();
|
||||
}
|
||||
else if (m_xml.name() == "Data") {
|
||||
icon.loadFromData(readBinary());
|
||||
@@ -493,8 +500,13 @@ Group* KeePass2XmlReader::parseGroup()
|
||||
if (m_xml.name() == "UUID") {
|
||||
Uuid uuid = readUuid();
|
||||
if (uuid.isNull()) {
|
||||
if (m_strictMode) {
|
||||
raiseError("Null group uuid");
|
||||
}
|
||||
else {
|
||||
group->setUuid(Uuid::random());
|
||||
}
|
||||
}
|
||||
else {
|
||||
group->setUuid(uuid);
|
||||
}
|
||||
@@ -508,8 +520,10 @@ Group* KeePass2XmlReader::parseGroup()
|
||||
else if (m_xml.name() == "IconID") {
|
||||
int iconId = readNumber();
|
||||
if (iconId < 0) {
|
||||
if (m_strictMode) {
|
||||
raiseError("Invalid group icon number");
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (iconId >= DatabaseIcons::IconCount) {
|
||||
qWarning("KeePass2XmlReader::parseGroup: icon id \"%d\" not supported", iconId);
|
||||
@@ -584,6 +598,10 @@ Group* KeePass2XmlReader::parseGroup()
|
||||
}
|
||||
}
|
||||
|
||||
if (group->uuid().isNull() && !m_strictMode) {
|
||||
group->setUuid(Uuid::random());
|
||||
}
|
||||
|
||||
if (!group->uuid().isNull()) {
|
||||
Group* tmpGroup = group;
|
||||
group = getGroup(tmpGroup->uuid());
|
||||
@@ -630,8 +648,10 @@ void KeePass2XmlReader::parseDeletedObject()
|
||||
if (m_xml.name() == "UUID") {
|
||||
Uuid uuid = readUuid();
|
||||
if (uuid.isNull()) {
|
||||
if (m_strictMode) {
|
||||
raiseError("Null DeleteObject uuid");
|
||||
}
|
||||
}
|
||||
else {
|
||||
delObj.uuid = uuid;
|
||||
}
|
||||
@@ -647,7 +667,7 @@ void KeePass2XmlReader::parseDeletedObject()
|
||||
if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) {
|
||||
m_db->addDeletedObject(delObj);
|
||||
}
|
||||
else {
|
||||
else if (m_strictMode) {
|
||||
raiseError("Missing DeletedObject uuid or time");
|
||||
}
|
||||
}
|
||||
@@ -665,8 +685,13 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
|
||||
if (m_xml.name() == "UUID") {
|
||||
Uuid uuid = readUuid();
|
||||
if (uuid.isNull()) {
|
||||
if (m_strictMode) {
|
||||
raiseError("Null entry uuid");
|
||||
}
|
||||
else {
|
||||
entry->setUuid(Uuid::random());
|
||||
}
|
||||
}
|
||||
else {
|
||||
entry->setUuid(uuid);
|
||||
}
|
||||
@@ -674,7 +699,9 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
|
||||
else if (m_xml.name() == "IconID") {
|
||||
int iconId = readNumber();
|
||||
if (iconId < 0) {
|
||||
raiseError("Invalud entry icon number");
|
||||
if (m_strictMode) {
|
||||
raiseError("Invalid entry icon number");
|
||||
}
|
||||
}
|
||||
else {
|
||||
entry->setIcon(iconId);
|
||||
@@ -726,6 +753,10 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
|
||||
}
|
||||
}
|
||||
|
||||
if (entry->uuid().isNull() && !m_strictMode) {
|
||||
entry->setUuid(Uuid::random());
|
||||
}
|
||||
|
||||
if (!entry->uuid().isNull()) {
|
||||
if (history) {
|
||||
entry->setUpdateTimeinfo(false);
|
||||
@@ -779,7 +810,16 @@ void KeePass2XmlReader::parseEntryString(Entry* entry)
|
||||
|
||||
if (isProtected && !value.isEmpty()) {
|
||||
if (m_randomStream) {
|
||||
value = QString::fromUtf8(m_randomStream->process(QByteArray::fromBase64(value.toLatin1())));
|
||||
QByteArray ciphertext = QByteArray::fromBase64(value.toLatin1());
|
||||
bool ok;
|
||||
QByteArray plaintext = m_randomStream->process(ciphertext, &ok);
|
||||
if (!ok) {
|
||||
value.clear();
|
||||
raiseError(m_randomStream->errorString());
|
||||
}
|
||||
else {
|
||||
value = QString::fromUtf8(plaintext);
|
||||
}
|
||||
}
|
||||
else {
|
||||
raiseError("Unable to decrypt entry string");
|
||||
@@ -795,8 +835,14 @@ void KeePass2XmlReader::parseEntryString(Entry* entry)
|
||||
}
|
||||
|
||||
if (keySet && valueSet) {
|
||||
// the default attributes are always there so additionally check if it's empty
|
||||
if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) {
|
||||
raiseError("Duplicate custom attribute found");
|
||||
}
|
||||
else {
|
||||
entry->attributes()->set(key, value, protect);
|
||||
}
|
||||
}
|
||||
else {
|
||||
raiseError("Entry string key or value missing");
|
||||
}
|
||||
@@ -826,13 +872,15 @@ QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
|
||||
m_xml.skipCurrentElement();
|
||||
}
|
||||
else {
|
||||
// format compatbility
|
||||
// format compatibility
|
||||
value = readBinary();
|
||||
bool isProtected = attr.hasAttribute("Protected")
|
||||
&& (attr.value("Protected") == "True");
|
||||
|
||||
if (isProtected && !value.isEmpty()) {
|
||||
m_randomStream->processInPlace(value);
|
||||
if (!m_randomStream->processInPlace(value)) {
|
||||
raiseError(m_randomStream->errorString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -844,8 +892,13 @@ QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
|
||||
}
|
||||
|
||||
if (keySet && valueSet) {
|
||||
if (entry->attachments()->hasKey(key)) {
|
||||
raiseError("Duplicate attachment found");
|
||||
}
|
||||
else {
|
||||
entry->attachments()->set(key, value);
|
||||
}
|
||||
}
|
||||
else {
|
||||
raiseError("Entry binary key or value missing");
|
||||
}
|
||||
@@ -986,8 +1039,13 @@ QDateTime KeePass2XmlReader::readDateTime()
|
||||
QDateTime dt = QDateTime::fromString(str, Qt::ISODate);
|
||||
|
||||
if (!dt.isValid()) {
|
||||
if (m_strictMode) {
|
||||
raiseError("Invalid date time value");
|
||||
}
|
||||
else {
|
||||
dt = Tools::currentDateTimeUtc();
|
||||
}
|
||||
}
|
||||
|
||||
return dt;
|
||||
}
|
||||
@@ -1001,7 +1059,9 @@ QColor KeePass2XmlReader::readColor()
|
||||
}
|
||||
|
||||
if (colorStr.length() != 7 || colorStr[0] != '#') {
|
||||
if (m_strictMode) {
|
||||
raiseError("Invalid color value");
|
||||
}
|
||||
return QColor();
|
||||
}
|
||||
|
||||
@@ -1011,7 +1071,9 @@ QColor KeePass2XmlReader::readColor()
|
||||
bool ok;
|
||||
int rgbPart = rgbPartStr.toInt(&ok, 16);
|
||||
if (!ok || rgbPart > 255) {
|
||||
if (m_strictMode) {
|
||||
raiseError("Invalid color rgb part");
|
||||
}
|
||||
return QColor();
|
||||
}
|
||||
|
||||
@@ -1042,8 +1104,13 @@ int KeePass2XmlReader::readNumber()
|
||||
Uuid KeePass2XmlReader::readUuid()
|
||||
{
|
||||
QByteArray uuidBin = readBinary();
|
||||
if (uuidBin.length() != Uuid::Length) {
|
||||
if (uuidBin.isEmpty()) {
|
||||
return Uuid();
|
||||
}
|
||||
else if (uuidBin.length() != Uuid::Length) {
|
||||
if (m_strictMode) {
|
||||
raiseError("Invalid uuid value");
|
||||
}
|
||||
return Uuid();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -47,6 +47,7 @@ public:
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
QByteArray headerHash();
|
||||
void setStrictMode(bool strictMode);
|
||||
|
||||
private:
|
||||
bool parseKeePassFile();
|
||||
@@ -95,6 +96,7 @@ private:
|
||||
QByteArray m_headerHash;
|
||||
bool m_error;
|
||||
QString m_errorStr;
|
||||
bool m_strictMode;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2XMLREADER_H
|
||||
|
||||
@@ -28,6 +28,7 @@ KeePass2XmlWriter::KeePass2XmlWriter()
|
||||
: m_db(Q_NULLPTR)
|
||||
, m_meta(Q_NULLPTR)
|
||||
, m_randomStream(Q_NULLPTR)
|
||||
, m_error(false)
|
||||
{
|
||||
m_xml.setAutoFormatting(true);
|
||||
m_xml.setAutoFormattingIndent(-1); // 1 tab
|
||||
@@ -65,6 +66,16 @@ void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db)
|
||||
writeDatabase(&file, db);
|
||||
}
|
||||
|
||||
bool KeePass2XmlWriter::hasError()
|
||||
{
|
||||
return m_error;
|
||||
}
|
||||
|
||||
QString KeePass2XmlWriter::errorString()
|
||||
{
|
||||
return m_errorStr;
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::generateIdMap()
|
||||
{
|
||||
QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
|
||||
@@ -340,7 +351,11 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
|
||||
if (protect) {
|
||||
if (m_randomStream) {
|
||||
m_xml.writeAttribute("Protected", "True");
|
||||
QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8());
|
||||
bool ok;
|
||||
QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok);
|
||||
if (!ok) {
|
||||
raiseError(m_randomStream->errorString());
|
||||
}
|
||||
value = QString::fromLatin1(rawData.toBase64());
|
||||
}
|
||||
else {
|
||||
@@ -527,3 +542,9 @@ QString KeePass2XmlWriter::colorPartToString(int value)
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
void KeePass2XmlWriter::raiseError(const QString& errorMessage)
|
||||
{
|
||||
m_error = true;
|
||||
m_errorStr = errorMessage;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public:
|
||||
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = Q_NULLPTR,
|
||||
const QByteArray& headerHash = QByteArray());
|
||||
void writeDatabase(const QString& filename, Database* db);
|
||||
bool error();
|
||||
bool hasError();
|
||||
QString errorString();
|
||||
|
||||
private:
|
||||
@@ -74,12 +74,16 @@ private:
|
||||
void writeTriState(const QString& qualifiedName, Group::TriState triState);
|
||||
QString colorPartToString(int value);
|
||||
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
QXmlStreamWriter m_xml;
|
||||
Database* m_db;
|
||||
Metadata* m_meta;
|
||||
KeePass2RandomStream* m_randomStream;
|
||||
QByteArray m_headerHash;
|
||||
QHash<QByteArray, int> m_idMap;
|
||||
bool m_error;
|
||||
QString m_errorStr;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "ui_AboutDialog.h"
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "version.h"
|
||||
#include "core/FilePath.h"
|
||||
|
||||
AboutDialog::AboutDialog(QWidget* parent)
|
||||
@@ -35,6 +36,19 @@ AboutDialog::AboutDialog(QWidget* parent)
|
||||
|
||||
m_ui->iconLabel->setPixmap(filePath()->applicationIcon().pixmap(48));
|
||||
|
||||
QString commitHash;
|
||||
if (!QString(GIT_HEAD).isEmpty()) {
|
||||
commitHash = GIT_HEAD;
|
||||
}
|
||||
else if (!QString(DIST_HASH).contains("Format")) {
|
||||
commitHash = DIST_HASH;
|
||||
}
|
||||
|
||||
if (!commitHash.isEmpty()) {
|
||||
QString labelText = tr("Revision").append(": ").append(commitHash);
|
||||
m_ui->label_git->setText(labelText);
|
||||
}
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(close()));
|
||||
}
|
||||
|
||||
@@ -54,6 +54,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_git">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
|
||||
@@ -120,7 +120,10 @@ void ChangeMasterKeyWidget::generateKey()
|
||||
FileKey fileKey;
|
||||
QString errorMsg;
|
||||
if (!fileKey.load(m_ui->keyFileCombo->currentText(), &errorMsg)) {
|
||||
// TODO: error handling
|
||||
MessageBox::critical(this, tr("Failed to set key file"),
|
||||
tr("Failed to set %1 as the Key file:\n%2")
|
||||
.arg(m_ui->keyFileCombo->currentText(), errorMsg));
|
||||
return;
|
||||
}
|
||||
m_key.addKey(fileKey);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>438</width>
|
||||
<height>249</height>
|
||||
<height>256</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@@ -155,8 +155,8 @@
|
||||
<tabstops>
|
||||
<tabstop>passwordGroup</tabstop>
|
||||
<tabstop>enterPasswordEdit</tabstop>
|
||||
<tabstop>togglePasswordButton</tabstop>
|
||||
<tabstop>repeatPasswordEdit</tabstop>
|
||||
<tabstop>togglePasswordButton</tabstop>
|
||||
<tabstop>keyFileGroup</tabstop>
|
||||
<tabstop>keyFileCombo</tabstop>
|
||||
<tabstop>browseKeyFileButton</tabstop>
|
||||
|
||||
@@ -21,11 +21,6 @@
|
||||
#include <QClipboard>
|
||||
#include <QTimer>
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
|
||||
#include "core/Config.h"
|
||||
|
||||
Clipboard* Clipboard::m_instance(Q_NULLPTR);
|
||||
@@ -51,6 +46,7 @@ void Clipboard::setText(const QString& text)
|
||||
if (config()->get("security/clearclipboard").toBool()) {
|
||||
int timeout = config()->get("security/clearclipboardtimeout").toInt();
|
||||
if (timeout > 0) {
|
||||
m_lastCopied = text;
|
||||
m_timer->start(timeout * 1000);
|
||||
}
|
||||
}
|
||||
@@ -65,15 +61,16 @@ void Clipboard::clearClipboard()
|
||||
return;
|
||||
}
|
||||
|
||||
if (clipboard->text(QClipboard::Clipboard) == m_lastCopied) {
|
||||
clipboard->clear(QClipboard::Clipboard);
|
||||
if (clipboard->supportsSelection()) {
|
||||
}
|
||||
|
||||
if (clipboard->supportsSelection()
|
||||
&& (clipboard->text(QClipboard::Selection) == m_lastCopied)) {
|
||||
clipboard->clear(QClipboard::Selection);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
|
||||
QDBusMessage message = QDBusMessage::createMethodCall("org.kde.klipper", "/klipper", "", "clearClipboardHistory");
|
||||
QDBusConnection::sessionBus().send(message);
|
||||
#endif
|
||||
m_lastCopied.clear();
|
||||
}
|
||||
|
||||
void Clipboard::cleanup()
|
||||
|
||||
@@ -43,6 +43,7 @@ private:
|
||||
static Clipboard* m_instance;
|
||||
|
||||
QTimer* m_timer;
|
||||
QString m_lastCopied;
|
||||
};
|
||||
|
||||
inline Clipboard* clipboard() {
|
||||
|
||||
@@ -67,11 +67,13 @@ void DatabaseOpenWidget::load(const QString& filename)
|
||||
|
||||
m_ui->labelFilename->setText(filename);
|
||||
|
||||
if (config()->get("RememberLastKeyFiles").toBool()) {
|
||||
QHash<QString, QVariant> lastKeyFiles = config()->get("LastKeyFiles").toHash();
|
||||
if (lastKeyFiles.contains(m_filename)) {
|
||||
m_ui->checkKeyFile->setChecked(true);
|
||||
m_ui->comboKeyFile->addItem(lastKeyFiles[m_filename].toString());
|
||||
}
|
||||
}
|
||||
|
||||
m_ui->editPassword->setFocus();
|
||||
}
|
||||
@@ -87,7 +89,7 @@ void DatabaseOpenWidget::enterKey(const QString& pw, const QString& keyFile)
|
||||
m_ui->editPassword->setText(pw);
|
||||
}
|
||||
if (!keyFile.isEmpty()) {
|
||||
m_ui->checkKeyFile->setText(keyFile);
|
||||
m_ui->comboKeyFile->setEditText(keyFile);
|
||||
}
|
||||
|
||||
openDatabase();
|
||||
@@ -117,8 +119,8 @@ void DatabaseOpenWidget::openDatabase()
|
||||
Q_EMIT editFinished(true);
|
||||
}
|
||||
else {
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.\n%1")
|
||||
.arg(reader.errorString()));
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to open the database.").append("\n")
|
||||
.append(reader.errorString()));
|
||||
m_ui->editPassword->clear();
|
||||
}
|
||||
}
|
||||
@@ -138,7 +140,7 @@ CompositeKey DatabaseOpenWidget::databaseKey()
|
||||
QString keyFilename = m_ui->comboKeyFile->currentText();
|
||||
QString errorMsg;
|
||||
if (!key.load(keyFilename, &errorMsg)) {
|
||||
MessageBox::warning(this, tr("Error"), tr("Can't open key file:\n%1").arg(errorMsg));
|
||||
MessageBox::warning(this, tr("Error"), tr("Can't open key file").append(":\n").append(errorMsg));
|
||||
return CompositeKey();
|
||||
}
|
||||
masterKey.addKey(key);
|
||||
@@ -148,7 +150,9 @@ CompositeKey DatabaseOpenWidget::databaseKey()
|
||||
lastKeyFiles.remove(m_filename);
|
||||
}
|
||||
|
||||
if (config()->get("RememberLastKeyFiles").toBool()) {
|
||||
config()->set("LastKeyFiles", lastKeyFiles);
|
||||
}
|
||||
|
||||
return masterKey;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,10 @@ void DatabaseSettingsWidget::reject()
|
||||
void DatabaseSettingsWidget::transformRoundsBenchmark()
|
||||
{
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
m_ui->transformRoundsSpinBox->setValue(CompositeKey::transformKeyBenchmark(1000));
|
||||
int rounds = CompositeKey::transformKeyBenchmark(1000);
|
||||
if (rounds != -1) {
|
||||
m_ui->transformRoundsSpinBox->setValue(rounds);
|
||||
}
|
||||
QApplication::restoreOverrideCursor();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,9 @@
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/qsavefile.h"
|
||||
#include "format/CsvExporter.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/DatabaseWidgetStateSync.h"
|
||||
#include "gui/DragTabBar.h"
|
||||
#include "gui/FileDialog.h"
|
||||
#include "gui/MessageBox.h"
|
||||
@@ -35,6 +37,7 @@
|
||||
|
||||
DatabaseManagerStruct::DatabaseManagerStruct()
|
||||
: dbWidget(Q_NULLPTR)
|
||||
, lockFile(Q_NULLPTR)
|
||||
, saveToFilename(false)
|
||||
, modified(false)
|
||||
, readOnly(false)
|
||||
@@ -46,12 +49,15 @@ const int DatabaseTabWidget::LastDatabasesCount = 5;
|
||||
|
||||
DatabaseTabWidget::DatabaseTabWidget(QWidget* parent)
|
||||
: QTabWidget(parent)
|
||||
, m_dbWidgetSateSync(new DatabaseWidgetStateSync(this))
|
||||
{
|
||||
DragTabBar* tabBar = new DragTabBar(this);
|
||||
tabBar->setDrawBase(false);
|
||||
setTabBar(tabBar);
|
||||
setDocumentMode(true);
|
||||
|
||||
connect(this, SIGNAL(tabCloseRequested(int)), SLOT(closeDatabase(int)));
|
||||
connect(this, SIGNAL(currentChanged(int)), SLOT(emitActivateDatabaseChanged()));
|
||||
connect(this, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), m_dbWidgetSateSync, SLOT(setActive(DatabaseWidget*)));
|
||||
connect(autoType(), SIGNAL(globalShortcutTriggered()), SLOT(performGlobalAutoType()));
|
||||
}
|
||||
|
||||
@@ -138,8 +144,35 @@ void DatabaseTabWidget::openDatabase(const QString& fileName, const QString& pw,
|
||||
}
|
||||
file.close();
|
||||
|
||||
QLockFile* lockFile = new QLockFile(QString("%1/.%2.lock").arg(fileInfo.canonicalPath(), fileInfo.fileName()));
|
||||
lockFile->setStaleLockTime(0);
|
||||
|
||||
if (!dbStruct.readOnly && !lockFile->tryLock()) {
|
||||
// for now silently ignore if we can't create a lock file
|
||||
// due to lack of permissions
|
||||
if (lockFile->error() != QLockFile::PermissionError) {
|
||||
QMessageBox::StandardButton result = MessageBox::question(this, tr("Open database"),
|
||||
tr("The database you are trying to open is locked by another instance of KeePassX.\n"
|
||||
"Do you want to open it anyway? Alternatively the database is opened read-only."),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (result == QMessageBox::No) {
|
||||
dbStruct.readOnly = true;
|
||||
delete lockFile;
|
||||
lockFile = Q_NULLPTR;
|
||||
}
|
||||
else {
|
||||
// take over the lock file if possible
|
||||
if (lockFile->removeStaleLockFile()) {
|
||||
lockFile->tryLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Database* db = new Database();
|
||||
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
||||
dbStruct.lockFile = lockFile;
|
||||
dbStruct.saveToFilename = !dbStruct.readOnly;
|
||||
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
@@ -189,28 +222,32 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
if (dbName.right(1) == "*") {
|
||||
dbName.chop(1);
|
||||
}
|
||||
if (dbStruct.dbWidget->currentMode() == DatabaseWidget::EditMode && db->hasKey()) {
|
||||
if (dbStruct.dbWidget->isInEditMode() && db->hasKey()) {
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Close?"),
|
||||
tr("\"%1\" is in edit mode.\nClose anyway?").arg(dbName),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (result == QMessageBox::No) {
|
||||
tr("\"%1\" is in edit mode.\nDiscard changes and close anyway?").arg(dbName),
|
||||
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
|
||||
if (result == QMessageBox::Cancel) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (dbStruct.modified) {
|
||||
if (config()->get("AutoSaveOnExit").toBool()) {
|
||||
saveDatabase(db);
|
||||
if (!saveDatabase(db)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Save changes?"),
|
||||
tr("\"%1\" was modified.\nSave changes?").arg(dbName),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
if (result == QMessageBox::Yes) {
|
||||
saveDatabase(db);
|
||||
if (!saveDatabase(db)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (result == QMessageBox::Cancel) {
|
||||
return false;
|
||||
@@ -234,6 +271,7 @@ void DatabaseTabWidget::deleteDatabase(Database* db)
|
||||
removeTab(index);
|
||||
toggleTabbar();
|
||||
m_dbList.remove(db);
|
||||
delete dbStruct.lockFile;
|
||||
delete dbStruct.dbWidget;
|
||||
delete db;
|
||||
|
||||
@@ -252,7 +290,7 @@ bool DatabaseTabWidget::closeAllDatabases()
|
||||
return true;
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::saveDatabase(Database* db)
|
||||
bool DatabaseTabWidget::saveDatabase(Database* db)
|
||||
{
|
||||
DatabaseManagerStruct& dbStruct = m_dbList[db];
|
||||
|
||||
@@ -268,18 +306,20 @@ void DatabaseTabWidget::saveDatabase(Database* db)
|
||||
if (result) {
|
||||
dbStruct.modified = false;
|
||||
updateTabName(db);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
+ saveFile.errorString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
saveDatabaseAs(db);
|
||||
return saveDatabaseAs(db);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
bool DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
{
|
||||
DatabaseManagerStruct& dbStruct = m_dbList[db];
|
||||
QString oldFileName;
|
||||
@@ -289,29 +329,68 @@ void DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
|
||||
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"));
|
||||
if (!fileName.isEmpty()) {
|
||||
bool result = false;
|
||||
QFileInfo fileInfo(fileName);
|
||||
QString lockFilePath;
|
||||
if (fileInfo.exists()) {
|
||||
// returns empty string when file doesn't exist
|
||||
lockFilePath = fileInfo.canonicalPath();
|
||||
}
|
||||
else {
|
||||
lockFilePath = fileInfo.absolutePath();
|
||||
}
|
||||
QString lockFileName = QString("%1/.%2.lock").arg(lockFilePath, fileInfo.fileName());
|
||||
QScopedPointer<QLockFile> lockFile(new QLockFile(lockFileName));
|
||||
lockFile->setStaleLockTime(0);
|
||||
if (!lockFile->tryLock()) {
|
||||
// for now silently ignore if we can't create a lock file
|
||||
// due to lack of permissions
|
||||
if (lockFile->error() != QLockFile::PermissionError) {
|
||||
QMessageBox::StandardButton result = MessageBox::question(this, tr("Save database as"),
|
||||
tr("The database you are trying to save as is locked by another instance of KeePassX.\n"
|
||||
"Do you want to save it anyway?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
QSaveFile saveFile(fileName);
|
||||
if (saveFile.open(QIODevice::WriteOnly)) {
|
||||
m_writer.writeDatabase(&saveFile, db);
|
||||
result = saveFile.commit();
|
||||
if (result == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// take over the lock file if possible
|
||||
if (lockFile->removeStaleLockFile()) {
|
||||
lockFile->tryLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QSaveFile saveFile(fileName);
|
||||
if (!saveFile.open(QIODevice::WriteOnly)) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
+ saveFile.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_writer.writeDatabase(&saveFile, db);
|
||||
if (!saveFile.commit()) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
+ saveFile.errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
dbStruct.modified = false;
|
||||
dbStruct.saveToFilename = true;
|
||||
QFileInfo fileInfo(fileName);
|
||||
dbStruct.readOnly = false;
|
||||
dbStruct.filePath = fileInfo.absoluteFilePath();
|
||||
dbStruct.canonicalFilePath = fileInfo.canonicalFilePath();
|
||||
dbStruct.fileName = fileInfo.fileName();
|
||||
dbStruct.dbWidget->updateFilename(dbStruct.filePath);
|
||||
delete dbStruct.lockFile;
|
||||
dbStruct.lockFile = lockFile.take();
|
||||
updateTabName(db);
|
||||
updateLastDatabases(dbStruct.filePath);
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the database failed.") + "\n\n"
|
||||
+ saveFile.errorString());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,21 +415,43 @@ void DatabaseTabWidget::closeDatabaseFromSender()
|
||||
closeDatabase(db);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::saveDatabase(int index)
|
||||
bool DatabaseTabWidget::saveDatabase(int index)
|
||||
{
|
||||
if (index == -1) {
|
||||
index = currentIndex();
|
||||
}
|
||||
|
||||
saveDatabase(indexDatabase(index));
|
||||
return saveDatabase(indexDatabase(index));
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::saveDatabaseAs(int index)
|
||||
bool DatabaseTabWidget::saveDatabaseAs(int index)
|
||||
{
|
||||
if (index == -1) {
|
||||
index = currentIndex();
|
||||
}
|
||||
saveDatabaseAs(indexDatabase(index));
|
||||
|
||||
return saveDatabaseAs(indexDatabase(index));
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::exportToCsv()
|
||||
{
|
||||
Database* db = indexDatabase(currentIndex());
|
||||
if (!db) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
QString fileName = fileDialog()->getSaveFileName(this, tr("Export database to CSV file"),
|
||||
QString(), tr("CSV file").append(" (*.csv)"));
|
||||
if (fileName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
CsvExporter csvExporter;
|
||||
if (!csvExporter.exportDatabase(fileName, db)) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Writing the CSV file failed.") + "\n\n"
|
||||
+ csvExporter.errorString());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::changeMasterKey()
|
||||
@@ -381,7 +482,7 @@ void DatabaseTabWidget::updateTabName(Database* db)
|
||||
|
||||
QString tabName;
|
||||
|
||||
if (dbStruct.saveToFilename) {
|
||||
if (dbStruct.saveToFilename || dbStruct.readOnly) {
|
||||
if (db->metadata()->name().isEmpty()) {
|
||||
tabName = dbStruct.fileName;
|
||||
}
|
||||
@@ -503,7 +604,7 @@ DatabaseWidget* DatabaseTabWidget::currentDatabaseWidget()
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseTabWidget::hasLockableDatabases()
|
||||
bool DatabaseTabWidget::hasLockableDatabases() const
|
||||
{
|
||||
QHashIterator<Database*, DatabaseManagerStruct> i(m_dbList);
|
||||
while (i.hasNext()) {
|
||||
@@ -521,16 +622,69 @@ bool DatabaseTabWidget::hasLockableDatabases()
|
||||
|
||||
void DatabaseTabWidget::lockDatabases()
|
||||
{
|
||||
QHashIterator<Database*, DatabaseManagerStruct> i(m_dbList);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
DatabaseWidget::Mode mode = i.value().dbWidget->currentMode();
|
||||
for (int i = 0; i < count(); i++) {
|
||||
DatabaseWidget* dbWidget = static_cast<DatabaseWidget*>(widget(i));
|
||||
Database* db = databaseFromDatabaseWidget(dbWidget);
|
||||
|
||||
if ((mode == DatabaseWidget::ViewMode || mode == DatabaseWidget::EditMode)
|
||||
&& i.value().dbWidget->dbHasKey()) {
|
||||
i.value().dbWidget->lock();
|
||||
updateTabName(i.key());
|
||||
DatabaseWidget::Mode mode = dbWidget->currentMode();
|
||||
|
||||
if ((mode != DatabaseWidget::ViewMode && mode != DatabaseWidget::EditMode)
|
||||
|| !dbWidget->dbHasKey()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// show the correct tab widget before we are asking questions about it
|
||||
setCurrentWidget(dbWidget);
|
||||
|
||||
if (mode == DatabaseWidget::EditMode) {
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Lock database"),
|
||||
tr("Can't lock the database as you are currently editing it.\nPlease press cancel to finish your changes or discard them."),
|
||||
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
|
||||
if (result == QMessageBox::Cancel) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (m_dbList[db].modified && !m_dbList[db].saveToFilename) {
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Lock database"),
|
||||
tr("This database has never been saved.\nYou can save the database or stop locking it."),
|
||||
QMessageBox::Save | QMessageBox::Cancel, QMessageBox::Cancel);
|
||||
if (result == QMessageBox::Save) {
|
||||
if (!saveDatabase(db)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (result == QMessageBox::Cancel) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (m_dbList[db].modified) {
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Lock database"),
|
||||
tr("This database has been modified.\nDo you want to save the database before locking it?\nOtherwise your changes are lost."),
|
||||
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
|
||||
if (result == QMessageBox::Save) {
|
||||
if (!saveDatabase(db)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (result == QMessageBox::Discard) {
|
||||
m_dbList[db].modified = false;
|
||||
}
|
||||
else if (result == QMessageBox::Cancel) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
dbWidget->lock();
|
||||
// database has changed so we can't use the db variable anymore
|
||||
updateTabName(dbWidget->database());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,6 +738,11 @@ void DatabaseTabWidget::changeDatabase(Database* newDb)
|
||||
connectDatabase(newDb, oldDb);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::emitActivateDatabaseChanged()
|
||||
{
|
||||
Q_EMIT activateDatabaseChanged(currentDatabaseWidget());
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::connectDatabase(Database* newDb, Database* oldDb)
|
||||
{
|
||||
if (oldDb) {
|
||||
|
||||
@@ -21,10 +21,12 @@
|
||||
#include <QHash>
|
||||
#include <QTabWidget>
|
||||
|
||||
#include "core/qlockfile.h"
|
||||
#include "format/KeePass2Writer.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
class DatabaseWidget;
|
||||
class DatabaseWidgetStateSync;
|
||||
class DatabaseOpenWidget;
|
||||
class QFile;
|
||||
|
||||
@@ -33,6 +35,7 @@ struct DatabaseManagerStruct
|
||||
DatabaseManagerStruct();
|
||||
|
||||
DatabaseWidget* dbWidget;
|
||||
QLockFile* lockFile;
|
||||
QString filePath;
|
||||
QString canonicalFilePath;
|
||||
QString fileName;
|
||||
@@ -53,7 +56,7 @@ public:
|
||||
void openDatabase(const QString& fileName, const QString& pw = QString(),
|
||||
const QString& keyFile = QString());
|
||||
DatabaseWidget* currentDatabaseWidget();
|
||||
bool hasLockableDatabases();
|
||||
bool hasLockableDatabases() const;
|
||||
|
||||
static const int LastDatabasesCount;
|
||||
|
||||
@@ -61,8 +64,9 @@ public Q_SLOTS:
|
||||
void newDatabase();
|
||||
void openDatabase();
|
||||
void importKeePass1Database();
|
||||
void saveDatabase(int index = -1);
|
||||
void saveDatabaseAs(int index = -1);
|
||||
bool saveDatabase(int index = -1);
|
||||
bool saveDatabaseAs(int index = -1);
|
||||
void exportToCsv();
|
||||
bool closeDatabase(int index = -1);
|
||||
void closeDatabaseFromSender();
|
||||
bool closeAllDatabases();
|
||||
@@ -75,6 +79,7 @@ public Q_SLOTS:
|
||||
Q_SIGNALS:
|
||||
void tabNameChanged();
|
||||
void databaseWithFileClosed(QString filePath);
|
||||
void activateDatabaseChanged(DatabaseWidget* dbWidget);
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateTabName(Database* db);
|
||||
@@ -83,10 +88,11 @@ private Q_SLOTS:
|
||||
void modified();
|
||||
void toggleTabbar();
|
||||
void changeDatabase(Database* newDb);
|
||||
void emitActivateDatabaseChanged();
|
||||
|
||||
private:
|
||||
void saveDatabase(Database* db);
|
||||
void saveDatabaseAs(Database* db);
|
||||
bool saveDatabase(Database* db);
|
||||
bool saveDatabaseAs(Database* db);
|
||||
bool closeDatabase(Database* db);
|
||||
void deleteDatabase(Database* db);
|
||||
int databaseIndex(Database* db);
|
||||
@@ -99,6 +105,7 @@ private:
|
||||
|
||||
KeePass2Writer m_writer;
|
||||
QHash<Database*, DatabaseManagerStruct> m_dbList;
|
||||
DatabaseWidgetStateSync* m_dbWidgetSateSync;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASETABWIDGET_H
|
||||
|
||||
@@ -25,10 +25,13 @@
|
||||
#include <QLineEdit>
|
||||
#include <QSplitter>
|
||||
#include <QTimer>
|
||||
#include <QProcess>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/EntrySearcher.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "core/Tools.h"
|
||||
#include "gui/ChangeMasterKeyWidget.h"
|
||||
@@ -59,12 +62,13 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
|
||||
m_mainWidget = new QWidget(this);
|
||||
QLayout* layout = new QHBoxLayout(m_mainWidget);
|
||||
QSplitter* splitter = new QSplitter(m_mainWidget);
|
||||
m_splitter = new QSplitter(m_mainWidget);
|
||||
m_splitter->setChildrenCollapsible(false);
|
||||
|
||||
QWidget* rightHandSideWidget = new QWidget(splitter);
|
||||
QWidget* rightHandSideWidget = new QWidget(m_splitter);
|
||||
m_searchWidget->setParent(rightHandSideWidget);
|
||||
|
||||
m_groupView = new GroupView(db, splitter);
|
||||
m_groupView = new GroupView(db, m_splitter);
|
||||
m_groupView->setObjectName("groupView");
|
||||
m_groupView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(m_groupView, SIGNAL(customContextMenuRequested(QPoint)),
|
||||
@@ -77,14 +81,6 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
connect(m_entryView, SIGNAL(customContextMenuRequested(QPoint)),
|
||||
SLOT(emitEntryContextMenuRequested(QPoint)));
|
||||
|
||||
QSizePolicy policy;
|
||||
policy = m_groupView->sizePolicy();
|
||||
policy.setHorizontalStretch(30);
|
||||
m_groupView->setSizePolicy(policy);
|
||||
policy = rightHandSideWidget->sizePolicy();
|
||||
policy.setHorizontalStretch(70);
|
||||
rightHandSideWidget->setSizePolicy(policy);
|
||||
|
||||
QAction* closeAction = new QAction(m_searchWidget);
|
||||
QIcon closeIcon = filePath()->icon("actions", "dialog-close");
|
||||
closeAction->setIcon(closeIcon);
|
||||
@@ -100,10 +96,17 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
|
||||
rightHandSideWidget->setLayout(vLayout);
|
||||
|
||||
splitter->addWidget(m_groupView);
|
||||
splitter->addWidget(rightHandSideWidget);
|
||||
setTabOrder(m_searchUi->searchRootRadioButton, m_entryView);
|
||||
setTabOrder(m_entryView, m_groupView);
|
||||
setTabOrder(m_groupView, m_searchWidget);
|
||||
|
||||
layout->addWidget(splitter);
|
||||
m_splitter->addWidget(m_groupView);
|
||||
m_splitter->addWidget(rightHandSideWidget);
|
||||
|
||||
m_splitter->setStretchFactor(0, 30);
|
||||
m_splitter->setStretchFactor(1, 70);
|
||||
|
||||
layout->addWidget(m_splitter);
|
||||
m_mainWidget->setLayout(layout);
|
||||
|
||||
m_editEntryWidget = new EditEntryWidget();
|
||||
@@ -135,6 +138,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
|
||||
addWidget(m_keepass1OpenWidget);
|
||||
addWidget(m_unlockDatabaseWidget);
|
||||
|
||||
connect(m_splitter, SIGNAL(splitterMoved(int,int)), SIGNAL(splitterSizesChanged()));
|
||||
connect(m_entryView->header(), SIGNAL(sectionResized(int,int,int)), SIGNAL(entryColumnSizesChanged()));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), this, SLOT(clearLastGroup(Group*)));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), SIGNAL(groupChanged()));
|
||||
connect(m_groupView, SIGNAL(groupChanged(Group*)), m_entryView, SLOT(setGroup(Group*)));
|
||||
@@ -166,7 +171,7 @@ DatabaseWidget::~DatabaseWidget()
|
||||
{
|
||||
}
|
||||
|
||||
DatabaseWidget::Mode DatabaseWidget::currentMode()
|
||||
DatabaseWidget::Mode DatabaseWidget::currentMode() const
|
||||
{
|
||||
if (currentWidget() == Q_NULLPTR) {
|
||||
return DatabaseWidget::None;
|
||||
@@ -182,21 +187,56 @@ DatabaseWidget::Mode DatabaseWidget::currentMode()
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isInEditMode() const
|
||||
{
|
||||
return currentMode() == DatabaseWidget::EditMode;
|
||||
}
|
||||
|
||||
QList<int> DatabaseWidget::splitterSizes() const
|
||||
{
|
||||
return m_splitter->sizes();
|
||||
}
|
||||
|
||||
void DatabaseWidget::setSplitterSizes(const QList<int>& sizes)
|
||||
{
|
||||
m_splitter->setSizes(sizes);
|
||||
}
|
||||
|
||||
QList<int> DatabaseWidget::entryHeaderViewSizes() const
|
||||
{
|
||||
QList<int> sizes;
|
||||
|
||||
for (int i = 0; i < m_entryView->header()->count(); i++) {
|
||||
sizes.append(m_entryView->header()->sectionSize(i));
|
||||
}
|
||||
|
||||
return sizes;
|
||||
}
|
||||
|
||||
void DatabaseWidget::setEntryViewHeaderSizes(const QList<int>& sizes)
|
||||
{
|
||||
if (sizes.size() != m_entryView->header()->count()) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < sizes.size(); i++) {
|
||||
m_entryView->header()->resizeSection(i, sizes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::clearAllWidgets()
|
||||
{
|
||||
m_editEntryWidget->clear();
|
||||
m_historyEditEntryWidget->clear();
|
||||
m_editGroupWidget->clear();
|
||||
}
|
||||
|
||||
void DatabaseWidget::emitCurrentModeChanged()
|
||||
{
|
||||
Q_EMIT currentModeChanged(currentMode());
|
||||
}
|
||||
|
||||
GroupView* DatabaseWidget::groupView()
|
||||
{
|
||||
return m_groupView;
|
||||
}
|
||||
|
||||
EntryView* DatabaseWidget::entryView()
|
||||
{
|
||||
return m_entryView;
|
||||
}
|
||||
|
||||
Database* DatabaseWidget::database()
|
||||
{
|
||||
return m_db;
|
||||
@@ -213,9 +253,37 @@ void DatabaseWidget::createEntry()
|
||||
m_newEntry->setUuid(Uuid::random());
|
||||
m_newEntry->setUsername(m_db->metadata()->defaultUserName());
|
||||
m_newParent = m_groupView->currentGroup();
|
||||
setIconFromParent();
|
||||
switchToEntryEdit(m_newEntry, true);
|
||||
}
|
||||
|
||||
void DatabaseWidget::setIconFromParent()
|
||||
{
|
||||
if (!config()->get("UseGroupIconOnEntryCreation").toBool()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_newParent->iconNumber() == Group::DefaultIconNumber && m_newParent->iconUuid().isNull()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_newParent->iconUuid().isNull()) {
|
||||
m_newEntry->setIcon(m_newParent->iconNumber());
|
||||
}
|
||||
else {
|
||||
m_newEntry->setIcon(m_newParent->iconUuid());
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::replaceDatabase(Database* db)
|
||||
{
|
||||
Database* oldDb = m_db;
|
||||
m_db = db;
|
||||
m_groupView->changeDatabase(m_db);
|
||||
Q_EMIT databaseChanged(m_db);
|
||||
delete oldDb;
|
||||
}
|
||||
|
||||
void DatabaseWidget::cloneEntry()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
@@ -274,8 +342,7 @@ void DatabaseWidget::deleteEntries()
|
||||
if (selected.size() > 1) {
|
||||
QMessageBox::StandardButton result = MessageBox::question(
|
||||
this, tr("Move entries to recycle bin?"),
|
||||
tr("Do you really want to move %1 entries to the recycle bin?")
|
||||
.arg(selected.size()),
|
||||
tr("Do you really want to move %n entry(s) to the recycle bin?", 0, selected.size()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::No) {
|
||||
return;
|
||||
@@ -386,8 +453,19 @@ void DatabaseWidget::openUrl()
|
||||
|
||||
void DatabaseWidget::openUrlForEntry(Entry* entry)
|
||||
{
|
||||
if (!entry->url().isEmpty()) {
|
||||
QDesktopServices::openUrl(entry->url());
|
||||
QString urlString = entry->resolvePlaceholders(entry->url());
|
||||
if (urlString.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (urlString.startsWith("cmd://")) {
|
||||
if (urlString.length() > 6) {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
}
|
||||
}
|
||||
else {
|
||||
QUrl url = QUrl::fromUserInput(urlString);
|
||||
QDesktopServices::openUrl(url);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -407,7 +485,7 @@ void DatabaseWidget::createGroup()
|
||||
void DatabaseWidget::deleteGroup()
|
||||
{
|
||||
Group* currentGroup = m_groupView->currentGroup();
|
||||
if (!currentGroup || !canDeleteCurrentGoup()) {
|
||||
if (!currentGroup || !canDeleteCurrentGroup()) {
|
||||
Q_ASSERT(false);
|
||||
return;
|
||||
}
|
||||
@@ -533,8 +611,13 @@ void DatabaseWidget::updateMasterKey(bool accepted)
|
||||
{
|
||||
if (accepted) {
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
||||
bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey());
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if (!result) {
|
||||
MessageBox::critical(this, tr("Error"), tr("Unable to calculate master key"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (!m_db->hasKey()) {
|
||||
Q_EMIT closeRequest();
|
||||
@@ -547,11 +630,7 @@ void DatabaseWidget::updateMasterKey(bool accepted)
|
||||
void DatabaseWidget::openDatabase(bool accepted)
|
||||
{
|
||||
if (accepted) {
|
||||
Database* oldDb = m_db;
|
||||
m_db = static_cast<DatabaseOpenWidget*>(sender())->database();
|
||||
m_groupView->changeDatabase(m_db);
|
||||
Q_EMIT databaseChanged(m_db);
|
||||
delete oldDb;
|
||||
replaceDatabase(static_cast<DatabaseOpenWidget*>(sender())->database());
|
||||
setCurrentWidget(m_mainWidget);
|
||||
|
||||
// We won't need those anymore and KeePass1OpenWidget closes
|
||||
@@ -571,11 +650,24 @@ void DatabaseWidget::openDatabase(bool accepted)
|
||||
|
||||
void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
{
|
||||
// cancel button is disabled
|
||||
Q_ASSERT(accepted);
|
||||
Q_UNUSED(accepted);
|
||||
if (!accepted) {
|
||||
Q_EMIT closeRequest();
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentWidget(widgetBeforeLock);
|
||||
replaceDatabase(static_cast<DatabaseOpenWidget*>(sender())->database());
|
||||
|
||||
QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
Q_FOREACH (Group* group, groups) {
|
||||
if (group->uuid() == m_groupBeforeLock) {
|
||||
m_groupView->setCurrentGroup(group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_groupBeforeLock = Uuid();
|
||||
setCurrentWidget(m_mainWidget);
|
||||
m_unlockDatabaseWidget->clearForms();
|
||||
Q_EMIT unlockedDatabase();
|
||||
}
|
||||
|
||||
@@ -645,10 +737,16 @@ void DatabaseWidget::switchToImportKeepass1(const QString& fileName)
|
||||
setCurrentWidget(m_keepass1OpenWidget);
|
||||
}
|
||||
|
||||
void DatabaseWidget::toggleSearch()
|
||||
void DatabaseWidget::openSearch()
|
||||
{
|
||||
if (m_entryView->inEntryListMode()) {
|
||||
closeSearch();
|
||||
if (isInSearchMode()) {
|
||||
m_searchUi->searchEdit->selectAll();
|
||||
|
||||
if (!m_searchUi->searchEdit->hasFocus()) {
|
||||
m_searchUi->searchEdit->setFocus();
|
||||
// make sure the search action is checked again
|
||||
emitCurrentModeChanged();
|
||||
}
|
||||
}
|
||||
else {
|
||||
showSearch();
|
||||
@@ -658,11 +756,19 @@ void DatabaseWidget::toggleSearch()
|
||||
void DatabaseWidget::closeSearch()
|
||||
{
|
||||
Q_ASSERT(m_lastGroup);
|
||||
|
||||
Q_EMIT listModeAboutToActivate();
|
||||
|
||||
m_groupView->setCurrentGroup(m_lastGroup);
|
||||
m_searchTimer->stop();
|
||||
|
||||
Q_EMIT listModeActivated();
|
||||
}
|
||||
|
||||
void DatabaseWidget::showSearch()
|
||||
{
|
||||
Q_EMIT searchModeAboutToActivate();
|
||||
|
||||
m_searchUi->searchEdit->blockSignals(true);
|
||||
m_searchUi->searchEdit->clear();
|
||||
m_searchUi->searchEdit->blockSignals(false);
|
||||
@@ -696,6 +802,8 @@ void DatabaseWidget::showSearch()
|
||||
m_searchWidget->show();
|
||||
search();
|
||||
m_searchUi->searchEdit->setFocus();
|
||||
|
||||
Q_EMIT searchModeActivated();
|
||||
}
|
||||
|
||||
void DatabaseWidget::search()
|
||||
@@ -721,8 +829,8 @@ void DatabaseWidget::search()
|
||||
else {
|
||||
sensitivity = Qt::CaseInsensitive;
|
||||
}
|
||||
QList<Entry*> searchResult = searchGroup->search(m_searchUi->searchEdit->text(), sensitivity);
|
||||
|
||||
QList<Entry*> searchResult = EntrySearcher().search(m_searchUi->searchEdit->text(), searchGroup, sensitivity);
|
||||
|
||||
m_entryView->setEntryList(searchResult);
|
||||
}
|
||||
@@ -753,19 +861,19 @@ void DatabaseWidget::emitEntryContextMenuRequested(const QPoint& pos)
|
||||
Q_EMIT entryContextMenuRequested(m_entryView->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
bool DatabaseWidget::dbHasKey()
|
||||
bool DatabaseWidget::dbHasKey() const
|
||||
{
|
||||
return m_db->hasKey();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::canDeleteCurrentGoup()
|
||||
bool DatabaseWidget::canDeleteCurrentGroup() const
|
||||
{
|
||||
bool isRootGroup = m_db->rootGroup() == m_groupView->currentGroup();
|
||||
bool isRecycleBin = m_db->metadata()->recycleBin() == m_groupView->currentGroup();
|
||||
return !isRootGroup && !isRecycleBin;
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isInSearchMode()
|
||||
bool DatabaseWidget::isInSearchMode() const
|
||||
{
|
||||
return m_entryView->inEntryListMode();
|
||||
}
|
||||
@@ -782,12 +890,86 @@ void DatabaseWidget::lock()
|
||||
{
|
||||
Q_ASSERT(currentMode() != DatabaseWidget::LockedMode);
|
||||
|
||||
widgetBeforeLock = currentWidget();
|
||||
m_unlockDatabaseWidget->load(m_filename, m_db);
|
||||
m_groupBeforeLock = m_groupView->currentGroup()->uuid();
|
||||
clearAllWidgets();
|
||||
m_unlockDatabaseWidget->load(m_filename);
|
||||
setCurrentWidget(m_unlockDatabaseWidget);
|
||||
Database* newDb = new Database();
|
||||
newDb->metadata()->setName(m_db->metadata()->name());
|
||||
replaceDatabase(newDb);
|
||||
}
|
||||
|
||||
void DatabaseWidget::updateFilename(const QString& fileName)
|
||||
{
|
||||
m_filename = fileName;
|
||||
}
|
||||
|
||||
int DatabaseWidget::numberOfSelectedEntries() const
|
||||
{
|
||||
return m_entryView->numberOfSelectedEntries();
|
||||
}
|
||||
|
||||
QStringList DatabaseWidget::customEntryAttributes() const
|
||||
{
|
||||
Entry* entry = m_entryView->currentEntry();
|
||||
if (!entry) {
|
||||
return QStringList();
|
||||
}
|
||||
|
||||
return entry->attributes()->customKeys();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isGroupSelected() const
|
||||
{
|
||||
return m_groupView->currentGroup() != Q_NULLPTR;
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasTitle()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
if (!currentEntry) {
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->title().isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasUsername()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
if (!currentEntry) {
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->username().isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasPassword()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
if (!currentEntry) {
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->password().isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasUrl()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
if (!currentEntry) {
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->url().isEmpty();
|
||||
}
|
||||
|
||||
bool DatabaseWidget::currentEntryHasNotes()
|
||||
{
|
||||
Entry* currentEntry = m_entryView->currentEntry();
|
||||
if (!currentEntry) {
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
return !currentEntry->notes().isEmpty();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user