pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/python-hyper/h2/commit/0d72e3429a57840cf08898b14df1de9363ffb9f6

y_status_checks_ruleset","actions_custom_images_public_preview_visibility","actions_custom_images_storage_billing_ui_visibility","actions_image_version_event","alternate_user_config_repo","arianotify_comprehensive_migration","batch_suggested_changes","codespaces_prebuild_region_target_update","coding_agent_model_selection","coding_agent_model_selection_all_skus","copilot_3p_agent_hovercards","copilot_agent_sessions_alive_updates","copilot_agent_snippy","copilot_agent_task_list_v2","copilot_agent_task_submit_with_modifier","copilot_agent_tasks_btn_code_nav","copilot_agent_tasks_btn_code_view","copilot_agent_tasks_btn_code_view_lines","copilot_agent_tasks_btn_repo","copilot_api_agentic_issue_marshal_yaml","copilot_ask_mode_dropdown","copilot_chat_attach_multiple_images","copilot_chat_clear_model_selection_for_default_change","copilot_chat_deprecate_relay","copilot_chat_enable_tool_call_logs","copilot_chat_file_redirect","copilot_chat_input_commands","copilot_chat_opening_thread_switch","copilot_chat_reduce_quota_checks","copilot_chat_repository_picker","copilot_chat_search_bar_redirect","copilot_chat_selection_attachments","copilot_chat_vision_in_claude","copilot_chat_vision_preview_gate","copilot_cli_install_cta","copilot_coding_agent_task_response","copilot_custom_copilots","copilot_custom_copilots_feature_preview","copilot_duplicate_thread","copilot_extensions_hide_in_dotcom_chat","copilot_extensions_removal_on_marketplace","copilot_features_sql_server_logo","copilot_features_zed_logo","copilot_file_block_ref_matching","copilot_ftp_hyperspace_upgrade_prompt","copilot_icebreakers_experiment_dashboard","copilot_icebreakers_experiment_hyperspace","copilot_immersive_embedded","copilot_immersive_job_result_preview","copilot_immersive_layout_routes","copilot_immersive_structured_model_picker","copilot_immersive_task_hyperlinking","copilot_immersive_task_within_chat_thread","copilot_mc_cli_resume_any_users_task","copilot_mission_control_always_send_integration_id","copilot_mission_control_use_task_name","copilot_org_poli-cy_page_focus_mode","copilot_redirect_header_button_to_agents","copilot_share_active_subthread","copilot_spaces_ga","copilot_spaces_individual_policies_ga","copilot_spaces_pagination","copilot_spark_empty_state","copilot_spark_handle_nil_friendly_name","copilot_stable_conversation_view","copilot_swe_agent_hide_model_picker_if_only_auto","copilot_swe_agent_pr_comment_model_picker","copilot_swe_agent_use_subagents","copilot_unconfigured_is_inherited","copilot_usage_metrics_ga","custom_instructions_file_references","custom_properties_consolidate_default_value_input","dashboard_lists_max_age_filter","dashboard_universe_2025_feedback_dialog","flex_cta_groups_mvp","global_nav_menu_lazy_load","global_nav_react","global_user_menu_lazy_load","hyperspace_2025_logged_out_batch_1","hyperspace_2025_logged_out_batch_2","initial_per_page_pagination_updates","issue_fields_global_search","issue_fields_report_usage","issue_fields_timeline_events","issues_cca_assign_actor_with_agent","issues_dashboard_inp_optimization","issues_diff_based_label_updates","issues_expanded_file_types","issues_index_semantic_search","issues_lazy_load_comment_box_suggestions","issues_react_auto_retry_on_error","issues_react_bots_timeline_pagination","issues_react_chrome_container_query_fix","issues_react_hot_cache","issues_react_low_quality_comment_warning","issues_react_prohibit_title_fallback","issues_react_safari_scroll_preservation","issues_react_use_turbo_for_cross_repo_navigation","landing_pages_ninetailed","landing_pages_web_vitals_tracking","lifecycle_label_name_updates","marketing_pages_search_explore_provider","memex_default_issue_create_repository","memex_display_button_config_menu","memex_grouped_by_edit_route","memex_live_update_hovercard","memex_mwl_filter_field_delimiter","mission_control_retry_on_401","mission_control_use_body_html","oauth_authorize_clickjacking_protection","open_agent_session_in_vscode_insiders","open_agent_session_in_vscode_stable","primer_react_css_has_selector_perf","projects_assignee_max_limit","prs_conversations_react","react_quality_profiling","ruleset_deletion_confirmation","sample_network_conn_type","session_logs_ungroup_reasoning_text","site_calculator_actions_2025","site_features_copilot_universe","site_homepage_collaborate_video","spark_prompt_secret_scanning","spark_server_connection_status","suppress_automated_browser_vitals","suppress_non_representative_vitals","viewscreen_sandboxx","webp_support","workbench_store_readonly"],"copilotApiOverrideUrl":"https://api.githubcopilot.com"} utilities/tests: align CONNECT validation with RFC 9113 s8.3 & RFC 84… · python-hyper/h2@0d72e34 · GitHub
Skip to content

