Add support for parsing and processing the HTTP/3 Priority header in QUIC.

Default disabled.

PiperOrigin-RevId: 961212830
diff --git a/quiche/quic/core/http/http_constants.cc b/quiche/quic/core/http/http_constants.cc
index 7dbb585..eb43a2f 100644
--- a/quiche/quic/core/http/http_constants.cc
+++ b/quiche/quic/core/http/http_constants.cc
@@ -30,6 +30,7 @@
 }
 
 ABSL_CONST_INIT const absl::string_view kUserAgentHeaderName = "user-agent";
+ABSL_CONST_INIT const absl::string_view kPriorityHeaderName = "priority";
 
 #undef RETURN_STRING_LITERAL  // undef for jumbo builds
 
diff --git a/quiche/quic/core/http/http_constants.h b/quiche/quic/core/http/http_constants.h
index 11e53be..21ecf26 100644
--- a/quiche/quic/core/http/http_constants.h
+++ b/quiche/quic/core/http/http_constants.h
@@ -68,6 +68,8 @@
 
 ABSL_CONST_INIT QUICHE_EXPORT extern const absl::string_view
     kUserAgentHeaderName;
+ABSL_CONST_INIT QUICHE_EXPORT extern const absl::string_view
+    kPriorityHeaderName;
 
 }  // namespace quic
 
diff --git a/quiche/quic/core/http/quic_spdy_session.h b/quiche/quic/core/http/quic_spdy_session.h
index 46cee7b..5e96a4d 100644
--- a/quiche/quic/core/http/quic_spdy_session.h
+++ b/quiche/quic/core/http/quic_spdy_session.h
@@ -331,6 +331,11 @@
 
   bool allow_extended_connect() const { return allow_extended_connect_; }
 
+  bool process_priority_header() const { return process_priority_header_; }
+  void set_process_priority_header(bool process_priority_header) {
+    process_priority_header_ = process_priority_header;
+  }
+
   // Returns true if the session has active request streams.
   bool HasActiveRequestStreams() const;
 
@@ -745,6 +750,9 @@
   // server cannot initiate WebTransport sessions.
   absl::flat_hash_map<WebTransportHttp3Version, QuicStreamCount>
       max_webtransport_sessions_;
+
+  // Whether to process priority headers from the client.
+  bool process_priority_header_ = false;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index a1e8107..a3509a6 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -2398,6 +2398,282 @@
             stream2->priority_source());
 }
 
+TEST_P(QuicSpdySessionTestServer, OnPriorityHeader) {
+  Initialize();
+  session_->set_process_priority_header(true);
+  if (!VersionIsIetfQuic(transport_version())) {
+    return;
+  }
+
+  CompleteHandshake();
+
+  const QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  TestStream* stream = session_->CreateIncomingStream(stream_id);
+
+  // Send request headers with Priority header.
+  quiche::HttpHeaderBlock headers;
+  headers[":path"] = "/foo";
+  headers[":authority"] = "www.google.com";
+  headers[":method"] = "GET";
+  headers[":scheme"] = "https";
+  headers["priority"] = "u=2, i";
+
+  QuicHeaderList header_list = AsHeaderList(headers);
+  stream->OnStreamHeaderList(
+      /* fin = */ false, header_list.uncompressed_header_bytes(), header_list);
+
+  EXPECT_EQ(QuicStreamPriority(HttpStreamPriority{2u, true}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::SET_BY_REQUEST_HEADER, stream->priority_source());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnPriorityHeaderAndPriorityUpdateFrame) {
+  Initialize();
+  session_->set_process_priority_header(true);
+
+  if (!VersionIsIetfQuic(transport_version())) {
+    return;
+  }
+
+  StrictMock<MockHttp3DebugVisitor> debug_visitor;
+  session_->set_debug_visitor(&debug_visitor);
+  EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_));
+  CompleteHandshake();
+
+  // Create control stream and send SETTINGS.
+  QuicStreamId receive_control_stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  absl::string_view stream_type(type, 1);
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame data1(receive_control_stream_id, false, offset, stream_type);
+  offset += stream_type.length();
+  EXPECT_CALL(debug_visitor,
+              OnPeerControlStreamCreated(receive_control_stream_id));
+  session_->OnStreamFrame(data1);
+  std::string serialized_settings = HttpEncoder::SerializeSettingsFrame({});
+  QuicStreamFrame data2(receive_control_stream_id, false, offset,
+                        serialized_settings);
+  offset += serialized_settings.length();
+  EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+  session_->OnStreamFrame(data2);
+
+  const QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+
+  // 1. PRIORITY_UPDATE frame arrives FIRST (before stream creation).
+  PriorityUpdateFrame priority_update{stream_id, "u=5, i"};
+  std::string serialized_priority_update =
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update);
+  QuicStreamFrame stream_frame3(receive_control_stream_id,
+                                /* fin = */ false, offset,
+                                serialized_priority_update);
+  EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update));
+  session_->OnStreamFrame(stream_frame3);
+
+  // 2. Stream is created. Priority from frame (u=5, i) is applied.
+  TestStream* stream = session_->CreateIncomingStream(stream_id);
+  EXPECT_EQ(QuicStreamPriority(HttpStreamPriority{5u, true}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::SET_BY_PRIORITY_UPDATE, stream->priority_source());
+
+  // 3. HEADERS frame with Priority header (u=2) arrives SECOND.
+  quiche::HttpHeaderBlock headers;
+  headers[":path"] = "/foo";
+  headers[":authority"] = "www.google.com";
+  headers[":method"] = "GET";
+  headers[":scheme"] = "https";
+  headers["priority"] = "u=2";  // Different priority!
+
+  QuicHeaderList header_list = AsHeaderList(headers);
+  // It should IGNORE the Priority header because priority was set by frame!
+  stream->OnStreamHeaderList(
+      /* fin = */ false, header_list.uncompressed_header_bytes(), header_list);
+
+  // Priority should STILL be u=5, i!
+  EXPECT_EQ(QuicStreamPriority(HttpStreamPriority{5u, true}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::SET_BY_PRIORITY_UPDATE, stream->priority_source());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+       OnPriorityUpdateFrameArrivesAfterStreamCreationButBeforeHeaders) {
+  Initialize();
+  session_->set_process_priority_header(true);
+  if (!VersionIsIetfQuic(transport_version())) {
+    return;
+  }
+
+  StrictMock<MockHttp3DebugVisitor> debug_visitor;
+  session_->set_debug_visitor(&debug_visitor);
+  EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_));
+  CompleteHandshake();
+
+  // Create control stream and send SETTINGS.
+  QuicStreamId receive_control_stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  absl::string_view stream_type(type, 1);
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame data1(receive_control_stream_id, false, offset, stream_type);
+  offset += stream_type.length();
+  EXPECT_CALL(debug_visitor,
+              OnPeerControlStreamCreated(receive_control_stream_id));
+  session_->OnStreamFrame(data1);
+  std::string serialized_settings = HttpEncoder::SerializeSettingsFrame({});
+  QuicStreamFrame data2(receive_control_stream_id, false, offset,
+                        serialized_settings);
+  offset += serialized_settings.length();
+  EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+  session_->OnStreamFrame(data2);
+
+  const QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+
+  // 1. Stream is created FIRST. It gets default priority.
+  TestStream* stream = session_->CreateIncomingStream(stream_id);
+  EXPECT_EQ(QuicStreamPriority(
+                HttpStreamPriority{HttpStreamPriority::kDefaultUrgency,
+                                   HttpStreamPriority::kDefaultIncremental}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::NOT_SET, stream->priority_source());
+
+  // 2. PRIORITY_UPDATE frame arrives SECOND. It updates priority to u=1.
+  PriorityUpdateFrame priority_update{stream_id, "u=1"};
+  std::string serialized_priority_update =
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update);
+  QuicStreamFrame stream_frame3(receive_control_stream_id,
+                                /* fin = */ false, offset,
+                                serialized_priority_update);
+  EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update));
+  session_->OnStreamFrame(stream_frame3);
+  EXPECT_EQ(QuicStreamPriority(HttpStreamPriority{
+                1u, HttpStreamPriority::kDefaultIncremental}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::SET_BY_PRIORITY_UPDATE, stream->priority_source());
+
+  // 3. HEADERS frame with Priority header (u=2) arrives THIRD.
+  quiche::HttpHeaderBlock headers;
+  headers[":path"] = "/foo";
+  headers[":authority"] = "www.google.com";
+  headers[":method"] = "GET";
+  headers[":scheme"] = "https";
+  headers["priority"] = "u=2";  // Different priority!
+
+  QuicHeaderList header_list = AsHeaderList(headers);
+  // It should IGNORE the Priority header because priority was set by frame!
+  stream->OnStreamHeaderList(
+      /* fin = */ false, header_list.uncompressed_header_bytes(), header_list);
+
+  // Priority should STILL be u=1!
+  EXPECT_EQ(QuicStreamPriority(HttpStreamPriority{
+                1u, HttpStreamPriority::kDefaultIncremental}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::SET_BY_PRIORITY_UPDATE, stream->priority_source());
+}
+
+TEST_P(QuicSpdySessionTestServer,
+       OnPriorityHeaderArrivesThenPriorityUpdateFrame) {
+  Initialize();
+  session_->set_process_priority_header(true);
+  if (!VersionIsIetfQuic(transport_version())) {
+    return;
+  }
+
+  StrictMock<MockHttp3DebugVisitor> debug_visitor;
+  session_->set_debug_visitor(&debug_visitor);
+  EXPECT_CALL(debug_visitor, OnSettingsFrameSent(_));
+  CompleteHandshake();
+
+  // Create control stream and send SETTINGS.
+  QuicStreamId receive_control_stream_id =
+      GetNthClientInitiatedUnidirectionalStreamId(transport_version(), 3);
+  char type[] = {kControlStream};
+  absl::string_view stream_type(type, 1);
+  QuicStreamOffset offset = 0;
+  QuicStreamFrame data1(receive_control_stream_id, false, offset, stream_type);
+  offset += stream_type.length();
+  EXPECT_CALL(debug_visitor,
+              OnPeerControlStreamCreated(receive_control_stream_id));
+  session_->OnStreamFrame(data1);
+  std::string serialized_settings = HttpEncoder::SerializeSettingsFrame({});
+  QuicStreamFrame data2(receive_control_stream_id, false, offset,
+                        serialized_settings);
+  offset += serialized_settings.length();
+  EXPECT_CALL(debug_visitor, OnSettingsFrameReceived(_));
+  session_->OnStreamFrame(data2);
+
+  const QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+
+  // 1. Stream is created.
+  TestStream* stream = session_->CreateIncomingStream(stream_id);
+  EXPECT_EQ(PrioritySource::NOT_SET, stream->priority_source());
+
+  // 2. HEADERS frame with Priority header (u=2) arrives FIRST.
+  quiche::HttpHeaderBlock headers;
+  headers[":path"] = "/foo";
+  headers[":authority"] = "www.google.com";
+  headers[":method"] = "GET";
+  headers[":scheme"] = "https";
+  headers["priority"] = "u=2";
+
+  QuicHeaderList header_list = AsHeaderList(headers);
+  stream->OnStreamHeaderList(
+      /* fin = */ false, header_list.uncompressed_header_bytes(), header_list);
+
+  // Priority should be u=2!
+  EXPECT_EQ(QuicStreamPriority(HttpStreamPriority{
+                2u, HttpStreamPriority::kDefaultIncremental}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::SET_BY_REQUEST_HEADER, stream->priority_source());
+
+  // 3. PRIORITY_UPDATE frame arrives SECOND. It updates priority to u=5, i.
+  PriorityUpdateFrame priority_update{stream_id, "u=5, i"};
+  std::string serialized_priority_update =
+      HttpEncoder::SerializePriorityUpdateFrame(priority_update);
+  QuicStreamFrame stream_frame3(receive_control_stream_id,
+                                /* fin = */ false, offset,
+                                serialized_priority_update);
+  EXPECT_CALL(debug_visitor, OnPriorityUpdateFrameReceived(priority_update));
+  session_->OnStreamFrame(stream_frame3);
+
+  // Priority should be OVERRIDDEN to u=5, i!
+  EXPECT_EQ(QuicStreamPriority(HttpStreamPriority{5u, true}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::SET_BY_PRIORITY_UPDATE, stream->priority_source());
+}
+
+TEST_P(QuicSpdySessionTestServer, OnPriorityHeaderDisabled) {
+  Initialize();
+  session_->set_process_priority_header(false);
+  if (!VersionIsIetfQuic(transport_version())) {
+    return;
+  }
+
+  CompleteHandshake();
+
+  const QuicStreamId stream_id = GetNthClientInitiatedBidirectionalId(0);
+  TestStream* stream = session_->CreateIncomingStream(stream_id);
+
+  // Send request headers with Priority header.
+  quiche::HttpHeaderBlock headers;
+  headers[":path"] = "/foo";
+  headers[":authority"] = "www.google.com";
+  headers[":method"] = "GET";
+  headers[":scheme"] = "https";
+  headers["priority"] = "u=2, i";
+
+  QuicHeaderList header_list = AsHeaderList(headers);
+  stream->OnStreamHeaderList(
+      /* fin = */ false, header_list.uncompressed_header_bytes(), header_list);
+
+  // Priority should remain the DEFAULT (u=3, no incremental) because flag is
+  // false!
+  EXPECT_EQ(QuicStreamPriority(
+                HttpStreamPriority{HttpStreamPriority::kDefaultUrgency,
+                                   HttpStreamPriority::kDefaultIncremental}),
+            stream->priority());
+  EXPECT_EQ(PrioritySource::NOT_SET, stream->priority_source());
+}
+
 TEST_P(QuicSpdySessionTestServer, OnInvalidPriorityUpdateFrame) {
   Initialize();
   if (!VersionIsIetfQuic(transport_version())) {
diff --git a/quiche/quic/core/http/quic_spdy_stream.cc b/quiche/quic/core/http/quic_spdy_stream.cc
index f58b650..245d6ca 100644
--- a/quiche/quic/core/http/quic_spdy_stream.cc
+++ b/quiche/quic/core/http/quic_spdy_stream.cc
@@ -678,6 +678,7 @@
 
   if (!header_too_large) {
     MaybeProcessReceivedWebTransportHeaders();
+    MaybeProcessPriorityHeader();
   }
 
   if (VersionIsIetfQuic(transport_version())) {
@@ -1443,6 +1444,40 @@
       std::make_unique<WebTransportHttp3>(spdy_session_, this, id());
 }
 
+void QuicSpdyStream::MaybeProcessPriorityHeader() {
+  if (!spdy_session_->process_priority_header()) {
+    return;
+  }
+  if (!VersionIsIetfQuic(transport_version())) {
+    return;
+  }
+  if (session()->perspective() != Perspective::IS_SERVER) {
+    return;
+  }
+  if (priority_source() == PrioritySource::SET_BY_PRIORITY_UPDATE) {
+    return;
+  }
+  std::string priority_value;
+  for (const auto& [header_name, header_value] : header_list_) {
+    if (quiche::QuicheTextUtils::ToLower(header_name) == kPriorityHeaderName) {
+      priority_value = header_value;
+      break;
+    }
+  }
+  if (priority_value.empty()) {
+    return;
+  }
+  std::optional<HttpStreamPriority> priority =
+      ParsePriorityFieldValue(priority_value);
+  if (priority.has_value()) {
+    SetPriority(QuicStreamPriority(*priority));
+    set_priority_source(PrioritySource::SET_BY_REQUEST_HEADER);
+  } else {
+    QUIC_DVLOG(1) << "Stream " << id()
+                  << " ignoring malformed Priority header: " << priority_value;
+  }
+}
+
 void QuicSpdyStream::MaybeProcessSentWebTransportHeaders(
     quiche::HttpHeaderBlock& headers) {
   if (!spdy_session_->SupportsWebTransport()) {
diff --git a/quiche/quic/core/http/quic_spdy_stream.h b/quiche/quic/core/http/quic_spdy_stream.h
index 5adb54e..54d58aa 100644
--- a/quiche/quic/core/http/quic_spdy_stream.h
+++ b/quiche/quic/core/http/quic_spdy_stream.h
@@ -474,6 +474,7 @@
 
   void MaybeProcessSentWebTransportHeaders(quiche::HttpHeaderBlock& headers);
   void MaybeProcessReceivedWebTransportHeaders();
+  void MaybeProcessPriorityHeader();
 
   // Writes HTTP/3 DATA frame header. If |force_write| is true, use
   // WriteOrBufferData if send buffer cannot accomodate the header + data.