Merge branch 'release/2.1.2' into develop

This commit is contained in:
Janek Bevendorff 2017-02-17 04:03:12 +01:00
commit e0e6f2b8e9
No known key found for this signature in database
GPG Key ID: CFEC2F6850BFFA53
70 changed files with 7701 additions and 723 deletions

4
.gitignore vendored
View File

@ -1,2 +1,6 @@
CMakeLists.txt.* CMakeLists.txt.*
build*/ build*/
release*/
.idea/
*.iml
*.kdev4

View File

@ -49,8 +49,6 @@ cp -a ../../bin-release/* .
cp -a ./usr/local/* ./usr cp -a ./usr/local/* ./usr
rm -R ./usr/local rm -R ./usr/local
rmdir ./opt 2> /dev/null rmdir ./opt 2> /dev/null
patch_strings_in_file /usr/local ././
patch_strings_in_file /usr ./
# bundle Qt platform plugins and themes # bundle Qt platform plugins and themes
QXCB_PLUGIN="$(find /usr/lib -name 'libqxcb.so' 2> /dev/null)" QXCB_PLUGIN="$(find /usr/lib -name 'libqxcb.so' 2> /dev/null)"
@ -76,6 +74,11 @@ cat << EOF > ./usr/bin/keepassxc_env
#export QT_QPA_PLATFORMTHEME=gtk2 #export QT_QPA_PLATFORMTHEME=gtk2
export LD_LIBRARY_PATH="../opt/qt58/lib:\${LD_LIBRARY_PATH}" export LD_LIBRARY_PATH="../opt/qt58/lib:\${LD_LIBRARY_PATH}"
export QT_PLUGIN_PATH="..${QT_PLUGIN_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 "\$@" exec keepassxc "\$@"
EOF EOF
chmod +x ./usr/bin/keepassxc_env chmod +x ./usr/bin/keepassxc_env

View File

@ -166,6 +166,9 @@ find_package(Qt5Widgets 5.2 REQUIRED)
find_package(Qt5Test 5.2 REQUIRED) find_package(Qt5Test 5.2 REQUIRED)
find_package(Qt5LinguistTools 5.2 REQUIRED) find_package(Qt5LinguistTools 5.2 REQUIRED)
find_package(Qt5Network 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) set(CMAKE_AUTOMOC ON)
# Debian sets the the build type to None for package builds. # Debian sets the the build type to None for package builds.
@ -176,10 +179,6 @@ find_package(LibGPGError REQUIRED)
find_package(Gcrypt 1.6.0 REQUIRED) find_package(Gcrypt 1.6.0 REQUIRED)
if (WITH_XC_HTTP)
find_package(LibMicroHTTPD REQUIRED)
endif(WITH_XC_HTTP)
find_package(ZLIB REQUIRED) find_package(ZLIB REQUIRED)
check_cxx_source_compiles(" check_cxx_source_compiles("
@ -217,7 +216,7 @@ if(UNIX)
endif() endif()
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) include(FeatureSummary)

View File

@ -21,9 +21,11 @@ install(FILES ${DATABASE_ICONS} DESTINATION ${DATA_INSTALL_DIR}/icons/database)
if(UNIX AND NOT APPLE) if(UNIX AND NOT APPLE)
install(DIRECTORY icons/application/ DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor 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 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.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages) install(FILES linux/keepassxc.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
endif(UNIX AND NOT APPLE) endif(UNIX AND NOT APPLE)

View File

@ -151,24 +151,9 @@ set(keepassx_FORMS
add_feature_info(KeePassHTTP WITH_XC_HTTP "KeePassHTTP support for ChromeIPass and PassIFox") 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_feature_info(Autotype WITH_XC_AUTOTYPE "Auto-type passwords in Input fields")
add_subdirectory(http)
if(WITH_XC_HTTP) if(WITH_XC_HTTP)
set(keepasshttp_SOURCES set(keepasshttp_LIB keepasshttp)
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})
endif() endif()
add_subdirectory(autotype) add_subdirectory(autotype)
@ -197,24 +182,19 @@ qt5_wrap_ui(keepassx_SOURCES ${keepassx_FORMS})
add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp) add_library(zxcvbn STATIC zxcvbn/zxcvbn.cpp)
target_link_libraries(zxcvbn) 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}) add_library(autotype STATIC ${autotype_SOURCES})
target_link_libraries(autotype Qt5::Core Qt5::Widgets) target_link_libraries(autotype Qt5::Core Qt5::Widgets)
set(autotype_LIB autotype) set(autotype_LIB autotype)
add_library(keepassx_core STATIC ${keepassx_SOURCES}) 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}) set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
target_link_libraries(${PROGNAME} target_link_libraries(keepassx_core
keepassx_core ${keepasshttp_LIB}
${autotype_LIB}
zxcvbn zxcvbn
qhttp
Qt5::Core Qt5::Core
Qt5::Concurrent Qt5::Concurrent
Qt5::Widgets Qt5::Widgets
@ -222,6 +202,12 @@ target_link_libraries(${PROGNAME}
${GCRYPT_LIBRARIES} ${GCRYPT_LIBRARIES}
${GPGERROR_LIBRARIES} ${GPGERROR_LIBRARIES}
${ZLIB_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) set_target_properties(${PROGNAME} PROPERTIES ENABLE_EXPORTS ON)

View File

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>455</width> <width>455</width>
<height>238</height> <height>266</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -53,7 +53,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string notr="true">&lt;a href=&quot;https://www.keepassxc.org/&quot;&gt;https://www.keepassxc.org/&lt;/a&gt;</string> <string notr="true">&lt;a href=&quot;https://keepassxc.org/&quot;&gt;https://keepassxc.org/&lt;/a&gt;</string>
</property> </property>
<property name="openExternalLinks"> <property name="openExternalLinks">
<bool>true</bool> <bool>true</bool>
@ -69,7 +69,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <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>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@ -82,7 +82,7 @@
<string/> <string/>
</property> </property>
<property name="textInteractionFlags"> <property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set> <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property> </property>
</widget> </widget>
</item> </item>
@ -91,6 +91,9 @@
<property name="text"> <property name="text">
<string>Using:</string> <string>Using:</string>
</property> </property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>
@ -99,6 +102,9 @@
<string>Extensions: <string>Extensions:
</string> </string>
</property> </property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget> </widget>
</item> </item>
<item> <item>

View File

@ -139,3 +139,8 @@ void ChangeMasterKeyWidget::reject()
{ {
Q_EMIT editFinished(false); Q_EMIT editFinished(false);
} }
void ChangeMasterKeyWidget::setCancelEnabled(bool enabled)
{
m_ui->buttonBox->button(QDialogButtonBox::Cancel)->setEnabled(enabled);
}

View File

@ -38,6 +38,7 @@ public:
void clearForms(); void clearForms();
CompositeKey newMasterKey(); CompositeKey newMasterKey();
QLabel* headlineLabel(); QLabel* headlineLabel();
void setCancelEnabled(bool enabled);
Q_SIGNALS: Q_SIGNALS:
void editFinished(bool accepted); void editFinished(bool accepted);

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>438</width> <width>818</width>
<height>342</height> <height>397</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
@ -44,8 +44,8 @@
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0"> <item row="0" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="enterPasswordLabel"> <widget class="QLabel" name="enterPasswordLabel">
<property name="text"> <property name="text">
<string>Enter password:</string> <string>Enter password:</string>
@ -70,7 +70,7 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="1" column="0"> <item row="1" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="repeatPasswordLabel"> <widget class="QLabel" name="repeatPasswordLabel">
<property name="text"> <property name="text">
<string>Repeat password:</string> <string>Repeat password:</string>

View File

@ -54,6 +54,13 @@ DatabaseOpenWidget::DatabaseOpenWidget(QWidget* parent)
connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase())); connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(openDatabase()));
connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); 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() DatabaseOpenWidget::~DatabaseOpenWidget()

View File

@ -10,7 +10,7 @@
<height>250</height> <height>250</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,0,1,0,0,3"> <layout class="QVBoxLayout" name="verticalLayout" stretch="0,1,0,1,0,0,3">
<property name="spacing"> <property name="spacing">
<number>8</number> <number>8</number>
</property> </property>
@ -31,14 +31,24 @@
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QLabel" name="labelHeadline"> <layout class="QVBoxLayout" name="labelLayout">
<property name="text"> <property name="leftMargin">
<string>Enter master key</string> <number>5</number>
</property> </property>
</widget> <property name="rightMargin">
</item> <number>5</number>
<item> </property>
<widget class="QLabel" name="labelFilename"/> <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>
<item> <item>
<spacer name="verticalSpacer_3"> <spacer name="verticalSpacer_3">
@ -55,17 +65,20 @@
</item> </item>
<item> <item>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="verticalSpacing"> <property name="verticalSpacing">
<number>8</number> <number>8</number>
</property> </property>
<item row="1" column="0"> <item row="1" column="0" alignment="Qt::AlignVCenter">
<widget class="QCheckBox" name="checkKeyFile"> <widget class="QCheckBox" name="checkKeyFile">
<property name="text"> <property name="text">
<string>Key File:</string> <string>Key File:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="0"> <item row="0" column="0" alignment="Qt::AlignVCenter">
<widget class="QCheckBox" name="checkPassword"> <widget class="QCheckBox" name="checkPassword">
<property name="text"> <property name="text">
<string>Password:</string> <string>Password:</string>
@ -73,9 +86,18 @@
</widget> </widget>
</item> </item>
<item row="1" column="1"> <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> <item>
<widget class="QComboBox" name="comboKeyFile"> <widget class="QComboBox" name="comboKeyFile">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -97,7 +119,13 @@
</layout> </layout>
</item> </item>
<item row="0" column="1"> <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> <item>
<widget class="PasswordEdit" name="editPassword"> <widget class="PasswordEdit" name="editPassword">
<property name="echoMode"> <property name="echoMode">
@ -117,14 +145,21 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <layout class="QHBoxLayout" name="dialogButtonsLayout">
<property name="orientation"> <property name="leftMargin">
<enum>Qt::Horizontal</enum> <number>5</number>
</property> </property>
<property name="standardButtons"> <property name="rightMargin">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> <number>5</number>
</property> </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>
<item> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
@ -161,7 +196,6 @@
<tabstop>checkKeyFile</tabstop> <tabstop>checkKeyFile</tabstop>
<tabstop>comboKeyFile</tabstop> <tabstop>comboKeyFile</tabstop>
<tabstop>buttonBrowseFile</tabstop> <tabstop>buttonBrowseFile</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -6,181 +6,225 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>500</width> <width>600</width>
<height>399</height> <height>340</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout" stretch="1,2,5,1">
<item> <item>
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>
</property> </property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>1</width> <width>0</width>
<height>3</height> <height>0</height>
</size> </size>
</property> </property>
</spacer> </spacer>
</item> </item>
<item> <item>
<layout class="QFormLayout" name="formLayout"> <layout class="QHBoxLayout" name="horizontalLayout_5" stretch="0,1,0">
<item row="1" column="0"> <item>
<widget class="QLabel" name="dbNameLabel"> <spacer name="horizontalSpacer">
<property name="text"> <property name="orientation">
<string>Database name:</string> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> <property name="sizeHint" stdset="0">
</item> <size>
<item row="1" column="1"> <width>0</width>
<widget class="QLineEdit" name="dbNameEdit"/> <height>0</height>
</item> </size>
<item row="2" column="0">
<widget class="QLabel" name="dbDescriptionLabel">
<property name="text">
<string>Database description:</string>
</property> </property>
</widget> </spacer>
</item> </item>
<item row="2" column="1"> <item>
<widget class="QLineEdit" name="dbDescriptionEdit"/> <widget class="QWidget" name="widget" native="true">
</item> <property name="maximumSize">
<item row="9" column="1"> <size>
<layout class="QHBoxLayout" name="horizontalLayout"> <width>800</width>
<item> <height>16777215</height>
<widget class="QSpinBox" name="historyMaxSizeSpinBox"> </size>
<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="8" 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="5" 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="8" column="0">
<widget class="QCheckBox" name="historyMaxItemsCheckBox">
<property name="text">
<string>Max. history items:</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" 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="7" column="1">
<widget class="QCheckBox" name="historyMaxSizeCheckBox">
<property name="text">
<string>Max. history size:</string>
</property>
</widget>
</item>
<item row="3" column="1" alignment="Qt::AlignRight">
<widget class="QLabel" name="transformRoundsLabel">
<property name="text">
<string>Transform rounds:</string>
</property>
</widget>
</item>
<item row="6" 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="6" 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="4" 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="7" 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="5" column="2">
<widget class="QCheckBox" name="recycleBinEnabledCheckBox">
<property name="text">
<string>Use recycle bin</string>
</property>
</widget>
</item>
<item row="4" 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>
<item row="2" column="2">
<widget class="QComboBox" name="AlgorithmComboBox">
<item>
<property name="text">
<string>AES: 256 Bit (default)</string>
</property>
</item>
<item>
<property name="text">
<string>Twofish: 256 Bit</string>
</property>
</item>
</widget>
</item>
<item row="2" column="1" alignment="Qt::AlignRight">
<widget class="QLabel" name="AlgorithmLabel">
<property name="text">
<string>Algorithm:</string>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
<item row="9" column="0"> <item>
<widget class="QCheckBox" name="historyMaxSizeCheckBox"> <spacer name="horizontalSpacer_2">
<property name="text"> <property name="orientation">
<string>Max. history size:</string> <enum>Qt::Horizontal</enum>
</property> </property>
</widget> <property name="sizeHint" stdset="0">
</item> <size>
<item row="5" column="0"> <width>0</width>
<widget class="QLabel" name="transformRoundsLabel"> <height>0</height>
<property name="text"> </size>
<string>Transform rounds:</string>
</property> </property>
</widget> </spacer>
</item>
<item row="7" column="1">
<widget class="QCheckBox" name="recycleBinEnabledCheckBox"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="defaultUsernameLabel">
<property name="text">
<string>Default username:</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Use recycle bin:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="defaultUsernameEdit">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="AlgorithmLabel">
<property name="text">
<string>Algorithm:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="AlgorithmComboBox">
<item>
<property name="text">
<string>AES: 256 Bit (default)</string>
</property>
</item>
<item>
<property name="text">
<string>Twofish: 256 Bit</string>
</property>
</item>
</widget>
</item> </item>
</layout> </layout>
</item> </item>

View File

@ -77,14 +77,9 @@ DatabaseTabWidget::~DatabaseTabWidget()
void DatabaseTabWidget::toggleTabbar() void DatabaseTabWidget::toggleTabbar()
{ {
if (count() > 1) { if (count() > 1) {
if (!tabBar()->isVisible()) { tabBar()->show();
tabBar()->show(); } else {
} tabBar()->hide();
}
else {
if (tabBar()->isVisible()) {
tabBar()->hide();
}
} }
} }
@ -94,10 +89,18 @@ void DatabaseTabWidget::newDatabase()
Database* db = new Database(); Database* db = new Database();
db->rootGroup()->setName(tr("Root")); db->rootGroup()->setName(tr("Root"));
dbStruct.dbWidget = new DatabaseWidget(db, this); dbStruct.dbWidget = new DatabaseWidget(db, this);
CompositeKey emptyKey;
db->setKey(emptyKey);
insertDatabase(db, dbStruct); insertDatabase(db, dbStruct);
if (!saveDatabaseAs(db)) {
closeDatabase(db);
return;
}
dbStruct.dbWidget->switchToMasterKeyChange(); dbStruct.dbWidget->switchToMasterKeyChange(true);
} }
void DatabaseTabWidget::openDatabase() void DatabaseTabWidget::openDatabase()
@ -360,80 +363,78 @@ bool DatabaseTabWidget::saveDatabase(Database* db)
bool DatabaseTabWidget::saveDatabaseAs(Database* db) bool DatabaseTabWidget::saveDatabaseAs(Database* db)
{ {
DatabaseManagerStruct& dbStruct = m_dbList[db]; while (true) {
QString oldFileName; DatabaseManagerStruct& dbStruct = m_dbList[db];
if (dbStruct.saveToFilename) { QString oldFileName;
oldFileName = dbStruct.filePath; if (dbStruct.saveToFilename) {
} oldFileName = dbStruct.filePath;
else { } else {
oldFileName = tr("New database").append(".kdbx"); oldFileName = tr("Passwords").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();
} }
else { QString fileName = fileDialog()->getSaveFileName(this, tr("Save database as"),
lockFilePath = fileInfo.absolutePath(); oldFileName, tr("KeePass 2 Database").append(" (*.kdbx)"),
} nullptr, 0, "kdbx");
QString lockFileName = QString("%1/.%2.lock").arg(lockFilePath, fileInfo.fileName()); if (!fileName.isEmpty()) {
QScopedPointer<QLockFile> lockFile(new QLockFile(lockFileName)); QFileInfo fileInfo(fileName);
lockFile->setStaleLockTime(0); QString lockFilePath;
if (!lockFile->tryLock()) { if (fileInfo.exists()) {
// for now silently ignore if we can't create a lock file // returns empty string when file doesn't exist
// due to lack of permissions lockFilePath = fileInfo.canonicalPath();
if (lockFile->error() != QLockFile::PermissionError) { } else {
QMessageBox::StandardButton result = MessageBox::question(this, tr("Save database as"), lockFilePath = fileInfo.absolutePath();
tr("The database you are trying to save as is locked by another instance of KeePassXC.\n" }
"Do you want to save it anyway?"), QString lockFileName = QString("%1/.%2.lock").arg(lockFilePath, fileInfo.fileName());
QMessageBox::Yes | QMessageBox::No); 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) { if (result == QMessageBox::No) {
return false; return false;
} } else {
else { // take over the lock file if possible
// take over the lock file if possible if (lockFile->removeStaleLockFile()) {
if (lockFile->removeStaleLockFile()) { lockFile->tryLock();
lockFile->tryLock(); }
} }
} }
} }
}
// setup variables so saveDatabase succeeds // setup variables so saveDatabase succeeds
dbStruct.saveToFilename = true; dbStruct.saveToFilename = true;
dbStruct.canonicalFilePath = fileName; dbStruct.canonicalFilePath = fileName;
if (!saveDatabase(db)) { if (!saveDatabase(db)) {
// failed to save, revert back // failed to save, revert back
dbStruct.saveToFilename = false; dbStruct.saveToFilename = false;
dbStruct.canonicalFilePath = oldFileName; 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; 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;
} }
} }

View File

@ -817,9 +817,10 @@ void DatabaseWidget::switchToGroupEdit()
switchToGroupEdit(group, false); switchToGroupEdit(group, false);
} }
void DatabaseWidget::switchToMasterKeyChange() void DatabaseWidget::switchToMasterKeyChange(bool disableCancel)
{ {
m_changeMasterKeyWidget->clearForms(); m_changeMasterKeyWidget->clearForms();
m_changeMasterKeyWidget->setCancelEnabled(!disableCancel);
setCurrentWidget(m_changeMasterKeyWidget); setCurrentWidget(m_changeMasterKeyWidget);
} }

View File

@ -138,7 +138,7 @@ public Q_SLOTS:
void switchToView(bool accepted); void switchToView(bool accepted);
void switchToEntryEdit(); void switchToEntryEdit();
void switchToGroupEdit(); void switchToGroupEdit();
void switchToMasterKeyChange(); void switchToMasterKeyChange(bool disableCancel = false);
void switchToDatabaseSettings(); void switchToDatabaseSettings();
void switchToOpenDatabase(const QString& fileName); void switchToOpenDatabase(const QString& fileName);
void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile); void switchToOpenDatabase(const QString& fileName, const QString& password, const QString& keyFile);

View File

@ -52,11 +52,18 @@
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<property name="standardButtons"> <property name="topMargin">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> <number>5</number>
</property> </property>
</widget> <item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -75,7 +82,6 @@
</customwidgets> </customwidgets>
<tabstops> <tabstops>
<tabstop>categoryList</tabstop> <tabstop>categoryList</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections/> <connections/>

View File

@ -28,6 +28,11 @@
#include "gui/IconModels.h" #include "gui/IconModels.h"
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
#include "http/qhttp/qhttpclient.hpp"
#include "http/qhttp/qhttpclientresponse.hpp"
using namespace qhttp::client;
IconStruct::IconStruct() IconStruct::IconStruct()
: uuid(Uuid()) : uuid(Uuid())
, number(0) , number(0)
@ -40,8 +45,7 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
, m_database(nullptr) , m_database(nullptr)
, m_defaultIconModel(new DefaultIconModel(this)) , m_defaultIconModel(new DefaultIconModel(this))
, m_customIconModel(new CustomIconModel(this)) , m_customIconModel(new CustomIconModel(this))
, m_networkAccessMngr(new QNetworkAccessManager(this)) , m_httpClient(nullptr)
, m_networkOperation(nullptr)
{ {
m_ui->setupUi(this); m_ui->setupUi(this);
@ -59,8 +63,6 @@ EditWidgetIcons::EditWidgetIcons(QWidget* parent)
connect(m_ui->addButton, SIGNAL(clicked()), SLOT(addCustomIcon())); connect(m_ui->addButton, SIGNAL(clicked()), SLOT(addCustomIcon()));
connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon())); connect(m_ui->deleteButton, SIGNAL(clicked()), SLOT(removeCustomIcon()));
connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon())); connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon()));
connect(m_networkAccessMngr, SIGNAL(finished(QNetworkReply*)),
this, SLOT(onRequestFinished(QNetworkReply*)) );
m_ui->faviconButton->setVisible(false); m_ui->faviconButton->setVisible(false);
} }
@ -93,7 +95,7 @@ IconStruct EditWidgetIcons::state()
iconStruct.number = -1; iconStruct.number = -1;
} }
} }
return iconStruct; return iconStruct;
} }
@ -103,7 +105,7 @@ void EditWidgetIcons::reset()
m_currentUuid = Uuid(); 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(database);
Q_ASSERT(!currentUuid.isNull()); 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_url = url;
m_ui->faviconButton->setVisible(!url.isEmpty()); m_ui->faviconButton->setVisible(!url.isEmpty());
abortFaviconDownload(); resetFaviconDownload();
} }
void EditWidgetIcons::downloadFavicon() void EditWidgetIcons::downloadFavicon()
@ -148,86 +150,98 @@ void EditWidgetIcons::downloadFavicon()
fetchFavicon(url); fetchFavicon(url);
} }
void EditWidgetIcons::fetchFavicon(QUrl url) void EditWidgetIcons::fetchFavicon(const QUrl& url)
{ {
if (m_networkOperation == nullptr) { if (nullptr == m_httpClient) {
m_networkOperation = m_networkAccessMngr->get(QNetworkRequest(url)); m_httpClient = new QHttpClient(this);
m_ui->faviconButton->setDisabled(true);
} }
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) { if (m_fallbackToGoogle) {
abortFaviconDownload(); resetFaviconDownload();
m_fallbackToGoogle = false; m_fallbackToGoogle = false;
fetchFavicon(QUrl("http://www.google.com/s2/favicons?domain=" + domain)); fetchFavicon(QUrl("http://www.google.com/s2/favicons?domain=" + domain));
} else { } else {
abortFaviconDownload(); resetFaviconDownload();
MessageBox::warning(this, tr("Error"), tr("Unable to fetch favicon.")); 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 (clearRedirect) {
if (!m_redirectUrl.isEmpty()) { m_redirectUrl.clear();
m_redirectUrl.clear();
}
m_redirectCount = 0; m_redirectCount = 0;
} }
if (nullptr != m_httpClient) {
m_httpClient->deleteLater();
m_httpClient = nullptr;
}
m_fallbackToGoogle = true; m_fallbackToGoogle = true;
m_ui->faviconButton->setDisabled(false); m_ui->faviconButton->setDisabled(false);
} }
void EditWidgetIcons::onRequestFinished(QNetworkReply *reply)
{
if (m_database == nullptr) {
return;
}
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() void EditWidgetIcons::addCustomIcon()
{ {
if (m_database) { if (m_database) {

View File

@ -20,9 +20,7 @@
#include <QWidget> #include <QWidget>
#include <QSet> #include <QSet>
#include <QUrl>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include "core/Global.h" #include "core/Global.h"
#include "core/Uuid.h" #include "core/Uuid.h"
@ -32,6 +30,11 @@ class Database;
class DefaultIconModel; class DefaultIconModel;
class CustomIconModel; class CustomIconModel;
namespace qhttp {
namespace client {
class QHttpClient;
}
}
namespace Ui { namespace Ui {
class EditWidgetIcons; class EditWidgetIcons;
} }
@ -54,10 +57,10 @@ public:
IconStruct state(); IconStruct state();
void reset(); 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: public Q_SLOTS:
void setUrl(const QString &url); void setUrl(const QString& url);
Q_SIGNALS: Q_SIGNALS:
void messageEditEntry(QString, MessageWidget::MessageType); void messageEditEntry(QString, MessageWidget::MessageType);
@ -65,10 +68,9 @@ Q_SIGNALS:
private Q_SLOTS: private Q_SLOTS:
void downloadFavicon(); void downloadFavicon();
void fetchFavicon(QUrl url); void fetchFavicon(const QUrl& url);
void fetchFaviconFromGoogle(QString domain); void fetchFaviconFromGoogle(const QString& domain);
void abortFaviconDownload(bool clearRedirect = true); void resetFaviconDownload(bool clearRedirect = true);
void onRequestFinished(QNetworkReply *reply);
void addCustomIcon(); void addCustomIcon();
void removeCustomIcon(); void removeCustomIcon();
void updateWidgetsDefaultIcons(bool checked); void updateWidgetsDefaultIcons(bool checked);
@ -86,8 +88,7 @@ private:
unsigned short m_redirectCount = 0; unsigned short m_redirectCount = 0;
DefaultIconModel* const m_defaultIconModel; DefaultIconModel* const m_defaultIconModel;
CustomIconModel* const m_customIconModel; CustomIconModel* const m_customIconModel;
QNetworkAccessManager* const m_networkAccessMngr; qhttp::client::QHttpClient* m_httpClient;
QNetworkReply* m_networkOperation;
Q_DISABLE_COPY(EditWidgetIcons) Q_DISABLE_COPY(EditWidgetIcons)
}; };

View File

@ -6,15 +6,27 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>437</width>
<height>300</height> <height>300</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <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> <item>
<widget class="QRadioButton" name="defaultIconsRadio"> <widget class="QRadioButton" name="defaultIconsRadio">
<property name="text"> <property name="text">
<string>Use default icon</string> <string>&amp;Use default icon</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -46,7 +58,7 @@
<item> <item>
<widget class="QRadioButton" name="customIconsRadio"> <widget class="QRadioButton" name="customIconsRadio">
<property name="text"> <property name="text">
<string>Use custom icon</string> <string>Use custo&amp;m icon</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -14,6 +14,15 @@
<property name="fieldGrowthPolicy"> <property name="fieldGrowthPolicy">
<enum>QFormLayout::ExpandingFieldsGrow</enum> <enum>QFormLayout::ExpandingFieldsGrow</enum>
</property> </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"> <item row="1" column="1">
<widget class="QLineEdit" name="createdEdit"> <widget class="QLineEdit" name="createdEdit">
<property name="sizePolicy"> <property name="sizePolicy">

View File

@ -22,6 +22,11 @@
#include <QShortcut> #include <QShortcut>
#include <QTimer> #include <QTimer>
#if defined(Q_OS_LINUX) && ! defined(QT_NO_DBUS)
#include <QList>
#include <QtDBus/QtDBus>
#endif
#include "config-keepassx.h" #include "config-keepassx.h"
#include "autotype/AutoType.h" #include "autotype/AutoType.h"
@ -590,7 +595,7 @@ void MainWindow::closeEvent(QCloseEvent* event)
if (minimizeOnClose && !appExitCalled) if (minimizeOnClose && !appExitCalled)
{ {
event->ignore(); event->ignore();
hide(); toggleWindow();
if (config()->get("security/lockdatabaseminimize").toBool()) { if (config()->get("security/lockdatabaseminimize").toBool()) {
m_ui->tabWidget->lockDatabases(); m_ui->tabWidget->lockDatabases();
@ -758,18 +763,37 @@ void MainWindow::trayIconTriggered(QSystemTrayIcon::ActivationReason reason)
void MainWindow::toggleWindow() void MainWindow::toggleWindow()
{ {
if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) { if ((QApplication::activeWindow() == this) && isVisible() && !isMinimized()) {
hide(); setWindowState(windowState() | Qt::WindowMinimized);
QTimer::singleShot(0, this, SLOT(hide()));
if (config()->get("security/lockdatabaseminimize").toBool()) { if (config()->get("security/lockdatabaseminimize").toBool()) {
m_ui->tabWidget->lockDatabases(); m_ui->tabWidget->lockDatabases();
} }
} } else {
else {
ensurePolished(); ensurePolished();
setWindowState(windowState() & ~Qt::WindowMinimized); setWindowState(windowState() & ~Qt::WindowMinimized);
show(); show();
raise(); raise();
activateWindow(); 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
} }
} }

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>500</width> <width>575</width>
<height>278</height> <height>284</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -16,10 +16,25 @@
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </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"> <property name="windowTitle">
<string/> <string/>
</property> </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> <item>
<layout class="QGridLayout" name="passwordFieldLayout"> <layout class="QGridLayout" name="passwordFieldLayout">
<property name="bottomMargin"> <property name="bottomMargin">
@ -232,13 +247,19 @@ QProgressBar::chunk {
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<layout class="QHBoxLayout" name="alphabetLayout"> <layout class="QHBoxLayout" name="alphabetLayout" stretch="0,0,0,0,1">
<item> <item>
<widget class="QToolButton" name="checkBoxUpper"> <widget class="QToolButton" name="checkBoxUpper">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>26</height> <height>25</height>
</size> </size>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
@ -260,10 +281,16 @@ QProgressBar::chunk {
</item> </item>
<item> <item>
<widget class="QToolButton" name="checkBoxLower"> <widget class="QToolButton" name="checkBoxLower">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>26</height> <height>25</height>
</size> </size>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
@ -285,10 +312,16 @@ QProgressBar::chunk {
</item> </item>
<item> <item>
<widget class="QToolButton" name="checkBoxNumbers"> <widget class="QToolButton" name="checkBoxNumbers">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>26</height> <height>25</height>
</size> </size>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">
@ -310,10 +343,16 @@ QProgressBar::chunk {
</item> </item>
<item> <item>
<widget class="QToolButton" name="checkBoxSpecialChars"> <widget class="QToolButton" name="checkBoxSpecialChars">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>26</height> <height>25</height>
</size> </size>
</property> </property>
<property name="focusPolicy"> <property name="focusPolicy">

View File

@ -65,6 +65,7 @@ SettingsWidget::SettingsWidget(QWidget* parent)
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
// systray not useful on OS X // systray not useful on OS X
m_generalUi->systrayShowCheckBox->setVisible(false); m_generalUi->systrayShowCheckBox->setVisible(false);
m_generalUi->systrayMinimizeOnCloseCheckBox->setVisible(false);
m_generalUi->systrayMinimizeToTrayCheckBox->setVisible(false); m_generalUi->systrayMinimizeToTrayCheckBox->setVisible(false);
#endif #endif

View File

@ -77,9 +77,10 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
connect(this, SIGNAL(accepted()), SLOT(saveEntry())); connect(this, SIGNAL(accepted()), SLOT(saveEntry()));
connect(this, SIGNAL(rejected()), SLOT(cancel())); connect(this, SIGNAL(rejected()), SLOT(cancel()));
connect(m_iconsWidget, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType))); connect(m_iconsWidget, SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)), SLOT(showMessage(QString, MessageWidget::MessageType)));
connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage())); connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
m_mainUi->passwordGenerator->layout()->setContentsMargins(0, 0, 0, 0);
} }
EditEntryWidget::~EditEntryWidget() EditEntryWidget::~EditEntryWidget()

View File

@ -7,10 +7,22 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>400</width> <width>400</width>
<height>315</height> <height>366</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <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> <item>
<widget class="QGroupBox" name="attributesBox"> <widget class="QGroupBox" name="attributesBox">
<property name="title"> <property name="title">

View File

@ -7,10 +7,22 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>567</width> <width>567</width>
<height>347</height> <height>348</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_2"> <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> <item>
<widget class="QCheckBox" name="enableButton"> <widget class="QCheckBox" name="enableButton">
<property name="text"> <property name="text">
@ -37,14 +49,14 @@
<item> <item>
<widget class="QRadioButton" name="inheritSequenceButton"> <widget class="QRadioButton" name="inheritSequenceButton">
<property name="text"> <property name="text">
<string>Inherit default Auto-Type sequence from the group</string> <string>Inherit default Auto-Type sequence from the &amp;group</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="customSequenceButton"> <widget class="QRadioButton" name="customSequenceButton">
<property name="text"> <property name="text">
<string>Use custom Auto-Type sequence:</string> <string>&amp;Use custom Auto-Type sequence:</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -90,39 +102,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4" stretch="2,1,1">
<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>
<item> <item>
<spacer name="horizontalSpacer_3"> <spacer name="horizontalSpacer_3">
<property name="orientation"> <property name="orientation">
@ -136,6 +116,50 @@
</property> </property>
</spacer> </spacer>
</item> </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> </layout>
</item> </item>
</layout> </layout>
@ -171,14 +195,14 @@
<item> <item>
<widget class="QRadioButton" name="defaultWindowSequenceButton"> <widget class="QRadioButton" name="defaultWindowSequenceButton">
<property name="text"> <property name="text">
<string>Use default sequence</string> <string>Use default se&amp;quence</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QRadioButton" name="customWindowSequenceButton"> <widget class="QRadioButton" name="customWindowSequenceButton">
<property name="text"> <property name="text">
<string>Set custom sequence:</string> <string>Set custo&amp;m sequence:</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -11,6 +11,18 @@
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <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> <item>
<widget class="QTreeView" name="historyView"> <widget class="QTreeView" name="historyView">
<property name="sortingEnabled"> <property name="sortingEnabled">

View File

@ -6,35 +6,50 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>372</width> <width>692</width>
<height>364</height> <height>323</height>
</rect> </rect>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <property name="sizePolicy">
<property name="fieldGrowthPolicy"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<enum>QFormLayout::ExpandingFieldsGrow</enum> <horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<property name="topMargin">
<number>0</number>
</property> </property>
<item row="0" column="0"> <property name="rightMargin">
<widget class="QLabel" name="titleLabel"> <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"> <property name="text">
<string>Title:</string> <string>URL:</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="4" column="1">
<widget class="QLineEdit" name="titleEdit"/> <widget class="PasswordGeneratorWidget" name="passwordGenerator" native="true">
</item> <property name="sizePolicy">
<item row="1" column="0"> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<widget class="QLabel" name="usernameLabel"> <horstretch>0</horstretch>
<property name="text"> <verstretch>0</verstretch>
<string>Username:</string> </sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="2" column="0" alignment="Qt::AlignRight">
<widget class="QLineEdit" name="usernameEdit"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="passwordLabel"> <widget class="QLabel" name="passwordLabel">
<property name="text"> <property name="text">
<string>Password:</string> <string>Password:</string>
@ -42,31 +57,6 @@
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="PasswordEdit" name="passwordEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="togglePasswordButton">
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<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"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<widget class="PasswordEdit" name="passwordRepeatEdit"> <widget class="PasswordEdit" name="passwordRepeatEdit">
@ -84,24 +74,46 @@
</item> </item>
</layout> </layout>
</item> </item>
<item row="7" column="0"> <item row="2" column="1">
<widget class="QLabel" name="urlLabel"> <layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="PasswordEdit" name="passwordEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="togglePasswordButton">
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="passwordRepeatLabel">
<property name="text"> <property name="text">
<string>URL:</string> <string>Repeat:</string>
</property>
</widget>
</item>
<item row="0" column="0" alignment="Qt::AlignRight">
<widget class="QLabel" name="titleLabel">
<property name="text">
<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> </property>
</widget> </widget>
</item> </item>
<item row="7" column="1"> <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"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<item> <item>
<widget class="QDateTimeEdit" name="expireDatePicker"> <widget class="QDateTimeEdit" name="expireDatePicker">
@ -115,6 +127,12 @@
</item> </item>
<item> <item>
<widget class="QPushButton" name="expirePresets"> <widget class="QPushButton" name="expirePresets">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text"> <property name="text">
<string>Presets</string> <string>Presets</string>
</property> </property>
@ -122,13 +140,6 @@
</item> </item>
</layout> </layout>
</item> </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"> <item row="9" column="1">
<widget class="QPlainTextEdit" name="notesEdit"> <widget class="QPlainTextEdit" name="notesEdit">
<property name="sizePolicy"> <property name="sizePolicy">
@ -139,8 +150,28 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="1"> <item row="1" column="0" alignment="Qt::AlignRight">
<widget class="PasswordGeneratorWidget" name="passwordGenerator" native="true"/> <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> </item>
</layout> </layout>
</widget> </widget>
@ -165,7 +196,6 @@
<tabstop>togglePasswordButton</tabstop> <tabstop>togglePasswordButton</tabstop>
<tabstop>togglePasswordGeneratorButton</tabstop> <tabstop>togglePasswordGeneratorButton</tabstop>
<tabstop>urlEdit</tabstop> <tabstop>urlEdit</tabstop>
<tabstop>expireCheck</tabstop>
<tabstop>expireDatePicker</tabstop> <tabstop>expireDatePicker</tabstop>
<tabstop>expirePresets</tabstop> <tabstop>expirePresets</tabstop>
<tabstop>notesEdit</tabstop> <tabstop>notesEdit</tabstop>

26
src/http/CMakeLists.txt Normal file
View 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()

View File

@ -28,7 +28,7 @@ This is required for accessing your databases from ChromeIPass or PassIFox</stri
<enum>QTabWidget::Rounded</enum> <enum>QTabWidget::Rounded</enum>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>2</number> <number>0</number>
</property> </property>
<widget class="QWidget" name="tab"> <widget class="QWidget" name="tab">
<attribute name="title"> <attribute name="title">

View File

@ -11,41 +11,30 @@
*************************************************************************** ***************************************************************************
*/ */
#include "Server.h" #include <QEventLoop>
#include <microhttpd.h>
#include "Protocol.h"
#include "HttpSettings.h"
#include "crypto/Crypto.h"
#include <QtCore/QHash> #include <QtCore/QHash>
#include <QtCore/QCryptographicHash> #include <QtCore/QCryptographicHash>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <QEventLoop>
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QHostAddress>
#ifdef Q_OS_WIN #include "qhttp/qhttpserver.hpp"
#include <winsock2.h> #include "qhttp/qhttpserverresponse.hpp"
#else #include "qhttp/qhttpserverrequest.hpp"
#include <netinet/in.h>
#endif #include "Server.h"
#include "Protocol.h"
#include "HttpSettings.h"
#include "crypto/Crypto.h"
using namespace KeepassHttpProtocol; using namespace KeepassHttpProtocol;
using namespace qhttp::server;
////////////////////////////////////////////////////////////////////////////////////////////////////
/// Request
////////////////////////////////////////////////////////////////////////////////////////////////////
Server::Server(QObject *parent) : Server::Server(QObject *parent) :
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) void Server::testAssociate(const Request& r, Response * protocolResp)
{ {
@ -132,39 +121,6 @@ void Server::setLogin(const Request &r, Response *protocolResp)
protocolResp->setVerifier(key); 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) void Server::generatePassword(const Request &r, Response *protocolResp)
{ {
QString key = getKey(r.id()); QString key = getKey(r.id());
@ -182,27 +138,10 @@ void Server::generatePassword(const Request &r, Response *protocolResp)
memset(password.data(), 0, password.length()); memset(password.data(), 0, password.length());
} }
void Server::handleRequest(const QByteArray& data, QHttpResponse* response)
int Server::request_handler_wrapper(void *me, struct MHD_Connection *connection,
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, void **con_cls)
{ {
Server *myself = static_cast<Server*>(me);
if (myself)
return myself->request_handler(connection, url, method, version,
upload_data, upload_data_size, con_cls);
else
return MHD_NO;
}
void Server::handleRequest(const QByteArray in, QByteArray *out)
{
*out = QByteArray();
Request r; Request r;
if (!r.fromJson(in)) if (!r.fromJson(data))
return; return;
QByteArray hash = QCryptographicHash::hash( QByteArray hash = QCryptographicHash::hash(
@ -221,7 +160,7 @@ void Server::handleRequest(const QByteArray in, QByteArray *out)
case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break; case GENERATE_PASSWORD: generatePassword(r, &protocolResp); break;
} }
*out = protocolResp.toJson().toUtf8(); QString out = protocolResp.toJson().toUtf8();
// THIS IS A FAKE HACK!!! // THIS IS A FAKE HACK!!!
// the real "error" is a misbehavior in the QJSON qobject2qvariant method // the real "error" is a misbehavior in the QJSON qobject2qvariant method
@ -232,100 +171,17 @@ void Server::handleRequest(const QByteArray in, QByteArray *out)
//(4. ChromeIPass tries to access Entries.length and fails with null pointer exception) //(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 // the fake workaround replaces the (wrong) "Entries":null with "Entries:[] to give
// chromeIPass (and passIFox) en empty list // chromeIPass (and passIFox) en empty list
QString tmp_out = QString(*out); int pos1 = out.indexOf("\"Count\":0,");
int tmp_pos1 = tmp_out.indexOf("\"Count\":0,"); int pos2 = out.indexOf("\"Entries\":null,");
int tmp_pos2 = tmp_out.indexOf("\"Entries\":null,"); if (pos1 != -1 && pos2 != -1) {
if (tmp_pos1 != -1 && tmp_pos2 != -1) { out.replace(pos2, 15, "\"Entries\":[],");
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);
} }
if (con_info == NULL) { response->setStatusCode(qhttp::ESTATUS_OK);
*con_cls = calloc(1, sizeof(*con_info)); response->addHeader("Content-Type", "application/json");
return MHD_YES; response->end(out.toUtf8());
}
if (strcmp (method, MHD_HTTP_METHOD_POST) != 0)
return MHD_NO;
if (*upload_data_size == 0) {
if (con_info && con_info->response)
return send_response(connection, con_info->response);
else
return MHD_NO;
}
QString type = MHD_lookup_connection_value(connection,
MHD_HEADER_KIND, "Content-Type");
if (!type.contains("application/json", Qt::CaseInsensitive))
return MHD_NO;
// Now process the POST request
QByteArray post = QByteArray(upload_data, *upload_data_size);
QByteArray s;
QEventLoop loop;
loop.connect(this, SIGNAL(donewrk()), SLOT(quit()));
Q_EMIT emitRequest(post, &s);
loop.exec();
if (s.size() == 0)
return MHD_NO;
con_info->response = static_cast<char*>(calloc(1, s.size()+1));
memcpy(con_info->response, s.data(), s.size());
*upload_data_size = 0;
return MHD_YES;
} }
void Server::request_completed(void *, struct MHD_Connection *,
void **con_cls, enum MHD_RequestTerminationCode)
{
struct Server::connection_info_struct *con_info =
static_cast<struct Server::connection_info_struct*>(*con_cls);
if (con_info == NULL)
return;
if (con_info->response) free(con_info->response);
free(con_info);
*con_cls = NULL;
}
void Server::start(void) void Server::start(void)
{ {
if (m_started) if (m_started)
@ -335,38 +191,37 @@ void Server::start(void)
// is not safe against interception // is not safe against interception
QHostAddress address("127.0.0.1"); QHostAddress address("127.0.0.1");
int port = HttpSettings::httpPort(); int port = HttpSettings::httpPort();
void* addrx = NULL;
unsigned int flags = MHD_USE_SELECT_INTERNALLY;
struct sockaddr_in *addr = static_cast<struct sockaddr_in*>(calloc(1, sizeof(struct sockaddr_in))); m_server = new QHttpServer(this);
addrx = static_cast<void*>(addr); m_server->listen(address, port);
addr->sin_family = AF_INET; connect(m_server, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)), this, SLOT(onNewRequest(QHttpRequest*, QHttpResponse*)));
addr->sin_port = htons(port);
addr->sin_addr.s_addr = htonl(address.toIPv4Address());
if (NULL == (daemon = MHD_start_daemon(flags, port, NULL, NULL, m_started = true;
&this->request_handler_wrapper, this,
MHD_OPTION_NOTIFY_COMPLETED,
this->request_completed, NULL,
MHD_OPTION_SOCK_ADDR,
addrx,
MHD_OPTION_END))) {
qWarning("HTTPPlugin: Failed to bind to localhost!");
} else {
m_started = true;
}
if (addrx != NULL)
free(addrx);
} }
void Server::stop(void) void Server::stop(void)
{ {
if (!m_started) if (!m_started)
return; return;
MHD_stop_daemon(daemon); m_server->stopListening();
m_server->deleteLater();
m_started = false; 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);
});
}

View File

@ -16,11 +16,19 @@
#include <QtCore/QObject> #include <QtCore/QObject>
#include <QtCore/QList> #include <QtCore/QList>
#include <microhttpd.h>
namespace qhttp {
namespace server {
class QHttpServer;
class QHttpRequest;
class QHttpResponse;
}
}
namespace KeepassHttpProtocol { namespace KeepassHttpProtocol {
using namespace qhttp::server;
class Request; class Request;
class Response; class Response;
class Entry; class Entry;
@ -31,7 +39,6 @@ class Server : public QObject
public: public:
explicit Server(QObject *parent = 0); explicit Server(QObject *parent = 0);
//TODO: use QByteArray?
virtual bool isDatabaseOpened() const = 0; virtual bool isDatabaseOpened() const = 0;
virtual bool openDatabase() = 0; virtual bool openDatabase() = 0;
virtual QString getDatabaseRootUuid() = 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 void updateEntry(const QString &id, const QString &uuid, const QString &login, const QString &password, const QString &url) = 0;
virtual QString generatePassword() = 0; virtual QString generatePassword() = 0;
public Q_SLOTS: public slots:
void start(); void start();
void stop(); void stop();
private Q_SLOTS: private slots:
void handleRequest(const QByteArray in, QByteArray *out); void onNewRequest(QHttpRequest* request, QHttpResponse* response);
void handleOpenDatabase(bool *success); void handleRequest(const QByteArray& data, QHttpResponse* response);
Q_SIGNALS:
void emitRequest(const QByteArray in, QByteArray *out);
void emitOpenDatabase(bool *success);
void donewrk();
private: private:
void testAssociate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); void testAssociate(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
@ -67,25 +69,9 @@ private:
void setLogin(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); void setLogin(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp);
void generatePassword(const KeepassHttpProtocol::Request &r, KeepassHttpProtocol::Response *protocolResp); 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; bool m_started;
struct MHD_Daemon *daemon;
struct connection_info_struct { QHttpServer* m_server;
char *response;
};
}; };
} /*namespace KeepassHttpProtocol*/ } /*namespace KeepassHttpProtocol*/

View File

@ -14,7 +14,6 @@
#include <QInputDialog> #include <QInputDialog>
#include <QMessageBox> #include <QMessageBox>
#include <QProgressDialog> #include <QProgressDialog>
#include <QDebug>
#include "Service.h" #include "Service.h"
#include "Protocol.h" #include "Protocol.h"

View 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
View 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
View 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.

View File

@ -0,0 +1,23 @@
http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright
Igor Sysoev.
Additional changes are licensed under the same terms as NGINX and
copyright Joyent, Inc. and other Node contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

View File

@ -0,0 +1,246 @@
HTTP Parser
===========
[![Build Status](https://api.travis-ci.org/nodejs/http-parser.svg?branch=master)](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

File diff suppressed because it is too large Load Diff

View 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

View 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__

View 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__

View 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__

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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__

View 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
///////////////////////////////////////////////////////////////////////////////

View 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

View 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
///////////////////////////////////////////////////////////////////////////////

View 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

View 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
///////////////////////////////////////////////////////////////////////////////

View 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

View 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
///////////////////////////////////////////////////////////////////////////////

View 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
View 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

View 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
///////////////////////////////////////////////////////////////////////////////

View 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

View 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
///////////////////////////////////////////////////////////////////////////////

View 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

View 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
///////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,82 @@
/** HTTP request which is received by the server.
* https://github.com/azadkuh/qhttp
*
* @author amir zamani
* @version 2.0.0
* @date 2014-07-11
*/
#ifndef QHTTPSERVER_REQUEST_HPP
#define QHTTPSERVER_REQUEST_HPP
///////////////////////////////////////////////////////////////////////////////
#include "qhttpabstracts.hpp"
#include <QUrl>
///////////////////////////////////////////////////////////////////////////////
namespace qhttp {
namespace server {
///////////////////////////////////////////////////////////////////////////////
/** The QHttpRequest class represents the header and body data sent by the client.
* The class is <b>read-only</b>.
* @sa QHttpConnection
*/
class QHTTP_API QHttpRequest : public QHttpAbstractInput
{
Q_OBJECT
public:
virtual ~QHttpRequest();
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 method used for the request. */
THttpMethod method() const ;
/** Returns the method string for the request.
* @note This will plainly transform the enum into a string HTTP_GET -> "HTTP_GET". */
const QString methodString() const;
/** The complete URL for the request.
* This includes the path and query string. @sa path(). */
const QUrl& url() const;
/** IP Address of the client in dotted decimal format. */
const QString& remoteAddress() const;
/** Outbound connection port for the client. */
quint16 remotePort() const;
/** returns the parent QHttpConnection object. */
QHttpConnection* connection() const;
protected:
explicit QHttpRequest(QHttpConnection*);
explicit QHttpRequest(QHttpRequestPrivate&, QHttpConnection*);
friend class QHttpConnectionPrivate;
Q_DECLARE_PRIVATE(QHttpRequest)
QScopedPointer<QHttpRequestPrivate> d_ptr;
};
///////////////////////////////////////////////////////////////////////////////
} // namespace server
} // namespace qhttp
///////////////////////////////////////////////////////////////////////////////
#endif // define QHTTPSERVER_REQUEST_HPP

View File

@ -0,0 +1,90 @@
#include "private/qhttpserverresponse_private.hpp"
///////////////////////////////////////////////////////////////////////////////
namespace qhttp {
namespace server {
///////////////////////////////////////////////////////////////////////////////
QHttpResponse::QHttpResponse(QHttpConnection* conn)
: QHttpAbstractOutput(conn) , d_ptr(new QHttpResponsePrivate(conn, this)) {
d_ptr->initialize();
QHTTP_LINE_LOG
}
QHttpResponse::QHttpResponse(QHttpResponsePrivate& dd, QHttpConnection* conn)
: QHttpAbstractOutput(conn) , d_ptr(&dd) {
d_ptr->initialize();
QHTTP_LINE_LOG
}
QHttpResponse::~QHttpResponse() {
QHTTP_LINE_LOG
}
void
QHttpResponse::setStatusCode(TStatusCode code) {
d_func()->istatus = code;
}
void
QHttpResponse::setVersion(const QString &versionString) {
d_func()->iversion = versionString;
}
void
QHttpResponse::addHeader(const QByteArray &field, const QByteArray &value) {
d_func()->addHeader(field, value);
}
THeaderHash&
QHttpResponse::headers() {
return d_func()->iheaders;
}
void
QHttpResponse::write(const QByteArray &data) {
d_func()->writeData(data);
}
void
QHttpResponse::end(const QByteArray &data) {
Q_D(QHttpResponse);
if ( d->endPacket(data) )
emit done(!d->ikeepAlive);
}
QHttpConnection*
QHttpResponse::connection() const {
return d_func()->iconnection;
}
///////////////////////////////////////////////////////////////////////////////
QByteArray
QHttpResponsePrivate::makeTitle() {
QString title = QString("HTTP/%1 %2 %3\r\n")
.arg(iversion)
.arg(istatus)
.arg(Stringify::toString(istatus));
return title.toLatin1();
}
void
QHttpResponsePrivate::prepareHeadersToWrite() {
if ( !iheaders.contains("date") ) {
// Sun, 06 Nov 1994 08:49:37 GMT - RFC 822. Use QLocale::c() so english is used for month and
// day.
QString dateString = QLocale::c().
toString(QDateTime::currentDateTimeUtc(),
"ddd, dd MMM yyyy hh:mm:ss")
.append(" GMT");
addHeader("date", dateString.toLatin1());
}
}
///////////////////////////////////////////////////////////////////////////////
} // namespace server
} // namespace qhttp
///////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,70 @@
/** HTTP response from a server.
* https://github.com/azadkuh/qhttp
*
* @author amir zamani
* @version 2.0.0
* @date 2014-07-11
*/
#ifndef QHTTPSERVER_RESPONSE_HPP
#define QHTTPSERVER_RESPONSE_HPP
///////////////////////////////////////////////////////////////////////////////
#include "qhttpabstracts.hpp"
///////////////////////////////////////////////////////////////////////////////
namespace qhttp {
namespace server {
///////////////////////////////////////////////////////////////////////////////
/** The QHttpResponse class handles sending data back to the client as a response to a request.
* @sa QHttpConnection
*/
class QHTTP_API QHttpResponse : public QHttpAbstractOutput
{
Q_OBJECT
public:
virtual ~QHttpResponse();
public:
/** set the response HTTP status code. @sa TStatusCode.
* default value is ESTATUS_BAD_REQUEST.
* @sa write()
*/
void setStatusCode(TStatusCode code);
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 the parent QHttpConnection object. */
QHttpConnection* connection() const;
protected:
explicit QHttpResponse(QHttpConnection*);
explicit QHttpResponse(QHttpResponsePrivate&, QHttpConnection*);
friend class QHttpConnectionPrivate;
Q_DECLARE_PRIVATE(QHttpResponse)
QScopedPointer<QHttpResponsePrivate> d_ptr;
};
///////////////////////////////////////////////////////////////////////////////
} // namespace server
} // namespace qhttp
///////////////////////////////////////////////////////////////////////////////
#endif // define QHTTPSERVER_RESPONSE_HPP

View File

@ -18,6 +18,7 @@
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QFile> #include <QFile>
#include <QTextStream> #include <QTextStream>
#include <QtGlobal>
#include "config-keepassx.h" #include "config-keepassx.h"
#include "core/Config.h" #include "core/Config.h"
@ -44,7 +45,10 @@ int main(int argc, char** argv)
Tools::disableCoreDumps(); Tools::disableCoreDumps();
#endif #endif
Tools::setupSearchPaths(); Tools::setupSearchPaths();
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
Application app(argc, argv); Application app(argc, argv);
Application::setApplicationName("keepassxc"); Application::setApplicationName("keepassxc");
Application::setApplicationVersion(KEEPASSX_VERSION); Application::setApplicationVersion(KEEPASSX_VERSION);
@ -97,14 +101,18 @@ int main(int argc, char** argv)
MainWindow mainWindow; MainWindow mainWindow;
app.setMainWindow(&mainWindow); app.setMainWindow(&mainWindow);
mainWindow.show();
QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString))); QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString)));
// start minimized if configured // start minimized if configured
if (config()->get("GUI/MinimizeOnStartup").toBool()) { bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool();
if (minimizeOnStartup) {
mainWindow.setWindowState(Qt::WindowMinimized); mainWindow.setWindowState(Qt::WindowMinimized);
} }
if (!(minimizeOnStartup && minimizeToTray)) {
mainWindow.show();
}
for (int ii=0; ii < args.length(); ii++) { for (int ii=0; ii < args.length(); ii++) {
QString filename = args[ii]; QString filename = args[ii];