mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-12-04 15:39:34 +01:00
Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9279f73fa | ||
|
|
d8923123fa | ||
|
|
9d55369c57 | ||
|
|
217e95e425 | ||
|
|
d44b811b0d | ||
|
|
b61ecabed3 | ||
|
|
ebdb10e7f9 | ||
|
|
c2f3396753 | ||
|
|
1b8366f040 | ||
|
|
7df6d27900 | ||
|
|
e9e92d0892 | ||
|
|
86f2c9d350 | ||
|
|
a0ebbf997d | ||
|
|
9d5d3081dc | ||
|
|
5274826e5c | ||
|
|
6dcb83f913 | ||
|
|
daf0b72eed | ||
|
|
ac52f73af2 | ||
|
|
832465a2a3 | ||
|
|
3768145c9b | ||
|
|
337161be02 | ||
|
|
510904ebea | ||
|
|
a37b98d95d | ||
|
|
35788f8654 | ||
|
|
ec17199feb | ||
|
|
012d0ee885 | ||
|
|
9b211928a9 | ||
|
|
fed210dc38 | ||
|
|
b73549fd35 | ||
|
|
5bb6c4d9e4 | ||
|
|
7a344930ec | ||
|
|
ef53600d74 | ||
|
|
81cdcb4b62 | ||
|
|
415b114dac | ||
|
|
ef082c2e1f | ||
|
|
8a26cfad79 | ||
|
|
38d64a34a1 | ||
|
|
d1b403333c | ||
|
|
6c45fcbfc7 | ||
|
|
44c58a66d1 | ||
|
|
8fc11c0791 | ||
|
|
db75d94961 | ||
|
|
26ff528a85 | ||
|
|
f7e9f85668 | ||
|
|
75eb0c6951 | ||
|
|
e31638d3dd | ||
|
|
e12cd83b80 | ||
|
|
040b476359 | ||
|
|
d7633f40ba | ||
|
|
80fc8d4da9 | ||
|
|
9a92d20001 | ||
|
|
2b18089641 | ||
|
|
1da87d1d19 | ||
|
|
0c54276fe2 | ||
|
|
dda9a95163 | ||
|
|
34fa456106 | ||
|
|
9fe4504623 | ||
|
|
08d68300bd | ||
|
|
e326e2c6b3 | ||
|
|
e94dc226b5 | ||
|
|
bd2edea1c9 | ||
|
|
00cd0e1ae3 | ||
|
|
05aefc6489 | ||
|
|
5652018cde | ||
|
|
a63ba6bc4f | ||
|
|
c043be3aa4 | ||
|
|
b7180893c6 | ||
|
|
96ca7a8cbc | ||
|
|
c87c811719 | ||
|
|
a3fd3205a9 | ||
|
|
55084499ac | ||
|
|
3e6f76826b | ||
|
|
01e9d39b63 | ||
|
|
0116d4176a | ||
|
|
7ea306a61a | ||
|
|
cdaab550e9 | ||
|
|
aa6f617715 | ||
|
|
7e4592c1e7 | ||
|
|
b97024c8f6 | ||
|
|
0f44f6752e | ||
|
|
3c9054c36f | ||
|
|
61379d4e05 | ||
|
|
fd2b779bb8 | ||
|
|
202e8ac3a1 | ||
|
|
6796f20683 | ||
|
|
cdc9fddc44 | ||
|
|
6409661a9f | ||
|
|
4ed03c2db2 | ||
|
|
7b2a9f7ea8 | ||
|
|
0d222e138f | ||
|
|
a80c415fc0 | ||
|
|
3f1a75f40a | ||
|
|
ad4bf1ac84 | ||
|
|
11dec27dd1 | ||
|
|
66253e142b | ||
|
|
b22b67c7d9 | ||
|
|
d8662b709a | ||
|
|
e826309d59 | ||
|
|
1554722a83 | ||
|
|
16ed89c471 | ||
|
|
6ccae6cc37 | ||
|
|
292ed892c1 | ||
|
|
bb8b91a100 | ||
|
|
1310b34e9c | ||
|
|
86cdb64b1d | ||
|
|
0456815bd5 | ||
|
|
1c12cd6b9e | ||
|
|
3c687d29d8 | ||
|
|
bb70edfdb3 | ||
|
|
a3f189f452 | ||
|
|
70727895f7 | ||
|
|
cdbf58b2c1 | ||
|
|
fe9bcc254a | ||
|
|
b0270550c5 | ||
|
|
597faee248 | ||
|
|
42c0815cda | ||
|
|
a5f12db6ba | ||
|
|
62808f8342 | ||
|
|
c6e89f938d | ||
|
|
1edc116532 | ||
|
|
40aa92c5f7 | ||
|
|
4eb39dc5ff | ||
|
|
b5cf6c7161 | ||
|
|
198691182b | ||
|
|
72b81bf403 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,6 +1,7 @@
|
||||
src/version.h.cmake export-subst
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.github export-ignore
|
||||
.travis.yml export-ignore
|
||||
.tx export-ignore
|
||||
snapcraft.yaml export-ignore
|
||||
|
||||
89
.github/CONTRIBUTING.md
vendored
89
.github/CONTRIBUTING.md
vendored
@@ -1,31 +1,32 @@
|
||||
# Contributing to KeePassX Reboot
|
||||
# Contributing to KeePassXC
|
||||
|
||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||
|
||||
The following is a set of guidelines for contributing to KeePassX Reboot on GitHub.
|
||||
The following is a set of guidelines for contributing to KeePassXC on GitHub.
|
||||
These are just guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request.
|
||||
|
||||
#### Table Of Contents
|
||||
#### Table of contents
|
||||
|
||||
[What should I know before I get started?](#what-should-i-know-before-i-get-started)
|
||||
* [Open Source Contribution Policy](#open-source-contribution-policy)
|
||||
|
||||
[How Can I Contribute?](#how-can-i-contribute)
|
||||
* [Feature Requests](#feature-requests)
|
||||
* [Bug Reports](#bug-reports)
|
||||
* [Your First Code Contribution](#your-first-code-contribution)
|
||||
* [Pull Requests](#pull-requests)
|
||||
[How can I contribute?](#how-can-i-contribute)
|
||||
* [Feature requests](#feature-requests)
|
||||
* [Bug reports](#bug-reports)
|
||||
* [Discuss with the team](#discuss-with-the-team)
|
||||
* [Your first code contribution](#your-first-code-contribution)
|
||||
* [Pull requests](#pull-requests)
|
||||
* [Translations](#translations)
|
||||
|
||||
[Styleguides](#styleguides)
|
||||
* [Git Branch Strategy](#git_branch_strategy)
|
||||
* [Git Commit Messages](#git-commit-messages)
|
||||
* [Coding Styleguide](#coding-styleguide)
|
||||
* [Git branch strategy](#git-branch-strategy)
|
||||
* [Git commit messages](#git-commit-messages)
|
||||
* [Coding styleguide](#coding-styleguide)
|
||||
|
||||
|
||||
## What should I know before I get started?
|
||||
### Open Source Contribution Policy
|
||||
[Version 0.3, 2015–11–18](https://medium.com/@jmaynard/a-contribution-policy-for-open-source-that-works-bfc4600c9d83#.i9ntbhmad)
|
||||
**Source**: [Version 0.3, 2015–11–18](https://medium.com/@jmaynard/a-contribution-policy-for-open-source-that-works-bfc4600c9d83#.i9ntbhmad)
|
||||
|
||||
#### Policy
|
||||
|
||||
@@ -49,35 +50,35 @@ If we reject your contribution, it means only that we do not consider it suitabl
|
||||
* 0.3, 2011–11–19: Added “irrevocably” to “we can use” and changed “it” to “your contribution” in the “if rejected” section. Thanks to Patrick Maupin.
|
||||
|
||||
|
||||
## How Can I Contribute?
|
||||
### Feature Requests
|
||||
## How can I contribute?
|
||||
### 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 Reboot, please use the Issues section or our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum.
|
||||
We're always looking for suggestions to improve our application. If you have a suggestion to improve an existing feature, or would like to suggest a completely new feature for KeePassXC, please use the [issue tracker on GitHub][issues-section]. For more general discussion, try using our [Google Groups][google-groups] forum.
|
||||
|
||||
### Bug Reports
|
||||
### Bug reports
|
||||
|
||||
Our software isn't always perfect, but we strive to always improve our work. You may file bug reports in the Issues section.
|
||||
Our software isn't always perfect, but we strive to always improve our work. You may file bug reports in the issue tracker.
|
||||
|
||||
Before submitting a Bug Report, check if the problem has already been reported. Please refrain from opening a duplicate issue. If you want to highlight a deficiency on an existing issue, simply add a comment.
|
||||
Before submitting a bug report, check if the problem has already been reported. Please refrain from opening a duplicate issue. If you want to add further information to an existing issue, simply add a comment on that issue.
|
||||
|
||||
### Discuss with the Team
|
||||
### Discuss with the team
|
||||
|
||||
You can talk to the KeePassX Reboot Team about Bugs, new feature, Issue and PullRequests at our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum
|
||||
As with feature requests, you can talk to the KeePassXC team about bugs, new features, other issues and pull requests on the dedicated issue tracker, using the [Google Groups][google-groups] forum, or in the IRC channel on Freenode (`#keepassxc-dev` on `irc.freenode.net`, or use a [webchat link](https://webchat.freenode.net/?channels=%23keepassxc-dev)).
|
||||
|
||||
### Your First Code Contribution
|
||||
### Your first code contribution
|
||||
|
||||
Unsure where to begin contributing to KeePassX Reboot? You can start by looking through these `beginner` and `help-wanted` issues:
|
||||
Unsure where to begin contributing to KeePassXC? You can start by looking through these `beginner` and `help-wanted` issues:
|
||||
|
||||
* [Beginner issues][beginner] - issues which should only require a few lines of code, and a test or two.
|
||||
* [Help wanted issues][help-wanted] - issues which should be a bit more involved than `beginner` issues.
|
||||
* [Beginner issues][beginner] – issues which should only require a few lines of code, and a test or two.
|
||||
* ['Help wanted' issues][help-wanted] – issues which should be a bit more involved than `beginner` issues.
|
||||
|
||||
Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy for impact a given change will have.
|
||||
Both issue lists are sorted by total number of comments. While not perfect, looking at the number of comments on an issue can give a general idea of how much an impact a given change will have.
|
||||
|
||||
### Pull Requests
|
||||
### 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.
|
||||
|
||||
All pull requests must comply with the above requirements and with the [Styleguides](#styleguides).
|
||||
All pull requests must comply with the above requirements and with the [styleguides](#styleguides).
|
||||
|
||||
### Translations
|
||||
|
||||
@@ -86,19 +87,20 @@ Please join an existing language team or request a new one if there is none.
|
||||
|
||||
## Styleguides
|
||||
|
||||
### Git Branch Strategy
|
||||
### Git branch strategy
|
||||
|
||||
The Branch Strategy is based on [git-flow-lite](http://nvie.com/posts/a-successful-git-branching-model/).
|
||||
|
||||
* **master** -> always points to the last release published
|
||||
* **develop** -> points to the next planned release, tested and reviewed code
|
||||
* **feature/**[name] -> points to brand new feature in codebase, candidate for merge into develop (subject to rebase)
|
||||
* **master** – points to the latest public release
|
||||
* **develop** – points to the development of the next release, contains tested and reviewed code
|
||||
* **feature/**[name] – points to a branch with a new feature, one which is candidate for merge into develop (subject to rebase)
|
||||
* **hotfix/**[id]-[description] – points to a branch with a fix for a particular issue ID
|
||||
|
||||
|
||||
### Git Commit Messages
|
||||
### Git commit messages
|
||||
|
||||
* Use the present tense ("Add feature" not "Added feature")
|
||||
* Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
||||
* Use the imperative mood ("Move cursor to…" not "Moves cursor to…")
|
||||
* Limit the first line to 72 characters or less
|
||||
* Reference issues and pull requests liberally
|
||||
* When only changing documentation, include `[ci skip]` in the commit description
|
||||
@@ -114,21 +116,21 @@ The Branch Strategy is based on [git-flow-lite](http://nvie.com/posts/a-successf
|
||||
* :lock: `:lock:` when dealing with security
|
||||
|
||||
|
||||
### Coding Styleguide
|
||||
### Coding styleguide
|
||||
|
||||
This project follows the [Qt Coding Style](https://wiki.qt.io/Qt_Coding_Style). All submissions are expected to follow this style.
|
||||
|
||||
In particular Code must follow the following specific rules:
|
||||
In particular, code must stick to the following rules:
|
||||
|
||||
#### Naming Convention
|
||||
#### Naming convention
|
||||
`lowerCamelCase`
|
||||
|
||||
For names made of only one word, the fist letter is lowercase.
|
||||
For names made of multiple concatenated words, the first letter is lowercase and each subsequent concatenated word is capitalized.
|
||||
For names made of only one word, the first letter should be lowercase.
|
||||
For names made of multiple concatenated words, the first letter of the whole is lowercase, and the first letter of each subsequent word is capitalized.
|
||||
|
||||
#### Indention
|
||||
For C++ files (.cpp .h): 4 spaces
|
||||
For Qt-UI files (.ui): 2 spaces
|
||||
For **C++ files** (*.cpp .h*): 4 spaces
|
||||
For **Qt-UI files** (*.ui*): 2 spaces
|
||||
|
||||
#### Pointers
|
||||
```c
|
||||
@@ -165,9 +167,8 @@ Use prefix: `m_*`
|
||||
|
||||
Example: `m_variable`
|
||||
|
||||
#### GUI Widget names
|
||||
Widget names must be related to the desired program behaviour.
|
||||
Preferably end the name with the Widget Classname
|
||||
#### GUI widget names
|
||||
Widget names must be related to the desired program behavior, and preferably end with the widget's classname.
|
||||
|
||||
Example: `<widget class="QCheckBox" name="rememberCheckBox">`
|
||||
|
||||
@@ -175,3 +176,5 @@ Example: `<widget class="QCheckBox" name="rememberCheckBox">`
|
||||
|
||||
[beginner]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3Abeginner+label%3A%22help+wanted%22+sort%3Acomments-desc
|
||||
[help-wanted]:https://github.com/keepassxreboot/keepassx/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22+sort%3Acomments-desc
|
||||
[issues-section]:https://github.com/keepassxreboot/keepassxc/issues
|
||||
[google-groups]:https://groups.google.com/forum/#!forum/keepassx-reboot
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,6 @@
|
||||
CMakeLists.txt.*
|
||||
build*/
|
||||
release*/
|
||||
.idea/
|
||||
*.iml
|
||||
*.kdev4
|
||||
|
||||
@@ -39,26 +39,57 @@ mkdir -p $APP.AppDir
|
||||
wget -q https://github.com/probonopd/AppImages/raw/master/functions.sh -O ./functions.sh
|
||||
. ./functions.sh
|
||||
|
||||
LIB_DIR=./usr/lib
|
||||
if [ -d ./usr/lib/x86_64-linux-gnu ]; then
|
||||
LIB_DIR=./usr/lib/x86_64-linux-gnu
|
||||
fi
|
||||
|
||||
cd $APP.AppDir
|
||||
cp -a ../../bin-release/* .
|
||||
mv ./usr/local/* ./usr
|
||||
rmdir ./usr/local
|
||||
patch_strings_in_file /usr/local ./
|
||||
patch_strings_in_file /usr ./
|
||||
cp -a ./usr/local/* ./usr
|
||||
rm -R ./usr/local
|
||||
rmdir ./opt 2> /dev/null
|
||||
|
||||
# bundle Qt platform plugins and themes
|
||||
QXCB_PLUGIN="$(find /usr/lib -name 'libqxcb.so' 2> /dev/null)"
|
||||
if [ "$QXCB_PLUGIN" == "" ]; then
|
||||
QXCB_PLUGIN="$(find /opt/qt*/plugins -name 'libqxcb.so' 2> /dev/null)"
|
||||
fi
|
||||
QT_PLUGIN_PATH="$(dirname $(dirname $QXCB_PLUGIN))"
|
||||
mkdir -p ".${QT_PLUGIN_PATH}/platforms"
|
||||
cp "$QXCB_PLUGIN" ".${QT_PLUGIN_PATH}/platforms/"
|
||||
|
||||
get_apprun
|
||||
copy_deps
|
||||
delete_blacklisted
|
||||
|
||||
# remove dbus and systemd libs as they are not blacklisted
|
||||
find . -name libdbus-1.so.3 -exec rm {} \;
|
||||
find . -name libsystemd.so.0 -exec rm {} \;
|
||||
|
||||
get_desktop
|
||||
get_icon
|
||||
cat << EOF > ./usr/bin/keepassxc_env
|
||||
#!/usr/bin/env bash
|
||||
#export QT_QPA_PLATFORMTHEME=gtk2
|
||||
export LD_LIBRARY_PATH="../opt/qt58/lib:\${LD_LIBRARY_PATH}"
|
||||
export QT_PLUGIN_PATH="..${QT_PLUGIN_PATH}"
|
||||
|
||||
# unset XDG_DATA_DIRS to make tray icon work in Ubuntu Unity
|
||||
# see https://github.com/probonopd/AppImageKit/issues/351
|
||||
unset XDG_DATA_DIRS
|
||||
|
||||
exec keepassxc "\$@"
|
||||
EOF
|
||||
chmod +x ./usr/bin/keepassxc_env
|
||||
sed -i 's/Exec=keepassxc/Exec=keepassxc_env/' keepassxc.desktop
|
||||
get_desktopintegration $LOWERAPP
|
||||
|
||||
GLIBC_NEEDED=$(glibc_needed)
|
||||
|
||||
cd ..
|
||||
|
||||
generate_appimage
|
||||
generate_type2_appimage
|
||||
|
||||
mv ../out/*.AppImage ..
|
||||
rmdir ../out > /dev/null 2>&1
|
||||
|
||||
25
CHANGELOG
25
CHANGELOG
@@ -1,3 +1,28 @@
|
||||
2.1.2 (2017-02-17)
|
||||
=========================
|
||||
|
||||
- Ask for save location when creating a new database [#302]
|
||||
- Remove Libmicrohttpd dependency to clean up the code and ensure better OS X compatibility [#317, #265]
|
||||
- Prevent Qt from degrading Wifi network performance on certain platforms [#318]
|
||||
- Visually refine user interface on OS X and other platforms [#299]
|
||||
- Remove unusable tray icon setting on OS X [#293]
|
||||
- Fix compositing glitches on Ubuntu and prevent flashing when minimizing to the tray at startup [#307]
|
||||
- Fix AppImage tray icon on Ubuntu [#277, #273]
|
||||
- Fix global menu disappearing after restoring KeePassXC from the tray on Ubuntu [#276]
|
||||
- Fix result order in entry search [#320]
|
||||
- Enable HiDPI scaling on supported platforms [#315]
|
||||
- Remove empty directories from installation target [#282]
|
||||
|
||||
2.1.1 (2017-02-06)
|
||||
=========================
|
||||
|
||||
- Enabled HTTP plugin build; plugin is disabled by default and limited to localhost [#147]
|
||||
- Escape HTML in dialog boxes [#247]
|
||||
- Corrected crashes in favicon download and password generator [#233, #226]
|
||||
- Increase font size of password meter [#228]
|
||||
- Fixed compatibility with Qt 5.8 [#211]
|
||||
- Use consistent button heights in password generator [#229]
|
||||
|
||||
2.1.0 (2017-01-22)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -38,8 +38,8 @@ option(WITH_XC_AUTOTYPE "Include Autotype." OFF)
|
||||
option(WITH_XC_HTTP "Include KeePassHTTP." OFF)
|
||||
option(WITH_XC_YUBIKEY "Include Yubikey support." OFF)
|
||||
|
||||
set(KEEPASSXC_VERSION "2.1.0")
|
||||
set(KEEPASSXC_VERSION_NUM "2.1.0")
|
||||
set(KEEPASSXC_VERSION "2.1.2")
|
||||
set(KEEPASSXC_VERSION_NUM "2.1.2")
|
||||
|
||||
if("${CMAKE_C_COMPILER}" MATCHES "clang$" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang")
|
||||
set(CMAKE_COMPILER_IS_CLANG 1)
|
||||
@@ -166,15 +166,18 @@ find_package(Qt5Widgets 5.2 REQUIRED)
|
||||
find_package(Qt5Test 5.2 REQUIRED)
|
||||
find_package(Qt5LinguistTools 5.2 REQUIRED)
|
||||
find_package(Qt5Network 5.2 REQUIRED)
|
||||
if (UNIX AND NOT APPLE)
|
||||
find_package(Qt5DBus 5.2 REQUIRED)
|
||||
endif()
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
# 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 1.6.0 REQUIRED)
|
||||
find_package(LibGPGError REQUIRED)
|
||||
|
||||
find_package(LibMicroHTTPD REQUIRED)
|
||||
find_package(Gcrypt 1.6.0 REQUIRED)
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
|
||||
@@ -213,7 +216,7 @@ if(UNIX)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${MHD_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
|
||||
include_directories(SYSTEM ${GCRYPT_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR})
|
||||
|
||||
include(FeatureSummary)
|
||||
|
||||
|
||||
32
Dockerfile
32
Dockerfile
@@ -14,21 +14,41 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
FROM ubuntu:16.04
|
||||
FROM ubuntu:14.04
|
||||
|
||||
RUN set -x && apt-get update
|
||||
RUN set -x \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes software-properties-common
|
||||
|
||||
RUN set -x \
|
||||
&& add-apt-repository --yes ppa:beineri/opt-qt58-trusty
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get update \
|
||||
&& apt-get install --yes \
|
||||
g++ \
|
||||
cmake \
|
||||
libgcrypt20-dev \
|
||||
qtbase5-dev \
|
||||
qttools5-dev-tools \
|
||||
qt58base \
|
||||
qt58tools \
|
||||
qt58x11extras \
|
||||
libmicrohttpd-dev \
|
||||
libqt5x11extras5-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
zlib1g-dev
|
||||
zlib1g-dev \
|
||||
wget \
|
||||
file \
|
||||
fuse \
|
||||
python
|
||||
|
||||
RUN set -x \
|
||||
&& apt-get install --yes mesa-common-dev
|
||||
|
||||
VOLUME /keepassxc/src
|
||||
VOLUME /keepassxc/out
|
||||
WORKDIR /keepassxc
|
||||
|
||||
ENV CMAKE_PREFIX_PATH=/opt/qt58/lib/cmake
|
||||
ENV LD_LIBRARY_PATH=/opt/qt58/lib
|
||||
RUN set -x \
|
||||
&& echo /opt/qt58/lib > /etc/ld.so.conf.d/qt58.conf
|
||||
|
||||
64
README.md
64
README.md
@@ -3,57 +3,63 @@
|
||||
[](https://travis-ci.org/keepassxreboot/keepassxc) [](https://coveralls.io/github/keepassxreboot/keepassxc)
|
||||
|
||||
## About
|
||||
|
||||
Fork of [KeePassX](https://www.keepassx.org/) that [aims to incorporate stalled Pull Requests, features, and bug fixes that are not being incorporated into the main KeePassX baseline](https://github.com/keepassxreboot/keepassx/issues/43).
|
||||
KeePassXC is a fork of [KeePassX](https://www.keepassx.org/) that [aims to incorporate stalled pull requests, features, and bug fixes that have never made it into the main KeePassX repository](https://github.com/keepassxreboot/keepassx/issues/43).
|
||||
|
||||
|
||||
#### Additional Reboot Features
|
||||
- keepasshttp support for use with [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) for Mozilla Firefox and [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) for Google Chrome.
|
||||
## Additional features compared to KeePassX
|
||||
- Autotype on all three major platforms (Linux, Windows, OS X)
|
||||
- Stand-alone password generator
|
||||
- Password strength meter
|
||||
- Use website's favicons as entry icons
|
||||
- Merging of databases
|
||||
- Automatic reload when the database changed on disk
|
||||
- KeePassHTTP support for use with [PassIFox](https://addons.mozilla.org/en-us/firefox/addon/passifox/) in Mozilla Firefox and [chromeIPass](https://chrome.google.com/webstore/detail/chromeipass/ompiailgknfdndiefoaoiligalphfdae) in Google Chrome or Chromium.
|
||||
|
||||
KeePassHttp implementation has been forked from jdachtera's repository, which in turn was based on code from code with Francois Ferrand's [keepassx-http](https://gitorious.org/keepassx/keepassx-http/source/master) repository.
|
||||
|
||||
This is a rebuild from [denk-mal's keepasshttp](https://github.com/denk-mal/keepassx.git) that brings it forward to Qt5 and KeePassX v2.x.
|
||||
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
|
||||
|
||||
### Note about KeePassHTTP
|
||||
KeePassHTTP is not a highly secure protocol and has certain flaw which allow an attacker to decrypt your passwords when they manage to intercept communication between a KeePassHTTP server and PassIFox/chromeIPass over a network connection (see [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)). KeePassXC therefore strictly limits communication between itself and the browser plugin to your local computer. As long as your computer is not compromised, your passwords are fairly safe that way, but still use it at your own risk!
|
||||
|
||||
### Installation
|
||||
Pre-compiled binaries can be found on the [downloads page](https://keepassxc.org/download). Additionally, individual Linux distributions may ship their own versions, so please check out your distribution's package list to see if KeePassXC is available.
|
||||
|
||||
Right now KeePassXC does not have a precompiled executable or an installation package.<br/>
|
||||
So you must install it from its source code.
|
||||
### Building KeePassXC yourself
|
||||
|
||||
**More detailed instructions are available in the INSTALL file or at the [Wiki page](https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source).**
|
||||
*More detailed instructions are available in the INSTALL file or on the [Wiki page](https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source).*
|
||||
|
||||
First you must download the KeePassXC source code as ZIP file or with Git.
|
||||
First, you must download the KeePassXC [source tarball](https://keepassxc.org/download#source) or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
|
||||
|
||||
Generally you can build and install KeePassXC with the following commands from a Terminal in the KeePassXC folder
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF ..
|
||||
make
|
||||
sudo make install
|
||||
```
|
||||
|
||||
|
||||
### Clone Repository
|
||||
|
||||
Clone the repository to a suitable location where you can extend and build this project.
|
||||
To clone the project from Git, `cd` to a suitable location and run
|
||||
|
||||
```bash
|
||||
git clone https://github.com/keepassxreboot/keepassxc.git
|
||||
```
|
||||
|
||||
**Note:** This will clone the entire contents of the repository at the HEAD revision.
|
||||
This will clone the entire contents of the repository and check out the current `develop` branch.
|
||||
|
||||
To update the project from within the project's folder you can run the following command:
|
||||
To update the project from within the project's folder, you can run the following command:
|
||||
|
||||
```bash
|
||||
git pull
|
||||
```
|
||||
|
||||
Once you have downloaded the source code, you can `cd` into the source code directory and build and install KeePassXC with
|
||||
|
||||
```
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DWITH_TESTS=OFF ..
|
||||
make -j8
|
||||
sudo make install
|
||||
```
|
||||
|
||||
To enable autotype, add `-DWITH_XC_AUTOTYPE=ON` to the `cmake` command. KeePassHTTP support is compiled in by adding `-DWITH_XC_HTTP=ON`. If these options are not specified, KeePassXC will be built without these plugins.
|
||||
|
||||
|
||||
### Contributing
|
||||
|
||||
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 Reboot, please use the [Issues](https://github.com/keepassxreboot/keepassxc/issues) section or our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum.
|
||||
We are always looking for suggestions how to improve our application. If you find any bugs or have an idea for a new feature, please let us know by opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues) on GitHub or write to our [Google Groups](https://groups.google.com/forum/#!forum/keepassx-reboot) forum.
|
||||
|
||||
Please review the [CONTRIBUTING](.github/CONTRIBUTING.md) document for further information.
|
||||
You can of course also directly contribute your own code. We are happy to accept your pull requests.
|
||||
|
||||
Please read the [CONTRIBUTING](.github/CONTRIBUTING.md) document for further information.
|
||||
|
||||
9
cmake/FindLibGPGError.cmake
Normal file
9
cmake/FindLibGPGError.cmake
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
find_path(GPGERROR_INCLUDE_DIR gpg-error.h)
|
||||
|
||||
find_library(GPGERROR_LIBRARIES gpg-error)
|
||||
|
||||
mark_as_advanced(GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LibGPGError DEFAULT_MSG GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR)
|
||||
350
make_release.sh
350
make_release.sh
@@ -1,350 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# KeePassXC Release Preparation Helper
|
||||
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
echo -e "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper"
|
||||
echo -e "Copyright (C) 2017 KeePassXC Team <https://keepassxc.org/>\n"
|
||||
|
||||
|
||||
# default values
|
||||
RELEASE_NAME=""
|
||||
APP_NAME="KeePassXC"
|
||||
APP_NAME_LOWER="keepassxc"
|
||||
SRC_DIR="."
|
||||
GPG_KEY="CFB4C2166397D0D2"
|
||||
GPG_GIT_KEY=""
|
||||
OUTPUT_DIR="release"
|
||||
BRANCH=""
|
||||
RELEASE_BRANCH="master"
|
||||
TAG_NAME=""
|
||||
BUILD_SOURCES=false
|
||||
DOCKER_IMAGE=""
|
||||
DOCKER_CONTAINER_NAME="${APP_NAME_LOWER}-build-container"
|
||||
CMAKE_OPTIONS=""
|
||||
COMPILER="g++"
|
||||
MAKE_OPTIONS="-j8"
|
||||
BUILD_PLUGINS="autotype"
|
||||
INSTALL_PREFIX="/usr/local"
|
||||
|
||||
ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)"
|
||||
ORIG_CWD="$(pwd)"
|
||||
|
||||
|
||||
# helper functions
|
||||
printUsage() {
|
||||
echo -e "\e[1mUsage:\e[0m $(basename $0) [options]"
|
||||
cat << EOF
|
||||
|
||||
Options:
|
||||
-v, --version Release version number or name (required)
|
||||
-a, --app-name Application name (default: '${APP_NAME}')
|
||||
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
||||
-k, --gpg-key GPG key used to sign the release tarball
|
||||
(default: '${GPG_KEY}')
|
||||
-g, --gpg-git-key GPG key used to sign the merge commit and release tag,
|
||||
leave empty to let Git choose your default key
|
||||
(default: '${GPG_GIT_KEY}')
|
||||
-o, --output-dir Output directory where to build the release
|
||||
(default: '${OUTPUT_DIR}')
|
||||
--develop-branch Development branch to merge from (default: 'release/VERSION')
|
||||
--release-branch Target release branch to merge to (default: '${RELEASE_BRANCH}')
|
||||
-t, --tag-name Override release tag name (defaults to version number)
|
||||
-b, --build Build sources after exporting release
|
||||
-d, --docker-image Use the specified Docker image to compile the application.
|
||||
The image must have all required build dependencies installed.
|
||||
This option has no effect if --build is not set.
|
||||
--container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}')
|
||||
The container must not exist already
|
||||
-c, --cmake-options Additional CMake options for compiling the sources
|
||||
--compiler Compiler to use (default: '${COMPILER}')
|
||||
-m, --make-options Make options for compiling sources (default: '${MAKE_OPTIONS}')
|
||||
-i, --install-prefix Install prefix (default: '${INSTALL_PREFIX}')
|
||||
-p, --plugins Space-separated list of plugins to build
|
||||
(default: ${BUILD_PLUGINS})
|
||||
-h, --help Show this help
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
logInfo() {
|
||||
echo -e "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1"
|
||||
}
|
||||
|
||||
logError() {
|
||||
echo -e "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1" >&2
|
||||
}
|
||||
|
||||
exitError() {
|
||||
logError "$1"
|
||||
if [ "" != "$ORIG_BRANCH" ]; then
|
||||
git checkout "$ORIG_BRANCH" > /dev/null 2>&1
|
||||
fi
|
||||
cd "$ORIG_CWD"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
||||
# parse command line options
|
||||
while [ $# -ge 1 ]; do
|
||||
arg="$1"
|
||||
|
||||
case "$arg" in
|
||||
-a|--app-name)
|
||||
APP_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-s|--source-dir)
|
||||
SRC_DIR"$2"
|
||||
shift ;;
|
||||
|
||||
-v|--version)
|
||||
RELEASE_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-k|--gpg-key)
|
||||
GPG_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-g|--gpg-git-key)
|
||||
GPG_GIT_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-o|--output-dir)
|
||||
OUTPUT_DIR="$2"
|
||||
shift ;;
|
||||
|
||||
--develop-branch)
|
||||
BRANCH="$2"
|
||||
shift ;;
|
||||
|
||||
--release-branch)
|
||||
RELEASE_BRANCH="$2"
|
||||
shift ;;
|
||||
|
||||
-t|--tag-name)
|
||||
TAG_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-b|--build)
|
||||
BUILD_SOURCES=true ;;
|
||||
|
||||
-d|--docker-image)
|
||||
DOCKER_IMAGE="$2"
|
||||
shift ;;
|
||||
|
||||
--container-name)
|
||||
DOCKER_CONTAINER_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-c|--cmake-options)
|
||||
CMAKE_OPTIONS="$2"
|
||||
shift ;;
|
||||
|
||||
-m|--make-options)
|
||||
MAKE_OPTIONS="$2"
|
||||
shift ;;
|
||||
|
||||
--compiler)
|
||||
COMPILER="$2"
|
||||
shift ;;
|
||||
|
||||
-p|--plugins)
|
||||
BUILD_PLUGINS="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
|
||||
if [ "" == "$RELEASE_NAME" ]; then
|
||||
logError "Missing arguments, --version is required!\n"
|
||||
printUsage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "" == "$TAG_NAME" ]; then
|
||||
TAG_NAME="$RELEASE_NAME"
|
||||
fi
|
||||
if [ "" == "$BRANCH" ]; then
|
||||
BRANCH="release/${RELEASE_NAME}"
|
||||
fi
|
||||
APP_NAME_LOWER="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
|
||||
APP_NAME_UPPER="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
|
||||
|
||||
SRC_DIR="$(realpath "$SRC_DIR")"
|
||||
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
||||
if [ ! -d "$SRC_DIR" ]; then
|
||||
exitError "Source directory '${SRC_DIR}' does not exist!"
|
||||
fi
|
||||
|
||||
logInfo "Changing to source directory..."
|
||||
cd "${SRC_DIR}"
|
||||
|
||||
logInfo "Performing basic checks..."
|
||||
|
||||
if [ -e "$OUTPUT_DIR" ]; then
|
||||
exitError "Output directory '$OUTPUT_DIR' already exists. Please choose a different location!"
|
||||
fi
|
||||
|
||||
if [ ! -d .git ] || [ ! -f CHANGELOG ]; then
|
||||
exitError "Source directory is not a valid Git repository!"
|
||||
fi
|
||||
|
||||
git tag | grep -q "$RELEASE_NAME"
|
||||
if [ $? -eq 0 ]; then
|
||||
exitError "Release '$RELEASE_NAME' already exists!"
|
||||
fi
|
||||
|
||||
git diff-index --quiet HEAD --
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Current working tree is not clean! Please commit or unstage any changes."
|
||||
fi
|
||||
|
||||
git checkout "$BRANCH" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Source branch '$BRANCH' does not exist!"
|
||||
fi
|
||||
|
||||
grep -q "${APP_NAME_UPPER}_VERSION \"${RELEASE_NAME}\"" CMakeLists.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "${APP_NAME_UPPER}_VERSION version not updated to '${RELEASE_NAME}' in CMakeLists.txt!"
|
||||
fi
|
||||
|
||||
grep -q "${APP_NAME_UPPER}_VERSION_NUM \"${RELEASE_NAME}\"" CMakeLists.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "${APP_NAME_UPPER}_VERSION_NUM version not updated to '${RELEASE_NAME}' in CMakeLists.txt!"
|
||||
fi
|
||||
|
||||
if [ ! -f CHANGELOG ]; then
|
||||
exitError "No CHANGELOG file found!"
|
||||
fi
|
||||
|
||||
grep -qPzo "${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n=+\n" CHANGELOG
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "CHANGELOG does not contain any information about the '${RELEASE_NAME}' release!"
|
||||
fi
|
||||
|
||||
git checkout "$RELEASE_BRANCH" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Release branch '$RELEASE_BRANCH' does not exist!"
|
||||
fi
|
||||
|
||||
logInfo "All checks pass, getting our hands dirty now!"
|
||||
|
||||
logInfo "Merging '${BRANCH}' into '${RELEASE_BRANCH}'..."
|
||||
|
||||
CHANGELOG=$(grep -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n)=+\n\n(?:.|\n)+?\n(?=\n)" \
|
||||
CHANGELOG | grep -Pzo '(?<=\n\n)(.|\n)+' | tr -d \\0)
|
||||
COMMIT_MSG="Release ${RELEASE_NAME}"
|
||||
|
||||
git merge "$BRANCH" --no-ff -m "$COMMIT_MSG" -m "${CHANGELOG}" "$BRANCH" -S"$GPG_GIT_KEY"
|
||||
|
||||
logInfo "Creating tag '${RELEASE_NAME}'..."
|
||||
if [ "" == "$GPG_GIT_KEY" ]; then
|
||||
git tag -a "$RELEASE_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s
|
||||
else
|
||||
git tag -a "$RELEASE_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s -u "$GPG_GIT_KEY"
|
||||
fi
|
||||
|
||||
logInfo "Merge done, creating target directory..."
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Failed to create output directory!"
|
||||
fi
|
||||
|
||||
logInfo "Creating source tarball..."
|
||||
TARBALL_NAME="${APP_NAME_LOWER}-${RELEASE_NAME}-src.tar.bz2"
|
||||
git archive --format=tar "$RELEASE_BRANCH" --prefix="${APP_NAME_LOWER}-${RELEASE_NAME}/" \
|
||||
| bzip2 -9 > "${OUTPUT_DIR}/${TARBALL_NAME}"
|
||||
|
||||
|
||||
if $BUILD_SOURCES; then
|
||||
logInfo "Creating build directory..."
|
||||
mkdir -p "${OUTPUT_DIR}/build-release"
|
||||
mkdir -p "${OUTPUT_DIR}/bin-release"
|
||||
cd "${OUTPUT_DIR}/build-release"
|
||||
|
||||
logInfo "Configuring sources..."
|
||||
for p in $BUILD_PLUGINS; do
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
|
||||
done
|
||||
|
||||
if [ "$COMPILER" == "g++" ]; then
|
||||
export CC=gcc
|
||||
elif [ "$COMPILER" == "clang++" ]; then
|
||||
export CC=clang
|
||||
fi
|
||||
export CXX="$COMPILER"
|
||||
|
||||
if [ "" == "$DOCKER_IMAGE" ]; then
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling sources..."
|
||||
make $MAKE_OPTIONS
|
||||
|
||||
logInfo "Installing to bin dir..."
|
||||
make DESTDIR="${OUTPUT_DIR}/bin-release" install/strip
|
||||
else
|
||||
logInfo "Launching Docker container to compile sources..."
|
||||
|
||||
docker run --name "$DOCKER_CONTAINER_NAME" --rm \
|
||||
-e "CC=${CC}" -e "CXX=${CXX}" \
|
||||
-v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \
|
||||
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
|
||||
"$DOCKER_IMAGE" \
|
||||
bash -c "cd /keepassxc/out/build-release && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX=\"${INSTALL_PREFIX}\" /keepassxc/src && \
|
||||
make $MAKE_OPTIONS && make DESTDIR=/keepassxc/out/bin-release install/strip"
|
||||
|
||||
logInfo "Build finished, Docker container terminated."
|
||||
fi
|
||||
|
||||
logInfo "Creating AppImage..."
|
||||
${SRC_DIR}/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME"
|
||||
|
||||
cd ..
|
||||
logInfo "Signing source tarball..."
|
||||
gpg --output "${TARBALL_NAME}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$TARBALL_NAME"
|
||||
|
||||
logInfo "Signing AppImage..."
|
||||
APPIMAGE_NAME="${APP_NAME}-${RELEASE_NAME}-x86_64.AppImage"
|
||||
gpg --output "${APPIMAGE_NAME}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$APPIMAGE_NAME"
|
||||
|
||||
logInfo "Creating digests..."
|
||||
sha256sum "$TARBALL_NAME" > "${TARBALL_NAME}.DIGEST"
|
||||
sha256sum "$APPIMAGE_NAME" > "${APPIMAGE_NAME}.DIGEST"
|
||||
fi
|
||||
|
||||
logInfo "Leaving source directory..."
|
||||
cd "$ORIG_CWD"
|
||||
git checkout "$ORIG_BRANCH" > /dev/null 2>&1
|
||||
|
||||
logInfo "All done!"
|
||||
logInfo "Please merge the release branch back into the develop branch now and then push your changes."
|
||||
logInfo "Don't forget to also push the tags using \e[1mgit push --tags\e[0m."
|
||||
682
release-tool
Executable file
682
release-tool
Executable file
@@ -0,0 +1,682 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# KeePassXC Release Preparation Helper
|
||||
# Copyright (C) 2017 KeePassXC team <https://keepassxc.org/>
|
||||
#
|
||||
# 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/>.
|
||||
|
||||
echo -e "\e[1m\e[32mKeePassXC\e[0m Release Preparation Helper"
|
||||
echo -e "Copyright (C) 2017 KeePassXC Team <https://keepassxc.org/>\n"
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# global default values
|
||||
# -----------------------------------------------------------------------
|
||||
RELEASE_NAME=""
|
||||
APP_NAME="KeePassXC"
|
||||
SRC_DIR="."
|
||||
GPG_KEY="CFB4C2166397D0D2"
|
||||
GPG_GIT_KEY=""
|
||||
OUTPUT_DIR="release"
|
||||
SOURCE_BRANCH=""
|
||||
TARGET_BRANCH="master"
|
||||
TAG_NAME=""
|
||||
DOCKER_IMAGE=""
|
||||
DOCKER_CONTAINER_NAME="keepassxc-build-container"
|
||||
CMAKE_OPTIONS=""
|
||||
COMPILER="g++"
|
||||
MAKE_OPTIONS="-j8"
|
||||
BUILD_PLUGINS="autotype"
|
||||
INSTALL_PREFIX="/usr/local"
|
||||
BUILD_SOURCE_TARBALL=true
|
||||
ORIG_BRANCH=""
|
||||
ORIG_CWD="$(pwd)"
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# helper functions
|
||||
# -----------------------------------------------------------------------
|
||||
printUsage() {
|
||||
local cmd
|
||||
if [ "" == "$1" ] || [ "help" == "$1" ]; then
|
||||
cmd="COMMAND"
|
||||
elif [ "merge" == "$1" ] || [ "build" == "$1" ] || [ "sign" == "$1" ]; then
|
||||
cmd="$1"
|
||||
else
|
||||
logError "Unknown command: '$1'\n"
|
||||
cmd="COMMAND"
|
||||
fi
|
||||
|
||||
echo -e "\e[1mUsage:\e[0m $(basename $0) $cmd [options]"
|
||||
|
||||
if [ "COMMAND" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Commands:
|
||||
merge Merge release branch into main branch and create release tags
|
||||
build Build and package binary release from sources
|
||||
sign Sign previously compiled release packages
|
||||
help Show help for the given command
|
||||
EOF
|
||||
elif [ "merge" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Merge release branch into main branch and create release tags
|
||||
|
||||
Options:
|
||||
-v, --version Release version number or name (required)
|
||||
-a, --app-name Application name (default: '${APP_NAME}')
|
||||
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
||||
-g, --gpg-key GPG key used to sign the merge commit and release tag,
|
||||
leave empty to let Git choose your default key
|
||||
(default: '${GPG_GIT_KEY}')
|
||||
-r, --release-branch Source release branch to merge from (default: 'release/VERSION')
|
||||
--target-branch Target branch to merge to (default: '${TARGET_BRANCH}')
|
||||
-t, --tag-name Override release tag name (defaults to version number)
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
elif [ "build" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Build and package binary release from sources
|
||||
|
||||
Options:
|
||||
-v, --version Release version number or name (required)
|
||||
-a, --app-name Application name (default: '${APP_NAME}')
|
||||
-s, --source-dir Source directory (default: '${SRC_DIR}')
|
||||
-o, --output-dir Output directory where to build the release
|
||||
(default: '${OUTPUT_DIR}')
|
||||
-t, --tag-name Release tag to check out (defaults to version number)
|
||||
-b, --build Build sources after exporting release
|
||||
-d, --docker-image Use the specified Docker image to compile the application.
|
||||
The image must have all required build dependencies installed.
|
||||
This option has no effect if --build is not set.
|
||||
--container-name Docker container name (default: '${DOCKER_CONTAINER_NAME}')
|
||||
The container must not exist already
|
||||
-c, --cmake-options Additional CMake options for compiling the sources
|
||||
--compiler Compiler to use (default: '${COMPILER}')
|
||||
-m, --make-options Make options for compiling sources (default: '${MAKE_OPTIONS}')
|
||||
-i, --install-prefix Install prefix (default: '${INSTALL_PREFIX}')
|
||||
-p, --plugins Space-separated list of plugins to build
|
||||
(default: ${BUILD_PLUGINS})
|
||||
-n, --no-source-tarball Don't build source tarball
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
elif [ "sign" == "$cmd" ]; then
|
||||
cat << EOF
|
||||
|
||||
Sign previously compiled release packages
|
||||
|
||||
Options:
|
||||
-f, --files Files to sign (required)
|
||||
-g, --gpg-key GPG key used to sign the files (default: '${GPG_KEY}')
|
||||
-h, --help Show this help
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
logInfo() {
|
||||
echo -e "\e[1m[ \e[34mINFO\e[39m ]\e[0m $1"
|
||||
}
|
||||
|
||||
logError() {
|
||||
echo -e "\e[1m[ \e[31mERROR\e[39m ]\e[0m $1" >&2
|
||||
}
|
||||
|
||||
init() {
|
||||
ORIG_CWD="$(pwd)"
|
||||
cd "$SRC_DIR" > /dev/null 2>&1
|
||||
ORIG_BRANCH="$(git rev-parse --abbrev-ref HEAD 2> /dev/null)"
|
||||
cd "$ORIG_CWD"
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
logInfo "Checking out original branch..."
|
||||
if [ "" != "$ORIG_BRANCH" ]; then
|
||||
git checkout "$ORIG_BRANCH" > /dev/null 2>&1
|
||||
fi
|
||||
logInfo "Leaving source directory..."
|
||||
cd "$ORIG_CWD"
|
||||
}
|
||||
|
||||
exitError() {
|
||||
logError "$1"
|
||||
cleanup
|
||||
exit 1
|
||||
}
|
||||
|
||||
exitTrap() {
|
||||
exitError "Existing upon user request..."
|
||||
}
|
||||
|
||||
checkSourceDirExists() {
|
||||
if [ ! -d "$SRC_DIR" ]; then
|
||||
exitError "Source directory '${SRC_DIR}' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkOutputDirDoesNotExist() {
|
||||
if [ -e "$OUTPUT_DIR" ]; then
|
||||
exitError "Output directory '$OUTPUT_DIR' already exists. Please choose a different location!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkGitRepository() {
|
||||
if [ ! -d .git ] || [ ! -f CHANGELOG ]; then
|
||||
exitError "Source directory is not a valid Git repository!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkTagExists() {
|
||||
git tag | grep -q "$TAG_NAME"
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Tag '${TAG_NAME}' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkReleaseDoesNotExist() {
|
||||
git tag | grep -q "$TAG_NAME"
|
||||
if [ $? -eq 0 ]; then
|
||||
exitError "Release '$RELEASE_NAME' (tag: '$TAG_NAME') already exists!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkWorkingTreeClean() {
|
||||
git diff-index --quiet HEAD --
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Current working tree is not clean! Please commit or unstage any changes."
|
||||
fi
|
||||
}
|
||||
|
||||
checkSourceBranchExists() {
|
||||
git rev-parse "$SOURCE_BRANCH" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Source branch '$SOURCE_BRANCH' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkTargetBranchExists() {
|
||||
git rev-parse "$TARGET_BRANCH" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Target branch '$TARGET_BRANCH' does not exist!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkVersionInCMake() {
|
||||
local app_name_upper="$(echo "$APP_NAME" | tr '[:lower:]' '[:upper:]')"
|
||||
|
||||
grep -q "${app_name_upper}_VERSION \"${RELEASE_NAME}\"" CMakeLists.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "${app_name_upper}_VERSION version not updated to '${RELEASE_NAME}' in CMakeLists.txt!"
|
||||
fi
|
||||
|
||||
grep -q "${app_name_upper}_VERSION_NUM \"${RELEASE_NAME}\"" CMakeLists.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "${app_name_upper}_VERSION_NUM version not updated to '${RELEASE_NAME}' in CMakeLists.txt!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkChangeLog() {
|
||||
if [ ! -f CHANGELOG ]; then
|
||||
exitError "No CHANGELOG file found!"
|
||||
fi
|
||||
|
||||
grep -qPzo "${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n=+\n" CHANGELOG
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "CHANGELOG does not contain any information about the '${RELEASE_NAME}' release!"
|
||||
fi
|
||||
}
|
||||
|
||||
checkTransifexCommandExists() {
|
||||
command -v tx > /dev/null
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Transifex tool 'tx' not installed! Please install it using 'pip install transifex-client'"
|
||||
fi
|
||||
}
|
||||
|
||||
# re-implement realpath for OS X (thanks mschrag)
|
||||
# https://superuser.com/questions/205127/
|
||||
if $(command -v realpath > /dev/null); then
|
||||
realpath() {
|
||||
pushd . > /dev/null
|
||||
if [ -d "$1" ]; then
|
||||
cd "$1"
|
||||
dirs -l +0
|
||||
else
|
||||
cd "$(dirname "$1")"
|
||||
cur_dir=$(dirs -l +0)
|
||||
|
||||
if [ "$cur_dir" == "/" ]; then
|
||||
echo "$cur_dir$(basename "$1")"
|
||||
else
|
||||
echo "$cur_dir/$(basename "$1")"
|
||||
fi
|
||||
fi
|
||||
popd > /dev/null
|
||||
}
|
||||
fi
|
||||
|
||||
|
||||
trap exitTrap SIGINT SIGTERM
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# merge command
|
||||
# -----------------------------------------------------------------------
|
||||
merge() {
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-v|--version)
|
||||
RELEASE_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-a|--app-name)
|
||||
APP_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-s|--source-dir)
|
||||
SRC_DIR="$2"
|
||||
shift ;;
|
||||
|
||||
-g|--gpg-key)
|
||||
GPG_GIT_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-r|--release-branch)
|
||||
SOURCE_BRANCH="$2"
|
||||
shift ;;
|
||||
|
||||
--target-branch)
|
||||
TARGET_BRANCH="$2"
|
||||
shift ;;
|
||||
|
||||
-t|--tag-name)
|
||||
TAG_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "merge"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "merge"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "" == "$RELEASE_NAME" ]; then
|
||||
logError "Missing arguments, --version is required!\n"
|
||||
printUsage "merge"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "" == "$TAG_NAME" ]; then
|
||||
TAG_NAME="$RELEASE_NAME"
|
||||
fi
|
||||
|
||||
if [ "" == "$SOURCE_BRANCH" ]; then
|
||||
SOURCE_BRANCH="release/${RELEASE_NAME}"
|
||||
fi
|
||||
|
||||
init
|
||||
|
||||
SRC_DIR="$(realpath "$SRC_DIR")"
|
||||
|
||||
logInfo "Performing basic checks..."
|
||||
|
||||
checkSourceDirExists
|
||||
|
||||
logInfo "Changing to source directory..."
|
||||
cd "${SRC_DIR}"
|
||||
|
||||
checkTransifexCommandExists
|
||||
checkGitRepository
|
||||
checkReleaseDoesNotExist
|
||||
checkWorkingTreeClean
|
||||
checkSourceBranchExists
|
||||
checkTargetBranchExists
|
||||
checkVersionInCMake
|
||||
checkChangeLog
|
||||
|
||||
logInfo "All checks pass, getting our hands dirty now!"
|
||||
|
||||
logInfo "Checking out source branch..."
|
||||
git checkout "$SOURCE_BRANCH"
|
||||
|
||||
logInfo "Updating language files..."
|
||||
./share/translations/update.sh
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Updating translations failed!"
|
||||
fi
|
||||
git diff-index --quiet HEAD --
|
||||
if [ $? -ne 0 ]; then
|
||||
git add ./share/translations/*
|
||||
logInfo "Committing changes..."
|
||||
if [ "" == "$GPG_GIT_KEY" ]; then
|
||||
git commit -m "Update translations"
|
||||
else
|
||||
git commit -m "Update translations" -S"$GPG_GIT_KEY"
|
||||
fi
|
||||
fi
|
||||
|
||||
logInfo "Checking out target branch '${TARGET_BRANCH}'..."
|
||||
git checkout "$TARGET_BRANCH"
|
||||
|
||||
logInfo "Merging '${SOURCE_BRANCH}' into '${TARGET_BRANCH}'..."
|
||||
|
||||
CHANGELOG=$(grep -Pzo "(?<=${RELEASE_NAME} \(\d{4}-\d{2}-\d{2}\)\n)=+\n\n?(?:.|\n)+?\n(?=\n)" \
|
||||
CHANGELOG | grep -Pzo '(?<=\n\n)(.|\n)+' | tr -d \\0)
|
||||
COMMIT_MSG="Release ${RELEASE_NAME}"
|
||||
|
||||
git merge "$SOURCE_BRANCH" --no-ff -m "$COMMIT_MSG" -m "${CHANGELOG}" "$SOURCE_BRANCH" -S"$GPG_GIT_KEY"
|
||||
|
||||
logInfo "Creating tag '${TAG_NAME}'..."
|
||||
if [ "" == "$GPG_GIT_KEY" ]; then
|
||||
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s
|
||||
else
|
||||
git tag -a "$TAG_NAME" -m "$COMMIT_MSG" -m "${CHANGELOG}" -s -u "$GPG_GIT_KEY"
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
logInfo "All done!"
|
||||
logInfo "Please merge the release branch back into the develop branch now and then push your changes."
|
||||
logInfo "Don't forget to also push the tags using \e[1mgit push --tags\e[0m."
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# build command
|
||||
# -----------------------------------------------------------------------
|
||||
build() {
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-v|--version)
|
||||
RELEASE_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-a|--app-name)
|
||||
APP_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-s|--source-dir)
|
||||
SRC_DIR="$2"
|
||||
shift ;;
|
||||
|
||||
-o|--output-dir)
|
||||
OUTPUT_DIR="$2"
|
||||
shift ;;
|
||||
|
||||
-t|--tag-name)
|
||||
TAG_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-d|--docker-image)
|
||||
DOCKER_IMAGE="$2"
|
||||
shift ;;
|
||||
|
||||
--container-name)
|
||||
DOCKER_CONTAINER_NAME="$2"
|
||||
shift ;;
|
||||
|
||||
-c|--cmake-options)
|
||||
CMAKE_OPTIONS="$2"
|
||||
shift ;;
|
||||
|
||||
--compiler)
|
||||
COMPILER="$2"
|
||||
shift ;;
|
||||
|
||||
-m|--make-options)
|
||||
MAKE_OPTIONS="$2"
|
||||
shift ;;
|
||||
|
||||
-i|--install-prefix)
|
||||
INSTALL_PREFIX="$2"
|
||||
shift ;;
|
||||
|
||||
-p|--plugins)
|
||||
BUILD_PLUGINS="$2"
|
||||
shift ;;
|
||||
|
||||
-n|--no-source-tarball)
|
||||
BUILD_SOURCE_TARBALL=false ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "build"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "build"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "" == "$RELEASE_NAME" ]; then
|
||||
logError "Missing arguments, --version is required!\n"
|
||||
printUsage "build"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "" == "$TAG_NAME" ]; then
|
||||
TAG_NAME="$RELEASE_NAME"
|
||||
fi
|
||||
|
||||
init
|
||||
|
||||
SRC_DIR="$(realpath "$SRC_DIR")"
|
||||
OUTPUT_DIR="$(realpath "$OUTPUT_DIR")"
|
||||
|
||||
logInfo "Performing basic checks..."
|
||||
|
||||
checkSourceDirExists
|
||||
|
||||
logInfo "Changing to source directory..."
|
||||
cd "${SRC_DIR}"
|
||||
|
||||
checkTagExists
|
||||
checkGitRepository
|
||||
checkWorkingTreeClean
|
||||
checkOutputDirDoesNotExist
|
||||
|
||||
logInfo "All checks pass, getting our hands dirty now!"
|
||||
|
||||
logInfo "Checking out release tag '${TAG_NAME}'..."
|
||||
git checkout "$TAG_NAME"
|
||||
|
||||
logInfo "Creating output directory..."
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
exitError "Failed to create output directory!"
|
||||
fi
|
||||
|
||||
if $BUILD_SOURCE_TARBALL; then
|
||||
logInfo "Creating source tarball..."
|
||||
local app_name_lower="$(echo "$APP_NAME" | tr '[:upper:]' '[:lower:]')"
|
||||
TARBALL_NAME="${app_name_lower}-${RELEASE_NAME}-src.tar.xz"
|
||||
git archive --format=tar "$TAG_NAME" --prefix="${app_name_lower}-${RELEASE_NAME}/" \
|
||||
| xz -6 > "${OUTPUT_DIR}/${TARBALL_NAME}"
|
||||
fi
|
||||
|
||||
logInfo "Creating build directory..."
|
||||
mkdir -p "${OUTPUT_DIR}/build-release"
|
||||
cd "${OUTPUT_DIR}/build-release"
|
||||
|
||||
logInfo "Configuring sources..."
|
||||
for p in $BUILD_PLUGINS; do
|
||||
CMAKE_OPTIONS="${CMAKE_OPTIONS} -DWITH_XC_$(echo $p | tr '[:lower:]' '[:upper:]')=On"
|
||||
done
|
||||
|
||||
if [ "$COMPILER" == "g++" ]; then
|
||||
export CC=gcc
|
||||
elif [ "$COMPILER" == "clang++" ]; then
|
||||
export CC=clang
|
||||
fi
|
||||
export CXX="$COMPILER"
|
||||
|
||||
if [ "" == "$DOCKER_IMAGE" ]; then
|
||||
if [ "$(uname -s)" == "Darwin" ]; then
|
||||
# Building on OS X
|
||||
local qt_vers="$(ls /usr/local/Cellar/qt5 2> /dev/null | sort -r | head -n1)"
|
||||
if [ "" == "$qt_vers" ]; then
|
||||
exitError "Couldn't find Qt5! Please make sure it is available in '/usr/local/Cellar/qt5'."
|
||||
fi
|
||||
export MACOSX_DEPLOYMENT_TARGET=10.7
|
||||
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" \
|
||||
-DCMAKE_OSX_ARCHITECTURES=x86_64 -DWITH_CXX11=OFF \
|
||||
-DCMAKE_PREFIX_PATH="/usr/local/Cellar/qt5/${qt_vers}/lib/cmake" \
|
||||
-DQT_BINARY_DIR="/usr/local/Cellar/qt5/${qt_vers}/bin" $CMAKE_OPTIONS "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling and packaging sources..."
|
||||
make $MAKE_OPTIONS package
|
||||
|
||||
mv "./${APP_NAME}-${RELEASE_NAME}.dmg" ../
|
||||
elif [ "$(uname -o)" == "Msys" ]; then
|
||||
# Building on Windows with Msys
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off -G"MSYS Makefiles" \
|
||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" $CMAKE_OPTIONS "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling and packaging sources..."
|
||||
make $MAKE_OPTIONS package
|
||||
|
||||
mv "./${APP_NAME}-${RELEASE_NAME}-"*.{exe,zip} ../
|
||||
else
|
||||
mkdir -p "${OUTPUT_DIR}/bin-release"
|
||||
|
||||
# Building on Linux without Docker container
|
||||
logInfo "Configuring build..."
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX="${INSTALL_PREFIX}" "$SRC_DIR"
|
||||
|
||||
logInfo "Compiling sources..."
|
||||
make $MAKE_OPTIONS
|
||||
|
||||
logInfo "Installing to bin dir..."
|
||||
make DESTDIR="${OUTPUT_DIR}/bin-release" install/strip
|
||||
|
||||
logInfo "Creating AppImage..."
|
||||
${SRC_DIR}/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME"
|
||||
fi
|
||||
else
|
||||
mkdir -p "${OUTPUT_DIR}/bin-release"
|
||||
|
||||
logInfo "Launching Docker container to compile sources..."
|
||||
|
||||
docker run --name "$DOCKER_CONTAINER_NAME" --rm \
|
||||
--cap-add SYS_ADMIN --device /dev/fuse \
|
||||
-e "CC=${CC}" -e "CXX=${CXX}" \
|
||||
-v "$(realpath "$SRC_DIR"):/keepassxc/src:ro" \
|
||||
-v "$(realpath "$OUTPUT_DIR"):/keepassxc/out:rw" \
|
||||
"$DOCKER_IMAGE" \
|
||||
bash -c "cd /keepassxc/out/build-release && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DWITH_TESTS=Off $CMAKE_OPTIONS \
|
||||
-DCMAKE_INSTALL_PREFIX=\"${INSTALL_PREFIX}\" /keepassxc/src && \
|
||||
make $MAKE_OPTIONS && make DESTDIR=/keepassxc/out/bin-release install/strip && \
|
||||
/keepassxc/src/AppImage-Recipe.sh "$APP_NAME" "$RELEASE_NAME""
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Docker build failed!"
|
||||
fi
|
||||
|
||||
logInfo "Build finished, Docker container terminated."
|
||||
fi
|
||||
|
||||
cleanup
|
||||
|
||||
logInfo "All done!"
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# sign command
|
||||
# -----------------------------------------------------------------------
|
||||
sign() {
|
||||
SIGN_FILES=()
|
||||
|
||||
while [ $# -ge 1 ]; do
|
||||
local arg="$1"
|
||||
case "$arg" in
|
||||
-f|--files)
|
||||
while [ "${2:0:1}" != "-" ] && [ $# -ge 2 ]; do
|
||||
SIGN_FILES+=("$2")
|
||||
shift
|
||||
done ;;
|
||||
|
||||
-g|--gpg-key)
|
||||
GPG_KEY="$2"
|
||||
shift ;;
|
||||
|
||||
-h|--help)
|
||||
printUsage "sign"
|
||||
exit ;;
|
||||
|
||||
*)
|
||||
logError "Unknown option '$arg'\n"
|
||||
printUsage "sign"
|
||||
exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ -z "$SIGN_FILES" ]; then
|
||||
logError "Missing arguments, --files is required!\n"
|
||||
printUsage "sign"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for f in "${SIGN_FILES[@]}"; do
|
||||
if [ ! -f "$f" ]; then
|
||||
exitError "File '${f}' does not exist!"
|
||||
fi
|
||||
|
||||
logInfo "Signing file '${f}'..."
|
||||
gpg --output "${f}.sig" --armor --local-user "$GPG_KEY" --detach-sig "$f"
|
||||
|
||||
if [ 0 -ne $? ]; then
|
||||
exitError "Signing failed!"
|
||||
fi
|
||||
|
||||
logInfo "Creating digest for file '${f}'..."
|
||||
sha256sum "$f" > "${f}.DIGEST"
|
||||
done
|
||||
|
||||
logInfo "All done!"
|
||||
}
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# parse global command line
|
||||
# -----------------------------------------------------------------------
|
||||
MODE="$1"
|
||||
shift
|
||||
if [ "" == "$MODE" ]; then
|
||||
logError "Missing arguments!\n"
|
||||
printUsage
|
||||
exit 1
|
||||
elif [ "help" == "$MODE" ]; then
|
||||
printUsage "$1"
|
||||
exit
|
||||
elif [ "merge" == "$MODE" ] || [ "build" == "$MODE" ] || [ "sign" == "$MODE" ]; then
|
||||
$MODE "$@"
|
||||
else
|
||||
printUsage "$MODE"
|
||||
fi
|
||||
@@ -21,9 +21,11 @@ install(FILES ${DATABASE_ICONS} DESTINATION ${DATA_INSTALL_DIR}/icons/database)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
|
||||
FILES_MATCHING PATTERN "keepassx*.png" PATTERN "keepassx*.svgz")
|
||||
FILES_MATCHING PATTERN "keepassx*.png" PATTERN "keepassx*.svgz"
|
||||
PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE)
|
||||
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor
|
||||
FILES_MATCHING PATTERN "application-x-keepassxc.png" PATTERN "application-x-keepassxc.svgz")
|
||||
FILES_MATCHING PATTERN "application-x-keepassxc.png" PATTERN "application-x-keepassxc.svgz"
|
||||
PATTERN "status" EXCLUDE PATTERN "actions" EXCLUDE)
|
||||
install(FILES linux/keepassxc.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||
install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,12 @@
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.</source>
|
||||
<source>Extensions:
|
||||
</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@@ -95,10 +100,6 @@ Please select whether you want to allow access.</source>
|
||||
<source>Repeat password:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Key file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browse</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -152,6 +153,10 @@ Please select whether you want to allow access.</source>
|
||||
%2</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Key file</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseOpenWidget</name>
|
||||
@@ -250,10 +255,6 @@ You can now save it.</source>
|
||||
<source>Default username:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use recycle bin:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> MiB</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -270,6 +271,10 @@ You can now save it.</source>
|
||||
<source>Max. history size:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use recycle bin</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseTabWidget</name>
|
||||
@@ -397,6 +402,10 @@ Do you want to open it anyway? Alternatively the database is opened read-only.</
|
||||
Do you want to save it anyway?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Passwords</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DatabaseWidget</name>
|
||||
@@ -475,6 +484,46 @@ Do you want to save it anyway?</source>
|
||||
<source>No Results</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Execute command?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you really want to execute the following command?<br><br>%1<br></source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remember my choice</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Autoreload Request</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed. Do you want to load the changes?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Merge Request</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>The database file has changed and you have unsaved changes.Do you want to merge your changes?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Autoreload Failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not parse or unlock the new database file while attempting to autoreload this database.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not open the new database file while attempting to autoreload this database.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditEntryWidget</name>
|
||||
@@ -603,14 +652,6 @@ Do you want to save it anyway?</source>
|
||||
<source>Enable Auto-Type for this entry</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Inherit default Auto-Type sequence from the group</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use custom Auto-Type sequence:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>+</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -624,11 +665,19 @@ Do you want to save it anyway?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use default sequence</source>
|
||||
<source>Inherit default Auto-Type sequence from the &group</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set custom sequence:</source>
|
||||
<source>&Use custom Auto-Type sequence:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use default se&quence</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set custo&m sequence:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@@ -669,10 +718,6 @@ Do you want to save it anyway?</source>
|
||||
<source>Repeat:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Gen.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>URL:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -758,14 +803,6 @@ Do you want to save it anyway?</source>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditWidgetIcons</name>
|
||||
<message>
|
||||
<source>Use default icon</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use custom icon</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add custom icon</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -810,6 +847,14 @@ Do you want to save it anyway?</source>
|
||||
<source>Can't delete icon. Still used by %1 items.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Use default icon</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use custo&m icon</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EditWidgetProperties</name>
|
||||
@@ -830,6 +875,13 @@ Do you want to save it anyway?</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>Entry</name>
|
||||
<message>
|
||||
<source> - Clone</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>EntryAttributesModel</name>
|
||||
<message>
|
||||
@@ -1226,6 +1278,10 @@ This is a one-way migration. You won't be able to open the imported databas
|
||||
<source>Re&pair database</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password Generator</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>OptionDialog</name>
|
||||
@@ -1299,14 +1355,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
<source>Automatic creates or updates are not supported for string fields!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>HTTP Host:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Default host: localhost</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>HTTP Port:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -1315,11 +1363,6 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
<source>Default port: 19455</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable KeepassXC Http protocol
|
||||
This is required for accessing your databases from ChromeIPass or PassIFox</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Re&quest to unlock the database if it is locked</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -1328,6 +1371,24 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
<source>Sort &matching entries by title</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable KeepassXC HTTP protocol
|
||||
This is required for accessing your databases from ChromeIPass or PassIFox</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>KeePassXC will listen to this port on 127.0.0.1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind to privileged ports</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind to privileged ports below 1024!
|
||||
Using default port 19455.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>PasswordGeneratorWidget</name>
|
||||
@@ -1335,10 +1396,6 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
<source>Password:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Length:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Character Types</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -1364,11 +1421,63 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Ensure that the password contains characters from every group</source>
|
||||
<source>Accept</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Accept</source>
|
||||
<source>%p%</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>strength</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>entropy</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>&Length:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Pick characters from every group</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Generate</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Close</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Apply</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Entropy: %1 bit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Password Quality: %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Poor</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Weak</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Good</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Excellent</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@@ -1415,16 +1524,20 @@ This is required for accessing your databases from ChromeIPass or PassIFox</sour
|
||||
</context>
|
||||
<context>
|
||||
<name>SearchWidget</name>
|
||||
<message>
|
||||
<source>Find:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Case Sensitive</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Search Current Group</source>
|
||||
<source>Search</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Find</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Clear</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@@ -1536,10 +1649,6 @@ give it a unique name to identify and accept it.</source>
|
||||
<source>Remember last databases</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Open previous databases on startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Automatically save on exit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
@@ -1581,11 +1690,19 @@ give it a unique name to identify and accept it.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray instead of App Exit</source>
|
||||
<source>Load previous databases on startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray on App start</source>
|
||||
<source>Automatically reload the database when modified externally</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide window to system tray instead of app exit</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Minimize window at application startup</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
@@ -1615,6 +1732,10 @@ give it a unique name to identify and accept it.</source>
|
||||
<source>Lock databases after minimizing the window</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Don't require password repeat when it is visible</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>UnlockDatabaseWidget</name>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
BIN
share/windows/installer-header.bmp
Normal file
BIN
share/windows/installer-header.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
BIN
share/windows/installer-wizard.bmp
Normal file
BIN
share/windows/installer-wizard.bmp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
@@ -149,24 +149,9 @@ set(keepassx_FORMS
|
||||
add_feature_info(KeePassHTTP WITH_XC_HTTP "KeePassHTTP support for ChromeIPass and PassIFox")
|
||||
add_feature_info(Autotype WITH_XC_AUTOTYPE "Auto-type passwords in Input fields")
|
||||
|
||||
add_subdirectory(http)
|
||||
if(WITH_XC_HTTP)
|
||||
set(keepasshttp_SOURCES
|
||||
http/AccessControlDialog.cpp
|
||||
http/EntryConfig.cpp
|
||||
http/HttpPasswordGeneratorWidget.cpp
|
||||
http/HttpSettings.cpp
|
||||
http/OptionDialog.cpp
|
||||
http/Protocol.cpp
|
||||
http/Server.cpp
|
||||
http/Service.cpp
|
||||
)
|
||||
set(keepasshttp_FORMS
|
||||
http/AccessControlDialog.ui
|
||||
http/HttpPasswordGeneratorWidget.ui
|
||||
http/OptionDialog.ui
|
||||
)
|
||||
set(keepasshttp_LIB keepasshttp)
|
||||
qt5_wrap_ui(keepasshttp_SOURCES ${keepasshttp_FORMS})
|
||||
set(keepasshttp_LIB keepasshttp)
|
||||
endif()
|
||||
|
||||
add_subdirectory(autotype)
|
||||
@@ -195,30 +180,32 @@ qt5_wrap_ui(keepassx_SOURCES ${keepassx_FORMS})
|
||||
add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp)
|
||||
target_link_libraries(zxcvbn)
|
||||
|
||||
if(WITH_XC_HTTP)
|
||||
add_library(keepasshttp STATIC ${keepasshttp_SOURCES})
|
||||
target_link_libraries(keepasshttp ${MHD_LIBRARIES} Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network)
|
||||
endif()
|
||||
|
||||
add_library(autotype STATIC ${autotype_SOURCES})
|
||||
target_link_libraries(autotype Qt5::Core Qt5::Widgets)
|
||||
|
||||
set(autotype_LIB autotype)
|
||||
|
||||
add_library(keepassx_core STATIC ${keepassx_SOURCES})
|
||||
set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
|
||||
target_link_libraries(keepassx_core zxcvbn ${keepasshttp_LIB} ${autotype_LIB} Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network)
|
||||
|
||||
add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE})
|
||||
target_link_libraries(${PROGNAME}
|
||||
keepassx_core
|
||||
set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
|
||||
target_link_libraries(keepassx_core
|
||||
${keepasshttp_LIB}
|
||||
${autotype_LIB}
|
||||
zxcvbn
|
||||
qhttp
|
||||
Qt5::Core
|
||||
Qt5::Concurrent
|
||||
Qt5::Widgets
|
||||
Qt5::Network
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${ZLIB_LIBRARIES})
|
||||
if (UNIX AND NOT APPLE)
|
||||
target_link_libraries(keepassx_core Qt5::DBus)
|
||||
endif()
|
||||
|
||||
add_executable(${PROGNAME} WIN32 MACOSX_BUNDLE ${keepassx_SOURCES_MAINEXE})
|
||||
target_link_libraries(${PROGNAME} keepassx_core)
|
||||
|
||||
set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)
|
||||
|
||||
@@ -257,9 +244,25 @@ if(APPLE)
|
||||
endif()
|
||||
|
||||
if(MINGW)
|
||||
set(CPACK_GENERATOR "ZIP")
|
||||
string(REPLACE "AMD" "Win" OUTPUT_FILE_POSTFIX "${CMAKE_HOST_SYSTEM_PROCESSOR}")
|
||||
set(CPACK_GENERATOR "ZIP;NSIS")
|
||||
set(CPACK_STRIP_FILES ON)
|
||||
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION_NUM}")
|
||||
set(CPACK_PACKAGE_FILE_NAME "${PROGNAME}-${KEEPASSXC_VERSION}-${OUTPUT_FILE_POSTFIX}")
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${PROGNAME})
|
||||
set(CPACK_PACKAGE_VERSION ${KEEPASSXC_VERSION})
|
||||
set(CPACK_PACKAGE_VENDOR "${PROGNAME} Team")
|
||||
string(REGEX REPLACE "/" "\\\\\\\\" CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/share/windows/installer-header.bmp")
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE.GPL-2")
|
||||
set(CPACK_NSIS_MUI_ICON "${CMAKE_SOURCE_DIR}/share/windows/keepassxc.ico")
|
||||
set(CPACK_NSIS_MUI_UNIICON "${CPACK_NSIS_MUI_ICON}")
|
||||
set(CPACK_NSIS_INSTALLED_ICON_NAME "\\\\${PROGNAME}.exe")
|
||||
string(REGEX REPLACE "/" "\\\\\\\\" CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP "${CMAKE_SOURCE_DIR}/share/windows/installer-wizard.bmp")
|
||||
set(CPACK_NSIS_MUI_UNWELCOMEFINISHPAGE_BITMAP "${CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP}")
|
||||
set(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\${PROGNAME}.lnk' '$INSTDIR\\\\${PROGNAME}.exe'")
|
||||
set(CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\${PROGNAME}.lnk'")
|
||||
set(CPACK_NSIS_URL_INFO_ABOUT "https://keepassxc.org")
|
||||
set(CPACK_NSIS_PACKAGE_NAME "${PROGNAME} v${KEEPASSXC_VERSION}")
|
||||
set(CPACK_NSIS_MUI_FINISHPAGE_RUN "../${PROGNAME}.exe")
|
||||
include(CPack)
|
||||
|
||||
install(CODE "
|
||||
@@ -267,5 +270,9 @@ if(MINGW)
|
||||
" COMPONENT Runtime)
|
||||
|
||||
include(DeployQt4)
|
||||
install_qt4_executable(${PROGNAME}.exe "qjpeg;qgif;qico;qtaccessiblewidgets")
|
||||
install_qt4_executable(${PROGNAME}.exe)
|
||||
add_custom_command(TARGET ${PROGNAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${Qt5Core_DIR}/../../../share/qt5/plugins/platforms/qwindows$<$<CONFIG:Debug>:d>.dll
|
||||
$<TARGET_FILE_DIR:${PROGNAME}>)
|
||||
install(FILES $<TARGET_FILE_DIR:${PROGNAME}>/qwindows$<$<CONFIG:Debug>:d>.dll DESTINATION "platforms")
|
||||
endif()
|
||||
|
||||
@@ -98,7 +98,9 @@ QString AutoTypePlatformMac::activeWindowTitle()
|
||||
if (windowLayer(window) == 0) {
|
||||
// First toplevel window in list (front to back order)
|
||||
title = windowTitle(window);
|
||||
break;
|
||||
if (!title.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -353,6 +353,12 @@ void Entry::setTitle(const QString& title)
|
||||
|
||||
void Entry::setUrl(const QString& url)
|
||||
{
|
||||
bool remove = url != m_attributes->value(EntryAttributes::URLKey) &&
|
||||
(m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "1" ||
|
||||
m_attributes->value(EntryAttributes::RememberCmdExecAttr) == "0");
|
||||
if (remove) {
|
||||
m_attributes->remove(EntryAttributes::RememberCmdExecAttr);
|
||||
}
|
||||
m_attributes->set(EntryAttributes::URLKey, url, m_attributes->isProtected(EntryAttributes::URLKey));
|
||||
}
|
||||
|
||||
@@ -508,7 +514,8 @@ Entry* Entry::clone(CloneFlags flags) const
|
||||
entry->m_data.timeInfo.setLocationChanged(now);
|
||||
}
|
||||
|
||||
|
||||
if (flags & CloneRenameTitle)
|
||||
entry->setTitle(entry->title() + tr(" - Clone"));
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,8 @@ public:
|
||||
CloneNoFlags = 0,
|
||||
CloneNewUuid = 1, // generate a random uuid for the clone
|
||||
CloneResetTimeInfo = 2, // set all TimeInfo attributes to the current time
|
||||
CloneIncludeHistory = 4 // clone the history items
|
||||
CloneIncludeHistory = 4, // clone the history items
|
||||
CloneRenameTitle = 8 // add "-Clone" after the original title
|
||||
};
|
||||
Q_DECLARE_FLAGS(CloneFlags, CloneFlag)
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ const QString EntryAttributes::URLKey = "URL";
|
||||
const QString EntryAttributes::NotesKey = "Notes";
|
||||
const QStringList EntryAttributes::DefaultAttributes(QStringList() << TitleKey << UserNameKey
|
||||
<< PasswordKey << URLKey << NotesKey);
|
||||
const QString EntryAttributes::RememberCmdExecAttr = "_EXEC_CMD";
|
||||
|
||||
EntryAttributes::EntryAttributes(QObject* parent)
|
||||
: QObject(parent)
|
||||
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
static const QString URLKey;
|
||||
static const QString NotesKey;
|
||||
static const QStringList DefaultAttributes;
|
||||
static const QString RememberCmdExecAttr;
|
||||
static bool isDefaultAttribute(const QString& key);
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
@@ -42,7 +42,11 @@ QList<Entry*> EntrySearcher::searchEntries(const QString& searchTerm, const Grou
|
||||
const QList<Group*> children = group->children();
|
||||
for (Group* childGroup : children) {
|
||||
if (childGroup->searchingEnabled() != Group::Disable) {
|
||||
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
|
||||
if (matchGroup(searchTerm, childGroup, caseSensitivity)) {
|
||||
searchResult.append(childGroup->entriesRecursive());
|
||||
} else {
|
||||
searchResult.append(searchEntries(searchTerm, childGroup, caseSensitivity));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,3 +73,21 @@ bool EntrySearcher::wordMatch(const QString& word, Entry* entry, Qt::CaseSensiti
|
||||
entry->url().contains(word, caseSensitivity) ||
|
||||
entry->notes().contains(word, caseSensitivity);
|
||||
}
|
||||
|
||||
bool EntrySearcher::matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
const QStringList wordList = searchTerm.split(QRegExp("\\s"), QString::SkipEmptyParts);
|
||||
for (const QString& word : wordList) {
|
||||
if (!wordMatch(word, group, caseSensitivity)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntrySearcher::wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity)
|
||||
{
|
||||
return group->name().contains(word, caseSensitivity) ||
|
||||
group->notes().contains(word, caseSensitivity);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ 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);
|
||||
bool matchGroup(const QString& searchTerm, const Group* group, Qt::CaseSensitivity caseSensitivity);
|
||||
bool wordMatch(const QString& word, const Group* group, Qt::CaseSensitivity caseSensitivity);
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_ENTRYSEARCHER_H
|
||||
|
||||
@@ -388,7 +388,7 @@ void KeePass2XmlReader::parseBinaries()
|
||||
QString id = attr.value("ID").toString();
|
||||
|
||||
QByteArray data;
|
||||
if (attr.value("Compressed").compare("True", Qt::CaseInsensitive) == 0) {
|
||||
if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) {
|
||||
data = readCompressedBinary();
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>455</width>
|
||||
<height>238</height>
|
||||
<height>266</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -53,7 +53,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true"><a href="https://www.keepassxc.org/">https://www.keepassxc.org/</a></string>
|
||||
<string notr="true"><a href="https://keepassxc.org/">https://keepassxc.org/</a></string>
|
||||
</property>
|
||||
<property name="openExternalLinks">
|
||||
<bool>true</bool>
|
||||
@@ -69,7 +69,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>KeePassXC is distributed under the term of the GNU General Public License (GPL) version 2 or (at your option) version 3.</string>
|
||||
<string>KeePassXC is distributed under the terms of the GNU General Public License (GPL) version 2 or (at your option) version 3.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
@@ -82,7 +82,7 @@
|
||||
<string/>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -91,6 +91,9 @@
|
||||
<property name="text">
|
||||
<string>Using:</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
@@ -99,6 +102,9 @@
|
||||
<string>Extensions:
|
||||
</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
||||
@@ -17,12 +17,20 @@
|
||||
*/
|
||||
|
||||
#include "Application.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include <QAbstractNativeEventFilter>
|
||||
#include <QFileOpenEvent>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||
class XcbEventFilter : public QAbstractNativeEventFilter
|
||||
{
|
||||
@@ -65,12 +73,18 @@ public:
|
||||
Application::Application(int& argc, char** argv)
|
||||
: QApplication(argc, argv)
|
||||
, m_mainWindow(nullptr)
|
||||
#ifdef Q_OS_UNIX
|
||||
, m_unixSignalNotifier(nullptr)
|
||||
#endif
|
||||
{
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||
installNativeEventFilter(new XcbEventFilter());
|
||||
#elif defined(Q_OS_WIN)
|
||||
installNativeEventFilter(new WinEventFilter());
|
||||
#endif
|
||||
#if defined(Q_OS_UNIX)
|
||||
registerUnixSignals();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Application::setMainWindow(QWidget* mainWindow)
|
||||
@@ -98,3 +112,57 @@ bool Application::event(QEvent* event)
|
||||
|
||||
return QApplication::event(event);
|
||||
}
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
int Application::unixSignalSocket[2];
|
||||
|
||||
void Application::registerUnixSignals()
|
||||
{
|
||||
int result = ::socketpair(AF_UNIX, SOCK_STREAM, 0, unixSignalSocket);
|
||||
Q_ASSERT(0 == result);
|
||||
if (0 != result) {
|
||||
// do not register handles when socket creation failed, otherwise
|
||||
// application will be unresponsive to signals such as SIGINT or SIGTERM
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<int> const handledSignals = { SIGQUIT, SIGINT, SIGTERM, SIGHUP };
|
||||
for (auto s: handledSignals) {
|
||||
struct sigaction sigAction;
|
||||
|
||||
sigAction.sa_handler = handleUnixSignal;
|
||||
sigemptyset(&sigAction.sa_mask);
|
||||
sigAction.sa_flags = 0 | SA_RESTART;
|
||||
sigaction(s, &sigAction, nullptr);
|
||||
}
|
||||
|
||||
m_unixSignalNotifier = new QSocketNotifier(unixSignalSocket[1], QSocketNotifier::Read, this);
|
||||
connect(m_unixSignalNotifier, SIGNAL(activated(int)), this, SLOT(quitBySignal()));
|
||||
}
|
||||
|
||||
void Application::handleUnixSignal(int sig)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGQUIT:
|
||||
case SIGINT:
|
||||
case SIGTERM:
|
||||
{
|
||||
char buf = 0;
|
||||
::write(unixSignalSocket[0], &buf, sizeof(buf));
|
||||
return;
|
||||
}
|
||||
case SIGHUP:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::quitBySignal()
|
||||
{
|
||||
m_unixSignalNotifier->setEnabled(false);
|
||||
char buf;
|
||||
::read(unixSignalSocket[1], &buf, sizeof(buf));
|
||||
|
||||
if (nullptr != m_mainWindow)
|
||||
static_cast<MainWindow*>(m_mainWindow)->appExit();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
class QSocketNotifier;
|
||||
|
||||
class Application : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -34,8 +36,23 @@ public:
|
||||
Q_SIGNALS:
|
||||
void openFile(const QString& filename);
|
||||
|
||||
private Q_SLOTS:
|
||||
#if defined(Q_OS_UNIX)
|
||||
void quitBySignal();
|
||||
#endif
|
||||
|
||||
private:
|
||||
QWidget* m_mainWindow;
|
||||
|
||||
#if defined(Q_OS_UNIX)
|
||||
/**
|
||||
* Register Unix signals such as SIGINT and SIGTERM for clean shutdown.
|
||||
*/
|
||||
void registerUnixSignals();
|
||||
QSocketNotifier* m_unixSignalNotifier;
|
||||
static void handleUnixSignal(int sig);
|
||||
static int unixSignalSocket[2];
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_APPLICATION_H
|
||||
|
||||
@@ -136,3 +136,8 @@ void ChangeMasterKeyWidget::reject()
|
||||
{
|
||||
Q_EMIT editFinished(false);
|
||||
}
|
||||
|
||||
void ChangeMasterKeyWidget::setCancelEnabled(bool enabled)
|
||||
{
|
||||
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(enabled);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
void clearForms();
|
||||
CompositeKey newMasterKey();
|
||||
QLabel* headlineLabel();
|
||||
void setCancelEnabled(bool enabled);
|
||||
|
||||
Q_SIGNALS:
|
||||
void editFinished(bool accepted);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>438</width>
|
||||
<height>256</height>
|
||||
<width>818</width>
|
||||
<height>397</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
@@ -41,8 +41,8 @@
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="enterPasswordLabel">
|
||||
<property name="text">
|
||||
<string>Enter password:</string>
|
||||
@@ -67,7 +67,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<item row="1" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="repeatPasswordLabel">
|
||||
<property name="text">
|
||||
<string>Repeat password:</string>
|
||||
@@ -87,7 +87,7 @@
|
||||
<item>
|
||||
<widget class="QGroupBox" name="keyFileGroup">
|
||||
<property name="title">
|
||||
<string>Key file</string>
|
||||
<string>&Key file</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
|
||||
@@ -52,6 +52,13 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
|
||||
|
||||
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
|
||||
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject()));
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// add random padding to layouts to align widgets properly
|
||||
m_ui->dialogButtonsLayout->setContentsMargins(10, 0, 15, 0);
|
||||
m_ui->gridLayout->setContentsMargins(10, 0, 0, 0);
|
||||
m_ui->labelLayout->setContentsMargins(10, 0, 10, 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
DatabaseOpenWidget::~DatabaseOpenWidget()
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<height>250</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,0,1,0,0,3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,0,1,0,0,3">
|
||||
<property name="spacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
@@ -28,14 +28,24 @@
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelHeadline">
|
||||
<property name="text">
|
||||
<string>Enter master key</string>
|
||||
<layout class="QVBoxLayout" name="labelLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelFilename"/>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelHeadline">
|
||||
<property name="text">
|
||||
<string>Enter master key</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="labelFilename"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_3">
|
||||
@@ -52,17 +62,20 @@
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>8</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<item row="1" column="0" alignment="Qt::AlignVCenter">
|
||||
<widget class="QCheckBox" name="checkKeyFile">
|
||||
<property name="text">
|
||||
<string>Key File:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<item row="0" column="0" alignment="Qt::AlignVCenter">
|
||||
<widget class="QCheckBox" name="checkPassword">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
@@ -70,9 +83,18 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<layout class="QHBoxLayout" name="keyFileLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="comboKeyFile">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
@@ -94,7 +116,13 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<layout class="QHBoxLayout" name="passwordLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="PasswordEdit" name="editPassword">
|
||||
<property name="echoMode">
|
||||
@@ -114,14 +142,21 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
<layout class="QHBoxLayout" name="dialogButtonsLayout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
<item alignment="Qt::AlignRight">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
@@ -152,7 +187,6 @@
|
||||
<tabstop>checkKeyFile</tabstop>
|
||||
<tabstop>comboKeyFile</tabstop>
|
||||
<tabstop>buttonBrowseFile</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@@ -6,160 +6,204 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>399</height>
|
||||
<width>600</width>
|
||||
<height>340</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,2,5,1">
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>1</width>
|
||||
<height>3</height>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="dbNameLabel">
|
||||
<property name="text">
|
||||
<string>Database name:</string>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5" stretch="0,1,0">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>800</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="2" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="transformRoundsSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="transformBenchmarkButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>25</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Benchmark</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="dbNameLabel">
|
||||
<property name="text">
|
||||
<string>Database name:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="historyMaxSizeCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="transformRoundsLabel">
|
||||
<property name="text">
|
||||
<string>Transform rounds:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="historyMaxItemsCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history items:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QLineEdit" name="dbNameEdit"/>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxItemsSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="defaultUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Default username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QLineEdit" name="dbDescriptionEdit"/>
|
||||
</item>
|
||||
<item row="6" column="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxSizeSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QCheckBox" name="recycleBinEnabledCheckBox">
|
||||
<property name="text">
|
||||
<string>Use recycle bin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<widget class="QLineEdit" name="defaultUsernameEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="dbDescriptionLabel">
|
||||
<property name="text">
|
||||
<string>Database description:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="dbNameEdit"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="dbDescriptionLabel">
|
||||
<property name="text">
|
||||
<string>Database description:</string>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="dbDescriptionEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="transformRoundsLabel">
|
||||
<property name="text">
|
||||
<string>Transform rounds:</string>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="defaultUsernameLabel">
|
||||
<property name="text">
|
||||
<string>Default username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="defaultUsernameEdit">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Use recycle bin:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxSizeSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> MiB</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="historyMaxItemsSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>2000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="transformRoundsSpinBox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>1000000000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="transformBenchmarkButton">
|
||||
<property name="text">
|
||||
<string>Benchmark</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QCheckBox" name="historyMaxItemsCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history items:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QCheckBox" name="historyMaxSizeCheckBox">
|
||||
<property name="text">
|
||||
<string>Max. history size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="recycleBinEnabledCheckBox"/>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
@@ -77,14 +77,9 @@ DatabaseTabWidget::~DatabaseTabWidget()
|
||||
void DatabaseTabWidget::toggleTabbar()
|
||||
{
|
||||
if (count() > 1) {
|
||||
if (!tabBar()->isVisible()) {
|
||||
tabBar()->show();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (tabBar()->isVisible()) {
|
||||
tabBar()->hide();
|
||||
}
|
||||
tabBar()->show();
|
||||
} else {
|
||||
tabBar()->hide();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,10 +89,18 @@ void DatabaseTabWidget::newDatabase()
|
||||
Database* db = new Database();
|
||||
db->rootGroup()->setName(tr("Root"));
|
||||
dbStruct.dbWidget = new DatabaseWidget(db, this);
|
||||
|
||||
CompositeKey emptyKey;
|
||||
db->setKey(emptyKey);
|
||||
|
||||
insertDatabase(db, dbStruct);
|
||||
|
||||
if (!saveDatabaseAs(db)) {
|
||||
closeDatabase(db);
|
||||
return;
|
||||
}
|
||||
|
||||
dbStruct.dbWidget->switchToMasterKeyChange();
|
||||
dbStruct.dbWidget->switchToMasterKeyChange(true);
|
||||
}
|
||||
|
||||
void DatabaseTabWidget::openDatabase()
|
||||
@@ -246,7 +249,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Close?"),
|
||||
tr("\"%1\" is in edit mode.\nDiscard changes and close anyway?").arg(dbName),
|
||||
tr("\"%1\" is in edit mode.\nDiscard changes and close anyway?").arg(dbName.toHtmlEscaped()),
|
||||
QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel);
|
||||
if (result == QMessageBox::Cancel) {
|
||||
return false;
|
||||
@@ -262,7 +265,7 @@ bool DatabaseTabWidget::closeDatabase(Database* db)
|
||||
QMessageBox::StandardButton result =
|
||||
MessageBox::question(
|
||||
this, tr("Save changes?"),
|
||||
tr("\"%1\" was modified.\nSave changes?").arg(dbName),
|
||||
tr("\"%1\" was modified.\nSave changes?").arg(dbName.toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
if (result == QMessageBox::Yes) {
|
||||
if (!saveDatabase(db)) {
|
||||
@@ -353,80 +356,78 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
|
||||
|
||||
bool DatabaseTabWidget::saveDatabaseAs(Database* db)
|
||||
{
|
||||
DatabaseManagerStruct& dbStruct = m_dbList[db];
|
||||
QString oldFileName;
|
||||
if (dbStruct.saveToFilename) {
|
||||
oldFileName = dbStruct.filePath;
|
||||
}
|
||||
else {
|
||||
oldFileName = tr("New database").append(".kdbx");
|
||||
}
|
||||
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
|
||||
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"),
|
||||
nullptr, 0, "kdbx");
|
||||
if (!fileName.isEmpty()) {
|
||||
QFileInfo fileInfo(fileName);
|
||||
QString lockFilePath;
|
||||
if (fileInfo.exists()) {
|
||||
// returns empty string when file doesn't exist
|
||||
lockFilePath = fileInfo.canonicalPath();
|
||||
while (true) {
|
||||
DatabaseManagerStruct& dbStruct = m_dbList[db];
|
||||
QString oldFileName;
|
||||
if (dbStruct.saveToFilename) {
|
||||
oldFileName = dbStruct.filePath;
|
||||
} else {
|
||||
oldFileName = tr("Passwords").append(".kdbx");
|
||||
}
|
||||
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 KeePassXC.\n"
|
||||
"Do you want to save it anyway?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
|
||||
oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"),
|
||||
nullptr, 0, "kdbx");
|
||||
if (!fileName.isEmpty()) {
|
||||
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 KeePassXC.\n"
|
||||
"Do you want to save it anyway?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (result == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// take over the lock file if possible
|
||||
if (lockFile->removeStaleLockFile()) {
|
||||
lockFile->tryLock();
|
||||
if (result == QMessageBox::No) {
|
||||
return false;
|
||||
} else {
|
||||
// take over the lock file if possible
|
||||
if (lockFile->removeStaleLockFile()) {
|
||||
lockFile->tryLock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// setup variables so saveDatabase succeeds
|
||||
dbStruct.saveToFilename = true;
|
||||
dbStruct.canonicalFilePath = fileName;
|
||||
// setup variables so saveDatabase succeeds
|
||||
dbStruct.saveToFilename = true;
|
||||
dbStruct.canonicalFilePath = fileName;
|
||||
|
||||
if (!saveDatabase(db)) {
|
||||
// failed to save, revert back
|
||||
dbStruct.saveToFilename = false;
|
||||
dbStruct.canonicalFilePath = oldFileName;
|
||||
if (!saveDatabase(db)) {
|
||||
// failed to save, revert back
|
||||
dbStruct.saveToFilename = false;
|
||||
dbStruct.canonicalFilePath = oldFileName;
|
||||
continue;
|
||||
}
|
||||
|
||||
// refresh fileinfo since the file didn't exist before
|
||||
fileInfo.refresh();
|
||||
|
||||
dbStruct.modified = false;
|
||||
dbStruct.saveToFilename = true;
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
|
||||
// refresh fileinfo since the file didn't exist before
|
||||
fileInfo.refresh();
|
||||
|
||||
dbStruct.modified = false;
|
||||
dbStruct.saveToFilename = true;
|
||||
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 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <QAction>
|
||||
#include <QDesktopServices>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFile>
|
||||
@@ -312,8 +313,10 @@ void DatabaseWidget::cloneEntry()
|
||||
return;
|
||||
}
|
||||
|
||||
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo);
|
||||
Entry* entry = currentEntry->clone(Entry::CloneNewUuid | Entry::CloneResetTimeInfo | Entry::CloneRenameTitle);
|
||||
entry->setGroup(currentEntry->group());
|
||||
if (isInSearchMode())
|
||||
search(m_lastSearchText);
|
||||
m_entryView->setFocus();
|
||||
m_entryView->setCurrentEntry(entry);
|
||||
}
|
||||
@@ -341,7 +344,7 @@ void DatabaseWidget::deleteEntries()
|
||||
result = MessageBox::question(
|
||||
this, tr("Delete entry?"),
|
||||
tr("Do you really want to delete the entry \"%1\" for good?")
|
||||
.arg(selectedEntries.first()->title()),
|
||||
.arg(selectedEntries.first()->title().toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
}
|
||||
else {
|
||||
@@ -365,7 +368,7 @@ void DatabaseWidget::deleteEntries()
|
||||
result = MessageBox::question(
|
||||
this, tr("Move entry to recycle bin?"),
|
||||
tr("Do you really want to move entry \"%1\" to the recycle bin?")
|
||||
.arg(selectedEntries.first()->title()),
|
||||
.arg(selectedEntries.first()->title().toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
}
|
||||
else {
|
||||
@@ -494,8 +497,46 @@ void DatabaseWidget::openUrlForEntry(Entry* entry)
|
||||
}
|
||||
|
||||
if (urlString.startsWith("cmd://")) {
|
||||
// check if decision to execute command was stored
|
||||
if (entry->attributes()->hasKey(EntryAttributes::RememberCmdExecAttr)) {
|
||||
if (entry->attributes()->value(EntryAttributes::RememberCmdExecAttr) == "1") {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise ask user
|
||||
if (urlString.length() > 6) {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
QString cmdTruncated = urlString.mid(6);
|
||||
if (cmdTruncated.length() > 400)
|
||||
cmdTruncated = cmdTruncated.left(400) + " […]";
|
||||
QMessageBox msgbox(QMessageBox::Icon::Question,
|
||||
tr("Execute command?"),
|
||||
tr("Do you really want to execute the following command?<br><br>%1<br>")
|
||||
.arg(cmdTruncated.toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No,
|
||||
this
|
||||
);
|
||||
msgbox.setDefaultButton(QMessageBox::No);
|
||||
|
||||
QCheckBox* checkbox = new QCheckBox(tr("Remember my choice"), &msgbox);
|
||||
msgbox.setCheckBox(checkbox);
|
||||
bool remember = false;
|
||||
QObject::connect(checkbox, &QCheckBox::stateChanged, [&](int state) {
|
||||
if (static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked) {
|
||||
remember = true;
|
||||
}
|
||||
});
|
||||
|
||||
int result = msgbox.exec();
|
||||
if (result == QMessageBox::Yes) {
|
||||
QProcess::startDetached(urlString.mid(6));
|
||||
}
|
||||
|
||||
if (remember) {
|
||||
entry->attributes()->set(EntryAttributes::RememberCmdExecAttr,
|
||||
result == QMessageBox::Yes ? "1" : "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -532,7 +573,7 @@ void DatabaseWidget::deleteGroup()
|
||||
QMessageBox::StandardButton result = MessageBox::question(
|
||||
this, tr("Delete group?"),
|
||||
tr("Do you really want to delete the group \"%1\" for good?")
|
||||
.arg(currentGroup->name()),
|
||||
.arg(currentGroup->name().toHtmlEscaped()),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
if (result == QMessageBox::Yes) {
|
||||
delete currentGroup;
|
||||
@@ -722,15 +763,10 @@ void DatabaseWidget::unlockDatabase(bool accepted)
|
||||
|
||||
replaceDatabase(db);
|
||||
|
||||
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
for (Group* group : groups) {
|
||||
if (group->uuid() == m_groupBeforeLock) {
|
||||
m_groupView->setCurrentGroup(group);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
restoreGroupEntryFocus(m_groupBeforeLock, m_entryBeforeLock);
|
||||
m_groupBeforeLock = Uuid();
|
||||
m_entryBeforeLock = Uuid();
|
||||
|
||||
setCurrentWidget(m_mainWidget);
|
||||
m_unlockDatabaseWidget->clearForms();
|
||||
Q_EMIT unlockedDatabase();
|
||||
@@ -755,7 +791,7 @@ void DatabaseWidget::entryActivationSignalReceived(Entry* entry, EntryModel::Mod
|
||||
void DatabaseWidget::switchToEntryEdit()
|
||||
{
|
||||
Entry* entry = m_entryView->currentEntry();
|
||||
Q_ASSERT(entry);
|
||||
|
||||
if (!entry) {
|
||||
return;
|
||||
}
|
||||
@@ -766,7 +802,7 @@ void DatabaseWidget::switchToEntryEdit()
|
||||
void DatabaseWidget::switchToGroupEdit()
|
||||
{
|
||||
Group* group = m_groupView->currentGroup();
|
||||
Q_ASSERT(group);
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
@@ -774,9 +810,10 @@ void DatabaseWidget::switchToGroupEdit()
|
||||
switchToGroupEdit(group, false);
|
||||
}
|
||||
|
||||
void DatabaseWidget::switchToMasterKeyChange()
|
||||
void DatabaseWidget::switchToMasterKeyChange(bool disableCancel)
|
||||
{
|
||||
m_changeMasterKeyWidget->clearForms();
|
||||
m_changeMasterKeyWidget->setCancelEnabled(!disableCancel);
|
||||
setCurrentWidget(m_changeMasterKeyWidget);
|
||||
}
|
||||
|
||||
@@ -943,6 +980,10 @@ void DatabaseWidget::lock()
|
||||
m_groupBeforeLock = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
if (m_entryView->currentEntry()) {
|
||||
m_entryBeforeLock = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
clearAllWidgets();
|
||||
m_unlockDatabaseWidget->load(m_filename);
|
||||
setCurrentWidget(m_unlockDatabaseWidget);
|
||||
@@ -1028,7 +1069,22 @@ void DatabaseWidget::reloadDatabaseFile()
|
||||
}
|
||||
}
|
||||
|
||||
Uuid groupBeforeReload;
|
||||
if (m_groupView && m_groupView->currentGroup()) {
|
||||
groupBeforeReload = m_groupView->currentGroup()->uuid();
|
||||
}
|
||||
else {
|
||||
groupBeforeReload = m_db->rootGroup()->uuid();
|
||||
}
|
||||
|
||||
Uuid entryBeforeReload;
|
||||
if (m_entryView && m_entryView->currentEntry()) {
|
||||
entryBeforeReload = m_entryView->currentEntry()->uuid();
|
||||
}
|
||||
|
||||
replaceDatabase(db);
|
||||
restoreGroupEntryFocus(groupBeforeReload, entryBeforeReload);
|
||||
|
||||
}
|
||||
else {
|
||||
MessageBox::critical(this, tr("Autoreload Failed"),
|
||||
@@ -1061,6 +1117,35 @@ QStringList DatabaseWidget::customEntryAttributes() const
|
||||
return entry->attributes()->customKeys();
|
||||
}
|
||||
|
||||
/*
|
||||
* Restores the focus on the group and entry that was focused
|
||||
* before the database was locked or reloaded.
|
||||
*/
|
||||
void DatabaseWidget::restoreGroupEntryFocus(Uuid groupUuid, Uuid entryUuid)
|
||||
{
|
||||
Group* restoredGroup = nullptr;
|
||||
const QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
|
||||
for (Group* group : groups) {
|
||||
if (group->uuid() == groupUuid) {
|
||||
restoredGroup = group;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (restoredGroup != nullptr) {
|
||||
m_groupView->setCurrentGroup(restoredGroup);
|
||||
|
||||
const QList<Entry*> entries = restoredGroup->entries();
|
||||
for (Entry* entry : entries) {
|
||||
if (entry->uuid() == entryUuid) {
|
||||
m_entryView->setCurrentEntry(entry);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool DatabaseWidget::isGroupSelected() const
|
||||
{
|
||||
return m_groupView->currentGroup() != nullptr;
|
||||
|
||||
@@ -132,7 +132,7 @@ public Q_SLOTS:
|
||||
void switchToView(bool accepted);
|
||||
void switchToEntryEdit();
|
||||
void switchToGroupEdit();
|
||||
void switchToMasterKeyChange();
|
||||
void switchToMasterKeyChange(bool disableCancel = false);
|
||||
void switchToDatabaseSettings();
|
||||
void switchToOpenDatabase(const QString& fileName);
|
||||
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);
|
||||
@@ -163,6 +163,7 @@ private Q_SLOTS:
|
||||
// Database autoreload slots
|
||||
void onWatchedFileChanged();
|
||||
void reloadDatabaseFile();
|
||||
void restoreGroupEntryFocus(Uuid groupUuid, Uuid EntryUuid);
|
||||
|
||||
private:
|
||||
void setClipboardTextAndMinimize(const QString& text);
|
||||
@@ -190,6 +191,7 @@ private:
|
||||
Group* m_newParent;
|
||||
QString m_filename;
|
||||
Uuid m_groupBeforeLock;
|
||||
Uuid m_entryBeforeLock;
|
||||
|
||||
// Search state
|
||||
QString m_lastSearchText;
|
||||
|
||||
@@ -49,11 +49,18 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -66,7 +73,6 @@
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>categoryList</tabstop>
|
||||
<tabstop>buttonBox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
#include "gui/IconModels.h"
|
||||
#include "gui/MessageBox.h"
|
||||
|
||||
#include "http/qhttp/qhttpclient.hpp"
|
||||
#include "http/qhttp/qhttpclientresponse.hpp"
|
||||
|
||||
using namespace qhttp::client;
|
||||
|
||||
IconStruct::IconStruct()
|
||||
: uuid(Uuid())
|
||||
, number(0)
|
||||
@@ -40,8 +45,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
|
||||
, m_database(nullptr)
|
||||
, m_defaultIconModel(new DefaultIconModel(this))
|
||||
, m_customIconModel(new CustomIconModel(this))
|
||||
, m_networkAccessMngr(new QNetworkAccessManager(this))
|
||||
, m_networkOperation(nullptr)
|
||||
, m_httpClient(nullptr)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
|
||||
@@ -59,8 +63,6 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
|
||||
connect(m_ui->addButton, SIGNAL(clicked()), SLOT(addCustomIcon()));
|
||||
connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon()));
|
||||
connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon()));
|
||||
connect(m_networkAccessMngr, SIGNAL(finished(QNetworkReply*)),
|
||||
this, SLOT(onRequestFinished(QNetworkReply*)) );
|
||||
|
||||
m_ui->faviconButton->setVisible(false);
|
||||
}
|
||||
@@ -93,7 +95,7 @@ IconStruct EditWidgetIcons::state()
|
||||
iconStruct.number = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return iconStruct;
|
||||
}
|
||||
|
||||
@@ -103,7 +105,7 @@ void EditWidgetIcons::reset()
|
||||
m_currentUuid = Uuid();
|
||||
}
|
||||
|
||||
void EditWidgetIcons::load(Uuid currentUuid, Database* database, IconStruct iconStruct, const QString &url)
|
||||
void EditWidgetIcons::load(const Uuid& currentUuid, Database* database, const IconStruct& iconStruct, const QString& url)
|
||||
{
|
||||
Q_ASSERT(database);
|
||||
Q_ASSERT(!currentUuid.isNull());
|
||||
@@ -134,11 +136,11 @@ void EditWidgetIcons::load(Uuid currentUuid, Database* database, IconStruct icon
|
||||
}
|
||||
}
|
||||
|
||||
void EditWidgetIcons::setUrl(const QString &url)
|
||||
void EditWidgetIcons::setUrl(const QString& url)
|
||||
{
|
||||
m_url = url;
|
||||
m_ui->faviconButton->setVisible(!url.isEmpty());
|
||||
abortFaviconDownload();
|
||||
resetFaviconDownload();
|
||||
}
|
||||
|
||||
void EditWidgetIcons::downloadFavicon()
|
||||
@@ -148,83 +150,99 @@ void EditWidgetIcons::downloadFavicon()
|
||||
fetchFavicon(url);
|
||||
}
|
||||
|
||||
void EditWidgetIcons::fetchFavicon(QUrl url)
|
||||
void EditWidgetIcons::fetchFavicon(const QUrl& url)
|
||||
{
|
||||
if (m_networkOperation == nullptr) {
|
||||
m_networkOperation = m_networkAccessMngr->get(QNetworkRequest(url));
|
||||
m_ui->faviconButton->setDisabled(true);
|
||||
if (nullptr == m_httpClient) {
|
||||
m_httpClient = new QHttpClient(this);
|
||||
}
|
||||
|
||||
bool requestMade = m_httpClient->request(qhttp::EHTTP_GET, url, [this, url](QHttpResponse* response) {
|
||||
if (m_database == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
response->collectData();
|
||||
response->onEnd([this, response, &url]() {
|
||||
int status = response->status();
|
||||
if (200 == status) {
|
||||
QImage image;
|
||||
image.loadFromData(response->collectedData());
|
||||
|
||||
if (!image.isNull()) {
|
||||
//Set the image
|
||||
Uuid uuid = Uuid::random();
|
||||
m_database->metadata()->addCustomIcon(uuid, image.scaled(16, 16));
|
||||
m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(),
|
||||
m_database->metadata()->customIconsOrder());
|
||||
QModelIndex index = m_customIconModel->indexFromUuid(uuid);
|
||||
m_ui->customIconsView->setCurrentIndex(index);
|
||||
m_ui->customIconsRadio->setChecked(true);
|
||||
|
||||
resetFaviconDownload();
|
||||
} else {
|
||||
fetchFaviconFromGoogle(url.host());
|
||||
}
|
||||
} else if (301 == status || 302 == status) {
|
||||
// Check if server has sent a redirect
|
||||
QUrl possibleRedirectUrl(response->headers().value("location", ""));
|
||||
if (!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != m_redirectUrl && m_redirectCount < 3) {
|
||||
resetFaviconDownload(false);
|
||||
m_redirectUrl = possibleRedirectUrl;
|
||||
++m_redirectCount;
|
||||
fetchFavicon(m_redirectUrl);
|
||||
} else {
|
||||
// website is trying to redirect to itself or
|
||||
// maximum number of redirects has been reached, fall back to Google
|
||||
fetchFaviconFromGoogle(url.host());
|
||||
}
|
||||
} else {
|
||||
fetchFaviconFromGoogle(url.host());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!requestMade) {
|
||||
resetFaviconDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
m_httpClient->setConnectingTimeOut(5000, [this]() {
|
||||
resetFaviconDownload();
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to fetch favicon."));
|
||||
});
|
||||
|
||||
m_ui->faviconButton->setDisabled(true);
|
||||
}
|
||||
|
||||
void EditWidgetIcons::fetchFaviconFromGoogle(QString domain)
|
||||
void EditWidgetIcons::fetchFaviconFromGoogle(const QString& domain)
|
||||
{
|
||||
if (m_fallbackToGoogle) {
|
||||
abortFaviconDownload();
|
||||
resetFaviconDownload();
|
||||
m_fallbackToGoogle = false;
|
||||
fetchFavicon(QUrl("http://www.google.com/s2/favicons?domain=" + domain));
|
||||
}
|
||||
else {
|
||||
abortFaviconDownload();
|
||||
resetFaviconDownload();
|
||||
MessageBox::warning(this, tr("Error"), tr("Unable to fetch favicon."));
|
||||
}
|
||||
}
|
||||
|
||||
void EditWidgetIcons::abortFaviconDownload(bool clearRedirect)
|
||||
void EditWidgetIcons::resetFaviconDownload(bool clearRedirect)
|
||||
{
|
||||
if (m_networkOperation != nullptr) {
|
||||
m_networkOperation->abort();
|
||||
m_networkOperation->deleteLater();
|
||||
m_networkOperation = nullptr;
|
||||
}
|
||||
|
||||
if (clearRedirect) {
|
||||
if (!m_redirectUrl.isEmpty()) {
|
||||
m_redirectUrl.clear();
|
||||
}
|
||||
m_redirectUrl.clear();
|
||||
m_redirectCount = 0;
|
||||
}
|
||||
|
||||
|
||||
if (nullptr != m_httpClient) {
|
||||
m_httpClient->deleteLater();
|
||||
m_httpClient = nullptr;
|
||||
}
|
||||
|
||||
m_fallbackToGoogle = true;
|
||||
m_ui->faviconButton->setDisabled(false);
|
||||
}
|
||||
|
||||
void EditWidgetIcons::onRequestFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (!reply->error()) {
|
||||
QImage image;
|
||||
image.loadFromData(reply->readAll());
|
||||
|
||||
if (!image.isNull()) {
|
||||
//Set the image
|
||||
Uuid uuid = Uuid::random();
|
||||
m_database->metadata()->addCustomIcon(uuid, image.scaled(16, 16));
|
||||
m_customIconModel->setIcons(m_database->metadata()->customIconsScaledPixmaps(),
|
||||
m_database->metadata()->customIconsOrder());
|
||||
QModelIndex index = m_customIconModel->indexFromUuid(uuid);
|
||||
m_ui->customIconsView->setCurrentIndex(index);
|
||||
m_ui->customIconsRadio->setChecked(true);
|
||||
|
||||
abortFaviconDownload();
|
||||
}
|
||||
else {
|
||||
// Check if server has sent a redirect
|
||||
QUrl possibleRedirectUrl = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl();
|
||||
if (!possibleRedirectUrl.isEmpty() && possibleRedirectUrl != m_redirectUrl && m_redirectCount < 3) {
|
||||
abortFaviconDownload(false);
|
||||
m_redirectUrl = possibleRedirectUrl;
|
||||
++m_redirectCount;
|
||||
fetchFavicon(m_redirectUrl);
|
||||
}
|
||||
else { // Webpage is trying to redirect back to itself or the maximum number of redirects has been reached, fallback to Google
|
||||
fetchFaviconFromGoogle(reply->url().host());
|
||||
}
|
||||
}
|
||||
}
|
||||
else { // Request Error e.g. 404, fallback to Google
|
||||
fetchFaviconFromGoogle(reply->url().host());
|
||||
}
|
||||
}
|
||||
|
||||
void EditWidgetIcons::addCustomIcon()
|
||||
{
|
||||
if (m_database) {
|
||||
|
||||
@@ -20,9 +20,7 @@
|
||||
|
||||
#include <QWidget>
|
||||
#include <QSet>
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QUrl>
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "core/Uuid.h"
|
||||
@@ -31,6 +29,11 @@ class Database;
|
||||
class DefaultIconModel;
|
||||
class CustomIconModel;
|
||||
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
class QHttpClient;
|
||||
}
|
||||
}
|
||||
namespace Ui {
|
||||
class EditWidgetIcons;
|
||||
}
|
||||
@@ -53,17 +56,16 @@ public:
|
||||
|
||||
IconStruct state();
|
||||
void reset();
|
||||
void load(Uuid currentUuid, Database* database, IconStruct iconStruct, const QString &url = QString());
|
||||
void load(const Uuid& currentUuid, Database* database, const IconStruct& iconStruct, const QString& url = "");
|
||||
|
||||
public Q_SLOTS:
|
||||
void setUrl(const QString &url);
|
||||
void setUrl(const QString& url);
|
||||
|
||||
private Q_SLOTS:
|
||||
void downloadFavicon();
|
||||
void fetchFavicon(QUrl url);
|
||||
void fetchFaviconFromGoogle(QString domain);
|
||||
void abortFaviconDownload(bool clearRedirect = true);
|
||||
void onRequestFinished(QNetworkReply *reply);
|
||||
void fetchFavicon(const QUrl& url);
|
||||
void fetchFaviconFromGoogle(const QString& domain);
|
||||
void resetFaviconDownload(bool clearRedirect = true);
|
||||
void addCustomIcon();
|
||||
void removeCustomIcon();
|
||||
void updateWidgetsDefaultIcons(bool checked);
|
||||
@@ -81,8 +83,7 @@ private:
|
||||
unsigned short m_redirectCount = 0;
|
||||
DefaultIconModel* const m_defaultIconModel;
|
||||
CustomIconModel* const m_customIconModel;
|
||||
QNetworkAccessManager* const m_networkAccessMngr;
|
||||
QNetworkReply* m_networkOperation;
|
||||
qhttp::client::QHttpClient* m_httpClient;
|
||||
|
||||
Q_DISABLE_COPY(EditWidgetIcons)
|
||||
};
|
||||
|
||||
@@ -6,15 +6,27 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<width>437</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="defaultIconsRadio">
|
||||
<property name="text">
|
||||
<string>Use default icon</string>
|
||||
<string>&Use default icon</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -46,7 +58,7 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="customIconsRadio">
|
||||
<property name="text">
|
||||
<string>Use custom icon</string>
|
||||
<string>Use custo&m icon</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -14,6 +14,15 @@
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="createdEdit">
|
||||
<property name="sizePolicy">
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
#include <QShortcut>
|
||||
#include <QTimer>
|
||||
|
||||
#if defined(Q_OS_LINUX) && ! defined(QT_NO_DBUS)
|
||||
#include <QList>
|
||||
#include <QtDBus/QtDBus>
|
||||
#endif
|
||||
|
||||
#include "config-keepassx.h"
|
||||
|
||||
#include "autotype/AutoType.h"
|
||||
@@ -364,7 +369,7 @@ void MainWindow::setMenuActionState(DatabaseWidget::Mode mode)
|
||||
bool groupSelected = dbWidget->isGroupSelected();
|
||||
|
||||
m_ui->actionEntryNew->setEnabled(!inSearch);
|
||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected && !inSearch);
|
||||
m_ui->actionEntryClone->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryEdit->setEnabled(singleEntrySelected);
|
||||
m_ui->actionEntryDelete->setEnabled(entriesSelected);
|
||||
m_ui->actionEntryCopyTitle->setEnabled(singleEntrySelected && dbWidget->currentEntryHasTitle());
|
||||
@@ -554,7 +559,7 @@ void MainWindow::closeEvent(QCloseEvent* event)
|
||||
if (minimizeOnClose && !appExitCalled)
|
||||
{
|
||||
event->ignore();
|
||||
hide();
|
||||
toggleWindow();
|
||||
|
||||
if (config()->get("security/lockdatabaseminimize").toBool()) {
|
||||
m_ui->tabWidget->lockDatabases();
|
||||
@@ -722,18 +727,37 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
|
||||
void MainWindow::toggleWindow()
|
||||
{
|
||||
if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) {
|
||||
hide();
|
||||
|
||||
setWindowState(windowState() | Qt::WindowMinimized);
|
||||
QTimer::singleShot(0, this, SLOT(hide()));
|
||||
|
||||
if (config()->get("security/lockdatabaseminimize").toBool()) {
|
||||
m_ui->tabWidget->lockDatabases();
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
ensurePolished();
|
||||
setWindowState(windowState() & ~Qt::WindowMinimized);
|
||||
show();
|
||||
raise();
|
||||
activateWindow();
|
||||
|
||||
#if defined(Q_OS_LINUX) && ! defined(QT_NO_DBUS)
|
||||
// re-register global D-Bus menu (needed on Ubuntu with Unity)
|
||||
// see https://github.com/keepassxreboot/keepassxc/issues/271
|
||||
// and https://bugreports.qt.io/browse/QTBUG-58723
|
||||
// check for !isVisible(), because isNativeMenuBar() does not work with appmenu-qt5
|
||||
if (!m_ui->menubar->isVisible()) {
|
||||
QDBusMessage msg = QDBusMessage::createMethodCall(
|
||||
"com.canonical.AppMenu.Registrar",
|
||||
"/com/canonical/AppMenu/Registrar",
|
||||
"com.canonical.AppMenu.Registrar",
|
||||
"RegisterWindow");
|
||||
QList<QVariant> args;
|
||||
args << QVariant::fromValue(static_cast<uint32_t>(winId()))
|
||||
<< QVariant::fromValue(QDBusObjectPath("/MenuBar/1"));
|
||||
msg.setArguments(args);
|
||||
QDBusConnection::sessionBus().send(msg);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ public:
|
||||
public Q_SLOTS:
|
||||
void openDatabase(const QString& fileName, const QString& pw = QString(),
|
||||
const QString& keyFile = QString());
|
||||
void appExit();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
@@ -68,7 +69,6 @@ private Q_SLOTS:
|
||||
void applySettingsChanges();
|
||||
void trayIconTriggered(QSystemTrayIcon::ActivationReason reason);
|
||||
void toggleWindow();
|
||||
void appExit();
|
||||
void lockDatabasesAfterInactivity();
|
||||
void repairDatabase();
|
||||
|
||||
|
||||
@@ -155,15 +155,15 @@
|
||||
<addaction name="actionEntryCopyNotes"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<addaction name="actionEntryNew"/>
|
||||
<addaction name="actionEntryClone"/>
|
||||
<addaction name="actionEntryEdit"/>
|
||||
<addaction name="actionEntryDelete"/>
|
||||
<addaction name="actionEntryCopyUsername"/>
|
||||
<addaction name="actionEntryCopyPassword"/>
|
||||
<addaction name="menuEntryCopyAttribute"/>
|
||||
<addaction name="actionEntryAutoType"/>
|
||||
<addaction name="actionEntryOpenUrl"/>
|
||||
<addaction name="actionEntryEdit"/>
|
||||
<addaction name="actionEntryClone"/>
|
||||
<addaction name="actionEntryDelete"/>
|
||||
<addaction name="actionEntryNew"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuGroups">
|
||||
<property name="title">
|
||||
|
||||
@@ -45,11 +45,15 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
|
||||
|
||||
connect(m_ui->optionButtons, SIGNAL(buttonClicked(int)), SLOT(updateGenerator()));
|
||||
|
||||
// set font size of password quality and entropy labels dynamically to 80% of the default font size
|
||||
// set font size of password quality and entropy labels dynamically to 80% of
|
||||
// the default font size, but make it no smaller than 8pt
|
||||
QFont defaultFont;
|
||||
defaultFont.setPointSize(static_cast<int>(defaultFont.pointSize() * 0.8f));
|
||||
m_ui->entropyLabel->setFont(defaultFont);
|
||||
m_ui->strengthLabel->setFont(defaultFont);
|
||||
int smallerSize = static_cast<int>(defaultFont.pointSize() * 0.8f);
|
||||
if (smallerSize >= 8) {
|
||||
defaultFont.setPointSize(smallerSize);
|
||||
m_ui->entropyLabel->setFont(defaultFont);
|
||||
m_ui->strengthLabel->setFont(defaultFont);
|
||||
}
|
||||
|
||||
loadSettings();
|
||||
reset();
|
||||
@@ -132,8 +136,10 @@ void PasswordGeneratorWidget::updatePasswordStrength(const QString& password)
|
||||
|
||||
void PasswordGeneratorWidget::generatePassword()
|
||||
{
|
||||
QString password = m_generator->generatePassword();
|
||||
m_ui->editNewPassword->setText(password);
|
||||
if (m_generator->isValid()) {
|
||||
QString password = m_generator->generatePassword();
|
||||
m_ui->editNewPassword->setText(password);
|
||||
}
|
||||
}
|
||||
|
||||
void PasswordGeneratorWidget::applyPassword()
|
||||
@@ -279,5 +285,11 @@ void PasswordGeneratorWidget::updateGenerator()
|
||||
m_generator->setCharClasses(classes);
|
||||
m_generator->setFlags(flags);
|
||||
|
||||
if (m_generator->isValid()) {
|
||||
m_ui->buttonGenerate->setEnabled(true);
|
||||
} else {
|
||||
m_ui->buttonGenerate->setEnabled(false);
|
||||
}
|
||||
|
||||
regeneratePassword();
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>500</width>
|
||||
<height>278</height>
|
||||
<width>575</width>
|
||||
<height>284</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@@ -16,10 +16,25 @@
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>284</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>16777215</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMinimumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="passwordFieldLayout">
|
||||
<property name="bottomMargin">
|
||||
@@ -104,11 +119,6 @@ QProgressBar::chunk {
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>strength</string>
|
||||
</property>
|
||||
@@ -144,11 +154,6 @@ QProgressBar::chunk {
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>entropy</string>
|
||||
</property>
|
||||
@@ -242,13 +247,19 @@ QProgressBar::chunk {
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="alphabetLayout">
|
||||
<layout class="QHBoxLayout" name="alphabetLayout" stretch="0,0,0,0,1">
|
||||
<item>
|
||||
<widget class="QToolButton" name="checkBoxUpper">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>26</height>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
@@ -270,10 +281,16 @@ QProgressBar::chunk {
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="checkBoxLower">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>26</height>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
@@ -295,6 +312,18 @@ QProgressBar::chunk {
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="checkBoxNumbers">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
@@ -314,6 +343,18 @@ QProgressBar::chunk {
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="checkBoxSpecialChars">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
|
||||
@@ -41,6 +41,7 @@ SearchWidget::SearchWidget(QWidget *parent)
|
||||
connect(this, SIGNAL(escapePressed()), m_ui->searchEdit, SLOT(clear()));
|
||||
|
||||
new QShortcut(Qt::CTRL + Qt::Key_F, this, SLOT(searchFocus()), nullptr, Qt::ApplicationShortcut);
|
||||
new QShortcut(Qt::Key_Escape, m_ui->searchEdit, SLOT(clear()), nullptr, Qt::ApplicationShortcut);
|
||||
|
||||
m_ui->searchEdit->installEventFilter(this);
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ SettingsWidget::SettingsWidget(QWidget* parent)
|
||||
#ifdef Q_OS_MAC
|
||||
// systray not useful on OS X
|
||||
m_generalUi->systrayShowCheckBox->setVisible(false);
|
||||
m_generalUi->systrayMinimizeOnCloseCheckBox->setVisible(false);
|
||||
m_generalUi->systrayMinimizeToTrayCheckBox->setVisible(false);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
|
||||
|
||||
connect(this, SIGNAL(accepted()), SLOT(saveEntry()));
|
||||
connect(this, SIGNAL(rejected()), SLOT(cancel()));
|
||||
|
||||
m_mainUi->passwordGenerator->layout()->setContentsMargins(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
EditEntryWidget::~EditEntryWidget()
|
||||
@@ -89,6 +91,7 @@ void EditEntryWidget::setupMain()
|
||||
add(tr("Entry"), m_mainWidget);
|
||||
|
||||
m_mainUi->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show"));
|
||||
m_mainUi->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false));
|
||||
connect(m_mainUi->togglePasswordButton, SIGNAL(toggled(bool)), m_mainUi->passwordEdit, SLOT(setShowPassword(bool)));
|
||||
connect(m_mainUi->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool)));
|
||||
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
||||
@@ -271,14 +274,15 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q
|
||||
m_history = history;
|
||||
|
||||
if (history) {
|
||||
setHeadline(QString("%1 > %2").arg(parentName, tr("Entry history")));
|
||||
setHeadline(QString("%1 > %2").arg(parentName.toHtmlEscaped(), tr("Entry history")));
|
||||
}
|
||||
else {
|
||||
if (create) {
|
||||
setHeadline(QString("%1 > %2").arg(parentName, tr("Add entry")));
|
||||
setHeadline(QString("%1 > %2").arg(parentName.toHtmlEscaped(), tr("Add entry")));
|
||||
}
|
||||
else {
|
||||
setHeadline(QString("%1 > %2 > %3").arg(parentName, entry->title(), tr("Edit entry")));
|
||||
setHeadline(QString("%1 > %2 > %3").arg(parentName.toHtmlEscaped(),
|
||||
entry->title().toHtmlEscaped(), tr("Edit entry")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,6 +437,9 @@ void EditEntryWidget::saveEntry()
|
||||
|
||||
void EditEntryWidget::updateEntryData(Entry* entry) const
|
||||
{
|
||||
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
|
||||
entry->attachments()->copyDataFrom(m_entryAttachments);
|
||||
|
||||
entry->setTitle(m_mainUi->titleEdit->text());
|
||||
entry->setUsername(m_mainUi->usernameEdit->text());
|
||||
entry->setUrl(m_mainUi->urlEdit->text());
|
||||
@@ -442,9 +449,6 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
|
||||
|
||||
entry->setNotes(m_mainUi->notesEdit->toPlainText());
|
||||
|
||||
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
|
||||
entry->attachments()->copyDataFrom(m_entryAttachments);
|
||||
|
||||
IconStruct iconStruct = m_iconsWidget->state();
|
||||
|
||||
if (iconStruct.number < 0) {
|
||||
|
||||
@@ -7,10 +7,22 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>315</height>
|
||||
<height>366</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="attributesBox">
|
||||
<property name="title">
|
||||
|
||||
@@ -7,10 +7,22 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>567</width>
|
||||
<height>347</height>
|
||||
<height>348</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableButton">
|
||||
<property name="text">
|
||||
@@ -37,14 +49,14 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="inheritSequenceButton">
|
||||
<property name="text">
|
||||
<string>Inherit default Auto-Type sequence from the group</string>
|
||||
<string>Inherit default Auto-Type sequence from the &group</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="customSequenceButton">
|
||||
<property name="text">
|
||||
<string>Use custom Auto-Type sequence:</string>
|
||||
<string>&Use custom Auto-Type sequence:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -90,39 +102,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="assocAddButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="assocRemoveButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4" stretch="2,1,1">
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<property name="orientation">
|
||||
@@ -136,6 +116,50 @@
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="assocAddButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>+</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="assocRemoveButton">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>-</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
@@ -171,14 +195,14 @@
|
||||
<item>
|
||||
<widget class="QRadioButton" name="defaultWindowSequenceButton">
|
||||
<property name="text">
|
||||
<string>Use default sequence</string>
|
||||
<string>Use default se&quence</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QRadioButton" name="customWindowSequenceButton">
|
||||
<property name="text">
|
||||
<string>Set custom sequence:</string>
|
||||
<string>Set custo&m sequence:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -11,6 +11,18 @@
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTreeView" name="historyView">
|
||||
<property name="sortingEnabled">
|
||||
|
||||
@@ -6,35 +6,50 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>372</width>
|
||||
<height>364</height>
|
||||
<width>692</width>
|
||||
<height>323</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="titleLabel">
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="5" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="urlLabel">
|
||||
<property name="text">
|
||||
<string>Title:</string>
|
||||
<string>URL:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="titleEdit"/>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="usernameLabel">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
<item row="4" column="1">
|
||||
<widget class="PasswordGeneratorWidget" name="passwordGenerator" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="usernameEdit"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<item row="2" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="passwordLabel">
|
||||
<property name="text">
|
||||
<string>Password:</string>
|
||||
@@ -42,6 +57,24 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="PasswordEdit" name="passwordRepeatEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="togglePasswordGeneratorButton">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="PasswordEdit" name="passwordEdit">
|
||||
@@ -59,52 +92,28 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<item row="3" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="passwordRepeatLabel">
|
||||
<property name="text">
|
||||
<string>Repeat:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="PasswordEdit" name="passwordRepeatEdit">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="togglePasswordGeneratorButton">
|
||||
<property name="text">
|
||||
<string>Generate</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="7" column="0">
|
||||
<widget class="QLabel" name="urlLabel">
|
||||
<item row="0" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="titleLabel">
|
||||
<property name="text">
|
||||
<string>URL:</string>
|
||||
<string>Title:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" alignment="Qt::AlignRight|Qt::AlignTop">
|
||||
<widget class="QLabel" name="notesLabel">
|
||||
<property name="text">
|
||||
<string>Notes:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QLineEdit" name="urlEdit"/>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="expireCheck">
|
||||
<property name="text">
|
||||
<string>Expires</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QDateTimeEdit" name="expireDatePicker">
|
||||
@@ -118,6 +127,12 @@
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="expirePresets">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Presets</string>
|
||||
</property>
|
||||
@@ -125,13 +140,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="notesLabel">
|
||||
<property name="text">
|
||||
<string>Notes:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1">
|
||||
<widget class="QPlainTextEdit" name="notesEdit">
|
||||
<property name="sizePolicy">
|
||||
@@ -142,8 +150,28 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="PasswordGeneratorWidget" name="passwordGenerator" native="true"/>
|
||||
<item row="1" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QLabel" name="usernameLabel">
|
||||
<property name="text">
|
||||
<string>Username:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="titleEdit"/>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="usernameEdit"/>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="urlEdit"/>
|
||||
</item>
|
||||
<item row="7" column="0" alignment="Qt::AlignRight">
|
||||
<widget class="QCheckBox" name="expireCheck">
|
||||
<property name="text">
|
||||
<string>Expires</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
@@ -168,7 +196,6 @@
|
||||
<tabstop>togglePasswordButton</tabstop>
|
||||
<tabstop>togglePasswordGeneratorButton</tabstop>
|
||||
<tabstop>urlEdit</tabstop>
|
||||
<tabstop>expireCheck</tabstop>
|
||||
<tabstop>expireDatePicker</tabstop>
|
||||
<tabstop>expirePresets</tabstop>
|
||||
<tabstop>notesEdit</tabstop>
|
||||
|
||||
@@ -140,15 +140,21 @@ Entry* EntryView::entryFromIndex(const QModelIndex& index)
|
||||
void EntryView::switchToEntryListMode()
|
||||
{
|
||||
m_sortModel->hideColumn(0, false);
|
||||
sortByColumn(1, Qt::AscendingOrder); // TODO: should probably be improved
|
||||
|
||||
m_sortModel->sort(1, Qt::AscendingOrder);
|
||||
m_sortModel->sort(0, Qt::AscendingOrder);
|
||||
sortByColumn(0, Qt::AscendingOrder);
|
||||
|
||||
m_inEntryListMode = true;
|
||||
}
|
||||
|
||||
void EntryView::switchToGroupMode()
|
||||
{
|
||||
m_sortModel->hideColumn(0, true);
|
||||
sortByColumn(-1, Qt::AscendingOrder);
|
||||
|
||||
m_sortModel->sort(-1, Qt::AscendingOrder);
|
||||
m_sortModel->sort(0, Qt::AscendingOrder);
|
||||
sortByColumn(0, Qt::AscendingOrder);
|
||||
|
||||
m_inEntryListMode = false;
|
||||
}
|
||||
|
||||
26
src/http/CMakeLists.txt
Normal file
26
src/http/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
add_subdirectory(qhttp)
|
||||
|
||||
if(WITH_XC_HTTP)
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(keepasshttp_SOURCES
|
||||
AccessControlDialog.cpp
|
||||
EntryConfig.cpp
|
||||
HttpPasswordGeneratorWidget.cpp
|
||||
HttpSettings.cpp
|
||||
OptionDialog.cpp
|
||||
Protocol.cpp
|
||||
Server.cpp
|
||||
Service.cpp
|
||||
)
|
||||
set(keepasshttp_FORMS
|
||||
AccessControlDialog.ui
|
||||
HttpPasswordGeneratorWidget.ui
|
||||
OptionDialog.ui
|
||||
)
|
||||
|
||||
qt5_wrap_ui(keepasshttp_SOURCES ${keepasshttp_FORMS})
|
||||
|
||||
add_library(keepasshttp STATIC ${keepasshttp_SOURCES})
|
||||
target_link_libraries(keepasshttp qhttp Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network)
|
||||
endif()
|
||||
@@ -18,7 +18,7 @@ PasswordGenerator HttpSettings::m_generator;
|
||||
|
||||
bool HttpSettings::isEnabled()
|
||||
{
|
||||
return config()->get("Http/Enabled", true).toBool();
|
||||
return config()->get("Http/Enabled", false).toBool();
|
||||
}
|
||||
|
||||
void HttpSettings::setEnabled(bool enabled)
|
||||
@@ -126,18 +126,6 @@ void HttpSettings::setSupportKphFields(bool supportKphFields)
|
||||
config()->set("Http/SupportKphFields", supportKphFields);
|
||||
}
|
||||
|
||||
QString HttpSettings::httpHost()
|
||||
{
|
||||
static const QString host = "localhost";
|
||||
|
||||
return config()->get("Http/Host", host).toString().toUtf8();
|
||||
}
|
||||
|
||||
void HttpSettings::setHttpHost(QString host)
|
||||
{
|
||||
config()->set("Http/Host", host);
|
||||
}
|
||||
|
||||
int HttpSettings::httpPort()
|
||||
{
|
||||
static const int PORT = 19455;
|
||||
|
||||
@@ -42,8 +42,6 @@ public:
|
||||
static void setSearchInAllDatabases(bool searchInAllDatabases);
|
||||
static bool supportKphFields();
|
||||
static void setSupportKphFields(bool supportKphFields);
|
||||
static QString httpHost();
|
||||
static void setHttpHost(QString host);
|
||||
static int httpPort();
|
||||
static void setHttpPort(int port);
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
#include "ui_OptionDialog.h"
|
||||
#include "HttpSettings.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
OptionDialog::OptionDialog(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::OptionDialog())
|
||||
@@ -41,7 +43,6 @@ void OptionDialog::loadSettings()
|
||||
ui->sortByUsername->setChecked(true);
|
||||
else
|
||||
ui->sortByTitle->setChecked(true);
|
||||
ui->httpHost->setText(settings.httpHost());
|
||||
ui->httpPort->setText(QString::number(settings.httpPort()));
|
||||
|
||||
/*
|
||||
@@ -70,8 +71,14 @@ void OptionDialog::saveSettings()
|
||||
settings.setUnlockDatabase(ui->unlockDatabase->isChecked());
|
||||
settings.setMatchUrlScheme(ui->matchUrlScheme->isChecked());
|
||||
settings.setSortByUsername(ui->sortByUsername->isChecked());
|
||||
settings.setHttpHost(ui->httpHost->text());
|
||||
settings.setHttpPort(ui->httpPort->text().toInt());
|
||||
|
||||
int port = ui->httpPort->text().toInt();
|
||||
if (port < 1024) {
|
||||
QMessageBox::warning(this, tr("Cannot bind to privileged ports"),
|
||||
tr("Cannot bind to privileged ports below 1024!\nUsing default port 19455."));
|
||||
port = 19455;
|
||||
}
|
||||
settings.setHttpPort(port);
|
||||
|
||||
/*
|
||||
settings.setPasswordUseLowercase(ui->checkBoxLower->isChecked());
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>605</width>
|
||||
<height>389</height>
|
||||
<height>429</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@@ -17,7 +17,7 @@
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enableHttpServer">
|
||||
<property name="text">
|
||||
<string>Enable KeepassXC Http protocol
|
||||
<string>Enable KeepassXC HTTP protocol
|
||||
This is required for accessing your databases from ChromeIPass or PassIFox</string>
|
||||
</property>
|
||||
</widget>
|
||||
@@ -201,32 +201,41 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>HTTP Host:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="httpHost">
|
||||
<property name="placeholderText">
|
||||
<string>Default host: localhost</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
<spacer name="verticalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="httpPort">
|
||||
<property name="inputMask">
|
||||
<string notr="true">d0000</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Default port: 19455</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>KeePassXC will listen to this port on 127.0.0.1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
@@ -237,15 +246,8 @@ Only entries with the same scheme (http://, https://, ftp://, ...) are returned<
|
||||
<property name="text">
|
||||
<string>HTTP Port:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="httpPort">
|
||||
<property name="inputMask">
|
||||
<string notr="true">d0000</string>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Default port: 19455</string>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -11,41 +11,30 @@
|
||||
***************************************************************************
|
||||
*/
|
||||
|
||||
#include "Server.h"
|
||||
#include <microhttpd.h>
|
||||
#include "Protocol.h"
|
||||
#include "HttpSettings.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include <QEventLoop>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QEventLoop>
|
||||
#include <QtNetwork/QHostInfo>
|
||||
#include <QtNetwork/QHostAddress>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#include "qhttp/qhttpserver.hpp"
|
||||
#include "qhttp/qhttpserverresponse.hpp"
|
||||
#include "qhttp/qhttpserverrequest.hpp"
|
||||
|
||||
#include "Server.h"
|
||||
#include "Protocol.h"
|
||||
#include "HttpSettings.h"
|
||||
#include "crypto/Crypto.h"
|
||||
|
||||
using namespace KeepassHttpProtocol;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// Request
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
using namespace qhttp::server;
|
||||
|
||||
Server::Server(QObject *parent) :
|
||||
QObject(parent),
|
||||
m_started(false)
|
||||
m_started(false),
|
||||
m_server(nullptr)
|
||||
{
|
||||
connect(this, SIGNAL(emitRequest(const QByteArray, QByteArray*)),
|
||||
this, SLOT(handleRequest(const QByteArray, QByteArray*)));
|
||||
connect(this, SIGNAL(emitOpenDatabase(bool*)),
|
||||
this, SLOT(handleOpenDatabase(bool*)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Server::testAssociate(const Request& r, Response * protocolResp)
|
||||
{
|
||||
@@ -132,39 +121,6 @@ void Server::setLogin(const Request &r, Response *protocolResp)
|
||||
protocolResp->setVerifier(key);
|
||||
}
|
||||
|
||||
|
||||
int Server::send_response(struct MHD_Connection *connection, const char *page)
|
||||
{
|
||||
int ret;
|
||||
struct MHD_Response *response;
|
||||
|
||||
response = MHD_create_response_from_buffer(
|
||||
strlen(page), static_cast<void*>(const_cast<char*>(page)),
|
||||
MHD_RESPMEM_PERSISTENT);
|
||||
if (!response) return MHD_NO;
|
||||
|
||||
MHD_add_response_header (response, "Content-Type", "application/json");
|
||||
ret = MHD_queue_response (connection, MHD_HTTP_OK, response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int Server::send_unavailable(struct MHD_Connection *connection)
|
||||
{
|
||||
int ret;
|
||||
struct MHD_Response *response;
|
||||
|
||||
response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
|
||||
if (!response) return MHD_NO;
|
||||
|
||||
ret = MHD_queue_response (connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
|
||||
MHD_destroy_response (response);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Server::generatePassword(const Request &r, Response *protocolResp)
|
||||
{
|
||||
QString key = getKey(r.id());
|
||||
@@ -182,27 +138,10 @@ void Server::generatePassword(const Request &r, Response *protocolResp)
|
||||
memset(password.data(), 0, password.length());
|
||||
}
|
||||
|
||||
|
||||
int Server::request_handler_wrapper(void *me, struct MHD_Connection *connection,
|
||||
const char *url, const char *method, const char *version,
|
||||
const char *upload_data, size_t *upload_data_size, void **con_cls)
|
||||
void Server::handleRequest(const QByteArray& data, QHttpResponse* response)
|
||||
{
|
||||
Server *myself = static_cast<Server*>(me);
|
||||
|
||||
if (myself)
|
||||
return myself->request_handler(connection, url, method, version,
|
||||
upload_data, upload_data_size, con_cls);
|
||||
else
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
|
||||
void Server::handleRequest(const QByteArray in, QByteArray *out)
|
||||
{
|
||||
*out = QByteArray();
|
||||
|
||||
Request r;
|
||||
if (!r.fromJson(in))
|
||||
if (!r.fromJson(data))
|
||||
return;
|
||||
|
||||
QByteArray hash = QCryptographicHash::hash(
|
||||
@@ -221,7 +160,7 @@ void Server::handleRequest(const QByteArray in, QByteArray *out)
|
||||
case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break;
|
||||
}
|
||||
|
||||
*out = protocolResp.toJson().toUtf8();
|
||||
QString out = protocolResp.toJson().toUtf8();
|
||||
|
||||
// THIS IS A FAKE HACK!!!
|
||||
// the real "error" is a misbehavior in the QJSON qobject2qvariant method
|
||||
@@ -232,175 +171,57 @@ void Server::handleRequest(const QByteArray in, QByteArray *out)
|
||||
//(4. ChromeIPass tries to access Entries.length and fails with null pointer exception)
|
||||
// the fake workaround replaces the (wrong) "Entries":null with "Entries:[] to give
|
||||
// chromeIPass (and passIFox) en empty list
|
||||
QString tmp_out = QString(*out);
|
||||
int tmp_pos1 = tmp_out.indexOf("\"Count\":0,");
|
||||
int tmp_pos2 = tmp_out.indexOf("\"Entries\":null,");
|
||||
if (tmp_pos1 != -1 && tmp_pos2 != -1) {
|
||||
tmp_out.replace(tmp_pos2, 15, "\"Entries\":[],");
|
||||
}
|
||||
*out = tmp_out.toUtf8();
|
||||
|
||||
Q_EMIT donewrk();
|
||||
}
|
||||
|
||||
|
||||
void Server::handleOpenDatabase(bool *success)
|
||||
{
|
||||
*success = openDatabase();
|
||||
Q_EMIT donewrk();
|
||||
}
|
||||
|
||||
|
||||
int Server::request_handler(struct MHD_Connection *connection,
|
||||
const char *, const char *method, const char *,
|
||||
const char *upload_data, size_t *upload_data_size, void **con_cls)
|
||||
{
|
||||
struct Server::connection_info_struct *con_info =
|
||||
static_cast<struct Server::connection_info_struct*>(*con_cls);
|
||||
|
||||
if (!isDatabaseOpened()) {
|
||||
bool success;
|
||||
QEventLoop loop1;
|
||||
loop1.connect(this, SIGNAL(donewrk()), SLOT(quit()));
|
||||
Q_EMIT emitOpenDatabase(&success);
|
||||
loop1.exec();
|
||||
|
||||
if (!success)
|
||||
return send_unavailable(connection);
|
||||
int pos1 = out.indexOf("\"Count\":0,");
|
||||
int pos2 = out.indexOf("\"Entries\":null,");
|
||||
if (pos1 != -1 && pos2 != -1) {
|
||||
out.replace(pos2, 15, "\"Entries\":[],");
|
||||
}
|
||||
|
||||
if (con_info == NULL) {
|
||||
*con_cls = calloc(1, sizeof(*con_info));
|
||||
return MHD_YES;
|
||||
}
|
||||
|
||||
if (strcmp (method, MHD_HTTP_METHOD_POST) != 0)
|
||||
return MHD_NO;
|
||||
|
||||
if (*upload_data_size == 0) {
|
||||
if (con_info && con_info->response)
|
||||
return send_response(connection, con_info->response);
|
||||
else
|
||||
return MHD_NO;
|
||||
}
|
||||
|
||||
QString type = MHD_lookup_connection_value(connection,
|
||||
MHD_HEADER_KIND, "Content-Type");
|
||||
if (!type.contains("application/json", Qt::CaseInsensitive))
|
||||
return MHD_NO;
|
||||
|
||||
// Now process the POST request
|
||||
|
||||
QByteArray post = QByteArray(upload_data, *upload_data_size);
|
||||
|
||||
QByteArray s;
|
||||
QEventLoop loop;
|
||||
loop.connect(this, SIGNAL(donewrk()), SLOT(quit()));
|
||||
Q_EMIT emitRequest(post, &s);
|
||||
loop.exec();
|
||||
|
||||
if (s.size() == 0)
|
||||
return MHD_NO;
|
||||
|
||||
con_info->response = static_cast<char*>(calloc(1, s.size()+1));
|
||||
memcpy(con_info->response, s.data(), s.size());
|
||||
|
||||
*upload_data_size = 0;
|
||||
|
||||
return MHD_YES;
|
||||
response->setStatusCode(qhttp::ESTATUS_OK);
|
||||
response->addHeader("Content-Type", "application/json");
|
||||
response->end(out.toUtf8());
|
||||
}
|
||||
|
||||
|
||||
void Server::request_completed(void *, struct MHD_Connection *,
|
||||
void **con_cls, enum MHD_RequestTerminationCode)
|
||||
{
|
||||
struct Server::connection_info_struct *con_info =
|
||||
static_cast<struct Server::connection_info_struct*>(*con_cls);
|
||||
|
||||
if (con_info == NULL)
|
||||
return;
|
||||
|
||||
if (con_info->response) free(con_info->response);
|
||||
free(con_info);
|
||||
*con_cls = NULL;
|
||||
}
|
||||
|
||||
|
||||
void Server::start(void)
|
||||
{
|
||||
if (m_started)
|
||||
return;
|
||||
|
||||
bool nohost = true;
|
||||
// local loopback hardcoded, since KeePassHTTP handshake
|
||||
// is not safe against interception
|
||||
QHostAddress address("127.0.0.1");
|
||||
int port = HttpSettings::httpPort();
|
||||
|
||||
QHostInfo info = QHostInfo::fromName(HttpSettings::httpHost());
|
||||
if (!info.addresses().isEmpty()) {
|
||||
void* addrx = NULL;
|
||||
unsigned int flags = MHD_USE_SELECT_INTERNALLY;
|
||||
QHostAddress address = info.addresses().first();
|
||||
|
||||
if (address.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
struct sockaddr_in *addr = static_cast<struct sockaddr_in*>(calloc(1, sizeof(struct sockaddr_in)));
|
||||
addrx = static_cast<void*>(addr);
|
||||
addr->sin_family = AF_INET;
|
||||
addr->sin_port = htons(HttpSettings::httpPort());
|
||||
addr->sin_addr.s_addr = htonl(address.toIPv4Address());
|
||||
nohost = false;
|
||||
} else {
|
||||
struct sockaddr_in6 *addr = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
|
||||
addrx = static_cast<void*>(addr);
|
||||
addr->sin6_family = AF_INET6;
|
||||
addr->sin6_port = htons(HttpSettings::httpPort());
|
||||
memcpy(&addr->sin6_addr, address.toIPv6Address().c, 16);
|
||||
nohost = false;
|
||||
flags |= MHD_USE_IPv6;
|
||||
}
|
||||
|
||||
if (nohost) {
|
||||
qWarning("HTTPPlugin: Faled to get configured host!");
|
||||
} else {
|
||||
if (NULL == (daemon = MHD_start_daemon(flags, port, NULL, NULL,
|
||||
&this->request_handler_wrapper, this,
|
||||
MHD_OPTION_NOTIFY_COMPLETED,
|
||||
this->request_completed, NULL,
|
||||
MHD_OPTION_SOCK_ADDR,
|
||||
addrx,
|
||||
MHD_OPTION_END))) {
|
||||
nohost = true;
|
||||
qWarning("HTTPPlugin: Failed to bind to configured host!");
|
||||
} else {
|
||||
nohost = false;
|
||||
//qWarning("HTTPPlugin: Binded to configured host.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (addrx != NULL)
|
||||
free(addrx);
|
||||
}
|
||||
|
||||
if (nohost) {
|
||||
if (NULL == (daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, port, NULL, NULL,
|
||||
&this->request_handler_wrapper, this,
|
||||
MHD_OPTION_NOTIFY_COMPLETED,
|
||||
this->request_completed, NULL,
|
||||
MHD_OPTION_END))) {
|
||||
qWarning("HTTPPlugin: Fatal! Failed to bind to both configured and default hosts!");
|
||||
} else {
|
||||
qWarning("HTTPPlugin: Bound to fallback address 0.0.0.0/:::!");
|
||||
}
|
||||
}
|
||||
m_server = new QHttpServer(this);
|
||||
m_server->listen(address, port);
|
||||
connect(m_server, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)), this, SLOT(onNewRequest(QHttpRequest*, QHttpResponse*)));
|
||||
|
||||
m_started = true;
|
||||
}
|
||||
|
||||
|
||||
void Server::stop(void)
|
||||
{
|
||||
if (!m_started)
|
||||
return;
|
||||
|
||||
MHD_stop_daemon(daemon);
|
||||
m_server->stopListening();
|
||||
m_server->deleteLater();
|
||||
m_started = false;
|
||||
}
|
||||
|
||||
void Server::onNewRequest(QHttpRequest* request, QHttpResponse* response)
|
||||
{
|
||||
if (!isDatabaseOpened()) {
|
||||
if (!openDatabase()) {
|
||||
response->setStatusCode(qhttp::ESTATUS_SERVICE_UNAVAILABLE);
|
||||
response->end();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
request->collectData(1024);
|
||||
|
||||
request->onEnd([=]() {
|
||||
this->handleRequest(request->collectedData(), response);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,11 +16,19 @@
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QList>
|
||||
#include <microhttpd.h>
|
||||
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
class QHttpServer;
|
||||
class QHttpRequest;
|
||||
class QHttpResponse;
|
||||
}
|
||||
}
|
||||
|
||||
namespace KeepassHttpProtocol {
|
||||
|
||||
using namespace qhttp::server;
|
||||
|
||||
class Request;
|
||||
class Response;
|
||||
class Entry;
|
||||
@@ -31,7 +39,6 @@ class Server : public QObject
|
||||
public:
|
||||
explicit Server(QObject *parent = 0);
|
||||
|
||||
//TODO: use QByteArray?
|
||||
virtual bool isDatabaseOpened() const = 0;
|
||||
virtual bool openDatabase() = 0;
|
||||
virtual QString getDatabaseRootUuid() = 0;
|
||||
@@ -45,18 +52,13 @@ public:
|
||||
virtual void updateEntry(const QString &id, const QString &uuid, const QString &login, const QString &password, const QString &url) = 0;
|
||||
virtual QString generatePassword() = 0;
|
||||
|
||||
public Q_SLOTS:
|
||||
public slots:
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
private Q_SLOTS:
|
||||
void handleRequest(const QByteArray in, QByteArray *out);
|
||||
void handleOpenDatabase(bool *success);
|
||||
|
||||
Q_SIGNALS:
|
||||
void emitRequest(const QByteArray in, QByteArray *out);
|
||||
void emitOpenDatabase(bool *success);
|
||||
void donewrk();
|
||||
private slots:
|
||||
void onNewRequest(QHttpRequest* request, QHttpResponse* response);
|
||||
void handleRequest(const QByteArray& data, QHttpResponse* response);
|
||||
|
||||
private:
|
||||
void testAssociate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
@@ -67,25 +69,9 @@ private:
|
||||
void setLogin(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
void generatePassword(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
|
||||
|
||||
static int request_handler_wrapper(void *me,
|
||||
struct MHD_Connection *connection,
|
||||
const char *url, const char *method, const char *version,
|
||||
const char *upload_data, size_t *upload_data_size, void **con_cls);
|
||||
static void request_completed(void *, struct MHD_Connection *,
|
||||
void **con_cls, enum MHD_RequestTerminationCode);
|
||||
|
||||
int request_handler(struct MHD_Connection *connection,
|
||||
const char *, const char *method, const char *,
|
||||
const char *upload_data, size_t *upload_data_size, void **con_cls);
|
||||
int send_response(struct MHD_Connection *connection, const char *page);
|
||||
int send_unavailable(struct MHD_Connection *connection);
|
||||
|
||||
bool m_started;
|
||||
struct MHD_Daemon *daemon;
|
||||
|
||||
struct connection_info_struct {
|
||||
char *response;
|
||||
};
|
||||
QHttpServer* m_server;
|
||||
};
|
||||
|
||||
} /*namespace KeepassHttpProtocol*/
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressDialog>
|
||||
#include <QDebug>
|
||||
|
||||
#include "Service.h"
|
||||
#include "Protocol.h"
|
||||
@@ -480,7 +479,8 @@ void Service::updateEntry(const QString &, const QString &uuid, const QString &l
|
||||
//ShowNotification(QString("%0: You have an entry change prompt waiting, click to activate").arg(requestId));
|
||||
if ( HttpSettings::alwaysAllowUpdate()
|
||||
|| QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
|
||||
tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host()).arg(u),
|
||||
tr("Do you want to update the information in %1 - %2?")
|
||||
.arg(QUrl(url).host().toHtmlEscaped()).arg(u.toHtmlEscaped()),
|
||||
QMessageBox::Yes|QMessageBox::No) == QMessageBox::Yes ) {
|
||||
entry->beginUpdate();
|
||||
entry->setUsername(login);
|
||||
|
||||
18
src/http/qhttp/CMakeLists.txt
Normal file
18
src/http/qhttp/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
project(qhttp)
|
||||
|
||||
set(qhttp_SOURCES
|
||||
qhttpabstracts.cpp
|
||||
qhttpserverconnection.cpp
|
||||
qhttpserverrequest.cpp
|
||||
qhttpserverresponse.cpp
|
||||
qhttpserver.cpp
|
||||
qhttpclientrequest.cpp
|
||||
qhttpclientresponse.cpp
|
||||
qhttpclient.cpp
|
||||
http-parser/http_parser.c
|
||||
)
|
||||
|
||||
add_library(qhttp STATIC ${qhttp_SOURCES})
|
||||
target_compile_definitions(qhttp PUBLIC QHTTP_MEMORY_LOG=0 QHTTP_EXPORT)
|
||||
target_include_directories(qhttp PRIVATE .)
|
||||
target_link_libraries(qhttp Qt5::Core Qt5::Network)
|
||||
22
src/http/qhttp/LICENSE
Normal file
22
src/http/qhttp/LICENSE
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Amir Zamani
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
229
src/http/qhttp/README.md
Normal file
229
src/http/qhttp/README.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# QHttp
|
||||
|
||||
|
||||
### Table of contents
|
||||
- [About](#about)
|
||||
- [Sample codes](#sample-codes)
|
||||
- [Features](#features)
|
||||
- [Setup](#setup)
|
||||
- [Multi-threading](#multi-threading)
|
||||
- [Source tree](#source-tree)
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [License](#license)
|
||||
|
||||
## About
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
`QHttp` is a lightweight, asynchronous and fast HTTP library, containing both server and client side classes for managing connections, parsing and building HTTP requests and responses. this project is inspired by [nikhilm/qhttpserver](https://github.com/nikhilm/qhttpserver) effort to implement a Qt HTTP server. `QHttp` pushes the idea further by implementing client classes and better memory management, a lot more Node.js-like API, ...
|
||||
|
||||
* the fantastic [nodejs/http-parser](https://github.com/nodejs/http-parser) is the core parser of HTTP requests (server mode) and responses (client mode).
|
||||
|
||||
* By using `std::function` and `c++11 lambda`, the API is intentionally similar to the [Node.js' http module](http://nodejs.org/api/http.html). Asynchronous and non-blocking HTTP programming is quite easy with `QHttp`. have a look at [sample codes](#sample-codes).
|
||||
|
||||
* the objective of `QHttp` is being light weight with a simple API for Qt developers to implement RESTful web services in private (internal) zones. [more](#disclaimer)
|
||||
|
||||
|
||||
|
||||
## Sample codes
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
a HelloWorld **HTTP server** by `QHttp` looks like:
|
||||
``` cpp
|
||||
int main(int argc, char** argv) {
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
using namespace qhttp::server;
|
||||
QHttpServer server(&app);
|
||||
// listening on 0.0.0.0:8080
|
||||
server.listen(QHostAddress::Any, 8080, [](QHttpRequest* req, QHttpResponse* res) {
|
||||
|
||||
res->setStatusCode(qhttp::ESTATUS_OK); // http status 200
|
||||
//res->addHeader("connection", "close"); // optional, it's the default header
|
||||
res->end("Hello World!\n"); // the response body data
|
||||
// by "connection: close", the req and res objects will be deleted automatically.
|
||||
});
|
||||
|
||||
if ( !server.isListening() ) {
|
||||
fprintf(stderr, "failed. can not listen at port 8080!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
```
|
||||
|
||||
to request weather information by **HTTP client**:
|
||||
```cpp
|
||||
int main(int argc, char** argv) {
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
using namespace qhttp::client;
|
||||
QHttpClient client(&app);
|
||||
QByteArray httpBody;
|
||||
|
||||
QUrl weatherUrl("http://api.openweathermap.org/data/2.5/weather?q=tehran,ir&units=metric&mode=xml");
|
||||
|
||||
client.request(qhttp::EHTTP_GET, weatherUrl, [&httpBody](QHttpResponse* res) {
|
||||
// response handler, called when the HTTP headers of the response are ready
|
||||
|
||||
// gather HTTP response data
|
||||
res->onData([&httpBody](const QByteArray& chunk) {
|
||||
httpBody.append(chunk);
|
||||
});
|
||||
|
||||
// called when all data in HTTP response have been read.
|
||||
res->onEnd([&httpBody]() {
|
||||
// print the XML body of the response
|
||||
puts("\n[incoming response:]");
|
||||
puts(httpBody.constData());
|
||||
puts("\n\n");
|
||||
|
||||
QCoreApplication::instance()->quit();
|
||||
});
|
||||
|
||||
// just for fun! print incoming headers:
|
||||
puts("\n[Headers:]");
|
||||
const qhttp::THeaderHash& hs = res->headers();
|
||||
for ( auto cit = hs.constBegin(); cit != hs.constEnd(); cit++) {
|
||||
printf("%s : %s\n", cit.key().constData(), cit.value().constData());
|
||||
}
|
||||
});
|
||||
|
||||
// set a timeout for making the request
|
||||
client.setConnectingTimeOut(10000, []{
|
||||
qDebug("connecting to HTTP server timed out!");
|
||||
QCoreApplication::quit();
|
||||
});
|
||||
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Features
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
* the only dependencies are: [Qt 5](http://qt-project.org/downloads), [c++11](http://en.wikipedia.org/wiki/C%2B%2B11) and [joyent/http-parser](https://github.com/joyent/http-parser)
|
||||
|
||||
* both TCP and UNIX (local) sockets are supported as backend.
|
||||
|
||||
* separate `namespace`s for server and client classes.
|
||||
|
||||
* HTTP server classes: [QHttpServer](./src/qhttpserver.hpp), [QHttpConnection](./src/qhttpserverconnection.hpp), [QHttpRequest](./src/qhttpserverrequest.hpp) and [QHttpResponse](./src/qhttpserverresponse.hpp).
|
||||
|
||||
* HTTP client classes: [QHttpClient](./src/qhttpclient.hpp), [QHttpRequest](./src/qhttpclientrequest.hpp) and [QHttpResponse](./src/qhttpclientresponse.hpp).
|
||||
|
||||
* **automatic memory management** of objects. Instances of connections, requests and replies will be deleted automatically when socket drops or disconnected.
|
||||
|
||||
* **PIMPL** (Private classes) to achieve better ABI compatibility and cleaner API.
|
||||
|
||||
* **Asynchronous** and **non-blocking**. You can handle thousands of concurrent HTTP connections efficiently by a single thread, although a multi-threaded HTTP server is easy to implement.
|
||||
|
||||
* **high throughput**, I have tried the `QHttp` and [gason++](https://github.com/azadkuh/gason--) to implement a REST/Json web service on an Ubuntu VPS (dual core + 512MB ram) with more than **5800** connections per second (stress test). On a MacBook Pro (i5 4258U 4cores with HT + 8096MB ram), `QHttp` easily reaches to more than **11700** connections / second. Generally `QHttp` is **1.5x ~ 3x** faster than `Node.js` depending on your machine / OS. check [benchmark app](./example/benchmard/README.md) to measure your system.
|
||||
|
||||
* Tested under **Linux** (Ubuntu 12.04 LTS, 14.04 LTS, g++) and **OS X** (10.9/10.10/10.11, clang). Easily portable where ever Qt 5 works. (tested by some users on Windows7/msvc2013 and Windows8.1/msvc2015)
|
||||
|
||||
|
||||
## Setup
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
instructions:
|
||||
```bash
|
||||
# first clone this repository:
|
||||
$> git clone --depth=1 https://github.com/azadkuh/qhttp.git -b master
|
||||
$> cd qhttp
|
||||
|
||||
# prepare dependencies:
|
||||
$> ./update-dependencies.sh
|
||||
|
||||
# now build the library and the examples
|
||||
$> qmake qhttp.pro
|
||||
$> make -j 8
|
||||
```
|
||||
|
||||
## Multi-threading
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
As `QHttp` is **asynchronous** and **non-blocking**, your app can handle thousands of concurrent HTTP connections by a single thread.
|
||||
|
||||
in some rare scenarios you may want to use multiple handler threads (although it's not the best solution):
|
||||
|
||||
* there are some blocking APIs (QSql, system calls, ...) in your connection handler (adopting asynchronous layer over the blocking API is a better approach).
|
||||
|
||||
* the hardware has lots of free cores and the measurement shows that the load on the main `QHttp` thread is close to highest limit. There you can spawn some other handler threads.
|
||||
|
||||
|
||||
[benchmark example](./example/benchmark/README.md) shows how to implement a single or multi threaded HTTP app (both server and client). This example uses worker `QThread` and `QObject::moveToThread()` for worker objects. see also: [Subclassing no longer recommended way of using QThread](http://qt-project.org/doc/note_revisions/5/8/view).
|
||||
|
||||
**Note**:
|
||||
> moving objects between threads is an expensive job, more ever the locking/unlocking mechanism, creating or stopping threads, ... cost even more! so using multiple threads in an application is not guaranteed to get better performance, but it's guaranteed to add more complexity, nasty bugs and headache!
|
||||
|
||||
see why other top performer networking libraries as ZeroMQ are concurrent but not multi-threaded by default:
|
||||
|
||||
* [ZeroMQ : Multithreading Magic](http://zeromq.org/blog:multithreading-magic)
|
||||
* [Node.js : about](http://nodejs.org/about/)
|
||||
|
||||
|
||||
## Source tree
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
|
||||
* **`3rdparty/`**:
|
||||
will contain `http-parser` source tree as the only dependency.
|
||||
this directory is created by setup. see also: [setup](#setup).
|
||||
|
||||
* **`example/`**:
|
||||
contains some sample applications representing the `QHttp` usage:
|
||||
* **`helloworld/`**:
|
||||
the HelloWorld example of `QHttp`, both server + client are represented.
|
||||
see: [README@helloworld](./example/helloworld/README.md)
|
||||
|
||||
* **`basic-server/`**:
|
||||
a basic HTTP server shows how to collect the request body, and respond to the clients.
|
||||
see: [README@basic-server](./example/basic-server/README.md)
|
||||
|
||||
|
||||
* **`benchmark/`**:
|
||||
a simple utility to measure the throughput (requests per second) of `QHttp` as a REST/Json server. this app provides both the server and attacking clients.
|
||||
see: [README@benchmark](./example/benchmark/README.md)
|
||||
|
||||
* **`nodejs/`**:
|
||||
Node.js implementation of `benchmark/` in server mode. Provided for benchmarking `QHttp` with `Node.js` as a RESTFul service app.
|
||||
see: [README@nodejs](./example/nodejs/README.md)
|
||||
|
||||
|
||||
* **`src/`**:
|
||||
holds the source code of `QHttp`. server classes are prefixed by `qhttpserver*` and client classes by `qhttpclient*`.
|
||||
* **`private/`**:
|
||||
Private classes of the library. see: [d-pointers](https://qt-project.org/wiki/Dpointer).
|
||||
|
||||
* **`tmp/`**:
|
||||
a temporary directory which is created while `make`ing the library and holds all the `.o`, `moc files`, etc.
|
||||
|
||||
* **`xbin/`**:
|
||||
all the executable and binaries will be placed on this folder by `make`.
|
||||
|
||||
|
||||
|
||||
|
||||
## Disclaimer
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
* Implementing a lightweight and simple HTTP server/client in Qt with Node.js like API, is the main purpose of `QHttp`.
|
||||
|
||||
* There are lots of features in a full blown HTTP server which are out of scope of this small library, although those can be added on top of `QHttp`.
|
||||
|
||||
* The client classes are by no mean designed as a `QNetworkAccessManager` replacement. `QHttpClient` is simpler and lighter, for serious scenarios just use `QNetworkAccessManager`.
|
||||
|
||||
* I'm a busy person.
|
||||
|
||||
|
||||
> If you have any ideas, critiques, suggestions or whatever you want to call it, please open an issue. I'll be happy to hear from you what you'd see in this lib. I think about all suggestions, and I try to add those that make sense.
|
||||
|
||||
|
||||
## License
|
||||
[TOC](#table-of-contents)
|
||||
|
||||
Distributed under the MIT license. Copyright (c) 2014, Amir Zamani.
|
||||
|
||||
23
src/http/qhttp/http-parser/LICENSE-MIT
Normal file
23
src/http/qhttp/http-parser/LICENSE-MIT
Normal file
@@ -0,0 +1,23 @@
|
||||
http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
|
||||
Igor Sysoev.
|
||||
|
||||
Additional changes are licensed under the same terms as NGINX and
|
||||
copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
246
src/http/qhttp/http-parser/README.md
Normal file
246
src/http/qhttp/http-parser/README.md
Normal file
@@ -0,0 +1,246 @@
|
||||
HTTP Parser
|
||||
===========
|
||||
|
||||
[](https://travis-ci.org/nodejs/http-parser)
|
||||
|
||||
This is a parser for HTTP messages written in C. It parses both requests and
|
||||
responses. The parser is designed to be used in performance HTTP
|
||||
applications. It does not make any syscalls nor allocations, it does not
|
||||
buffer data, it can be interrupted at anytime. Depending on your
|
||||
architecture, it only requires about 40 bytes of data per message
|
||||
stream (in a web server that is per connection).
|
||||
|
||||
Features:
|
||||
|
||||
* No dependencies
|
||||
* Handles persistent streams (keep-alive).
|
||||
* Decodes chunked encoding.
|
||||
* Upgrade support
|
||||
* Defends against buffer overflow attacks.
|
||||
|
||||
The parser extracts the following information from HTTP messages:
|
||||
|
||||
* Header fields and values
|
||||
* Content-Length
|
||||
* Request method
|
||||
* Response status code
|
||||
* Transfer-Encoding
|
||||
* HTTP version
|
||||
* Request URL
|
||||
* Message body
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
One `http_parser` object is used per TCP connection. Initialize the struct
|
||||
using `http_parser_init()` and set the callbacks. That might look something
|
||||
like this for a request parser:
|
||||
```c
|
||||
http_parser_settings settings;
|
||||
settings.on_url = my_url_callback;
|
||||
settings.on_header_field = my_header_field_callback;
|
||||
/* ... */
|
||||
|
||||
http_parser *parser = malloc(sizeof(http_parser));
|
||||
http_parser_init(parser, HTTP_REQUEST);
|
||||
parser->data = my_socket;
|
||||
```
|
||||
|
||||
When data is received on the socket execute the parser and check for errors.
|
||||
|
||||
```c
|
||||
size_t len = 80*1024, nparsed;
|
||||
char buf[len];
|
||||
ssize_t recved;
|
||||
|
||||
recved = recv(fd, buf, len, 0);
|
||||
|
||||
if (recved < 0) {
|
||||
/* Handle error. */
|
||||
}
|
||||
|
||||
/* Start up / continue the parser.
|
||||
* Note we pass recved==0 to signal that EOF has been received.
|
||||
*/
|
||||
nparsed = http_parser_execute(parser, &settings, buf, recved);
|
||||
|
||||
if (parser->upgrade) {
|
||||
/* handle new protocol */
|
||||
} else if (nparsed != recved) {
|
||||
/* Handle error. Usually just close the connection. */
|
||||
}
|
||||
```
|
||||
|
||||
HTTP needs to know where the end of the stream is. For example, sometimes
|
||||
servers send responses without Content-Length and expect the client to
|
||||
consume input (for the body) until EOF. To tell http_parser about EOF, give
|
||||
`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors
|
||||
can still be encountered during an EOF, so one must still be prepared
|
||||
to receive them.
|
||||
|
||||
Scalar valued message information such as `status_code`, `method`, and the
|
||||
HTTP version are stored in the parser structure. This data is only
|
||||
temporally stored in `http_parser` and gets reset on each new message. If
|
||||
this information is needed later, copy it out of the structure during the
|
||||
`headers_complete` callback.
|
||||
|
||||
The parser decodes the transfer-encoding for both requests and responses
|
||||
transparently. That is, a chunked encoding is decoded before being sent to
|
||||
the on_body callback.
|
||||
|
||||
|
||||
The Special Problem of Upgrade
|
||||
------------------------------
|
||||
|
||||
HTTP supports upgrading the connection to a different protocol. An
|
||||
increasingly common example of this is the WebSocket protocol which sends
|
||||
a request like
|
||||
|
||||
GET /demo HTTP/1.1
|
||||
Upgrade: WebSocket
|
||||
Connection: Upgrade
|
||||
Host: example.com
|
||||
Origin: http://example.com
|
||||
WebSocket-Protocol: sample
|
||||
|
||||
followed by non-HTTP data.
|
||||
|
||||
(See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the
|
||||
WebSocket protocol.)
|
||||
|
||||
To support this, the parser will treat this as a normal HTTP message without a
|
||||
body, issuing both on_headers_complete and on_message_complete callbacks. However
|
||||
http_parser_execute() will stop parsing at the end of the headers and return.
|
||||
|
||||
The user is expected to check if `parser->upgrade` has been set to 1 after
|
||||
`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
|
||||
offset by the return value of `http_parser_execute()`.
|
||||
|
||||
|
||||
Callbacks
|
||||
---------
|
||||
|
||||
During the `http_parser_execute()` call, the callbacks set in
|
||||
`http_parser_settings` will be executed. The parser maintains state and
|
||||
never looks behind, so buffering the data is not necessary. If you need to
|
||||
save certain data for later usage, you can do that from the callbacks.
|
||||
|
||||
There are two types of callbacks:
|
||||
|
||||
* notification `typedef int (*http_cb) (http_parser*);`
|
||||
Callbacks: on_message_begin, on_headers_complete, on_message_complete.
|
||||
* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
|
||||
Callbacks: (requests only) on_url,
|
||||
(common) on_header_field, on_header_value, on_body;
|
||||
|
||||
Callbacks must return 0 on success. Returning a non-zero value indicates
|
||||
error to the parser, making it exit immediately.
|
||||
|
||||
For cases where it is necessary to pass local information to/from a callback,
|
||||
the `http_parser` object's `data` field can be used.
|
||||
An example of such a case is when using threads to handle a socket connection,
|
||||
parse a request, and then give a response over that socket. By instantiation
|
||||
of a thread-local struct containing relevant data (e.g. accepted socket,
|
||||
allocated memory for callbacks to write into, etc), a parser's callbacks are
|
||||
able to communicate data between the scope of the thread and the scope of the
|
||||
callback in a threadsafe manner. This allows http-parser to be used in
|
||||
multi-threaded contexts.
|
||||
|
||||
Example:
|
||||
```c
|
||||
typedef struct {
|
||||
socket_t sock;
|
||||
void* buffer;
|
||||
int buf_len;
|
||||
} custom_data_t;
|
||||
|
||||
|
||||
int my_url_callback(http_parser* parser, const char *at, size_t length) {
|
||||
/* access to thread local custom_data_t struct.
|
||||
Use this access save parsed data for later use into thread local
|
||||
buffer, or communicate over socket
|
||||
*/
|
||||
parser->data;
|
||||
...
|
||||
return 0;
|
||||
}
|
||||
|
||||
...
|
||||
|
||||
void http_parser_thread(socket_t sock) {
|
||||
int nparsed = 0;
|
||||
/* allocate memory for user data */
|
||||
custom_data_t *my_data = malloc(sizeof(custom_data_t));
|
||||
|
||||
/* some information for use by callbacks.
|
||||
* achieves thread -> callback information flow */
|
||||
my_data->sock = sock;
|
||||
|
||||
/* instantiate a thread-local parser */
|
||||
http_parser *parser = malloc(sizeof(http_parser));
|
||||
http_parser_init(parser, HTTP_REQUEST); /* initialise parser */
|
||||
/* this custom data reference is accessible through the reference to the
|
||||
parser supplied to callback functions */
|
||||
parser->data = my_data;
|
||||
|
||||
http_parser_settings settings; /* set up callbacks */
|
||||
settings.on_url = my_url_callback;
|
||||
|
||||
/* execute parser */
|
||||
nparsed = http_parser_execute(parser, &settings, buf, recved);
|
||||
|
||||
...
|
||||
/* parsed information copied from callback.
|
||||
can now perform action on data copied into thread-local memory from callbacks.
|
||||
achieves callback -> thread information flow */
|
||||
my_data->buffer;
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In case you parse HTTP message in chunks (i.e. `read()` request line
|
||||
from socket, parse, read half headers, parse, etc) your data callbacks
|
||||
may be called more than once. Http-parser guarantees that data pointer is only
|
||||
valid for the lifetime of callback. You can also `read()` into a heap allocated
|
||||
buffer to avoid copying memory around if this fits your application.
|
||||
|
||||
Reading headers may be a tricky task if you read/parse headers partially.
|
||||
Basically, you need to remember whether last header callback was field or value
|
||||
and apply the following logic:
|
||||
|
||||
(on_header_field and on_header_value shortened to on_h_*)
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| State (prev. callback) | Callback | Description/action |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
|
||||
| | | into it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| value | on_h_field | New header started. |
|
||||
| | | Copy current name,value buffers to headers |
|
||||
| | | list and allocate new buffer for new name |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| field | on_h_field | Previous name continues. Reallocate name |
|
||||
| | | buffer and append callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| field | on_h_value | Value for current header started. Allocate |
|
||||
| | | new buffer and copy callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
| value | on_h_value | Value continues. Reallocate value buffer |
|
||||
| | | and append callback data to it |
|
||||
------------------------ ------------ --------------------------------------------
|
||||
|
||||
|
||||
Parsing URLs
|
||||
------------
|
||||
|
||||
A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`.
|
||||
Users of this library may wish to use it to parse URLs constructed from
|
||||
consecutive `on_url` callbacks.
|
||||
|
||||
See examples of reading in headers:
|
||||
|
||||
* [partial example](http://gist.github.com/155877) in C
|
||||
* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C
|
||||
* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript
|
||||
2470
src/http/qhttp/http-parser/http_parser.c
Normal file
2470
src/http/qhttp/http-parser/http_parser.c
Normal file
File diff suppressed because it is too large
Load Diff
432
src/http/qhttp/http-parser/http_parser.h
Normal file
432
src/http/qhttp/http-parser/http_parser.h
Normal file
@@ -0,0 +1,432 @@
|
||||
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
#ifndef http_parser_h
|
||||
#define http_parser_h
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Also update SONAME in the Makefile whenever you change these. */
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 7
|
||||
#define HTTP_PARSER_VERSION_PATCH 1
|
||||
|
||||
#include <sys/types.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && \
|
||||
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
|
||||
#include <BaseTsd.h>
|
||||
#include <stddef.h>
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
|
||||
* faster
|
||||
*/
|
||||
#ifndef HTTP_PARSER_STRICT
|
||||
# define HTTP_PARSER_STRICT 1
|
||||
#endif
|
||||
|
||||
/* Maximium header size allowed. If the macro is not defined
|
||||
* before including this header then the default is used. To
|
||||
* change the maximum header size, define the macro in the build
|
||||
* environment (e.g. -DHTTP_MAX_HEADER_SIZE=<value>). To remove
|
||||
* the effective limit on the size of the header, define the macro
|
||||
* to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff)
|
||||
*/
|
||||
#ifndef HTTP_MAX_HEADER_SIZE
|
||||
# define HTTP_MAX_HEADER_SIZE (80*1024)
|
||||
#endif
|
||||
|
||||
typedef struct http_parser http_parser;
|
||||
typedef struct http_parser_settings http_parser_settings;
|
||||
|
||||
|
||||
/* Callbacks should return non-zero to indicate an error. The parser will
|
||||
* then halt execution.
|
||||
*
|
||||
* The one exception is on_headers_complete. In a HTTP_RESPONSE parser
|
||||
* returning '1' from on_headers_complete will tell the parser that it
|
||||
* should not expect a body. This is used when receiving a response to a
|
||||
* HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
|
||||
* chunked' headers that indicate the presence of a body.
|
||||
*
|
||||
* Returning `2` from on_headers_complete will tell parser that it should not
|
||||
* expect neither a body nor any futher responses on this connection. This is
|
||||
* useful for handling responses to a CONNECT request which may not contain
|
||||
* `Upgrade` or `Connection: upgrade` headers.
|
||||
*
|
||||
* http_data_cb does not return data chunks. It will be called arbitrarily
|
||||
* many times for each string. E.G. you might get 10 callbacks for "on_url"
|
||||
* each providing just a few characters more data.
|
||||
*/
|
||||
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
|
||||
typedef int (*http_cb) (http_parser*);
|
||||
|
||||
|
||||
/* Status Codes */
|
||||
#define HTTP_STATUS_MAP(XX) \
|
||||
XX(100, CONTINUE, Continue) \
|
||||
XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \
|
||||
XX(102, PROCESSING, Processing) \
|
||||
XX(200, OK, OK) \
|
||||
XX(201, CREATED, Created) \
|
||||
XX(202, ACCEPTED, Accepted) \
|
||||
XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \
|
||||
XX(204, NO_CONTENT, No Content) \
|
||||
XX(205, RESET_CONTENT, Reset Content) \
|
||||
XX(206, PARTIAL_CONTENT, Partial Content) \
|
||||
XX(207, MULTI_STATUS, Multi-Status) \
|
||||
XX(208, ALREADY_REPORTED, Already Reported) \
|
||||
XX(226, IM_USED, IM Used) \
|
||||
XX(300, MULTIPLE_CHOICES, Multiple Choices) \
|
||||
XX(301, MOVED_PERMANENTLY, Moved Permanently) \
|
||||
XX(302, FOUND, Found) \
|
||||
XX(303, SEE_OTHER, See Other) \
|
||||
XX(304, NOT_MODIFIED, Not Modified) \
|
||||
XX(305, USE_PROXY, Use Proxy) \
|
||||
XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \
|
||||
XX(308, PERMANENT_REDIRECT, Permanent Redirect) \
|
||||
XX(400, BAD_REQUEST, Bad Request) \
|
||||
XX(401, UNAUTHORIZED, Unauthorized) \
|
||||
XX(402, PAYMENT_REQUIRED, Payment Required) \
|
||||
XX(403, FORBIDDEN, Forbidden) \
|
||||
XX(404, NOT_FOUND, Not Found) \
|
||||
XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \
|
||||
XX(406, NOT_ACCEPTABLE, Not Acceptable) \
|
||||
XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \
|
||||
XX(408, REQUEST_TIMEOUT, Request Timeout) \
|
||||
XX(409, CONFLICT, Conflict) \
|
||||
XX(410, GONE, Gone) \
|
||||
XX(411, LENGTH_REQUIRED, Length Required) \
|
||||
XX(412, PRECONDITION_FAILED, Precondition Failed) \
|
||||
XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \
|
||||
XX(414, URI_TOO_LONG, URI Too Long) \
|
||||
XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \
|
||||
XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \
|
||||
XX(417, EXPECTATION_FAILED, Expectation Failed) \
|
||||
XX(421, MISDIRECTED_REQUEST, Misdirected Request) \
|
||||
XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \
|
||||
XX(423, LOCKED, Locked) \
|
||||
XX(424, FAILED_DEPENDENCY, Failed Dependency) \
|
||||
XX(426, UPGRADE_REQUIRED, Upgrade Required) \
|
||||
XX(428, PRECONDITION_REQUIRED, Precondition Required) \
|
||||
XX(429, TOO_MANY_REQUESTS, Too Many Requests) \
|
||||
XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \
|
||||
XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \
|
||||
XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \
|
||||
XX(501, NOT_IMPLEMENTED, Not Implemented) \
|
||||
XX(502, BAD_GATEWAY, Bad Gateway) \
|
||||
XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \
|
||||
XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \
|
||||
XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \
|
||||
XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \
|
||||
XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \
|
||||
XX(508, LOOP_DETECTED, Loop Detected) \
|
||||
XX(510, NOT_EXTENDED, Not Extended) \
|
||||
XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \
|
||||
|
||||
enum http_status
|
||||
{
|
||||
#define XX(num, name, string) HTTP_STATUS_##name = num,
|
||||
HTTP_STATUS_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
/* Request Methods */
|
||||
#define HTTP_METHOD_MAP(XX) \
|
||||
XX(0, DELETE, DELETE) \
|
||||
XX(1, GET, GET) \
|
||||
XX(2, HEAD, HEAD) \
|
||||
XX(3, POST, POST) \
|
||||
XX(4, PUT, PUT) \
|
||||
/* pathological */ \
|
||||
XX(5, CONNECT, CONNECT) \
|
||||
XX(6, OPTIONS, OPTIONS) \
|
||||
XX(7, TRACE, TRACE) \
|
||||
/* WebDAV */ \
|
||||
XX(8, COPY, COPY) \
|
||||
XX(9, LOCK, LOCK) \
|
||||
XX(10, MKCOL, MKCOL) \
|
||||
XX(11, MOVE, MOVE) \
|
||||
XX(12, PROPFIND, PROPFIND) \
|
||||
XX(13, PROPPATCH, PROPPATCH) \
|
||||
XX(14, SEARCH, SEARCH) \
|
||||
XX(15, UNLOCK, UNLOCK) \
|
||||
XX(16, BIND, BIND) \
|
||||
XX(17, REBIND, REBIND) \
|
||||
XX(18, UNBIND, UNBIND) \
|
||||
XX(19, ACL, ACL) \
|
||||
/* subversion */ \
|
||||
XX(20, REPORT, REPORT) \
|
||||
XX(21, MKACTIVITY, MKACTIVITY) \
|
||||
XX(22, CHECKOUT, CHECKOUT) \
|
||||
XX(23, MERGE, MERGE) \
|
||||
/* upnp */ \
|
||||
XX(24, MSEARCH, M-SEARCH) \
|
||||
XX(25, NOTIFY, NOTIFY) \
|
||||
XX(26, SUBSCRIBE, SUBSCRIBE) \
|
||||
XX(27, UNSUBSCRIBE, UNSUBSCRIBE) \
|
||||
/* RFC-5789 */ \
|
||||
XX(28, PATCH, PATCH) \
|
||||
XX(29, PURGE, PURGE) \
|
||||
/* CalDAV */ \
|
||||
XX(30, MKCALENDAR, MKCALENDAR) \
|
||||
/* RFC-2068, section 19.6.1.2 */ \
|
||||
XX(31, LINK, LINK) \
|
||||
XX(32, UNLINK, UNLINK) \
|
||||
|
||||
enum http_method
|
||||
{
|
||||
#define XX(num, name, string) HTTP_##name = num,
|
||||
HTTP_METHOD_MAP(XX)
|
||||
#undef XX
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
|
||||
|
||||
|
||||
/* Flag values for http_parser.flags field */
|
||||
enum flags
|
||||
{ F_CHUNKED = 1 << 0
|
||||
, F_CONNECTION_KEEP_ALIVE = 1 << 1
|
||||
, F_CONNECTION_CLOSE = 1 << 2
|
||||
, F_CONNECTION_UPGRADE = 1 << 3
|
||||
, F_TRAILING = 1 << 4
|
||||
, F_UPGRADE = 1 << 5
|
||||
, F_SKIPBODY = 1 << 6
|
||||
, F_CONTENTLENGTH = 1 << 7
|
||||
};
|
||||
|
||||
|
||||
/* Map for errno-related constants
|
||||
*
|
||||
* The provided argument should be a macro that takes 2 arguments.
|
||||
*/
|
||||
#define HTTP_ERRNO_MAP(XX) \
|
||||
/* No error */ \
|
||||
XX(OK, "success") \
|
||||
\
|
||||
/* Callback-related errors */ \
|
||||
XX(CB_message_begin, "the on_message_begin callback failed") \
|
||||
XX(CB_url, "the on_url callback failed") \
|
||||
XX(CB_header_field, "the on_header_field callback failed") \
|
||||
XX(CB_header_value, "the on_header_value callback failed") \
|
||||
XX(CB_headers_complete, "the on_headers_complete callback failed") \
|
||||
XX(CB_body, "the on_body callback failed") \
|
||||
XX(CB_message_complete, "the on_message_complete callback failed") \
|
||||
XX(CB_status, "the on_status callback failed") \
|
||||
XX(CB_chunk_header, "the on_chunk_header callback failed") \
|
||||
XX(CB_chunk_complete, "the on_chunk_complete callback failed") \
|
||||
\
|
||||
/* Parsing-related errors */ \
|
||||
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
|
||||
XX(HEADER_OVERFLOW, \
|
||||
"too many header bytes seen; overflow detected") \
|
||||
XX(CLOSED_CONNECTION, \
|
||||
"data received after completed connection: close message") \
|
||||
XX(INVALID_VERSION, "invalid HTTP version") \
|
||||
XX(INVALID_STATUS, "invalid HTTP status code") \
|
||||
XX(INVALID_METHOD, "invalid HTTP method") \
|
||||
XX(INVALID_URL, "invalid URL") \
|
||||
XX(INVALID_HOST, "invalid host") \
|
||||
XX(INVALID_PORT, "invalid port") \
|
||||
XX(INVALID_PATH, "invalid path") \
|
||||
XX(INVALID_QUERY_STRING, "invalid query string") \
|
||||
XX(INVALID_FRAGMENT, "invalid fragment") \
|
||||
XX(LF_EXPECTED, "LF character expected") \
|
||||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(UNEXPECTED_CONTENT_LENGTH, \
|
||||
"unexpected content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\
|
||||
XX(STRICT, "strict mode assertion failed") \
|
||||
XX(PAUSED, "parser is paused") \
|
||||
XX(UNKNOWN, "an unknown error occurred")
|
||||
|
||||
|
||||
/* Define HPE_* values for each errno value above */
|
||||
#define HTTP_ERRNO_GEN(n, s) HPE_##n,
|
||||
enum http_errno {
|
||||
HTTP_ERRNO_MAP(HTTP_ERRNO_GEN)
|
||||
};
|
||||
#undef HTTP_ERRNO_GEN
|
||||
|
||||
|
||||
/* Get an http_errno value from an http_parser */
|
||||
#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno)
|
||||
|
||||
|
||||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned int type : 2; /* enum http_parser_type */
|
||||
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned int state : 7; /* enum state from http_parser.c */
|
||||
unsigned int header_state : 7; /* enum header_state from http_parser.c */
|
||||
unsigned int index : 7; /* index into current matcher */
|
||||
unsigned int lenient_http_headers : 1;
|
||||
|
||||
uint32_t nread; /* # bytes read in various scenarios */
|
||||
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
|
||||
|
||||
/** READ-ONLY **/
|
||||
unsigned short http_major;
|
||||
unsigned short http_minor;
|
||||
unsigned int status_code : 16; /* responses only */
|
||||
unsigned int method : 8; /* requests only */
|
||||
unsigned int http_errno : 7;
|
||||
|
||||
/* 1 = Upgrade header was present and the parser has exited because of that.
|
||||
* 0 = No upgrade header present.
|
||||
* Should be checked when http_parser_execute() returns in addition to
|
||||
* error checking.
|
||||
*/
|
||||
unsigned int upgrade : 1;
|
||||
|
||||
/** PUBLIC **/
|
||||
void *data; /* A pointer to get hook to the "connection" or "socket" object */
|
||||
};
|
||||
|
||||
|
||||
struct http_parser_settings {
|
||||
http_cb on_message_begin;
|
||||
http_data_cb on_url;
|
||||
http_data_cb on_status;
|
||||
http_data_cb on_header_field;
|
||||
http_data_cb on_header_value;
|
||||
http_cb on_headers_complete;
|
||||
http_data_cb on_body;
|
||||
http_cb on_message_complete;
|
||||
/* When on_chunk_header is called, the current chunk length is stored
|
||||
* in parser->content_length.
|
||||
*/
|
||||
http_cb on_chunk_header;
|
||||
http_cb on_chunk_complete;
|
||||
};
|
||||
|
||||
|
||||
enum http_parser_url_fields
|
||||
{ UF_SCHEMA = 0
|
||||
, UF_HOST = 1
|
||||
, UF_PORT = 2
|
||||
, UF_PATH = 3
|
||||
, UF_QUERY = 4
|
||||
, UF_FRAGMENT = 5
|
||||
, UF_USERINFO = 6
|
||||
, UF_MAX = 7
|
||||
};
|
||||
|
||||
|
||||
/* Result structure for http_parser_parse_url().
|
||||
*
|
||||
* Callers should index into field_data[] with UF_* values iff field_set
|
||||
* has the relevant (1 << UF_*) bit set. As a courtesy to clients (and
|
||||
* because we probably have padding left over), we convert any port to
|
||||
* a uint16_t.
|
||||
*/
|
||||
struct http_parser_url {
|
||||
uint16_t field_set; /* Bitmask of (1 << UF_*) values */
|
||||
uint16_t port; /* Converted UF_PORT string */
|
||||
|
||||
struct {
|
||||
uint16_t off; /* Offset into buffer in which field starts */
|
||||
uint16_t len; /* Length of run in buffer */
|
||||
} field_data[UF_MAX];
|
||||
};
|
||||
|
||||
|
||||
/* Returns the library version. Bits 16-23 contain the major version number,
|
||||
* bits 8-15 the minor version number and bits 0-7 the patch level.
|
||||
* Usage example:
|
||||
*
|
||||
* unsigned long version = http_parser_version();
|
||||
* unsigned major = (version >> 16) & 255;
|
||||
* unsigned minor = (version >> 8) & 255;
|
||||
* unsigned patch = version & 255;
|
||||
* printf("http_parser v%u.%u.%u\n", major, minor, patch);
|
||||
*/
|
||||
unsigned long http_parser_version(void);
|
||||
|
||||
void http_parser_init(http_parser *parser, enum http_parser_type type);
|
||||
|
||||
|
||||
/* Initialize http_parser_settings members to 0
|
||||
*/
|
||||
void http_parser_settings_init(http_parser_settings *settings);
|
||||
|
||||
|
||||
/* Executes the parser. Returns number of parsed bytes. Sets
|
||||
* `parser->http_errno` on error. */
|
||||
size_t http_parser_execute(http_parser *parser,
|
||||
const http_parser_settings *settings,
|
||||
const char *data,
|
||||
size_t len);
|
||||
|
||||
|
||||
/* If http_should_keep_alive() in the on_headers_complete or
|
||||
* on_message_complete callback returns 0, then this should be
|
||||
* the last message on the connection.
|
||||
* If you are the server, respond with the "Connection: close" header.
|
||||
* If you are the client, close the connection.
|
||||
*/
|
||||
int http_should_keep_alive(const http_parser *parser);
|
||||
|
||||
/* Returns a string version of the HTTP method. */
|
||||
const char *http_method_str(enum http_method m);
|
||||
|
||||
/* Return a string name of the given error */
|
||||
const char *http_errno_name(enum http_errno err);
|
||||
|
||||
/* Return a string description of the given error */
|
||||
const char *http_errno_description(enum http_errno err);
|
||||
|
||||
/* Initialize all http_parser_url members to 0 */
|
||||
void http_parser_url_init(struct http_parser_url *u);
|
||||
|
||||
/* Parse a URL; return nonzero on failure */
|
||||
int http_parser_parse_url(const char *buf, size_t buflen,
|
||||
int is_connect,
|
||||
struct http_parser_url *u);
|
||||
|
||||
/* Pause or un-pause the parser; a nonzero value pauses */
|
||||
void http_parser_pause(http_parser *parser, int paused);
|
||||
|
||||
/* Checks if this is the final chunk of the body. */
|
||||
int http_body_is_final(const http_parser *parser);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
119
src/http/qhttp/private/httpparser.hxx
Normal file
119
src/http/qhttp/private/httpparser.hxx
Normal file
@@ -0,0 +1,119 @@
|
||||
/** @file httpparser.hxx
|
||||
*
|
||||
* @copyright (C) 2016
|
||||
* @date 2016.05.26
|
||||
* @version 1.0.0
|
||||
* @author amir zamani <azadkuh@live.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QHTTP_HTTPPARSER_HXX__
|
||||
#define __QHTTP_HTTPPARSER_HXX__
|
||||
|
||||
#include "qhttpbase.hpp"
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace details {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
/// base HttpParser based on joyent http_parser
|
||||
template<class TImpl>
|
||||
class HttpParser
|
||||
{
|
||||
public:
|
||||
explicit HttpParser(http_parser_type type) {
|
||||
// create http_parser object
|
||||
iparser.data = static_cast<TImpl*>(this);
|
||||
http_parser_init(&iparser, type);
|
||||
|
||||
memset(&iparserSettings, 0, sizeof(http_parser_settings));
|
||||
iparserSettings.on_message_begin = onMessageBegin;
|
||||
iparserSettings.on_url = onUrl;
|
||||
iparserSettings.on_status = onStatus;
|
||||
iparserSettings.on_header_field = onHeaderField;
|
||||
iparserSettings.on_header_value = onHeaderValue;
|
||||
iparserSettings.on_headers_complete = onHeadersComplete;
|
||||
iparserSettings.on_body = onBody;
|
||||
iparserSettings.on_message_complete = onMessageComplete;
|
||||
}
|
||||
|
||||
size_t parse(const char* data, size_t length) {
|
||||
return http_parser_execute(&iparser,
|
||||
&iparserSettings,
|
||||
data,
|
||||
length);
|
||||
}
|
||||
|
||||
public: // callback functions for http_parser_settings
|
||||
static int onMessageBegin(http_parser* p) {
|
||||
return me(p)->messageBegin(p);
|
||||
}
|
||||
|
||||
static int onUrl(http_parser* p, const char* at, size_t length) {
|
||||
return me(p)->url(p, at, length);
|
||||
}
|
||||
|
||||
static int onStatus(http_parser* p, const char* at, size_t length) {
|
||||
return me(p)->status(p, at, length);
|
||||
}
|
||||
|
||||
static int onHeaderField(http_parser* p, const char* at, size_t length) {
|
||||
return me(p)->headerField(p, at, length);
|
||||
}
|
||||
|
||||
static int onHeaderValue(http_parser* p, const char* at, size_t length) {
|
||||
return me(p)->headerValue(p, at, length);
|
||||
}
|
||||
|
||||
static int onHeadersComplete(http_parser* p) {
|
||||
return me(p)->headersComplete(p);
|
||||
}
|
||||
|
||||
static int onBody(http_parser* p, const char* at, size_t length) {
|
||||
return me(p)->body(p, at, length);
|
||||
}
|
||||
|
||||
static int onMessageComplete(http_parser* p) {
|
||||
return me(p)->messageComplete(p);
|
||||
}
|
||||
|
||||
|
||||
protected:
|
||||
// The ones we are reading in from the parser
|
||||
QByteArray itempHeaderField;
|
||||
QByteArray itempHeaderValue;
|
||||
// if connection has a timeout, these fields will be used
|
||||
quint32 itimeOut = 0;
|
||||
QBasicTimer itimer;
|
||||
// uniform socket object
|
||||
QSocket isocket;
|
||||
// if connection should persist
|
||||
bool ikeepAlive = false;
|
||||
|
||||
// joyent http_parser
|
||||
http_parser iparser;
|
||||
http_parser_settings iparserSettings;
|
||||
|
||||
static TImpl* me(http_parser* p) {
|
||||
return static_cast<TImpl*>(p->data);
|
||||
}
|
||||
}; //
|
||||
|
||||
/// basic request parser (server)
|
||||
template<class TImpl>
|
||||
struct HttpRequestParser : public HttpParser<TImpl> {
|
||||
HttpRequestParser() : HttpParser<TImpl>(HTTP_REQUEST) {}
|
||||
};
|
||||
|
||||
/// basic response parser (clinet)
|
||||
template<class TImpl>
|
||||
struct HttpResponseParser : public HttpParser<TImpl> {
|
||||
HttpResponseParser() : HttpParser<TImpl>(HTTP_RESPONSE) {}
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace details
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // __QHTTP_HTTPPARSER_HXX__
|
||||
78
src/http/qhttp/private/httpreader.hxx
Normal file
78
src/http/qhttp/private/httpreader.hxx
Normal file
@@ -0,0 +1,78 @@
|
||||
/** @file httpreader.hxx
|
||||
*
|
||||
* @copyright (C) 2016
|
||||
* @date 2016.05.26
|
||||
* @version 1.0.0
|
||||
* @author amir zamani <azadkuh@live.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QHTTP_HTTPREADER_HXX__
|
||||
#define __QHTTP_HTTPREADER_HXX__
|
||||
|
||||
#include "qhttpbase.hpp"
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace details {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// usage in client::QHttpResponse, server::QHttpRequest
|
||||
template<class TBase>
|
||||
class HttpReader : public TBase
|
||||
{
|
||||
public:
|
||||
enum TReadState {
|
||||
EEmpty,
|
||||
EPartial,
|
||||
EComplete,
|
||||
ESent
|
||||
};
|
||||
|
||||
public:
|
||||
void collectData(int atMost) {
|
||||
icollectRequired = true;
|
||||
icollectCapacity = atMost;
|
||||
icollectedData.clear();
|
||||
if ( atMost > 0 )
|
||||
icollectedData.reserve(atMost);
|
||||
}
|
||||
|
||||
bool append(const char* data, size_t length) {
|
||||
if ( !icollectRequired ) // not allowed to collect data
|
||||
return false;
|
||||
|
||||
int newLength = icollectedData.length() + (int) length;
|
||||
|
||||
if ( icollectCapacity > 0 && newLength > icollectCapacity )
|
||||
return false; // the capacity is full
|
||||
|
||||
icollectedData.append(data, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
// call cb if the message is not finalized yet
|
||||
template<class Func>
|
||||
void finalizeSending(Func cb) {
|
||||
if ( ireadState != EComplete ) {
|
||||
ireadState = EComplete;
|
||||
isuccessful = true;
|
||||
|
||||
cb();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
bool isuccessful = false;
|
||||
TReadState ireadState = EEmpty;
|
||||
|
||||
/// shall I collect incoming body data by myself?
|
||||
bool icollectRequired = false;
|
||||
int icollectCapacity = 0;
|
||||
QByteArray icollectedData;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace details
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // __QHTTP_HTTPREADER_HXX__
|
||||
112
src/http/qhttp/private/httpwriter.hxx
Normal file
112
src/http/qhttp/private/httpwriter.hxx
Normal file
@@ -0,0 +1,112 @@
|
||||
/** @file httpwriter.hxx
|
||||
*
|
||||
* @copyright (C) 2016
|
||||
* @date 2016.05.26
|
||||
* @version 1.0.0
|
||||
* @author amir zamani <azadkuh@live.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QHTTP_HTTPWRITER_HXX__
|
||||
#define __QHTTP_HTTPWRITER_HXX__
|
||||
|
||||
#include "qhttpbase.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace details {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// usage in client::QHttpRequest, server::QHttpResponse
|
||||
template<class TBase, class TImpl>
|
||||
class HttpWriter : public TBase
|
||||
{
|
||||
public:
|
||||
bool addHeader(const QByteArray &field, const QByteArray &value) {
|
||||
if ( ifinished )
|
||||
return false;
|
||||
|
||||
TBase::iheaders.insert(field.toLower(), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeHeader(const QByteArray& field, const QByteArray& value) {
|
||||
if ( ifinished )
|
||||
return false;
|
||||
|
||||
QByteArray buffer = QByteArray(field)
|
||||
.append(": ")
|
||||
.append(value)
|
||||
.append("\r\n");
|
||||
|
||||
isocket.writeRaw(buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeData(const QByteArray& data) {
|
||||
if ( ifinished )
|
||||
return false;
|
||||
|
||||
ensureWritingHeaders();
|
||||
isocket.writeRaw(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool endPacket(const QByteArray& data) {
|
||||
if ( !writeData(data) )
|
||||
return false;
|
||||
|
||||
isocket.flush();
|
||||
ifinished = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void ensureWritingHeaders() {
|
||||
if ( ifinished || iheaderWritten )
|
||||
return;
|
||||
|
||||
TImpl* me = static_cast<TImpl*>(this);
|
||||
isocket.writeRaw(me->makeTitle());
|
||||
writeHeaders();
|
||||
|
||||
iheaderWritten = true;
|
||||
}
|
||||
|
||||
void writeHeaders(bool doFlush = false) {
|
||||
if ( ifinished || iheaderWritten )
|
||||
return;
|
||||
|
||||
if ( TBase::iheaders.keyHasValue("connection", "keep-alive") )
|
||||
ikeepAlive = true;
|
||||
else
|
||||
TBase::iheaders.insert("connection", "close");
|
||||
|
||||
TImpl* me = static_cast<TImpl*>(this);
|
||||
me->prepareHeadersToWrite();
|
||||
|
||||
for ( auto cit = TBase::iheaders.constBegin(); cit != TBase::iheaders.constEnd(); cit++ ) {
|
||||
const QByteArray& field = cit.key();
|
||||
const QByteArray& value = cit.value();
|
||||
|
||||
writeHeader(field, value);
|
||||
}
|
||||
|
||||
isocket.writeRaw("\r\n");
|
||||
if ( doFlush )
|
||||
isocket.flush();
|
||||
}
|
||||
|
||||
public:
|
||||
QSocket isocket;
|
||||
|
||||
bool ifinished = false;
|
||||
bool iheaderWritten = false;
|
||||
bool ikeepAlive = false;
|
||||
};
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace details
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // __QHTTP_HTTPWRITER_HXX__
|
||||
52
src/http/qhttp/private/qhttpbase.hpp
Normal file
52
src/http/qhttp/private/qhttpbase.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
/** base classes for private implementations.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPBASE_HPP
|
||||
#define QHTTPBASE_HPP
|
||||
|
||||
#include "qhttpfwd.hpp"
|
||||
|
||||
#include "qsocket.hpp"
|
||||
#include <QHostAddress>
|
||||
#include <QBasicTimer>
|
||||
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace details {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct HttpBase
|
||||
{
|
||||
QString iversion;
|
||||
THeaderHash iheaders;
|
||||
}; // struct HttpBase
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct HttpRequestBase : public HttpBase
|
||||
{
|
||||
QUrl iurl;
|
||||
THttpMethod imethod;
|
||||
}; // HttpRequestBase
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct HttpResponseBase : public HttpBase
|
||||
{
|
||||
TStatusCode istatus = ESTATUS_BAD_REQUEST;
|
||||
|
||||
HttpResponseBase() { iversion = "1.1"; }
|
||||
}; // HttpResponseBase
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace details
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // QHTTPBASE_HPP
|
||||
222
src/http/qhttp/private/qhttpclient_private.hpp
Normal file
222
src/http/qhttp/private/qhttpclient_private.hpp
Normal file
@@ -0,0 +1,222 @@
|
||||
/** private imeplementation.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPCLIENT_PRIVATE_HPP
|
||||
#define QHTTPCLIENT_PRIVATE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "qhttpclient.hpp"
|
||||
#include "httpparser.hxx"
|
||||
#include "qhttpclientrequest_private.hpp"
|
||||
#include "qhttpclientresponse_private.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class QHttpClientPrivate :
|
||||
public details::HttpResponseParser<QHttpClientPrivate>
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QHttpClient)
|
||||
|
||||
public:
|
||||
explicit QHttpClientPrivate(QHttpClient* q) : q_ptr(q) {
|
||||
QObject::connect(
|
||||
q_func(), &QHttpClient::disconnected,
|
||||
[this](){ release(); }
|
||||
);
|
||||
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
virtual ~QHttpClientPrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
void release() {
|
||||
// if socket drops and http_parser can not call messageComplete,
|
||||
// dispatch the ilastResponse
|
||||
finalizeConnection();
|
||||
|
||||
isocket.disconnectAllQtConnections();
|
||||
isocket.release();
|
||||
|
||||
if ( ilastRequest ) {
|
||||
ilastRequest->deleteLater();
|
||||
ilastRequest = nullptr;
|
||||
}
|
||||
|
||||
if ( ilastResponse ) {
|
||||
ilastResponse->deleteLater();
|
||||
ilastResponse = nullptr;
|
||||
}
|
||||
|
||||
// must be called! or the later http_parser_execute() may fail
|
||||
http_parser_init(&iparser, HTTP_RESPONSE);
|
||||
}
|
||||
|
||||
void initializeSocket() {
|
||||
// no need to reconnect. do nothing and simply return
|
||||
if ( ikeepAlive )
|
||||
return;
|
||||
|
||||
// close previous connection now!
|
||||
// instead being called by emitted disconnected signal
|
||||
release();
|
||||
|
||||
ikeepAlive = false;
|
||||
|
||||
// create a tcp connection
|
||||
if ( isocket.ibackendType == ETcpSocket ) {
|
||||
initTcpSocket();
|
||||
|
||||
} else if ( isocket.ibackendType == ESslSocket ) {
|
||||
initSslSocket();
|
||||
|
||||
} else if ( isocket.ibackendType == ELocalSocket ) {
|
||||
initLocalSocket();
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
int messageBegin(http_parser* parser);
|
||||
int url(http_parser*, const char*, size_t) {
|
||||
return 0; // not used in parsing incoming response.
|
||||
}
|
||||
int status(http_parser* parser, const char* at, size_t length) ;
|
||||
int headerField(http_parser* parser, const char* at, size_t length);
|
||||
int headerValue(http_parser* parser, const char* at, size_t length);
|
||||
int headersComplete(http_parser* parser);
|
||||
int body(http_parser* parser, const char* at, size_t length);
|
||||
int messageComplete(http_parser* parser);
|
||||
|
||||
protected:
|
||||
void onConnected() {
|
||||
iconnectingTimer.stop();
|
||||
|
||||
if ( itimeOut > 0 )
|
||||
itimer.start(itimeOut, Qt::CoarseTimer, q_func());
|
||||
|
||||
if ( ireqHandler )
|
||||
ireqHandler(ilastRequest);
|
||||
else
|
||||
q_func()->onRequestReady(ilastRequest);
|
||||
}
|
||||
|
||||
void onReadyRead() {
|
||||
while ( isocket.bytesAvailable() > 0 ) {
|
||||
char buffer[4097] = {0};
|
||||
size_t readLength = (size_t) isocket.readRaw(buffer, 4096);
|
||||
|
||||
parse(buffer, readLength);
|
||||
}
|
||||
}
|
||||
|
||||
void finalizeConnection() {
|
||||
if ( ilastResponse == nullptr )
|
||||
return;
|
||||
|
||||
ilastResponse->d_func()->finalizeSending([this]{
|
||||
emit ilastResponse->end();
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
void initTcpSocket() {
|
||||
QTcpSocket* sok = new QTcpSocket(q_func());
|
||||
isocket.itcpSocket = sok;
|
||||
|
||||
QObject::connect(
|
||||
sok, &QTcpSocket::connected,
|
||||
[this](){ onConnected(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QTcpSocket::readyRead,
|
||||
[this](){ onReadyRead(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QTcpSocket::bytesWritten,
|
||||
[this](qint64){
|
||||
const auto& ts = isocket.itcpSocket;
|
||||
if ( ts->bytesToWrite() == 0 && ilastRequest )
|
||||
emit ilastRequest->allBytesWritten();
|
||||
});
|
||||
QObject::connect(
|
||||
sok, &QTcpSocket::disconnected,
|
||||
q_func(), &QHttpClient::disconnected
|
||||
);
|
||||
}
|
||||
|
||||
void initSslSocket() {
|
||||
QSslSocket* sok = new QSslSocket(q_func());
|
||||
isocket.itcpSocket = sok;
|
||||
|
||||
QObject::connect(
|
||||
sok, &QSslSocket::encrypted,
|
||||
[this](){ onConnected(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QSslSocket::readyRead,
|
||||
[this](){ onReadyRead(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QSslSocket::bytesWritten,
|
||||
[this](qint64){
|
||||
const auto& ts = isocket.itcpSocket;
|
||||
if ( ts->bytesToWrite() == 0 && ilastRequest )
|
||||
emit ilastRequest->allBytesWritten();
|
||||
});
|
||||
QObject::connect(
|
||||
sok, &QSslSocket::disconnected,
|
||||
q_func(), &QHttpClient::disconnected
|
||||
);
|
||||
}
|
||||
|
||||
void initLocalSocket() {
|
||||
QLocalSocket* sok = new QLocalSocket(q_func());
|
||||
isocket.ilocalSocket = sok;
|
||||
|
||||
QObject::connect(
|
||||
sok, &QLocalSocket::connected,
|
||||
[this](){ onConnected(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QLocalSocket::readyRead,
|
||||
[this](){ onReadyRead(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QLocalSocket::bytesWritten,
|
||||
[this](qint64){
|
||||
const auto* ls = isocket.ilocalSocket;
|
||||
if ( ls->bytesToWrite() == 0 && ilastRequest )
|
||||
emit ilastRequest->allBytesWritten();
|
||||
});
|
||||
QObject::connect(
|
||||
sok, &QLocalSocket::disconnected,
|
||||
q_func(), &QHttpClient::disconnected
|
||||
);
|
||||
}
|
||||
|
||||
protected:
|
||||
QHttpClient* const q_ptr;
|
||||
|
||||
QHttpRequest* ilastRequest = nullptr;
|
||||
QHttpResponse* ilastResponse = nullptr;
|
||||
TRequstHandler ireqHandler;
|
||||
TResponseHandler irespHandler;
|
||||
|
||||
QBasicTimer iconnectingTimer;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif // QHTTPCLIENT_PRIVATE_HPP
|
||||
57
src/http/qhttp/private/qhttpclientrequest_private.hpp
Normal file
57
src/http/qhttp/private/qhttpclientrequest_private.hpp
Normal file
@@ -0,0 +1,57 @@
|
||||
/** private imeplementation.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPCLIENT_REQUEST_PRIVATE_HPP
|
||||
#define QHTTPCLIENT_REQUEST_PRIVATE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include "httpwriter.hxx"
|
||||
#include "qhttpclient.hpp"
|
||||
#include "qhttpclientrequest.hpp"
|
||||
|
||||
#include <QTcpSocket>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class QHttpRequestPrivate :
|
||||
public details::HttpWriter<details::HttpRequestBase, QHttpRequestPrivate>
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QHttpRequest)
|
||||
|
||||
public:
|
||||
explicit QHttpRequestPrivate(QHttpClient* cli, QHttpRequest* q) : q_ptr(q), iclient(cli) {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
virtual ~QHttpRequestPrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
iversion = "1.1";
|
||||
|
||||
isocket.ibackendType = iclient->backendType();
|
||||
isocket.itcpSocket = iclient->tcpSocket();
|
||||
isocket.ilocalSocket = iclient->localSocket();
|
||||
}
|
||||
|
||||
QByteArray makeTitle();
|
||||
|
||||
void prepareHeadersToWrite();
|
||||
|
||||
protected:
|
||||
QHttpRequest* const q_ptr;
|
||||
QHttpClient* const iclient;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // QHTTPCLIENT_REQUEST_PRIVATE_HPP
|
||||
51
src/http/qhttp/private/qhttpclientresponse_private.hpp
Normal file
51
src/http/qhttp/private/qhttpclientresponse_private.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/** private imeplementation.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPCLIENT_RESPONSE_PRIVATE_HPP
|
||||
#define QHTTPCLIENT_RESPONSE_PRIVATE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "httpreader.hxx"
|
||||
#include "qhttpclient.hpp"
|
||||
#include "qhttpclientresponse.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class QHttpResponsePrivate :
|
||||
public details::HttpReader<details::HttpResponseBase>
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QHttpResponse)
|
||||
QHttpResponse* const q_ptr;
|
||||
|
||||
public:
|
||||
explicit QHttpResponsePrivate(QHttpClient* cli, QHttpResponse* q)
|
||||
: q_ptr(q), iclient(cli) {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
virtual ~QHttpResponsePrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
}
|
||||
|
||||
public:
|
||||
QString icustomStatusMessage;
|
||||
|
||||
protected:
|
||||
QHttpClient* const iclient;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // QHTTPCLIENT_RESPONSE_PRIVATE_HPP
|
||||
90
src/http/qhttp/private/qhttpserver_private.hpp
Normal file
90
src/http/qhttp/private/qhttpserver_private.hpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/** private imeplementation.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPSERVER_PRIVATE_HPP
|
||||
#define QHTTPSERVER_PRIVATE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "qhttpserver.hpp"
|
||||
#include "qhttpserverconnection.hpp"
|
||||
#include "qhttpserverrequest.hpp"
|
||||
#include "qhttpserverresponse.hpp"
|
||||
|
||||
#include <QTcpServer>
|
||||
#include <QLocalServer>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
class QHttpServerPrivate
|
||||
{
|
||||
public:
|
||||
template<class TServer>
|
||||
class BackendServer : public TServer
|
||||
{
|
||||
public:
|
||||
QHttpServer* iserver;
|
||||
|
||||
explicit BackendServer(QHttpServer* s) : TServer(s), iserver(s) {
|
||||
}
|
||||
|
||||
protected:
|
||||
// if it's a QTcpServer
|
||||
virtual void incomingConnection(qintptr socketDescriptor) {
|
||||
iserver->incomingConnection(socketDescriptor);
|
||||
}
|
||||
|
||||
// if it's a QLocalServer
|
||||
virtual void incomingConnection(quintptr socketDescriptor) {
|
||||
iserver->incomingConnection((qintptr) socketDescriptor);
|
||||
}
|
||||
};
|
||||
|
||||
using TTcpServer = QScopedPointer<BackendServer<QTcpServer>>;
|
||||
using TLocalServer = QScopedPointer<BackendServer<QLocalServer>>;
|
||||
|
||||
public:
|
||||
quint32 itimeOut = 0;
|
||||
TServerHandler ihandler = nullptr;
|
||||
|
||||
TBackend ibackend = ETcpSocket;
|
||||
|
||||
TTcpServer itcpServer;
|
||||
TLocalServer ilocalServer;
|
||||
|
||||
public:
|
||||
explicit QHttpServerPrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
virtual ~QHttpServerPrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
void initialize(TBackend backend, QHttpServer* parent) {
|
||||
ibackend = backend;
|
||||
|
||||
if ( ibackend == ETcpSocket || ibackend == ESslSocket ) {
|
||||
itcpServer.reset( new BackendServer<QTcpServer>(parent) );
|
||||
ilocalServer.reset( nullptr );
|
||||
|
||||
} else if ( ibackend == ELocalSocket ) {
|
||||
itcpServer.reset( nullptr );
|
||||
ilocalServer.reset( new BackendServer<QLocalServer>(parent) );
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#endif // QHTTPSERVER_PRIVATE_HPP
|
||||
201
src/http/qhttp/private/qhttpserverconnection_private.hpp
Normal file
201
src/http/qhttp/private/qhttpserverconnection_private.hpp
Normal file
@@ -0,0 +1,201 @@
|
||||
/** private imeplementation.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPSERVER_CONNECTION_PRIVATE_HPP
|
||||
#define QHTTPSERVER_CONNECTION_PRIVATE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "qhttpserverconnection.hpp"
|
||||
#include "httpparser.hxx"
|
||||
#include "qhttpserverrequest.hpp"
|
||||
#include "qhttpserverresponse.hpp"
|
||||
|
||||
#include "private/qhttpserverrequest_private.hpp"
|
||||
#include "private/qhttpserverresponse_private.hpp"
|
||||
|
||||
#include <QBasicTimer>
|
||||
#include <QFile>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class QHttpConnectionPrivate :
|
||||
public details::HttpRequestParser<QHttpConnectionPrivate>
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QHttpConnection)
|
||||
|
||||
public:
|
||||
explicit QHttpConnectionPrivate(QHttpConnection* q) : q_ptr(q) {
|
||||
|
||||
QObject::connect(
|
||||
q_func(), &QHttpConnection::disconnected,
|
||||
[this](){ release(); }
|
||||
);
|
||||
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
virtual ~QHttpConnectionPrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
void createSocket(qintptr sokDesc, TBackend bend) {
|
||||
isocket.ibackendType = bend;
|
||||
|
||||
if ( bend == ETcpSocket ) {
|
||||
initTcpSocket(sokDesc);
|
||||
|
||||
} else if ( bend == ESslSocket) {
|
||||
initSslSocket(sokDesc);
|
||||
|
||||
} else if ( bend == ELocalSocket ) {
|
||||
initLocalSocket(sokDesc);
|
||||
}
|
||||
}
|
||||
|
||||
void release() {
|
||||
// if socket drops and http_parser can not call
|
||||
// messageComplete, dispatch the ilastRequest
|
||||
finalizeConnection();
|
||||
|
||||
isocket.disconnectAllQtConnections();
|
||||
isocket.release();
|
||||
|
||||
if ( ilastRequest ) {
|
||||
ilastRequest->deleteLater();
|
||||
ilastRequest = nullptr;
|
||||
}
|
||||
|
||||
if ( ilastResponse ) {
|
||||
ilastResponse->deleteLater();
|
||||
ilastResponse = nullptr;
|
||||
}
|
||||
|
||||
q_func()->deleteLater();
|
||||
}
|
||||
|
||||
public:
|
||||
void onReadyRead() {
|
||||
while ( isocket.bytesAvailable() > 0 ) {
|
||||
char buffer[4097] = {0};
|
||||
size_t readLength = (size_t) isocket.readRaw(buffer, 4096);
|
||||
|
||||
parse(buffer, readLength);
|
||||
}
|
||||
}
|
||||
|
||||
void finalizeConnection() {
|
||||
if ( ilastRequest == nullptr )
|
||||
return;
|
||||
|
||||
ilastRequest->d_func()->finalizeSending([this]{
|
||||
emit ilastRequest->end();
|
||||
});
|
||||
}
|
||||
|
||||
public:
|
||||
int messageBegin(http_parser* parser);
|
||||
int url(http_parser* parser, const char* at, size_t length);
|
||||
int status(http_parser*, const char*, size_t) {
|
||||
return 0; // not used in parsing incoming request.
|
||||
}
|
||||
int headerField(http_parser* parser, const char* at, size_t length);
|
||||
int headerValue(http_parser* parser, const char* at, size_t length);
|
||||
int headersComplete(http_parser* parser);
|
||||
int body(http_parser* parser, const char* at, size_t length);
|
||||
int messageComplete(http_parser* parser);
|
||||
|
||||
private:
|
||||
void initTcpSocket(qintptr sokDesc) {
|
||||
QTcpSocket* sok = new QTcpSocket( q_func() );
|
||||
isocket.itcpSocket = sok;
|
||||
sok->setSocketDescriptor(sokDesc);
|
||||
|
||||
QObject::connect(
|
||||
sok, &QTcpSocket::readyRead,
|
||||
[this](){ onReadyRead(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QTcpSocket::bytesWritten,
|
||||
[this](){
|
||||
auto btw = isocket.itcpSocket->bytesToWrite();
|
||||
if ( btw == 0 && ilastResponse )
|
||||
emit ilastResponse->allBytesWritten();
|
||||
});
|
||||
QObject::connect(
|
||||
sok, &QTcpSocket::disconnected,
|
||||
q_func(), &QHttpConnection::disconnected,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
}
|
||||
|
||||
void initSslSocket(qintptr sokDesc) {
|
||||
QSslSocket* sok = new QSslSocket( q_func() );
|
||||
isocket.itcpSocket = sok;
|
||||
sok->setSocketDescriptor(sokDesc);
|
||||
|
||||
QObject::connect(
|
||||
sok, &QSslSocket::readyRead,
|
||||
[this](){ onReadyRead(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QSslSocket::bytesWritten,
|
||||
[this](){
|
||||
auto btw = isocket.itcpSocket->bytesToWrite();
|
||||
if ( btw == 0 && ilastResponse )
|
||||
emit ilastResponse->allBytesWritten();
|
||||
});
|
||||
QObject::connect(
|
||||
sok, &QSslSocket::disconnected,
|
||||
q_func(), &QHttpConnection::disconnected,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
}
|
||||
|
||||
void initLocalSocket(qintptr sokDesc) {
|
||||
QLocalSocket* sok = new QLocalSocket( q_func() );
|
||||
isocket.ilocalSocket = sok;
|
||||
sok->setSocketDescriptor(sokDesc);
|
||||
|
||||
QObject::connect(
|
||||
sok, &QLocalSocket::readyRead,
|
||||
[this](){ onReadyRead(); }
|
||||
);
|
||||
QObject::connect(
|
||||
sok, &QLocalSocket::bytesWritten,
|
||||
[this](){
|
||||
auto btw = isocket.ilocalSocket->bytesToWrite();
|
||||
if ( btw == 0 && ilastResponse )
|
||||
emit ilastResponse->allBytesWritten();
|
||||
});
|
||||
QObject::connect(
|
||||
sok, &QLocalSocket::disconnected,
|
||||
q_func(), &QHttpConnection::disconnected,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
}
|
||||
|
||||
protected:
|
||||
QHttpConnection* const q_ptr;
|
||||
|
||||
QByteArray itempUrl;
|
||||
|
||||
// Since there can only be one request/response pair per connection at any
|
||||
// time even with pipelining.
|
||||
QHttpRequest* ilastRequest = nullptr;
|
||||
QHttpResponse* ilastResponse = nullptr;
|
||||
|
||||
TServerHandler ihandler = nullptr;
|
||||
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // QHTTPSERVER_CONNECTION_PRIVATE_HPP
|
||||
51
src/http/qhttp/private/qhttpserverrequest_private.hpp
Normal file
51
src/http/qhttp/private/qhttpserverrequest_private.hpp
Normal file
@@ -0,0 +1,51 @@
|
||||
/** private imeplementation.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPSERVER_REQUEST_PRIVATE_HPP
|
||||
#define QHTTPSERVER_REQUEST_PRIVATE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "httpreader.hxx"
|
||||
#include "qhttpserverrequest.hpp"
|
||||
#include "qhttpserverconnection.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class QHttpRequestPrivate :
|
||||
public details::HttpReader<details::HttpRequestBase>
|
||||
{
|
||||
protected:
|
||||
Q_DECLARE_PUBLIC(QHttpRequest)
|
||||
QHttpRequest* const q_ptr;
|
||||
|
||||
public:
|
||||
explicit QHttpRequestPrivate(QHttpConnection* conn, QHttpRequest* q) : q_ptr(q), iconnection(conn) {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
virtual ~QHttpRequestPrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
}
|
||||
|
||||
public:
|
||||
QString iremoteAddress;
|
||||
quint16 iremotePort = 0;
|
||||
|
||||
QHttpConnection* const iconnection = nullptr;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // QHTTPSERVER_REQUEST_PRIVATE_HPP
|
||||
62
src/http/qhttp/private/qhttpserverresponse_private.hpp
Normal file
62
src/http/qhttp/private/qhttpserverresponse_private.hpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/** private imeplementation.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPSERVER_RESPONSE_PRIVATE_HPP
|
||||
#define QHTTPSERVER_RESPONSE_PRIVATE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include "httpwriter.hxx"
|
||||
#include "qhttpserverresponse.hpp"
|
||||
#include "qhttpserver.hpp"
|
||||
#include "qhttpserverconnection.hpp"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QLocale>
|
||||
#include <QTcpSocket>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class QHttpResponsePrivate :
|
||||
public details::HttpWriter<details::HttpResponseBase, QHttpResponsePrivate>
|
||||
{
|
||||
Q_DECLARE_PUBLIC(QHttpResponse)
|
||||
|
||||
public:
|
||||
explicit QHttpResponsePrivate(QHttpConnection* conn, QHttpResponse* q)
|
||||
: q_ptr(q), iconnection(conn) {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
virtual ~QHttpResponsePrivate() {
|
||||
QHTTP_LINE_DEEPLOG
|
||||
}
|
||||
|
||||
void initialize() {
|
||||
isocket.ibackendType = iconnection->backendType();
|
||||
isocket.ilocalSocket = iconnection->localSocket();
|
||||
isocket.itcpSocket = iconnection->tcpSocket();
|
||||
|
||||
QObject::connect(iconnection, &QHttpConnection::disconnected,
|
||||
q_func(), &QHttpResponse::deleteLater);
|
||||
}
|
||||
|
||||
QByteArray makeTitle();
|
||||
|
||||
void prepareHeadersToWrite();
|
||||
|
||||
protected:
|
||||
QHttpResponse* const q_ptr;
|
||||
QHttpConnection* const iconnection;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // QHTTPSERVER_RESPONSE_PRIVATE_HPP
|
||||
131
src/http/qhttp/private/qsocket.hpp
Normal file
131
src/http/qhttp/private/qsocket.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/** @file qsocket.hpp
|
||||
*
|
||||
* @copyright (C) 2016
|
||||
* @date 2016.05.26
|
||||
* @version 1.0.0
|
||||
* @author amir zamani <azadkuh@live.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __QHTTP_SOCKET_HPP__
|
||||
#define __QHTTP_SOCKET_HPP__
|
||||
|
||||
#include "qhttpfwd.hpp"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QSslSocket>
|
||||
#include <QLocalSocket>
|
||||
#include <QUrl>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace details {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** an adapter for different socket types.
|
||||
* the main purpose of QHttp was to create a small HTTP server with ability to
|
||||
* support UNIX sockets (QLocalSocket)
|
||||
*/
|
||||
class QSocket
|
||||
{
|
||||
public:
|
||||
void close() {
|
||||
if ( itcpSocket )
|
||||
itcpSocket->close();
|
||||
|
||||
if ( ilocalSocket )
|
||||
ilocalSocket->close();
|
||||
}
|
||||
|
||||
void release() {
|
||||
close();
|
||||
if ( itcpSocket )
|
||||
itcpSocket->deleteLater();
|
||||
|
||||
if ( ilocalSocket )
|
||||
ilocalSocket->deleteLater();
|
||||
|
||||
itcpSocket = nullptr;
|
||||
ilocalSocket = nullptr;
|
||||
}
|
||||
|
||||
void flush() {
|
||||
if ( itcpSocket )
|
||||
itcpSocket->flush();
|
||||
|
||||
else if ( ilocalSocket )
|
||||
ilocalSocket->flush();
|
||||
}
|
||||
|
||||
bool isOpen() const {
|
||||
if ( ibackendType == ETcpSocket && itcpSocket )
|
||||
return itcpSocket->isOpen()
|
||||
&& itcpSocket->state() == QTcpSocket::ConnectedState;
|
||||
|
||||
else if ( ibackendType == ELocalSocket && ilocalSocket )
|
||||
return ilocalSocket->isOpen()
|
||||
&& ilocalSocket->state() == QLocalSocket::ConnectedState;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void connectTo(const QUrl& url) {
|
||||
if ( ilocalSocket )
|
||||
ilocalSocket->connectToServer(url.path());
|
||||
}
|
||||
|
||||
void connectTo(const QString& host, quint16 port) {
|
||||
if ( itcpSocket ) {
|
||||
if ( ibackendType == ESslSocket )
|
||||
static_cast<QSslSocket*>(itcpSocket)->connectToHostEncrypted(host, port);
|
||||
else
|
||||
itcpSocket->connectToHost(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
qint64 readRaw(char* buffer, int maxlen) {
|
||||
if ( itcpSocket )
|
||||
return itcpSocket->read(buffer, maxlen);
|
||||
|
||||
else if ( ilocalSocket )
|
||||
return ilocalSocket->read(buffer, maxlen);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void writeRaw(const QByteArray& data) {
|
||||
if ( itcpSocket )
|
||||
itcpSocket->write(data);
|
||||
|
||||
else if ( ilocalSocket )
|
||||
ilocalSocket->write(data);
|
||||
}
|
||||
|
||||
qint64 bytesAvailable() {
|
||||
if ( itcpSocket )
|
||||
return itcpSocket->bytesAvailable();
|
||||
|
||||
else if ( ilocalSocket )
|
||||
return ilocalSocket->bytesAvailable();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void disconnectAllQtConnections() {
|
||||
if ( itcpSocket )
|
||||
QObject::disconnect(itcpSocket, 0, 0, 0);
|
||||
|
||||
if ( ilocalSocket )
|
||||
QObject::disconnect(ilocalSocket, 0, 0, 0);
|
||||
}
|
||||
|
||||
public:
|
||||
TBackend ibackendType = ETcpSocket;
|
||||
QTcpSocket* itcpSocket = nullptr;
|
||||
QLocalSocket* ilocalSocket = nullptr;
|
||||
}; // class QSocket
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace details
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // __QHTTP_SOCKET_HPP__
|
||||
114
src/http/qhttp/qhttpabstracts.cpp
Normal file
114
src/http/qhttp/qhttpabstracts.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "qhttpabstracts.hpp"
|
||||
#include "http-parser/http_parser.h"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
|
||||
# error "to compile QHttp classes, Qt 5.0 or later is needed."
|
||||
#endif
|
||||
|
||||
#define HTTP_STATUS_MAP(XX) \
|
||||
XX(100, "Continue") \
|
||||
XX(101, "Switching Protocols") \
|
||||
/* RFC 2518) obsoleted by RFC 4918 */ \
|
||||
XX(102, "Processing") \
|
||||
XX(200, "OK") \
|
||||
XX(201, "Created") \
|
||||
XX(202, "Accepted") \
|
||||
XX(203, "Non-Authoritative Information") \
|
||||
XX(204, "No Content") \
|
||||
XX(205, "Reset Content") \
|
||||
XX(206, "Partial Content") \
|
||||
/* RFC 4918 */ \
|
||||
XX(207, "Multi-Status") \
|
||||
XX(300, "Multiple Choices") \
|
||||
XX(301, "Moved Permanently") \
|
||||
XX(302, "Moved Temporarily") \
|
||||
XX(303, "See Other") \
|
||||
XX(304, "Not Modified") \
|
||||
XX(305, "Use Proxy") \
|
||||
XX(307, "Temporary Redirect") \
|
||||
XX(400, "Bad Request") \
|
||||
XX(401, "Unauthorized") \
|
||||
XX(402, "Payment Required") \
|
||||
XX(403, "Forbidden") \
|
||||
XX(404, "Not Found") \
|
||||
XX(405, "Method Not Allowed") \
|
||||
XX(406, "Not Acceptable") \
|
||||
XX(407, "Proxy Authentication Required") \
|
||||
XX(408, "Request Time-out") \
|
||||
XX(409, "Conflict") \
|
||||
XX(410, "Gone") \
|
||||
XX(411, "Length Required") \
|
||||
XX(412, "Precondition Failed") \
|
||||
XX(413, "Request Entity Too Large") \
|
||||
XX(414, "Request-URI Too Large") \
|
||||
XX(415, "Unsupported Media Type") \
|
||||
XX(416, "Requested Range Not Satisfiable") \
|
||||
XX(417, "Expectation Failed") \
|
||||
/* RFC 2324 */ \
|
||||
XX(418, "I\"m a teapot") \
|
||||
/* RFC 4918 */ \
|
||||
XX(422, "Unprocessable Entity") \
|
||||
/* RFC 4918 */ \
|
||||
XX(423, "Locked") \
|
||||
/* RFC 4918 */ \
|
||||
XX(424, "Failed Dependency") \
|
||||
/* RFC 4918 */ \
|
||||
XX(425, "Unordered Collection") \
|
||||
/* RFC 2817 */ \
|
||||
XX(426, "Upgrade Required") \
|
||||
XX(500, "Internal Server Error") \
|
||||
XX(501, "Not Implemented") \
|
||||
XX(502, "Bad Gateway") \
|
||||
XX(503, "Service Unavailable") \
|
||||
XX(504, "Gateway Time-out") \
|
||||
XX(505, "HTTP Version not supported") \
|
||||
/* RFC 2295 */ \
|
||||
XX(506, "Variant Also Negotiates") \
|
||||
/* RFC 4918 */ \
|
||||
XX(507, "Insufficient Storage") \
|
||||
XX(509, "Bandwidth Limit Exceeded") \
|
||||
/* RFC 2774 */ \
|
||||
XX(510, "Not Extended")
|
||||
|
||||
#define PATCH_STATUS_CODES(n,s) {n, s},
|
||||
static struct {
|
||||
int code;
|
||||
const char* message;
|
||||
} g_status_codes[] {
|
||||
HTTP_STATUS_MAP(PATCH_STATUS_CODES)
|
||||
};
|
||||
#undef PATCH_STATUS_CODES
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const char*
|
||||
Stringify::toString(TStatusCode code) {
|
||||
size_t count = sizeof(g_status_codes) / sizeof(g_status_codes[0]);
|
||||
for ( size_t i = 0; i < count; i++ ) {
|
||||
if ( g_status_codes[i].code == code )
|
||||
return g_status_codes[i].message;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char*
|
||||
Stringify::toString(THttpMethod method) {
|
||||
return http_method_str(static_cast<http_method>(method));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
QHttpAbstractInput::QHttpAbstractInput(QObject* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
QHttpAbstractOutput::QHttpAbstractOutput(QObject *parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
190
src/http/qhttp/qhttpabstracts.hpp
Normal file
190
src/http/qhttp/qhttpabstracts.hpp
Normal file
@@ -0,0 +1,190 @@
|
||||
/** interfaces of QHttp' incomming and outgoing classes.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPABSTRACTS_HPP
|
||||
#define QHTTPABSTRACTS_HPP
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include "qhttpfwd.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <functional>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** a utility class to give the string representation of qhttp types. */
|
||||
class QHTTP_API Stringify
|
||||
{
|
||||
public:
|
||||
/** returns the standard message for an HTTP status code. */
|
||||
static const char* toString(TStatusCode);
|
||||
|
||||
/** returns the standars name of an HTTP method. */
|
||||
static const char* toString(THttpMethod);
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** an interface for input (incoming) HTTP packets.
|
||||
* server::QHttpRequest or client::QHttpResponse inherit from this class. */
|
||||
class QHTTP_API QHttpAbstractInput : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/** Return all the headers in the incomming packet.
|
||||
* This returns a reference. If you want to store headers
|
||||
* somewhere else, where the request may be deleted,
|
||||
* make sure you store them as a copy.
|
||||
* @note All header names are <b>lowercase</b> . */
|
||||
virtual const THeaderHash& headers() const=0;
|
||||
|
||||
/** The HTTP version of the packet.
|
||||
* @return A string in the form of "x.x" */
|
||||
virtual const QString& httpVersion() const=0;
|
||||
|
||||
/** If this packet was successfully received.
|
||||
* Set before end() has been emitted, stating whether
|
||||
* the message was properly received. This is false
|
||||
* until the receiving the full request has completed. */
|
||||
virtual bool isSuccessful() const=0;
|
||||
|
||||
signals:
|
||||
/** Emitted when new body data has been received.
|
||||
* @param data Received data.
|
||||
* @note This may be emitted zero or more times depending on the transfer type.
|
||||
* @see onData();
|
||||
*/
|
||||
void data(QByteArray data);
|
||||
|
||||
/** Emitted when the incomming packet has been fully received.
|
||||
* @note The no more data() signals will be emitted after this.
|
||||
* @see onEnd();
|
||||
*/
|
||||
void end();
|
||||
|
||||
public:
|
||||
/** optionally set a handler for data() signal.
|
||||
* @param dataHandler a std::function or lambda handler to receive incoming data.
|
||||
* @note if you set this handler, the data() signal won't be emitted anymore.
|
||||
*/
|
||||
template<class Func>
|
||||
void onData(Func f) {
|
||||
QObject::connect(this, &QHttpAbstractInput::data, f);
|
||||
}
|
||||
|
||||
|
||||
/** optionally set a handler for end() signal.
|
||||
* @param endHandler a std::function or lambda handler to receive end notification.
|
||||
* @note if you set this handler, the end() signal won't be emitted anymore.
|
||||
*/
|
||||
template<class Func>
|
||||
void onEnd(Func f) {
|
||||
QObject::connect(this, &QHttpAbstractInput::end, f);
|
||||
}
|
||||
|
||||
public:
|
||||
/** tries to collect all the incoming data internally.
|
||||
* @note if you call this method, data() signal won't be emitted and
|
||||
* onData() will have no effect.
|
||||
*
|
||||
* @param atMost maximum acceptable incoming data. if the incoming data
|
||||
* exceeds this value, the connection won't read any more data and
|
||||
* end() signal will be emitted.
|
||||
* default value (-1) means read data as "content-length" or unlimited if
|
||||
* the body size is unknown.
|
||||
*/
|
||||
virtual void collectData(int atMost = -1) =0;
|
||||
|
||||
/** returns the collected data requested by collectData(). */
|
||||
virtual const QByteArray& collectedData()const =0;
|
||||
|
||||
|
||||
public:
|
||||
virtual ~QHttpAbstractInput() = default;
|
||||
|
||||
explicit QHttpAbstractInput(QObject* parent);
|
||||
|
||||
Q_DISABLE_COPY(QHttpAbstractInput)
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** an interface for output (outgoing) HTTP packets.
|
||||
* server::QHttpResponse or client::QHttpRequest inherit from this class. */
|
||||
class QHTTP_API QHttpAbstractOutput : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/** changes the HTTP version string ex: "1.1" or "1.0".
|
||||
* version is "1.1" set by default. */
|
||||
virtual void setVersion(const QString& versionString)=0;
|
||||
|
||||
/** helper function. @sa addHeader */
|
||||
template<typename T>
|
||||
void addHeaderValue(const QByteArray &field, T value);
|
||||
|
||||
/** adds an HTTP header to the packet.
|
||||
* @note this method does not actually write anything to socket, just prepares the headers(). */
|
||||
virtual void addHeader(const QByteArray& field, const QByteArray& value)=0;
|
||||
|
||||
/** returns all the headers that already been set. */
|
||||
virtual THeaderHash& headers()=0;
|
||||
|
||||
/** writes a block of data into the HTTP packet.
|
||||
* @note headers are written (flushed) before any data.
|
||||
* @warning after calling this method add a new header, set staus code, set Url have no effect! */
|
||||
virtual void write(const QByteArray &data)=0;
|
||||
|
||||
/** ends (finishes) the outgoing packet by calling write().
|
||||
* headers and data will be flushed to the underlying socket.
|
||||
*
|
||||
* @sa write() */
|
||||
virtual void end(const QByteArray &data = QByteArray())=0;
|
||||
|
||||
signals:
|
||||
/** Emitted when all the data has been sent.
|
||||
* this signal indicates that the underlaying socket has transmitted all
|
||||
* of it's buffered data. */
|
||||
void allBytesWritten();
|
||||
|
||||
/** Emitted when the packet is finished and reports if it was the last packet.
|
||||
* if it was the last packet (google for "Connection: keep-alive / close")
|
||||
* the http connection (socket) will be closed automatically. */
|
||||
void done(bool wasTheLastPacket);
|
||||
|
||||
public:
|
||||
virtual ~QHttpAbstractOutput() = default;
|
||||
|
||||
protected:
|
||||
explicit QHttpAbstractOutput(QObject* parent);
|
||||
|
||||
Q_DISABLE_COPY(QHttpAbstractOutput)
|
||||
};
|
||||
|
||||
template<> inline void
|
||||
QHttpAbstractOutput::addHeaderValue<int>(const QByteArray &field, int value) {
|
||||
addHeader(field, QString::number(value).toLatin1());
|
||||
}
|
||||
|
||||
template<> inline void
|
||||
QHttpAbstractOutput::addHeaderValue<size_t>(const QByteArray &field, size_t value) {
|
||||
addHeader(field, QString::number(value).toLatin1());
|
||||
}
|
||||
|
||||
template<> inline void
|
||||
QHttpAbstractOutput::addHeaderValue<QString>(const QByteArray &field, QString value) {
|
||||
addHeader(field, value.toUtf8());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // QHTTPABSTRACTS_HPP
|
||||
286
src/http/qhttp/qhttpclient.cpp
Normal file
286
src/http/qhttp/qhttpclient.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
#include "private/qhttpclient_private.hpp"
|
||||
|
||||
#include <QTimerEvent>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QHttpClient::QHttpClient(QObject *parent)
|
||||
: QObject(parent), d_ptr(new QHttpClientPrivate(this)) {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpClient::QHttpClient(QHttpClientPrivate &dd, QObject *parent)
|
||||
: QObject(parent), d_ptr(&dd) {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpClient::~QHttpClient() {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
quint32
|
||||
QHttpClient::timeOut() const {
|
||||
return d_func()->itimeOut;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpClient::setTimeOut(quint32 t) {
|
||||
d_func()->itimeOut = t;
|
||||
}
|
||||
|
||||
bool
|
||||
QHttpClient::isOpen() const {
|
||||
return d_func()->isocket.isOpen();
|
||||
}
|
||||
|
||||
void
|
||||
QHttpClient::killConnection() {
|
||||
Q_D(QHttpClient);
|
||||
|
||||
d->iconnectingTimer.stop();
|
||||
d->itimer.stop();
|
||||
d->isocket.close();
|
||||
}
|
||||
|
||||
TBackend
|
||||
QHttpClient::backendType() const {
|
||||
return d_func()->isocket.ibackendType;
|
||||
}
|
||||
|
||||
QTcpSocket*
|
||||
QHttpClient::tcpSocket() const {
|
||||
return d_func()->isocket.itcpSocket;
|
||||
}
|
||||
|
||||
QLocalSocket*
|
||||
QHttpClient::localSocket() const {
|
||||
return d_func()->isocket.ilocalSocket;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpClient::setConnectingTimeOut(quint32 timeout) {
|
||||
Q_D(QHttpClient);
|
||||
|
||||
if ( timeout == 0 ) {
|
||||
d->iconnectingTimer.stop();
|
||||
|
||||
} else {
|
||||
d->iconnectingTimer.start(timeout,
|
||||
Qt::CoarseTimer,
|
||||
this
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
QHttpClient::request(THttpMethod method, QUrl url,
|
||||
const TRequstHandler &reqHandler,
|
||||
const TResponseHandler &resHandler) {
|
||||
Q_D(QHttpClient);
|
||||
|
||||
d->ireqHandler = nullptr;
|
||||
d->irespHandler = nullptr;
|
||||
|
||||
// if url is a local file (UNIX socket) the host could be empty!
|
||||
if ( !url.isValid() || url.isEmpty() /*|| url.host().isEmpty()*/ )
|
||||
return false;
|
||||
|
||||
// process handlers
|
||||
if ( resHandler ) {
|
||||
d->irespHandler = resHandler;
|
||||
|
||||
if ( reqHandler )
|
||||
d->ireqHandler = reqHandler;
|
||||
else
|
||||
d->ireqHandler = [](QHttpRequest* req) ->void {
|
||||
req->addHeader("connection", "close");
|
||||
req->end();
|
||||
};
|
||||
}
|
||||
|
||||
auto requestCreator = [this, method, url]() {
|
||||
// create request object
|
||||
if ( d_ptr->ilastRequest )
|
||||
d_ptr->ilastRequest->deleteLater();
|
||||
|
||||
d_ptr->ilastRequest = new QHttpRequest(this);
|
||||
QObject::connect(d_ptr->ilastRequest, &QHttpRequest::done, [this](bool wasTheLastPacket){
|
||||
d_ptr->ikeepAlive = !wasTheLastPacket;
|
||||
});
|
||||
|
||||
d_ptr->ilastRequest->d_ptr->imethod = method;
|
||||
d_ptr->ilastRequest->d_ptr->iurl = url;
|
||||
};
|
||||
|
||||
// connecting to host/server must be the last thing. (after all function handlers and ...)
|
||||
// check for type
|
||||
if ( url.scheme().toLower() == QLatin1String("file") ) {
|
||||
d->isocket.ibackendType = ELocalSocket;
|
||||
d->initializeSocket();
|
||||
|
||||
requestCreator();
|
||||
|
||||
if ( d->isocket.isOpen() )
|
||||
d->onConnected();
|
||||
else
|
||||
d->isocket.connectTo(url);
|
||||
|
||||
} else {
|
||||
bool ssl = url.scheme() == "https";
|
||||
|
||||
d->isocket.ibackendType = ssl ? ESslSocket : ETcpSocket;
|
||||
d->initializeSocket();
|
||||
|
||||
requestCreator();
|
||||
|
||||
if ( d->isocket.isOpen() )
|
||||
d->onConnected();
|
||||
else
|
||||
d->isocket.connectTo(url.host(), url.port(ssl ? 443 : 80));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpClient::timerEvent(QTimerEvent *e) {
|
||||
Q_D(QHttpClient);
|
||||
|
||||
if ( e->timerId() == d->itimer.timerId() ) {
|
||||
killConnection();
|
||||
|
||||
} else if ( e->timerId() == d->iconnectingTimer.timerId() ) {
|
||||
d->iconnectingTimer.stop();
|
||||
emit connectingTimeOut();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QHttpClient::onRequestReady(QHttpRequest *req) {
|
||||
emit httpConnected(req);
|
||||
}
|
||||
|
||||
void
|
||||
QHttpClient::onResponseReady(QHttpResponse *res) {
|
||||
emit newResponse(res);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// if server closes the connection, ends the response or by any other reason
|
||||
// the socket disconnects, then the irequest and iresponse instances may have
|
||||
// been deleted. In these situations reading more http body or emitting end()
|
||||
// for incoming request are not possible:
|
||||
// if ( ilastRequest == nullptr )
|
||||
// return 0;
|
||||
|
||||
|
||||
|
||||
int
|
||||
QHttpClientPrivate::messageBegin(http_parser*) {
|
||||
itempHeaderField.clear();
|
||||
itempHeaderValue.clear();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpClientPrivate::status(http_parser* parser, const char* at, size_t length) {
|
||||
if ( ilastResponse )
|
||||
ilastResponse->deleteLater();
|
||||
|
||||
ilastResponse = new QHttpResponse(q_func());
|
||||
ilastResponse->d_func()->istatus = static_cast<TStatusCode>(parser->status_code);
|
||||
ilastResponse->d_func()->iversion = QString("%1.%2")
|
||||
.arg(parser->http_major)
|
||||
.arg(parser->http_minor);
|
||||
ilastResponse->d_func()->icustomStatusMessage = QString::fromUtf8(at, length);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpClientPrivate::headerField(http_parser*, const char* at, size_t length) {
|
||||
if ( ilastResponse == nullptr )
|
||||
return 0;
|
||||
|
||||
// insert the header we parsed previously
|
||||
// into the header map
|
||||
if ( !itempHeaderField.isEmpty() && !itempHeaderValue.isEmpty() ) {
|
||||
// header names are always lower-cased
|
||||
ilastResponse->d_func()->iheaders.insert(
|
||||
itempHeaderField.toLower(),
|
||||
itempHeaderValue.toLower()
|
||||
);
|
||||
// clear header value. this sets up a nice
|
||||
// feedback loop where the next time
|
||||
// HeaderValue is called, it can simply append
|
||||
itempHeaderField.clear();
|
||||
itempHeaderValue.clear();
|
||||
}
|
||||
|
||||
itempHeaderField.append(at, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpClientPrivate::headerValue(http_parser*, const char* at, size_t length) {
|
||||
|
||||
itempHeaderValue.append(at, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpClientPrivate::headersComplete(http_parser*) {
|
||||
if ( ilastResponse == nullptr )
|
||||
return 0;
|
||||
|
||||
// Insert last remaining header
|
||||
ilastResponse->d_func()->iheaders.insert(
|
||||
itempHeaderField.toLower(),
|
||||
itempHeaderValue.toLower()
|
||||
);
|
||||
|
||||
if ( irespHandler )
|
||||
irespHandler(ilastResponse);
|
||||
else
|
||||
q_func()->onResponseReady(ilastResponse);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpClientPrivate::body(http_parser*, const char* at, size_t length) {
|
||||
if ( ilastResponse == nullptr )
|
||||
return 0;
|
||||
|
||||
ilastResponse->d_func()->ireadState = QHttpResponsePrivate::EPartial;
|
||||
|
||||
if ( ilastResponse->d_func()->icollectRequired ) {
|
||||
if ( !ilastResponse->d_func()->append(at, length) ) {
|
||||
// forcefully dispatch the ilastResponse
|
||||
finalizeConnection();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
emit ilastResponse->data(QByteArray(at, length));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpClientPrivate::messageComplete(http_parser*) {
|
||||
if ( ilastResponse == nullptr )
|
||||
return 0;
|
||||
|
||||
// response is done
|
||||
finalizeConnection();
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
179
src/http/qhttp/qhttpclient.hpp
Normal file
179
src/http/qhttp/qhttpclient.hpp
Normal file
@@ -0,0 +1,179 @@
|
||||
/** HTTP client class.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPCLIENT_HPP
|
||||
#define QHTTPCLIENT_HPP
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include "qhttpfwd.hpp"
|
||||
|
||||
#include <QTcpSocket>
|
||||
#include <QSslSocket>
|
||||
#include <QUrl>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
using TRequstHandler = std::function<void (QHttpRequest*)>;
|
||||
using TResponseHandler = std::function<void (QHttpResponse*)>;
|
||||
|
||||
/** a simple and async HTTP client class which sends a request to an HTTP server and parses the
|
||||
* corresponding response.
|
||||
* This class internally handles the memory management and life cycle of QHttpRequest and
|
||||
* QHttpResponse instances. you do not have to manually delete or keep their pointers.
|
||||
* in fact the QHttpRequest and QHttpResponse object will be deleted when the internal socket
|
||||
* disconnects.
|
||||
*/
|
||||
class QHTTP_API QHttpClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(quint32 timeOut READ timeOut WRITE setTimeOut)
|
||||
|
||||
public:
|
||||
explicit QHttpClient(QObject *parent = nullptr);
|
||||
|
||||
virtual ~QHttpClient();
|
||||
|
||||
/** tries to connect to a HTTP server.
|
||||
* when the connection is made, the reqHandler will be called
|
||||
* and when the response is ready, resHandler will be called.
|
||||
* @note httpConnected() and newResponse() won't be emitted.
|
||||
*
|
||||
* @param method an HTTP method, ex: GET, POST, ...
|
||||
* @param url specifies server's address, port and optional path and query strings.
|
||||
* if url starts with socket:// the request will be made on QLocalSocket, otherwise
|
||||
* normal QTcpSocket will be used.
|
||||
* @param resHandler response handler (a lambda, std::function object, ...)
|
||||
* @return true if the url is valid or false (no connection will be made).
|
||||
*/
|
||||
bool request(THttpMethod method, QUrl url,
|
||||
const TRequstHandler& reqHandler,
|
||||
const TResponseHandler& resHandler);
|
||||
|
||||
/** tries to connect to a HTTP server.
|
||||
* when the connection is made, a default request handler is called automatically (
|
||||
* simply calls req->end()) and when the response is ready, resHandler will be called.
|
||||
* @note httpConnected() and newResponse() won't be emitted.
|
||||
*
|
||||
* @param method an HTTP method, ex: GET, POST, ...
|
||||
* @param url specifies server's address, port and optional path and query strings.
|
||||
* @param resHandler response handler (a lambda, std::function object, ...)
|
||||
* @return true if the url is valid or false (no connection will be made).
|
||||
*/
|
||||
inline bool request(THttpMethod method, QUrl url, const TResponseHandler& resHandler) {
|
||||
return request(method, url, nullptr, resHandler);
|
||||
}
|
||||
|
||||
/** tries to connect to a HTTP server.
|
||||
* when the connection is made, creates and emits a QHttpRequest instance
|
||||
* by @sa httpConnected(QHttpRequest*).
|
||||
* @note both httpConnected() and newResponse() may be emitted.
|
||||
*
|
||||
* @param method an HTTP method, ex: GET, POST, ...
|
||||
* @param url specifies server's address, port and optional path and query strings.
|
||||
* @return true if the url is valid or false (no connection will be made).
|
||||
*/
|
||||
inline bool request(THttpMethod method, QUrl url) {
|
||||
return request(method, url, nullptr, nullptr);
|
||||
}
|
||||
|
||||
/** checks if the connetion to the server is open. */
|
||||
bool isOpen() const;
|
||||
|
||||
/** forcefully close the connection. */
|
||||
void killConnection();
|
||||
|
||||
|
||||
/** returns time-out value [mSec] for ESTABLISHED connections (sockets).
|
||||
* @sa setTimeOut(). */
|
||||
quint32 timeOut()const;
|
||||
|
||||
/** set time-out for ESTABLISHED connections in miliseconds [mSec].
|
||||
* each (already opened) connection will be forcefully closed after this timeout.
|
||||
* a zero (0) value disables timer for new connections. */
|
||||
void setTimeOut(quint32);
|
||||
|
||||
/** set a time-out [mSec] for making a new connection (make a request).
|
||||
* if connecting to server takes more than this time-out value,
|
||||
* the @sa timedOut(quint32) signal will be emitted and connection will be killed.
|
||||
* 0 (default) timeout value means to disable this timer.
|
||||
*/
|
||||
void setConnectingTimeOut(quint32);
|
||||
|
||||
template<class Handler>
|
||||
void setConnectingTimeOut(quint32 timeout, Handler&& func) {
|
||||
setConnectingTimeOut(timeout);
|
||||
QObject::connect(this, &QHttpClient::connectingTimeOut,
|
||||
std::forward<Handler&&>(func)
|
||||
);
|
||||
}
|
||||
|
||||
/** returns the backend type of this client. */
|
||||
TBackend backendType() const;
|
||||
|
||||
/** returns tcp socket of the connection if backend() == ETcpSocket. */
|
||||
QTcpSocket* tcpSocket() const;
|
||||
|
||||
/** returns local socket of the connection if backend() == ELocalSocket. */
|
||||
QLocalSocket* localSocket() const;
|
||||
|
||||
signals:
|
||||
/** emitted when a new HTTP connection to the server is established.
|
||||
* if you overload onRequestReady this signal won't be emitted.
|
||||
* @sa onRequestReady
|
||||
* @sa QHttpRequest
|
||||
*/
|
||||
void httpConnected(QHttpRequest* req);
|
||||
|
||||
/** emitted when a new response is received from the server.
|
||||
* if you overload onResponseReady this signal won't be emitted.
|
||||
* @sa onResponseReady
|
||||
* @sa QHttpResponse
|
||||
*/
|
||||
void newResponse(QHttpResponse* res);
|
||||
|
||||
/** emitted when the HTTP connection drops or being disconnected. */
|
||||
void disconnected();
|
||||
|
||||
/// emitted when fails to connect to a HTTP server. @sa setConnectingTimeOut()
|
||||
void connectingTimeOut();
|
||||
|
||||
|
||||
protected:
|
||||
/** called when a new HTTP connection is established.
|
||||
* you can overload this method, the default implementaion only emits connected().
|
||||
* @param req use this request instance for assinging the
|
||||
* request headers and sending optional body.
|
||||
* @see httpConnected(QHttpRequest*)
|
||||
*/
|
||||
virtual void onRequestReady(QHttpRequest* req);
|
||||
|
||||
/** called when a new response is received from the server.
|
||||
* you can overload this method, the default implementaion only emits newResponse().
|
||||
* @param res use this instance for reading incoming response.
|
||||
* @see newResponse(QHttpResponse*)
|
||||
*/
|
||||
virtual void onResponseReady(QHttpResponse* res);
|
||||
|
||||
protected:
|
||||
explicit QHttpClient(QHttpClientPrivate&, QObject*);
|
||||
|
||||
void timerEvent(QTimerEvent*) override;
|
||||
|
||||
Q_DECLARE_PRIVATE(QHttpClient)
|
||||
Q_DISABLE_COPY(QHttpClient)
|
||||
QScopedPointer<QHttpClientPrivate> d_ptr;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // define QHTTPCLIENT_HPP
|
||||
97
src/http/qhttp/qhttpclientrequest.cpp
Normal file
97
src/http/qhttp/qhttpclientrequest.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#include "private/qhttpclientrequest_private.hpp"
|
||||
#include "qhttpclient.hpp"
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QHttpRequest::QHttpRequest(QHttpClient* cli)
|
||||
: QHttpAbstractOutput(cli) , d_ptr(new QHttpRequestPrivate(cli, this)) {
|
||||
d_ptr->initialize();
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpRequest::QHttpRequest(QHttpRequestPrivate& dd, QHttpClient* cli)
|
||||
: QHttpAbstractOutput(cli) , d_ptr(&dd) {
|
||||
d_ptr->initialize();
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpRequest::~QHttpRequest() {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
void
|
||||
QHttpRequest::setVersion(const QString &versionString) {
|
||||
d_func()->iversion = versionString;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpRequest::addHeader(const QByteArray &field, const QByteArray &value) {
|
||||
d_func()->addHeader(field, value);
|
||||
}
|
||||
|
||||
THeaderHash&
|
||||
QHttpRequest::headers() {
|
||||
return d_func()->iheaders;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpRequest::write(const QByteArray &data) {
|
||||
d_func()->writeData(data);
|
||||
}
|
||||
|
||||
void
|
||||
QHttpRequest::end(const QByteArray &data) {
|
||||
Q_D(QHttpRequest);
|
||||
|
||||
if ( d->endPacket(data) )
|
||||
emit done(!d->ikeepAlive);
|
||||
}
|
||||
|
||||
QHttpClient*
|
||||
QHttpRequest::connection() const {
|
||||
return d_func()->iclient;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QByteArray
|
||||
QHttpRequestPrivate::makeTitle() {
|
||||
|
||||
QByteArray title;
|
||||
title.reserve(512);
|
||||
title.append(qhttp::Stringify::toString(imethod))
|
||||
.append(" ");
|
||||
|
||||
QByteArray path = iurl.path(QUrl::FullyEncoded).toLatin1();
|
||||
if ( path.size() == 0 )
|
||||
path = "/";
|
||||
title.append(path);
|
||||
|
||||
if ( iurl.hasQuery() )
|
||||
title.append("?").append(iurl.query(QUrl::FullyEncoded).toLatin1());
|
||||
|
||||
|
||||
title.append(" HTTP/")
|
||||
.append(iversion.toLatin1())
|
||||
.append("\r\n");
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpRequestPrivate::prepareHeadersToWrite() {
|
||||
|
||||
if ( !iheaders.contains("host") ) {
|
||||
QString portStr = ( -1 != iurl.port() ) ?
|
||||
QString(":%1").arg(iurl.port()) : "";
|
||||
|
||||
iheaders.insert("host",
|
||||
QString("%1%2").arg(iurl.host()).arg(portStr).toLatin1()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
63
src/http/qhttp/qhttpclientrequest.hpp
Normal file
63
src/http/qhttp/qhttpclientrequest.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
/** HTTP request from a client.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPCLIENT_REQUEST_HPP
|
||||
#define QHTTPCLIENT_REQUEST_HPP
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include "qhttpabstracts.hpp"
|
||||
#include <QUrl>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/** a class for building a new HTTP request.
|
||||
* the life cycle of this class and the memory management is handled by QHttpClient.
|
||||
* @sa QHttpClient
|
||||
*/
|
||||
class QHTTP_API QHttpRequest : public QHttpAbstractOutput
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual ~QHttpRequest();
|
||||
|
||||
public: // QHttpAbstractOutput methods:
|
||||
/** @see QHttpAbstractOutput::setVersion(). */
|
||||
void setVersion(const QString& versionString) override;
|
||||
|
||||
/** @see QHttpAbstractOutput::addHeader(). */
|
||||
void addHeader(const QByteArray& field, const QByteArray& value) override;
|
||||
|
||||
/** @see QHttpAbstractOutput::headers(). */
|
||||
THeaderHash& headers() override;
|
||||
|
||||
/** @see QHttpAbstractOutput::write(). */
|
||||
void write(const QByteArray &data) override;
|
||||
|
||||
/** @see QHttpAbstractOutput::end(). */
|
||||
void end(const QByteArray &data = QByteArray()) override;
|
||||
|
||||
public:
|
||||
/** returns parent QHttpClient object. */
|
||||
QHttpClient* connection() const;
|
||||
|
||||
protected:
|
||||
explicit QHttpRequest(QHttpClient*);
|
||||
explicit QHttpRequest(QHttpRequestPrivate&, QHttpClient*);
|
||||
friend class QHttpClient;
|
||||
|
||||
Q_DECLARE_PRIVATE(QHttpRequest)
|
||||
QScopedPointer<QHttpRequestPrivate> d_ptr;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // define QHTTPCLIENT_REQUEST_HPP
|
||||
66
src/http/qhttp/qhttpclientresponse.cpp
Normal file
66
src/http/qhttp/qhttpclientresponse.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "private/qhttpclientresponse_private.hpp"
|
||||
#include "qhttpclient.hpp"
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QHttpResponse::QHttpResponse(QHttpClient *cli)
|
||||
: QHttpAbstractInput(cli), d_ptr(new QHttpResponsePrivate(cli, this)) {
|
||||
d_ptr->initialize();
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpResponse::QHttpResponse(QHttpResponsePrivate &dd, QHttpClient *cli)
|
||||
: QHttpAbstractInput(cli), d_ptr(&dd) {
|
||||
d_ptr->initialize();
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpResponse::~QHttpResponse() {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
TStatusCode
|
||||
QHttpResponse::status() const {
|
||||
return d_func()->istatus;
|
||||
}
|
||||
|
||||
const QString&
|
||||
QHttpResponse::statusString() const {
|
||||
return d_func()->icustomStatusMessage;
|
||||
}
|
||||
|
||||
const QString&
|
||||
QHttpResponse::httpVersion() const {
|
||||
return d_func()->iversion;
|
||||
}
|
||||
|
||||
const THeaderHash&
|
||||
QHttpResponse::headers() const {
|
||||
return d_func()->iheaders;
|
||||
}
|
||||
|
||||
bool
|
||||
QHttpResponse::isSuccessful() const {
|
||||
return d_func()->isuccessful;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpResponse::collectData(int atMost) {
|
||||
d_func()->collectData(atMost);
|
||||
}
|
||||
|
||||
const QByteArray&
|
||||
QHttpResponse::collectedData() const {
|
||||
return d_func()->icollectedData;
|
||||
}
|
||||
|
||||
QHttpClient*
|
||||
QHttpResponse::connection() const {
|
||||
return d_func()->iclient;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
73
src/http/qhttp/qhttpclientresponse.hpp
Normal file
73
src/http/qhttp/qhttpclientresponse.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
/** HTTP response received by client.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPCLIENT_RESPONSE_HPP
|
||||
#define QHTTPCLIENT_RESPONSE_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "qhttpabstracts.hpp"
|
||||
|
||||
#include <QUrl>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/** a class for reading incoming HTTP response from a server.
|
||||
* the life cycle of this class and the memory management is handled by QHttpClient.
|
||||
* @sa QHttpClient
|
||||
*/
|
||||
class QHTTP_API QHttpResponse : public QHttpAbstractInput
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual ~QHttpResponse();
|
||||
|
||||
public: // QHttpAbstractInput methods:
|
||||
/** @see QHttpAbstractInput::headers(). */
|
||||
const THeaderHash& headers() const override;
|
||||
|
||||
/** @see QHttpAbstractInput::httpVersion(). */
|
||||
const QString& httpVersion() const override;
|
||||
|
||||
/** @see QHttpAbstractInput::isSuccessful(). */
|
||||
bool isSuccessful() const override;
|
||||
|
||||
/** @see QHttpAbstractInput::collectData(). */
|
||||
void collectData(int atMost = -1) override;
|
||||
|
||||
/** @see QHttpAbstractInput::collectedData(). */
|
||||
const QByteArray& collectedData()const override;
|
||||
|
||||
|
||||
public:
|
||||
/** The status code of this response. */
|
||||
TStatusCode status() const ;
|
||||
|
||||
/** The server status message as string.
|
||||
* may be slightly different than: @code qhttp::Stringify::toString(status()); @endcode
|
||||
* depending on implementation of HTTP server. */
|
||||
const QString& statusString() const;
|
||||
|
||||
/** returns parent QHttpClient object. */
|
||||
QHttpClient* connection() const;
|
||||
|
||||
protected:
|
||||
explicit QHttpResponse(QHttpClient*);
|
||||
explicit QHttpResponse(QHttpResponsePrivate&, QHttpClient*);
|
||||
friend class QHttpClientPrivate;
|
||||
|
||||
Q_DECLARE_PRIVATE(QHttpResponse)
|
||||
QScopedPointer<QHttpResponsePrivate> d_ptr;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // define QHTTPCLIENT_RESPONSE_HPP
|
||||
216
src/http/qhttp/qhttpfwd.hpp
Normal file
216
src/http/qhttp/qhttpfwd.hpp
Normal file
@@ -0,0 +1,216 @@
|
||||
/** forward declarations and general definitions of the QHttp.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPFWD_HPP
|
||||
#define QHTTPFWD_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include <QHash>
|
||||
#include <QString>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include <functional>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Qt
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
class QLocalServer;
|
||||
class QLocalSocket;
|
||||
|
||||
// http_parser
|
||||
struct http_parser_settings;
|
||||
struct http_parser;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** A map of request or response headers. */
|
||||
class THeaderHash : public QHash<QByteArray, QByteArray>
|
||||
{
|
||||
public:
|
||||
/** checks for a header item, regardless of the case of the characters. */
|
||||
bool has(const QByteArray& key) const {
|
||||
return contains(key.toLower());
|
||||
}
|
||||
|
||||
/** checks if a header has the specified value ignoring the case of the characters. */
|
||||
bool keyHasValue(const QByteArray& key, const QByteArray& value) const {
|
||||
if ( !contains(key) )
|
||||
return false;
|
||||
|
||||
const QByteArray& v = QHash<QByteArray, QByteArray>::value(key);
|
||||
return qstrnicmp(value.constData(), v.constData(), v.size()) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
/// QHash/QMap iterators are incompatibility with range for
|
||||
template<class Iterator, class Func>
|
||||
void for_each(Iterator first, Iterator last, Func&& f) {
|
||||
while ( first != last ) {
|
||||
f( first );
|
||||
++first;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Request method enumeration.
|
||||
* @note Taken from http_parser.h */
|
||||
enum THttpMethod {
|
||||
EHTTP_DELETE = 0, ///< DELETE
|
||||
EHTTP_GET = 1, ///< GET
|
||||
EHTTP_HEAD = 2, ///< HEAD
|
||||
EHTTP_POST = 3, ///< POST
|
||||
EHTTP_PUT = 4, ///< PUT
|
||||
/* pathological */
|
||||
EHTTP_CONNECT = 5, ///< CONNECT
|
||||
EHTTP_OPTIONS = 6, ///< OPTIONS
|
||||
EHTTP_TRACE = 7, ///< TRACE
|
||||
/* webdav */
|
||||
EHTTP_COPY = 8, ///< COPY
|
||||
EHTTP_LOCK = 9, ///< LOCK
|
||||
EHTTP_MKCOL = 10, ///< MKCOL
|
||||
EHTTP_MOVE = 11, ///< MOVE
|
||||
EHTTP_PROPFIND = 12, ///< PROPFIND
|
||||
EHTTP_PROPPATCH = 13, ///< PROPPATCH
|
||||
EHTTP_SEARCH = 14, ///< SEARCH
|
||||
EHTTP_UNLOCK = 15, ///< UNLOCK
|
||||
EHTTP_BIND = 16, ///< BIND
|
||||
EHTTP_REBIND = 17, ///< REBIND
|
||||
EHTTP_UNBIND = 18, ///< UNBIND
|
||||
EHTTP_ACL = 19, ///< ACL
|
||||
/* subversion */
|
||||
EHTTP_REPORT = 20, ///< REPORT
|
||||
EHTTP_MKACTIVITY = 21, ///< MKACTIVITY
|
||||
EHTTP_CHECKOUT = 22, ///< CHECKOUT
|
||||
EHTTP_MERGE = 23, ///< MERGE
|
||||
/* upnp */
|
||||
EHTTP_MSEARCH = 24, ///< M-SEARCH
|
||||
EHTTP_NOTIFY = 25, ///< NOTIFY
|
||||
EHTTP_SUBSCRIBE = 26, ///< SUBSCRIBE
|
||||
EHTTP_UNSUBSCRIBE = 27, ///< UNSUBSCRIBE
|
||||
/* RFC-5789 */
|
||||
EHTTP_PATCH = 28, ///< PATCH
|
||||
EHTTP_PURGE = 29, ///< PURGE
|
||||
/* CalDAV */
|
||||
EHTTP_MKCALENDAR = 30, ///< MKCALENDAR
|
||||
/* RFC-2068, section 19.6.1.2 */
|
||||
EHTTP_LINK = 31, ///< LINK
|
||||
EHTTP_UNLINK = 32, ///< UNLINK
|
||||
};
|
||||
|
||||
/** HTTP status codes. */
|
||||
enum TStatusCode {
|
||||
ESTATUS_CONTINUE = 100,
|
||||
ESTATUS_SWITCH_PROTOCOLS = 101,
|
||||
ESTATUS_OK = 200,
|
||||
ESTATUS_CREATED = 201,
|
||||
ESTATUS_ACCEPTED = 202,
|
||||
ESTATUS_NON_AUTHORITATIVE_INFORMATION = 203,
|
||||
ESTATUS_NO_CONTENT = 204,
|
||||
ESTATUS_RESET_CONTENT = 205,
|
||||
ESTATUS_PARTIAL_CONTENT = 206,
|
||||
ESTATUS_MULTI_STATUS = 207,
|
||||
ESTATUS_MULTIPLE_CHOICES = 300,
|
||||
ESTATUS_MOVED_PERMANENTLY = 301,
|
||||
ESTATUS_FOUND = 302,
|
||||
ESTATUS_SEE_OTHER = 303,
|
||||
ESTATUS_NOT_MODIFIED = 304,
|
||||
ESTATUS_USE_PROXY = 305,
|
||||
ESTATUS_TEMPORARY_REDIRECT = 307,
|
||||
ESTATUS_BAD_REQUEST = 400,
|
||||
ESTATUS_UNAUTHORIZED = 401,
|
||||
ESTATUS_PAYMENT_REQUIRED = 402,
|
||||
ESTATUS_FORBIDDEN = 403,
|
||||
ESTATUS_NOT_FOUND = 404,
|
||||
ESTATUS_METHOD_NOT_ALLOWED = 405,
|
||||
ESTATUS_NOT_ACCEPTABLE = 406,
|
||||
ESTATUS_PROXY_AUTHENTICATION_REQUIRED = 407,
|
||||
ESTATUS_REQUEST_TIMEOUT = 408,
|
||||
ESTATUS_CONFLICT = 409,
|
||||
ESTATUS_GONE = 410,
|
||||
ESTATUS_LENGTH_REQUIRED = 411,
|
||||
ESTATUS_PRECONDITION_FAILED = 412,
|
||||
ESTATUS_REQUEST_ENTITY_TOO_LARGE = 413,
|
||||
ESTATUS_REQUEST_URI_TOO_LONG = 414,
|
||||
ESTATUS_REQUEST_UNSUPPORTED_MEDIA_TYPE = 415,
|
||||
ESTATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416,
|
||||
ESTATUS_EXPECTATION_FAILED = 417,
|
||||
ESTATUS_INTERNAL_SERVER_ERROR = 500,
|
||||
ESTATUS_NOT_IMPLEMENTED = 501,
|
||||
ESTATUS_BAD_GATEWAY = 502,
|
||||
ESTATUS_SERVICE_UNAVAILABLE = 503,
|
||||
ESTATUS_GATEWAY_TIMEOUT = 504,
|
||||
ESTATUS_HTTP_VERSION_NOT_SUPPORTED = 505
|
||||
};
|
||||
|
||||
/** The backend of QHttp library. */
|
||||
enum TBackend {
|
||||
ETcpSocket = 0, ///< client / server work on top of TCP/IP stack. (default)
|
||||
ESslSocket = 1, ///< client / server work on SSL/TLS tcp stack.
|
||||
ELocalSocket = 2 ///< client / server work on local socket (unix socket).
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class QHttpServer;
|
||||
class QHttpConnection;
|
||||
class QHttpRequest;
|
||||
class QHttpResponse;
|
||||
|
||||
// Privte classes
|
||||
class QHttpServerPrivate;
|
||||
class QHttpConnectionPrivate;
|
||||
class QHttpRequestPrivate;
|
||||
class QHttpResponsePrivate;
|
||||
|
||||
using TServerHandler = std::function<void (QHttpRequest*, QHttpResponse*)>;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace client {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
class QHttpClient;
|
||||
class QHttpRequest;
|
||||
class QHttpResponse;
|
||||
|
||||
// Private classes
|
||||
class QHttpClientPrivate;
|
||||
class QHttpRequestPrivate;
|
||||
class QHttpResponsePrivate;
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace client
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#ifdef Q_OS_WIN
|
||||
# if defined(QHTTP_EXPORT)
|
||||
# define QHTTP_API __declspec(dllexport)
|
||||
# else
|
||||
# define QHTTP_API __declspec(dllimport)
|
||||
# endif
|
||||
#else
|
||||
# define QHTTP_API
|
||||
#endif
|
||||
|
||||
|
||||
#if QHTTP_MEMORY_LOG > 0
|
||||
# define QHTTP_LINE_LOG fprintf(stderr, "%s(): obj = %p @ %s[%d]\n",\
|
||||
__FUNCTION__, this, __FILE__, __LINE__);
|
||||
#else
|
||||
# define QHTTP_LINE_LOG
|
||||
#endif
|
||||
|
||||
#if QHTTP_MEMORY_LOG > 1
|
||||
# define QHTTP_LINE_DEEPLOG QHTTP_LINE_LOG
|
||||
#else
|
||||
# define QHTTP_LINE_DEEPLOG
|
||||
#endif
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // define QHTTPFWD_HPP
|
||||
118
src/http/qhttp/qhttpserver.cpp
Normal file
118
src/http/qhttp/qhttpserver.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "private/qhttpserver_private.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
QHttpServer::QHttpServer(QObject *parent)
|
||||
: QObject(parent), d_ptr(new QHttpServerPrivate) {
|
||||
}
|
||||
|
||||
QHttpServer::QHttpServer(QHttpServerPrivate &dd, QObject *parent)
|
||||
: QObject(parent), d_ptr(&dd) {
|
||||
}
|
||||
|
||||
QHttpServer::~QHttpServer() {
|
||||
stopListening();
|
||||
}
|
||||
|
||||
bool
|
||||
QHttpServer::listen(const QString &socketOrPort, const TServerHandler &handler) {
|
||||
Q_D(QHttpServer);
|
||||
|
||||
bool isNumber = false;
|
||||
quint16 tcpPort = socketOrPort.toUShort(&isNumber);
|
||||
if ( isNumber && tcpPort > 0 )
|
||||
return listen(QHostAddress::Any, tcpPort, handler);
|
||||
|
||||
d->initialize(ELocalSocket, this);
|
||||
d->ihandler = handler;
|
||||
return d->ilocalServer->listen(socketOrPort);
|
||||
}
|
||||
|
||||
bool
|
||||
QHttpServer::listen(const QHostAddress& address, quint16 port, const qhttp::server::TServerHandler& handler) {
|
||||
Q_D(QHttpServer);
|
||||
|
||||
d->initialize(d->ibackend, this);
|
||||
d->ihandler = handler;
|
||||
return d->itcpServer->listen(address, port);
|
||||
}
|
||||
|
||||
bool
|
||||
QHttpServer::isListening() const {
|
||||
const Q_D(QHttpServer);
|
||||
|
||||
if ( ( d->ibackend == ETcpSocket || d->ibackend == ESslSocket ) && d->itcpServer )
|
||||
return d->itcpServer->isListening();
|
||||
|
||||
else if ( d->ibackend == ELocalSocket && d->ilocalServer )
|
||||
return d->ilocalServer->isListening();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpServer::stopListening() {
|
||||
Q_D(QHttpServer);
|
||||
|
||||
if ( d->itcpServer )
|
||||
d->itcpServer->close();
|
||||
|
||||
if ( d->ilocalServer ) {
|
||||
d->ilocalServer->close();
|
||||
QLocalServer::removeServer( d->ilocalServer->fullServerName() );
|
||||
}
|
||||
}
|
||||
|
||||
quint32
|
||||
QHttpServer::timeOut() const {
|
||||
return d_func()->itimeOut;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpServer::setTimeOut(quint32 newValue) {
|
||||
d_func()->itimeOut = newValue;
|
||||
}
|
||||
|
||||
TBackend
|
||||
QHttpServer::backendType() const {
|
||||
return d_func()->ibackend;
|
||||
}
|
||||
|
||||
QTcpServer*
|
||||
QHttpServer::tcpServer() const {
|
||||
return d_func()->itcpServer.data();
|
||||
}
|
||||
|
||||
QLocalServer*
|
||||
QHttpServer::localServer() const {
|
||||
return d_func()->ilocalServer.data();
|
||||
}
|
||||
|
||||
void
|
||||
QHttpServer::incomingConnection(qintptr handle) {
|
||||
QHttpConnection* conn = new QHttpConnection(this);
|
||||
conn->setSocketDescriptor(handle, backendType());
|
||||
conn->setTimeOut(d_func()->itimeOut);
|
||||
|
||||
emit newConnection(conn);
|
||||
|
||||
Q_D(QHttpServer);
|
||||
if ( d->ihandler )
|
||||
QObject::connect(conn, &QHttpConnection::newRequest, d->ihandler);
|
||||
else
|
||||
incomingConnection(conn);
|
||||
}
|
||||
|
||||
void
|
||||
QHttpServer::incomingConnection(QHttpConnection *connection) {
|
||||
QObject::connect(connection, &QHttpConnection::newRequest,
|
||||
this, &QHttpServer::newRequest);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
131
src/http/qhttp/qhttpserver.hpp
Normal file
131
src/http/qhttp/qhttpserver.hpp
Normal file
@@ -0,0 +1,131 @@
|
||||
/** HTTP server class.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPSERVER_HPP
|
||||
#define QHTTPSERVER_HPP
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include "qhttpfwd.hpp"
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/** The QHttpServer class is a fast, async (non-blocking) HTTP server. */
|
||||
class QHTTP_API QHttpServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(quint32 timeOut READ timeOut WRITE setTimeOut)
|
||||
|
||||
public:
|
||||
/** construct a new HTTP Server. */
|
||||
explicit QHttpServer(QObject *parent = nullptr);
|
||||
|
||||
virtual ~QHttpServer();
|
||||
|
||||
/** starts a TCP or Local (unix domain socket) server.
|
||||
* if you provide a server handler, the newRequest() signal won't be emitted.
|
||||
*
|
||||
* @param socketOrPort could be a tcp port number as "8080" or a unix socket name as
|
||||
* "/tmp/sample.socket" or "sample.socket".
|
||||
* @param handler optional server handler (a lambda, std::function, ...)
|
||||
* @return false if listening fails.
|
||||
*/
|
||||
bool listen(const QString& socketOrPort,
|
||||
const TServerHandler& handler = nullptr);
|
||||
|
||||
/** starts a TCP server on specified address and port.
|
||||
* if you provide a server handler, the newRequest() signal won't be emitted.
|
||||
*
|
||||
* @param address listening address as QHostAddress::Any.
|
||||
* @param port listening port.
|
||||
* @param handler optional server handler (a lambda, std::function, ...)
|
||||
* @return false if listening fails.
|
||||
*/
|
||||
bool listen(const QHostAddress& address, quint16 port,
|
||||
const TServerHandler& handler = nullptr);
|
||||
|
||||
/** @overload listen() */
|
||||
bool listen(quint16 port) {
|
||||
return listen(QHostAddress::Any, port);
|
||||
}
|
||||
|
||||
/** returns true if server successfully listens. @sa listen() */
|
||||
bool isListening() const;
|
||||
|
||||
/** closes the server and stops from listening. */
|
||||
void stopListening();
|
||||
|
||||
/** returns timeout value [mSec] for open connections (sockets).
|
||||
* @sa setTimeOut(). */
|
||||
quint32 timeOut()const;
|
||||
|
||||
/** set time-out for new open connections in miliseconds [mSec].
|
||||
* new incoming connections will be forcefully closed after this time out.
|
||||
* a zero (0) value disables timer for new connections. */
|
||||
void setTimeOut(quint32);
|
||||
|
||||
/** returns the QHttpServer's backend type. */
|
||||
TBackend backendType() const;
|
||||
|
||||
signals:
|
||||
/** emitted when a client makes a new request to the server if you do not override
|
||||
* incomingConnection(QHttpConnection *connection);
|
||||
* @sa incommingConnection(). */
|
||||
void newRequest(QHttpRequest *request, QHttpResponse *response);
|
||||
|
||||
/** emitted when a new connection comes to the server if you do not override
|
||||
* incomingConnection(QHttpConnection *connection);
|
||||
* @sa incomingConnection(); */
|
||||
void newConnection(QHttpConnection* connection);
|
||||
|
||||
protected:
|
||||
/** returns the tcp server instance if the backend() == ETcpSocket. */
|
||||
QTcpServer* tcpServer() const;
|
||||
|
||||
/** returns the local server instance if the backend() == ELocalSocket. */
|
||||
QLocalServer* localServer() const;
|
||||
|
||||
|
||||
/** is called when server accepts a new connection.
|
||||
* you can override this function for using a thread-pool or ... some other reasons.
|
||||
*
|
||||
* the default implementation just connects QHttpConnection::newRequest signal.
|
||||
* @note if you override this method, the signal won't be emitted by QHttpServer.
|
||||
* (perhaps, you do not need it anymore).
|
||||
*
|
||||
* @param connection New incoming connection. */
|
||||
virtual void incomingConnection(QHttpConnection* connection);
|
||||
|
||||
/** overrides QTcpServer::incomingConnection() to make a new QHttpConnection.
|
||||
* override this function if you like to create your derived QHttpConnection instances.
|
||||
*
|
||||
* @note if you override this method, incomingConnection(QHttpConnection*) or
|
||||
* newRequest(QHttpRequest *, QHttpResponse *) signal won't be called.
|
||||
*
|
||||
* @see example/benchmark/server.cpp to see how to override.
|
||||
*/
|
||||
virtual void incomingConnection(qintptr handle);
|
||||
|
||||
private:
|
||||
explicit QHttpServer(QHttpServerPrivate&, QObject *parent);
|
||||
|
||||
Q_DECLARE_PRIVATE(QHttpServer)
|
||||
Q_DISABLE_COPY(QHttpServer)
|
||||
QScopedPointer<QHttpServerPrivate> d_ptr;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // define QHTTPSERVER_HPP
|
||||
217
src/http/qhttp/qhttpserverconnection.cpp
Normal file
217
src/http/qhttp/qhttpserverconnection.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
#include "private/qhttpserverconnection_private.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QHttpConnection::QHttpConnection(QObject *parent)
|
||||
: QObject(parent), d_ptr(new QHttpConnectionPrivate(this)) {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpConnection::QHttpConnection(QHttpConnectionPrivate& dd, QObject* parent)
|
||||
: QObject(parent), d_ptr(&dd) {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
void
|
||||
QHttpConnection::setSocketDescriptor(qintptr sokDescriptor, TBackend backendType) {
|
||||
d_ptr->createSocket(sokDescriptor, backendType);
|
||||
}
|
||||
|
||||
QHttpConnection::~QHttpConnection() {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
void
|
||||
QHttpConnection::setTimeOut(quint32 miliSeconds) {
|
||||
if ( miliSeconds != 0 ) {
|
||||
d_func()->itimeOut = miliSeconds;
|
||||
d_func()->itimer.start(miliSeconds, Qt::CoarseTimer, this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
QHttpConnection::killConnection() {
|
||||
d_func()->isocket.close();
|
||||
}
|
||||
|
||||
TBackend
|
||||
QHttpConnection::backendType() const {
|
||||
return d_func()->isocket.ibackendType;
|
||||
}
|
||||
|
||||
QTcpSocket*
|
||||
QHttpConnection::tcpSocket() const {
|
||||
return d_func()->isocket.itcpSocket;
|
||||
}
|
||||
|
||||
QLocalSocket*
|
||||
QHttpConnection::localSocket() const {
|
||||
return d_func()->isocket.ilocalSocket;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpConnection::onHandler(const TServerHandler &handler) {
|
||||
d_func()->ihandler = handler;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpConnection::timerEvent(QTimerEvent *) {
|
||||
killConnection();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// if user closes the connection, ends the response or by any other reason the
|
||||
// socket disconnects, then the irequest and iresponse instances may have
|
||||
// been deleted. In these situations reading more http body or emitting end()
|
||||
// for incoming request are not possible:
|
||||
// if ( ilastRequest == nullptr )
|
||||
// return 0;
|
||||
|
||||
|
||||
int
|
||||
QHttpConnectionPrivate::messageBegin(http_parser*) {
|
||||
itempUrl.clear();
|
||||
itempUrl.reserve(128);
|
||||
|
||||
if ( ilastRequest )
|
||||
ilastRequest->deleteLater();
|
||||
|
||||
ilastRequest = new QHttpRequest(q_func());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpConnectionPrivate::url(http_parser*, const char* at, size_t length) {
|
||||
Q_ASSERT(ilastRequest);
|
||||
|
||||
itempUrl.append(at, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpConnectionPrivate::headerField(http_parser*, const char* at, size_t length) {
|
||||
if ( ilastRequest == nullptr )
|
||||
return 0;
|
||||
|
||||
// insert the header we parsed previously
|
||||
// into the header map
|
||||
if ( !itempHeaderField.isEmpty() && !itempHeaderValue.isEmpty() ) {
|
||||
// header names are always lower-cased
|
||||
ilastRequest->d_func()->iheaders.insert(
|
||||
itempHeaderField.toLower(),
|
||||
itempHeaderValue.toLower()
|
||||
);
|
||||
// clear header value. this sets up a nice
|
||||
// feedback loop where the next time
|
||||
// HeaderValue is called, it can simply append
|
||||
itempHeaderField.clear();
|
||||
itempHeaderValue.clear();
|
||||
}
|
||||
|
||||
itempHeaderField.append(at, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpConnectionPrivate::headerValue(http_parser*, const char* at, size_t length) {
|
||||
if ( ilastRequest == nullptr )
|
||||
return 0;
|
||||
|
||||
itempHeaderValue.append(at, length);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpConnectionPrivate::headersComplete(http_parser* parser) {
|
||||
if ( ilastRequest == nullptr )
|
||||
return 0;
|
||||
|
||||
ilastRequest->d_func()->iurl = QUrl(itempUrl);
|
||||
|
||||
// set method
|
||||
ilastRequest->d_func()->imethod =
|
||||
static_cast<THttpMethod>(parser->method);
|
||||
|
||||
// set version
|
||||
ilastRequest->d_func()->iversion = QString("%1.%2")
|
||||
.arg(parser->http_major)
|
||||
.arg(parser->http_minor);
|
||||
|
||||
// Insert last remaining header
|
||||
ilastRequest->d_func()->iheaders.insert(
|
||||
itempHeaderField.toLower(),
|
||||
itempHeaderValue.toLower()
|
||||
);
|
||||
|
||||
// set client information
|
||||
if ( isocket.ibackendType == ETcpSocket || isocket.ibackendType == ESslSocket ) {
|
||||
ilastRequest->d_func()->iremoteAddress = isocket.itcpSocket->peerAddress().toString();
|
||||
ilastRequest->d_func()->iremotePort = isocket.itcpSocket->peerPort();
|
||||
|
||||
} else if ( isocket.ibackendType == ELocalSocket ) {
|
||||
ilastRequest->d_func()->iremoteAddress = isocket.ilocalSocket->fullServerName();
|
||||
ilastRequest->d_func()->iremotePort = 0; // not used in local sockets
|
||||
}
|
||||
|
||||
if ( ilastResponse )
|
||||
ilastResponse->deleteLater();
|
||||
ilastResponse = new QHttpResponse(q_func());
|
||||
|
||||
if ( parser->http_major < 1 || parser->http_minor < 1 )
|
||||
ilastResponse->d_func()->ikeepAlive = false;
|
||||
|
||||
// close the connection if response was the last packet
|
||||
QObject::connect(ilastResponse, &QHttpResponse::done, [this](bool wasTheLastPacket){
|
||||
ikeepAlive = !wasTheLastPacket;
|
||||
if ( wasTheLastPacket ) {
|
||||
isocket.flush();
|
||||
isocket.close();
|
||||
}
|
||||
});
|
||||
|
||||
// we are good to go!
|
||||
if ( ihandler )
|
||||
ihandler(ilastRequest, ilastResponse);
|
||||
else
|
||||
emit q_ptr->newRequest(ilastRequest, ilastResponse);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpConnectionPrivate::body(http_parser*, const char* at, size_t length) {
|
||||
if ( ilastRequest == nullptr )
|
||||
return 0;
|
||||
|
||||
ilastRequest->d_func()->ireadState = QHttpRequestPrivate::EPartial;
|
||||
|
||||
if ( ilastRequest->d_func()->icollectRequired ) {
|
||||
if ( !ilastRequest->d_func()->append(at, length) ) {
|
||||
// forcefully dispatch the ilastRequest
|
||||
finalizeConnection();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
emit ilastRequest->data(QByteArray(at, length));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
QHttpConnectionPrivate::messageComplete(http_parser*) {
|
||||
if ( ilastRequest == nullptr )
|
||||
return 0;
|
||||
|
||||
// request is done
|
||||
finalizeConnection();
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
87
src/http/qhttp/qhttpserverconnection.hpp
Normal file
87
src/http/qhttp/qhttpserverconnection.hpp
Normal file
@@ -0,0 +1,87 @@
|
||||
/** HTTP connection class.
|
||||
* https://github.com/azadkuh/qhttp
|
||||
*
|
||||
* @author amir zamani
|
||||
* @version 2.0.0
|
||||
* @date 2014-07-11
|
||||
*/
|
||||
|
||||
#ifndef QHTTPSERVER_CONNECTION_HPP
|
||||
#define QHTTPSERVER_CONNECTION_HPP
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#include "qhttpfwd.hpp"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/** a HTTP connection in the server.
|
||||
* this class controls the HTTP connetion and handles life cycle and the memory management
|
||||
* of QHttpRequest and QHttpResponse instances autoamtically.
|
||||
*/
|
||||
class QHTTP_API QHttpConnection : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
virtual ~QHttpConnection();
|
||||
|
||||
/** set an optional timer event to close the connection. */
|
||||
void setTimeOut(quint32 miliSeconds);
|
||||
|
||||
/** forcefully kills (closes) a connection. */
|
||||
void killConnection();
|
||||
|
||||
/** optionally set a handler for connection class.
|
||||
* @note if you set this handler, the newRequest() signal won't be emitted.
|
||||
*/
|
||||
void onHandler(const TServerHandler& handler);
|
||||
|
||||
/** returns the backend type of the connection. */
|
||||
TBackend backendType() const;
|
||||
|
||||
/** returns connected socket if the backend() == ETcpSocket. */
|
||||
QTcpSocket* tcpSocket() const;
|
||||
|
||||
/** returns connected socket if the backend() == ELocalSocket. */
|
||||
QLocalSocket* localSocket() const;
|
||||
|
||||
/** creates a new QHttpConnection based on arguments. */
|
||||
static
|
||||
QHttpConnection* create(qintptr sokDescriptor, TBackend backendType, QObject* parent) {
|
||||
QHttpConnection* conn = new QHttpConnection(parent);
|
||||
conn->setSocketDescriptor(sokDescriptor, backendType);
|
||||
return conn;
|
||||
}
|
||||
|
||||
signals:
|
||||
/** emitted when a pair of HTTP request and response are ready to interact.
|
||||
* @param req incoming request by the client.
|
||||
* @param res outgoing response to the client.
|
||||
*/
|
||||
void newRequest(QHttpRequest* req, QHttpResponse* res);
|
||||
|
||||
/** emitted when the tcp/local socket, disconnects. */
|
||||
void disconnected();
|
||||
|
||||
protected:
|
||||
explicit QHttpConnection(QObject *parent);
|
||||
explicit QHttpConnection(QHttpConnectionPrivate&, QObject *);
|
||||
|
||||
void setSocketDescriptor(qintptr sokDescriptor, TBackend backendType);
|
||||
void timerEvent(QTimerEvent*) override;
|
||||
|
||||
Q_DISABLE_COPY(QHttpConnection)
|
||||
Q_DECLARE_PRIVATE(QHttpConnection)
|
||||
QScopedPointer<QHttpConnectionPrivate> d_ptr;
|
||||
|
||||
friend class QHttpServer;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
#endif // #define QHTTPSERVER_CONNECTION_HPP
|
||||
81
src/http/qhttp/qhttpserverrequest.cpp
Normal file
81
src/http/qhttp/qhttpserverrequest.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#include "private/qhttpserverrequest_private.hpp"
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
namespace qhttp {
|
||||
namespace server {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
QHttpRequest::QHttpRequest(QHttpConnection *conn)
|
||||
: QHttpAbstractInput(conn), d_ptr(new QHttpRequestPrivate(conn, this)) {
|
||||
d_ptr->initialize();
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpRequest::QHttpRequest(QHttpRequestPrivate &dd, QHttpConnection *conn)
|
||||
: QHttpAbstractInput(conn), d_ptr(&dd) {
|
||||
d_ptr->initialize();
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
QHttpRequest::~QHttpRequest() {
|
||||
QHTTP_LINE_LOG
|
||||
}
|
||||
|
||||
THttpMethod
|
||||
QHttpRequest::method() const {
|
||||
return d_func()->imethod;
|
||||
}
|
||||
|
||||
const QString
|
||||
QHttpRequest::methodString() const {
|
||||
return http_method_str(static_cast<http_method>(d_func()->imethod));
|
||||
}
|
||||
|
||||
const QUrl&
|
||||
QHttpRequest::url() const {
|
||||
return d_func()->iurl;
|
||||
}
|
||||
|
||||
const QString&
|
||||
QHttpRequest::httpVersion() const {
|
||||
return d_func()->iversion;
|
||||
}
|
||||
|
||||
const THeaderHash&
|
||||
QHttpRequest::headers() const {
|
||||
return d_func()->iheaders;
|
||||
}
|
||||
|
||||
const QString&
|
||||
QHttpRequest::remoteAddress() const {
|
||||
return d_func()->iremoteAddress;
|
||||
}
|
||||
|
||||
quint16
|
||||
QHttpRequest::remotePort() const {
|
||||
return d_func()->iremotePort;
|
||||
}
|
||||
|
||||
bool
|
||||
QHttpRequest::isSuccessful() const {
|
||||
return d_func()->isuccessful;
|
||||
}
|
||||
|
||||
void
|
||||
QHttpRequest::collectData(int atMost) {
|
||||
d_func()->collectData(atMost);
|
||||
}
|
||||
|
||||
const QByteArray&
|
||||
QHttpRequest::collectedData() const {
|
||||
return d_func()->icollectedData;
|
||||
}
|
||||
|
||||
QHttpConnection*
|
||||
QHttpRequest::connection() const {
|
||||
return d_ptr->iconnection;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace server
|
||||
} // namespace qhttp
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user