Parse the path component of the indication on the server.

gfe-relnote: n/a (not used in production)
PiperOrigin-RevId: 284179158
Change-Id: I5246996a99e93e5863502b0dbc950945cf1e6c25
diff --git a/quic/quic_transport/quic_transport_server_session.cc b/quic/quic_transport/quic_transport_server_session.cc
index 7f00acd..a6d7c21 100644
--- a/quic/quic_transport/quic_transport_server_session.cc
+++ b/quic/quic_transport/quic_transport_server_session.cc
@@ -4,9 +4,12 @@
 
 #include "net/third_party/quiche/src/quic/quic_transport/quic_transport_server_session.h"
 
+#include <algorithm>
 #include <memory>
+#include <string>
 
 #include "url/gurl.h"
+#include "url/url_constants.h"
 #include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
 #include "net/third_party/quiche/src/quic/core/quic_stream.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
@@ -28,6 +31,7 @@
     return true;
   }
 };
+
 }  // namespace
 
 QuicTransportServerSession::QuicTransportServerSession(
@@ -96,6 +100,7 @@
 
 bool QuicTransportServerSession::ClientIndicationParser::Parse() {
   bool origin_received = false;
+  bool path_received = false;
   while (!reader_.IsDoneReading()) {
     uint16_t key;
     if (!reader_.ReadUInt16(&key)) {
@@ -127,6 +132,14 @@
         break;
       }
 
+      case QuicTransportClientIndicationKeys::kPath: {
+        if (!ProcessPath(value)) {
+          return false;
+        }
+        path_received = true;
+        break;
+      }
+
       default:
         QUIC_DLOG(INFO) << "Unknown client indication key: " << key;
         break;
@@ -137,10 +150,40 @@
     Error("No origin received");
     return false;
   }
+  if (!path_received) {
+    Error("No path received");
+    return false;
+  }
 
   return true;
 }
 
+bool QuicTransportServerSession::ClientIndicationParser::ProcessPath(
+    QuicStringPiece path) {
+  if (path.empty() || path[0] != '/') {
+    // https://tools.ietf.org/html/draft-vvv-webtransport-quic-01#section-3.2.2
+    Error("Path must begin with a '/'");
+    return false;
+  }
+
+  // TODO(b/145674008): use the SNI value from the handshake instead of the IP
+  // address.
+  std::string url_text =
+      QuicStrCat(url::kQuicTransportScheme, url::kStandardSchemeSeparator,
+                 session_->self_address().ToString(), path);
+  GURL url{url_text};
+  if (!url.is_valid()) {
+    Error("Invalid path specified");
+    return false;
+  }
+
+  if (!session_->visitor_->ProcessPath(url)) {
+    Error("Specified path rejected");
+    return false;
+  }
+  return true;
+}
+
 void QuicTransportServerSession::ClientIndicationParser::Error(
     const std::string& error_message) {
   session_->connection()->CloseConnection(
diff --git a/quic/quic_transport/quic_transport_server_session.h b/quic/quic_transport/quic_transport_server_session.h
index b3fcfa0..8b0c063 100644
--- a/quic/quic_transport/quic_transport_server_session.h
+++ b/quic/quic_transport/quic_transport_server_session.h
@@ -5,6 +5,7 @@
 #ifndef QUICHE_QUIC_QUIC_TRANSPORT_QUIC_TRANSPORT_SERVER_SESSION_H_
 #define QUICHE_QUIC_QUIC_TRANSPORT_QUIC_TRANSPORT_SERVER_SESSION_H_
 
+#include "url/gurl.h"
 #include "url/origin.h"
 #include "net/third_party/quiche/src/quic/core/quic_connection.h"
 #include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
@@ -25,7 +26,14 @@
    public:
     virtual ~ServerVisitor() {}
 
+    // Allows the server to decide whether the specified origin is allowed to
+    // connect to it.
     virtual bool CheckOrigin(url::Origin origin) = 0;
+
+    // Indicates that the server received a path parameter from the client.  The
+    // path parameter is parsed, and can be retrived from url.path() and
+    // url.query().  If this method returns false, the connection is closed.
+    virtual bool ProcessPath(const GURL& url) = 0;
   };
 
   QuicTransportServerSession(QuicConnection* connection,
@@ -90,6 +98,9 @@
     void Error(const std::string& error_message);
     void ParseError(QuicStringPiece error_message);
 
+    // Processes the path portion of the client indication.
+    bool ProcessPath(QuicStringPiece path);
+
     QuicTransportServerSession* session_;
     QuicDataReader reader_;
   };
diff --git a/quic/quic_transport/quic_transport_server_session_test.cc b/quic/quic_transport/quic_transport_server_session_test.cc
index 5e67748..efa668c 100644
--- a/quic/quic_transport/quic_transport_server_session_test.cc
+++ b/quic/quic_transport/quic_transport_server_session_test.cc
@@ -36,7 +36,12 @@
 
 constexpr char kTestOrigin[] = "https://test-origin.test";
 constexpr char kTestOriginClientIndication[] =
-    "\0\0\0\x18https://test-origin.test";
+    "\0\0"                      // key (0x0000, origin)
+    "\0\x18"                    // length
+    "https://test-origin.test"  // value
+    "\0\x01"                    // key (0x0001, path)
+    "\0\x05"                    // length
+    "/test";                    // value
 const url::Origin GetTestOrigin() {
   return url::Origin::Create(GURL(kTestOrigin));
 }
@@ -75,6 +80,8 @@
     if (!GetQuicReloadableFlag(quic_version_negotiated_by_default_at_server)) {
       crypto_stream_->OnSuccessfulVersionNegotiation(GetVersions()[0]);
     }
+    ON_CALL(visitor_, ProcessPath(_))
+        .WillByDefault(DoAll(SaveArg<0>(&path_), Return(true)));
   }
 
   void Connect() {
@@ -101,6 +108,20 @@
                                             QuicStringPiece()));
   }
 
+  void ReceiveIndicationWithPath(QuicStringPiece path) {
+    constexpr char kTestOriginClientIndicationPrefix[] =
+        "\0\0"                      // key (0x0000, origin)
+        "\0\x18"                    // length
+        "https://test-origin.test"  // value
+        "\0\x01";                   // key (0x0001, path)
+    std::string indication{kTestOriginClientIndicationPrefix,
+                           sizeof(kTestOriginClientIndicationPrefix) - 1};
+    indication.push_back(static_cast<char>(path.size() >> 8));
+    indication.push_back(static_cast<char>(path.size() & 0xff));
+    indication += std::string{path};
+    ReceiveIndication(indication);
+  }
+
  protected:
   MockAlarmFactory alarm_factory_;
   MockQuicConnectionHelper helper_;
@@ -109,8 +130,9 @@
   QuicCryptoServerConfig crypto_config_;
   std::unique_ptr<QuicTransportServerSession> session_;
   QuicCompressedCertsCache compressed_certs_cache_;
-  testing::StrictMock<MockServerVisitor> visitor_;
+  testing::NiceMock<MockServerVisitor> visitor_;
   QuicCryptoServerStream* crypto_stream_;
+  GURL path_;
 };
 
 TEST_F(QuicTransportServerSessionTest, SuccessfulHandshake) {
@@ -122,6 +144,7 @@
   ReceiveIndication(GetTestOriginClientIndication());
   EXPECT_TRUE(session_->IsSessionReady());
   EXPECT_EQ(origin, GetTestOrigin());
+  EXPECT_EQ(path_.path(), "/test");
 }
 
 TEST_F(QuicTransportServerSessionTest, PiecewiseClientIndication) {
@@ -231,6 +254,41 @@
   EXPECT_FALSE(session_->IsSessionReady());
 }
 
+TEST_F(QuicTransportServerSessionTest, PathWithQuery) {
+  Connect();
+  EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true));
+  ReceiveIndicationWithPath("/test?foo=bar");
+  EXPECT_TRUE(session_->IsSessionReady());
+  EXPECT_EQ(path_.path(), "/test");
+  EXPECT_EQ(path_.query(), "foo=bar");
+}
+
+TEST_F(QuicTransportServerSessionTest, PathNormalization) {
+  Connect();
+  EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true));
+  ReceiveIndicationWithPath("/foo/../bar");
+  EXPECT_TRUE(session_->IsSessionReady());
+  EXPECT_EQ(path_.path(), "/bar");
+}
+
+TEST_F(QuicTransportServerSessionTest, EmptyPath) {
+  Connect();
+  EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true));
+  EXPECT_CALL(connection_,
+              CloseConnection(_, HasSubstr("Path must begin with a '/'"), _));
+  ReceiveIndicationWithPath("");
+  EXPECT_FALSE(session_->IsSessionReady());
+}
+
+TEST_F(QuicTransportServerSessionTest, UnprefixedPath) {
+  Connect();
+  EXPECT_CALL(visitor_, CheckOrigin(_)).WillOnce(Return(true));
+  EXPECT_CALL(connection_,
+              CloseConnection(_, HasSubstr("Path must begin with a '/'"), _));
+  ReceiveIndicationWithPath("test");
+  EXPECT_FALSE(session_->IsSessionReady());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/test_tools/quic_transport_test_tools.h b/quic/test_tools/quic_transport_test_tools.h
index c6a8b46..9632d4c 100644
--- a/quic/test_tools/quic_transport_test_tools.h
+++ b/quic/test_tools/quic_transport_test_tools.h
@@ -21,6 +21,7 @@
 class MockServerVisitor : public QuicTransportServerSession::ServerVisitor {
  public:
   MOCK_METHOD1(CheckOrigin, bool(url::Origin));
+  MOCK_METHOD1(ProcessPath, bool(const GURL&));
 };
 
 class MockStreamVisitor : public QuicTransportStream::Visitor {
diff --git a/quic/tools/quic_transport_simple_server_session.cc b/quic/tools/quic_transport_simple_server_session.cc
index 6e86cca..40e5af3 100644
--- a/quic/tools/quic_transport_simple_server_session.cc
+++ b/quic/tools/quic_transport_simple_server_session.cc
@@ -202,6 +202,11 @@
   return false;
 }
 
+bool QuicTransportSimpleServerSession::ProcessPath(const GURL& url) {
+  QUIC_DLOG(INFO) << "Path requested: " << url;
+  return true;
+}
+
 void QuicTransportSimpleServerSession::MaybeEchoStreamsBack() {
   while (!streams_to_echo_back_.empty() &&
          CanOpenNextOutgoingUnidirectionalStream()) {
diff --git a/quic/tools/quic_transport_simple_server_session.h b/quic/tools/quic_transport_simple_server_session.h
index 11f82f2..5857930 100644
--- a/quic/tools/quic_transport_simple_server_session.h
+++ b/quic/tools/quic_transport_simple_server_session.h
@@ -13,6 +13,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
 #include "net/third_party/quiche/src/quic/platform/api/quic_flags.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
 #include "net/third_party/quiche/src/quic/quic_transport/quic_transport_server_session.h"
 #include "net/third_party/quiche/src/quic/quic_transport/quic_transport_stream.h"
 
@@ -51,6 +52,7 @@
   void OnIncomingDataStream(QuicTransportStream* stream) override;
   void OnCanCreateNewOutgoingStream(bool unidirectional) override;
   bool CheckOrigin(url::Origin origin) override;
+  bool ProcessPath(const GURL& url) override;
 
   void EchoStreamBack(const std::string& data) {
     streams_to_echo_back_.push_back(data);