summaryrefslogtreecommitdiffstats
path: root/tests/auto/network/access/http2
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/network/access/http2')
-rw-r--r--tests/auto/network/access/http2/BLACKLIST2
-rw-r--r--tests/auto/network/access/http2/CMakeLists.txt20
-rw-r--r--tests/auto/network/access/http2/certs/fluke.cert105
-rw-r--r--tests/auto/network/access/http2/certs/fluke.key67
-rw-r--r--tests/auto/network/access/http2/http2.pro9
-rw-r--r--tests/auto/network/access/http2/http2srv.cpp149
-rw-r--r--tests/auto/network/access/http2/http2srv.h52
-rw-r--r--tests/auto/network/access/http2/tst_http2.cpp788
8 files changed, 952 insertions, 240 deletions
diff --git a/tests/auto/network/access/http2/BLACKLIST b/tests/auto/network/access/http2/BLACKLIST
new file mode 100644
index 0000000000..3de8d6d448
--- /dev/null
+++ b/tests/auto/network/access/http2/BLACKLIST
@@ -0,0 +1,2 @@
+[duplicateRequestsWithAborts]
+qnx ci # QTBUG-119616
diff --git a/tests/auto/network/access/http2/CMakeLists.txt b/tests/auto/network/access/http2/CMakeLists.txt
index ed5f7c8275..7ea559940b 100644
--- a/tests/auto/network/access/http2/CMakeLists.txt
+++ b/tests/auto/network/access/http2/CMakeLists.txt
@@ -1,20 +1,24 @@
-# Generated from http2.pro.
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## tst_http2 Test:
#####################################################################
-qt_add_test(tst_http2
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(tst_http2 LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_test(tst_http2
SOURCES
- ../../../../shared/emulationdetector.h
http2srv.cpp http2srv.h
tst_http2.cpp
- DEFINES
- SRCDIR=\\\"${CMAKE_CURRENT_SOURCE_DIR}/\\\"
- INCLUDE_DIRECTORIES
- ../../../../shared
- PUBLIC_LIBRARIES
+ LIBRARIES
Qt::CorePrivate
Qt::Network
Qt::NetworkPrivate
+ Qt::TestPrivate
+ BUNDLE_ANDROID_OPENSSL_LIBS
)
diff --git a/tests/auto/network/access/http2/certs/fluke.cert b/tests/auto/network/access/http2/certs/fluke.cert
index ace4e4f0eb..4cc4d9a5ea 100644
--- a/tests/auto/network/access/http2/certs/fluke.cert
+++ b/tests/auto/network/access/http2/certs/fluke.cert
@@ -1,75 +1,34 @@
-Certificate:
- Data:
- Version: 3 (0x2)
- Serial Number: 0 (0x0)
- Signature Algorithm: sha1WithRSAEncryption
- Issuer: C=NO, ST=Oslo, L=Nydalen, O=Nokia Corporation and/or its subsidiary(-ies), OU=Development, CN=fluke.troll.no/emailAddress=ahanssen@trolltech.com
- Validity
- Not Before: Dec 4 01:10:32 2007 GMT
- Not After : Apr 21 01:10:32 2035 GMT
- Subject: C=NO, ST=Oslo, O=Nokia Corporation and/or its subsidiary(-ies), OU=Development, CN=fluke.troll.no
- Subject Public Key Info:
- Public Key Algorithm: rsaEncryption
- RSA Public Key: (1024 bit)
- Modulus (1024 bit):
- 00:a7:c8:a0:4a:c4:19:05:1b:66:ba:32:e2:d2:f1:
- 1c:6f:17:82:e4:39:2e:01:51:90:db:04:34:32:11:
- 21:c2:0d:6f:59:d8:53:90:54:3f:83:8f:a9:d3:b3:
- d5:ee:1a:9b:80:ae:c3:25:c9:5e:a5:af:4b:60:05:
- aa:a0:d1:91:01:1f:ca:04:83:e3:58:1c:99:32:45:
- 84:70:72:58:03:98:4a:63:8b:41:f5:08:49:d2:91:
- 02:60:6b:e4:64:fe:dd:a0:aa:74:08:e9:34:4c:91:
- 5f:12:3d:37:4d:54:2c:ad:7f:5b:98:60:36:02:8c:
- 3b:f6:45:f3:27:6a:9b:94:9d
- Exponent: 65537 (0x10001)
- X509v3 extensions:
- X509v3 Basic Constraints:
- CA:FALSE
- Netscape Comment:
- OpenSSL Generated Certificate
- X509v3 Subject Key Identifier:
- 21:85:04:3D:23:01:66:E5:F7:9F:1A:84:24:8A:AF:0A:79:F4:E5:AC
- X509v3 Authority Key Identifier:
- DirName:/C=NO/ST=Oslo/L=Nydalen/O=Nokia Corporation and/or its subsidiary(-ies)/OU=Development/CN=fluke.troll.no/emailAddress=ahanssen@trolltech.com
- serial:8E:A8:B4:E8:91:B7:54:2E
-
- Signature Algorithm: sha1WithRSAEncryption
- 6d:57:5f:d1:05:43:f0:62:05:ec:2a:71:a5:dc:19:08:f2:c4:
- a6:bd:bb:25:d9:ca:89:01:0e:e4:cf:1f:c1:8c:c8:24:18:35:
- 53:59:7b:c0:43:b4:32:e6:98:b2:a6:ef:15:05:0b:48:5f:e1:
- a0:0c:97:a9:a1:77:d8:35:18:30:bc:a9:8f:d3:b7:54:c7:f1:
- a9:9e:5d:e6:19:bf:f6:3c:5b:2b:d8:e4:3e:62:18:88:8b:d3:
- 24:e1:40:9b:0c:e6:29:16:62:ab:ea:05:24:70:36:aa:55:93:
- ef:02:81:1b:23:10:a2:04:eb:56:95:75:fc:f8:94:b1:5d:42:
- c5:3f:36:44:85:5d:3a:2e:90:46:8a:a2:b9:6f:87:ae:0c:15:
- 40:19:31:90:fc:3b:25:bb:ae:f1:66:13:0d:85:90:d9:49:34:
- 8f:f2:5d:f9:7a:db:4d:5d:27:f6:76:9d:35:8c:06:a6:4c:a3:
- b1:b2:b6:6f:1d:d7:a3:00:fd:72:eb:9e:ea:44:a1:af:21:34:
- 7d:c7:42:e2:49:91:19:8b:c0:ad:ba:82:80:a8:71:70:f4:35:
- 31:91:63:84:20:95:e9:60:af:64:8b:cc:ff:3d:8a:76:74:3d:
- c8:55:6d:e4:8e:c3:2b:1c:e8:42:18:ae:9f:e6:6b:9c:34:06:
- ec:6a:f2:c3
-----BEGIN CERTIFICATE-----
-MIIEEzCCAvugAwIBAgIBADANBgkqhkiG9w0BAQUFADCBnDELMAkGA1UEBhMCTk8x
-DTALBgNVBAgTBE9zbG8xEDAOBgNVBAcTB055ZGFsZW4xFjAUBgNVBAoTDVRyb2xs
-dGVjaCBBU0ExFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5mbHVrZS50
-cm9sbC5ubzElMCMGCSqGSIb3DQEJARYWYWhhbnNzZW5AdHJvbGx0ZWNoLmNvbTAe
-Fw0wNzEyMDQwMTEwMzJaFw0zNTA0MjEwMTEwMzJaMGMxCzAJBgNVBAYTAk5PMQ0w
-CwYDVQQIEwRPc2xvMRYwFAYDVQQKEw1Ucm9sbHRlY2ggQVNBMRQwEgYDVQQLEwtE
-ZXZlbG9wbWVudDEXMBUGA1UEAxMOZmx1a2UudHJvbGwubm8wgZ8wDQYJKoZIhvcN
-AQEBBQADgY0AMIGJAoGBAKfIoErEGQUbZroy4tLxHG8XguQ5LgFRkNsENDIRIcIN
-b1nYU5BUP4OPqdOz1e4am4CuwyXJXqWvS2AFqqDRkQEfygSD41gcmTJFhHByWAOY
-SmOLQfUISdKRAmBr5GT+3aCqdAjpNEyRXxI9N01ULK1/W5hgNgKMO/ZF8ydqm5Sd
-AgMBAAGjggEaMIIBFjAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NM
-IEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUIYUEPSMBZuX3nxqEJIqv
-Cnn05awwgbsGA1UdIwSBszCBsKGBoqSBnzCBnDELMAkGA1UEBhMCTk8xDTALBgNV
-BAgTBE9zbG8xEDAOBgNVBAcTB055ZGFsZW4xFjAUBgNVBAoTDVRyb2xsdGVjaCBB
-U0ExFDASBgNVBAsTC0RldmVsb3BtZW50MRcwFQYDVQQDEw5mbHVrZS50cm9sbC5u
-bzElMCMGCSqGSIb3DQEJARYWYWhhbnNzZW5AdHJvbGx0ZWNoLmNvbYIJAI6otOiR
-t1QuMA0GCSqGSIb3DQEBBQUAA4IBAQBtV1/RBUPwYgXsKnGl3BkI8sSmvbsl2cqJ
-AQ7kzx/BjMgkGDVTWXvAQ7Qy5piypu8VBQtIX+GgDJepoXfYNRgwvKmP07dUx/Gp
-nl3mGb/2PFsr2OQ+YhiIi9Mk4UCbDOYpFmKr6gUkcDaqVZPvAoEbIxCiBOtWlXX8
-+JSxXULFPzZEhV06LpBGiqK5b4euDBVAGTGQ/Dslu67xZhMNhZDZSTSP8l35ettN
-XSf2dp01jAamTKOxsrZvHdejAP1y657qRKGvITR9x0LiSZEZi8CtuoKAqHFw9DUx
-kWOEIJXpYK9ki8z/PYp2dD3IVW3kjsMrHOhCGK6f5mucNAbsavLD
+MIIF6zCCA9OgAwIBAgIUfo9amJtJGWqWE6f+SkAO85zkGr4wDQYJKoZIhvcNAQEL
+BQAwgYMxCzAJBgNVBAYTAk5PMQ0wCwYDVQQIDARPc2xvMQ0wCwYDVQQHDARPc2xv
+MRcwFQYDVQQKDA5UaGUgUXQgQ29tcGFueTEMMAoGA1UECwwDUiZEMRIwEAYDVQQD
+DAlIMiBUZXN0ZXIxGzAZBgkqhkiG9w0BCQEWDG1pbmltaUBxdC5pbzAgFw0yMDEw
+MjYxMjAxMzFaGA8yMTIwMTAwMjEyMDEzMVowgYMxCzAJBgNVBAYTAk5PMQ0wCwYD
+VQQIDARPc2xvMQ0wCwYDVQQHDARPc2xvMRcwFQYDVQQKDA5UaGUgUXQgQ29tcGFu
+eTEMMAoGA1UECwwDUiZEMRIwEAYDVQQDDAlIMiBUZXN0ZXIxGzAZBgkqhkiG9w0B
+CQEWDG1pbmltaUBxdC5pbzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
+AOiUp5+E4blouKH7q+rVNR8NoYX2XkBW+q+rpy1zu5ssRSzbqxAjDx9dkht7Qlnf
+VlDT00JvpOWdeuPon5915edQRsY4Unl6mKH29ra3OtUa1/yCJXsGVJTKCj7k4Bxb
+5mZzb/fTlZntMLdTIBMfUbw62FKir1WjKIcJ9fCoG8JaGeKVO4Rh5p0ezd4UUUId
+r1BXl5Nqdqy2vTMsEDnjOsD3egkv8I2SKN4O6n/C3wWYpMOWYZkGoZiKz7rJs/i/
+ez7bsV7JlwdzTlhpJzkcOSVFBP6JlEOxTNNxZ1wtKy7PtZGmsSSATq2e6+bw38Ae
+Op0XnzzqcGjtDDofBmT7OFzZWjS9VZS6+DOOe2QHWle1nCHcHyH4ku6IRlsr9xkR
+NAIlOfnvHHxqJUenoeaZ4oQDjCBKS1KXygJO/tL7BLTQVn/xK1EmPvKNnjzWk4tR
+PnibUhhs5635qpOU/YPqFBh1JjVruZbsWcDAhRcew0uxONXOa9E+4lttQ9ySYa1A
+LvWqJuAX7gu2BsBMLyqfm811YnA7CIFMyO+HlqmkLFfv5L/xIRAXR7l26YGO0VwX
+CGjMfz4NVPMMke4nB7qa9NkpXQBQKMms3Qzd5JW0Hy9Ruj5O8GPcFZmV0twjd1uJ
+PD/cAjkWLaXjdNsJ16QWc2nghQRS6HYqKRX6j+CXOxupAgMBAAGjUzBRMB0GA1Ud
+DgQWBBRSCOU58j9NJZkMamt623qyCrhN3TAfBgNVHSMEGDAWgBRSCOU58j9NJZkM
+amt623qyCrhN3TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCq
+q4jxsWeNDv5Nq14hJtF9HB+ZL64zcZtRjJP1YgNs0QppKICmjPOL2nIMGmI/jKrs
+0eGAL/9XXNVHPxm1OPOncvimMMmU6emZfpMdEtTfKP43+Pg9HgKRjLoQp406vGeQ
+8ki/mbBhrItVPgEm3tu2AFA02XTYi+YxCI9kRZLGkM3FbgtOuTLPl0Z9y+kiPc9F
+uCSC03anBEqv+vDSI8+wODymQ/IJ3Jyz1lxIRDfp4qAekmy0jU2c91VOHHEmOmqq
+kqygGFRdwbe99m9yP63r6q0b5K3X2UnJ6bns0hmTwThYwpVPXLU8jdaTddbMukN2
+/Ef96Tsw8nWOEOPMySHOTIPgwyZRp26b0kA9EmhLwOP401SxXVQCmSRmtwNagmtg
+jJKmZoYBN+//D45ibK8z6Q0oOm9P+Whf/uUXehcRxBxyV3xz7k0wKGQbHj/ddwcy
+IUoIN4lrAlib+lK170kTKN352PDmrpo2gmIzPEsfurKAIMSelDl6H+kih16BtZ8y
+Nz6fh9Soqrg3OSAware8pxV7k51crBMoPLN78KoRV8MFCK4K7Fddq4rRISq6hiXq
+r1nsjoEPuKM9huprmZVZe9t5YcDa2I+wb3IiE3uwpZbAdaLDyQ5n6F/qpsiIkZXn
+gtcF7oqpG5oYrwCcZ53y/ezUgUg7PlSz2XwAGvQtgg==
-----END CERTIFICATE-----
diff --git a/tests/auto/network/access/http2/certs/fluke.key b/tests/auto/network/access/http2/certs/fluke.key
index 9d1664d609..337ce541a6 100644
--- a/tests/auto/network/access/http2/certs/fluke.key
+++ b/tests/auto/network/access/http2/certs/fluke.key
@@ -1,15 +1,52 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQCnyKBKxBkFG2a6MuLS8RxvF4LkOS4BUZDbBDQyESHCDW9Z2FOQ
-VD+Dj6nTs9XuGpuArsMlyV6lr0tgBaqg0ZEBH8oEg+NYHJkyRYRwclgDmEpji0H1
-CEnSkQJga+Rk/t2gqnQI6TRMkV8SPTdNVCytf1uYYDYCjDv2RfMnapuUnQIDAQAB
-AoGANFzLkanTeSGNFM0uttBipFT9F4a00dqHz6JnO7zXAT26I5r8sU1pqQBb6uLz
-/+Qz5Zwk8RUAQcsMRgJetuPQUb0JZjF6Duv24hNazqXBCu7AZzUenjafwmKC/8ri
-KpX3fTwqzfzi//FKGgbXQ80yykSSliDL3kn/drATxsLCgQECQQDXhEFWLJ0vVZ1s
-1Ekf+3NITE+DR16X+LQ4W6vyEHAjTbaNWtcTKdAWLA2l6N4WAAPYSi6awm+zMxx4
-VomVTsjdAkEAx0z+e7natLeFcrrq8pbU+wa6SAP1VfhQWKitxL1e7u/QO90NCpxE
-oQYKzMkmmpOOFjQwEMAy1dvFMbm4LHlewQJAC/ksDBaUcQHHqjktCtrUb8rVjAyW
-A8lscckeB2fEYyG5J6dJVaY4ClNOOs5yMDS2Afk1F6H/xKvtQ/5CzInA/QJATDub
-K+BPU8jO9q+gpuIi3VIZdupssVGmCgObVCHLakG4uO04y9IyPhV9lA9tALtoIf4c
-VIvv5fWGXBrZ48kZAQJBAJmVCdzQxd9LZI5vxijUCj5EI4e+x5DRqVUvyP8KCZrC
-AiNyoDP85T+hBZaSXK3aYGpVwelyj3bvo1GrTNwNWLw=
------END RSA PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDolKefhOG5aLih
++6vq1TUfDaGF9l5AVvqvq6ctc7ubLEUs26sQIw8fXZIbe0JZ31ZQ09NCb6TlnXrj
+6J+fdeXnUEbGOFJ5epih9va2tzrVGtf8giV7BlSUygo+5OAcW+Zmc2/305WZ7TC3
+UyATH1G8OthSoq9VoyiHCfXwqBvCWhnilTuEYeadHs3eFFFCHa9QV5eTanastr0z
+LBA54zrA93oJL/CNkijeDup/wt8FmKTDlmGZBqGYis+6ybP4v3s+27FeyZcHc05Y
+aSc5HDklRQT+iZRDsUzTcWdcLSsuz7WRprEkgE6tnuvm8N/AHjqdF5886nBo7Qw6
+HwZk+zhc2Vo0vVWUuvgzjntkB1pXtZwh3B8h+JLuiEZbK/cZETQCJTn57xx8aiVH
+p6HmmeKEA4wgSktSl8oCTv7S+wS00FZ/8StRJj7yjZ481pOLUT54m1IYbOet+aqT
+lP2D6hQYdSY1a7mW7FnAwIUXHsNLsTjVzmvRPuJbbUPckmGtQC71qibgF+4LtgbA
+TC8qn5vNdWJwOwiBTMjvh5appCxX7+S/8SEQF0e5dumBjtFcFwhozH8+DVTzDJHu
+Jwe6mvTZKV0AUCjJrN0M3eSVtB8vUbo+TvBj3BWZldLcI3dbiTw/3AI5Fi2l43Tb
+CdekFnNp4IUEUuh2KikV+o/glzsbqQIDAQABAoICAFw1q6tr5I48vY7DF+rXsuLn
+5ZUWE1IQ6fzB4lr72nJv/9EEGnMgYzt9PpMUsD6vdCpBgS2C0+6RHArFzJtNA+RM
+iHLIG7K7702veyr/xBx/MwiSlMeMv/XpkFxVI6E6skMGG2s3AMXxKvJTy5CpRx+I
+eQFyLG+Ya1X2lgJes/q+/CpAHkOjCOpcLySQC5NZ74q734V7nSdmn+Zs3tYEh+O/
+eiuwTP/j5b38Te5vVTqDxTciJPmljmXLCwa0N100lWlbcpvw8qbqiTI2Jm3XCbUE
+AzHjW9vmrF3cRS1fXxKFGShw3SRqlkbxjfeWoi8qDPUBS4m8LOr8qG9Wo5Nfon0z
+zLP4bci3zHDvVcaaZrrsUBs/yZbg+Dgka1DmX7ekmeccr2yTdKDFgPupYUyxVbTl
+a9ZLJysjFD7rgBv1ZclHonLp6Vbm+ZoTqvteo4ikAy6L9RtBWJ23XEK34PkP/+c5
+2vWZaOrnjSeBHbFce8cdJSxqWpP+eSCI5I9XbDrYFIsQ/gqKgtzDKy2ihJ2Y8STL
+yO4hyFPFjxc+Gg4/P2PpmT5CY2ty44M0BWs+JGW96CJPrrplf2lmQUQJj5LZY66X
+Z/4C9L7ZYtKZ+bs5SvU46yWugAvQZX22Xm9xLXWyVXRdx3bj+3M3fDnF9di/zdbh
+CgLx7oWPNrXc7FCajnn9AoIBAQD5FMYwRpw9NWT9WDxQwx+cSI4Icbd88ByTW63S
+LzeRwZA0J9/SfwO+aBRupzc9GkGXCiZcGMw3AGsCtig8yFlw8E5KnzN7KlftDMnM
+9NUxxzlR8VwKyLnZfG7sDTl057ZlUujnqhmt/F8F7dIy7FVO1dE/8nngA+FYTCOG
+UZdGjwyBDlDM0JJdUWGY3xslutcpCDN5mzSTKjy9drMvImAshRawxRF6WBpn7vr2
+nC6vciqfx1Mzx1vyk0Jm0ilaydDdLMADjt/iL4Nkr0BEs4k+UzQiKDwp8gu7abQ1
+eBfxd9Iar4htQa2I1Ewl6P01G/q+ZYwgHhJ9RVn4AxQXefILAoIBAQDvCouORdQX
+C8wsyp7MwXlF/3NQeNN5/+B2mhbxrBOf7PmMCXLnkRWcjwJtzypWFqJ0sqai/2+0
+bqbMcjX5maT8stT2shl3zXe/Ejt2e3TBYpc1tyuses8Kb5BMU8hu6tTd3G2CMXpD
+dT6DVemJZCTtwj9aBNIxSizvlgMolJnCpzhPnlfHSI6E+g3m/LTTo3HwbjMSw/Uq
+irgjOpI2wSBB6LZPSgjvfcYPRyWUk16L4A5uSX0cADnovDFLa5/h0wJvN/OoCSQg
+rLCXG5E18EyL5Wc58BCY1ZvxmjG3lQtgPxYu2Jwc36R/y/JKlxW5suER5ZNpbbD4
+uOyTt2VxMQ2bAoIBAQC5+MzRFqdo/AjfL5Y5JrbfVTzXCTDa09xCGd16ZU60QTWN
++4ed/r+o1sUKqUcRFB2MzEM/2DQBjQpZB/CbEWvWa1XJWXxypXbowveZU+QqOnmN
+uQvj8WLyA3o+PNF9e9QvauwCrHpn8VpxbtPWuaYoKnUFreFZZQxHhPGxRBIS2JOZ
+eDrT8ZaWnkCkh1AZp5smQ71LOprSlmKrg4jd1GjCVMxQR5N5KXbtyv0OTCZ/UFqK
+2aRBsMPyJgkaBChkZPLRcKwc+/wlQRx1fHQb14DNTApMxoXFO7eOwqmOkpAt9iyl
+SBIwoS0UUI5ab88+bBmXNvKcuFdNuQ4nowTJUn9pAoIBADMNkILBXSvS5DeIyuO2
+Sp1tkoZUV+5NfPY3sMDK3KIibaW/+t+EOBZo4L7tKQCb8vRzl21mmsfxfgRaPDbj
+3r3tv9g0b4YLxxBy52pFscj/soXRai17SS7UZwA2QK+XzgDYbDcLNC6mIsTQG4Gx
+dsWk3/zs3KuUSQaehmwrWK+fIUK38c1pLK8v7LoxrLkqxlHwZ04RthHw8KTthH7X
+Pnl1J0LF8CSeOyfWLSuPUfkT0GEzptnNHpEbaHfQM6R6eaGhVJPF6AZme4y6YYgg
+m2ihhSt1n0XVEWpHYWjxFy3mK2mz75unFC4LM+NEY2p2zuUQoCw7NjnY3QYrfCnx
+rRMCggEAXeXsMSLFjjyuoL7iKbAxo52HD/P0fBoy58LyRcwfNVr0lvYan4pYEx+o
+KijIh9K16PqXZXKMA9v003B+ulmF8bJ7SddCZ5NGvnFhUTDe4DdTKgp2RuwQ3Bsc
+3skPIDbhVETyOLCtys34USHrq8U/0DlGY3eLRfxw9GnbKxSBGa/KEu/qQLPNUo50
+7xHZDg7GKeC3kqNJeqKM9rkp0VzIGkEnaD9127LeNDmERDfftxJzFoC/THvUBLfU
+6Sus2ZYwRE8VFvKC30Q45t/c54X3IuhYvAuiCuTmyfE4ruyzyOwKzhUkeeLq1APX
+g0veFbyfzlJ0q8qzD/iffqqIa2ZSmQ==
+-----END PRIVATE KEY-----
diff --git a/tests/auto/network/access/http2/http2.pro b/tests/auto/network/access/http2/http2.pro
deleted file mode 100644
index 646ea117f7..0000000000
--- a/tests/auto/network/access/http2/http2.pro
+++ /dev/null
@@ -1,9 +0,0 @@
-QT = core core-private network network-private testlib
-
-CONFIG += testcase parallel_test c++11
-TARGET = tst_http2
-INCLUDEPATH += ../../../../shared/
-HEADERS += http2srv.h ../../../../shared/emulationdetector.h
-SOURCES += tst_http2.cpp http2srv.cpp
-
-DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/auto/network/access/http2/http2srv.cpp b/tests/auto/network/access/http2/http2srv.cpp
index 9513744476..b52ea5527b 100644
--- a/tests/auto/network/access/http2/http2srv.cpp
+++ b/tests/auto/network/access/http2/http2srv.cpp
@@ -1,32 +1,7 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QtTest/QtTest>
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QTest>
#include <QtNetwork/private/http2protocol_p.h>
#include <QtNetwork/private/bitstreams_p.h>
@@ -109,6 +84,12 @@ Http2Server::~Http2Server()
{
}
+void Http2Server::setInformationalStatusCode(int code)
+{
+ if (code == 100 || (102 <= code && code <= 199))
+ informationalStatusCode = code;
+}
+
void Http2Server::enablePushPromise(bool pushEnabled, const QByteArray &path)
{
pushPromiseEnabled = pushEnabled;
@@ -125,6 +106,28 @@ void Http2Server::setContentEncoding(const QByteArray &encoding)
contentEncoding = encoding;
}
+void Http2Server::setAuthenticationHeader(const QByteArray &authentication)
+{
+ authenticationHeader = authentication;
+}
+
+void Http2Server::setAuthenticationRequired(bool enable)
+{
+ Q_ASSERT(!enable || authenticationHeader.isEmpty());
+ authenticationRequired = enable;
+}
+
+void Http2Server::setRedirect(const QByteArray &url, int count)
+{
+ redirectUrl = url;
+ redirectCount = count;
+}
+
+void Http2Server::setSendTrailingHEADERS(bool enable)
+{
+ sendTrailingHEADERS = enable;
+}
+
void Http2Server::emulateGOAWAY(int timeout)
{
Q_ASSERT(timeout >= 0);
@@ -143,6 +146,17 @@ bool Http2Server::isClearText() const
return connectionType == H2Type::h2c || connectionType == H2Type::h2cDirect;
}
+QByteArray Http2Server::requestAuthorizationHeader()
+{
+ const auto isAuthHeader = [](const HeaderField &field) {
+ return field.name == "authorization";
+ };
+ const auto requestHeaders = decoder.decodedHeader();
+ const auto authentication =
+ std::find_if(requestHeaders.cbegin(), requestHeaders.cend(), isAuthHeader);
+ return authentication == requestHeaders.cend() ? QByteArray() : authentication->value;
+}
+
void Http2Server::startServer()
{
if (listen()) {
@@ -251,9 +265,20 @@ void Http2Server::sendDATA(quint32 streamID, quint32 windowSize)
return;
if (last) {
- writer.start(FrameType::DATA, FrameFlag::END_STREAM, streamID);
- writer.setPayloadSize(0);
- writer.write(*socket);
+ if (sendTrailingHEADERS) {
+ writer.start(FrameType::HEADERS,
+ FrameFlag::PRIORITY | FrameFlag::END_HEADERS | FrameFlag::END_STREAM, streamID);
+ const quint32 maxFrameSize(clientSetting(Settings::MAX_FRAME_SIZE_ID,
+ Http2::maxPayloadSize));
+ // 5 bytes for PRIORITY data:
+ writer.append(quint32(0)); // streamID 0 (32-bit)
+ writer.append(quint8(0)); // + weight 0 (8-bit)
+ writer.writeHEADERS(*socket, maxFrameSize);
+ } else {
+ writer.start(FrameType::DATA, FrameFlag::END_STREAM, streamID);
+ writer.setPayloadSize(0);
+ writer.write(*socket);
+ }
suspendedStreams.erase(it);
activeRequests.erase(streamID);
@@ -302,11 +327,12 @@ void Http2Server::incomingConnection(qintptr socketDescriptor)
sslSocket->setProtocol(QSsl::TlsV1_2OrLater);
connect(sslSocket, SIGNAL(sslErrors(QList<QSslError>)),
this, SLOT(ignoreErrorSlot()));
- QFile file(SRCDIR "certs/fluke.key");
- file.open(QIODevice::ReadOnly);
+ QFile file(QT_TESTCASE_SOURCEDIR "/certs/fluke.key");
+ if (!file.open(QIODevice::ReadOnly))
+ qFatal("Cannot open certificate file %s", qPrintable(file.fileName()));
QSslKey key(file.readAll(), QSsl::Rsa, QSsl::Pem, QSsl::PrivateKey);
sslSocket->setPrivateKey(key);
- auto localCert = QSslCertificate::fromPath(SRCDIR "certs/fluke.cert");
+ auto localCert = QSslCertificate::fromPath(QT_TESTCASE_SOURCEDIR "/certs/fluke.cert");
sslSocket->setLocalCertificateChain(localCert);
sslSocket->setSocketDescriptor(socketDescriptor, QAbstractSocket::ConnectedState);
// Stop listening.
@@ -364,16 +390,12 @@ bool Http2Server::verifyProtocolUpgradeRequest()
bool settingsOk = false;
QHttpNetworkReplyPrivate *firstRequestReader = protocolUpgradeHandler->d_func();
+ const auto headers = firstRequestReader->headers();
// That's how we append them, that's what I expect to find:
- for (const auto &header : firstRequestReader->fields) {
- if (header.first == "Connection")
- connectionOk = header.second.contains("Upgrade, HTTP2-Settings");
- else if (header.first == "Upgrade")
- upgradeOk = header.second.contains("h2c");
- else if (header.first == "HTTP2-Settings")
- settingsOk = true;
- }
+ connectionOk = headers.combinedValue(QHttpHeaders::WellKnownHeader::Connection).contains("Upgrade, HTTP2-Settings");
+ upgradeOk = headers.combinedValue(QHttpHeaders::WellKnownHeader::Upgrade).contains("h2c");
+ settingsOk = headers.contains("HTTP2-Settings");
return connectionOk && upgradeOk && settingsOk;
}
@@ -737,10 +759,15 @@ void Http2Server::handleDATA()
}
if (inboundFrame.flags().testFlag(FrameFlag::END_STREAM)) {
- closedStreams.insert(streamID); // Enter "half-closed remote" state.
- streamWindows.erase(it);
+ if (responseBody.isEmpty()) {
+ closedStreams.insert(streamID); // Enter "half-closed remote" state.
+ streamWindows.erase(it);
+ }
emit receivedData(streamID);
}
+ emit receivedDATAFrame(streamID,
+ QByteArray(reinterpret_cast<const char *>(inboundFrame.dataBegin()),
+ inboundFrame.dataSize()));
}
void Http2Server::handleWINDOW_UPDATE()
@@ -817,10 +844,32 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
// Now we'll continue with _normal_ response.
}
+ // Create a header with an informational status code and some random header
+ // fields. The setter ensures that the value is 100 or is between 102 and 199
+ // (inclusive) if set - otherwise it is 0
+
+ if (informationalStatusCode > 0) {
+ writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID);
+
+ HttpHeader informationalHeader;
+ informationalHeader.push_back({":status", QByteArray::number(informationalStatusCode)});
+ informationalHeader.push_back(HeaderField("a_random_header_field", "it_will_be_dropped"));
+ informationalHeader.push_back(HeaderField("another_random_header_field", "drop_this_too"));
+
+ HPack::BitOStream ostream(writer.outboundFrame().buffer);
+ const bool result = encoder.encodeResponse(ostream, informationalHeader);
+ Q_ASSERT(result);
+
+ writer.writeHEADERS(*socket, maxFrameSize);
+ }
+
writer.start(FrameType::HEADERS, FrameFlag::END_HEADERS, streamID);
if (emptyBody)
writer.addFlag(FrameFlag::END_STREAM);
+ // We assume any auth is correct. Leaves the checking to the test itself
+ const bool hasAuth = !requestAuthorizationHeader().isEmpty();
+
HttpHeader header;
if (redirectWhileReading) {
if (redirectSent) {
@@ -836,7 +885,15 @@ void Http2Server::sendResponse(quint32 streamID, bool emptyBody)
const QString url("%1://localhost:%2/");
header.push_back({"location", url.arg(isClearText() ? QStringLiteral("http") : QStringLiteral("https"),
QString::number(targetPort)).toLatin1()});
-
+ } else if (redirectCount > 0) { // Not redirecting while reading, unlike above
+ --redirectCount;
+ header.push_back({":status", "308"});
+ header.push_back({"location", redirectUrl});
+ } else if (!authenticationHeader.isEmpty() && !hasAuth) {
+ header.push_back({ ":status", "401" });
+ header.push_back(HPack::HeaderField("www-authenticate", authenticationHeader));
+ } else if (authenticationRequired) {
+ header.push_back({ ":status", "401" });
} else {
header.push_back({":status", "200"});
}
diff --git a/tests/auto/network/access/http2/http2srv.h b/tests/auto/network/access/http2/http2srv.h
index baf0155988..dc94318527 100644
--- a/tests/auto/network/access/http2/http2srv.h
+++ b/tests/auto/network/access/http2/http2srv.h
@@ -1,30 +1,5 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#ifndef HTTP2SRV_H
#define HTTP2SRV_H
@@ -83,16 +58,29 @@ public:
~Http2Server();
+ // To send responses with status code 1xx
+ void setInformationalStatusCode(int code);
// To be called before server started:
void enablePushPromise(bool enabled, const QByteArray &path = QByteArray());
void setResponseBody(const QByteArray &body);
// No content encoding is actually performed, call setResponseBody with already encoded data
void setContentEncoding(const QByteArray &contentEncoding);
+ // No authentication data is generated for the method, the full header value must be set
+ void setAuthenticationHeader(const QByteArray &authentication);
+ // Authentication always required, no challenge provided
+ void setAuthenticationRequired(bool enable);
+ // Set the redirect URL and count. The server will return a redirect response with the url
+ // 'count' amount of times
+ void setRedirect(const QByteArray &redirectUrl, int count);
+ // Send a trailing HEADERS frame with PRIORITY and END_STREAM flag
+ void setSendTrailingHEADERS(bool enable);
void emulateGOAWAY(int timeout);
void redirectOpenStream(quint16 targetPort);
bool isClearText() const;
+ QByteArray requestAuthorizationHeader();
+
// Invokables, since we can call them from the main thread,
// but server (can) work on its own thread.
Q_INVOKABLE void startServer();
@@ -129,6 +117,8 @@ Q_SIGNALS:
void decompressionFailed(quint32 streamID);
void receivedRequest(quint32 streamID);
void receivedData(quint32 streamID);
+ // Emitted for every DATA frame. Includes the content of the frame as \a body.
+ void receivedDATAFrame(quint32 streamID, const QByteArray &body);
void windowUpdate(quint32 streamID);
void sendingData();
@@ -215,6 +205,14 @@ private:
QAtomicInt interrupted;
QByteArray contentEncoding;
+ QByteArray authenticationHeader;
+ bool authenticationRequired = false;
+
+ QByteArray redirectUrl;
+ int redirectCount = 0;
+
+ bool sendTrailingHEADERS = false;
+ int informationalStatusCode = 0;
protected slots:
void ignoreErrorSlot();
};
diff --git a/tests/auto/network/access/http2/tst_http2.cpp b/tests/auto/network/access/http2/tst_http2.cpp
index 0282942225..396a6f2fda 100644
--- a/tests/auto/network/access/http2/tst_http2.cpp
+++ b/tests/auto/network/access/http2/tst_http2.cpp
@@ -1,32 +1,13 @@
-/****************************************************************************
-**
-** Copyright (C) 2016 The Qt Company Ltd.
-** Contact: https://www.qt.io/licensing/
-**
-** This file is part of the test suite of the Qt Toolkit.
-**
-** $QT_BEGIN_LICENSE:GPL-EXCEPT$
-** Commercial License Usage
-** Licensees holding valid commercial Qt licenses may use this file in
-** accordance with the commercial license agreement provided with the
-** Software or, alternatively, in accordance with the terms contained in
-** a written agreement between you and The Qt Company. For licensing terms
-** and conditions see https://www.qt.io/terms-conditions. For further
-** information use the contact form at https://www.qt.io/contact-us.
-**
-** GNU General Public License Usage
-** Alternatively, this file may be used under the terms of the GNU
-** General Public License version 3 as published by the Free Software
-** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-** included in the packaging of this file. Please review the following
-** information to ensure the GNU General Public License requirements will
-** be met: https://www.gnu.org/licenses/gpl-3.0.html.
-**
-** $QT_END_LICENSE$
-**
-****************************************************************************/
-
-#include <QtTest/QtTest>
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtNetwork/qtnetworkglobal.h>
+
+#include <QTest>
+#include <QTestEventLoop>
+#include <QScopeGuard>
+#include <QRandomGenerator>
+#include <QSignalSpy>
#include "http2srv.h"
@@ -36,38 +17,29 @@
#include <QtNetwork/qnetworkrequest.h>
#include <QtNetwork/qnetworkreply.h>
+#if QT_CONFIG(ssl)
+#include <QtNetwork/qsslsocket.h>
+#endif
+
#include <QtCore/qglobal.h>
#include <QtCore/qobject.h>
#include <QtCore/qthread.h>
#include <QtCore/qurl.h>
-
-#ifndef QT_NO_SSL
-#ifndef QT_NO_OPENSSL
-#include <QtNetwork/private/qsslsocket_openssl_symbols_p.h>
-#endif // NO_OPENSSL
-#endif // NO_SSL
+#include <QtCore/qset.h>
#include <cstdlib>
#include <memory>
#include <string>
-#include "emulationdetector.h"
-
-#if (!defined(QT_NO_OPENSSL) && OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT)) \
- || QT_CONFIG(schannel)
-// HTTP/2 over TLS requires ALPN/NPN to negotiate the protocol version.
-const bool clearTextHTTP2 = false;
-#else
-// No ALPN/NPN support to negotiate HTTP/2, we'll use cleartext 'h2c' with
-// a protocol upgrade procedure.
-const bool clearTextHTTP2 = true;
-#endif
+#include <QtTest/private/qemulationdetector_p.h>
Q_DECLARE_METATYPE(H2Type)
Q_DECLARE_METATYPE(QNetworkRequest::Attribute)
QT_BEGIN_NAMESPACE
+using namespace Qt::StringLiterals;
+
QHttp2Configuration qt_defaultH2Configuration()
{
QHttp2Configuration config;
@@ -98,8 +70,11 @@ public slots:
void init();
private slots:
// Tests:
+ void defaultQnamHttp2Configuration();
void singleRequest_data();
void singleRequest();
+ void informationalRequest_data();
+ void informationalRequest();
void multipleRequests();
void flowControlClientSide();
void flowControlServerSide();
@@ -110,10 +85,29 @@ private slots:
void connectToHost_data();
void connectToHost();
void maxFrameSize();
+ void http2DATAFrames();
+
+ void moreActivitySignals_data();
+ void moreActivitySignals();
void contentEncoding_data();
void contentEncoding();
+ void authenticationRequired_data();
+ void authenticationRequired();
+
+ void unsupportedAuthenticateChallenge();
+
+ void h2cAllowedAttribute_data();
+ void h2cAllowedAttribute();
+
+ void redirect_data();
+ void redirect();
+
+ void trailingHEADERS();
+
+ void duplicateRequestsWithAborts();
+
protected slots:
// Slots to listen to our in-process server:
void serverStarted(quint16 port);
@@ -157,6 +151,7 @@ private:
int windowUpdates = 0;
bool prefaceOK = false;
bool serverGotSettingsACK = false;
+ bool POSTResponseHEADOnly = true;
static const RawSettings defaultServerSettings;
};
@@ -182,6 +177,8 @@ struct ServerDeleter
}
};
+bool clearTextHTTP2 = false;
+
using ServerPtr = QScopedPointer<Http2Server, ServerDeleter>;
H2Type defaultConnectionType()
@@ -194,6 +191,12 @@ H2Type defaultConnectionType()
tst_Http2::tst_Http2()
: workerThread(new QThread)
{
+#if QT_CONFIG(ssl)
+ const auto features = QSslSocket::supportedFeatures();
+ clearTextHTTP2 = !features.contains(QSsl::SupportedFeature::ServerSideAlpn);
+#else
+ clearTextHTTP2 = true;
+#endif
workerThread->start();
}
@@ -215,6 +218,12 @@ void tst_Http2::init()
manager.reset(new QNetworkAccessManager);
}
+void tst_Http2::defaultQnamHttp2Configuration()
+{
+ // The configuration we also implicitly use in QNAM.
+ QCOMPARE(qt_defaultH2Configuration(), QNetworkRequest().http2Configuration());
+}
+
void tst_Http2::singleRequest_data()
{
QTest::addColumn<QNetworkRequest::Attribute>("h2Attribute");
@@ -245,7 +254,7 @@ void tst_Http2::singleRequest()
// we have to use TLS sockets (== private key) and thus suppress a
// keychain UI asking for permission to use a private key.
// Our CI has this, but somebody testing locally - will have a problem.
- qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1"));
+ qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
auto envRollback = qScopeGuard([](){
qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN");
});
@@ -266,10 +275,15 @@ void tst_Http2::singleRequest()
url.setPath("/index.html");
QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
QFETCH(const QNetworkRequest::Attribute, h2Attribute);
request.setAttribute(h2Attribute, QVariant(true));
auto reply = manager->get(request);
+#if QT_CONFIG(ssl)
+ QSignalSpy encSpy(reply, &QNetworkReply::encrypted);
+#endif // QT_CONFIG(ssl)
+
connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished);
// Since we're using self-signed certificates,
// ignore SSL errors:
@@ -278,12 +292,81 @@ void tst_Http2::singleRequest()
runEventLoop();
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
QCOMPARE(reply->error(), QNetworkReply::NoError);
QVERIFY(reply->isFinished());
+
+#if QT_CONFIG(ssl)
+ if (connectionType == H2Type::h2Alpn || connectionType == H2Type::h2Direct)
+ QCOMPARE(encSpy.size(), 1);
+#endif // QT_CONFIG(ssl)
+}
+
+void tst_Http2::informationalRequest_data()
+{
+ QTest::addColumn<int>("statusCode");
+
+ // 'Clear text' that should always work, either via the protocol upgrade
+ // or as direct.
+ QTest::addRow("statusCode-100") << 100;
+ QTest::addRow("statusCode-125") << 125;
+ QTest::addRow("statusCode-150") << 150;
+ QTest::addRow("statusCode-175") << 175;
+}
+
+void tst_Http2::informationalRequest()
+{
+ clearHTTP2State();
+
+ serverPort = 0;
+ nRequests = 1;
+
+ ServerPtr srv(newServer(defaultServerSettings, defaultConnectionType()));
+
+ QFETCH(const int, statusCode);
+ srv->setInformationalStatusCode(statusCode);
+
+ QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ auto url = requestUrl(defaultConnectionType());
+ url.setPath("/index.html");
+
+ QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
+
+ auto reply = manager->get(request);
+
+ connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+ // Since we're using self-signed certificates,
+ // ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+ STOP_ON_FAILURE
+
+ QCOMPARE(nRequests, 0);
+ QVERIFY(prefaceOK);
+ QVERIFY(serverGotSettingsACK);
+
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+ QVERIFY(reply->isFinished());
+
+ const QVariant code(reply->attribute(QNetworkRequest::HttpStatusCodeAttribute));
+
+ // We are discarding informational headers if the status code is in the range of
+ // 102-199 or if it is 100. As these header fields were part of the informational
+ // header used for this test case, we should not see them at this point and the
+ // status code should be 200.
+
+ QCOMPARE(code.value<int>(), 200);
+ QVERIFY(!reply->hasRawHeader("a_random_header_field"));
+ QVERIFY(!reply->hasRawHeader("another_random_header_field"));
}
void tst_Http2::multipleRequests()
@@ -314,7 +397,7 @@ void tst_Http2::multipleRequests()
runEventLoop();
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
}
@@ -359,7 +442,7 @@ void tst_Http2::flowControlClientSide()
runEventLoop(120000);
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
QVERIFY(windowUpdates > 0);
@@ -375,7 +458,7 @@ void tst_Http2::flowControlServerSide()
// to let all replies finish without any error.
using namespace Http2;
- if (EmulationDetector::isRunningArmOnX86())
+ if (QTestPrivate::isRunningArmOnX86())
QSKIP("Test is too slow to run on emulator");
clearHTTP2State();
@@ -400,7 +483,7 @@ void tst_Http2::flowControlServerSide()
runEventLoop(120000);
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
}
@@ -432,6 +515,7 @@ void tst_Http2::pushPromise()
url.setPath("/index.html");
QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true));
request.setHttp2Configuration(params);
@@ -443,7 +527,7 @@ void tst_Http2::pushPromise()
runEventLoop();
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
@@ -458,6 +542,7 @@ void tst_Http2::pushPromise()
url.setPath("/script.js");
QNetworkRequest promisedRequest(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
promisedRequest.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true));
reply = manager->get(promisedRequest);
connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished);
@@ -512,6 +597,7 @@ void tst_Http2::goaway()
for (int i = 0; i < nRequests; ++i) {
url.setPath(QString("/%1").arg(i));
QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true));
replies[i] = manager->get(request);
QCOMPARE(replies[i]->error(), QNetworkReply::NoError);
@@ -567,7 +653,7 @@ void tst_Http2::earlyResponse()
runEventLoop();
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
}
@@ -624,7 +710,7 @@ void tst_Http2::connectToHost()
// we have to use TLS sockets (== private key) and thus suppress a
// keychain UI asking for permission to use a private key.
// Our CI has this, but somebody testing locally - will have a problem.
- qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1"));
+ qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
auto envRollback = qScopeGuard([](){
qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN");
});
@@ -653,6 +739,7 @@ void tst_Http2::connectToHost()
auto copyUrl = url;
copyUrl.setScheme(QLatin1String("preconnect-https"));
QNetworkRequest request(copyUrl);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
request.setAttribute(requestAttribute, true);
reply = manager->get(request);
// Since we're using self-signed certificates, ignore SSL errors:
@@ -665,6 +752,7 @@ void tst_Http2::connectToHost()
auto copyUrl = url;
copyUrl.setScheme(QLatin1String("preconnect-http"));
QNetworkRequest request(copyUrl);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
request.setAttribute(requestAttribute, true);
reply = manager->get(request);
}
@@ -685,6 +773,7 @@ void tst_Http2::connectToHost()
QCOMPARE(nRequests, 1);
QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
request.setAttribute(requestAttribute, QVariant(true));
reply = manager->get(request);
connect(reply, &QNetworkReply::finished, this, &tst_Http2::replyFinished);
@@ -695,7 +784,7 @@ void tst_Http2::connectToHost()
runEventLoop();
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
@@ -719,7 +808,7 @@ void tst_Http2::maxFrameSize()
// we have to use TLS sockets (== private key) and thus suppress a
// keychain UI asking for permission to use a private key.
// Our CI has this, but somebody testing locally - will have a problem.
- qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1"));
+ qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
auto envRollback = qScopeGuard([](){
qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN");
});
@@ -750,6 +839,7 @@ void tst_Http2::maxFrameSize()
url.setPath(QString("/stream1.html"));
QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
request.setAttribute(attribute, QVariant(true));
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
request.setHttp2Configuration(h2Config);
@@ -763,13 +853,168 @@ void tst_Http2::maxFrameSize()
// Normally, with a 16kb limit, our server would split such
// a response into 3 'DATA' frames (16kb + 16kb + 0|END_STREAM).
- QCOMPARE(frameCounter.count(), 1);
+ QCOMPARE(frameCounter.size(), 1);
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
}
+void tst_Http2::http2DATAFrames()
+{
+ using namespace Http2;
+
+ {
+ // 0. DATA frame with payload, no padding.
+
+ FrameWriter writer(FrameType::DATA, FrameFlag::EMPTY, 1);
+ writer.append('a');
+ writer.append('b');
+ writer.append('c');
+
+ const Frame frame = writer.outboundFrame();
+ const auto &buffer = frame.buffer;
+ // Frame's header is 9 bytes + 3 bytes of payload
+ // (+ 0 bytes of padding and no padding length):
+ QCOMPARE(int(buffer.size()), 12);
+
+ QVERIFY(!frame.padding());
+ QCOMPARE(int(frame.payloadSize()), 3);
+ QCOMPARE(int(frame.dataSize()), 3);
+ QCOMPARE(frame.dataBegin() - buffer.data(), 9);
+ QCOMPARE(char(*frame.dataBegin()), 'a');
+ }
+
+ {
+ // 1. DATA with padding.
+
+ const int padLength = 10;
+ FrameWriter writer(FrameType::DATA, FrameFlag::END_STREAM | FrameFlag::PADDED, 1);
+ writer.append(uchar(padLength)); // The length of padding is 1 byte long.
+ writer.append('a');
+ for (int i = 0; i < padLength; ++i)
+ writer.append('b');
+
+ const Frame frame = writer.outboundFrame();
+ const auto &buffer = frame.buffer;
+ // Frame's header is 9 bytes + 1 byte for padding length
+ // + 1 byte of data + 10 bytes of padding:
+ QCOMPARE(int(buffer.size()), 21);
+
+ QCOMPARE(frame.padding(), padLength);
+ QCOMPARE(int(frame.payloadSize()), 12); // Includes padding, its length + data.
+ QCOMPARE(int(frame.dataSize()), 1);
+
+ // Skipping 9 bytes long header and padding length:
+ QCOMPARE(frame.dataBegin() - buffer.data(), 10);
+
+ QCOMPARE(char(frame.dataBegin()[0]), 'a');
+ QCOMPARE(char(frame.dataBegin()[1]), 'b');
+
+ QVERIFY(frame.flags().testFlag(FrameFlag::END_STREAM));
+ QVERIFY(frame.flags().testFlag(FrameFlag::PADDED));
+ }
+ {
+ // 2. DATA with PADDED flag, but 0 as padding length.
+
+ FrameWriter writer(FrameType::DATA, FrameFlag::END_STREAM | FrameFlag::PADDED, 1);
+
+ writer.append(uchar(0)); // Number of padding bytes is 1 byte long.
+ writer.append('a');
+
+ const Frame frame = writer.outboundFrame();
+ const auto &buffer = frame.buffer;
+
+ // Frame's header is 9 bytes + 1 byte for padding length + 1 byte of data
+ // + 0 bytes of padding:
+ QCOMPARE(int(buffer.size()), 11);
+
+ QCOMPARE(frame.padding(), 0);
+ QCOMPARE(int(frame.payloadSize()), 2); // Includes padding (0 bytes), its length + data.
+ QCOMPARE(int(frame.dataSize()), 1);
+
+ // Skipping 9 bytes long header and padding length:
+ QCOMPARE(frame.dataBegin() - buffer.data(), 10);
+
+ QCOMPARE(char(*frame.dataBegin()), 'a');
+
+ QVERIFY(frame.flags().testFlag(FrameFlag::END_STREAM));
+ QVERIFY(frame.flags().testFlag(FrameFlag::PADDED));
+ }
+}
+
+void tst_Http2::moreActivitySignals_data()
+{
+ QTest::addColumn<QNetworkRequest::Attribute>("h2Attribute");
+ QTest::addColumn<H2Type>("connectionType");
+
+ QTest::addRow("h2c-upgrade")
+ << QNetworkRequest::Http2AllowedAttribute << H2Type::h2c;
+ QTest::addRow("h2c-direct")
+ << QNetworkRequest::Http2DirectAttribute << H2Type::h2cDirect;
+
+ if (!clearTextHTTP2)
+ QTest::addRow("h2-ALPN")
+ << QNetworkRequest::Http2AllowedAttribute << H2Type::h2Alpn;
+
+#if QT_CONFIG(ssl)
+ QTest::addRow("h2-direct")
+ << QNetworkRequest::Http2DirectAttribute << H2Type::h2Direct;
+#endif
+}
+
+void tst_Http2::moreActivitySignals()
+{
+ clearHTTP2State();
+
+#if QT_CONFIG(securetransport)
+ // Normally on macOS we use plain text only for SecureTransport
+ // does not support ALPN on the server side. With 'direct encrytped'
+ // we have to use TLS sockets (== private key) and thus suppress a
+ // keychain UI asking for permission to use a private key.
+ // Our CI has this, but somebody testing locally - will have a problem.
+ qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
+ auto envRollback = qScopeGuard([]() { qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); });
+#endif
+
+ serverPort = 0;
+ QFETCH(H2Type, connectionType);
+ ServerPtr srv(newServer(defaultServerSettings, connectionType));
+ QMetaObject::invokeMethod(srv.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop(100);
+ QVERIFY(serverPort != 0);
+ auto url = requestUrl(connectionType);
+ url.setPath(QString("/stream1.html"));
+ QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
+ QFETCH(const QNetworkRequest::Attribute, h2Attribute);
+ request.setAttribute(h2Attribute, QVariant(true));
+ request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
+ QSharedPointer<QNetworkReply> reply(manager->get(request));
+ nRequests = 1;
+ connect(reply.data(), &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+ QSignalSpy spy1(reply.data(), SIGNAL(socketStartedConnecting()));
+ QSignalSpy spy2(reply.data(), SIGNAL(requestSent()));
+ QSignalSpy spy3(reply.data(), SIGNAL(metaDataChanged()));
+ // Since we're using self-signed certificates,
+ // ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ spy1.wait();
+ spy2.wait();
+ spy3.wait();
+
+ runEventLoop();
+ STOP_ON_FAILURE
+
+ QCOMPARE(nRequests, 0);
+ QVERIFY(prefaceOK);
+ QVERIFY(serverGotSettingsACK);
+
+ QVERIFY(reply->error() == QNetworkReply::NoError);
+ QVERIFY(reply->isFinished());
+}
+
void tst_Http2::contentEncoding_data()
{
QTest::addColumn<QByteArray>("encoding");
@@ -839,7 +1084,7 @@ void tst_Http2::contentEncoding()
// we have to use TLS sockets (== private key) and thus suppress a
// keychain UI asking for permission to use a private key.
// Our CI has this, but somebody testing locally - will have a problem.
- qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", QByteArray("1"));
+ qputenv("QT_SSL_USE_TEMPORARY_KEYCHAIN", "1");
auto envRollback = qScopeGuard([]() { qunsetenv("QT_SSL_USE_TEMPORARY_KEYCHAIN"); });
#endif
@@ -862,6 +1107,7 @@ void tst_Http2::contentEncoding()
url.setPath("/index.html");
QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
QFETCH(const QNetworkRequest::Attribute, h2Attribute);
request.setAttribute(h2Attribute, QVariant(true));
@@ -874,7 +1120,7 @@ void tst_Http2::contentEncoding()
runEventLoop();
STOP_ON_FAILURE
- QVERIFY(nRequests == 0);
+ QCOMPARE(nRequests, 0);
QVERIFY(prefaceOK);
QVERIFY(serverGotSettingsACK);
@@ -883,6 +1129,422 @@ void tst_Http2::contentEncoding()
QTEST(reply->readAll(), "expected");
}
+void tst_Http2::authenticationRequired_data()
+{
+ QTest::addColumn<bool>("success");
+ QTest::addColumn<bool>("responseHEADOnly");
+ QTest::addColumn<bool>("withChallenge");
+
+ QTest::addRow("failed-auth") << false << true << true;
+ QTest::addRow("successful-auth") << true << true << true;
+ // Include a DATA frame in the response from the remote server. An example would be receiving a
+ // JSON response on a request along with the 401 error.
+ QTest::addRow("failed-auth-with-response") << false << false << true;
+ QTest::addRow("successful-auth-with-response") << true << false << true;
+
+ // Don't provide a challenge header. This is valid if you are actually just
+ // denied access for whatever reason.
+ QTest::addRow("no-challenge") << false << false << false;
+}
+
+void tst_Http2::authenticationRequired()
+{
+ clearHTTP2State();
+ serverPort = 0;
+ QFETCH(const bool, responseHEADOnly);
+ POSTResponseHEADOnly = responseHEADOnly;
+
+ QFETCH(const bool, success);
+ QFETCH(const bool, withChallenge);
+
+ ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType()));
+ QByteArray responseBody = "Hello"_ba;
+ targetServer->setResponseBody(responseBody);
+ if (withChallenge)
+ targetServer->setAuthenticationHeader("Basic realm=\"Shadow\"");
+ else
+ targetServer->setAuthenticationRequired(true);
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ nRequests = 1;
+
+ auto url = requestUrl(defaultConnectionType());
+ url.setPath("/index.html");
+ QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
+
+ QByteArray expectedBody = "Hello, World!";
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+ QScopedPointer<QNetworkReply> reply;
+ reply.reset(manager->post(request, expectedBody));
+
+ bool authenticationRequested = false;
+ connect(manager.get(), &QNetworkAccessManager::authenticationRequired, reply.get(),
+ [&](QNetworkReply *, QAuthenticator *auth) {
+ authenticationRequested = true;
+ if (success) {
+ auth->setUser("admin");
+ auth->setPassword("admin");
+ }
+ });
+
+ QByteArray receivedBody;
+ connect(targetServer.get(), &Http2Server::receivedDATAFrame, reply.get(),
+ [&receivedBody](quint32 streamID, const QByteArray &body) {
+ if (streamID == 3) // The expected body is on the retry, so streamID == 3
+ receivedBody += body;
+ });
+
+ if (success) {
+ connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+ } else {
+ // Use queued connection so that the finished signal can be emitted and the isFinished
+ // property can be set.
+ connect(reply.get(), &QNetworkReply::errorOccurred, this,
+ &tst_Http2::replyFinishedWithError, Qt::QueuedConnection);
+ }
+ // Since we're using self-signed certificates,
+ // ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+ STOP_ON_FAILURE
+ QVERIFY2(reply->isFinished(),
+ "The reply should error out if authentication fails, or finish if it succeeds");
+
+ if (!success)
+ QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError);
+ // else: no error (is checked in tst_Http2::replyFinished)
+
+ QVERIFY(authenticationRequested || !withChallenge);
+
+ const auto isAuthenticated = [](const QByteArray &bv) {
+ return bv == "Basic YWRtaW46YWRtaW4="; // admin:admin
+ };
+ // Get the "authorization" header out from the server and make sure it's as expected:
+ auto reqAuthHeader = targetServer->requestAuthorizationHeader();
+ QCOMPARE(isAuthenticated(reqAuthHeader), success);
+ if (success)
+ QCOMPARE(receivedBody, expectedBody);
+ if (responseHEADOnly) {
+ const QVariant contentLenHeader = reply->header(QNetworkRequest::ContentLengthHeader);
+ QVERIFY2(!contentLenHeader.isValid(), "We expect no DATA frames to be received");
+ QCOMPARE(reply->readAll(), QByteArray());
+ } else {
+ const qint32 contentLen = reply->header(QNetworkRequest::ContentLengthHeader).toInt();
+ QCOMPARE(contentLen, responseBody.length());
+ QCOMPARE(reply->bytesAvailable(), responseBody.length());
+ QCOMPARE(reply->readAll(), QByteArray("Hello"));
+ }
+ // In the `!success` case we need to wait for the server to emit this or it might cause issues
+ // in the next test running after this. In the `success` case we anyway expect it to have been
+ // received.
+ QTRY_VERIFY(serverGotSettingsACK);
+}
+
+void tst_Http2::unsupportedAuthenticateChallenge()
+{
+ clearHTTP2State();
+ serverPort = 0;
+
+ if (defaultConnectionType() == H2Type::h2c)
+ QSKIP("This test requires TLS with ALPN to work");
+
+ ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType()));
+ QByteArray responseBody = "Hello"_ba;
+ targetServer->setResponseBody(responseBody);
+ targetServer->setAuthenticationHeader("Bearer realm=\"qt.io accounts\"");
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ nRequests = 1;
+
+ QUrl url = requestUrl(defaultConnectionType());
+ url.setPath("/index.html");
+ QNetworkRequest request(url);
+
+ QByteArray expectedBody = "Hello, World!";
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+ QScopedPointer<QNetworkReply> reply;
+ reply.reset(manager->post(request, expectedBody));
+
+ bool authenticationRequested = false;
+ connect(manager.get(), &QNetworkAccessManager::authenticationRequired, reply.get(),
+ [&](QNetworkReply *, QAuthenticator *) {
+ authenticationRequested = true;
+ });
+
+ bool finishedReceived = false;
+ connect(reply.get(), &QNetworkReply::finished, reply.get(),
+ [&]() { finishedReceived = true; });
+ bool errorReceived = false;
+ connect(reply.get(), &QNetworkReply::errorOccurred, reply.get(),
+ [&]() { errorReceived = true; });
+
+ QSet<quint32> receivedDataOnStreams;
+ connect(targetServer.get(), &Http2Server::receivedDATAFrame, reply.get(),
+ [&receivedDataOnStreams](quint32 streamID, const QByteArray &body) {
+ Q_UNUSED(body);
+ receivedDataOnStreams.insert(streamID);
+ });
+
+ // Use queued connection so that the finished signal can be emitted and the
+ // isFinished property can be set.
+ connect(reply.get(), &QNetworkReply::errorOccurred, this,
+ &tst_Http2::replyFinishedWithError, Qt::QueuedConnection);
+
+ // Since we're using self-signed certificates, ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+ STOP_ON_FAILURE
+ QVERIFY2(reply->isFinished(),
+ "The reply should error out if authentication fails, or finish if it succeeds");
+
+ QCOMPARE(reply->error(), QNetworkReply::AuthenticationRequiredError);
+ QVERIFY(reply->isFinished());
+ QVERIFY(errorReceived);
+ QVERIFY(finishedReceived);
+ QCOMPARE(receivedDataOnStreams.size(), 1);
+ QVERIFY(receivedDataOnStreams.contains(1)); // the original, failed, request
+
+ QVERIFY(!authenticationRequested);
+
+ // We should not have sent any authentication headers to the server, since
+ // we don't support the challenge.
+ const QByteArray reqAuthHeader = targetServer->requestAuthorizationHeader();
+ QVERIFY(reqAuthHeader.isEmpty());
+
+ // In the `!success` case we need to wait for the server to emit this or it might cause issues
+ // in the next test running after this. In the `success` case we anyway expect it to have been
+ // received.
+ QTRY_VERIFY(serverGotSettingsACK);
+
+}
+
+void tst_Http2::h2cAllowedAttribute_data()
+{
+ QTest::addColumn<bool>("h2cAllowed");
+ QTest::addColumn<bool>("useAttribute"); // true: use attribute, false: use environment variable
+ QTest::addColumn<bool>("success");
+
+ QTest::addRow("h2c-not-allowed") << false << false << false;
+ // Use the attribute to enable/disable the H2C:
+ QTest::addRow("attribute") << true << true << true;
+ // Use the QT_NETWORK_H2C_ALLOWED environment variable to enable/disable the H2C:
+ QTest::addRow("environment-variable") << true << false << true;
+}
+
+void tst_Http2::h2cAllowedAttribute()
+{
+ QFETCH(const bool, h2cAllowed);
+ QFETCH(const bool, useAttribute);
+ QFETCH(const bool, success);
+
+ clearHTTP2State();
+ serverPort = 0;
+
+ ServerPtr targetServer(newServer(defaultServerSettings, H2Type::h2c));
+ targetServer->setResponseBody("Hello");
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ nRequests = 1;
+
+ auto url = requestUrl(H2Type::h2c);
+ url.setPath("/index.html");
+ QNetworkRequest request(url);
+ if (h2cAllowed) {
+ if (useAttribute)
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
+ else
+ qputenv("QT_NETWORK_H2C_ALLOWED", "1");
+ }
+ auto envCleanup = qScopeGuard([]() { qunsetenv("QT_NETWORK_H2C_ALLOWED"); });
+
+ QScopedPointer<QNetworkReply> reply;
+ reply.reset(manager->get(request));
+
+ if (success)
+ connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+ else
+ connect(reply.get(), &QNetworkReply::errorOccurred, this, &tst_Http2::replyFinishedWithError);
+
+ // Since we're using self-signed certificates,
+ // ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+ STOP_ON_FAILURE
+
+ if (!success) {
+ QCOMPARE(reply->error(), QNetworkReply::ConnectionRefusedError);
+ } else {
+ QCOMPARE(reply->readAll(), QByteArray("Hello"));
+ QTRY_VERIFY(serverGotSettingsACK);
+ }
+}
+
+void tst_Http2::redirect_data()
+{
+ QTest::addColumn<int>("maxRedirects");
+ QTest::addColumn<int>("redirectCount");
+ QTest::addColumn<bool>("success");
+
+ QTest::addRow("1-redirects-none-allowed-failure") << 0 << 1 << false;
+ QTest::addRow("1-redirects-success") << 1 << 1 << true;
+ QTest::addRow("2-redirects-1-allowed-failure") << 1 << 2 << false;
+}
+
+void tst_Http2::redirect()
+{
+ QFETCH(const int, maxRedirects);
+ QFETCH(const int, redirectCount);
+ QFETCH(const bool, success);
+ const QByteArray redirectUrl = "/b.html"_ba;
+
+ clearHTTP2State();
+ serverPort = 0;
+
+ ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType()));
+ targetServer->setRedirect(redirectUrl, redirectCount);
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ nRequests = 1;
+
+ auto originalUrl = requestUrl(defaultConnectionType());
+ auto url = originalUrl;
+ url.setPath("/index.html");
+ QNetworkRequest request(url);
+ request.setMaximumRedirectsAllowed(maxRedirects);
+ // H2C might be used on macOS where SecureTransport doesn't support server-side ALPN
+ qputenv("QT_NETWORK_H2C_ALLOWED", "1");
+ auto envCleanup = qScopeGuard([]() { qunsetenv("QT_NETWORK_H2C_ALLOWED"); });
+
+ QScopedPointer<QNetworkReply> reply;
+ reply.reset(manager->get(request));
+
+ if (success) {
+ connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+ } else {
+ connect(reply.get(), &QNetworkReply::errorOccurred, this,
+ &tst_Http2::replyFinishedWithError);
+ }
+
+ // Since we're using self-signed certificates,
+ // ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+ STOP_ON_FAILURE
+
+ QCOMPARE(nRequests, 0);
+ if (success) {
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+ QCOMPARE(reply->url().toString(),
+ originalUrl.resolved(QString::fromLatin1(redirectUrl)).toString());
+ } else if (maxRedirects < redirectCount) {
+ QCOMPARE(reply->error(), QNetworkReply::TooManyRedirectsError);
+ }
+ QTRY_VERIFY(serverGotSettingsACK);
+}
+
+void tst_Http2::trailingHEADERS()
+{
+ clearHTTP2State();
+ serverPort = 0;
+
+ ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType()));
+ targetServer->setSendTrailingHEADERS(true);
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ nRequests = 1;
+
+ const auto url = requestUrl(defaultConnectionType());
+ QNetworkRequest request(url);
+ // H2C might be used on macOS where SecureTransport doesn't support server-side ALPN
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
+
+ std::unique_ptr<QNetworkReply> reply{ manager->get(request) };
+ connect(reply.get(), &QNetworkReply::finished, this, &tst_Http2::replyFinished);
+
+ // Since we're using self-signed certificates, ignore SSL errors:
+ reply->ignoreSslErrors();
+
+ runEventLoop();
+ STOP_ON_FAILURE
+
+ QCOMPARE(nRequests, 0);
+
+ QCOMPARE(reply->error(), QNetworkReply::NoError);
+ QTRY_VERIFY(serverGotSettingsACK);
+}
+
+void tst_Http2::duplicateRequestsWithAborts()
+{
+ clearHTTP2State();
+ serverPort = 0;
+
+ ServerPtr targetServer(newServer(defaultServerSettings, defaultConnectionType()));
+
+ QMetaObject::invokeMethod(targetServer.data(), "startServer", Qt::QueuedConnection);
+ runEventLoop();
+
+ QVERIFY(serverPort != 0);
+
+ constexpr int ExpectedSuccessfulRequests = 1;
+ nRequests = ExpectedSuccessfulRequests;
+
+ const auto url = requestUrl(defaultConnectionType());
+ QNetworkRequest request(url);
+ // H2C might be used on macOS where SecureTransport doesn't support server-side ALPN
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
+
+ qint32 finishedCount = 0;
+ auto connectToSlots = [this, &finishedCount](QNetworkReply *reply){
+ const auto onFinished = [&finishedCount, reply, this]() {
+ ++finishedCount;
+ if (reply->error() == QNetworkReply::NoError)
+ replyFinished();
+ };
+ connect(reply, &QNetworkReply::finished, reply, onFinished);
+ };
+
+ std::vector<QNetworkReply *> replies;
+ for (qint32 i = 0; i < 3; ++i) {
+ auto &reply = replies.emplace_back(manager->get(request));
+ connectToSlots(reply);
+ if (i < 2) // Delete and abort all-but-one:
+ reply->deleteLater();
+ // Since we're using self-signed certificates, ignore SSL errors:
+ reply->ignoreSslErrors();
+ }
+
+ runEventLoop();
+ STOP_ON_FAILURE
+
+ QCOMPARE(nRequests, 0);
+ QCOMPARE(finishedCount, ExpectedSuccessfulRequests);
+}
+
void tst_Http2::serverStarted(quint16 port)
{
serverPort = port;
@@ -894,6 +1556,7 @@ void tst_Http2::clearHTTP2State()
windowUpdates = 0;
prefaceOK = false;
serverGotSettingsACK = false;
+ POSTResponseHEADOnly = true;
}
void tst_Http2::runEventLoop(int ms)
@@ -939,6 +1602,7 @@ void tst_Http2::sendRequest(int streamNumber,
url.setPath(QString("/stream%1.html").arg(streamNumber));
QNetworkRequest request(url);
+ request.setAttribute(QNetworkRequest::Http2CleartextAllowedAttribute, true);
request.setAttribute(QNetworkRequest::Http2AllowedAttribute, QVariant(true));
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/plain"));
@@ -1026,7 +1690,7 @@ void tst_Http2::receivedData(quint32 streamID)
Q_ASSERT(srv);
QMetaObject::invokeMethod(srv, "sendResponse", Qt::QueuedConnection,
Q_ARG(quint32, streamID),
- Q_ARG(bool, true /*HEADERS only*/));
+ Q_ARG(bool, POSTResponseHEADOnly /*true = HEADERS only*/));
}
void tst_Http2::windowUpdated(quint32 streamID)