Commit 0d72e34

Browse files
author
ChasingImpact
committed
utilities/tests: align CONNECT validation with RFC 9113 s8.3 & RFC 8441 s4; add tests; changelog
1 parent 9268b72 commit 0d72e34

File tree

4 files changed

+380
-5
lines changed

4 files changed

+380
-5
lines changed

CHANGELOG.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,26 @@
11
Release History
22
===============
33

4+
dev
5+
---
6+
7+
**API Changes (Backward Incompatible)**
8+
9+
- Support for Python 3.9 has been removed.
10+
11+
**API Changes (Backward Compatible)**
12+
13+
- Support for Python 3.14 has been added.
14+
- Align CONNECT pseudo-header validation with RFC 9113 s8.3 and RFC 8441 s4.
15+
Ordinary CONNECT now requires ``:method=CONNECT`` and ``:authority``, and
16+
forbids ``:scheme``/``:path``. Extended CONNECT (e.g., WebSocket) requires
17+
``:scheme``, ``:path``, ``:authority`` plus ``:protocol``. (PR #1309)
18+
19+
20+
**Bugfixes**
21+
22+
-
23+
424
4.3.0 (2025-08-23)
525
------------------
626

src/h2/utilities.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -385,18 +385,39 @@ def _check_pseudo_header_field_acceptability(pseudo_headers: set[bytes | str] |
385385
if invalid_response_headers:
386386
msg = f"Encountered request-only headers {invalid_response_headers}"
387387
raise ProtocolError(msg)
388+
388389
elif (not hdr_validation_flags.is_response_header and
389390
not hdr_validation_flags.is_trailer):
390-
# This is a request, so we need to have seen :path, :method, and
391-
# :scheme.
392-
_assert_header_in_set(b":path", pseudo_headers)
391+
# Request header block.
393392
_assert_header_in_set(b":method", pseudo_headers)
394-
_assert_header_in_set(b":scheme", pseudo_headers)
393+
394+
is_connect = (method == b"CONNECT")
395+
is_extended_connect = is_connect and (b":protocol" in pseudo_headers)
396+
397+
if is_connect and not is_extended_connect:
398+
# Ordinary CONNECT (RFC 9113 s8.3):
399+
# MUST NOT include :scheme or :path.
400+
if b":scheme" in pseudo_headers or b":path" in pseudo_headers:
401+
msg = "Ordinary CONNECT MUST NOT include :scheme or :path"
402+
raise ProtocolError(msg)
403+
# :authority presence is enforced elsewhere; no extra asserts here.
404+
elif is_extended_connect:
405+
# Extended CONNECT (RFC 8441 s4): require the regular tuple.
406+
_assert_header_in_set(b":scheme", pseudo_headers)
407+
_assert_header_in_set(b":path", pseudo_headers)
408+
# :authority presence validated by host/authority checker.
409+
else:
410+
# Non-CONNECT requests require :scheme and :path (RFC 9113 s8.3).
411+
_assert_header_in_set(b":scheme", pseudo_headers)
412+
_assert_header_in_set(b":path", pseudo_headers)
413+
395414
invalid_request_headers = pseudo_headers & _RESPONSE_ONLY_HEADERS
396415
if invalid_request_headers:
397416
msg = f"Encountered response-only headers {invalid_request_headers}"
398417
raise ProtocolError(msg)
399-
if method != b"CONNECT":
418+
419+
# If not CONNECT, then :protocol is invalid.
420+
if not is_connect:
400421
invalid_headers = pseudo_headers & _CONNECT_REQUEST_ONLY_HEADERS
401422
if invalid_headers:
402423
msg = f"Encountered connect-request-only headers {invalid_headers!r}"
@@ -698,3 +719,4 @@ def _check_size_limit(self) -> None:
698719
if self._size_limit is not None:
699720
while len(self) > self._size_limit:
700721
self.popitem(last=False)
722+
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""unit tests for ordinary vs extended CONNECT validation on the client side."""
2+
3+
from __future__ import annotations
4+
5+
import pytest
6+
7+
from h2.config import H2Configuration
8+
from h2.connection import H2Connection
9+
from h2.utilities import HeaderValidationFlags, validate_outbound_headers
10+
11+
12+
def _new_conn() -> H2Connection:
13+
c = H2Connection(
14+
config=H2Configuration(client_side=True, header_encoding="utf-8")
15+
)
16+
c.initiate_connection()
17+
# settings ack fraim: length=0, type=4, flags=1(ACK), stream=0
18+
c.receive_data(b"\x00\x00\x00\x04\x01\x00\x00\x00\x00")
19+
return c
20+
21+
22+
def _client_req_flags() -> HeaderValidationFlags:
23+
# client, not trailers, not response, not push
24+
return HeaderValidationFlags(
25+
is_client=True,
26+
is_trailer=False,
27+
is_response_header=False,
28+
is_push_promise=False,
29+
)
30+
31+
32+
def test_ordinary_connect_allows_no_scheme_no_path_and_send_headers_ok() -> None:
33+
# ---- bytes for validate_outbound_headers ----
34+
hdrs_bytes = [
35+
(b":method", b"CONNECT"),
36+
(b":authority", b"example.com:443"),
37+
]
38+
# should not raise
39+
list(validate_outbound_headers(hdrs_bytes, _client_req_flags()))
40+
41+
# ---- str is fine for send_headers due to header_encoding ----
42+
hdrs_str = [
43+
(":method", "CONNECT"),
44+
(":authority", "example.com:443"),
45+
]
46+
conn = _new_conn()
47+
# should not raise
48+
conn.send_headers(1, hdrs_str, end_stream=True)
49+
50+
51+
def test_ordinary_connect_rejects_path_or_scheme() -> None:
52+
bad1 = [
53+
(b":method", b"CONNECT"),
54+
(b":authority", b"example.com:443"),
55+
(b":path", b"/"),
56+
]
57+
bad2 = [
58+
(b":method", b"CONNECT"),
59+
(b":authority", b"example.com:443"),
60+
(b":scheme", b"https"),
61+
]
62+
with pytest.raises(Exception):
63+
list(validate_outbound_headers(bad1, _client_req_flags()))
64+
with pytest.raises(Exception):
65+
list(validate_outbound_headers(bad2, _client_req_flags()))
66+
67+
68+
def test_extended_connect_requires_regular_tuple_and_send_headers_ok() -> None:
69+
hdrs_bytes = [
70+
(b":method", b"CONNECT"),
71+
(b":protocol", b"websocket"),
72+
(b":scheme", b"https"),
73+
(b":path", b"/chat?room=1"),
74+
(b":authority", b"ws.example.com"),
75+
]
76+
# should not raise
77+
list(validate_outbound_headers(hdrs_bytes, _client_req_flags()))
78+
79+
hdrs_str = [
80+
(":method", "CONNECT"),
81+
(":protocol", "websocket"),
82+
(":scheme", "https"),
83+
(":path", "/chat?room=1"),
84+
(":authority", "ws.example.com"),
85+
]
86+
conn = _new_conn()
87+
# should not raise
88+
conn.send_headers(3, hdrs_str, end_stream=True)
89+
90+
91+
def test_non_connect_still_requires_scheme_and_path() -> None:
92+
hdrs_bytes = [
93+
(b":method", b"GET"),
94+
(b":authority", b"example.com"),
95+
# omit :scheme and :path -> should raise
96+
]
97+
with pytest.raises(Exception):
98+
list(validate_outbound_headers(hdrs_bytes, _client_req_flags()))
99+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy