summaryrefslogtreecommitdiffstats
path: root/src/network
diff options
context:
space:
mode:
Diffstat (limited to 'src/network')
-rw-r--r--src/network/CMakeLists.txt121
-rw-r--r--src/network/access/http2/hpack.cpp49
-rw-r--r--src/network/access/http2/hpack_p.h3
-rw-r--r--src/network/access/http2/hpacktable.cpp6
-rw-r--r--src/network/access/http2/http2frames.cpp13
-rw-r--r--src/network/access/http2/http2frames_p.h19
-rw-r--r--src/network/access/http2/http2protocol.cpp50
-rw-r--r--src/network/access/http2/http2protocol_p.h5
-rw-r--r--src/network/access/qabstractnetworkcache.cpp43
-rw-r--r--src/network/access/qabstractnetworkcache.h3
-rw-r--r--src/network/access/qabstractprotocolhandler_p.h4
-rw-r--r--src/network/access/qdecompresshelper.cpp26
-rw-r--r--src/network/access/qdecompresshelper_p.h4
-rw-r--r--src/network/access/qformdatabuilder.cpp313
-rw-r--r--src/network/access/qformdatabuilder.h126
-rw-r--r--src/network/access/qhsts.cpp62
-rw-r--r--src/network/access/qhsts_p.h6
-rw-r--r--src/network/access/qhstspolicy.cpp4
-rw-r--r--src/network/access/qhttp1configuration.cpp154
-rw-r--r--src/network/access/qhttp1configuration.h62
-rw-r--r--src/network/access/qhttp2configuration.h2
-rw-r--r--src/network/access/qhttp2connection.cpp1752
-rw-r--r--src/network/access/qhttp2connection_p.h372
-rw-r--r--src/network/access/qhttp2protocolhandler.cpp359
-rw-r--r--src/network/access/qhttp2protocolhandler_p.h5
-rw-r--r--src/network/access/qhttpheaderparser.cpp42
-rw-r--r--src/network/access/qhttpheaderparser_p.h15
-rw-r--r--src/network/access/qhttpheaders.cpp1551
-rw-r--r--src/network/access/qhttpheaders.h282
-rw-r--r--src/network/access/qhttpheadershelper.cpp25
-rw-r--r--src/network/access/qhttpheadershelper_p.h30
-rw-r--r--src/network/access/qhttpmultipart.cpp52
-rw-r--r--src/network/access/qhttpmultipart.h4
-rw-r--r--src/network/access/qhttpmultipart_p.h7
-rw-r--r--src/network/access/qhttpnetworkconnection.cpp267
-rw-r--r--src/network/access/qhttpnetworkconnection_p.h52
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel.cpp233
-rw-r--r--src/network/access/qhttpnetworkconnectionchannel_p.h6
-rw-r--r--src/network/access/qhttpnetworkheader.cpp6
-rw-r--r--src/network/access/qhttpnetworkheader_p.h11
-rw-r--r--src/network/access/qhttpnetworkreply.cpp48
-rw-r--r--src/network/access/qhttpnetworkreply_p.h32
-rw-r--r--src/network/access/qhttpnetworkrequest.cpp26
-rw-r--r--src/network/access/qhttpnetworkrequest_p.h8
-rw-r--r--src/network/access/qhttpprotocolhandler.cpp18
-rw-r--r--src/network/access/qhttpthreaddelegate.cpp54
-rw-r--r--src/network/access/qhttpthreaddelegate_p.h26
-rw-r--r--src/network/access/qnetworkaccessbackend.cpp37
-rw-r--r--src/network/access/qnetworkaccessbackend_p.h3
-rw-r--r--src/network/access/qnetworkaccesscache.cpp3
-rw-r--r--src/network/access/qnetworkaccesscache_p.h3
-rw-r--r--src/network/access/qnetworkaccesscachebackend.cpp30
-rw-r--r--src/network/access/qnetworkaccessdebugpipebackend.cpp6
-rw-r--r--src/network/access/qnetworkaccessmanager.cpp170
-rw-r--r--src/network/access/qnetworkaccessmanager.h24
-rw-r--r--src/network/access/qnetworkaccessmanager_p.h2
-rw-r--r--src/network/access/qnetworkcookie.cpp87
-rw-r--r--src/network/access/qnetworkcookie.h3
-rw-r--r--src/network/access/qnetworkcookie_p.h4
-rw-r--r--src/network/access/qnetworkcookiejar.cpp65
-rw-r--r--src/network/access/qnetworkdiskcache.cpp172
-rw-r--r--src/network/access/qnetworkdiskcache_p.h17
-rw-r--r--src/network/access/qnetworkfile.cpp7
-rw-r--r--src/network/access/qnetworkfile_p.h2
-rw-r--r--src/network/access/qnetworkreply.cpp68
-rw-r--r--src/network/access/qnetworkreply.h10
-rw-r--r--src/network/access/qnetworkreplydataimpl.cpp7
-rw-r--r--src/network/access/qnetworkreplyfileimpl.cpp18
-rw-r--r--src/network/access/qnetworkreplyfileimpl_p.h8
-rw-r--r--src/network/access/qnetworkreplyhttpimpl.cpp406
-rw-r--r--src/network/access/qnetworkreplyhttpimpl_p.h2
-rw-r--r--src/network/access/qnetworkreplyimpl.cpp50
-rw-r--r--src/network/access/qnetworkreplyimpl_p.h3
-rw-r--r--src/network/access/qnetworkreplywasmimpl.cpp244
-rw-r--r--src/network/access/qnetworkreplywasmimpl_p.h30
-rw-r--r--src/network/access/qnetworkrequest.cpp776
-rw-r--r--src/network/access/qnetworkrequest.h54
-rw-r--r--src/network/access/qnetworkrequest_p.h42
-rw-r--r--src/network/access/qnetworkrequestfactory.cpp721
-rw-r--r--src/network/access/qnetworkrequestfactory.h100
-rw-r--r--src/network/access/qnetworkrequestfactory_p.h56
-rw-r--r--src/network/access/qrestaccessmanager.cpp828
-rw-r--r--src/network/access/qrestaccessmanager.h127
-rw-r--r--src/network/access/qrestaccessmanager_p.h91
-rw-r--r--src/network/access/qrestreply.cpp608
-rw-r--r--src/network/access/qrestreply.h71
-rw-r--r--src/network/access/qrestreply_p.h40
-rw-r--r--src/network/access/qsocketabstraction_p.h91
-rw-r--r--src/network/android/jar/CMakeLists.txt11
-rw-r--r--src/network/android/jar/build.gradle8
-rw-r--r--src/network/compat/removed_api.cpp70
-rw-r--r--src/network/configure.cmake61
-rw-r--r--src/network/doc/images/network-examples.pngbin8946 -> 0 bytes
-rw-r--r--src/network/doc/images/network-examples.webpbin0 -> 8250 bytes
-rw-r--r--src/network/doc/qtnetwork.qdocconf11
-rw-r--r--src/network/doc/snippets/CMakeLists.txt2
-rw-r--r--src/network/doc/snippets/code/doc_src_qtnetwork.cpp6
-rw-r--r--src/network/doc/snippets/code/src_network_access_qhttpmultipart.cpp2
-rw-r--r--src/network/doc/snippets/code/src_network_access_qhttppart.cpp2
-rw-r--r--src/network/doc/snippets/code/src_network_access_qnetworkdiskcache.cpp9
-rw-r--r--src/network/doc/snippets/code/src_network_access_qnetworkrequestfactory.cpp27
-rw-r--r--src/network/doc/snippets/code/src_network_access_qrestaccessmanager.cpp104
-rw-r--r--src/network/doc/snippets/code/src_network_kernel_qdnslookup.cpp3
-rw-r--r--src/network/doc/snippets/code/src_network_kernel_qhostinfo.cpp12
-rw-r--r--src/network/doc/snippets/code/src_network_kernel_qnetworkinterface.cpp2
-rw-r--r--src/network/doc/snippets/code/src_network_ssl_qsslconfiguration.cpp7
-rw-r--r--src/network/doc/snippets/code/src_network_ssl_qsslpresharedkeyauthenticator.cpp2
-rw-r--r--src/network/doc/snippets/code/src_network_ssl_qsslsocket.cpp10
-rw-r--r--src/network/doc/snippets/network/CMakeLists.txt2
-rw-r--r--src/network/doc/src/examples.qdoc8
-rw-r--r--src/network/doc/src/ssl.qdoc2
-rw-r--r--src/network/kernel/qauthenticator.cpp59
-rw-r--r--src/network/kernel/qauthenticator.h1
-rw-r--r--src/network/kernel/qauthenticator_p.h6
-rw-r--r--src/network/kernel/qdnslookup.cpp764
-rw-r--r--src/network/kernel/qdnslookup.h124
-rw-r--r--src/network/kernel/qdnslookup_android.cpp20
-rw-r--r--src/network/kernel/qdnslookup_dummy.cpp15
-rw-r--r--src/network/kernel/qdnslookup_p.h199
-rw-r--r--src/network/kernel/qdnslookup_unix.cpp597
-rw-r--r--src/network/kernel/qdnslookup_win.cpp234
-rw-r--r--src/network/kernel/qhostaddress.cpp36
-rw-r--r--src/network/kernel/qhostaddress.h1
-rw-r--r--src/network/kernel/qhostinfo.cpp120
-rw-r--r--src/network/kernel/qhostinfo.h63
-rw-r--r--src/network/kernel/qhostinfo_p.h32
-rw-r--r--src/network/kernel/qhostinfo_unix.cpp228
-rw-r--r--src/network/kernel/qnetconmonitor_darwin.mm2
-rw-r--r--src/network/kernel/qnetconmonitor_win.cpp6
-rw-r--r--src/network/kernel/qnetworkdatagram.cpp2
-rw-r--r--src/network/kernel/qnetworkinformation.cpp86
-rw-r--r--src/network/kernel/qnetworkinformation.h2
-rw-r--r--src/network/kernel/qnetworkinformation_p.h37
-rw-r--r--src/network/kernel/qnetworkinterface_linux.cpp9
-rw-r--r--src/network/kernel/qnetworkinterface_unix.cpp88
-rw-r--r--src/network/kernel/qnetworkproxy.cpp60
-rw-r--r--src/network/kernel/qnetworkproxy.h5
-rw-r--r--src/network/kernel/qnetworkproxy_android.cpp2
-rw-r--r--src/network/kernel/qnetworkproxy_darwin.cpp (renamed from src/network/kernel/qnetworkproxy_mac.cpp)133
-rw-r--r--src/network/kernel/qnetworkproxy_libproxy.cpp9
-rw-r--r--src/network/kernel/qtldurl.cpp4
-rw-r--r--src/network/kernel/qtldurl_p.h3
-rw-r--r--src/network/kernel/qtnetworkglobal_p.h1
-rw-r--r--src/network/socket/qabstractsocket.cpp62
-rw-r--r--src/network/socket/qabstractsocket_p.h1
-rw-r--r--src/network/socket/qabstractsocketengine_p.h14
-rw-r--r--src/network/socket/qhttpsocketengine.cpp97
-rw-r--r--src/network/socket/qhttpsocketengine_p.h17
-rw-r--r--src/network/socket/qlocalserver.cpp20
-rw-r--r--src/network/socket/qlocalserver.h1
-rw-r--r--src/network/socket/qlocalserver_unix.cpp3
-rw-r--r--src/network/socket/qlocalsocket_unix.cpp18
-rw-r--r--src/network/socket/qnativesocketengine.cpp32
-rw-r--r--src/network/socket/qnativesocketengine_p.h220
-rw-r--r--src/network/socket/qnativesocketengine_p_p.h189
-rw-r--r--src/network/socket/qnativesocketengine_unix.cpp44
-rw-r--r--src/network/socket/qnativesocketengine_win.cpp35
-rw-r--r--src/network/socket/qnet_unix_p.h11
-rw-r--r--src/network/socket/qsctpsocket.cpp3
-rw-r--r--src/network/socket/qsocks5socketengine.cpp74
-rw-r--r--src/network/socket/qsocks5socketengine_p.h15
-rw-r--r--src/network/socket/qtcpserver.cpp6
-rw-r--r--src/network/socket/qtcpsocket.cpp6
-rw-r--r--src/network/socket/qudpsocket.cpp4
-rw-r--r--src/network/ssl/qpassworddigestor.cpp98
-rw-r--r--src/network/ssl/qssl.cpp2
-rw-r--r--src/network/ssl/qssl.h13
-rw-r--r--src/network/ssl/qsslcertificate.cpp72
-rw-r--r--src/network/ssl/qsslcertificate.h2
-rw-r--r--src/network/ssl/qsslcertificate_p.h4
-rw-r--r--src/network/ssl/qsslconfiguration.cpp33
-rw-r--r--src/network/ssl/qssldiffiehellmanparameters.cpp13
-rw-r--r--src/network/ssl/qsslkey.h2
-rw-r--r--src/network/ssl/qsslpresharedkeyauthenticator.h1
-rw-r--r--src/network/ssl/qsslserver.cpp4
-rw-r--r--src/network/ssl/qsslsocket.cpp34
-rw-r--r--src/network/ssl/qsslsocket.h2
-rw-r--r--src/network/ssl/qtlsbackend.cpp30
-rw-r--r--src/network/ssl/qtlsbackend_p.h18
179 files changed, 13036 insertions, 2917 deletions
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
index 48f0e737f0..08789d89de 100644
--- a/src/network/CMakeLists.txt
+++ b/src/network/CMakeLists.txt
@@ -1,8 +1,6 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from network.pro.
-
#####################################################################
## Network Module:
#####################################################################
@@ -22,12 +20,15 @@ qt_internal_add_module(Network
access/qnetworkcookie.cpp access/qnetworkcookie.h access/qnetworkcookie_p.h
access/qnetworkcookiejar.cpp access/qnetworkcookiejar.h access/qnetworkcookiejar_p.h
access/qnetworkfile.cpp access/qnetworkfile_p.h
+ access/qhttpheaders.cpp access/qhttpheaders.h
access/qhttpheaderparser.cpp access/qhttpheaderparser_p.h
+ access/qhttpheadershelper.cpp access/qhttpheadershelper_p.h
access/qnetworkreply.cpp access/qnetworkreply.h access/qnetworkreply_p.h
access/qnetworkreplydataimpl.cpp access/qnetworkreplydataimpl_p.h
access/qnetworkreplyfileimpl.cpp access/qnetworkreplyfileimpl_p.h
access/qnetworkreplyimpl.cpp access/qnetworkreplyimpl_p.h
access/qnetworkrequest.cpp access/qnetworkrequest.h access/qnetworkrequest_p.h
+ compat/removed_api.cpp
kernel/qauthenticator.cpp kernel/qauthenticator.h kernel/qauthenticator_p.h
kernel/qhostaddress.cpp kernel/qhostaddress.h kernel/qhostaddress_p.h
kernel/qhostinfo.cpp kernel/qhostinfo.h kernel/qhostinfo_p.h
@@ -40,7 +41,7 @@ qt_internal_add_module(Network
kernel/qtnetworkglobal.h kernel/qtnetworkglobal_p.h
socket/qabstractsocket.cpp socket/qabstractsocket.h socket/qabstractsocket_p.h
socket/qabstractsocketengine.cpp socket/qabstractsocketengine_p.h
- socket/qnativesocketengine.cpp socket/qnativesocketengine_p.h
+ socket/qnativesocketengine.cpp socket/qnativesocketengine_p.h socket/qnativesocketengine_p_p.h
socket/qtcpserver.cpp socket/qtcpserver.h socket/qtcpserver_p.h
socket/qtcpsocket.cpp socket/qtcpsocket.h socket/qtcpsocket_p.h
socket/qudpsocket.cpp socket/qudpsocket.h
@@ -55,8 +56,14 @@ qt_internal_add_module(Network
ssl/qsslsocket.h
ssl/qtlsbackend.cpp ssl/qtlsbackend_p.h
DEFINES
+ QT_NO_CONTEXTLESS_CONNECT
QT_NO_FOREACH
QT_NO_USING_NAMESPACE
+ QT_NO_CAST_FROM_ASCII
+ QT_NO_CAST_TO_ASCII
+ QT_NO_CAST_FROM_BYTEARRAY
+ QT_NO_URL_CAST_FROM_STRING
+ QT_USE_NODISCARD_FILE_OPEN
INCLUDE_DIRECTORIES
kernel
LIBRARIES
@@ -65,15 +72,13 @@ qt_internal_add_module(Network
Qt::Core
PRIVATE_MODULE_INTERFACE
Qt::CorePrivate
+ NO_PCH_SOURCES
+ compat/removed_api.cpp
PRECOMPILED_HEADER
"../corelib/global/qt_pch.h"
GENERATE_CPP_EXPORTS
- GENERATE_PRIVATE_CPP_EXPORTS
)
-#### Keys ignored in scope 1:.:.:network.pro:<TRUE>:
-# QMAKE_LIBS = "$$QMAKE_LIBS_NETWORK"
-
## Scopes:
#####################################################################
@@ -105,6 +110,7 @@ qt_internal_extend_target(Network CONDITION APPLE
qt_internal_extend_target(Network CONDITION WASM
SOURCES
+ access/qformdatabuilder.cpp access/qformdatabuilder.h
access/qhttpmultipart.cpp access/qhttpmultipart.h access/qhttpmultipart_p.h
access/qhttpnetworkheader.cpp access/qhttpnetworkheader_p.h
access/qnetworkreplywasmimpl.cpp access/qnetworkreplywasmimpl_p.h
@@ -121,7 +127,10 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_http
access/http2/huffman.cpp access/http2/huffman_p.h
access/qabstractprotocolhandler.cpp access/qabstractprotocolhandler_p.h
access/qdecompresshelper.cpp access/qdecompresshelper_p.h
+ access/qformdatabuilder.cpp access/qformdatabuilder.h
+ access/qhttp1configuration.cpp access/qhttp1configuration.h
access/qhttp2configuration.cpp access/qhttp2configuration.h
+ access/qhttp2connection.cpp access/qhttp2connection_p.h
access/qhttp2protocolhandler.cpp access/qhttp2protocolhandler_p.h
access/qhttpmultipart.cpp access/qhttpmultipart.h access/qhttpmultipart_p.h
access/qhttpnetworkconnection.cpp access/qhttpnetworkconnection_p.h
@@ -132,6 +141,11 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_http
access/qhttpprotocolhandler.cpp access/qhttpprotocolhandler_p.h
access/qhttpthreaddelegate.cpp access/qhttpthreaddelegate_p.h
access/qnetworkreplyhttpimpl.cpp access/qnetworkreplyhttpimpl_p.h
+ access/qnetworkrequestfactory.cpp access/qnetworkrequestfactory_p.h
+ access/qnetworkrequestfactory.h
+ access/qrestaccessmanager.cpp access/qrestaccessmanager.h access/qrestaccessmanager_p.h
+ access/qrestreply.cpp access/qrestreply.h access/qrestreply_p.h
+ access/qsocketabstraction_p.h
socket/qhttpsocketengine.cpp socket/qhttpsocketengine_p.h
)
@@ -180,11 +194,6 @@ qt_internal_extend_target(Network CONDITION UNIX
socket/qnet_unix_p.h
)
-qt_internal_extend_target(Network CONDITION QT_FEATURE_dlopen AND UNIX
- LIBRARIES
- ${CMAKE_DL_LIBS}
-)
-
qt_internal_extend_target(Network CONDITION QT_FEATURE_linux_netlink AND UNIX
SOURCES
kernel/qnetworkinterface_linux.cpp
@@ -195,11 +204,6 @@ qt_internal_extend_target(Network CONDITION UNIX AND NOT QT_FEATURE_linux_netlin
kernel/qnetworkinterface_unix.cpp
)
-qt_internal_extend_target(Network CONDITION ANDROID AND QT_FEATURE_dnslookup
- SOURCES
- kernel/qdnslookup_android.cpp
-)
-
qt_internal_extend_target(Network CONDITION WIN32
SOURCES
kernel/qhostinfo_win.cpp
@@ -212,6 +216,26 @@ qt_internal_extend_target(Network CONDITION WIN32
iphlpapi
secur32
winhttp
+ DEFINES
+ NOMINMAX
+)
+
+qt_internal_extend_target(Network CONDITION APPLE AND NOT UIKIT
+ LIBRARIES
+ ${FWCoreServices}
+ ${FWSystemConfiguration}
+)
+
+qt_internal_extend_target(Network CONDITION APPLE
+ LIBRARIES
+ ${FWCFNetwork}
+)
+
+qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND QT_FEATURE_libresolv
+ SOURCES
+ kernel/qdnslookup_unix.cpp
+ LIBRARIES
+ WrapResolv::WrapResolv
)
qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND WIN32
@@ -219,13 +243,12 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND WIN32
kernel/qdnslookup_win.cpp
)
-qt_internal_extend_target(Network CONDITION APPLE AND NOT UIKIT
- LIBRARIES
- ${FWCoreServices}
- ${FWSystemConfiguration}
+qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND NOT QT_FEATURE_libresolv AND NOT WIN32
+ SOURCES
+ kernel/qdnslookup_dummy.cpp
)
-qt_internal_extend_target(Network CONDITION IOS OR MACOS
+qt_internal_extend_target(Network CONDITION APPLE
SOURCES
kernel/qnetconmonitor_darwin.mm
LIBRARIES
@@ -237,7 +260,7 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_networklistmanager AND NO
kernel/qnetconmonitor_win.cpp
)
-qt_internal_extend_target(Network CONDITION NOT IOS AND NOT MACOS AND NOT QT_FEATURE_networklistmanager
+qt_internal_extend_target(Network CONDITION NOT APPLE AND NOT QT_FEATURE_networklistmanager
SOURCES
kernel/qnetconmonitor_stub.cpp
)
@@ -252,9 +275,9 @@ qt_internal_extend_target(Network CONDITION UIKIT
kernel/qnetworkinterface_uikit_p.h
)
-qt_internal_extend_target(Network CONDITION MACOS
+qt_internal_extend_target(Network CONDITION APPLE AND NOT VISIONOS
SOURCES
- kernel/qnetworkproxy_mac.cpp
+ kernel/qnetworkproxy_darwin.cpp
)
qt_internal_extend_target(Network CONDITION QT_FEATURE_libproxy AND UNIX AND NOT MACOS
@@ -265,19 +288,19 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_libproxy AND UNIX AND NOT
PkgConfig::Libproxy
)
-qt_internal_extend_target(Network CONDITION ANDROID # special case
+qt_internal_extend_target(Network CONDITION ANDROID
SOURCES
kernel/qnetworkproxy_android.cpp
)
-qt_internal_extend_target(Network CONDITION UNIX AND NOT ANDROID AND NOT MACOS AND NOT QT_FEATURE_libproxy AND (UNIX OR WINRT) # special case
+qt_internal_extend_target(Network CONDITION UNIX AND NOT ANDROID AND NOT (APPLE AND NOT VISIONOS) AND NOT QT_FEATURE_libproxy AND (UNIX OR WINRT)
SOURCES
kernel/qnetworkproxy_generic.cpp
)
if(ANDROID AND (ANDROID))
set_property(TARGET Network APPEND PROPERTY QT_ANDROID_BUNDLED_JAR_DEPENDENCIES
- jar/Qt${QtBase_VERSION_MAJOR}AndroidNetwork.jar # special case
+ jar/Qt${QtBase_VERSION_MAJOR}AndroidNetwork.jar
)
endif()
@@ -323,6 +346,11 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_localserver AND WIN32
socket/qlocalsocket_win.cpp
)
+qt_internal_extend_target(Network CONDITION QT_FEATURE_openssl_linked AND QT_FEATURE_opensslv30
+ LIBRARIES
+ WrapOpenSSL::WrapOpenSSL
+)
+
qt_internal_extend_target(Network CONDITION QT_FEATURE_system_proxies
DEFINES
QT_USE_SYSTEM_PROXIES
@@ -352,21 +380,44 @@ qt_internal_extend_target(Network CONDITION QT_FEATURE_ocsp AND QT_FEATURE_opens
ssl/qocsp_p.h
)
-qt_internal_extend_target(Network CONDITION QT_FEATURE_dnslookup AND UNIX AND NOT ANDROID
- SOURCES
- kernel/qdnslookup_unix.cpp
-)
qt_internal_add_docs(Network
doc/qtnetwork.qdocconf
)
-qt_internal_extend_target(Network CONDITION WIN32 PUBLIC_LIBRARIES ws2_32) # special case: mkspecs/common/msvc-desktop.conf
+# See mkspecs/common/msvc-desktop.conf
+qt_internal_extend_target(Network CONDITION WIN32 PUBLIC_LIBRARIES ws2_32)
-qt_internal_extend_target(Network CONDITION QNX PUBLIC_LIBRARIES socket) # special case: mkspecs/common/qcc-base-qnx.conf
+# See mkspecs/common/qcc-base-qnx.conf
+qt_internal_extend_target(Network CONDITION QNX PUBLIC_LIBRARIES socket)
-qt_internal_extend_target(Network CONDITION SOLARIS PUBLIC_LIBRARIES socket nsl) # special case
+qt_internal_extend_target(Network CONDITION SOLARIS PUBLIC_LIBRARIES socket nsl)
+
+qt_internal_extend_target(Network CONDITION WIN32
+ NO_UNITY_BUILD_SOURCES
+ kernel/qauthenticator.cpp
+ kernel/qdnslookup_win.cpp
+ kernel/qhostaddress.cpp
+ kernel/qhostinfo.cpp
+ kernel/qhostinfo_win.cpp
+ kernel/qnetconmonitor_win.cpp
+ kernel/qnetworkinterface_win.cpp
+ kernel/qnetworkproxy_win.cpp
+ socket/qabstractsocket.cpp
+ socket/qlocalserver.cpp
+ socket/qlocalserver_win.cpp
+ socket/qlocalsocket_win.cpp
+ socket/qnativesocketengine.cpp
+ socket/qnativesocketengine_win.cpp
+)
# include the snippet projects for developer-builds
if(QT_FEATURE_private_tests)
add_subdirectory(doc/snippets/network)
endif()
+qt_internal_extend_target(Network
+ # Workaround for QTBUG-118229:
+ # Function called by inline methods taking a pointer to a private class as a parameter
+ EXTRA_LINKER_SCRIPT_EXPORTS
+ # QNetworkDatagram::destroy(QNetworkDatagramPrivate *d)
+ "_ZN*16QNetworkDatagram7destroyEP*23QNetworkDatagramPrivate*"
+)
diff --git a/src/network/access/http2/hpack.cpp b/src/network/access/http2/hpack.cpp
index 58af04bbb5..9e970dda53 100644
--- a/src/network/access/http2/hpack.cpp
+++ b/src/network/access/http2/hpack.cpp
@@ -91,7 +91,7 @@ bool read_bit_pattern(const BitPattern &pattern, BitIStream &inputStream)
return true;
}
-bool is_request_pseudo_header(const QByteArray &name)
+bool is_request_pseudo_header(QByteArrayView name)
{
return name == ":method" || name == ":scheme" ||
name == ":authority" || name == ":path";
@@ -194,8 +194,8 @@ bool Encoder::encodeRequestPseudoHeaders(BitOStream &outputStream,
using size_type = decltype(header.size());
bool methodFound = false;
- const char *headerName[] = {":authority", ":scheme", ":path"};
- const size_type nHeaders = sizeof headerName / sizeof headerName[0];
+ constexpr QByteArrayView headerName[] = {":authority", ":scheme", ":path"};
+ constexpr size_type nHeaders = std::size(headerName);
bool headerFound[nHeaders] = {};
for (const auto &field : header) {
@@ -504,6 +504,49 @@ void Decoder::handleStreamError(BitIStream &inputStream)
// HTTP2 layer will end with session error/COMPRESSION_ERROR.
}
+std::optional<QUrl> makePromiseKeyUrl(const HttpHeader &requestHeader)
+{
+ constexpr QByteArrayView names[] = { ":authority", ":method", ":path", ":scheme" };
+ enum PseudoHeaderEnum
+ {
+ Authority,
+ Method,
+ Path,
+ Scheme
+ };
+ std::array<std::optional<QByteArrayView>, std::size(names)> pseudoHeaders{};
+ for (const auto &field : requestHeader) {
+ const auto *it = std::find(std::begin(names), std::end(names), QByteArrayView(field.name));
+ if (it != std::end(names)) {
+ const auto index = std::distance(std::begin(names), it);
+ if (field.value.isEmpty() || pseudoHeaders.at(index).has_value())
+ return {};
+ pseudoHeaders[index] = field.value;
+ }
+ }
+
+ auto optionalIsSet = [](const auto &x) { return x.has_value(); };
+ if (!std::all_of(pseudoHeaders.begin(), pseudoHeaders.end(), optionalIsSet)) {
+ // All four required, HTTP/2 8.1.2.3.
+ return {};
+ }
+
+ const QByteArrayView method = pseudoHeaders[Method].value();
+ if (method.compare("get", Qt::CaseInsensitive) != 0 &&
+ method.compare("head", Qt::CaseInsensitive) != 0) {
+ return {};
+ }
+
+ QUrl url;
+ url.setScheme(QLatin1StringView(pseudoHeaders[Scheme].value()));
+ url.setAuthority(QLatin1StringView(pseudoHeaders[Authority].value()));
+ url.setPath(QLatin1StringView(pseudoHeaders[Path].value()));
+
+ if (!url.isValid())
+ return {};
+ return url;
+}
+
}
QT_END_NAMESPACE
diff --git a/src/network/access/http2/hpack_p.h b/src/network/access/http2/hpack_p.h
index 75693da73c..b407b81941 100644
--- a/src/network/access/http2/hpack_p.h
+++ b/src/network/access/http2/hpack_p.h
@@ -18,8 +18,10 @@
#include "hpacktable_p.h"
#include <QtCore/qglobal.h>
+#include <QtCore/qurl.h>
#include <vector>
+#include <optional>
QT_BEGIN_NAMESPACE
@@ -112,6 +114,7 @@ private:
FieldLookupTable lookupTable;
};
+std::optional<QUrl> makePromiseKeyUrl(const HttpHeader &requestHeader);
}
QT_END_NAMESPACE
diff --git a/src/network/access/http2/hpacktable.cpp b/src/network/access/http2/hpacktable.cpp
index 74a09a207f..2c728b37e3 100644
--- a/src/network/access/http2/hpacktable.cpp
+++ b/src/network/access/http2/hpacktable.cpp
@@ -26,8 +26,10 @@ HeaderSize entry_size(QByteArrayView name, QByteArrayView value)
// for counting the number of references to the name and value would have
// 32 octets of overhead."
- const unsigned sum = unsigned(name.size() + value.size());
- if (std::numeric_limits<unsigned>::max() - 32 < sum)
+ size_t sum;
+ if (qAddOverflow(size_t(name.size()), size_t(value.size()), &sum))
+ return HeaderSize();
+ if (sum > (std::numeric_limits<unsigned>::max() - 32))
return HeaderSize();
return HeaderSize(true, quint32(sum + 32));
}
diff --git a/src/network/access/http2/http2frames.cpp b/src/network/access/http2/http2frames.cpp
index 0c70c98ef8..e07c96b803 100644
--- a/src/network/access/http2/http2frames.cpp
+++ b/src/network/access/http2/http2frames.cpp
@@ -135,6 +135,7 @@ FrameStatus Frame::validateHeader() const
// 6.6 PUSH_PROMISE
if (framePayloadSize < 4)
return FrameStatus::sizeError;
+ break;
default:
// DATA/HEADERS/CONTINUATION will be verified
// when we have payload.
@@ -258,7 +259,7 @@ const uchar *Frame::hpackBlockBegin() const
return begin;
}
-FrameStatus FrameReader::read(QAbstractSocket &socket)
+FrameStatus FrameReader::read(QIODevice &socket)
{
if (offset < frameHeaderSize) {
if (!readHeader(socket))
@@ -286,7 +287,7 @@ FrameStatus FrameReader::read(QAbstractSocket &socket)
return frame.validatePayload();
}
-bool FrameReader::readHeader(QAbstractSocket &socket)
+bool FrameReader::readHeader(QIODevice &socket)
{
Q_ASSERT(offset < frameHeaderSize);
@@ -302,7 +303,7 @@ bool FrameReader::readHeader(QAbstractSocket &socket)
return offset == frameHeaderSize;
}
-bool FrameReader::readPayload(QAbstractSocket &socket)
+bool FrameReader::readPayload(QIODevice &socket)
{
Q_ASSERT(offset < frame.buffer.size());
Q_ASSERT(frame.buffer.size() > frameHeaderSize);
@@ -393,7 +394,7 @@ void FrameWriter::updatePayloadSize()
setPayloadSize(size);
}
-bool FrameWriter::write(QAbstractSocket &socket) const
+bool FrameWriter::write(QIODevice &socket) const
{
auto &buffer = frame.buffer;
Q_ASSERT(buffer.size() >= frameHeaderSize);
@@ -407,7 +408,7 @@ bool FrameWriter::write(QAbstractSocket &socket) const
return nWritten != -1 && size_type(nWritten) == buffer.size();
}
-bool FrameWriter::writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit)
+bool FrameWriter::writeHEADERS(QIODevice &socket, quint32 sizeLimit)
{
auto &buffer = frame.buffer;
Q_ASSERT(buffer.size() >= frameHeaderSize);
@@ -457,7 +458,7 @@ bool FrameWriter::writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit)
return true;
}
-bool FrameWriter::writeDATA(QAbstractSocket &socket, quint32 sizeLimit,
+bool FrameWriter::writeDATA(QIODevice &socket, quint32 sizeLimit,
const uchar *src, quint32 size)
{
// With DATA frame(s) we always have:
diff --git a/src/network/access/http2/http2frames_p.h b/src/network/access/http2/http2frames_p.h
index be87f43fbe..48e3f751b7 100644
--- a/src/network/access/http2/http2frames_p.h
+++ b/src/network/access/http2/http2frames_p.h
@@ -27,7 +27,7 @@
QT_BEGIN_NAMESPACE
class QHttp2ProtocolHandler;
-class QAbstractSocket;
+class QIODevice;
namespace Http2
{
@@ -65,15 +65,15 @@ struct Q_AUTOTEST_EXPORT Frame
class Q_AUTOTEST_EXPORT FrameReader
{
public:
- FrameStatus read(QAbstractSocket &socket);
+ FrameStatus read(QIODevice &socket);
Frame &inboundFrame()
{
return frame;
}
private:
- bool readHeader(QAbstractSocket &socket);
- bool readPayload(QAbstractSocket &socket);
+ bool readHeader(QIODevice &socket);
+ bool readPayload(QIODevice &socket);
quint32 offset = 0;
Frame frame;
@@ -123,20 +123,25 @@ public:
{
append(&payload[0], &payload[0] + payload.size());
}
+ void append(QByteArrayView payload)
+ {
+ append(reinterpret_cast<const uchar *>(payload.begin()),
+ reinterpret_cast<const uchar *>(payload.end()));
+ }
void append(const uchar *begin, const uchar *end);
// Write as a single frame:
- bool write(QAbstractSocket &socket) const;
+ bool write(QIODevice &socket) const;
// Two types of frames we are sending are affected by frame size limits:
// HEADERS and DATA. HEADERS' payload (hpacked HTTP headers, following a
// frame header) is always in our 'buffer', we send the initial HEADERS
// frame first and then CONTINUTATION frame(s) if needed:
- bool writeHEADERS(QAbstractSocket &socket, quint32 sizeLimit);
+ bool writeHEADERS(QIODevice &socket, quint32 sizeLimit);
// With DATA frames the actual payload is never in our 'buffer', it's a
// 'readPointer' from QNonContiguousData. We split this payload as needed
// into DATA frames with correct payload size fitting into frame size limit:
- bool writeDATA(QAbstractSocket &socket, quint32 sizeLimit,
+ bool writeDATA(QIODevice &socket, quint32 sizeLimit,
const uchar *src, quint32 size);
private:
void updatePayloadSize();
diff --git a/src/network/access/http2/http2protocol.cpp b/src/network/access/http2/http2protocol.cpp
index 966f294e81..8e7e176c41 100644
--- a/src/network/access/http2/http2protocol.cpp
+++ b/src/network/access/http2/http2protocol.cpp
@@ -76,12 +76,10 @@ void appendProtocolUpgradeHeaders(const QHttp2Configuration &config, QHttpNetwor
Q_ASSERT(request);
// RFC 2616, 14.10
// RFC 7540, 3.2
- QByteArray value(request->headerField("Connection"));
+ const QByteArray connectionHeader = request->headerField("Connection");
+ const auto separator = connectionHeader.isEmpty() ? QByteArrayView() : QByteArrayView(", ");
// We _append_ 'Upgrade':
- if (value.size())
- value += ", ";
-
- value += "Upgrade, HTTP2-Settings";
+ QByteArray value = connectionHeader + separator + "Upgrade, HTTP2-Settings";
request->setHeaderField("Connection", value);
// This we just (re)write.
request->setHeaderField("Upgrade", "h2c");
@@ -186,19 +184,45 @@ QNetworkReply::NetworkError qt_error(quint32 errorCode)
bool is_protocol_upgraded(const QHttpNetworkReply &reply)
{
- if (reply.statusCode() == 101) {
- // Do some minimal checks here - we expect 'Upgrade: h2c' to be found.
- const auto &header = reply.header();
- for (const QPair<QByteArray, QByteArray> &field : header) {
- if (field.first.compare("upgrade", Qt::CaseInsensitive) == 0 &&
- field.second.compare("h2c", Qt::CaseInsensitive) == 0)
- return true;
- }
+ if (reply.statusCode() != 101)
+ return false;
+
+ // Do some minimal checks here - we expect 'Upgrade: h2c' to be found.
+ for (const auto &v : reply.header().values(QHttpHeaders::WellKnownHeader::Upgrade)) {
+ if (v.compare("h2c", Qt::CaseInsensitive) == 0)
+ return true;
}
return false;
}
+std::vector<uchar> assemble_hpack_block(const std::vector<Frame> &frames)
+{
+ std::vector<uchar> hpackBlock;
+
+ size_t total = 0;
+ for (const auto &frame : frames) {
+ if (qAddOverflow(total, size_t{frame.hpackBlockSize()}, &total))
+ return hpackBlock;
+ }
+
+ if (!total)
+ return hpackBlock;
+
+ hpackBlock.resize(total);
+ auto dst = hpackBlock.begin();
+ for (const auto &frame : frames) {
+ if (const auto hpackBlockSize = frame.hpackBlockSize()) {
+ const uchar *src = frame.hpackBlockBegin();
+ std::copy(src, src + hpackBlockSize, dst);
+ dst += hpackBlockSize;
+ }
+ }
+
+ return hpackBlock;
+}
+
+
} // namespace Http2
QT_END_NAMESPACE
diff --git a/src/network/access/http2/http2protocol_p.h b/src/network/access/http2/http2protocol_p.h
index e5068ad81a..f0f18d1dd5 100644
--- a/src/network/access/http2/http2protocol_p.h
+++ b/src/network/access/http2/http2protocol_p.h
@@ -21,6 +21,8 @@
#include <QtCore/private/qglobal_p.h>
#include <QtCore/qmap.h>
+#include <vector>
+
// Different HTTP/2 constants/values as defined by RFC 7540.
QT_BEGIN_NAMESPACE
@@ -115,9 +117,10 @@ const qint32 maxSessionReceiveWindowSize((quint32(1) << 31) - 1);
// Presumably, we never use up to 100 streams so let it be 10 simultaneous:
const qint32 qtDefaultStreamReceiveWindowSize = maxSessionReceiveWindowSize / 10;
-struct Frame configurationToSettingsFrame(const QHttp2Configuration &configuration);
+struct Frame Q_AUTOTEST_EXPORT configurationToSettingsFrame(const QHttp2Configuration &configuration);
QByteArray settingsFrameToBase64(const Frame &settingsFrame);
void appendProtocolUpgradeHeaders(const QHttp2Configuration &configuration, QHttpNetworkRequest *request);
+std::vector<uchar> assemble_hpack_block(const std::vector<Frame> &frames);
extern const Q_AUTOTEST_EXPORT char Http2clientPreface[clientPrefaceLength];
diff --git a/src/network/access/qabstractnetworkcache.cpp b/src/network/access/qabstractnetworkcache.cpp
index c8b940d801..3cd55d46fa 100644
--- a/src/network/access/qabstractnetworkcache.cpp
+++ b/src/network/access/qabstractnetworkcache.cpp
@@ -3,6 +3,8 @@
#include "qabstractnetworkcache.h"
#include "qabstractnetworkcache_p.h"
+#include "qnetworkrequest_p.h"
+#include "qhttpheadershelper_p.h"
#include <qdatastream.h>
#include <qdatetime.h>
@@ -28,14 +30,14 @@ public:
url == other.url
&& lastModified == other.lastModified
&& expirationDate == other.expirationDate
- && headers == other.headers
- && saveToDisk == other.saveToDisk;
+ && saveToDisk == other.saveToDisk
+ && QHttpHeadersHelper::compareStrict(headers, other.headers);
}
QUrl url;
QDateTime lastModified;
QDateTime expirationDate;
- QNetworkCacheMetaData::RawHeaderList headers;
+ QHttpHeaders headers;
QNetworkCacheMetaData::AttributesMap attributes;
bool saveToDisk;
@@ -207,21 +209,45 @@ void QNetworkCacheMetaData::setUrl(const QUrl &url)
Returns a list of all raw headers that are set in this meta data.
The list is in the same order that the headers were set.
- \sa setRawHeaders()
+ \sa setRawHeaders(), headers()
*/
QNetworkCacheMetaData::RawHeaderList QNetworkCacheMetaData::rawHeaders() const
{
- return d->headers;
+ return QNetworkHeadersPrivate::fromHttpToRaw(d->headers);
}
/*!
Sets the raw headers to \a list.
- \sa rawHeaders()
+ \sa rawHeaders(), setHeaders()
*/
void QNetworkCacheMetaData::setRawHeaders(const RawHeaderList &list)
{
- d->headers = list;
+ d->headers = QNetworkHeadersPrivate::fromRawToHttp(list);
+}
+
+/*!
+ \since 6.8
+
+ Returns headers in form of QHttpHeaders that are set in this meta data.
+
+ \sa setHeaders()
+*/
+QHttpHeaders QNetworkCacheMetaData::headers() const
+{
+ return d->headers;
+}
+
+/*!
+ \since 6.8
+
+ Sets the headers of this network cache meta data to \a headers.
+
+ \sa headers()
+*/
+void QNetworkCacheMetaData::setHeaders(const QHttpHeaders &headers)
+{
+ d->headers = headers;
}
/*!
@@ -367,7 +393,8 @@ void QNetworkCacheMetaDataPrivate::load(QDataStream &in, QNetworkCacheMetaData &
in >> p->lastModified;
in >> p->saveToDisk;
in >> p->attributes;
- in >> p->headers;
+ QNetworkCacheMetaData::RawHeaderList list; in >> list;
+ metaData.setRawHeaders(list);
}
/*!
diff --git a/src/network/access/qabstractnetworkcache.h b/src/network/access/qabstractnetworkcache.h
index c70dcf737c..b12fd4f863 100644
--- a/src/network/access/qabstractnetworkcache.h
+++ b/src/network/access/qabstractnetworkcache.h
@@ -48,6 +48,9 @@ public:
RawHeaderList rawHeaders() const;
void setRawHeaders(const RawHeaderList &headers);
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &headers);
+
QDateTime lastModified() const;
void setLastModified(const QDateTime &dateTime);
diff --git a/src/network/access/qabstractprotocolhandler_p.h b/src/network/access/qabstractprotocolhandler_p.h
index ad82aae66e..da5eaeeb74 100644
--- a/src/network/access/qabstractprotocolhandler_p.h
+++ b/src/network/access/qabstractprotocolhandler_p.h
@@ -23,7 +23,7 @@ QT_BEGIN_NAMESPACE
class QHttpNetworkConnectionChannel;
class QHttpNetworkReply;
-class QAbstractSocket;
+class QIODevice;
class QHttpNetworkConnection;
class QAbstractProtocolHandler {
@@ -39,7 +39,7 @@ public:
protected:
QHttpNetworkConnectionChannel *m_channel;
QHttpNetworkReply *m_reply;
- QAbstractSocket *m_socket;
+ QIODevice *m_socket;
QHttpNetworkConnection *m_connection;
};
diff --git a/src/network/access/qdecompresshelper.cpp b/src/network/access/qdecompresshelper.cpp
index 98c9860450..52a0d9fc06 100644
--- a/src/network/access/qdecompresshelper.cpp
+++ b/src/network/access/qdecompresshelper.cpp
@@ -3,7 +3,6 @@
#include "qdecompresshelper_p.h"
-#include <QtCore/private/qbytearray_p.h>
#include <QtCore/qiodevice.h>
#include <QtCore/qcoreapplication.h>
@@ -24,7 +23,7 @@ QT_BEGIN_NAMESPACE
namespace {
struct ContentEncodingMapping
{
- char name[8];
+ QByteArrayView name;
QDecompressHelper::ContentEncoding encoding;
};
@@ -39,10 +38,10 @@ constexpr ContentEncodingMapping contentEncodingMapping[] {
{ "deflate", QDecompressHelper::Deflate },
};
-QDecompressHelper::ContentEncoding encodingFromByteArray(const QByteArray &ce) noexcept
+QDecompressHelper::ContentEncoding encodingFromByteArray(QByteArrayView ce) noexcept
{
for (const auto &mapping : contentEncodingMapping) {
- if (ce.compare(QByteArrayView(mapping.name, strlen(mapping.name)), Qt::CaseInsensitive) == 0)
+ if (ce.compare(mapping.name, Qt::CaseInsensitive) == 0)
return mapping.encoding;
}
return QDecompressHelper::None;
@@ -68,22 +67,19 @@ ZSTD_DStream *toZstandardPointer(void *ptr)
#endif
}
-bool QDecompressHelper::isSupportedEncoding(const QByteArray &encoding)
+bool QDecompressHelper::isSupportedEncoding(QByteArrayView encoding)
{
return encodingFromByteArray(encoding) != QDecompressHelper::None;
}
QByteArrayList QDecompressHelper::acceptedEncoding()
{
- static QByteArrayList accepted = []() {
- QByteArrayList list;
- list.reserve(sizeof(contentEncodingMapping) / sizeof(contentEncodingMapping[0]));
- for (const auto &mapping : contentEncodingMapping) {
- list << QByteArray(mapping.name);
- }
- return list;
- }();
- return accepted;
+ QByteArrayList list;
+ list.reserve(std::size(contentEncodingMapping));
+ for (const auto &mapping : contentEncodingMapping) {
+ list << mapping.name.toByteArray();
+ }
+ return list;
}
QDecompressHelper::~QDecompressHelper()
@@ -91,7 +87,7 @@ QDecompressHelper::~QDecompressHelper()
clear();
}
-bool QDecompressHelper::setEncoding(const QByteArray &encoding)
+bool QDecompressHelper::setEncoding(QByteArrayView encoding)
{
Q_ASSERT(contentEncoding == QDecompressHelper::None);
if (contentEncoding != QDecompressHelper::None) {
diff --git a/src/network/access/qdecompresshelper_p.h b/src/network/access/qdecompresshelper_p.h
index 54a9fe92c8..c837c14521 100644
--- a/src/network/access/qdecompresshelper_p.h
+++ b/src/network/access/qdecompresshelper_p.h
@@ -37,7 +37,7 @@ public:
QDecompressHelper() = default;
~QDecompressHelper();
- bool setEncoding(const QByteArray &contentEncoding);
+ bool setEncoding(QByteArrayView contentEncoding);
bool isCountingBytes() const;
void setCountingBytesEnabled(bool shouldCount);
@@ -57,7 +57,7 @@ public:
void setDecompressedSafetyCheckThreshold(qint64 threshold);
- static bool isSupportedEncoding(const QByteArray &encoding);
+ static bool isSupportedEncoding(QByteArrayView encoding);
static QByteArrayList acceptedEncoding();
QString errorString() const;
diff --git a/src/network/access/qformdatabuilder.cpp b/src/network/access/qformdatabuilder.cpp
new file mode 100644
index 0000000000..dbbb3a49a6
--- /dev/null
+++ b/src/network/access/qformdatabuilder.cpp
@@ -0,0 +1,313 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qformdatabuilder.h"
+
+#include <QtCore/private/qstringconverter_p.h>
+#if QT_CONFIG(mimetype)
+#include "QtCore/qmimedatabase.h"
+#endif
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QFormDataPartBuilder
+ \brief The QFormDataPartBuilder class is a convenience class to simplify
+ the construction of QHttpPart objects.
+ \since 6.8
+
+ \ingroup network
+ \ingroup shared
+ \inmodule QtNetwork
+
+ The QFormDataPartBuilder class can be used to build a QHttpPart object with
+ the content disposition header set to be form-data by default. Then the
+ generated object can be used as part of a multipart message (which is
+ represented by the QHttpMultiPart class).
+
+ \sa QHttpPart, QHttpMultiPart, QFormDataBuilder
+*/
+
+/*!
+ Constructs a QFormDataPartBuilder object and sets \a name as the name
+ parameter of the form-data.
+*/
+QFormDataPartBuilder::QFormDataPartBuilder(QLatin1StringView name, PrivateConstructor /*unused*/)
+{
+ static_assert(std::is_nothrow_move_constructible_v<decltype(m_body)>);
+ static_assert(std::is_nothrow_move_assignable_v<decltype(m_body)>);
+
+ m_headerValue += "form-data; name=\"";
+ for (auto c : name) {
+ if (c == '"' || c == '\\')
+ m_headerValue += '\\';
+ m_headerValue += c;
+ }
+ m_headerValue += "\"";
+}
+
+/*!
+ \fn QFormDataPartBuilder::QFormDataPartBuilder(QFormDataPartBuilder &&other) noexcept
+
+ Move-constructs a QFormDataPartBuilder instance, making it point at the same
+ object that \a other was pointing to.
+*/
+
+/*!
+ \fn QFormDataPartBuilder &QFormDataPartBuilder::operator=(QFormDataPartBuilder &&other)
+
+ Move-assigns \a other to this QFormDataPartBuilder instance.
+*/
+
+/*!
+ Destroys the QFormDataPartBuilder object.
+*/
+
+QFormDataPartBuilder::~QFormDataPartBuilder()
+ = default;
+
+
+static auto encodeFileName(QStringView view)
+{
+ struct R { QByteArrayView encoding; QByteArray encoded; };
+
+ QByteArrayView encoding = "=";
+ bool needsUtf8 = false;
+
+ for (QChar c : view) {
+ if (c > u'\xff') {
+ encoding = "*=UTF-8''";
+ needsUtf8 = true;
+ break;
+ } else if (c > u'\x7f') {
+ encoding = "*=ISO-8859-1''";
+ }
+ }
+
+ return R{encoding, needsUtf8 ? view.toUtf8() : view.toLatin1()};
+}
+
+static void convertInto_impl(QByteArray &dst, QUtf8StringView in)
+{
+ dst.clear();
+ dst += QByteArrayView{in}; // it's ASCII, anyway
+}
+
+static void convertInto_impl(QByteArray &dst, QLatin1StringView in)
+{
+ dst.clear();
+ dst += QByteArrayView{in}; // it's ASCII, anyway
+}
+
+static void convertInto_impl(QByteArray &dst, QStringView in)
+{
+ dst.resize(in.size());
+ (void)QLatin1::convertFromUnicode(dst.data(), in);
+}
+
+static void convertInto(QByteArray &dst, QAnyStringView in)
+{
+ in.visit([&dst](auto in) { convertInto_impl(dst, in); });
+}
+
+QFormDataPartBuilder &QFormDataPartBuilder::setBodyHelper(const QByteArray &data,
+ QAnyStringView name,
+ QAnyStringView mimeType)
+{
+ m_originalBodyName = name.toString();
+ convertInto(m_mimeType, mimeType);
+ m_body = data;
+ return *this;
+}
+
+/*!
+ Sets \a data as the body of this MIME part and, if given, \a fileName as the
+ file name parameter in the content disposition header.
+
+ If \a mimeType is not given (is empty), then QFormDataPartBuilder tries to
+ auto-detect the mime-type of \a data using QMimeDatabase.
+
+ A subsequent call to setBodyDevice() discards the body and the device will
+ be used instead.
+
+ For a large amount of data (e.g. an image), setBodyDevice() is preferred,
+ which will not copy the data internally.
+
+ \sa setBodyDevice()
+*/
+
+QFormDataPartBuilder &QFormDataPartBuilder::setBody(QByteArrayView data,
+ QAnyStringView fileName,
+ QAnyStringView mimeType)
+{
+ return setBody(data.toByteArray(), fileName, mimeType);
+}
+
+/*!
+ Sets \a body as the body device of this part and \a fileName as the file
+ name parameter in the content disposition header.
+
+ If \a mimeType is not given (is empty), then QFormDataPartBuilder tries to
+ auto-detect the mime-type of \a body using QMimeDatabase.
+
+ A subsequent call to setBody() discards the body device and the data set by
+ setBody() will be used instead.
+
+ For large amounts of data this method should be preferred over setBody(),
+ because the content is not copied when using this method, but read
+ directly from the device.
+
+ \a body must be open and readable. QFormDataPartBuilder does not take
+ ownership of \a body, i.e. the device must be closed and destroyed if
+ necessary.
+
+ \sa setBody(), QHttpPart::setBodyDevice()
+ */
+
+QFormDataPartBuilder &QFormDataPartBuilder::setBodyDevice(QIODevice *body, QAnyStringView fileName,
+ QAnyStringView mimeType)
+{
+ m_originalBodyName = fileName.toString();
+ convertInto(m_mimeType, mimeType);
+ m_body = body;
+ return *this;
+}
+
+/*!
+ Sets the headers specified in \a headers.
+
+ \note The "content-type" and "content-disposition" headers, if any are
+ specified in \a headers, will be overwritten by the class.
+*/
+
+QFormDataPartBuilder &QFormDataPartBuilder::setHeaders(const QHttpHeaders &headers)
+{
+ m_httpHeaders = headers;
+ return *this;
+}
+
+/*!
+ Generates a QHttpPart and sets the content disposition header as form-data.
+
+ When this function called, it uses the MIME database to deduce the type the
+ body based on its name and then sets the deduced type as the content type
+ header.
+*/
+
+QHttpPart QFormDataPartBuilder::build()
+{
+ QHttpPart httpPart;
+
+ if (!m_originalBodyName.isNull()) {
+ const auto enc = encodeFileName(m_originalBodyName);
+ m_headerValue += "; filename" + enc.encoding
+ + enc.encoded.toPercentEncoding(); // RFC 5987 Section 3.2.1
+ }
+
+#if QT_CONFIG(mimetype)
+ if (m_mimeType.isEmpty()) {
+ // auto-detect
+ QMimeDatabase db;
+ convertInto(m_mimeType, std::visit([&](auto &arg) {
+ return db.mimeTypeForFileNameAndData(m_originalBodyName, arg);
+ }, m_body).name());
+ }
+#endif
+
+ for (qsizetype i = 0; i < m_httpHeaders.size(); i++) {
+ httpPart.setRawHeader(QByteArrayView(m_httpHeaders.nameAt(i)).toByteArray(),
+ m_httpHeaders.valueAt(i).toByteArray());
+ }
+
+ if (!m_mimeType.isEmpty())
+ httpPart.setHeader(QNetworkRequest::ContentTypeHeader, m_mimeType);
+
+ httpPart.setHeader(QNetworkRequest::ContentDispositionHeader, m_headerValue);
+
+ if (auto d = std::get_if<QIODevice*>(&m_body))
+ httpPart.setBodyDevice(*d);
+ else if (auto b = std::get_if<QByteArray>(&m_body))
+ httpPart.setBody(*b);
+ else
+ Q_UNREACHABLE();
+
+ return httpPart;
+}
+
+/*!
+ \class QFormDataBuilder
+ \brief The QFormDataBuilder class is a convenience class to simplify
+ the construction of QHttpMultiPart objects.
+ \since 6.8
+
+ \ingroup network
+ \ingroup shared
+ \inmodule QtNetwork
+
+ The QFormDataBuilder class can be used to build a QHttpMultiPart object
+ with the content type set to be FormDataType by default.
+
+ \sa QHttpPart, QHttpMultiPart, QFormDataPartBuilder
+*/
+
+/*!
+ Constructs an empty QFormDataBuilder object.
+*/
+
+QFormDataBuilder::QFormDataBuilder()
+ = default;
+
+/*!
+ Destroys the QFormDataBuilder object.
+*/
+
+QFormDataBuilder::~QFormDataBuilder()
+ = default;
+
+/*!
+ \fn QFormDataBuilder::QFormDataBuilder(QFormDataBuilder &&other) noexcept
+
+ Move-constructs a QFormDataBuilder instance, making it point at the same
+ object that \a other was pointing to.
+*/
+
+/*!
+ \fn QFormDataBuilder &QFormDataBuilder::operator=(QFormDataBuilder &&other) noexcept
+
+ Move-assigns \a other to this QFormDataBuilder instance.
+*/
+
+/*!
+ Constructs and returns a reference to a QFormDataPartBuilder object and sets
+ \a name as the name parameter of the form-data. The returned reference is
+ valid until the next call to this function.
+
+ \sa QFormDataPartBuilder, QHttpPart
+*/
+
+QFormDataPartBuilder &QFormDataBuilder::part(QLatin1StringView name)
+{
+ static_assert(std::is_nothrow_move_constructible_v<decltype(m_parts)>);
+ static_assert(std::is_nothrow_move_assignable_v<decltype(m_parts)>);
+
+ return m_parts.emplace_back(name, QFormDataPartBuilder::PrivateConstructor());
+}
+
+/*!
+ Constructs and returns a pointer to a QHttpMultipart object. The caller
+ takes ownership of the generated QHttpMultiPart object.
+
+ \sa QHttpMultiPart
+*/
+
+std::unique_ptr<QHttpMultiPart> QFormDataBuilder::buildMultiPart()
+{
+ auto multiPart = std::make_unique<QHttpMultiPart>(QHttpMultiPart::FormDataType);
+
+ for (auto &part : m_parts)
+ multiPart->append(part.build());
+
+ return multiPart;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qformdatabuilder.h b/src/network/access/qformdatabuilder.h
new file mode 100644
index 0000000000..68f9f3742c
--- /dev/null
+++ b/src/network/access/qformdatabuilder.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QFORMDATABUILDER_H
+#define QFORMDATABUILDER_H
+
+#include <QtNetwork/qtnetworkglobal.h>
+#include <QtNetwork/qhttpheaders.h>
+#include <QtNetwork/qhttpmultipart.h>
+
+#include <QtCore/qbytearray.h>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qstring.h>
+
+#include <memory>
+#include <variant>
+#include <vector>
+
+#ifndef Q_OS_WASM
+QT_REQUIRE_CONFIG(http);
+#endif
+
+class tst_QFormDataBuilder;
+
+QT_BEGIN_NAMESPACE
+
+class QHttpPartPrivate;
+class QHttpMultiPart;
+class QDebug;
+
+class QFormDataPartBuilder
+{
+ struct PrivateConstructor { explicit PrivateConstructor() = default; };
+public:
+ Q_NETWORK_EXPORT explicit QFormDataPartBuilder(QLatin1StringView name, PrivateConstructor);
+
+ QFormDataPartBuilder(QFormDataPartBuilder &&other) noexcept
+ : m_headerValue(std::move(other.m_headerValue)),
+ m_originalBodyName(std::move(other.m_originalBodyName)),
+ m_httpHeaders(std::move(other.m_httpHeaders)),
+ m_body(std::move(other.m_body)),
+ m_reserved(std::exchange(other.m_reserved, nullptr))
+ {
+
+ }
+
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QFormDataPartBuilder)
+ void swap(QFormDataPartBuilder &other) noexcept
+ {
+ m_headerValue.swap(other.m_headerValue);
+ m_originalBodyName.swap(other.m_originalBodyName);
+ m_httpHeaders.swap(other.m_httpHeaders);
+ m_body.swap(other.m_body);
+ qt_ptr_swap(m_reserved, other.m_reserved);
+ }
+
+ Q_NETWORK_EXPORT ~QFormDataPartBuilder();
+
+ Q_WEAK_OVERLOAD QFormDataPartBuilder &setBody(const QByteArray &data,
+ QAnyStringView fileName = {},
+ QAnyStringView mimeType = {})
+ { return setBodyHelper(data, fileName, mimeType); }
+
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setBody(QByteArrayView data,
+ QAnyStringView fileName = {},
+ QAnyStringView mimeType = {});
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setBodyDevice(QIODevice *body,
+ QAnyStringView fileName = {},
+ QAnyStringView mimeType = {});
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setHeaders(const QHttpHeaders &headers);
+private:
+ Q_DISABLE_COPY(QFormDataPartBuilder)
+
+ Q_NETWORK_EXPORT QFormDataPartBuilder &setBodyHelper(const QByteArray &data,
+ QAnyStringView fileName,
+ QAnyStringView mimeType);
+ Q_NETWORK_EXPORT QHttpPart build();
+
+ QByteArray m_headerValue;
+ QByteArray m_mimeType;
+ QString m_originalBodyName;
+ QHttpHeaders m_httpHeaders;
+ std::variant<QIODevice*, QByteArray> m_body;
+ void *m_reserved = nullptr;
+
+ friend class QFormDataBuilder;
+ friend class ::tst_QFormDataBuilder;
+ friend void swap(QFormDataPartBuilder &lhs, QFormDataPartBuilder &rhs) noexcept
+ { lhs.swap(rhs); }
+};
+
+class QFormDataBuilder
+{
+public:
+ Q_NETWORK_EXPORT explicit QFormDataBuilder();
+
+ QFormDataBuilder(QFormDataBuilder &&other) noexcept
+ : m_parts(std::move(other.m_parts)),
+ m_reserved(std::exchange(other.m_reserved, nullptr))
+ {
+
+ }
+
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QFormDataBuilder)
+ void swap(QFormDataBuilder &other) noexcept
+ {
+ m_parts.swap(other.m_parts);
+ qt_ptr_swap(m_reserved, other.m_reserved);
+ }
+
+ Q_NETWORK_EXPORT ~QFormDataBuilder();
+ Q_NETWORK_EXPORT QFormDataPartBuilder &part(QLatin1StringView name);
+ Q_NETWORK_EXPORT std::unique_ptr<QHttpMultiPart> buildMultiPart();
+private:
+ std::vector<QFormDataPartBuilder> m_parts;
+ void *m_reserved = nullptr;
+
+ friend void swap(QFormDataBuilder &lhs, QFormDataBuilder &rhs) noexcept
+ { lhs.swap(rhs); }
+
+ Q_DISABLE_COPY(QFormDataBuilder)
+};
+
+QT_END_NAMESPACE
+
+#endif // QFORMDATABUILDER_H
diff --git a/src/network/access/qhsts.cpp b/src/network/access/qhsts.cpp
index 39905f3548..21ed08ce4a 100644
--- a/src/network/access/qhsts.cpp
+++ b/src/network/access/qhsts.cpp
@@ -3,6 +3,8 @@
#include "qhsts_p.h"
+#include "qhttpheaders.h"
+
#include "QtCore/private/qipaddress_p.h"
#include "QtCore/qlist.h"
@@ -40,7 +42,7 @@ static bool is_valid_domain_name(const QString &host)
return true;
}
-void QHstsCache::updateFromHeaders(const QList<QPair<QByteArray, QByteArray>> &headers,
+void QHstsCache::updateFromHeaders(const QHttpHeaders &headers,
const QUrl &url)
{
if (!url.isValid())
@@ -286,7 +288,7 @@ static bool isSeparator(char c)
return isLWS(c) || std::find(separators, end, c) != end;
}
-static QByteArray unescapeMaxAge(const QByteArray &value)
+static QByteArrayView unescapeMaxAge(QByteArrayView value)
{
if (value.size() < 2 || value[0] != '"')
return value;
@@ -324,27 +326,25 @@ quoted-pair = "\" CHAR
*/
-bool QHstsHeaderParser::parse(const QList<QPair<QByteArray, QByteArray>> &headers)
+bool QHstsHeaderParser::parse(const QHttpHeaders &headers)
{
- for (const auto &h : headers) {
- // We use '==' since header name was already 'trimmed' for us:
- if (h.first == "Strict-Transport-Security") {
- header = h.second;
- // RFC6797, 8.1:
- //
- // The UA MUST ignore any STS header fields not conforming to the
- // grammar specified in Section 6.1 ("Strict-Transport-Security HTTP
- // Response Header Field").
- //
- // If a UA receives more than one STS header field in an HTTP
- // response message over secure transport, then the UA MUST process
- // only the first such header field.
- //
- // We read this as: ignore all invalid headers and take the first valid:
- if (parseSTSHeader() && maxAgeFound) {
- expiry = QDateTime::currentDateTimeUtc().addSecs(maxAge);
- return true;
- }
+ for (const auto &value : headers.values(
+ QHttpHeaders::WellKnownHeader::StrictTransportSecurity)) {
+ header = value;
+ // RFC6797, 8.1:
+ //
+ // The UA MUST ignore any STS header fields not conforming to the
+ // grammar specified in Section 6.1 ("Strict-Transport-Security HTTP
+ // Response Header Field").
+ //
+ // If a UA receives more than one STS header field in an HTTP
+ // response message over secure transport, then the UA MUST process
+ // only the first such header field.
+ //
+ // We read this as: ignore all invalid headers and take the first valid:
+ if (parseSTSHeader() && maxAgeFound) {
+ expiry = QDateTime::currentDateTimeUtc().addSecs(maxAge);
+ return true;
}
}
@@ -400,7 +400,7 @@ bool QHstsHeaderParser::parseDirective()
if (token == ";") // That's a weird grammar, but that's what it is.
return true;
- if (!isTOKEN(token[0])) // Not a valid directive-name.
+ if (!isTOKEN(token.at(0))) // Not a valid directive-name.
return false;
const QByteArray directiveName = token;
@@ -445,7 +445,7 @@ bool QHstsHeaderParser::processDirective(const QByteArray &name, const QByteArra
return false;
}
- const QByteArray unescapedValue = unescapeMaxAge(value);
+ const QByteArrayView unescapedValue = unescapeMaxAge(value);
if (!unescapedValue.size())
return false;
@@ -481,13 +481,13 @@ bool QHstsHeaderParser::nextToken()
// Fortunately enough, by this point qhttpnetworkreply already got rid of
// [CRLF] parts, but we can have 1*(SP|HT) yet.
- while (tokenPos < header.size() && isLWS(header[tokenPos]))
+ while (tokenPos < header.size() && isLWS(header.at(tokenPos)))
++tokenPos;
if (tokenPos == header.size())
return true;
- const char ch = header[tokenPos];
+ const char ch = header.at(tokenPos);
if (ch == ';' || ch == '=') {
token.append(ch);
++tokenPos;
@@ -501,17 +501,17 @@ bool QHstsHeaderParser::nextToken()
if (ch == '"') {
int last = tokenPos + 1;
while (last < header.size()) {
- if (header[last] == '"') {
+ if (header.at(last) == '"') {
// The end of a quoted-string.
break;
- } else if (header[last] == '\\') {
+ } else if (header.at(last) == '\\') {
// quoted-pair = "\" CHAR
- if (last + 1 < header.size() && isCHAR(header[last + 1]))
+ if (last + 1 < header.size() && isCHAR(header.at(last + 1)))
last += 2;
else
return false;
} else {
- if (!isTEXT(header[last]))
+ if (!isTEXT(header.at(last)))
return false;
++last;
}
@@ -532,7 +532,7 @@ bool QHstsHeaderParser::nextToken()
return false;
int last = tokenPos + 1;
- while (last < header.size() && isTOKEN(header[last]))
+ while (last < header.size() && isTOKEN(header.at(last)))
++last;
token = header.mid(tokenPos, last - tokenPos);
diff --git a/src/network/access/qhsts_p.h b/src/network/access/qhsts_p.h
index 96d4ee8dc5..ff9378197b 100644
--- a/src/network/access/qhsts_p.h
+++ b/src/network/access/qhsts_p.h
@@ -31,11 +31,13 @@
QT_BEGIN_NAMESPACE
+class QHttpHeaders;
+
class Q_AUTOTEST_EXPORT QHstsCache
{
public:
- void updateFromHeaders(const QList<QPair<QByteArray, QByteArray>> &headers,
+ void updateFromHeaders(const QHttpHeaders &headers,
const QUrl &url);
void updateFromPolicies(const QList<QHstsPolicy> &hosts);
void updateKnownHost(const QUrl &url, const QDateTime &expires,
@@ -90,7 +92,7 @@ class Q_AUTOTEST_EXPORT QHstsHeaderParser
{
public:
- bool parse(const QList<QPair<QByteArray, QByteArray>> &headers);
+ bool parse(const QHttpHeaders &headers);
QDateTime expirationDate() const { return expiry; }
bool includeSubDomains() const { return subDomainsFound; }
diff --git a/src/network/access/qhstspolicy.cpp b/src/network/access/qhstspolicy.cpp
index 504c2203a4..323e562c3c 100644
--- a/src/network/access/qhstspolicy.cpp
+++ b/src/network/access/qhstspolicy.cpp
@@ -21,8 +21,8 @@ QT_BEGIN_NAMESPACE
RFC6797.
You can set expiry time and host name for this policy, and control whether it
- applies to subdomains, either in the constructor or by calling setExpiry(),
- setHost() and setIncludesSubdomains().
+ applies to subdomains, either in the constructor or by calling \l setExpiry(),
+ \l setHost() and \l setIncludesSubDomains().
\sa QNetworkAccessManager::setStrictTransportSecurityEnabled()
*/
diff --git a/src/network/access/qhttp1configuration.cpp b/src/network/access/qhttp1configuration.cpp
new file mode 100644
index 0000000000..cfa929bca5
--- /dev/null
+++ b/src/network/access/qhttp1configuration.cpp
@@ -0,0 +1,154 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qhttp1configuration.h"
+
+#include <QtCore/private/qnumeric_p.h>
+#include <QtCore/qhashfunctions.h>
+
+QT_BEGIN_NAMESPACE
+
+// QHttp1ConfigurationPrivate is unused until we need it:
+static_assert(sizeof(QHttp1Configuration) == sizeof(void*),
+ "You have added too many members to QHttp1Configuration::ShortData. "
+ "Decrease their size or switch to using a d-pointer.");
+
+/*!
+ \class QHttp1Configuration
+ \brief The QHttp1Configuration class controls HTTP/1 parameters and settings.
+ \since 6.5
+
+ \reentrant
+ \inmodule QtNetwork
+ \ingroup network
+ \ingroup shared
+
+ QHttp1Configuration controls HTTP/1 parameters and settings that
+ QNetworkAccessManager will use to send requests and process responses.
+
+ \note The configuration must be set before the first request
+ was sent to a given host (and thus an HTTP/1 session established).
+
+ \sa QNetworkRequest::setHttp1Configuration(), QNetworkRequest::http1Configuration(), QNetworkAccessManager
+*/
+
+/*!
+ Default constructs a QHttp1Configuration object.
+*/
+QHttp1Configuration::QHttp1Configuration()
+ : u(ShortData{6, {}}) // QHttpNetworkConnectionPrivate::defaultHttpChannelCount
+{
+}
+
+/*!
+ Copy-constructs this QHttp1Configuration.
+*/
+QHttp1Configuration::QHttp1Configuration(const QHttp1Configuration &)
+ = default;
+
+/*!
+ \fn QHttp1Configuration::QHttp1Configuration(QHttp1Configuration &&other)
+
+ Move-constructs this QHttp1Configuration from \a other.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+*/
+
+/*!
+ Copy-assigns \a other to this QHttp1Configuration.
+*/
+QHttp1Configuration &QHttp1Configuration::operator=(const QHttp1Configuration &)
+ = default;
+
+/*!
+ \fn QHttp1Configuration &QHttp1Configuration::operator=(QHttp1Configuration &&)
+
+ Move-assigns \a other to this QHttp1Configuration.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+*/
+
+/*!
+ Destructor.
+*/
+QHttp1Configuration::~QHttp1Configuration()
+ = default;
+
+/*!
+ Sets the number of connections (minimum: 1; maximum: 255)
+ used per http(s) \e{host}:\e{port} combination to \a number.
+
+ If \a number is ≤ 0, does nothing. If \a number is > 255, 255 is used.
+
+ \sa numberOfConnectionsPerHost
+*/
+void QHttp1Configuration::setNumberOfConnectionsPerHost(qsizetype number)
+{
+ auto n = qt_saturate<std::uint8_t>(number);
+ if (n == 0)
+ return;
+ u.data.numConnectionsPerHost = n;
+}
+
+/*!
+ Returns the number of connections used per http(s) \c{host}:\e{port}
+ combination. The default is six (6).
+
+ \sa setNumberOfConnectionsPerHost
+*/
+qsizetype QHttp1Configuration::numberOfConnectionsPerHost() const
+{
+ return u.data.numConnectionsPerHost;
+}
+
+/*!
+ \fn void QHttp1Configuration::swap(QHttp1Configuration &other)
+
+ Swaps this HTTP/1 configuration with \a other. This operation is very fast
+ and never fails.
+*/
+
+/*!
+ \fn bool QHttp1Configuration::operator==(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
+ \since 6.5
+
+ Returns \c true if \a lhs and \a rhs represent the same set of HTTP/1
+ parameters.
+*/
+
+/*!
+ \fn bool QHttp1Configuration::operator!=(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
+ \since 6.5
+
+ Returns \c true if \a lhs and \a rhs do not represent the same set of
+ HTTP/1 parameters.
+*/
+
+/*!
+ \fn size_t QHttp1Configuration::qHash(const QHttp1Configuration &key, size_t seed)
+ \since 6.5
+
+ Returns the hash value for the \a key, using \a seed to seed the calculation.
+*/
+
+/*!
+ \internal
+*/
+bool QHttp1Configuration::equals(const QHttp1Configuration &other) const noexcept
+{
+ return u.data.numConnectionsPerHost == other.u.data.numConnectionsPerHost;
+}
+
+/*!
+ \internal
+*/
+size_t QHttp1Configuration::hash(size_t seed) const noexcept
+{
+ return qHash(u.data.numConnectionsPerHost, seed);
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttp1configuration.h b/src/network/access/qhttp1configuration.h
new file mode 100644
index 0000000000..128b8aa5aa
--- /dev/null
+++ b/src/network/access/qhttp1configuration.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QHTTP1CONFIGURATION_H
+#define QHTTP1CONFIGURATION_H
+
+#include <QtNetwork/qtnetworkglobal.h>
+
+#include <utility>
+#include <cstdint>
+
+QT_REQUIRE_CONFIG(http);
+
+QT_BEGIN_NAMESPACE
+
+class QHttp1ConfigurationPrivate;
+class QHttp1Configuration
+{
+public:
+ Q_NETWORK_EXPORT QHttp1Configuration();
+ Q_NETWORK_EXPORT QHttp1Configuration(const QHttp1Configuration &other);
+ QHttp1Configuration(QHttp1Configuration &&other) noexcept
+ : u{other.u} { other.u.d = nullptr; }
+
+ Q_NETWORK_EXPORT QHttp1Configuration &operator=(const QHttp1Configuration &other);
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QHttp1Configuration)
+
+ Q_NETWORK_EXPORT ~QHttp1Configuration();
+
+ Q_NETWORK_EXPORT void setNumberOfConnectionsPerHost(qsizetype amount);
+ Q_NETWORK_EXPORT qsizetype numberOfConnectionsPerHost() const;
+
+ void swap(QHttp1Configuration &other) noexcept
+ { std::swap(u, other.u); }
+
+private:
+ struct ShortData {
+ std::uint8_t numConnectionsPerHost;
+ char reserved[sizeof(void*) - sizeof(numConnectionsPerHost)];
+ };
+ union U {
+ U(ShortData _data) : data(_data) {}
+ QHttp1ConfigurationPrivate *d;
+ ShortData data;
+ } u;
+
+ Q_NETWORK_EXPORT bool equals(const QHttp1Configuration &other) const noexcept;
+ Q_NETWORK_EXPORT size_t hash(size_t seed) const noexcept;
+
+ friend bool operator==(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
+ { return lhs.equals(rhs); }
+ friend bool operator!=(const QHttp1Configuration &lhs, const QHttp1Configuration &rhs) noexcept
+ { return !lhs.equals(rhs); }
+
+ friend size_t qHash(const QHttp1Configuration &key, size_t seed = 0) noexcept { return key.hash(seed); }
+};
+
+Q_DECLARE_SHARED(QHttp1Configuration)
+
+QT_END_NAMESPACE
+
+#endif // QHTTP1CONFIGURATION_H
diff --git a/src/network/access/qhttp2configuration.h b/src/network/access/qhttp2configuration.h
index 4b3b7d54a2..ae08b664d4 100644
--- a/src/network/access/qhttp2configuration.h
+++ b/src/network/access/qhttp2configuration.h
@@ -8,9 +8,7 @@
#include <QtCore/qshareddata.h>
-#ifndef Q_QDOC
QT_REQUIRE_CONFIG(http);
-#endif
QT_BEGIN_NAMESPACE
diff --git a/src/network/access/qhttp2connection.cpp b/src/network/access/qhttp2connection.cpp
new file mode 100644
index 0000000000..8560e0da38
--- /dev/null
+++ b/src/network/access/qhttp2connection.cpp
@@ -0,0 +1,1752 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qhttp2connection_p.h"
+
+#include <private/bitstreams_p.h>
+
+#include <QtCore/private/qnumeric_p.h>
+#include <QtCore/private/qiodevice_p.h>
+#include <QtCore/private/qnoncontiguousbytedevice_p.h>
+#include <QtCore/qcoreapplication.h>
+#include <QtCore/QRandomGenerator>
+#include <QtCore/qloggingcategory.h>
+
+#include <algorithm>
+#include <memory>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(qHttp2ConnectionLog, "qt.network.http2.connection", QtCriticalMsg)
+
+using namespace Qt::StringLiterals;
+using namespace Http2;
+
+/*!
+ \class QHttp2Stream
+ \inmodule QtNetwork
+ \internal
+
+ The QHttp2Stream class represents a single HTTP/2 stream.
+ Must be created by QHttp2Connection.
+
+ \sa QHttp2Connection
+*/
+
+QHttp2Stream::QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept
+ : QObject(connection), m_streamID(streamID)
+{
+ Q_ASSERT(connection);
+ Q_ASSERT(streamID); // stream id 0 is reserved for connection control messages
+ qCDebug(qHttp2ConnectionLog, "[%p] new stream %u", connection, streamID);
+}
+
+QHttp2Stream::~QHttp2Stream() noexcept = default;
+
+/*!
+ \fn quint32 QHttp2Stream::streamID() const noexcept
+
+ Returns the stream ID of this stream.
+*/
+
+/*!
+ \fn void QHttp2Stream::headersReceived(const HPack::HttpHeader &headers, bool endStream)
+
+ This signal is emitted when the remote peer has sent a HEADERS frame, and
+ potentially some CONTINUATION frames, ending with the END_HEADERS flag
+ to this stream.
+
+ The headers are internally combined and decompressed, and are accessible
+ through the \a headers parameter. If the END_STREAM flag was set, the
+ \a endStream parameter will be \c true, indicating that the peer does not
+ intend to send any more frames on this stream.
+
+ \sa receivedHeaders()
+*/
+
+/*!
+ \fn void QHttp2Stream::headersUpdated()
+
+ This signal may be emitted if a new HEADERS frame was received after
+ already processing a previous HEADERS frame.
+
+ \sa headersReceived(), receivedHeaders()
+*/
+
+/*!
+ \fn void QHttp2Stream::errorOccurred(quint32 errorCode, const QString &errorString)
+
+ This signal is emitted when the stream has encountered an error. The
+ \a errorCode parameter is the HTTP/2 error code, and the \a errorString
+ parameter is a human-readable description of the error.
+
+ \sa https://www.rfc-editor.org/rfc/rfc7540#section-7
+*/
+
+/*!
+ \fn void QHttp2Stream::stateChanged(State newState)
+
+ This signal is emitted when the state of the stream changes. The \a newState
+ parameter is the new state of the stream.
+
+ Examples of this is sending or receiving a frame with the END_STREAM flag.
+ This will transition the stream to the HalfClosedLocal or HalfClosedRemote
+ state, respectively.
+
+ \sa state()
+*/
+
+
+/*!
+ \fn void QHttp2Stream::promisedStreamReceived(quint32 newStreamID)
+
+ This signal is emitted when the remote peer has promised a new stream with
+ the given \a newStreamID.
+
+ \sa QHttp2Connection::promisedStream()
+*/
+
+/*!
+ \fn void QHttp2Stream::uploadBlocked()
+
+ This signal is emitted when the stream is unable to send more data because
+ the remote peer's receive window is full.
+
+ This is mostly intended for diagnostics as there is no expectation that the
+ user can do anything to react to this.
+*/
+
+/*!
+ \fn void QHttp2Stream::dataReceived(const QByteArray &data, bool endStream)
+
+ This signal is emitted when the stream has received a DATA frame from the
+ remote peer. The \a data parameter contains the payload of the frame, and
+ the \a endStream parameter is \c true if the END_STREAM flag was set.
+
+ \sa downloadBuffer()
+*/
+
+/*!
+ \fn void QHttp2Stream::bytesWritten(qint64 bytesWritten)
+
+ This signal is emitted when the stream has written \a bytesWritten bytes to
+ the network.
+*/
+
+/*!
+ \fn void QHttp2Stream::uploadDeviceError(const QString &errorString)
+
+ This signal is emitted if the upload device encounters an error while
+ sending data. The \a errorString parameter is a human-readable description
+ of the error.
+*/
+
+/*!
+ \fn void QHttp2Stream::uploadFinished()
+
+ This signal is emitted when the stream has finished sending all the data
+ from the upload device.
+
+ If the END_STREAM flag was set for sendData() then the stream will be
+ closed for further writes before this signal is emitted.
+*/
+
+/*!
+ \fn bool QHttp2Stream::isUploadingDATA() const noexcept
+
+ Returns \c true if the stream is currently sending DATA frames.
+*/
+
+/*!
+ \fn State QHttp2Stream::state() const noexcept
+
+ Returns the current state of the stream.
+
+ \sa stateChanged()
+*/
+/*!
+ \fn bool QHttp2Stream::isActive() const noexcept
+
+ Returns \c true if the stream has been opened and is not yet closed.
+*/
+/*!
+ \fn bool QHttp2Stream::isPromisedStream() const noexcept
+
+ Returns \c true if the stream was promised by the remote peer.
+*/
+/*!
+ \fn bool QHttp2Stream::wasReset() const noexcept
+
+ Returns \c true if the stream was reset by the remote peer.
+*/
+/*!
+ \fn quint32 QHttp2Stream::RST_STREAM_code() const noexcept
+
+ Returns the HTTP/2 error code if the stream was reset by the remote peer.
+ If the stream was not reset, this function returns 0.
+*/
+/*!
+ \fn HPack::HttpHeader QHttp2Stream::receivedHeaders() const noexcept
+
+ Returns the headers received from the remote peer, if any.
+*/
+/*!
+ \fn QByteDataBuffer QHttp2Stream::downloadBuffer() const noexcept
+
+ Returns the buffer containing the data received from the remote peer.
+*/
+
+void QHttp2Stream::finishWithError(quint32 errorCode, const QString &message)
+{
+ qCDebug(qHttp2ConnectionLog, "[%p] stream %u finished with error: %ls (error code: %u)",
+ getConnection(), m_streamID, qUtf16Printable(message), errorCode);
+ transitionState(StateTransition::RST);
+ emit errorOccurred(errorCode, message);
+}
+
+void QHttp2Stream::finishWithError(quint32 errorCode)
+{
+ QNetworkReply::NetworkError error = QNetworkReply::NoError;
+ QString message;
+ qt_error(errorCode, error, message);
+ finishWithError(error, message);
+}
+
+/*!
+ Sends a RST_STREAM frame with the given \a errorCode.
+ This closes the stream for both sides, any further frames will be dropped.
+
+ Returns \c false if the stream is closed or idle, also if it fails to send
+ the RST_STREAM frame. Otherwise, returns \c true.
+*/
+bool QHttp2Stream::sendRST_STREAM(quint32 errorCode)
+{
+ if (m_state == State::Closed || m_state == State::Idle)
+ return false;
+ qCDebug(qHttp2ConnectionLog, "[%p] sending RST_STREAM on stream %u, code: %u", getConnection(),
+ m_streamID, errorCode);
+ transitionState(StateTransition::RST);
+
+ QHttp2Connection *connection = getConnection();
+ FrameWriter &frameWriter = connection->frameWriter;
+ frameWriter.start(FrameType::RST_STREAM, FrameFlag::EMPTY, m_streamID);
+ frameWriter.append(errorCode);
+ return frameWriter.write(*connection->getSocket());
+}
+
+/*!
+ Sends a DATA frame with the bytes obtained from \a device.
+
+ This function will send as many DATA frames as needed to send all the data
+ from \a device. If \a endStream is \c true, the END_STREAM flag will be set.
+
+ \a device must stay alive for the duration of the upload.
+ A way of doing this is to heap-allocate the \a device and parent it to the
+ QHttp2Stream.
+*/
+void QHttp2Stream::sendDATA(QIODevice *device, bool endStream)
+{
+ Q_ASSERT(!m_uploadDevice);
+ Q_ASSERT(!m_uploadByteDevice);
+ Q_ASSERT(device);
+ if (m_state != State::Open && m_state != State::HalfClosedRemote)
+ return;
+
+ auto *byteDevice = QNonContiguousByteDeviceFactory::create(device);
+ connect(this, &QHttp2Stream::uploadFinished, byteDevice, &QObject::deleteLater);
+ byteDevice->setParent(this);
+ m_uploadDevice = device;
+ qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of IODevice: %p",
+ getConnection(), m_streamID, device);
+ sendDATA(byteDevice, endStream);
+}
+
+/*!
+ Sends a DATA frame with the bytes obtained from \a device.
+
+ This function will send as many DATA frames as needed to send all the data
+ from \a device. If \a endStream is \c true, the END_STREAM flag will be set.
+
+ \a device must stay alive for the duration of the upload.
+ A way of doing this is to heap-allocate the \a device and parent it to the
+ QHttp2Stream.
+*/
+void QHttp2Stream::sendDATA(QNonContiguousByteDevice *device, bool endStream)
+{
+ Q_ASSERT(!m_uploadByteDevice);
+ Q_ASSERT(device);
+ if (m_state != State::Open && m_state != State::HalfClosedRemote)
+ return;
+
+ qCDebug(qHttp2ConnectionLog, "[%p] starting sendDATA on stream %u, of device: %p",
+ getConnection(), m_streamID, device);
+
+ m_uploadByteDevice = device;
+ m_endStreamAfterDATA = endStream;
+ connect(m_uploadByteDevice, &QNonContiguousByteDevice::readyRead, this,
+ &QHttp2Stream::maybeResumeUpload);
+ connect(m_uploadByteDevice, &QObject::destroyed, this, &QHttp2Stream::uploadDeviceDestroyed);
+
+ internalSendDATA();
+}
+
+void QHttp2Stream::internalSendDATA()
+{
+ Q_ASSERT(m_uploadByteDevice);
+ QHttp2Connection *connection = getConnection();
+ Q_ASSERT(connection->maxFrameSize > frameHeaderSize);
+ QIODevice *socket = connection->getSocket();
+
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] stream %u, about to write to socket, current session window size: %d, stream "
+ "window size: %d, bytes available: %lld",
+ connection, m_streamID, connection->sessionSendWindowSize, m_sendWindow,
+ m_uploadByteDevice->size() - m_uploadByteDevice->pos());
+
+ qint32 remainingWindowSize = std::min<qint32>(connection->sessionSendWindowSize, m_sendWindow);
+ FrameWriter &frameWriter = connection->frameWriter;
+ qint64 totalBytesWritten = 0;
+ const auto deviceCanRead = [this, connection] {
+ // We take advantage of knowing the internals of one of the devices used.
+ // It will request X bytes to move over to the http thread if there's
+ // not enough left, so we give it a large size. It will anyway return
+ // the size it can actually provide.
+ const qint64 requestSize = connection->maxFrameSize * 10ll;
+ qint64 tmp = 0;
+ return m_uploadByteDevice->readPointer(requestSize, tmp) != nullptr && tmp > 0;
+ };
+
+ bool sentEND_STREAM = false;
+ while (remainingWindowSize && deviceCanRead()) {
+ quint32 bytesWritten = 0;
+ qint32 remainingBytesInFrame = qint32(connection->maxFrameSize);
+ frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, streamID());
+
+ while (remainingWindowSize && deviceCanRead() && remainingBytesInFrame) {
+ const qint32 maxToWrite = std::min(remainingWindowSize, remainingBytesInFrame);
+
+ qint64 outBytesAvail = 0;
+ const char *readPointer = m_uploadByteDevice->readPointer(maxToWrite, outBytesAvail);
+ if (!readPointer || outBytesAvail <= 0) {
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] stream %u, cannot write data, device (%p) has %lld bytes available",
+ connection, m_streamID, m_uploadByteDevice, outBytesAvail);
+ break;
+ }
+ const qint32 bytesToWrite = qint32(std::min<qint64>(maxToWrite, outBytesAvail));
+ frameWriter.append(QByteArrayView(readPointer, bytesToWrite));
+ m_uploadByteDevice->advanceReadPointer(bytesToWrite);
+
+ bytesWritten += bytesToWrite;
+
+ m_sendWindow -= bytesToWrite;
+ Q_ASSERT(m_sendWindow >= 0);
+ connection->sessionSendWindowSize -= bytesToWrite;
+ Q_ASSERT(connection->sessionSendWindowSize >= 0);
+ remainingBytesInFrame -= bytesToWrite;
+ Q_ASSERT(remainingBytesInFrame >= 0);
+ remainingWindowSize -= bytesToWrite;
+ Q_ASSERT(remainingWindowSize >= 0);
+ }
+
+ qCDebug(qHttp2ConnectionLog, "[%p] stream %u, writing %u bytes to socket", connection,
+ m_streamID, bytesWritten);
+ if (!deviceCanRead() && m_uploadByteDevice->atEnd() && m_endStreamAfterDATA) {
+ sentEND_STREAM = true;
+ frameWriter.addFlag(FrameFlag::END_STREAM);
+ }
+ if (!frameWriter.write(*socket)) {
+ qCDebug(qHttp2ConnectionLog, "[%p] stream %u, failed to write to socket", connection,
+ m_streamID);
+ finishWithError(QNetworkReply::ProtocolFailure, "failed to write to socket"_L1);
+ return;
+ }
+
+ totalBytesWritten += bytesWritten;
+ }
+
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] stream %u, wrote %lld bytes total, if the device is not exhausted, we'll write "
+ "more later. Remaining window size: %d",
+ connection, m_streamID, totalBytesWritten, remainingWindowSize);
+
+ emit bytesWritten(totalBytesWritten);
+ if (sentEND_STREAM || (!deviceCanRead() && m_uploadByteDevice->atEnd())) {
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] stream %u, exhausted device %p, sent END_STREAM? %d, %ssending end stream "
+ "after DATA",
+ connection, m_streamID, m_uploadByteDevice, sentEND_STREAM,
+ m_endStreamAfterDATA ? "" : "not ");
+ if (!sentEND_STREAM && m_endStreamAfterDATA) {
+ // We need to send an empty DATA frame with END_STREAM since we
+ // have exhausted the device, but we haven't sent END_STREAM yet.
+ // This can happen if we got a final readyRead to signify no more
+ // data available, but we hadn't sent the END_STREAM flag yet.
+ frameWriter.start(FrameType::DATA, FrameFlag::END_STREAM, streamID());
+ frameWriter.write(*socket);
+ }
+ finishSendDATA();
+ } else if (isUploadBlocked()) {
+ qCDebug(qHttp2ConnectionLog, "[%p] stream %u, upload blocked", connection, m_streamID);
+ emit uploadBlocked();
+ }
+}
+
+void QHttp2Stream::finishSendDATA()
+{
+ if (m_endStreamAfterDATA)
+ transitionState(StateTransition::CloseLocal);
+
+ disconnect(m_uploadByteDevice, nullptr, this, nullptr);
+ m_uploadDevice = nullptr;
+ m_uploadByteDevice = nullptr;
+ emit uploadFinished();
+}
+
+void QHttp2Stream::maybeResumeUpload()
+{
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] stream %u, maybeResumeUpload. Upload device: %p, bytes available: %lld, blocked? "
+ "%d",
+ getConnection(), m_streamID, m_uploadByteDevice,
+ !m_uploadByteDevice ? 0 : m_uploadByteDevice->size() - m_uploadByteDevice->pos(),
+ isUploadBlocked());
+ if (isUploadingDATA() && !isUploadBlocked())
+ internalSendDATA();
+}
+
+/*!
+ Returns \c true if the stream is currently unable to send more data because
+ the remote peer's receive window is full.
+*/
+bool QHttp2Stream::isUploadBlocked() const noexcept
+{
+ constexpr auto MinFrameSize = Http2::frameHeaderSize + 1; // 1 byte payload
+ return isUploadingDATA()
+ && (m_sendWindow <= MinFrameSize
+ || getConnection()->sessionSendWindowSize <= MinFrameSize);
+}
+
+void QHttp2Stream::uploadDeviceReadChannelFinished()
+{
+ maybeResumeUpload();
+}
+
+/*!
+ Sends a HEADERS frame with the given \a headers and \a priority.
+ If \a endStream is \c true, the END_STREAM flag will be set, and the stream
+ will be closed for future writes.
+ If the headers are too large, or the stream is not in the correct state,
+ this function will return \c false. Otherwise, it will return \c true.
+*/
+bool QHttp2Stream::sendHEADERS(const HPack::HttpHeader &headers, bool endStream, quint8 priority)
+{
+ using namespace HPack;
+ if (auto hs = header_size(headers);
+ !hs.first || hs.second > getConnection()->maxHeaderListSize()) {
+ return false;
+ }
+
+ transitionState(StateTransition::Open);
+
+ Q_ASSERT(m_state == State::Open || m_state == State::HalfClosedRemote);
+
+ QHttp2Connection *connection = getConnection();
+
+ qCDebug(qHttp2ConnectionLog, "[%p] stream %u, sending HEADERS frame with %u entries",
+ connection, streamID(), uint(headers.size()));
+
+ QIODevice *socket = connection->getSocket();
+ FrameWriter &frameWriter = connection->frameWriter;
+
+ frameWriter.start(FrameType::HEADERS, FrameFlag::PRIORITY | FrameFlag::END_HEADERS, streamID());
+ if (endStream)
+ frameWriter.addFlag(FrameFlag::END_STREAM);
+
+ frameWriter.append(quint32()); // No stream dependency in Qt.
+ frameWriter.append(priority);
+
+ // Compress in-place:
+ BitOStream outputStream(frameWriter.outboundFrame().buffer);
+ if (connection->m_connectionType == QHttp2Connection::Type::Client) {
+ if (!connection->encoder.encodeRequest(outputStream, headers))
+ return false;
+ } else {
+ if (!connection->encoder.encodeResponse(outputStream, headers))
+ return false;
+ }
+
+ bool result = frameWriter.writeHEADERS(*socket, connection->maxFrameSize);
+ if (endStream)
+ transitionState(StateTransition::CloseLocal);
+
+ return result;
+}
+
+/*!
+ Sends a WINDOW_UPDATE frame with the given \a delta.
+ This increases our receive window size for this stream, allowing the remote
+ peer to send more data.
+*/
+void QHttp2Stream::sendWINDOW_UPDATE(quint32 delta)
+{
+ QHttp2Connection *connection = getConnection();
+ m_recvWindow += qint32(delta);
+ connection->sendWINDOW_UPDATE(streamID(), delta);
+}
+
+void QHttp2Stream::uploadDeviceDestroyed()
+{
+ if (isUploadingDATA()) {
+ // We're in the middle of sending DATA frames, we need to abort
+ // the stream.
+ sendRST_STREAM(CANCEL);
+ emit uploadDeviceError("Upload device destroyed while uploading"_L1);
+ }
+ m_uploadDevice = nullptr;
+}
+
+void QHttp2Stream::setState(State newState)
+{
+ if (m_state == newState)
+ return;
+ qCDebug(qHttp2ConnectionLog, "[%p] stream %u, state changed from %d to %d", getConnection(),
+ streamID(), int(m_state), int(newState));
+ m_state = newState;
+ emit stateChanged(newState);
+}
+
+// Changes the state as appropriate given the current state and the transition.
+// Always call this before emitting any signals since the recipient might rely
+// on the new state!
+void QHttp2Stream::transitionState(StateTransition transition)
+{
+ switch (m_state) {
+ case State::Idle:
+ if (transition == StateTransition::Open)
+ setState(State::Open);
+ else
+ Q_UNREACHABLE(); // We should transition to Open before ever getting here
+ break;
+ case State::Open:
+ switch (transition) {
+ case StateTransition::CloseLocal:
+ setState(State::HalfClosedLocal);
+ break;
+ case StateTransition::CloseRemote:
+ setState(State::HalfClosedRemote);
+ break;
+ case StateTransition::RST:
+ setState(State::Closed);
+ break;
+ case StateTransition::Open: // no-op
+ break;
+ }
+ break;
+ case State::HalfClosedLocal:
+ if (transition == StateTransition::CloseRemote || transition == StateTransition::RST)
+ setState(State::Closed);
+ break;
+ case State::HalfClosedRemote:
+ if (transition == StateTransition::CloseLocal || transition == StateTransition::RST)
+ setState(State::Closed);
+ break;
+ case State::ReservedRemote:
+ if (transition == StateTransition::RST) {
+ setState(State::Closed);
+ } else if (transition == StateTransition::CloseLocal) { // Receiving HEADER closes local
+ setState(State::HalfClosedLocal);
+ }
+ break;
+ case State::Closed:
+ break;
+ }
+}
+
+void QHttp2Stream::handleDATA(const Frame &inboundFrame)
+{
+ QHttp2Connection *connection = getConnection();
+
+ qCDebug(qHttp2ConnectionLog, "[%p] stream %u, received DATA frame with payload of %u bytes",
+ connection, m_streamID, inboundFrame.payloadSize());
+
+ if (qint32(inboundFrame.payloadSize()) > m_recvWindow) {
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] stream %u, received DATA frame with payload size %u, "
+ "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
+ connection, m_streamID, inboundFrame.payloadSize(), m_recvWindow);
+ finishWithError(QNetworkReply::ProtocolFailure, "flow control error"_L1);
+ sendRST_STREAM(FLOW_CONTROL_ERROR);
+ return;
+ }
+ m_recvWindow -= qint32(inboundFrame.payloadSize());
+ const bool endStream = inboundFrame.flags().testFlag(FrameFlag::END_STREAM);
+ // Uncompress data if needed and append it ...
+ if (inboundFrame.dataSize() > 0 || endStream) {
+ QByteArray fragment(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
+ inboundFrame.dataSize());
+ if (endStream)
+ transitionState(StateTransition::CloseRemote);
+ emit dataReceived(fragment, endStream);
+ m_downloadBuffer.append(std::move(fragment));
+ }
+
+ if (!endStream && m_recvWindow < connection->streamInitialReceiveWindowSize / 2) {
+ // @future[consider]: emit signal instead
+ sendWINDOW_UPDATE(quint32(connection->streamInitialReceiveWindowSize - m_recvWindow));
+ }
+}
+
+void QHttp2Stream::handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers)
+{
+ if (m_state == State::Idle)
+ transitionState(StateTransition::Open);
+ const bool endStream = frameFlags.testFlag(FrameFlag::END_STREAM);
+ if (endStream)
+ transitionState(StateTransition::CloseRemote);
+ if (!headers.empty()) {
+ m_headers.insert(m_headers.end(), headers.begin(), headers.end());
+ emit headersUpdated();
+ }
+ emit headersReceived(headers, endStream);
+}
+
+void QHttp2Stream::handleRST_STREAM(const Frame &inboundFrame)
+{
+ transitionState(StateTransition::RST);
+ m_RST_STREAM_code = qFromBigEndian<quint32>(inboundFrame.dataBegin());
+ if (isUploadingDATA()) {
+ disconnect(m_uploadByteDevice, nullptr, this, nullptr);
+ m_uploadDevice = nullptr;
+ m_uploadByteDevice = nullptr;
+ }
+ finishWithError(*m_RST_STREAM_code, ""_L1);
+}
+
+void QHttp2Stream::handleWINDOW_UPDATE(const Frame &inboundFrame)
+{
+ const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
+ const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
+ qint32 sum = 0;
+ if (!valid || qAddOverflow(m_sendWindow, qint32(delta), &sum)) {
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] stream %u, received WINDOW_UPDATE frame with invalid delta %u, sending "
+ "PROTOCOL_ERROR",
+ getConnection(), m_streamID, delta);
+ finishWithError(QNetworkReply::ProtocolFailure, "invalid WINDOW_UPDATE delta"_L1);
+ sendRST_STREAM(PROTOCOL_ERROR);
+ return;
+ }
+ m_sendWindow = sum;
+ // Stream may have been unblocked, so maybe try to write again
+ if (isUploadingDATA())
+ maybeResumeUpload();
+}
+
+/*!
+ \class QHttp2Connection
+ \inmodule QtNetwork
+ \internal
+
+ The QHttp2Connection class represents a HTTP/2 connection.
+ It can only be created through the static functions
+ createDirectConnection(), createUpgradedConnection(),
+ and createDirectServerConnection().
+
+ createDirectServerConnection() is used for server-side connections, and has
+ certain limitations that a client does not.
+
+ As a client you can create a QHttp2Stream with createStream().
+
+ \sa QHttp2Stream
+*/
+
+/*!
+ \fn void QHttp2Connection::newIncomingStream(QHttp2Stream *stream)
+
+ This signal is emitted when a new \a stream is received from the remote
+ peer.
+*/
+
+/*!
+ \fn void QHttp2Connection::newPromisedStream(QHttp2Stream *stream)
+
+ This signal is emitted when the remote peer has promised a new \a stream.
+*/
+
+/*!
+ \fn void QHttp2Connection::errorReceived()
+
+ This signal is emitted when the connection has received an error.
+*/
+
+/*!
+ \fn void QHttp2Connection::connectionClosed()
+
+ This signal is emitted when the connection has been closed.
+*/
+
+/*!
+ \fn void QHttp2Connection::settingsFrameReceived()
+
+ This signal is emitted when the connection has received a SETTINGS frame.
+*/
+
+/*!
+ \fn void QHttp2Connection::errorOccurred(Http2::Http2Error errorCode, const QString &errorString)
+
+ This signal is emitted when the connection has encountered an error. The
+ \a errorCode parameter is the HTTP/2 error code, and the \a errorString
+ parameter is a human-readable description of the error.
+*/
+
+/*!
+ \fn void QHttp2Connection::receivedGOAWAY(quint32 errorCode, quint32 lastStreamID)
+
+ This signal is emitted when the connection has received a GOAWAY frame. The
+ \a errorCode parameter is the HTTP/2 error code, and the \a lastStreamID
+ parameter is the last stream ID that the remote peer will process.
+
+ Any streams of a higher stream ID created by us will be ignored or reset.
+*/
+
+/*!
+ Create a new HTTP2 connection given a \a config and a \a socket.
+ This function assumes that the Upgrade headers etc. in http/1 have already
+ been sent and that the connection is already upgraded to http/2.
+
+ The object returned will be a child to the \a socket, or null on failure.
+*/
+QHttp2Connection *QHttp2Connection::createUpgradedConnection(QIODevice *socket,
+ const QHttp2Configuration &config)
+{
+ Q_ASSERT(socket);
+
+ auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
+ connection->setH2Configuration(config);
+ connection->m_connectionType = QHttp2Connection::Type::Client;
+ // HTTP2 connection is already established and request was sent, so stream 1
+ // is already 'active' and is closed for any further outgoing data.
+ QHttp2Stream *stream = connection->createStreamInternal().unwrap();
+ Q_ASSERT(stream->streamID() == 1);
+ stream->setState(QHttp2Stream::State::HalfClosedLocal);
+ connection->m_upgradedConnection = true;
+
+ if (!connection->sendClientPreface()) {
+ qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", connection.get());
+ return nullptr;
+ }
+
+ return connection.release();
+}
+
+/*!
+ Create a new HTTP2 connection given a \a config and a \a socket.
+ This function will immediately send the client preface.
+
+ The object returned will be a child to the \a socket, or null on failure.
+*/
+QHttp2Connection *QHttp2Connection::createDirectConnection(QIODevice *socket,
+ const QHttp2Configuration &config)
+{
+ auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
+ connection->setH2Configuration(config);
+ connection->m_connectionType = QHttp2Connection::Type::Client;
+
+ if (!connection->sendClientPreface()) {
+ qCWarning(qHttp2ConnectionLog, "[%p] Failed to send client preface", connection.get());
+ return nullptr;
+ }
+
+ return connection.release();
+}
+
+/*!
+ Create a new HTTP2 connection given a \a config and a \a socket.
+
+ The object returned will be a child to the \a socket, or null on failure.
+*/
+QHttp2Connection *QHttp2Connection::createDirectServerConnection(QIODevice *socket,
+ const QHttp2Configuration &config)
+{
+ auto connection = std::unique_ptr<QHttp2Connection>(new QHttp2Connection(socket));
+ connection->setH2Configuration(config);
+ connection->m_connectionType = QHttp2Connection::Type::Server;
+
+ connection->m_nextStreamID = 2; // server-initiated streams must be even
+
+ connection->m_waitingForClientPreface = true;
+
+ return connection.release();
+}
+
+/*!
+ Creates a stream on this connection.
+
+ Automatically picks the next available stream ID and returns a pointer to
+ the new stream, if possible. Otherwise returns an error.
+
+ \sa QHttp2Connection::CreateStreamError, QHttp2Stream
+*/
+QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> QHttp2Connection::createStream()
+{
+ Q_ASSERT(m_connectionType == Type::Client); // This overload is just for clients
+ if (m_nextStreamID > lastValidStreamID)
+ return { QHttp2Connection::CreateStreamError::StreamIdsExhausted };
+ return createStreamInternal();
+}
+
+QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError>
+QHttp2Connection::createStreamInternal()
+{
+ if (m_goingAway)
+ return { QHttp2Connection::CreateStreamError::ReceivedGOAWAY };
+ const quint32 streamID = m_nextStreamID;
+ if (size_t(m_maxConcurrentStreams) <= size_t(numActiveLocalStreams()))
+ return { QHttp2Connection::CreateStreamError::MaxConcurrentStreamsReached };
+ m_nextStreamID += 2;
+ return { createStreamInternal_impl(streamID) };
+}
+
+QHttp2Stream *QHttp2Connection::createStreamInternal_impl(quint32 streamID)
+{
+ qsizetype numStreams = m_streams.size();
+ QPointer<QHttp2Stream> &stream = m_streams[streamID];
+ if (numStreams == m_streams.size()) // stream already existed
+ return nullptr;
+ stream = new QHttp2Stream(this, streamID);
+ stream->m_recvWindow = streamInitialReceiveWindowSize;
+ stream->m_sendWindow = streamInitialSendWindowSize;
+ return stream;
+}
+
+qsizetype QHttp2Connection::numActiveStreamsImpl(quint32 mask) const noexcept
+{
+ const auto shouldCount = [mask](const QPointer<QHttp2Stream> &stream) -> bool {
+ return stream && (stream->streamID() & 1) == mask;
+ };
+ return std::count_if(m_streams.cbegin(), m_streams.cend(), shouldCount);
+}
+
+/*!
+ \internal
+ The number of streams the remote peer has started that are still active.
+*/
+qsizetype QHttp2Connection::numActiveRemoteStreams() const noexcept
+{
+ const quint32 RemoteMask = m_connectionType == Type::Client ? 0 : 1;
+ return numActiveStreamsImpl(RemoteMask);
+}
+
+/*!
+ \internal
+ The number of streams we have started that are still active.
+*/
+qsizetype QHttp2Connection::numActiveLocalStreams() const noexcept
+{
+ const quint32 LocalMask = m_connectionType == Type::Client ? 1 : 0;
+ return numActiveStreamsImpl(LocalMask);
+}
+
+/*!
+ Return a pointer to a stream with the given \a streamID, or null if no such
+ stream exists or it was deleted.
+*/
+QHttp2Stream *QHttp2Connection::getStream(quint32 streamID) const
+{
+ return m_streams.value(streamID, nullptr).get();
+}
+
+
+/*!
+ \fn QHttp2Stream *QHttp2Connection::promisedStream(const QUrl &streamKey) const
+
+ Returns a pointer to the stream that was promised with the given
+ \a streamKey, if any. Otherwise, returns null.
+*/
+
+/*!
+ \fn void QHttp2Connection::close()
+
+ This sends a GOAWAY frame on the connection stream, gracefully closing the
+ connection.
+*/
+
+/*!
+ \fn bool QHttp2Connection::isGoingAway() const noexcept
+
+ Returns \c true if the connection is in the process of being closed, or
+ \c false otherwise.
+*/
+
+/*!
+ \fn quint32 QHttp2Connection::maxConcurrentStreams() const noexcept
+
+ Returns the maximum number of concurrent streams we are allowed to have
+ active at any given time. This is a directional setting, and the remote
+ peer may have a different value.
+*/
+
+/*!
+ \fn quint32 QHttp2Connection::maxHeaderListSize() const noexcept
+
+ Returns the maximum size of the header which the peer is willing to accept.
+*/
+
+/*!
+ \fn bool QHttp2Connection::isUpgradedConnection() const noexcept
+
+ Returns \c true if this connection was created as a result of an HTTP/1
+ upgrade to HTTP/2, or \c false otherwise.
+*/
+
+QHttp2Connection::QHttp2Connection(QIODevice *socket) : QObject(socket)
+{
+ Q_ASSERT(socket);
+ Q_ASSERT(socket->isOpen());
+ Q_ASSERT(socket->openMode() & QIODevice::ReadWrite);
+ // We don't make any connections directly because this is used in
+ // in the http2 protocol handler, which is used by
+ // QHttpNetworkConnectionChannel. Which in turn owns and deals with all the
+ // socket connections.
+}
+
+QHttp2Connection::~QHttp2Connection() noexcept
+{
+ // delete streams now so that any calls it might make back to this
+ // Connection will operate on a valid object.
+ for (QPointer<QHttp2Stream> &stream : std::exchange(m_streams, {}))
+ delete stream.get();
+}
+
+bool QHttp2Connection::serverCheckClientPreface()
+{
+ if (!m_waitingForClientPreface)
+ return true;
+ auto *socket = getSocket();
+ if (socket->bytesAvailable() < Http2::clientPrefaceLength)
+ return false;
+ if (!readClientPreface()) {
+ socket->close();
+ emit errorOccurred(Http2Error::PROTOCOL_ERROR, "invalid client preface"_L1);
+ qCDebug(qHttp2ConnectionLog, "[%p] Invalid client preface", this);
+ return false;
+ }
+ qCDebug(qHttp2ConnectionLog, "[%p] Peer sent valid client preface", this);
+ m_waitingForClientPreface = false;
+ if (!sendServerPreface()) {
+ connectionError(Http2::INTERNAL_ERROR, "Failed to send server preface");
+ return false;
+ }
+ return true;
+}
+
+bool QHttp2Connection::sendPing()
+{
+ std::array<char, 8> data;
+
+ QRandomGenerator gen;
+ gen.generate(data.begin(), data.end());
+ return sendPing(data);
+}
+
+bool QHttp2Connection::sendPing(QByteArrayView data)
+{
+ frameWriter.start(FrameType::PING, FrameFlag::EMPTY, connectionStreamID);
+
+ Q_ASSERT(data.length() == 8);
+ if (!m_lastPingSignature) {
+ m_lastPingSignature = data.toByteArray();
+ } else {
+ qCWarning(qHttp2ConnectionLog, "[%p] No PING is sent while waiting for the previous PING.", this);
+ return false;
+ }
+
+ frameWriter.append((uchar*)data.data(), (uchar*)data.end());
+ frameWriter.write(*getSocket());
+ return true;
+}
+
+/*!
+ This function must be called when you have received a readyRead signal
+ (or equivalent) from the QIODevice. It will read and process any incoming
+ HTTP/2 frames and emit signals as appropriate.
+*/
+void QHttp2Connection::handleReadyRead()
+{
+ /* event loop */
+ if (m_connectionType == Type::Server && !serverCheckClientPreface())
+ return;
+
+ const auto streamIsActive = [](const QPointer<QHttp2Stream> &stream) {
+ return stream && stream->isActive();
+ };
+ if (m_goingAway && std::none_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
+ close();
+ return;
+ }
+ QIODevice *socket = getSocket();
+
+ qCDebug(qHttp2ConnectionLog, "[%p] Receiving data, %lld bytes available", this,
+ socket->bytesAvailable());
+
+ using namespace Http2;
+ while (!m_goingAway || std::any_of(m_streams.cbegin(), m_streams.cend(), streamIsActive)) {
+ const auto result = frameReader.read(*socket);
+ if (result != FrameStatus::goodFrame)
+ qCDebug(qHttp2ConnectionLog, "[%p] Tried to read frame, got %d", this, int(result));
+ switch (result) {
+ case FrameStatus::incompleteFrame:
+ return;
+ case FrameStatus::protocolError:
+ return connectionError(PROTOCOL_ERROR, "invalid frame");
+ case FrameStatus::sizeError:
+ return connectionError(FRAME_SIZE_ERROR, "invalid frame size");
+ default:
+ break;
+ }
+
+ Q_ASSERT(result == FrameStatus::goodFrame);
+
+ inboundFrame = std::move(frameReader.inboundFrame());
+
+ const auto frameType = inboundFrame.type();
+ qCDebug(qHttp2ConnectionLog, "[%p] Successfully read a frame, with type: %d", this,
+ int(frameType));
+ if (continuationExpected && frameType != FrameType::CONTINUATION)
+ return connectionError(PROTOCOL_ERROR, "CONTINUATION expected");
+
+ switch (frameType) {
+ case FrameType::DATA:
+ handleDATA();
+ break;
+ case FrameType::HEADERS:
+ handleHEADERS();
+ break;
+ case FrameType::PRIORITY:
+ handlePRIORITY();
+ break;
+ case FrameType::RST_STREAM:
+ handleRST_STREAM();
+ break;
+ case FrameType::SETTINGS:
+ handleSETTINGS();
+ break;
+ case FrameType::PUSH_PROMISE:
+ handlePUSH_PROMISE();
+ break;
+ case FrameType::PING:
+ handlePING();
+ break;
+ case FrameType::GOAWAY:
+ handleGOAWAY();
+ break;
+ case FrameType::WINDOW_UPDATE:
+ handleWINDOW_UPDATE();
+ break;
+ case FrameType::CONTINUATION:
+ handleCONTINUATION();
+ break;
+ case FrameType::LAST_FRAME_TYPE:
+ // 5.1 - ignore unknown frames.
+ break;
+ }
+ }
+}
+
+bool QHttp2Connection::readClientPreface()
+{
+ auto *socket = getSocket();
+ Q_ASSERT(socket->bytesAvailable() >= Http2::clientPrefaceLength);
+ char buffer[Http2::clientPrefaceLength];
+ const qint64 read = socket->read(buffer, Http2::clientPrefaceLength);
+ if (read != Http2::clientPrefaceLength)
+ return false;
+ return memcmp(buffer, Http2::Http2clientPreface, Http2::clientPrefaceLength) == 0;
+}
+
+/*!
+ This function must be called when the socket has been disconnected, and will
+ end all remaining streams with an error.
+*/
+void QHttp2Connection::handleConnectionClosure()
+{
+ const auto errorString = QCoreApplication::translate("QHttp", "Connection closed");
+ for (auto it = m_streams.begin(), end = m_streams.end(); it != end; ++it) {
+ auto stream = it.value();
+ if (stream && stream->isActive())
+ stream->finishWithError(QNetworkReply::RemoteHostClosedError, errorString);
+ }
+}
+
+void QHttp2Connection::setH2Configuration(QHttp2Configuration config)
+{
+ m_config = std::move(config);
+
+ // These values comes from our own API so trust it to be sane.
+ maxSessionReceiveWindowSize = qint32(m_config.sessionReceiveWindowSize());
+ pushPromiseEnabled = m_config.serverPushEnabled();
+ streamInitialReceiveWindowSize = qint32(m_config.streamReceiveWindowSize());
+ encoder.setCompressStrings(m_config.huffmanCompressionEnabled());
+}
+
+void QHttp2Connection::connectionError(Http2Error errorCode, const char *message)
+{
+ Q_ASSERT(message);
+ if (m_goingAway)
+ return;
+
+ qCCritical(qHttp2ConnectionLog, "[%p] Connection error: %s (%d)", this, message,
+ int(errorCode));
+
+ m_goingAway = true;
+ sendGOAWAY(errorCode);
+ const auto error = qt_error(errorCode);
+ auto messageView = QLatin1StringView(message);
+
+ for (QHttp2Stream *stream : std::as_const(m_streams)) {
+ if (stream && stream->isActive())
+ stream->finishWithError(error, messageView);
+ }
+
+ closeSession();
+}
+
+void QHttp2Connection::closeSession()
+{
+ emit connectionClosed();
+}
+
+bool QHttp2Connection::streamWasReset(quint32 streamID) noexcept
+{
+ return m_resetStreamIDs.contains(streamID);
+}
+
+bool QHttp2Connection::isInvalidStream(quint32 streamID) noexcept
+{
+ auto stream = m_streams.value(streamID, nullptr);
+ return !stream && !streamWasReset(streamID);
+}
+
+bool QHttp2Connection::sendClientPreface()
+{
+ QIODevice *socket = getSocket();
+ // 3.5 HTTP/2 Connection Preface
+ const qint64 written = socket->write(Http2clientPreface, clientPrefaceLength);
+ if (written != clientPrefaceLength)
+ return false;
+
+ if (!sendSETTINGS()) {
+ qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
+ return false;
+ }
+ return true;
+}
+
+bool QHttp2Connection::sendServerPreface()
+{
+ // We send our SETTINGS frame and ACK the client's SETTINGS frame when it
+ // arrives.
+ if (!sendSETTINGS()) {
+ qCWarning(qHttp2ConnectionLog, "[%p] Failed to send SETTINGS", this);
+ return false;
+ }
+ return true;
+}
+
+bool QHttp2Connection::sendSETTINGS()
+{
+ QIODevice *socket = getSocket();
+ // 6.5 SETTINGS
+ frameWriter.setOutboundFrame(configurationToSettingsFrame(m_config));
+ qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS frame, %d bytes", this,
+ frameWriter.outboundFrame().payloadSize());
+ Q_ASSERT(frameWriter.outboundFrame().payloadSize());
+
+ if (!frameWriter.write(*socket))
+ return false;
+
+ sessionReceiveWindowSize = maxSessionReceiveWindowSize;
+ // We only send WINDOW_UPDATE for the connection if the size differs from the
+ // default 64 KB:
+ const auto delta = maxSessionReceiveWindowSize - defaultSessionWindowSize;
+ if (delta && !sendWINDOW_UPDATE(connectionStreamID, delta))
+ return false;
+
+ waitingForSettingsACK = true;
+ return true;
+}
+
+bool QHttp2Connection::sendWINDOW_UPDATE(quint32 streamID, quint32 delta)
+{
+ qCDebug(qHttp2ConnectionLog, "[%p] Sending WINDOW_UPDATE frame, stream %d, delta %u", this,
+ streamID, delta);
+ frameWriter.start(FrameType::WINDOW_UPDATE, FrameFlag::EMPTY, streamID);
+ frameWriter.append(delta);
+ return frameWriter.write(*getSocket());
+}
+
+bool QHttp2Connection::sendGOAWAY(quint32 errorCode)
+{
+ frameWriter.start(FrameType::GOAWAY, FrameFlag::EMPTY,
+ Http2PredefinedParameters::connectionStreamID);
+ frameWriter.append(quint32(m_lastIncomingStreamID));
+ frameWriter.append(errorCode);
+ return frameWriter.write(*getSocket());
+}
+
+bool QHttp2Connection::sendSETTINGS_ACK()
+{
+ frameWriter.start(FrameType::SETTINGS, FrameFlag::ACK, Http2::connectionStreamID);
+ return frameWriter.write(*getSocket());
+}
+
+void QHttp2Connection::handleDATA()
+{
+ Q_ASSERT(inboundFrame.type() == FrameType::DATA);
+
+ const auto streamID = inboundFrame.streamID();
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "DATA on the connection stream");
+
+ if (isInvalidStream(streamID))
+ return connectionError(ENHANCE_YOUR_CALM, "DATA on invalid stream");
+
+ if (qint32(inboundFrame.payloadSize()) > sessionReceiveWindowSize) {
+ qCDebug(qHttp2ConnectionLog,
+ "[%p] Received DATA frame with payload size %u, "
+ "but recvWindow is %d, sending FLOW_CONTROL_ERROR",
+ this, inboundFrame.payloadSize(), sessionReceiveWindowSize);
+ return connectionError(FLOW_CONTROL_ERROR, "Flow control error");
+ }
+
+ sessionReceiveWindowSize -= inboundFrame.payloadSize();
+
+ auto it = m_streams.constFind(streamID);
+ if (it != m_streams.cend() && it.value())
+ it.value()->handleDATA(inboundFrame);
+
+ if (sessionReceiveWindowSize < maxSessionReceiveWindowSize / 2) {
+ // @future[consider]: emit signal instead
+ QMetaObject::invokeMethod(this, &QHttp2Connection::sendWINDOW_UPDATE, Qt::QueuedConnection,
+ quint32(connectionStreamID),
+ quint32(maxSessionReceiveWindowSize - sessionReceiveWindowSize));
+ sessionReceiveWindowSize = maxSessionReceiveWindowSize;
+ }
+}
+
+void QHttp2Connection::handleHEADERS()
+{
+ Q_ASSERT(inboundFrame.type() == FrameType::HEADERS);
+
+ const auto streamID = inboundFrame.streamID();
+ qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS frame on stream %d", this, streamID);
+
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "HEADERS on 0x0 stream");
+
+ const bool isClient = m_connectionType == Type::Client;
+ const bool isClientInitiatedStream = !!(streamID & 1);
+ const bool isRemotelyInitiatedStream = isClient ^ isClientInitiatedStream;
+
+ if (isRemotelyInitiatedStream && streamID > m_lastIncomingStreamID) {
+ QHttp2Stream *newStream = createStreamInternal_impl(streamID);
+ Q_ASSERT(newStream);
+ m_lastIncomingStreamID = streamID;
+ qCDebug(qHttp2ConnectionLog, "[%p] Created new incoming stream %d", this, streamID);
+ emit newIncomingStream(newStream);
+ } else if (auto it = m_streams.constFind(streamID); it == m_streams.cend()) {
+ qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on non-existent stream %d", this,
+ streamID);
+ return connectionError(PROTOCOL_ERROR, "HEADERS on invalid stream");
+ } else if (!*it || (*it)->wasReset()) {
+ qCDebug(qHttp2ConnectionLog, "[%p] Received HEADERS on reset stream %d", this, streamID);
+ return connectionError(ENHANCE_YOUR_CALM, "HEADERS on invalid stream");
+ }
+
+ const auto flags = inboundFrame.flags();
+ if (flags.testFlag(FrameFlag::PRIORITY)) {
+ qCDebug(qHttp2ConnectionLog, "[%p] HEADERS frame on stream %d has PRIORITY flag", this,
+ streamID);
+ handlePRIORITY();
+ if (m_goingAway)
+ return;
+ }
+
+ const bool endHeaders = flags.testFlag(FrameFlag::END_HEADERS);
+ continuedFrames.clear();
+ continuedFrames.push_back(std::move(inboundFrame));
+ if (!endHeaders) {
+ continuationExpected = true;
+ return;
+ }
+
+ handleContinuedHEADERS();
+}
+
+void QHttp2Connection::handlePRIORITY()
+{
+ Q_ASSERT(inboundFrame.type() == FrameType::PRIORITY
+ || inboundFrame.type() == FrameType::HEADERS);
+
+ const auto streamID = inboundFrame.streamID();
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "PRIORITY on 0x0 stream");
+
+ if (isInvalidStream(streamID))
+ return connectionError(ENHANCE_YOUR_CALM, "PRIORITY on invalid stream");
+
+ quint32 streamDependency = 0;
+ uchar weight = 0;
+ const bool noErr = inboundFrame.priority(&streamDependency, &weight);
+ Q_UNUSED(noErr);
+ Q_ASSERT(noErr);
+
+ const bool exclusive = streamDependency & 0x80000000;
+ streamDependency &= ~0x80000000;
+
+ // Ignore this for now ...
+ // Can be used for streams (re)prioritization - 5.3
+ Q_UNUSED(exclusive);
+ Q_UNUSED(weight);
+}
+
+void QHttp2Connection::handleRST_STREAM()
+{
+ Q_ASSERT(inboundFrame.type() == FrameType::RST_STREAM);
+
+ // "RST_STREAM frames MUST be associated with a stream.
+ // If a RST_STREAM frame is received with a stream identifier of 0x0,
+ // the recipient MUST treat this as a connection error (Section 5.4.1)
+ // of type PROTOCOL_ERROR.
+ const auto streamID = inboundFrame.streamID();
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "RST_STREAM on 0x0");
+
+ if (!(streamID & 0x1)) { // @future[server]: must be updated for server-side handling
+ // RST_STREAM on a promised stream:
+ // since we do not keep track of such streams,
+ // just ignore.
+ return;
+ }
+
+ // Anything greater than m_nextStreamID has not been started yet.
+ if (streamID >= m_nextStreamID) {
+ // "RST_STREAM frames MUST NOT be sent for a stream
+ // in the "idle" state. .. the recipient MUST treat this
+ // as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
+ return connectionError(PROTOCOL_ERROR, "RST_STREAM on idle stream");
+ }
+
+ Q_ASSERT(inboundFrame.dataSize() == 4);
+
+ if (QPointer<QHttp2Stream> stream = m_streams[streamID])
+ stream->handleRST_STREAM(inboundFrame);
+}
+
+void QHttp2Connection::handleSETTINGS()
+{
+ // 6.5 SETTINGS.
+ Q_ASSERT(inboundFrame.type() == FrameType::SETTINGS);
+
+ if (inboundFrame.streamID() != connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "SETTINGS on invalid stream");
+
+ if (inboundFrame.flags().testFlag(FrameFlag::ACK)) {
+ if (!waitingForSettingsACK)
+ return connectionError(PROTOCOL_ERROR, "unexpected SETTINGS ACK");
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ACK", this);
+ waitingForSettingsACK = false;
+ return;
+ }
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS frame", this);
+
+ if (inboundFrame.dataSize()) {
+ auto src = inboundFrame.dataBegin();
+ for (const uchar *end = src + inboundFrame.dataSize(); src != end; src += 6) {
+ const Settings identifier = Settings(qFromBigEndian<quint16>(src));
+ const quint32 intVal = qFromBigEndian<quint32>(src + 2);
+ if (!acceptSetting(identifier, intVal)) {
+ // If not accepted - we finish with connectionError.
+ qCDebug(qHttp2ConnectionLog, "[%p] Received an unacceptable setting, %u, %u", this,
+ quint32(identifier), intVal);
+ return; // connectionError already called in acceptSetting.
+ }
+ }
+ }
+
+ qCDebug(qHttp2ConnectionLog, "[%p] Sending SETTINGS ACK", this);
+ emit settingsFrameReceived();
+ sendSETTINGS_ACK();
+}
+
+void QHttp2Connection::handlePUSH_PROMISE()
+{
+ // 6.6 PUSH_PROMISE.
+ Q_ASSERT(inboundFrame.type() == FrameType::PUSH_PROMISE);
+
+ if (!pushPromiseEnabled && !waitingForSettingsACK) {
+ // This means, server ACKed our 'NO PUSH',
+ // but sent us PUSH_PROMISE anyway.
+ return connectionError(PROTOCOL_ERROR, "unexpected PUSH_PROMISE frame");
+ }
+
+ const auto streamID = inboundFrame.streamID();
+ if (streamID == connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid associated stream (0x0)");
+
+ auto it = m_streams.constFind(streamID);
+#if 0 // Needs to be done after some timeout in case the stream has only just been reset
+ if (it != m_streams.constEnd()) {
+ QHttp2Stream *associatedStream = it->get();
+ if (associatedStream->state() != QHttp2Stream::State::Open
+ && associatedStream->state() != QHttp2Stream::State::HalfClosedLocal) {
+ // Cause us to error out below:
+ it = m_streams.constEnd();
+ }
+ }
+#endif
+ if (it == m_streams.constEnd())
+ return connectionError(ENHANCE_YOUR_CALM, "PUSH_PROMISE with invalid associated stream");
+
+ const auto reservedID = qFromBigEndian<quint32>(inboundFrame.dataBegin());
+ if ((reservedID & 1) || reservedID <= m_lastIncomingStreamID || reservedID > lastValidStreamID)
+ return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with invalid promised stream ID");
+
+ auto *stream = createStreamInternal_impl(reservedID);
+ if (!stream)
+ return connectionError(PROTOCOL_ERROR, "PUSH_PROMISE with already active stream ID");
+ m_lastIncomingStreamID = reservedID;
+ stream->setState(QHttp2Stream::State::ReservedRemote);
+
+ if (!pushPromiseEnabled) {
+ // "ignoring a PUSH_PROMISE frame causes the stream state to become
+ // indeterminate" - let's send RST_STREAM frame with REFUSE_STREAM code.
+ stream->sendRST_STREAM(REFUSE_STREAM);
+ }
+
+ const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
+ continuedFrames.clear();
+ continuedFrames.push_back(std::move(inboundFrame));
+
+ if (!endHeaders) {
+ continuationExpected = true;
+ return;
+ }
+
+ handleContinuedHEADERS();
+}
+
+void QHttp2Connection::handlePING()
+{
+ Q_ASSERT(inboundFrame.type() == FrameType::PING);
+ Q_ASSERT(inboundFrame.dataSize() == 8);
+
+ if (inboundFrame.streamID() != connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "PING on invalid stream");
+
+ if (inboundFrame.flags() & FrameFlag::ACK) {
+ QByteArrayView pingSignature(reinterpret_cast<const char *>(inboundFrame.dataBegin()), 8);
+ if (!m_lastPingSignature.has_value()) {
+ emit pingFrameRecived(PingState::PongNoPingSent);
+ qCWarning(qHttp2ConnectionLog, "[%p] PING with ACK received but no PING was sent.", this);
+ } else if (pingSignature != m_lastPingSignature) {
+ emit pingFrameRecived(PingState::PongSignatureChanged);
+ qCWarning(qHttp2ConnectionLog, "[%p] PING signature does not match the last PING.", this);
+ } else {
+ emit pingFrameRecived(PingState::PongSignatureIdentical);
+ }
+ m_lastPingSignature.reset();
+ return;
+ } else {
+ emit pingFrameRecived(PingState::Ping);
+
+ }
+
+
+ frameWriter.start(FrameType::PING, FrameFlag::ACK, connectionStreamID);
+ frameWriter.append(inboundFrame.dataBegin(), inboundFrame.dataBegin() + 8);
+ frameWriter.write(*getSocket());
+}
+
+void QHttp2Connection::handleGOAWAY()
+{
+ // 6.8 GOAWAY
+
+ Q_ASSERT(inboundFrame.type() == FrameType::GOAWAY);
+ // "An endpoint MUST treat a GOAWAY frame with a stream identifier
+ // other than 0x0 as a connection error (Section 5.4.1) of type PROTOCOL_ERROR."
+ if (inboundFrame.streamID() != connectionStreamID)
+ return connectionError(PROTOCOL_ERROR, "GOAWAY on invalid stream");
+
+ const uchar *const src = inboundFrame.dataBegin();
+ quint32 lastStreamID = qFromBigEndian<quint32>(src);
+ const quint32 errorCode = qFromBigEndian<quint32>(src + 4);
+
+ if (!lastStreamID) {
+ // "The last stream identifier can be set to 0 if no
+ // streams were processed."
+ lastStreamID = 1;
+ } else if (!(lastStreamID & 0x1)) {
+ // 5.1.1 - we (client) use only odd numbers as stream identifiers.
+ return connectionError(PROTOCOL_ERROR, "GOAWAY with invalid last stream ID");
+ } else if (lastStreamID >= m_nextStreamID) {
+ // "A server that is attempting to gracefully shut down a connection SHOULD
+ // send an initial GOAWAY frame with the last stream identifier set to 2^31-1
+ // and a NO_ERROR code."
+ if (lastStreamID != lastValidStreamID || errorCode != HTTP2_NO_ERROR)
+ return connectionError(PROTOCOL_ERROR, "GOAWAY invalid stream/error code");
+ } else {
+ lastStreamID += 2;
+ }
+
+ m_goingAway = true;
+
+ emit receivedGOAWAY(errorCode, lastStreamID);
+
+ for (quint32 id = lastStreamID; id < m_nextStreamID; id += 2) {
+ QHttp2Stream *stream = m_streams.value(id, nullptr);
+ if (stream && stream->isActive())
+ stream->finishWithError(errorCode, "Received GOAWAY"_L1);
+ }
+
+ const auto isActive = [](const QHttp2Stream *stream) { return stream && stream->isActive(); };
+ if (std::none_of(m_streams.cbegin(), m_streams.cend(), isActive))
+ closeSession();
+}
+
+void QHttp2Connection::handleWINDOW_UPDATE()
+{
+ Q_ASSERT(inboundFrame.type() == FrameType::WINDOW_UPDATE);
+
+ const quint32 delta = qFromBigEndian<quint32>(inboundFrame.dataBegin());
+ const bool valid = delta && delta <= quint32(std::numeric_limits<qint32>::max());
+ const auto streamID = inboundFrame.streamID();
+
+ qCDebug(qHttp2ConnectionLog(), "[%p] Received WINDOW_UPDATE, stream %d, delta %d", this,
+ streamID, delta);
+ if (streamID == connectionStreamID) {
+ qint32 sum = 0;
+ if (!valid || qAddOverflow(sessionSendWindowSize, qint32(delta), &sum))
+ return connectionError(PROTOCOL_ERROR, "WINDOW_UPDATE invalid delta");
+ sessionSendWindowSize = sum;
+ for (auto &stream : m_streams) {
+ if (!stream || !stream->isActive())
+ continue;
+ // Stream may have been unblocked, so maybe try to write again
+ if (stream->isUploadingDATA() && !stream->isUploadBlocked())
+ QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
+ Qt::QueuedConnection);
+ }
+ } else {
+ QHttp2Stream *stream = m_streams.value(streamID);
+ if (!stream || !stream->isActive()) {
+ // WINDOW_UPDATE on closed streams can be ignored.
+ qCDebug(qHttp2ConnectionLog, "[%p] Received WINDOW_UPDATE on closed stream %d", this,
+ streamID);
+ return;
+ }
+ stream->handleWINDOW_UPDATE(inboundFrame);
+ }
+}
+
+void QHttp2Connection::handleCONTINUATION()
+{
+ Q_ASSERT(inboundFrame.type() == FrameType::CONTINUATION);
+ if (continuedFrames.empty())
+ return connectionError(PROTOCOL_ERROR,
+ "CONTINUATION without a preceding HEADERS or PUSH_PROMISE");
+
+ if (inboundFrame.streamID() != continuedFrames.front().streamID())
+ return connectionError(PROTOCOL_ERROR, "CONTINUATION on invalid stream");
+
+ const bool endHeaders = inboundFrame.flags().testFlag(FrameFlag::END_HEADERS);
+ continuedFrames.push_back(std::move(inboundFrame));
+
+ if (!endHeaders)
+ return;
+
+ continuationExpected = false;
+ handleContinuedHEADERS();
+}
+
+void QHttp2Connection::handleContinuedHEADERS()
+{
+ // 'Continued' HEADERS can be: the initial HEADERS/PUSH_PROMISE frame
+ // with/without END_HEADERS flag set plus, if no END_HEADERS flag,
+ // a sequence of one or more CONTINUATION frames.
+ Q_ASSERT(!continuedFrames.empty());
+ const auto firstFrameType = continuedFrames[0].type();
+ Q_ASSERT(firstFrameType == FrameType::HEADERS || firstFrameType == FrameType::PUSH_PROMISE);
+
+ const auto streamID = continuedFrames[0].streamID();
+
+ const auto streamIt = m_streams.constFind(streamID);
+ if (firstFrameType == FrameType::HEADERS) {
+ if (streamIt != m_streams.cend()) {
+ QHttp2Stream *stream = streamIt.value();
+ if (stream->state() != QHttp2Stream::State::HalfClosedLocal
+ && stream->state() != QHttp2Stream::State::ReservedRemote
+ && stream->state() != QHttp2Stream::State::Idle
+ && stream->state() != QHttp2Stream::State::Open) {
+ // We can receive HEADERS on streams initiated by our requests
+ // (these streams are in halfClosedLocal or open state) or
+ // remote-reserved streams from a server's PUSH_PROMISE.
+ stream->finishWithError(QNetworkReply::ProtocolFailure,
+ "HEADERS on invalid stream"_L1);
+ stream->sendRST_STREAM(CANCEL);
+ return;
+ }
+ }
+ // Else: we cannot just ignore our peer's HEADERS frames - they change
+ // HPACK context - even though the stream was reset; apparently the peer
+ // has yet to see the reset.
+ }
+
+ std::vector<uchar> hpackBlock(assemble_hpack_block(continuedFrames));
+ const bool hasHeaderFields = !hpackBlock.empty();
+ if (hasHeaderFields) {
+ HPack::BitIStream inputStream{ hpackBlock.data(), hpackBlock.data() + hpackBlock.size() };
+ if (!decoder.decodeHeaderFields(inputStream))
+ return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
+ } else {
+ if (firstFrameType == FrameType::PUSH_PROMISE) {
+ // It could be a PRIORITY sent in HEADERS - already handled by this
+ // point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
+ // "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
+ // frames MUST be a valid and complete set of request header fields
+ // (Section 8.1.2.3) ... If a client receives a PUSH_PROMISE that does
+ // not include a complete and valid set of header fields or the :method
+ // pseudo-header field identifies a method that is not safe, it MUST
+ // respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
+ if (streamIt != m_streams.cend())
+ (*streamIt)->sendRST_STREAM(PROTOCOL_ERROR);
+ return;
+ }
+
+ // We got back an empty hpack block. Now let's figure out if there was an error.
+ constexpr auto hpackBlockHasContent = [](const auto &c) { return c.hpackBlockSize() > 0; };
+ const bool anyHpackBlock = std::any_of(continuedFrames.cbegin(), continuedFrames.cend(),
+ hpackBlockHasContent);
+ if (anyHpackBlock) // There was hpack block data, but returned empty => it overflowed.
+ return connectionError(FRAME_SIZE_ERROR, "HEADERS frame too large");
+ }
+
+ if (streamIt == m_streams.cend()) // No more processing without a stream from here on.
+ return;
+
+ switch (firstFrameType) {
+ case FrameType::HEADERS:
+ streamIt.value()->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
+ break;
+ case FrameType::PUSH_PROMISE: {
+ std::optional<QUrl> promiseKey = HPack::makePromiseKeyUrl(decoder.decodedHeader());
+ if (!promiseKey)
+ return; // invalid URL/key !
+ if (m_promisedStreams.contains(*promiseKey))
+ return; // already promised!
+ const auto promiseID = qFromBigEndian<quint32>(continuedFrames[0].dataBegin());
+ QHttp2Stream *stream = m_streams.value(promiseID);
+ stream->transitionState(QHttp2Stream::StateTransition::CloseLocal);
+ stream->handleHEADERS(continuedFrames[0].flags(), decoder.decodedHeader());
+ emit newPromisedStream(stream); // @future[consider] add promise key as argument?
+ m_promisedStreams.emplace(*promiseKey, promiseID);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+bool QHttp2Connection::acceptSetting(Http2::Settings identifier, quint32 newValue)
+{
+ switch (identifier) {
+ case Settings::HEADER_TABLE_SIZE_ID: {
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS HEADER_TABLE_SIZE %d", this, newValue);
+ if (newValue > maxAcceptableTableSize) {
+ connectionError(PROTOCOL_ERROR, "SETTINGS invalid table size");
+ return false;
+ }
+ encoder.setMaxDynamicTableSize(newValue);
+ break;
+ }
+ case Settings::INITIAL_WINDOW_SIZE_ID: {
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS INITIAL_WINDOW_SIZE %d", this,
+ newValue);
+ // For every active stream - adjust its window
+ // (and handle possible overflows as errors).
+ if (newValue > quint32(std::numeric_limits<qint32>::max())) {
+ connectionError(FLOW_CONTROL_ERROR, "SETTINGS invalid initial window size");
+ return false;
+ }
+
+ const qint32 delta = qint32(newValue) - streamInitialSendWindowSize;
+ streamInitialSendWindowSize = qint32(newValue);
+
+ qCDebug(qHttp2ConnectionLog, "[%p] Adjusting initial window size for %zu streams by %d",
+ this, size_t(m_streams.size()), delta);
+ for (const QPointer<QHttp2Stream> &stream : std::as_const(m_streams)) {
+ if (!stream || !stream->isActive())
+ continue;
+ qint32 sum = 0;
+ if (qAddOverflow(stream->m_sendWindow, delta, &sum)) {
+ stream->sendRST_STREAM(PROTOCOL_ERROR);
+ stream->finishWithError(QNetworkReply::ProtocolFailure,
+ "SETTINGS window overflow"_L1);
+ continue;
+ }
+ stream->m_sendWindow = sum;
+ if (delta > 0 && stream->isUploadingDATA() && !stream->isUploadBlocked()) {
+ QMetaObject::invokeMethod(stream, &QHttp2Stream::maybeResumeUpload,
+ Qt::QueuedConnection);
+ }
+ }
+ break;
+ }
+ case Settings::MAX_CONCURRENT_STREAMS_ID: {
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_CONCURRENT_STREAMS %d", this,
+ newValue);
+ m_maxConcurrentStreams = newValue;
+ break;
+ }
+ case Settings::MAX_FRAME_SIZE_ID: {
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_FRAME_SIZE %d", this, newValue);
+ if (newValue < Http2::minPayloadLimit || newValue > Http2::maxPayloadSize) {
+ connectionError(PROTOCOL_ERROR, "SETTINGS max frame size is out of range");
+ return false;
+ }
+ maxFrameSize = newValue;
+ break;
+ }
+ case Settings::MAX_HEADER_LIST_SIZE_ID: {
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS MAX_HEADER_LIST_SIZE %d", this,
+ newValue);
+ // We just remember this value, it can later
+ // prevent us from sending any request (and this
+ // will end up in request/reply error).
+ m_maxHeaderListSize = newValue;
+ break;
+ }
+ case Http2::Settings::ENABLE_PUSH_ID:
+ qCDebug(qHttp2ConnectionLog, "[%p] Received SETTINGS ENABLE_PUSH %d", this, newValue);
+ if (newValue != 0 && newValue != 1) {
+ connectionError(PROTOCOL_ERROR, "SETTINGS peer sent illegal value for ENABLE_PUSH");
+ return false;
+ }
+ if (m_connectionType == Type::Client) {
+ if (newValue == 1) {
+ connectionError(PROTOCOL_ERROR, "SETTINGS server sent ENABLE_PUSH=1");
+ return false;
+ }
+ } else { // server-side
+ pushPromiseEnabled = newValue;
+ break;
+ }
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qhttp2connection_p.cpp"
diff --git a/src/network/access/qhttp2connection_p.h b/src/network/access/qhttp2connection_p.h
new file mode 100644
index 0000000000..ca2cae58e0
--- /dev/null
+++ b/src/network/access/qhttp2connection_p.h
@@ -0,0 +1,372 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef HTTP2CONNECTION_P_H
+#define HTTP2CONNECTION_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <private/qtnetworkglobal_p.h>
+
+#include <QtCore/qobject.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qvarlengtharray.h>
+#include <QtCore/qxpfunctional.h>
+#include <QtNetwork/qhttp2configuration.h>
+#include <QtNetwork/qtcpsocket.h>
+
+#include <private/http2protocol_p.h>
+#include <private/http2streams_p.h>
+#include <private/http2frames_p.h>
+#include <private/hpack_p.h>
+
+#include <variant>
+#include <optional>
+#include <type_traits>
+#include <limits>
+
+QT_BEGIN_NAMESPACE
+
+template <typename T, typename Err>
+class QH2Expected
+{
+ static_assert(!std::is_same_v<T, Err>, "T and Err must be different types");
+public:
+ // Rule Of Zero applies
+ QH2Expected(T &&value) : m_data(std::move(value)) { }
+ QH2Expected(const T &value) : m_data(value) { }
+ QH2Expected(Err &&error) : m_data(std::move(error)) { }
+ QH2Expected(const Err &error) : m_data(error) { }
+
+ QH2Expected &operator=(T &&value)
+ {
+ m_data = std::move(value);
+ return *this;
+ }
+ QH2Expected &operator=(const T &value)
+ {
+ m_data = value;
+ return *this;
+ }
+ QH2Expected &operator=(Err &&error)
+ {
+ m_data = std::move(error);
+ return *this;
+ }
+ QH2Expected &operator=(const Err &error)
+ {
+ m_data = error;
+ return *this;
+ }
+ T unwrap() const
+ {
+ Q_ASSERT(ok());
+ return std::get<T>(m_data);
+ }
+ Err error() const
+ {
+ Q_ASSERT(has_error());
+ return std::get<Err>(m_data);
+ }
+ bool ok() const noexcept { return std::holds_alternative<T>(m_data); }
+ bool has_value() const noexcept { return ok(); }
+ bool has_error() const noexcept { return std::holds_alternative<Err>(m_data); }
+ void clear() noexcept { m_data.reset(); }
+
+private:
+ std::variant<T, Err> m_data;
+};
+
+class QHttp2Connection;
+class Q_NETWORK_EXPORT QHttp2Stream : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY_MOVE(QHttp2Stream)
+
+public:
+ enum class State { Idle, ReservedRemote, Open, HalfClosedLocal, HalfClosedRemote, Closed };
+ Q_ENUM(State)
+ constexpr static quint8 DefaultPriority = 127;
+
+ ~QHttp2Stream() noexcept;
+
+ // HTTP2 things
+ quint32 streamID() const noexcept { return m_streamID; }
+
+ // Are we waiting for a larger send window before sending more data?
+ bool isUploadBlocked() const noexcept;
+ bool isUploadingDATA() const noexcept { return m_uploadByteDevice != nullptr; }
+ State state() const noexcept { return m_state; }
+ bool isActive() const noexcept { return m_state != State::Closed && m_state != State::Idle; }
+ bool isPromisedStream() const noexcept { return m_isReserved; }
+ bool wasReset() const noexcept { return m_RST_STREAM_code.has_value(); }
+ quint32 RST_STREAM_code() const noexcept { return m_RST_STREAM_code.value_or(0); }
+ // Just the list of headers, as received, may contain duplicates:
+ HPack::HttpHeader receivedHeaders() const noexcept { return m_headers; }
+
+ QByteDataBuffer downloadBuffer() const noexcept { return m_downloadBuffer; }
+
+Q_SIGNALS:
+ void headersReceived(const HPack::HttpHeader &headers, bool endStream);
+ void headersUpdated();
+ void errorOccurred(quint32 errorCode, const QString &errorString);
+ void stateChanged(QHttp2Stream::State newState);
+ void promisedStreamReceived(quint32 newStreamID);
+ void uploadBlocked();
+ void dataReceived(const QByteArray &data, bool endStream);
+
+ void bytesWritten(qint64 bytesWritten);
+ void uploadDeviceError(const QString &errorString);
+ void uploadFinished();
+
+public Q_SLOTS:
+ bool sendRST_STREAM(quint32 errorCode);
+ bool sendHEADERS(const HPack::HttpHeader &headers, bool endStream,
+ quint8 priority = DefaultPriority);
+ void sendDATA(QIODevice *device, bool endStream);
+ void sendDATA(QNonContiguousByteDevice *device, bool endStream);
+ void sendWINDOW_UPDATE(quint32 delta);
+
+private Q_SLOTS:
+ void maybeResumeUpload();
+ void uploadDeviceReadChannelFinished();
+ void uploadDeviceDestroyed();
+
+private:
+ friend class QHttp2Connection;
+ QHttp2Stream(QHttp2Connection *connection, quint32 streamID) noexcept;
+
+ [[nodiscard]] QHttp2Connection *getConnection() const
+ {
+ return qobject_cast<QHttp2Connection *>(parent());
+ }
+
+ enum class StateTransition {
+ Open,
+ CloseLocal,
+ CloseRemote,
+ RST,
+ };
+
+ void setState(State newState);
+ void transitionState(StateTransition transition);
+ void internalSendDATA();
+ void finishSendDATA();
+
+ void handleDATA(const Http2::Frame &inboundFrame);
+ void handleHEADERS(Http2::FrameFlags frameFlags, const HPack::HttpHeader &headers);
+ void handleRST_STREAM(const Http2::Frame &inboundFrame);
+ void handleWINDOW_UPDATE(const Http2::Frame &inboundFrame);
+
+ void finishWithError(quint32 errorCode, const QString &message);
+ void finishWithError(quint32 errorCode);
+
+ // Keep it const since it never changes after creation
+ const quint32 m_streamID = 0;
+ qint32 m_recvWindow = 0;
+ qint32 m_sendWindow = 0;
+ bool m_endStreamAfterDATA = false;
+ std::optional<quint32> m_RST_STREAM_code;
+
+ QIODevice *m_uploadDevice = nullptr;
+ QNonContiguousByteDevice *m_uploadByteDevice = nullptr;
+
+ QByteDataBuffer m_downloadBuffer;
+ State m_state = State::Idle;
+ HPack::HttpHeader m_headers;
+ bool m_isReserved = false;
+};
+
+class Q_NETWORK_EXPORT QHttp2Connection : public QObject
+{
+ Q_OBJECT
+ Q_DISABLE_COPY_MOVE(QHttp2Connection)
+
+public:
+ enum class CreateStreamError {
+ MaxConcurrentStreamsReached,
+ StreamIdsExhausted,
+ ReceivedGOAWAY,
+ };
+ Q_ENUM(CreateStreamError)
+
+ enum class PingState {
+ Ping,
+ PongSignatureIdentical,
+ PongSignatureChanged,
+ PongNoPingSent, // We got an ACKed ping but had not sent any
+ };
+
+ // For a pre-established connection:
+ [[nodiscard]] static QHttp2Connection *
+ createUpgradedConnection(QIODevice *socket, const QHttp2Configuration &config);
+ // For a new connection, potential TLS handshake must already be finished:
+ [[nodiscard]] static QHttp2Connection *createDirectConnection(QIODevice *socket,
+ const QHttp2Configuration &config);
+ [[nodiscard]] static QHttp2Connection *
+ createDirectServerConnection(QIODevice *socket, const QHttp2Configuration &config);
+ ~QHttp2Connection();
+
+ [[nodiscard]] QH2Expected<QHttp2Stream *, CreateStreamError> createStream();
+
+ QHttp2Stream *getStream(quint32 streamId) const;
+ QHttp2Stream *promisedStream(const QUrl &streamKey) const
+ {
+ if (quint32 id = m_promisedStreams.value(streamKey, 0); id)
+ return m_streams.value(id);
+ return nullptr;
+ }
+
+ void close() { sendGOAWAY(Http2::HTTP2_NO_ERROR); }
+
+ bool isGoingAway() const noexcept { return m_goingAway; }
+
+ quint32 maxConcurrentStreams() const noexcept { return m_maxConcurrentStreams; }
+ quint32 maxHeaderListSize() const noexcept { return m_maxHeaderListSize; }
+
+ bool isUpgradedConnection() const noexcept { return m_upgradedConnection; }
+
+Q_SIGNALS:
+ void newIncomingStream(QHttp2Stream *stream);
+ void newPromisedStream(QHttp2Stream *stream);
+ void errorReceived(/*@future: add as needed?*/); // Connection errors only, no stream-specific errors
+ void connectionClosed();
+ void settingsFrameReceived();
+ void pingFrameRecived(QHttp2Connection::PingState state);
+ void errorOccurred(Http2::Http2Error errorCode, const QString &errorString);
+ void receivedGOAWAY(quint32 errorCode, quint32 lastStreamID);
+public Q_SLOTS:
+ bool sendPing();
+ bool sendPing(QByteArrayView data);
+ void handleReadyRead();
+ void handleConnectionClosure();
+
+private:
+ friend class QHttp2Stream;
+ [[nodiscard]] QIODevice *getSocket() const { return qobject_cast<QIODevice *>(parent()); }
+
+ QH2Expected<QHttp2Stream *, QHttp2Connection::CreateStreamError> createStreamInternal();
+ QHttp2Stream *createStreamInternal_impl(quint32 streamID);
+
+ bool isInvalidStream(quint32 streamID) noexcept;
+ bool streamWasReset(quint32 streamID) noexcept;
+
+ void connectionError(Http2::Http2Error errorCode,
+ const char *message); // Connection failed to be established?
+ void setH2Configuration(QHttp2Configuration config);
+ void closeSession();
+ qsizetype numActiveStreamsImpl(quint32 mask) const noexcept;
+ qsizetype numActiveRemoteStreams() const noexcept;
+ qsizetype numActiveLocalStreams() const noexcept;
+
+ bool sendClientPreface();
+ bool sendSETTINGS();
+ bool sendServerPreface();
+ bool serverCheckClientPreface();
+ bool sendWINDOW_UPDATE(quint32 streamID, quint32 delta);
+ bool sendGOAWAY(quint32 errorCode);
+ bool sendSETTINGS_ACK();
+
+ void handleDATA();
+ void handleHEADERS();
+ void handlePRIORITY();
+ void handleRST_STREAM();
+ void handleSETTINGS();
+ void handlePUSH_PROMISE();
+ void handlePING();
+ void handleGOAWAY();
+ void handleWINDOW_UPDATE();
+ void handleCONTINUATION();
+
+ void handleContinuedHEADERS();
+
+ bool acceptSetting(Http2::Settings identifier, quint32 newValue);
+
+ bool readClientPreface();
+
+ explicit QHttp2Connection(QIODevice *socket);
+
+ enum class Type { Client, Server } m_connectionType = Type::Client;
+
+ bool waitingForSettingsACK = false;
+
+ static constexpr quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
+ // HTTP/2 4.3: Header compression is stateful. One compression context and
+ // one decompression context are used for the entire connection.
+ HPack::Decoder decoder = HPack::Decoder(HPack::FieldLookupTable::DefaultSize);
+ HPack::Encoder encoder = HPack::Encoder(HPack::FieldLookupTable::DefaultSize, true);
+
+ QHttp2Configuration m_config;
+ QHash<quint32, QPointer<QHttp2Stream>> m_streams;
+ QHash<QUrl, quint32> m_promisedStreams;
+ QVarLengthArray<quint32> m_resetStreamIDs;
+ std::optional<QByteArray> m_lastPingSignature = std::nullopt;
+ quint32 m_nextStreamID = 1;
+
+ // Peer's max frame size (this min is the default value
+ // we start with, that can be updated by SETTINGS frame):
+ quint32 maxFrameSize = Http2::minPayloadLimit;
+
+ Http2::FrameReader frameReader;
+ Http2::Frame inboundFrame;
+ Http2::FrameWriter frameWriter;
+
+ // Temporary storage to assemble HEADERS' block
+ // from several CONTINUATION frames ...
+ bool continuationExpected = false;
+ std::vector<Http2::Frame> continuedFrames;
+
+ // Control flow:
+
+ // This is how many concurrent streams our peer allows us, 100 is the
+ // initial value, can be updated by the server's SETTINGS frame(s):
+ quint32 m_maxConcurrentStreams = Http2::maxConcurrentStreams;
+ // While we allow sending SETTTINGS_MAX_CONCURRENT_STREAMS to limit our peer,
+ // it's just a hint and we do not actually enforce it (and we can continue
+ // sending requests and creating streams while maxConcurrentStreams allows).
+
+ // This is our (client-side) maximum possible receive window size, we set
+ // it in a ctor from QHttp2Configuration, it does not change after that.
+ // The default is 64Kb:
+ qint32 maxSessionReceiveWindowSize = Http2::defaultSessionWindowSize;
+
+ // Our session current receive window size, updated in a ctor from
+ // QHttp2Configuration. Signed integer since it can become negative
+ // (it's still a valid window size).
+ qint32 sessionReceiveWindowSize = Http2::defaultSessionWindowSize;
+ // Our per-stream receive window size, default is 64 Kb, will be updated
+ // from QHttp2Configuration. Again, signed - can become negative.
+ qint32 streamInitialReceiveWindowSize = Http2::defaultSessionWindowSize;
+
+ // These are our peer's receive window sizes, they will be updated by the
+ // peer's SETTINGS and WINDOW_UPDATE frames, defaults presumed to be 64Kb.
+ qint32 sessionSendWindowSize = Http2::defaultSessionWindowSize;
+ qint32 streamInitialSendWindowSize = Http2::defaultSessionWindowSize;
+
+ // Our peer's header size limitations. It's unlimited by default, but can
+ // be changed via peer's SETTINGS frame.
+ quint32 m_maxHeaderListSize = (std::numeric_limits<quint32>::max)();
+ // While we can send SETTINGS_MAX_HEADER_LIST_SIZE value (our limit on
+ // the headers size), we never enforce it, it's just a hint to our peer.
+
+ bool m_upgradedConnection = false;
+ bool m_goingAway = false;
+ bool pushPromiseEnabled = false;
+ quint32 m_lastIncomingStreamID = Http2::connectionStreamID;
+
+ // Server-side only:
+ bool m_waitingForClientPreface = false;
+};
+
+QT_END_NAMESPACE
+
+#endif // HTTP2CONNECTION_P_H
diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp
index f41cde067a..d9341dc643 100644
--- a/src/network/access/qhttp2protocolhandler.cpp
+++ b/src/network/access/qhttp2protocolhandler.cpp
@@ -10,10 +10,12 @@
#include <private/qnoncontiguousbytedevice_p.h>
#include <QtNetwork/qabstractsocket.h>
+
#include <QtCore/qloggingcategory.h>
#include <QtCore/qendian.h>
#include <QtCore/qdebug.h>
#include <QtCore/qlist.h>
+#include <QtCore/qnumeric.h>
#include <QtCore/qurl.h>
#include <qhttp2configuration.h>
@@ -26,6 +28,7 @@
#include <algorithm>
#include <vector>
+#include <optional>
QT_BEGIN_NAMESPACE
@@ -45,10 +48,10 @@ HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxH
// 1. Before anything - mandatory fields, if they do not fit into maxHeaderList -
// then stop immediately with error.
const auto auth = request.url().authority(QUrl::FullyEncoded | QUrl::RemoveUserInfo).toLatin1();
- header.push_back(HeaderField(":authority", auth));
- header.push_back(HeaderField(":method", request.methodName()));
- header.push_back(HeaderField(":path", request.uri(useProxy)));
- header.push_back(HeaderField(":scheme", request.url().scheme().toLatin1()));
+ header.emplace_back(":authority", auth);
+ header.emplace_back(":method", request.methodName());
+ header.emplace_back(":path", request.uri(useProxy));
+ header.emplace_back(":scheme", request.url().scheme().toLatin1());
HeaderSize size = header_size(header);
if (!size.first) // Ooops!
@@ -57,9 +60,11 @@ HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxH
if (size.second > maxHeaderListSize)
return HttpHeader(); // Bad, we cannot send this request ...
- const auto requestHeader = request.header();
- for (const auto &field : requestHeader) {
- const HeaderSize delta = entry_size(field.first, field.second);
+ const QHttpHeaders requestHeader = request.header();
+ for (qsizetype i = 0; i < requestHeader.size(); ++i) {
+ const auto name = requestHeader.nameAt(i);
+ const auto value = requestHeader.valueAt(i);
+ const HeaderSize delta = entry_size(name, value);
if (!delta.first) // Overflow???
break;
if (std::numeric_limits<quint32>::max() - delta.second < size.second)
@@ -68,47 +73,22 @@ HPack::HttpHeader build_headers(const QHttpNetworkRequest &request, quint32 maxH
if (size.second > maxHeaderListSize)
break;
- if (field.first.compare("connection", Qt::CaseInsensitive) == 0 ||
- field.first.compare("host", Qt::CaseInsensitive) == 0 ||
- field.first.compare("keep-alive", Qt::CaseInsensitive) == 0 ||
- field.first.compare("proxy-connection", Qt::CaseInsensitive) == 0 ||
- field.first.compare("transfer-encoding", Qt::CaseInsensitive) == 0)
+ if (name == "connection"_L1 || name == "host"_L1 || name == "keep-alive"_L1
+ || name == "proxy-connection"_L1 || name == "transfer-encoding"_L1) {
continue; // Those headers are not valid (section 3.2.1) - from QSpdyProtocolHandler
+ }
// TODO: verify with specs, which fields are valid to send ....
- // toLower - 8.1.2 .... "header field names MUST be converted to lowercase prior
- // to their encoding in HTTP/2.
- // A request or response containing uppercase header field names
- // MUST be treated as malformed (Section 8.1.2.6)".
- header.push_back(HeaderField(field.first.toLower(), field.second));
+ //
+ // Note: RFC 7450 8.1.2 (HTTP/2) states that header field names must be lower-cased
+ // prior to their encoding in HTTP/2; header name fields in QHttpHeaders are already
+ // lower-cased
+ header.emplace_back(QByteArray{name.data(), name.size()},
+ QByteArray{value.data(), value.size()});
}
return header;
}
-std::vector<uchar> assemble_hpack_block(const std::vector<Http2::Frame> &frames)
-{
- std::vector<uchar> hpackBlock;
-
- quint32 total = 0;
- for (const auto &frame : frames)
- total += frame.hpackBlockSize();
-
- if (!total)
- return hpackBlock;
-
- hpackBlock.resize(total);
- auto dst = hpackBlock.begin();
- for (const auto &frame : frames) {
- if (const auto hpackBlockSize = frame.hpackBlockSize()) {
- const uchar *src = frame.hpackBlockBegin();
- std::copy(src, src + hpackBlockSize, dst);
- dst += hpackBlockSize;
- }
- }
-
- return hpackBlock;
-}
-
QUrl urlkey_from_request(const QHttpNetworkRequest &request)
{
QUrl url;
@@ -120,21 +100,11 @@ QUrl urlkey_from_request(const QHttpNetworkRequest &request)
return url;
}
-bool sum_will_overflow(qint32 windowSize, qint32 delta)
-{
- if (windowSize > 0)
- return std::numeric_limits<qint32>::max() - windowSize < delta;
- return std::numeric_limits<qint32>::min() - windowSize > delta;
-}
-
}// Unnamed namespace
// Since we anyway end up having this in every function definition:
using namespace Http2;
-const std::deque<quint32>::size_type QHttp2ProtocolHandler::maxRecycledStreams = 10000;
-const quint32 QHttp2ProtocolHandler::maxAcceptableTableSize;
-
QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *channel)
: QAbstractProtocolHandler(channel),
decoder(HPack::FieldLookupTable::DefaultSize),
@@ -318,8 +288,7 @@ bool QHttp2ProtocolHandler::sendRequest()
auto &requests = m_channel->h2RequestsToSend;
for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) {
const auto &pair = *it;
- const QString scheme(pair.first.url().scheme());
- if (scheme == "preconnect-http"_L1 || scheme == "preconnect-https"_L1) {
+ if (pair.first.isPreConnect()) {
m_connection->preConnectFinished();
emit pair.second->finished();
it = requests.erase(it);
@@ -357,11 +326,13 @@ bool QHttp2ProtocolHandler::sendRequest()
initReplyFromPushPromise(message, key);
}
- const auto streamsToUse = std::min<quint32>(maxConcurrentStreams > quint32(activeStreams.size())
- ? maxConcurrentStreams - quint32(activeStreams.size()) : 0,
- requests.size());
+ const auto isClientSide = [](const auto &pair) -> bool { return (pair.first & 1) == 1; };
+ const auto activeClientSideStreams = std::count_if(
+ activeStreams.constKeyValueBegin(), activeStreams.constKeyValueEnd(), isClientSide);
+ const qint64 streamsToUse = qBound(0, qint64(maxConcurrentStreams) - activeClientSideStreams,
+ requests.size());
auto it = requests.begin();
- for (quint32 i = 0; i < streamsToUse; ++i) {
+ for (qint64 i = 0; i < streamsToUse; ++i) {
const qint32 newStreamID = createNewStream(*it);
if (!newStreamID) {
// TODO: actually we have to open a new connection.
@@ -504,7 +475,7 @@ bool QHttp2ProtocolHandler::sendDATA(Stream &stream)
}
frameWriter.start(FrameType::DATA, FrameFlag::EMPTY, stream.streamID);
- const qint32 bytesWritten = std::min<qint32>(slot, chunkSize);
+ const qint32 bytesWritten = qint32(std::min<qint64>(slot, chunkSize));
if (!frameWriter.writeDATA(*m_socket, maxFrameSize, src, bytesWritten))
return false;
@@ -576,8 +547,9 @@ void QHttp2ProtocolHandler::handleDATA()
sessionReceiveWindowSize -= inboundFrame.payloadSize();
- if (activeStreams.contains(streamID)) {
- auto &stream = activeStreams[streamID];
+ auto it = activeStreams.find(streamID);
+ if (it != activeStreams.end()) {
+ Stream &stream = it.value();
if (qint32(inboundFrame.payloadSize()) > stream.recvWindow) {
finishStreamWithError(stream, QNetworkReply::ProtocolFailure, "flow control error"_L1);
@@ -883,16 +855,19 @@ void QHttp2ProtocolHandler::handleWINDOW_UPDATE()
const auto streamID = inboundFrame.streamID();
if (streamID == Http2::connectionStreamID) {
- if (!valid || sum_will_overflow(sessionSendWindowSize, delta))
+ qint32 sum = 0;
+ if (!valid || qAddOverflow(sessionSendWindowSize, qint32(delta), &sum))
return connectionError(PROTOCOL_ERROR, "WINDOW_UPDATE invalid delta");
- sessionSendWindowSize += delta;
+ sessionSendWindowSize = sum;
} else {
- if (!activeStreams.contains(streamID)) {
+ auto it = activeStreams.find(streamID);
+ if (it == activeStreams.end()) {
// WINDOW_UPDATE on closed streams can be ignored.
return;
}
- auto &stream = activeStreams[streamID];
- if (!valid || sum_will_overflow(stream.sendWindow, delta)) {
+ Stream &stream = it.value();
+ qint32 sum = 0;
+ if (!valid || qAddOverflow(stream.sendWindow, qint32(delta), &sum)) {
finishStreamWithError(stream, QNetworkReply::ProtocolFailure,
"invalid WINDOW_UPDATE delta"_L1);
sendRST_STREAM(streamID, PROTOCOL_ERROR);
@@ -900,7 +875,7 @@ void QHttp2ProtocolHandler::handleWINDOW_UPDATE()
deleteActiveStream(streamID);
return;
}
- stream.sendWindow += delta;
+ stream.sendWindow = sum;
}
// Since we're in _q_receiveReply at the moment, let's first handle other
@@ -939,9 +914,10 @@ void QHttp2ProtocolHandler::handleContinuedHEADERS()
const auto streamID = continuedFrames[0].streamID();
+ const auto streamIt = activeStreams.find(streamID);
if (firstFrameType == FrameType::HEADERS) {
- if (activeStreams.contains(streamID)) {
- Stream &stream = activeStreams[streamID];
+ if (streamIt != activeStreams.end()) {
+ Stream &stream = streamIt.value();
if (stream.state != Stream::halfClosedLocal
&& stream.state != Stream::remoteReserved
&& stream.state != Stream::open) {
@@ -963,8 +939,13 @@ void QHttp2ProtocolHandler::handleContinuedHEADERS()
// has yet to see the reset.
}
- std::vector<uchar> hpackBlock(assemble_hpack_block(continuedFrames));
- if (!hpackBlock.size()) {
+ std::vector<uchar> hpackBlock(Http2::assemble_hpack_block(continuedFrames));
+ const bool hasHeaderFields = !hpackBlock.empty();
+ if (hasHeaderFields) {
+ HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()};
+ if (!decoder.decodeHeaderFields(inputStream))
+ return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
+ } else if (firstFrameType == FrameType::PUSH_PROMISE) {
// It could be a PRIORITY sent in HEADERS - already handled by this
// point in handleHEADERS. If it was PUSH_PROMISE (HTTP/2 8.2.1):
// "The header fields in PUSH_PROMISE and any subsequent CONTINUATION
@@ -973,21 +954,16 @@ void QHttp2ProtocolHandler::handleContinuedHEADERS()
// not include a complete and valid set of header fields or the :method
// pseudo-header field identifies a method that is not safe, it MUST
// respond with a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
- if (firstFrameType == FrameType::PUSH_PROMISE)
- resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR);
-
+ resetPromisedStream(continuedFrames[0], Http2::PROTOCOL_ERROR);
return;
}
- HPack::BitIStream inputStream{&hpackBlock[0], &hpackBlock[0] + hpackBlock.size()};
- if (!decoder.decodeHeaderFields(inputStream))
- return connectionError(COMPRESSION_ERROR, "HPACK decompression failed");
-
switch (firstFrameType) {
case FrameType::HEADERS:
- if (activeStreams.contains(streamID)) {
- Stream &stream = activeStreams[streamID];
- updateStream(stream, decoder.decodedHeader());
+ if (streamIt != activeStreams.end()) {
+ Stream &stream = streamIt.value();
+ if (hasHeaderFields)
+ updateStream(stream, decoder.decodedHeader());
// Needs to resend the request; we should finish and delete the current stream
const bool needResend = stream.request().d->needResendWithCredentials;
// No DATA frames. Or needs to resend.
@@ -1030,11 +1006,12 @@ bool QHttp2ProtocolHandler::acceptSetting(Http2::Settings identifier, quint32 ne
std::vector<quint32> brokenStreams;
brokenStreams.reserve(activeStreams.size());
for (auto &stream : activeStreams) {
- if (sum_will_overflow(stream.sendWindow, delta)) {
+ qint32 sum = 0;
+ if (qAddOverflow(stream.sendWindow, delta, &sum)) {
brokenStreams.push_back(stream.streamID);
continue;
}
- stream.sendWindow += delta;
+ stream.sendWindow = sum;
}
for (auto id : brokenStreams) {
@@ -1101,7 +1078,7 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
int statusCode = 0;
for (const auto &pair : headers) {
const auto &name = pair.name;
- auto value = pair.value;
+ const auto value = QByteArrayView(pair.value);
// TODO: part of this code copies what SPDY protocol handler does when
// processing headers. Binary nature of HTTP/2 and SPDY saves us a lot
@@ -1121,68 +1098,16 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const HPack::HttpHeader
if (ok)
httpReply->setContentLength(length);
} else {
- QByteArray binder(", ");
- if (name == "set-cookie")
- binder = "\n";
- httpReply->appendHeaderField(name, value.replace('\0', binder));
+ const auto binder = name == "set-cookie" ? QByteArrayView("\n") : QByteArrayView(", ");
+ httpReply->appendHeaderField(name, QByteArray(pair.value).replace('\0', binder));
}
}
- const auto handleAuth = [&, this](const QByteArray &authField, bool isProxy) -> bool {
- Q_ASSERT(httpReply);
- const auto auth = authField.trimmed();
- if (auth.startsWith("Negotiate") || auth.startsWith("NTLM")) {
- // @todo: We're supposed to fall back to http/1.1:
- // https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-10/http2-on-iis#when-is-http2-not-supported
- // "Windows authentication (NTLM/Kerberos/Negotiate) is not supported with HTTP/2.
- // In this case IIS will fall back to HTTP/1.1."
- // Though it might be OK to ignore this. The server shouldn't let us connect with
- // HTTP/2 if it doesn't support us using it.
- } else if (!auth.isEmpty()) {
- // Somewhat mimics parts of QHttpNetworkConnectionChannel::handleStatus
- bool resend = false;
- const bool authenticateHandled = m_connection->d_func()->handleAuthenticateChallenge(
- m_socket, httpReply, isProxy, resend);
- if (authenticateHandled && resend) {
- httpReply->d_func()->eraseData();
- // Add the request back in queue, we'll retry later now that
- // we've gotten some username/password set on it:
- httpRequest.d->needResendWithCredentials = true;
- m_channel->h2RequestsToSend.insert(httpRequest.priority(), stream.httpPair);
- httpReply->d_func()->clearHeaders();
- // If we have data we were uploading we need to reset it:
- if (stream.data()) {
- stream.data()->reset();
- httpReplyPrivate->totallyUploadedData = 0;
- }
- return true;
- } // else: Authentication failed or was cancelled
- }
- return false;
- };
-
- if (httpReply) {
- // See Note further down. These statuses would in HTTP/1.1 be handled
- // by QHttpNetworkConnectionChannel::handleStatus. But because h2 has
- // multiple streams/requests in a single channel this structure does not
- // map properly to that function.
- if (httpReply->statusCode() == 401) {
- const auto wwwAuth = httpReply->headerField("www-authenticate");
- if (handleAuth(wwwAuth, false)) {
- sendRST_STREAM(stream.streamID, CANCEL);
- markAsReset(stream.streamID);
- // The stream is finalized and deleted after returning
- return;
- } // else: errors handled later
- } else if (httpReply->statusCode() == 407) {
- const auto proxyAuth = httpReply->headerField("proxy-authenticate");
- if (handleAuth(proxyAuth, true)) {
- sendRST_STREAM(stream.streamID, CANCEL);
- markAsReset(stream.streamID);
- // The stream is finalized and deleted after returning
- return;
- } // else: errors handled later
- }
+ // Discard all informational (1xx) replies with the exception of 101.
+ // Also see RFC 9110 (Chapter 15.2)
+ if (statusCode == 100 || (102 <= statusCode && statusCode <= 199)) {
+ httpReplyPrivate->clearHttpLayerInformation();
+ return;
}
if (QHttpNetworkReply::isHttpRedirect(statusCode) && httpRequest.isFollowRedirects()) {
@@ -1259,6 +1184,91 @@ void QHttp2ProtocolHandler::updateStream(Stream &stream, const Frame &frame,
}
}
+// After calling this function, either the request will be re-sent or
+// the reply will be finishedWithError! Do not emit finished() or similar on the
+// reply after this!
+void QHttp2ProtocolHandler::handleAuthorization(Stream &stream)
+{
+ auto *httpReply = stream.reply();
+ auto *httpReplyPrivate = httpReply->d_func();
+ auto &httpRequest = stream.request();
+
+ Q_ASSERT(httpReply && (httpReply->statusCode() == 401 || httpReply->statusCode() == 407));
+
+ const auto handleAuth = [&, this](QByteArrayView authField, bool isProxy) -> bool {
+ Q_ASSERT(httpReply);
+ const QByteArrayView auth = authField.trimmed();
+ if (auth.startsWith("Negotiate") || auth.startsWith("NTLM")) {
+ // @todo: We're supposed to fall back to http/1.1:
+ // https://docs.microsoft.com/en-us/iis/get-started/whats-new-in-iis-10/http2-on-iis#when-is-http2-not-supported
+ // "Windows authentication (NTLM/Kerberos/Negotiate) is not supported with HTTP/2.
+ // In this case IIS will fall back to HTTP/1.1."
+ // Though it might be OK to ignore this. The server shouldn't let us connect with
+ // HTTP/2 if it doesn't support us using it.
+ return false;
+ }
+ // Somewhat mimics parts of QHttpNetworkConnectionChannel::handleStatus
+ bool resend = false;
+ const bool authenticateHandled = m_connection->d_func()->handleAuthenticateChallenge(
+ m_socket, httpReply, isProxy, resend);
+ if (authenticateHandled) {
+ if (resend) {
+ httpReply->d_func()->eraseData();
+ // Add the request back in queue, we'll retry later now that
+ // we've gotten some username/password set on it:
+ httpRequest.d->needResendWithCredentials = true;
+ m_channel->h2RequestsToSend.insert(httpRequest.priority(), stream.httpPair);
+ httpReply->d_func()->clearHeaders();
+ // If we have data we were uploading we need to reset it:
+ if (stream.data()) {
+ stream.data()->reset();
+ httpReplyPrivate->totallyUploadedData = 0;
+ }
+ // We automatically try to send new requests when the stream is
+ // closed, so we don't need to call sendRequest ourselves.
+ return true;
+ } // else: we're just not resending the request.
+ // @note In the http/1.x case we (at time of writing) call close()
+ // for the connectionChannel (which is a bit weird, we could surely
+ // reuse the open socket outside "connection:close"?), but in http2
+ // we only have one channel, so we won't close anything.
+ } else {
+ // No authentication header or authentication isn't supported, but
+ // we got a 401/407 so we cannot succeed. We need to emit signals
+ // for headers and data, and then finishWithError.
+ emit httpReply->headerChanged();
+ emit httpReply->readyRead();
+ QNetworkReply::NetworkError error = httpReply->statusCode() == 401
+ ? QNetworkReply::AuthenticationRequiredError
+ : QNetworkReply::ProxyAuthenticationRequiredError;
+ finishStreamWithError(stream, QNetworkReply::AuthenticationRequiredError,
+ m_connection->d_func()->errorDetail(error, m_socket));
+ }
+ return false;
+ };
+
+ // These statuses would in HTTP/1.1 be handled by
+ // QHttpNetworkConnectionChannel::handleStatus. But because h2 has
+ // multiple streams/requests in a single channel this structure does not
+ // map properly to that function.
+ bool authOk = true;
+ switch (httpReply->statusCode()) {
+ case 401:
+ authOk = handleAuth(httpReply->headerField("www-authenticate"), false);
+ break;
+ case 407:
+ authOk = handleAuth(httpReply->headerField("proxy-authenticate"), true);
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ if (authOk) {
+ markAsReset(stream.streamID);
+ deleteActiveStream(stream.streamID);
+ } // else: errors handled inside handleAuth
+}
+
+// Called when we have received a frame with the END_STREAM flag set
void QHttp2ProtocolHandler::finishStream(Stream &stream, Qt::ConnectionType connectionType)
{
Q_ASSERT(stream.state == Stream::remoteReserved || stream.reply());
@@ -1266,6 +1276,15 @@ void QHttp2ProtocolHandler::finishStream(Stream &stream, Qt::ConnectionType conn
stream.state = Stream::closed;
auto httpReply = stream.reply();
if (httpReply) {
+ int statusCode = httpReply->statusCode();
+ if (statusCode == 401 || statusCode == 407) {
+ // handleAuthorization will either re-send the request or
+ // finishWithError. In either case we don't want to emit finished
+ // here.
+ handleAuthorization(stream);
+ return;
+ }
+
httpReply->disconnect(this);
if (stream.data())
stream.data()->disconnect(this);
@@ -1389,9 +1408,10 @@ quint32 QHttp2ProtocolHandler::popStreamToResume()
auto &queue = suspendedStreams[rank];
auto it = queue.begin();
for (; it != queue.end(); ++it) {
- if (!activeStreams.contains(*it))
+ auto stream = activeStreams.constFind(*it);
+ if (stream == activeStreams.cend())
continue;
- if (activeStreams[*it].sendWindow > 0)
+ if (stream->sendWindow > 0)
break;
}
@@ -1414,8 +1434,8 @@ void QHttp2ProtocolHandler::removeFromSuspended(quint32 streamID)
void QHttp2ProtocolHandler::deleteActiveStream(quint32 streamID)
{
- if (activeStreams.contains(streamID)) {
- auto &stream = activeStreams[streamID];
+ if (const auto it = activeStreams.constFind(streamID); it != activeStreams.cend()) {
+ const Stream &stream = it.value();
if (stream.reply()) {
stream.reply()->disconnect(this);
streamIDs.remove(stream.reply());
@@ -1424,7 +1444,7 @@ void QHttp2ProtocolHandler::deleteActiveStream(quint32 streamID)
stream.data()->disconnect(this);
streamIDs.remove(stream.data());
}
- activeStreams.remove(streamID);
+ activeStreams.erase(it);
}
removeFromSuspended(streamID);
@@ -1447,10 +1467,11 @@ void QHttp2ProtocolHandler::resumeSuspendedStreams()
if (!streamID)
return;
- if (!activeStreams.contains(streamID))
+ auto it = activeStreams.find(streamID);
+ if (it == activeStreams.end())
continue;
+ Stream &stream = it.value();
- Stream &stream = activeStreams[streamID];
if (!sendDATA(stream)) {
finishStreamWithError(stream, QNetworkReply::UnknownNetworkError,
"failed to send DATA"_L1);
@@ -1479,42 +1500,18 @@ bool QHttp2ProtocolHandler::tryReserveStream(const Http2::Frame &pushPromiseFram
{
Q_ASSERT(pushPromiseFrame.type() == FrameType::PUSH_PROMISE);
- QMap<QByteArray, QByteArray> pseudoHeaders;
- for (const auto &field : requestHeader) {
- if (field.name == ":scheme" || field.name == ":path"
- || field.name == ":authority" || field.name == ":method") {
- if (field.value.isEmpty() || pseudoHeaders.contains(field.name))
- return false;
- pseudoHeaders[field.name] = field.value;
- }
- }
-
- if (pseudoHeaders.size() != 4) {
- // All four required, HTTP/2 8.1.2.3.
- return false;
- }
-
- const QByteArray method = pseudoHeaders[":method"];
- if (method.compare("get", Qt::CaseInsensitive) != 0 &&
- method.compare("head", Qt::CaseInsensitive) != 0)
- return false;
-
- QUrl url;
- url.setScheme(QLatin1StringView(pseudoHeaders[":scheme"]));
- url.setAuthority(QLatin1StringView(pseudoHeaders[":authority"]));
- url.setPath(QLatin1StringView(pseudoHeaders[":path"]));
-
- if (!url.isValid())
+ const auto url = HPack::makePromiseKeyUrl(requestHeader);
+ if (!url.has_value())
return false;
Q_ASSERT(activeStreams.contains(pushPromiseFrame.streamID()));
const Stream &associatedStream = activeStreams[pushPromiseFrame.streamID()];
const auto associatedUrl = urlkey_from_request(associatedStream.request());
- if (url.adjusted(QUrl::RemovePath) != associatedUrl.adjusted(QUrl::RemovePath))
+ if (url->adjusted(QUrl::RemovePath) != associatedUrl.adjusted(QUrl::RemovePath))
return false;
- const auto urlKey = url.toString();
+ const auto urlKey = url->toString();
if (promisedData.contains(urlKey)) // duplicate push promise
return false;
@@ -1553,8 +1550,8 @@ void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &mess
bool replyFinished = false;
Stream *promisedStream = nullptr;
- if (activeStreams.contains(promise.reservedID)) {
- promisedStream = &activeStreams[promise.reservedID];
+ if (auto it = activeStreams.find(promise.reservedID); it != activeStreams.end()) {
+ promisedStream = &it.value();
// Ok, we have an active (not closed yet) stream waiting for more frames,
// let's pretend we requested it:
promisedStream->httpPair = message;
@@ -1564,8 +1561,8 @@ void QHttp2ProtocolHandler::initReplyFromPushPromise(const HttpMessagePair &mess
streamInitialSendWindowSize,
streamInitialReceiveWindowSize);
closedStream.state = Stream::halfClosedLocal;
- activeStreams.insert(promise.reservedID, closedStream);
- promisedStream = &activeStreams[promise.reservedID];
+ it = activeStreams.insert(promise.reservedID, closedStream);
+ promisedStream = &it.value();
replyFinished = true;
}
diff --git a/src/network/access/qhttp2protocolhandler_p.h b/src/network/access/qhttp2protocolhandler_p.h
index c4340d7eeb..3b818771a6 100644
--- a/src/network/access/qhttp2protocolhandler_p.h
+++ b/src/network/access/qhttp2protocolhandler_p.h
@@ -94,6 +94,7 @@ private:
bool acceptSetting(Http2::Settings identifier, quint32 newValue);
+ void handleAuthorization(Stream &stream);
void updateStream(Stream &stream, const HPack::HttpHeader &headers,
Qt::ConnectionType connectionType = Qt::DirectConnection);
void updateStream(Stream &stream, const Http2::Frame &dataFrame,
@@ -120,7 +121,7 @@ private:
// the client's preface 24-byte message.
bool waitingForSettingsACK = false;
- static const quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
+ inline static const quint32 maxAcceptableTableSize = 16 * HPack::FieldLookupTable::DefaultSize;
// HTTP/2 4.3: Header compression is stateful. One compression context and
// one decompression context are used for the entire connection.
HPack::Decoder decoder;
@@ -129,7 +130,7 @@ private:
QHash<QObject *, int> streamIDs;
QHash<quint32, Stream> activeStreams;
std::deque<quint32> suspendedStreams[3]; // 3 for priorities: High, Normal, Low.
- static const std::deque<quint32>::size_type maxRecycledStreams;
+ inline static const std::deque<quint32>::size_type maxRecycledStreams = 10000;
std::deque<quint32> recycledStreams;
// Peer's max frame size (this min is the default value
diff --git a/src/network/access/qhttpheaderparser.cpp b/src/network/access/qhttpheaderparser.cpp
index 489defa8d8..0b7882c18a 100644
--- a/src/network/access/qhttpheaderparser.cpp
+++ b/src/network/access/qhttpheaderparser.cpp
@@ -52,11 +52,11 @@ bool QHttpHeaderParser::parseHeaders(QByteArrayView header)
if (header.size() - (header.endsWith("\r\n") ? 2 : 1) > maxTotalSize)
return false;
- QList<QPair<QByteArray, QByteArray>> result;
+ QHttpHeaders result;
while (!header.empty()) {
const qsizetype colon = header.indexOf(':');
if (colon == -1) // if no colon check if empty headers
- return result.empty() && (header == "\n" || header == "\r\n");
+ return result.isEmpty() && (header == "\n" || header == "\r\n");
if (result.size() >= maxFieldCount)
return false;
QByteArrayView name = header.first(colon);
@@ -75,14 +75,14 @@ bool QHttpHeaderParser::parseHeaders(QByteArrayView header)
line = line.trimmed();
if (!line.empty()) {
if (value.size())
- value += ' ' + line.toByteArray();
+ value += ' ' + line;
else
value = line.toByteArray();
}
header = header.sliced(endLine + 1);
} while (hSpaceStart(header));
Q_ASSERT(name.size() + 1 + value.size() <= maxFieldSize);
- result.append(qMakePair(name.toByteArray(), value));
+ result.append(name, value);
}
fields = result;
@@ -128,22 +128,18 @@ bool QHttpHeaderParser::parseStatus(QByteArrayView status)
return ok && uint(majorVersion) <= 9 && uint(minorVersion) <= 9;
}
-const QList<QPair<QByteArray, QByteArray> >& QHttpHeaderParser::headers() const
+const QHttpHeaders& QHttpHeaderParser::headers() const
{
return fields;
}
-QByteArray QHttpHeaderParser::firstHeaderField(const QByteArray &name,
+QByteArray QHttpHeaderParser::firstHeaderField(QByteArrayView name,
const QByteArray &defaultValue) const
{
- for (auto it = fields.constBegin(); it != fields.constEnd(); ++it) {
- if (name.compare(it->first, Qt::CaseInsensitive) == 0)
- return it->second;
- }
- return defaultValue;
+ return fields.value(name, defaultValue).toByteArray();
}
-QByteArray QHttpHeaderParser::combinedHeaderValue(const QByteArray &name, const QByteArray &defaultValue) const
+QByteArray QHttpHeaderParser::combinedHeaderValue(QByteArrayView name, const QByteArray &defaultValue) const
{
const QList<QByteArray> allValues = headerFieldValues(name);
if (allValues.isEmpty())
@@ -151,38 +147,30 @@ QByteArray QHttpHeaderParser::combinedHeaderValue(const QByteArray &name, const
return allValues.join(", ");
}
-QList<QByteArray> QHttpHeaderParser::headerFieldValues(const QByteArray &name) const
+QList<QByteArray> QHttpHeaderParser::headerFieldValues(QByteArrayView name) const
{
- QList<QByteArray> result;
- for (auto it = fields.constBegin(); it != fields.constEnd(); ++it)
- if (name.compare(it->first, Qt::CaseInsensitive) == 0)
- result += it->second;
-
- return result;
+ return fields.values(name);
}
-void QHttpHeaderParser::removeHeaderField(const QByteArray &name)
+void QHttpHeaderParser::removeHeaderField(QByteArrayView name)
{
- auto firstEqualsName = [&name](const QPair<QByteArray, QByteArray> &header) {
- return name.compare(header.first, Qt::CaseInsensitive) == 0;
- };
- fields.removeIf(firstEqualsName);
+ fields.removeAll(name);
}
void QHttpHeaderParser::setHeaderField(const QByteArray &name, const QByteArray &data)
{
removeHeaderField(name);
- fields.append(qMakePair(name, data));
+ fields.append(name, data);
}
void QHttpHeaderParser::prependHeaderField(const QByteArray &name, const QByteArray &data)
{
- fields.prepend(qMakePair(name, data));
+ fields.insert(0, name, data);
}
void QHttpHeaderParser::appendHeaderField(const QByteArray &name, const QByteArray &data)
{
- fields.append(qMakePair(name, data));
+ fields.append(name, data);
}
void QHttpHeaderParser::clearHeaders()
diff --git a/src/network/access/qhttpheaderparser_p.h b/src/network/access/qhttpheaderparser_p.h
index 9b149570e0..5e8f3c8130 100644
--- a/src/network/access/qhttpheaderparser_p.h
+++ b/src/network/access/qhttpheaderparser_p.h
@@ -16,6 +16,7 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
+#include <QtNetwork/qhttpheaders.h>
#include <QByteArray>
#include <QList>
@@ -43,7 +44,7 @@ static constexpr int MAX_TOTAL_HEADER_SIZE = 256 * 1024;
}
-class Q_NETWORK_PRIVATE_EXPORT QHttpHeaderParser
+class Q_NETWORK_EXPORT QHttpHeaderParser
{
public:
QHttpHeaderParser();
@@ -52,7 +53,7 @@ public:
bool parseHeaders(QByteArrayView headers);
bool parseStatus(QByteArrayView status);
- const QList<QPair<QByteArray, QByteArray> >& headers() const;
+ const QHttpHeaders& headers() const;
void setStatusCode(int code);
int getStatusCode() const;
int getMajorVersion() const;
@@ -62,15 +63,15 @@ public:
QString getReasonPhrase() const;
void setReasonPhrase(const QString &reason);
- QByteArray firstHeaderField(const QByteArray &name,
+ QByteArray firstHeaderField(QByteArrayView name,
const QByteArray &defaultValue = QByteArray()) const;
- QByteArray combinedHeaderValue(const QByteArray &name,
+ QByteArray combinedHeaderValue(QByteArrayView name,
const QByteArray &defaultValue = QByteArray()) const;
- QList<QByteArray> headerFieldValues(const QByteArray &name) const;
+ QList<QByteArray> headerFieldValues(QByteArrayView name) const;
void setHeaderField(const QByteArray &name, const QByteArray &data);
void prependHeaderField(const QByteArray &name, const QByteArray &data);
void appendHeaderField(const QByteArray &name, const QByteArray &data);
- void removeHeaderField(const QByteArray &name);
+ void removeHeaderField(QByteArrayView name);
void clearHeaders();
void setMaxHeaderFieldSize(qsizetype size) { maxFieldSize = size; }
@@ -83,7 +84,7 @@ public:
qsizetype maxHeaderFields() const { return maxFieldCount; }
private:
- QList<QPair<QByteArray, QByteArray> > fields;
+ QHttpHeaders fields;
QString reasonPhrase;
int statusCode;
int majorVersion;
diff --git a/src/network/access/qhttpheaders.cpp b/src/network/access/qhttpheaders.cpp
new file mode 100644
index 0000000000..c63da899a8
--- /dev/null
+++ b/src/network/access/qhttpheaders.cpp
@@ -0,0 +1,1551 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qhttpheaders.h"
+
+#include <private/qoffsetstringarray_p.h>
+
+#include <QtCore/qcompare.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmap.h>
+#include <QtCore/qset.h>
+#include <QtCore/qttypetraits.h>
+
+#include <q20algorithm.h>
+#include <string_view>
+#include <variant>
+
+QT_BEGIN_NAMESPACE
+
+Q_LOGGING_CATEGORY(lcQHttpHeaders, "qt.network.http.headers");
+
+/*!
+ \class QHttpHeaders
+ \since 6.7
+ \ingroup
+ \inmodule QtNetwork
+
+ \brief QHttpHeaders is a class for holding HTTP headers.
+
+ The class is an interface type for Qt networking APIs that
+ use or consume such headers.
+
+ \section1 Allowed field name and value characters
+
+ An HTTP header consists of \e name and \e value.
+ When setting these, QHttpHeaders validates \e name and \e value
+ to only contain characters allowed by the HTTP RFCs. For detailed
+ information see
+ \l {https://datatracker.ietf.org/doc/html/rfc9110#name-field-values}
+ {RFC 9110 Chapters 5.1 and 5.5}.
+
+ In all, this means:
+ \list
+ \li \c name must consist of visible ASCII characters, and must not be
+ empty
+ \li \c value may consist of arbitrary bytes, as long as header
+ and use case specific encoding rules are adhered to. \c value
+ may be empty
+ \endlist
+
+ The setters of this class automatically remove any leading or trailing
+ whitespaces from \e value, as they must be ignored during the
+ \e value processing.
+
+ \section1 Combining values
+
+ Most HTTP header values can be combined with a single comma \c {','}
+ plus an optional whitespace, and the semantic meaning is preserved.
+ As an example, these two should be semantically similar:
+ \badcode
+ // Values as separate header entries
+ myheadername: myheadervalue1
+ myheadername: myheadervalue2
+ // Combined value
+ myheadername: myheadervalue1, myheadervalue2
+ \endcode
+
+ However, there is a notable exception to this rule:
+ \l {https://datatracker.ietf.org/doc/html/rfc9110#name-field-order}
+ {Set-Cookie}. Due to this and the possibility of custom use cases,
+ QHttpHeaders does not automatically combine the values.
+
+ \section1 Performance
+
+ Most QHttpHeaders functions provide both
+ \l QHttpHeaders::WellKnownHeader and \l QAnyStringView overloads.
+ From a memory-usage and computation point of view it is recommended
+ to use the \l QHttpHeaders::WellKnownHeader overloads.
+*/
+
+// This list is from IANA HTTP Field Name Registry
+// https://www.iana.org/assignments/http-fields
+// It contains entries that are either "permanent"
+// or "deprecated" as of October 2023.
+// Usage relies on enum values keeping in same order.
+// ### Qt7 check if some of these headers have been obsoleted,
+// and also check if the enums benefit from reordering
+static constexpr auto headerNames = qOffsetStringArray(
+ // IANA Permanent status:
+ "a-im",
+ "accept",
+ "accept-additions",
+ "accept-ch",
+ "accept-datetime",
+ "accept-encoding",
+ "accept-features",
+ "accept-language",
+ "accept-patch",
+ "accept-post",
+ "accept-ranges",
+ "accept-signature",
+ "access-control-allow-credentials",
+ "access-control-allow-headers",
+ "access-control-allow-methods",
+ "access-control-allow-origin",
+ "access-control-expose-headers",
+ "access-control-max-age",
+ "access-control-request-headers",
+ "access-control-request-method",
+ "age",
+ "allow",
+ "alpn",
+ "alt-svc",
+ "alt-used",
+ "alternates",
+ "apply-to-redirect-ref",
+ "authentication-control",
+ "authentication-info",
+ "authorization",
+ "cache-control",
+ "cache-status",
+ "cal-managed-id",
+ "caldav-timezones",
+ "capsule-protocol",
+ "cdn-cache-control",
+ "cdn-loop",
+ "cert-not-after",
+ "cert-not-before",
+ "clear-site-data",
+ "client-cert",
+ "client-cert-chain",
+ "close",
+ "connection",
+ "content-digest",
+ "content-disposition",
+ "content-encoding",
+ "content-id",
+ "content-language",
+ "content-length",
+ "content-location",
+ "content-range",
+ "content-security-policy",
+ "content-security-policy-report-only",
+ "content-type",
+ "cookie",
+ "cross-origin-embedder-policy",
+ "cross-origin-embedder-policy-report-only",
+ "cross-origin-opener-policy",
+ "cross-origin-opener-policy-report-only",
+ "cross-origin-resource-policy",
+ "dasl",
+ "date",
+ "dav",
+ "delta-base",
+ "depth",
+ "destination",
+ "differential-id",
+ "dpop",
+ "dpop-nonce",
+ "early-data",
+ "etag",
+ "expect",
+ "expect-ct",
+ "expires",
+ "forwarded",
+ "from",
+ "hobareg",
+ "host",
+ "if",
+ "if-match",
+ "if-modified-since",
+ "if-none-match",
+ "if-range",
+ "if-schedule-tag-match",
+ "if-unmodified-since",
+ "im",
+ "include-referred-token-binding-id",
+ "keep-alive",
+ "label",
+ "last-event-id",
+ "last-modified",
+ "link",
+ "location",
+ "lock-token",
+ "max-forwards",
+ "memento-datetime",
+ "meter",
+ "mime-version",
+ "negotiate",
+ "nel",
+ "odata-entityid",
+ "odata-isolation",
+ "odata-maxversion",
+ "odata-version",
+ "optional-www-authenticate",
+ "ordering-type",
+ "origin",
+ "origin-agent-cluster",
+ "oscore",
+ "oslc-core-version",
+ "overwrite",
+ "ping-from",
+ "ping-to",
+ "position",
+ "prefer",
+ "preference-applied",
+ "priority",
+ "proxy-authenticate",
+ "proxy-authentication-info",
+ "proxy-authorization",
+ "proxy-status",
+ "public-key-pins",
+ "public-key-pins-report-only",
+ "range",
+ "redirect-ref",
+ "referer",
+ "refresh",
+ "replay-nonce",
+ "repr-digest",
+ "retry-after",
+ "schedule-reply",
+ "schedule-tag",
+ "sec-purpose",
+ "sec-token-binding",
+ "sec-websocket-accept",
+ "sec-websocket-extensions",
+ "sec-websocket-key",
+ "sec-websocket-protocol",
+ "sec-websocket-version",
+ "server",
+ "server-timing",
+ "set-cookie",
+ "signature",
+ "signature-input",
+ "slug",
+ "soapaction",
+ "status-uri",
+ "strict-transport-security",
+ "sunset",
+ "surrogate-capability",
+ "surrogate-control",
+ "tcn",
+ "te",
+ "timeout",
+ "topic",
+ "traceparent",
+ "tracestate",
+ "trailer",
+ "transfer-encoding",
+ "ttl",
+ "upgrade",
+ "urgency",
+ "user-agent",
+ "variant-vary",
+ "vary",
+ "via",
+ "want-content-digest",
+ "want-repr-digest",
+ "www-authenticate",
+ "x-content-type-options",
+ "x-frame-options",
+ // IANA Deprecated status:
+ "accept-charset",
+ "c-pep-info",
+ "pragma",
+ "protocol-info",
+ "protocol-query"
+ // If you append here, regenerate the index table
+);
+
+namespace {
+struct ByIndirectHeaderName
+{
+ constexpr bool operator()(quint8 lhs, quint8 rhs) const noexcept
+ {
+ return (*this)(map(lhs), map(rhs));
+ }
+ constexpr bool operator()(quint8 lhs, QByteArrayView rhs) const noexcept
+ {
+ return (*this)(map(lhs), rhs);
+ }
+ constexpr bool operator()(QByteArrayView lhs, quint8 rhs) const noexcept
+ {
+ return (*this)(lhs, map(rhs));
+ }
+ constexpr bool operator()(QByteArrayView lhs, QByteArrayView rhs) const noexcept
+ {
+ // ### just `lhs < rhs` when QByteArrayView relational operators are constexpr
+ return std::string_view(lhs) < std::string_view(rhs);
+ }
+private:
+ static constexpr QByteArrayView map(quint8 i) noexcept
+ {
+ return headerNames.viewAt(i);
+ }
+};
+} // unnamed namespace
+
+// This index table contains the indexes of 'headerNames' entries (above) in alphabetical order.
+// This allows a more efficient binary search for the names [O(logN)]. The 'headerNames' itself
+// cannot be guaranteed to be in alphabetical order, as it must keep the same order as the
+// WellKnownHeader enum, which may get appended over time.
+//
+// Note: when appending new enums, this must be regenerated
+static constexpr quint8 orderedHeaderNameIndexes[] = {
+ 0, // a-im
+ 1, // accept
+ 2, // accept-additions
+ 3, // accept-ch
+ 172, // accept-charset
+ 4, // accept-datetime
+ 5, // accept-encoding
+ 6, // accept-features
+ 7, // accept-language
+ 8, // accept-patch
+ 9, // accept-post
+ 10, // accept-ranges
+ 11, // accept-signature
+ 12, // access-control-allow-credentials
+ 13, // access-control-allow-headers
+ 14, // access-control-allow-methods
+ 15, // access-control-allow-origin
+ 16, // access-control-expose-headers
+ 17, // access-control-max-age
+ 18, // access-control-request-headers
+ 19, // access-control-request-method
+ 20, // age
+ 21, // allow
+ 22, // alpn
+ 23, // alt-svc
+ 24, // alt-used
+ 25, // alternates
+ 26, // apply-to-redirect-ref
+ 27, // authentication-control
+ 28, // authentication-info
+ 29, // authorization
+ 173, // c-pep-info
+ 30, // cache-control
+ 31, // cache-status
+ 32, // cal-managed-id
+ 33, // caldav-timezones
+ 34, // capsule-protocol
+ 35, // cdn-cache-control
+ 36, // cdn-loop
+ 37, // cert-not-after
+ 38, // cert-not-before
+ 39, // clear-site-data
+ 40, // client-cert
+ 41, // client-cert-chain
+ 42, // close
+ 43, // connection
+ 44, // content-digest
+ 45, // content-disposition
+ 46, // content-encoding
+ 47, // content-id
+ 48, // content-language
+ 49, // content-length
+ 50, // content-location
+ 51, // content-range
+ 52, // content-security-policy
+ 53, // content-security-policy-report-only
+ 54, // content-type
+ 55, // cookie
+ 56, // cross-origin-embedder-policy
+ 57, // cross-origin-embedder-policy-report-only
+ 58, // cross-origin-opener-policy
+ 59, // cross-origin-opener-policy-report-only
+ 60, // cross-origin-resource-policy
+ 61, // dasl
+ 62, // date
+ 63, // dav
+ 64, // delta-base
+ 65, // depth
+ 66, // destination
+ 67, // differential-id
+ 68, // dpop
+ 69, // dpop-nonce
+ 70, // early-data
+ 71, // etag
+ 72, // expect
+ 73, // expect-ct
+ 74, // expires
+ 75, // forwarded
+ 76, // from
+ 77, // hobareg
+ 78, // host
+ 79, // if
+ 80, // if-match
+ 81, // if-modified-since
+ 82, // if-none-match
+ 83, // if-range
+ 84, // if-schedule-tag-match
+ 85, // if-unmodified-since
+ 86, // im
+ 87, // include-referred-token-binding-id
+ 88, // keep-alive
+ 89, // label
+ 90, // last-event-id
+ 91, // last-modified
+ 92, // link
+ 93, // location
+ 94, // lock-token
+ 95, // max-forwards
+ 96, // memento-datetime
+ 97, // meter
+ 98, // mime-version
+ 99, // negotiate
+ 100, // nel
+ 101, // odata-entityid
+ 102, // odata-isolation
+ 103, // odata-maxversion
+ 104, // odata-version
+ 105, // optional-www-authenticate
+ 106, // ordering-type
+ 107, // origin
+ 108, // origin-agent-cluster
+ 109, // oscore
+ 110, // oslc-core-version
+ 111, // overwrite
+ 112, // ping-from
+ 113, // ping-to
+ 114, // position
+ 174, // pragma
+ 115, // prefer
+ 116, // preference-applied
+ 117, // priority
+ 175, // protocol-info
+ 176, // protocol-query
+ 118, // proxy-authenticate
+ 119, // proxy-authentication-info
+ 120, // proxy-authorization
+ 121, // proxy-status
+ 122, // public-key-pins
+ 123, // public-key-pins-report-only
+ 124, // range
+ 125, // redirect-ref
+ 126, // referer
+ 127, // refresh
+ 128, // replay-nonce
+ 129, // repr-digest
+ 130, // retry-after
+ 131, // schedule-reply
+ 132, // schedule-tag
+ 133, // sec-purpose
+ 134, // sec-token-binding
+ 135, // sec-websocket-accept
+ 136, // sec-websocket-extensions
+ 137, // sec-websocket-key
+ 138, // sec-websocket-protocol
+ 139, // sec-websocket-version
+ 140, // server
+ 141, // server-timing
+ 142, // set-cookie
+ 143, // signature
+ 144, // signature-input
+ 145, // slug
+ 146, // soapaction
+ 147, // status-uri
+ 148, // strict-transport-security
+ 149, // sunset
+ 150, // surrogate-capability
+ 151, // surrogate-control
+ 152, // tcn
+ 153, // te
+ 154, // timeout
+ 155, // topic
+ 156, // traceparent
+ 157, // tracestate
+ 158, // trailer
+ 159, // transfer-encoding
+ 160, // ttl
+ 161, // upgrade
+ 162, // urgency
+ 163, // user-agent
+ 164, // variant-vary
+ 165, // vary
+ 166, // via
+ 167, // want-content-digest
+ 168, // want-repr-digest
+ 169, // www-authenticate
+ 170, // x-content-type-options
+ 171, // x-frame-options
+};
+static_assert(std::size(orderedHeaderNameIndexes) == size_t(headerNames.count()));
+static_assert(q20::is_sorted(std::begin(orderedHeaderNameIndexes),
+ std::end(orderedHeaderNameIndexes),
+ ByIndirectHeaderName{}));
+
+/*!
+ \enum QHttpHeaders::WellKnownHeader
+
+ List of well known headers as per
+ \l {https://www.iana.org/assignments/http-fields}{IANA registry}.
+
+ \value AIM
+ \value Accept
+ \value AcceptAdditions
+ \value AcceptCH
+ \value AcceptDatetime
+ \value AcceptEncoding
+ \value AcceptFeatures
+ \value AcceptLanguage
+ \value AcceptPatch
+ \value AcceptPost
+ \value AcceptRanges
+ \value AcceptSignature
+ \value AccessControlAllowCredentials
+ \value AccessControlAllowHeaders
+ \value AccessControlAllowMethods
+ \value AccessControlAllowOrigin
+ \value AccessControlExposeHeaders
+ \value AccessControlMaxAge
+ \value AccessControlRequestHeaders
+ \value AccessControlRequestMethod
+ \value Age
+ \value Allow
+ \value ALPN
+ \value AltSvc
+ \value AltUsed
+ \value Alternates
+ \value ApplyToRedirectRef
+ \value AuthenticationControl
+ \value AuthenticationInfo
+ \value Authorization
+ \value CacheControl
+ \value CacheStatus
+ \value CalManagedID
+ \value CalDAVTimezones
+ \value CapsuleProtocol
+ \value CDNCacheControl
+ \value CDNLoop
+ \value CertNotAfter
+ \value CertNotBefore
+ \value ClearSiteData
+ \value ClientCert
+ \value ClientCertChain
+ \value Close
+ \value Connection
+ \value ContentDigest
+ \value ContentDisposition
+ \value ContentEncoding
+ \value ContentID
+ \value ContentLanguage
+ \value ContentLength
+ \value ContentLocation
+ \value ContentRange
+ \value ContentSecurityPolicy
+ \value ContentSecurityPolicyReportOnly
+ \value ContentType
+ \value Cookie
+ \value CrossOriginEmbedderPolicy
+ \value CrossOriginEmbedderPolicyReportOnly
+ \value CrossOriginOpenerPolicy
+ \value CrossOriginOpenerPolicyReportOnly
+ \value CrossOriginResourcePolicy
+ \value DASL
+ \value Date
+ \value DAV
+ \value DeltaBase
+ \value Depth
+ \value Destination
+ \value DifferentialID
+ \value DPoP
+ \value DPoPNonce
+ \value EarlyData
+ \value ETag
+ \value Expect
+ \value ExpectCT
+ \value Expires
+ \value Forwarded
+ \value From
+ \value Hobareg
+ \value Host
+ \value If
+ \value IfMatch
+ \value IfModifiedSince
+ \value IfNoneMatch
+ \value IfRange
+ \value IfScheduleTagMatch
+ \value IfUnmodifiedSince
+ \value IM
+ \value IncludeReferredTokenBindingID
+ \value KeepAlive
+ \value Label
+ \value LastEventID
+ \value LastModified
+ \value Link
+ \value Location
+ \value LockToken
+ \value MaxForwards
+ \value MementoDatetime
+ \value Meter
+ \value MIMEVersion
+ \value Negotiate
+ \value NEL
+ \value ODataEntityId
+ \value ODataIsolation
+ \value ODataMaxVersion
+ \value ODataVersion
+ \value OptionalWWWAuthenticate
+ \value OrderingType
+ \value Origin
+ \value OriginAgentCluster
+ \value OSCORE
+ \value OSLCCoreVersion
+ \value Overwrite
+ \value PingFrom
+ \value PingTo
+ \value Position
+ \value Prefer
+ \value PreferenceApplied
+ \value Priority
+ \value ProxyAuthenticate
+ \value ProxyAuthenticationInfo
+ \value ProxyAuthorization
+ \value ProxyStatus
+ \value PublicKeyPins
+ \value PublicKeyPinsReportOnly
+ \value Range
+ \value RedirectRef
+ \value Referer
+ \value Refresh
+ \value ReplayNonce
+ \value ReprDigest
+ \value RetryAfter
+ \value ScheduleReply
+ \value ScheduleTag
+ \value SecPurpose
+ \value SecTokenBinding
+ \value SecWebSocketAccept
+ \value SecWebSocketExtensions
+ \value SecWebSocketKey
+ \value SecWebSocketProtocol
+ \value SecWebSocketVersion
+ \value Server
+ \value ServerTiming
+ \value SetCookie
+ \value Signature
+ \value SignatureInput
+ \value SLUG
+ \value SoapAction
+ \value StatusURI
+ \value StrictTransportSecurity
+ \value Sunset
+ \value SurrogateCapability
+ \value SurrogateControl
+ \value TCN
+ \value TE
+ \value Timeout
+ \value Topic
+ \value Traceparent
+ \value Tracestate
+ \value Trailer
+ \value TransferEncoding
+ \value TTL
+ \value Upgrade
+ \value Urgency
+ \value UserAgent
+ \value VariantVary
+ \value Vary
+ \value Via
+ \value WantContentDigest
+ \value WantReprDigest
+ \value WWWAuthenticate
+ \value XContentTypeOptions
+ \value XFrameOptions
+ \value AcceptCharset
+ \value CPEPInfo
+ \value Pragma
+ \value ProtocolInfo
+ \value ProtocolQuery
+*/
+
+static QByteArray fieldToByteArray(QLatin1StringView s) noexcept
+{
+ return QByteArray(s.data(), s.size());
+}
+
+static QByteArray fieldToByteArray(QUtf8StringView s) noexcept
+{
+ return QByteArray(s.data(), s.size());
+}
+
+static QByteArray fieldToByteArray(QStringView s)
+{
+ return s.toLatin1();
+}
+
+static QByteArray normalizedName(QAnyStringView name)
+{
+ return name.visit([](auto name){ return fieldToByteArray(name); }).toLower();
+}
+
+struct HeaderName
+{
+ explicit HeaderName(QHttpHeaders::WellKnownHeader name) : data(name)
+ {
+ }
+
+ explicit HeaderName(QAnyStringView name)
+ {
+ auto nname = normalizedName(name);
+ if (auto h = HeaderName::toWellKnownHeader(nname))
+ data = *h;
+ else
+ data = std::move(nname);
+ }
+
+ // Returns an enum corresponding with the 'name' if possible. Uses binary search (O(logN)).
+ // The function doesn't normalize the data; needs to be done by the caller if needed
+ static std::optional<QHttpHeaders::WellKnownHeader> toWellKnownHeader(QByteArrayView name) noexcept
+ {
+ auto indexesBegin = std::cbegin(orderedHeaderNameIndexes);
+ auto indexesEnd = std::cend(orderedHeaderNameIndexes);
+
+ auto result = std::lower_bound(indexesBegin, indexesEnd, name, ByIndirectHeaderName{});
+
+ if (result != indexesEnd && name == headerNames[*result])
+ return static_cast<QHttpHeaders::WellKnownHeader>(*result);
+ return std::nullopt;
+ }
+
+ QByteArrayView asView() const noexcept
+ {
+ return std::visit([](const auto &arg) -> QByteArrayView {
+ using T = decltype(arg);
+ if constexpr (std::is_same_v<T, const QByteArray &>)
+ return arg;
+ else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>)
+ return headerNames.viewAt(qToUnderlying(arg));
+ else
+ static_assert(QtPrivate::type_dependent_false<T>());
+ }, data);
+ }
+
+ QByteArray asByteArray() const noexcept
+ {
+ return std::visit([](const auto &arg) -> QByteArray {
+ using T = decltype(arg);
+ if constexpr (std::is_same_v<T, const QByteArray &>) {
+ return arg;
+ } else if constexpr (std::is_same_v<T, const QHttpHeaders::WellKnownHeader &>) {
+ const auto view = headerNames.viewAt(qToUnderlying(arg));
+ return QByteArray::fromRawData(view.constData(), view.size());
+ } else {
+ static_assert(QtPrivate::type_dependent_false<T>());
+ }
+ }, data);
+ }
+
+private:
+ // Store the data as 'enum' whenever possible; more performant, and comparison relies on that
+ std::variant<QHttpHeaders::WellKnownHeader, QByteArray> data;
+
+ friend bool comparesEqual(const HeaderName &lhs, const HeaderName &rhs) noexcept
+ {
+ // Here we compare two std::variants, which will return false if the types don't match.
+ // That is beneficial here because we avoid unnecessary comparisons; but it also means
+ // we must always store the data as WellKnownHeader when possible (in other words, if
+ // we get a string that is mappable to a WellKnownHeader). To guard against accidental
+ // misuse, the 'data' is private and the constructors must be used.
+ return lhs.data == rhs.data;
+ }
+ Q_DECLARE_EQUALITY_COMPARABLE(HeaderName)
+};
+
+// A clarification on case-sensitivity:
+// - Header *names* are case-insensitive; Content-Type and content-type are considered equal
+// - Header *values* are case-sensitive
+// (In addition, the HTTP/2 and HTTP/3 standards mandate that all headers must be lower-cased when
+// encoded into transmission)
+struct Header {
+ HeaderName name;
+ QByteArray value;
+};
+
+auto headerNameMatches(const HeaderName &name)
+{
+ return [&name](const Header &header) { return header.name == name; };
+}
+
+class QHttpHeadersPrivate : public QSharedData
+{
+public:
+ QHttpHeadersPrivate() = default;
+
+ // The 'Self' is supplied as parameter to static functions so that
+ // we can define common methods which 'detach()' the private itself.
+ using Self = QExplicitlySharedDataPointer<QHttpHeadersPrivate>;
+ static void removeAll(Self &d, const HeaderName &name);
+ static void replaceOrAppend(Self &d, const HeaderName &name, const QByteArray &value);
+
+ void combinedValue(const HeaderName &name, QByteArray &result) const;
+ void values(const HeaderName &name, QList<QByteArray> &result) const;
+ QByteArrayView value(const HeaderName &name, QByteArrayView defaultValue) const noexcept;
+
+ QList<Header> headers;
+};
+
+QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QHttpHeadersPrivate)
+template <> void QExplicitlySharedDataPointer<QHttpHeadersPrivate>::detach()
+{
+ if (!d) {
+ d = new QHttpHeadersPrivate();
+ d->ref.ref();
+ } else if (d->ref.loadRelaxed() != 1) {
+ detach_helper();
+ }
+}
+
+void QHttpHeadersPrivate::removeAll(Self &d, const HeaderName &name)
+{
+ const auto it = std::find_if(d->headers.cbegin(), d->headers.cend(), headerNameMatches(name));
+
+ if (it != d->headers.cend()) {
+ // Found something to remove, calculate offset so we can proceed from the match-location
+ const auto matchOffset = it - d->headers.cbegin();
+ d.detach();
+ // Rearrange all matches to the end and erase them
+ d->headers.erase(std::remove_if(d->headers.begin() + matchOffset, d->headers.end(),
+ headerNameMatches(name)),
+ d->headers.end());
+ }
+}
+
+void QHttpHeadersPrivate::combinedValue(const HeaderName &name, QByteArray &result) const
+{
+ const char* separator = "";
+ for (const auto &h : std::as_const(headers)) {
+ if (h.name == name) {
+ result.append(separator);
+ result.append(h.value);
+ separator = ", ";
+ }
+ }
+}
+
+void QHttpHeadersPrivate::values(const HeaderName &name, QList<QByteArray> &result) const
+{
+ for (const auto &h : std::as_const(headers)) {
+ if (h.name == name)
+ result.append(h.value);
+ }
+}
+
+QByteArrayView QHttpHeadersPrivate::value(const HeaderName &name, QByteArrayView defaultValue) const noexcept
+{
+ for (const auto &h : std::as_const(headers)) {
+ if (h.name == name)
+ return h.value;
+ }
+ return defaultValue;
+}
+
+void QHttpHeadersPrivate::replaceOrAppend(Self &d, const HeaderName &name, const QByteArray &value)
+{
+ d.detach();
+ auto it = std::find_if(d->headers.begin(), d->headers.end(), headerNameMatches(name));
+ if (it != d->headers.end()) {
+ // Found something to replace => replace, and then rearrange any remaining
+ // matches to the end and erase them
+ it->value = value;
+ d->headers.erase(
+ std::remove_if(it + 1, d->headers.end(), headerNameMatches(name)),
+ d->headers.end());
+ } else {
+ // Found nothing to replace => append
+ d->headers.append(Header{name, value});
+ }
+}
+
+/*!
+ Creates a new QHttpHeaders object.
+*/
+QHttpHeaders::QHttpHeaders() noexcept : d()
+{
+}
+
+/*!
+ Creates a new QHttpHeaders object that is populated with
+ \a headers.
+
+ \sa {Allowed field name and value characters}
+*/
+QHttpHeaders QHttpHeaders::fromListOfPairs(const QList<std::pair<QByteArray, QByteArray>> &headers)
+{
+ QHttpHeaders h;
+ h.reserve(headers.size());
+ for (const auto &header : headers)
+ h.append(header.first, header.second);
+ return h;
+}
+
+/*!
+ Creates a new QHttpHeaders object that is populated with
+ \a headers.
+
+ \sa {Allowed field name and value characters}
+*/
+QHttpHeaders QHttpHeaders::fromMultiMap(const QMultiMap<QByteArray, QByteArray> &headers)
+{
+ QHttpHeaders h;
+ h.reserve(headers.size());
+ for (const auto &[name,value] : headers.asKeyValueRange())
+ h.append(name, value);
+ return h;
+}
+
+/*!
+ Creates a new QHttpHeaders object that is populated with
+ \a headers.
+
+ \sa {Allowed field name and value characters}
+*/
+QHttpHeaders QHttpHeaders::fromMultiHash(const QMultiHash<QByteArray, QByteArray> &headers)
+{
+ QHttpHeaders h;
+ h.reserve(headers.size());
+ for (const auto &[name,value] : headers.asKeyValueRange())
+ h.append(name, value);
+ return h;
+}
+
+/*!
+ Disposes of the headers object.
+*/
+QHttpHeaders::~QHttpHeaders()
+ = default;
+
+/*!
+ Creates a copy of \a other.
+*/
+QHttpHeaders::QHttpHeaders(const QHttpHeaders &other)
+ = default;
+
+/*!
+ Assigns the contents of \a other and returns a reference to this object.
+*/
+QHttpHeaders &QHttpHeaders::operator=(const QHttpHeaders &other)
+ = default;
+
+/*!
+ \fn QHttpHeaders::QHttpHeaders(QHttpHeaders &&other) noexcept
+
+ Move-constructs the object from \a other, which will be left
+ \l{isEmpty()}{empty}.
+*/
+
+/*!
+ \fn QHttpHeaders &QHttpHeaders::operator=(QHttpHeaders &&other) noexcept
+
+ Move-assigns \a other and returns a reference to this object.
+
+ \a other will be left \l{isEmpty()}{empty}.
+*/
+
+/*!
+ \fn void QHttpHeaders::swap(QHttpHeaders &other)
+
+ Swaps this QHttpHeaders with \a other. This function is very fast and
+ never fails.
+*/
+
+#ifndef QT_NO_DEBUG_STREAM
+/*!
+ \fn QDebug QHttpHeaders::operator<<(QDebug debug,
+ const QHttpHeaders &headers)
+
+ Writes \a headers into \a debug stream.
+*/
+QDebug operator<<(QDebug debug, const QHttpHeaders &headers)
+{
+ const QDebugStateSaver saver(debug);
+ debug.resetFormat().nospace();
+
+ debug << "QHttpHeaders(";
+ if (headers.d) {
+ debug << "headers = ";
+ const char *separator = "";
+ for (const auto &h : headers.d->headers) {
+ debug << separator << h.name.asView() << ':' << h.value;
+ separator = " | ";
+ }
+ }
+ debug << ")";
+ return debug;
+}
+#endif
+
+// A clarification on string encoding:
+// Setters and getters only accept names and values that are Latin-1 representable:
+// Either they are directly ASCII/Latin-1, or if they are UTF-X, they only use first 256
+// of the unicode points. For example using a '€' (U+20AC) in value would yield a warning
+// and the call is ignored.
+// Furthermore the 'name' has more strict rules than the 'value'
+
+// TODO FIXME REMOVEME once this is merged:
+// https://codereview.qt-project.org/c/qt/qtbase/+/508829
+static bool isUtf8Latin1Representable(QUtf8StringView s) noexcept
+{
+ // L1 encoded in UTF8 has at most the form
+ // - 0b0XXX'XXXX - US-ASCII
+ // - 0b1100'00XX 0b10XX'XXXX - at most 8 non-zero LSB bits allowed in L1
+ bool inMultibyte = false;
+ for (unsigned char c : s) {
+ if (c < 128) { // US-ASCII
+ if (inMultibyte)
+ return false; // invalid sequence
+ } else {
+ // decode as UTF-8:
+ if ((c & 0b1110'0000) == 0b1100'0000) { // two-octet UTF-8 leader
+ if (inMultibyte)
+ return false; // invalid sequence
+ inMultibyte = true;
+ const auto bits_7_to_11 = c & 0b0001'1111;
+ if (bits_7_to_11 < 0b10)
+ return false; // invalid sequence (US-ASCII encoded in two octets)
+ if (bits_7_to_11 > 0b11) // more than the two LSB
+ return false; // outside L1
+ } else if ((c & 0b1100'0000) == 0b1000'0000) { // trailing UTF-8 octet
+ if (!inMultibyte)
+ return false; // invalid sequence
+ inMultibyte = false; // only one continuation allowed
+ } else {
+ return false; // invalid sequence or outside of L1
+ }
+ }
+ }
+ if (inMultibyte)
+ return false; // invalid sequence: premature end
+ return true;
+}
+
+static constexpr auto isValidHttpHeaderNameChar = [](uchar c) noexcept
+{
+ // RFC 9110 Chapters "5.1 Field Names" and "5.6.2 Tokens"
+ // field-name = token
+ // token = 1*tchar
+ // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" /
+ // "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
+ // / DIGIT / ALPHA
+ // ; any VCHAR, except delimiters
+ // (for explanation on VCHAR see isValidHttpHeaderValueChar)
+ return (('A' <= c && c <= 'Z')
+ || ('a' <= c && c <= 'z')
+ || ('0' <= c && c <= '9')
+ || ('#' <= c && c <= '\'')
+ || ('^' <= c && c <= '`')
+ || c == '|' || c == '~' || c == '!' || c == '*' || c == '+' || c == '-' || c == '.');
+};
+
+static bool headerNameValidImpl(QLatin1StringView name) noexcept
+{
+ return std::all_of(name.begin(), name.end(), isValidHttpHeaderNameChar);
+}
+
+static bool headerNameValidImpl(QUtf8StringView name) noexcept
+{
+ // Traversing the UTF-8 string char-by-char is fine in this case as
+ // the isValidHttpHeaderNameChar rejects any value above 0x7E. UTF-8
+ // only has bytes <= 0x7F if they truly represent that ASCII character.
+ return headerNameValidImpl(QLatin1StringView(QByteArrayView(name)));
+}
+
+static bool headerNameValidImpl(QStringView name) noexcept
+{
+ return std::all_of(name.begin(), name.end(), [](QChar c) {
+ return isValidHttpHeaderNameChar(c.toLatin1());
+ });
+}
+
+static bool isValidHttpHeaderNameField(QAnyStringView name) noexcept
+{
+ if (name.isEmpty()) {
+ qCWarning(lcQHttpHeaders, "HTTP header name cannot be empty");
+ return false;
+ }
+ const bool valid = name.visit([](auto name){ return headerNameValidImpl(name); });
+ if (!valid)
+ qCWarning(lcQHttpHeaders, "HTTP header name contained illegal character(s)");
+ return valid;
+}
+
+static constexpr auto isValidHttpHeaderValueChar = [](uchar c) noexcept
+{
+ // RFC 9110 Chapter 5.5, Field Values
+ // field-value = *field-content
+ // field-content = field-vchar
+ // [ 1*( SP / HTAB / field-vchar ) field-vchar ]
+ // field-vchar = VCHAR / obs-text
+ // obs-text = %x80-FF
+ // VCHAR is defined as "any visible US-ASCII character", and RFC 5234 B.1.
+ // defines it as %x21-7E
+ // Note: The ABNF above states that field-content and thus field-value cannot
+ // start or end with SP/HTAB. The caller should handle this.
+ return (c >= 0x80 // obs-text (extended ASCII)
+ || (0x20 <= c && c <= 0x7E) // SP (0x20) + VCHAR
+ || (c == 0x09)); // HTAB
+};
+
+static bool headerValueValidImpl(QLatin1StringView value) noexcept
+{
+ return std::all_of(value.begin(), value.end(), isValidHttpHeaderValueChar);
+}
+
+static bool headerValueValidImpl(QUtf8StringView value) noexcept
+{
+ if (!isUtf8Latin1Representable(value)) // TODO FIXME see the function
+ return false;
+ return std::all_of(value.begin(), value.end(), isValidHttpHeaderValueChar);
+}
+
+static bool headerValueValidImpl(QStringView value) noexcept
+{
+ return std::all_of(value.begin(), value.end(), [](QChar c) {
+ return isValidHttpHeaderValueChar(c.toLatin1());
+ });
+}
+
+static bool isValidHttpHeaderValueField(QAnyStringView value) noexcept
+{
+ const bool valid = value.visit([](auto value){ return headerValueValidImpl(value); });
+ if (!valid)
+ qCWarning(lcQHttpHeaders, "HTTP header value contained illegal character(s)");
+ return valid;
+}
+
+static QByteArray normalizedValue(QAnyStringView value)
+{
+ // Note on trimming away any leading or trailing whitespace of 'value':
+ // RFC 9110 (HTTP 1.1, 2022, Chapter 5.5) does not allow leading or trailing whitespace
+ // RFC 7230 (HTTP 1.1, 2014, Chapter 3.2) allows them optionally, but also mandates that
+ // they are ignored during processing
+ // RFC 7540 (HTTP/2) does not seem explicit about it
+ // => for maximum compatibility, trim away any leading or trailing whitespace
+ return value.visit([](auto value){ return fieldToByteArray(value); }).trimmed();
+}
+
+/*!
+ Appends a header entry with \a name and \a value and returns \c true
+ if successful.
+
+ \sa append(QHttpHeaders::WellKnownHeader, QAnyStringView)
+ \sa {Allowed field name and value characters}
+*/
+bool QHttpHeaders::append(QAnyStringView name, QAnyStringView value)
+{
+ if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value))
+ return false;
+
+ d.detach();
+ d->headers.push_back({HeaderName{name}, normalizedValue(value)});
+ return true;
+}
+
+/*!
+ \overload append(QAnyStringView, QAnyStringView)
+*/
+bool QHttpHeaders::append(WellKnownHeader name, QAnyStringView value)
+{
+ if (!isValidHttpHeaderValueField(value))
+ return false;
+
+ d.detach();
+ d->headers.push_back({HeaderName{name}, normalizedValue(value)});
+ return true;
+}
+
+/*!
+ Inserts a header entry at index \a i, with \a name and \a value. The index
+ must be valid (see \l size()). Returns whether the insert succeeded.
+
+ \sa append(),
+ insert(qsizetype, QHttpHeaders::WellKnownHeader, QAnyStringView), size()
+ \sa {Allowed field name and value characters}
+*/
+bool QHttpHeaders::insert(qsizetype i, QAnyStringView name, QAnyStringView value)
+{
+ verify(i, 0);
+ if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(value))
+ return false;
+
+ d.detach();
+ d->headers.insert(i, {HeaderName{name}, normalizedValue(value)});
+ return true;
+}
+
+/*!
+ \overload insert(qsizetype, QAnyStringView, QAnyStringView)
+*/
+bool QHttpHeaders::insert(qsizetype i, WellKnownHeader name, QAnyStringView value)
+{
+ verify(i, 0);
+ if (!isValidHttpHeaderValueField(value))
+ return false;
+
+ d.detach();
+ d->headers.insert(i, {HeaderName{name}, normalizedValue(value)});
+ return true;
+}
+
+/*!
+ Replaces the header entry at index \a i, with \a name and \a newValue.
+ The index must be valid (see \l size()). Returns whether the replace
+ succeeded.
+
+ \sa append(),
+ replace(qsizetype, QHttpHeaders::WellKnownHeader, QAnyStringView), size()
+ \sa {Allowed field name and value characters}
+*/
+bool QHttpHeaders::replace(qsizetype i, QAnyStringView name, QAnyStringView newValue)
+{
+ verify(i);
+ if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(newValue))
+ return false;
+
+ d.detach();
+ d->headers.replace(i, {HeaderName{name}, normalizedValue(newValue)});
+ return true;
+}
+
+/*!
+ \overload replace(qsizetype, QAnyStringView, QAnyStringView)
+*/
+bool QHttpHeaders::replace(qsizetype i, WellKnownHeader name, QAnyStringView newValue)
+{
+ verify(i);
+ if (!isValidHttpHeaderValueField(newValue))
+ return false;
+
+ d.detach();
+ d->headers.replace(i, {HeaderName{name}, normalizedValue(newValue)});
+ return true;
+}
+
+/*!
+ \since 6.8
+
+ If QHttpHeaders already contains \a name, replaces its value with
+ \a newValue and removes possible additional \a name entries.
+ If \a name didn't exist, appends a new entry. Returns \c true
+ if successful.
+
+ This function is a convenience method for setting a unique
+ \a name : \a newValue header. For most headers the relative order does not
+ matter, which allows reusing an existing entry if one exists.
+
+ \sa replaceOrAppend(QAnyStringView, QAnyStringView)
+*/
+bool QHttpHeaders::replaceOrAppend(WellKnownHeader name, QAnyStringView newValue)
+{
+ if (isEmpty())
+ return append(name, newValue);
+
+ if (!isValidHttpHeaderValueField(newValue))
+ return false;
+
+ QHttpHeadersPrivate::replaceOrAppend(d, HeaderName{name}, normalizedValue(newValue));
+ return true;
+}
+
+/*!
+ \overload replaceOrAppend(WellKnownHeader, QAnyStringView)
+*/
+bool QHttpHeaders::replaceOrAppend(QAnyStringView name, QAnyStringView newValue)
+{
+ if (isEmpty())
+ return append(name, newValue);
+
+ if (!isValidHttpHeaderNameField(name) || !isValidHttpHeaderValueField(newValue))
+ return false;
+
+ QHttpHeadersPrivate::replaceOrAppend(d, HeaderName{name}, normalizedValue(newValue));
+ return true;
+}
+
+/*!
+ Returns whether the headers contain header with \a name.
+
+ \sa contains(QHttpHeaders::WellKnownHeader)
+*/
+bool QHttpHeaders::contains(QAnyStringView name) const
+{
+ if (isEmpty())
+ return false;
+
+ return std::any_of(d->headers.cbegin(), d->headers.cend(), headerNameMatches(HeaderName{name}));
+}
+
+/*!
+ \overload has(QAnyStringView)
+*/
+bool QHttpHeaders::contains(WellKnownHeader name) const
+{
+ if (isEmpty())
+ return false;
+
+ return std::any_of(d->headers.cbegin(), d->headers.cend(), headerNameMatches(HeaderName{name}));
+}
+
+/*!
+ Removes the header \a name.
+
+ \sa removeAt(), removeAll(QHttpHeaders::WellKnownHeader)
+*/
+void QHttpHeaders::removeAll(QAnyStringView name)
+{
+ if (isEmpty())
+ return;
+
+ return QHttpHeadersPrivate::removeAll(d, HeaderName(name));
+}
+
+/*!
+ \overload removeAll(QAnyStringView)
+*/
+void QHttpHeaders::removeAll(WellKnownHeader name)
+{
+ if (isEmpty())
+ return;
+
+ return QHttpHeadersPrivate::removeAll(d, HeaderName(name));
+}
+
+/*!
+ Removes the header at index \a i. The index \a i must be valid
+ (see \l size()).
+
+ \sa removeAll(QHttpHeaders::WellKnownHeader),
+ removeAll(QAnyStringView), size()
+*/
+void QHttpHeaders::removeAt(qsizetype i)
+{
+ verify(i);
+ d.detach();
+ d->headers.removeAt(i);
+}
+
+/*!
+ Returns the value of the (first) header \a name, or \a defaultValue if it
+ doesn't exist.
+
+ \sa value(QHttpHeaders::WellKnownHeader, QByteArrayView)
+*/
+QByteArrayView QHttpHeaders::value(QAnyStringView name, QByteArrayView defaultValue) const noexcept
+{
+ if (isEmpty())
+ return defaultValue;
+
+ return d->value(HeaderName{name}, defaultValue);
+}
+
+/*!
+ \overload value(QAnyStringView, QByteArrayView)
+*/
+QByteArrayView QHttpHeaders::value(WellKnownHeader name, QByteArrayView defaultValue) const noexcept
+{
+ if (isEmpty())
+ return defaultValue;
+
+ return d->value(HeaderName{name}, defaultValue);
+}
+
+/*!
+ Returns the values of header \a name in a list. Returns an empty
+ list if header with \a name doesn't exist.
+
+ \sa values(QHttpHeaders::WellKnownHeader)
+*/
+QList<QByteArray> QHttpHeaders::values(QAnyStringView name) const
+{
+ QList<QByteArray> result;
+ if (isEmpty())
+ return result;
+
+ d->values(HeaderName{name}, result);
+ return result;
+}
+
+/*!
+ \overload values(QAnyStringView)
+*/
+QList<QByteArray> QHttpHeaders::values(WellKnownHeader name) const
+{
+ QList<QByteArray> result;
+ if (isEmpty())
+ return result;
+
+ d->values(HeaderName{name}, result);
+ return result;
+}
+
+/*!
+ Returns the header value at index \a i. The index \a i must be valid
+ (see \l size()).
+
+ \sa size(), value(), values(), combinedValue(), nameAt()
+*/
+QByteArrayView QHttpHeaders::valueAt(qsizetype i) const noexcept
+{
+ verify(i);
+ return d->headers.at(i).value;
+}
+
+/*!
+ Returns the header name at index \a i. The index \a i must be valid
+ (see \l size()).
+
+ Header names are case-insensitive, and the returned names are lower-cased.
+
+ \sa size(), valueAt()
+*/
+QLatin1StringView QHttpHeaders::nameAt(qsizetype i) const noexcept
+{
+ verify(i);
+ return QLatin1StringView{d->headers.at(i).name.asView()};
+}
+
+/*!
+ Returns the values of header \a name in a comma-combined string.
+ Returns a \c null QByteArray if the header with \a name doesn't
+ exist.
+
+ \note Accessing the value(s) of 'Set-Cookie' header this way may not work
+ as intended. It is a notable exception in the
+ \l {https://datatracker.ietf.org/doc/html/rfc9110#name-field-order}{HTTP RFC}
+ in that its values cannot be combined this way. Prefer \l values() instead.
+
+ \sa values(QAnyStringView)
+*/
+QByteArray QHttpHeaders::combinedValue(QAnyStringView name) const
+{
+ QByteArray result;
+ if (isEmpty())
+ return result;
+
+ d->combinedValue(HeaderName{name}, result);
+ return result;
+}
+
+/*!
+ \overload combinedValue(QAnyStringView)
+*/
+QByteArray QHttpHeaders::combinedValue(WellKnownHeader name) const
+{
+ QByteArray result;
+ if (isEmpty())
+ return result;
+
+ d->combinedValue(HeaderName{name}, result);
+ return result;
+}
+
+/*!
+ Returns the number of header entries.
+*/
+qsizetype QHttpHeaders::size() const noexcept
+{
+ if (!d)
+ return 0;
+ return d->headers.size();
+}
+
+/*!
+ Attempts to allocate memory for at least \a size header entries.
+
+ If you know in advance how how many header entries there will
+ be, you may call this function to prevent reallocations
+ and memory fragmentation.
+*/
+void QHttpHeaders::reserve(qsizetype size)
+{
+ d.detach();
+ d->headers.reserve(size);
+}
+
+/*!
+ \fn bool QHttpHeaders::isEmpty() const noexcept
+
+ Returns \c true if the headers have size 0; otherwise returns \c false.
+
+ \sa size()
+*/
+
+/*!
+ Returns a header name corresponding to the provided \a name as a view.
+*/
+QByteArrayView QHttpHeaders::wellKnownHeaderName(WellKnownHeader name) noexcept
+{
+ return headerNames[qToUnderlying(name)];
+}
+
+/*!
+ Returns the header entries as a list of (name, value) pairs.
+ Header names are case-insensitive, and the returned names are lower-cased.
+*/
+QList<std::pair<QByteArray, QByteArray>> QHttpHeaders::toListOfPairs() const
+{
+ QList<std::pair<QByteArray, QByteArray>> list;
+ if (isEmpty())
+ return list;
+ list.reserve(size());
+ for (const auto & h : std::as_const(d->headers))
+ list.append({h.name.asByteArray(), h.value});
+ return list;
+}
+
+/*!
+ Returns the header entries as a map from name to value(s).
+ Header names are case-insensitive, and the returned names are lower-cased.
+*/
+QMultiMap<QByteArray, QByteArray> QHttpHeaders::toMultiMap() const
+{
+ QMultiMap<QByteArray, QByteArray> map;
+ if (isEmpty())
+ return map;
+ for (const auto &h : std::as_const(d->headers))
+ map.insert(h.name.asByteArray(), h.value);
+ return map;
+}
+
+/*!
+ Returns the header entries as a hash from name to value(s).
+ Header names are case-insensitive, and the returned names are lower-cased.
+*/
+QMultiHash<QByteArray, QByteArray> QHttpHeaders::toMultiHash() const
+{
+ QMultiHash<QByteArray, QByteArray> hash;
+ if (isEmpty())
+ return hash;
+ hash.reserve(size());
+ for (const auto &h : std::as_const(d->headers))
+ hash.insert(h.name.asByteArray(), h.value);
+ return hash;
+}
+
+/*!
+ Clears all header entries.
+
+ \sa size()
+*/
+void QHttpHeaders::clear()
+{
+ if (isEmpty())
+ return;
+ d.detach();
+ d->headers.clear();
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttpheaders.h b/src/network/access/qhttpheaders.h
new file mode 100644
index 0000000000..260df1421b
--- /dev/null
+++ b/src/network/access/qhttpheaders.h
@@ -0,0 +1,282 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QHTTPHEADERS_H
+#define QHTTPHEADERS_H
+
+#include <QtNetwork/qtnetworkglobal.h>
+#include <QtCore/qmetaobject.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qcontainerfwd.h>
+
+QT_BEGIN_NAMESPACE
+
+class QDebug;
+
+class QHttpHeadersPrivate;
+QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QHttpHeadersPrivate, Q_NETWORK_EXPORT)
+class QHttpHeaders
+{
+ Q_GADGET_EXPORT(Q_NETWORK_EXPORT)
+public:
+ enum class WellKnownHeader {
+ // IANA Permanent status:
+ AIM,
+ Accept,
+ AcceptAdditions,
+ AcceptCH,
+ AcceptDatetime,
+ AcceptEncoding,
+ AcceptFeatures,
+ AcceptLanguage,
+ AcceptPatch,
+ AcceptPost,
+ AcceptRanges,
+ AcceptSignature,
+ AccessControlAllowCredentials,
+ AccessControlAllowHeaders,
+ AccessControlAllowMethods,
+ AccessControlAllowOrigin,
+ AccessControlExposeHeaders,
+ AccessControlMaxAge,
+ AccessControlRequestHeaders,
+ AccessControlRequestMethod,
+ Age,
+ Allow,
+ ALPN,
+ AltSvc,
+ AltUsed,
+ Alternates,
+ ApplyToRedirectRef,
+ AuthenticationControl,
+ AuthenticationInfo,
+ Authorization,
+ CacheControl,
+ CacheStatus,
+ CalManagedID,
+ CalDAVTimezones,
+ CapsuleProtocol,
+ CDNCacheControl,
+ CDNLoop,
+ CertNotAfter,
+ CertNotBefore,
+ ClearSiteData,
+ ClientCert,
+ ClientCertChain,
+ Close,
+ Connection,
+ ContentDigest,
+ ContentDisposition,
+ ContentEncoding,
+ ContentID,
+ ContentLanguage,
+ ContentLength,
+ ContentLocation,
+ ContentRange,
+ ContentSecurityPolicy,
+ ContentSecurityPolicyReportOnly,
+ ContentType,
+ Cookie,
+ CrossOriginEmbedderPolicy,
+ CrossOriginEmbedderPolicyReportOnly,
+ CrossOriginOpenerPolicy,
+ CrossOriginOpenerPolicyReportOnly,
+ CrossOriginResourcePolicy,
+ DASL,
+ Date,
+ DAV,
+ DeltaBase,
+ Depth,
+ Destination,
+ DifferentialID,
+ DPoP,
+ DPoPNonce,
+ EarlyData,
+ ETag,
+ Expect,
+ ExpectCT,
+ Expires,
+ Forwarded,
+ From,
+ Hobareg,
+ Host,
+ If,
+ IfMatch,
+ IfModifiedSince,
+ IfNoneMatch,
+ IfRange,
+ IfScheduleTagMatch,
+ IfUnmodifiedSince,
+ IM,
+ IncludeReferredTokenBindingID,
+ KeepAlive,
+ Label,
+ LastEventID,
+ LastModified,
+ Link,
+ Location,
+ LockToken,
+ MaxForwards,
+ MementoDatetime,
+ Meter,
+ MIMEVersion,
+ Negotiate,
+ NEL,
+ ODataEntityId,
+ ODataIsolation,
+ ODataMaxVersion,
+ ODataVersion,
+ OptionalWWWAuthenticate,
+ OrderingType,
+ Origin,
+ OriginAgentCluster,
+ OSCORE,
+ OSLCCoreVersion,
+ Overwrite,
+ PingFrom,
+ PingTo,
+ Position,
+ Prefer,
+ PreferenceApplied,
+ Priority,
+ ProxyAuthenticate,
+ ProxyAuthenticationInfo,
+ ProxyAuthorization,
+ ProxyStatus,
+ PublicKeyPins,
+ PublicKeyPinsReportOnly,
+ Range,
+ RedirectRef,
+ Referer,
+ Refresh,
+ ReplayNonce,
+ ReprDigest,
+ RetryAfter,
+ ScheduleReply,
+ ScheduleTag,
+ SecPurpose,
+ SecTokenBinding,
+ SecWebSocketAccept,
+ SecWebSocketExtensions,
+ SecWebSocketKey,
+ SecWebSocketProtocol,
+ SecWebSocketVersion,
+ Server,
+ ServerTiming,
+ SetCookie,
+ Signature,
+ SignatureInput,
+ SLUG,
+ SoapAction,
+ StatusURI,
+ StrictTransportSecurity,
+ Sunset,
+ SurrogateCapability,
+ SurrogateControl,
+ TCN,
+ TE,
+ Timeout,
+ Topic,
+ Traceparent,
+ Tracestate,
+ Trailer,
+ TransferEncoding,
+ TTL,
+ Upgrade,
+ Urgency,
+ UserAgent,
+ VariantVary,
+ Vary,
+ Via,
+ WantContentDigest,
+ WantReprDigest,
+ WWWAuthenticate,
+ XContentTypeOptions,
+ XFrameOptions,
+ // IANA Deprecated status:
+ AcceptCharset,
+ CPEPInfo,
+ Pragma,
+ ProtocolInfo,
+ ProtocolQuery,
+ };
+ Q_ENUM(WellKnownHeader)
+
+ Q_NETWORK_EXPORT QHttpHeaders() noexcept;
+ Q_NETWORK_EXPORT ~QHttpHeaders();
+
+ Q_NETWORK_EXPORT QHttpHeaders(const QHttpHeaders &other);
+ QHttpHeaders(QHttpHeaders &&other) noexcept = default;
+ Q_NETWORK_EXPORT QHttpHeaders &operator=(const QHttpHeaders &other);
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QHttpHeaders)
+ void swap(QHttpHeaders &other) noexcept { d.swap(other.d); }
+
+ Q_NETWORK_EXPORT bool append(QAnyStringView name, QAnyStringView value);
+ Q_NETWORK_EXPORT bool append(WellKnownHeader name, QAnyStringView value);
+
+ Q_NETWORK_EXPORT bool insert(qsizetype i, QAnyStringView name, QAnyStringView value);
+ Q_NETWORK_EXPORT bool insert(qsizetype i, WellKnownHeader name, QAnyStringView value);
+
+ Q_NETWORK_EXPORT bool replace(qsizetype i, QAnyStringView name, QAnyStringView newValue);
+ Q_NETWORK_EXPORT bool replace(qsizetype i, WellKnownHeader name, QAnyStringView newValue);
+
+ Q_NETWORK_EXPORT bool replaceOrAppend(QAnyStringView name, QAnyStringView newValue);
+ Q_NETWORK_EXPORT bool replaceOrAppend(WellKnownHeader name, QAnyStringView newValue);
+
+ Q_NETWORK_EXPORT bool contains(QAnyStringView name) const;
+ Q_NETWORK_EXPORT bool contains(WellKnownHeader name) const;
+
+ Q_NETWORK_EXPORT void clear();
+ Q_NETWORK_EXPORT void removeAll(QAnyStringView name);
+ Q_NETWORK_EXPORT void removeAll(WellKnownHeader name);
+ Q_NETWORK_EXPORT void removeAt(qsizetype i);
+
+ Q_NETWORK_EXPORT QByteArrayView value(QAnyStringView name, QByteArrayView defaultValue = {}) const noexcept;
+ Q_NETWORK_EXPORT QByteArrayView value(WellKnownHeader name, QByteArrayView defaultValue = {}) const noexcept;
+
+ Q_NETWORK_EXPORT QList<QByteArray> values(QAnyStringView name) const;
+ Q_NETWORK_EXPORT QList<QByteArray> values(WellKnownHeader name) const;
+
+ Q_NETWORK_EXPORT QByteArrayView valueAt(qsizetype i) const noexcept;
+ Q_NETWORK_EXPORT QLatin1StringView nameAt(qsizetype i) const noexcept;
+
+ Q_NETWORK_EXPORT QByteArray combinedValue(QAnyStringView name) const;
+ Q_NETWORK_EXPORT QByteArray combinedValue(WellKnownHeader name) const;
+
+ Q_NETWORK_EXPORT qsizetype size() const noexcept;
+ Q_NETWORK_EXPORT void reserve(qsizetype size);
+ bool isEmpty() const noexcept { return size() == 0; }
+
+ Q_NETWORK_EXPORT static QByteArrayView wellKnownHeaderName(WellKnownHeader name) noexcept;
+
+ Q_NETWORK_EXPORT static QHttpHeaders
+ fromListOfPairs(const QList<std::pair<QByteArray, QByteArray>> &headers);
+ Q_NETWORK_EXPORT static QHttpHeaders
+ fromMultiMap(const QMultiMap<QByteArray, QByteArray> &headers);
+ Q_NETWORK_EXPORT static QHttpHeaders
+ fromMultiHash(const QMultiHash<QByteArray, QByteArray> &headers);
+
+ Q_NETWORK_EXPORT QList<std::pair<QByteArray, QByteArray>> toListOfPairs() const;
+ Q_NETWORK_EXPORT QMultiMap<QByteArray, QByteArray> toMultiMap() const;
+ Q_NETWORK_EXPORT QMultiHash<QByteArray, QByteArray> toMultiHash() const;
+
+private:
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QHttpHeaders &headers);
+#endif
+ Q_ALWAYS_INLINE void verify([[maybe_unused]] qsizetype pos = 0,
+ [[maybe_unused]] qsizetype n = 1) const
+ {
+ Q_ASSERT(pos >= 0);
+ Q_ASSERT(pos <= size());
+ Q_ASSERT(n >= 0);
+ Q_ASSERT(n <= size() - pos);
+ }
+ QExplicitlySharedDataPointer<QHttpHeadersPrivate> d;
+};
+
+Q_DECLARE_SHARED(QHttpHeaders)
+
+QT_END_NAMESPACE
+
+#endif // QHTTPHEADERS_H
diff --git a/src/network/access/qhttpheadershelper.cpp b/src/network/access/qhttpheadershelper.cpp
new file mode 100644
index 0000000000..d3cc9e439f
--- /dev/null
+++ b/src/network/access/qhttpheadershelper.cpp
@@ -0,0 +1,25 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qhttpheadershelper_p.h"
+
+#include <QtNetwork/qhttpheaders.h>
+
+QT_BEGIN_NAMESPACE
+
+bool QHttpHeadersHelper::compareStrict(const QHttpHeaders &left, const QHttpHeaders &right)
+{
+ if (left.size() != right.size())
+ return false;
+
+ for (qsizetype i = 0; i < left.size(); ++i) {
+ if (left.nameAt(i) != right.nameAt(i))
+ return false;
+ if (left.valueAt(i) != right.valueAt(i))
+ return false;
+ }
+
+ return true;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qhttpheadershelper_p.h b/src/network/access/qhttpheadershelper_p.h
new file mode 100644
index 0000000000..d1e38a1a8e
--- /dev/null
+++ b/src/network/access/qhttpheadershelper_p.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QHTTPHEADERSHELPER_H
+#define QHTTPHEADERSHELPER_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtNetwork/private/qtnetworkglobal_p.h>
+
+QT_BEGIN_NAMESPACE
+
+class QHttpHeaders;
+
+namespace QHttpHeadersHelper {
+ Q_NETWORK_EXPORT bool compareStrict(const QHttpHeaders &left, const QHttpHeaders &right);
+};
+
+QT_END_NAMESPACE
+
+#endif // QHTTPHEADERSHELPER_H
diff --git a/src/network/access/qhttpmultipart.cpp b/src/network/access/qhttpmultipart.cpp
index f24c06dda3..a695969f00 100644
--- a/src/network/access/qhttpmultipart.cpp
+++ b/src/network/access/qhttpmultipart.cpp
@@ -386,10 +386,12 @@ void QHttpPartPrivate::checkHeaderCreated() const
{
if (!headerCreated) {
// copied from QHttpNetworkRequestPrivate::header() and adapted
- QList<QPair<QByteArray, QByteArray> > fields = allRawHeaders();
- QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
- for (; it != fields.constEnd(); ++it)
- header += it->first + ": " + it->second + "\r\n";
+ const auto h = headers();
+ for (qsizetype i = 0; i < h.size(); ++i) {
+ const auto name = h.nameAt(i);
+ header += QByteArrayView(name.data(), name.size()) + ": " + h.valueAt(i) + "\r\n";
+ }
+
header += "\r\n";
headerCreated = true;
}
@@ -512,6 +514,48 @@ qint64 QHttpMultiPartIODevice::writeData(const char *data, qint64 maxSize)
return -1;
}
+#ifndef QT_NO_DEBUG_STREAM
+
+/*!
+ \fn QDebug QHttpPart::operator<<(QDebug debug, const QHttpPart &part)
+
+ Writes the \a part into the \a debug object for debugging purposes.
+ Unless a device is set, the size of the body is shown.
+
+ \sa {Debugging Techniques}
+ \since 6.8
+*/
+
+QDebug operator<<(QDebug debug, const QHttpPart &part)
+{
+ const QDebugStateSaver saver(debug);
+ debug.resetFormat().nospace().noquote();
+
+ debug << "QHttpPart(headers = ["
+ << part.d->cookedHeaders
+ << "], http headers = ["
+ << part.d->httpHeaders
+ << "],";
+
+ if (part.d->bodyDevice) {
+ debug << " bodydevice = ["
+ << part.d->bodyDevice
+ << ", is open: "
+ << part.d->bodyDevice->isOpen()
+ << "]";
+ } else {
+ debug << " size of body = "
+ << part.d->body.size()
+ << " bytes";
+ }
+
+ debug << ")";
+
+ return debug;
+}
+
+#endif // QT_NO_DEBUG_STREAM
+
QT_END_NAMESPACE
diff --git a/src/network/access/qhttpmultipart.h b/src/network/access/qhttpmultipart.h
index 26e5fafdf2..9732bbd99d 100644
--- a/src/network/access/qhttpmultipart.h
+++ b/src/network/access/qhttpmultipart.h
@@ -19,6 +19,7 @@ QT_BEGIN_NAMESPACE
class QHttpPartPrivate;
class QHttpMultiPart;
+class QDebug;
class Q_NETWORK_EXPORT QHttpPart
{
@@ -45,6 +46,9 @@ private:
QSharedDataPointer<QHttpPartPrivate> d;
friend class QHttpMultiPartIODevice;
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QHttpPart &httpPart);
+#endif
};
Q_DECLARE_SHARED(QHttpPart)
diff --git a/src/network/access/qhttpmultipart_p.h b/src/network/access/qhttpmultipart_p.h
index d485fcf5cd..7a12ce8424 100644
--- a/src/network/access/qhttpmultipart_p.h
+++ b/src/network/access/qhttpmultipart_p.h
@@ -18,6 +18,7 @@
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "QtCore/qshareddata.h"
#include "qnetworkrequest_p.h" // for deriving QHttpPartPrivate from QNetworkHeadersPrivate
+#include "qhttpheadershelper_p.h"
#include "private/qobject_p.h"
#ifndef Q_OS_WASM
@@ -47,8 +48,10 @@ public:
inline bool operator==(const QHttpPartPrivate &other) const
{
- return rawHeaders == other.rawHeaders && body == other.body &&
- bodyDevice == other.bodyDevice && readPointer == other.readPointer;
+ return QHttpHeadersHelper::compareStrict(httpHeaders, other.httpHeaders)
+ && body == other.body
+ && bodyDevice == other.bodyDevice
+ && readPointer == other.readPointer;
}
void setBodyDevice(QIODevice *device) {
diff --git a/src/network/access/qhttpnetworkconnection.cpp b/src/network/access/qhttpnetworkconnection.cpp
index 73bf8bebc3..3ef07c6993 100644
--- a/src/network/access/qhttpnetworkconnection.cpp
+++ b/src/network/access/qhttpnetworkconnection.cpp
@@ -13,10 +13,13 @@
#include <qauthenticator.h>
#include <qcoreapplication.h>
#include <private/qdecompresshelper_p.h>
+#include <private/qsocketabstraction_p.h>
#include <qbuffer.h>
#include <qpair.h>
#include <qdebug.h>
+#include <qspan.h>
+#include <qvarlengtharray.h>
#ifndef QT_NO_SSL
# include <private/qsslsocket_p.h>
@@ -32,51 +35,42 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-const int QHttpNetworkConnectionPrivate::defaultHttpChannelCount = 6;
-
// The pipeline length. So there will be 4 requests in flight.
const int QHttpNetworkConnectionPrivate::defaultPipelineLength = 3;
// Only re-fill the pipeline if there's defaultRePipelineLength slots free in the pipeline.
// This means that there are 2 requests in flight and 2 slots free that will be re-filled.
const int QHttpNetworkConnectionPrivate::defaultRePipelineLength = 2;
-
-QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(const QString &hostName,
- quint16 port, bool encrypt,
- QHttpNetworkConnection::ConnectionType type)
-: state(RunningState),
- networkLayerState(Unknown),
- hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true)
- , activeChannelCount(type == QHttpNetworkConnection::ConnectionTypeHTTP2
- || type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct
- ? 1 : defaultHttpChannelCount)
- , channelCount(defaultHttpChannelCount)
-#ifndef QT_NO_NETWORKPROXY
- , networkProxy(QNetworkProxy::NoProxy)
-#endif
- , preConnectRequests(0)
- , connectionType(type)
+static int getPreferredActiveChannelCount(QHttpNetworkConnection::ConnectionType type,
+ int defaultValue)
{
- // We allocate all 6 channels even if it's HTTP/2 enabled connection:
- // in case the protocol negotiation via NPN/ALPN fails, we will have
- // normally working HTTP/1.1.
- Q_ASSERT(channelCount >= activeChannelCount);
- channels = new QHttpNetworkConnectionChannel[channelCount];
+ return (type == QHttpNetworkConnection::ConnectionTypeHTTP2
+ || type == QHttpNetworkConnection::ConnectionTypeHTTP2Direct)
+ ? 1
+ : defaultValue;
}
-QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName,
- quint16 port, bool encrypt,
- QHttpNetworkConnection::ConnectionType type)
-: state(RunningState), networkLayerState(Unknown),
- hostName(hostName), port(port), encrypt(encrypt), delayIpv4(true),
- activeChannelCount(connectionCount), channelCount(connectionCount)
+QHttpNetworkConnectionPrivate::QHttpNetworkConnectionPrivate(
+ quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt,
+ bool isLocalSocket, QHttpNetworkConnection::ConnectionType type)
+ : hostName(hostName),
+ port(port),
+ encrypt(encrypt),
+ isLocalSocket(isLocalSocket),
+ activeChannelCount(getPreferredActiveChannelCount(type, connectionCount)),
+ channelCount(connectionCount),
+ channels(new QHttpNetworkConnectionChannel[channelCount]),
#ifndef QT_NO_NETWORKPROXY
- , networkProxy(QNetworkProxy::NoProxy)
+ networkProxy(QNetworkProxy::NoProxy),
#endif
- , preConnectRequests(0)
- , connectionType(type)
+ connectionType(type)
{
- channels = new QHttpNetworkConnectionChannel[channelCount];
+ if (isLocalSocket) // Don't try to do host lookup for local sockets
+ networkLayerState = IPv4;
+ // We allocate all 6 channels even if it's an HTTP/2-enabled
+ // connection: in case the protocol negotiation via NPN/ALPN fails,
+ // we will have normally working HTTP/1.1.
+ Q_ASSERT(channelCount >= activeChannelCount);
}
@@ -111,13 +105,18 @@ void QHttpNetworkConnectionPrivate::pauseConnection()
// Disable all socket notifiers
for (int i = 0; i < activeChannelCount; i++) {
- if (channels[i].socket) {
+ if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
#ifndef QT_NO_SSL
if (encrypt)
- QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
+ QSslSocketPrivate::pauseSocketNotifiers(static_cast<QSslSocket*>(absSocket));
else
#endif
- QAbstractSocketPrivate::pauseSocketNotifiers(channels[i].socket);
+ QAbstractSocketPrivate::pauseSocketNotifiers(absSocket);
+ } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
+ // @todo how would we do this?
+#if 0 // @todo Enable this when there is a debug category for this
+ qDebug() << "Should pause socket but there is no way to do it for local sockets";
+#endif
}
}
}
@@ -127,17 +126,21 @@ void QHttpNetworkConnectionPrivate::resumeConnection()
state = RunningState;
// Enable all socket notifiers
for (int i = 0; i < activeChannelCount; i++) {
- if (channels[i].socket) {
+ if (auto *absSocket = qobject_cast<QAbstractSocket *>(channels[i].socket)) {
#ifndef QT_NO_SSL
if (encrypt)
- QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(channels[i].socket));
+ QSslSocketPrivate::resumeSocketNotifiers(static_cast<QSslSocket*>(absSocket));
else
#endif
- QAbstractSocketPrivate::resumeSocketNotifiers(channels[i].socket);
+ QAbstractSocketPrivate::resumeSocketNotifiers(absSocket);
// Resume pending upload if needed
if (channels[i].state == QHttpNetworkConnectionChannel::WritingState)
QMetaObject::invokeMethod(&channels[i], "_q_uploadDataReadyRead", Qt::QueuedConnection);
+ } else if (qobject_cast<QLocalSocket *>(channels[i].socket)) {
+#if 0 // @todo Enable this when there is a debug category for this
+ qDebug() << "Should resume socket but there is no way to do it for local sockets";
+#endif
}
}
@@ -145,7 +148,7 @@ void QHttpNetworkConnectionPrivate::resumeConnection()
QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
}
-int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const
+int QHttpNetworkConnectionPrivate::indexOf(QIODevice *socket) const
{
for (int i = 0; i < activeChannelCount; ++i)
if (channels[i].socket == socket)
@@ -159,7 +162,7 @@ int QHttpNetworkConnectionPrivate::indexOf(QAbstractSocket *socket) const
// emitted. This function will check the status of the connection channels if we
// have not decided the networkLayerState and will return true if the channel error
// should be emitted by the channel.
-bool QHttpNetworkConnectionPrivate::shouldEmitChannelError(QAbstractSocket *socket)
+bool QHttpNetworkConnectionPrivate::shouldEmitChannelError(QIODevice *socket)
{
Q_Q(QHttpNetworkConnection);
@@ -217,6 +220,17 @@ qint64 QHttpNetworkConnectionPrivate::uncompressedBytesAvailableNextBlock(const
return reply.d_func()->responseData.sizeNextBlock();
}
+static QByteArray makeAcceptLanguage()
+{
+ QString systemLocale = QLocale::system().name();
+ if (systemLocale == "C"_L1)
+ return "en,*"_ba;
+ systemLocale.replace('_'_L1, '-'_L1);
+ if (systemLocale.startsWith("en-"_L1))
+ return (systemLocale + ",*"_L1).toLatin1();
+ return (systemLocale + ",en,*"_L1).toLatin1();
+}
+
void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
{
QHttpNetworkRequest &request = messagePair.first;
@@ -267,8 +281,8 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
value = request.headerField("accept-encoding");
if (value.isEmpty()) {
#ifndef QT_NO_COMPRESS
- const QByteArrayList &acceptedEncoding = QDecompressHelper::acceptedEncoding();
- request.setHeaderField("Accept-Encoding", acceptedEncoding.join(", "));
+ const static QByteArray acceptedEncoding = QDecompressHelper::acceptedEncoding().join(", ");
+ request.setHeaderField("Accept-Encoding", acceptedEncoding);
request.d->autoDecompress = true;
#else
// if zlib is not available set this to false always
@@ -281,17 +295,8 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
// not with us, but we work around this by setting
// one always.
value = request.headerField("accept-language");
- if (value.isEmpty()) {
- QString systemLocale = QLocale::system().name().replace(QChar::fromLatin1('_'),QChar::fromLatin1('-'));
- QString acceptLanguage;
- if (systemLocale == "C"_L1)
- acceptLanguage = QString::fromLatin1("en,*");
- else if (systemLocale.startsWith("en-"_L1))
- acceptLanguage = systemLocale + ",*"_L1;
- else
- acceptLanguage = systemLocale + ",en,*"_L1;
- request.setHeaderField("Accept-Language", std::move(acceptLanguage).toLatin1());
- }
+ if (value.isEmpty())
+ request.setHeaderField("Accept-Language", makeAcceptLanguage());
// set the User Agent
value = request.headerField("user-agent");
@@ -299,12 +304,17 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
request.setHeaderField("User-Agent", "Mozilla/5.0");
// set the host
value = request.headerField("host");
- if (value.isEmpty()) {
+ if (isLocalSocket && value.isEmpty()) {
+ // The local socket connections might have a full file path, and that
+ // may not be suitable for the Host header. But we can use whatever the
+ // user has set in the URL.
+ request.prependHeaderField("Host", request.url().host().toLocal8Bit());
+ } else if (value.isEmpty()) {
QHostAddress add;
QByteArray host;
if (add.setAddress(hostName)) {
if (add.protocol() == QAbstractSocket::IPv6Protocol)
- host = '[' + hostName.toLatin1() + ']'; //format the ipv6 in the standard way
+ host = (u'[' + hostName + u']').toLatin1(); //format the ipv6 in the standard way
else
host = hostName.toLatin1();
@@ -327,7 +337,7 @@ void QHttpNetworkConnectionPrivate::prepareRequest(HttpMessagePair &messagePair)
-void QHttpNetworkConnectionPrivate::emitReplyError(QAbstractSocket *socket,
+void QHttpNetworkConnectionPrivate::emitReplyError(QIODevice *socket,
QHttpNetworkReply *reply,
QNetworkReply::NetworkError errorCode)
{
@@ -392,7 +402,7 @@ void QHttpNetworkConnectionPrivate::copyCredentials(int fromChannel, QAuthentica
// handles the authentication for one channel and eventually re-starts the other channels
-bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply,
+bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QIODevice *socket, QHttpNetworkReply *reply,
bool isProxy, bool &resend)
{
Q_ASSERT(socket);
@@ -400,7 +410,7 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket
resend = false;
//create the response header to be used with QAuthenticatorPrivate.
- QList<QPair<QByteArray, QByteArray> > fields = reply->header();
+ const auto headers = reply->header();
// Check that any of the proposed authenticate methods are supported
const QByteArray header = isProxy ? "proxy-authenticate" : "www-authenticate";
@@ -416,14 +426,15 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket
if (auth->isNull())
auth->detach();
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(*auth);
- priv->parseHttpResponse(fields, isProxy, reply->url().host());
+ priv->parseHttpResponse(headers, isProxy, reply->url().host());
// Update method in case it changed
if (priv->method == QAuthenticatorPrivate::None)
return false;
if (priv->phase == QAuthenticatorPrivate::Done ||
(priv->phase == QAuthenticatorPrivate::Start
- && priv->method == QAuthenticatorPrivate::Ntlm)) {
+ && (priv->method == QAuthenticatorPrivate::Ntlm
+ || priv->method == QAuthenticatorPrivate::Negotiate))) {
if (priv->phase == QAuthenticatorPrivate::Start)
priv->phase = QAuthenticatorPrivate::Phase1;
@@ -493,7 +504,7 @@ bool QHttpNetworkConnectionPrivate::handleAuthenticateChallenge(QAbstractSocket
}
// Used by the HTTP1 code-path
-QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QAbstractSocket *socket,
+QUrl QHttpNetworkConnectionPrivate::parseRedirectResponse(QIODevice *socket,
QHttpNetworkReply *reply)
{
ParseRedirectResult result = parseRedirectResponse(reply);
@@ -511,12 +522,9 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
return {{}, QNetworkReply::NoError};
QUrl redirectUrl;
- const QList<QPair<QByteArray, QByteArray> > fields = reply->header();
- for (const QNetworkReply::RawHeaderPair &header : fields) {
- if (header.first.compare("location", Qt::CaseInsensitive) == 0) {
- redirectUrl = QUrl::fromEncoded(header.second);
- break;
- }
+ const QHttpHeaders fields = reply->header();
+ if (const auto h = fields.values(QHttpHeaders::WellKnownHeader::Location); !h.empty()) {
+ redirectUrl = QUrl::fromEncoded(h.first());
}
// If the location url is invalid/empty, we return ProtocolUnknownError
@@ -533,7 +541,9 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
// Check redirect url protocol
const QUrl priorUrl(reply->request().url());
- if (redirectUrl.scheme() == "http"_L1 || redirectUrl.scheme() == "https"_L1) {
+ const QString targetUrlScheme = redirectUrl.scheme();
+ if (targetUrlScheme == "http"_L1 || targetUrlScheme == "https"_L1
+ || targetUrlScheme.startsWith("unix"_L1)) {
switch (reply->request().redirectPolicy()) {
case QNetworkRequest::NoLessSafeRedirectPolicy:
// Here we could handle https->http redirects as InsecureProtocolError.
@@ -544,7 +554,7 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
break;
case QNetworkRequest::SameOriginRedirectPolicy:
if (priorUrl.host() != redirectUrl.host()
- || priorUrl.scheme() != redirectUrl.scheme()
+ || priorUrl.scheme() != targetUrlScheme
|| priorUrl.port() != redirectUrl.port()) {
return {{}, QNetworkReply::InsecureRedirectError};
}
@@ -560,7 +570,7 @@ QHttpNetworkConnectionPrivate::parseRedirectResponse(QHttpNetworkReply *reply)
return {std::move(redirectUrl), QNetworkReply::NoError};
}
-void QHttpNetworkConnectionPrivate::createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request)
+void QHttpNetworkConnectionPrivate::createAuthorization(QIODevice *socket, QHttpNetworkRequest &request)
{
Q_ASSERT(socket);
@@ -696,7 +706,7 @@ void QHttpNetworkConnectionPrivate::requeueRequest(const HttpMessagePair &pair)
QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
}
-bool QHttpNetworkConnectionPrivate::dequeueRequest(QAbstractSocket *socket)
+bool QHttpNetworkConnectionPrivate::dequeueRequest(QIODevice *socket)
{
int i = 0;
if (socket)
@@ -750,7 +760,7 @@ QHttpNetworkReply* QHttpNetworkConnectionPrivate::predictNextRequestsReply() con
}
// this is called from _q_startNextRequest and when a request has been sent down a socket from the channel
-void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket)
+void QHttpNetworkConnectionPrivate::fillPipeline(QIODevice *socket)
{
// return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
@@ -778,7 +788,7 @@ void QHttpNetworkConnectionPrivate::fillPipeline(QAbstractSocket *socket)
return;
// check if socket is connected
- if (socket->state() != QAbstractSocket::ConnectedState)
+ if (QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState)
return;
// check for resendCurrent
@@ -872,16 +882,15 @@ bool QHttpNetworkConnectionPrivate::fillPipeline(QList<HttpMessagePair> &queue,
}
-QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket, const QString &extraDetail)
+QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError errorCode, QIODevice *socket, const QString &extraDetail)
{
QString errorString;
switch (errorCode) {
- case QNetworkReply::HostNotFoundError:
- if (socket)
- errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(socket->peerName());
- else
- errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(hostName);
+ case QNetworkReply::HostNotFoundError: {
+ const QString peerName = socket ? QSocketAbstraction::socketPeerName(socket) : hostName;
+ errorString = QCoreApplication::translate("QHttp", "Host %1 not found").arg(peerName);
break;
+ }
case QNetworkReply::ConnectionRefusedError:
errorString = QCoreApplication::translate("QHttp", "Connection refused");
break;
@@ -906,7 +915,7 @@ QString QHttpNetworkConnectionPrivate::errorDetail(QNetworkReply::NetworkError e
case QNetworkReply::SslHandshakeFailedError:
errorString = QCoreApplication::translate("QHttp", "SSL handshake failed");
if (socket)
- errorString += QStringLiteral(": ") + socket->errorString();
+ errorString += ": "_L1 + socket->errorString();
break;
case QNetworkReply::TooManyRedirectsError:
errorString = QCoreApplication::translate("QHttp", "Too many redirects");
@@ -978,19 +987,18 @@ void QHttpNetworkConnectionPrivate::removeReply(QHttpNetworkReply *reply)
return;
}
}
-#ifndef QT_NO_SSL
// is the reply inside the H2 pipeline of this channel already?
- QMultiMap<int, HttpMessagePair>::iterator it = channels[i].h2RequestsToSend.begin();
- QMultiMap<int, HttpMessagePair>::iterator end = channels[i].h2RequestsToSend.end();
- for (; it != end; ++it) {
- if (it.value().second == reply) {
- channels[i].h2RequestsToSend.remove(it.key());
-
- QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
- return;
- }
+ const auto foundReply = [reply](const HttpMessagePair &pair) {
+ return pair.second == reply;
+ };
+ auto &seq = channels[i].h2RequestsToSend;
+ const auto end = seq.cend();
+ auto it = std::find_if(seq.cbegin(), end, foundReply);
+ if (it != end) {
+ seq.erase(it);
+ QMetaObject::invokeMethod(q, "_q_startNextRequest", Qt::QueuedConnection);
+ return;
}
-#endif
}
// remove from the high priority queue
if (!highPriorityQueue.isEmpty()) {
@@ -1034,6 +1042,11 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
//resend the necessary ones.
for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].resendCurrent && (channels[i].state != QHttpNetworkConnectionChannel::ClosingState)) {
+ if (!channels[i].socket
+ || QSocketAbstraction::socketState(channels[i].socket) == QAbstractSocket::UnconnectedState) {
+ if (!channels[i].ensureConnection())
+ continue;
+ }
channels[i].resendCurrent = false;
// if this is not possible, error will be emitted and connection terminated
@@ -1054,7 +1067,9 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
// try to get a free AND connected socket
for (int i = 0; i < activeChannelCount; ++i) {
if (channels[i].socket) {
- if (!channels[i].reply && !channels[i].isSocketBusy() && channels[i].socket->state() == QAbstractSocket::ConnectedState) {
+ if (!channels[i].reply && !channels[i].isSocketBusy()
+ && QSocketAbstraction::socketState(channels[i].socket)
+ == QAbstractSocket::ConnectedState) {
if (dequeueRequest(channels[i].socket))
channels[i].sendRequest();
}
@@ -1074,7 +1089,8 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
else if (networkLayerState == IPv6)
channels[0].networkLayerPreference = QAbstractSocket::IPv6Protocol;
channels[0].ensureConnection();
- if (channels[0].socket && channels[0].socket->state() == QAbstractSocket::ConnectedState
+ if (auto *s = channels[0].socket; s
+ && QSocketAbstraction::socketState(s) == QAbstractSocket::ConnectedState
&& !channels[0].pendingEncrypt) {
if (channels[0].h2RequestsToSend.size()) {
channels[0].sendRequest();
@@ -1101,9 +1117,13 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
// return fast if there is nothing to pipeline
if (highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty())
return;
- for (int i = 0; i < activeChannelCount; i++)
- if (channels[i].socket && channels[i].socket->state() == QAbstractSocket::ConnectedState)
+ for (int i = 0; i < activeChannelCount; i++) {
+ if (channels[i].socket
+ && QSocketAbstraction::socketState(channels[i].socket)
+ == QAbstractSocket::ConnectedState) {
fillPipeline(channels[i].socket);
+ }
+ }
// If there is not already any connected channels we need to connect a new one.
// We do not pair the channel with the request until we know if it is
@@ -1121,23 +1141,24 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
if (neededOpenChannels <= 0)
return;
- QQueue<int> channelsToConnect;
+ QVarLengthArray<int> channelsToConnect;
// use previously used channels first
for (int i = 0; i < activeChannelCount && neededOpenChannels > 0; ++i) {
if (!channels[i].socket)
continue;
- if ((channels[i].socket->state() == QAbstractSocket::ConnectingState)
- || (channels[i].socket->state() == QAbstractSocket::HostLookupState)
+ using State = QAbstractSocket::SocketState;
+ if ((QSocketAbstraction::socketState(channels[i].socket) == State::ConnectingState)
+ || (QSocketAbstraction::socketState(channels[i].socket) == State::HostLookupState)
|| channels[i].pendingEncrypt) { // pendingEncrypt == "EncryptingState"
neededOpenChannels--;
continue;
}
if (!channels[i].reply && !channels[i].isSocketBusy()
- && (channels[i].socket->state() == QAbstractSocket::UnconnectedState)) {
- channelsToConnect.enqueue(i);
+ && (QSocketAbstraction::socketState(channels[i].socket) == State::UnconnectedState)) {
+ channelsToConnect.push_back(i);
neededOpenChannels--;
}
}
@@ -1147,12 +1168,14 @@ void QHttpNetworkConnectionPrivate::_q_startNextRequest()
if (channels[i].socket)
continue;
- channelsToConnect.enqueue(i);
+ channelsToConnect.push_back(i);
neededOpenChannels--;
}
- while (!channelsToConnect.isEmpty()) {
- const int channel = channelsToConnect.dequeue();
+ auto channelToConnectSpan = QSpan{channelsToConnect};
+ while (!channelToConnectSpan.isEmpty()) {
+ const int channel = channelToConnectSpan.front();
+ channelToConnectSpan = channelToConnectSpan.sliced(1);
if (networkLayerState == IPv4)
channels[channel].networkLayerPreference = QAbstractSocket::IPv4Protocol;
@@ -1256,8 +1279,16 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info)
networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
QMetaObject::invokeMethod(this->q_func(), "_q_startNextRequest", Qt::QueuedConnection);
} else {
+ auto lookupError = QNetworkReply::HostNotFoundError;
+#ifndef QT_NO_NETWORKPROXY
+ // if the proxy can lookup hostnames, all hostname lookups except for the lookup of the
+ // proxy hostname are delegated to the proxy.
+ auto proxyCapabilities = networkProxy.capabilities() | channels[0].proxy.capabilities();
+ if (proxyCapabilities & QNetworkProxy::HostNameLookupCapability)
+ lookupError = QNetworkReply::ProxyNotFoundError;
+#endif
if (dequeueRequest(channels[0].socket)) {
- emitReplyError(channels[0].socket, channels[0].reply, QNetworkReply::HostNotFoundError);
+ emitReplyError(channels[0].socket, channels[0].reply, lookupError);
networkLayerState = QHttpNetworkConnectionPrivate::Unknown;
} else if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
@@ -1265,7 +1296,7 @@ void QHttpNetworkConnectionPrivate::_q_hostLookupFinished(const QHostInfo &info)
// emit error for all replies
QHttpNetworkReply *currentReply = h2Pair.second;
Q_ASSERT(currentReply);
- emitReplyError(channels[0].socket, currentReply, QNetworkReply::HostNotFoundError);
+ emitReplyError(channels[0].socket, currentReply, lookupError);
}
} else {
// We can end up here if a request has been aborted or otherwise failed (e.g. timeout)
@@ -1324,22 +1355,10 @@ void QHttpNetworkConnectionPrivate::_q_connectDelayedChannel()
channels[1].ensureConnection();
}
-QHttpNetworkConnection::QHttpNetworkConnection(const QString &hostName, quint16 port, bool encrypt,
- QHttpNetworkConnection::ConnectionType connectionType, QObject *parent)
- : QObject(*(new QHttpNetworkConnectionPrivate(hostName, port, encrypt , connectionType)), parent)
-{
- Q_D(QHttpNetworkConnection);
- d->init();
- if (QNetworkConnectionMonitor::isEnabled()) {
- connect(&d->connectionMonitor, &QNetworkConnectionMonitor::reachabilityChanged,
- this, &QHttpNetworkConnection::onlineStateChanged, Qt::QueuedConnection);
- }
-}
-
QHttpNetworkConnection::QHttpNetworkConnection(quint16 connectionCount, const QString &hostName,
- quint16 port, bool encrypt, QObject *parent,
+ quint16 port, bool encrypt, bool isLocalSocket, QObject *parent,
QHttpNetworkConnection::ConnectionType connectionType)
- : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt,
+ : QObject(*(new QHttpNetworkConnectionPrivate(connectionCount, hostName, port, encrypt, isLocalSocket,
connectionType)), parent)
{
Q_D(QHttpNetworkConnection);
@@ -1423,9 +1442,9 @@ QNetworkProxy QHttpNetworkConnection::transparentProxy() const
}
#endif
-QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType()
+QHttpNetworkConnection::ConnectionType QHttpNetworkConnection::connectionType() const
{
- Q_D(QHttpNetworkConnection);
+ Q_D(const QHttpNetworkConnection);
return d->connectionType;
}
@@ -1460,9 +1479,9 @@ void QHttpNetworkConnection::setSslConfiguration(const QSslConfiguration &config
d->channels[i].setSslConfiguration(config);
}
-std::shared_ptr<QSslContext> QHttpNetworkConnection::sslContext()
+std::shared_ptr<QSslContext> QHttpNetworkConnection::sslContext() const
{
- Q_D(QHttpNetworkConnection);
+ Q_D(const QHttpNetworkConnection);
return d->sslContext;
}
diff --git a/src/network/access/qhttpnetworkconnection_p.h b/src/network/access/qhttpnetworkconnection_p.h
index c3556f6eff..5e4bce5eb0 100644
--- a/src/network/access/qhttpnetworkconnection_p.h
+++ b/src/network/access/qhttpnetworkconnection_p.h
@@ -52,7 +52,7 @@ class QSslContext;
#endif // !QT_NO_SSL
class QHttpNetworkConnectionPrivate;
-class Q_AUTOTEST_EXPORT QHttpNetworkConnection : public QObject
+class Q_NETWORK_EXPORT QHttpNetworkConnection : public QObject
{
Q_OBJECT
public:
@@ -63,11 +63,9 @@ public:
ConnectionTypeHTTP2Direct
};
- explicit QHttpNetworkConnection(const QString &hostName, quint16 port = 80, bool encrypt = false,
- ConnectionType connectionType = ConnectionTypeHTTP,
- QObject *parent = nullptr);
QHttpNetworkConnection(quint16 channelCount, const QString &hostName, quint16 port = 80,
- bool encrypt = false, QObject *parent = nullptr,
+ bool encrypt = false, bool isLocalSocket = false,
+ QObject *parent = nullptr,
ConnectionType connectionType = ConnectionTypeHTTP);
~QHttpNetworkConnection();
@@ -92,7 +90,7 @@ public:
QHttpNetworkConnectionChannel *channels() const;
- ConnectionType connectionType();
+ ConnectionType connectionType() const;
void setConnectionType(ConnectionType type);
QHttp2Configuration http2Parameters() const;
@@ -102,7 +100,7 @@ public:
void setSslConfiguration(const QSslConfiguration &config);
void ignoreSslErrors(int channel = -1);
void ignoreSslErrors(const QList<QSslError> &errors, int channel = -1);
- std::shared_ptr<QSslContext> sslContext();
+ std::shared_ptr<QSslContext> sslContext() const;
void setSslContext(std::shared_ptr<QSslContext> context);
#endif
@@ -137,8 +135,10 @@ typedef QPair<QHttpNetworkRequest, QHttpNetworkReply*> HttpMessagePair;
class QHttpNetworkConnectionPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QHttpNetworkConnection)
+ Q_DISABLE_COPY_MOVE(QHttpNetworkConnectionPrivate)
public:
- static const int defaultHttpChannelCount;
+ // Note: Only used from auto tests, normal usage is via QHttp1Configuration
+ static constexpr int defaultHttpChannelCount = 6;
static const int defaultPipelineLength;
static const int defaultRePipelineLength;
@@ -155,32 +155,31 @@ public:
IPv4or6
};
- QHttpNetworkConnectionPrivate(const QString &hostName, quint16 port, bool encrypt,
- QHttpNetworkConnection::ConnectionType type);
- QHttpNetworkConnectionPrivate(quint16 channelCount, const QString &hostName, quint16 port, bool encrypt,
+ QHttpNetworkConnectionPrivate(quint16 connectionCount, const QString &hostName, quint16 port,
+ bool encrypt, bool isLocalSocket,
QHttpNetworkConnection::ConnectionType type);
~QHttpNetworkConnectionPrivate();
void init();
void pauseConnection();
void resumeConnection();
- ConnectionState state;
- NetworkLayerPreferenceState networkLayerState;
+ ConnectionState state = RunningState;
+ NetworkLayerPreferenceState networkLayerState = Unknown;
enum { ChunkSize = 4096 };
- int indexOf(QAbstractSocket *socket) const;
+ int indexOf(QIODevice *socket) const;
QHttpNetworkReply *queueRequest(const QHttpNetworkRequest &request);
void requeueRequest(const HttpMessagePair &pair); // e.g. after pipeline broke
void fillHttp2Queue();
- bool dequeueRequest(QAbstractSocket *socket);
+ bool dequeueRequest(QIODevice *socket);
void prepareRequest(HttpMessagePair &request);
void updateChannel(int i, const HttpMessagePair &messagePair);
QHttpNetworkRequest predictNextRequest() const;
QHttpNetworkReply* predictNextRequestsReply() const;
- void fillPipeline(QAbstractSocket *socket);
+ void fillPipeline(QIODevice *socket);
bool fillPipeline(QList<HttpMessagePair> &queue, QHttpNetworkConnectionChannel &channel);
// read more HTTP body after the next event loop spin
@@ -198,9 +197,9 @@ public:
void _q_hostLookupFinished(const QHostInfo &info);
void _q_connectDelayedChannel();
- void createAuthorization(QAbstractSocket *socket, QHttpNetworkRequest &request);
+ void createAuthorization(QIODevice *socket, QHttpNetworkRequest &request);
- QString errorDetail(QNetworkReply::NetworkError errorCode, QAbstractSocket *socket,
+ QString errorDetail(QNetworkReply::NetworkError errorCode, QIODevice *socket,
const QString &extraDetail = QString());
void removeReply(QHttpNetworkReply *reply);
@@ -208,29 +207,30 @@ public:
QString hostName;
quint16 port;
bool encrypt;
- bool delayIpv4;
+ bool isLocalSocket;
+ bool delayIpv4 = true;
// Number of channels we are trying to use at the moment:
int activeChannelCount;
// The total number of channels we reserved:
const int channelCount;
QTimer delayedConnectionTimer;
- QHttpNetworkConnectionChannel *channels; // parallel connections to the server
- bool shouldEmitChannelError(QAbstractSocket *socket);
+ QHttpNetworkConnectionChannel * const channels; // parallel connections to the server
+ bool shouldEmitChannelError(QIODevice *socket);
qint64 uncompressedBytesAvailable(const QHttpNetworkReply &reply) const;
qint64 uncompressedBytesAvailableNextBlock(const QHttpNetworkReply &reply) const;
- void emitReplyError(QAbstractSocket *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode);
- bool handleAuthenticateChallenge(QAbstractSocket *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend);
+ void emitReplyError(QIODevice *socket, QHttpNetworkReply *reply, QNetworkReply::NetworkError errorCode);
+ bool handleAuthenticateChallenge(QIODevice *socket, QHttpNetworkReply *reply, bool isProxy, bool &resend);
struct ParseRedirectResult {
QUrl redirectUrl;
QNetworkReply::NetworkError errorCode;
};
- ParseRedirectResult parseRedirectResponse(QHttpNetworkReply *reply);
+ static ParseRedirectResult parseRedirectResponse(QHttpNetworkReply *reply);
// Used by the HTTP1 code-path
- QUrl parseRedirectResponse(QAbstractSocket *socket, QHttpNetworkReply *reply);
+ QUrl parseRedirectResponse(QIODevice *socket, QHttpNetworkReply *reply);
#ifndef QT_NO_NETWORKPROXY
QNetworkProxy networkProxy;
@@ -241,7 +241,7 @@ public:
QList<HttpMessagePair> highPriorityQueue;
QList<HttpMessagePair> lowPriorityQueue;
- int preConnectRequests;
+ int preConnectRequests = 0;
QHttpNetworkConnection::ConnectionType connectionType;
diff --git a/src/network/access/qhttpnetworkconnectionchannel.cpp b/src/network/access/qhttpnetworkconnectionchannel.cpp
index 1e036bdc45..8688e4b8d7 100644
--- a/src/network/access/qhttpnetworkconnectionchannel.cpp
+++ b/src/network/access/qhttpnetworkconnectionchannel.cpp
@@ -7,12 +7,12 @@
#include "qhttp2configuration.h"
#include "private/qnoncontiguousbytedevice_p.h"
-#include <qpair.h>
#include <qdebug.h>
#include <private/qhttp2protocolhandler_p.h>
#include <private/qhttpprotocolhandler_p.h>
#include <private/http2protocol_p.h>
+#include <private/qsocketabstraction_p.h>
#ifndef QT_NO_SSL
# include <private/qsslsocket_p.h>
@@ -23,6 +23,7 @@
#include "private/qnetconmonitor_p.h"
#include <memory>
+#include <utility>
QT_BEGIN_NAMESPACE
@@ -78,6 +79,8 @@ void QHttpNetworkConnectionChannel::init()
#ifndef QT_NO_SSL
if (connection->d_func()->encrypt)
socket = new QSslSocket;
+ else if (connection->d_func()->isLocalSocket)
+ socket = new QLocalSocket;
else
socket = new QTcpSocket;
#else
@@ -85,58 +88,75 @@ void QHttpNetworkConnectionChannel::init()
#endif
#ifndef QT_NO_NETWORKPROXY
// Set by QNAM anyway, but let's be safe here
- socket->setProxy(QNetworkProxy::NoProxy);
+ if (auto s = qobject_cast<QAbstractSocket *>(socket))
+ s->setProxy(QNetworkProxy::NoProxy);
#endif
// After some back and forth in all the last years, this is now a DirectConnection because otherwise
// the state inside the *Socket classes gets messed up, also in conjunction with the socket notifiers
// which behave slightly differently on Windows vs Linux
- QObject::connect(socket, SIGNAL(bytesWritten(qint64)),
- this, SLOT(_q_bytesWritten(qint64)),
+ QObject::connect(socket, &QIODevice::bytesWritten,
+ this, &QHttpNetworkConnectionChannel::_q_bytesWritten,
Qt::DirectConnection);
- QObject::connect(socket, SIGNAL(connected()),
- this, SLOT(_q_connected()),
- Qt::DirectConnection);
- QObject::connect(socket, SIGNAL(readyRead()),
- this, SLOT(_q_readyRead()),
+ QObject::connect(socket, &QIODevice::readyRead,
+ this, &QHttpNetworkConnectionChannel::_q_readyRead,
Qt::DirectConnection);
- // The disconnected() and error() signals may already come
- // while calling connectToHost().
- // In case of a cached hostname or an IP this
- // will then emit a signal to the user of QNetworkReply
- // but cannot be caught because the user did not have a chance yet
- // to connect to QNetworkReply's signals.
- qRegisterMetaType<QAbstractSocket::SocketError>();
- QObject::connect(socket, SIGNAL(disconnected()),
- this, SLOT(_q_disconnected()),
- Qt::DirectConnection);
- QObject::connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)),
- this, SLOT(_q_error(QAbstractSocket::SocketError)),
- Qt::DirectConnection);
+
+ QSocketAbstraction::visit([this](auto *socket){
+ using SocketType = std::remove_pointer_t<decltype(socket)>;
+ QObject::connect(socket, &SocketType::connected,
+ this, &QHttpNetworkConnectionChannel::_q_connected,
+ Qt::DirectConnection);
+
+ // The disconnected() and error() signals may already come
+ // while calling connectToHost().
+ // In case of a cached hostname or an IP this
+ // will then emit a signal to the user of QNetworkReply
+ // but cannot be caught because the user did not have a chance yet
+ // to connect to QNetworkReply's signals.
+ QObject::connect(socket, &SocketType::disconnected,
+ this, &QHttpNetworkConnectionChannel::_q_disconnected,
+ Qt::DirectConnection);
+ if constexpr (std::is_same_v<SocketType, QAbstractSocket>) {
+ QObject::connect(socket, &QAbstractSocket::errorOccurred,
+ this, &QHttpNetworkConnectionChannel::_q_error,
+ Qt::DirectConnection);
+ } else if constexpr (std::is_same_v<SocketType, QLocalSocket>) {
+ auto convertAndForward = [this](QLocalSocket::LocalSocketError error) {
+ _q_error(static_cast<QAbstractSocket::SocketError>(error));
+ };
+ QObject::connect(socket, &SocketType::errorOccurred,
+ this, std::move(convertAndForward),
+ Qt::DirectConnection);
+ }
+ }, socket);
+
#ifndef QT_NO_NETWORKPROXY
- QObject::connect(socket, SIGNAL(proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
- this, SLOT(_q_proxyAuthenticationRequired(QNetworkProxy,QAuthenticator*)),
- Qt::DirectConnection);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket)) {
+ QObject::connect(s, &QAbstractSocket::proxyAuthenticationRequired,
+ this, &QHttpNetworkConnectionChannel::_q_proxyAuthenticationRequired,
+ Qt::DirectConnection);
+ }
#endif
#ifndef QT_NO_SSL
QSslSocket *sslSocket = qobject_cast<QSslSocket*>(socket);
if (sslSocket) {
// won't be a sslSocket if encrypt is false
- QObject::connect(sslSocket, SIGNAL(encrypted()),
- this, SLOT(_q_encrypted()),
+ QObject::connect(sslSocket, &QSslSocket::encrypted,
+ this, &QHttpNetworkConnectionChannel::_q_encrypted,
Qt::DirectConnection);
- QObject::connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
- this, SLOT(_q_sslErrors(QList<QSslError>)),
+ QObject::connect(sslSocket, &QSslSocket::sslErrors,
+ this, &QHttpNetworkConnectionChannel::_q_sslErrors,
Qt::DirectConnection);
- QObject::connect(sslSocket, SIGNAL(preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
- this, SLOT(_q_preSharedKeyAuthenticationRequired(QSslPreSharedKeyAuthenticator*)),
+ QObject::connect(sslSocket, &QSslSocket::preSharedKeyAuthenticationRequired,
+ this, &QHttpNetworkConnectionChannel::_q_preSharedKeyAuthenticationRequired,
Qt::DirectConnection);
- QObject::connect(sslSocket, SIGNAL(encryptedBytesWritten(qint64)),
- this, SLOT(_q_encryptedBytesWritten(qint64)),
+ QObject::connect(sslSocket, &QSslSocket::encryptedBytesWritten,
+ this, &QHttpNetworkConnectionChannel::_q_encryptedBytesWritten,
Qt::DirectConnection);
if (ignoreAllSslErrors)
@@ -156,8 +176,10 @@ void QHttpNetworkConnectionChannel::init()
#endif
#ifndef QT_NO_NETWORKPROXY
- if (proxy.type() != QNetworkProxy::NoProxy)
- socket->setProxy(proxy);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket);
+ s && proxy.type() != QNetworkProxy::NoProxy) {
+ s->setProxy(proxy);
+ }
#endif
isInitialized = true;
}
@@ -170,7 +192,7 @@ void QHttpNetworkConnectionChannel::close()
if (!socket)
state = QHttpNetworkConnectionChannel::IdleState;
- else if (socket->state() == QAbstractSocket::UnconnectedState)
+ else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState)
state = QHttpNetworkConnectionChannel::IdleState;
else
state = QHttpNetworkConnectionChannel::ClosingState;
@@ -190,7 +212,7 @@ void QHttpNetworkConnectionChannel::abort()
{
if (!socket)
state = QHttpNetworkConnectionChannel::IdleState;
- else if (socket->state() == QAbstractSocket::UnconnectedState)
+ else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState)
state = QHttpNetworkConnectionChannel::IdleState;
else
state = QHttpNetworkConnectionChannel::ClosingState;
@@ -201,7 +223,10 @@ void QHttpNetworkConnectionChannel::abort()
if (socket) {
// socket can be 0 since the host lookup is done from qhttpnetworkconnection.cpp while
// there is no socket yet.
- socket->abort();
+ auto callAbort = [](auto *s) {
+ s->abort();
+ };
+ QSocketAbstraction::visit(callAbort, socket);
}
}
@@ -268,7 +293,7 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
if (!isInitialized)
init();
- QAbstractSocket::SocketState socketState = socket->state();
+ QAbstractSocket::SocketState socketState = QSocketAbstraction::socketState(socket);
// resend this request after we receive the disconnected signal
// If !socket->isOpen() then we have already called close() on the socket, but there was still a
@@ -325,8 +350,8 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
QHttpNetworkReply *potentialReply = connection->d_func()->predictNextRequestsReply();
if (potentialReply) {
QMetaObject::invokeMethod(potentialReply, "socketStartedConnecting", Qt::QueuedConnection);
- } else if (h2RequestsToSend.size() > 0) {
- QMetaObject::invokeMethod(h2RequestsToSend.values().at(0).second, "socketStartedConnecting", Qt::QueuedConnection);
+ } else if (!h2RequestsToSend.isEmpty()) {
+ QMetaObject::invokeMethod(std::as_const(h2RequestsToSend).first().second, "socketStartedConnecting", Qt::QueuedConnection);
}
#ifndef QT_NO_NETWORKPROXY
@@ -335,7 +360,8 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
connectHost = connection->d_func()->networkProxy.hostName();
connectPort = connection->d_func()->networkProxy.port();
}
- if (socket->proxy().type() == QNetworkProxy::HttpProxy) {
+ if (auto *abSocket = qobject_cast<QAbstractSocket *>(socket);
+ abSocket && abSocket->proxy().type() == QNetworkProxy::HttpProxy) {
// Make user-agent field available to HTTP proxy socket engine (QTBUG-17223)
QByteArray value;
// ensureConnection is called before any request has been assigned, but can also be
@@ -344,8 +370,8 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
if (connection->connectionType()
== QHttpNetworkConnection::ConnectionTypeHTTP2Direct
|| (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
- && h2RequestsToSend.size() > 0)) {
- value = h2RequestsToSend.first().first.headerField("user-agent");
+ && !h2RequestsToSend.isEmpty())) {
+ value = std::as_const(h2RequestsToSend).first().first.headerField("user-agent");
} else {
value = connection->d_func()->predictNextRequest().headerField("user-agent");
}
@@ -353,9 +379,11 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
value = request.headerField("user-agent");
}
if (!value.isEmpty()) {
- QNetworkProxy proxy(socket->proxy());
- proxy.setRawHeader("User-Agent", value); //detaches
- socket->setProxy(proxy);
+ QNetworkProxy proxy(abSocket->proxy());
+ auto h = proxy.headers();
+ h.replaceOrAppend(QHttpHeaders::WellKnownHeader::UserAgent, value);
+ proxy.setHeaders(std::move(h));
+ abSocket->setProxy(proxy);
}
}
#endif
@@ -378,7 +406,7 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
// limit the socket read buffer size. we will read everything into
// the QHttpNetworkReply anyway, so let's grow only that and not
// here and there.
- socket->setReadBufferSize(64*1024);
+ sslSocket->setReadBufferSize(64*1024);
#else
// Need to dequeue the request so that we can emit the error.
if (!reply)
@@ -392,17 +420,24 @@ bool QHttpNetworkConnectionChannel::ensureConnection()
&& connection->cacheProxy().type() == QNetworkProxy::NoProxy
&& connection->transparentProxy().type() == QNetworkProxy::NoProxy) {
#endif
- socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite | QIODevice::Unbuffered, networkLayerPreference);
- // For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
- socket->setReadBufferSize(1*1024);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket)) {
+ s->connectToHost(connectHost, connectPort,
+ QIODevice::ReadWrite | QIODevice::Unbuffered,
+ networkLayerPreference);
+ // For an Unbuffered QTcpSocket, the read buffer size has a special meaning.
+ s->setReadBufferSize(1 * 1024);
+ } else if (auto *s = qobject_cast<QLocalSocket *>(socket)) {
+ s->connectToServer(connectHost);
+ }
#ifndef QT_NO_NETWORKPROXY
} else {
- socket->connectToHost(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference);
-
+ auto *s = qobject_cast<QAbstractSocket *>(socket);
+ Q_ASSERT(s);
// limit the socket read buffer size. we will read everything into
// the QHttpNetworkReply anyway, so let's grow only that and not
// here and there.
- socket->setReadBufferSize(64*1024);
+ s->connectToHost(connectHost, connectPort, QIODevice::ReadWrite, networkLayerPreference);
+ s->setReadBufferSize(64 * 1024);
}
#endif
}
@@ -505,7 +540,7 @@ void QHttpNetworkConnectionChannel::allDone()
// move next from pipeline to current request
if (!alreadyPipelinedRequests.isEmpty()) {
- if (resendCurrent || connectionCloseEnabled || socket->state() != QAbstractSocket::ConnectedState) {
+ if (resendCurrent || connectionCloseEnabled || QSocketAbstraction::socketState(socket) != QAbstractSocket::ConnectedState) {
// move the pipelined ones back to the main queue
requeueCurrentlyPipelinedRequests();
close();
@@ -536,7 +571,7 @@ void QHttpNetworkConnectionChannel::allDone()
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
} else if (alreadyPipelinedRequests.isEmpty()) {
if (connectionCloseEnabled)
- if (socket->state() != QAbstractSocket::UnconnectedState)
+ if (QSocketAbstraction::socketState(socket) != QAbstractSocket::UnconnectedState)
close();
if (qobject_cast<QHttpNetworkConnection*>(connection))
QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
@@ -554,7 +589,7 @@ void QHttpNetworkConnectionChannel::detectPipeliningSupport()
// check for not having connection close
&& (!reply->d_func()->isConnectionCloseEnabled())
// check if it is still connected
- && (socket->state() == QAbstractSocket::ConnectedState)
+ && (QSocketAbstraction::socketState(socket) == QAbstractSocket::ConnectedState)
// check for broken servers in server reply header
// this is adapted from http://mxr.mozilla.org/firefox/ident?i=SupportsPipelining
&& (serverHeaderField = reply->headerField("Server"), !serverHeaderField.contains("Microsoft-IIS/4."))
@@ -677,8 +712,8 @@ bool QHttpNetworkConnectionChannel::resetUploadData()
void QHttpNetworkConnectionChannel::setProxy(const QNetworkProxy &networkProxy)
{
- if (socket)
- socket->setProxy(networkProxy);
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket))
+ s->setProxy(networkProxy);
proxy = networkProxy;
}
@@ -839,7 +874,7 @@ void QHttpNetworkConnectionChannel::_q_disconnected()
}
-void QHttpNetworkConnectionChannel::_q_connected()
+void QHttpNetworkConnectionChannel::_q_connected_abstract_socket(QAbstractSocket *absSocket)
{
// For the Happy Eyeballs we need to check if this is the first channel to connect.
if (connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::HostLookupPending || connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4or6) {
@@ -850,12 +885,14 @@ void QHttpNetworkConnectionChannel::_q_connected()
else if (networkLayerPreference == QAbstractSocket::IPv6Protocol)
connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
else {
- if (socket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol)
+ if (absSocket->peerAddress().protocol() == QAbstractSocket::IPv4Protocol)
connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv4;
else
connection->d_func()->networkLayerState = QHttpNetworkConnectionPrivate::IPv6;
}
connection->d_func()->networkLayerDetected(networkLayerPreference);
+ if (connection->d_func()->activeChannelCount > 1 && !connection->d_func()->encrypt)
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
} else {
bool anyProtocol = networkLayerPreference == QAbstractSocket::AnyIPProtocol;
if (((connection->d_func()->networkLayerState == QHttpNetworkConnectionPrivate::IPv4)
@@ -871,7 +908,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
}
// improve performance since we get the request sent by the kernel ASAP
- //socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
+ //absSocket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
// We have this commented out now. It did not have the effect we wanted. If we want to
// do this properly, Qt has to combine multiple HTTP requests into one buffer
// and send this to the kernel in one syscall and then the kernel immediately sends
@@ -880,7 +917,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
// the requests into one TCP packet.
// not sure yet if it helps, but it makes sense
- socket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
+ absSocket->setSocketOption(QAbstractSocket::KeepAliveOption, 1);
pipeliningSupported = QHttpNetworkConnectionChannel::PipeliningSupportUnknown;
@@ -889,7 +926,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
if (!connectionPrivate->connectionMonitor.isMonitoring()) {
// Now that we have a pair of addresses, we can start monitoring the
// connection status to handle its loss properly.
- if (connectionPrivate->connectionMonitor.setTargets(socket->localAddress(), socket->peerAddress()))
+ if (connectionPrivate->connectionMonitor.setTargets(absSocket->localAddress(), absSocket->peerAddress()))
connectionPrivate->connectionMonitor.startMonitoring();
}
}
@@ -901,7 +938,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
if (!connection->sslContext()) {
// this socket is making the 1st handshake for this connection,
// we need to set the SSL context so new sockets can reuse it
- if (auto socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(socket)))
+ if (auto socketSslContext = QSslSocketPrivate::sslContext(static_cast<QSslSocket*>(absSocket)))
connection->setSslContext(std::move(socketSslContext));
}
#endif
@@ -923,7 +960,7 @@ void QHttpNetworkConnectionChannel::_q_connected()
switchedToHttp2 = false;
if (!reply)
- connection->d_func()->dequeueRequest(socket);
+ connection->d_func()->dequeueRequest(absSocket);
if (reply) {
if (tryProtocolUpgrade) {
@@ -936,6 +973,22 @@ void QHttpNetworkConnectionChannel::_q_connected()
}
}
+void QHttpNetworkConnectionChannel::_q_connected_local_socket(QLocalSocket *localSocket)
+{
+ state = QHttpNetworkConnectionChannel::IdleState;
+ if (!reply) // No reply object, try to dequeue a request (which is paired with a reply):
+ connection->d_func()->dequeueRequest(localSocket);
+ if (reply)
+ sendRequest();
+}
+
+void QHttpNetworkConnectionChannel::_q_connected()
+{
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket))
+ _q_connected_abstract_socket(s);
+ else if (auto *s = qobject_cast<QLocalSocket *>(socket))
+ _q_connected_local_socket(s);
+}
void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socketError)
{
@@ -949,6 +1002,10 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
break;
case QAbstractSocket::ConnectionRefusedError:
errorCode = QNetworkReply::ConnectionRefusedError;
+#ifndef QT_NO_NETWORKPROXY
+ if (connection->d_func()->networkProxy.type() != QNetworkProxy::NoProxy && !ssl)
+ errorCode = QNetworkReply::ProxyConnectionRefusedError;
+#endif
break;
case QAbstractSocket::RemoteHostClosedError:
// This error for SSL comes twice in a row, first from SSL layer ("The TLS/SSL connection has been closed") then from TCP layer.
@@ -1030,6 +1087,9 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
}
errorCode = QNetworkReply::TimeoutError;
break;
+ case QAbstractSocket::ProxyConnectionRefusedError:
+ errorCode = QNetworkReply::ProxyConnectionRefusedError;
+ break;
case QAbstractSocket::ProxyAuthenticationRequiredError:
errorCode = QNetworkReply::ProxyAuthenticationRequiredError;
break;
@@ -1087,16 +1147,15 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
- QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
- for (int a = 0; a < h2Pairs.size(); ++a) {
+ const auto h2RequestsToSendCopy = std::exchange(h2RequestsToSend, {});
+ for (const auto &httpMessagePair : h2RequestsToSendCopy) {
// emit error for all replies
- QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
+ QHttpNetworkReply *currentReply = httpMessagePair.second;
currentReply->d_func()->errorString = errorString;
currentReply->d_func()->httpErrorCode = errorCode;
Q_ASSERT(currentReply);
emit currentReply->finishedWithError(errorCode, errorString);
}
- h2RequestsToSend.clear();
}
// send the next request
@@ -1106,7 +1165,7 @@ void QHttpNetworkConnectionChannel::_q_error(QAbstractSocket::SocketError socket
//signal emission triggered event loop
if (!socket)
state = QHttpNetworkConnectionChannel::IdleState;
- else if (socket->state() == QAbstractSocket::UnconnectedState)
+ else if (QSocketAbstraction::socketState(socket) == QAbstractSocket::UnconnectedState)
state = QHttpNetworkConnectionChannel::IdleState;
else
state = QHttpNetworkConnectionChannel::ClosingState;
@@ -1145,9 +1204,9 @@ void QHttpNetworkConnectionChannel::emitFinishedWithError(QNetworkReply::Network
{
if (reply)
emit reply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
- QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
- for (int a = 0; a < h2Pairs.size(); ++a) {
- QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
+ const auto h2RequestsToSendCopy = h2RequestsToSend;
+ for (const auto &httpMessagePair : h2RequestsToSendCopy) {
+ QHttpNetworkReply *currentReply = httpMessagePair.second;
Q_ASSERT(currentReply);
emit currentReply->finishedWithError(error, QHttpNetworkConnectionChannel::tr(message));
}
@@ -1228,14 +1287,12 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
if (connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2 ||
connection->connectionType() == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
- if (h2RequestsToSend.size() > 0) {
+ if (!h2RequestsToSend.isEmpty()) {
// Similar to HTTP/1.1 counterpart below:
- const auto &h2Pairs = h2RequestsToSend.values(); // (request, reply)
- const auto &pair = h2Pairs.first();
+ const auto &pair = std::as_const(h2RequestsToSend).first();
emit pair.second->encrypted();
// In case our peer has sent us its settings (window size, max concurrent streams etc.)
// let's give _q_receiveReply a chance to read them first ('invokeMethod', QueuedConnection).
- QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
}
} else { // HTTP
if (!reply)
@@ -1248,14 +1305,14 @@ void QHttpNetworkConnectionChannel::_q_encrypted()
if (reply)
sendRequestDelayed();
}
+ QMetaObject::invokeMethod(connection, "_q_startNextRequest", Qt::QueuedConnection);
}
void QHttpNetworkConnectionChannel::requeueHttp2Requests()
{
- QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
- for (int a = 0; a < h2Pairs.size(); ++a)
- connection->d_func()->requeueRequest(h2Pairs.at(a));
- h2RequestsToSend.clear();
+ const auto h2RequestsToSendCopy = std::exchange(h2RequestsToSend, {});
+ for (const auto &httpMessagePair : h2RequestsToSendCopy)
+ connection->d_func()->requeueRequest(httpMessagePair);
}
void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
@@ -1274,10 +1331,10 @@ void QHttpNetworkConnectionChannel::_q_sslErrors(const QList<QSslError> &errors)
}
#ifndef QT_NO_SSL
else { // HTTP/2
- QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
- for (int a = 0; a < h2Pairs.size(); ++a) {
+ const auto h2RequestsToSendCopy = h2RequestsToSend;
+ for (const auto &httpMessagePair : h2RequestsToSendCopy) {
// emit SSL errors for all replies
- QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
+ QHttpNetworkReply *currentReply = httpMessagePair.second;
Q_ASSERT(currentReply);
emit currentReply->sslErrors(errors);
}
@@ -1297,10 +1354,10 @@ void QHttpNetworkConnectionChannel::_q_preSharedKeyAuthenticationRequired(QSslPr
if (reply)
emit reply->preSharedKeyAuthenticationRequired(authenticator);
} else {
- QList<HttpMessagePair> h2Pairs = h2RequestsToSend.values();
- for (int a = 0; a < h2Pairs.size(); ++a) {
+ const auto h2RequestsToSendCopy = h2RequestsToSend;
+ for (const auto &httpMessagePair : h2RequestsToSendCopy) {
// emit SSL errors for all replies
- QHttpNetworkReply *currentReply = h2Pairs.at(a).second;
+ QHttpNetworkReply *currentReply = httpMessagePair.second;
Q_ASSERT(currentReply);
emit currentReply->preSharedKeyAuthenticationRequired(authenticator);
}
diff --git a/src/network/access/qhttpnetworkconnectionchannel_p.h b/src/network/access/qhttpnetworkconnectionchannel_p.h
index 0772a4452b..853b647ecc 100644
--- a/src/network/access/qhttpnetworkconnectionchannel_p.h
+++ b/src/network/access/qhttpnetworkconnectionchannel_p.h
@@ -19,6 +19,7 @@
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkreply.h>
#include <QtNetwork/qabstractsocket.h>
+#include <QtNetwork/qlocalsocket.h>
#include <private/qobject_p.h>
#include <qauthenticator.h>
@@ -40,6 +41,7 @@
# include <QtNetwork/qtcpsocket.h>
#endif
+#include <QtCore/qpointer.h>
#include <QtCore/qscopedpointer.h>
#include <memory>
@@ -70,7 +72,7 @@ public:
ClosingState = 16,
BusyState = (ConnectingState|WritingState|WaitingState|ReadingState|ClosingState)
};
- QAbstractSocket *socket;
+ QIODevice *socket;
bool ssl;
bool isInitialized;
ChannelState state;
@@ -155,6 +157,8 @@ public:
void _q_bytesWritten(qint64 bytes); // proceed sending
void _q_readyRead(); // pending data to read
void _q_disconnected(); // disconnected from host
+ void _q_connected_abstract_socket(QAbstractSocket *socket);
+ void _q_connected_local_socket(QLocalSocket *socket);
void _q_connected(); // start sending request
void _q_error(QAbstractSocket::SocketError); // error from socket
#ifndef QT_NO_NETWORKPROXY
diff --git a/src/network/access/qhttpnetworkheader.cpp b/src/network/access/qhttpnetworkheader.cpp
index e0a964d253..7f9c94dc9c 100644
--- a/src/network/access/qhttpnetworkheader.cpp
+++ b/src/network/access/qhttpnetworkheader.cpp
@@ -29,7 +29,7 @@ void QHttpNetworkHeaderPrivate::setContentLength(qint64 length)
setHeaderField("Content-Length", QByteArray::number(length));
}
-QByteArray QHttpNetworkHeaderPrivate::headerField(const QByteArray &name, const QByteArray &defaultValue) const
+QByteArray QHttpNetworkHeaderPrivate::headerField(QByteArrayView name, const QByteArray &defaultValue) const
{
QList<QByteArray> allValues = headerFieldValues(name);
if (allValues.isEmpty())
@@ -38,7 +38,7 @@ QByteArray QHttpNetworkHeaderPrivate::headerField(const QByteArray &name, const
return allValues.join(", ");
}
-QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(const QByteArray &name) const
+QList<QByteArray> QHttpNetworkHeaderPrivate::headerFieldValues(QByteArrayView name) const
{
return parser.headerFieldValues(name);
}
@@ -53,7 +53,7 @@ void QHttpNetworkHeaderPrivate::prependHeaderField(const QByteArray &name, const
parser.prependHeaderField(name, data);
}
-QList<QPair<QByteArray, QByteArray> > QHttpNetworkHeaderPrivate::headers() const
+QHttpHeaders QHttpNetworkHeaderPrivate::headers() const
{
return parser.headers();
}
diff --git a/src/network/access/qhttpnetworkheader_p.h b/src/network/access/qhttpnetworkheader_p.h
index fc5d388ae5..afbc6cb6fe 100644
--- a/src/network/access/qhttpnetworkheader_p.h
+++ b/src/network/access/qhttpnetworkheader_p.h
@@ -17,6 +17,7 @@
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include <QtNetwork/private/qhttpheaderparser_p.h>
+#include <QtNetwork/qhttpheaders.h>
#include <qshareddata.h>
#include <qurl.h>
@@ -40,8 +41,8 @@ public:
virtual qint64 contentLength() const = 0;
virtual void setContentLength(qint64 length) = 0;
- virtual QList<QPair<QByteArray, QByteArray> > header() const = 0;
- virtual QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const = 0;
+ virtual QHttpHeaders header() const = 0;
+ virtual QByteArray headerField(QByteArrayView name, const QByteArray &defaultValue = QByteArray()) const = 0;
virtual void setHeaderField(const QByteArray &name, const QByteArray &data) = 0;
};
@@ -56,12 +57,12 @@ public:
qint64 contentLength() const;
void setContentLength(qint64 length);
- QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const;
- QList<QByteArray> headerFieldValues(const QByteArray &name) const;
+ QByteArray headerField(QByteArrayView name, const QByteArray &defaultValue = QByteArray()) const;
+ QList<QByteArray> headerFieldValues(QByteArrayView name) const;
void setHeaderField(const QByteArray &name, const QByteArray &data);
void prependHeaderField(const QByteArray &name, const QByteArray &data);
void clearHeaders();
- QList<QPair<QByteArray, QByteArray> > headers() const;
+ QHttpHeaders headers() const;
bool operator==(const QHttpNetworkHeaderPrivate &other) const;
};
diff --git a/src/network/access/qhttpnetworkreply.cpp b/src/network/access/qhttpnetworkreply.cpp
index 0d69dec980..5711c96b18 100644
--- a/src/network/access/qhttpnetworkreply.cpp
+++ b/src/network/access/qhttpnetworkreply.cpp
@@ -67,12 +67,12 @@ void QHttpNetworkReply::setContentLength(qint64 length)
d->setContentLength(length);
}
-QList<QPair<QByteArray, QByteArray> > QHttpNetworkReply::header() const
+QHttpHeaders QHttpNetworkReply::header() const
{
return d_func()->parser.headers();
}
-QByteArray QHttpNetworkReply::headerField(const QByteArray &name, const QByteArray &defaultValue) const
+QByteArray QHttpNetworkReply::headerField(QByteArrayView name, const QByteArray &defaultValue) const
{
return d_func()->headerField(name, defaultValue);
}
@@ -89,7 +89,7 @@ void QHttpNetworkReply::appendHeaderField(const QByteArray &name, const QByteArr
d->appendHeaderField(name, data);
}
-void QHttpNetworkReply::parseHeader(const QByteArray &header)
+void QHttpNetworkReply::parseHeader(QByteArrayView header)
{
Q_D(QHttpNetworkReply);
d->parseHeader(header);
@@ -368,7 +368,7 @@ void QHttpNetworkReplyPrivate::removeAutoDecompressHeader()
{
// The header "Content-Encoding = gzip" is retained.
// Content-Length is removed since the actual one sent by the server is for compressed data
- QByteArray name("content-length");
+ constexpr auto name = QByteArrayView("content-length");
QByteArray contentLength = parser.firstHeaderField(name);
bool parseOk = false;
qint64 value = contentLength.toLongLong(&parseOk);
@@ -378,23 +378,7 @@ void QHttpNetworkReplyPrivate::removeAutoDecompressHeader()
}
}
-bool QHttpNetworkReplyPrivate::findChallenge(bool forProxy, QByteArray &challenge) const
-{
- challenge.clear();
- // find out the type of authentication protocol requested.
- QByteArray header = forProxy ? "proxy-authenticate" : "www-authenticate";
- // pick the best protocol (has to match parsing in QAuthenticatorPrivate)
- QList<QByteArray> challenges = headerFieldValues(header);
- for (int i = 0; i<challenges.size(); i++) {
- QByteArray line = challenges.at(i);
- // todo use qstrincmp
- if (!line.toLower().startsWith("negotiate"))
- challenge = line;
- }
- return !challenge.isEmpty();
-}
-
-qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket)
+qint64 QHttpNetworkReplyPrivate::readStatus(QIODevice *socket)
{
if (fragment.isEmpty()) {
// reserve bytes for the status line. This is better than always append() which reallocs the byte array
@@ -443,12 +427,12 @@ qint64 QHttpNetworkReplyPrivate::readStatus(QAbstractSocket *socket)
return bytes;
}
-bool QHttpNetworkReplyPrivate::parseStatus(const QByteArray &status)
+bool QHttpNetworkReplyPrivate::parseStatus(QByteArrayView status)
{
return parser.parseStatus(status);
}
-qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
+qint64 QHttpNetworkReplyPrivate::readHeader(QIODevice *socket)
{
if (fragment.isEmpty()) {
// according to http://dev.opera.com/articles/view/mama-http-headers/ the average size of the header
@@ -510,7 +494,7 @@ qint64 QHttpNetworkReplyPrivate::readHeader(QAbstractSocket *socket)
return bytes;
}
-void QHttpNetworkReplyPrivate::parseHeader(const QByteArray &header)
+void QHttpNetworkReplyPrivate::parseHeader(QByteArrayView header)
{
parser.parseHeaders(header);
}
@@ -532,7 +516,7 @@ bool QHttpNetworkReplyPrivate::isConnectionCloseEnabled()
// note this function can only be used for non-chunked, non-compressed with
// known content length
-qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QAbstractSocket *socket, char *b)
+qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QIODevice *socket, char *b)
{
// This first read is to flush the buffer inside the socket
qint64 haveRead = 0;
@@ -551,7 +535,7 @@ qint64 QHttpNetworkReplyPrivate::readBodyVeryFast(QAbstractSocket *socket, char
// note this function can only be used for non-chunked, non-compressed with
// known content length
-qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb)
+qint64 QHttpNetworkReplyPrivate::readBodyFast(QIODevice *socket, QByteDataBuffer *rb)
{
qint64 toBeRead = qMin(socket->bytesAvailable(), bodyLength - contentRead);
@@ -581,7 +565,7 @@ qint64 QHttpNetworkReplyPrivate::readBodyFast(QAbstractSocket *socket, QByteData
}
-qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuffer *out)
+qint64 QHttpNetworkReplyPrivate::readBody(QIODevice *socket, QByteDataBuffer *out)
{
qint64 bytes = 0;
@@ -601,7 +585,7 @@ qint64 QHttpNetworkReplyPrivate::readBody(QAbstractSocket *socket, QByteDataBuff
return bytes;
}
-qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QAbstractSocket *socket, QByteDataBuffer *out, qint64 size)
+qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QIODevice *socket, QByteDataBuffer *out, qint64 size)
{
// FIXME get rid of this function and just use readBodyFast and give it socket->bytesAvailable()
qint64 bytes = 0;
@@ -634,7 +618,7 @@ qint64 QHttpNetworkReplyPrivate::readReplyBodyRaw(QAbstractSocket *socket, QByte
}
-qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QAbstractSocket *socket, QByteDataBuffer *out)
+qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QIODevice *socket, QByteDataBuffer *out)
{
qint64 bytes = 0;
while (socket->bytesAvailable()) {
@@ -696,7 +680,7 @@ qint64 QHttpNetworkReplyPrivate::readReplyBodyChunked(QAbstractSocket *socket, Q
return bytes;
}
-qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *chunkSize)
+qint64 QHttpNetworkReplyPrivate::getChunkSize(QIODevice *socket, qint64 *chunkSize)
{
qint64 bytes = 0;
char crlf[2];
@@ -717,8 +701,8 @@ qint64 QHttpNetworkReplyPrivate::getChunkSize(QAbstractSocket *socket, qint64 *c
bytes += socket->read(crlf, 1); // read the \n
bool ok = false;
// ignore the chunk-extension
- fragment = fragment.mid(0, fragment.indexOf(';')).trimmed();
- *chunkSize = fragment.toLong(&ok, 16);
+ const auto fragmentView = QByteArrayView(fragment).mid(0, fragment.indexOf(';')).trimmed();
+ *chunkSize = fragmentView.toLong(&ok, 16);
fragment.clear();
break; // size done
} else {
diff --git a/src/network/access/qhttpnetworkreply_p.h b/src/network/access/qhttpnetworkreply_p.h
index ed76fde1d6..caec82bd7e 100644
--- a/src/network/access/qhttpnetworkreply_p.h
+++ b/src/network/access/qhttpnetworkreply_p.h
@@ -41,6 +41,9 @@ Q_MOC_INCLUDE(<QtNetwork/QNetworkProxy>)
Q_MOC_INCLUDE(<QtNetwork/QAuthenticator>)
#include <private/qdecompresshelper_p.h>
+#include <QtNetwork/qhttpheaders.h>
+
+#include <QtCore/qpointer.h>
QT_REQUIRE_CONFIG(http);
@@ -51,7 +54,7 @@ class QHttpNetworkConnectionChannel;
class QHttpNetworkRequest;
class QHttpNetworkConnectionPrivate;
class QHttpNetworkReplyPrivate;
-class Q_AUTOTEST_EXPORT QHttpNetworkReply : public QObject, public QHttpNetworkHeader
+class Q_NETWORK_EXPORT QHttpNetworkReply : public QObject, public QHttpNetworkHeader
{
Q_OBJECT
public:
@@ -70,11 +73,11 @@ public:
qint64 contentLength() const override;
void setContentLength(qint64 length) override;
- QList<QPair<QByteArray, QByteArray> > header() const override;
- QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const override;
+ QHttpHeaders header() const override;
+ QByteArray headerField(QByteArrayView name, const QByteArray &defaultValue = QByteArray()) const override;
void setHeaderField(const QByteArray &name, const QByteArray &data) override;
void appendHeaderField(const QByteArray &name, const QByteArray &data);
- void parseHeader(const QByteArray &header); // used for testing
+ void parseHeader(QByteArrayView header); // used for testing
QHttpNetworkRequest request() const;
void setRequest(const QHttpNetworkRequest &request);
@@ -169,21 +172,20 @@ class Q_AUTOTEST_EXPORT QHttpNetworkReplyPrivate : public QObjectPrivate, public
public:
QHttpNetworkReplyPrivate(const QUrl &newUrl = QUrl());
~QHttpNetworkReplyPrivate();
- qint64 readStatus(QAbstractSocket *socket);
- bool parseStatus(const QByteArray &status);
- qint64 readHeader(QAbstractSocket *socket);
- void parseHeader(const QByteArray &header);
+ qint64 readStatus(QIODevice *socket);
+ bool parseStatus(QByteArrayView status);
+ qint64 readHeader(QIODevice *socket);
+ void parseHeader(QByteArrayView header);
void appendHeaderField(const QByteArray &name, const QByteArray &data);
- qint64 readBody(QAbstractSocket *socket, QByteDataBuffer *out);
- qint64 readBodyVeryFast(QAbstractSocket *socket, char *b);
- qint64 readBodyFast(QAbstractSocket *socket, QByteDataBuffer *rb);
- bool findChallenge(bool forProxy, QByteArray &challenge) const;
+ qint64 readBody(QIODevice *socket, QByteDataBuffer *out);
+ qint64 readBodyVeryFast(QIODevice *socket, char *b);
+ qint64 readBodyFast(QIODevice *socket, QByteDataBuffer *rb);
void clear();
void clearHttpLayerInformation();
- qint64 readReplyBodyRaw(QAbstractSocket *in, QByteDataBuffer *out, qint64 size);
- qint64 readReplyBodyChunked(QAbstractSocket *in, QByteDataBuffer *out);
- qint64 getChunkSize(QAbstractSocket *in, qint64 *chunkSize);
+ qint64 readReplyBodyRaw(QIODevice *in, QByteDataBuffer *out, qint64 size);
+ qint64 readReplyBodyChunked(QIODevice *in, QByteDataBuffer *out);
+ qint64 getChunkSize(QIODevice *in, qint64 *chunkSize);
bool isRedirecting() const;
bool shouldEmitSignals();
diff --git a/src/network/access/qhttpnetworkrequest.cpp b/src/network/access/qhttpnetworkrequest.cpp
index d5e529f6b9..06cc0b4464 100644
--- a/src/network/access/qhttpnetworkrequest.cpp
+++ b/src/network/access/qhttpnetworkrequest.cpp
@@ -112,9 +112,9 @@ QByteArray QHttpNetworkRequest::uri(bool throughProxy) const
QByteArray QHttpNetworkRequestPrivate::header(const QHttpNetworkRequest &request, bool throughProxy)
{
- QList<QPair<QByteArray, QByteArray> > fields = request.header();
+ const QHttpHeaders headers = request.header();
QByteArray ba;
- ba.reserve(40 + fields.size()*25); // very rough lower bound estimation
+ ba.reserve(40 + headers.size() * 25); // very rough lower bound estimation
ba += request.methodName();
ba += ' ';
@@ -126,12 +126,10 @@ QByteArray QHttpNetworkRequestPrivate::header(const QHttpNetworkRequest &request
ba += QByteArray::number(request.minorVersion());
ba += "\r\n";
- QList<QPair<QByteArray, QByteArray> >::const_iterator it = fields.constBegin();
- QList<QPair<QByteArray, QByteArray> >::const_iterator endIt = fields.constEnd();
- for (; it != endIt; ++it) {
- ba += it->first;
+ for (qsizetype i = 0; i < headers.size(); ++i) {
+ ba += headers.nameAt(i);
ba += ": ";
- ba += it->second;
+ ba += headers.valueAt(i);
ba += "\r\n";
}
if (request.d->operation == QHttpNetworkRequest::Post) {
@@ -237,12 +235,12 @@ void QHttpNetworkRequest::setContentLength(qint64 length)
d->setContentLength(length);
}
-QList<QPair<QByteArray, QByteArray> > QHttpNetworkRequest::header() const
+QHttpHeaders QHttpNetworkRequest::header() const
{
return d->parser.headers();
}
-QByteArray QHttpNetworkRequest::headerField(const QByteArray &name, const QByteArray &defaultValue) const
+QByteArray QHttpNetworkRequest::headerField(QByteArrayView name, const QByteArray &defaultValue) const
{
return d->headerField(name, defaultValue);
}
@@ -383,5 +381,15 @@ void QHttpNetworkRequest::setPeerVerifyName(const QString &peerName)
d->peerVerifyName = peerName;
}
+QString QHttpNetworkRequest::fullLocalServerName() const
+{
+ return d->fullLocalServerName;
+}
+
+void QHttpNetworkRequest::setFullLocalServerName(const QString &fullServerName)
+{
+ d->fullLocalServerName = fullServerName;
+}
+
QT_END_NAMESPACE
diff --git a/src/network/access/qhttpnetworkrequest_p.h b/src/network/access/qhttpnetworkrequest_p.h
index e2014c4221..4444020402 100644
--- a/src/network/access/qhttpnetworkrequest_p.h
+++ b/src/network/access/qhttpnetworkrequest_p.h
@@ -65,8 +65,8 @@ public:
qint64 contentLength() const override;
void setContentLength(qint64 length) override;
- QList<QPair<QByteArray, QByteArray> > header() const override;
- QByteArray headerField(const QByteArray &name, const QByteArray &defaultValue = QByteArray()) const override;
+ QHttpHeaders header() const override;
+ QByteArray headerField(QByteArrayView name, const QByteArray &defaultValue = QByteArray()) const override;
void setHeaderField(const QByteArray &name, const QByteArray &data) override;
void prependHeaderField(const QByteArray &name, const QByteArray &data);
void clearHeaders();
@@ -117,6 +117,9 @@ public:
QString peerVerifyName() const;
void setPeerVerifyName(const QString &peerName);
+ QString fullLocalServerName() const;
+ void setFullLocalServerName(const QString &fullServerName);
+
private:
QSharedDataPointer<QHttpNetworkRequestPrivate> d;
friend class QHttpNetworkRequestPrivate;
@@ -140,6 +143,7 @@ public:
QHttpNetworkRequest::Operation operation;
QByteArray customVerb;
+ QString fullLocalServerName; // for local sockets
QHttpNetworkRequest::Priority priority;
mutable QNonContiguousByteDevice* uploadByteDevice;
bool autoDecompress;
diff --git a/src/network/access/qhttpprotocolhandler.cpp b/src/network/access/qhttpprotocolhandler.cpp
index b2576a85e6..fb584eb9cc 100644
--- a/src/network/access/qhttpprotocolhandler.cpp
+++ b/src/network/access/qhttpprotocolhandler.cpp
@@ -5,6 +5,7 @@
#include <private/qhttpprotocolhandler_p.h>
#include <private/qnoncontiguousbytedevice_p.h>
#include <private/qhttpnetworkconnectionchannel_p.h>
+#include <private/qsocketabstraction_p.h>
QT_BEGIN_NAMESPACE
@@ -34,10 +35,8 @@ void QHttpProtocolHandler::_q_receiveReply()
return;
}
- QAbstractSocket::SocketState socketState = m_socket->state();
-
// connection might be closed to signal the end of data
- if (socketState == QAbstractSocket::UnconnectedState) {
+ if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::UnconnectedState) {
if (m_socket->bytesAvailable() <= 0) {
if (m_reply->d_func()->state == QHttpNetworkReplyPrivate::ReadingDataState) {
// finish this reply. this case happens when the server did not send a content length
@@ -93,7 +92,8 @@ void QHttpProtocolHandler::_q_receiveReply()
} else {
replyPrivate->autoDecompress = false;
}
- if (m_reply->statusCode() == 100) {
+ const int statusCode = m_reply->statusCode();
+ if (statusCode == 100 || (102 <= statusCode && statusCode <= 199)) {
replyPrivate->clearHttpLayerInformation();
replyPrivate->state = QHttpNetworkReplyPrivate::ReadingStatusState;
break; // ignore
@@ -114,7 +114,7 @@ void QHttpProtocolHandler::_q_receiveReply()
}
case QHttpNetworkReplyPrivate::ReadingDataState: {
QHttpNetworkReplyPrivate *replyPrivate = m_reply->d_func();
- if (m_socket->state() == QAbstractSocket::ConnectedState &&
+ if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::ConnectedState &&
replyPrivate->downstreamLimited && !replyPrivate->responseData.isEmpty() && replyPrivate->shouldEmitSignals()) {
// (only do the following when still connected, not when we have already been disconnected and there is still data)
// We already have some HTTP body data. We don't read more from the socket until
@@ -192,7 +192,8 @@ void QHttpProtocolHandler::_q_receiveReply()
void QHttpProtocolHandler::_q_readyRead()
{
- if (m_socket->state() == QAbstractSocket::ConnectedState && m_socket->bytesAvailable() == 0) {
+ if (QSocketAbstraction::socketState(m_socket) == QAbstractSocket::ConnectedState
+ && m_socket->bytesAvailable() == 0) {
// We got a readyRead but no bytes are available..
// This happens for the Unbuffered QTcpSocket
// Also check if socket is in ConnectedState since
@@ -200,7 +201,7 @@ void QHttpProtocolHandler::_q_readyRead()
char c;
qint64 ret = m_socket->peek(&c, 1);
if (ret < 0) {
- m_channel->_q_error(m_socket->error());
+ m_channel->_q_error(QSocketAbstraction::socketError(m_socket));
// We still need to handle the reply so it emits its signals etc.
if (m_reply)
_q_receiveReply();
@@ -238,8 +239,7 @@ bool QHttpProtocolHandler::sendRequest()
// _q_connected or _q_encrypted
return false;
}
- QString scheme = m_channel->request.url().scheme();
- if (scheme == "preconnect-http"_L1 || scheme == "preconnect-https"_L1) {
+ if (m_channel->request.isPreConnect()) {
m_channel->state = QHttpNetworkConnectionChannel::IdleState;
m_reply->d_func()->state = QHttpNetworkReplyPrivate::AllDoneState;
m_channel->allDone();
diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp
index 4a11b828a8..4e5cf05aef 100644
--- a/src/network/access/qhttpthreaddelegate.cpp
+++ b/src/network/access/qhttpthreaddelegate.cpp
@@ -95,7 +95,9 @@ static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &p
QUrl copy = url;
QString scheme = copy.scheme();
bool isEncrypted = scheme == "https"_L1 || scheme == "preconnect-https"_L1;
- copy.setPort(copy.port(isEncrypted ? 443 : 80));
+ const bool isLocalSocket = scheme.startsWith("unix"_L1);
+ if (!isLocalSocket)
+ copy.setPort(copy.port(isEncrypted ? 443 : 80));
if (scheme == "preconnect-http"_L1)
copy.setScheme("http"_L1);
else if (scheme == "preconnect-https"_L1)
@@ -145,9 +147,9 @@ class QNetworkAccessCachedHttpConnection: public QHttpNetworkConnection,
{
// Q_OBJECT
public:
- QNetworkAccessCachedHttpConnection(const QString &hostName, quint16 port, bool encrypt,
+ QNetworkAccessCachedHttpConnection(quint16 connectionCount, const QString &hostName, quint16 port, bool encrypt, bool isLocalSocket,
QHttpNetworkConnection::ConnectionType connectionType)
- : QHttpNetworkConnection(hostName, port, encrypt, connectionType)
+ : QHttpNetworkConnection(connectionCount, hostName, port, encrypt, isLocalSocket, /*parent=*/nullptr, connectionType)
{
setExpires(true);
setShareable(true);
@@ -244,7 +246,9 @@ void QHttpThreadDelegate::startRequest()
// check if we have an open connection to this host
QUrl urlCopy = httpRequest.url();
- urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
+ const bool isLocalSocket = urlCopy.scheme().startsWith("unix"_L1);
+ if (!isLocalSocket)
+ urlCopy.setPort(urlCopy.port(ssl ? 443 : 80));
QHttpNetworkConnection::ConnectionType connectionType
= httpRequest.isHTTP2Allowed() ? QHttpNetworkConnection::ConnectionTypeHTTP2
@@ -279,10 +283,19 @@ void QHttpThreadDelegate::startRequest()
} else
#endif // QT_CONFIG(ssl)
{
- urlCopy.setScheme(QStringLiteral("h2"));
+ if (isLocalSocket)
+ urlCopy.setScheme(QStringLiteral("unix+h2"));
+ else
+ urlCopy.setScheme(QStringLiteral("h2"));
}
}
+ QString extraData = httpRequest.peerVerifyName();
+ if (isLocalSocket) {
+ if (QString path = httpRequest.fullLocalServerName(); !path.isEmpty())
+ extraData = path;
+ }
+
#ifndef QT_NO_NETWORKPROXY
if (transparentProxy.type() != QNetworkProxy::NoProxy)
cacheKey = makeCacheKey(urlCopy, &transparentProxy, httpRequest.peerVerifyName());
@@ -295,10 +308,19 @@ void QHttpThreadDelegate::startRequest()
// the http object is actually a QHttpNetworkConnection
httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey));
if (!httpConnection) {
+
+ QString host = urlCopy.host();
+ // Update the host if a unix socket path or named pipe is used:
+ if (isLocalSocket) {
+ if (QString path = httpRequest.fullLocalServerName(); !path.isEmpty())
+ host = path;
+ }
+
// no entry in cache; create an object
// the http object is actually a QHttpNetworkConnection
- httpConnection = new QNetworkAccessCachedHttpConnection(urlCopy.host(), urlCopy.port(), ssl,
- connectionType);
+ httpConnection = new QNetworkAccessCachedHttpConnection(
+ http1Parameters.numberOfConnectionsPerHost(), host, urlCopy.port(), ssl,
+ isLocalSocket, connectionType);
if (connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2
|| connectionType == QHttpNetworkConnection::ConnectionTypeHTTP2Direct) {
httpConnection->setHttp2Parameters(http2Parameters);
@@ -567,11 +589,6 @@ void QHttpThreadDelegate::synchronousFinishedWithErrorSlot(QNetworkReply::Networ
httpReply = nullptr;
}
-static void downloadBufferDeleter(char *ptr)
-{
- delete[] ptr;
-}
-
void QHttpThreadDelegate::headerChangedSlot()
{
if (!httpReply)
@@ -589,14 +606,11 @@ void QHttpThreadDelegate::headerChangedSlot()
// Is using a zerocopy buffer allowed by user and possible with this reply?
if (httpReply->supportsUserProvidedDownloadBuffer()
&& (downloadBufferMaximumSize > 0) && (httpReply->contentLength() <= downloadBufferMaximumSize)) {
- QT_TRY {
- char *buf = new char[httpReply->contentLength()]; // throws if allocation fails
- if (buf) {
- downloadBuffer = QSharedPointer<char>(buf, downloadBufferDeleter);
- httpReply->setUserProvidedDownloadBuffer(buf);
- }
- } QT_CATCH(const std::bad_alloc &) {
- // in out of memory situations, don't use downloadbuffer.
+ char *buf = new (std::nothrow) char[httpReply->contentLength()];
+ // in out of memory situations, don't use downloadBuffer.
+ if (buf) {
+ downloadBuffer = QSharedPointer<char>(buf, [](auto p) { delete[] p; });
+ httpReply->setUserProvidedDownloadBuffer(buf);
}
}
diff --git a/src/network/access/qhttpthreaddelegate_p.h b/src/network/access/qhttpthreaddelegate_p.h
index d16edf3b0f..38e9fb4d78 100644
--- a/src/network/access/qhttpthreaddelegate_p.h
+++ b/src/network/access/qhttpthreaddelegate_p.h
@@ -26,12 +26,14 @@
#include <QNetworkReply>
#include "qhttpnetworkrequest_p.h"
#include "qhttpnetworkconnection_p.h"
+#include "qhttp1configuration.h"
#include "qhttp2configuration.h"
#include <QSharedPointer>
#include <QScopedPointer>
#include "private/qnoncontiguousbytedevice_p.h"
#include "qnetworkaccessauthenticationmanager_p.h"
#include <QtNetwork/private/http2protocol_p.h>
+#include <QtNetwork/qhttpheaders.h>
QT_REQUIRE_CONFIG(http);
@@ -73,7 +75,7 @@ public:
// outgoing, Retrieved in the synchronous HTTP case
QByteArray synchronousDownloadData;
- QList<QPair<QByteArray,QByteArray> > incomingHeaders;
+ QHttpHeaders incomingHeaders;
int incomingStatusCode;
QString incomingReasonPhrase;
bool isPipeliningUsed;
@@ -82,6 +84,7 @@ public:
qint64 removedContentLength;
QNetworkReply::NetworkError incomingErrorCode;
QString incomingErrorDetail;
+ QHttp1Configuration http1Parameters;
QHttp2Configuration http2Parameters;
bool isCompressed;
@@ -110,7 +113,7 @@ signals:
#endif
void socketStartedConnecting();
void requestSent();
- void downloadMetaData(const QList<QPair<QByteArray,QByteArray> > &, int, const QString &, bool,
+ void downloadMetaData(const QHttpHeaders &, int, const QString &, bool,
QSharedPointer<char>, qint64, qint64, bool, bool);
void downloadProgress(qint64, qint64);
void downloadData(const QByteArray &);
@@ -162,22 +165,18 @@ class QNonContiguousByteDeviceThreadForwardImpl : public QNonContiguousByteDevic
{
Q_OBJECT
protected:
- bool wantDataPending;
- qint64 m_amount;
- char *m_data;
+ bool wantDataPending = false;
+ qint64 m_amount = 0;
+ char *m_data = nullptr;
QByteArray m_dataArray;
- bool m_atEnd;
- qint64 m_size;
- qint64 m_pos; // to match calls of haveDataSlot with the expected position
+ bool m_atEnd = false;
+ qint64 m_size = 0;
+ qint64 m_pos = 0; // to match calls of haveDataSlot with the expected position
public:
QNonContiguousByteDeviceThreadForwardImpl(bool aE, qint64 s)
: QNonContiguousByteDevice(),
- wantDataPending(false),
- m_amount(0),
- m_data(nullptr),
m_atEnd(aE),
- m_size(s),
- m_pos(0)
+ m_size(s)
{
}
@@ -250,6 +249,7 @@ public:
if (b) {
// the reset succeeded, we're at pos 0 again
m_pos = 0;
+ m_atEnd = false;
// the HTTP code will anyway abort the request if !b.
}
return b;
diff --git a/src/network/access/qnetworkaccessbackend.cpp b/src/network/access/qnetworkaccessbackend.cpp
index 5dbcef4bbe..3c7fee567d 100644
--- a/src/network/access/qnetworkaccessbackend.cpp
+++ b/src/network/access/qnetworkaccessbackend.cpp
@@ -568,6 +568,43 @@ void QNetworkAccessBackend::setRawHeader(const QByteArray &header, const QByteAr
}
/*!
+ \since 6.8
+
+ Returns headers that are set in this QNetworkAccessBackend instance.
+
+ \sa setHeaders()
+*/
+QHttpHeaders QNetworkAccessBackend::headers() const
+{
+ return d_func()->m_reply->headers();
+}
+
+/*!
+ \since 6.8
+
+ Sets \a newHeaders as headers, overriding any previously set headers.
+
+ These headers are accessible on the QNetworkReply instance which was
+ returned when calling one of the appropriate functions on
+ QNetworkAccessManager.
+
+ \sa headers()
+*/
+void QNetworkAccessBackend::setHeaders(QHttpHeaders &&newHeaders)
+{
+ d_func()->m_reply->setHeaders(std::move(newHeaders));
+}
+
+/*!
+ \overload
+ \since 6.8
+*/
+void QNetworkAccessBackend::setHeaders(const QHttpHeaders &newHeaders)
+{
+ d_func()->m_reply->setHeaders(newHeaders);
+}
+
+/*!
Returns the operation which was requested when calling
QNetworkAccessManager.
*/
diff --git a/src/network/access/qnetworkaccessbackend_p.h b/src/network/access/qnetworkaccessbackend_p.h
index 799ae3faad..4f8d7e4372 100644
--- a/src/network/access/qnetworkaccessbackend_p.h
+++ b/src/network/access/qnetworkaccessbackend_p.h
@@ -104,6 +104,9 @@ public:
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
QByteArray rawHeader(const QByteArray &header) const;
void setRawHeader(const QByteArray &header, const QByteArray &value);
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
QNetworkAccessManager::Operation operation() const;
bool isCachingEnabled() const;
diff --git a/src/network/access/qnetworkaccesscache.cpp b/src/network/access/qnetworkaccesscache.cpp
index 9a1a3d3806..2bc0e8fb70 100644
--- a/src/network/access/qnetworkaccesscache.cpp
+++ b/src/network/access/qnetworkaccesscache.cpp
@@ -14,9 +14,6 @@
QT_BEGIN_NAMESPACE
-QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkAccessCache::CacheableObject*,
- QNetworkAccessCache__CacheableObject_ptr)
-
enum ExpiryTimeEnum {
ExpiryTime = 120
};
diff --git a/src/network/access/qnetworkaccesscache_p.h b/src/network/access/qnetworkaccesscache_p.h
index d0c032de6f..3be7967ca1 100644
--- a/src/network/access/qnetworkaccesscache_p.h
+++ b/src/network/access/qnetworkaccesscache_p.h
@@ -87,7 +87,4 @@ private:
QT_END_NAMESPACE
-QT_DECL_METATYPE_EXTERN_TAGGED(QNetworkAccessCache::CacheableObject*,
- QNetworkAccessCache__CacheableObject_ptr, /* not exported */)
-
#endif
diff --git a/src/network/access/qnetworkaccesscachebackend.cpp b/src/network/access/qnetworkaccesscachebackend.cpp
index 99bef10488..ead5fe2ef5 100644
--- a/src/network/access/qnetworkaccesscachebackend.cpp
+++ b/src/network/access/qnetworkaccesscachebackend.cpp
@@ -12,6 +12,8 @@
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
QNetworkAccessCacheBackend::QNetworkAccessCacheBackend()
: QNetworkAccessBackend(QNetworkAccessBackend::TargetType::Local)
{
@@ -45,21 +47,21 @@ bool QNetworkAccessCacheBackend::sendCacheContents()
return false;
QNetworkCacheMetaData::AttributesMap attributes = item.attributes();
- setAttribute(QNetworkRequest::HttpStatusCodeAttribute, attributes.value(QNetworkRequest::HttpStatusCodeAttribute));
- setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
-
- // set the raw headers
- const QNetworkCacheMetaData::RawHeaderList rawHeaders = item.rawHeaders();
- for (const auto &header : rawHeaders) {
- if (header.first.toLower() == "cache-control") {
- const QByteArray cacheControlValue = header.second.toLower();
- if (cacheControlValue.contains("must-revalidate")
- || cacheControlValue.contains("no-cache")) {
- return false;
- }
- }
- setRawHeader(header.first, header.second);
+ setAttribute(QNetworkRequest::HttpStatusCodeAttribute,
+ attributes.value(QNetworkRequest::HttpStatusCodeAttribute));
+ setAttribute(QNetworkRequest::HttpReasonPhraseAttribute,
+ attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
+
+ // set the headers
+ auto headers = item.headers();
+ const auto cacheControlValue = QLatin1StringView(
+ headers.value(QHttpHeaders::WellKnownHeader::CacheControl));
+ // RFC 9111 Section 5.2 Cache Control
+ if (cacheControlValue.contains("must-revalidate"_L1, Qt::CaseInsensitive)
+ || cacheControlValue.contains("no-cache"_L1, Qt::CaseInsensitive)) {
+ return false;
}
+ setHeaders(std::move(headers));
// handle a possible redirect
QVariant redirectionTarget = attributes.value(QNetworkRequest::RedirectionTargetAttribute);
diff --git a/src/network/access/qnetworkaccessdebugpipebackend.cpp b/src/network/access/qnetworkaccessdebugpipebackend.cpp
index 2df14289a4..e0a89e3646 100644
--- a/src/network/access/qnetworkaccessdebugpipebackend.cpp
+++ b/src/network/access/qnetworkaccessdebugpipebackend.cpp
@@ -97,7 +97,9 @@ qint64 QNetworkAccessDebugPipeBackend::read(char *data, qint64 maxlen)
if (haveRead == -1) {
hasDownloadFinished = true;
// this ensures a good last downloadProgress is emitted
- setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
+ auto h = headers();
+ h.removeAll(QHttpHeaders::WellKnownHeader::ContentLength);
+ setHeaders(std::move(h));
possiblyFinish();
return haveRead;
}
@@ -184,7 +186,7 @@ void QNetworkAccessDebugPipeBackend::possiblyFinish()
void QNetworkAccessDebugPipeBackend::close()
{
- qWarning("QNetworkAccessDebugPipeBackend::closeDownstreamChannel() %d",operation());;
+ qWarning("QNetworkAccessDebugPipeBackend::closeDownstreamChannel() %d",operation());
//if (operation() == QNetworkAccessManager::GetOperation)
// socket.disconnectFromHost();
}
diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp
index 2e3a3d81f1..ae99721758 100644
--- a/src/network/access/qnetworkaccessmanager.cpp
+++ b/src/network/access/qnetworkaccessmanager.cpp
@@ -68,16 +68,17 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+using namespace std::chrono_literals;
Q_LOGGING_CATEGORY(lcQnam, "qt.network.access.manager")
Q_APPLICATION_STATIC(QNetworkAccessFileBackendFactory, fileBackend)
-#ifdef QT_BUILD_INTERNAL
+#if QT_CONFIG(private_tests)
Q_GLOBAL_STATIC(QNetworkAccessDebugPipeBackendFactory, debugpipeBackend)
#endif
-Q_APPLICATION_STATIC(QFactoryLoader, loader, QNetworkAccessBackendFactory_iid, "/networkaccess"_L1)
+Q_APPLICATION_STATIC(QFactoryLoader, qnabfLoader, QNetworkAccessBackendFactory_iid, "/networkaccess"_L1)
#if defined(Q_OS_MACOS)
bool getProxyAuth(const QString& proxyHostname, const QString &scheme, QString& username, QString& password)
@@ -153,7 +154,7 @@ bool getProxyAuth(const QString& proxyHostname, const QString &scheme, QString&
static void ensureInitialized()
{
-#ifdef QT_BUILD_INTERNAL
+#if QT_CONFIG(private_tests)
(void) debugpipeBackend();
#endif
@@ -780,6 +781,46 @@ QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request)
}
/*!
+ \since 6.7
+
+ \overload
+
+ \note A GET request with a message body is not cached.
+
+ \note If the request is redirected, the message body will be kept only if the status code is
+ 307 or 308.
+*/
+
+QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request, QIODevice *data)
+{
+ QNetworkRequest newRequest(request);
+ return d_func()->postProcess(
+ createRequest(QNetworkAccessManager::GetOperation, newRequest, data));
+}
+
+/*!
+ \since 6.7
+
+ \overload
+
+ \note A GET request with a message body is not cached.
+
+ \note If the request is redirected, the message body will be kept only if the status code is
+ 307 or 308.
+*/
+
+QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request, const QByteArray &data)
+{
+ QBuffer *buffer = new QBuffer;
+ buffer->setData(data);
+ buffer->open(QIODevice::ReadOnly);
+
+ QNetworkReply *reply = get(request, buffer);
+ buffer->setParent(reply);
+ return reply;
+}
+
+/*!
Sends an HTTP POST request to the destination specified by \a request
and returns a new QNetworkReply object opened for reading that will
contain the reply sent by the server. The contents of the \a data
@@ -815,6 +856,24 @@ QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, const
return reply;
}
+/*!
+ \overload
+
+ \since 6.8
+
+ Sends the POST request specified by \a request without a body and returns
+ a new QNetworkReply object.
+*/
+QNetworkReply *QNetworkAccessManager::post(const QNetworkRequest &request, std::nullptr_t nptr)
+{
+ Q_UNUSED(nptr);
+ QIODevice *dev = nullptr;
+
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::PostOperation,
+ request,
+ dev));
+}
+
#if QT_CONFIG(http) || defined(Q_OS_WASM)
/*!
\since 4.8
@@ -899,6 +958,23 @@ QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, const
}
/*!
+ \overload
+
+ \since 6.8
+
+ Sends the PUT request specified by \a request without a body and returns
+ a new QNetworkReply object.
+*/
+
+QNetworkReply *QNetworkAccessManager::put(const QNetworkRequest &request, std::nullptr_t nptr)
+{
+ Q_UNUSED(nptr);
+ QIODevice *dev = nullptr;
+
+ return d_func()->postProcess(createRequest(QNetworkAccessManager::PutOperation, request, dev));
+}
+
+/*!
\since 4.6
Sends a request to delete the resource identified by the URL of \a request.
@@ -1132,8 +1208,8 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
}
#if QT_CONFIG(http) || defined (Q_OS_WASM)
- if (!req.transferTimeout())
- req.setTransferTimeout(transferTimeout());
+ if (req.transferTimeoutAsDuration() == 0ms)
+ req.setTransferTimeout(transferTimeoutAsDuration());
#endif
if (autoDeleteReplies()
@@ -1144,6 +1220,13 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
bool isLocalFile = req.url().isLocalFile();
QString scheme = req.url().scheme();
+ // Remap local+http to unix+http to make further processing easier
+ if (scheme == "local+http"_L1) {
+ scheme = u"unix+http"_s;
+ QUrl url = req.url();
+ url.setScheme(scheme);
+ req.setUrl(url);
+ }
// fast path for GET on file:// URLs
// The QNetworkAccessFileBackend will right now only be used for PUT
@@ -1179,12 +1262,14 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
}
}
QNetworkRequest request = req;
+ auto h = request.headers();
#ifndef Q_OS_WASM // Content-length header is not allowed to be set by user in wasm
- if (!request.header(QNetworkRequest::ContentLengthHeader).isValid() &&
+ if (!h.contains(QHttpHeaders::WellKnownHeader::ContentLength) &&
outgoingData && !outgoingData->isSequential()) {
// request has no Content-Length
// but the data that is outgoing is random-access
- request.setHeader(QNetworkRequest::ContentLengthHeader, outgoingData->size());
+ h.append(QHttpHeaders::WellKnownHeader::ContentLength,
+ QByteArray::number(outgoingData->size()));
}
#endif
if (static_cast<QNetworkRequest::LoadControl>
@@ -1193,9 +1278,11 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
if (d->cookieJar) {
QList<QNetworkCookie> cookies = d->cookieJar->cookiesForUrl(request.url());
if (!cookies.isEmpty())
- request.setHeader(QNetworkRequest::CookieHeader, QVariant::fromValue(cookies));
+ h.replaceOrAppend(QHttpHeaders::WellKnownHeader::Cookie,
+ QNetworkHeadersPrivate::fromCookieList(cookies));
}
}
+ request.setHeaders(std::move(h));
#ifdef Q_OS_WASM
Q_UNUSED(isLocalFile);
// Support http, https, and relative urls
@@ -1216,11 +1303,15 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera
u"https",
u"preconnect-https",
#endif
+ u"unix+http",
};
// Since Qt 5 we use the new QNetworkReplyHttpImpl
if (std::find(std::begin(httpSchemes), std::end(httpSchemes), scheme) != std::end(httpSchemes)) {
+
#ifndef QT_NO_SSL
- if (isStrictTransportSecurityEnabled() && d->stsCache.isKnownHost(request.url())) {
+ const bool isLocalSocket = scheme.startsWith("unix"_L1);
+ if (!isLocalSocket && isStrictTransportSecurityEnabled()
+ && d->stsCache.isKnownHost(request.url())) {
QUrl stsUrl(request.url());
// RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818],
@@ -1311,6 +1402,8 @@ QStringList QNetworkAccessManager::supportedSchemesImplementation() const
// Those ones don't exist in backends
#if QT_CONFIG(http)
schemes << QStringLiteral("http");
+ schemes << QStringLiteral("unix+http");
+ schemes << QStringLiteral("local+http");
#ifndef QT_NO_SSL
if (QSslSocket::supportsSsl())
schemes << QStringLiteral("https");
@@ -1384,38 +1477,59 @@ void QNetworkAccessManager::setAutoDeleteReplies(bool shouldAutoDelete)
}
/*!
+ \fn int QNetworkAccessManager::transferTimeout() const
\since 5.15
Returns the timeout used for transfers, in milliseconds.
- This timeout is zero if setTransferTimeout() hasn't been
- called, which means that the timeout is not used.
+ \sa setTransferTimeout()
*/
-int QNetworkAccessManager::transferTimeout() const
+
+/*!
+ \fn void QNetworkAccessManager::setTransferTimeout(int timeout)
+ \since 5.15
+
+ Sets \a timeout as the transfer timeout in milliseconds.
+
+ \sa setTransferTimeout(std::chrono::milliseconds),
+ transferTimeout(), transferTimeoutAsDuration()
+*/
+
+/*!
+ \since 6.7
+
+ Returns the timeout duration after which the transfer is aborted if no
+ data is exchanged.
+
+ The default duration is zero, which means that the timeout is not used.
+
+ \sa setTransferTimeout(std::chrono::milliseconds)
+ */
+std::chrono::milliseconds QNetworkAccessManager::transferTimeoutAsDuration() const
{
return d_func()->transferTimeout;
}
/*!
- \since 5.15
+ \since 6.7
- Sets \a timeout as the transfer timeout in milliseconds.
+ Sets the timeout \a duration to abort the transfer if no data is exchanged.
Transfers are aborted if no bytes are transferred before
the timeout expires. Zero means no timer is set. If no
argument is provided, the timeout is
- QNetworkRequest::DefaultTransferTimeoutConstant. If this function
+ QNetworkRequest::DefaultTransferTimeout. If this function
is not called, the timeout is disabled and has the
value zero. The request-specific non-zero timeouts set for
the requests that are executed override this value. This means
that if QNetworkAccessManager has an enabled timeout, it needs
to be disabled to execute a request without a timeout.
- \sa transferTimeout()
-*/
-void QNetworkAccessManager::setTransferTimeout(int timeout)
+ \sa transferTimeoutAsDuration()
+ */
+void QNetworkAccessManager::setTransferTimeout(std::chrono::milliseconds duration)
{
- d_func()->transferTimeout = timeout;
+ d_func()->transferTimeout = duration;
}
void QNetworkAccessManagerPrivate::_q_replyFinished(QNetworkReply *reply)
@@ -1649,9 +1763,10 @@ QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkReq
{
// copy the request, we probably need to add some headers
QNetworkRequest newRequest(request);
+ auto h = newRequest.headers();
// add Content-Type header if not there already
- if (!request.header(QNetworkRequest::ContentTypeHeader).isValid()) {
+ if (!h.contains(QHttpHeaders::WellKnownHeader::ContentType)) {
QByteArray contentType;
contentType.reserve(34 + multiPart->d_func()->boundary.size());
contentType += "multipart/";
@@ -1671,14 +1786,15 @@ QNetworkRequest QNetworkAccessManagerPrivate::prepareMultipart(const QNetworkReq
}
// putting the boundary into quotes, recommended in RFC 2046 section 5.1.1
contentType += "; boundary=\"" + multiPart->d_func()->boundary + '"';
- newRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(contentType));
+ h.append(QHttpHeaders::WellKnownHeader::ContentType, contentType);
}
// add MIME-Version header if not there already (we must include the header
// if the message conforms to RFC 2045, see section 4 of that RFC)
- QByteArray mimeHeader("MIME-Version");
- if (!request.hasRawHeader(mimeHeader))
- newRequest.setRawHeader(mimeHeader, QByteArray("1.0"));
+ if (!h.contains(QHttpHeaders::WellKnownHeader::MIMEVersion))
+ h.append(QHttpHeaders::WellKnownHeader::MIMEVersion, "1.0"_ba);
+
+ newRequest.setHeaders(std::move(h));
QIODevice *device = multiPart->d_func()->device;
if (!device->isReadable()) {
@@ -1703,13 +1819,13 @@ void QNetworkAccessManagerPrivate::ensureBackendPluginsLoaded()
{
Q_CONSTINIT static QBasicMutex mutex;
std::unique_lock locker(mutex);
- if (!loader())
+ if (!qnabfLoader())
return;
#if QT_CONFIG(library)
- loader->update();
+ qnabfLoader->update();
#endif
int index = 0;
- while (loader->instance(index))
+ while (qnabfLoader->instance(index))
++index;
}
diff --git a/src/network/access/qnetworkaccessmanager.h b/src/network/access/qnetworkaccessmanager.h
index f0c99c03b8..0d069b2a9b 100644
--- a/src/network/access/qnetworkaccessmanager.h
+++ b/src/network/access/qnetworkaccessmanager.h
@@ -80,10 +80,14 @@ public:
QNetworkReply *head(const QNetworkRequest &request);
QNetworkReply *get(const QNetworkRequest &request);
+ QNetworkReply *get(const QNetworkRequest &request, QIODevice *data);
+ QNetworkReply *get(const QNetworkRequest &request, const QByteArray &data);
QNetworkReply *post(const QNetworkRequest &request, QIODevice *data);
QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data);
+ QNetworkReply *post(const QNetworkRequest &request, std::nullptr_t nptr);
QNetworkReply *put(const QNetworkRequest &request, QIODevice *data);
QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data);
+ QNetworkReply *put(const QNetworkRequest &request, std::nullptr_t nptr);
QNetworkReply *deleteResource(const QNetworkRequest &request);
QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data = nullptr);
QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data);
@@ -109,8 +113,14 @@ public:
bool autoDeleteReplies() const;
void setAutoDeleteReplies(bool autoDelete);
+ QT_NETWORK_INLINE_SINCE(6, 8)
int transferTimeout() const;
- void setTransferTimeout(int timeout = QNetworkRequest::DefaultTransferTimeoutConstant);
+ QT_NETWORK_INLINE_SINCE(6, 8)
+ void setTransferTimeout(int timeout);
+
+ std::chrono::milliseconds transferTimeoutAsDuration() const;
+ void setTransferTimeout(std::chrono::milliseconds duration =
+ QNetworkRequest::DefaultTransferTimeout);
Q_SIGNALS:
#ifndef QT_NO_NETWORKPROXY
@@ -147,6 +157,18 @@ private:
#endif
};
+#if QT_NETWORK_INLINE_IMPL_SINCE(6, 8)
+int QNetworkAccessManager::transferTimeout() const
+{
+ return int(transferTimeoutAsDuration().count());
+}
+
+void QNetworkAccessManager::setTransferTimeout(int timeout)
+{
+ setTransferTimeout(std::chrono::milliseconds(timeout));
+}
+#endif // INLINE_SINCE 6.8
+
QT_END_NAMESPACE
#endif
diff --git a/src/network/access/qnetworkaccessmanager_p.h b/src/network/access/qnetworkaccessmanager_p.h
index 050da2060f..491a5acaa4 100644
--- a/src/network/access/qnetworkaccessmanager_p.h
+++ b/src/network/access/qnetworkaccessmanager_p.h
@@ -131,7 +131,7 @@ public:
bool autoDeleteReplies = false;
- int transferTimeout = 0;
+ std::chrono::milliseconds transferTimeout{0};
Q_DECLARE_PUBLIC(QNetworkAccessManager)
};
diff --git a/src/network/access/qnetworkcookie.cpp b/src/network/access/qnetworkcookie.cpp
index b195609697..8ea5fdbe57 100644
--- a/src/network/access/qnetworkcookie.cpp
+++ b/src/network/access/qnetworkcookie.cpp
@@ -7,12 +7,14 @@
#include "qnetworkrequest.h"
#include "qnetworkreply.h"
#include "QtCore/qbytearray.h"
+#include "QtCore/qdatetime.h"
#include "QtCore/qdebug.h"
#include "QtCore/qlist.h"
#include "QtCore/qlocale.h"
#include <QtCore/qregularexpression.h>
#include "QtCore/qstring.h"
#include "QtCore/qstringlist.h"
+#include "QtCore/qtimezone.h"
#include "QtCore/qurl.h"
#include "QtNetwork/qhostaddress.h"
#include "private/qobject_p.h"
@@ -374,7 +376,7 @@ void QNetworkCookie::setValue(const QByteArray &value)
}
// ### move this to qnetworkcookie_p.h and share with qnetworkaccesshttpbackend
-static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &position, bool isNameValue)
+static QPair<QByteArray, QByteArray> nextField(QByteArrayView text, int &position, bool isNameValue)
{
// format is one of:
// (1) token
@@ -394,11 +396,11 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi
equalsPosition = semiColonPosition; //no '=' means there is an attribute-name but no attribute-value
}
- QByteArray first = text.mid(position, equalsPosition - position).trimmed();
+ QByteArray first = text.mid(position, equalsPosition - position).trimmed().toByteArray();
QByteArray second;
int secondLength = semiColonPosition - equalsPosition - 1;
if (secondLength > 0)
- second = text.mid(equalsPosition + 1, secondLength).trimmed();
+ second = text.mid(equalsPosition + 1, secondLength).trimmed().toByteArray();
position = semiColonPosition;
return qMakePair(first, second);
@@ -441,29 +443,33 @@ static QPair<QByteArray, QByteArray> nextField(const QByteArray &text, int &posi
*/
namespace {
-QByteArray sameSiteToRawString(QNetworkCookie::SameSite samesite)
+
+constexpr QByteArrayView sameSiteNone() noexcept { return "None"; }
+constexpr QByteArrayView sameSiteLax() noexcept { return "Lax"; }
+constexpr QByteArrayView sameSiteStrict() noexcept { return "Strict"; }
+
+QByteArrayView sameSiteToRawString(QNetworkCookie::SameSite samesite) noexcept
{
switch (samesite) {
case QNetworkCookie::SameSite::None:
- return QByteArrayLiteral("None");
+ return sameSiteNone();
case QNetworkCookie::SameSite::Lax:
- return QByteArrayLiteral("Lax");
+ return sameSiteLax();
case QNetworkCookie::SameSite::Strict:
- return QByteArrayLiteral("Strict");
+ return sameSiteStrict();
case QNetworkCookie::SameSite::Default:
break;
}
- return QByteArray();
+ return QByteArrayView();
}
-QNetworkCookie::SameSite sameSiteFromRawString(QByteArray str)
+QNetworkCookie::SameSite sameSiteFromRawString(QByteArrayView str) noexcept
{
- str = str.toLower();
- if (str == QByteArrayLiteral("none"))
+ if (str.compare(sameSiteNone(), Qt::CaseInsensitive) == 0)
return QNetworkCookie::SameSite::None;
- if (str == QByteArrayLiteral("lax"))
+ if (str.compare(sameSiteLax(), Qt::CaseInsensitive) == 0)
return QNetworkCookie::SameSite::Lax;
- if (str == QByteArrayLiteral("strict"))
+ if (str.compare(sameSiteStrict(), Qt::CaseInsensitive) == 0)
return QNetworkCookie::SameSite::Strict;
return QNetworkCookie::SameSite::Default;
}
@@ -574,7 +580,7 @@ static inline bool isValueSeparator(char c)
static inline bool isWhitespace(char c)
{ return c == ' ' || c == '\t'; }
-static bool checkStaticArray(int &val, const QByteArray &dateString, int at, const char *array, int size)
+static bool checkStaticArray(int &val, QByteArrayView dateString, int at, const char *array, int size)
{
if (dateString[at] < 'a' || dateString[at] > 'z')
return false;
@@ -621,7 +627,7 @@ static bool checkStaticArray(int &val, const QByteArray &dateString, int at, con
Or in their own words:
"} // else what the hell is this."
*/
-static QDateTime parseDateString(const QByteArray &dateString)
+static QDateTime parseDateString(QByteArrayView dateString)
{
QTime time;
// placeholders for values when we are not sure it is a year, month or day
@@ -684,13 +690,13 @@ static QDateTime parseDateString(const QByteArray &dateString)
int hours = 0;
switch (end - 1) {
case 4:
- minutes = atoi(dateString.mid(at + 3, 2).constData());
+ minutes = dateString.mid(at + 3, 2).toInt();
Q_FALLTHROUGH();
case 2:
- hours = atoi(dateString.mid(at + 1, 2).constData());
+ hours = dateString.mid(at + 1, 2).toInt();
break;
case 1:
- hours = atoi(dateString.mid(at + 1, 1).constData());
+ hours = dateString.mid(at + 1, 1).toInt();
break;
default:
at += end;
@@ -741,7 +747,7 @@ static QDateTime parseDateString(const QByteArray &dateString)
if (isNumber(dateString[at + 1])
&& isNumber(dateString[at + 2])
&& isNumber(dateString[at + 3])) {
- year = atoi(dateString.mid(at, 4).constData());
+ year = dateString.mid(at, 4).toInt();
at += 4;
#ifdef PARSEDATESTRINGDEBUG
qDebug() << "Year:" << year;
@@ -757,7 +763,7 @@ static QDateTime parseDateString(const QByteArray &dateString)
if (dateString.size() > at + 1
&& isNumber(dateString[at + 1]))
++length;
- int x = atoi(dateString.mid(at, length).constData());
+ int x = dateString.mid(at, length).toInt();
if (year == -1 && (x > 31 || x == 0)) {
year = x;
} else {
@@ -906,11 +912,11 @@ static QDateTime parseDateString(const QByteArray &dateString)
if (!date.isValid())
date = QDate(day + y2k, month, year);
- QDateTime dateTime(date, time, Qt::UTC);
+ QDateTime dateTime(date, time, QTimeZone::UTC);
- if (zoneOffset != -1) {
+ if (zoneOffset != -1)
dateTime = dateTime.addSecs(zoneOffset);
- }
+
if (!dateTime.isValid())
return QDateTime();
return dateTime;
@@ -926,19 +932,19 @@ static QDateTime parseDateString(const QByteArray &dateString)
cookie that is parsed.
\sa toRawForm()
+ \note In Qt versions prior to 6.7, this function took QByteArray only.
*/
-QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString)
+QList<QNetworkCookie> QNetworkCookie::parseCookies(QByteArrayView cookieString)
{
// cookieString can be a number of set-cookie header strings joined together
// by \n, parse each line separately.
QList<QNetworkCookie> cookies;
- QList<QByteArray> list = cookieString.split('\n');
- for (int a = 0; a < list.size(); a++)
- cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(list.at(a));
+ for (auto s : QLatin1StringView(cookieString).tokenize('\n'_L1))
+ cookies += QNetworkCookiePrivate::parseSetCookieHeaderLine(s);
return cookies;
}
-QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByteArray &cookieString)
+QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(QByteArrayView cookieString)
{
// According to http://wp.netscape.com/newsref/std/cookie_spec.html,<
// the Set-Cookie response header is of the format:
@@ -971,28 +977,27 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
case ';':
// new field in the cookie
field = nextField(cookieString, position, false);
- field.first = field.first.toLower(); // everything but the NAME=VALUE is case-insensitive
- if (field.first == "expires") {
+ if (field.first.compare("expires", Qt::CaseInsensitive) == 0) {
position -= field.second.size();
int end;
for (end = position; end < length; ++end)
if (isValueSeparator(cookieString.at(end)))
break;
- QByteArray dateString = cookieString.mid(position, end - position).trimmed();
+ QByteArray dateString = cookieString.mid(position, end - position).trimmed().toByteArray().toLower();
position = end;
- QDateTime dt = parseDateString(dateString.toLower());
+ QDateTime dt = parseDateString(dateString);
if (dt.isValid())
cookie.setExpirationDate(dt);
//if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.1)
- } else if (field.first == "domain") {
- QByteArray rawDomain = field.second;
+ } else if (field.first.compare("domain", Qt::CaseInsensitive) == 0) {
+ QByteArrayView rawDomain = field.second;
//empty domain should be ignored (RFC6265 section 5.2.3)
if (!rawDomain.isEmpty()) {
- QString maybeLeadingDot;
+ QLatin1StringView maybeLeadingDot;
if (rawDomain.startsWith('.')) {
- maybeLeadingDot = u'.';
+ maybeLeadingDot = "."_L1;
rawDomain = rawDomain.mid(1);
}
@@ -1007,7 +1012,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
return result;
}
}
- } else if (field.first == "max-age") {
+ } else if (field.first.compare("max-age", Qt::CaseInsensitive) == 0) {
bool ok = false;
int secs = field.second.toInt(&ok);
if (ok) {
@@ -1019,7 +1024,7 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
}
}
//if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.2)
- } else if (field.first == "path") {
+ } else if (field.first.compare("path", Qt::CaseInsensitive) == 0) {
if (field.second.startsWith('/')) {
// ### we should treat cookie paths as an octet sequence internally
// However RFC6265 says we should assume UTF-8 for presentation as a string
@@ -1029,11 +1034,11 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
// and also IETF test case path0030 which has valid and empty path in the same cookie
cookie.setPath(QString());
}
- } else if (field.first == "secure") {
+ } else if (field.first.compare("secure", Qt::CaseInsensitive) == 0) {
cookie.setSecure(true);
- } else if (field.first == "httponly") {
+ } else if (field.first.compare("httponly", Qt::CaseInsensitive) == 0) {
cookie.setHttpOnly(true);
- } else if (field.first == "samesite") {
+ } else if (field.first.compare("samesite", Qt::CaseInsensitive) == 0) {
cookie.setSameSitePolicy(sameSiteFromRawString(field.second));
} else {
// ignore unknown fields in the cookie (RFC6265 section 5.2, rule 6)
diff --git a/src/network/access/qnetworkcookie.h b/src/network/access/qnetworkcookie.h
index d4f4942288..aed9c8af12 100644
--- a/src/network/access/qnetworkcookie.h
+++ b/src/network/access/qnetworkcookie.h
@@ -75,7 +75,10 @@ public:
bool hasSameIdentifier(const QNetworkCookie &other) const;
void normalize(const QUrl &url);
+#if QT_NETWORK_REMOVED_SINCE(6, 7)
static QList<QNetworkCookie> parseCookies(const QByteArray &cookieString);
+#endif
+ static QList<QNetworkCookie> parseCookies(QByteArrayView cookieString);
private:
QSharedDataPointer<QNetworkCookiePrivate> d;
diff --git a/src/network/access/qnetworkcookie_p.h b/src/network/access/qnetworkcookie_p.h
index 7874b2c16a..ce4378fd64 100644
--- a/src/network/access/qnetworkcookie_p.h
+++ b/src/network/access/qnetworkcookie_p.h
@@ -25,7 +25,7 @@ class QNetworkCookiePrivate: public QSharedData
{
public:
QNetworkCookiePrivate() = default;
- static QList<QNetworkCookie> parseSetCookieHeaderLine(const QByteArray &cookieString);
+ static QList<QNetworkCookie> parseSetCookieHeaderLine(QByteArrayView cookieString);
QDateTime expirationDate;
QString domain;
@@ -43,7 +43,7 @@ static inline bool isLWS(char c)
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
-static int nextNonWhitespace(const QByteArray &text, int from)
+static int nextNonWhitespace(QByteArrayView text, int from)
{
// RFC 2616 defines linear whitespace as:
// LWS = [CRLF] 1*( SP | HT )
diff --git a/src/network/access/qnetworkcookiejar.cpp b/src/network/access/qnetworkcookiejar.cpp
index bbb62455a5..82746f91b1 100644
--- a/src/network/access/qnetworkcookiejar.cpp
+++ b/src/network/access/qnetworkcookiejar.cpp
@@ -112,7 +112,7 @@ void QNetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList)
d->allCookies = cookieList;
}
-static inline bool isParentPath(const QString &path, const QString &reference)
+static inline bool isParentPath(QStringView path, QStringView reference)
{
if ((path.isEmpty() && reference == "/"_L1) || path.startsWith(reference)) {
//The cookie-path and the request-path are identical.
@@ -131,12 +131,12 @@ static inline bool isParentPath(const QString &path, const QString &reference)
return false;
}
-static inline bool isParentDomain(const QString &domain, const QString &reference)
+static inline bool isParentDomain(QStringView domain, QStringView reference)
{
if (!reference.startsWith(u'.'))
return domain == reference;
- return domain.endsWith(reference) || domain == QStringView{reference}.mid(1);
+ return domain.endsWith(reference) || domain == reference.mid(1);
}
/*!
@@ -200,49 +200,38 @@ QList<QNetworkCookie> QNetworkCookieJar::cookiesForUrl(const QUrl &url) const
Q_D(const QNetworkCookieJar);
const QDateTime now = QDateTime::currentDateTimeUtc();
QList<QNetworkCookie> result;
- bool isEncrypted = url.scheme() == "https"_L1;
+ const bool isEncrypted = url.scheme() == "https"_L1;
// scan our cookies for something that matches
- QList<QNetworkCookie>::ConstIterator it = d->allCookies.constBegin(),
- end = d->allCookies.constEnd();
- for ( ; it != end; ++it) {
- if (!isParentDomain(url.host(), it->domain()))
+ for (const auto &cookie : std::as_const(d->allCookies)) {
+ if (!isEncrypted && cookie.isSecure())
continue;
- if (!isParentPath(url.path(), it->path()))
+ if (!cookie.isSessionCookie() && cookie.expirationDate() < now)
continue;
- if (!(*it).isSessionCookie() && (*it).expirationDate() < now)
+ const QString urlHost = url.host();
+ const QString cookieDomain = cookie.domain();
+ if (!isParentDomain(urlHost, cookieDomain))
continue;
- if ((*it).isSecure() && !isEncrypted)
+ if (!isParentPath(url.path(), cookie.path()))
continue;
- QString domain = it->domain();
+ QStringView domain = cookieDomain;
if (domain.startsWith(u'.')) /// Qt6?: remove when compliant with RFC6265
- domain = domain.mid(1);
+ domain = domain.sliced(1);
#if QT_CONFIG(topleveldomain)
- if (qIsEffectiveTLD(domain) && url.host() != domain)
+ if (urlHost != domain && qIsEffectiveTLD(domain))
continue;
#else
- if (!domain.contains(u'.') && url.host() != domain)
+ if (!domain.contains(u'.') && urlHost != domain)
continue;
#endif // topleveldomain
- // insert this cookie into result, sorted by path
- QList<QNetworkCookie>::Iterator insertIt = result.begin();
- while (insertIt != result.end()) {
- if (insertIt->path().size() < it->path().size()) {
- // insert here
- insertIt = result.insert(insertIt, *it);
- break;
- } else {
- ++insertIt;
- }
- }
-
- // this is the shortest path yet, just append
- if (insertIt == result.end())
- result += *it;
+ result += cookie;
}
+ auto longerPath = [](const auto &c1, const auto &c2)
+ { return c1.path().size() > c2.path().size(); };
+ std::sort(result.begin(), result.end(), longerPath);
return result;
}
@@ -299,12 +288,11 @@ bool QNetworkCookieJar::updateCookie(const QNetworkCookie &cookie)
bool QNetworkCookieJar::deleteCookie(const QNetworkCookie &cookie)
{
Q_D(QNetworkCookieJar);
- QList<QNetworkCookie>::Iterator it;
- for (it = d->allCookies.begin(); it != d->allCookies.end(); ++it) {
- if (it->hasSameIdentifier(cookie)) {
- d->allCookies.erase(it);
- return true;
- }
+ const auto it = std::find_if(d->allCookies.cbegin(), d->allCookies.cend(),
+ [&cookie](const auto &c) { return c.hasSameIdentifier(cookie); });
+ if (it != d->allCookies.cend()) {
+ d->allCookies.erase(it);
+ return true;
}
return false;
}
@@ -317,13 +305,14 @@ bool QNetworkCookieJar::deleteCookie(const QNetworkCookie &cookie)
*/
bool QNetworkCookieJar::validateCookie(const QNetworkCookie &cookie, const QUrl &url) const
{
- QString domain = cookie.domain();
+ const QString cookieDomain = cookie.domain();
+ QStringView domain = cookieDomain;
const QString host = url.host();
if (!isParentDomain(domain, host) && !isParentDomain(host, domain))
return false; // not accepted
if (domain.startsWith(u'.'))
- domain = domain.mid(1);
+ domain = domain.sliced(1);
// We shouldn't reject if:
// "[...] the domain-attribute is identical to the canonicalized request-host"
diff --git a/src/network/access/qnetworkdiskcache.cpp b/src/network/access/qnetworkdiskcache.cpp
index 6b52b8d49a..b39924025e 100644
--- a/src/network/access/qnetworkdiskcache.cpp
+++ b/src/network/access/qnetworkdiskcache.cpp
@@ -12,16 +12,14 @@
#include <qdir.h>
#include <qdatastream.h>
#include <qdatetime.h>
-#include <qdiriterator.h>
+#include <qdirlisting.h>
#include <qurl.h>
#include <qcryptographichash.h>
#include <qdebug.h>
-#include <QMultiMap>
#include <memory>
#define CACHE_POSTFIX ".d"_L1
-#define PREPARED_SLASH "prepared/"_L1
#define CACHE_VERSION 8
#define DATA_DIR "data"_L1
@@ -156,15 +154,11 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData)
return nullptr;
}
- const auto headers = metaData.rawHeaders();
- for (const auto &header : headers) {
- if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
- const qint64 size = header.second.toLongLong();
- if (size > (maximumCacheSize() * 3)/4)
- return nullptr;
- break;
- }
- }
+ const auto sizeValue = metaData.headers().value(QHttpHeaders::WellKnownHeader::ContentLength);
+ const qint64 size = sizeValue.toLongLong();
+ if (size > (maximumCacheSize() * 3)/4)
+ return nullptr;
+
std::unique_ptr<QCacheItem> cacheItem = std::make_unique<QCacheItem>();
cacheItem->metaData = metaData;
@@ -173,13 +167,9 @@ QIODevice *QNetworkDiskCache::prepare(const QNetworkCacheMetaData &metaData)
cacheItem->data.open(QBuffer::ReadWrite);
device = &(cacheItem->data);
} else {
- QString templateName = d->tmpCacheFileName();
- QT_TRY {
- cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
- } QT_CATCH(...) {
- cacheItem->file = nullptr;
- }
- if (!cacheItem->file || !cacheItem->file->open()) {
+ QString fileName = d->cacheFileName(cacheItem->metaData.url());
+ cacheItem->file = new(std::nothrow) QSaveFile(fileName, &cacheItem->data);
+ if (!cacheItem->file || !cacheItem->file->open(QFileDevice::WriteOnly)) {
qWarning("QNetworkDiskCache::prepare() unable to open temporary file");
cacheItem.reset();
return nullptr;
@@ -219,7 +209,6 @@ void QNetworkDiskCache::insert(QIODevice *device)
void QNetworkDiskCachePrivate::prepareLayout()
{
QDir helper;
- helper.mkpath(cacheDirectory + PREPARED_SLASH);
//Create directory and subdirectories 0-F
helper.mkpath(dataDirectory);
@@ -248,9 +237,8 @@ void QNetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem)
currentCacheSize = q->expire();
if (!cacheItem->file) {
- QString templateName = tmpCacheFileName();
- cacheItem->file = new QTemporaryFile(templateName, &cacheItem->data);
- if (cacheItem->file->open()) {
+ cacheItem->file = new QSaveFile(fileName, &cacheItem->data);
+ if (cacheItem->file->open(QFileDevice::WriteOnly)) {
cacheItem->writeHeader(cacheItem->file);
cacheItem->writeCompressedData(cacheItem->file);
}
@@ -258,13 +246,15 @@ void QNetworkDiskCachePrivate::storeItem(QCacheItem *cacheItem)
if (cacheItem->file
&& cacheItem->file->isOpen()
- && cacheItem->file->error() == QFile::NoError) {
- cacheItem->file->setAutoRemove(false);
- // ### use atomic rename rather then remove & rename
- if (cacheItem->file->rename(fileName))
- currentCacheSize += cacheItem->file->size();
- else
- cacheItem->file->setAutoRemove(true);
+ && cacheItem->file->error() == QFileDevice::NoError) {
+ // We have to call size() here instead of inside the if-body because
+ // commit() invalidates the file-engine, and size() will create a new
+ // one, pointing at an empty filename.
+ qint64 size = cacheItem->file->size();
+ if (cacheItem->file->commit())
+ currentCacheSize += size;
+ // Delete and unset the QSaveFile, it's invalid now.
+ delete std::exchange(cacheItem->file, nullptr);
}
if (cacheItem->metaData.url() == lastItem.metaData.url())
lastItem.reset();
@@ -483,47 +473,45 @@ qint64 QNetworkDiskCache::expire()
// close file handle to prevent "in use" error when QFile::remove() is called
d->lastItem.reset();
- QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
- QDirIterator it(cacheDirectory(), filters, QDirIterator::Subdirectories);
+ const QDir::Filters filters = QDir::AllDirs | QDir:: Files | QDir::NoDotAndDotDot;
- QMultiMap<QDateTime, QString> cacheItems;
+ struct CacheItem
+ {
+ std::chrono::milliseconds msecs;
+ QString path;
+ qint64 size = 0;
+ };
+ std::vector<CacheItem> cacheItems;
qint64 totalSize = 0;
- while (it.hasNext()) {
- QFileInfo info = it.nextFileInfo();
- QString path = info.filePath();
- QString fileName = info.fileName();
- if (fileName.endsWith(CACHE_POSTFIX)) {
- const QDateTime birthTime = info.fileTime(QFile::FileBirthTime);
- cacheItems.insert(birthTime.isValid() ? birthTime
- : info.fileTime(QFile::FileMetadataChangeTime), path);
- totalSize += info.size();
- }
+ using F = QDirListing::IteratorFlag;
+ for (const auto &dirEntry : QDirListing(cacheDirectory(), filters, F::Recursive)) {
+ if (!dirEntry.fileName().endsWith(CACHE_POSTFIX))
+ continue;
+
+ const QFileInfo &info = dirEntry.fileInfo();
+ QDateTime fileTime = info.birthTime(QTimeZone::UTC);
+ if (!fileTime.isValid())
+ fileTime = info.metadataChangeTime(QTimeZone::UTC);
+ const std::chrono::milliseconds msecs{fileTime.toMSecsSinceEpoch()};
+ const qint64 size = info.size();
+ cacheItems.push_back(CacheItem{msecs, info.filePath(), size});
+ totalSize += size;
}
+ const qint64 goal = (maximumCacheSize() * 9) / 10;
+ if (totalSize < goal)
+ return totalSize; // Nothing to do
+
+ auto byFileTime = [&](const auto &a, const auto &b) { return a.msecs < b.msecs; };
+ std::sort(cacheItems.begin(), cacheItems.end(), byFileTime);
+
[[maybe_unused]] int removedFiles = 0; // used under QNETWORKDISKCACHE_DEBUG
- qint64 goal = (maximumCacheSize() * 9) / 10;
- QMultiMap<QDateTime, QString>::const_iterator i = cacheItems.constBegin();
- while (i != cacheItems.constEnd()) {
+ for (const CacheItem &cached : cacheItems) {
+ QFile::remove(cached.path);
+ ++removedFiles;
+ totalSize -= cached.size;
if (totalSize < goal)
break;
- QString name = i.value();
- QFile file(name);
-
- if (name.contains(PREPARED_SLASH)) {
- for (QCacheItem *item : std::as_const(d->inserting)) {
- if (item && item->file && item->file->fileName() == name) {
- delete item->file;
- item->file = nullptr;
- break;
- }
- }
- }
-
- qint64 size = file.size();
- file.remove();
- totalSize -= size;
- ++removedFiles;
- ++i;
}
#if defined(QNETWORKDISKCACHE_DEBUG)
if (removedFiles > 0) {
@@ -569,12 +557,6 @@ QString QNetworkDiskCachePrivate::uniqueFileName(const QUrl &url)
return pathFragment;
}
-QString QNetworkDiskCachePrivate::tmpCacheFileName() const
-{
- //The subdirectory is presumed to be already read for use.
- return cacheDirectory + PREPARED_SLASH + "XXXXXX"_L1 + CACHE_POSTFIX;
-}
-
/*!
Generates fully qualified path of cached resource from a URL.
*/
@@ -592,31 +574,27 @@ QString QNetworkDiskCachePrivate::cacheFileName(const QUrl &url) const
*/
bool QCacheItem::canCompress() const
{
- bool sizeOk = false;
- bool typeOk = false;
- const auto headers = metaData.rawHeaders();
- for (const auto &header : headers) {
- if (header.first.compare("content-length", Qt::CaseInsensitive) == 0) {
- qint64 size = header.second.toLongLong();
- if (size > MAX_COMPRESSION_SIZE)
- return false;
- else
- sizeOk = true;
- }
+ const auto h = metaData.headers();
- if (header.first.compare("content-type", Qt::CaseInsensitive) == 0) {
- QByteArray type = header.second;
- if (type.startsWith("text/")
- || (type.startsWith("application/")
- && (type.endsWith("javascript") || type.endsWith("ecmascript"))))
- typeOk = true;
- else
- return false;
- }
- if (sizeOk && typeOk)
- return true;
+ const auto sizeValue = h.value(QHttpHeaders::WellKnownHeader::ContentLength);
+ if (sizeValue.empty())
+ return false;
+
+ qint64 size = sizeValue.toLongLong();
+ if (size > MAX_COMPRESSION_SIZE)
+ return false;
+
+ const auto type = h.value(QHttpHeaders::WellKnownHeader::ContentType);
+ if (!type.empty())
+ return false;
+
+ if (!type.startsWith("text/")
+ && !(type.startsWith("application/")
+ && (type.endsWith("javascript") || type.endsWith("ecmascript")))) {
+ return false;
}
- return false;
+
+ return true;
}
enum
@@ -625,7 +603,7 @@ enum
CurrentCacheVersion = CACHE_VERSION
};
-void QCacheItem::writeHeader(QFile *device) const
+void QCacheItem::writeHeader(QFileDevice *device) const
{
QDataStream out(device);
@@ -637,7 +615,7 @@ void QCacheItem::writeHeader(QFile *device) const
out << compressed;
}
-void QCacheItem::writeCompressedData(QFile *device) const
+void QCacheItem::writeCompressedData(QFileDevice *device) const
{
QDataStream out(device);
@@ -648,7 +626,7 @@ void QCacheItem::writeCompressedData(QFile *device) const
Returns \c false if the file is a cache file,
but is an older version and should be removed otherwise true.
*/
-bool QCacheItem::read(QFile *device, bool readData)
+bool QCacheItem::read(QFileDevice *device, bool readData)
{
reset();
@@ -687,7 +665,7 @@ bool QCacheItem::read(QFile *device, bool readData)
if (!device->fileName().endsWith(expectedFilename))
return false;
- return metaData.isValid();
+ return metaData.isValid() && !metaData.headers().isEmpty();
}
QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkdiskcache_p.h b/src/network/access/qnetworkdiskcache_p.h
index 05c97dff08..826f0a1d7d 100644
--- a/src/network/access/qnetworkdiskcache_p.h
+++ b/src/network/access/qnetworkdiskcache_p.h
@@ -20,20 +20,16 @@
#include <qbuffer.h>
#include <qhash.h>
-#include <qtemporaryfile.h>
+#include <qsavefile.h>
QT_REQUIRE_CONFIG(networkdiskcache);
QT_BEGIN_NAMESPACE
-class QFile;
-
class QCacheItem
{
public:
- QCacheItem() : file(nullptr)
- {
- }
+ QCacheItem() = default;
~QCacheItem()
{
reset();
@@ -41,7 +37,7 @@ public:
QNetworkCacheMetaData metaData;
QBuffer data;
- QTemporaryFile *file;
+ QSaveFile *file = nullptr;
inline qint64 size() const
{ return file ? file->size() : data.size(); }
@@ -51,9 +47,9 @@ public:
delete file;
file = nullptr;
}
- void writeHeader(QFile *device) const;
- void writeCompressedData(QFile *device) const;
- bool read(QFile *device, bool readData);
+ void writeHeader(QFileDevice *device) const;
+ void writeCompressedData(QFileDevice *device) const;
+ bool read(QFileDevice *device, bool readData);
bool canCompress() const;
};
@@ -69,7 +65,6 @@ public:
static QString uniqueFileName(const QUrl &url);
QString cacheFileName(const QUrl &url) const;
- QString tmpCacheFileName() const;
bool removeFile(const QString &file);
void storeItem(QCacheItem *item);
void prepareLayout();
diff --git a/src/network/access/qnetworkfile.cpp b/src/network/access/qnetworkfile.cpp
index bfedf044de..fb9ce8232d 100644
--- a/src/network/access/qnetworkfile.cpp
+++ b/src/network/access/qnetworkfile.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qnetworkfile_p.h"
+#include "qnetworkrequest_p.h"
#include <QtCore/QDebug>
#include <QNetworkReply>
@@ -31,8 +32,10 @@ void QNetworkFile::open()
"Cannot open %1: Path is a directory").arg(fileName());
emit error(QNetworkReply::ContentOperationNotPermittedError, msg);
} else {
- emit headerRead(QNetworkRequest::LastModifiedHeader, QVariant::fromValue(fi.lastModified()));
- emit headerRead(QNetworkRequest::ContentLengthHeader, QVariant::fromValue(fi.size()));
+ emit headerRead(QHttpHeaders::WellKnownHeader::LastModified,
+ QNetworkHeadersPrivate::toHttpDate(fi.lastModified()));
+ emit headerRead(QHttpHeaders::WellKnownHeader::ContentLength,
+ QByteArray::number(fi.size()));
opened = QFile::open(QIODevice::ReadOnly | QIODevice::Unbuffered);
if (!opened) {
QString msg = QCoreApplication::translate("QNetworkAccessFileBackend",
diff --git a/src/network/access/qnetworkfile_p.h b/src/network/access/qnetworkfile_p.h
index df772251b4..9dcb63711e 100644
--- a/src/network/access/qnetworkfile_p.h
+++ b/src/network/access/qnetworkfile_p.h
@@ -35,7 +35,7 @@ public Q_SLOTS:
Q_SIGNALS:
void finished(bool ok);
- void headerRead(QNetworkRequest::KnownHeaders header, const QVariant &value);
+ void headerRead(QHttpHeaders::WellKnownHeader, const QByteArray &value);
void error(QNetworkReply::NetworkError error, const QString &message);
};
diff --git a/src/network/access/qnetworkreply.cpp b/src/network/access/qnetworkreply.cpp
index 335a0131ff..3301ae85d2 100644
--- a/src/network/access/qnetworkreply.cpp
+++ b/src/network/access/qnetworkreply.cpp
@@ -612,11 +612,12 @@ QVariant QNetworkReply::header(QNetworkRequest::KnownHeaders header) const
the remote server
\sa rawHeader()
+ \note In Qt versions prior to 6.7, this function took QByteArray only.
*/
-bool QNetworkReply::hasRawHeader(const QByteArray &headerName) const
+bool QNetworkReply::hasRawHeader(QAnyStringView headerName) const
{
Q_D(const QNetworkReply);
- return d->findRawHeader(headerName) != d->rawHeaders.constEnd();
+ return d->headers().contains(headerName);
}
/*!
@@ -627,15 +628,12 @@ bool QNetworkReply::hasRawHeader(const QByteArray &headerName) const
header field.
\sa setRawHeader(), hasRawHeader(), header()
+ \note In Qt versions prior to 6.7, this function took QByteArray only.
*/
-QByteArray QNetworkReply::rawHeader(const QByteArray &headerName) const
+QByteArray QNetworkReply::rawHeader(QAnyStringView headerName) const
{
Q_D(const QNetworkReply);
- QNetworkHeadersPrivate::RawHeadersList::ConstIterator it =
- d->findRawHeader(headerName);
- if (it != d->rawHeaders.constEnd())
- return it->second;
- return QByteArray();
+ return d->rawHeader(headerName);
}
/*! \typedef QNetworkReply::RawHeaderPair
@@ -650,7 +648,20 @@ QByteArray QNetworkReply::rawHeader(const QByteArray &headerName) const
const QList<QNetworkReply::RawHeaderPair>& QNetworkReply::rawHeaderPairs() const
{
Q_D(const QNetworkReply);
- return d->rawHeaders;
+ return d->allRawHeaders();
+}
+
+/*!
+ \since 6.8
+
+ Returns headers that were sent by the remote server.
+
+ \sa setHeaders(), QNetworkRequest::setAttribute(), QNetworkRequest::Attribute
+*/
+QHttpHeaders QNetworkReply::headers() const
+{
+ Q_D(const QNetworkReply);
+ return d->headers();
}
/*!
@@ -888,6 +899,45 @@ void QNetworkReply::setUrl(const QUrl &url)
}
/*!
+ \since 6.8
+
+ Sets \a newHeaders as headers in this network reply, overriding
+ any previously set headers.
+
+ If some headers correspond to the known headers, they will be
+ parsed and the corresponding parsed form will also be set.
+
+ \sa headers(), QNetworkRequest::KnownHeaders
+*/
+void QNetworkReply::setHeaders(const QHttpHeaders &newHeaders)
+{
+ Q_D(QNetworkReply);
+ d->setHeaders(newHeaders);
+}
+
+/*!
+ \overload
+ \since 6.8
+*/
+void QNetworkReply::setHeaders(QHttpHeaders &&newHeaders)
+{
+ Q_D(QNetworkReply);
+ d->setHeaders(std::move(newHeaders));
+}
+
+/*!
+ \since 6.8
+
+ Sets the header \a name to be of value \a value. If \a
+ name was previously set, it is overridden.
+*/
+void QNetworkReply::setWellKnownHeader(QHttpHeaders::WellKnownHeader name, const QByteArray &value)
+{
+ Q_D(QNetworkReply);
+ d->setHeader(name, value);
+}
+
+/*!
Sets the known header \a header to be of value \a value. The
corresponding raw form of the header will be set as well.
diff --git a/src/network/access/qnetworkreply.h b/src/network/access/qnetworkreply.h
index 2a505b07c0..48ec0397c6 100644
--- a/src/network/access/qnetworkreply.h
+++ b/src/network/access/qnetworkreply.h
@@ -97,12 +97,19 @@ public:
QVariant header(QNetworkRequest::KnownHeaders header) const;
// raw headers:
+#if QT_NETWORK_REMOVED_SINCE(6, 7)
bool hasRawHeader(const QByteArray &headerName) const;
+#endif
+ bool hasRawHeader(QAnyStringView headerName) const;
QList<QByteArray> rawHeaderList() const;
+#if QT_NETWORK_REMOVED_SINCE(6, 7)
QByteArray rawHeader(const QByteArray &headerName) const;
+#endif
+ QByteArray rawHeader(QAnyStringView headerName) const;
typedef QPair<QByteArray, QByteArray> RawHeaderPair;
const QList<RawHeaderPair>& rawHeaderPairs() const;
+ QHttpHeaders headers() const;
// attributes
QVariant attribute(QNetworkRequest::Attribute code) const;
@@ -146,6 +153,9 @@ protected:
void setUrl(const QUrl &url);
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
void setRawHeader(const QByteArray &headerName, const QByteArray &value);
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
+ void setWellKnownHeader(QHttpHeaders::WellKnownHeader name, const QByteArray &value);
void setAttribute(QNetworkRequest::Attribute code, const QVariant &value);
#if QT_CONFIG(ssl)
diff --git a/src/network/access/qnetworkreplydataimpl.cpp b/src/network/access/qnetworkreplydataimpl.cpp
index 7cb7621bca..006cfd57cb 100644
--- a/src/network/access/qnetworkreplydataimpl.cpp
+++ b/src/network/access/qnetworkreplydataimpl.cpp
@@ -36,8 +36,11 @@ QNetworkReplyDataImpl::QNetworkReplyDataImpl(QObject *parent, const QNetworkRequ
QByteArray payload;
if (qDecodeDataUrl(url, mimeType, payload)) {
qint64 size = payload.size();
- setHeader(QNetworkRequest::ContentTypeHeader, mimeType);
- setHeader(QNetworkRequest::ContentLengthHeader, size);
+ auto h = headers();
+ h.replaceOrAppend(QHttpHeaders::WellKnownHeader::ContentType, mimeType);
+ h.replaceOrAppend(QHttpHeaders::WellKnownHeader::ContentLength, QByteArray::number(size));
+ setHeaders(std::move(h));
+
QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
d->decodedData.setData(payload);
diff --git a/src/network/access/qnetworkreplyfileimpl.cpp b/src/network/access/qnetworkreplyfileimpl.cpp
index 7d2ba39069..bad0bb7b0a 100644
--- a/src/network/access/qnetworkreplyfileimpl.cpp
+++ b/src/network/access/qnetworkreplyfileimpl.cpp
@@ -60,7 +60,7 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, con
setFinished(true); // We're finished, will emit finished() after ctor is done.
QMetaObject::invokeMethod(this, "errorOccurred", Qt::QueuedConnection,
Q_ARG(QNetworkReply::NetworkError, QNetworkReply::ProtocolInvalidOperationError));
- QMetaObject::invokeMethod(this, [this](){ fileOpenFinished(false); }, Qt::QueuedConnection);
+ QMetaObject::invokeMethod(this, &QNetworkReplyFileImpl::fileOpenFinished, Qt::QueuedConnection, false);
return;
}
#endif
@@ -85,7 +85,7 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, con
if (req.attribute(QNetworkRequest::BackgroundRequestAttribute).toBool()) { // Asynchronous open
auto realFile = new QNetworkFile(fileName);
- connect(realFile, &QNetworkFile::headerRead, this, &QNetworkReplyFileImpl::setHeader,
+ connect(realFile, &QNetworkFile::headerRead, this, &QNetworkReplyFileImpl::setWellKnownHeader,
Qt::QueuedConnection);
connect(realFile, &QNetworkFile::error, this, &QNetworkReplyFileImpl::setError,
Qt::QueuedConnection);
@@ -128,8 +128,12 @@ QNetworkReplyFileImpl::QNetworkReplyFileImpl(QNetworkAccessManager *manager, con
QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection);
return;
}
- setHeader(QNetworkRequest::LastModifiedHeader, fi.lastModified());
- setHeader(QNetworkRequest::ContentLengthHeader, fi.size());
+ auto h = headers();
+ h.replaceOrAppend(QHttpHeaders::WellKnownHeader::LastModified,
+ QNetworkHeadersPrivate::toHttpDate(fi.lastModified()));
+ h.replaceOrAppend(QHttpHeaders::WellKnownHeader::ContentLength,
+ QByteArray::number(fi.size()));
+ setHeaders(std::move(h));
QMetaObject::invokeMethod(this, "metaDataChanged", Qt::QueuedConnection);
QMetaObject::invokeMethod(this, "downloadProgress", Qt::QueuedConnection,
@@ -171,9 +175,9 @@ bool QNetworkReplyFileImpl::isSequential () const
qint64 QNetworkReplyFileImpl::size() const
{
- bool ok;
- int size = header(QNetworkRequest::ContentLengthHeader).toInt(&ok);
- return ok ? size : 0;
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
+ return totalSizeOpt.value_or(0);
}
/*!
diff --git a/src/network/access/qnetworkreplyfileimpl_p.h b/src/network/access/qnetworkreplyfileimpl_p.h
index 20a09983ac..6413903d8f 100644
--- a/src/network/access/qnetworkreplyfileimpl_p.h
+++ b/src/network/access/qnetworkreplyfileimpl_p.h
@@ -1,8 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#ifndef QNETWORKREPLYFILEIMPL_H
-#define QNETWORKREPLYFILEIMPL_H
+#ifndef QNETWORKREPLYFILEIMPL_P_H
+#define QNETWORKREPLYFILEIMPL_P_H
//
// W A R N I N G
@@ -19,7 +19,9 @@
#include "qnetworkreply.h"
#include "qnetworkreply_p.h"
#include "qnetworkaccessmanager.h"
+
#include <QFile>
+#include <QtCore/qpointer.h>
#include <private/qabstractfileengine_p.h>
QT_BEGIN_NAMESPACE
@@ -65,4 +67,4 @@ QT_END_NAMESPACE
QT_DECL_METATYPE_EXTERN_TAGGED(QNetworkRequest::KnownHeaders,
QNetworkRequest__KnownHeaders, Q_NETWORK_EXPORT)
-#endif // QNETWORKREPLYFILEIMPL_H
+#endif // QNETWORKREPLYFILEIMPL_P_H
diff --git a/src/network/access/qnetworkreplyhttpimpl.cpp b/src/network/access/qnetworkreplyhttpimpl.cpp
index 58e768583d..89458825e9 100644
--- a/src/network/access/qnetworkreplyhttpimpl.cpp
+++ b/src/network/access/qnetworkreplyhttpimpl.cpp
@@ -21,6 +21,7 @@
#include "QtCore/qcoreapplication.h"
#include <QtCore/private/qthread_p.h>
+#include <QtCore/private/qtools_p.h>
#include "qnetworkcookiejar.h"
#include "qnetconmonitor_p.h"
@@ -32,17 +33,17 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+using namespace QtMiscUtils;
+using namespace std::chrono_literals;
class QNetworkProxy;
-static inline bool isSeparator(char c)
-{
- static const char separators[] = "()<>@,;:\\\"/[]?={}";
- return isLWS(c) || strchr(separators, c) != nullptr;
-}
+static inline QByteArray rangeName() { return "Range"_ba; }
+static inline QByteArray cacheControlName() { return "Cache-Control"_ba; }
+static constexpr QByteArrayView bytesEqualPrefix() noexcept { return "bytes="; }
// ### merge with nextField in cookiejar.cpp
-static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &header)
+static QHash<QByteArray, QByteArray> parseHttpOptionHeader(QByteArrayView header)
{
// The HTTP header is of the form:
// header = #1(directives)
@@ -71,7 +72,7 @@ static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &hea
end = header.size();
if (equal != -1 && end > equal)
end = equal; // equal sign comes before comma/end
- QByteArray key = QByteArray(header.constData() + pos, end - pos).trimmed().toLower();
+ const auto key = header.sliced(pos, end - pos).trimmed();
pos = end + 1;
if (uint(equal) < uint(comma)) {
@@ -107,6 +108,11 @@ static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &hea
++pos;
}
} else {
+ const auto isSeparator = [](char c) {
+ static const char separators[] = "()<>@,;:\\\"/[]?={}";
+ return isLWS(c) || strchr(separators, c) != nullptr;
+ };
+
// case: token
while (pos < header.size()) {
char c = header.at(pos);
@@ -117,7 +123,7 @@ static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &hea
}
}
- result.insert(key, value);
+ result.insert(key.toByteArray().toLower(), value);
// find the comma now:
comma = header.indexOf(',', pos);
@@ -127,7 +133,7 @@ static QHash<QByteArray, QByteArray> parseHttpOptionHeader(const QByteArray &hea
} else {
// case: token
// key is already set
- result.insert(key, QByteArray());
+ result.insert(key.toByteArray().toLower(), QByteArray());
}
}
}
@@ -152,6 +158,9 @@ QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manage
d->sslConfiguration.reset(new QSslConfiguration(request.sslConfiguration()));
#endif
+ QObjectPrivate::connect(this, &QNetworkReplyHttpImpl::redirectAllowed, d,
+ &QNetworkReplyHttpImplPrivate::followRedirect, Qt::QueuedConnection);
+
// FIXME Later maybe set to Unbuffered, especially if it is zerocopy or from cache?
QIODevice::open(QIODevice::ReadOnly);
@@ -193,7 +202,10 @@ QNetworkReplyHttpImpl::QNetworkReplyHttpImpl(QNetworkAccessManager* const manage
if (bufferingDisallowed) {
// if a valid content-length header for the request was supplied, we can disable buffering
// if not, we will buffer anyway
- if (request.header(QNetworkRequest::ContentLengthHeader).isValid()) {
+
+ const auto sizeOpt = QNetworkHeadersPrivate::toInt(
+ request.headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
+ if (sizeOpt) {
QMetaObject::invokeMethod(this, "_q_startOperation", Qt::QueuedConnection);
// FIXME make direct call?
} else {
@@ -469,19 +481,22 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h
{
QNetworkRequest::CacheLoadControl CacheLoadControlAttribute =
(QNetworkRequest::CacheLoadControl)request.attribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferNetwork).toInt();
+
+ auto requestHeaders = request.headers();
if (CacheLoadControlAttribute == QNetworkRequest::AlwaysNetwork) {
// If the request does not already specify preferred cache-control
// force reload from the network and tell any caching proxy servers to reload too
- if (!request.rawHeaderList().contains("Cache-Control")) {
- httpRequest.setHeaderField("Cache-Control", "no-cache");
- httpRequest.setHeaderField("Pragma", "no-cache");
+ if (!requestHeaders.contains(QHttpHeaders::WellKnownHeader::CacheControl)) {
+ const auto noCache = "no-cache"_ba;
+ httpRequest.setHeaderField(cacheControlName(), noCache);
+ httpRequest.setHeaderField("Pragma"_ba, noCache);
}
return false;
}
// The disk cache API does not currently support partial content retrieval.
// That is why we don't use the disk cache for any such requests.
- if (request.hasRawHeader("Range"))
+ if (requestHeaders.contains(QHttpHeaders::WellKnownHeader::Range))
return false;
QAbstractNetworkCache *nc = managerPrivate->networkCache;
@@ -495,24 +510,30 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h
if (!metaData.saveToDisk())
return false;
- QNetworkHeadersPrivate cacheHeaders;
- QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
- cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
+ QHttpHeaders cacheHeaders = metaData.headers();
+
+ const auto sizeOpt = QNetworkHeadersPrivate::toInt(
+ cacheHeaders.value(QHttpHeaders::WellKnownHeader::ContentLength));
+ if (sizeOpt) {
+ std::unique_ptr<QIODevice> data(nc->data(httpRequest.url()));
+ if (!data || data->size() < sizeOpt.value())
+ return false; // The data is smaller than the content-length specified
+ }
- it = cacheHeaders.findRawHeader("etag");
- if (it != cacheHeaders.rawHeaders.constEnd())
- httpRequest.setHeaderField("If-None-Match", it->second);
+ auto value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::ETag);
+ if (!value.empty())
+ httpRequest.setHeaderField("If-None-Match"_ba, value.toByteArray());
QDateTime lastModified = metaData.lastModified();
if (lastModified.isValid())
- httpRequest.setHeaderField("If-Modified-Since", QNetworkHeadersPrivate::toHttpDate(lastModified));
+ httpRequest.setHeaderField("If-Modified-Since"_ba, QNetworkHeadersPrivate::toHttpDate(lastModified));
- it = cacheHeaders.findRawHeader("Cache-Control");
- if (it != cacheHeaders.rawHeaders.constEnd()) {
- QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
- if (cacheControl.contains("must-revalidate"))
+ value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::CacheControl);
+ if (!value.empty()) {
+ QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(value);
+ if (cacheControl.contains("must-revalidate"_ba))
return false;
- if (cacheControl.contains("no-cache"))
+ if (cacheControl.contains("no-cache"_ba))
return false;
}
@@ -536,16 +557,15 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h
* now
* is the current (local) time
*/
- qint64 age_value = 0;
- it = cacheHeaders.findRawHeader("age");
- if (it != cacheHeaders.rawHeaders.constEnd())
- age_value = it->second.toLongLong();
+ const auto ageOpt = QNetworkHeadersPrivate::toInt(
+ cacheHeaders.value(QHttpHeaders::WellKnownHeader::Age));
+ const qint64 age_value = ageOpt.value_or(0);
QDateTime dateHeader;
qint64 date_value = 0;
- it = cacheHeaders.findRawHeader("date");
- if (it != cacheHeaders.rawHeaders.constEnd()) {
- dateHeader = QNetworkHeadersPrivate::fromHttpDate(it->second);
+ value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::Date);
+ if (!value.empty()) {
+ dateHeader = QNetworkHeadersPrivate::fromHttpDate(value);
date_value = dateHeader.toSecsSinceEpoch();
}
@@ -567,10 +587,11 @@ bool QNetworkReplyHttpImplPrivate::loadFromCacheIfAllowed(QHttpNetworkRequest &h
if (lastModified.isValid() && dateHeader.isValid()) {
qint64 diff = lastModified.secsTo(dateHeader);
freshness_lifetime = diff / 10;
- if (httpRequest.headerField("Warning").isEmpty()) {
+ const auto warningHeader = "Warning"_ba;
+ if (httpRequest.headerField(warningHeader).isEmpty()) {
QDateTime dt = currentDateTime.addSecs(current_age);
if (currentDateTime.daysTo(dt) > 1)
- httpRequest.setHeaderField("Warning", "113");
+ httpRequest.setHeaderField(warningHeader, "113"_ba);
}
}
@@ -682,8 +703,13 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
switch (operation) {
case QNetworkAccessManager::GetOperation:
httpRequest.setOperation(QHttpNetworkRequest::Get);
- if (loadFromCacheIfAllowed(httpRequest))
+ // If the request has a body, createUploadByteDevice() and don't use caching
+ if (outgoingData) {
+ invalidateCache();
+ createUploadByteDevice();
+ } else if (loadFromCacheIfAllowed(httpRequest)) {
return; // no need to send the request! :)
+ }
break;
case QNetworkAccessManager::HeadOperation:
@@ -721,16 +747,16 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
break; // can't happen
}
- QList<QByteArray> headers = newHttpRequest.rawHeaderList();
+ QHttpHeaders newRequestHeaders = newHttpRequest.headers();
if (resumeOffset != 0) {
- const int rangeIndex = headers.indexOf("Range");
- if (rangeIndex != -1) {
+ if (newRequestHeaders.contains(QHttpHeaders::WellKnownHeader::Range)) {
// Need to adjust resume offset for user specified range
- headers.removeAt(rangeIndex);
-
// We've already verified that requestRange starts with "bytes=", see canResume.
- QByteArray requestRange = newHttpRequest.rawHeader("Range").mid(6);
+ const auto rangeHeader = newRequestHeaders.value(QHttpHeaders::WellKnownHeader::Range);
+ const auto requestRange = QByteArrayView(rangeHeader).mid(bytesEqualPrefix().size());
+
+ newRequestHeaders.removeAll(QHttpHeaders::WellKnownHeader::Range);
int index = requestRange.indexOf('-');
@@ -738,17 +764,20 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
quint64 requestEndOffset = requestRange.mid(index + 1).toULongLong();
// In case an end offset is not given it is skipped from the request range
- requestRange = "bytes=" + QByteArray::number(resumeOffset + requestStartOffset) +
+ QByteArray newRange = bytesEqualPrefix() + QByteArray::number(resumeOffset + requestStartOffset) +
'-' + (requestEndOffset ? QByteArray::number(requestEndOffset) : QByteArray());
- httpRequest.setHeaderField("Range", requestRange);
+ httpRequest.setHeaderField(rangeName(), newRange);
} else {
- httpRequest.setHeaderField("Range", "bytes=" + QByteArray::number(resumeOffset) + '-');
+ httpRequest.setHeaderField(rangeName(), bytesEqualPrefix() + QByteArray::number(resumeOffset) + '-');
}
}
- for (const QByteArray &header : std::as_const(headers))
- httpRequest.setHeaderField(header, newHttpRequest.rawHeader(header));
+ for (int i = 0; i < newRequestHeaders.size(); i++) {
+ const auto name = newRequestHeaders.nameAt(i);
+ const auto value = newRequestHeaders.valueAt(i);
+ httpRequest.setHeaderField(QByteArray(name.data(), name.size()), value.toByteArray());
+ }
if (newHttpRequest.attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool())
httpRequest.setPipeliningAllowed(true);
@@ -780,10 +809,18 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
httpRequest.setPeerVerifyName(newHttpRequest.peerVerifyName());
+ if (scheme.startsWith(("unix"_L1))) {
+ if (QVariant path = newHttpRequest.attribute(QNetworkRequest::FullLocalServerNameAttribute);
+ path.isValid() && path.canConvert<QString>()) {
+ httpRequest.setFullLocalServerName(path.toString());
+ }
+ }
+
// Create the HTTP thread delegate
QHttpThreadDelegate *delegate = new QHttpThreadDelegate;
// Propagate Http/2 settings:
delegate->http2Parameters = request.http2Configuration();
+ delegate->http1Parameters = request.http1Configuration();
if (request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).isValid())
delegate->connectionCacheExpiryTimeoutSeconds = request.attribute(QNetworkRequest::ConnectionCacheExpiryTimeoutSecondsAttribute).toInt();
@@ -861,9 +898,6 @@ void QNetworkReplyHttpImplPrivate::postRequest(const QNetworkRequest &newHttpReq
q, SLOT(onRedirected(QUrl,int,int)),
Qt::QueuedConnection);
- QObject::connect(q, SIGNAL(redirectAllowed()), q, SLOT(followRedirect()),
- Qt::QueuedConnection);
-
#ifndef QT_NO_SSL
QObject::connect(delegate, SIGNAL(sslConfigurationChanged(QSslConfiguration)),
q, SLOT(replySslConfigurationChanged(QSslConfiguration)),
@@ -1129,7 +1163,8 @@ void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d)
}
lastReadyReadEmittedSize = bytesDownloaded;
- QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
emit q->readyRead();
// emit readyRead before downloadProgress in case this will cause events to be
@@ -1137,8 +1172,7 @@ void QNetworkReplyHttpImplPrivate::replyDownloadData(QByteArray d)
if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval
&& (!decompressHelper.isValid() || decompressHelper.isCountingBytes())) {
downloadProgressSignalChoke.restart();
- emit q->downloadProgress(bytesDownloaded,
- totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+ emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1));
}
}
@@ -1201,7 +1235,8 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
if (httpRequest.isFollowRedirects()) // update the reply's url as it could've changed
url = redirectUrl;
- if (managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
+ const bool wasLocalSocket = schemeBefore.startsWith("unix"_L1);
+ if (!wasLocalSocket && managerPrivate->stsEnabled && managerPrivate->stsCache.isKnownHost(url)) {
// RFC6797, 8.3:
// The UA MUST replace the URI scheme with "https" [RFC2818],
// and if the URI contains an explicit port component of "80",
@@ -1215,21 +1250,30 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
url.setPort(443);
}
- const bool isLessSafe = schemeBefore == "https"_L1 && url.scheme() == "http"_L1;
- if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy
- && isLessSafe) {
+ // Just to be on the safe side for local sockets, any changes to the scheme
+ // are considered less safe
+ const bool changingLocalScheme = wasLocalSocket && url.scheme() != schemeBefore;
+ const bool isLessSafe = changingLocalScheme
+ || (schemeBefore == "https"_L1 && url.scheme() == "http"_L1);
+ if (httpRequest.redirectPolicy() == QNetworkRequest::NoLessSafeRedirectPolicy && isLessSafe) {
error(QNetworkReply::InsecureRedirectError,
QCoreApplication::translate("QHttp", "Insecure redirect"));
return;
}
+ // If the original operation was a GET with a body and the status code is either
+ // 307 or 308 then keep the message body
+ const bool getOperationKeepsBody = (operation == QNetworkAccessManager::GetOperation)
+ && (httpStatus == 307 || httpStatus == 308);
+
redirectRequest = createRedirectRequest(originalRequest, url, maxRedirectsRemaining);
operation = getRedirectOperation(operation, httpStatus);
// Clear stale headers, the relevant ones get set again later
httpRequest.clearHeaders();
- if (operation == QNetworkAccessManager::GetOperation
- || operation == QNetworkAccessManager::HeadOperation) {
+ auto newHeaders = redirectRequest.headers();
+ if ((operation == QNetworkAccessManager::GetOperation
+ || operation == QNetworkAccessManager::HeadOperation) && !getOperationKeepsBody) {
// possibly changed from not-GET/HEAD to GET/HEAD, make sure to get rid of upload device
uploadByteDevice.reset();
uploadByteDevicePosition = 0;
@@ -1242,18 +1286,20 @@ void QNetworkReplyHttpImplPrivate::onRedirected(const QUrl &redirectUrl, int htt
outgoingData = nullptr;
outgoingDataBuffer.reset();
// We need to explicitly unset these headers so they're not reapplied to the httpRequest
- redirectRequest.setHeader(QNetworkRequest::ContentLengthHeader, QVariant());
- redirectRequest.setHeader(QNetworkRequest::ContentTypeHeader, QVariant());
+ newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentLength);
+ newHeaders.removeAll(QHttpHeaders::WellKnownHeader::ContentType);
}
if (const QNetworkCookieJar *const cookieJar = manager->cookieJar()) {
auto cookies = cookieJar->cookiesForUrl(url);
if (!cookies.empty()) {
- redirectRequest.setHeader(QNetworkRequest::KnownHeaders::CookieHeader,
- QVariant::fromValue(cookies));
+ auto cookieHeader = QNetworkHeadersPrivate::fromCookieList(cookies);
+ newHeaders.replaceOrAppend(QHttpHeaders::WellKnownHeader::Cookie, cookieHeader);
}
}
+ redirectRequest.setHeaders(std::move(newHeaders));
+
if (httpRequest.redirectPolicy() != QNetworkRequest::UserVerifiedRedirectPolicy)
followRedirect();
@@ -1266,8 +1312,7 @@ void QNetworkReplyHttpImplPrivate::followRedirect()
Q_ASSERT(managerPrivate);
decompressHelper.clear();
- rawHeaders.clear();
- cookedHeaders.clear();
+ clearHeaders();
if (managerPrivate->thread)
managerPrivate->thread->disconnect();
@@ -1276,6 +1321,8 @@ void QNetworkReplyHttpImplPrivate::followRedirect()
q, [this]() { postRequest(redirectRequest); }, Qt::QueuedConnection);
}
+static constexpr QLatin1StringView locationHeader() noexcept { return "location"_L1; }
+
void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
{
Q_Q(QNetworkReplyHttpImpl);
@@ -1288,7 +1335,7 @@ void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
// What do we do about the caching of the HTML note?
// The response to a 303 MUST NOT be cached, while the response to
// all of the others is cacheable if the headers indicate it to be
- QByteArray header = q->rawHeader("location");
+ QByteArrayView header = q->headers().value(locationHeader());
QUrl url = QUrl(QString::fromUtf8(header));
if (!url.isValid())
url = QUrl(QLatin1StringView(header));
@@ -1296,7 +1343,7 @@ void QNetworkReplyHttpImplPrivate::checkForRedirect(const int statusCode)
}
}
-void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByteArray,QByteArray> > &hm,
+void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QHttpHeaders &hm,
int sc, const QString &rp, bool pu,
QSharedPointer<char> db,
qint64 contentLength,
@@ -1332,28 +1379,25 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByte
// A user having manually defined which encodings they accept is, for
// somwehat unknown (presumed legacy compatibility) reasons treated as
// disabling our decompression:
- const bool autoDecompress = request.rawHeader("accept-encoding").isEmpty();
+ const bool autoDecompress = !request.headers().contains(QHttpHeaders::WellKnownHeader::AcceptEncoding);
const bool shouldDecompress = isCompressed && autoDecompress;
// reconstruct the HTTP header
- QList<QPair<QByteArray, QByteArray> > headerMap = hm;
- QList<QPair<QByteArray, QByteArray> >::ConstIterator it = headerMap.constBegin(),
- end = headerMap.constEnd();
- for (; it != end; ++it) {
- QByteArray value = q->rawHeader(it->first);
+ auto h = q->headers();
+ for (qsizetype i = 0; i < hm.size(); ++i) {
+ const auto key = hm.nameAt(i);
+ const auto originValue = hm.valueAt(i);
// Reset any previous "location" header set in the reply. In case of
// redirects, we don't want to 'append' multiple location header values,
// rather we keep only the latest one
- if (it->first.toLower() == "location")
- value.clear();
-
- if (shouldDecompress && !decompressHelper.isValid()
- && it->first.compare("content-encoding", Qt::CaseInsensitive) == 0) {
+ if (key.compare(locationHeader(), Qt::CaseInsensitive) == 0)
+ h.removeAll(key);
+ if (shouldDecompress && !decompressHelper.isValid() && key == "content-encoding"_L1) {
if (!synchronous) // with synchronous all the data is expected to be handled at once
decompressHelper.setCountingBytesEnabled(true);
- if (!decompressHelper.setEncoding(it->second)) {
+ if (!decompressHelper.setEncoding(originValue)) {
error(QNetworkReplyImpl::NetworkError::UnknownContentError,
QCoreApplication::translate("QHttp", "Failed to initialize decompression: %1")
.arg(decompressHelper.errorString()));
@@ -1363,17 +1407,9 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByte
request.decompressedSafetyCheckThreshold());
}
- if (!value.isEmpty()) {
- // Why are we appending values for headers which are already
- // present?
- if (it->first.compare("set-cookie", Qt::CaseInsensitive) == 0)
- value += '\n';
- else
- value += ", ";
- }
- value += it->second;
- q->setRawHeader(it->first, value);
+ h.append(key, originValue);
}
+ q->setHeaders(std::move(h));
q->setAttribute(QNetworkRequest::HttpStatusCodeAttribute, statusCode);
q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, reasonPhrase);
@@ -1388,14 +1424,11 @@ void QNetworkReplyHttpImplPrivate::replyDownloadMetaData(const QList<QPair<QByte
QAbstractNetworkCache *nc = managerPrivate->networkCache;
if (nc) {
QNetworkCacheMetaData metaData = nc->metaData(httpRequest.url());
- QNetworkHeadersPrivate cacheHeaders;
- cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
- QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
- it = cacheHeaders.findRawHeader("Cache-Control");
+ auto value = metaData.headers().value(QHttpHeaders::WellKnownHeader::CacheControl);
bool mustReValidate = false;
- if (it != cacheHeaders.rawHeaders.constEnd()) {
- QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(it->second);
- if (cacheControl.contains("must-revalidate"))
+ if (!value.empty()) {
+ QHash<QByteArray, QByteArray> cacheControl = parseHttpOptionHeader(value);
+ if (cacheControl.contains("must-revalidate"_ba))
mustReValidate = true;
}
if (!mustReValidate && sendCacheContents(metaData))
@@ -1633,16 +1666,21 @@ bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData
q->setAttribute(QNetworkRequest::HttpReasonPhraseAttribute, attributes.value(QNetworkRequest::HttpReasonPhraseAttribute));
q->setAttribute(QNetworkRequest::SourceIsFromCacheAttribute, true);
- QNetworkCacheMetaData::RawHeaderList rawHeaders = metaData.rawHeaders();
- QNetworkCacheMetaData::RawHeaderList::ConstIterator it = rawHeaders.constBegin(),
- end = rawHeaders.constEnd();
+ QHttpHeaders cachedHeaders = metaData.headers();
+ QHttpHeaders h = headers();
QUrl redirectUrl;
- for ( ; it != end; ++it) {
- if (httpRequest.isFollowRedirects() &&
- !it->first.compare("location", Qt::CaseInsensitive))
- redirectUrl = QUrl::fromEncoded(it->second);
- setRawHeader(it->first, it->second);
+ for (qsizetype i = 0; i < cachedHeaders.size(); ++i) {
+ const auto name = cachedHeaders.nameAt(i);
+ const auto value = cachedHeaders.valueAt(i);
+
+ if (httpRequest.isFollowRedirects()
+ && !name.compare(locationHeader(), Qt::CaseInsensitive)) {
+ redirectUrl = QUrl::fromEncoded(value);
+ }
+
+ h.replaceOrAppend(name, value);
}
+ setHeaders(std::move(h));
if (!isHttpRedirectResponse())
checkForRedirect(status);
@@ -1676,33 +1714,43 @@ bool QNetworkReplyHttpImplPrivate::sendCacheContents(const QNetworkCacheMetaData
return true;
}
+static auto caseInsensitiveCompare(QByteArrayView value)
+{
+ return [value](QByteArrayView element)
+ {
+ return value.compare(element, Qt::CaseInsensitive) == 0;
+ };
+}
+
+static bool isHopByHop(QByteArrayView header)
+{
+ constexpr QByteArrayView headers[] = { "connection",
+ "keep-alive",
+ "proxy-authenticate",
+ "proxy-authorization",
+ "te",
+ "trailers",
+ "transfer-encoding",
+ "upgrade"};
+ return std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(header));
+}
+
QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNetworkCacheMetaData &oldMetaData) const
{
Q_Q(const QNetworkReplyHttpImpl);
QNetworkCacheMetaData metaData = oldMetaData;
+ QHttpHeaders cacheHeaders = metaData.headers();
- QNetworkHeadersPrivate cacheHeaders;
- cacheHeaders.setAllRawHeaders(metaData.rawHeaders());
- QNetworkHeadersPrivate::RawHeadersList::ConstIterator it;
-
- const QList<QByteArray> newHeaders = q->rawHeaderList();
- for (QByteArray header : newHeaders) {
- QByteArray originalHeader = header;
- header = header.toLower();
- bool hop_by_hop =
- (header == "connection"
- || header == "keep-alive"
- || header == "proxy-authenticate"
- || header == "proxy-authorization"
- || header == "te"
- || header == "trailers"
- || header == "transfer-encoding"
- || header == "upgrade");
- if (hop_by_hop)
+ const auto newHeaders = q->headers();
+ for (qsizetype i = 0; i < newHeaders.size(); ++i) {
+ const auto name = newHeaders.nameAt(i);
+ const auto value = newHeaders.valueAt(i);
+
+ if (isHopByHop(name))
continue;
- if (header == "set-cookie")
+ if (name.compare("set-cookie", Qt::CaseInsensitive) == 0)
continue;
// for 4.6.0, we were planning to not store the date header in the
@@ -1715,51 +1763,47 @@ QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNe
//continue;
// Don't store Warning 1xx headers
- if (header == "warning") {
- QByteArray v = q->rawHeader(header);
- if (v.size() == 3
- && v[0] == '1'
- && v[1] >= '0' && v[1] <= '9'
- && v[2] >= '0' && v[2] <= '9')
+ if (name.compare("warning", Qt::CaseInsensitive) == 0) {
+ if (value.size() == 3
+ && value[0] == '1'
+ && isAsciiDigit(value[1])
+ && isAsciiDigit(value[2]))
continue;
}
- it = cacheHeaders.findRawHeader(header);
- if (it != cacheHeaders.rawHeaders.constEnd()) {
+ if (cacheHeaders.contains(name)) {
// Match the behavior of Firefox and assume Cache-Control: "no-transform"
- if (header == "content-encoding"
- || header == "content-range"
- || header == "content-type")
+ constexpr QByteArrayView headers[]=
+ {"content-encoding", "content-range", "content-type"};
+ if (std::any_of(std::begin(headers), std::end(headers), caseInsensitiveCompare(name)))
continue;
}
// IIS has been known to send "Content-Length: 0" on 304 responses, so
// ignore this too
- if (header == "content-length" && statusCode == 304)
+ if (statusCode == 304 && name.compare("content-length", Qt::CaseInsensitive) == 0)
continue;
#if defined(QNETWORKACCESSHTTPBACKEND_DEBUG)
- QByteArray n = q->rawHeader(header);
- QByteArray o;
- if (it != cacheHeaders.rawHeaders.constEnd())
- o = (*it).second;
- if (n != o && header != "date") {
- qDebug() << "replacing" << header;
+ QByteArrayView n = newHeaders.value(name);
+ QByteArrayView o = cacheHeaders.value(name);
+ if (n != o && name.compare("date", Qt::CaseInsensitive) != 0) {
+ qDebug() << "replacing" << name;
qDebug() << "new" << n;
qDebug() << "old" << o;
}
#endif
- cacheHeaders.setRawHeader(originalHeader, q->rawHeader(header));
+ cacheHeaders.replaceOrAppend(name, value);
}
- metaData.setRawHeaders(cacheHeaders.rawHeaders);
+ metaData.setHeaders(cacheHeaders);
bool checkExpired = true;
QHash<QByteArray, QByteArray> cacheControl;
- it = cacheHeaders.findRawHeader("Cache-Control");
- if (it != cacheHeaders.rawHeaders.constEnd()) {
- cacheControl = parseHttpOptionHeader(it->second);
- QByteArray maxAge = cacheControl.value("max-age");
+ auto value = cacheHeaders.value(QHttpHeaders::WellKnownHeader::CacheControl);
+ if (!value.empty()) {
+ cacheControl = parseHttpOptionHeader(value);
+ QByteArray maxAge = cacheControl.value("max-age"_ba);
if (!maxAge.isEmpty()) {
checkExpired = false;
QDateTime dt = QDateTime::currentDateTimeUtc();
@@ -1768,16 +1812,18 @@ QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNe
}
}
if (checkExpired) {
- it = cacheHeaders.findRawHeader("expires");
- if (it != cacheHeaders.rawHeaders.constEnd()) {
- QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(it->second);
+ if (const auto value = cacheHeaders.value(
+ QHttpHeaders::WellKnownHeader::Expires); !value.isEmpty()) {
+ QDateTime expiredDateTime = QNetworkHeadersPrivate::fromHttpDate(value);
metaData.setExpirationDate(expiredDateTime);
}
}
- it = cacheHeaders.findRawHeader("last-modified");
- if (it != cacheHeaders.rawHeaders.constEnd())
- metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(it->second));
+ if (const auto value = cacheHeaders.value(
+ QHttpHeaders::WellKnownHeader::LastModified); !value.isEmpty()) {
+ metaData.setLastModified(QNetworkHeadersPrivate::fromHttpDate(value));
+ }
+
bool canDiskCache;
// only cache GET replies by default, all other replies (POST, PUT, DELETE)
@@ -1786,7 +1832,7 @@ QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNe
canDiskCache = true;
// HTTP/1.1. Check the Cache-Control header
- if (cacheControl.contains("no-store"))
+ if (cacheControl.contains("no-store"_ba))
canDiskCache = false;
// responses to POST might be cacheable
@@ -1795,7 +1841,7 @@ QNetworkCacheMetaData QNetworkReplyHttpImplPrivate::fetchCacheMetaData(const QNe
canDiskCache = false;
// some pages contain "expires:" and "cache-control: no-cache" field,
// so we only might cache POST requests if we get "cache-control: max-age ..."
- if (cacheControl.contains("max-age"))
+ if (cacheControl.contains("max-age"_ba))
canDiskCache = true;
// responses to PUT and DELETE are not cacheable
@@ -1825,15 +1871,17 @@ bool QNetworkReplyHttpImplPrivate::canResume() const
if (operation != QNetworkAccessManager::GetOperation)
return false;
+ const auto h = q->headers();
+
// Can only resume if server/resource supports Range header.
- QByteArray acceptRangesheaderName("Accept-Ranges");
- if (!q->hasRawHeader(acceptRangesheaderName) || q->rawHeader(acceptRangesheaderName) == "none")
+ const auto acceptRanges = h.value(QHttpHeaders::WellKnownHeader::AcceptRanges);
+ if (acceptRanges.empty() || acceptRanges == "none")
return false;
// We only support resuming for byte ranges.
- if (request.hasRawHeader("Range")) {
- QByteArray range = request.rawHeader("Range");
- if (!range.startsWith("bytes="))
+ const auto range = h.value(QHttpHeaders::WellKnownHeader::Range);
+ if (!range.empty()) {
+ if (!range.startsWith(bytesEqualPrefix()))
return false;
}
@@ -1852,7 +1900,9 @@ void QNetworkReplyHttpImplPrivate::setResumeOffset(quint64 offset)
void QNetworkReplyHttpImplPrivate::_q_startOperation()
{
- if (state == Working) // ensure this function is only being called once
+ // Ensure this function is only being called once, and not at all if we were
+ // cancelled
+ if (state >= Working)
return;
state = Working;
@@ -1879,8 +1929,8 @@ void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
// Needs to be done where sendCacheContents() (?) of HTTP is emitting
// metaDataChanged ?
-
- QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
// emit readyRead before downloadProgress in case this will cause events to be
// processed and we get into a recursive call (as in QProgressDialog).
@@ -1891,8 +1941,7 @@ void QNetworkReplyHttpImplPrivate::_q_cacheLoadReadyRead()
if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
downloadProgressSignalChoke.restart();
- emit q->downloadProgress(bytesDownloaded,
- totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+ emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1));
}
}
@@ -2012,9 +2061,9 @@ void QNetworkReplyHttpImplPrivate::setupTransferTimeout()
Qt::QueuedConnection);
}
transferTimeout->stop();
- if (request.transferTimeout()) {
+ if (request.transferTimeoutAsDuration() > 0ms) {
transferTimeout->setSingleShot(true);
- transferTimeout->setInterval(request.transferTimeout());
+ transferTimeout->setInterval(request.transferTimeoutAsDuration());
QMetaObject::invokeMethod(transferTimeout, "start",
Qt::QueuedConnection);
@@ -2079,11 +2128,13 @@ void QNetworkReplyHttpImplPrivate::finished()
if (state == Finished || state == Aborted)
return;
- QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
+ const qint64 totalSize = totalSizeOpt.value_or(-1);
// if we don't know the total size of or we received everything save the cache.
// If the data is compressed then this is done in readData()
- if ((totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
+ if ((totalSize == -1 || bytesDownloaded == totalSize)
&& !decompressHelper.isValid()) {
completeCacheSave();
}
@@ -2096,10 +2147,10 @@ void QNetworkReplyHttpImplPrivate::finished()
state = Finished;
q->setFinished(true);
- if (totalSize.isNull() || totalSize == -1) {
+ if (totalSize == -1) {
emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
} else {
- emit q->downloadProgress(bytesDownloaded, totalSize.toLongLong());
+ emit q->downloadProgress(bytesDownloaded, totalSize);
}
if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
@@ -2120,7 +2171,9 @@ void QNetworkReplyHttpImplPrivate::error(QNetworkReplyImpl::NetworkError code, c
Q_Q(QNetworkReplyHttpImpl);
// Can't set and emit multiple errors.
if (errorCode != QNetworkReply::NoError) {
- qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
+ // But somewhat unavoidable if we have cancelled the request:
+ if (errorCode != QNetworkReply::OperationCanceledError)
+ qWarning("QNetworkReplyImplPrivate::error: Internal problem, this method must only be called once.");
return;
}
@@ -2141,14 +2194,15 @@ void QNetworkReplyHttpImplPrivate::_q_metaDataChanged()
// 1. do we have cookies?
// 2. are we allowed to set them?
Q_ASSERT(manager);
- const auto it = cookedHeaders.constFind(QNetworkRequest::SetCookieHeader);
- if (it != cookedHeaders.cend()
+
+ const auto cookiesOpt = QNetworkHeadersPrivate::toSetCookieList(
+ headers().values(QHttpHeaders::WellKnownHeader::SetCookie));
+ const auto cookies = cookiesOpt.value_or(QList<QNetworkCookie>());
+ if (!cookies.empty()
&& request.attribute(QNetworkRequest::CookieSaveControlAttribute,
QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
QNetworkCookieJar *jar = manager->cookieJar();
if (jar) {
- QList<QNetworkCookie> cookies =
- qvariant_cast<QList<QNetworkCookie> >(it.value());
jar->setCookiesFromUrl(cookies, url);
}
}
diff --git a/src/network/access/qnetworkreplyhttpimpl_p.h b/src/network/access/qnetworkreplyhttpimpl_p.h
index 11897d1420..e00c43bdb3 100644
--- a/src/network/access/qnetworkreplyhttpimpl_p.h
+++ b/src/network/access/qnetworkreplyhttpimpl_p.h
@@ -244,7 +244,7 @@ public:
// From HTTP thread:
void replyDownloadData(QByteArray);
void replyFinished();
- void replyDownloadMetaData(const QList<QPair<QByteArray,QByteArray> > &, int, const QString &,
+ void replyDownloadMetaData(const QHttpHeaders &, int, const QString &,
bool, QSharedPointer<char>, qint64, qint64, bool, bool);
void replyDownloadProgressSlot(qint64,qint64);
void httpAuthenticationRequired(const QHttpNetworkRequest &request, QAuthenticator *auth);
diff --git a/src/network/access/qnetworkreplyimpl.cpp b/src/network/access/qnetworkreplyimpl.cpp
index 2c713e69fd..b90ec1cc4c 100644
--- a/src/network/access/qnetworkreplyimpl.cpp
+++ b/src/network/access/qnetworkreplyimpl.cpp
@@ -118,15 +118,16 @@ void QNetworkReplyImplPrivate::_q_copyReadyRead()
return;
}
- QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
+
pauseNotificationHandling();
// emit readyRead before downloadProgress in case this will cause events to be
// processed and we get into a recursive call (as in QProgressDialog).
emit q->readyRead();
if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
downloadProgressSignalChoke.restart();
- emit q->downloadProgress(bytesDownloaded,
- totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+ emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1));
}
resumeNotificationHandling();
}
@@ -243,7 +244,10 @@ void QNetworkReplyImplPrivate::setup(QNetworkAccessManager::Operation op, const
if (bufferingDisallowed) {
// if a valid content-length header for the request was supplied, we can disable buffering
// if not, we will buffer anyway
- if (req.header(QNetworkRequest::ContentLengthHeader).isValid()) {
+ const auto sizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
+
+ if (sizeOpt) {
QMetaObject::invokeMethod(q, "_q_startOperation", Qt::QueuedConnection);
} else {
state = Buffering;
@@ -478,7 +482,8 @@ void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions()
{
Q_Q(QNetworkReplyImpl);
- QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
pauseNotificationHandling();
// important: At the point of this readyRead(), the data parameter list must be empty,
// else implicit sharing will trigger memcpy when the user is reading data!
@@ -487,8 +492,7 @@ void QNetworkReplyImplPrivate::appendDownstreamDataSignalEmissions()
// processed and we get into a recursive call (as in QProgressDialog).
if (downloadProgressSignalChoke.elapsed() >= progressSignalInterval) {
downloadProgressSignalChoke.restart();
- emit q->downloadProgress(bytesDownloaded,
- totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+ emit q->downloadProgress(bytesDownloaded, totalSizeOpt.value_or(-1));
}
resumeNotificationHandling();
@@ -519,11 +523,6 @@ void QNetworkReplyImplPrivate::appendDownstreamData(QIODevice *data)
_q_copyReadyRead();
}
-static void downloadBufferDeleter(char *ptr)
-{
- delete[] ptr;
-}
-
char* QNetworkReplyImplPrivate::getDownloadBuffer(qint64 size)
{
Q_Q(QNetworkReplyImpl);
@@ -536,7 +535,7 @@ char* QNetworkReplyImplPrivate::getDownloadBuffer(qint64 size)
downloadBufferCurrentSize = 0;
downloadBufferMaximumSize = size;
downloadBuffer = new char[downloadBufferMaximumSize]; // throws if allocation fails
- downloadBufferPointer = QSharedPointer<char>(downloadBuffer, downloadBufferDeleter);
+ downloadBufferPointer = QSharedPointer<char>(downloadBuffer, [](auto p) { delete[] p; });
q->setAttribute(QNetworkRequest::DownloadBufferAttribute, QVariant::fromValue<QSharedPointer<char> > (downloadBufferPointer));
}
@@ -594,7 +593,9 @@ void QNetworkReplyImplPrivate::finished()
return;
pauseNotificationHandling();
- QVariant totalSize = cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
+ const auto totalSize = totalSizeOpt.value_or(-1);
resumeNotificationHandling();
@@ -604,10 +605,10 @@ void QNetworkReplyImplPrivate::finished()
pendingNotifications.clear();
pauseNotificationHandling();
- if (totalSize.isNull() || totalSize == -1) {
+ if (totalSize == -1) {
emit q->downloadProgress(bytesDownloaded, bytesDownloaded);
} else {
- emit q->downloadProgress(bytesDownloaded, totalSize.toLongLong());
+ emit q->downloadProgress(bytesDownloaded, totalSize);
}
if (bytesUploaded == -1 && (outgoingData || outgoingDataBuffer))
@@ -615,7 +616,7 @@ void QNetworkReplyImplPrivate::finished()
resumeNotificationHandling();
// if we don't know the total size of or we received everything save the cache
- if (totalSize.isNull() || totalSize == -1 || bytesDownloaded == totalSize)
+ if (totalSize == -1 || bytesDownloaded == totalSize)
completeCacheSave();
// note: might not be a good idea, since users could decide to delete us
@@ -651,14 +652,14 @@ void QNetworkReplyImplPrivate::metaDataChanged()
// 1. do we have cookies?
// 2. are we allowed to set them?
if (!manager.isNull()) {
- const auto it = cookedHeaders.constFind(QNetworkRequest::SetCookieHeader);
- if (it != cookedHeaders.cend()
+ const auto cookiesOpt = QNetworkHeadersPrivate::toSetCookieList(
+ headers().values(QHttpHeaders::WellKnownHeader::SetCookie));
+ const auto cookies = cookiesOpt.value_or(QList<QNetworkCookie>());
+ if (!cookies.empty()
&& request.attribute(QNetworkRequest::CookieSaveControlAttribute,
QNetworkRequest::Automatic).toInt() == QNetworkRequest::Automatic) {
QNetworkCookieJar *jar = manager->cookieJar();
if (jar) {
- QList<QNetworkCookie> cookies =
- qvariant_cast<QList<QNetworkCookie> >(it.value());
jar->setCookiesFromUrl(cookies, url);
}
}
@@ -862,9 +863,10 @@ qint64 QNetworkReplyImpl::readData(char *data, qint64 maxlen)
break;
}
}
- QVariant totalSize = d->cookedHeaders.value(QNetworkRequest::ContentLengthHeader);
- emit downloadProgress(bytesRead,
- totalSize.isNull() ? Q_INT64_C(-1) : totalSize.toLongLong());
+
+ const auto totalSizeOpt = QNetworkHeadersPrivate::toInt(
+ headers().value(QHttpHeaders::WellKnownHeader::ContentLength));
+ emit downloadProgress(bytesRead, totalSizeOpt.value_or(-1));
return bytesRead;
} else if (d->backend && d->backend->bytesAvailable()) {
return d->backend->read(data, maxlen);
diff --git a/src/network/access/qnetworkreplyimpl_p.h b/src/network/access/qnetworkreplyimpl_p.h
index d24edfb567..9648b8b57a 100644
--- a/src/network/access/qnetworkreplyimpl_p.h
+++ b/src/network/access/qnetworkreplyimpl_p.h
@@ -157,7 +157,4 @@ Q_DECLARE_TYPEINFO(QNetworkReplyImplPrivate::InternalNotifications, Q_PRIMITIVE_
QT_END_NAMESPACE
-// ### move to qsharedpointer_impl.h
-QT_DECL_METATYPE_EXTERN_TAGGED(QSharedPointer<char>, QSharedPointer_char, Q_NETWORK_EXPORT)
-
#endif
diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp
index 70d2a6be44..7d2b6a701e 100644
--- a/src/network/access/qnetworkreplywasmimpl.cpp
+++ b/src/network/access/qnetworkreplywasmimpl.cpp
@@ -9,6 +9,9 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qfileinfo.h>
#include <QtCore/qthread.h>
+#include <QtCore/private/qeventdispatcher_wasm_p.h>
+#include <QtCore/private/qoffsetstringarray_p.h>
+#include <QtCore/private/qtools_p.h>
#include <private/qnetworkaccessmanager_p.h>
#include <private/qnetworkfile_p.h>
@@ -17,8 +20,12 @@
#include <emscripten/fetch.h>
QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
namespace {
-constexpr const char *BannedHeaders[] = {
+
+static constexpr auto BannedHeaders = qOffsetStringArray(
"accept-charset",
"accept-encoding",
"access-control-request-headers",
@@ -38,19 +45,14 @@ constexpr const char *BannedHeaders[] = {
"trailer",
"transfer-encoding",
"upgrade",
- "via",
-};
+ "via"
+);
-bool isUnsafeHeader(QLatin1StringView header)
+bool isUnsafeHeader(QLatin1StringView header) noexcept
{
- return header.startsWith(QStringLiteral("proxy-"), Qt::CaseInsensitive)
- || header.startsWith(QStringLiteral("sec-"), Qt::CaseInsensitive)
- || std::any_of(std::begin(BannedHeaders), std::end(BannedHeaders),
- [&header](const char *bannedHeader) {
- return 0
- == header.compare(QLatin1StringView(bannedHeader),
- Qt::CaseInsensitive);
- });
+ return header.startsWith("proxy-"_L1, Qt::CaseInsensitive)
+ || header.startsWith("sec-"_L1, Qt::CaseInsensitive)
+ || BannedHeaders.contains(header, Qt::CaseInsensitive);
}
} // namespace
@@ -61,12 +63,28 @@ QNetworkReplyWasmImplPrivate::QNetworkReplyWasmImplPrivate()
, downloadBufferCurrentSize(0)
, totalDownloadSize(0)
, percentFinished(0)
- , m_fetch(0)
+ , m_fetch(nullptr)
+ , m_fetchContext(nullptr)
{
}
QNetworkReplyWasmImplPrivate::~QNetworkReplyWasmImplPrivate()
{
+
+ if (m_fetchContext) { // fetch has been initiated
+ std::unique_lock lock{ m_fetchContext->mutex };
+
+ if (m_fetchContext->state == FetchContext::State::SCHEDULED
+ || m_fetchContext->state == FetchContext::State::SENT
+ || m_fetchContext->state == FetchContext::State::CANCELED) {
+ m_fetchContext->reply = nullptr;
+ m_fetchContext->state = FetchContext::State::TO_BE_DESTROYED;
+ } else if (m_fetchContext->state == FetchContext::State::FINISHED) {
+ lock.unlock();
+ delete m_fetchContext;
+ }
+ }
+
}
QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent)
@@ -78,6 +96,9 @@ QNetworkReplyWasmImpl::QNetworkReplyWasmImpl(QObject *parent)
QNetworkReplyWasmImpl::~QNetworkReplyWasmImpl()
{
+ if (isRunning())
+ abort();
+ close();
}
QByteArray QNetworkReplyWasmImpl::methodName() const
@@ -112,7 +133,7 @@ void QNetworkReplyWasmImpl::close()
d->state = QNetworkReplyPrivate::Finished;
d->setCanceled();
}
-
+ emscripten_fetch_close(d->m_fetch);
QNetworkReply::close();
}
@@ -130,7 +151,14 @@ void QNetworkReplyWasmImpl::abort()
void QNetworkReplyWasmImplPrivate::setCanceled()
{
Q_Q(QNetworkReplyWasmImpl);
- m_fetch->userData = nullptr;
+ {
+ if (m_fetchContext) {
+ std::scoped_lock lock{ m_fetchContext->mutex };
+ if (m_fetchContext->state == FetchContext::State::SCHEDULED
+ || m_fetchContext->state == FetchContext::State::SENT)
+ m_fetchContext->state = FetchContext::State::CANCELED;
+ }
+ }
emitReplyError(QNetworkReply::OperationCanceledError, QStringLiteral("Operation canceled"));
q->setFinished(true);
@@ -222,48 +250,7 @@ void QNetworkReplyWasmImplPrivate::doSendRequest()
emscripten_fetch_attr_t attr;
emscripten_fetch_attr_init(&attr);
- strcpy(attr.requestMethod, q->methodName().constData());
-
- QList<QByteArray> headersData = request.rawHeaderList();
- int arrayLength = getArraySize(headersData.count());
- const char *customHeaders[arrayLength];
- QStringList trimmedHeaders;
-
- if (headersData.count() > 0) {
- int i = 0;
- for (const auto &headerName : headersData) {
- if (isUnsafeHeader(QLatin1StringView(headerName.constData()))) {
- trimmedHeaders.push_back(QString::fromLatin1(headerName));
- } else {
- customHeaders[i++] = headerName.constData();
- customHeaders[i++] = request.rawHeader(headerName).constData();
- }
- }
- if (!trimmedHeaders.isEmpty()) {
- qWarning() << "Qt has trimmed the following forbidden headers from the request:"
- << trimmedHeaders.join(QLatin1StringView(", "));
- }
- customHeaders[i] = nullptr;
- attr.requestHeaders = customHeaders;
- }
-
- if (outgoingData) { // data from post request
- // handle extra data
- requestData = outgoingData->readAll(); // is there a size restriction here?
- if (!requestData.isEmpty()) {
- attr.requestData = requestData.data();
- attr.requestDataSize = requestData.size();
- }
- }
-
- QByteArray userName, password;
- // username & password
- if (!request.url().userInfo().isEmpty()) {
- userName = request.url().userName().toUtf8();
- password = request.url().password().toUtf8();
- attr.userName = userName.constData();
- attr.password = password.constData();
- }
+ qstrncpy(attr.requestMethod, q->methodName().constData(), 32); // requestMethod is char[32] in emscripten
attr.attributes = EMSCRIPTEN_FETCH_LOAD_TO_MEMORY;
@@ -281,22 +268,74 @@ void QNetworkReplyWasmImplPrivate::doSendRequest()
request.attribute(QNetworkRequest::CacheSaveControlAttribute, false).toBool()) {
attr.attributes -= EMSCRIPTEN_FETCH_PERSIST_FILE;
}
- if (request.attribute(QNetworkRequest::UseCredentialsAttribute, true).toBool()) {
- attr.withCredentials = true;
- }
+ attr.withCredentials = request.attribute(QNetworkRequest::UseCredentialsAttribute, false).toBool();
attr.onsuccess = QNetworkReplyWasmImplPrivate::downloadSucceeded;
attr.onerror = QNetworkReplyWasmImplPrivate::downloadFailed;
attr.onprogress = QNetworkReplyWasmImplPrivate::downloadProgress;
attr.onreadystatechange = QNetworkReplyWasmImplPrivate::stateChange;
attr.timeoutMSecs = request.transferTimeout();
- attr.userData = reinterpret_cast<void *>(this);
- QString dPath = QStringLiteral("/home/web_user/") + request.url().fileName();
- QByteArray destinationPath = dPath.toUtf8();
- attr.destinationPath = destinationPath.constData();
+ m_fetchContext = new FetchContext(this);;
+ attr.userData = static_cast<void *>(m_fetchContext);
+ if (outgoingData) { // data from post request
+ m_fetchContext->requestData = outgoingData->readAll(); // is there a size restriction here?
+ if (!m_fetchContext->requestData.isEmpty()) {
+ attr.requestData = m_fetchContext->requestData.data();
+ attr.requestDataSize = m_fetchContext->requestData.size();
+ }
+ }
- m_fetch = emscripten_fetch(&attr, request.url().toString().toUtf8());
+ QEventDispatcherWasm::runOnMainThread([attr, fetchContext = m_fetchContext]() mutable {
+ std::unique_lock lock{ fetchContext->mutex };
+ if (fetchContext->state == FetchContext::State::CANCELED) {
+ fetchContext->state = FetchContext::State::FINISHED;
+ return;
+ } else if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
+ lock.unlock();
+ delete fetchContext;
+ return;
+ }
+ const auto reply = fetchContext->reply;
+ const auto &request = reply->request;
+
+ QByteArray userName, password;
+ if (!request.url().userInfo().isEmpty()) {
+ userName = request.url().userName().toUtf8();
+ password = request.url().password().toUtf8();
+ attr.userName = userName.constData();
+ attr.password = password.constData();
+ }
+
+ QList<QByteArray> headersData = request.rawHeaderList();
+ int arrayLength = getArraySize(headersData.count());
+ const char *customHeaders[arrayLength];
+ QStringList trimmedHeaders;
+ if (headersData.count() > 0) {
+ int i = 0;
+ for (const auto &headerName : headersData) {
+ if (isUnsafeHeader(QLatin1StringView(headerName.constData()))) {
+ trimmedHeaders.push_back(QString::fromLatin1(headerName));
+ } else {
+ customHeaders[i++] = headerName.constData();
+ customHeaders[i++] = request.rawHeader(headerName).constData();
+ }
+ }
+ if (!trimmedHeaders.isEmpty()) {
+ qWarning() << "Qt has trimmed the following forbidden headers from the request:"
+ << trimmedHeaders.join(QLatin1StringView(", "));
+ }
+ customHeaders[i] = nullptr;
+ attr.requestHeaders = customHeaders;
+ }
+
+ auto url = request.url().toString().toUtf8();
+ QString dPath = "/home/web_user/"_L1 + request.url().fileName();
+ QByteArray destinationPath = dPath.toUtf8();
+ attr.destinationPath = destinationPath.constData();
+ reply->m_fetch = emscripten_fetch(&attr, url.constData());
+ fetchContext->state = FetchContext::State::SENT;
+ });
state = Working;
}
@@ -314,15 +353,16 @@ void QNetworkReplyWasmImplPrivate::emitDataReadProgress(qint64 bytesReceived, qi
totalDownloadSize = bytesTotal;
- percentFinished = (bytesReceived / bytesTotal) * 100;
+ percentFinished = bytesTotal ? (bytesReceived / bytesTotal) * 100 : 100;
emit q->downloadProgress(bytesReceived, bytesTotal);
}
-void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer, int bufferSize)
+void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer)
{
Q_Q(QNetworkReplyWasmImpl);
+ const qsizetype bufferSize = buffer.size();
if (bufferSize > 0)
q->setReadBufferSize(bufferSize);
@@ -335,7 +375,7 @@ void QNetworkReplyWasmImplPrivate::dataReceived(const QByteArray &buffer, int bu
totalDownloadSize = downloadBufferCurrentSize;
- downloadBuffer.append(buffer, bufferSize);
+ downloadBuffer.append(buffer);
emit q->readyRead();
}
@@ -346,32 +386,36 @@ static int parseHeaderName(const QByteArray &headerName)
if (headerName.isEmpty())
return -1;
- switch (tolower(headerName.at(0))) {
+ auto is = [&](const char *what) {
+ return qstrnicmp(headerName.data(), headerName.size(), what) == 0;
+ };
+
+ switch (QtMiscUtils::toAsciiLower(headerName.front())) {
case 'c':
- if (qstricmp(headerName.constData(), "content-type") == 0)
+ if (is("content-type"))
return QNetworkRequest::ContentTypeHeader;
- else if (qstricmp(headerName.constData(), "content-length") == 0)
+ else if (is("content-length"))
return QNetworkRequest::ContentLengthHeader;
- else if (qstricmp(headerName.constData(), "cookie") == 0)
+ else if (is("cookie"))
return QNetworkRequest::CookieHeader;
break;
case 'l':
- if (qstricmp(headerName.constData(), "location") == 0)
+ if (is("location"))
return QNetworkRequest::LocationHeader;
- else if (qstricmp(headerName.constData(), "last-modified") == 0)
+ else if (is("last-modified"))
return QNetworkRequest::LastModifiedHeader;
break;
case 's':
- if (qstricmp(headerName.constData(), "set-cookie") == 0)
+ if (is("set-cookie"))
return QNetworkRequest::SetCookieHeader;
- else if (qstricmp(headerName.constData(), "server") == 0)
+ else if (is("server"))
return QNetworkRequest::ServerHeader;
break;
case 'u':
- if (qstricmp(headerName.constData(), "user-agent") == 0)
+ if (is("user-agent"))
return QNetworkRequest::UserAgentHeader;
break;
}
@@ -469,23 +513,34 @@ void QNetworkReplyWasmImplPrivate::_q_bufferOutgoingData()
void QNetworkReplyWasmImplPrivate::downloadSucceeded(emscripten_fetch_t *fetch)
{
- auto reply = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
- if (reply) {
+ auto fetchContext = static_cast<FetchContext *>(fetch->userData);
+ std::unique_lock lock{ fetchContext->mutex };
+
+ if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
+ lock.unlock();
+ delete fetchContext;
+ return;
+ } else if (fetchContext->state == FetchContext::State::CANCELED) {
+ fetchContext->state = FetchContext::State::FINISHED;
+ return;
+ } else if (fetchContext->state == FetchContext::State::SENT) {
+ const auto reply = fetchContext->reply;
if (reply->state != QNetworkReplyPrivate::Aborted) {
QByteArray buffer(fetch->data, fetch->numBytes);
- reply->dataReceived(buffer, buffer.size());
+ reply->dataReceived(buffer);
QByteArray statusText(fetch->statusText);
reply->setStatusCode(fetch->status, statusText);
reply->setReplyFinished();
}
reply->m_fetch = nullptr;
+ fetchContext->state = FetchContext::State::FINISHED;
}
- emscripten_fetch_close(fetch);
}
void QNetworkReplyWasmImplPrivate::setReplyFinished()
{
Q_Q(QNetworkReplyWasmImpl);
+ state = QNetworkReplyPrivate::Finished;
q->setFinished(true);
emit q->readChannelFinished();
emit q->finished();
@@ -500,7 +555,8 @@ void QNetworkReplyWasmImplPrivate::setStatusCode(int status, const QByteArray &s
void QNetworkReplyWasmImplPrivate::stateChange(emscripten_fetch_t *fetch)
{
- auto reply = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
+ const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
+ const auto reply = fetchContext->reply;
if (reply && reply->state != QNetworkReplyPrivate::Aborted) {
if (fetch->readyState == /*HEADERS_RECEIVED*/ 2) {
size_t headerLength = emscripten_fetch_get_response_headers_length(fetch);
@@ -513,7 +569,8 @@ void QNetworkReplyWasmImplPrivate::stateChange(emscripten_fetch_t *fetch)
void QNetworkReplyWasmImplPrivate::downloadProgress(emscripten_fetch_t *fetch)
{
- auto reply = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
+ const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
+ const auto reply = fetchContext->reply;
if (reply && reply->state != QNetworkReplyPrivate::Aborted) {
if (fetch->status < 400) {
uint64_t bytes = fetch->dataOffset + fetch->numBytes;
@@ -527,22 +584,35 @@ void QNetworkReplyWasmImplPrivate::downloadProgress(emscripten_fetch_t *fetch)
void QNetworkReplyWasmImplPrivate::downloadFailed(emscripten_fetch_t *fetch)
{
- auto reply = reinterpret_cast<QNetworkReplyWasmImplPrivate*>(fetch->userData);
- if (reply) {
+ const auto fetchContext = static_cast<FetchContext*>(fetch->userData);
+ std::unique_lock lock{ fetchContext->mutex };
+
+ if (fetchContext->state == FetchContext::State::TO_BE_DESTROYED) {
+ lock.unlock();
+ delete fetchContext;
+ return;
+ } else if (fetchContext->state == FetchContext::State::CANCELED) {
+ fetchContext->state = FetchContext::State::FINISHED;
+ return;
+ } else if (fetchContext->state == FetchContext::State::SENT) {
+ const auto reply = fetchContext->reply;
if (reply->state != QNetworkReplyPrivate::Aborted) {
QString reasonStr;
if (fetch->status > 600)
reasonStr = QStringLiteral("Operation canceled");
else
reasonStr = QString::fromUtf8(fetch->statusText);
+ QByteArray buffer(fetch->data, fetch->numBytes);
+ reply->dataReceived(buffer);
QByteArray statusText(fetch->statusText);
reply->setStatusCode(fetch->status, statusText);
- reply->emitReplyError(reply->statusCodeFromHttp(fetch->status, reply->request.url()), reasonStr);
+ reply->emitReplyError(reply->statusCodeFromHttp(fetch->status, reply->request.url()),
+ reasonStr);
reply->setReplyFinished();
}
reply->m_fetch = nullptr;
+ fetchContext->state = FetchContext::State::FINISHED;
}
- emscripten_fetch_close(fetch);
}
//taken from qhttpthreaddelegate.cpp
diff --git a/src/network/access/qnetworkreplywasmimpl_p.h b/src/network/access/qnetworkreplywasmimpl_p.h
index d8c814621b..4b00bb09ea 100644
--- a/src/network/access/qnetworkreplywasmimpl_p.h
+++ b/src/network/access/qnetworkreplywasmimpl_p.h
@@ -28,6 +28,7 @@
#include <emscripten/fetch.h>
#include <memory>
+#include <mutex>
QT_BEGIN_NAMESPACE
@@ -57,12 +58,35 @@ public:
Q_PRIVATE_SLOT(d_func(), void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &errorString))
Q_PRIVATE_SLOT(d_func(), void emitDataReadProgress(qint64 done, qint64 total))
- Q_PRIVATE_SLOT(d_func(), void dataReceived(char *buffer, int bufferSize))
+ Q_PRIVATE_SLOT(d_func(), void dataReceived(const QByteArray &buffer))
private:
QByteArray methodName() const;
};
+class QNetworkReplyWasmImplPrivate;
+
+/*!
+ The FetchContext class ensures the requestData object remains valid
+ while a fetch operation is pending. Since Emscripten fetch is asynchronous,
+ requestData must persist until one of the final callbacks is invoked.
+ Additionally, there's a potential race condition between the thread
+ scheduling the fetch operation and the one executing it. Since fetch must
+ occur on the main thread due to browser limitations,
+ a mutex safeguards the FetchContext to ensure atomic state transitions.
+*/
+struct FetchContext
+{
+ enum class State { SCHEDULED, SENT, FINISHED, CANCELED, TO_BE_DESTROYED };
+
+ FetchContext(QNetworkReplyWasmImplPrivate *networkReply) : reply(networkReply) { }
+
+ QNetworkReplyWasmImplPrivate *reply{ nullptr };
+ std::mutex mutex;
+ QByteArray requestData;
+ State state{ State::SCHEDULED };
+};
+
class QNetworkReplyWasmImplPrivate: public QNetworkReplyPrivate
{
public:
@@ -75,7 +99,7 @@ public:
void emitReplyError(QNetworkReply::NetworkError errorCode, const QString &);
void emitDataReadProgress(qint64 done, qint64 total);
- void dataReceived(const QByteArray &buffer, int bufferSize);
+ void dataReceived(const QByteArray &buffer);
void headersReceived(const QByteArray &buffer);
void setStatusCode(int status, const QByteArray &statusText);
@@ -101,7 +125,6 @@ public:
QIODevice *outgoingData;
std::shared_ptr<QRingBuffer> outgoingDataBuffer;
- QByteArray requestData;
static void downloadProgress(emscripten_fetch_t *fetch);
static void downloadFailed(emscripten_fetch_t *fetch);
@@ -111,6 +134,7 @@ public:
static QNetworkReply::NetworkError statusCodeFromHttp(int httpStatusCode, const QUrl &url);
emscripten_fetch_t *m_fetch;
+ FetchContext *m_fetchContext;
void setReplyFinished();
void setCanceled();
diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp
index af5113ce2f..0e1172b15c 100644
--- a/src/network/access/qnetworkrequest.cpp
+++ b/src/network/access/qnetworkrequest.cpp
@@ -6,13 +6,19 @@
#include "qplatformdefs.h"
#include "qnetworkcookie.h"
#include "qsslconfiguration.h"
-#if QT_CONFIG(http) || defined(Q_QDOC)
+#include "qhttpheadershelper_p.h"
+#if QT_CONFIG(http)
+#include "qhttp1configuration.h"
#include "qhttp2configuration.h"
#include "private/http2protocol_p.h"
#endif
-#include "QtCore/qshareddata.h"
-#include "QtCore/qlocale.h"
+
#include "QtCore/qdatetime.h"
+#include "QtCore/qlocale.h"
+#include "QtCore/qshareddata.h"
+#include "QtCore/qtimezone.h"
+#include "QtCore/private/qduplicatetracker_p.h"
+#include "QtCore/private/qtools_p.h"
#include <ctype.h>
#if QT_CONFIG(datestring)
@@ -20,10 +26,14 @@
#endif
#include <algorithm>
+#include <q20algorithm.h>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+using namespace std::chrono_literals;
+
+constexpr std::chrono::milliseconds QNetworkRequest::DefaultTransferTimeout;
QT_IMPL_METATYPE_EXTERN(QNetworkRequest)
QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest__RedirectPolicy)
@@ -102,6 +112,8 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
\value ServerHeader The Server header received by HTTP clients.
+ \omitvalue NumKnownHeaders
+
\sa header(), setHeader(), rawHeader(), setRawHeader()
*/
@@ -217,7 +229,7 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
Requests only, type: QMetaType::Int (default: QNetworkRequest::Automatic)
Indicates whether to use cached authorization credentials in the request,
if available. If this is set to QNetworkRequest::Manual and the authentication
- mechanism is 'Basic' or 'Digest', Qt will not send an an 'Authorization' HTTP
+ mechanism is 'Basic' or 'Digest', Qt will not send an 'Authorization' HTTP
header with any cached credentials it may have for the request's URL.
This attribute is set to QNetworkRequest::Manual by Qt WebKit when creating a cross-origin
XMLHttpRequest where withCredentials has not been set explicitly to true by the
@@ -312,6 +324,16 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
same-origin requests. This only affects the WebAssembly platform.
(This value was introduced in 6.5.)
+ \value FullLocalServerNameAttribute
+ Requests only, type: QMetaType::String
+ Holds the full local server name to be used for the underlying
+ QLocalSocket. This attribute is used by the QNetworkAccessManager
+ to connect to a specific local server, when QLocalSocket's behavior for
+ a simple name isn't enough. The URL in the QNetworkRequest must still
+ use unix+http: or local+http: scheme. And the hostname in the URL will
+ be used for the Host header in the HTTP request.
+ (This value was introduced in 6.8.)
+
\value User
Special type. Additional information can be passed in
QVariants with types ranging from User to UserMax. The default
@@ -403,6 +425,16 @@ QT_IMPL_METATYPE_EXTERN_TAGGED(QNetworkRequest::RedirectPolicy, QNetworkRequest_
\value DefaultTransferTimeoutConstant The transfer timeout in milliseconds.
Used if setTimeout() is called
without an argument.
+
+ \sa QNetworkRequest::DefaultTransferTimeout
+ */
+
+/*!
+ \variable QNetworkRequest::DefaultTransferTimeout
+
+ The transfer timeout with \l {QNetworkRequest::TransferTimeoutConstant}
+ milliseconds. Used if setTransferTimeout() is called without an
+ argument.
*/
class QNetworkRequestPrivate: public QSharedData, public QNetworkHeadersPrivate
@@ -415,7 +447,6 @@ public:
, sslConfiguration(nullptr)
#endif
, maxRedirectsAllowed(maxRedirectCount)
- , transferTimeout(0)
{ qRegisterMetaType<QNetworkRequest>(); }
~QNetworkRequestPrivate()
{
@@ -438,6 +469,7 @@ public:
#endif
peerVerifyName = other.peerVerifyName;
#if QT_CONFIG(http)
+ h1Configuration = other.h1Configuration;
h2Configuration = other.h2Configuration;
decompressedSafetyCheckThreshold = other.decompressedSafetyCheckThreshold;
#endif
@@ -448,15 +480,16 @@ public:
{
return url == other.url &&
priority == other.priority &&
- rawHeaders == other.rawHeaders &&
attributes == other.attributes &&
maxRedirectsAllowed == other.maxRedirectsAllowed &&
peerVerifyName == other.peerVerifyName
#if QT_CONFIG(http)
+ && h1Configuration == other.h1Configuration
&& h2Configuration == other.h2Configuration
&& decompressedSafetyCheckThreshold == other.decompressedSafetyCheckThreshold
#endif
&& transferTimeout == other.transferTimeout
+ && QHttpHeadersHelper::compareStrict(httpHeaders, other.httpHeaders)
;
// don't compare cookedHeaders
}
@@ -469,10 +502,11 @@ public:
int maxRedirectsAllowed;
QString peerVerifyName;
#if QT_CONFIG(http)
+ QHttp1Configuration h1Configuration;
QHttp2Configuration h2Configuration;
qint64 decompressedSafetyCheckThreshold = 10ll * 1024ll * 1024ll;
#endif
- int transferTimeout;
+ std::chrono::milliseconds transferTimeout = 0ms;
};
/*!
@@ -582,6 +616,43 @@ void QNetworkRequest::setUrl(const QUrl &url)
}
/*!
+ \since 6.8
+
+ Returns headers that are set in this network request.
+
+ \sa setHeaders()
+*/
+QHttpHeaders QNetworkRequest::headers() const
+{
+ return d->headers();
+}
+
+/*!
+ \since 6.8
+
+ Sets \a newHeaders as headers in this network request, overriding
+ any previously set headers.
+
+ If some headers correspond to the known headers, the values will
+ be parsed and the corresponding parsed form will also be set.
+
+ \sa headers(), KnownHeaders
+*/
+void QNetworkRequest::setHeaders(QHttpHeaders &&newHeaders)
+{
+ d->setHeaders(std::move(newHeaders));
+}
+
+/*!
+ \overload
+ \since 6.8
+*/
+void QNetworkRequest::setHeaders(const QHttpHeaders &newHeaders)
+{
+ d->setHeaders(newHeaders);
+}
+
+/*!
Returns the value of the known network header \a header if it is
present in this request. If it is not present, returns QVariant()
(i.e., an invalid variant).
@@ -610,10 +681,11 @@ void QNetworkRequest::setHeader(KnownHeaders header, const QVariant &value)
network request.
\sa rawHeader(), setRawHeader()
+ \note In Qt versions prior to 6.7, this function took QByteArray only.
*/
-bool QNetworkRequest::hasRawHeader(const QByteArray &headerName) const
+bool QNetworkRequest::hasRawHeader(QAnyStringView headerName) const
{
- return d->findRawHeader(headerName) != d->rawHeaders.constEnd();
+ return d->headers().contains(headerName);
}
/*!
@@ -625,14 +697,11 @@ bool QNetworkRequest::hasRawHeader(const QByteArray &headerName) const
Raw headers can be set with setRawHeader() or with setHeader().
\sa header(), setRawHeader()
+ \note In Qt versions prior to 6.7, this function took QByteArray only.
*/
-QByteArray QNetworkRequest::rawHeader(const QByteArray &headerName) const
+QByteArray QNetworkRequest::rawHeader(QAnyStringView headerName) const
{
- QNetworkHeadersPrivate::RawHeadersList::ConstIterator it =
- d->findRawHeader(headerName);
- if (it != d->rawHeaders.constEnd())
- return it->second;
- return QByteArray();
+ return d->rawHeader(headerName);
}
/*!
@@ -852,7 +921,31 @@ void QNetworkRequest::setPeerVerifyName(const QString &peerName)
d->peerVerifyName = peerName;
}
-#if QT_CONFIG(http) || defined(Q_QDOC)
+#if QT_CONFIG(http)
+/*!
+ \since 6.5
+
+ Returns the current parameters that QNetworkAccessManager is
+ using for the underlying HTTP/1 connection of this request.
+
+ \sa setHttp1Configuration
+*/
+QHttp1Configuration QNetworkRequest::http1Configuration() const
+{
+ return d->h1Configuration;
+}
+/*!
+ \since 6.5
+
+ Sets request's HTTP/1 parameters from \a configuration.
+
+ \sa http1Configuration, QNetworkAccessManager, QHttp1Configuration
+*/
+void QNetworkRequest::setHttp1Configuration(const QHttp1Configuration &configuration)
+{
+ d->h1Configuration = configuration;
+}
+
/*!
\since 5.14
@@ -938,91 +1031,144 @@ void QNetworkRequest::setDecompressedSafetyCheckThreshold(qint64 threshold)
{
d->decompressedSafetyCheckThreshold = threshold;
}
-#endif // QT_CONFIG(http) || defined(Q_QDOC)
+#endif // QT_CONFIG(http)
-#if QT_CONFIG(http) || defined(Q_QDOC) || defined (Q_OS_WASM)
+#if QT_CONFIG(http) || defined (Q_OS_WASM)
/*!
+ \fn int QNetworkRequest::transferTimeout() const
\since 5.15
Returns the timeout used for transfers, in milliseconds.
- This timeout is zero if setTransferTimeout hasn't been
- called, which means that the timeout is not used.
+ \sa setTransferTimeout()
+*/
+
+/*!
+ \fn void QNetworkRequest::setTransferTimeout(int timeout)
+ \since 5.15
+
+ Sets \a timeout as the transfer timeout in milliseconds.
+
+ \sa setTransferTimeout(std::chrono::milliseconds),
+ transferTimeout(), transferTimeoutAsDuration()
+*/
+
+/*!
+ \since 6.7
+
+ Returns the timeout duration after which the transfer is aborted if no
+ data is exchanged.
+
+ The default duration is zero, which means that the timeout is not used.
- \sa setTransferTimeout
+ \sa setTransferTimeout(std::chrono::milliseconds)
*/
-int QNetworkRequest::transferTimeout() const
+std::chrono::milliseconds QNetworkRequest::transferTimeoutAsDuration() const
{
return d->transferTimeout;
}
/*!
- \since 5.15
+ \since 6.7
- Sets \a timeout as the transfer timeout in milliseconds.
+ Sets the timeout \a duration to abort the transfer if no data is exchanged.
Transfers are aborted if no bytes are transferred before
the timeout expires. Zero means no timer is set. If no
argument is provided, the timeout is
- QNetworkRequest::DefaultTransferTimeoutConstant. If this function
+ QNetworkRequest::DefaultTransferTimeout. If this function
is not called, the timeout is disabled and has the
value zero.
- \sa transferTimeout
+ \sa transferTimeoutAsDuration()
*/
-void QNetworkRequest::setTransferTimeout(int timeout)
+void QNetworkRequest::setTransferTimeout(std::chrono::milliseconds duration)
{
- d->transferTimeout = timeout;
+ d->transferTimeout = duration;
}
-#endif // QT_CONFIG(http) || defined(Q_QDOC) || defined (Q_OS_WASM)
-
-static QByteArray headerName(QNetworkRequest::KnownHeaders header)
-{
- switch (header) {
- case QNetworkRequest::ContentTypeHeader:
- return "Content-Type";
-
- case QNetworkRequest::ContentLengthHeader:
- return "Content-Length";
+#endif // QT_CONFIG(http) || defined (Q_OS_WASM)
- case QNetworkRequest::LocationHeader:
- return "Location";
-
- case QNetworkRequest::LastModifiedHeader:
- return "Last-Modified";
-
- case QNetworkRequest::IfModifiedSinceHeader:
- return "If-Modified-Since";
+namespace {
- case QNetworkRequest::ETagHeader:
- return "ETag";
+struct HeaderPair {
+ QHttpHeaders::WellKnownHeader wellKnownHeader;
+ QNetworkRequest::KnownHeaders knownHeader;
+};
- case QNetworkRequest::IfMatchHeader:
- return "If-Match";
+constexpr bool operator<(const HeaderPair &lhs, const HeaderPair &rhs)
+{
+ return lhs.wellKnownHeader < rhs.wellKnownHeader;
+}
- case QNetworkRequest::IfNoneMatchHeader:
- return "If-None-Match";
+constexpr bool operator<(const HeaderPair &lhs, QHttpHeaders::WellKnownHeader rhs)
+{
+ return lhs.wellKnownHeader < rhs;
+}
- case QNetworkRequest::CookieHeader:
- return "Cookie";
+constexpr bool operator<(QHttpHeaders::WellKnownHeader lhs, const HeaderPair &rhs)
+{
+ return lhs < rhs.wellKnownHeader;
+}
- case QNetworkRequest::SetCookieHeader:
- return "Set-Cookie";
+} // anonymous namespace
+
+static constexpr HeaderPair knownHeadersArr[] = {
+ { QHttpHeaders::WellKnownHeader::ContentDisposition, QNetworkRequest::KnownHeaders::ContentDispositionHeader },
+ { QHttpHeaders::WellKnownHeader::ContentLength, QNetworkRequest::KnownHeaders::ContentLengthHeader },
+ { QHttpHeaders::WellKnownHeader::ContentType, QNetworkRequest::KnownHeaders::ContentTypeHeader },
+ { QHttpHeaders::WellKnownHeader::Cookie, QNetworkRequest::KnownHeaders::CookieHeader },
+ { QHttpHeaders::WellKnownHeader::ETag, QNetworkRequest::KnownHeaders::ETagHeader },
+ { QHttpHeaders::WellKnownHeader::IfMatch , QNetworkRequest::KnownHeaders::IfMatchHeader },
+ { QHttpHeaders::WellKnownHeader::IfModifiedSince, QNetworkRequest::KnownHeaders::IfModifiedSinceHeader },
+ { QHttpHeaders::WellKnownHeader::IfNoneMatch, QNetworkRequest::KnownHeaders::IfNoneMatchHeader },
+ { QHttpHeaders::WellKnownHeader::LastModified, QNetworkRequest::KnownHeaders::LastModifiedHeader},
+ { QHttpHeaders::WellKnownHeader::Location, QNetworkRequest::KnownHeaders::LocationHeader},
+ { QHttpHeaders::WellKnownHeader::Server, QNetworkRequest::KnownHeaders::ServerHeader },
+ { QHttpHeaders::WellKnownHeader::SetCookie, QNetworkRequest::KnownHeaders::SetCookieHeader },
+ { QHttpHeaders::WellKnownHeader::UserAgent, QNetworkRequest::KnownHeaders::UserAgentHeader }
+};
- case QNetworkRequest::ContentDispositionHeader:
- return "Content-Disposition";
+static_assert(std::size(knownHeadersArr) == size_t(QNetworkRequest::KnownHeaders::NumKnownHeaders));
+static_assert(q20::is_sorted(std::begin(knownHeadersArr), std::end(knownHeadersArr)));
- case QNetworkRequest::UserAgentHeader:
- return "User-Agent";
+static std::optional<QNetworkRequest::KnownHeaders> toKnownHeader(QHttpHeaders::WellKnownHeader key)
+{
+ const auto it = std::lower_bound(std::begin(knownHeadersArr), std::end(knownHeadersArr), key);
+ if (it == std::end(knownHeadersArr) || key < *it)
+ return std::nullopt;
+ return it->knownHeader;
+}
- case QNetworkRequest::ServerHeader:
- return "Server";
+static std::optional<QHttpHeaders::WellKnownHeader> toWellKnownHeader(QNetworkRequest::KnownHeaders key)
+{
+ auto pred = [key](const HeaderPair &pair) { return pair.knownHeader == key; };
+ const auto it = std::find_if(std::begin(knownHeadersArr), std::end(knownHeadersArr), pred);
+ if (it == std::end(knownHeadersArr))
+ return std::nullopt;
+ return it->wellKnownHeader;
+}
- // no default:
- // if new values are added, this will generate a compiler warning
+static QByteArray makeCookieHeader(const QList<QNetworkCookie> &cookies,
+ QNetworkCookie::RawForm type,
+ QByteArrayView separator)
+{
+ QByteArray result;
+ for (const QNetworkCookie &cookie : cookies) {
+ result += cookie.toRawForm(type);
+ result += separator;
}
+ if (!result.isEmpty())
+ result.chop(separator.size());
+ return result;
+}
- return QByteArray();
+static QByteArray makeCookieHeader(const QVariant &value, QNetworkCookie::RawForm type,
+ QByteArrayView separator)
+{
+ const QList<QNetworkCookie> *cookies = get_if<QList<QNetworkCookie>>(&value);
+ if (!cookies)
+ return {};
+ return makeCookieHeader(*cookies, type, separator);
}
static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVariant &value)
@@ -1052,7 +1198,7 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria
switch (value.userType()) {
// Generate RFC 1123/822 dates:
case QMetaType::QDate:
- return QNetworkHeadersPrivate::toHttpDate(value.toDate().startOfDay(Qt::UTC));
+ return QNetworkHeadersPrivate::toHttpDate(value.toDate().startOfDay(QTimeZone::UTC));
case QMetaType::QDateTime:
return QNetworkHeadersPrivate::toHttpDate(value.toDateTime());
@@ -1060,89 +1206,68 @@ static QByteArray headerValue(QNetworkRequest::KnownHeaders header, const QVaria
return value.toByteArray();
}
- case QNetworkRequest::CookieHeader: {
- QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie> >(value);
- if (cookies.isEmpty() && value.userType() == qMetaTypeId<QNetworkCookie>())
- cookies << qvariant_cast<QNetworkCookie>(value);
-
- QByteArray result;
- bool first = true;
- for (const QNetworkCookie &cookie : std::as_const(cookies)) {
- if (!first)
- result += "; ";
- first = false;
- result += cookie.toRawForm(QNetworkCookie::NameAndValueOnly);
- }
- return result;
- }
+ case QNetworkRequest::CookieHeader:
+ return makeCookieHeader(value, QNetworkCookie::NameAndValueOnly, "; ");
- case QNetworkRequest::SetCookieHeader: {
- QList<QNetworkCookie> cookies = qvariant_cast<QList<QNetworkCookie> >(value);
- if (cookies.isEmpty() && value.userType() == qMetaTypeId<QNetworkCookie>())
- cookies << qvariant_cast<QNetworkCookie>(value);
-
- QByteArray result;
- bool first = true;
- for (const QNetworkCookie &cookie : std::as_const(cookies)) {
- if (!first)
- result += ", ";
- first = false;
- result += cookie.toRawForm(QNetworkCookie::Full);
- }
- return result;
- }
- }
+ case QNetworkRequest::SetCookieHeader:
+ return makeCookieHeader(value, QNetworkCookie::Full, ", ");
- return QByteArray();
+ default:
+ Q_UNREACHABLE_RETURN({});
+ }
}
-static int parseHeaderName(const QByteArray &headerName)
+static int parseHeaderName(QByteArrayView headerName)
{
if (headerName.isEmpty())
return -1;
- switch (tolower(headerName.at(0))) {
+ auto is = [headerName](QByteArrayView what) {
+ return headerName.compare(what, Qt::CaseInsensitive) == 0;
+ };
+
+ switch (QtMiscUtils::toAsciiLower(headerName.front())) {
case 'c':
- if (headerName.compare("content-type", Qt::CaseInsensitive) == 0)
+ if (is("content-type"))
return QNetworkRequest::ContentTypeHeader;
- else if (headerName.compare("content-length", Qt::CaseInsensitive) == 0)
+ else if (is("content-length"))
return QNetworkRequest::ContentLengthHeader;
- else if (headerName.compare("cookie", Qt::CaseInsensitive) == 0)
+ else if (is("cookie"))
return QNetworkRequest::CookieHeader;
- else if (qstricmp(headerName.constData(), "content-disposition") == 0)
+ else if (is("content-disposition"))
return QNetworkRequest::ContentDispositionHeader;
break;
case 'e':
- if (qstricmp(headerName.constData(), "etag") == 0)
+ if (is("etag"))
return QNetworkRequest::ETagHeader;
break;
case 'i':
- if (qstricmp(headerName.constData(), "if-modified-since") == 0)
+ if (is("if-modified-since"))
return QNetworkRequest::IfModifiedSinceHeader;
- if (qstricmp(headerName.constData(), "if-match") == 0)
+ if (is("if-match"))
return QNetworkRequest::IfMatchHeader;
- if (qstricmp(headerName.constData(), "if-none-match") == 0)
+ if (is("if-none-match"))
return QNetworkRequest::IfNoneMatchHeader;
break;
case 'l':
- if (headerName.compare("location", Qt::CaseInsensitive) == 0)
+ if (is("location"))
return QNetworkRequest::LocationHeader;
- else if (headerName.compare("last-modified", Qt::CaseInsensitive) == 0)
+ else if (is("last-modified"))
return QNetworkRequest::LastModifiedHeader;
break;
case 's':
- if (headerName.compare("set-cookie", Qt::CaseInsensitive) == 0)
+ if (is("set-cookie"))
return QNetworkRequest::SetCookieHeader;
- else if (headerName.compare("server", Qt::CaseInsensitive) == 0)
+ else if (is("server"))
return QNetworkRequest::ServerHeader;
break;
case 'u':
- if (headerName.compare("user-agent", Qt::CaseInsensitive) == 0)
+ if (is("user-agent"))
return QNetworkRequest::UserAgentHeader;
break;
}
@@ -1150,7 +1275,7 @@ static int parseHeaderName(const QByteArray &headerName)
return -1; // nothing found
}
-static QVariant parseHttpDate(const QByteArray &raw)
+static QVariant parseHttpDate(QByteArrayView raw)
{
QDateTime dt = QNetworkHeadersPrivate::fromHttpDate(raw);
if (dt.isValid())
@@ -1158,24 +1283,23 @@ static QVariant parseHttpDate(const QByteArray &raw)
return QVariant(); // transform an invalid QDateTime into a null QVariant
}
-static QVariant parseCookieHeader(const QByteArray &raw)
+static QList<QNetworkCookie> parseCookieHeader(QByteArrayView raw)
{
QList<QNetworkCookie> result;
- const QList<QByteArray> cookieList = raw.split(';');
- for (const QByteArray &cookie : cookieList) {
+ for (auto cookie : QLatin1StringView(raw).tokenize(';'_L1)) {
QList<QNetworkCookie> parsed = QNetworkCookie::parseCookies(cookie.trimmed());
if (parsed.size() != 1)
- return QVariant(); // invalid Cookie: header
+ return {}; // invalid Cookie: header
result += parsed;
}
- return QVariant::fromValue(result);
+ return result;
}
-static QVariant parseETag(const QByteArray &raw)
+static QVariant parseETag(QByteArrayView raw)
{
- const QByteArray trimmed = raw.trimmed();
+ const QByteArrayView trimmed = raw.trimmed();
if (!trimmed.startsWith('"') && !trimmed.startsWith(R"(W/")"))
return QVariant();
@@ -1185,50 +1309,38 @@ static QVariant parseETag(const QByteArray &raw)
return QString::fromLatin1(trimmed);
}
-static QVariant parseIfMatch(const QByteArray &raw)
+template<typename T>
+static QStringList parseMatchImpl(QByteArrayView raw, T op)
{
- const QByteArray trimmedRaw = raw.trimmed();
+ const QByteArrayView trimmedRaw = raw.trimmed();
if (trimmedRaw == "*")
return QStringList(QStringLiteral("*"));
QStringList tags;
- const QList<QByteArray> split = trimmedRaw.split(',');
- for (const QByteArray &element : split) {
- const QByteArray trimmed = element.trimmed();
- if (!trimmed.startsWith('"'))
- continue;
-
- if (!trimmed.endsWith('"'))
- continue;
-
- tags += QString::fromLatin1(trimmed);
+ for (auto &element : QLatin1StringView(trimmedRaw).tokenize(','_L1)) {
+ if (const auto trimmed = element.trimmed(); op(trimmed))
+ tags += QString::fromLatin1(trimmed);
}
return tags;
}
-static QVariant parseIfNoneMatch(const QByteArray &raw)
-{
- const QByteArray trimmedRaw = raw.trimmed();
- if (trimmedRaw == "*")
- return QStringList(QStringLiteral("*"));
- QStringList tags;
- const QList<QByteArray> split = trimmedRaw.split(',');
- for (const QByteArray &element : split) {
- const QByteArray trimmed = element.trimmed();
- if (!trimmed.startsWith('"') && !trimmed.startsWith(R"(W/")"))
- continue;
-
- if (!trimmed.endsWith('"'))
- continue;
+static QStringList parseIfMatch(QByteArrayView raw)
+{
+ return parseMatchImpl(raw, [](QByteArrayView element) {
+ return element.startsWith('"') && element.endsWith('"');
+ });
+}
- tags += QString::fromLatin1(trimmed);
- }
- return tags;
+static QStringList parseIfNoneMatch(QByteArrayView raw)
+{
+ return parseMatchImpl(raw, [](QByteArrayView element) {
+ return (element.startsWith('"') || element.startsWith(R"(W/")")) && element.endsWith('"');
+ });
}
-static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QByteArray &value)
+static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, QByteArrayView value)
{
// header is always a valid value
switch (header) {
@@ -1241,7 +1353,7 @@ static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QBy
case QNetworkRequest::ContentLengthHeader: {
bool ok;
- qint64 result = value.trimmed().toLongLong(&ok);
+ qint64 result = QByteArrayView(value).trimmed().toLongLong(&ok);
if (ok)
return result;
return QVariant();
@@ -1268,43 +1380,127 @@ static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, const QBy
return parseIfNoneMatch(value);
case QNetworkRequest::CookieHeader:
- return parseCookieHeader(value);
+ return QVariant::fromValue(parseCookieHeader(value));
case QNetworkRequest::SetCookieHeader:
return QVariant::fromValue(QNetworkCookie::parseCookies(value));
default:
- Q_ASSERT(0);
+ Q_UNREACHABLE_RETURN({});
+ }
+}
+
+static QVariant parseHeaderValue(QNetworkRequest::KnownHeaders header, QList<QByteArray> values)
+{
+ if (values.empty())
+ return QVariant();
+
+ // header is always a valid value
+ switch (header) {
+ case QNetworkRequest::IfMatchHeader: {
+ QStringList res;
+ for (const auto &val : values)
+ res << parseIfMatch(val);
+ return res;
+ }
+ case QNetworkRequest::IfNoneMatchHeader: {
+ QStringList res;
+ for (const auto &val : values)
+ res << parseIfNoneMatch(val);
+ return res;
+ }
+ case QNetworkRequest::CookieHeader: {
+ auto listOpt = QNetworkHeadersPrivate::toCookieList(values);
+ return listOpt.has_value() ? QVariant::fromValue(listOpt.value()) : QVariant();
+ }
+ case QNetworkRequest::SetCookieHeader: {
+ QList<QNetworkCookie> res;
+ for (const auto &val : values)
+ res << QNetworkCookie::parseCookies(val);
+ return QVariant::fromValue(res);
+ }
+ default:
+ return parseHeaderValue(header, values.first());
}
return QVariant();
}
-QNetworkHeadersPrivate::RawHeadersList::ConstIterator
-QNetworkHeadersPrivate::findRawHeader(const QByteArray &key) const
+static bool isSetCookie(QByteArrayView name)
{
- RawHeadersList::ConstIterator it = rawHeaders.constBegin();
- RawHeadersList::ConstIterator end = rawHeaders.constEnd();
- for ( ; it != end; ++it)
- if (it->first.compare(key, Qt::CaseInsensitive) == 0)
- return it;
+ return name.compare(QHttpHeaders::wellKnownHeaderName(QHttpHeaders::WellKnownHeader::SetCookie),
+ Qt::CaseInsensitive) == 0;
+}
+
+static bool isSetCookie(QHttpHeaders::WellKnownHeader name)
+{
+ return name == QHttpHeaders::WellKnownHeader::SetCookie;
+}
- return end; // not found
+template<class HeaderName>
+static void setFromRawHeader(QHttpHeaders &headers, HeaderName header,
+ QByteArrayView value)
+{
+ headers.removeAll(header);
+
+ if (value.isNull())
+ // only wanted to erase key
+ return;
+
+ if (isSetCookie(header)) {
+ for (auto cookie : QLatin1StringView(value).tokenize('\n'_L1))
+ headers.append(QHttpHeaders::WellKnownHeader::SetCookie, cookie);
+ } else {
+ headers.append(header, value);
+ }
}
-QNetworkHeadersPrivate::RawHeadersList QNetworkHeadersPrivate::allRawHeaders() const
+const QNetworkHeadersPrivate::RawHeadersList &QNetworkHeadersPrivate::allRawHeaders() const
{
- return rawHeaders;
+ if (rawHeaderCache.isCached)
+ return rawHeaderCache.headersList;
+
+ rawHeaderCache.headersList = fromHttpToRaw(httpHeaders);
+ rawHeaderCache.isCached = true;
+ return rawHeaderCache.headersList;
}
QList<QByteArray> QNetworkHeadersPrivate::rawHeadersKeys() const
{
+ if (httpHeaders.isEmpty())
+ return {};
+
QList<QByteArray> result;
- result.reserve(rawHeaders.size());
- RawHeadersList::ConstIterator it = rawHeaders.constBegin(),
- end = rawHeaders.constEnd();
- for ( ; it != end; ++it)
- result << it->first;
+ result.reserve(httpHeaders.size());
+ QDuplicateTracker<QByteArray> seen(httpHeaders.size());
+
+ for (qsizetype i = 0; i < httpHeaders.size(); i++) {
+ const auto nameL1 = httpHeaders.nameAt(i);
+ const auto name = QByteArray(nameL1.data(), nameL1.size());
+ if (seen.hasSeen(name))
+ continue;
+
+ result << name;
+ }
+
+ return result;
+}
+QByteArray QNetworkHeadersPrivate::rawHeader(QAnyStringView headerName) const
+{
+ QByteArrayView setCookieStr = QHttpHeaders::wellKnownHeaderName(
+ QHttpHeaders::WellKnownHeader::SetCookie);
+ if (QAnyStringView::compare(headerName, setCookieStr, Qt::CaseInsensitive) != 0)
+ return httpHeaders.combinedValue(headerName);
+
+ QByteArray result;
+ const char* separator = "";
+ for (qsizetype i = 0; i < httpHeaders.size(); ++i) {
+ if (QAnyStringView::compare(httpHeaders.nameAt(i), headerName, Qt::CaseInsensitive) == 0) {
+ result.append(separator);
+ result.append(httpHeaders.valueAt(i));
+ separator = "\n";
+ }
+ }
return result;
}
@@ -1314,88 +1510,101 @@ void QNetworkHeadersPrivate::setRawHeader(const QByteArray &key, const QByteArra
// refuse to accept an empty raw header
return;
- setRawHeaderInternal(key, value);
+ setFromRawHeader(httpHeaders, key, value);
parseAndSetHeader(key, value);
-}
-
-/*!
- \internal
- Sets the internal raw headers list to match \a list. The cooked headers
- will also be updated.
- If \a list contains duplicates, they will be stored, but only the first one
- is usually accessed.
-*/
-void QNetworkHeadersPrivate::setAllRawHeaders(const RawHeadersList &list)
-{
- cookedHeaders.clear();
- rawHeaders = list;
-
- RawHeadersList::ConstIterator it = rawHeaders.constBegin();
- RawHeadersList::ConstIterator end = rawHeaders.constEnd();
- for ( ; it != end; ++it)
- parseAndSetHeader(it->first, it->second);
+ invalidateHeaderCache();
}
void QNetworkHeadersPrivate::setCookedHeader(QNetworkRequest::KnownHeaders header,
const QVariant &value)
{
- QByteArray name = headerName(header);
- if (name.isEmpty()) {
- // headerName verifies that \a header is a known value
+ const auto wellKnownOpt = toWellKnownHeader(header);
+ if (!wellKnownOpt) {
+ // verifies that \a header is a known value
qWarning("QNetworkRequest::setHeader: invalid header value KnownHeader(%d) received", header);
return;
}
if (value.isNull()) {
- setRawHeaderInternal(name, QByteArray());
+ httpHeaders.removeAll(wellKnownOpt.value());
cookedHeaders.remove(header);
} else {
QByteArray rawValue = headerValue(header, value);
if (rawValue.isEmpty()) {
qWarning("QNetworkRequest::setHeader: QVariant of type %s cannot be used with header %s",
- value.typeName(), name.constData());
+ value.typeName(),
+ QHttpHeaders::wellKnownHeaderName(wellKnownOpt.value()).constData());
return;
}
- setRawHeaderInternal(name, rawValue);
+ setFromRawHeader(httpHeaders, wellKnownOpt.value(), rawValue);
cookedHeaders.insert(header, value);
}
+
+ invalidateHeaderCache();
}
-void QNetworkHeadersPrivate::setRawHeaderInternal(const QByteArray &key, const QByteArray &value)
+QHttpHeaders QNetworkHeadersPrivate::headers() const
{
- auto firstEqualsKey = [&key](const RawHeaderPair &header) {
- return header.first.compare(key, Qt::CaseInsensitive) == 0;
- };
- rawHeaders.removeIf(firstEqualsKey);
+ return httpHeaders;
+}
- if (value.isNull())
- return; // only wanted to erase key
+void QNetworkHeadersPrivate::setHeaders(const QHttpHeaders &newHeaders)
+{
+ httpHeaders = newHeaders;
+ setCookedFromHttp(httpHeaders);
+ invalidateHeaderCache();
+}
+
+void QNetworkHeadersPrivate::setHeaders(QHttpHeaders &&newHeaders)
+{
+ httpHeaders = std::move(newHeaders);
+ setCookedFromHttp(httpHeaders);
+ invalidateHeaderCache();
+}
+
+void QNetworkHeadersPrivate::setHeader(QHttpHeaders::WellKnownHeader name, QByteArrayView value)
+{
+ httpHeaders.replaceOrAppend(name, value);
+
+ // set cooked header
+ const auto knownHeaderOpt = toKnownHeader(name);
+ if (knownHeaderOpt)
+ parseAndSetHeader(knownHeaderOpt.value(), value);
- RawHeaderPair pair;
- pair.first = key;
- pair.second = value;
- rawHeaders.append(pair);
+ invalidateHeaderCache();
}
-void QNetworkHeadersPrivate::parseAndSetHeader(const QByteArray &key, const QByteArray &value)
+void QNetworkHeadersPrivate::clearHeaders()
+{
+ httpHeaders.clear();
+ cookedHeaders.clear();
+ invalidateHeaderCache();
+}
+
+void QNetworkHeadersPrivate::parseAndSetHeader(QByteArrayView key, QByteArrayView value)
{
// is it a known header?
const int parsedKeyAsInt = parseHeaderName(key);
if (parsedKeyAsInt != -1) {
const QNetworkRequest::KnownHeaders parsedKey
= static_cast<QNetworkRequest::KnownHeaders>(parsedKeyAsInt);
- if (value.isNull()) {
- cookedHeaders.remove(parsedKey);
- } else if (parsedKey == QNetworkRequest::ContentLengthHeader
- && cookedHeaders.contains(QNetworkRequest::ContentLengthHeader)) {
- // Only set the cooked header "Content-Length" once.
- // See bug QTBUG-15311
- } else {
- cookedHeaders.insert(parsedKey, parseHeaderValue(parsedKey, value));
- }
+ parseAndSetHeader(parsedKey, value);
+ }
+}
+void QNetworkHeadersPrivate::parseAndSetHeader(QNetworkRequest::KnownHeaders key,
+ QByteArrayView value)
+{
+ if (value.isNull()) {
+ cookedHeaders.remove(key);
+ } else if (key == QNetworkRequest::ContentLengthHeader
+ && cookedHeaders.contains(QNetworkRequest::ContentLengthHeader)) {
+ // Only set the cooked header "Content-Length" once.
+ // See bug QTBUG-15311
+ } else {
+ cookedHeaders.insert(key, parseHeaderValue(key, value));
}
}
@@ -1449,7 +1658,7 @@ static int name_to_month(const char* month_str)
return 0;
}
-QDateTime QNetworkHeadersPrivate::fromHttpDate(const QByteArray &value)
+QDateTime QNetworkHeadersPrivate::fromHttpDate(QByteArrayView value)
{
// HTTP dates have three possible formats:
// RFC 1123/822 - ddd, dd MMM yyyy hh:mm:ss "GMT"
@@ -1489,7 +1698,7 @@ QDateTime QNetworkHeadersPrivate::fromHttpDate(const QByteArray &value)
#endif // datestring
if (dt.isValid())
- dt.setTimeSpec(Qt::UTC);
+ dt.setTimeZone(QTimeZone::UTC);
return dt;
}
@@ -1498,4 +1707,137 @@ QByteArray QNetworkHeadersPrivate::toHttpDate(const QDateTime &dt)
return QLocale::c().toString(dt.toUTC(), u"ddd, dd MMM yyyy hh:mm:ss 'GMT'").toLatin1();
}
+QNetworkHeadersPrivate::RawHeadersList QNetworkHeadersPrivate::fromHttpToRaw(
+ const QHttpHeaders &headers)
+{
+ if (headers.isEmpty())
+ return {};
+
+ QNetworkHeadersPrivate::RawHeadersList list;
+ QHash<QByteArray, qsizetype> nameToIndex;
+ list.reserve(headers.size());
+ nameToIndex.reserve(headers.size());
+
+ for (qsizetype i = 0; i < headers.size(); ++i) {
+ const auto nameL1 = headers.nameAt(i);
+ const auto value = headers.valueAt(i);
+
+ const bool isSetCookie = nameL1 == QHttpHeaders::wellKnownHeaderName(
+ QHttpHeaders::WellKnownHeader::SetCookie);
+
+ const auto name = QByteArray(nameL1.data(), nameL1.size());
+ if (auto it = nameToIndex.find(name); it != nameToIndex.end()) {
+ list[it.value()].second += isSetCookie ? "\n" : ", ";
+ list[it.value()].second += value;
+ } else {
+ nameToIndex[name] = list.size();
+ list.emplaceBack(name, value.toByteArray());
+ }
+ }
+
+ return list;
+}
+
+QHttpHeaders QNetworkHeadersPrivate::fromRawToHttp(const RawHeadersList &raw)
+{
+ if (raw.empty())
+ return {};
+
+ QHttpHeaders headers;
+ headers.reserve(raw.size());
+
+ for (const auto &[key, value] : raw) {
+ const bool isSetCookie = key.compare(QHttpHeaders::wellKnownHeaderName(
+ QHttpHeaders::WellKnownHeader::SetCookie),
+ Qt::CaseInsensitive) == 0;
+ if (isSetCookie) {
+ for (auto header : QLatin1StringView(value).tokenize('\n'_L1))
+ headers.append(key, header);
+ } else {
+ headers.append(key, value);
+ }
+ }
+
+ return headers;
+}
+
+std::optional<qint64> QNetworkHeadersPrivate::toInt(QByteArrayView value)
+{
+ if (value.empty())
+ return std::nullopt;
+
+ bool ok;
+ qint64 res = value.toLongLong(&ok);
+ if (ok)
+ return res;
+ return std::nullopt;
+}
+
+std::optional<QNetworkHeadersPrivate::NetworkCookieList> QNetworkHeadersPrivate::toSetCookieList(
+ const QList<QByteArray> &values)
+{
+ if (values.empty())
+ return std::nullopt;
+
+ QList<QNetworkCookie> cookies;
+ for (const auto &s : values)
+ cookies += QNetworkCookie::parseCookies(s);
+
+ if (cookies.empty())
+ return std::nullopt;
+ return cookies;
+}
+
+QByteArray QNetworkHeadersPrivate::fromCookieList(const QList<QNetworkCookie> &cookies)
+{
+ return makeCookieHeader(cookies, QNetworkCookie::NameAndValueOnly, "; ");
+}
+
+std::optional<QNetworkHeadersPrivate::NetworkCookieList> QNetworkHeadersPrivate::toCookieList(
+ const QList<QByteArray> &values)
+{
+ if (values.empty())
+ return std::nullopt;
+
+ QList<QNetworkCookie> cookies;
+ for (const auto &s : values)
+ cookies += parseCookieHeader(s);
+
+ if (cookies.empty())
+ return std::nullopt;
+ return cookies;
+}
+
+void QNetworkHeadersPrivate::invalidateHeaderCache()
+{
+ rawHeaderCache.headersList.clear();
+ rawHeaderCache.isCached = false;
+}
+
+void QNetworkHeadersPrivate::setCookedFromHttp(const QHttpHeaders &newHeaders)
+{
+ cookedHeaders.clear();
+
+ QMap<QNetworkRequest::KnownHeaders, QList<QByteArray>> multipleHeadersMap;
+ for (int i = 0; i < newHeaders.size(); ++i) {
+ const auto name = newHeaders.nameAt(i);
+ const auto value = newHeaders.valueAt(i);
+
+ const int parsedKeyAsInt = parseHeaderName(name);
+ if (parsedKeyAsInt == -1)
+ continue;
+
+ const QNetworkRequest::KnownHeaders parsedKey
+ = static_cast<QNetworkRequest::KnownHeaders>(parsedKeyAsInt);
+
+ auto &list = multipleHeadersMap[parsedKey];
+ list.append(value.toByteArray());
+ }
+
+ for (auto i = multipleHeadersMap.cbegin(), end = multipleHeadersMap.cend(); i != end; ++i)
+ cookedHeaders.insert(i.key(), parseHeaderValue(i.key(), i.value()));
+}
+
QT_END_NAMESPACE
+
+#include "moc_qnetworkrequest.cpp"
diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h
index 8dd16e16d7..368eb99d95 100644
--- a/src/network/access/qnetworkrequest.h
+++ b/src/network/access/qnetworkrequest.h
@@ -5,6 +5,7 @@
#define QNETWORKREQUEST_H
#include <QtNetwork/qtnetworkglobal.h>
+#include <QtNetwork/qhttpheaders.h>
#include <QtCore/QSharedDataPointer>
#include <QtCore/QString>
#include <QtCore/QUrl>
@@ -14,10 +15,12 @@ QT_BEGIN_NAMESPACE
class QSslConfiguration;
class QHttp2Configuration;
+class QHttp1Configuration;
class QNetworkRequestPrivate;
class Q_NETWORK_EXPORT QNetworkRequest
{
+ Q_GADGET
public:
enum KnownHeaders {
ContentTypeHeader,
@@ -32,8 +35,11 @@ public:
IfModifiedSinceHeader,
ETagHeader,
IfMatchHeader,
- IfNoneMatchHeader
+ IfNoneMatchHeader,
+ NumKnownHeaders
};
+ Q_ENUM(KnownHeaders)
+
enum Attribute {
HttpStatusCodeAttribute,
HttpReasonPhraseAttribute,
@@ -64,6 +70,7 @@ public:
ConnectionCacheExpiryTimeoutSecondsAttribute,
Http2CleartextAllowedAttribute,
UseCredentialsAttribute,
+ FullLocalServerNameAttribute,
User = 1000,
UserMax = 32767
@@ -96,6 +103,9 @@ public:
DefaultTransferTimeoutConstant = 30000
};
+ static constexpr auto DefaultTransferTimeout =
+ std::chrono::milliseconds(DefaultTransferTimeoutConstant);
+
QNetworkRequest();
explicit QNetworkRequest(const QUrl &url);
QNetworkRequest(const QNetworkRequest &other);
@@ -112,14 +122,24 @@ public:
QUrl url() const;
void setUrl(const QUrl &url);
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
+
// "cooked" headers
QVariant header(KnownHeaders header) const;
void setHeader(KnownHeaders header, const QVariant &value);
// raw headers:
+#if QT_NETWORK_REMOVED_SINCE(6, 7)
bool hasRawHeader(const QByteArray &headerName) const;
+#endif
+ bool hasRawHeader(QAnyStringView headerName) const;
QList<QByteArray> rawHeaderList() const;
+#if QT_NETWORK_REMOVED_SINCE(6, 7)
QByteArray rawHeader(const QByteArray &headerName) const;
+#endif
+ QByteArray rawHeader(QAnyStringView headerName) const;
void setRawHeader(const QByteArray &headerName, const QByteArray &value);
// attributes
@@ -143,18 +163,26 @@ public:
QString peerVerifyName() const;
void setPeerVerifyName(const QString &peerName);
-#if QT_CONFIG(http) || defined(Q_QDOC)
+#if QT_CONFIG(http)
+ QHttp1Configuration http1Configuration() const;
+ void setHttp1Configuration(const QHttp1Configuration &configuration);
+
QHttp2Configuration http2Configuration() const;
void setHttp2Configuration(const QHttp2Configuration &configuration);
qint64 decompressedSafetyCheckThreshold() const;
void setDecompressedSafetyCheckThreshold(qint64 threshold);
-#endif // QT_CONFIG(http) || defined(Q_QDOC)
+#endif // QT_CONFIG(http)
-#if QT_CONFIG(http) || defined(Q_QDOC) || defined (Q_OS_WASM)
+#if QT_CONFIG(http) || defined (Q_OS_WASM)
+ QT_NETWORK_INLINE_SINCE(6, 8)
int transferTimeout() const;
- void setTransferTimeout(int timeout = DefaultTransferTimeoutConstant);
-#endif // QT_CONFIG(http) || defined(Q_QDOC) || defined (Q_OS_WASM)
+ QT_NETWORK_INLINE_SINCE(6, 8)
+ void setTransferTimeout(int timeout);
+
+ std::chrono::milliseconds transferTimeoutAsDuration() const;
+ void setTransferTimeout(std::chrono::milliseconds duration = DefaultTransferTimeout);
+#endif // QT_CONFIG(http) || defined (Q_OS_WASM)
private:
QSharedDataPointer<QNetworkRequestPrivate> d;
friend class QNetworkRequestPrivate;
@@ -162,6 +190,20 @@ private:
Q_DECLARE_SHARED(QNetworkRequest)
+#if QT_NETWORK_INLINE_IMPL_SINCE(6, 8)
+#if QT_CONFIG(http) || defined (Q_OS_WASM)
+int QNetworkRequest::transferTimeout() const
+{
+ return int(transferTimeoutAsDuration().count());
+}
+
+void QNetworkRequest::setTransferTimeout(int timeout)
+{
+ setTransferTimeout(std::chrono::milliseconds(timeout));
+}
+#endif // QT_CONFIG(http) || defined (Q_OS_WASM)
+#endif // INLINE_SINCE 6.8
+
QT_END_NAMESPACE
QT_DECL_METATYPE_EXTERN(QNetworkRequest, Q_NETWORK_EXPORT)
diff --git a/src/network/access/qnetworkrequest_p.h b/src/network/access/qnetworkrequest_p.h
index 57fb7b35be..7268e1a4aa 100644
--- a/src/network/access/qnetworkrequest_p.h
+++ b/src/network/access/qnetworkrequest_p.h
@@ -16,6 +16,7 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
+#include <QtNetwork/qhttpheaders.h>
#include "qnetworkrequest.h"
#include "QtCore/qbytearray.h"
#include "QtCore/qlist.h"
@@ -26,6 +27,8 @@
QT_BEGIN_NAMESPACE
+class QNetworkCookie;
+
// this is the common part between QNetworkRequestPrivate, QNetworkReplyPrivate and QHttpPartPrivate
class QNetworkHeadersPrivate
{
@@ -35,24 +38,49 @@ public:
typedef QHash<QNetworkRequest::KnownHeaders, QVariant> CookedHeadersMap;
typedef QHash<QNetworkRequest::Attribute, QVariant> AttributesMap;
- RawHeadersList rawHeaders;
+ mutable struct {
+ RawHeadersList headersList;
+ bool isCached = false;
+ } rawHeaderCache;
+
+ QHttpHeaders httpHeaders;
CookedHeadersMap cookedHeaders;
AttributesMap attributes;
QPointer<QObject> originatingObject;
- RawHeadersList::ConstIterator findRawHeader(const QByteArray &key) const;
- RawHeadersList allRawHeaders() const;
+ const RawHeadersList &allRawHeaders() const;
QList<QByteArray> rawHeadersKeys() const;
+ QByteArray rawHeader(QAnyStringView headerName) const;
void setRawHeader(const QByteArray &key, const QByteArray &value);
- void setAllRawHeaders(const RawHeadersList &list);
void setCookedHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
- static QDateTime fromHttpDate(const QByteArray &value);
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
+ void setHeader(QHttpHeaders::WellKnownHeader name, QByteArrayView value);
+
+ void clearHeaders();
+
+ static QDateTime fromHttpDate(QByteArrayView value);
static QByteArray toHttpDate(const QDateTime &dt);
+ static std::optional<qint64> toInt(QByteArrayView value);
+
+ typedef QList<QNetworkCookie> NetworkCookieList;
+ static QByteArray fromCookieList(const NetworkCookieList &cookies);
+ static std::optional<NetworkCookieList> toSetCookieList(const QList<QByteArray> &values);
+ static std::optional<NetworkCookieList> toCookieList(const QList<QByteArray> &values);
+
+ static RawHeadersList fromHttpToRaw(const QHttpHeaders &headers);
+ static QHttpHeaders fromRawToHttp(const RawHeadersList &raw);
+
private:
- void setRawHeaderInternal(const QByteArray &key, const QByteArray &value);
- void parseAndSetHeader(const QByteArray &key, const QByteArray &value);
+ void invalidateHeaderCache();
+
+ void setCookedFromHttp(const QHttpHeaders &newHeaders);
+ void parseAndSetHeader(QByteArrayView key, QByteArrayView value);
+ void parseAndSetHeader(QNetworkRequest::KnownHeaders key, QByteArrayView value);
+
};
Q_DECLARE_TYPEINFO(QNetworkHeadersPrivate::RawHeaderPair, Q_RELOCATABLE_TYPE);
diff --git a/src/network/access/qnetworkrequestfactory.cpp b/src/network/access/qnetworkrequestfactory.cpp
new file mode 100644
index 0000000000..d9c536cef2
--- /dev/null
+++ b/src/network/access/qnetworkrequestfactory.cpp
@@ -0,0 +1,721 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qnetworkrequestfactory.h"
+#include "qnetworkrequestfactory_p.h"
+
+#if QT_CONFIG(ssl)
+#include <QtNetwork/qsslconfiguration.h>
+#endif
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qmap.h>
+
+QT_BEGIN_NAMESPACE
+
+QT_DEFINE_QESDP_SPECIALIZATION_DTOR(QNetworkRequestFactoryPrivate)
+
+using namespace Qt::StringLiterals;
+
+Q_LOGGING_CATEGORY(lcQrequestfactory, "qt.network.access.request.factory")
+
+/*!
+ \class QNetworkRequestFactory
+ \since 6.7
+ \ingroup shared
+ \inmodule QtNetwork
+
+ \brief Convenience class for grouping remote server endpoints that share
+ common network request properties.
+
+ \preliminary
+
+ REST servers often have endpoints that require the same headers and other data.
+ Grouping such endpoints with a QNetworkRequestFactory makes it more
+ convenient to issue requests to these endpoints; only the typically
+ varying parts such as \e path and \e query parameters are provided
+ when creating a new request.
+
+ Basic usage steps of QNetworkRequestFactory are as follows:
+ \list
+ \li Instantiation
+ \li Setting the data common to all requests
+ \li Issuing requests
+ \endlist
+
+ An example of usage:
+
+ \snippet code/src_network_access_qnetworkrequestfactory.cpp 0
+*/
+
+/*!
+ Creates a new QNetworkRequestFactory object.
+ Use setBaseUrl() to set a valid base URL for the requests.
+
+ \sa QNetworkRequestFactory(const QUrl &baseUrl), setBaseUrl()
+*/
+
+QNetworkRequestFactory::QNetworkRequestFactory()
+ : d(new QNetworkRequestFactoryPrivate)
+{
+}
+
+/*!
+ Creates a new QNetworkRequestFactory object, initializing the base URL to
+ \a baseUrl. The base URL is used to populate subsequent network
+ requests.
+
+ If the URL contains a \e path component, it will be extracted and used
+ as a base path in subsequent network requests. This means that any
+ paths provided when requesting individual requests will be appended
+ to this base path, as illustrated below:
+
+ \snippet code/src_network_access_qnetworkrequestfactory.cpp 1
+ */
+QNetworkRequestFactory::QNetworkRequestFactory(const QUrl &baseUrl)
+ : d(new QNetworkRequestFactoryPrivate(baseUrl))
+{
+}
+
+/*!
+ Destroys this QNetworkRequestFactory object.
+ */
+QNetworkRequestFactory::~QNetworkRequestFactory()
+ = default;
+
+/*!
+ Creates a copy of \a other.
+ */
+QNetworkRequestFactory::QNetworkRequestFactory(const QNetworkRequestFactory &other)
+ = default;
+
+/*!
+ Creates a copy of \a other and returns a reference to this factory.
+ */
+QNetworkRequestFactory &QNetworkRequestFactory::operator=(const QNetworkRequestFactory &other)
+ = default;
+
+/*!
+ \fn QNetworkRequestFactory::QNetworkRequestFactory(QNetworkRequestFactory &&other) noexcept
+
+ Move-constructs the factory from \a other.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+*/
+
+/*!
+ \fn QNetworkRequestFactory &QNetworkRequestFactory::operator=(QNetworkRequestFactory &&other) noexcept
+
+ Move-assigns \a other and returns a reference to this factory.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+ */
+
+/*!
+ \fn void QNetworkRequestFactory::swap(QNetworkRequestFactory &other)
+
+ Swaps this factory with \a other. This operation is
+ very fast and never fails.
+ */
+
+/*!
+ Returns the base URL used for the individual requests.
+
+ The base URL may contain a path component. This path is used
+ as path "prefix" for the paths that are provided when generating
+ individual requests.
+
+ \sa setBaseUrl()
+ */
+QUrl QNetworkRequestFactory::baseUrl() const
+{
+ return d->baseUrl;
+}
+
+/*!
+ Sets the base URL used in individual requests to \a url.
+
+ \sa baseUrl()
+ */
+void QNetworkRequestFactory::setBaseUrl(const QUrl &url)
+{
+ if (d->baseUrl == url)
+ return;
+
+ d.detach();
+ d->baseUrl = url;
+}
+
+#if QT_CONFIG(ssl)
+/*!
+ Returns the SSL configuration set to this factory. The SSL configuration
+ is set to each individual request.
+
+ \sa setSslConfiguration()
+ */
+QSslConfiguration QNetworkRequestFactory::sslConfiguration() const
+{
+ return d->sslConfig;
+}
+
+/*!
+ Sets the SSL configuration to \a configuration.
+
+ \sa sslConfiguration()
+ */
+void QNetworkRequestFactory::setSslConfiguration(const QSslConfiguration &configuration)
+{
+ if (d->sslConfig == configuration)
+ return;
+
+ d.detach();
+ d->sslConfig = configuration;
+}
+#endif
+
+/*!
+ Returns a QNetworkRequest.
+
+ The returned request is filled with the data that this factory
+ has been configured with.
+
+ \sa createRequest(const QUrlQuery&), createRequest(const QString&, const QUrlQuery&)
+*/
+
+QNetworkRequest QNetworkRequestFactory::createRequest() const
+{
+ return d->newRequest(d->requestUrl());
+}
+
+/*!
+ Returns a QNetworkRequest.
+
+ The returned request's URL is formed by appending the provided \a path
+ to the baseUrl (which may itself have a path component).
+
+ \sa createRequest(const QString &, const QUrlQuery &), createRequest(), baseUrl()
+*/
+QNetworkRequest QNetworkRequestFactory::createRequest(const QString &path) const
+{
+ return d->newRequest(d->requestUrl(&path));
+}
+
+/*!
+ Returns a QNetworkRequest.
+
+ The returned request's URL is formed by appending the provided \a query
+ to the baseUrl.
+
+ \sa createRequest(const QString &, const QUrlQuery &), createRequest(), baseUrl()
+*/
+QNetworkRequest QNetworkRequestFactory::createRequest(const QUrlQuery &query) const
+{
+ return d->newRequest(d->requestUrl(nullptr, &query));
+}
+
+/*!
+ Returns a QNetworkRequest.
+
+ The returned requests URL is formed by appending the provided \a path
+ and \a query to the baseUrl (which may have a path component).
+
+ If the provided \a path contains query items, they will be combined
+ with the items in \a query.
+
+ \sa createRequest(const QUrlQuery&), createRequest(), baseUrl()
+ */
+QNetworkRequest QNetworkRequestFactory::createRequest(const QString &path, const QUrlQuery &query) const
+{
+ return d->newRequest(d->requestUrl(&path, &query));
+}
+
+/*!
+ Sets \a headers that are common to all requests.
+
+ These headers are added to individual requests' headers.
+ This is a convenience mechanism for setting headers that
+ repeat across requests.
+
+ \sa commonHeaders(), clearCommonHeaders(), createRequest()
+ */
+void QNetworkRequestFactory::setCommonHeaders(const QHttpHeaders &headers)
+{
+ d.detach();
+ d->headers = headers;
+}
+
+/*!
+ Returns the currently set headers.
+
+ \sa setCommonHeaders(), clearCommonHeaders()
+ */
+QHttpHeaders QNetworkRequestFactory::commonHeaders() const
+{
+ return d->headers;
+}
+
+/*!
+ Clears current headers.
+
+ \sa commonHeaders(), setCommonHeaders()
+*/
+void QNetworkRequestFactory::clearCommonHeaders()
+{
+ if (d->headers.isEmpty())
+ return;
+ d.detach();
+ d->headers.clear();
+}
+
+/*!
+ Returns the bearer token that has been set.
+
+ The bearer token, if present, is used to set the
+ \c {Authorization: Bearer my_token} header for requests. This is a common
+ authorization convention and is provided as an additional convenience.
+
+ The means to acquire the bearer token vary. Standard methods include \c OAuth2
+ and the service provider's website/dashboard. It is expected that the bearer
+ token changes over time. For example, when updated with a refresh token,
+ always setting the new token again ensures that subsequent requests have
+ the latest, valid token.
+
+ The presence of the bearer token does not impact the \l commonHeaders()
+ listing. If the \l commonHeaders() also lists \c Authorization header, it
+ will be overwritten.
+
+ \sa setBearerToken(), commonHeaders()
+ */
+QByteArray QNetworkRequestFactory::bearerToken() const
+{
+ return d->bearerToken;
+}
+
+/*!
+ Sets the bearer token to \a token.
+
+ \sa bearerToken(), clearBearerToken()
+*/
+void QNetworkRequestFactory::setBearerToken(const QByteArray &token)
+{
+ if (d->bearerToken == token)
+ return;
+
+ d.detach();
+ d->bearerToken = token;
+}
+
+/*!
+ Clears the bearer token.
+
+ \sa bearerToken()
+*/
+void QNetworkRequestFactory::clearBearerToken()
+{
+ if (d->bearerToken.isEmpty())
+ return;
+
+ d.detach();
+ d->bearerToken.clear();
+}
+
+/*!
+ Returns the username set to this factory.
+
+ \sa setUserName(), clearUserName(), password()
+*/
+QString QNetworkRequestFactory::userName() const
+{
+ return d->userName;
+}
+
+/*!
+ Sets the username of this factory to \a userName.
+
+ The username is set in the request URL when \l createRequest() is called.
+ The QRestAccessManager / QNetworkAccessManager will attempt to use
+ these credentials when the server indicates that authentication
+ is required.
+
+ \sa userName(), clearUserName(), password()
+*/
+void QNetworkRequestFactory::setUserName(const QString &userName)
+{
+ if (d->userName == userName)
+ return;
+ d.detach();
+ d->userName = userName;
+}
+
+/*!
+ Clears the username set to this factory.
+*/
+void QNetworkRequestFactory::clearUserName()
+{
+ if (d->userName.isEmpty())
+ return;
+ d.detach();
+ d->userName.clear();
+}
+
+/*!
+ Returns the password set to this factory.
+
+ \sa password(), clearPassword(), userName()
+*/
+QString QNetworkRequestFactory::password() const
+{
+ return d->password;
+}
+
+/*!
+ Sets the password of this factory to \a password.
+
+ The password is set in the request URL when \l createRequest() is called.
+ The QRestAccessManager / QNetworkAccessManager will attempt to use
+ these credentials when the server indicates that authentication
+ is required.
+
+ \sa password(), clearPassword(), userName()
+*/
+void QNetworkRequestFactory::setPassword(const QString &password)
+{
+ if (d->password == password)
+ return;
+ d.detach();
+ d->password = password;
+}
+
+/*!
+ Clears the password set to this factory.
+
+ \sa password(), setPassword(), userName()
+*/
+void QNetworkRequestFactory::clearPassword()
+{
+ if (d->password.isEmpty())
+ return;
+ d.detach();
+ d->password.clear();
+}
+
+/*!
+ Sets \a timeout used for transfers.
+
+ \sa transferTimeout(), QNetworkRequest::setTransferTimeout(),
+ QNetworkAccessManager::setTransferTimeout()
+*/
+void QNetworkRequestFactory::setTransferTimeout(std::chrono::milliseconds timeout)
+{
+ if (d->transferTimeout == timeout)
+ return;
+
+ d.detach();
+ d->transferTimeout = timeout;
+}
+
+/*!
+ Returns the timeout used for transfers.
+
+ \sa setTransferTimeout(), QNetworkRequest::transferTimeout(),
+ QNetworkAccessManager::transferTimeout()
+*/
+std::chrono::milliseconds QNetworkRequestFactory::transferTimeout() const
+{
+ return d->transferTimeout;
+}
+
+/*!
+ Returns query parameters that are added to individual requests' query
+ parameters. The query parameters are added to any potential query
+ parameters provided with the individual \l createRequest() calls.
+
+ Use cases for using repeating query parameters are server dependent,
+ but typical examples include language setting \c {?lang=en}, format
+ specification \c {?format=json}, API version specification
+ \c {?version=1.0} and API key authentication.
+
+ \sa setQueryParameters(), clearQueryParameters(), createRequest()
+*/
+QUrlQuery QNetworkRequestFactory::queryParameters() const
+{
+ return d->queryParameters;
+}
+
+/*!
+ Sets \a query parameters that are added to individual requests' query
+ parameters.
+
+ \sa queryParameters(), clearQueryParameters()
+ */
+void QNetworkRequestFactory::setQueryParameters(const QUrlQuery &query)
+{
+ if (d->queryParameters == query)
+ return;
+
+ d.detach();
+ d->queryParameters = query;
+}
+
+/*!
+ Clears the query parameters.
+
+ \sa queryParameters()
+*/
+void QNetworkRequestFactory::clearQueryParameters()
+{
+ if (d->queryParameters.isEmpty())
+ return;
+
+ d.detach();
+ d->queryParameters.clear();
+}
+
+/*!
+ \since 6.8
+
+ Sets the priority for any future requests created by this factory to
+ \a priority.
+
+ The default priority is \l QNetworkRequest::NormalPriority.
+
+ \sa priority(), QNetworkRequest::setPriority()
+*/
+void QNetworkRequestFactory::setPriority(QNetworkRequest::Priority priority)
+{
+ if (d->priority == priority)
+ return;
+ d.detach();
+ d->priority = priority;
+}
+
+/*!
+ \since 6.8
+
+ Returns the priority assigned to any future requests created by this
+ factory.
+
+ \sa setPriority(), QNetworkRequest::priority()
+*/
+QNetworkRequest::Priority QNetworkRequestFactory::priority() const
+{
+ return d->priority;
+}
+
+/*!
+ \since 6.8
+
+ Sets the value associated with \a attribute to \a value.
+ If the attribute is already set, the previous value is
+ replaced. The attributes are set to any future requests
+ created by this factory.
+
+ \sa attribute(), clearAttribute(), clearAttributes(),
+ QNetworkRequest::Attribute
+*/
+void QNetworkRequestFactory::setAttribute(QNetworkRequest::Attribute attribute,
+ const QVariant &value)
+{
+ if (attribute == QNetworkRequest::HttpStatusCodeAttribute
+ || attribute == QNetworkRequest::HttpReasonPhraseAttribute
+ || attribute == QNetworkRequest::RedirectionTargetAttribute
+ || attribute == QNetworkRequest::ConnectionEncryptedAttribute
+ || attribute == QNetworkRequest::SourceIsFromCacheAttribute
+ || attribute == QNetworkRequest::HttpPipeliningWasUsedAttribute
+ || attribute == QNetworkRequest::Http2WasUsedAttribute
+ || attribute == QNetworkRequest::OriginalContentLengthAttribute)
+ {
+ qCWarning(lcQrequestfactory, "%i is a reply-only attribute, ignoring.", attribute);
+ return;
+ }
+ d.detach();
+ d->attributes.insert(attribute, value);
+}
+
+/*!
+ \since 6.8
+
+ Returns the value associated with \a attribute. If the
+ attribute has not been set, returns a default-constructed \l QVariant.
+
+ \sa attribute(QNetworkRequest::Attribute, const QVariant &),
+ setAttribute(), clearAttributes(), QNetworkRequest::Attribute
+
+*/
+QVariant QNetworkRequestFactory::attribute(QNetworkRequest::Attribute attribute) const
+{
+ return d->attributes.value(attribute);
+}
+
+/*!
+ \since 6.8
+
+ Returns the value associated with \a attribute. If the
+ attribute has not been set, returns \a defaultValue.
+
+ \sa attribute(), setAttribute(), clearAttributes(),
+ QNetworkRequest::Attribute
+*/
+QVariant QNetworkRequestFactory::attribute(QNetworkRequest::Attribute attribute,
+ const QVariant &defaultValue) const
+{
+ return d->attributes.value(attribute, defaultValue);
+}
+
+/*!
+ \since 6.8
+
+ Clears \a attribute set to this factory.
+
+ \sa attribute(), setAttribute()
+*/
+void QNetworkRequestFactory::clearAttribute(QNetworkRequest::Attribute attribute)
+{
+ if (!d->attributes.contains(attribute))
+ return;
+ d.detach();
+ d->attributes.remove(attribute);
+}
+
+/*!
+ \since 6.8
+
+ Clears any attributes set to this factory.
+
+ \sa attribute(), setAttribute()
+*/
+void QNetworkRequestFactory::clearAttributes()
+{
+ if (d->attributes.isEmpty())
+ return;
+ d.detach();
+ d->attributes.clear();
+}
+
+QNetworkRequestFactoryPrivate::QNetworkRequestFactoryPrivate()
+ = default;
+
+QNetworkRequestFactoryPrivate::QNetworkRequestFactoryPrivate(const QUrl &baseUrl)
+ : baseUrl(baseUrl)
+{
+}
+
+QNetworkRequestFactoryPrivate::~QNetworkRequestFactoryPrivate()
+ = default;
+
+QNetworkRequest QNetworkRequestFactoryPrivate::newRequest(const QUrl &url) const
+{
+ QNetworkRequest request;
+ request.setUrl(url);
+#if QT_CONFIG(ssl)
+ if (!sslConfig.isNull())
+ request.setSslConfiguration(sslConfig);
+#endif
+ auto h = headers;
+ constexpr char Bearer[] = "Bearer ";
+ if (!bearerToken.isEmpty())
+ h.replaceOrAppend(QHttpHeaders::WellKnownHeader::Authorization, Bearer + bearerToken);
+ request.setHeaders(std::move(h));
+
+ request.setTransferTimeout(transferTimeout);
+ request.setPriority(priority);
+
+ for (const auto &[attribute, value] : attributes.asKeyValueRange())
+ request.setAttribute(attribute, value);
+
+ return request;
+}
+
+QUrl QNetworkRequestFactoryPrivate::requestUrl(const QString *path,
+ const QUrlQuery *query) const
+{
+ const QUrl providedPath = path ? QUrl(*path) : QUrl{};
+ const QUrlQuery providedQuery = query ? *query : QUrlQuery();
+
+ if (!providedPath.scheme().isEmpty() || !providedPath.host().isEmpty()) {
+ qCWarning(lcQrequestfactory, "The provided path %ls may only contain path and query item "
+ "components, and other parts will be ignored. Set the baseUrl instead",
+ qUtf16Printable(providedPath.toDisplayString()));
+ }
+
+ QUrl resultUrl = baseUrl;
+ QUrlQuery resultQuery(providedQuery);
+ QString basePath = baseUrl.path();
+
+ resultUrl.setUserName(userName);
+ resultUrl.setPassword(password);
+
+ // Separate the path and query parameters components on the application-provided path
+ const QString requestPath{providedPath.path()};
+ const QUrlQuery pathQueryItems{providedPath};
+
+ if (!pathQueryItems.isEmpty()) {
+ // Add any query items provided as part of the path
+ const auto items = pathQueryItems.queryItems(QUrl::ComponentFormattingOption::FullyEncoded);
+ for (const auto &[key, value]: items)
+ resultQuery.addQueryItem(key, value);
+ }
+
+ if (!queryParameters.isEmpty()) {
+ // Add any query items set to this factory
+ const QList<std::pair<QString,QString>> items =
+ queryParameters.queryItems(QUrl::ComponentFormattingOption::FullyEncoded);
+ for (const auto &item: items)
+ resultQuery.addQueryItem(item.first, item.second);
+ }
+
+ if (!resultQuery.isEmpty())
+ resultUrl.setQuery(resultQuery);
+
+ if (requestPath.isEmpty())
+ return resultUrl;
+
+ // Ensure that the "base path" (the path that may be present
+ // in the baseUrl), and the request path are joined with one '/'
+ // If both have it, remove one, if neither has it, add one
+ if (basePath.endsWith(u'/') && requestPath.startsWith(u'/'))
+ basePath.chop(1);
+ else if (!requestPath.startsWith(u'/') && !basePath.endsWith(u'/'))
+ basePath.append(u'/');
+
+ resultUrl.setPath(basePath.append(requestPath));
+ return resultUrl;
+}
+
+#ifndef QT_NO_DEBUG_STREAM
+/*!
+ \fn QDebug QNetworkRequestFactory::operator<<(QDebug debug,
+ const QNetworkRequestFactory &factory)
+
+ Writes \a factory into \a debug stream.
+
+ \sa {Debugging Techniques}
+*/
+QDebug operator<<(QDebug debug, const QNetworkRequestFactory &factory)
+{
+ const QDebugStateSaver saver(debug);
+ debug.resetFormat().nospace();
+
+ debug << "QNetworkRequestFactory(baseUrl = " << factory.baseUrl()
+ << ", headers = " << factory.commonHeaders()
+ << ", queryParameters = " << factory.queryParameters().queryItems()
+ << ", bearerToken = " << (factory.bearerToken().isEmpty() ? "(empty)" : "(is set)")
+ << ", transferTimeout = " << factory.transferTimeout()
+ << ", userName = " << (factory.userName().isEmpty() ? "(empty)" : "(is set)")
+ << ", password = " << (factory.password().isEmpty() ? "(empty)" : "(is set)")
+#if QT_CONFIG(ssl)
+ << ", SSL configuration"
+ << (factory.sslConfiguration().isNull() ? " is not set (default)" : " is set")
+#else
+ << ", no SSL support"
+#endif
+ << ")";
+ return debug;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qnetworkrequestfactory.h b/src/network/access/qnetworkrequestfactory.h
new file mode 100644
index 0000000000..9d955a51e7
--- /dev/null
+++ b/src/network/access/qnetworkrequestfactory.h
@@ -0,0 +1,100 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QNETWORKREQUESTFACTORY_H
+#define QNETWORKREQUESTFACTORY_H
+
+#include <QtNetwork/qnetworkrequest.h>
+#include <QtNetwork/qhttpheaders.h>
+
+#include <QtCore/qcompare.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qurlquery.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qvariant.h>
+
+#include <chrono>
+
+QT_BEGIN_NAMESPACE
+
+class QDebug;
+#if QT_CONFIG(ssl)
+class QSslConfiguration;
+#endif
+
+class QNetworkRequestFactoryPrivate;
+QT_DECLARE_QESDP_SPECIALIZATION_DTOR_WITH_EXPORT(QNetworkRequestFactoryPrivate, Q_NETWORK_EXPORT)
+
+class QT_TECH_PREVIEW_API QNetworkRequestFactory
+{
+public:
+ Q_NETWORK_EXPORT QNetworkRequestFactory();
+ Q_NETWORK_EXPORT explicit QNetworkRequestFactory(const QUrl &baseUrl);
+ Q_NETWORK_EXPORT ~QNetworkRequestFactory();
+
+ Q_NETWORK_EXPORT QNetworkRequestFactory(const QNetworkRequestFactory &other);
+ QNetworkRequestFactory(QNetworkRequestFactory &&other) noexcept = default;
+ Q_NETWORK_EXPORT QNetworkRequestFactory &operator=(const QNetworkRequestFactory &other);
+
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QNetworkRequestFactory)
+ void swap(QNetworkRequestFactory &other) noexcept { d.swap(other.d); }
+
+ Q_NETWORK_EXPORT QUrl baseUrl() const;
+ Q_NETWORK_EXPORT void setBaseUrl(const QUrl &url);
+
+#if QT_CONFIG(ssl)
+ Q_NETWORK_EXPORT QSslConfiguration sslConfiguration() const;
+ Q_NETWORK_EXPORT void setSslConfiguration(const QSslConfiguration &configuration);
+#endif
+
+ Q_NETWORK_EXPORT QNetworkRequest createRequest() const;
+ Q_NETWORK_EXPORT QNetworkRequest createRequest(const QUrlQuery &query) const;
+ Q_NETWORK_EXPORT QNetworkRequest createRequest(const QString &path) const;
+ Q_NETWORK_EXPORT QNetworkRequest createRequest(const QString &path, const QUrlQuery &query) const;
+
+ Q_NETWORK_EXPORT void setCommonHeaders(const QHttpHeaders &headers);
+ Q_NETWORK_EXPORT QHttpHeaders commonHeaders() const;
+ Q_NETWORK_EXPORT void clearCommonHeaders();
+
+ Q_NETWORK_EXPORT QByteArray bearerToken() const;
+ Q_NETWORK_EXPORT void setBearerToken(const QByteArray &token);
+ Q_NETWORK_EXPORT void clearBearerToken();
+
+ Q_NETWORK_EXPORT QString userName() const;
+ Q_NETWORK_EXPORT void setUserName(const QString &userName);
+ Q_NETWORK_EXPORT void clearUserName();
+
+ Q_NETWORK_EXPORT QString password() const;
+ Q_NETWORK_EXPORT void setPassword(const QString &password);
+ Q_NETWORK_EXPORT void clearPassword();
+
+ Q_NETWORK_EXPORT void setTransferTimeout(std::chrono::milliseconds timeout);
+ Q_NETWORK_EXPORT std::chrono::milliseconds transferTimeout() const;
+
+ Q_NETWORK_EXPORT QUrlQuery queryParameters() const;
+ Q_NETWORK_EXPORT void setQueryParameters(const QUrlQuery &query);
+ Q_NETWORK_EXPORT void clearQueryParameters();
+
+ Q_NETWORK_EXPORT void setPriority(QNetworkRequest::Priority priority);
+ Q_NETWORK_EXPORT QNetworkRequest::Priority priority() const;
+
+ Q_NETWORK_EXPORT QVariant attribute(QNetworkRequest::Attribute attribute) const;
+ Q_NETWORK_EXPORT QVariant attribute(QNetworkRequest::Attribute attribute,
+ const QVariant &defaultValue) const;
+ Q_NETWORK_EXPORT void setAttribute(QNetworkRequest::Attribute attribute, const QVariant &value);
+ Q_NETWORK_EXPORT void clearAttribute(QNetworkRequest::Attribute attribute);
+ Q_NETWORK_EXPORT void clearAttributes();
+
+private:
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QNetworkRequestFactory &reply);
+#endif
+
+ QExplicitlySharedDataPointer<QNetworkRequestFactoryPrivate> d;
+};
+
+Q_DECLARE_SHARED(QNetworkRequestFactory)
+
+QT_END_NAMESPACE
+
+#endif // QNETWORKREQUESTFACTORY_H
diff --git a/src/network/access/qnetworkrequestfactory_p.h b/src/network/access/qnetworkrequestfactory_p.h
new file mode 100644
index 0000000000..4116669f21
--- /dev/null
+++ b/src/network/access/qnetworkrequestfactory_p.h
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QNETWORKREQUESTFACTORY_P_H
+#define QNETWORKREQUESTFACTORY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access framework. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtNetwork/qhttpheaders.h>
+#include <QtNetwork/qnetworkrequest.h>
+#if QT_CONFIG(ssl)
+#include <QtNetwork/qsslconfiguration.h>
+#endif
+#include <QtCore/qhash.h>
+#include <QtCore/qshareddata.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qurlquery.h>
+#include <QtCore/qvariant.h>
+
+QT_BEGIN_NAMESPACE
+
+class QNetworkRequestFactoryPrivate : public QSharedData
+{
+public:
+ QNetworkRequestFactoryPrivate();
+ explicit QNetworkRequestFactoryPrivate(const QUrl &baseUrl);
+ ~QNetworkRequestFactoryPrivate();
+ QNetworkRequest newRequest(const QUrl &url) const;
+ QUrl requestUrl(const QString *path = nullptr, const QUrlQuery *query = nullptr) const;
+
+#if QT_CONFIG(ssl)
+ QSslConfiguration sslConfig;
+#endif
+ QUrl baseUrl;
+ QHttpHeaders headers;
+ QByteArray bearerToken;
+ QString userName;
+ QString password;
+ QUrlQuery queryParameters;
+ QNetworkRequest::Priority priority = QNetworkRequest::NormalPriority;
+ std::chrono::milliseconds transferTimeout{0};
+ QHash<QNetworkRequest::Attribute, QVariant> attributes;
+};
+
+QT_END_NAMESPACE
+
+#endif // QNETWORKREQUESTFACTORY_P_H
diff --git a/src/network/access/qrestaccessmanager.cpp b/src/network/access/qrestaccessmanager.cpp
new file mode 100644
index 0000000000..7ef682e955
--- /dev/null
+++ b/src/network/access/qrestaccessmanager.cpp
@@ -0,0 +1,828 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrestaccessmanager.h"
+#include "qrestaccessmanager_p.h"
+#include "qrestreply.h"
+
+#include <QtNetwork/qhttpmultipart.h>
+#include <QtNetwork/qnetworkaccessmanager.h>
+#include <QtNetwork/qnetworkreply.h>
+
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qthread.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Q_LOGGING_CATEGORY(lcQrest, "qt.network.access.rest")
+
+/*!
+
+ \class QRestAccessManager
+ \brief The QRestAccessManager is a convenience wrapper for
+ QNetworkAccessManager.
+ \since 6.7
+
+ \ingroup network
+ \inmodule QtNetwork
+ \reentrant
+
+ \preliminary
+
+ QRestAccessManager is a convenience wrapper on top of
+ QNetworkAccessManager. It amends datatypes and HTTP methods
+ that are useful for typical RESTful client applications.
+
+ The usual Qt networking features are accessible by configuring the
+ wrapped QNetworkAccessManager directly. QRestAccessManager does not
+ take ownership of the wrapped QNetworkAccessManager.
+
+ QRestAccessManager and related QRestReply classes can only be used in the
+ thread they live in. See \l {QObject#Thread Affinity}{QObject thread affinity}
+ for more information.
+
+ \section1 Issuing Network Requests and Handling Replies
+
+ Network requests are initiated with a function call corresponding to
+ the desired HTTP method, such as \c get() and \c post().
+
+ \section2 Using Signals and Slots
+
+ The function returns a QNetworkReply* object, whose signals can be used
+ to follow up on the completion of the request in a traditional
+ Qt-signals-and-slots way.
+
+ Here's an example of how you could send a GET request and handle the
+ response:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 0
+
+ \section2 Using Callbacks and Context Objects
+
+ The functions also take a context object of QObject (subclass) type
+ and a callback function as parameters. The callback takes one QRestReply&
+ as a parameter. The callback can be any callable, including a
+ pointer-to-member-function.
+
+ These callbacks are invoked when the reply has finished processing
+ (also in the case the processing finished due to an error).
+
+ The context object can be \c nullptr, although, generally speaking,
+ this is discouraged. Using a valid context object ensures that if the
+ context object is destroyed during request processing, the callback will
+ not be called. Stray callbacks which access a destroyed context is a source
+ of application misbehavior.
+
+ Here's an example of how you could send a GET request and check the
+ response:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 1
+
+ Many of the functions take in data for sending to a server. The data is
+ supplied as the second parameter after the request.
+
+ Here's an example of how you could send a POST request and check the
+ response:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 2
+
+ The provided QRestReply& is valid only while the callback
+ executes. If you need it for longer, you can either move it
+ to another QRestReply, or construct a new one and initialize
+ it with the QNetworkReply (see QRestReply::networkReply()).
+
+ \section2 Supported data types
+
+ The following table summarizes the methods and the supported data types.
+ \c X means support.
+
+ \table
+ \header
+ \li Data type
+ \li \c get()
+ \li \c post()
+ \li \c put()
+ \li \c head()
+ \li \c patch()
+ \li \c deleteResource()
+ \li \c sendCustomRequest()
+ \row
+ \li No data
+ \li X
+ \li -
+ \li -
+ \li X
+ \li -
+ \li X
+ \li -
+ \row
+ \li QByteArray
+ \li X
+ \li X
+ \li X
+ \li -
+ \li X
+ \li -
+ \li X
+ \row
+ \li QJsonDocument *)
+ \li X
+ \li X
+ \li X
+ \li -
+ \li X
+ \li -
+ \li -
+ \row
+ \li QVariantMap **)
+ \li -
+ \li X
+ \li X
+ \li -
+ \li X
+ \li -
+ \li -
+ \row
+ \li QHttpMultiPart
+ \li -
+ \li X
+ \li X
+ \li -
+ \li -
+ \li -
+ \li X
+ \row
+ \li QIODevice
+ \li X
+ \li X
+ \li X
+ \li -
+ \li X
+ \li -
+ \li X
+ \endtable
+
+ *) QJsonDocument is sent in \l QJsonDocument::Compact format,
+ and the \c Content-Type header is set to \c {application/json} if the
+ \c Content-Type header was not set
+
+ **) QVariantMap is converted to and treated as a QJsonObject
+
+ \sa QRestReply, QNetworkRequestFactory, QNetworkAccessManager
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::get(
+ const QNetworkRequest &request,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP GET} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 3
+
+ Alternatively the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \sa QRestReply
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::get(
+ const QNetworkRequest &request, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP GET} based on \a request and provided \a data.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 4
+
+ Alternatively the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \sa QRestReply
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::get(
+ const QNetworkRequest &request, const QJsonDocument &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::get(
+ const QNetworkRequest &request, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::post(
+ const QNetworkRequest &request, const QJsonDocument &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP POST} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 5
+
+ Alternatively, the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ The \c post() method always requires \a data parameter. The following
+ data types are supported:
+ \list
+ \li QByteArray
+ \li QJsonDocument *)
+ \li QVariantMap **)
+ \li QHttpMultiPart*
+ \li QIODevice*
+ \endlist
+
+ *) Sent in \l QJsonDocument::Compact format, and the
+ \c Content-Type header is set to \c {application/json} if the
+ \c Content-Type header was not set
+ **) QVariantMap is converted to and treated as a QJsonObject
+
+ \sa QRestReply
+*/
+
+/*!
+
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::post(
+ const QNetworkRequest &request, const QVariantMap &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::post(
+ const QNetworkRequest &request, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::post(
+ const QNetworkRequest &request, QHttpMultiPart *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::post(
+ const QNetworkRequest &request, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::put(
+ const QNetworkRequest &request, const QJsonDocument &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP PUT} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 6
+
+ Alternatively the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ The \c put() method always requires \a data parameter. The following
+ data types are supported:
+ \list
+ \li QByteArray
+ \li QJsonDocument *)
+ \li QVariantMap **)
+ \li QHttpMultiPart*
+ \li QIODevice*
+ \endlist
+
+ *) Sent in \l QJsonDocument::Compact format, and the
+ \c Content-Type header is set to \c {application/json} if the
+ \c Content-Type header was not set
+ **) QVariantMap is converted to and treated as a QJsonObject
+
+ \sa QRestReply
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::put(
+ const QNetworkRequest &request, const QVariantMap &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::put(
+ const QNetworkRequest &request, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::put(
+ const QNetworkRequest &request, QHttpMultiPart *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::put(
+ const QNetworkRequest &request, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::patch(
+ const QNetworkRequest &request, const QJsonDocument &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP PATCH} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 10
+
+ Alternatively the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ The \c patch() method always requires \a data parameter. The following
+ data types are supported:
+ \list
+ \li QByteArray
+ \li QJsonDocument *)
+ \li QVariantMap **)
+ \li QIODevice*
+ \endlist
+
+ *) Sent in \l QJsonDocument::Compact format, and the
+ \c Content-Type header is set to \c {application/json} if the
+ \c Content-Type header was not set
+ **) QVariantMap is converted to and treated as a QJsonObject
+
+ \sa QRestReply
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::patch(
+ const QNetworkRequest &request, const QVariantMap &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::patch(
+ const QNetworkRequest &request, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::patch(
+ const QNetworkRequest &request, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::head(
+ const QNetworkRequest &request,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP HEAD} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 7
+
+ Alternatively the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \c head() request does not support providing data.
+
+ \sa QRestReply
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::deleteResource(
+ const QNetworkRequest &request,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues an \c {HTTP DELETE} based on \a request.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 8
+
+ Alternatively the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+ \c deleteResource() request does not support providing data.
+
+ \sa QRestReply
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::sendCustomRequest(
+ const QNetworkRequest& request, const QByteArray &method, const QByteArray &data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ Issues \a request based HTTP request with custom \a method and the
+ provided \a data.
+
+ The optional \a callback and \a context object can be provided for
+ handling the request completion as illustrated below:
+
+ \snippet code/src_network_access_qrestaccessmanager.cpp 9
+
+ Alternatively the signals of the returned QNetworkReply* object can be
+ used. For further information see
+ \l {Issuing Network Requests and Handling Replies}.
+
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::sendCustomRequest(
+ const QNetworkRequest& request, const QByteArray &method, QIODevice *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ \fn template<typename Functor, QRestAccessManager::if_compatible_callback<Functor>> QNetworkReply *QRestAccessManager::sendCustomRequest(
+ const QNetworkRequest& request, const QByteArray &method, QHttpMultiPart *data,
+ const ContextTypeForFunctor<Functor> *context,
+ Functor &&callback)
+
+ \overload
+*/
+
+/*!
+ Constructs a QRestAccessManager object and sets \a parent as the parent
+ object, and \a manager as the underlying QNetworkAccessManager which
+ is used for communication.
+
+ \sa networkAccessManager()
+*/
+
+QRestAccessManager::QRestAccessManager(QNetworkAccessManager *manager, QObject *parent)
+ : QObject(*new QRestAccessManagerPrivate, parent)
+{
+ Q_D(QRestAccessManager);
+ d->qnam = manager;
+ if (!d->qnam)
+ qCWarning(lcQrest, "QRestAccessManager: QNetworkAccesManager is nullptr");
+}
+
+/*!
+ Destroys the QRestAccessManager object.
+*/
+QRestAccessManager::~QRestAccessManager()
+ = default;
+
+/*!
+ Returns the underlying QNetworkAccessManager instance.
+
+ \sa QNetworkAccessManager
+*/
+QNetworkAccessManager *QRestAccessManager::networkAccessManager() const
+{
+ Q_D(const QRestAccessManager);
+ return d->qnam;
+}
+
+QRestAccessManagerPrivate::QRestAccessManagerPrivate()
+ = default;
+
+QRestAccessManagerPrivate::~QRestAccessManagerPrivate()
+{
+ if (!activeRequests.isEmpty()) {
+ qCWarning(lcQrest, "Access manager destroyed while %lld requests were still in progress",
+ qlonglong(activeRequests.size()));
+ }
+}
+
+QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ const QJsonDocument &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([](auto qnam, auto req, auto data) { return qnam->post(req, data); },
+ data, request, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ const QVariantMap &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ return postWithDataImpl(request, QJsonDocument::fromVariant(data), context, slot);
+}
+
+QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ const QByteArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->post(request, data); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ QHttpMultiPart *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->post(request, data); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::postWithDataImpl(const QNetworkRequest &request,
+ QIODevice *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->post(request, data); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::getNoDataImpl(const QNetworkRequest &request,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->get(request); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request,
+ const QByteArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->get(request, data); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request,
+ const QJsonDocument &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([](auto qnam, auto req, auto data) { return qnam->get(req, data); },
+ data, request, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::getWithDataImpl(const QNetworkRequest &request,
+ QIODevice *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->get(request, data); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::deleteResourceNoDataImpl(const QNetworkRequest &request,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->deleteResource(request); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::headNoDataImpl(const QNetworkRequest &request,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->head(request); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request,
+ const QJsonDocument &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([](auto qnam, auto req, auto data) { return qnam->put(req, data); },
+ data, request, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request,
+ const QVariantMap &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ return putWithDataImpl(request, QJsonDocument::fromVariant(data), context, slot);
+}
+
+QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request,
+ const QByteArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->put(request, data); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request,
+ QHttpMultiPart *data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->put(request, data); }, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::putWithDataImpl(const QNetworkRequest &request, QIODevice *data,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->put(request, data); }, context, slot);
+}
+
+static const auto PATCH = "PATCH"_ba;
+
+QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request,
+ const QJsonDocument &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest(
+ [](auto qnam, auto req, auto data) { return qnam->sendCustomRequest(req, PATCH, data); },
+ data, request, context, slot);
+}
+
+QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request,
+ const QVariantMap &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ return patchWithDataImpl(request, QJsonDocument::fromVariant(data), context, slot);
+}
+
+QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request,
+ const QByteArray &data, const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->sendCustomRequest(request, PATCH, data); },
+ context, slot);
+}
+
+QNetworkReply *QRestAccessManager::patchWithDataImpl(const QNetworkRequest &request, QIODevice *data,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->sendCustomRequest(request, PATCH, data); },
+ context, slot);
+}
+
+QNetworkReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request,
+ const QByteArray& method, const QByteArray &data,
+ const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->sendCustomRequest(request, method, data); },
+ context, slot);
+}
+
+QNetworkReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request,
+ const QByteArray& method, QIODevice *data,
+ const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->sendCustomRequest(request, method, data); },
+ context, slot);
+}
+
+QNetworkReply *QRestAccessManager::customWithDataImpl(const QNetworkRequest &request,
+ const QByteArray& method, QHttpMultiPart *data,
+ const QObject *context,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_D(QRestAccessManager);
+ return d->executeRequest([&](auto qnam) { return qnam->sendCustomRequest(request, method, data); },
+ context, slot);
+}
+
+QNetworkReply *QRestAccessManagerPrivate::createActiveRequest(QNetworkReply *reply,
+ const QObject *contextObject,
+ QtPrivate::QSlotObjectBase *slot)
+{
+ Q_Q(QRestAccessManager);
+ Q_ASSERT(reply);
+ QtPrivate::SlotObjSharedPtr slotPtr(QtPrivate::SlotObjUniquePtr{slot}); // adopts
+ activeRequests.insert(reply, CallerInfo{contextObject, slotPtr});
+ // The signal connections below are made to 'q' to avoid stray signal
+ // handling upon its destruction while requests were still in progress
+
+ QObject::connect(reply, &QNetworkReply::finished, q, [reply, this]() {
+ handleReplyFinished(reply);
+ });
+ // Safe guard in case reply is destroyed before it's finished
+ QObject::connect(reply, &QObject::destroyed, q, [reply, this]() {
+ activeRequests.remove(reply);
+ });
+ // If context object is destroyed, clean up any possible replies it had associated with it
+ if (contextObject) {
+ QObject::connect(contextObject, &QObject::destroyed, q, [reply, this]() {
+ activeRequests.remove(reply);
+ });
+ }
+ return reply;
+}
+
+void QRestAccessManagerPrivate::verifyThreadAffinity(const QObject *contextObject)
+{
+ Q_Q(QRestAccessManager);
+ if (QThread::currentThread() != q->thread()) {
+ qCWarning(lcQrest, "QRestAccessManager can only be called in the thread it belongs to");
+ Q_ASSERT(false);
+ }
+ if (contextObject && (contextObject->thread() != q->thread())) {
+ qCWarning(lcQrest, "QRestAccessManager: the context object must reside in the same thread");
+ Q_ASSERT(false);
+ }
+}
+
+QNetworkReply* QRestAccessManagerPrivate::warnNoAccessManager()
+{
+ qCWarning(lcQrest, "QRestAccessManager: QNetworkAccessManager not set");
+ return nullptr;
+}
+
+void QRestAccessManagerPrivate::handleReplyFinished(QNetworkReply *reply)
+{
+ auto request = activeRequests.find(reply);
+ if (request == activeRequests.end()) {
+ qCDebug(lcQrest, "QRestAccessManager: Unexpected reply received, ignoring");
+ return;
+ }
+
+ CallerInfo caller = request.value();
+ activeRequests.erase(request);
+
+ if (caller.slot) {
+ // Callback was provided
+ QRestReply restReply(reply);
+ void *argv[] = { nullptr, &restReply };
+ // If we have context object, use it
+ QObject *context = caller.contextObject
+ ? const_cast<QObject*>(caller.contextObject.get()) : nullptr;
+ caller.slot->call(context, argv);
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qrestaccessmanager.cpp"
diff --git a/src/network/access/qrestaccessmanager.h b/src/network/access/qrestaccessmanager.h
new file mode 100644
index 0000000000..3245b41785
--- /dev/null
+++ b/src/network/access/qrestaccessmanager.h
@@ -0,0 +1,127 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTACCESSMANAGER_H
+#define QRESTACCESSMANAGER_H
+
+#if 0
+#pragma qt_class(QRestAccessManager)
+#endif
+
+#include <QtNetwork/qnetworkaccessmanager.h>
+
+QT_BEGIN_NAMESPACE
+
+class QDebug;
+class QRestReply;
+
+#define QREST_METHOD_WITH_DATA(METHOD, DATA) \
+public: \
+template <typename Functor, if_compatible_callback<Functor> = true> \
+QNetworkReply *METHOD(const QNetworkRequest &request, DATA data, \
+ const ContextTypeForFunctor<Functor> *context, \
+ Functor &&callback) \
+{ \
+ return METHOD##WithDataImpl(request, data, context, \
+ QtPrivate::makeCallableObject<CallbackPrototype>(std::forward<Functor>(callback))); \
+} \
+QNetworkReply *METHOD(const QNetworkRequest &request, DATA data) \
+{ \
+ return METHOD##WithDataImpl(request, data, nullptr, nullptr); \
+} \
+private: \
+QNetworkReply *METHOD##WithDataImpl(const QNetworkRequest &request, DATA data, \
+ const QObject *context, QtPrivate::QSlotObjectBase *slot); \
+/* end */
+
+#define QREST_METHOD_NO_DATA(METHOD) \
+public: \
+template <typename Functor, if_compatible_callback<Functor> = true> \
+QNetworkReply *METHOD(const QNetworkRequest &request, \
+ const ContextTypeForFunctor<Functor> *context, \
+ Functor &&callback) \
+{ \
+ return METHOD##NoDataImpl(request, context, \
+ QtPrivate::makeCallableObject<CallbackPrototype>(std::forward<Functor>(callback))); \
+} \
+QNetworkReply *METHOD(const QNetworkRequest &request) \
+{ \
+ return METHOD##NoDataImpl(request, nullptr, nullptr); \
+} \
+private: \
+QNetworkReply *METHOD##NoDataImpl(const QNetworkRequest &request, \
+ const QObject *context, QtPrivate::QSlotObjectBase *slot); \
+/* end */
+
+#define QREST_METHOD_CUSTOM_WITH_DATA(DATA) \
+public: \
+template <typename Functor, if_compatible_callback<Functor> = true> \
+QNetworkReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data, \
+ const ContextTypeForFunctor<Functor> *context, \
+ Functor &&callback) \
+{ \
+ return customWithDataImpl(request, method, data, context, \
+ QtPrivate::makeCallableObject<CallbackPrototype>(std::forward<Functor>(callback))); \
+} \
+QNetworkReply *sendCustomRequest(const QNetworkRequest& request, const QByteArray &method, DATA data) \
+{ \
+ return customWithDataImpl(request, method, data, nullptr, nullptr); \
+} \
+private: \
+QNetworkReply *customWithDataImpl(const QNetworkRequest& request, const QByteArray &method, \
+ DATA data, const QObject* context, \
+ QtPrivate::QSlotObjectBase *slot); \
+/* end */
+
+class QRestAccessManagerPrivate;
+class QT_TECH_PREVIEW_API Q_NETWORK_EXPORT QRestAccessManager : public QObject
+{
+ Q_OBJECT
+ using CallbackPrototype = void(*)(QRestReply&);
+ template <typename Functor>
+ using ContextTypeForFunctor = typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType;
+ template <typename Functor>
+ using if_compatible_callback = std::enable_if_t<
+ QtPrivate::AreFunctionsCompatible<CallbackPrototype, Functor>::value, bool>;
+public:
+ explicit QRestAccessManager(QNetworkAccessManager *manager, QObject *parent = nullptr);
+ ~QRestAccessManager() override;
+
+ QNetworkAccessManager *networkAccessManager() const;
+
+ QREST_METHOD_NO_DATA(deleteResource)
+ QREST_METHOD_NO_DATA(head)
+ QREST_METHOD_NO_DATA(get)
+ QREST_METHOD_WITH_DATA(get, const QByteArray &)
+ QREST_METHOD_WITH_DATA(get, const QJsonDocument &)
+ QREST_METHOD_WITH_DATA(get, QIODevice *)
+ QREST_METHOD_WITH_DATA(post, const QJsonDocument &)
+ QREST_METHOD_WITH_DATA(post, const QVariantMap &)
+ QREST_METHOD_WITH_DATA(post, const QByteArray &)
+ QREST_METHOD_WITH_DATA(post, QHttpMultiPart *)
+ QREST_METHOD_WITH_DATA(post, QIODevice *)
+ QREST_METHOD_WITH_DATA(put, const QJsonDocument &)
+ QREST_METHOD_WITH_DATA(put, const QVariantMap &)
+ QREST_METHOD_WITH_DATA(put, const QByteArray &)
+ QREST_METHOD_WITH_DATA(put, QHttpMultiPart *)
+ QREST_METHOD_WITH_DATA(put, QIODevice *)
+ QREST_METHOD_WITH_DATA(patch, const QJsonDocument &)
+ QREST_METHOD_WITH_DATA(patch, const QVariantMap &)
+ QREST_METHOD_WITH_DATA(patch, const QByteArray &)
+ QREST_METHOD_WITH_DATA(patch, QIODevice *)
+ QREST_METHOD_CUSTOM_WITH_DATA(const QByteArray &)
+ QREST_METHOD_CUSTOM_WITH_DATA(QIODevice *)
+ QREST_METHOD_CUSTOM_WITH_DATA(QHttpMultiPart *)
+
+private:
+ Q_DECLARE_PRIVATE(QRestAccessManager)
+ Q_DISABLE_COPY(QRestAccessManager)
+};
+
+#undef QREST_METHOD_NO_DATA
+#undef QREST_METHOD_WITH_DATA
+#undef QREST_METHOD_CUSTOM_WITH_DATA
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qrestaccessmanager_p.h b/src/network/access/qrestaccessmanager_p.h
new file mode 100644
index 0000000000..2e6c1afb90
--- /dev/null
+++ b/src/network/access/qrestaccessmanager_p.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTACCESSMANAGER_P_H
+#define QRESTACCESSMANAGER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qrestaccessmanager.h"
+#include "private/qobject_p.h"
+
+#include <QtNetwork/qnetworkaccessmanager.h>
+
+#include <QtCore/qjsonarray.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qjsonobject.h>
+#include <QtCore/qxpfunctional.h>
+
+QT_BEGIN_NAMESPACE
+
+class QRestReply;
+class QRestAccessManagerPrivate : public QObjectPrivate
+{
+public:
+ QRestAccessManagerPrivate();
+ ~QRestAccessManagerPrivate() override;
+
+ QNetworkReply* createActiveRequest(QNetworkReply *reply, const QObject *contextObject,
+ QtPrivate::QSlotObjectBase *slot);
+ void handleReplyFinished(QNetworkReply *reply);
+
+ using ReqOpRef = qxp::function_ref<QNetworkReply*(QNetworkAccessManager*) const>;
+ QNetworkReply *executeRequest(ReqOpRef requestOperation,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+ {
+ if (!qnam)
+ return warnNoAccessManager();
+ verifyThreadAffinity(context);
+ QNetworkReply *reply = requestOperation(qnam);
+ return createActiveRequest(reply, context, slot);
+ }
+
+ using ReqOpRefJson = qxp::function_ref<QNetworkReply*(QNetworkAccessManager*,
+ const QNetworkRequest &,
+ const QByteArray &) const>;
+ QNetworkReply *executeRequest(ReqOpRefJson requestOperation, const QJsonDocument &jsonDoc,
+ const QNetworkRequest &request,
+ const QObject *context, QtPrivate::QSlotObjectBase *slot)
+ {
+ if (!qnam)
+ return warnNoAccessManager();
+ verifyThreadAffinity(context);
+ QNetworkRequest req(request);
+ auto h = req.headers();
+ if (!h.contains(QHttpHeaders::WellKnownHeader::ContentType)) {
+ h.append(QHttpHeaders::WellKnownHeader::ContentType,
+ QLatin1StringView{"application/json"});
+ }
+ req.setHeaders(std::move(h));
+ QNetworkReply *reply = requestOperation(qnam, req, jsonDoc.toJson(QJsonDocument::Compact));
+ return createActiveRequest(reply, context, slot);
+ }
+
+ void verifyThreadAffinity(const QObject *contextObject);
+ Q_DECL_COLD_FUNCTION
+ QNetworkReply* warnNoAccessManager();
+
+ struct CallerInfo {
+ QPointer<const QObject> contextObject = nullptr;
+ QtPrivate::SlotObjSharedPtr slot;
+ };
+ QHash<QNetworkReply*, CallerInfo> activeRequests;
+
+ QNetworkAccessManager *qnam = nullptr;
+ bool deletesRepliesOnFinished = true;
+ Q_DECLARE_PUBLIC(QRestAccessManager)
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qrestreply.cpp b/src/network/access/qrestreply.cpp
new file mode 100644
index 0000000000..2d8d101084
--- /dev/null
+++ b/src/network/access/qrestreply.cpp
@@ -0,0 +1,608 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qrestreply.h"
+#include "qrestreply_p.h"
+
+#include <QtNetwork/private/qnetworkreply_p.h>
+
+#include <QtCore/qbytearrayview.h>
+#include <QtCore/qjsondocument.h>
+#include <QtCore/qlatin1stringmatcher.h>
+#include <QtCore/qlatin1stringview.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstringconverter.h>
+
+#include <QtCore/qxpfunctional.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+Q_DECLARE_LOGGING_CATEGORY(lcQrest)
+
+/*!
+ \class QRestReply
+ \since 6.7
+ \brief QRestReply is a convenience wrapper for QNetworkReply.
+
+ \reentrant
+ \ingroup network
+ \inmodule QtNetwork
+
+ \preliminary
+
+ QRestReply wraps a QNetworkReply and provides convenience methods for data
+ and status handling. The methods provide convenience for typical RESTful
+ client applications.
+
+ QRestReply doesn't take ownership of the wrapped QNetworkReply, and the
+ lifetime and ownership of the reply is as defined by QNetworkAccessManager
+ documentation.
+
+ QRestReply object is not copyable, but is movable.
+
+ \sa QRestAccessManager, QNetworkReply, QNetworkAccessManager,
+ QNetworkAccessManager::setAutoDeleteReplies()
+*/
+
+/*!
+ Creates a QRestReply and initializes the wrapped QNetworkReply to \a reply.
+*/
+QRestReply::QRestReply(QNetworkReply *reply)
+ : wrapped(reply)
+{
+ if (!wrapped)
+ qCWarning(lcQrest, "QRestReply: QNetworkReply is nullptr");
+}
+
+/*!
+ Destroys this QRestReply object.
+*/
+QRestReply::~QRestReply()
+{
+ delete d;
+}
+
+/*!
+ \fn QRestReply::QRestReply(QRestReply &&other) noexcept
+
+ Move-constructs the reply from \a other.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+*/
+
+/*!
+ \fn QRestReply &QRestReply::operator=(QRestReply &&other) noexcept
+
+ Move-assigns \a other and returns a reference to this reply.
+
+ \note The moved-from object \a other is placed in a
+ partially-formed state, in which the only valid operations are
+ destruction and assignment of a new value.
+*/
+
+/*!
+ Returns a pointer to the underlying QNetworkReply wrapped by this object.
+*/
+QNetworkReply *QRestReply::networkReply() const
+{
+ return wrapped;
+}
+
+/*!
+ Returns the received data as a QJsonDocument.
+
+ The returned value is wrapped in \c std::optional. If the conversion
+ from the received data fails (empty data or JSON parsing error),
+ \c std::nullopt is returned, and \a error is filled with details.
+
+ Calling this function consumes the received data, and any further calls
+ to get response data will return empty.
+
+ This function returns \c {std::nullopt} and will not consume
+ any data if the reply is not finished. If \a error is passed, it will be
+ set to QJsonParseError::NoError to distinguish this case from an actual
+ error.
+
+ \sa readBody(), readText()
+*/
+std::optional<QJsonDocument> QRestReply::readJson(QJsonParseError *error)
+{
+ if (!wrapped) {
+ if (error)
+ *error = {0, QJsonParseError::ParseError::NoError};
+ return std::nullopt;
+ }
+
+ if (!wrapped->isFinished()) {
+ qCWarning(lcQrest, "readJson() called on an unfinished reply, ignoring");
+ if (error)
+ *error = {0, QJsonParseError::ParseError::NoError};
+ return std::nullopt;
+ }
+ QJsonParseError parseError;
+ const QByteArray data = wrapped->readAll();
+ const QJsonDocument doc = QJsonDocument::fromJson(data, &parseError);
+ if (error)
+ *error = parseError;
+ if (parseError.error)
+ return std::nullopt;
+ return doc;
+}
+
+/*!
+ Returns the received data as a QByteArray.
+
+ Calling this function consumes the data received so far, and any further
+ calls to get response data will return empty until further data has been
+ received.
+
+ \sa readJson(), readText(), QNetworkReply::bytesAvailable(),
+ QNetworkReply::readyRead()
+*/
+QByteArray QRestReply::readBody()
+{
+ return wrapped ? wrapped->readAll() : QByteArray{};
+}
+
+/*!
+ Returns the received data as a QString.
+
+ The received data is decoded into a QString (UTF-16). If available, the decoding
+ uses the \e Content-Type header's \e charset parameter to determine the
+ source encoding. If the encoding information is not available or not supported
+ by \l QStringConverter, UTF-8 is used by default.
+
+ Calling this function consumes the data received so far. Returns
+ a default constructed value if no new data is available, or if the
+ decoding is not supported by \l QStringConverter, or if the decoding
+ has errors (for example invalid characters).
+
+ \sa readJson(), readBody(), QNetworkReply::readyRead()
+*/
+QString QRestReply::readText()
+{
+ QString result;
+ if (!wrapped)
+ return result;
+
+ QByteArray data = wrapped->readAll();
+ if (data.isEmpty())
+ return result;
+
+ // Text decoding needs to persist decoding state across calls to this function,
+ // so allocate decoder if not yet allocated.
+ if (!d)
+ d = new QRestReplyPrivate;
+
+ if (!d->decoder) {
+ const QByteArray charset = QRestReplyPrivate::contentCharset(wrapped);
+ d->decoder.emplace(charset.constData());
+ if (!d->decoder->isValid()) { // the decoder may not support the mimetype's charset
+ qCWarning(lcQrest, "readText(): Charset \"%s\" is not supported", charset.constData());
+ return result;
+ }
+ }
+ // Check if the decoder already had an error, or has errors after decoding current data chunk
+ if (d->decoder->hasError() || (result = (*d->decoder)(data), d->decoder->hasError())) {
+ qCWarning(lcQrest, "readText(): Decoding error occurred");
+ return {};
+ }
+ return result;
+}
+
+/*!
+ Returns the HTTP status received in the server response.
+ The value is \e 0 if not available (the status line has not been received,
+ yet).
+
+ \note The HTTP status is reported as indicated by the received HTTP
+ response. An error() may occur after receiving the status, for instance
+ due to network disconnection while receiving a long response.
+ These potential subsequent errors are not represented by the reported
+ HTTP status.
+
+ \sa isSuccess(), hasError(), error()
+*/
+int QRestReply::httpStatus() const
+{
+ return wrapped ? wrapped->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() : 0;
+}
+
+/*!
+ \fn bool QRestReply::isSuccess() const
+
+ Returns whether the HTTP status is between 200..299 and no
+ further errors have occurred while receiving the response (for example,
+ abrupt disconnection while receiving the body data). This function
+ is a convenient way to check whether the response is considered successful.
+
+ \sa httpStatus(), hasError(), error()
+*/
+
+/*!
+ Returns whether the HTTP status is between 200..299.
+
+ \sa isSuccess(), httpStatus(), hasError(), error()
+*/
+bool QRestReply::isHttpStatusSuccess() const
+{
+ const int status = httpStatus();
+ return status >= 200 && status < 300;
+}
+
+/*!
+ Returns whether an error has occurred. This includes errors such as
+ network and protocol errors, but excludes cases where the server
+ successfully responded with an HTTP error status (for example
+ \c {500 Internal Server Error}). Use \l httpStatus() or
+ \l isHttpStatusSuccess() to get the HTTP status information.
+
+ \sa httpStatus(), isSuccess(), error(), errorString()
+*/
+bool QRestReply::hasError() const
+{
+ if (!wrapped)
+ return false;
+
+ const int status = httpStatus();
+ if (status > 0) {
+ // The HTTP status is set upon receiving the response headers, but the
+ // connection might still fail later while receiving the body data.
+ return wrapped->error() == QNetworkReply::RemoteHostClosedError;
+ }
+ return wrapped->error() != QNetworkReply::NoError;
+}
+
+/*!
+ Returns the last error, if any. The errors include
+ errors such as network and protocol errors, but exclude
+ cases when the server successfully responded with an HTTP status.
+
+ \sa httpStatus(), isSuccess(), hasError(), errorString()
+*/
+QNetworkReply::NetworkError QRestReply::error() const
+{
+ if (!hasError())
+ return QNetworkReply::NetworkError::NoError;
+ return wrapped->error();
+}
+
+/*!
+ Returns a human-readable description of the last network error.
+
+ \sa httpStatus(), isSuccess(), hasError(), error()
+*/
+QString QRestReply::errorString() const
+{
+ if (hasError())
+ return wrapped->errorString();
+ return {};
+}
+
+QRestReplyPrivate::QRestReplyPrivate()
+ = default;
+
+QRestReplyPrivate::~QRestReplyPrivate()
+ = default;
+
+#ifndef QT_NO_DEBUG_STREAM
+static QLatin1StringView operationName(QNetworkAccessManager::Operation operation)
+{
+ switch (operation) {
+ case QNetworkAccessManager::Operation::GetOperation:
+ return "GET"_L1;
+ case QNetworkAccessManager::Operation::HeadOperation:
+ return "HEAD"_L1;
+ case QNetworkAccessManager::Operation::PostOperation:
+ return "POST"_L1;
+ case QNetworkAccessManager::Operation::PutOperation:
+ return "PUT"_L1;
+ case QNetworkAccessManager::Operation::DeleteOperation:
+ return "DELETE"_L1;
+ case QNetworkAccessManager::Operation::CustomOperation:
+ return "CUSTOM"_L1;
+ case QNetworkAccessManager::Operation::UnknownOperation:
+ return "UNKNOWN"_L1;
+ }
+ Q_UNREACHABLE_RETURN({});
+}
+
+/*!
+ \fn QDebug QRestReply::operator<<(QDebug debug, const QRestReply &reply)
+
+ Writes the \a reply into the \a debug object for debugging purposes.
+
+ \sa {Debugging Techniques}
+*/
+QDebug operator<<(QDebug debug, const QRestReply &reply)
+{
+ const QDebugStateSaver saver(debug);
+ debug.resetFormat().nospace();
+ if (!reply.networkReply()) {
+ debug << "QRestReply(no network reply)";
+ return debug;
+ }
+ debug << "QRestReply(isSuccess = " << reply.isSuccess()
+ << ", httpStatus = " << reply.httpStatus()
+ << ", isHttpStatusSuccess = " << reply.isHttpStatusSuccess()
+ << ", hasError = " << reply.hasError()
+ << ", errorString = " << reply.errorString()
+ << ", error = " << reply.error()
+ << ", isFinished = " << reply.networkReply()->isFinished()
+ << ", bytesAvailable = " << reply.networkReply()->bytesAvailable()
+ << ", url " << reply.networkReply()->url()
+ << ", operation = " << operationName(reply.networkReply()->operation())
+ << ", reply headers = " << reply.networkReply()->headers()
+ << ")";
+ return debug;
+}
+#endif // QT_NO_DEBUG_STREAM
+
+static constexpr auto parse_OWS(QByteArrayView data) noexcept
+{
+ struct R {
+ QByteArrayView ows, tail;
+ };
+
+ constexpr auto is_OWS_char = [](auto ch) { return ch == ' ' || ch == '\t'; };
+
+ qsizetype i = 0;
+ while (i < data.size() && is_OWS_char(data[i]))
+ ++i;
+
+ return R{data.first(i), data.sliced(i)};
+}
+
+static constexpr void eat_OWS(QByteArrayView &data) noexcept
+{
+ data = parse_OWS(data).tail;
+}
+
+static constexpr auto parse_quoted_string(QByteArrayView data, qxp::function_ref<void(char) const> yield)
+{
+ struct R {
+ QByteArrayView quotedString, tail;
+ constexpr explicit operator bool() const noexcept { return !quotedString.isEmpty(); }
+ };
+
+ if (!data.startsWith('"'))
+ return R{{}, data};
+
+ qsizetype i = 1; // one past initial DQUOTE
+ while (i < data.size()) {
+ switch (auto ch = data[i++]) {
+ case '"': // final DQUOTE -> end of string
+ return R{data.first(i), data.sliced(i)};
+ case '\\': // quoted-pair
+ // https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.4-3:
+ // Recipients that process the value of a quoted-string MUST handle a
+ // quoted-pair as if it were replaced by the octet following the backslash.
+ if (i == data.size())
+ break; // premature end
+ ch = data[i++]; // eat '\\'
+ [[fallthrough]];
+ default:
+ // we don't validate quoted-string octets to be only qdtext (Postel's Law)
+ yield(ch);
+ }
+ }
+
+ return R{{}, data}; // premature end
+}
+
+static constexpr bool is_tchar(char ch) noexcept
+{
+ // ### optimize
+ switch (ch) {
+ case '!':
+ case '#':
+ case '$':
+ case '%':
+ case '&':
+ case '\'':
+ case '*':
+ case '+':
+ case '-':
+ case '.':
+ case '^':
+ case '_':
+ case '`':
+ case '|':
+ case '~':
+ return true;
+ default:
+ return (ch >= 'a' && ch <= 'z')
+ || (ch >= '0' && ch <= '9')
+ || (ch >= 'A' && ch <= 'Z');
+ }
+}
+
+static constexpr auto parse_comment(QByteArrayView data) noexcept
+{
+ struct R {
+ QByteArrayView comment, tail;
+ constexpr explicit operator bool() const noexcept { return !comment.isEmpty(); }
+ };
+
+ const auto invalid = R{{}, data}; // preserves original `data`
+
+ // comment = "(" *( ctext / quoted-pair / comment ) ")"
+ // ctext = HTAB / SP / %x21-27 / %x2A-5B / %x5D-7E / obs-text
+
+ if (!data.startsWith('('))
+ return invalid;
+
+ qsizetype i = 1;
+ qsizetype level = 1;
+ while (i < data.size()) {
+ switch (data[i++]) {
+ case '(': // nested comment
+ ++level;
+ break;
+ case ')': // end of comment
+ if (--level == 0)
+ return R{data.first(i), data.sliced(i)};
+ break;
+ case '\\': // quoted-pair
+ if (i == data.size())
+ return invalid; // premature end
+ ++i; // eat escaped character
+ break;
+ default:
+ ; // don't validate ctext - accept everything (Postel's Law)
+ }
+ }
+
+ return invalid; // premature end / unbalanced nesting levels
+}
+
+static constexpr void eat_CWS(QByteArrayView &data) noexcept
+{
+ eat_OWS(data);
+ while (const auto comment = parse_comment(data)) {
+ data = comment.tail;
+ eat_OWS(data);
+ }
+}
+
+static constexpr auto parse_token(QByteArrayView data) noexcept
+{
+ struct R {
+ QByteArrayView token, tail;
+ constexpr explicit operator bool() const noexcept { return !token.isEmpty(); }
+ };
+
+ qsizetype i = 0;
+ while (i < data.size() && is_tchar(data[i]))
+ ++i;
+
+ return R{data.first(i), data.sliced(i)};
+}
+
+static constexpr auto parse_parameter(QByteArrayView data, qxp::function_ref<void(char) const> yield)
+{
+ struct R {
+ QLatin1StringView name; QByteArrayView value; QByteArrayView tail;
+ constexpr explicit operator bool() const noexcept { return !name.isEmpty(); }
+ };
+
+ const auto invalid = R{{}, {}, data}; // preserves original `data`
+
+ // parameter = parameter-name "=" parameter-value
+ // parameter-name = token
+ // parameter-value = ( token / quoted-string )
+
+ const auto name = parse_token(data);
+ if (!name)
+ return invalid;
+ data = name.tail;
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ if (!data.startsWith('='))
+ return invalid;
+ data = data.sliced(1);
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ if (Q_UNLIKELY(data.startsWith('"'))) { // value is a quoted-string
+
+ const auto value = parse_quoted_string(data, yield);
+ if (!value)
+ return invalid;
+ data = value.tail;
+
+ return R{QLatin1StringView{name.token}, value.quotedString, data};
+
+ } else { // value is a token
+
+ const auto value = parse_token(data);
+ if (!value)
+ return invalid;
+ data = value.tail;
+
+ return R{QLatin1StringView{name.token}, value.token, data};
+ }
+}
+
+static auto parse_content_type(QByteArrayView data)
+{
+ struct R {
+ QLatin1StringView type, subtype;
+ std::string charset;
+ constexpr explicit operator bool() const noexcept { return !type.isEmpty(); }
+ };
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ const auto type = parse_token(data);
+ if (!type)
+ return R{};
+ data = type.tail;
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ if (!data.startsWith('/'))
+ return R{};
+ data = data.sliced(1);
+
+ eat_CWS(data); // not in the grammar, but accepted under Postel's Law
+
+ const auto subtype = parse_token(data);
+ if (!subtype)
+ return R{};
+ data = subtype.tail;
+
+ eat_CWS(data);
+
+ auto r = R{QLatin1StringView{type.token}, QLatin1StringView{subtype.token}, {}};
+
+ while (data.startsWith(';')) {
+
+ data = data.sliced(1); // eat ';'
+
+ eat_CWS(data);
+
+ const auto param = parse_parameter(data, [&](char ch) { r.charset.append(1, ch); });
+ if (param.name.compare("charset"_L1, Qt::CaseInsensitive) == 0) {
+ if (r.charset.empty() && !param.value.startsWith('"')) // wasn't a quoted-string
+ r.charset.assign(param.value.begin(), param.value.end());
+ return r; // charset found
+ }
+ r.charset.clear(); // wasn't an actual charset
+ if (param.tail.size() == data.size()) // no progress was made
+ break; // returns {type, subtype}
+ // otherwise, continue (accepting e.g. `;;`)
+ data = param.tail;
+
+ eat_CWS(data);
+ }
+
+ return r; // no charset found
+}
+
+QByteArray QRestReplyPrivate::contentCharset(const QNetworkReply* reply)
+{
+ // Content-type consists of mimetype and optional parameters, of which one may be 'charset'
+ // Example values and their combinations below are all valid, see RFC 7231 section 3.1.1.5
+ // and RFC 2045 section 5.1
+ //
+ // text/plain; charset=utf-8
+ // text/plain; charset=utf-8;version=1.7
+ // text/plain; charset = utf-8
+ // text/plain; charset ="utf-8"
+
+ const QByteArray contentTypeValue =
+ reply->headers().value(QHttpHeaders::WellKnownHeader::ContentType).toByteArray();
+
+ const auto r = parse_content_type(contentTypeValue);
+ if (r && !r.charset.empty())
+ return QByteArrayView(r.charset).toByteArray();
+ else
+ return "UTF-8"_ba; // Default to the most commonly used UTF-8.
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/access/qrestreply.h b/src/network/access/qrestreply.h
new file mode 100644
index 0000000000..c32fff1d4e
--- /dev/null
+++ b/src/network/access/qrestreply.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTREPLY_H
+#define QRESTREPLY_H
+
+#include <QtNetwork/qnetworkreply.h>
+
+#include <QtCore/qpointer.h>
+
+#include <optional>
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+class QByteArray;
+class QDebug;
+struct QJsonParseError;
+class QJsonDocument;
+class QString;
+
+class QRestReplyPrivate;
+class QT_TECH_PREVIEW_API QRestReply
+{
+public:
+ Q_NETWORK_EXPORT explicit QRestReply(QNetworkReply *reply);
+ Q_NETWORK_EXPORT ~QRestReply();
+
+ QRestReply(QRestReply &&other) noexcept
+ : wrapped(std::move(other.wrapped)),
+ d(std::exchange(other.d, nullptr))
+ {
+ }
+ QT_MOVE_ASSIGNMENT_OPERATOR_IMPL_VIA_PURE_SWAP(QRestReply)
+ void swap(QRestReply &other) noexcept
+ {
+ wrapped.swap(other.wrapped);
+ qt_ptr_swap(d, other.d);
+ }
+
+ Q_NETWORK_EXPORT QNetworkReply *networkReply() const;
+
+ Q_NETWORK_EXPORT std::optional<QJsonDocument> readJson(QJsonParseError *error = nullptr);
+ Q_NETWORK_EXPORT QByteArray readBody();
+ Q_NETWORK_EXPORT QString readText();
+
+ bool isSuccess() const
+ {
+ return !hasError() && isHttpStatusSuccess();
+ }
+ Q_NETWORK_EXPORT int httpStatus() const;
+ Q_NETWORK_EXPORT bool isHttpStatusSuccess() const;
+
+ Q_NETWORK_EXPORT bool hasError() const;
+ Q_NETWORK_EXPORT QNetworkReply::NetworkError error() const;
+ Q_NETWORK_EXPORT QString errorString() const;
+
+private:
+#ifndef QT_NO_DEBUG_STREAM
+ friend Q_NETWORK_EXPORT QDebug operator<<(QDebug debug, const QRestReply &reply);
+#endif
+ QPointer<QNetworkReply> wrapped;
+ QRestReplyPrivate *d = nullptr;
+ Q_DISABLE_COPY(QRestReply)
+};
+
+Q_DECLARE_SHARED(QRestReply)
+
+QT_END_NAMESPACE
+
+#endif // QRESTREPLY_H
diff --git a/src/network/access/qrestreply_p.h b/src/network/access/qrestreply_p.h
new file mode 100644
index 0000000000..ec963cf168
--- /dev/null
+++ b/src/network/access/qrestreply_p.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QRESTREPLY_P_H
+#define QRESTREPLY_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtCore/private/qstringconverter_p.h>
+
+#include <optional>
+
+QT_BEGIN_NAMESPACE
+
+class QByteArray;
+class QNetworkReply;
+
+class QRestReplyPrivate
+{
+public:
+ QRestReplyPrivate();
+ ~QRestReplyPrivate();
+
+ std::optional<QStringDecoder> decoder;
+
+ static QByteArray contentCharset(const QNetworkReply *reply);
+};
+
+QT_END_NAMESPACE
+
+#endif
diff --git a/src/network/access/qsocketabstraction_p.h b/src/network/access/qsocketabstraction_p.h
new file mode 100644
index 0000000000..2b40b80244
--- /dev/null
+++ b/src/network/access/qsocketabstraction_p.h
@@ -0,0 +1,91 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSOCKETABSTRACTION_P_H
+#define QSOCKETABSTRACTION_P_H
+
+#include <private/qtnetworkglobal_p.h>
+
+#include <QtNetwork/qabstractsocket.h>
+#include <QtNetwork/qlocalsocket.h>
+
+#include <QtCore/qxpfunctional.h>
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists for the convenience
+// of the Network Access API. This header file may change from
+// version to version without notice, or even be removed.
+//
+// We mean it.
+//
+
+QT_BEGIN_NAMESPACE
+
+// Helper functions to deal with a QIODevice that is either a socket or a local
+// socket.
+namespace QSocketAbstraction {
+template <typename Fn, typename... Args>
+auto visit(Fn &&fn, QIODevice *socket, Args &&...args)
+{
+ if (auto *s = qobject_cast<QAbstractSocket *>(socket))
+ return std::forward<Fn>(fn)(s, std::forward<Args>(args)...);
+ if (auto *s = qobject_cast<QLocalSocket *>(socket))
+ return std::forward<Fn>(fn)(s, std::forward<Args>(args)...);
+ Q_UNREACHABLE();
+}
+
+// Since QLocalSocket's LocalSocketState's values are defined as being equal
+// to some of QAbstractSocket's SocketState's values, we can use the superset
+// of the two as the return type.
+inline QAbstractSocket::SocketState socketState(QIODevice *device)
+{
+ auto getState = [](auto *s) {
+ using T = std::remove_pointer_t<decltype(s)>;
+ if constexpr (std::is_same_v<T, QAbstractSocket>) {
+ return s->state();
+ } else if constexpr (std::is_same_v<T, QLocalSocket>) {
+ QLocalSocket::LocalSocketState st = s->state();
+ return static_cast<QAbstractSocket::SocketState>(st);
+ }
+ Q_UNREACHABLE();
+ };
+ return visit(getState, device);
+}
+
+// Same as for socketState(), but for the errors
+inline QAbstractSocket::SocketError socketError(QIODevice *device)
+{
+ auto getError = [](auto *s) {
+ using T = std::remove_pointer_t<decltype(s)>;
+ if constexpr (std::is_same_v<T, QAbstractSocket>) {
+ return s->error();
+ } else if constexpr (std::is_same_v<T, QLocalSocket>) {
+ QLocalSocket::LocalSocketError st = s->error();
+ return static_cast<QAbstractSocket::SocketError>(st);
+ }
+ Q_UNREACHABLE();
+ };
+ return visit(getError, device);
+}
+
+inline QString socketPeerName(QIODevice *device)
+{
+ auto getPeerName = [](auto *s) {
+ using T = std::remove_pointer_t<decltype(s)>;
+ if constexpr (std::is_same_v<T, QAbstractSocket>) {
+ return s->peerName();
+ } else if constexpr (std::is_same_v<T, QLocalSocket>) {
+ return s->serverName();
+ }
+ Q_UNREACHABLE();
+ };
+ return visit(getPeerName, device);
+}
+} // namespace QSocketAbstraction
+
+QT_END_NAMESPACE
+
+#endif // QSOCKETABSTRACTION_P_H
diff --git a/src/network/android/jar/CMakeLists.txt b/src/network/android/jar/CMakeLists.txt
index d4dd9d3d89..b5172b6aba 100644
--- a/src/network/android/jar/CMakeLists.txt
+++ b/src/network/android/jar/CMakeLists.txt
@@ -1,20 +1,19 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
-# Generated from jar.pro.
-
set(java_sources
src/org/qtproject/qt/android/network/QtNetwork.java
)
-qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetwork # special case
+qt_internal_add_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetwork
INCLUDE_JARS ${QT_ANDROID_JAR}
SOURCES ${java_sources}
OUTPUT_DIR "${QT_BUILD_DIR}/jar"
)
-install_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetwork # special case
- DESTINATION jar
+qt_path_join(destination ${INSTALL_DATADIR} "jar")
+
+install_jar(Qt${QtBase_VERSION_MAJOR}AndroidNetwork
+ DESTINATION ${destination}
COMPONENT Devel
)
-
diff --git a/src/network/android/jar/build.gradle b/src/network/android/jar/build.gradle
index c947852f79..ea6d06c257 100644
--- a/src/network/android/jar/build.gradle
+++ b/src/network/android/jar/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:7.0.2'
+ classpath 'com.android.tools.build:gradle:8.4.0'
}
}
@@ -23,12 +23,10 @@ repositories {
}
android {
- compileSdkVersion 31
- buildToolsVersion "31.0.3"
+ compileSdk 34
defaultConfig {
- minSdkVersion 23
- targetSdkVersion 31
+ minSdkVersion 28
}
sourceSets {
diff --git a/src/network/compat/removed_api.cpp b/src/network/compat/removed_api.cpp
new file mode 100644
index 0000000000..ceda117538
--- /dev/null
+++ b/src/network/compat/removed_api.cpp
@@ -0,0 +1,70 @@
+// Copyright (c) 2023 LLC «V Kontakte»
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#define QT_NETWORK_BUILD_REMOVED_API
+
+#include "qtnetworkglobal.h"
+
+QT_USE_NAMESPACE
+
+#if QT_NETWORK_REMOVED_SINCE(6, 7)
+
+#include "qhostinfo.h"
+
+// static
+int QHostInfo::lookupHost(const QString &name, QObject *receiver, const char *member)
+{
+ const auto *r = receiver;
+ return lookupHost(name, r, member);
+}
+
+
+#include "qnetworkreply.h"
+
+QByteArray QNetworkReply::rawHeader(const QByteArray &headerName) const
+{
+ return rawHeader(qToByteArrayViewIgnoringNull(headerName));
+}
+
+bool QNetworkReply::hasRawHeader(const QByteArray &headerName) const
+{
+ return hasRawHeader(qToByteArrayViewIgnoringNull(headerName));
+}
+
+#include "qnetworkrequest.h"
+
+QByteArray QNetworkRequest::rawHeader(const QByteArray &headerName) const
+{
+ return rawHeader(qToByteArrayViewIgnoringNull(headerName));
+}
+
+bool QNetworkRequest::hasRawHeader(const QByteArray &headerName) const
+{
+ return hasRawHeader(qToByteArrayViewIgnoringNull(headerName));
+}
+
+#include "qnetworkcookie.h"
+
+QList<QNetworkCookie> QNetworkCookie::parseCookies(const QByteArray &cookieString)
+{
+ return parseCookies(qToByteArrayViewIgnoringNull(cookieString));
+}
+
+// #include "qotherheader.h"
+// // implement removed functions from qotherheader.h
+// order sections alphabetically
+
+#endif // QT_NETWORK_REMOVED_SINCE(6, 7)
+
+#if QT_NETWORK_REMOVED_SINCE(6, 8)
+
+#if QT_CONFIG(dnslookup)
+# include "qdnslookup.h" // inlined API
+#endif
+#include "qnetworkrequest.h" // inlined API
+
+// #include "qotherheader.h"
+// // implement removed functions from qotherheader.h
+// order sections alphabetically
+
+#endif // QT_NETWORK_REMOVED_SINCE(6, 8)
diff --git a/src/network/configure.cmake b/src/network/configure.cmake
index 6afa139058..cda444b873 100644
--- a/src/network/configure.cmake
+++ b/src/network/configure.cmake
@@ -12,7 +12,7 @@ qt_find_package(Libproxy PROVIDED_TARGETS PkgConfig::Libproxy MODULE_NAME networ
qt_find_package(GSSAPI PROVIDED_TARGETS GSSAPI::GSSAPI MODULE_NAME network QMAKE_LIB gssapi)
qt_find_package(GLIB2 OPTIONAL_COMPONENTS GOBJECT PROVIDED_TARGETS GLIB2::GOBJECT MODULE_NAME core QMAKE_LIB gobject)
qt_find_package(GLIB2 OPTIONAL_COMPONENTS GIO PROVIDED_TARGETS GLIB2::GIO MODULE_NAME core QMAKE_LIB gio)
-
+qt_find_package(WrapResolv PROVIDED_TARGETS WrapResolv::WrapResolv MODULE_NAME network QMAKE_LIB libresolv)
#### Tests
@@ -37,22 +37,6 @@ freeifaddrs(list);
"# FIXME: use: unmapped library: network
)
-# ifr_index
-qt_config_compile_test(ifr_index
- LABEL "ifr_index"
- CODE
-"#include <net/if.h>
-
-int main(void)
-{
- /* BEGIN TEST: */
-struct ifreq req;
-req.ifr_index = 0;
- /* END TEST: */
- return 0;
-}
-")
-
# ipv6ifname
qt_config_compile_test(ipv6ifname
LABEL "IPv6 ifname"
@@ -100,6 +84,25 @@ ci.ifa_prefered = ci.ifa_valid = 0;
}
")
+# res_setserver
+qt_config_compile_test(res_setservers
+ LABEL "res_setservers()"
+ LIBRARIES
+ WrapResolv::WrapResolv
+ CODE
+"#include <sys/types.h>
+#include <netinet/in.h>
+#include <resolv.h>
+int main()
+{
+ union res_sockaddr_union sa;
+ res_state s = nullptr;
+ res_setservers(s, &sa, 1);
+ return 0;
+}
+"
+)
+
# sctp
qt_config_compile_test(sctp
LABEL "SCTP support"
@@ -190,18 +193,19 @@ connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents, &con
qt_feature("getifaddrs" PUBLIC
LABEL "getifaddrs()"
- CONDITION TEST_getifaddrs
+ CONDITION VXWORKS OR UNIX AND NOT QT_FEATURE_linux_netlink AND TEST_getifaddrs
)
qt_feature_definition("getifaddrs" "QT_NO_GETIFADDRS" NEGATE VALUE "1")
-qt_feature("ifr_index" PRIVATE
- LABEL "ifr_index"
- CONDITION TEST_ifr_index
-)
qt_feature("ipv6ifname" PUBLIC
LABEL "IPv6 ifname"
- CONDITION TEST_ipv6ifname
+ CONDITION VXWORKS OR UNIX AND NOT QT_FEATURE_linux_netlink AND TEST_ipv6ifname
)
qt_feature_definition("ipv6ifname" "QT_NO_IPV6IFNAME" NEGATE VALUE "1")
+qt_feature("libresolv" PRIVATE
+ LABEL "libresolv"
+ CONDITION WrapResolv_FOUND
+ AUTODETECT UNIX
+)
qt_feature("libproxy" PRIVATE
LABEL "libproxy"
AUTODETECT OFF
@@ -211,6 +215,10 @@ qt_feature("linux-netlink" PRIVATE
LABEL "Linux AF_NETLINK"
CONDITION LINUX AND NOT ANDROID AND TEST_linux_netlink
)
+qt_feature("res_setservers" PRIVATE
+ LABEL "res_setservers()"
+ CONDITION QT_FEATURE_libresolv AND TEST_res_setservers
+)
qt_feature("securetransport" PUBLIC
LABEL "SecureTransport"
CONDITION APPLE
@@ -307,7 +315,7 @@ qt_feature("dnslookup" PUBLIC
SECTION "Networking"
LABEL "QDnsLookup"
PURPOSE "Provides API for DNS lookups."
- CONDITION NOT INTEGRITY
+ CONDITION QT_FEATURE_thread AND NOT INTEGRITY
)
qt_feature("gssapi" PUBLIC
SECTION "Networking"
@@ -331,8 +339,9 @@ qt_feature("networklistmanager" PRIVATE
)
qt_feature("topleveldomain" PUBLIC
SECTION "Networking"
- LABEL "qTopLevelDomain()"
- PURPOSE "Provides support for extracting the top level domain from URLs. If enabled, a binary dump of the Public Suffix List (http://www.publicsuffix.org, Mozilla License) is included. The data is then also used in QNetworkCookieJar::validateCookie."
+ LABEL "qIsEffectiveTLD()"
+ PURPOSE "Provides support for checking if a domain is a top level domain. If enabled, a binary dump of the Public Suffix List (http://www.publicsuffix.org, Mozilla License) is included. The data is used in QNetworkCookieJar."
+ AUTODETECT NOT WASM
DISABLE INPUT_publicsuffix STREQUAL "no"
)
qt_feature("publicsuffix-qt" PRIVATE
diff --git a/src/network/doc/images/network-examples.png b/src/network/doc/images/network-examples.png
deleted file mode 100644
index 15dfba850a..0000000000
--- a/src/network/doc/images/network-examples.png
+++ /dev/null
Binary files differ
diff --git a/src/network/doc/images/network-examples.webp b/src/network/doc/images/network-examples.webp
new file mode 100644
index 0000000000..4f6269ec8b
--- /dev/null
+++ b/src/network/doc/images/network-examples.webp
Binary files differ
diff --git a/src/network/doc/qtnetwork.qdocconf b/src/network/doc/qtnetwork.qdocconf
index ad6f5d2850..1e1162bdac 100644
--- a/src/network/doc/qtnetwork.qdocconf
+++ b/src/network/doc/qtnetwork.qdocconf
@@ -23,7 +23,7 @@ qhp.QtNetwork.subprojects.classes.sortPages = true
tagfile = ../../../doc/qtnetwork/qtnetwork.tags
-depends += qtcore qtgui qtdoc qmake qtcmake
+depends += qtcore qtgui qtdoc qmake qtcmake qtwidgets
headerdirs += ..
@@ -40,5 +40,12 @@ imagedirs += images \
navigation.landingpage = "Qt Network"
navigation.cppclassespage = "Qt Network C++ Classes"
-# Fail the documentation build if there are more warnings than the limit
+manifestmeta.highlighted.names = \
+ "QtNetwork/Fortune Client" \
+ "QtNetwork/Fortune Server" \
+ "QtNetwork/HTTP Client" \
+ "QtNetwork/Secure Socket Client" \
+ "QtNetwork/Torrent Example"
+
+# Enforce zero documentation warnings
warninglimit = 0
diff --git a/src/network/doc/snippets/CMakeLists.txt b/src/network/doc/snippets/CMakeLists.txt
index 97574156ad..05e5668e24 100644
--- a/src/network/doc/snippets/CMakeLists.txt
+++ b/src/network/doc/snippets/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
cmake_minimum_required(VERSION 3.16)
diff --git a/src/network/doc/snippets/code/doc_src_qtnetwork.cpp b/src/network/doc/snippets/code/doc_src_qtnetwork.cpp
deleted file mode 100644
index dae56d5db5..0000000000
--- a/src/network/doc/snippets/code/doc_src_qtnetwork.cpp
+++ /dev/null
@@ -1,6 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
-
-//! [1]
-#include <QtNetwork>
-//! [1]
diff --git a/src/network/doc/snippets/code/src_network_access_qhttpmultipart.cpp b/src/network/doc/snippets/code/src_network_access_qhttpmultipart.cpp
index cf51c68861..73308b5547 100644
--- a/src/network/doc/snippets/code/src_network_access_qhttpmultipart.cpp
+++ b/src/network/doc/snippets/code/src_network_access_qhttpmultipart.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
diff --git a/src/network/doc/snippets/code/src_network_access_qhttppart.cpp b/src/network/doc/snippets/code/src_network_access_qhttppart.cpp
index 38d1b7aff0..059a4c60e9 100644
--- a/src/network/doc/snippets/code/src_network_access_qhttppart.cpp
+++ b/src/network/doc/snippets/code/src_network_access_qhttppart.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
Content-Type: text/plain
diff --git a/src/network/doc/snippets/code/src_network_access_qnetworkdiskcache.cpp b/src/network/doc/snippets/code/src_network_access_qnetworkdiskcache.cpp
index f830439124..1087f52035 100644
--- a/src/network/doc/snippets/code/src_network_access_qnetworkdiskcache.cpp
+++ b/src/network/doc/snippets/code/src_network_access_qnetworkdiskcache.cpp
@@ -4,17 +4,20 @@
//! [0]
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
QNetworkDiskCache *diskCache = new QNetworkDiskCache(this);
-diskCache->setCacheDirectory("cacheDir");
+QString directory = QStandardPaths::writableLocation(QStandardPaths::CacheLocation)
+ + QLatin1StringView("/cacheDir/");
+diskCache->setCacheDirectory(directory);
manager->setCache(diskCache);
//! [0]
//! [1]
+using namespace Qt::StringLiterals;
// do a normal request (preferred from network, as this is the default)
-QNetworkRequest request(QUrl(QString("http://qt-project.org")));
+QNetworkRequest request(QUrl(u"http://qt-project.org"_s));
manager->get(request);
// do a request preferred from cache
-QNetworkRequest request2(QUrl(QString("http://qt-project.org")));
+QNetworkRequest request2(QUrl(u"http://qt-project.org"_s));
request2.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
manager->get(request2);
//! [1]
diff --git a/src/network/doc/snippets/code/src_network_access_qnetworkrequestfactory.cpp b/src/network/doc/snippets/code/src_network_access_qnetworkrequestfactory.cpp
new file mode 100644
index 0000000000..f7994d442c
--- /dev/null
+++ b/src/network/doc/snippets/code/src_network_access_qnetworkrequestfactory.cpp
@@ -0,0 +1,27 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+using namespace Qt::StringLiterals;
+
+//! [0]
+// Instantiate a factory somewhere suitable in the application
+QNetworkRequestFactory api{{"https://example.com/v1"_L1}};
+
+// Set bearer token
+api.setBearerToken("my_token");
+
+// Issue requests (reply handling omitted for brevity)
+manager.get(api.createRequest("models"_L1)); // https://example.com/v1/models
+// The conventional leading '/' for the path can be used as well
+manager.get(api.createRequest("/models"_L1)); // https://example.com/v1/models
+//! [0]
+
+
+//! [1]
+// Here the API version v2 is used as the base path:
+QNetworkRequestFactory api{{"https://example.com/v2"_L1}};
+// ...
+manager.get(api.createRequest("models"_L1)); // https://example.com/v2/models
+// Equivalent with a leading '/'
+manager.get(api.createRequest("/models"_L1)); // https://example.com/v2/models
+//! [1]
+
diff --git a/src/network/doc/snippets/code/src_network_access_qrestaccessmanager.cpp b/src/network/doc/snippets/code/src_network_access_qrestaccessmanager.cpp
new file mode 100644
index 0000000000..8f9e00f4b6
--- /dev/null
+++ b/src/network/doc/snippets/code/src_network_access_qrestaccessmanager.cpp
@@ -0,0 +1,104 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+//! [0]
+QNetworkReply *reply = manager->get(request);
+QObject::connect(reply, &QNetworkReply::finished, this, [reply]() {
+ // The reply may be wrapped in the finish handler:
+ QRestReply restReply(reply);
+ if (restReply.isSuccess())
+ // ...
+});
+//! [0]
+
+
+//! [1]
+// With lambda
+manager->get(request, this, [this](QRestReply &reply) {
+ if (reply.isSuccess()) {
+ // ...
+ }
+});
+// With member function
+manager->get(request, this, &MyClass::handleFinished);
+//! [1]
+
+
+//! [2]
+QJsonDocument myJson;
+// ...
+manager->post(request, myJson, this, [this](QRestReply &reply) {
+ if (!reply.isSuccess()) {
+ // ...
+ }
+ if (std::optional json = reply.readJson()) {
+ // use *json
+ }
+});
+//! [2]
+
+
+//! [3]
+manager->get(request, this, [this](QRestReply &reply) {
+ if (!reply.isSuccess())
+ // handle error
+ if (std::optional json = reply.readJson())
+ // use *json
+});
+//! [3]
+
+
+//! [4]
+manager->get(request, myData, this, [this](QRestReply &reply) {
+ if (reply.isSuccess())
+ // ...
+});
+//! [4]
+
+
+//! [5]
+manager->post(request, myData, this, [this](QRestReply &reply) {
+ if (reply.isSuccess())
+ // ...
+});
+//! [5]
+
+
+//! [6]
+manager->put(request, myData, this, [this](QRestReply &reply) {
+ if (reply.isSuccess())
+ // ...
+});
+//! [6]
+
+
+//! [7]
+manager->head(request, this, [this](QRestReply &reply) {
+ if (reply.isSuccess())
+ // ...
+});
+//! [7]
+
+
+//! [8]
+manager->deleteResource(request, this, [this](QRestReply &reply) {
+ if (reply.isSuccess())
+ // ...
+});
+//! [8]
+
+
+//! [9]
+manager->sendCustomRequest(request, "MYMETHOD", myData, this, [this](QRestReply &reply) {
+ if (reply.isSuccess())
+ // ...
+});
+//! [9]
+
+
+//! [10]
+manager->patch(request, myData, this, [this](QRestReply &reply) {
+ if (reply.isSuccess())
+ // ...
+});
+//! [10]
diff --git a/src/network/doc/snippets/code/src_network_kernel_qdnslookup.cpp b/src/network/doc/snippets/code/src_network_kernel_qdnslookup.cpp
index fdcc8b0ce4..ce976001bb 100644
--- a/src/network/doc/snippets/code/src_network_kernel_qdnslookup.cpp
+++ b/src/network/doc/snippets/code/src_network_kernel_qdnslookup.cpp
@@ -6,8 +6,7 @@ void MyObject::lookupServers()
{
// Create a DNS lookup.
dns = new QDnsLookup(this);
- connect(dns, SIGNAL(finished()),
- this, SLOT(handleServers()));
+ connect(dns, &QDnsLookup::finished, this, &MyObject::handleServers);
// Find the XMPP servers for gmail.com
dns->setType(QDnsLookup::SRV);
diff --git a/src/network/doc/snippets/code/src_network_kernel_qhostinfo.cpp b/src/network/doc/snippets/code/src_network_kernel_qhostinfo.cpp
index b845e5bcc7..7c39827b94 100644
--- a/src/network/doc/snippets/code/src_network_kernel_qhostinfo.cpp
+++ b/src/network/doc/snippets/code/src_network_kernel_qhostinfo.cpp
@@ -3,12 +3,10 @@
//! [0]
// To find the IP address of qt-project.org
-QHostInfo::lookupHost("qt-project.org",
- this, SLOT(printResults(QHostInfo)));
+QHostInfo::lookupHost("qt-project.org", this, &MyWidget::printResults);
// To find the host name for 4.2.2.1
-QHostInfo::lookupHost("4.2.2.1",
- this, SLOT(printResults(QHostInfo)));
+QHostInfo::lookupHost("4.2.2.1", this, &MyWidget::printResults);
//! [0]
@@ -18,8 +16,7 @@ QHostInfo info = QHostInfo::fromName("qt-project.org");
//! [2]
-QHostInfo::lookupHost("www.kde.org",
- this, SLOT(lookedUp(QHostInfo)));
+QHostInfo::lookupHost("www.kde.org", this, &MyWidget::lookedUp);
//! [2]
@@ -39,8 +36,7 @@ void MyWidget::lookedUp(const QHostInfo &host)
//! [4]
-QHostInfo::lookupHost("4.2.2.1",
- this, SLOT(lookedUp(QHostInfo)));
+QHostInfo::lookupHost("4.2.2.1", this, &MyWidget::lookedUp);
//! [4]
diff --git a/src/network/doc/snippets/code/src_network_kernel_qnetworkinterface.cpp b/src/network/doc/snippets/code/src_network_kernel_qnetworkinterface.cpp
index 4f3839532d..d34750aaaf 100644
--- a/src/network/doc/snippets/code/src_network_kernel_qnetworkinterface.cpp
+++ b/src/network/doc/snippets/code/src_network_kernel_qnetworkinterface.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
QNetworkInterface::interfaceFromName(name).index()
diff --git a/src/network/doc/snippets/code/src_network_ssl_qsslconfiguration.cpp b/src/network/doc/snippets/code/src_network_ssl_qsslconfiguration.cpp
index edc542ac3f..d9a493d1ce 100644
--- a/src/network/doc/snippets/code/src_network_ssl_qsslconfiguration.cpp
+++ b/src/network/doc/snippets/code/src_network_ssl_qsslconfiguration.cpp
@@ -6,10 +6,3 @@ QSslConfiguration config = sslSocket.sslConfiguration();
config.setProtocol(QSsl::TlsV1_2);
sslSocket.setSslConfiguration(config);
//! [0]
-
-
-//! [1]
-QSslConfiguration tlsConfig = QSslConfiguration::defaultConfiguration();
-tlsConfig.setCiphers(QStringLiteral("DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-SHA"));
-//! [1]
-
diff --git a/src/network/doc/snippets/code/src_network_ssl_qsslpresharedkeyauthenticator.cpp b/src/network/doc/snippets/code/src_network_ssl_qsslpresharedkeyauthenticator.cpp
index 1af356ee00..b3242e057d 100644
--- a/src/network/doc/snippets/code/src_network_ssl_qsslpresharedkeyauthenticator.cpp
+++ b/src/network/doc/snippets/code/src_network_ssl_qsslpresharedkeyauthenticator.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2018 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
//! [0]
connect(socket, &QSslSocket::preSharedKeyAuthenticationRequired,
diff --git a/src/network/doc/snippets/code/src_network_ssl_qsslsocket.cpp b/src/network/doc/snippets/code/src_network_ssl_qsslsocket.cpp
index 6ad795fcb9..eed032589e 100644
--- a/src/network/doc/snippets/code/src_network_ssl_qsslsocket.cpp
+++ b/src/network/doc/snippets/code/src_network_ssl_qsslsocket.cpp
@@ -5,7 +5,7 @@ using namespace Qt::StringLiterals;
//! [0]
QSslSocket *socket = new QSslSocket(this);
-connect(socket, SIGNAL(encrypted()), this, SLOT(ready()));
+connect(socket, &QSslSocket::encrypted, this, &Receiver::ready);
socket->connectToHostEncrypted("imap.example.com", 993);
//! [0]
@@ -42,19 +42,13 @@ while (socket.waitForReadyRead())
//! [3]
QSslSocket socket;
-connect(&socket, SIGNAL(encrypted()), receiver, SLOT(socketEncrypted()));
+connect(&socket, &QSslSocket::encrypted, receiver, &Receiver::socketEncrypted);
socket.connectToHostEncrypted("imap", 993);
socket->write("1 CAPABILITY\r\n");
//! [3]
-//! [4]
-QSslSocket socket;
-socket.setCiphers("DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:AES256-SHA");
-//! [4]
-
-
//! [5]
socket->connectToHostEncrypted("imap", 993);
if (socket->waitForEncrypted(1000))
diff --git a/src/network/doc/snippets/network/CMakeLists.txt b/src/network/doc/snippets/network/CMakeLists.txt
index be403e79b1..b75aeafea0 100644
--- a/src/network/doc/snippets/network/CMakeLists.txt
+++ b/src/network/doc/snippets/network/CMakeLists.txt
@@ -1,5 +1,5 @@
# Copyright (C) 2022 The Qt Company Ltd.
-# SPDX-License-Identifier: BSD-3-Clause
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
add_library(network_cppsnippets OBJECT tcpwait.cpp)
diff --git a/src/network/doc/src/examples.qdoc b/src/network/doc/src/examples.qdoc
index 1eb397d011..ee9084c74c 100644
--- a/src/network/doc/src/examples.qdoc
+++ b/src/network/doc/src/examples.qdoc
@@ -7,7 +7,7 @@
\title Network Examples
\brief How to do network programming in Qt.
- \image network-examples.png
+ \image network-examples.webp
Qt is provided with an extensive set of network classes to support both
client-based and server side network programming.
@@ -22,18 +22,12 @@
\li \l{network/blockingfortuneclient}{Blocking Fortune Client}\raisedaster
\li \l{network/broadcastreceiver}{Broadcast Receiver}
\li \l{network/broadcastsender}{Broadcast Sender}
- \li \l{network/download}{Download}
- \li \l{network/downloadmanager}{Download Manager}
\li \l{network/network-chat}{Network Chat}
\li \l{network/fortuneclient}{Fortune Client}\raisedaster
\li \l{network/fortuneserver}{Fortune Server}\raisedaster
\li \l{network/http}{HTTP}
- \li \l{network/loopback}{Loopback}
\li \l{network/threadedfortuneserver}{Threaded Fortune Server}\raisedaster
\li \l{network/torrent}{Torrent}
- \li \l{network/googlesuggest}{Google Suggest}
- \li \l{network/bearercloud}{Bearer Cloud}\raisedaster
- \li \l{network/bearermonitor}{Bearer Monitor}
\li \l{network/securesocketclient}{Secure Socket Client}
\li \l{network/multicastreceiver}{Multicast Receiver}
\li \l{network/multicastsender}{Multicast Sender}
diff --git a/src/network/doc/src/ssl.qdoc b/src/network/doc/src/ssl.qdoc
index ba13c97cfc..83549f61e8 100644
--- a/src/network/doc/src/ssl.qdoc
+++ b/src/network/doc/src/ssl.qdoc
@@ -16,6 +16,8 @@
From Qt version 5.15 onward, the officially supported version for OpenSSL
is 1.1.1 or later.
+ Qt version 5.15.1 onward is also compatible with OpenSSL 3.
+
\annotatedlist ssl
For Android applications see \l{Adding OpenSSL Support for Android}.
diff --git a/src/network/kernel/qauthenticator.cpp b/src/network/kernel/qauthenticator.cpp
index 91660cf2f7..e42450d7e5 100644
--- a/src/network/kernel/qauthenticator.cpp
+++ b/src/network/kernel/qauthenticator.cpp
@@ -14,6 +14,7 @@
#include <qstring.h>
#include <qdatetime.h>
#include <qrandom.h>
+#include <QtNetwork/qhttpheaders.h>
#ifdef Q_OS_WIN
#include <qmutex.h>
@@ -444,13 +445,14 @@ static bool verifyDigestMD5(QByteArrayView value)
return true; // assume it's ok if algorithm is not specified
}
-void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByteArray>> &values,
+void QAuthenticatorPrivate::parseHttpResponse(const QHttpHeaders &headers,
bool isProxy, QStringView host)
{
#if !QT_CONFIG(gssapi)
Q_UNUSED(host);
#endif
- const char *search = isProxy ? "proxy-authenticate" : "www-authenticate";
+ const auto search = isProxy ? QHttpHeaders::WellKnownHeader::ProxyAuthenticate
+ : QHttpHeaders::WellKnownHeader::WWWAuthenticate;
method = None;
/*
@@ -463,26 +465,23 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt
authentication parameters.
*/
- QByteArray headerVal;
- for (int i = 0; i < values.size(); ++i) {
- const QPair<QByteArray, QByteArray> &current = values.at(i);
- if (current.first.compare(search, Qt::CaseInsensitive) != 0)
- continue;
- QByteArray str = current.second.toLower();
- if (method < Basic && str.startsWith("basic")) {
+ QByteArrayView headerVal;
+ for (const auto &current : headers.values(search)) {
+ const QLatin1StringView str(current);
+ if (method < Basic && str.startsWith("basic"_L1, Qt::CaseInsensitive)) {
method = Basic;
- headerVal = current.second.mid(6);
- } else if (method < Ntlm && str.startsWith("ntlm")) {
+ headerVal = QByteArrayView(current).mid(6);
+ } else if (method < Ntlm && str.startsWith("ntlm"_L1, Qt::CaseInsensitive)) {
method = Ntlm;
- headerVal = current.second.mid(5);
- } else if (method < DigestMd5 && str.startsWith("digest")) {
+ headerVal = QByteArrayView(current).mid(5);
+ } else if (method < DigestMd5 && str.startsWith("digest"_L1, Qt::CaseInsensitive)) {
// Make sure the algorithm is actually MD5 before committing to it:
- if (!verifyDigestMD5(QByteArrayView(current.second).sliced(7)))
+ if (!verifyDigestMD5(QByteArrayView(current).sliced(7)))
continue;
method = DigestMd5;
- headerVal = current.second.mid(7);
- } else if (method < Negotiate && str.startsWith("negotiate")) {
+ headerVal = QByteArrayView(current).mid(7);
+ } else if (method < Negotiate && str.startsWith("negotiate"_L1, Qt::CaseInsensitive)) {
#if QT_CONFIG(sspi) || QT_CONFIG(gssapi) // if it's not supported then we shouldn't try to use it
#if QT_CONFIG(gssapi)
// For GSSAPI there needs to be a KDC set up for the host (afaict).
@@ -492,14 +491,14 @@ void QAuthenticatorPrivate::parseHttpResponse(const QList<QPair<QByteArray, QByt
continue;
#endif
method = Negotiate;
- headerVal = current.second.mid(10);
+ headerVal = QByteArrayView(current).mid(10);
#endif
}
}
// Reparse credentials since we know the method now
updateCredentials();
- challenge = headerVal.trimmed();
+ challenge = headerVal.trimmed().toByteArray();
QHash<QByteArray, QByteArray> options = parseDigestAuthenticationChallenge(challenge);
// Sets phase to Start if this updates our realm and sets the two locations where we store
@@ -547,16 +546,14 @@ QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod
Q_UNUSED(host);
#endif
QByteArray response;
- const char* methodString = nullptr;
+ QByteArrayView methodString;
switch(method) {
case QAuthenticatorPrivate::None:
- methodString = "";
phase = Done;
break;
case QAuthenticatorPrivate::Basic:
methodString = "Basic";
- response = user.toLatin1() + ':' + password.toLatin1();
- response = response.toBase64();
+ response = (user + ':'_L1 + password).toLatin1().toBase64();
phase = Done;
break;
case QAuthenticatorPrivate::DigestMd5:
@@ -645,12 +642,21 @@ QByteArray QAuthenticatorPrivate::calculateResponse(QByteArrayView requestMethod
break;
}
- return QByteArray::fromRawData(methodString, qstrlen(methodString)) + ' ' + response;
+ return methodString + ' ' + response;
}
// ---------------------------- Digest Md5 code ----------------------------------------
+static bool containsAuth(QByteArrayView data)
+{
+ for (auto element : QLatin1StringView(data).tokenize(','_L1)) {
+ if (element == "auth"_L1)
+ return true;
+ }
+ return false;
+}
+
QHash<QByteArray, QByteArray>
QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challenge)
{
@@ -664,7 +670,7 @@ QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challen
const char *start = d;
while (d < end && *d != '=')
++d;
- QByteArray key = QByteArray(start, d - start);
+ QByteArrayView key = QByteArrayView(start, d - start);
++d;
if (d >= end)
break;
@@ -695,13 +701,12 @@ QAuthenticatorPrivate::parseDigestAuthenticationChallenge(QByteArrayView challen
while (d < end && *d != ',')
++d;
++d;
- options[key] = value;
+ options[key.toByteArray()] = std::move(value);
}
QByteArray qop = options.value("qop");
if (!qop.isEmpty()) {
- QList<QByteArray> qopoptions = qop.split(',');
- if (!qopoptions.contains("auth"))
+ if (!containsAuth(qop))
return QHash<QByteArray, QByteArray>();
// #### can't do auth-int currently
// if (qop.contains("auth-int"))
diff --git a/src/network/kernel/qauthenticator.h b/src/network/kernel/qauthenticator.h
index 4d8e85c000..a05d359e93 100644
--- a/src/network/kernel/qauthenticator.h
+++ b/src/network/kernel/qauthenticator.h
@@ -16,6 +16,7 @@ class QUrl;
class Q_NETWORK_EXPORT QAuthenticator
{
+ Q_GADGET
public:
QAuthenticator();
~QAuthenticator();
diff --git a/src/network/kernel/qauthenticator_p.h b/src/network/kernel/qauthenticator_p.h
index f8579a46c0..bc16139941 100644
--- a/src/network/kernel/qauthenticator_p.h
+++ b/src/network/kernel/qauthenticator_p.h
@@ -26,13 +26,14 @@
QT_BEGIN_NAMESPACE
class QHttpResponseHeader;
+class QHttpHeaders;
#if QT_CONFIG(sspi) // SSPI
class QSSPIWindowsHandles;
#elif QT_CONFIG(gssapi) // GSSAPI
class QGssApiHandles;
#endif
-class Q_AUTOTEST_EXPORT QAuthenticatorPrivate
+class Q_NETWORK_EXPORT QAuthenticatorPrivate
{
public:
enum Method { None, Basic, Negotiate, Ntlm, DigestMd5, };
@@ -80,8 +81,7 @@ public:
static QHash<QByteArray, QByteArray>
parseDigestAuthenticationChallenge(QByteArrayView challenge);
- void parseHttpResponse(const QList<QPair<QByteArray, QByteArray>> &, bool isProxy,
- QStringView host);
+ void parseHttpResponse(const QHttpHeaders &headers, bool isProxy, QStringView host);
void updateCredentials();
static bool isMethodSupported(QByteArrayView method);
diff --git a/src/network/kernel/qdnslookup.cpp b/src/network/kernel/qdnslookup.cpp
index fb8a96a049..1b4db7130b 100644
--- a/src/network/kernel/qdnslookup.cpp
+++ b/src/network/kernel/qdnslookup.cpp
@@ -1,23 +1,43 @@
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+// Copyright (C) 2023 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qdnslookup.h"
#include "qdnslookup_p.h"
+#include <qapplicationstatic.h>
#include <qcoreapplication.h>
#include <qdatetime.h>
+#include <qendian.h>
+#include <qloggingcategory.h>
#include <qrandom.h>
+#include <qspan.h>
#include <qurl.h>
+#if QT_CONFIG(ssl)
+# include <qsslsocket.h>
+#endif
+
#include <algorithm>
QT_BEGIN_NAMESPACE
-QT_IMPL_METATYPE_EXTERN(QDnsLookupReply)
+using namespace Qt::StringLiterals;
-#if QT_CONFIG(thread)
-Q_GLOBAL_STATIC(QDnsLookupThreadPool, theDnsLookupThreadPool);
-#endif
+static Q_LOGGING_CATEGORY(lcDnsLookup, "qt.network.dnslookup", QtCriticalMsg)
+
+namespace {
+struct QDnsLookupThreadPool : QThreadPool
+{
+ QDnsLookupThreadPool()
+ {
+ // Run up to 5 lookups in parallel.
+ setMaxThreadCount(5);
+ }
+};
+}
+
+Q_APPLICATION_STATIC(QDnsLookupThreadPool, theDnsLookupThreadPool);
static bool qt_qdnsmailexchangerecord_less_than(const QDnsMailExchangeRecord &r1, const QDnsMailExchangeRecord &r2)
{
@@ -121,9 +141,6 @@ static void qt_qdnsservicerecord_sort(QList<QDnsServiceRecord> &records)
}
}
-const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses =
- QT_TRANSLATE_NOOP("QDnsLookupRunnable", "IPv6 addresses for nameservers are currently not supported");
-
/*!
\class QDnsLookup
\brief The QDnsLookup class represents a DNS lookup.
@@ -150,6 +167,42 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses =
\note If you simply want to find the IP address(es) associated with a host
name, or the host name associated with an IP address you should use
QHostInfo instead.
+
+ \section1 DNS-over-TLS and Authentic Data
+
+ QDnsLookup supports DNS-over-TLS (DoT, as specified by \l{RFC 7858}) on
+ some platforms. That currently includes all Unix platforms where regular
+ queries are supported, if \l QSslSocket support is present in Qt. To query
+ if support is present at runtime, use isProtocolSupported().
+
+ When using DNS-over-TLS, QDnsLookup only implements the "Opportunistic
+ Privacy Profile" method of authentication, as described in \l{RFC 7858}
+ section 4.1. In this mode, QDnsLookup (through \l QSslSocket) only
+ validates that the server presents a certificate that is valid for the
+ server being connected to. Clients may use setSslConfiguration() to impose
+ additional restrictions and sslConfiguration() to obtain information after
+ the query is complete.
+
+ QDnsLookup will request DNS servers queried over TLS to perform
+ authentication on the data they return. If they confirm the data is valid,
+ the \l authenticData property will be set to true. QDnsLookup does not
+ verify the integrity of the data by itself, so applications should only
+ trust this property on servers they have confirmed through other means to
+ be trustworthy.
+
+ \section2 Authentic Data without TLS
+
+ QDnsLookup request Authentic Data for any server set with setNameserver(),
+ even if TLS encryption is not required. This is useful when querying a
+ caching nameserver on the same host as the application or on a trusted
+ network. Though similar to the TLS case, the application is responsible for
+ determining if the server it chose to use is trustworthy, and if the
+ unencrypted connection cannot be tampered with.
+
+ QDnsLookup obeys the system configuration to request Authentic Data on the
+ default nameserver (that is, if setNameserver() is not called). This is
+ currently only supported on Linux systems using glibc 2.31 or later. On any
+ other systems, QDnsLookup will ignore the AD bit in the query header.
*/
/*!
@@ -178,6 +231,9 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses =
\value NotFoundError the requested domain name does not exist
(NXDOMAIN).
+
+ \value TimeoutError the server was not reached or did not reply
+ in time (since 6.6).
*/
/*!
@@ -201,10 +257,72 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses =
\value SRV service records.
+ \value[since 6.8] TLSA TLS association records.
+
\value TXT text records.
*/
/*!
+ \enum QDnsLookup::Protocol
+
+ Indicates the type of DNS server that is being queried.
+
+ \value Standard
+ Regular, unencrypted DNS, using UDP and falling back to TCP as necessary
+ (default port: 53)
+
+ \value DnsOverTls
+ Encrypted DNS over TLS (DoT, as specified by \l{RFC 7858}), over TCP
+ (default port: 853)
+
+ \sa isProtocolSupported(), nameserverProtocol, setNameserver()
+*/
+
+/*!
+ \since 6.8
+
+ Returns true if DNS queries using \a protocol are supported with QDnsLookup.
+
+ \sa nameserverProtocol
+*/
+bool QDnsLookup::isProtocolSupported(Protocol protocol)
+{
+#if QT_CONFIG(libresolv) || defined(Q_OS_WIN)
+ switch (protocol) {
+ case QDnsLookup::Standard:
+ return true;
+ case QDnsLookup::DnsOverTls:
+# if QT_CONFIG(ssl)
+ if (QSslSocket::supportsSsl())
+ return true;
+# endif
+ return false;
+ }
+#else
+ Q_UNUSED(protocol)
+#endif
+ return false;
+}
+
+/*!
+ \since 6.8
+
+ Returns the standard (default) port number for the protocol \a protocol.
+
+ \sa isProtocolSupported()
+*/
+quint16 QDnsLookup::defaultPortForProtocol(Protocol protocol) noexcept
+{
+ switch (protocol) {
+ case QDnsLookup::Standard:
+ return DnsPort;
+ case QDnsLookup::DnsOverTls:
+ return DnsOverTlsPort;
+ }
+ return 0; // will probably fail somewhere
+}
+
+/*!
\fn void QDnsLookup::finished()
This signal is emitted when the reply has finished processing.
@@ -233,8 +351,8 @@ const char *QDnsLookupPrivate::msgNoIpV6NameServerAdresses =
QDnsLookup::QDnsLookup(QObject *parent)
: QObject(*new QDnsLookupPrivate, parent)
{
- qRegisterMetaType<QDnsLookupReply>();
}
+
/*!
Constructs a QDnsLookup object for the given \a type and \a name and sets
\a parent as the parent object.
@@ -244,7 +362,6 @@ QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent)
: QObject(*new QDnsLookupPrivate, parent)
{
Q_D(QDnsLookup);
- qRegisterMetaType<QDnsLookupReply>();
d->name = name;
d->type = type;
}
@@ -252,18 +369,65 @@ QDnsLookup::QDnsLookup(Type type, const QString &name, QObject *parent)
/*!
\fn QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent)
\since 5.4
- Constructs a QDnsLookup object for the given \a type, \a name and
- \a nameserver and sets \a parent as the parent object.
+
+ Constructs a QDnsLookup object to issue a query for \a name of record type
+ \a type, using the DNS server \a nameserver running on the default DNS port,
+ and sets \a parent as the parent object.
*/
QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent)
+ : QDnsLookup(type, name, nameserver, 0, parent)
+{
+}
+
+/*!
+ \fn QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port, QObject *parent)
+ \since 6.6
+
+ Constructs a QDnsLookup object to issue a query for \a name of record type
+ \a type, using the DNS server \a nameserver running on port \a port, and
+ sets \a parent as the parent object.
+
+//! [nameserver-port]
+ \note Setting the port number to any value other than the default (53) can
+ cause the name resolution to fail, depending on the operating system
+ limitations and firewalls, if the nameserverProtocol() to be used
+ QDnsLookup::Standard. Notably, the Windows API used by QDnsLookup is unable
+ to handle alternate port numbers.
+//! [nameserver-port]
+*/
+QDnsLookup::QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port, QObject *parent)
+ : QObject(*new QDnsLookupPrivate, parent)
+{
+ Q_D(QDnsLookup);
+ d->name = name;
+ d->type = type;
+ d->port = port;
+ d->nameserver = nameserver;
+}
+
+/*!
+ \since 6.8
+
+ Constructs a QDnsLookup object to issue a query for \a name of record type
+ \a type, using the DNS server \a nameserver running on port \a port, and
+ sets \a parent as the parent object.
+
+ The query will be sent using \a protocol, if supported. Use
+ isProtocolSupported() to check if it is supported.
+
+ \include qdnslookup.cpp nameserver-port
+*/
+QDnsLookup::QDnsLookup(Type type, const QString &name, Protocol protocol,
+ const QHostAddress &nameserver, quint16 port, QObject *parent)
: QObject(*new QDnsLookupPrivate, parent)
{
Q_D(QDnsLookup);
- qRegisterMetaType<QDnsLookupReply>();
d->name = name;
d->type = type;
d->nameserver = nameserver;
+ d->port = port;
+ d->protocol = protocol;
}
/*!
@@ -278,6 +442,28 @@ QDnsLookup::~QDnsLookup()
}
/*!
+ \since 6.8
+ \property QDnsLookup::authenticData
+ \brief whether the reply was authenticated by the resolver.
+
+ QDnsLookup does not perform the authentication itself. Instead, it trusts
+ the name server that was queried to perform the authentication and report
+ it. The application is responsible for determining if any servers it
+ configured with setNameserver() are trustworthy; if no server was set,
+ QDnsLookup obeys system configuration on whether responses should be
+ trusted.
+
+ This property may be set even if error() indicates a resolver error
+ occurred.
+
+ \sa setNameserver(), nameserverProtocol()
+*/
+bool QDnsLookup::isAuthenticData() const
+{
+ return d_func()->reply.authenticData;
+}
+
+/*!
\property QDnsLookup::error
\brief the type of error that occurred if the DNS lookup failed, or NoError.
*/
@@ -310,6 +496,10 @@ bool QDnsLookup::isFinished() const
\property QDnsLookup::name
\brief the name to lookup.
+ If the name to look up is empty, QDnsLookup will attempt to resolve the
+ root domain of DNS. That query is usually performed with QDnsLookup::type
+ set to \l{QDnsLookup::Type}{NS}.
+
\note The name will be encoded using IDNA, which means it's unsuitable for
querying SRV records compatible with the DNS-SD specification.
*/
@@ -376,6 +566,76 @@ QBindable<QHostAddress> QDnsLookup::bindableNameserver()
}
/*!
+ \property QDnsLookup::nameserverPort
+ \since 6.6
+ \brief the port number of nameserver to use for DNS lookup.
+
+ The value of 0 indicates that QDnsLookup should use the default port for
+ the nameserverProtocol().
+
+ \include qdnslookup.cpp nameserver-port
+*/
+
+quint16 QDnsLookup::nameserverPort() const
+{
+ return d_func()->port;
+}
+
+void QDnsLookup::setNameserverPort(quint16 nameserverPort)
+{
+ Q_D(QDnsLookup);
+ d->port = nameserverPort;
+}
+
+QBindable<quint16> QDnsLookup::bindableNameserverPort()
+{
+ Q_D(QDnsLookup);
+ return &d->port;
+}
+
+/*!
+ \property QDnsLookup::nameserverProtocol
+ \since 6.8
+ \brief the protocol to use when sending the DNS query
+
+ \sa isProtocolSupported()
+*/
+QDnsLookup::Protocol QDnsLookup::nameserverProtocol() const
+{
+ return d_func()->protocol;
+}
+
+void QDnsLookup::setNameserverProtocol(Protocol protocol)
+{
+ d_func()->protocol = protocol;
+}
+
+QBindable<QDnsLookup::Protocol> QDnsLookup::bindableNameserverProtocol()
+{
+ return &d_func()->protocol;
+}
+
+/*!
+ \fn void QDnsLookup::setNameserver(const QHostAddress &nameserver, quint16 port)
+ \since 6.6
+
+ Sets the nameserver to \a nameserver and the port to \a port.
+
+ \include qdnslookup.cpp nameserver-port
+
+ \sa QDnsLookup::nameserver, QDnsLookup::nameserverPort
+*/
+
+void QDnsLookup::setNameserver(Protocol protocol, const QHostAddress &nameserver, quint16 port)
+{
+ Qt::beginPropertyUpdateGroup();
+ setNameserver(nameserver);
+ setNameserverPort(port);
+ setNameserverProtocol(protocol);
+ Qt::endPropertyUpdateGroup();
+}
+
+/*!
Returns the list of canonical name records associated with this lookup.
*/
@@ -447,6 +707,46 @@ QList<QDnsTextRecord> QDnsLookup::textRecords() const
}
/*!
+ \since 6.8
+ Returns the list of TLS association records associated with this lookup.
+
+ According to the standards relating to DNS-based Authentication of Named
+ Entities (DANE), this field should be ignored and must not be used for
+ verifying the authentity of a given server if the authenticity of the DNS
+ reply cannot itself be confirmed. See isAuthenticData() for more
+ information.
+ */
+QList<QDnsTlsAssociationRecord> QDnsLookup::tlsAssociationRecords() const
+{
+ return d_func()->reply.tlsAssociationRecords;
+}
+
+#if QT_CONFIG(ssl)
+/*!
+ \since 6.8
+ Sets the \a sslConfiguration to use for outgoing DNS-over-TLS connections.
+
+ \sa sslConfiguration(), QSslSocket::setSslConfiguration()
+*/
+void QDnsLookup::setSslConfiguration(const QSslConfiguration &sslConfiguration)
+{
+ Q_D(QDnsLookup);
+ d->sslConfiguration.emplace(sslConfiguration);
+}
+
+/*!
+ Returns the current SSL configuration.
+
+ \sa setSslConfiguration()
+*/
+QSslConfiguration QDnsLookup::sslConfiguration() const
+{
+ const Q_D(QDnsLookup);
+ return d->sslConfiguration.value_or(QSslConfiguration::defaultConfiguration());
+}
+#endif
+
+/*!
Aborts the DNS lookup operation.
If the lookup is already finished, does nothing.
@@ -476,13 +776,32 @@ void QDnsLookup::lookup()
Q_D(QDnsLookup);
d->isFinished = false;
d->reply = QDnsLookupReply();
- d->runnable = new QDnsLookupRunnable(d->type, QUrl::toAce(d->name), d->nameserver);
- connect(d->runnable, SIGNAL(finished(QDnsLookupReply)),
- this, SLOT(_q_lookupFinished(QDnsLookupReply)),
- Qt::BlockingQueuedConnection);
-#if QT_CONFIG(thread)
- theDnsLookupThreadPool()->start(d->runnable);
+ if (!QCoreApplication::instance()) {
+ // NOT qCWarning because this isn't a result of the lookup
+ qWarning("QDnsLookup requires a QCoreApplication");
+ return;
+ }
+
+ auto l = [this](const QDnsLookupReply &reply) {
+ Q_D(QDnsLookup);
+ if (d->runnable == sender()) {
+#ifdef QDNSLOOKUP_DEBUG
+ qDebug("DNS reply for %s: %i (%s)", qPrintable(d->name), reply.error, qPrintable(reply.errorString));
#endif
+#if QT_CONFIG(ssl)
+ d->sslConfiguration = std::move(reply.sslConfiguration);
+#endif
+ d->reply = reply;
+ d->runnable = nullptr;
+ d->isFinished = true;
+ emit finished();
+ }
+ };
+
+ d->runnable = new QDnsLookupRunnable(d);
+ connect(d->runnable, &QDnsLookupRunnable::finished, this, l,
+ Qt::BlockingQueuedConnection);
+ theDnsLookupThreadPool->start(d->runnable);
}
/*!
@@ -959,18 +1278,249 @@ QDnsTextRecord &QDnsTextRecord::operator=(const QDnsTextRecord &other)
very fast and never fails.
*/
-void QDnsLookupPrivate::_q_lookupFinished(const QDnsLookupReply &_reply)
+/*!
+ \class QDnsTlsAssociationRecord
+ \since 6.8
+ \brief The QDnsTlsAssociationRecord class stores information about a DNS TLSA record.
+
+ \inmodule QtNetwork
+ \ingroup network
+ \ingroup shared
+
+ When performing a text lookup, zero or more records will be returned. Each
+ record is represented by a QDnsTlsAssociationRecord instance.
+
+ The meaning of the fields is defined in \l{RFC 6698}.
+
+ \sa QDnsLookup
+*/
+
+QT_DEFINE_QSDP_SPECIALIZATION_DTOR(QDnsTlsAssociationRecordPrivate)
+
+/*!
+ \enum QDnsTlsAssociationRecord::CertificateUsage
+
+ This enumeration contains valid values for the certificate usage field of
+ TLS Association queries. The following list is up-to-date with \l{RFC 6698}
+ section 2.1.1 and RFC 7218 section 2.1. Please refer to those documents for
+ authoritative instructions on interpreting this enumeration.
+
+ \value CertificateAuthorityConstrait
+ Indicates the record includes an association to a specific Certificate
+ Authority that must be found in the TLS server's certificate chain and
+ must pass PKIX validation.
+
+ \value ServiceCertificateConstraint
+ Indicates the record includes an association to a certificate that must
+ match the end entity certificate provided by the TLS server and must
+ pass PKIX validation.
+
+ \value TrustAnchorAssertion
+ Indicates the record includes an association to a certificate that MUST
+ be used as the ultimate trust anchor to validate the TLS server's
+ certificate and must pass PKIX validation.
+
+ \value DomainIssuedCertificate
+ Indicates the record includes an association to a certificate that must
+ match the end entity certificate provided by the TLS server. PKIX
+ validation is not tested.
+
+ \value PrivateUse
+ No standard meaning applied.
+
+ \value PKIX_TA
+ Alias; mnemonic for Public Key Infrastructure Trust Anchor
+
+ \value PKIX_EE
+ Alias; mnemonic for Public Key Infrastructure End Entity
+
+ \value DANE_TA
+ Alias; mnemonic for DNS-based Authentication of Named Entities Trust Anchor
+
+ \value DANE_EE
+ Alias; mnemonic for DNS-based Authentication of Named Entities End Entity
+
+ \value PrivCert
+ Alias
+
+ Other values are currently reserved, but may be unreserved by future
+ standards. This enumeration can be used for those values even if no
+ enumerator is provided.
+
+ \sa certificateUsage()
+*/
+
+/*!
+ \enum QDnsTlsAssociationRecord::Selector
+
+ This enumeration contains valid values for the selector field of TLS
+ Association queries. The following list is up-to-date with \l{RFC 6698}
+ section 2.1.2 and RFC 7218 section 2.2. Please refer to those documents for
+ authoritative instructions on interpreting this enumeration.
+
+ \value FullCertificate
+ Indicates this record refers to the full certificate in its binary
+ structure form.
+
+ \value SubjectPublicKeyInfo
+ Indicates the record refers to the certificate's subject and public
+ key information, in DER-encoded binary structure form.
+
+ \value PrivateUse
+ No standard meaning applied.
+
+ \value Cert
+ Alias
+
+ \value SPKI
+ Alias
+
+ \value PrivSel
+ Alias
+
+ Other values are currently reserved, but may be unreserved by future
+ standards. This enumeration can be used for those values even if no
+ enumerator is provided.
+
+ \sa selector()
+*/
+
+/*!
+ \enum QDnsTlsAssociationRecord::MatchingType
+
+ This enumeration contains valid values for the matching type field of TLS
+ Association queries. The following list is up-to-date with \l{RFC 6698}
+ section 2.1.3 and RFC 7218 section 2.3. Please refer to those documents for
+ authoritative instructions on interpreting this enumeration.
+
+ \value Exact
+ Indicates this the certificate or SPKI data is stored verbatim in this
+ record.
+
+ \value Sha256
+ Indicates this a SHA-256 checksum of the the certificate or SPKI data
+ present in this record.
+
+ \value Sha512
+ Indicates this a SHA-512 checksum of the the certificate or SPKI data
+ present in this record.
+
+ \value PrivateUse
+ No standard meaning applied.
+
+ \value PrivMatch
+ Alias
+
+ Other values are currently reserved, but may be unreserved by future
+ standards. This enumeration can be used for those values even if no
+ enumerator is provided.
+
+ \sa matchingType()
+*/
+
+/*!
+ Constructs an empty TLS Association record.
+ */
+QDnsTlsAssociationRecord::QDnsTlsAssociationRecord()
+ : d(new QDnsTlsAssociationRecordPrivate)
{
- Q_Q(QDnsLookup);
- if (runnable == q->sender()) {
-#ifdef QDNSLOOKUP_DEBUG
- qDebug("DNS reply for %s: %i (%s)", qPrintable(name), _reply.error, qPrintable(_reply.errorString));
+}
+
+/*!
+ Constructs a copy of \a other.
+ */
+QDnsTlsAssociationRecord::QDnsTlsAssociationRecord(const QDnsTlsAssociationRecord &other) = default;
+
+/*!
+ Moves the content of \a other into this object.
+ */
+QDnsTlsAssociationRecord &
+QDnsTlsAssociationRecord::operator=(const QDnsTlsAssociationRecord &other) = default;
+
+/*!
+ Destroys this TLS Association record object.
+ */
+QDnsTlsAssociationRecord::~QDnsTlsAssociationRecord() = default;
+
+/*!
+ Returns the name of this record.
+*/
+QString QDnsTlsAssociationRecord::name() const
+{
+ return d->name;
+}
+
+/*!
+ Returns the duration in seconds for which this record is valid.
+*/
+quint32 QDnsTlsAssociationRecord::timeToLive() const
+{
+ return d->timeToLive;
+}
+
+/*!
+ Returns the certificate usage field for this record.
+ */
+QDnsTlsAssociationRecord::CertificateUsage QDnsTlsAssociationRecord::usage() const
+{
+ return d->usage;
+}
+
+/*!
+ Returns the selector field for this record.
+ */
+QDnsTlsAssociationRecord::Selector QDnsTlsAssociationRecord::selector() const
+{
+ return d->selector;
+}
+
+/*!
+ Returns the match type field for this record.
+ */
+QDnsTlsAssociationRecord::MatchingType QDnsTlsAssociationRecord::matchType() const
+{
+ return d->matchType;
+}
+
+/*!
+ Returns the binary data field for this record. The interpretation of this
+ binary data depends on the three numeric fields provided by
+ certificateUsage(), selector(), and matchType().
+
+ Do note this is a binary field, even for the checksums, similar to what
+ QCyrptographicHash::result() returns.
+ */
+QByteArray QDnsTlsAssociationRecord::value() const
+{
+ return d->value;
+}
+
+static QDnsLookupRunnable::EncodedLabel encodeLabel(const QString &label)
+{
+ QDnsLookupRunnable::EncodedLabel::value_type rootDomain = u'.';
+ if (label.isEmpty())
+ return QDnsLookupRunnable::EncodedLabel(1, rootDomain);
+
+ QString encodedLabel = qt_ACE_do(label, ToAceOnly, ForbidLeadingDot);
+#ifdef Q_OS_WIN
+ return encodedLabel;
+#else
+ return std::move(encodedLabel).toLatin1();
+#endif
+}
+
+inline QDnsLookupRunnable::QDnsLookupRunnable(const QDnsLookupPrivate *d)
+ : requestName(encodeLabel(d->name)),
+ nameserver(d->nameserver),
+ requestType(d->type),
+ port(d->port),
+ protocol(d->protocol)
+{
+ if (port == 0)
+ port = QDnsLookup::defaultPortForProtocol(protocol);
+#if QT_CONFIG(ssl)
+ sslConfiguration = d->sslConfiguration;
#endif
- reply = _reply;
- runnable = nullptr;
- isFinished = true;
- emit q->finished();
- }
}
void QDnsLookupRunnable::run()
@@ -978,60 +1528,144 @@ void QDnsLookupRunnable::run()
QDnsLookupReply reply;
// Validate input.
- if (requestName.isEmpty()) {
+ if (qsizetype n = requestName.size(); n > MaxDomainNameLength || n == 0) {
reply.error = QDnsLookup::InvalidRequestError;
- reply.errorString = tr("Invalid domain name");
- emit finished(reply);
- return;
+ reply.errorString = QDnsLookup::tr("Invalid domain name");
+ } else {
+ // Perform request.
+ query(&reply);
+
+ // Sort results.
+ qt_qdnsmailexchangerecord_sort(reply.mailExchangeRecords);
+ qt_qdnsservicerecord_sort(reply.serviceRecords);
}
- // Perform request.
- query(requestType, requestName, nameserver, &reply);
+ emit finished(reply);
- // Sort results.
- qt_qdnsmailexchangerecord_sort(reply.mailExchangeRecords);
- qt_qdnsservicerecord_sort(reply.serviceRecords);
+ // maybe print the lookup error as warning
+ switch (reply.error) {
+ case QDnsLookup::NoError:
+ case QDnsLookup::OperationCancelledError:
+ case QDnsLookup::NotFoundError:
+ case QDnsLookup::ServerFailureError:
+ case QDnsLookup::ServerRefusedError:
+ case QDnsLookup::TimeoutError:
+ break; // no warning for these
+
+ case QDnsLookup::ResolverError:
+ case QDnsLookup::InvalidRequestError:
+ case QDnsLookup::InvalidReplyError:
+ qCWarning(lcDnsLookup()).nospace()
+ << "DNS lookup failed (" << reply.error << "): "
+ << qUtf16Printable(reply.errorString)
+ << "; request was " << this; // continues below
+ }
+}
- emit finished(reply);
+inline QDebug operator<<(QDebug &d, QDnsLookupRunnable *r)
+{
+ // continued: print the information about the request
+ d << r->requestName.left(MaxDomainNameLength);
+ if (r->requestName.size() > MaxDomainNameLength)
+ d << "... (truncated)";
+ d << " type " << r->requestType;
+ if (!r->nameserver.isNull()) {
+ d << " to nameserver " << qUtf16Printable(r->nameserver.toString())
+ << " port " << (r->port ? r->port : QDnsLookup::defaultPortForProtocol(r->protocol));
+ switch (r->protocol) {
+ case QDnsLookup::Standard:
+ break;
+ case QDnsLookup::DnsOverTls:
+ d << " (TLS)";
+ }
+ }
+ return d;
}
-#if QT_CONFIG(thread)
-QDnsLookupThreadPool::QDnsLookupThreadPool()
- : signalsConnected(false)
+#if QT_CONFIG(ssl)
+static constexpr std::chrono::milliseconds DnsOverTlsConnectTimeout(15'000);
+static constexpr std::chrono::milliseconds DnsOverTlsTimeout(120'000);
+static constexpr quint8 DnsAuthenticDataBit = 0x20;
+
+static int makeReplyErrorFromSocket(QDnsLookupReply *reply, const QAbstractSocket *socket)
{
- // Run up to 5 lookups in parallel.
- setMaxThreadCount(5);
+ QDnsLookup::Error error = [&] {
+ switch (socket->error()) {
+ case QAbstractSocket::SocketTimeoutError:
+ case QAbstractSocket::ProxyConnectionTimeoutError:
+ return QDnsLookup::TimeoutError;
+ default:
+ return QDnsLookup::ResolverError;
+ }
+ }();
+ reply->setError(error, socket->errorString());
+ return false;
}
-void QDnsLookupThreadPool::start(QRunnable *runnable)
+bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned char> query,
+ ReplyBuffer &response)
{
- // Ensure threads complete at application destruction.
- if (!signalsConnected) {
- QMutexLocker signalsLocker(&signalsMutex);
- if (!signalsConnected) {
- QCoreApplication *app = QCoreApplication::instance();
- if (!app) {
- qWarning("QDnsLookup requires a QCoreApplication");
- delete runnable;
- return;
- }
+ QSslSocket socket;
+ socket.setSslConfiguration(sslConfiguration.value_or(QSslConfiguration::defaultConfiguration()));
+
+# if QT_CONFIG(networkproxy)
+ socket.setProtocolTag("domain-s"_L1);
+# endif
+
+ // Request the name server attempt to authenticate the reply.
+ query[3] |= DnsAuthenticDataBit;
- moveToThread(app->thread());
- connect(app, SIGNAL(destroyed()),
- SLOT(_q_applicationDestroyed()), Qt::DirectConnection);
- signalsConnected = true;
+ do {
+ quint16 size = qToBigEndian<quint16>(query.size());
+ QDeadlineTimer timeout(DnsOverTlsTimeout);
+
+ socket.connectToHostEncrypted(nameserver.toString(), port);
+ socket.write(reinterpret_cast<const char *>(&size), sizeof(size));
+ socket.write(reinterpret_cast<const char *>(query.data()), query.size());
+ if (!socket.waitForEncrypted(DnsOverTlsConnectTimeout.count()))
+ break;
+
+ reply->sslConfiguration = socket.sslConfiguration();
+
+ // accumulate reply
+ auto waitForBytes = [&](void *buffer, int count) {
+ int remaining = timeout.remainingTime();
+ while (remaining >= 0 && socket.bytesAvailable() < count) {
+ if (!socket.waitForReadyRead(remaining))
+ return false;
+ }
+ return socket.read(static_cast<char *>(buffer), count) == count;
+ };
+ if (!waitForBytes(&size, sizeof(size)))
+ break;
+
+ // note: strictly speaking, we're allocating memory based on untrusted data
+ // but in practice, due to limited range of the data type (16 bits),
+ // the maximum allocation is small.
+ size = qFromBigEndian(size);
+ response.resize(size);
+ if (waitForBytes(response.data(), size)) {
+ // check if the AD bit is set; we'll trust it over TLS requests
+ if (size >= 4)
+ reply->authenticData = response[3] & DnsAuthenticDataBit;
+ return true;
}
- }
+ } while (false);
- QThreadPool::start(runnable);
+ // handle errors
+ return makeReplyErrorFromSocket(reply, &socket);
}
-
-void QDnsLookupThreadPool::_q_applicationDestroyed()
+#else
+bool QDnsLookupRunnable::sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned char> query,
+ ReplyBuffer &response)
{
- waitForDone();
- signalsConnected = false;
+ Q_UNUSED(query)
+ Q_UNUSED(response)
+ reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("SSL/TLS support not present"));
+ return false;
}
-#endif // QT_CONFIG(thread)
+#endif
+
QT_END_NAMESPACE
#include "moc_qdnslookup.cpp"
diff --git a/src/network/kernel/qdnslookup.h b/src/network/kernel/qdnslookup.h
index b0bb34ee75..8d21e99c84 100644
--- a/src/network/kernel/qdnslookup.h
+++ b/src/network/kernel/qdnslookup.h
@@ -8,7 +8,6 @@
#include <QtCore/qlist.h>
#include <QtCore/qobject.h>
#include <QtCore/qshareddata.h>
-#include <QtCore/qsharedpointer.h>
#include <QtCore/qstring.h>
#include <QtCore/qproperty.h>
@@ -23,6 +22,10 @@ class QDnsHostAddressRecordPrivate;
class QDnsMailExchangeRecordPrivate;
class QDnsServiceRecordPrivate;
class QDnsTextRecordPrivate;
+class QDnsTlsAssociationRecordPrivate;
+class QSslConfiguration;
+
+QT_DECLARE_QSDP_SPECIALIZATION_DTOR(QDnsTlsAssociationRecordPrivate)
class Q_NETWORK_EXPORT QDnsDomainNameRecord
{
@@ -138,15 +141,92 @@ private:
Q_DECLARE_SHARED(QDnsTextRecord)
+class Q_NETWORK_EXPORT QDnsTlsAssociationRecord
+{
+ Q_GADGET
+public:
+ enum class CertificateUsage : quint8 {
+ // https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#certificate-usages
+ // RFC 6698
+ CertificateAuthorityConstrait = 0,
+ ServiceCertificateConstraint = 1,
+ TrustAnchorAssertion = 2,
+ DomainIssuedCertificate = 3,
+ PrivateUse = 255,
+
+ // Aliases by RFC 7218
+ PKIX_TA = 0,
+ PKIX_EE = 1,
+ DANE_TA = 2,
+ DANE_EE = 3,
+ PrivCert = 255,
+ };
+ Q_ENUM(CertificateUsage)
+
+ enum class Selector : quint8 {
+ // https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#selectors
+ // RFC 6698
+ FullCertificate = 0,
+ SubjectPublicKeyInfo = 1,
+ PrivateUse = 255,
+
+ // Aliases by RFC 7218
+ Cert = FullCertificate,
+ SPKI = SubjectPublicKeyInfo,
+ PrivSel = PrivateUse,
+ };
+ Q_ENUM(Selector)
+
+ enum class MatchingType : quint8 {
+ // https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#matching-types
+ // RFC 6698
+ Exact = 0,
+ Sha256 = 1,
+ Sha512 = 2,
+ PrivateUse = 255,
+ PrivMatch = PrivateUse,
+ };
+ Q_ENUM(MatchingType)
+
+ QDnsTlsAssociationRecord();
+ QDnsTlsAssociationRecord(const QDnsTlsAssociationRecord &other);
+ QDnsTlsAssociationRecord(QDnsTlsAssociationRecord &&other)
+ : d(std::move(other.d))
+ {}
+ QDnsTlsAssociationRecord &operator=(QDnsTlsAssociationRecord &&other) noexcept { swap(other); return *this; }
+ QDnsTlsAssociationRecord &operator=(const QDnsTlsAssociationRecord &other);
+ ~QDnsTlsAssociationRecord();
+
+ void swap(QDnsTlsAssociationRecord &other) noexcept { d.swap(other.d); }
+
+ QString name() const;
+ quint32 timeToLive() const;
+ CertificateUsage usage() const;
+ Selector selector() const;
+ MatchingType matchType() const;
+ QByteArray value() const;
+
+private:
+ QSharedDataPointer<QDnsTlsAssociationRecordPrivate> d;
+ friend class QDnsLookupRunnable;
+};
+
+Q_DECLARE_SHARED(QDnsTlsAssociationRecord)
+
class Q_NETWORK_EXPORT QDnsLookup : public QObject
{
Q_OBJECT
Q_PROPERTY(Error error READ error NOTIFY finished)
+ Q_PROPERTY(bool authenticData READ isAuthenticData NOTIFY finished)
Q_PROPERTY(QString errorString READ errorString NOTIFY finished)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged BINDABLE bindableName)
Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged BINDABLE bindableType)
Q_PROPERTY(QHostAddress nameserver READ nameserver WRITE setNameserver NOTIFY nameserverChanged
BINDABLE bindableNameserver)
+ Q_PROPERTY(quint16 nameserverPort READ nameserverPort WRITE setNameserverPort
+ NOTIFY nameserverPortChanged BINDABLE bindableNameserverPort)
+ Q_PROPERTY(Protocol nameserverProtocol READ nameserverProtocol WRITE setNameserverProtocol
+ NOTIFY nameserverProtocolChanged BINDABLE bindableNameserverProtocol)
public:
enum Error
@@ -158,7 +238,8 @@ public:
InvalidReplyError,
ServerFailureError,
ServerRefusedError,
- NotFoundError
+ NotFoundError,
+ TimeoutError,
};
Q_ENUM(Error)
@@ -172,15 +253,27 @@ public:
NS = 2,
PTR = 12,
SRV = 33,
+ TLSA = 52,
TXT = 16
};
Q_ENUM(Type)
+ enum Protocol : quint8 {
+ Standard = 0,
+ DnsOverTls,
+ };
+ Q_ENUM(Protocol)
+
explicit QDnsLookup(QObject *parent = nullptr);
QDnsLookup(Type type, const QString &name, QObject *parent = nullptr);
QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, QObject *parent = nullptr);
+ QDnsLookup(Type type, const QString &name, const QHostAddress &nameserver, quint16 port,
+ QObject *parent = nullptr);
+ QDnsLookup(Type type, const QString &name, Protocol protocol, const QHostAddress &nameserver,
+ quint16 port = 0, QObject *parent = nullptr);
~QDnsLookup();
+ bool isAuthenticData() const;
Error error() const;
QString errorString() const;
bool isFinished() const;
@@ -196,6 +289,15 @@ public:
QHostAddress nameserver() const;
void setNameserver(const QHostAddress &nameserver);
QBindable<QHostAddress> bindableNameserver();
+ quint16 nameserverPort() const;
+ void setNameserverPort(quint16 port);
+ QBindable<quint16> bindableNameserverPort();
+ Protocol nameserverProtocol() const;
+ void setNameserverProtocol(Protocol protocol);
+ QBindable<Protocol> bindableNameserverProtocol();
+ void setNameserver(Protocol protocol, const QHostAddress &nameserver, quint16 port = 0);
+ QT_NETWORK_INLINE_SINCE(6, 8)
+ void setNameserver(const QHostAddress &nameserver, quint16 port);
QList<QDnsDomainNameRecord> canonicalNameRecords() const;
QList<QDnsHostAddressRecord> hostAddressRecords() const;
@@ -204,7 +306,15 @@ public:
QList<QDnsDomainNameRecord> pointerRecords() const;
QList<QDnsServiceRecord> serviceRecords() const;
QList<QDnsTextRecord> textRecords() const;
+ QList<QDnsTlsAssociationRecord> tlsAssociationRecords() const;
+
+#if QT_CONFIG(ssl)
+ void setSslConfiguration(const QSslConfiguration &sslConfiguration);
+ QSslConfiguration sslConfiguration() const;
+#endif
+ static bool isProtocolSupported(Protocol protocol);
+ static quint16 defaultPortForProtocol(Protocol protocol) noexcept Q_DECL_CONST_FUNCTION;
public Q_SLOTS:
void abort();
@@ -215,12 +325,20 @@ Q_SIGNALS:
void nameChanged(const QString &name);
void typeChanged(Type type);
void nameserverChanged(const QHostAddress &nameserver);
+ void nameserverPortChanged(quint16 port);
+ void nameserverProtocolChanged(Protocol protocol);
private:
Q_DECLARE_PRIVATE(QDnsLookup)
- Q_PRIVATE_SLOT(d_func(), void _q_lookupFinished(const QDnsLookupReply &reply))
};
+#if QT_NETWORK_INLINE_IMPL_SINCE(6, 8)
+void QDnsLookup::setNameserver(const QHostAddress &nameserver, quint16 port)
+{
+ setNameserver(Standard, nameserver, port);
+}
+#endif
+
QT_END_NAMESPACE
#endif // QDNSLOOKUP_H
diff --git a/src/network/kernel/qdnslookup_android.cpp b/src/network/kernel/qdnslookup_android.cpp
deleted file mode 100644
index 8fc1265e80..0000000000
--- a/src/network/kernel/qdnslookup_android.cpp
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright (C) 2012 Collabora Ltd, author <robin.burchell@collabora.co.uk>
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-
-#include "qdnslookup_p.h"
-
-QT_BEGIN_NAMESPACE
-
-void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
-{
- Q_UNUSED(requestType);
- Q_UNUSED(requestName);
- Q_UNUSED(nameserver);
- Q_UNUSED(reply);
- qWarning("Not yet supported on Android");
- reply->error = QDnsLookup::ResolverError;
- reply->errorString = tr("Not yet supported on Android");
- return;
-}
-
-QT_END_NAMESPACE
diff --git a/src/network/kernel/qdnslookup_dummy.cpp b/src/network/kernel/qdnslookup_dummy.cpp
new file mode 100644
index 0000000000..6cc6ed92c5
--- /dev/null
+++ b/src/network/kernel/qdnslookup_dummy.cpp
@@ -0,0 +1,15 @@
+// Copyright (C) 2012 Collabora Ltd, author <robin.burchell@collabora.co.uk>
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qdnslookup_p.h"
+
+QT_BEGIN_NAMESPACE
+
+void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+{
+ reply->error = QDnsLookup::ResolverError;
+ reply->errorString = tr("Not yet supported on this OS");
+ return;
+}
+
+QT_END_NAMESPACE
diff --git a/src/network/kernel/qdnslookup_p.h b/src/network/kernel/qdnslookup_p.h
index 21262346b5..1f32b4ee4f 100644
--- a/src/network/kernel/qdnslookup_p.h
+++ b/src/network/kernel/qdnslookup_p.h
@@ -1,4 +1,5 @@
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+// Copyright (C) 2023 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QDNSLOOKUP_P_H
@@ -18,13 +19,17 @@
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "QtCore/qmutex.h"
#include "QtCore/qrunnable.h"
-#include "QtCore/qsharedpointer.h"
#if QT_CONFIG(thread)
#include "QtCore/qthreadpool.h"
#endif
#include "QtNetwork/qdnslookup.h"
#include "QtNetwork/qhostaddress.h"
#include "private/qobject_p.h"
+#include "private/qurl_p.h"
+
+#if QT_CONFIG(ssl)
+# include "qsslconfiguration.h"
+#endif
QT_REQUIRE_CONFIG(dnslookup);
@@ -32,16 +37,18 @@ QT_BEGIN_NAMESPACE
//#define QDNSLOOKUP_DEBUG
+constexpr qsizetype MaxDomainNameLength = 255;
+constexpr quint16 DnsPort = 53;
+constexpr quint16 DnsOverTlsPort = 853;
+
class QDnsLookupRunnable;
+QDebug operator<<(QDebug &, QDnsLookupRunnable *);
class QDnsLookupReply
{
public:
- QDnsLookupReply()
- : error(QDnsLookup::NoError)
- { }
-
- QDnsLookup::Error error;
+ QDnsLookup::Error error = QDnsLookup::NoError;
+ bool authenticData = false;
QString errorString;
QList<QDnsDomainNameRecord> canonicalNameRecords;
@@ -50,24 +57,94 @@ public:
QList<QDnsDomainNameRecord> nameServerRecords;
QList<QDnsDomainNameRecord> pointerRecords;
QList<QDnsServiceRecord> serviceRecords;
+ QList<QDnsTlsAssociationRecord> tlsAssociationRecords;
QList<QDnsTextRecord> textRecords;
+
+#if QT_CONFIG(ssl)
+ std::optional<QSslConfiguration> sslConfiguration;
+#endif
+
+ // helper methods
+ void setError(QDnsLookup::Error err, QString &&msg)
+ {
+ error = err;
+ errorString = std::move(msg);
+ }
+
+ void makeResolverSystemError(int code = -1)
+ {
+ Q_ASSERT(allAreEmpty());
+ setError(QDnsLookup::ResolverError, qt_error_string(code));
+ }
+
+ void makeTimeoutError()
+ {
+ Q_ASSERT(allAreEmpty());
+ setError(QDnsLookup::TimeoutError, QDnsLookup::tr("Request timed out"));
+ }
+
+ void makeDnsRcodeError(quint8 rcode)
+ {
+ Q_ASSERT(allAreEmpty());
+ switch (rcode) {
+ case 1: // FORMERR
+ error = QDnsLookup::InvalidRequestError;
+ errorString = QDnsLookup::tr("Server could not process query");
+ return;
+ case 2: // SERVFAIL
+ case 4: // NOTIMP
+ error = QDnsLookup::ServerFailureError;
+ errorString = QDnsLookup::tr("Server failure");
+ return;
+ case 3: // NXDOMAIN
+ error = QDnsLookup::NotFoundError;
+ errorString = QDnsLookup::tr("Non existent domain");
+ return;
+ case 5: // REFUSED
+ error = QDnsLookup::ServerRefusedError;
+ errorString = QDnsLookup::tr("Server refused to answer");
+ return;
+ default:
+ error = QDnsLookup::InvalidReplyError;
+ errorString = QDnsLookup::tr("Invalid reply received (rcode %1)")
+ .arg(rcode);
+ return;
+ }
+ }
+
+ void makeInvalidReplyError(QString &&msg = QString())
+ {
+ if (msg.isEmpty())
+ msg = QDnsLookup::tr("Invalid reply received");
+ else
+ msg = QDnsLookup::tr("Invalid reply received (%1)").arg(std::move(msg));
+ *this = QDnsLookupReply(); // empty our lists
+ setError(QDnsLookup::InvalidReplyError, std::move(msg));
+ }
+
+private:
+ bool allAreEmpty() const
+ {
+ return canonicalNameRecords.isEmpty()
+ && hostAddressRecords.isEmpty()
+ && mailExchangeRecords.isEmpty()
+ && nameServerRecords.isEmpty()
+ && pointerRecords.isEmpty()
+ && serviceRecords.isEmpty()
+ && tlsAssociationRecords.isEmpty()
+ && textRecords.isEmpty();
+ }
};
class QDnsLookupPrivate : public QObjectPrivate
{
public:
QDnsLookupPrivate()
- : isFinished(false)
- , type(QDnsLookup::A)
- , runnable(nullptr)
+ : type(QDnsLookup::A)
+ , port(0)
+ , protocol(QDnsLookup::Standard)
{ }
- void _q_lookupFinished(const QDnsLookupReply &reply);
-
- static const char *msgNoIpV6NameServerAdresses;
-
- bool isFinished;
-
void nameChanged()
{
emit q_func()->nameChanged(name);
@@ -75,6 +152,13 @@ public:
Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QString, name,
&QDnsLookupPrivate::nameChanged);
+ void nameserverChanged()
+ {
+ emit q_func()->nameserverChanged(nameserver);
+ }
+ Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QHostAddress, nameserver,
+ &QDnsLookupPrivate::nameserverChanged);
+
void typeChanged()
{
emit q_func()->typeChanged(type);
@@ -83,15 +167,29 @@ public:
Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QDnsLookup::Type,
type, &QDnsLookupPrivate::typeChanged);
- void nameserverChanged()
+ void nameserverPortChanged()
{
- emit q_func()->nameserverChanged(nameserver);
+ emit q_func()->nameserverPortChanged(port);
}
- Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QHostAddress, nameserver,
- &QDnsLookupPrivate::nameserverChanged);
+
+ Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, quint16,
+ port, &QDnsLookupPrivate::nameserverPortChanged);
+
+ void nameserverProtocolChanged()
+ {
+ emit q_func()->nameserverProtocolChanged(protocol);
+ }
+
+ Q_OBJECT_BINDABLE_PROPERTY(QDnsLookupPrivate, QDnsLookup::Protocol,
+ protocol, &QDnsLookupPrivate::nameserverProtocolChanged);
QDnsLookupReply reply;
- QDnsLookupRunnable *runnable;
+ QDnsLookupRunnable *runnable = nullptr;
+ bool isFinished = false;
+
+#if QT_CONFIG(ssl)
+ std::optional<QSslConfiguration> sslConfiguration;
+#endif
Q_DECLARE_PUBLIC(QDnsLookup)
};
@@ -101,40 +199,40 @@ class QDnsLookupRunnable : public QObject, public QRunnable
Q_OBJECT
public:
- QDnsLookupRunnable(QDnsLookup::Type type, const QByteArray &name, const QHostAddress &nameserver)
- : requestType(type)
- , requestName(name)
- , nameserver(nameserver)
- { }
+#ifdef Q_OS_WIN
+ using EncodedLabel = QString;
+#else
+ using EncodedLabel = QByteArray;
+#endif
+ // minimum IPv6 MTU (1280) minus the IPv6 (40) and UDP headers (8)
+ static constexpr qsizetype ReplyBufferSize = 1280 - 40 - 8;
+ using ReplyBuffer = QVarLengthArray<unsigned char, ReplyBufferSize>;
+
+ QDnsLookupRunnable(const QDnsLookupPrivate *d);
void run() override;
+ bool sendDnsOverTls(QDnsLookupReply *reply, QSpan<unsigned char> query, ReplyBuffer &response);
signals:
void finished(const QDnsLookupReply &reply);
private:
- static void query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply);
- QDnsLookup::Type requestType;
- QByteArray requestName;
- QHostAddress nameserver;
-};
-
-#if QT_CONFIG(thread)
-class QDnsLookupThreadPool : public QThreadPool
-{
- Q_OBJECT
-
-public:
- QDnsLookupThreadPool();
- void start(QRunnable *runnable);
+ template <typename T> static QString decodeLabel(T encodedLabel)
+ {
+ return qt_ACE_do(encodedLabel.toString(), NormalizeAce, ForbidLeadingDot);
+ }
+ void query(QDnsLookupReply *reply);
-private slots:
- void _q_applicationDestroyed();
+ EncodedLabel requestName;
+ QHostAddress nameserver;
+ QDnsLookup::Type requestType;
+ quint16 port;
+ QDnsLookup::Protocol protocol;
-private:
- QMutex signalsMutex;
- bool signalsConnected;
+#if QT_CONFIG(ssl)
+ std::optional<QSslConfiguration> sslConfiguration;
+#endif
+ friend QDebug operator<<(QDebug &, QDnsLookupRunnable *);
};
-#endif // QT_CONFIG(thread)
class QDnsRecordPrivate : public QSharedData
{
@@ -200,8 +298,15 @@ public:
QList<QByteArray> values;
};
-QT_END_NAMESPACE
+class QDnsTlsAssociationRecordPrivate : public QDnsRecordPrivate
+{
+public:
+ QDnsTlsAssociationRecord::CertificateUsage usage;
+ QDnsTlsAssociationRecord::Selector selector;
+ QDnsTlsAssociationRecord::MatchingType matchType;
+ QByteArray value;
+};
-QT_DECL_METATYPE_EXTERN(QDnsLookupReply, Q_NETWORK_EXPORT)
+QT_END_NAMESPACE
#endif // QDNSLOOKUP_P_H
diff --git a/src/network/kernel/qdnslookup_unix.cpp b/src/network/kernel/qdnslookup_unix.cpp
index 75f7c6c440..9de073b781 100644
--- a/src/network/kernel/qdnslookup_unix.cpp
+++ b/src/network/kernel/qdnslookup_unix.cpp
@@ -1,387 +1,448 @@
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+// Copyright (C) 2023 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qdnslookup_p.h"
-#if QT_CONFIG(library)
-#include <qlibrary.h>
-#endif
-#include <qvarlengtharray.h>
+#include <qendian.h>
#include <qscopedpointer.h>
+#include <qspan.h>
#include <qurl.h>
-#include <private/qnativesocketengine_p.h>
+#include <qvarlengtharray.h>
+#include <private/qnativesocketengine_p.h> // for setSockAddr
+#include <private/qtnetwork-config_p.h>
+
+QT_REQUIRE_CONFIG(libresolv);
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
-#if !defined(Q_OS_OPENBSD)
+#if __has_include(<arpa/nameser_compat.h>)
# include <arpa/nameser_compat.h>
#endif
+#include <errno.h>
#include <resolv.h>
-#if defined(__GNU_LIBRARY__) && !defined(__UCLIBC__)
-# include <gnu/lib-names.h>
-#endif
+#include <array>
-#if defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)
-# include <dlfcn.h>
+#ifndef T_OPT
+// the older arpa/nameser_compat.h wasn't updated between 1999 and 2016 in glibc
+# define T_OPT ns_t_opt
#endif
-#include <cstring>
-
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+using ReplyBuffer = QDnsLookupRunnable::ReplyBuffer;
+
+// https://www.rfc-editor.org/rfc/rfc6891
+static constexpr unsigned char Edns0Record[] = {
+ 0x00, // root label
+ T_OPT >> 8, T_OPT & 0xff, // type OPT
+ ReplyBuffer::PreallocatedSize >> 8, ReplyBuffer::PreallocatedSize & 0xff, // payload size
+ NOERROR, // extended rcode
+ 0, // version
+ 0x00, 0x00, // flags
+ 0x00, 0x00, // option length
+};
-#if QT_CONFIG(library)
+// maximum length of a EDNS0 query with a 255-character domain (rounded up to 16)
+static constexpr qsizetype QueryBufferSize =
+ HFIXEDSZ + QFIXEDSZ + MAXCDNAME + 1 + sizeof(Edns0Record);
+using QueryBuffer = std::array<unsigned char, (QueryBufferSize + 15) / 16 * 16>;
-#if defined(Q_OS_OPENBSD)
-typedef struct __res_state* res_state;
-#endif
-typedef int (*dn_expand_proto)(const unsigned char *, const unsigned char *, const unsigned char *, char *, int);
-static dn_expand_proto local_dn_expand = nullptr;
-typedef void (*res_nclose_proto)(res_state);
-static res_nclose_proto local_res_nclose = nullptr;
-typedef int (*res_ninit_proto)(res_state);
-static res_ninit_proto local_res_ninit = nullptr;
-typedef int (*res_nquery_proto)(res_state, const char *, int, int, unsigned char *, int);
-static res_nquery_proto local_res_nquery = nullptr;
-
-// Custom deleter to close resolver state.
-
-struct QDnsLookupStateDeleter
+namespace {
+struct QDnsCachedName
{
- static inline void cleanup(struct __res_state *pointer)
- {
- local_res_nclose(pointer);
- }
+ QString name;
+ int code = 0;
+ QDnsCachedName(const QString &name, int code) : name(name), code(code) {}
};
+}
+Q_DECLARE_TYPEINFO(QDnsCachedName, Q_RELOCATABLE_TYPE);
+using Cache = QList<QDnsCachedName>; // QHash or QMap are overkill
-static QFunctionPointer resolveSymbol(QLibrary &lib, const char *sym)
+#if QT_CONFIG(res_setservers)
+// https://www.ibm.com/docs/en/i/7.3?topic=ssw_ibm_i_73/apis/ressetservers.html
+// https://docs.oracle.com/cd/E86824_01/html/E54774/res-setservers-3resolv.html
+static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
{
- if (lib.isLoaded())
- return lib.resolve(sym);
-
-#if defined(RTLD_DEFAULT) && (defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen))
- return reinterpret_cast<QFunctionPointer>(dlsym(RTLD_DEFAULT, sym));
+ union res_sockaddr_union u;
+ setSockaddr(reinterpret_cast<sockaddr *>(&u.sin), nameserver, port);
+ res_setservers(state, &u, 1);
+ return true;
+}
#else
- return nullptr;
-#endif
+template <typename T> void setNsMap(T &ext, std::enable_if_t<sizeof(T::nsmap) != 0, uint16_t> v)
+{
+ // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
+ // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
+ // Unneeded since glibc 2.22 (2015), but doesn't hurt to set it
+ // See: https://sourceware.org/git/?p=glibc.git;a=commit;h=2212c1420c92a33b0e0bd9a34938c9814a56c0f7
+ ext.nsmap[0] = v;
}
-
-static bool resolveLibraryInternal()
+template <typename T> void setNsMap(T &, ...)
{
- QLibrary lib;
-#ifdef LIBRESOLV_SO
- lib.setFileName(QStringLiteral(LIBRESOLV_SO));
- if (!lib.load())
-#endif
- {
- lib.setFileName("resolv"_L1);
- lib.load();
- }
+ // fallback
+}
- local_dn_expand = dn_expand_proto(resolveSymbol(lib, "__dn_expand"));
- if (!local_dn_expand)
- local_dn_expand = dn_expand_proto(resolveSymbol(lib, "dn_expand"));
+template <bool Condition>
+using EnableIfIPv6 = std::enable_if_t<Condition, const QHostAddress *>;
- local_res_nclose = res_nclose_proto(resolveSymbol(lib, "__res_nclose"));
- if (!local_res_nclose)
- local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_9_nclose"));
- if (!local_res_nclose)
- local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_nclose"));
+template <typename State>
+bool setIpv6NameServer(State *state,
+ EnableIfIPv6<sizeof(std::declval<State>()._u._ext.nsaddrs) != 0> addr,
+ quint16 port)
+{
+ // glibc-like API to set IPv6 name servers
+ struct sockaddr_in6 *ns = state->_u._ext.nsaddrs[0];
+
+ // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
+ if (!ns) {
+ // Memory allocated here will be free()'d in res_close() as we
+ // have done res_init() above.
+ ns = static_cast<struct sockaddr_in6*>(calloc(1, sizeof(struct sockaddr_in6)));
+ Q_CHECK_PTR(ns);
+ state->_u._ext.nsaddrs[0] = ns;
+ }
- local_res_ninit = res_ninit_proto(resolveSymbol(lib, "__res_ninit"));
- if (!local_res_ninit)
- local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_9_ninit"));
- if (!local_res_ninit)
- local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_ninit"));
+ setNsMap(state->_u._ext, MAXNS + 1);
+ state->_u._ext.nscount6 = 1;
+ setSockaddr(ns, *addr, port);
+ return true;
+}
- local_res_nquery = res_nquery_proto(resolveSymbol(lib, "__res_nquery"));
- if (!local_res_nquery)
- local_res_nquery = res_nquery_proto(resolveSymbol(lib, "res_9_nquery"));
- if (!local_res_nquery)
- local_res_nquery = res_nquery_proto(resolveSymbol(lib, "res_nquery"));
+template <typename State> bool setIpv6NameServer(State *, const void *, quint16)
+{
+ // fallback
+ return false;
+}
+static bool applyNameServer(res_state state, const QHostAddress &nameserver, quint16 port)
+{
+ state->nscount = 1;
+ state->nsaddr_list[0].sin_family = AF_UNSPEC;
+ if (nameserver.protocol() == QAbstractSocket::IPv6Protocol)
+ return setIpv6NameServer(state, &nameserver, port);
+ setSockaddr(&state->nsaddr_list[0], nameserver, port);
return true;
}
-Q_GLOBAL_STATIC_WITH_ARGS(bool, resolveLibrary, (resolveLibraryInternal()))
+#endif // !QT_CONFIG(res_setservers)
-void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
+static int
+prepareQueryBuffer(res_state state, QueryBuffer &buffer, const char *label, ns_rcode type)
{
- // Load dn_expand, res_ninit and res_nquery on demand.
- resolveLibrary();
+ // Create header and our query
+ int queryLength = res_nmkquery(state, QUERY, label, C_IN, type, nullptr, 0, nullptr,
+ buffer.data(), buffer.size());
+ Q_ASSERT(queryLength < int(buffer.size()));
+ if (Q_UNLIKELY(queryLength < 0))
+ return queryLength;
+
+ // Append EDNS0 record and set the number of additional RRs to 1
+ Q_ASSERT(queryLength + sizeof(Edns0Record) < buffer.size());
+ std::copy_n(std::begin(Edns0Record), sizeof(Edns0Record), buffer.begin() + queryLength);
+ reinterpret_cast<HEADER *>(buffer.data())->arcount = qToBigEndian<quint16>(1);
+
+ return queryLength + sizeof(Edns0Record);
+}
- // If dn_expand, res_ninit or res_nquery is missing, fail.
- if (!local_dn_expand || !local_res_nclose || !local_res_ninit || !local_res_nquery) {
- reply->error = QDnsLookup::ResolverError;
- reply->errorString = tr("Resolver functions not found");
- return;
+static int sendStandardDns(QDnsLookupReply *reply, res_state state, QSpan<unsigned char> qbuffer,
+ ReplyBuffer &buffer, const QHostAddress &nameserver, quint16 port)
+{
+ // Check if a nameserver was set. If so, use it.
+ if (!nameserver.isNull()) {
+ if (!applyNameServer(state, nameserver, port)) {
+ reply->setError(QDnsLookup::ResolverError,
+ QDnsLookup::tr("IPv6 nameservers are currently not supported on this OS"));
+ return -1;
+ }
+
+ // Request the name server attempt to authenticate the reply.
+ reinterpret_cast<HEADER *>(buffer.data())->ad = true;
+
+#ifdef RES_TRUSTAD
+ // Need to set this option even though we set the AD bit, otherwise
+ // glibc turns it off.
+ state->options |= RES_TRUSTAD;
+#endif
}
- // Initialize state.
- struct __res_state state;
- std::memset(&state, 0, sizeof(state));
- if (local_res_ninit(&state) < 0) {
- reply->error = QDnsLookup::ResolverError;
- reply->errorString = tr("Resolver initialization failed");
- return;
+ auto attemptToSend = [&]() {
+ std::memset(buffer.data(), 0, HFIXEDSZ); // the header is enough
+ int responseLength = res_nsend(state, qbuffer.data(), qbuffer.size(), buffer.data(), buffer.size());
+ if (responseLength >= 0)
+ return responseLength; // success
+
+ // libresolv uses ETIMEDOUT for resolver errors ("no answer")
+ if (errno == ECONNREFUSED)
+ reply->setError(QDnsLookup::ServerRefusedError, qt_error_string());
+ else if (errno != ETIMEDOUT)
+ reply->makeResolverSystemError(); // some other error
+
+ auto query = reinterpret_cast<HEADER *>(qbuffer.data());
+ auto header = reinterpret_cast<HEADER *>(buffer.data());
+ if (query->id == header->id && header->qr)
+ reply->makeDnsRcodeError(header->rcode);
+ else
+ reply->makeTimeoutError(); // must really be a timeout
+ return -1;
+ };
+
+ // strictly use UDP, we'll deal with truncated replies ourselves
+ state->options |= RES_IGNTC;
+ int responseLength = attemptToSend();
+ if (responseLength < 0)
+ return responseLength;
+
+ // check if we need to use the virtual circuit (TCP)
+ auto header = reinterpret_cast<HEADER *>(buffer.data());
+ if (header->rcode == NOERROR && header->tc) {
+ // yes, increase our buffer size
+ buffer.resize(std::numeric_limits<quint16>::max());
+ header = reinterpret_cast<HEADER *>(buffer.data());
+
+ // remove the EDNS record in the query
+ reinterpret_cast<HEADER *>(qbuffer.data())->arcount = 0;
+ qbuffer = qbuffer.first(qbuffer.size() - sizeof(Edns0Record));
+
+ // send using the virtual circuit
+ state->options |= RES_USEVC;
+ responseLength = attemptToSend();
+ if (Q_UNLIKELY(responseLength > buffer.size())) {
+ // Ok, we give up.
+ reply->setError(QDnsLookup::ResolverError, QDnsLookup::tr("Reply was too large"));
+ return -1;
+ }
}
- //Check if a nameserver was set. If so, use it
- if (!nameserver.isNull()) {
- if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) {
- state.nsaddr_list[0].sin_addr.s_addr = htonl(nameserver.toIPv4Address());
- state.nscount = 1;
- } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) {
-#if defined(Q_OS_LINUX)
- struct sockaddr_in6 *ns;
- ns = state._u._ext.nsaddrs[0];
- // nsaddrs will be NULL if no nameserver is set in /etc/resolv.conf
- if (!ns) {
- // Memory allocated here will be free'd in res_close() as we
- // have done res_init() above.
- ns = (struct sockaddr_in6*) calloc(1, sizeof(struct sockaddr_in6));
- Q_CHECK_PTR(ns);
- state._u._ext.nsaddrs[0] = ns;
- }
-#ifndef __UCLIBC__
- // Set nsmap[] to indicate that nsaddrs[0] is an IPv6 address
- // See: https://sourceware.org/ml/libc-hacker/2002-05/msg00035.html
- state._u._ext.nsmap[0] = MAXNS + 1;
-#endif
- state._u._ext.nscount6 = 1;
- ns->sin6_family = AF_INET6;
- ns->sin6_port = htons(53);
- SetSALen::set(ns, sizeof(*ns));
-
- Q_IPV6ADDR ipv6Address = nameserver.toIPv6Address();
- for (int i=0; i<16; i++) {
- ns->sin6_addr.s6_addr[i] = ipv6Address[i];
- }
-#else
- qWarning("%s", QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
- reply->error = QDnsLookup::ResolverError;
- reply->errorString = tr(QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
- return;
+ // We only trust the AD bit in the reply if we're querying a custom name
+ // server or if we can tell the system administrator configured the resolver
+ // to trust replies.
+#ifndef RES_TRUSTAD
+ if (nameserver.isNull())
+ header->ad = false;
#endif
- }
+ reply->authenticData = header->ad;
+
+ return responseLength;
+}
+
+void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+{
+ // Initialize state.
+ std::remove_pointer_t<res_state> state = {};
+ if (res_ninit(&state) < 0) {
+ int error = errno;
+ qErrnoWarning(error, "QDnsLookup: Resolver initialization failed");
+ return reply->makeResolverSystemError(error);
}
+ auto guard = qScopeGuard([&] { res_nclose(&state); });
+
#ifdef QDNSLOOKUP_DEBUG
state.options |= RES_DEBUG;
#endif
- QScopedPointer<struct __res_state, QDnsLookupStateDeleter> state_ptr(&state);
+
+ // Prepare the DNS query.
+ QueryBuffer qbuffer;
+ int queryLength = prepareQueryBuffer(&state, qbuffer, requestName.constData(), ns_rcode(requestType));
+ if (Q_UNLIKELY(queryLength < 0))
+ return reply->makeResolverSystemError();
// Perform DNS query.
- QVarLengthArray<unsigned char, PACKETSZ> buffer(PACKETSZ);
- std::memset(buffer.data(), 0, buffer.size());
- int responseLength = local_res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
- if (Q_UNLIKELY(responseLength > PACKETSZ)) {
- buffer.resize(responseLength);
- std::memset(buffer.data(), 0, buffer.size());
- responseLength = local_res_nquery(&state, requestName, C_IN, requestType, buffer.data(), buffer.size());
- if (Q_UNLIKELY(responseLength > buffer.size())) {
- // Ok, we give up.
- reply->error = QDnsLookup::ResolverError;
- reply->errorString.clear(); // We cannot be more specific, alas.
+ ReplyBuffer buffer(ReplyBufferSize);
+ int responseLength = -1;
+ switch (protocol) {
+ case QDnsLookup::Standard:
+ responseLength = sendStandardDns(reply, &state, qbuffer, buffer, nameserver, port);
+ break;
+ case QDnsLookup::DnsOverTls:
+ if (!sendDnsOverTls(reply, qbuffer, buffer))
return;
- }
+ responseLength = buffer.size();
+ break;
}
- unsigned char *response = buffer.data();
- // Check the response header. Though res_nquery returns -1 as a
- // responseLength in case of error, we still can extract the
- // exact error code from the response.
- HEADER *header = (HEADER*)response;
- const int answerCount = ntohs(header->ancount);
- switch (header->rcode) {
- case NOERROR:
- break;
- case FORMERR:
- reply->error = QDnsLookup::InvalidRequestError;
- reply->errorString = tr("Server could not process query");
+ if (responseLength < 0)
return;
- case SERVFAIL:
- reply->error = QDnsLookup::ServerFailureError;
- reply->errorString = tr("Server failure");
- return;
- case NXDOMAIN:
- reply->error = QDnsLookup::NotFoundError;
- reply->errorString = tr("Non existent domain");
- return;
- case REFUSED:
- reply->error = QDnsLookup::ServerRefusedError;
- reply->errorString = tr("Server refused to answer");
- return;
- default:
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid reply received");
- return;
- }
// Check the reply is valid.
- if (responseLength < int(sizeof(HEADER))) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid reply received");
- return;
- }
+ if (responseLength < int(sizeof(HEADER)))
+ return reply->makeInvalidReplyError();
- // Skip the query host, type (2 bytes) and class (2 bytes).
- char host[PACKETSZ], answer[PACKETSZ];
- unsigned char *p = response + sizeof(HEADER);
- int status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
- if (status < 0) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Could not expand domain name");
- return;
+ // Parse the reply.
+ auto header = reinterpret_cast<HEADER *>(buffer.data());
+ if (header->rcode)
+ return reply->makeDnsRcodeError(header->rcode);
+
+ qptrdiff offset = sizeof(HEADER);
+ unsigned char *response = buffer.data();
+ int status;
+
+ auto expandHost = [&, cache = Cache{}](qptrdiff offset) mutable {
+ if (uchar n = response[offset]; n & NS_CMPRSFLGS) {
+ // compressed name, see if we already have it cached
+ if (offset + 1 < responseLength) {
+ int id = ((n & ~NS_CMPRSFLGS) << 8) | response[offset + 1];
+ auto it = std::find_if(cache.constBegin(), cache.constEnd(),
+ [id](const QDnsCachedName &n) { return n.code == id; });
+ if (it != cache.constEnd()) {
+ status = 2;
+ return it->name;
+ }
+ }
+ }
+
+ // uncached, expand it
+ char host[MAXCDNAME + 1];
+ status = dn_expand(response, response + responseLength, response + offset,
+ host, sizeof(host));
+ if (status >= 0)
+ return cache.emplaceBack(decodeLabel(QLatin1StringView(host)), offset).name;
+
+ // failed
+ reply->makeInvalidReplyError(QDnsLookup::tr("Could not expand domain name"));
+ return QString();
+ };
+
+ if (ntohs(header->qdcount) == 1) {
+ // Skip the query host, type (2 bytes) and class (2 bytes).
+ expandHost(offset);
+ if (status < 0)
+ return;
+ if (offset + status + 4 > responseLength)
+ header->qdcount = 0xffff; // invalid reply below
+ else
+ offset += status + 4;
}
- p += status + 4;
+ if (ntohs(header->qdcount) > 1)
+ return reply->makeInvalidReplyError();
// Extract results.
+ const int answerCount = ntohs(header->ancount);
int answerIndex = 0;
- while ((p < response + responseLength) && (answerIndex < answerCount)) {
- status = local_dn_expand(response, response + responseLength, p, host, sizeof(host));
- if (status < 0) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Could not expand domain name");
+ while ((offset < responseLength) && (answerIndex < answerCount)) {
+ const QString name = expandHost(offset);
+ if (status < 0)
return;
- }
- const QString name = QUrl::fromAce(host);
- p += status;
- const quint16 type = (p[0] << 8) | p[1];
- p += 2; // RR type
- p += 2; // RR class
- const quint32 ttl = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
- p += 4;
- const quint16 size = (p[0] << 8) | p[1];
- p += 2;
+ offset += status;
+ if (offset + RRFIXEDSZ > responseLength) {
+ // probably just a truncated reply, return what we have
+ return;
+ }
+ const quint16 type = qFromBigEndian<quint16>(response + offset);
+ const qint16 rrclass = qFromBigEndian<quint16>(response + offset + 2);
+ const quint32 ttl = qFromBigEndian<quint32>(response + offset + 4);
+ const quint16 size = qFromBigEndian<quint16>(response + offset + 8);
+ offset += RRFIXEDSZ;
+ if (offset + size > responseLength)
+ return; // truncated
+ if (rrclass != C_IN)
+ continue;
if (type == QDnsLookup::A) {
- if (size != 4) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid IPv4 address record");
- return;
- }
- const quint32 addr = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+ if (size != 4)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv4 address record"));
+ const quint32 addr = qFromBigEndian<quint32>(response + offset);
QDnsHostAddressRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
record.d->value = QHostAddress(addr);
reply->hostAddressRecords.append(record);
} else if (type == QDnsLookup::AAAA) {
- if (size != 16) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid IPv6 address record");
- return;
- }
+ if (size != 16)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid IPv6 address record"));
QDnsHostAddressRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- record.d->value = QHostAddress(p);
+ record.d->value = QHostAddress(response + offset);
reply->hostAddressRecords.append(record);
} else if (type == QDnsLookup::CNAME) {
- status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
- if (status < 0) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid canonical name record");
- return;
- }
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- record.d->value = QUrl::fromAce(answer);
+ record.d->value = expandHost(offset);
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid canonical name record"));
reply->canonicalNameRecords.append(record);
} else if (type == QDnsLookup::NS) {
- status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
- if (status < 0) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid name server record");
- return;
- }
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- record.d->value = QUrl::fromAce(answer);
+ record.d->value = expandHost(offset);
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid name server record"));
reply->nameServerRecords.append(record);
} else if (type == QDnsLookup::PTR) {
- status = local_dn_expand(response, response + responseLength, p, answer, sizeof(answer));
- if (status < 0) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid pointer record");
- return;
- }
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- record.d->value = QUrl::fromAce(answer);
+ record.d->value = expandHost(offset);
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid pointer record"));
reply->pointerRecords.append(record);
} else if (type == QDnsLookup::MX) {
- const quint16 preference = (p[0] << 8) | p[1];
- status = local_dn_expand(response, response + responseLength, p + 2, answer, sizeof(answer));
- if (status < 0) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid mail exchange record");
- return;
- }
+ const quint16 preference = qFromBigEndian<quint16>(response + offset);
QDnsMailExchangeRecord record;
- record.d->exchange = QUrl::fromAce(answer);
+ record.d->exchange = expandHost(offset + 2);
record.d->name = name;
record.d->preference = preference;
record.d->timeToLive = ttl;
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid mail exchange record"));
reply->mailExchangeRecords.append(record);
} else if (type == QDnsLookup::SRV) {
- const quint16 priority = (p[0] << 8) | p[1];
- const quint16 weight = (p[2] << 8) | p[3];
- const quint16 port = (p[4] << 8) | p[5];
- status = local_dn_expand(response, response + responseLength, p + 6, answer, sizeof(answer));
- if (status < 0) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid service record");
- return;
- }
+ if (size < 7)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
+ const quint16 priority = qFromBigEndian<quint16>(response + offset);
+ const quint16 weight = qFromBigEndian<quint16>(response + offset + 2);
+ const quint16 port = qFromBigEndian<quint16>(response + offset + 4);
QDnsServiceRecord record;
record.d->name = name;
- record.d->target = QUrl::fromAce(answer);
+ record.d->target = expandHost(offset + 6);
record.d->port = port;
record.d->priority = priority;
record.d->timeToLive = ttl;
record.d->weight = weight;
+ if (status < 0)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid service record"));
reply->serviceRecords.append(record);
+ } else if (type == QDnsLookup::TLSA) {
+ // https://datatracker.ietf.org/doc/html/rfc6698#section-2.1
+ if (size < 3)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid TLS association record"));
+
+ const quint8 usage = response[offset];
+ const quint8 selector = response[offset + 1];
+ const quint8 matchType = response[offset + 2];
+
+ QDnsTlsAssociationRecord record;
+ record.d->name = name;
+ record.d->timeToLive = ttl;
+ record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
+ record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
+ record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
+ record.d->value.assign(response + offset + 3, response + offset + size);
+ reply->tlsAssociationRecords.append(std::move(record));
} else if (type == QDnsLookup::TXT) {
- unsigned char *txt = p;
QDnsTextRecord record;
record.d->name = name;
record.d->timeToLive = ttl;
- while (txt < p + size) {
- const unsigned char length = *txt;
+ qptrdiff txt = offset;
+ while (txt < offset + size) {
+ const unsigned char length = response[txt];
txt++;
- if (txt + length > p + size) {
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = tr("Invalid text record");
- return;
- }
- record.d->values << QByteArray((char*)txt, length);
+ if (txt + length > offset + size)
+ return reply->makeInvalidReplyError(QDnsLookup::tr("Invalid text record"));
+ record.d->values << QByteArrayView(response + txt, length).toByteArray();
txt += length;
}
reply->textRecords.append(record);
}
- p += size;
+ offset += size;
answerIndex++;
}
}
-#else
-void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
-{
- Q_UNUSED(requestType);
- Q_UNUSED(requestName);
- Q_UNUSED(nameserver);
- reply->error = QDnsLookup::ResolverError;
- reply->errorString = tr("Resolver library can't be loaded: No runtime library loading support");
- return;
-}
-
-#endif /* QT_CONFIG(library) */
-
QT_END_NAMESPACE
diff --git a/src/network/kernel/qdnslookup_win.cpp b/src/network/kernel/qdnslookup_win.cpp
index 564966e395..1b07776db9 100644
--- a/src/network/kernel/qdnslookup_win.cpp
+++ b/src/network/kernel/qdnslookup_win.cpp
@@ -1,69 +1,180 @@
// Copyright (C) 2012 Jeremy Lainé <jeremy.laine@m4x.org>
+// Copyright (C) 2023 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <winsock2.h>
#include "qdnslookup_p.h"
-#include <qurl.h>
+#include <qendian.h>
+#include <private/qnativesocketengine_p.h>
#include <private/qsystemerror_p.h>
+#include <qurl.h>
+#include <qspan.h>
#include <qt_windows.h>
#include <windns.h>
#include <memory.h>
+#ifndef DNS_ADDR_MAX_SOCKADDR_LENGTH
+// MinGW headers are missing almost all of this
+typedef struct Qt_DnsAddr {
+ CHAR MaxSa[32];
+ DWORD DnsAddrUserDword[8];
+} DNS_ADDR, *PDNS_ADDR;
+typedef struct Qt_DnsAddrArray {
+ DWORD MaxCount;
+ DWORD AddrCount;
+ DWORD Tag;
+ WORD Family;
+ WORD WordReserved;
+ DWORD Flags;
+ DWORD MatchFlag;
+ DWORD Reserved1;
+ DWORD Reserved2;
+ DNS_ADDR AddrArray[];
+} DNS_ADDR_ARRAY, *PDNS_ADDR_ARRAY;
+# ifndef DNS_QUERY_RESULTS_VERSION1
+typedef struct Qt_DNS_QUERY_RESULT {
+ ULONG Version;
+ DNS_STATUS QueryStatus;
+ ULONG64 QueryOptions;
+ PDNS_RECORD pQueryRecords;
+ PVOID Reserved;
+} DNS_QUERY_RESULT, *PDNS_QUERY_RESULT;
+typedef VOID WINAPI DNS_QUERY_COMPLETION_ROUTINE(PVOID pQueryContext,PDNS_QUERY_RESULT pQueryResults);
+typedef DNS_QUERY_COMPLETION_ROUTINE *PDNS_QUERY_COMPLETION_ROUTINE;
+# endif
+typedef struct Qt_DNS_QUERY_REQUEST {
+ ULONG Version;
+ PCWSTR QueryName;
+ WORD QueryType;
+ ULONG64 QueryOptions;
+ PDNS_ADDR_ARRAY pDnsServerList;
+ ULONG InterfaceIndex;
+ PDNS_QUERY_COMPLETION_ROUTINE pQueryCompletionCallback;
+ PVOID pQueryContext;
+} DNS_QUERY_REQUEST, *PDNS_QUERY_REQUEST;
+
+typedef void *PDNS_QUERY_CANCEL; // not really, but we don't need it
+extern "C" {
+DNS_STATUS WINAPI DnsQueryEx(PDNS_QUERY_REQUEST pQueryRequest,
+ PDNS_QUERY_RESULT pQueryResults,
+ PDNS_QUERY_CANCEL pCancelHandle);
+}
+#endif
+
QT_BEGIN_NAMESPACE
-void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestName, const QHostAddress &nameserver, QDnsLookupReply *reply)
+static DNS_STATUS sendAlternate(QDnsLookupRunnable *self, QDnsLookupReply *reply,
+ PDNS_QUERY_REQUEST request, PDNS_QUERY_RESULT results)
{
- // Perform DNS query.
- PDNS_RECORD dns_records = 0;
- const QString requestNameUtf16 = QString::fromUtf8(requestName.data(), requestName.size());
- IP4_ARRAY srvList;
- memset(&srvList, 0, sizeof(IP4_ARRAY));
- if (!nameserver.isNull()) {
- if (nameserver.protocol() == QAbstractSocket::IPv4Protocol) {
- // The below code is referenced from: http://support.microsoft.com/kb/831226
- srvList.AddrCount = 1;
- srvList.AddrArray[0] = htonl(nameserver.toIPv4Address());
- } else if (nameserver.protocol() == QAbstractSocket::IPv6Protocol) {
- // For supoprting IPv6 nameserver addresses, we'll need to switch
- // from DnsQuey() to DnsQueryEx() as it supports passing an IPv6
- // address in the nameserver list
- qWarning("%s", QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
- reply->error = QDnsLookup::ResolverError;
- reply->errorString = tr(QDnsLookupPrivate::msgNoIpV6NameServerAdresses);
- return;
+ // WinDNS wants MTU - IP Header - UDP header for some reason, in spite
+ // of never needing that much
+ QVarLengthArray<unsigned char, 1472> query(1472);
+
+ auto dnsBuffer = new (query.data()) DNS_MESSAGE_BUFFER;
+ DWORD dnsBufferSize = query.size();
+ WORD xid = 0;
+ bool recursionDesired = true;
+
+ SetLastError(ERROR_SUCCESS);
+
+ // MinGW winheaders incorrectly declare the third parameter as LPWSTR
+ if (!DnsWriteQuestionToBuffer_W(dnsBuffer, &dnsBufferSize,
+ const_cast<LPWSTR>(request->QueryName), request->QueryType,
+ xid, recursionDesired)) {
+ // let's try reallocating
+ query.resize(dnsBufferSize);
+ if (!DnsWriteQuestionToBuffer_W(dnsBuffer, &dnsBufferSize,
+ const_cast<LPWSTR>(request->QueryName), request->QueryType,
+ xid, recursionDesired)) {
+ return GetLastError();
}
}
- const DNS_STATUS status = DnsQuery_W(reinterpret_cast<const wchar_t*>(requestNameUtf16.utf16()), requestType, DNS_QUERY_STANDARD, &srvList, &dns_records, NULL);
- switch (status) {
- case ERROR_SUCCESS:
+
+ // set AD bit: we want to trust this server
+ dnsBuffer->MessageHead.AuthenticatedData = true;
+
+ QDnsLookupRunnable::ReplyBuffer replyBuffer;
+ if (!self->sendDnsOverTls(reply, { query.data(), qsizetype(dnsBufferSize) }, replyBuffer))
+ return DNS_STATUS(-1); // error set in reply
+
+ // interpret the RCODE in the reply
+ auto response = reinterpret_cast<PDNS_MESSAGE_BUFFER>(replyBuffer.data());
+ DNS_HEADER *header = &response->MessageHead;
+ if (!header->IsResponse)
+ return DNS_ERROR_BAD_PACKET; // not a reply
+
+ // Convert the byte order for the 16-bit quantities in the header, so
+ // DnsExtractRecordsFromMessage can parse the contents.
+ //header->Xid = qFromBigEndian(header->Xid);
+ header->QuestionCount = qFromBigEndian(header->QuestionCount);
+ header->AnswerCount = qFromBigEndian(header->AnswerCount);
+ header->NameServerCount = qFromBigEndian(header->NameServerCount);
+ header->AdditionalCount = qFromBigEndian(header->AdditionalCount);
+
+ results->QueryOptions = request->QueryOptions;
+ return DnsExtractRecordsFromMessage_W(response, replyBuffer.size(), &results->pQueryRecords);
+}
+
+void QDnsLookupRunnable::query(QDnsLookupReply *reply)
+{
+ // Perform DNS query.
+ alignas(DNS_ADDR_ARRAY) uchar dnsAddresses[sizeof(DNS_ADDR_ARRAY) + sizeof(DNS_ADDR)];
+ DNS_QUERY_REQUEST request = {};
+ request.Version = 1;
+ request.QueryName = reinterpret_cast<const wchar_t *>(requestName.constData());
+ request.QueryType = requestType;
+ request.QueryOptions = DNS_QUERY_STANDARD | DNS_QUERY_TREAT_AS_FQDN;
+
+ if (protocol == QDnsLookup::Standard && !nameserver.isNull()) {
+ memset(dnsAddresses, 0, sizeof(dnsAddresses));
+ request.pDnsServerList = new (dnsAddresses) DNS_ADDR_ARRAY;
+ auto addr = new (request.pDnsServerList->AddrArray) DNS_ADDR[1];
+ auto sa = new (addr[0].MaxSa) sockaddr;
+ request.pDnsServerList->MaxCount = sizeof(dnsAddresses);
+ request.pDnsServerList->AddrCount = 1;
+ // ### setting port 53 seems to cause some systems to fail
+ setSockaddr(sa, nameserver, port == DnsPort ? 0 : port);
+ request.pDnsServerList->Family = sa->sa_family;
+ }
+
+ DNS_QUERY_RESULT results = {};
+ results.Version = 1;
+ DNS_STATUS status = ERROR_INVALID_PARAMETER;
+ switch (protocol) {
+ case QDnsLookup::Standard:
+ status = DnsQueryEx(&request, &results, nullptr);
+ break;
+ case QDnsLookup::DnsOverTls:
+ status = sendAlternate(this, reply, &request, &results);
break;
- case DNS_ERROR_RCODE_FORMAT_ERROR:
- reply->error = QDnsLookup::InvalidRequestError;
- reply->errorString = tr("Server could not process query");
- return;
- case DNS_ERROR_RCODE_SERVER_FAILURE:
- reply->error = QDnsLookup::ServerFailureError;
- reply->errorString = tr("Server failure");
- return;
- case DNS_ERROR_RCODE_NAME_ERROR:
- reply->error = QDnsLookup::NotFoundError;
- reply->errorString = tr("Non existent domain");
- return;
- case DNS_ERROR_RCODE_REFUSED:
- reply->error = QDnsLookup::ServerRefusedError;
- reply->errorString = tr("Server refused to answer");
- return;
- default:
- reply->error = QDnsLookup::InvalidReplyError;
- reply->errorString = QSystemError(status, QSystemError::NativeError).toString();
- return;
}
+ if (status == DNS_STATUS(-1))
+ return; // error already set in reply
+ if (status >= DNS_ERROR_RCODE_FORMAT_ERROR && status <= DNS_ERROR_RCODE_LAST)
+ return reply->makeDnsRcodeError(status - DNS_ERROR_RCODE_FORMAT_ERROR + 1);
+ else if (status == ERROR_TIMEOUT)
+ return reply->makeTimeoutError();
+ else if (status != ERROR_SUCCESS)
+ return reply->makeResolverSystemError(status);
+
+ QStringView lastEncodedName;
+ QString cachedDecodedName;
+ auto extractAndCacheHost = [&](QStringView name) -> const QString & {
+ lastEncodedName = name;
+ cachedDecodedName = decodeLabel(name);
+ return cachedDecodedName;
+ };
+ auto extractMaybeCachedHost = [&](QStringView name) -> const QString & {
+ return lastEncodedName == name ? cachedDecodedName : extractAndCacheHost(name);
+ };
+
// Extract results.
- for (PDNS_RECORD ptr = dns_records; ptr != NULL; ptr = ptr->pNext) {
- const QString name = QUrl::fromAce( QString::fromWCharArray( ptr->pName ).toLatin1() );
+ for (PDNS_RECORD ptr = results.pQueryRecords; ptr != NULL; ptr = ptr->pNext) {
+ // warning: always assign name to the record before calling extractXxxHost() again
+ const QString &name = extractMaybeCachedHost(ptr->pName);
if (ptr->wType == QDnsLookup::A) {
QDnsHostAddressRecord record;
record.d->name = name;
@@ -83,12 +194,12 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ptr->dwTtl;
- record.d->value = QUrl::fromAce(QString::fromWCharArray(ptr->Data.Cname.pNameHost).toLatin1());
+ record.d->value = extractAndCacheHost(ptr->Data.Cname.pNameHost);
reply->canonicalNameRecords.append(record);
} else if (ptr->wType == QDnsLookup::MX) {
QDnsMailExchangeRecord record;
record.d->name = name;
- record.d->exchange = QUrl::fromAce(QString::fromWCharArray(ptr->Data.Mx.pNameExchange).toLatin1());
+ record.d->exchange = decodeLabel(QStringView(ptr->Data.Mx.pNameExchange));
record.d->preference = ptr->Data.Mx.wPreference;
record.d->timeToLive = ptr->dwTtl;
reply->mailExchangeRecords.append(record);
@@ -96,35 +207,54 @@ void QDnsLookupRunnable::query(const int requestType, const QByteArray &requestN
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ptr->dwTtl;
- record.d->value = QUrl::fromAce(QString::fromWCharArray(ptr->Data.Ns.pNameHost).toLatin1());
+ record.d->value = decodeLabel(QStringView(ptr->Data.Ns.pNameHost));
reply->nameServerRecords.append(record);
} else if (ptr->wType == QDnsLookup::PTR) {
QDnsDomainNameRecord record;
record.d->name = name;
record.d->timeToLive = ptr->dwTtl;
- record.d->value = QUrl::fromAce(QString::fromWCharArray(ptr->Data.Ptr.pNameHost).toLatin1());
+ record.d->value = decodeLabel(QStringView(ptr->Data.Ptr.pNameHost));
reply->pointerRecords.append(record);
} else if (ptr->wType == QDnsLookup::SRV) {
QDnsServiceRecord record;
record.d->name = name;
- record.d->target = QUrl::fromAce(QString::fromWCharArray(ptr->Data.Srv.pNameTarget).toLatin1());
+ record.d->target = decodeLabel(QStringView(ptr->Data.Srv.pNameTarget));
record.d->port = ptr->Data.Srv.wPort;
record.d->priority = ptr->Data.Srv.wPriority;
record.d->timeToLive = ptr->dwTtl;
record.d->weight = ptr->Data.Srv.wWeight;
reply->serviceRecords.append(record);
+ } else if (ptr->wType == QDnsLookup::TLSA) {
+ // Note: untested, because the DNS_RECORD reply appears to contain
+ // no records relating to TLSA. Maybe WinDNS filters them out of
+ // zones without DNSSEC.
+ QDnsTlsAssociationRecord record;
+ record.d->name = name;
+ record.d->timeToLive = ptr->dwTtl;
+
+ const auto &tlsa = ptr->Data.Tlsa;
+ const quint8 usage = tlsa.bCertUsage;
+ const quint8 selector = tlsa.bSelector;
+ const quint8 matchType = tlsa.bMatchingType;
+
+ record.d->usage = QDnsTlsAssociationRecord::CertificateUsage(usage);
+ record.d->selector = QDnsTlsAssociationRecord::Selector(selector);
+ record.d->matchType = QDnsTlsAssociationRecord::MatchingType(matchType);
+ record.d->value.assign(tlsa.bCertificateAssociationData,
+ tlsa.bCertificateAssociationData + tlsa.bCertificateAssociationDataLength);
+ reply->tlsAssociationRecords.append(std::move(record));
} else if (ptr->wType == QDnsLookup::TXT) {
QDnsTextRecord record;
record.d->name = name;
record.d->timeToLive = ptr->dwTtl;
for (unsigned int i = 0; i < ptr->Data.Txt.dwStringCount; ++i) {
- record.d->values << QString::fromWCharArray((ptr->Data.Txt.pStringArray[i])).toLatin1();;
+ record.d->values << QStringView(ptr->Data.Txt.pStringArray[i]).toLatin1();
}
reply->textRecords.append(record);
}
}
- DnsRecordListFree(dns_records, DnsFreeRecordList);
+ DnsRecordListFree(results.pQueryRecords, DnsFreeRecordList);
}
QT_END_NAMESPACE
diff --git a/src/network/kernel/qhostaddress.cpp b/src/network/kernel/qhostaddress.cpp
index 827b622ac0..0330fb091b 100644
--- a/src/network/kernel/qhostaddress.cpp
+++ b/src/network/kernel/qhostaddress.cpp
@@ -164,8 +164,12 @@ AddressClassification QHostAddressPrivate::classify() const
return BroadcastAddress;
return UnknownAddress;
}
+ if (((a & 0xff000000U) == 0x0a000000U) // 10.0.0.0/8
+ || ((a & 0xfff00000U) == 0xac100000U) // 172.16.0.0/12
+ || ((a & 0xffff0000U) == 0xc0a80000U)) // 192.168.0.0/16
+ return PrivateNetworkAddress;
- // Not testing for PrivateNetworkAddress and TestNetworkAddress
+ // Not testing for TestNetworkAddress
// since we don't need them yet.
return GlobalAddress;
}
@@ -673,7 +677,7 @@ QHostAddress::NetworkLayerProtocol QHostAddress::protocol() const
\l{QAbstractSocket::}{IPv6Protocol}.
If the protocol is
\l{QAbstractSocket::}{IPv4Protocol},
- then the address is returned an an IPv4 mapped IPv6 address. (RFC4291)
+ then the address is returned as an IPv4 mapped IPv6 address. (RFC4291)
\sa toString()
*/
@@ -702,7 +706,7 @@ QString QHostAddress::toString() const
} else if (d->protocol == QHostAddress::IPv6Protocol) {
QIPAddressUtils::toString(s, d->a6.c);
if (!d->scopeId.isEmpty())
- s.append(u'%' + d->scopeId);
+ s += u'%' + d->scopeId;
}
return s;
}
@@ -1113,7 +1117,7 @@ bool QHostAddress::isLoopback() const
considered as global in new applications. This function returns true for
site-local addresses too.
- \sa isLoopback(), isSiteLocal(), isUniqueLocalUnicast()
+ \sa isLoopback(), isSiteLocal(), isUniqueLocalUnicast(), isPrivateUse()
*/
bool QHostAddress::isGlobal() const
{
@@ -1131,7 +1135,7 @@ bool QHostAddress::isGlobal() const
\l{https://www.iana.org/assignments/ipv6-address-space/ipv6-address-space.xhtml}{IANA
IPv6 Address Space} registry for more information.
- \sa isLoopback(), isGlobal(), isMulticast(), isSiteLocal(), isUniqueLocalUnicast()
+ \sa isLoopback(), isGlobal(), isMulticast(), isSiteLocal(), isUniqueLocalUnicast(), isPrivateUse()
*/
bool QHostAddress::isLinkLocal() const
{
@@ -1154,7 +1158,7 @@ bool QHostAddress::isLinkLocal() const
isGlobal() also returns true). Site-local addresses were replaced by Unique
Local Addresses (ULA).
- \sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast()
+ \sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast(), isPrivateUse()
*/
bool QHostAddress::isSiteLocal() const
{
@@ -1175,7 +1179,7 @@ bool QHostAddress::isSiteLocal() const
4193 says that, in practice, "applications may treat these addresses like
global scoped addresses." Only routers need care about the distinction.
- \sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast()
+ \sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast(), isPrivateUse()
*/
bool QHostAddress::isUniqueLocalUnicast() const
{
@@ -1188,7 +1192,7 @@ bool QHostAddress::isUniqueLocalUnicast() const
Returns \c true if the address is an IPv4 or IPv6 multicast address, \c
false otherwise.
- \sa isLoopback(), isGlobal(), isLinkLocal(), isSiteLocal(), isUniqueLocalUnicast()
+ \sa isLoopback(), isGlobal(), isLinkLocal(), isSiteLocal(), isUniqueLocalUnicast(), isPrivateUse()
*/
bool QHostAddress::isMulticast() const
{
@@ -1205,13 +1209,27 @@ bool QHostAddress::isMulticast() const
broadcast address. For that, please use \l QNetworkInterface to obtain the
broadcast addresses of the local machine.
- \sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast()
+ \sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast(), isPrivateUse()
*/
bool QHostAddress::isBroadcast() const
{
return d->classify() == BroadcastAddress;
}
+/*!
+ \since 6.6
+
+ Returns \c true if the address is an IPv6 unique local unicast address or
+ IPv4 address reserved for local networks by \l {RFC 1918}, \c false otherwise.
+
+ \sa isLoopback(), isGlobal(), isMulticast(), isLinkLocal(), isUniqueLocalUnicast(), isBroadcast()
+*/
+bool QHostAddress::isPrivateUse() const
+{
+ const AddressClassification classification = d->classify();
+ return (classification == PrivateNetworkAddress) || (classification == UniqueLocalAddress);
+}
+
#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug d, const QHostAddress &address)
{
diff --git a/src/network/kernel/qhostaddress.h b/src/network/kernel/qhostaddress.h
index e71f954ac8..6aa045c959 100644
--- a/src/network/kernel/qhostaddress.h
+++ b/src/network/kernel/qhostaddress.h
@@ -127,6 +127,7 @@ public:
bool isUniqueLocalUnicast() const;
bool isMulticast() const;
bool isBroadcast() const;
+ bool isPrivateUse() const;
static QPair<QHostAddress, int> parseSubnet(const QString &subnet);
diff --git a/src/network/kernel/qhostinfo.cpp b/src/network/kernel/qhostinfo.cpp
index 2283766b97..62bb210ca1 100644
--- a/src/network/kernel/qhostinfo.cpp
+++ b/src/network/kernel/qhostinfo.cpp
@@ -72,6 +72,16 @@ Q_APPLICATION_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
}
+QHostInfoResult::QHostInfoResult(const QObject *receiver, QtPrivate::SlotObjUniquePtr slot)
+ : receiver{receiver ? receiver : this}, slotObj{std::move(slot)}
+{
+ Q_ASSERT(this->receiver);
+ moveToThread(this->receiver->thread());
+}
+
+QHostInfoResult::~QHostInfoResult()
+ = default;
+
/*
The calling thread is likely the one that executes the lookup via
QHostInfoRunnable. Unless we operate with a queued connection already,
@@ -80,8 +90,8 @@ Q_APPLICATION_STATIC(QHostInfoLookupManager, theHostInfoLookupManager)
the thread that made the call to lookupHost. That QHostInfoResult object
then calls the user code in the correct thread.
- The 'result' object deletes itself (via deleteLater) when the metacall
- event is received.
+ The 'result' object deletes itself (via deleteLater) when
+ finalizePostResultsReady is called.
*/
void QHostInfoResult::postResultsReady(const QHostInfo &info)
{
@@ -91,55 +101,33 @@ void QHostInfoResult::postResultsReady(const QHostInfo &info)
return;
}
// we used to have a context object, but it's already destroyed
- if (withContextObject && !receiver)
+ if (!receiver)
return;
- static const int signal_index = []() -> int {
- auto senderMetaObject = &QHostInfoResult::staticMetaObject;
- auto signal = &QHostInfoResult::resultsReady;
- int signal_index = -1;
- void *args[] = { &signal_index, &signal };
- senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);
- return signal_index + QMetaObjectPrivate::signalOffset(senderMetaObject);
- }();
-
// a long-living version of this
auto result = new QHostInfoResult(this);
Q_CHECK_PTR(result);
- const int nargs = 2;
- auto metaCallEvent = new QMetaCallEvent(slotObj, nullptr, signal_index, nargs);
- Q_CHECK_PTR(metaCallEvent);
- void **args = metaCallEvent->args();
- QMetaType *types = metaCallEvent->types();
- auto voidType = QMetaType::fromType<void>();
- auto hostInfoType = QMetaType::fromType<QHostInfo>();
- types[0] = voidType;
- types[1] = hostInfoType;
- args[0] = nullptr;
- args[1] = hostInfoType.create(&info);
- Q_CHECK_PTR(args[1]);
- qApp->postEvent(result, metaCallEvent);
+ QMetaObject::invokeMethod(result,
+ &QHostInfoResult::finalizePostResultsReady,
+ Qt::QueuedConnection,
+ info);
}
/*
- Receives the event posted by postResultsReady, and calls the functor.
+ Receives the info from postResultsReady, and calls the functor.
*/
-bool QHostInfoResult::event(QEvent *event)
+void QHostInfoResult::finalizePostResultsReady(const QHostInfo &info)
{
- if (event->type() == QEvent::MetaCall) {
- Q_ASSERT(slotObj);
- auto metaCallEvent = static_cast<QMetaCallEvent *>(event);
- auto args = metaCallEvent->args();
- // we didn't have a context object, or it's still alive
- if (!withContextObject || receiver)
- slotObj->call(const_cast<QObject*>(receiver.data()), args);
- slotObj->destroyIfLastRef();
-
- deleteLater();
- return true;
+ Q_ASSERT(slotObj);
+
+ // we used to have a context object, but it's already destroyed
+ if (receiver) {
+ void *args[] = { nullptr, const_cast<QHostInfo *>(&info) };
+ slotObj->call(const_cast<QObject *>(receiver.data()), args);
}
- return QObject::event(event);
+
+ deleteLater();
}
/*!
@@ -233,10 +221,17 @@ static int nextId()
\note There is no guarantee on the order the signals will be emitted
if you start multiple requests with lookupHost().
+ \note In Qt versions prior to 6.7, this function took \a receiver as
+ (non-const) \c{QObject*}.
+
\sa abortHostLookup(), addresses(), error(), fromName()
*/
-int QHostInfo::lookupHost(const QString &name, QObject *receiver, const char *member)
+int QHostInfo::lookupHost(const QString &name, const QObject *receiver, const char *member)
{
+ if (!receiver || !member) {
+ qWarning("QHostInfo::lookupHost: both the receiver and the member to invoke must be non-null");
+ return -1;
+ }
return QHostInfo::lookupHostImpl(name, receiver, nullptr, member);
}
@@ -262,7 +257,7 @@ int QHostInfo::lookupHost(const QString &name, QObject *receiver, const char *me
*/
/*!
- \fn template<typename Functor> int QHostInfo::lookupHost(const QString &name, Functor functor)
+ \fn template<typename Functor> int QHostInfo::lookupHost(const QString &name, Functor &&functor)
\since 5.9
@@ -734,22 +729,25 @@ QString QHostInfo::localHostName()
\internal
Called by the various lookupHost overloads to perform the lookup.
- Signals either the functor encapuslated in the \a slotObj in the context
+ Signals either the functor encapuslated in the \a slotObjRaw in the context
of \a receiver, or the \a member slot of the \a receiver.
- \a receiver might be the nullptr, but only if a \a slotObj is provided.
+ \a receiver might be the nullptr, but only if a \a slotObjRaw is provided.
*/
int QHostInfo::lookupHostImpl(const QString &name,
const QObject *receiver,
- QtPrivate::QSlotObjectBase *slotObj,
+ QtPrivate::QSlotObjectBase *slotObjRaw,
const char *member)
{
+ QtPrivate::SlotObjUniquePtr slotObj{slotObjRaw};
#if defined QHOSTINFO_DEBUG
qDebug("QHostInfo::lookupHostImpl(\"%s\", %p, %p, %s)",
- name.toLatin1().constData(), receiver, slotObj, member ? member + 1 : 0);
+ name.toLatin1().constData(), receiver, slotObj.get(), member ? member + 1 : 0);
#endif
Q_ASSERT(!member != !slotObj); // one of these must be set, but not both
Q_ASSERT(receiver || slotObj);
+ Q_ASSERT(!member || receiver); // if member is set, also is receiver
+ const bool isUsingStringBasedSlot = static_cast<bool>(member);
if (!QAbstractEventDispatcher::instance(QThread::currentThread())) {
qWarning("QHostInfo::lookupHost() called with no event dispatcher");
@@ -765,10 +763,11 @@ int QHostInfo::lookupHostImpl(const QString &name,
hostInfo.setError(QHostInfo::HostNotFound);
hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given"));
- QHostInfoResult result(receiver, slotObj);
- if (receiver && member)
+ QHostInfoResult result(receiver, std::move(slotObj));
+ if (isUsingStringBasedSlot) {
QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)),
receiver, member, Qt::QueuedConnection);
+ }
result.postResultsReady(hostInfo);
return id;
@@ -782,10 +781,11 @@ int QHostInfo::lookupHostImpl(const QString &name,
QHostInfo hostInfo = QHostInfoAgent::lookup(name);
hostInfo.setLookupId(id);
- QHostInfoResult result(receiver, slotObj);
- if (receiver && member)
+ QHostInfoResult result(receiver, std::move(slotObj));
+ if (isUsingStringBasedSlot) {
QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)),
receiver, member, Qt::QueuedConnection);
+ }
result.postResultsReady(hostInfo);
#else
QHostInfoLookupManager *manager = theHostInfoLookupManager();
@@ -798,20 +798,22 @@ int QHostInfo::lookupHostImpl(const QString &name,
QHostInfo info = manager->cache.get(name, &valid);
if (valid) {
info.setLookupId(id);
- QHostInfoResult result(receiver, slotObj);
- if (receiver && member)
+ QHostInfoResult result(receiver, std::move(slotObj));
+ if (isUsingStringBasedSlot) {
QObject::connect(&result, SIGNAL(resultsReady(QHostInfo)),
receiver, member, Qt::QueuedConnection);
+ }
result.postResultsReady(info);
return id;
}
}
// cache is not enabled or it was not in the cache, do normal lookup
- QHostInfoRunnable *runnable = new QHostInfoRunnable(name, id, receiver, slotObj);
- if (receiver && member)
+ QHostInfoRunnable *runnable = new QHostInfoRunnable(name, id, receiver, std::move(slotObj));
+ if (isUsingStringBasedSlot) {
QObject::connect(&runnable->resultEmitter, SIGNAL(resultsReady(QHostInfo)),
receiver, member, Qt::QueuedConnection);
+ }
manager->scheduleLookup(runnable);
}
#endif // Q_OS_WASM
@@ -819,12 +821,15 @@ int QHostInfo::lookupHostImpl(const QString &name,
}
QHostInfoRunnable::QHostInfoRunnable(const QString &hn, int i, const QObject *receiver,
- QtPrivate::QSlotObjectBase *slotObj) :
- toBeLookedUp(hn), id(i), resultEmitter(receiver, slotObj)
+ QtPrivate::SlotObjUniquePtr slotObj)
+ : toBeLookedUp{hn}, id{i}, resultEmitter{receiver, std::move(slotObj)}
{
setAutoDelete(true);
}
+QHostInfoRunnable::~QHostInfoRunnable()
+ = default;
+
// the QHostInfoLookupManager will at some point call this via a QThreadPool
void QHostInfoRunnable::run()
{
@@ -962,7 +967,7 @@ void QHostInfoLookupManager::rescheduleWithMutexHeld()
isAlreadyRunning).second,
scheduledLookups.end());
- const int availableThreads = threadPool.maxThreadCount() - currentLookups.size();
+ const int availableThreads = std::max(threadPool.maxThreadCount(), 1) - currentLookups.size();
if (availableThreads > 0) {
int readyToStartCount = qMin(availableThreads, scheduledLookups.size());
auto it = scheduledLookups.begin();
@@ -1000,6 +1005,9 @@ void QHostInfoLookupManager::abortLookup(int id)
if (wasDeleted)
return;
+ if (id == -1)
+ return;
+
#if QT_CONFIG(thread)
// is postponed? delete and return
for (int i = 0; i < postponedLookups.size(); i++) {
diff --git a/src/network/kernel/qhostinfo.h b/src/network/kernel/qhostinfo.h
index 47db075b44..3942e41498 100644
--- a/src/network/kernel/qhostinfo.h
+++ b/src/network/kernel/qhostinfo.h
@@ -17,6 +17,7 @@ class QHostInfoPrivate;
class Q_NETWORK_EXPORT QHostInfo
{
+ Q_GADGET
public:
enum HostInfoError {
NoError,
@@ -48,7 +49,10 @@ public:
void setLookupId(int id);
int lookupId() const;
+#if QT_NETWORK_REMOVED_SINCE(6, 7)
static int lookupHost(const QString &name, QObject *receiver, const char *member);
+#endif
+ static int lookupHost(const QString &name, const QObject *receiver, const char *member);
static void abortHostLookup(int lookupId);
static QHostInfo fromName(const QString &name);
@@ -57,59 +61,30 @@ public:
#ifdef Q_QDOC
template<typename Functor>
- static int lookupHost(const QString &name, Functor functor);
- template<typename Functor>
static int lookupHost(const QString &name, const QObject *context, Functor functor);
#else
- // lookupHost to a QObject slot
- template <typename Func>
+ // lookupHost to a callable (with context)
+ template <typename Functor>
static inline int lookupHost(const QString &name,
- const typename QtPrivate::FunctionPointer<Func>::Object *receiver,
- Func slot)
+ const typename QtPrivate::ContextTypeForFunctor<Functor>::ContextType *receiver,
+ Functor &&func)
{
- typedef QtPrivate::FunctionPointer<Func> SlotType;
-
- typedef QtPrivate::FunctionPointer<void (*)(QHostInfo)> SignalType;
- static_assert(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
- "The slot requires more arguments than the signal provides.");
- static_assert((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments,
- typename SlotType::Arguments>::value),
- "Signal and slot arguments are not compatible.");
- static_assert((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType,
- typename SignalType::ReturnType>::value),
- "Return type of the slot is not compatible "
- "with the return type of the signal.");
-
- auto slotObj = new QtPrivate::QSlotObject<Func, typename SlotType::Arguments, void>(slot);
- return lookupHostImpl(name, receiver, slotObj, nullptr);
+ using Prototype = void(*)(QHostInfo);
+ QtPrivate::AssertCompatibleFunctions<Prototype, Functor>();
+ return lookupHostImpl(name, receiver,
+ QtPrivate::makeCallableObject<Prototype>(std::forward<Functor>(func)),
+ nullptr);
}
+#endif // Q_QDOC
+#ifndef QT_NO_CONTEXTLESS_CONNECT
// lookupHost to a callable (without context)
- template <typename Func>
- static inline typename std::enable_if<!QtPrivate::FunctionPointer<Func>::IsPointerToMemberFunction &&
- !std::is_same<const char *, Func>::value, int>::type
- lookupHost(const QString &name, Func slot)
- {
- return lookupHost(name, nullptr, std::move(slot));
- }
-
- // lookupHost to a functor or function pointer (with context)
- template <typename Func1>
- static inline typename std::enable_if<!QtPrivate::FunctionPointer<Func1>::IsPointerToMemberFunction &&
- !std::is_same<const char*, Func1>::value, int>::type
- lookupHost(const QString &name, QObject *context, Func1 slot)
+ template <typename Functor>
+ static inline int lookupHost(const QString &name, Functor &&slot)
{
- typedef QtPrivate::FunctionPointer<Func1> SlotType;
-
- static_assert(int(SlotType::ArgumentCount) <= 1,
- "The slot must not require more than one argument");
-
- auto slotObj = new QtPrivate::QFunctorSlotObject<Func1, 1,
- typename QtPrivate::List<QHostInfo>,
- void>(std::move(slot));
- return lookupHostImpl(name, context, slotObj, nullptr);
+ return lookupHost(name, nullptr, std::forward<Functor>(slot));
}
-#endif // Q_QDOC
+#endif // QT_NO_CONTEXTLESS_CONNECT
private:
QHostInfoPrivate *d_ptr;
diff --git a/src/network/kernel/qhostinfo_p.h b/src/network/kernel/qhostinfo_p.h
index 22b6ccc017..b229eb1cd8 100644
--- a/src/network/kernel/qhostinfo_p.h
+++ b/src/network/kernel/qhostinfo_p.h
@@ -34,8 +34,6 @@
#include <QElapsedTimer>
#include <QCache>
-#include <QSharedPointer>
-
#include <atomic>
QT_BEGIN_NAMESPACE
@@ -45,26 +43,21 @@ class QHostInfoResult : public QObject
{
Q_OBJECT
public:
- QHostInfoResult(const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj)
- : receiver(receiver), slotObj(slotObj),
- withContextObject(slotObj && receiver)
- {
- if (receiver)
- moveToThread(receiver->thread());
- }
+ explicit QHostInfoResult(const QObject *receiver, QtPrivate::SlotObjUniquePtr slot);
+ ~QHostInfoResult() override;
void postResultsReady(const QHostInfo &info);
Q_SIGNALS:
void resultsReady(const QHostInfo &info);
-protected:
- bool event(QEvent *event) override;
+private Q_SLOTS:
+ void finalizePostResultsReady(const QHostInfo &info);
private:
- QHostInfoResult(const QHostInfoResult *other)
- : receiver(other->receiver), slotObj(other->slotObj),
- withContextObject(other->withContextObject)
+ QHostInfoResult(QHostInfoResult *other)
+ : receiver(other->receiver.get() != other ? other->receiver.get() : this),
+ slotObj{std::move(other->slotObj)}
{
// cleanup if the application terminates before results are delivered
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit,
@@ -73,9 +66,10 @@ private:
moveToThread(other->thread());
}
+ // receiver is either a QObject provided by the user,
+ // or it's set to `this` (to emulate the behavior of the contextless connect())
QPointer<const QObject> receiver = nullptr;
- QtPrivate::QSlotObjectBase *slotObj = nullptr;
- const bool withContextObject = false;
+ QtPrivate::SlotObjUniquePtr slotObj;
};
class QHostInfoAgent
@@ -143,8 +137,10 @@ private:
class QHostInfoRunnable : public QRunnable
{
public:
- QHostInfoRunnable(const QString &hn, int i, const QObject *receiver,
- QtPrivate::QSlotObjectBase *slotObj);
+ explicit QHostInfoRunnable(const QString &hn, int i, const QObject *receiver,
+ QtPrivate::SlotObjUniquePtr slotObj);
+ ~QHostInfoRunnable() override;
+
void run() override;
QString toBeLookedUp;
diff --git a/src/network/kernel/qhostinfo_unix.cpp b/src/network/kernel/qhostinfo_unix.cpp
index 12d8c04d10..80d386a13d 100644
--- a/src/network/kernel/qhostinfo_unix.cpp
+++ b/src/network/kernel/qhostinfo_unix.cpp
@@ -3,140 +3,78 @@
//#define QHOSTINFO_DEBUG
-#include "qplatformdefs.h"
-
#include "qhostinfo_p.h"
-#include "private/qnativesocketengine_p.h"
-#include "qiodevice.h"
+
#include <qbytearray.h>
-#if QT_CONFIG(library)
-#include <qlibrary.h>
-#endif
-#include <qbasicatomic.h>
-#include <qurl.h>
#include <qfile.h>
-#include <private/qnet_unix_p.h>
-
-#include "QtCore/qapplicationstatic.h"
+#include <qplatformdefs.h>
+#include <qurl.h>
#include <sys/types.h>
#include <netdb.h>
-#include <arpa/inet.h>
-#if defined(Q_OS_VXWORKS)
-# include <hostLib.h>
-#else
-# include <resolv.h>
-#endif
+#include <netinet/in.h>
-#if defined(__GNU_LIBRARY__) && !defined(__UCLIBC__)
-# include <gnu/lib-names.h>
+#if QT_CONFIG(libresolv)
+# include <resolv.h>
#endif
-#if defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen)
-# include <dlfcn.h>
+#ifndef _PATH_RESCONF
+# define _PATH_RESCONF "/etc/resolv.conf"
#endif
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-enum LibResolvFeature {
- NeedResInit,
- NeedResNInit
-};
-
-typedef struct __res_state *res_state_ptr;
-
-typedef int (*res_init_proto)(void);
-static res_init_proto local_res_init = nullptr;
-typedef int (*res_ninit_proto)(res_state_ptr);
-static res_ninit_proto local_res_ninit = nullptr;
-typedef void (*res_nclose_proto)(res_state_ptr);
-static res_nclose_proto local_res_nclose = nullptr;
-static res_state_ptr local_res = nullptr;
-
-#if QT_CONFIG(library) && !defined(Q_OS_QNX)
-namespace {
-struct LibResolv
-{
- enum {
-#ifdef RES_NORELOAD
- // If RES_NORELOAD is defined, then the libc is capable of watching
- // /etc/resolv.conf for changes and reloading as necessary. So accept
- // whatever is configured.
- ReinitNecessary = false
-#else
- ReinitNecessary = true
-#endif
- };
-
- QLibrary lib;
- LibResolv();
- ~LibResolv() { lib.unload(); }
-};
-}
-
-static QFunctionPointer resolveSymbol(QLibrary &lib, const char *sym)
-{
- if (lib.isLoaded())
- return lib.resolve(sym);
-
-#if defined(RTLD_DEFAULT) && (defined(Q_OS_FREEBSD) || QT_CONFIG(dlopen))
- return reinterpret_cast<QFunctionPointer>(dlsym(RTLD_DEFAULT, sym));
-#else
- return nullptr;
-#endif
-}
-
-LibResolv::LibResolv()
+static void maybeRefreshResolver()
{
-#ifdef LIBRESOLV_SO
- lib.setFileName(QStringLiteral(LIBRESOLV_SO));
- if (!lib.load())
+#if defined(RES_NORELOAD)
+ // If RES_NORELOAD is defined, then the libc is capable of watching
+ // /etc/resolv.conf for changes and reloading as necessary. So accept
+ // whatever is configured.
+ return;
+#elif defined(Q_OS_DARWIN)
+ // Apple's libsystem_info.dylib:getaddrinfo() uses the
+ // libsystem_dnssd.dylib to resolve hostnames. Using res_init() has no
+ // effect on it and is thread-unsafe.
+ return;
+#elif defined(Q_OS_FREEBSD)
+ // FreeBSD automatically refreshes:
+ // https://github.com/freebsd/freebsd-src/blob/b3fe5d932264445cbf9a1c4eab01afb6179b499b/lib/libc/resolv/res_state.c#L69
+ return;
+#elif defined(Q_OS_OPENBSD)
+ // OpenBSD automatically refreshes:
+ // https://github.com/ligurio/openbsd-src/blob/b1ce0da17da254cc15b8aff25b3d55d3c7a82cec/lib/libc/asr/asr.c#L367
+ return;
+#elif defined(Q_OS_QNX)
+ // res_init() is not thread-safe; executing it leads to state corruption.
+ // Whether it reloads resolv.conf on its own is unknown.
+ return;
#endif
- {
- lib.setFileName("resolv"_L1);
- lib.load();
- }
-
- // res_ninit is required for localDomainName()
- local_res_ninit = res_ninit_proto(resolveSymbol(lib, "__res_ninit"));
- if (!local_res_ninit)
- local_res_ninit = res_ninit_proto(resolveSymbol(lib, "res_ninit"));
- if (local_res_ninit) {
- // we must now find res_nclose
- local_res_nclose = res_nclose_proto(resolveSymbol(lib, "res_nclose"));
- if (!local_res_nclose)
- local_res_nclose = res_nclose_proto(resolveSymbol(lib, "__res_nclose"));
- if (!local_res_nclose)
- local_res_ninit = nullptr;
- }
-
- if (ReinitNecessary || !local_res_ninit) {
- local_res_init = res_init_proto(resolveSymbol(lib, "__res_init"));
- if (!local_res_init)
- local_res_init = res_init_proto(resolveSymbol(lib, "res_init"));
- if (local_res_init && !local_res_ninit) {
- // if we can't get a thread-safe context, we have to use the global _res state
- local_res = res_state_ptr(resolveSymbol(lib, "_res"));
+#if QT_CONFIG(libresolv)
+ // OSes known or thought to reach here: AIX, NetBSD, Solaris,
+ // Linux with MUSL (though res_init() does nothing and is unnecessary)
+
+ Q_CONSTINIT static QT_STATBUF lastStat = {};
+ Q_CONSTINIT static QBasicMutex mutex = {};
+ if (QT_STATBUF st; QT_STAT(_PATH_RESCONF, &st) == 0) {
+ QMutexLocker locker(&mutex);
+ bool refresh = false;
+ if ((_res.options & RES_INIT) == 0)
+ refresh = true;
+ else if (lastStat.st_ctime != st.st_ctime)
+ refresh = true; // file was updated
+ else if (lastStat.st_dev != st.st_dev || lastStat.st_ino != st.st_ino)
+ refresh = true; // file was replaced
+ if (refresh) {
+ lastStat = st;
+ res_init();
}
}
+#endif
}
-Q_APPLICATION_STATIC(LibResolv, libResolv)
-
-static void resolveLibrary(LibResolvFeature f)
-{
- if (LibResolv::ReinitNecessary || f == NeedResNInit)
- libResolv();
-}
-#else // QT_CONFIG(library) || Q_OS_QNX
-static void resolveLibrary(LibResolvFeature)
-{
-}
-#endif // QT_CONFIG(library) || Q_OS_QNX
-
QHostInfo QHostInfoAgent::fromName(const QString &hostName)
{
QHostInfo results;
@@ -146,12 +84,7 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName)
hostName.toLatin1().constData());
#endif
- // Load res_init on demand.
- resolveLibrary(NeedResInit);
-
- // If res_init is available, poll it.
- if (local_res_init)
- local_res_init();
+ maybeRefreshResolver();
QHostAddress address;
if (address.setAddress(hostName))
@@ -162,56 +95,49 @@ QHostInfo QHostInfoAgent::fromName(const QString &hostName)
QString QHostInfo::localDomainName()
{
-#if !defined(Q_OS_VXWORKS) && !defined(Q_OS_ANDROID)
- resolveLibrary(NeedResNInit);
- if (local_res_ninit) {
- // using thread-safe version
- res_state_ptr state = res_state_ptr(malloc(sizeof(*state)));
- Q_CHECK_PTR(state);
- memset(state, 0, sizeof(*state));
- local_res_ninit(state);
- QString domainName = QUrl::fromAce(state->defdname);
+#if QT_CONFIG(libresolv)
+ auto domainNameFromRes = [](res_state r) {
+ QString domainName;
+ if (r->defdname[0])
+ domainName = QUrl::fromAce(r->defdname);
if (domainName.isEmpty())
- domainName = QUrl::fromAce(state->dnsrch[0]);
- local_res_nclose(state);
- free(state);
-
+ domainName = QUrl::fromAce(r->dnsrch[0]);
return domainName;
+ };
+ std::remove_pointer_t<res_state> state = {};
+ if (res_ninit(&state) == 0) {
+ // using thread-safe version
+ auto guard = qScopeGuard([&] { res_nclose(&state); });
+ return domainNameFromRes(&state);
}
- if (local_res_init && local_res) {
- // using thread-unsafe version
+ // using thread-unsafe version
+ maybeRefreshResolver();
+ return domainNameFromRes(&_res);
+#endif // !QT_CONFIG(libresolv)
- local_res_init();
- QString domainName = QUrl::fromAce(local_res->defdname);
- if (domainName.isEmpty())
- domainName = QUrl::fromAce(local_res->dnsrch[0]);
- return domainName;
- }
-#endif
// nothing worked, try doing it by ourselves:
QFile resolvconf;
-#if defined(_PATH_RESCONF)
- resolvconf.setFileName(QFile::decodeName(_PATH_RESCONF));
-#else
- resolvconf.setFileName("/etc/resolv.conf"_L1);
-#endif
+ resolvconf.setFileName(_PATH_RESCONF ""_L1);
if (!resolvconf.open(QIODevice::ReadOnly))
return QString(); // failure
QString domainName;
while (!resolvconf.atEnd()) {
- QByteArray line = resolvconf.readLine().trimmed();
- if (line.startsWith("domain "))
- return QUrl::fromAce(line.mid(sizeof "domain " - 1).trimmed());
+ const QByteArray lineArray = resolvconf.readLine();
+ QByteArrayView line = QByteArrayView(lineArray).trimmed();
+ constexpr QByteArrayView domainWithSpace = "domain ";
+ if (line.startsWith(domainWithSpace))
+ return QUrl::fromAce(line.mid(domainWithSpace.size()).trimmed().toByteArray());
// in case there's no "domain" line, fall back to the first "search" entry
- if (domainName.isEmpty() && line.startsWith("search ")) {
- QByteArray searchDomain = line.mid(sizeof "search " - 1).trimmed();
+ constexpr QByteArrayView searchWithSpace = "search ";
+ if (domainName.isEmpty() && line.startsWith(searchWithSpace)) {
+ QByteArrayView searchDomain = line.mid(searchWithSpace.size()).trimmed();
int pos = searchDomain.indexOf(' ');
if (pos != -1)
searchDomain.truncate(pos);
- domainName = QUrl::fromAce(searchDomain);
+ domainName = QUrl::fromAce(searchDomain.toByteArray());
}
}
diff --git a/src/network/kernel/qnetconmonitor_darwin.mm b/src/network/kernel/qnetconmonitor_darwin.mm
index 639e267c05..60b3cd6581 100644
--- a/src/network/kernel/qnetconmonitor_darwin.mm
+++ b/src/network/kernel/qnetconmonitor_darwin.mm
@@ -1,7 +1,7 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-#include "private/qnativesocketengine_p.h"
+#include "private/qnativesocketengine_p_p.h"
#include "private/qnetconmonitor_p.h"
#include "private/qobject_p.h"
diff --git a/src/network/kernel/qnetconmonitor_win.cpp b/src/network/kernel/qnetconmonitor_win.cpp
index 64bd90b0ad..bf6aff1e46 100644
--- a/src/network/kernel/qnetconmonitor_win.cpp
+++ b/src/network/kernel/qnetconmonitor_win.cpp
@@ -167,6 +167,12 @@ QNetworkConnectionEvents::~QNetworkConnectionEvents()
ComPtr<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid)
{
+ if (!networkListManager) {
+ qCDebug(lcNetMon) << "Failed to enumerate network connections:"
+ << "NetworkListManager was not instantiated";
+ return nullptr;
+ }
+
ComPtr<IEnumNetworkConnections> connections;
auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf());
if (FAILED(hr)) {
diff --git a/src/network/kernel/qnetworkdatagram.cpp b/src/network/kernel/qnetworkdatagram.cpp
index e9e3a67edf..a97eb25a51 100644
--- a/src/network/kernel/qnetworkdatagram.cpp
+++ b/src/network/kernel/qnetworkdatagram.cpp
@@ -481,7 +481,7 @@ void QNetworkDatagram::makeReply_helper_inplace(const QByteArray &data)
void QNetworkDatagram::destroy(QNetworkDatagramPrivate *d)
{
- Q_ASSUME(d);
+ Q_ASSERT(d);
delete d;
}
diff --git a/src/network/kernel/qnetworkinformation.cpp b/src/network/kernel/qnetworkinformation.cpp
index 16d8e4d040..10d6b89e2c 100644
--- a/src/network/kernel/qnetworkinformation.cpp
+++ b/src/network/kernel/qnetworkinformation.cpp
@@ -26,7 +26,7 @@ struct QNetworkInformationDeleter
void operator()(QNetworkInformation *information) { delete information; }
};
-Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
+Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, qniLoader,
(QNetworkInformationBackendFactory_iid,
QStringLiteral("/networkinformation")))
@@ -47,14 +47,21 @@ static void networkInfoCleanup()
if (!instance)
return;
- auto needsReinvoke = instance->thread() && instance->thread() != QThread::currentThread();
- if (needsReinvoke) {
- QMetaObject::invokeMethod(dataHolder->instanceHolder.get(), []() { networkInfoCleanup(); });
- return;
- }
dataHolder->instanceHolder.reset();
}
+using namespace Qt::Literals::StringLiterals;
+
+class QNetworkInformationDummyBackend : public QNetworkInformationBackend {
+ Q_OBJECT
+public:
+ QString name() const override { return u"dummy"_s; }
+ QNetworkInformation::Features featuresSupported() const override
+ {
+ return {};
+ }
+};
+
class QNetworkInformationPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QNetworkInformation)
@@ -65,6 +72,7 @@ public:
static QNetworkInformation *create(QNetworkInformation::Features features);
static QNetworkInformation *create(QStringView name);
+ static QNetworkInformation *createDummy();
static QNetworkInformation *instance()
{
if (!dataHolder())
@@ -84,7 +92,7 @@ private:
bool QNetworkInformationPrivate::initializeList()
{
- if (!loader())
+ if (!qniLoader())
return false;
if (!dataHolder())
return false;
@@ -92,11 +100,11 @@ bool QNetworkInformationPrivate::initializeList()
QMutexLocker initLocker(&mutex);
#if QT_CONFIG(library)
- loader->update();
+ qniLoader->update();
#endif
// Instantiates the plugins (and registers the factories)
int index = 0;
- while (loader->instance(index))
+ while (qniLoader->instance(index))
++index;
initLocker.unlock();
@@ -188,7 +196,7 @@ QNetworkInformation *QNetworkInformationPrivate::create(QStringView name)
QString listNames;
listNames.reserve(8 * dataHolder->factories.count());
for (const auto *factory : std::as_const(dataHolder->factories))
- listNames += factory->name() + QStringLiteral(", ");
+ listNames += factory->name() + ", "_L1;
listNames.chop(2);
qDebug().nospace() << "Couldn't find " << name << " in list with names: { "
<< listNames << " }";
@@ -230,7 +238,7 @@ QNetworkInformation *QNetworkInformationPrivate::create(QNetworkInformation::Fea
return dataHolder->instanceHolder.get();
const auto supportsRequestedFeatures = [features](QNetworkInformationBackendFactory *factory) {
- return factory && (factory->featuresSupported() & features) == features;
+ return factory && factory->featuresSupported().testFlags(features);
};
for (auto it = dataHolder->factories.cbegin(), end = dataHolder->factories.cend(); it != end;
@@ -271,6 +279,20 @@ QNetworkInformation *QNetworkInformationPrivate::create(QNetworkInformation::Fea
return nullptr;
}
+QNetworkInformation *QNetworkInformationPrivate::createDummy()
+{
+ if (!dataHolder())
+ return nullptr;
+
+ QMutexLocker locker(&dataHolder->instanceMutex);
+ if (dataHolder->instanceHolder)
+ return dataHolder->instanceHolder.get();
+
+ QNetworkInformationBackend *backend = new QNetworkInformationDummyBackend;
+ dataHolder->instanceHolder.reset(new QNetworkInformation(backend));
+ return dataHolder->instanceHolder.get();
+}
+
/*!
\class QNetworkInformationBackend
\internal (Semi-private)
@@ -492,6 +514,14 @@ QNetworkInformation::QNetworkInformation(QNetworkInformationBackend *backend)
&QNetworkInformation::transportMediumChanged);
connect(backend, &QNetworkInformationBackend::isMeteredChanged, this,
&QNetworkInformation::isMeteredChanged);
+
+ QThread *main = nullptr;
+
+ if (QCoreApplication::instance())
+ main = QCoreApplication::instance()->thread();
+
+ if (main && thread() != main)
+ moveToThread(main);
}
/*!
@@ -599,6 +629,12 @@ QNetworkInformation::Features QNetworkInformation::supportedFeatures() const
Attempts to load the platform-default backend.
+ \note Starting with 6.7 this tries to load any backend that supports
+ \l{QNetworkInformation::Feature::Reachability}{Reachability} if the
+ platform-default backend is not available or fails to load.
+ If this also fails it will fall back to a backend that only returns
+ the default values for all properties.
+
This platform-to-plugin mapping is as follows:
\table
@@ -619,12 +655,14 @@ QNetworkInformation::Features QNetworkInformation::supportedFeatures() const
\li networkmanager
\endtable
- This function is provided for convenience where the default for a given
- platform is good enough. If you are not using the default plugins you must
- use one of the other load() overloads.
+ This function is provided for convenience where the logic earlier
+ is good enough. If you require a specific plugin then you should call
+ loadBackendByName() or loadBackendByFeatures() directly instead.
- Returns \c true if it managed to load the backend or if it was already
- loaded. Returns \c false otherwise.
+ Determines a suitable backend to load and returns \c true if this backend
+ is already loaded or on successful loading of it. Returns \c false if any
+ other backend has already been loaded, or if loading of the selected
+ backend fails.
\sa instance(), load()
*/
@@ -640,9 +678,15 @@ bool QNetworkInformation::loadDefaultBackend()
#elif defined(Q_OS_LINUX)
index = QNetworkInformationBackend::PluginNamesLinuxIndex;
#endif
- if (index == -1)
- return false;
- return loadBackendByName(QNetworkInformationBackend::PluginNames[index]);
+ if (index != -1 && loadBackendByName(QNetworkInformationBackend::PluginNames[index]))
+ return true;
+ // We assume reachability is the most commonly wanted feature, and try to
+ // load the backend that advertises the most features including that:
+ if (loadBackendByFeatures(Feature::Reachability))
+ return true;
+
+ // Fall back to the dummy backend
+ return loadBackendByName(u"dummy");
}
/*!
@@ -658,6 +702,9 @@ bool QNetworkInformation::loadDefaultBackend()
*/
bool QNetworkInformation::loadBackendByName(QStringView backend)
{
+ if (backend == u"dummy")
+ return QNetworkInformationPrivate::createDummy() != nullptr;
+
auto loadedBackend = QNetworkInformationPrivate::create(backend);
return loadedBackend && loadedBackend->backendName().compare(backend, Qt::CaseInsensitive) == 0;
}
@@ -724,3 +771,4 @@ QT_END_NAMESPACE
#include "moc_qnetworkinformation.cpp"
#include "moc_qnetworkinformation_p.cpp"
+#include "qnetworkinformation.moc"
diff --git a/src/network/kernel/qnetworkinformation.h b/src/network/kernel/qnetworkinformation.h
index 818da98aad..57a49f23c8 100644
--- a/src/network/kernel/qnetworkinformation.h
+++ b/src/network/kernel/qnetworkinformation.h
@@ -23,6 +23,7 @@ class Q_NETWORK_EXPORT QNetworkInformation : public QObject
NOTIFY isBehindCaptivePortalChanged)
Q_PROPERTY(TransportMedium transportMedium READ transportMedium NOTIFY transportMediumChanged)
Q_PROPERTY(bool isMetered READ isMetered NOTIFY isMeteredChanged)
+ Q_CLASSINFO("RegisterEnumClassesUnscoped", "false")
public:
enum class Reachability {
Unknown,
@@ -82,7 +83,6 @@ Q_SIGNALS:
private:
friend struct QNetworkInformationDeleter;
- friend class QNetworkInformationPrivate;
QNetworkInformation(QNetworkInformationBackend *backend);
~QNetworkInformation() override;
diff --git a/src/network/kernel/qnetworkinformation_p.h b/src/network/kernel/qnetworkinformation_p.h
index 40c12391c5..504955a6e1 100644
--- a/src/network/kernel/qnetworkinformation_p.h
+++ b/src/network/kernel/qnetworkinformation_p.h
@@ -20,6 +20,7 @@
#include <QtNetwork/qnetworkinformation.h>
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qreadwritelock.h>
QT_BEGIN_NAMESPACE
@@ -48,10 +49,29 @@ public:
virtual QString name() const = 0;
virtual QNetworkInformation::Features featuresSupported() const = 0;
- Reachability reachability() const { return m_reachability; }
- bool behindCaptivePortal() const { return m_behindCaptivePortal; }
- TransportMedium transportMedium() const { return m_transportMedium; }
- bool isMetered() const { return m_metered; }
+ Reachability reachability() const
+ {
+ QReadLocker locker(&m_lock);
+ return m_reachability;
+ }
+
+ bool behindCaptivePortal() const
+ {
+ QReadLocker locker(&m_lock);
+ return m_behindCaptivePortal;
+ }
+
+ TransportMedium transportMedium() const
+ {
+ QReadLocker locker(&m_lock);
+ return m_transportMedium;
+ }
+
+ bool isMetered() const
+ {
+ QReadLocker locker(&m_lock);
+ return m_metered;
+ }
Q_SIGNALS:
void reachabilityChanged(Reachability reachability);
@@ -62,37 +82,46 @@ Q_SIGNALS:
protected:
void setReachability(QNetworkInformation::Reachability reachability)
{
+ QWriteLocker locker(&m_lock);
if (m_reachability != reachability) {
m_reachability = reachability;
+ locker.unlock();
emit reachabilityChanged(reachability);
}
}
void setBehindCaptivePortal(bool behindPortal)
{
+ QWriteLocker locker(&m_lock);
if (m_behindCaptivePortal != behindPortal) {
m_behindCaptivePortal = behindPortal;
+ locker.unlock();
emit behindCaptivePortalChanged(behindPortal);
}
}
void setTransportMedium(TransportMedium medium)
{
+ QWriteLocker locker(&m_lock);
if (m_transportMedium != medium) {
m_transportMedium = medium;
+ locker.unlock();
emit transportMediumChanged(medium);
}
}
void setMetered(bool isMetered)
{
+ QWriteLocker locker(&m_lock);
if (m_metered != isMetered) {
m_metered = isMetered;
+ locker.unlock();
emit isMeteredChanged(isMetered);
}
}
private:
+ mutable QReadWriteLock m_lock;
Reachability m_reachability = Reachability::Unknown;
TransportMedium m_transportMedium = TransportMedium::Unknown;
bool m_behindCaptivePortal = false;
diff --git a/src/network/kernel/qnetworkinterface_linux.cpp b/src/network/kernel/qnetworkinterface_linux.cpp
index 622cd2976b..67ed8006bb 100644
--- a/src/network/kernel/qnetworkinterface_linux.cpp
+++ b/src/network/kernel/qnetworkinterface_linux.cpp
@@ -111,7 +111,10 @@ struct NetlinkSocket
template <typename Lambda> struct ProcessNetlinkRequest
{
using FunctionTraits = QtPrivate::FunctionPointer<decltype(&Lambda::operator())>;
- using FirstArgument = typename FunctionTraits::Arguments::Car;
+ using FirstArgumentPointer = typename FunctionTraits::Arguments::Car;
+ using FirstArgument = std::remove_pointer_t<FirstArgumentPointer>;
+ static_assert(std::is_pointer_v<FirstArgumentPointer>);
+ static_assert(std::is_aggregate_v<FirstArgument>);
static int expectedTypeForRequest(int rtype)
{
@@ -136,7 +139,7 @@ template <typename Lambda> struct ProcessNetlinkRequest
if (!NLMSG_OK(hdr, quint32(len)))
return;
- auto arg = reinterpret_cast<FirstArgument>(NLMSG_DATA(hdr));
+ auto arg = static_cast<FirstArgument *>(NLMSG_DATA(hdr));
size_t payloadLen = NLMSG_PAYLOAD(hdr, 0);
// is this a multipart message?
@@ -156,7 +159,7 @@ template <typename Lambda> struct ProcessNetlinkRequest
// NLMSG_NEXT also updates the len variable
hdr = NLMSG_NEXT(hdr, len);
- arg = reinterpret_cast<FirstArgument>(NLMSG_DATA(hdr));
+ arg = static_cast<FirstArgument *>(NLMSG_DATA(hdr));
payloadLen = NLMSG_PAYLOAD(hdr, 0);
} while (NLMSG_OK(hdr, quint32(len)));
diff --git a/src/network/kernel/qnetworkinterface_unix.cpp b/src/network/kernel/qnetworkinterface_unix.cpp
index 51a266b2e3..39ff8dbb92 100644
--- a/src/network/kernel/qnetworkinterface_unix.cpp
+++ b/src/network/kernel/qnetworkinterface_unix.cpp
@@ -17,11 +17,7 @@
# include "qdatetime.h"
#endif
-#if defined(QT_LINUXBASE)
-# define QT_NO_GETIFADDRS
-#endif
-
-#ifndef QT_NO_GETIFADDRS
+#if QT_CONFIG(getifaddrs)
# include <ifaddrs.h>
#endif
@@ -56,30 +52,38 @@ static QHostAddress addressFromSockaddr(sockaddr *sa, int ifindex = 0, const QSt
}
}
return address;
+}
+template <typename Req> [[maybe_unused]]
+static auto &ifreq_index(Req &req, std::enable_if_t<sizeof(std::declval<Req>().ifr_index) != 0, int> = 0)
+{
+ return req.ifr_index;
+}
+
+template <typename Req> [[maybe_unused]]
+static auto &ifreq_index(Req &req, std::enable_if_t<sizeof(std::declval<Req>().ifr_ifindex) != 0, int> = 0)
+{
+ return req.ifr_ifindex;
}
uint QNetworkInterfaceManager::interfaceIndexFromName(const QString &name)
{
-#ifndef QT_NO_IPV6IFNAME
- return ::if_nametoindex(name.toLatin1());
+#if QT_CONFIG(ipv6ifname)
+ return ::if_nametoindex(name.toLatin1().constData());
#elif defined(SIOCGIFINDEX)
struct ifreq req;
int socket = qt_safe_socket(AF_INET, SOCK_STREAM, 0);
if (socket < 0)
return 0;
- QByteArray name8bit = name.toLatin1();
+ const QByteArray name8bit = name.toLatin1();
memset(&req, 0, sizeof(ifreq));
- memcpy(req.ifr_name, name8bit, qMin<int>(name8bit.length() + 1, sizeof(req.ifr_name) - 1));
+ if (!name8bit.isNull())
+ memcpy(req.ifr_name, name8bit.data(), qMin(size_t(name8bit.length()) + 1, sizeof(req.ifr_name) - 1));
uint id = 0;
if (qt_safe_ioctl(socket, SIOCGIFINDEX, &req) >= 0)
-# if QT_CONFIG(ifr_index)
- id = req.ifr_index;
-# else
- id = req.ifr_ifindex;
-# endif
+ id = ifreq_index(req);
qt_safe_close(socket);
return id;
#else
@@ -90,7 +94,7 @@ uint QNetworkInterfaceManager::interfaceIndexFromName(const QString &name)
QString QNetworkInterfaceManager::interfaceNameFromIndex(uint index)
{
-#ifndef QT_NO_IPV6IFNAME
+#if QT_CONFIG(ipv6ifname)
char buf[IF_NAMESIZE];
if (::if_indextoname(index, buf))
return QString::fromLatin1(buf);
@@ -99,12 +103,7 @@ QString QNetworkInterfaceManager::interfaceNameFromIndex(uint index)
int socket = qt_safe_socket(AF_INET, SOCK_STREAM, 0);
if (socket >= 0) {
memset(&req, 0, sizeof(ifreq));
-# if QT_CONFIG(ifr_index)
- req.ifr_index = index;
-# else
- req.ifr_ifindex = index;
-# endif
-
+ ifreq_index(req) = index;
if (qt_safe_ioctl(socket, SIOCGIFNAME, &req) >= 0) {
qt_safe_close(socket);
return QString::fromLatin1(req.ifr_name);
@@ -124,13 +123,13 @@ static int getMtu(int socket, struct ifreq *req)
return 0;
}
-#ifdef QT_NO_GETIFADDRS
+#if !QT_CONFIG(getifaddrs)
// getifaddrs not available
static QSet<QByteArray> interfaceNames(int socket)
{
QSet<QByteArray> result;
-#ifdef QT_NO_IPV6IFNAME
+#if !QT_CONFIG(ipv6ifname)
QByteArray storageBuffer;
struct ifconf interfaceList;
static const int STORAGEBUFFER_GROWTH = 256;
@@ -185,15 +184,11 @@ static QNetworkInterfacePrivate *findInterface(int socket, QList<QNetworkInterfa
QNetworkInterfacePrivate *iface = nullptr;
int ifindex = 0;
-#if !defined(QT_NO_IPV6IFNAME) || defined(SIOCGIFINDEX)
+#if QT_CONFIG(ipv6ifname) || defined(SIOCGIFINDEX)
// Get the interface index
# ifdef SIOCGIFINDEX
if (qt_safe_ioctl(socket, SIOCGIFINDEX, &req) >= 0)
-# if QT_CONFIG(ifr_index)
- ifindex = req.ifr_index;
-# else
- ifindex = req.ifr_ifindex;
-# endif
+ ifindex = ifreq_index(req);
# else
ifindex = if_nametoindex(req.ifr_name);
# endif
@@ -241,7 +236,8 @@ static QList<QNetworkInterfacePrivate *> interfaceListing()
for ( ; it != names.constEnd(); ++it) {
ifreq req;
memset(&req, 0, sizeof(ifreq));
- memcpy(req.ifr_name, *it, qMin<int>(it->length() + 1, sizeof(req.ifr_name) - 1));
+ if (!it->isNull())
+ memcpy(req.ifr_name, it->constData(), qMin(size_t(it->length()) + 1, sizeof(req.ifr_name) - 1));
QNetworkInterfacePrivate *iface = findInterface(socket, interfaces, req);
@@ -252,7 +248,8 @@ static QList<QNetworkInterfacePrivate *> interfaceListing()
iface->name = QString::fromLatin1(req.ifr_name);
// reset the name:
- memcpy(req.ifr_name, oldName, qMin<int>(oldName.length() + 1, sizeof(req.ifr_name) - 1));
+ if (!oldName.isNull())
+ memcpy(req.ifr_name, oldName.constData(), qMin(size_t(oldName.length()) + 1, sizeof(req.ifr_name) - 1));
} else
#endif
{
@@ -313,12 +310,20 @@ QT_BEGIN_INCLUDE_NAMESPACE
QT_END_INCLUDE_NAMESPACE
# endif
+static int openSocket(int &socket)
+{
+ if (socket == -1)
+ socket = qt_safe_socket(AF_INET, SOCK_DGRAM, 0);
+ return socket;
+}
+
# if defined(Q_OS_LINUX) && __GLIBC__ - 0 >= 2 && __GLIBC_MINOR__ - 0 >= 1 && !defined(QT_LINUXBASE)
# include <netpacket/packet.h>
static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList)
{
Q_UNUSED(getMtu);
+ Q_UNUSED(openSocket);
QList<QNetworkInterfacePrivate *> interfaces;
QDuplicateTracker<QString> seenInterfaces;
QDuplicateTracker<int> seenIndexes;
@@ -390,13 +395,6 @@ QT_BEGIN_INCLUDE_NAMESPACE
#endif // QT_PLATFORM_UIKIT
QT_END_INCLUDE_NAMESPACE
-static int openSocket(int &socket)
-{
- if (socket == -1)
- socket = qt_safe_socket(AF_INET, SOCK_DGRAM, 0);
- return socket;
-}
-
static QNetworkInterface::InterfaceType probeIfType(int socket, int iftype, struct ifmediareq *req)
{
// Determine the interface type.
@@ -459,7 +457,8 @@ static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList)
// ensure both structs start with the name field, of size IFNAMESIZ
static_assert(sizeof(mediareq.ifm_name) == sizeof(req.ifr_name));
- Q_ASSERT(&mediareq.ifm_name == &req.ifr_name);
+ static_assert(offsetof(struct ifmediareq, ifm_name) == 0);
+ static_assert(offsetof(struct ifreq, ifr_name) == 0);
// on NetBSD we use AF_LINK and sockaddr_dl
// scan the list for that family
@@ -539,8 +538,8 @@ static void getAddressExtraInfo(QNetworkAddressEntry *entry, struct sockaddr *sa
static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList)
{
- Q_UNUSED(getMtu);
QList<QNetworkInterfacePrivate *> interfaces;
+ int socket = -1;
// make sure there's one entry for each interface
for (ifaddrs *ptr = rawList; ptr; ptr = ptr->ifa_next) {
@@ -561,9 +560,18 @@ static QList<QNetworkInterfacePrivate *> createInterfaces(ifaddrs *rawList)
iface->index = ifindex;
iface->name = QString::fromLatin1(ptr->ifa_name);
iface->flags = convertFlags(ptr->ifa_flags);
+
+ if ((socket = openSocket(socket)) >= 0) {
+ struct ifreq ifr;
+ qstrncpy(ifr.ifr_name, ptr->ifa_name, sizeof(ifr.ifr_name));
+ iface->mtu = getMtu(socket, &ifr);
+ }
}
}
+ if (socket != -1)
+ qt_safe_close(socket);
+
return interfaces;
}
diff --git a/src/network/kernel/qnetworkproxy.cpp b/src/network/kernel/qnetworkproxy.cpp
index a00b0031ac..9b91b11d6b 100644
--- a/src/network/kernel/qnetworkproxy.cpp
+++ b/src/network/kernel/qnetworkproxy.cpp
@@ -752,6 +752,53 @@ QNetworkProxy QNetworkProxy::applicationProxy()
}
/*!
+ \since 6.8
+
+ Returns headers that are set in this network request.
+
+ If the proxy is not of type HttpProxy or HttpCachingProxy,
+ default constructed QHttpHeaders is returned.
+
+ \sa setHeaders()
+*/
+QHttpHeaders QNetworkProxy::headers() const
+{
+ if (d->type != HttpProxy && d->type != HttpCachingProxy)
+ return {};
+ return d->headers.headers();
+}
+
+/*!
+ \since 6.8
+
+ Sets \a newHeaders as headers in this network request, overriding
+ any previously set headers.
+
+ If some headers correspond to the known headers, the values will
+ be parsed and the corresponding parsed form will also be set.
+
+ If the proxy is not of type HttpProxy or HttpCachingProxy this has no
+ effect.
+
+ \sa headers(), QNetworkRequest::KnownHeaders
+*/
+void QNetworkProxy::setHeaders(QHttpHeaders &&newHeaders)
+{
+ if (d->type == HttpProxy || d->type == HttpCachingProxy)
+ d->headers.setHeaders(std::move(newHeaders));
+}
+
+/*!
+ \overload
+ \since 6.8
+*/
+void QNetworkProxy::setHeaders(const QHttpHeaders &newHeaders)
+{
+ if (d->type == HttpProxy || d->type == HttpCachingProxy)
+ d->headers.setHeaders(newHeaders);
+}
+
+/*!
\since 5.0
Returns the value of the known network header \a header if it is
in use for this proxy. If it is not present, returns QVariant()
@@ -795,7 +842,7 @@ bool QNetworkProxy::hasRawHeader(const QByteArray &headerName) const
{
if (d->type != HttpProxy && d->type != HttpCachingProxy)
return false;
- return d->headers.findRawHeader(headerName) != d->headers.rawHeaders.constEnd();
+ return d->headers.headers().contains(headerName);
}
/*!
@@ -814,11 +861,7 @@ QByteArray QNetworkProxy::rawHeader(const QByteArray &headerName) const
{
if (d->type != HttpProxy && d->type != HttpCachingProxy)
return QByteArray();
- QNetworkHeadersPrivate::RawHeadersList::ConstIterator it =
- d->headers.findRawHeader(headerName);
- if (it != d->headers.rawHeaders.constEnd())
- return it->second;
- return QByteArray();
+ return d->headers.rawHeader(headerName);
}
/*!
@@ -1466,7 +1509,7 @@ void QNetworkProxyFactory::setApplicationProxyFactory(QNetworkProxyFactory *fact
Internet Explorer's settings and use them.
On \macos, this function will obtain the proxy settings using the
- SystemConfiguration framework from Apple. It will apply the FTP,
+ CFNetwork framework from Apple. It will apply the FTP,
HTTP and HTTPS proxy configurations for queries that contain the
protocol tag "ftp", "http" and "https", respectively. If the SOCKS
proxy is enabled in that configuration, this function will use the
@@ -1489,9 +1532,6 @@ void QNetworkProxyFactory::setApplicationProxyFactory(QNetworkProxyFactory *fact
listed here.
\list
- \li On \macos, this function will ignore the Proxy Auto Configuration
- settings, since it cannot execute the associated ECMAScript code.
-
\li On Windows platforms, this function may take several seconds to
execute depending on the configuration of the user's system.
\endlist
diff --git a/src/network/kernel/qnetworkproxy.h b/src/network/kernel/qnetworkproxy.h
index c39fff5318..9f92ffeb12 100644
--- a/src/network/kernel/qnetworkproxy.h
+++ b/src/network/kernel/qnetworkproxy.h
@@ -77,6 +77,7 @@ class QNetworkProxyPrivate;
class Q_NETWORK_EXPORT QNetworkProxy
{
+ Q_GADGET
public:
enum ProxyType {
DefaultProxy,
@@ -135,6 +136,10 @@ public:
static void setApplicationProxy(const QNetworkProxy &proxy);
static QNetworkProxy applicationProxy();
+ QHttpHeaders headers() const;
+ void setHeaders(const QHttpHeaders &newHeaders);
+ void setHeaders(QHttpHeaders &&newHeaders);
+
// "cooked" headers
QVariant header(QNetworkRequest::KnownHeaders header) const;
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value);
diff --git a/src/network/kernel/qnetworkproxy_android.cpp b/src/network/kernel/qnetworkproxy_android.cpp
index 2e1fb50234..3d37266b70 100644
--- a/src/network/kernel/qnetworkproxy_android.cpp
+++ b/src/network/kernel/qnetworkproxy_android.cpp
@@ -24,7 +24,7 @@ Q_GLOBAL_STATIC(ProxyInfoObject, proxyInfoInstance)
static const char networkClass[] = "org/qtproject/qt/android/network/QtNetwork";
-Q_DECLARE_JNI_TYPE(ProxyInfo, "Landroid/net/ProxyInfo;")
+Q_DECLARE_JNI_CLASS(ProxyInfo, "android/net/ProxyInfo")
Q_DECLARE_JNI_TYPE(JStringArray, "[Ljava/lang/String;")
ProxyInfoObject::ProxyInfoObject()
diff --git a/src/network/kernel/qnetworkproxy_mac.cpp b/src/network/kernel/qnetworkproxy_darwin.cpp
index aa691090cc..d2bd4958dd 100644
--- a/src/network/kernel/qnetworkproxy_mac.cpp
+++ b/src/network/kernel/qnetworkproxy_darwin.cpp
@@ -14,6 +14,7 @@
#include <QtCore/QUrl>
#include <QtCore/qendian.h>
#include <QtCore/qstringlist.h>
+#include <QtCore/qsystemdetection.h>
#include "private/qcore_mac_p.h"
/*
@@ -32,11 +33,11 @@
* \li Bypass list (by default: *.local, 169.254/16)
* \endlist
*
- * The matching configuration can be obtained by calling SCDynamicStoreCopyProxies
- * (from <SystemConfiguration/SCDynamicStoreCopySpecific.h>). See
+ * The matching configuration can be obtained by calling CFNetworkCopySystemProxySettings()
+ * (from <CFNetwork/CFProxySupport.h>). See
* Apple's documentation:
*
- * http://developer.apple.com/DOCUMENTATION/Networking/Reference/SysConfig/SCDynamicStoreCopySpecific/CompositePage.html#//apple_ref/c/func/SCDynamicStoreCopyProxies
+ * https://developer.apple.com/documentation/cfnetwork/1426754-cfnetworkcopysystemproxysettings?language=objc
*
*/
@@ -46,13 +47,19 @@ using namespace Qt::StringLiterals;
static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
{
+ Q_ASSERT(dict);
+
if (host.isEmpty())
return true;
+#ifndef Q_OS_IOS
+ // On iOS all those keys are not available, and worse so - entries
+ // for HTTPS are not in the dictionary, but instead in some nested dictionary
+ // with undocumented keys/object types.
bool isSimple = !host.contains(u'.') && !host.contains(u':');
CFNumberRef excludeSimples;
if (isSimple &&
- (excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExcludeSimpleHostnames))) {
+ (excludeSimples = (CFNumberRef)CFDictionaryGetValue(dict, kCFNetworkProxiesExcludeSimpleHostnames))) {
int enabled;
if (CFNumberGetValue(excludeSimples, kCFNumberIntType, &enabled) && enabled)
return true;
@@ -63,7 +70,7 @@ static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
// not a simple host name
// does it match the list of exclusions?
- CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kSCPropNetProxiesExceptionsList);
+ CFArrayRef exclusionList = (CFArrayRef)CFDictionaryGetValue(dict, kCFNetworkProxiesExceptionsList);
if (!exclusionList)
return false;
@@ -81,7 +88,9 @@ static bool isHostExcluded(CFDictionaryRef dict, const QString &host)
return true;
}
}
-
+#else
+ Q_UNUSED(dict);
+#endif // Q_OS_IOS
// host was not excluded
return false;
}
@@ -112,7 +121,6 @@ static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict, QNetworkProxy::Pr
return QNetworkProxy();
}
-
static QNetworkProxy proxyFromDictionary(CFDictionaryRef dict)
{
QNetworkProxy::ProxyType proxyType = QNetworkProxy::DefaultProxy;
@@ -178,16 +186,43 @@ QCFType<CFStringRef> stringByAddingPercentEscapes(CFStringRef originalPath)
return escaped.toCFString();
}
-} // anon namespace
+#ifdef Q_OS_IOS
+QList<QNetworkProxy> proxiesForQueryUrl(CFDictionaryRef dict, const QUrl &url)
+{
+ Q_ASSERT(dict);
+
+ const QCFType<CFURLRef> cfUrl = url.toCFURL();
+ const QCFType<CFArrayRef> proxies = CFNetworkCopyProxiesForURL(cfUrl, dict);
+ Q_ASSERT(proxies);
+
+ QList<QNetworkProxy> result;
+ const auto count = CFArrayGetCount(proxies);
+ if (!count) // Could be no proper proxy or host excluded.
+ return result;
+
+ for (CFIndex i = 0; i < count; ++i) {
+ const void *obj = CFArrayGetValueAtIndex(proxies, i);
+ if (CFGetTypeID(obj) != CFDictionaryGetTypeID())
+ continue;
+ const QNetworkProxy proxy = proxyFromDictionary(static_cast<CFDictionaryRef>(obj));
+ if (proxy.type() == QNetworkProxy::NoProxy || proxy.type() == QNetworkProxy::DefaultProxy)
+ continue;
+ result << proxy;
+ }
+
+ return result;
+}
+#endif // Q_OS_IOS
+} // unnamed namespace.
QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
{
QList<QNetworkProxy> result;
// obtain a dictionary to the proxy settings:
- const QCFType<CFDictionaryRef> dict = SCDynamicStoreCopyProxies(NULL);
+ const QCFType<CFDictionaryRef> dict = CFNetworkCopySystemProxySettings();
if (!dict) {
- qWarning("QNetworkProxyFactory::systemProxyForQuery: SCDynamicStoreCopyProxies returned NULL");
+ qWarning("QNetworkProxyFactory::systemProxyForQuery: CFNetworkCopySystemProxySettings returned nullptr");
return result; // failed
}
@@ -196,13 +231,13 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
// is there a PAC enabled? If so, use it first.
CFNumberRef pacEnabled;
- if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigEnable))) {
+ if ((pacEnabled = (CFNumberRef)CFDictionaryGetValue(dict, kCFNetworkProxiesProxyAutoConfigEnable))) {
int enabled;
if (CFNumberGetValue(pacEnabled, kCFNumberIntType, &enabled) && enabled) {
// PAC is enabled
// kSCPropNetProxiesProxyAutoConfigURLString returns the URL string
// as entered in the system proxy configuration dialog
- CFStringRef pacLocationSetting = (CFStringRef)CFDictionaryGetValue(dict, kSCPropNetProxiesProxyAutoConfigURLString);
+ CFStringRef pacLocationSetting = (CFStringRef)CFDictionaryGetValue(dict, kCFNetworkProxiesProxyAutoConfigURLString);
auto cfPacLocation = stringByAddingPercentEscapes(pacLocationSetting);
QCFType<CFDataRef> pacData;
QCFType<CFURLRef> pacUrl = CFURLCreateWithString(kCFAllocatorDefault, cfPacLocation, NULL);
@@ -252,53 +287,77 @@ QList<QNetworkProxy> macQueryInternal(const QNetworkProxyQuery &query)
}
}
- // no PAC, decide which proxy we're looking for based on the query
- bool isHttps = false;
- QString protocol = query.protocolTag().toLower();
-
+ // No PAC, decide which proxy we're looking for based on the query
// try the protocol-specific proxy
+ const QString protocol = query.protocolTag();
QNetworkProxy protocolSpecificProxy;
- if (protocol == "ftp"_L1) {
- protocolSpecificProxy =
- proxyFromDictionary(dict, QNetworkProxy::FtpCachingProxy,
- kSCPropNetProxiesFTPEnable,
- kSCPropNetProxiesFTPProxy,
- kSCPropNetProxiesFTPPort);
- } else if (protocol == "http"_L1) {
+ if (protocol.compare("http"_L1, Qt::CaseInsensitive) == 0) {
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
- kSCPropNetProxiesHTTPEnable,
- kSCPropNetProxiesHTTPProxy,
- kSCPropNetProxiesHTTPPort);
- } else if (protocol == "https"_L1) {
+ kCFNetworkProxiesHTTPEnable,
+ kCFNetworkProxiesHTTPProxy,
+ kCFNetworkProxiesHTTPPort);
+ }
+
+
+#ifdef Q_OS_IOS
+ if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy
+ && protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy) {
+ // HTTP proxy is enabled (on iOS there is no separate HTTPS, though
+ // 'dict' contains deeply buried entries which are the same as HTTP.
+ result << protocolSpecificProxy;
+ }
+
+ // TODO: check query.queryType()? It's possible, the exclude list
+ // did exclude it but above we added a proxy because HTTP proxy
+ // is found. We'll deal with such a situation later, since now NMI.
+ const auto proxiesForUrl = proxiesForQueryUrl(dict, query.url());
+ for (const auto &proxy : proxiesForUrl) {
+ if (!result.contains(proxy))
+ result << proxy;
+ }
+#else
+ bool isHttps = false;
+ if (protocol.compare("ftp"_L1, Qt::CaseInsensitive) == 0) {
+ protocolSpecificProxy =
+ proxyFromDictionary(dict, QNetworkProxy::FtpCachingProxy,
+ kCFNetworkProxiesFTPEnable,
+ kCFNetworkProxiesFTPProxy,
+ kCFNetworkProxiesFTPPort);
+ } else if (protocol.compare("https"_L1, Qt::CaseInsensitive) == 0) {
isHttps = true;
protocolSpecificProxy =
proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
- kSCPropNetProxiesHTTPSEnable,
- kSCPropNetProxiesHTTPSProxy,
- kSCPropNetProxiesHTTPSPort);
+ kCFNetworkProxiesHTTPSEnable,
+ kCFNetworkProxiesHTTPSProxy,
+ kCFNetworkProxiesHTTPSPort);
}
+
if (protocolSpecificProxy.type() != QNetworkProxy::DefaultProxy)
result << protocolSpecificProxy;
// let's add SOCKSv5 if present too
QNetworkProxy socks5 = proxyFromDictionary(dict, QNetworkProxy::Socks5Proxy,
- kSCPropNetProxiesSOCKSEnable,
- kSCPropNetProxiesSOCKSProxy,
- kSCPropNetProxiesSOCKSPort);
+ kCFNetworkProxiesSOCKSEnable,
+ kCFNetworkProxiesSOCKSProxy,
+ kCFNetworkProxiesSOCKSPort);
if (socks5.type() != QNetworkProxy::DefaultProxy)
result << socks5;
// let's add the HTTPS proxy if present (and if we haven't added
// yet)
if (!isHttps) {
- QNetworkProxy https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
- kSCPropNetProxiesHTTPSEnable,
- kSCPropNetProxiesHTTPSProxy,
- kSCPropNetProxiesHTTPSPort);
+ QNetworkProxy https;
+ https = proxyFromDictionary(dict, QNetworkProxy::HttpProxy,
+ kCFNetworkProxiesHTTPSEnable,
+ kCFNetworkProxiesHTTPSProxy,
+ kCFNetworkProxiesHTTPSPort);
+
+
if (https.type() != QNetworkProxy::DefaultProxy && https != protocolSpecificProxy)
result << https;
}
+#endif // !Q_OS_IOS
return result;
}
diff --git a/src/network/kernel/qnetworkproxy_libproxy.cpp b/src/network/kernel/qnetworkproxy_libproxy.cpp
index 46066b86f7..da1e8fdbd4 100644
--- a/src/network/kernel/qnetworkproxy_libproxy.cpp
+++ b/src/network/kernel/qnetworkproxy_libproxy.cpp
@@ -12,6 +12,7 @@
#include <QtCore/QUrl>
#include <QtCore/private/qeventdispatcher_unix_p.h>
#include <QtCore/private/qthread_p.h>
+#include <QtCore/qapplicationstatic.h>
#include <proxy.h>
#include <dlfcn.h>
@@ -72,7 +73,7 @@ private:
Data *request;
};
-Q_GLOBAL_STATIC(QLibProxyWrapper, libProxyWrapper);
+Q_APPLICATION_STATIC(QLibProxyWrapper, libProxyWrapper)
QLibProxyWrapper::QLibProxyWrapper()
{
@@ -165,13 +166,15 @@ QList<QNetworkProxy> QNetworkProxyFactory::systemProxyForQuery(const QNetworkPro
break;
// fake URLs to get libproxy to tell us the SOCKS proxy
case QNetworkProxyQuery::TcpSocket:
- queryUrl.setScheme(QStringLiteral("tcp"));
+ if (queryUrl.scheme().isEmpty())
+ queryUrl.setScheme(QStringLiteral("tcp"));
queryUrl.setHost(query.peerHostName());
queryUrl.setPort(query.peerPort());
requiredCapabilities |= QNetworkProxy::TunnelingCapability;
break;
case QNetworkProxyQuery::UdpSocket:
- queryUrl.setScheme(QStringLiteral("udp"));
+ if (queryUrl.scheme().isEmpty())
+ queryUrl.setScheme(QStringLiteral("udp"));
queryUrl.setHost(query.peerHostName());
queryUrl.setPort(query.peerPort());
requiredCapabilities |= QNetworkProxy::UdpTunnelingCapability;
diff --git a/src/network/kernel/qtldurl.cpp b/src/network/kernel/qtldurl.cpp
index 7bda9845b9..a7aceddb18 100644
--- a/src/network/kernel/qtldurl.cpp
+++ b/src/network/kernel/qtldurl.cpp
@@ -7,9 +7,7 @@
#if QT_CONFIG(topleveldomain)
-#include "qurl.h"
#include "QtCore/qfile.h"
-#include "QtCore/qfileinfo.h"
#include "QtCore/qloggingcategory.h"
#include "QtCore/qstandardpaths.h"
#include "QtCore/qstring.h"
@@ -214,4 +212,4 @@ Q_NETWORK_EXPORT bool qIsEffectiveTLD(QStringView domain)
QT_END_NAMESPACE
-#endif
+#endif // QT_CONFIG(topleveldomain)
diff --git a/src/network/kernel/qtldurl_p.h b/src/network/kernel/qtldurl_p.h
index f051fba4df..86b163f161 100644
--- a/src/network/kernel/qtldurl_p.h
+++ b/src/network/kernel/qtldurl_p.h
@@ -16,7 +16,6 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
-#include "QtCore/qurl.h"
#include "QtCore/qstring.h"
QT_REQUIRE_CONFIG(topleveldomain);
@@ -31,4 +30,4 @@ inline bool qIsEffectiveTLD(const QString &domain)
QT_END_NAMESPACE
-#endif // QDATAURL_P_H
+#endif // QTLDURL_P_H
diff --git a/src/network/kernel/qtnetworkglobal_p.h b/src/network/kernel/qtnetworkglobal_p.h
index ff2b75e3a9..b90e675cb4 100644
--- a/src/network/kernel/qtnetworkglobal_p.h
+++ b/src/network/kernel/qtnetworkglobal_p.h
@@ -18,7 +18,6 @@
#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/private/qglobal_p.h>
#include <QtNetwork/private/qtnetwork-config_p.h>
-#include <QtNetwork/private/qtnetworkexports_p.h>
QT_BEGIN_NAMESPACE
diff --git a/src/network/socket/qabstractsocket.cpp b/src/network/socket/qabstractsocket.cpp
index 7d7db11e01..e456d00713 100644
--- a/src/network/socket/qabstractsocket.cpp
+++ b/src/network/socket/qabstractsocket.cpp
@@ -173,8 +173,9 @@
parameter describes the type of error that occurred.
When this signal is emitted, the socket may not be ready for a reconnect
- attempt. In that case, attempts to reconnect should be done from the event
- loop. For example, use a QTimer::singleShot() with 0 as the timeout.
+ attempt. In that case, attempts to reconnect should be done from the
+ event loop. For example, use QChronoTimer::singleShot() with 0ns as
+ the timeout.
QAbstractSocket::SocketError is not a registered metatype, so for queued
connections, you will have to register it with Q_DECLARE_METATYPE() and
@@ -439,7 +440,7 @@
#include <qmetaobject.h>
#include <qpointer.h>
#include <qtimer.h>
-#include <qelapsedtimer.h>
+#include <qdeadlinetimer.h>
#include <qscopedvaluerollback.h>
#include <qvarlengtharray.h>
@@ -465,11 +466,12 @@
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+using namespace std::chrono_literals;
QT_IMPL_METATYPE_EXTERN_TAGGED(QAbstractSocket::SocketState, QAbstractSocket__SocketState)
QT_IMPL_METATYPE_EXTERN_TAGGED(QAbstractSocket::SocketError, QAbstractSocket__SocketError)
-static const int DefaultConnectTimeout = 30000;
+static constexpr auto DefaultConnectTimeout = 30s;
static bool isProxyError(QAbstractSocket::SocketError error)
{
@@ -636,11 +638,19 @@ bool QAbstractSocketPrivate::canReadNotification()
return !q->isReadable();
}
} else {
- if (hasPendingData) {
+ const bool isUdpSocket = (socketType == QAbstractSocket::UdpSocket);
+ if (hasPendingData && (!isUdpSocket || hasPendingDatagram)) {
socketEngine->setReadNotificationEnabled(false);
return true;
}
- hasPendingData = true;
+ if (!isUdpSocket
+#if QT_CONFIG(udpsocket)
+ || socketEngine->hasPendingDatagrams()
+#endif
+ ) {
+ hasPendingData = true;
+ hasPendingDatagram = isUdpSocket;
+ }
}
emitReadyRead();
@@ -1057,8 +1067,7 @@ void QAbstractSocketPrivate::_q_connectToNextAddress()
q, SLOT(_q_abortConnectionAttempt()),
Qt::DirectConnection);
}
- int connectTimeout = DefaultConnectTimeout;
- connectTimer->start(connectTimeout);
+ connectTimer->start(DefaultConnectTimeout);
}
// Wait for a write notification that will eventually call
@@ -2052,8 +2061,7 @@ bool QAbstractSocket::waitForConnected(int msecs)
bool wasPendingClose = d->pendingClose;
d->pendingClose = false;
- QElapsedTimer stopWatch;
- stopWatch.start();
+ QDeadlineTimer deadline{msecs};
if (d->state == HostLookupState) {
#if defined (QABSTRACTSOCKET_DEBUG)
@@ -2073,22 +2081,21 @@ bool QAbstractSocket::waitForConnected(int msecs)
if (state() == UnconnectedState)
return false; // connect not im progress anymore!
- int connectTimeout = DefaultConnectTimeout;
bool timedOut = true;
#if defined (QABSTRACTSOCKET_DEBUG)
int attempt = 1;
#endif
- while (state() == ConnectingState && (msecs == -1 || stopWatch.elapsed() < msecs)) {
- int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed());
- if (msecs != -1 && timeout > connectTimeout)
- timeout = connectTimeout;
+ while (state() == ConnectingState && !deadline.hasExpired()) {
+ QDeadlineTimer timer = deadline;
+ if (!deadline.isForever() && deadline.remainingTimeAsDuration() > DefaultConnectTimeout)
+ timer = QDeadlineTimer(DefaultConnectTimeout);
#if defined (QABSTRACTSOCKET_DEBUG)
qDebug("QAbstractSocket::waitForConnected(%i) waiting %.2f secs for connection attempt #%i",
- msecs, timeout / 1000.0, attempt++);
+ msecs, timer.remainingTime() / 1000.0, attempt++);
#endif
timedOut = false;
- if (d->socketEngine && d->socketEngine->waitForWrite(timeout, &timedOut) && !timedOut) {
+ if (d->socketEngine && d->socketEngine->waitForWrite(timer, &timedOut) && !timedOut) {
d->_q_testConnection();
} else {
d->_q_connectToNextAddress();
@@ -2143,8 +2150,7 @@ bool QAbstractSocket::waitForReadyRead(int msecs)
return false;
}
- QElapsedTimer stopWatch;
- stopWatch.start();
+ QDeadlineTimer deadline{msecs};
// handle a socket in connecting state
if (state() == HostLookupState || state() == ConnectingState) {
@@ -2160,7 +2166,7 @@ bool QAbstractSocket::waitForReadyRead(int msecs)
bool readyToRead = false;
bool readyToWrite = false;
if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, true, !d->writeBuffer.isEmpty(),
- qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
+ deadline)) {
#if defined (QABSTRACTSOCKET_DEBUG)
qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)",
msecs, d->socketEngine->error(), d->socketEngine->errorString().toLatin1().constData());
@@ -2178,7 +2184,7 @@ bool QAbstractSocket::waitForReadyRead(int msecs)
if (readyToWrite)
d->canWriteNotification();
- } while (msecs == -1 || qt_subtract_from_timeout(msecs, stopWatch.elapsed()) > 0);
+ } while (!deadline.hasExpired());
return false;
}
@@ -2214,8 +2220,7 @@ bool QAbstractSocket::waitForBytesWritten(int msecs)
if (d->writeBuffer.isEmpty())
return false;
- QElapsedTimer stopWatch;
- stopWatch.start();
+ QDeadlineTimer deadline{msecs};
// handle a socket in connecting state
if (state() == HostLookupState || state() == ConnectingState) {
@@ -2223,13 +2228,13 @@ bool QAbstractSocket::waitForBytesWritten(int msecs)
return false;
}
- forever {
+ for (;;) {
bool readyToRead = false;
bool readyToWrite = false;
if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite,
!d->readBufferMaxSize || d->buffer.size() < d->readBufferMaxSize,
!d->writeBuffer.isEmpty(),
- qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
+ deadline)) {
#if defined (QABSTRACTSOCKET_DEBUG)
qDebug("QAbstractSocket::waitForBytesWritten(%i) failed (%i, %s)",
msecs, d->socketEngine->error(), d->socketEngine->errorString().toLatin1().constData());
@@ -2293,8 +2298,7 @@ bool QAbstractSocket::waitForDisconnected(int msecs)
return false;
}
- QElapsedTimer stopWatch;
- stopWatch.start();
+ QDeadlineTimer deadline{msecs};
// handle a socket in connecting state
if (state() == HostLookupState || state() == ConnectingState) {
@@ -2304,12 +2308,12 @@ bool QAbstractSocket::waitForDisconnected(int msecs)
return true;
}
- forever {
+ for (;;) {
bool readyToRead = false;
bool readyToWrite = false;
if (!d->socketEngine->waitForReadOrWrite(&readyToRead, &readyToWrite, state() == ConnectedState,
!d->writeBuffer.isEmpty(),
- qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
+ deadline)) {
#if defined (QABSTRACTSOCKET_DEBUG)
qDebug("QAbstractSocket::waitForReadyRead(%i) failed (%i, %s)",
msecs, d->socketEngine->error(), d->socketEngine->errorString().toLatin1().constData());
diff --git a/src/network/socket/qabstractsocket_p.h b/src/network/socket/qabstractsocket_p.h
index 98d74dcfd4..cc5f53179c 100644
--- a/src/network/socket/qabstractsocket_p.h
+++ b/src/network/socket/qabstractsocket_p.h
@@ -110,6 +110,7 @@ public:
qint64 readBufferMaxSize = 0;
bool isBuffered = false;
bool hasPendingData = false;
+ bool hasPendingDatagram = false;
QTimer *connectTimer = nullptr;
diff --git a/src/network/socket/qabstractsocketengine_p.h b/src/network/socket/qabstractsocketengine_p.h
index 7b1ec85802..9ee79cebc1 100644
--- a/src/network/socket/qabstractsocketengine_p.h
+++ b/src/network/socket/qabstractsocketengine_p.h
@@ -19,8 +19,9 @@
#include <QtNetwork/private/qtnetworkglobal_p.h>
#include "QtNetwork/qhostaddress.h"
#include "QtNetwork/qabstractsocket.h"
-#include "private/qobject_p.h"
+#include <QtCore/qdeadlinetimer.h>
#include "private/qnetworkdatagram_p.h"
+#include "private/qobject_p.h"
QT_BEGIN_NAMESPACE
@@ -44,6 +45,8 @@ public:
#endif
};
+static constexpr std::chrono::seconds DefaultTimeout{30};
+
class Q_AUTOTEST_EXPORT QAbstractSocketEngine : public QObject
{
Q_OBJECT
@@ -128,11 +131,14 @@ public:
virtual int option(SocketOption option) const = 0;
virtual bool setOption(SocketOption option, int value) = 0;
- virtual bool waitForRead(int msecs = 30000, bool *timedOut = nullptr) = 0;
- virtual bool waitForWrite(int msecs = 30000, bool *timedOut = nullptr) = 0;
+ virtual bool waitForRead(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) = 0;
+ virtual bool waitForWrite(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) = 0;
virtual bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
- int msecs = 30000, bool *timedOut = nullptr) = 0;
+ QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) = 0;
QAbstractSocket::SocketError error() const;
QString errorString() const;
diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp
index 6f93685d2a..725bc21359 100644
--- a/src/network/socket/qhttpsocketengine.cpp
+++ b/src/network/socket/qhttpsocketengine.cpp
@@ -7,7 +7,7 @@
#include "qurl.h"
#include "private/qhttpnetworkreply_p.h"
#include "private/qiodevice_p.h"
-#include "qelapsedtimer.h"
+#include "qdeadlinetimer.h"
#include "qnetworkinterface.h"
#if !defined(QT_NO_NETWORKPROXY)
@@ -310,19 +310,16 @@ bool QHttpSocketEngine::setOption(SocketOption option, int value)
return false;
}
-bool QHttpSocketEngine::waitForRead(int msecs, bool *timedOut)
+bool QHttpSocketEngine::waitForRead(QDeadlineTimer deadline, bool *timedOut)
{
Q_D(const QHttpSocketEngine);
if (!d->socket || d->socket->state() == QAbstractSocket::UnconnectedState)
return false;
- QElapsedTimer stopWatch;
- stopWatch.start();
-
// Wait for more data if nothing is available.
if (!d->socket->bytesAvailable()) {
- if (!d->socket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
+ if (!d->socket->waitForReadyRead(deadline.remainingTime())) {
if (d->socket->state() == QAbstractSocket::UnconnectedState)
return true;
setError(d->socket->error(), d->socket->errorString());
@@ -332,11 +329,7 @@ bool QHttpSocketEngine::waitForRead(int msecs, bool *timedOut)
}
}
- // If we're not connected yet, wait until we are, or until an error
- // occurs.
- while (d->state != Connected && d->socket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
- // Loop while the protocol handshake is taking place.
- }
+ waitForProtocolHandshake(deadline);
// Report any error that may occur.
if (d->state != Connected) {
@@ -348,14 +341,14 @@ bool QHttpSocketEngine::waitForRead(int msecs, bool *timedOut)
return true;
}
-bool QHttpSocketEngine::waitForWrite(int msecs, bool *timedOut)
+bool QHttpSocketEngine::waitForWrite(QDeadlineTimer deadline, bool *timedOut)
{
Q_D(const QHttpSocketEngine);
// If we're connected, just forward the call.
if (d->state == Connected) {
if (d->socket->bytesToWrite()) {
- if (!d->socket->waitForBytesWritten(msecs)) {
+ if (!d->socket->waitForBytesWritten(deadline.remainingTime())) {
if (d->socket->error() == QAbstractSocket::SocketTimeoutError && timedOut)
*timedOut = true;
return false;
@@ -364,15 +357,7 @@ bool QHttpSocketEngine::waitForWrite(int msecs, bool *timedOut)
return true;
}
- QElapsedTimer stopWatch;
- stopWatch.start();
-
- // If we're not connected yet, wait until we are, and until bytes have
- // been received (i.e., the socket has connected, we have sent the
- // greeting, and then received the response).
- while (d->state != Connected && d->socket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
- // Loop while the protocol handshake is taking place.
- }
+ waitForProtocolHandshake(deadline);
// Report any error that may occur.
if (d->state != Connected) {
@@ -386,25 +371,37 @@ bool QHttpSocketEngine::waitForWrite(int msecs, bool *timedOut)
bool QHttpSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
- int msecs, bool *timedOut)
+ QDeadlineTimer deadline, bool *timedOut)
{
Q_UNUSED(checkRead);
if (!checkWrite) {
// Not interested in writing? Then we wait for read notifications.
- bool canRead = waitForRead(msecs, timedOut);
+ bool canRead = waitForRead(deadline, timedOut);
if (readyToRead)
*readyToRead = canRead;
return canRead;
}
// Interested in writing? Then we wait for write notifications.
- bool canWrite = waitForWrite(msecs, timedOut);
+ bool canWrite = waitForWrite(deadline, timedOut);
if (readyToWrite)
*readyToWrite = canWrite;
return canWrite;
}
+void QHttpSocketEngine::waitForProtocolHandshake(QDeadlineTimer deadline) const
+{
+ Q_D(const QHttpSocketEngine);
+
+ // If we're not connected yet, wait until we are (and until bytes have
+ // been received, i.e. the socket has connected, we have sent the
+ // greeting, and then received the response), or until an error occurs.
+ while (d->state != Connected && d->socket->waitForReadyRead(deadline.remainingTime())) {
+ // Loop while the protocol handshake is taking place.
+ }
+}
+
bool QHttpSocketEngine::isReadNotificationEnabled() const
{
Q_D(const QHttpSocketEngine);
@@ -470,11 +467,14 @@ void QHttpSocketEngine::slotSocketConnected()
data += " HTTP/1.1\r\n";
data += "Proxy-Connection: keep-alive\r\n";
data += "Host: " + peerAddress + "\r\n";
- if (!d->proxy.hasRawHeader("User-Agent"))
+ const auto headers = d->proxy.headers();
+ if (!headers.contains(QHttpHeaders::WellKnownHeader::UserAgent))
data += "User-Agent: Mozilla/5.0\r\n";
- const auto headers = d->proxy.rawHeaderList();
- for (const QByteArray &header : headers)
- data += header + ": " + d->proxy.rawHeader(header) + "\r\n";
+ for (qsizetype i = 0; i < headers.size(); ++i) {
+ const auto name = headers.nameAt(i);
+ data += QByteArrayView(name.data(), name.size()) + ": "
+ + headers.valueAt(i) + "\r\n";
+ }
QAuthenticatorPrivate *priv = QAuthenticatorPrivate::getPrivate(d->authenticator);
//qDebug() << "slotSocketConnected: priv=" << priv << (priv ? (int)priv->method : -1);
if (priv && priv->method != QAuthenticatorPrivate::None) {
@@ -556,16 +556,8 @@ void QHttpSocketEngine::slotSocketReadNotification()
d->authenticator.detach();
priv = QAuthenticatorPrivate::getPrivate(d->authenticator);
- if (d->credentialsSent && priv->phase != QAuthenticatorPrivate::Phase2) {
- // Remember that (e.g.) NTLM is two-phase, so only reset when the authentication is not currently in progress.
- //407 response again means the provided username/password were invalid.
- d->authenticator = QAuthenticator(); //this is needed otherwise parseHttpResponse won't set the state, and then signal isn't emitted.
- d->authenticator.detach();
- priv = QAuthenticatorPrivate::getPrivate(d->authenticator);
- priv->hasFailed = true;
- }
-
- priv->parseHttpResponse(d->reply->header(), true, d->proxy.hostName());
+ const auto headers = d->reply->header();
+ priv->parseHttpResponse(headers, true, d->proxy.hostName());
if (priv->phase == QAuthenticatorPrivate::Invalid) {
// problem parsing the reply
@@ -576,6 +568,29 @@ void QHttpSocketEngine::slotSocketReadNotification()
return;
}
+ if (priv->phase == QAuthenticatorPrivate::Done
+ || (priv->phase == QAuthenticatorPrivate::Start
+ && (priv->method == QAuthenticatorPrivate::Ntlm
+ || priv->method == QAuthenticatorPrivate::Negotiate))) {
+ if (priv->phase == QAuthenticatorPrivate::Start)
+ priv->phase = QAuthenticatorPrivate::Phase1;
+ bool credentialsWasSent = d->credentialsSent;
+ if (d->credentialsSent) {
+ // Remember that (e.g.) NTLM is two-phase, so only reset when the authentication is
+ // not currently in progress. 407 response again means the provided
+ // username/password were invalid.
+ d->authenticator.detach();
+ priv = QAuthenticatorPrivate::getPrivate(d->authenticator);
+ priv->hasFailed = true;
+ d->credentialsSent = false;
+ priv->phase = QAuthenticatorPrivate::Done;
+ }
+ if ((priv->method != QAuthenticatorPrivate::Ntlm
+ && priv->method != QAuthenticatorPrivate::Negotiate)
+ || credentialsWasSent)
+ proxyAuthenticationRequired(d->proxy, &d->authenticator);
+ }
+
bool willClose;
QByteArray proxyConnectionHeader = d->reply->headerField("Proxy-Connection");
// Although most proxies use the unofficial Proxy-Connection header, the Connection header
@@ -603,10 +618,8 @@ void QHttpSocketEngine::slotSocketReadNotification()
d->reply = new QHttpNetworkReply(QUrl(), this);
}
- if (priv->phase == QAuthenticatorPrivate::Done)
- proxyAuthenticationRequired(d->proxy, &d->authenticator);
- // priv->phase will get reset to QAuthenticatorPrivate::Start if the authenticator got modified in the signal above.
if (priv->phase == QAuthenticatorPrivate::Done) {
+ d->authenticator = QAuthenticator();
setError(QAbstractSocket::ProxyAuthenticationRequiredError, tr("Authentication required"));
d->socket->disconnectFromHost();
} else {
diff --git a/src/network/socket/qhttpsocketengine_p.h b/src/network/socket/qhttpsocketengine_p.h
index 24ce2d50af..7926abf513 100644
--- a/src/network/socket/qhttpsocketengine_p.h
+++ b/src/network/socket/qhttpsocketengine_p.h
@@ -16,10 +16,12 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
-#include "private/qabstractsocketengine_p.h"
+
+#include <QtNetwork/qnetworkproxy.h>
+
#include "qabstractsocket.h"
-#include "qnetworkproxy.h"
#include "private/qauthenticator_p.h"
+#include "private/qabstractsocketengine_p.h"
QT_REQUIRE_CONFIG(http);
@@ -90,11 +92,16 @@ public:
int option(SocketOption option) const override;
bool setOption(SocketOption option, int value) override;
- bool waitForRead(int msecs = 30000, bool *timedOut = nullptr) override;
- bool waitForWrite(int msecs = 30000, bool *timedOut = nullptr) override;
+ bool waitForRead(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
+ bool waitForWrite(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
- int msecs = 30000, bool *timedOut = nullptr) override;
+ QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
+
+ void waitForProtocolHandshake(QDeadlineTimer deadline) const;
bool isReadNotificationEnabled() const override;
void setReadNotificationEnabled(bool enable) override;
diff --git a/src/network/socket/qlocalserver.cpp b/src/network/socket/qlocalserver.cpp
index d96f540148..5ef2db6b94 100644
--- a/src/network/socket/qlocalserver.cpp
+++ b/src/network/socket/qlocalserver.cpp
@@ -265,9 +265,27 @@ bool QLocalServer::hasPendingConnections() const
*/
void QLocalServer::incomingConnection(quintptr socketDescriptor)
{
- Q_D(QLocalServer);
QLocalSocket *socket = new QLocalSocket(this);
socket->setSocketDescriptor(socketDescriptor);
+ addPendingConnection(socket);
+}
+
+/*!
+ This function is called by QLocalServer::incomingConnection()
+ to add the \a socket to the list of pending incoming connections.
+
+ \note Don't forget to call this member from reimplemented
+ incomingConnection() if you do not want to break the
+ Pending Connections mechanism. This function emits the
+ pendingConnectionAvailable() signal after the socket has been
+ added.
+
+ \sa incomingConnection(), pendingConnectionAvailable()
+ \since 6.8
+*/
+void QLocalServer::addPendingConnection(QLocalSocket *socket)
+{
+ Q_D(QLocalServer);
d->pendingConnections.enqueue(socket);
emit newConnection();
}
diff --git a/src/network/socket/qlocalserver.h b/src/network/socket/qlocalserver.h
index 81073450e2..685253e8be 100644
--- a/src/network/socket/qlocalserver.h
+++ b/src/network/socket/qlocalserver.h
@@ -68,6 +68,7 @@ public:
protected:
virtual void incomingConnection(quintptr socketDescriptor);
+ void addPendingConnection(QLocalSocket *socket);
private:
Q_DISABLE_COPY(QLocalServer)
diff --git a/src/network/socket/qlocalserver_unix.cpp b/src/network/socket/qlocalserver_unix.cpp
index dcd001a263..9aa9a5b86f 100644
--- a/src/network/socket/qlocalserver_unix.cpp
+++ b/src/network/socket/qlocalserver_unix.cpp
@@ -279,8 +279,7 @@ void QLocalServerPrivate::_q_onNewConnection()
void QLocalServerPrivate::waitForNewConnection(int msec, bool *timedOut)
{
pollfd pfd = qt_make_pollfd(listenSocket, POLLIN);
-
- switch (qt_poll_msecs(&pfd, 1, msec)) {
+ switch (qt_safe_poll(&pfd, 1, QDeadlineTimer(msec))) {
case 0:
if (timedOut)
*timedOut = true;
diff --git a/src/network/socket/qlocalsocket_unix.cpp b/src/network/socket/qlocalsocket_unix.cpp
index 626d46d7bf..af0dc988af 100644
--- a/src/network/socket/qlocalsocket_unix.cpp
+++ b/src/network/socket/qlocalsocket_unix.cpp
@@ -13,16 +13,17 @@
#include <errno.h>
#include <qdir.h>
+#include <qdeadlinetimer.h>
#include <qdebug.h>
-#include <qelapsedtimer.h>
#include <qstringconverter.h>
#ifdef Q_OS_VXWORKS
# include <selectLib.h>
#endif
-#define QT_CONNECT_TIMEOUT 30000
+using namespace std::chrono_literals;
+#define QT_CONNECT_TIMEOUT 30000
QT_BEGIN_NAMESPACE
@@ -585,21 +586,20 @@ bool QLocalSocket::waitForConnected(int msec)
if (state() != ConnectingState)
return (state() == ConnectedState);
- QElapsedTimer timer;
- timer.start();
-
pollfd pfd = qt_make_pollfd(d->connectingSocket, POLLIN);
- do {
- const int timeout = (msec > 0) ? qMax(msec - timer.elapsed(), Q_INT64_C(0)) : msec;
- const int result = qt_poll_msecs(&pfd, 1, timeout);
+ QDeadlineTimer deadline{msec};
+ auto remainingTime = deadline.remainingTimeAsDuration();
+ do {
+ const int result = qt_safe_poll(&pfd, 1, deadline);
if (result == -1)
d->setErrorAndEmit(QLocalSocket::UnknownSocketError,
"QLocalSocket::waitForConnected"_L1);
else if (result > 0)
d->_q_connectToSocket();
- } while (state() == ConnectingState && !timer.hasExpired(msec));
+ } while (state() == ConnectingState
+ && (remainingTime = deadline.remainingTimeAsDuration()) > 0ns);
return (state() == ConnectedState);
}
diff --git a/src/network/socket/qnativesocketengine.cpp b/src/network/socket/qnativesocketengine.cpp
index 44efe95428..4c8b3ebf3f 100644
--- a/src/network/socket/qnativesocketengine.cpp
+++ b/src/network/socket/qnativesocketengine.cpp
@@ -78,7 +78,7 @@
\sa readDatagram(), QNetworkDatagram
*/
-#include "qnativesocketengine_p.h"
+#include "qnativesocketengine_p_p.h"
#include <qabstracteventdispatcher.h>
#include <qsocketnotifier.h>
@@ -948,23 +948,23 @@ void QNativeSocketEngine::close()
d->peerAddress.clear();
d->inboundStreamCount = d->outboundStreamCount = 0;
if (d->readNotifier) {
- qDeleteInEventHandler(d->readNotifier);
+ delete d->readNotifier;
d->readNotifier = nullptr;
}
if (d->writeNotifier) {
- qDeleteInEventHandler(d->writeNotifier);
+ delete d->writeNotifier;
d->writeNotifier = nullptr;
}
if (d->exceptNotifier) {
- qDeleteInEventHandler(d->exceptNotifier);
+ delete d->exceptNotifier;
d->exceptNotifier = nullptr;
}
}
/*!
- Waits for \a msecs milliseconds or until the socket is ready for
- reading. If \a timedOut is not \nullptr and \a msecs milliseconds
- have passed, the value of \a timedOut is set to true.
+ Waits until \a deadline has expired or until the socket is ready for
+ reading. If \a timedOut is not \nullptr and \a deadline has expired,
+ the value of \a timedOut is set to true.
Returns \c true if data is available for reading; otherwise returns
false.
@@ -976,7 +976,7 @@ void QNativeSocketEngine::close()
is to create a QSocketNotifier, passing the socket descriptor
returned by socketDescriptor() to its constructor.
*/
-bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut)
+bool QNativeSocketEngine::waitForRead(QDeadlineTimer deadline, bool *timedOut)
{
Q_D(const QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForRead(), false);
@@ -986,7 +986,7 @@ bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut)
if (timedOut)
*timedOut = false;
- int ret = d->nativeSelect(msecs, true);
+ int ret = d->nativeSelect(deadline, true);
if (ret == 0) {
if (timedOut)
*timedOut = true;
@@ -1002,9 +1002,9 @@ bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut)
}
/*!
- Waits for \a msecs milliseconds or until the socket is ready for
- writing. If \a timedOut is not \nullptr and \a msecs milliseconds
- have passed, the value of \a timedOut is set to true.
+ Waits until \a deadline has expired or until the socket is ready for
+ writing. If \a timedOut is not \nullptr and \a deadline has expired,
+ the value of \a timedOut is set to true.
Returns \c true if data is available for writing; otherwise returns
false.
@@ -1016,7 +1016,7 @@ bool QNativeSocketEngine::waitForRead(int msecs, bool *timedOut)
is to create a QSocketNotifier, passing the socket descriptor
returned by socketDescriptor() to its constructor.
*/
-bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut)
+bool QNativeSocketEngine::waitForWrite(QDeadlineTimer deadline, bool *timedOut)
{
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForWrite(), false);
@@ -1026,7 +1026,7 @@ bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut)
if (timedOut)
*timedOut = false;
- int ret = d->nativeSelect(msecs, false);
+ int ret = d->nativeSelect(deadline, false);
// On Windows, the socket is in connected state if a call to
// select(writable) is successful. In this case we should not
// issue a second call to WSAConnect()
@@ -1074,14 +1074,14 @@ bool QNativeSocketEngine::waitForWrite(int msecs, bool *timedOut)
bool QNativeSocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
- int msecs, bool *timedOut)
+ QDeadlineTimer deadline, bool *timedOut)
{
Q_D(QNativeSocketEngine);
Q_CHECK_VALID_SOCKETLAYER(QNativeSocketEngine::waitForReadOrWrite(), false);
Q_CHECK_NOT_STATE(QNativeSocketEngine::waitForReadOrWrite(),
QAbstractSocket::UnconnectedState, false);
- int ret = d->nativeSelect(msecs, checkRead, checkWrite, readyToRead, readyToWrite);
+ int ret = d->nativeSelect(deadline, checkRead, checkWrite, readyToRead, readyToWrite);
// On Windows, the socket is in connected state if a call to
// select(writable) is successful. In this case we should not
// issue a second call to WSAConnect()
diff --git a/src/network/socket/qnativesocketengine_p.h b/src/network/socket/qnativesocketengine_p.h
index 2f77c15cd2..4c185b7a4a 100644
--- a/src/network/socket/qnativesocketengine_p.h
+++ b/src/network/socket/qnativesocketengine_p.h
@@ -20,8 +20,9 @@
#include "QtNetwork/qhostaddress.h"
#include "QtNetwork/qnetworkinterface.h"
#include "private/qabstractsocketengine_p.h"
+#include "qplatformdefs.h"
+
#ifndef Q_OS_WIN
-# include "qplatformdefs.h"
# include <netinet/in.h>
#else
# include <winsock2.h>
@@ -34,51 +35,63 @@ QT_BEGIN_NAMESPACE
#ifdef Q_OS_WIN
# define QT_SOCKLEN_T int
# define QT_SOCKOPTLEN_T int
-
-// The following definitions are copied from the MinGW header mswsock.h which
-// was placed in the public domain. The WSASendMsg and WSARecvMsg functions
-// were introduced with Windows Vista, so some Win32 headers are lacking them.
-// There are no known versions of Windows CE or Embedded that contain them.
-# ifndef WSAID_WSARECVMSG
-typedef INT (WINAPI *LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg,
- LPDWORD lpdwNumberOfBytesRecvd,
- LPWSAOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
-# define WSAID_WSARECVMSG {0xf689d7c8,0x6f1f,0x436b,{0x8a,0x53,0xe5,0x4f,0xe3,0x51,0xc3,0x22}}
-# endif // !WSAID_WSARECVMSG
-# ifndef WSAID_WSASENDMSG
-typedef struct {
- LPWSAMSG lpMsg;
- DWORD dwFlags;
- LPDWORD lpNumberOfBytesSent;
- LPWSAOVERLAPPED lpOverlapped;
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine;
-} WSASENDMSG, *LPWSASENDMSG;
-
-typedef INT (WSAAPI *LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags,
- LPDWORD lpNumberOfBytesSent,
- LPWSAOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
-
-# define WSAID_WSASENDMSG {0xa441e712,0x754f,0x43ca,{0x84,0xa7,0x0d,0xee,0x44,0xcf,0x60,0x6d}}
-# endif // !WSAID_WSASENDMSG
-#endif // Q_OS_WIN
-
-union qt_sockaddr {
- sockaddr a;
- sockaddr_in a4;
- sockaddr_in6 a6;
-};
+#endif
namespace {
namespace SetSALen {
template <typename T> void set(T *sa, typename std::enable_if<(&T::sa_len, true), QT_SOCKLEN_T>::type len)
{ sa->sa_len = len; }
+ template <typename T> void set(T *sa, typename std::enable_if<(&T::sin_len, true), QT_SOCKLEN_T>::type len)
+ { sa->sin_len = len; }
template <typename T> void set(T *sin6, typename std::enable_if<(&T::sin6_len, true), QT_SOCKLEN_T>::type len)
{ sin6->sin6_len = len; }
template <typename T> void set(T *, ...) {}
}
+
+inline QT_SOCKLEN_T setSockaddr(sockaddr_in *sin, const QHostAddress &addr, quint16 port = 0)
+{
+ *sin = {};
+ SetSALen::set(sin, sizeof(*sin));
+ sin->sin_family = AF_INET;
+ sin->sin_port = htons(port);
+ sin->sin_addr.s_addr = htonl(addr.toIPv4Address());
+ return sizeof(*sin);
+}
+
+inline QT_SOCKLEN_T setSockaddr(sockaddr_in6 *sin6, const QHostAddress &addr, quint16 port = 0)
+{
+ *sin6 = {};
+ SetSALen::set(sin6, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+ sin6->sin6_port = htons(port);
+ memcpy(sin6->sin6_addr.s6_addr, addr.toIPv6Address().c, sizeof(sin6->sin6_addr));
+#if QT_CONFIG(networkinterface)
+ sin6->sin6_scope_id = QNetworkInterface::interfaceIndexFromName(addr.scopeId());
+#else
+ // it had better be a number then, if it is not empty
+ sin6->sin6_scope_id = addr.scopeId().toUInt();
+#endif
+ return sizeof(*sin6);
+}
+
+inline QT_SOCKLEN_T setSockaddr(sockaddr *sa, const QHostAddress &addr, quint16 port = 0)
+{
+ switch (addr.protocol()) {
+ case QHostAddress::IPv4Protocol:
+ return setSockaddr(reinterpret_cast<sockaddr_in *>(sa), addr, port);
+
+ case QHostAddress::IPv6Protocol:
+ case QHostAddress::AnyIPProtocol:
+ return setSockaddr(reinterpret_cast<sockaddr_in6 *>(sa), addr, port);
+
+ case QHostAddress::UnknownNetworkLayerProtocol:
+ break;
+ }
+ *sa = {};
+ sa->sa_family = AF_UNSPEC;
+ return 0;
}
+} // unnamed namespace
class QNativeSocketEnginePrivate;
#ifndef QT_NO_NETWORKINTERFACE
@@ -141,11 +154,14 @@ public:
int option(SocketOption option) const override;
bool setOption(SocketOption option, int value) override;
- bool waitForRead(int msecs = 30000, bool *timedOut = nullptr) override;
- bool waitForWrite(int msecs = 30000, bool *timedOut = nullptr) override;
+ bool waitForRead(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
+ bool waitForWrite(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
- int msecs = 30000, bool *timedOut = nullptr) override;
+ QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
bool isReadNotificationEnabled() const override;
void setReadNotificationEnabled(bool enable) override;
@@ -163,132 +179,6 @@ private:
Q_DISABLE_COPY_MOVE(QNativeSocketEngine)
};
-class QSocketNotifier;
-
-class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate
-{
- Q_DECLARE_PUBLIC(QNativeSocketEngine)
-public:
- QNativeSocketEnginePrivate();
- ~QNativeSocketEnginePrivate();
-
- qintptr socketDescriptor;
-
- QSocketNotifier *readNotifier, *writeNotifier, *exceptNotifier;
-
-#if defined(Q_OS_WIN)
- LPFN_WSASENDMSG sendmsg;
- LPFN_WSARECVMSG recvmsg;
-# endif
- enum ErrorString {
- NonBlockingInitFailedErrorString,
- BroadcastingInitFailedErrorString,
- NoIpV6ErrorString,
- RemoteHostClosedErrorString,
- TimeOutErrorString,
- ResourceErrorString,
- OperationUnsupportedErrorString,
- ProtocolUnsupportedErrorString,
- InvalidSocketErrorString,
- HostUnreachableErrorString,
- NetworkUnreachableErrorString,
- AccessErrorString,
- ConnectionTimeOutErrorString,
- ConnectionRefusedErrorString,
- AddressInuseErrorString,
- AddressNotAvailableErrorString,
- AddressProtectedErrorString,
- DatagramTooLargeErrorString,
- SendDatagramErrorString,
- ReceiveDatagramErrorString,
- WriteErrorString,
- ReadErrorString,
- PortInuseErrorString,
- NotSocketErrorString,
- InvalidProxyTypeString,
- TemporaryErrorString,
- NetworkDroppedConnectionErrorString,
- ConnectionResetErrorString,
-
- UnknownSocketErrorString = -1
- };
-
- void setError(QAbstractSocket::SocketError error, ErrorString errorString) const;
- QHostAddress adjustAddressProtocol(const QHostAddress &address) const;
-
- // native functions
- int option(QNativeSocketEngine::SocketOption option) const;
- bool setOption(QNativeSocketEngine::SocketOption option, int value);
-
- bool createNewSocket(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol &protocol);
-
- bool nativeConnect(const QHostAddress &address, quint16 port);
- bool nativeBind(const QHostAddress &address, quint16 port);
- bool nativeListen(int backlog);
- qintptr nativeAccept();
-#ifndef QT_NO_NETWORKINTERFACE
- bool nativeJoinMulticastGroup(const QHostAddress &groupAddress,
- const QNetworkInterface &iface);
- bool nativeLeaveMulticastGroup(const QHostAddress &groupAddress,
- const QNetworkInterface &iface);
- QNetworkInterface nativeMulticastInterface() const;
- bool nativeSetMulticastInterface(const QNetworkInterface &iface);
-#endif
- qint64 nativeBytesAvailable() const;
-
- bool nativeHasPendingDatagrams() const;
- qint64 nativePendingDatagramSize() const;
- qint64 nativeReceiveDatagram(char *data, qint64 maxLength, QIpPacketHeader *header,
- QAbstractSocketEngine::PacketHeaderOptions options);
- qint64 nativeSendDatagram(const char *data, qint64 length, const QIpPacketHeader &header);
- qint64 nativeRead(char *data, qint64 maxLength);
- qint64 nativeWrite(const char *data, qint64 length);
- int nativeSelect(int timeout, bool selectForRead) const;
- int nativeSelect(int timeout, bool checkRead, bool checkWrite,
- bool *selectForRead, bool *selectForWrite) const;
-
- void nativeClose();
-
- bool checkProxy(const QHostAddress &address);
- bool fetchConnectionParameters();
-
-#if QT_CONFIG(networkinterface)
- static uint scopeIdFromString(const QString &scopeid)
- { return QNetworkInterface::interfaceIndexFromName(scopeid); }
-#endif
-
- /*! \internal
- Sets \a address and \a port in the \a aa sockaddr structure and the size in \a sockAddrSize.
- The address \a is converted to IPv6 if the current socket protocol is also IPv6.
- */
- void setPortAndAddress(quint16 port, const QHostAddress &address, qt_sockaddr *aa, QT_SOCKLEN_T *sockAddrSize)
- {
- if (address.protocol() == QAbstractSocket::IPv6Protocol
- || address.protocol() == QAbstractSocket::AnyIPProtocol
- || socketProtocol == QAbstractSocket::IPv6Protocol
- || socketProtocol == QAbstractSocket::AnyIPProtocol) {
- memset(&aa->a6, 0, sizeof(sockaddr_in6));
- aa->a6.sin6_family = AF_INET6;
-#if QT_CONFIG(networkinterface)
- aa->a6.sin6_scope_id = scopeIdFromString(address.scopeId());
-#endif
- aa->a6.sin6_port = htons(port);
- Q_IPV6ADDR tmp = address.toIPv6Address();
- memcpy(&aa->a6.sin6_addr, &tmp, sizeof(tmp));
- *sockAddrSize = sizeof(sockaddr_in6);
- SetSALen::set(&aa->a, sizeof(sockaddr_in6));
- } else {
- memset(&aa->a, 0, sizeof(sockaddr_in));
- aa->a4.sin_family = AF_INET;
- aa->a4.sin_port = htons(port);
- aa->a4.sin_addr.s_addr = htonl(address.toIPv4Address());
- *sockAddrSize = sizeof(sockaddr_in);
- SetSALen::set(&aa->a, sizeof(sockaddr_in));
- }
- }
-
-};
-
QT_END_NAMESPACE
#endif // QNATIVESOCKETENGINE_P_H
diff --git a/src/network/socket/qnativesocketengine_p_p.h b/src/network/socket/qnativesocketengine_p_p.h
new file mode 100644
index 0000000000..189a4327fd
--- /dev/null
+++ b/src/network/socket/qnativesocketengine_p_p.h
@@ -0,0 +1,189 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// Copyright (C) 2016 Intel Corporation.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QNATIVESOCKETENGINE_P_P_H
+#define QNATIVESOCKETENGINE_P_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "private/qabstractsocketengine_p.h"
+#include "private/qnativesocketengine_p.h"
+
+#ifndef Q_OS_WIN
+# include <netinet/in.h>
+#else
+# include <winsock2.h>
+# include <ws2tcpip.h>
+# include <mswsock.h>
+#endif
+
+QT_BEGIN_NAMESPACE
+
+#ifdef Q_OS_WIN
+
+// The following definitions are copied from the MinGW header mswsock.h which
+// was placed in the public domain. The WSASendMsg and WSARecvMsg functions
+// were introduced with Windows Vista, so some Win32 headers are lacking them.
+// There are no known versions of Windows CE or Embedded that contain them.
+# ifndef WSAID_WSARECVMSG
+typedef INT (WINAPI *LPFN_WSARECVMSG)(SOCKET s, LPWSAMSG lpMsg,
+ LPDWORD lpdwNumberOfBytesRecvd,
+ LPWSAOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+# define WSAID_WSARECVMSG {0xf689d7c8,0x6f1f,0x436b,{0x8a,0x53,0xe5,0x4f,0xe3,0x51,0xc3,0x22}}
+# endif // !WSAID_WSARECVMSG
+# ifndef WSAID_WSASENDMSG
+typedef struct {
+ LPWSAMSG lpMsg;
+ DWORD dwFlags;
+ LPDWORD lpNumberOfBytesSent;
+ LPWSAOVERLAPPED lpOverlapped;
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine;
+} WSASENDMSG, *LPWSASENDMSG;
+
+typedef INT (WSAAPI *LPFN_WSASENDMSG)(SOCKET s, LPWSAMSG lpMsg, DWORD dwFlags,
+ LPDWORD lpNumberOfBytesSent,
+ LPWSAOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+
+# define WSAID_WSASENDMSG {0xa441e712,0x754f,0x43ca,{0x84,0xa7,0x0d,0xee,0x44,0xcf,0x60,0x6d}}
+# endif // !WSAID_WSASENDMSG
+#endif // Q_OS_WIN
+
+union qt_sockaddr {
+ sockaddr a;
+ sockaddr_in a4;
+ sockaddr_in6 a6;
+};
+
+class QSocketNotifier;
+
+class QNativeSocketEnginePrivate : public QAbstractSocketEnginePrivate
+{
+ Q_DECLARE_PUBLIC(QNativeSocketEngine)
+public:
+ QNativeSocketEnginePrivate();
+ ~QNativeSocketEnginePrivate();
+
+ qintptr socketDescriptor;
+
+ QSocketNotifier *readNotifier, *writeNotifier, *exceptNotifier;
+
+#if defined(Q_OS_WIN)
+ LPFN_WSASENDMSG sendmsg;
+ LPFN_WSARECVMSG recvmsg;
+# endif
+ enum ErrorString {
+ NonBlockingInitFailedErrorString,
+ BroadcastingInitFailedErrorString,
+ NoIpV6ErrorString,
+ RemoteHostClosedErrorString,
+ TimeOutErrorString,
+ ResourceErrorString,
+ OperationUnsupportedErrorString,
+ ProtocolUnsupportedErrorString,
+ InvalidSocketErrorString,
+ HostUnreachableErrorString,
+ NetworkUnreachableErrorString,
+ AccessErrorString,
+ ConnectionTimeOutErrorString,
+ ConnectionRefusedErrorString,
+ AddressInuseErrorString,
+ AddressNotAvailableErrorString,
+ AddressProtectedErrorString,
+ DatagramTooLargeErrorString,
+ SendDatagramErrorString,
+ ReceiveDatagramErrorString,
+ WriteErrorString,
+ ReadErrorString,
+ PortInuseErrorString,
+ NotSocketErrorString,
+ InvalidProxyTypeString,
+ TemporaryErrorString,
+ NetworkDroppedConnectionErrorString,
+ ConnectionResetErrorString,
+
+ UnknownSocketErrorString = -1
+ };
+
+ void setError(QAbstractSocket::SocketError error, ErrorString errorString) const;
+ QHostAddress adjustAddressProtocol(const QHostAddress &address) const;
+
+ // native functions
+ int option(QNativeSocketEngine::SocketOption option) const;
+ bool setOption(QNativeSocketEngine::SocketOption option, int value);
+
+ bool createNewSocket(QAbstractSocket::SocketType type, QAbstractSocket::NetworkLayerProtocol &protocol);
+
+ bool nativeConnect(const QHostAddress &address, quint16 port);
+ bool nativeBind(const QHostAddress &address, quint16 port);
+ bool nativeListen(int backlog);
+ qintptr nativeAccept();
+#ifndef QT_NO_NETWORKINTERFACE
+ bool nativeJoinMulticastGroup(const QHostAddress &groupAddress,
+ const QNetworkInterface &iface);
+ bool nativeLeaveMulticastGroup(const QHostAddress &groupAddress,
+ const QNetworkInterface &iface);
+ QNetworkInterface nativeMulticastInterface() const;
+ bool nativeSetMulticastInterface(const QNetworkInterface &iface);
+#endif
+ qint64 nativeBytesAvailable() const;
+
+ bool nativeHasPendingDatagrams() const;
+ qint64 nativePendingDatagramSize() const;
+ qint64 nativeReceiveDatagram(char *data, qint64 maxLength, QIpPacketHeader *header,
+ QAbstractSocketEngine::PacketHeaderOptions options);
+ qint64 nativeSendDatagram(const char *data, qint64 length, const QIpPacketHeader &header);
+ qint64 nativeRead(char *data, qint64 maxLength);
+ qint64 nativeWrite(const char *data, qint64 length);
+ int nativeSelect(QDeadlineTimer deadline, bool selectForRead) const;
+ int nativeSelect(QDeadlineTimer deadline, bool checkRead, bool checkWrite,
+ bool *selectForRead, bool *selectForWrite) const;
+
+ void nativeClose();
+
+ bool checkProxy(const QHostAddress &address);
+ bool fetchConnectionParameters();
+
+ /*! \internal
+ Sets \a address and \a port in the \a aa sockaddr structure and the size in \a sockAddrSize.
+ The address \a is converted to IPv6 if the current socket protocol is also IPv6.
+ */
+ void setPortAndAddress(quint16 port, const QHostAddress &address, qt_sockaddr *aa, QT_SOCKLEN_T *sockAddrSize)
+ {
+ switch (socketProtocol) {
+ case QHostAddress::IPv6Protocol:
+ case QHostAddress::AnyIPProtocol:
+ // force to IPv6
+ setSockaddr(&aa->a6, address, port);
+ *sockAddrSize = sizeof(sockaddr_in6);
+ return;
+
+ case QHostAddress::IPv4Protocol:
+ // force to IPv4
+ setSockaddr(&aa->a4, address, port);
+ *sockAddrSize = sizeof(sockaddr_in);
+ return;
+
+ case QHostAddress::UnknownNetworkLayerProtocol:
+ // don't force
+ break;
+ }
+ *sockAddrSize = setSockaddr(&aa->a, address, port);
+ }
+
+};
+
+QT_END_NAMESPACE
+
+#endif // QNATIVESOCKETENGINE_P_P_H
diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp
index 13db3e6232..b6df412253 100644
--- a/src/network/socket/qnativesocketengine_unix.cpp
+++ b/src/network/socket/qnativesocketengine_unix.cpp
@@ -3,11 +3,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
//#define QNATIVESOCKETENGINE_DEBUG
-#include "qnativesocketengine_p.h"
+#include "qnativesocketengine_p_p.h"
#include "private/qnet_unix_p.h"
+#include "qdeadlinetimer.h"
#include "qiodevice.h"
#include "qhostaddress.h"
-#include "qelapsedtimer.h"
#include "qvarlengtharray.h"
#include "qnetworkinterface.h"
#include "qendian.h"
@@ -17,15 +17,6 @@
#include <time.h>
#include <errno.h>
#include <fcntl.h>
-#ifndef QT_NO_IPV6IFNAME
-#include <net/if.h>
-#endif
-#ifdef QT_LINUXBASE
-#include <arpa/inet.h>
-#endif
-#ifdef Q_OS_BSD4
-#include <net/if_dl.h>
-#endif
#ifdef Q_OS_INTEGRITY
#include <sys/uio.h>
#endif
@@ -40,6 +31,9 @@
#include <sys/socket.h>
#include <netinet/sctp.h>
#endif
+#ifdef Q_OS_BSD4
+# include <net/if_dl.h>
+#endif
QT_BEGIN_NAMESPACE
@@ -442,6 +436,7 @@ bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &addr, quint16
case EFAULT:
case ENOTSOCK:
socketState = QAbstractSocket::UnconnectedState;
+ break;
default:
break;
}
@@ -799,7 +794,7 @@ bool QNativeSocketEnginePrivate::nativeHasPendingDatagrams() const
// Peek 1 bytes into the next message.
ssize_t readBytes;
char c;
- EINTR_LOOP(readBytes, ::recv(socketDescriptor, &c, 1, MSG_PEEK));
+ QT_EINTR_LOOP(readBytes, ::recv(socketDescriptor, &c, 1, MSG_PEEK));
// If there's no error, or if our buffer was too small, there must be a
// pending datagram.
@@ -818,7 +813,7 @@ qint64 QNativeSocketEnginePrivate::nativePendingDatagramSize() const
#ifdef Q_OS_LINUX
// Linux can return the actual datagram size if we use MSG_TRUNC
char c;
- EINTR_LOOP(recvResult, ::recv(socketDescriptor, &c, 1, MSG_PEEK | MSG_TRUNC));
+ QT_EINTR_LOOP(recvResult, ::recv(socketDescriptor, &c, 1, MSG_PEEK | MSG_TRUNC));
#elif defined(SO_NREAD)
// macOS can return the actual datagram size if we use SO_NREAD
int value;
@@ -1281,6 +1276,9 @@ qint64 QNativeSocketEnginePrivate::nativeWrite(const char *data, qint64 len)
setError(QAbstractSocket::RemoteHostClosedError, RemoteHostClosedErrorString);
q->close();
break;
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
case EAGAIN:
writtenBytes = 0;
break;
@@ -1350,16 +1348,17 @@ qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxSize)
return qint64(r);
}
-int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const
+int QNativeSocketEnginePrivate::nativeSelect(QDeadlineTimer deadline, bool selectForRead) const
{
bool dummy;
- return nativeSelect(timeout, selectForRead, !selectForRead, &dummy, &dummy);
+ return nativeSelect(deadline, selectForRead, !selectForRead, &dummy, &dummy);
}
#ifndef Q_OS_WASM
-int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite,
- bool *selectForRead, bool *selectForWrite) const
+int QNativeSocketEnginePrivate::nativeSelect(QDeadlineTimer deadline, bool checkRead,
+ bool checkWrite, bool *selectForRead,
+ bool *selectForWrite) const
{
pollfd pfd = qt_make_pollfd(socketDescriptor, 0);
@@ -1369,7 +1368,7 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool c
if (checkWrite)
pfd.events |= POLLOUT;
- const int ret = qt_poll_msecs(&pfd, 1, timeout);
+ const int ret = qt_safe_poll(&pfd, 1, deadline);
if (ret <= 0)
return ret;
@@ -1390,13 +1389,16 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool c
#else
-int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool checkRead, bool checkWrite,
- bool *selectForRead, bool *selectForWrite) const
+int QNativeSocketEnginePrivate::nativeSelect(QDeadlineTimer deadline, bool checkRead,
+ bool checkWrite, bool *selectForRead,
+ bool *selectForWrite) const
{
*selectForRead = checkRead;
*selectForWrite = checkWrite;
bool socketDisconnect = false;
- QEventDispatcherWasm::socketSelect(timeout, socketDescriptor, checkRead, checkWrite,selectForRead, selectForWrite, &socketDisconnect);
+ QEventDispatcherWasm::socketSelect(deadline.remainingTime(), socketDescriptor, checkRead,
+ checkWrite, selectForRead, selectForWrite,
+ &socketDisconnect);
// The disconnect/close handling code in QAbstractsScket::canReadNotification()
// does not detect remote disconnect properly; do that here as a workardound.
diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp
index f3a0a06668..6525f46e30 100644
--- a/src/network/socket/qnativesocketengine_win.cpp
+++ b/src/network/socket/qnativesocketengine_win.cpp
@@ -2,13 +2,10 @@
// Copyright (C) 2016 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-// Prevent windows system header files from defining min/max as macros.
-#define NOMINMAX 1
-
#include <winsock2.h>
#include <ws2tcpip.h>
-#include "qnativesocketengine_p.h"
+#include "qnativesocketengine_p_p.h"
#include <qabstracteventdispatcher.h>
#include <qsocketnotifier.h>
@@ -19,6 +16,7 @@
#include <qvarlengtharray.h>
#include <algorithm>
+#include <chrono>
//#define QNATIVESOCKETENGINE_DEBUG
#if defined(QNATIVESOCKETENGINE_DEBUG)
@@ -1431,7 +1429,18 @@ qint64 QNativeSocketEnginePrivate::nativeRead(char *data, qint64 maxLength)
return ret;
}
-int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) const
+inline timeval durationToTimeval(std::chrono::nanoseconds dur) noexcept
+{
+ using namespace std::chrono;
+ const auto secs = duration_cast<seconds>(dur);
+ const auto frac = duration_cast<microseconds>(dur - secs);
+ struct timeval tval;
+ tval.tv_sec = secs.count();
+ tval.tv_usec = frac.count();
+ return tval;
+}
+
+int QNativeSocketEnginePrivate::nativeSelect(QDeadlineTimer deadline, bool selectForRead) const
{
bool readEnabled = selectForRead && readNotifier && readNotifier->isEnabled();
if (readEnabled)
@@ -1445,12 +1454,10 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) co
fds.fd_count = 1;
fds.fd_array[0] = (SOCKET)socketDescriptor;
- struct timeval tv;
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout % 1000) * 1000;
+ struct timeval tv = durationToTimeval(deadline.remainingTimeAsDuration());
if (selectForRead) {
- ret = select(0, &fds, 0, 0, timeout < 0 ? 0 : &tv);
+ ret = select(0, &fds, 0, 0, &tv);
} else {
// select for write
@@ -1459,7 +1466,7 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) co
FD_ZERO(&fdexception);
FD_SET((SOCKET)socketDescriptor, &fdexception);
- ret = select(0, 0, &fds, &fdexception, timeout < 0 ? 0 : &tv);
+ ret = select(0, 0, &fds, &fdexception, &tv);
// ... but if it is actually set, pretend it did not happen
if (ret > 0 && FD_ISSET((SOCKET)socketDescriptor, &fdexception))
@@ -1472,7 +1479,7 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout, bool selectForRead) co
return ret;
}
-int QNativeSocketEnginePrivate::nativeSelect(int timeout,
+int QNativeSocketEnginePrivate::nativeSelect(QDeadlineTimer deadline,
bool checkRead, bool checkWrite,
bool *selectForRead, bool *selectForWrite) const
{
@@ -1501,11 +1508,9 @@ int QNativeSocketEnginePrivate::nativeSelect(int timeout,
FD_SET((SOCKET)socketDescriptor, &fdexception);
}
- struct timeval tv;
- tv.tv_sec = timeout / 1000;
- tv.tv_usec = (timeout % 1000) * 1000;
+ struct timeval tv = durationToTimeval(deadline.remainingTimeAsDuration());
- ret = select(socketDescriptor + 1, &fdread, &fdwrite, &fdexception, timeout < 0 ? 0 : &tv);
+ ret = select(socketDescriptor + 1, &fdread, &fdwrite, &fdexception, &tv);
//... but if it is actually set, pretend it did not happen
if (ret > 0 && FD_ISSET((SOCKET)socketDescriptor, &fdexception))
diff --git a/src/network/socket/qnet_unix_p.h b/src/network/socket/qnet_unix_p.h
index c72e5745b0..a172a14a10 100644
--- a/src/network/socket/qnet_unix_p.h
+++ b/src/network/socket/qnet_unix_p.h
@@ -108,7 +108,7 @@ static inline int qt_safe_connect(int sockfd, const struct sockaddr *addr, QT_SO
{
int ret;
// Solaris e.g. expects a non-const 2nd parameter
- EINTR_LOOP(ret, QT_SOCKET_CONNECT(sockfd, const_cast<struct sockaddr *>(addr), addrlen));
+ QT_EINTR_LOOP(ret, QT_SOCKET_CONNECT(sockfd, const_cast<struct sockaddr *>(addr), addrlen));
return ret;
}
#undef QT_SOCKET_CONNECT
@@ -124,15 +124,10 @@ static inline int qt_safe_connect(int sockfd, const struct sockaddr *addr, QT_SO
# undef listen
#endif
-// VxWorks' headers specify 'int' instead of '...' for the 3rd ioctl() parameter.
template <typename T>
static inline int qt_safe_ioctl(int sockfd, unsigned long request, T arg)
{
-#ifdef Q_OS_VXWORKS
- return ::ioctl(sockfd, request, (int) arg);
-#else
return ::ioctl(sockfd, request, arg);
-#endif
}
static inline int qt_safe_sendmsg(int sockfd, const struct msghdr *msg, int flags)
@@ -144,7 +139,7 @@ static inline int qt_safe_sendmsg(int sockfd, const struct msghdr *msg, int flag
#endif
int ret;
- EINTR_LOOP(ret, ::sendmsg(sockfd, msg, flags));
+ QT_EINTR_LOOP(ret, ::sendmsg(sockfd, msg, flags));
return ret;
}
@@ -152,7 +147,7 @@ static inline int qt_safe_recvmsg(int sockfd, struct msghdr *msg, int flags)
{
int ret;
- EINTR_LOOP(ret, ::recvmsg(sockfd, msg, flags));
+ QT_EINTR_LOOP(ret, ::recvmsg(sockfd, msg, flags));
return ret;
}
diff --git a/src/network/socket/qsctpsocket.cpp b/src/network/socket/qsctpsocket.cpp
index 27c6fc930c..868c9e3785 100644
--- a/src/network/socket/qsctpsocket.cpp
+++ b/src/network/socket/qsctpsocket.cpp
@@ -83,7 +83,6 @@
#include "qsctpsocket_p.h"
#include "qabstractsocketengine_p.h"
-#include "private/qbytearray_p.h"
#ifdef QSCTPSOCKET_DEBUG
#include <qdebug.h>
@@ -133,7 +132,7 @@ bool QSctpSocketPrivate::canReadNotification()
bytesToRead = 4096;
}
- Q_ASSERT((datagramSize + qsizetype(bytesToRead)) < MaxByteArraySize);
+ Q_ASSERT((datagramSize + qsizetype(bytesToRead)) < QByteArray::max_size());
incomingDatagram.resize(datagramSize + int(bytesToRead));
#if defined (QSCTPSOCKET_DEBUG)
diff --git a/src/network/socket/qsocks5socketengine.cpp b/src/network/socket/qsocks5socketengine.cpp
index b1bca2bf4d..b0fdc63d66 100644
--- a/src/network/socket/qsocks5socketengine.cpp
+++ b/src/network/socket/qsocks5socketengine.cpp
@@ -9,6 +9,7 @@
#include "qdebug.h"
#include "qhash.h"
#include "qqueue.h"
+#include "qdeadlinetimer.h"
#include "qelapsedtimer.h"
#include "qmutex.h"
#include "qthread.h"
@@ -20,18 +21,21 @@
#include <qendian.h>
#include <qnetworkinterface.h>
+#include <QtCore/qpointer.h>
+
#include <memory>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+using namespace std::chrono_literals;
static const int MaxWriteBufferSize = 128*1024;
//#define QSOCKS5SOCKETLAYER_DEBUG
#define MAX_DATA_DUMP 256
-#define SOCKS5_BLOCKING_BIND_TIMEOUT 5000
+static constexpr auto Socks5BlockingBindTimeout = 5s;
#define Q_INIT_CHECK(returnValue) do { \
if (!d->data) { \
@@ -317,9 +321,10 @@ void QSocks5BindStore::add(qintptr socketDescriptor, QSocks5BindData *bindData)
}
bindData->timeStamp.start();
store.insert(socketDescriptor, bindData);
+
// start sweep timer if not started
if (sweepTimerId == -1)
- sweepTimerId = startTimer(60000);
+ sweepTimerId = startTimer(1min);
}
bool QSocks5BindStore::contains(qintptr socketDescriptor)
@@ -1325,11 +1330,8 @@ bool QSocks5SocketEngine::bind(const QHostAddress &addr, quint16 port)
return false;
}
- int msecs = SOCKS5_BLOCKING_BIND_TIMEOUT;
- QElapsedTimer stopWatch;
- stopWatch.start();
d->data->controlSocket->connectToHost(d->proxyInfo.hostName(), d->proxyInfo.port());
- if (!d->waitForConnected(msecs, nullptr) ||
+ if (!d->waitForConnected(QDeadlineTimer{Socks5BlockingBindTimeout}, nullptr) ||
d->data->controlSocket->state() == QAbstractSocket::UnconnectedState) {
// waitForConnected sets the error state and closes the socket
QSOCKS5_Q_DEBUG << "waitForConnected to proxy server" << d->data->controlSocket->errorString();
@@ -1420,11 +1422,9 @@ void QSocks5SocketEngine::close()
Q_D(QSocks5SocketEngine);
if (d->data && d->data->controlSocket) {
if (d->data->controlSocket->state() == QAbstractSocket::ConnectedState) {
- int msecs = 100;
- QElapsedTimer stopWatch;
- stopWatch.start();
+ QDeadlineTimer deadline(100ms);
while (!d->data->controlSocket->bytesToWrite()) {
- if (!d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed())))
+ if (!d->data->controlSocket->waitForBytesWritten(deadline.remainingTime()))
break;
}
}
@@ -1445,7 +1445,7 @@ qint64 QSocks5SocketEngine::bytesAvailable() const
#ifndef QT_NO_UDPSOCKET
else if (d->mode == QSocks5SocketEnginePrivate::UdpAssociateMode
&& !d->udpData->pendingDatagrams.isEmpty())
- return d->udpData->pendingDatagrams.first().data.size();
+ return d->udpData->pendingDatagrams.constFirst().data.size();
#endif
return 0;
}
@@ -1460,7 +1460,7 @@ qint64 QSocks5SocketEngine::read(char *data, qint64 maxlen)
//imitate remote closed
close();
setError(QAbstractSocket::RemoteHostClosedError,
- "Remote host closed connection###"_L1);
+ "Remote host closed connection"_L1);
setState(QAbstractSocket::UnconnectedState);
return -1;
} else {
@@ -1677,7 +1677,7 @@ bool QSocks5SocketEngine::setOption(SocketOption option, int value)
return false;
}
-bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut)
+bool QSocks5SocketEnginePrivate::waitForConnected(QDeadlineTimer deadline, bool *timedOut)
{
if (data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return false;
@@ -1687,11 +1687,8 @@ bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut)
mode == BindMode ? BindSuccess :
UdpAssociateSuccess;
- QElapsedTimer stopWatch;
- stopWatch.start();
-
while (socks5State != wantedState) {
- if (!data->controlSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
+ if (!data->controlSocket->waitForReadyRead(deadline.remainingTime())) {
if (data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
@@ -1705,18 +1702,15 @@ bool QSocks5SocketEnginePrivate::waitForConnected(int msecs, bool *timedOut)
return true;
}
-bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut)
+bool QSocks5SocketEngine::waitForRead(QDeadlineTimer deadline, bool *timedOut)
{
Q_D(QSocks5SocketEngine);
- QSOCKS5_DEBUG << "waitForRead" << msecs;
+ QSOCKS5_DEBUG << "waitForRead" << deadline.remainingTimeAsDuration();
d->readNotificationActivated = false;
- QElapsedTimer stopWatch;
- stopWatch.start();
-
// are we connected yet?
- if (!d->waitForConnected(msecs, timedOut))
+ if (!d->waitForConnected(deadline, timedOut))
return false;
if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
@@ -1730,7 +1724,7 @@ bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut)
if (d->mode == QSocks5SocketEnginePrivate::ConnectMode ||
d->mode == QSocks5SocketEnginePrivate::BindMode) {
while (!d->readNotificationActivated) {
- if (!d->data->controlSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
+ if (!d->data->controlSocket->waitForReadyRead(deadline.remainingTime())) {
if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
@@ -1743,7 +1737,7 @@ bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut)
#ifndef QT_NO_UDPSOCKET
} else {
while (!d->readNotificationActivated) {
- if (!d->udpData->udpSocket->waitForReadyRead(qt_subtract_from_timeout(msecs, stopWatch.elapsed()))) {
+ if (!d->udpData->udpSocket->waitForReadyRead(deadline.remainingTime())) {
setError(d->udpData->udpSocket->error(), d->udpData->udpSocket->errorString());
if (timedOut && d->udpData->udpSocket->error() == QAbstractSocket::SocketTimeoutError)
*timedOut = true;
@@ -1762,16 +1756,13 @@ bool QSocks5SocketEngine::waitForRead(int msecs, bool *timedOut)
}
-bool QSocks5SocketEngine::waitForWrite(int msecs, bool *timedOut)
+bool QSocks5SocketEngine::waitForWrite(QDeadlineTimer deadline, bool *timedOut)
{
Q_D(QSocks5SocketEngine);
- QSOCKS5_DEBUG << "waitForWrite" << msecs;
-
- QElapsedTimer stopWatch;
- stopWatch.start();
+ QSOCKS5_DEBUG << "waitForWrite" << deadline.remainingTimeAsDuration();
// are we connected yet?
- if (!d->waitForConnected(msecs, timedOut))
+ if (!d->waitForConnected(deadline, timedOut))
return false;
if (d->data->controlSocket->state() == QAbstractSocket::UnconnectedState)
return true;
@@ -1780,27 +1771,32 @@ bool QSocks5SocketEngine::waitForWrite(int msecs, bool *timedOut)
// flush any bytes we may still have buffered in the time that we have left
if (d->data->controlSocket->bytesToWrite())
- d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed()));
- while ((msecs == -1 || stopWatch.elapsed() < msecs)
- && d->data->controlSocket->state() == QAbstractSocket::ConnectedState
- && d->data->controlSocket->bytesToWrite() >= MaxWriteBufferSize)
- d->data->controlSocket->waitForBytesWritten(qt_subtract_from_timeout(msecs, stopWatch.elapsed()));
+ d->data->controlSocket->waitForBytesWritten(deadline.remainingTime());
+
+ auto shouldWriteBytes = [&]() {
+ return d->data->controlSocket->state() == QAbstractSocket::ConnectedState
+ && d->data->controlSocket->bytesToWrite() >= MaxWriteBufferSize;
+ };
+
+ qint64 remainingTime = deadline.remainingTime();
+ for (; remainingTime > 0 && shouldWriteBytes(); remainingTime = deadline.remainingTime())
+ d->data->controlSocket->waitForBytesWritten(remainingTime);
return d->data->controlSocket->bytesToWrite() < MaxWriteBufferSize;
}
bool QSocks5SocketEngine::waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
- int msecs, bool *timedOut)
+ QDeadlineTimer deadline, bool *timedOut)
{
Q_UNUSED(checkRead);
if (!checkWrite) {
- bool canRead = waitForRead(msecs, timedOut);
+ bool canRead = waitForRead(deadline, timedOut);
if (readyToRead)
*readyToRead = canRead;
return canRead;
}
- bool canWrite = waitForWrite(msecs, timedOut);
+ bool canWrite = waitForWrite(deadline, timedOut);
if (readyToWrite)
*readyToWrite = canWrite;
return canWrite;
diff --git a/src/network/socket/qsocks5socketengine_p.h b/src/network/socket/qsocks5socketengine_p.h
index c446f47184..3a169812df 100644
--- a/src/network/socket/qsocks5socketengine_p.h
+++ b/src/network/socket/qsocks5socketengine_p.h
@@ -16,8 +16,10 @@
//
#include <QtNetwork/private/qtnetworkglobal_p.h>
+
+#include <QtNetwork/qnetworkproxy.h>
+
#include "qabstractsocketengine_p.h"
-#include "qnetworkproxy.h"
QT_REQUIRE_CONFIG(socks5);
@@ -76,11 +78,14 @@ public:
int option(SocketOption option) const override;
bool setOption(SocketOption option, int value) override;
- bool waitForRead(int msecs = 30000, bool *timedOut = nullptr) override;
- bool waitForWrite(int msecs = 30000, bool *timedOut = nullptr) override;
+ bool waitForRead(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
+ bool waitForWrite(QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
bool waitForReadOrWrite(bool *readyToRead, bool *readyToWrite,
bool checkRead, bool checkWrite,
- int msecs = 30000, bool *timedOut = nullptr) override;
+ QDeadlineTimer deadline = QDeadlineTimer{DefaultTimeout},
+ bool *timedOut = nullptr) override;
bool isReadNotificationEnabled() const override;
void setReadNotificationEnabled(bool enable) override;
@@ -206,7 +211,7 @@ public:
void parseRequestMethodReply();
void parseNewConnection();
- bool waitForConnected(int msecs, bool *timedOut);
+ bool waitForConnected(QDeadlineTimer deadline, bool *timedOut);
void _q_controlSocketConnected();
void _q_controlSocketReadNotification();
diff --git a/src/network/socket/qtcpserver.cpp b/src/network/socket/qtcpserver.cpp
index cc748b9d7a..a0c0f00aaa 100644
--- a/src/network/socket/qtcpserver.cpp
+++ b/src/network/socket/qtcpserver.cpp
@@ -43,8 +43,8 @@
use waitForNewConnection(), which blocks until either a
connection is available or a timeout expires.
- \sa QTcpSocket, {Fortune Server Example}, {Threaded Fortune Server Example},
- {Loopback Example}, {Torrent Example}
+ \sa QTcpSocket, {Fortune Server}, {Threaded Fortune Server},
+ {Torrent Example}
*/
/*! \fn void QTcpServer::newConnection()
@@ -498,7 +498,7 @@ bool QTcpServer::waitForNewConnection(int msec, bool *timedOut)
if (d->state != QAbstractSocket::ListeningState)
return false;
- if (!d->socketEngine->waitForRead(msec, timedOut)) {
+ if (!d->socketEngine->waitForRead(QDeadlineTimer(msec), timedOut)) {
d->serverSocketError = d->socketEngine->error();
d->serverSocketErrorString = d->socketEngine->errorString();
return false;
diff --git a/src/network/socket/qtcpsocket.cpp b/src/network/socket/qtcpsocket.cpp
index 3d06197b1e..979382f26c 100644
--- a/src/network/socket/qtcpsocket.cpp
+++ b/src/network/socket/qtcpsocket.cpp
@@ -23,9 +23,9 @@
\note TCP sockets cannot be opened in QIODevice::Unbuffered mode.
\sa QTcpServer, QUdpSocket, QNetworkAccessManager,
- {Fortune Server Example}, {Fortune Client Example},
- {Threaded Fortune Server Example}, {Blocking Fortune Client Example},
- {Loopback Example}, {Torrent Example}
+ {Fortune Server}, {Fortune Client},
+ {Threaded Fortune Server}, {Blocking Fortune Client},
+ {Torrent Example}
*/
#include "qtcpsocket.h"
diff --git a/src/network/socket/qudpsocket.cpp b/src/network/socket/qudpsocket.cpp
index 2a7982057e..bfeea307b2 100644
--- a/src/network/socket/qudpsocket.cpp
+++ b/src/network/socket/qudpsocket.cpp
@@ -382,7 +382,7 @@ qint64 QUdpSocket::writeDatagram(const QNetworkDatagram &datagram)
if (state() == UnconnectedState)
bind();
- qint64 sent = d->socketEngine->writeDatagram(datagram.d->data,
+ qint64 sent = d->socketEngine->writeDatagram(datagram.d->data.constData(),
datagram.d->data.size(),
datagram.d->header);
d->cachedSocketDescriptor = d->socketEngine->socketDescriptor();
@@ -430,6 +430,7 @@ QNetworkDatagram QUdpSocket::receiveDatagram(qint64 maxSize)
qint64 readBytes = d->socketEngine->readDatagram(result.d->data.data(), maxSize, &result.d->header,
QAbstractSocketEngine::WantAll);
d->hasPendingData = false;
+ d->hasPendingDatagram = false;
d->socketEngine->setReadNotificationEnabled(true);
if (readBytes < 0) {
d->setErrorAndEmit(d->socketEngine->error(), d->socketEngine->errorString());
@@ -479,6 +480,7 @@ qint64 QUdpSocket::readDatagram(char *data, qint64 maxSize, QHostAddress *addres
}
d->hasPendingData = false;
+ d->hasPendingDatagram = false;
d->socketEngine->setReadNotificationEnabled(true);
if (readBytes < 0) {
if (readBytes == -2) {
diff --git a/src/network/ssl/qpassworddigestor.cpp b/src/network/ssl/qpassworddigestor.cpp
index a08702e7b6..94de14abd4 100644
--- a/src/network/ssl/qpassworddigestor.cpp
+++ b/src/network/ssl/qpassworddigestor.cpp
@@ -6,9 +6,20 @@
#include <QtCore/QDebug>
#include <QtCore/QMessageAuthenticationCode>
#include <QtCore/QtEndian>
+#include <QtCore/QList>
+
+#include "qtcore-config_p.h"
#include <limits>
+#if QT_CONFIG(opensslv30) && QT_CONFIG(openssl_linked)
+#define USING_OPENSSL30
+#include <openssl/core_names.h>
+#include <openssl/kdf.h>
+#include <openssl/params.h>
+#include <openssl/provider.h>
+#endif
+
QT_BEGIN_NAMESPACE
namespace QPasswordDigestor {
@@ -86,6 +97,85 @@ Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf1(QCryptographicHash::Algorithm algori
return key.left(dkLen);
}
+#ifdef USING_OPENSSL30
+// Copied from QCryptographicHashPrivate
+static constexpr const char * methodToName(QCryptographicHash::Algorithm method) noexcept
+{
+ switch (method) {
+#define CASE(Enum, Name) \
+ case QCryptographicHash:: Enum : \
+ return Name \
+ /*end*/
+ CASE(Sha1, "SHA1");
+ CASE(Md4, "MD4");
+ CASE(Md5, "MD5");
+ CASE(Sha224, "SHA224");
+ CASE(Sha256, "SHA256");
+ CASE(Sha384, "SHA384");
+ CASE(Sha512, "SHA512");
+ CASE(RealSha3_224, "SHA3-224");
+ CASE(RealSha3_256, "SHA3-256");
+ CASE(RealSha3_384, "SHA3-384");
+ CASE(RealSha3_512, "SHA3-512");
+ CASE(Keccak_224, "SHA3-224");
+ CASE(Keccak_256, "SHA3-256");
+ CASE(Keccak_384, "SHA3-384");
+ CASE(Keccak_512, "SHA3-512");
+ CASE(Blake2b_512, "BLAKE2B512");
+ CASE(Blake2s_256, "BLAKE2S256");
+#undef CASE
+ default: return nullptr;
+ }
+}
+
+static QByteArray opensslDeriveKeyPbkdf2(QCryptographicHash::Algorithm algorithm,
+ const QByteArray &data, const QByteArray &salt,
+ uint64_t iterations, quint64 dkLen)
+{
+ EVP_KDF *kdf = EVP_KDF_fetch(nullptr, "PBKDF2", nullptr);
+
+ if (!kdf)
+ return QByteArray();
+
+ auto cleanUpKdf = qScopeGuard([kdf] {
+ EVP_KDF_free(kdf);
+ });
+
+ EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf);
+
+ if (!ctx)
+ return QByteArray();
+
+ auto cleanUpCtx = qScopeGuard([ctx] {
+ EVP_KDF_CTX_free(ctx);
+ });
+
+ // Do not enable SP800-132 compliance check, otherwise we will require:
+ // - the iteration count is at least 1000
+ // - the salt length is at least 128 bits
+ // - the derived key length is at least 112 bits
+ // This would be a different behavior from the original implementation.
+ int checkDisabled = 1;
+ QList<OSSL_PARAM> params;
+ params.append(OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, const_cast<char*>(methodToName(algorithm)), 0));
+ params.append(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, const_cast<char*>(salt.data()), salt.size()));
+ params.append(OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_PASSWORD, const_cast<char*>(data.data()), data.size()));
+ params.append(OSSL_PARAM_construct_uint64(OSSL_KDF_PARAM_ITER, &iterations));
+ params.append(OSSL_PARAM_construct_int(OSSL_KDF_PARAM_PKCS5, &checkDisabled));
+ params.append(OSSL_PARAM_construct_end());
+
+ if (EVP_KDF_CTX_set_params(ctx, params.data()) <= 0)
+ return QByteArray();
+
+ QByteArray derived(dkLen, '\0');
+
+ if (!EVP_KDF_derive(ctx, reinterpret_cast<unsigned char*>(derived.data()), derived.size(), nullptr))
+ return QByteArray();
+
+ return derived;
+}
+#endif
+
/*!
\since 5.12
@@ -107,8 +197,6 @@ Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf2(QCryptographicHash::Algorithm algori
const QByteArray &data, const QByteArray &salt,
int iterations, quint64 dkLen)
{
- // https://tools.ietf.org/html/rfc8018#section-5.2
-
// The RFC recommends checking that 'dkLen' is not greater than '(2^32 - 1) * hLen'
int hashLen = QCryptographicHash::hashLength(algorithm);
const quint64 maxLen = quint64(std::numeric_limits<quint32>::max() - 1) * hashLen;
@@ -122,6 +210,12 @@ Q_NETWORK_EXPORT QByteArray deriveKeyPbkdf2(QCryptographicHash::Algorithm algori
if (iterations < 1 || dkLen < 1)
return QByteArray();
+#ifdef USING_OPENSSL30
+ if (methodToName(algorithm))
+ return opensslDeriveKeyPbkdf2(algorithm, data, salt, iterations, dkLen);
+#endif
+
+ // https://tools.ietf.org/html/rfc8018#section-5.2
QByteArray key;
quint32 currentIteration = 1;
QMessageAuthenticationCode hmac(algorithm, data);
diff --git a/src/network/ssl/qssl.cpp b/src/network/ssl/qssl.cpp
index 7525df8c87..dfd3745d3e 100644
--- a/src/network/ssl/qssl.cpp
+++ b/src/network/ssl/qssl.cpp
@@ -256,3 +256,5 @@ Q_LOGGING_CATEGORY(lcSsl, "qt.network.ssl");
*/
QT_END_NAMESPACE
+
+#include "moc_qssl.cpp"
diff --git a/src/network/ssl/qssl.h b/src/network/ssl/qssl.h
index 3d01168172..e52b8c6361 100644
--- a/src/network/ssl/qssl.h
+++ b/src/network/ssl/qssl.h
@@ -10,21 +10,26 @@
#endif
#include <QtNetwork/qtnetworkglobal.h>
+#include <QtCore/qobjectdefs.h>
#include <QtCore/QFlags>
QT_BEGIN_NAMESPACE
namespace QSsl {
+ Q_NAMESPACE_EXPORT(Q_NETWORK_EXPORT)
+
enum KeyType {
PrivateKey,
PublicKey
};
+ Q_ENUM_NS(KeyType)
enum EncodingFormat {
Pem,
Der
};
+ Q_ENUM_NS(EncodingFormat)
enum KeyAlgorithm {
Opaque,
@@ -33,12 +38,14 @@ namespace QSsl {
Ec,
Dh,
};
+ Q_ENUM_NS(KeyAlgorithm)
enum AlternativeNameEntryType {
EmailEntry,
DnsEntry,
IpAddressEntry
};
+ Q_ENUM_NS(AlternativeNameEntryType)
enum SslProtocol {
TlsV1_0 QT_DEPRECATED_VERSION_X_6_3("Use TlsV1_2OrLater instead."),
@@ -61,6 +68,7 @@ namespace QSsl {
UnknownProtocol = -1
};
+ Q_ENUM_NS(SslProtocol)
enum SslOption {
SslOptionDisableEmptyFragments = 0x01,
@@ -72,6 +80,7 @@ namespace QSsl {
SslOptionDisableSessionPersistence = 0x40,
SslOptionDisableServerCipherPreference = 0x80
};
+ Q_ENUM_NS(SslOption)
Q_DECLARE_FLAGS(SslOptions, SslOption)
enum class AlertLevel {
@@ -79,6 +88,7 @@ namespace QSsl {
Fatal,
Unknown
};
+ Q_ENUM_NS(AlertLevel)
enum class AlertType {
CloseNotify,
@@ -116,6 +126,7 @@ namespace QSsl {
NoApplicationProtocol = 120,
UnknownAlertMessage = 255
};
+ Q_ENUM_NS(AlertType)
enum class ImplementedClass
{
@@ -127,6 +138,7 @@ namespace QSsl {
Dtls,
DtlsCookie
};
+ Q_ENUM_NS(ImplementedClass)
enum class SupportedFeature
{
@@ -138,6 +150,7 @@ namespace QSsl {
SessionTicket,
Alerts
};
+ Q_ENUM_NS(SupportedFeature)
}
Q_DECLARE_OPERATORS_FOR_FLAGS(QSsl::SslOptions)
diff --git a/src/network/ssl/qsslcertificate.cpp b/src/network/ssl/qsslcertificate.cpp
index 0f851ec7c9..9878c603b6 100644
--- a/src/network/ssl/qsslcertificate.cpp
+++ b/src/network/ssl/qsslcertificate.cpp
@@ -110,7 +110,7 @@
#endif
#include <QtCore/qdir.h>
-#include <QtCore/qdiriterator.h>
+#include <QtCore/qdirlisting.h>
#include <QtCore/qfile.h>
QT_BEGIN_NAMESPACE
@@ -186,7 +186,7 @@ QSslCertificate::QSslCertificate(const QByteArray &data, QSsl::EncodingFormat fo
return;
}
- QList<QSslCertificate> certs = X509Reader(data, 1);
+ const QList<QSslCertificate> certs = X509Reader(data, 1);
if (!certs.isEmpty())
d = certs.first().d;
}
@@ -624,7 +624,7 @@ QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
QString sourcePath = QDir::fromNativeSeparators(path);
// Find the path without the filename
- QString pathPrefix = sourcePath.left(sourcePath.lastIndexOf(u'/'));
+ QStringView pathPrefix = QStringView(sourcePath).left(sourcePath.lastIndexOf(u'/'));
// Check if the path contains any special chars
int pos = -1;
@@ -647,7 +647,7 @@ QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
if (lastIndexOfSlash != -1)
pathPrefix = pathPrefix.left(lastIndexOfSlash);
else
- pathPrefix.clear();
+ pathPrefix = {};
} else {
// Check if the path is a file.
if (QFileInfo(sourcePath).isFile()) {
@@ -664,10 +664,12 @@ QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
// Special case - if the prefix ends up being nothing, use "." instead.
int startIndex = 0;
if (pathPrefix.isEmpty()) {
- pathPrefix = "."_L1;
+ pathPrefix = u".";
startIndex = 2;
}
+ const QString pathPrefixString = pathPrefix.toString();
+
// The path can be a file or directory.
QList<QSslCertificate> certs;
@@ -678,9 +680,12 @@ QList<QSslCertificate> QSslCertificate::fromPath(const QString &path,
QRegularExpression pattern(QRegularExpression::anchoredPattern(sourcePath));
#endif
- QDirIterator it(pathPrefix, QDir::Files, QDirIterator::FollowSymlinks | QDirIterator::Subdirectories);
- while (it.hasNext()) {
- QString filePath = startIndex == 0 ? it.next() : it.next().mid(startIndex);
+ using F = QDirListing::IteratorFlag;
+ constexpr auto iterFlags = F::FollowSymlinks | F::Recursive;
+ for (const auto &dirEntry : QDirListing(pathPrefixString, QDir::Files, iterFlags)) {
+ QString filePath = dirEntry.filePath();
+ if (startIndex > 0)
+ filePath.remove(0, startIndex);
#if QT_CONFIG(regularexpression)
if (!pattern.match(filePath).hasMatch())
@@ -878,7 +883,7 @@ static const char *const certificate_blacklist[] = {
bool QSslCertificatePrivate::isBlacklisted(const QSslCertificate &certificate)
{
for (int a = 0; certificate_blacklist[a] != nullptr; a++) {
- QString blacklistedCommonName = QString::fromUtf8(certificate_blacklist[(a+1)]);
+ auto blacklistedCommonName = QAnyStringView(QUtf8StringView(certificate_blacklist[(a+1)]));
if (certificate.serialNumber() == certificate_blacklist[a++] &&
(certificate.subjectInfo(QSslCertificate::CommonName).contains(blacklistedCommonName) ||
certificate.issuerInfo(QSslCertificate::CommonName).contains(blacklistedCommonName)))
@@ -889,19 +894,18 @@ bool QSslCertificatePrivate::isBlacklisted(const QSslCertificate &certificate)
QByteArray QSslCertificatePrivate::subjectInfoToString(QSslCertificate::SubjectInfo info)
{
- QByteArray str;
switch (info) {
- case QSslCertificate::Organization: str = QByteArray("O"); break;
- case QSslCertificate::CommonName: str = QByteArray("CN"); break;
- case QSslCertificate::LocalityName: str = QByteArray("L"); break;
- case QSslCertificate::OrganizationalUnitName: str = QByteArray("OU"); break;
- case QSslCertificate::CountryName: str = QByteArray("C"); break;
- case QSslCertificate::StateOrProvinceName: str = QByteArray("ST"); break;
- case QSslCertificate::DistinguishedNameQualifier: str = QByteArray("dnQualifier"); break;
- case QSslCertificate::SerialNumber: str = QByteArray("serialNumber"); break;
- case QSslCertificate::EmailAddress: str = QByteArray("emailAddress"); break;
+ case QSslCertificate::Organization: return "O"_ba;
+ case QSslCertificate::CommonName: return "CN"_ba;
+ case QSslCertificate::LocalityName: return"L"_ba;
+ case QSslCertificate::OrganizationalUnitName: return "OU"_ba;
+ case QSslCertificate::CountryName: return "C"_ba;
+ case QSslCertificate::StateOrProvinceName: return "ST"_ba;
+ case QSslCertificate::DistinguishedNameQualifier: return "dnQualifier"_ba;
+ case QSslCertificate::SerialNumber: return "serialNumber"_ba;
+ case QSslCertificate::EmailAddress: return "emailAddress"_ba;
}
- return str;
+ return QByteArray();
}
/*!
@@ -918,13 +922,13 @@ QString QSslCertificate::issuerDisplayName() const
QStringList names;
names = issuerInfo(QSslCertificate::CommonName);
if (!names.isEmpty())
- return names.first();
+ return names.constFirst();
names = issuerInfo(QSslCertificate::Organization);
if (!names.isEmpty())
- return names.first();
+ return names.constFirst();
names = issuerInfo(QSslCertificate::OrganizationalUnitName);
if (!names.isEmpty())
- return names.first();
+ return names.constFirst();
return QString();
}
@@ -943,13 +947,13 @@ QString QSslCertificate::subjectDisplayName() const
QStringList names;
names = subjectInfo(QSslCertificate::CommonName);
if (!names.isEmpty())
- return names.first();
+ return names.constFirst();
names = subjectInfo(QSslCertificate::Organization);
if (!names.isEmpty())
- return names.first();
+ return names.constFirst();
names = subjectInfo(QSslCertificate::OrganizationalUnitName);
if (!names.isEmpty())
- return names.first();
+ return names.constFirst();
return QString();
}
@@ -974,15 +978,15 @@ QDebug operator<<(QDebug debug, const QSslCertificate &certificate)
QDebugStateSaver saver(debug);
debug.resetFormat().nospace();
debug << "QSslCertificate("
- << certificate.version()
- << ", " << certificate.serialNumber()
- << ", " << certificate.digest().toBase64()
- << ", " << certificate.issuerDisplayName()
- << ", " << certificate.subjectDisplayName()
- << ", " << certificate.subjectAlternativeNames()
+ << "Version=" << certificate.version()
+ << ", SerialNumber=" << certificate.serialNumber()
+ << ", Digest=" << certificate.digest().toBase64()
+ << ", Issuer=" << certificate.issuerDisplayName()
+ << ", Subject=" << certificate.subjectDisplayName()
+ << ", AlternativeSubjectNames=" << certificate.subjectAlternativeNames()
#if QT_CONFIG(datestring)
- << ", " << certificate.effectiveDate()
- << ", " << certificate.expiryDate()
+ << ", EffectiveDate=" << certificate.effectiveDate()
+ << ", ExpiryDate=" << certificate.expiryDate()
#endif
<< ')';
return debug;
diff --git a/src/network/ssl/qsslcertificate.h b/src/network/ssl/qsslcertificate.h
index 5807c8d2c7..cdf11b28b0 100644
--- a/src/network/ssl/qsslcertificate.h
+++ b/src/network/ssl/qsslcertificate.h
@@ -14,8 +14,8 @@
#include <QtCore/qbytearray.h>
#include <QtCore/qcryptographichash.h>
#include <QtCore/qdatetime.h>
-#include <QtCore/qsharedpointer.h>
#include <QtCore/qmap.h>
+#include <QtCore/qshareddata.h>
#include <QtNetwork/qssl.h>
QT_BEGIN_NAMESPACE
diff --git a/src/network/ssl/qsslcertificate_p.h b/src/network/ssl/qsslcertificate_p.h
index 02cf002460..ca59abae82 100644
--- a/src/network/ssl/qsslcertificate_p.h
+++ b/src/network/ssl/qsslcertificate_p.h
@@ -35,8 +35,8 @@ public:
~QSslCertificatePrivate();
QList<QSslCertificateExtension> extensions() const;
- Q_NETWORK_PRIVATE_EXPORT static bool isBlacklisted(const QSslCertificate &certificate);
- Q_NETWORK_PRIVATE_EXPORT static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info);
+ Q_NETWORK_EXPORT static bool isBlacklisted(const QSslCertificate &certificate);
+ Q_NETWORK_EXPORT static QByteArray subjectInfoToString(QSslCertificate::SubjectInfo info);
QAtomicInt ref;
std::unique_ptr<QTlsPrivate::X509Certificate> backend;
diff --git a/src/network/ssl/qsslconfiguration.cpp b/src/network/ssl/qsslconfiguration.cpp
index 63eaa6d092..fd308d7037 100644
--- a/src/network/ssl/qsslconfiguration.cpp
+++ b/src/network/ssl/qsslconfiguration.cpp
@@ -105,6 +105,12 @@ const char QSslConfiguration::NextProtocolHttp1_1[] = "http/1.1";
*/
/*!
+ \variable QSslConfiguration::ALPNProtocolHTTP2
+ \brief The value used for negotiating HTTP 2 during the Application-Layer
+ Protocol Negotiation.
+*/
+
+/*!
Constructs an empty SSL configuration. This configuration contains
no valid settings and the state will be empty. isNull() will
return true after this constructor is called.
@@ -550,8 +556,6 @@ void QSslConfiguration::setPrivateKey(const QSslKey &key)
ciphers. You can revert to using the entire set by calling
setCiphers() with the list returned by supportedCiphers().
- \note This is not currently supported in the Schannel backend.
-
\sa setCiphers(), supportedCiphers()
*/
QList<QSslCipher> QSslConfiguration::ciphers() const
@@ -567,8 +571,6 @@ QList<QSslCipher> QSslConfiguration::ciphers() const
Restricting the cipher suite must be done before the handshake
phase, where the session cipher is chosen.
- \note This is not currently supported in the Schannel backend.
-
\sa ciphers(), supportedCiphers()
*/
void QSslConfiguration::setCiphers(const QList<QSslCipher> &ciphers)
@@ -581,16 +583,14 @@ void QSslConfiguration::setCiphers(const QList<QSslCipher> &ciphers)
Sets the cryptographic cipher suite for this configuration to \a ciphers,
which is a colon-separated list of cipher suite names. The ciphers are listed
- in order of preference, starting with the most preferred cipher. For example:
-
- \snippet code/src_network_ssl_qsslconfiguration.cpp 1
-
+ in order of preference, starting with the most preferred cipher.
Each cipher name in \a ciphers must be the name of a cipher in the
list returned by supportedCiphers(). Restricting the cipher suite
must be done before the handshake phase, where the session cipher
is chosen.
- \note This is not currently supported in the Schannel backend.
+ \note With the Schannel backend the order of the ciphers is ignored and Schannel
+ picks the most secure one during the handshake.
\sa ciphers()
*/
@@ -922,7 +922,11 @@ void QSslConfiguration::setPreSharedKeyIdentityHint(const QByteArray &hint)
Retrieves the current set of Diffie-Hellman parameters.
If no Diffie-Hellman parameters have been set, the QSslConfiguration object
- defaults to using the 1024-bit MODP group from RFC 2409.
+ defaults to using the 2048-bit MODP group from RFC 3526.
+
+ \note The default parameters may change in future Qt versions.
+ Please check the documentation of the \e{exact Qt version} that you
+ are using in order to know what defaults that version uses.
*/
QSslDiffieHellmanParameters QSslConfiguration::diffieHellmanParameters() const
{
@@ -936,7 +940,14 @@ QSslDiffieHellmanParameters QSslConfiguration::diffieHellmanParameters() const
a server to \a dhparams.
If no Diffie-Hellman parameters have been set, the QSslConfiguration object
- defaults to using the 1024-bit MODP group from RFC 2409.
+ defaults to using the 2048-bit MODP group from RFC 3526.
+
+ Since 6.7 you can provide an empty Diffie-Hellman parameter to use auto selection
+ (see SSL_CTX_set_dh_auto of openssl) if the tls backend supports it.
+
+ \note The default parameters may change in future Qt versions.
+ Please check the documentation of the \e{exact Qt version} that you
+ are using in order to know what defaults that version uses.
*/
void QSslConfiguration::setDiffieHellmanParameters(const QSslDiffieHellmanParameters &dhparams)
{
diff --git a/src/network/ssl/qssldiffiehellmanparameters.cpp b/src/network/ssl/qssldiffiehellmanparameters.cpp
index b2c112bbf9..7da14f3536 100644
--- a/src/network/ssl/qssldiffiehellmanparameters.cpp
+++ b/src/network/ssl/qssldiffiehellmanparameters.cpp
@@ -33,17 +33,18 @@
QT_BEGIN_NAMESPACE
-// The 1024-bit MODP group from RFC 2459 (Second Oakley Group)
+// The 2048-bit MODP group from RFC 3526
Q_AUTOTEST_EXPORT const char *qssl_dhparams_default_base64 =
- "MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR"
- "Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL"
- "/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC";
+ "MIIBCAKCAQEA///////////JD9qiIWjCNMTGYouA3BzRKQJOCIpnzHQCC76mOxObIlFKCHmO"
+ "NATd75UZs806QxswKwpt8l8UN0/hNW1tUcJF5IW1dmJefsb0TELppjftawv/XLb0Brft7jhr"
+ "+1qJn6WunyQRfEsf5kkoZlHs5Fs9wgB8uKFjvwWY2kg2HFXTmmkWP6j9JM9fg2VdI9yjrZYc"
+ "YvNWIIVSu57VKQdwlpZtZww1Tkq8mATxdGwIyhghfDKQXkYuNs474553LBgOhgObJ4Oi7Aei"
+ "j7XFXfBvTFLJ3ivL9pVYFxg5lUl86pVq5RXSJhiY+gUQFXKOWoqsqmj//////////wIBAg==";
/*!
Returns the default QSslDiffieHellmanParameters used by QSslSocket.
- This is currently the 1024-bit MODP group from RFC 2459, also
- known as the Second Oakley Group.
+ This is currently the 2048-bit MODP group from RFC 3526.
*/
QSslDiffieHellmanParameters QSslDiffieHellmanParameters::defaultParameters()
{
diff --git a/src/network/ssl/qsslkey.h b/src/network/ssl/qsslkey.h
index db14c89c98..decfc4b5a1 100644
--- a/src/network/ssl/qsslkey.h
+++ b/src/network/ssl/qsslkey.h
@@ -8,7 +8,7 @@
#include <QtNetwork/qtnetworkglobal.h>
#include <QtCore/qnamespace.h>
#include <QtCore/qbytearray.h>
-#include <QtCore/qsharedpointer.h>
+#include <QtCore/qshareddata.h>
#include <QtNetwork/qssl.h>
QT_BEGIN_NAMESPACE
diff --git a/src/network/ssl/qsslpresharedkeyauthenticator.h b/src/network/ssl/qsslpresharedkeyauthenticator.h
index 98cfad19ed..a3912406d3 100644
--- a/src/network/ssl/qsslpresharedkeyauthenticator.h
+++ b/src/network/ssl/qsslpresharedkeyauthenticator.h
@@ -16,6 +16,7 @@ QT_BEGIN_NAMESPACE
class QSslPreSharedKeyAuthenticatorPrivate;
class QSslPreSharedKeyAuthenticator
{
+ Q_GADGET_EXPORT(Q_NETWORK_EXPORT)
public:
Q_NETWORK_EXPORT QSslPreSharedKeyAuthenticator();
Q_NETWORK_EXPORT ~QSslPreSharedKeyAuthenticator();
diff --git a/src/network/ssl/qsslserver.cpp b/src/network/ssl/qsslserver.cpp
index 967c478904..40a6a6f526 100644
--- a/src/network/ssl/qsslserver.cpp
+++ b/src/network/ssl/qsslserver.cpp
@@ -341,7 +341,7 @@ void QSslServerPrivate::initializeHandshakeProcess(QSslSocket *socket)
});
auto it = socketData.emplace(quintptr(socket), readyRead, destroyed, std::make_shared<QTimer>());
it->timeoutTimer->setSingleShot(true);
- it->timeoutTimer->callOnTimeout([this, socket]() { handleHandshakeTimedOut(socket); });
+ it->timeoutTimer->callOnTimeout(q, [this, socket]() { handleHandshakeTimedOut(socket); });
it->timeoutTimer->setInterval(handshakeTimeout);
it->timeoutTimer->start();
}
@@ -408,3 +408,5 @@ void QSslServerPrivate::handleHandshakeTimedOut(QSslSocket *socket)
}
QT_END_NAMESPACE
+
+#include "moc_qsslserver.cpp"
diff --git a/src/network/ssl/qsslsocket.cpp b/src/network/ssl/qsslsocket.cpp
index 4eefe43929..395394d432 100644
--- a/src/network/ssl/qsslsocket.cpp
+++ b/src/network/ssl/qsslsocket.cpp
@@ -97,8 +97,7 @@
\list
\li The socket's cryptographic cipher suite can be customized before
- the handshake phase with QSslConfiguration::setCiphers()
- and QSslConfiguration::setDefaultCiphers().
+ the handshake phase with QSslConfiguration::setCiphers().
\li The socket's local certificate and private key can be customized
before the handshake phase with setLocalCertificate() and
setPrivateKey().
@@ -365,6 +364,12 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
+#ifdef Q_OS_VXWORKS
+constexpr auto isVxworks = true;
+#else
+constexpr auto isVxworks = false;
+#endif
+
class QSslSocketGlobalData
{
public:
@@ -1539,7 +1544,12 @@ QList<QString> QSslSocket::availableBackends()
from the list of available backends.
\note When selecting a default backend implicitly, QSslSocket prefers
- the OpenSSL backend if available.
+ the OpenSSL backend if available. If it's not available, the Schannel backend
+ is implicitly selected on Windows, and Secure Transport on Darwin platforms.
+ Failing these, if a custom TLS backend is found, it is used.
+ If no other backend is found, the "certificate only" backend is selected.
+ For more information about TLS plugins, please see
+ \l {Enabling and Disabling SSL Support when Building Qt from Source}.
\sa setActiveBackend(), availableBackends()
*/
@@ -1973,6 +1983,10 @@ QSslSocketPrivate::QSslSocketPrivate()
, flushTriggered(false)
{
QSslConfigurationPrivate::deepCopyDefaultConfiguration(&configuration);
+ // If the global configuration doesn't allow root certificates to be loaded
+ // on demand then we have to disable it for this socket as well.
+ if (!configuration.allowRootCertOnDemandLoading)
+ allowRootCertOnDemandLoading = false;
const auto *tlsBackend = tlsBackendInUse();
if (!tlsBackend) {
@@ -2281,6 +2295,7 @@ void QSslConfigurationPrivate::deepCopyDefaultConfiguration(QSslConfigurationPri
ptr->sessionProtocol = global->sessionProtocol;
ptr->ciphers = global->ciphers;
ptr->caCertificates = global->caCertificates;
+ ptr->allowRootCertOnDemandLoading = global->allowRootCertOnDemandLoading;
ptr->protocol = global->protocol;
ptr->peerVerifyMode = global->peerVerifyMode;
ptr->peerVerifyDepth = global->peerVerifyDepth;
@@ -2955,7 +2970,13 @@ QList<QByteArray> QSslSocketPrivate::unixRootCertDirectories()
ba("/opt/openssl/certs/"), // HP-UX
ba("/etc/ssl/"), // OpenBSD
};
- return QList<QByteArray>::fromReadOnlyData(dirs);
+ QList<QByteArray> result = QList<QByteArray>::fromReadOnlyData(dirs);
+ if constexpr (isVxworks) {
+ static QByteArray vxworksCertsDir = qgetenv("VXWORKS_CERTS_DIR");
+ if (!vxworksCertsDir.isEmpty())
+ result.push_back(vxworksCertsDir);
+ }
+ return result;
}
/*!
@@ -3086,10 +3107,11 @@ QTlsBackend *QSslSocketPrivate::tlsBackendInUse()
tlsBackend = QTlsBackend::findBackend(activeBackendName);
if (tlsBackend) {
- QObject::connect(tlsBackend, &QObject::destroyed, [] {
+ QObject::connect(tlsBackend, &QObject::destroyed, tlsBackend, [] {
const QMutexLocker locker(&backendMutex);
tlsBackend = nullptr;
- });
+ },
+ Qt::DirectConnection);
}
return tlsBackend;
}
diff --git a/src/network/ssl/qsslsocket.h b/src/network/ssl/qsslsocket.h
index 3174a68953..3ed1bc45cc 100644
--- a/src/network/ssl/qsslsocket.h
+++ b/src/network/ssl/qsslsocket.h
@@ -35,6 +35,7 @@ public:
SslClientMode,
SslServerMode
};
+ Q_ENUM(SslMode)
enum PeerVerifyMode {
VerifyNone,
@@ -42,6 +43,7 @@ public:
VerifyPeer,
AutoVerifyPeer
};
+ Q_ENUM(PeerVerifyMode)
explicit QSslSocket(QObject *parent = nullptr);
~QSslSocket();
diff --git a/src/network/ssl/qtlsbackend.cpp b/src/network/ssl/qtlsbackend.cpp
index d1157e5838..761ab33fbe 100644
--- a/src/network/ssl/qtlsbackend.cpp
+++ b/src/network/ssl/qtlsbackend.cpp
@@ -27,7 +27,7 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
-Q_APPLICATION_STATIC(QFactoryLoader, loader, QTlsBackend_iid,
+Q_APPLICATION_STATIC(QFactoryLoader, qtlsbLoader, QTlsBackend_iid,
QStringLiteral("/tls"))
namespace {
@@ -54,7 +54,7 @@ public:
bool tryPopulateCollection()
{
- if (!loader())
+ if (!qtlsbLoader())
return false;
Q_CONSTINIT static QBasicMutex mutex;
@@ -63,10 +63,10 @@ public:
return true;
#if QT_CONFIG(library)
- loader->update();
+ qtlsbLoader->update();
#endif
int index = 0;
- while (loader->instance(index))
+ while (qtlsbLoader->instance(index))
++index;
return true;
@@ -889,20 +889,28 @@ QSslCipher QTlsBackend::createCiphersuite(const QString &suiteName, QSsl::SslPro
/*!
\internal
- Auxiliary function. Creates a new QSslCipher from \a name (which is an implementation-specific
- string), \a protocol and \a protocolString, e.g.:
+ Auxiliary function. Creates a new QSslCipher from \a name, \a keyExchangeMethod, \a encryptionMethod,
+ \a authenticationMethod, \a bits, \a protocol version and \a protocolString.
+ For example:
\code
- createCipher(QStringLiteral("schannel"), QSsl::TlsV1_2, "TLSv1.2"_L1);
+ createCiphersuite("ECDHE-RSA-AES256-GCM-SHA256"_L1, "ECDH"_L1, "AES"_L1, "RSA"_L1, 256,
+ QSsl::TlsV1_2, "TLSv1.2"_L1);
\endcode
*/
-QSslCipher QTlsBackend::createCipher(const QString &name, QSsl::SslProtocol protocol,
- const QString &protocolString)
+QSslCipher QTlsBackend::createCiphersuite(const QString &name, const QString &keyExchangeMethod,
+ const QString &encryptionMethod,
+ const QString &authenticationMethod,
+ int bits, QSsl::SslProtocol protocol,
+ const QString &protocolString)
{
- // Note the name 'createCipher' (not 'ciphersuite'): we don't provide
- // information about Kx, Au, bits/supported etc.
QSslCipher cipher;
cipher.d->isNull = false;
cipher.d->name = name;
+ cipher.d->bits = bits;
+ cipher.d->supportedBits = bits;
+ cipher.d->keyExchangeMethod = keyExchangeMethod;
+ cipher.d->encryptionMethod = encryptionMethod;
+ cipher.d->authenticationMethod = authenticationMethod;
cipher.d->protocol = protocol;
cipher.d->protocolString = protocolString;
return cipher;
diff --git a/src/network/ssl/qtlsbackend_p.h b/src/network/ssl/qtlsbackend_p.h
index c69e641854..090531014b 100644
--- a/src/network/ssl/qtlsbackend_p.h
+++ b/src/network/ssl/qtlsbackend_p.h
@@ -31,7 +31,6 @@
#include <QtNetwork/qssl.h>
#include <QtCore/qloggingcategory.h>
-#include <QtCore/qsharedpointer.h>
#include <QtCore/qnamespace.h>
#include <QtCore/qobject.h>
#include <QtCore/qglobal.h>
@@ -58,7 +57,7 @@ class QSslKey;
namespace QTlsPrivate {
-class Q_NETWORK_PRIVATE_EXPORT TlsKey {
+class Q_NETWORK_EXPORT TlsKey {
public:
virtual ~TlsKey();
@@ -95,7 +94,7 @@ public:
QByteArray pemFooter() const;
};
-class Q_NETWORK_PRIVATE_EXPORT X509Certificate
+class Q_NETWORK_EXPORT X509Certificate
{
public:
virtual ~X509Certificate();
@@ -152,7 +151,7 @@ using X509Pkcs12ReaderPtr = bool (*)(QIODevice *device, QSslKey *key, QSslCertif
#if QT_CONFIG(ssl)
// TLS over TCP. Handshake, encryption/decryption.
-class Q_NETWORK_PRIVATE_EXPORT TlsCryptograph : public QObject
+class Q_NETWORK_EXPORT TlsCryptograph : public QObject
{
public:
virtual ~TlsCryptograph();
@@ -188,7 +187,7 @@ class TlsCryptograph;
#if QT_CONFIG(dtls)
-class Q_NETWORK_PRIVATE_EXPORT DtlsBase
+class Q_NETWORK_EXPORT DtlsBase
{
public:
virtual ~DtlsBase();
@@ -218,7 +217,7 @@ public:
};
// TLS over UDP. Handshake, encryption/decryption.
-class Q_NETWORK_PRIVATE_EXPORT DtlsCryptograph : virtual public DtlsBase
+class Q_NETWORK_EXPORT DtlsCryptograph : virtual public DtlsBase
{
public:
@@ -347,8 +346,11 @@ public:
static QSslCipher createCiphersuite(const QString &description, int bits, int supportedBits);
static QSslCipher createCiphersuite(const QString &suiteName, QSsl::SslProtocol protocol,
const QString &protocolString);
- static QSslCipher createCipher(const QString &name, QSsl::SslProtocol protocol,
- const QString &protocolString);
+ static QSslCipher createCiphersuite(const QString &name, const QString &keyExchangeMethod,
+ const QString &encryptionMethod,
+ const QString &authenticationMethod,
+ int bits, QSsl::SslProtocol protocol,
+ const QString &protocolString);
// Those statics are implemented using QSslSocketPrivate (which is not exported,
// unlike QTlsBackend).