Add helpers to match characters in CBS

Needed for URI parsing in crypto/x509.

Change-Id: I15c04d535d2453538c38502e127c295a6a6a6964
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/94307
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Lily Chen <chlily@google.com>
diff --git a/crypto/bytestring/bytestring_test.cc b/crypto/bytestring/bytestring_test.cc
index 95719c0..0e36a2e 100644
--- a/crypto/bytestring/bytestring_test.cc
+++ b/crypto/bytestring/bytestring_test.cc
@@ -153,6 +153,54 @@
   EXPECT_EQ(CBS_len(&data), sizeof(kData) - 2);
 }
 
+TEST(CBSTest, GetUntilFirstOf) {
+  static const uint8_t kData[] = {0, 'a', 'b', 'c', 0, 'a', 'b', 'c'};
+  CBS data;
+  CBS_init(&data, kData, sizeof(kData));
+
+  CBS prefix;
+  EXPECT_FALSE(CBS_get_until_first_of(&data, &prefix, "A"));
+  EXPECT_EQ(CBS_data(&data), kData);
+  EXPECT_EQ(CBS_len(&data), sizeof(kData));
+
+  ASSERT_TRUE(CBS_get_until_first_of(&data, &prefix, "Abc"));
+  EXPECT_EQ(CBS_data(&prefix), kData);
+  EXPECT_EQ(CBS_len(&prefix), 2u);
+  EXPECT_EQ(CBS_data(&data), kData + 2);
+  EXPECT_EQ(CBS_len(&data), sizeof(kData) - 2);
+}
+
+TEST(CBSTest, GetUntilFirstNotOf) {
+  {
+    static const uint8_t kData[] = {'a', 'b', 'c', 'd', 'a', 'b', 'c'};
+    CBS data;
+    CBS_init(&data, kData, sizeof(kData));
+
+    CBS prefix;
+    EXPECT_FALSE(CBS_get_until_first_not_of(&data, &prefix, "abcd"));
+    EXPECT_EQ(CBS_data(&data), kData);
+    EXPECT_EQ(CBS_len(&data), sizeof(kData));
+
+    ASSERT_TRUE(CBS_get_until_first_not_of(&data, &prefix, "abcD"));
+    EXPECT_EQ(CBS_data(&prefix), kData);
+    EXPECT_EQ(CBS_len(&prefix), 3u);
+    EXPECT_EQ(CBS_data(&data), kData + 3);
+    EXPECT_EQ(CBS_len(&data), sizeof(kData) - 3);
+  }
+  {
+    static const uint8_t kData[] = {'a', 'b', 'c', 0, 'a', 'b', 'c'};
+    CBS data;
+    CBS_init(&data, kData, sizeof(kData));
+
+    CBS prefix;
+    EXPECT_TRUE(CBS_get_until_first_not_of(&data, &prefix, "abcd"));
+    EXPECT_EQ(CBS_data(&prefix), kData);
+    EXPECT_EQ(CBS_len(&prefix), 3u);
+    EXPECT_EQ(CBS_data(&data), kData + 3);
+    EXPECT_EQ(CBS_len(&data), sizeof(kData) - 3);
+  }
+}
+
 TEST(CBSTest, GetASN1) {
   static const uint8_t kData1[] = {0x30, 2, 1, 2};
   static const uint8_t kData2[] = {0x30, 3, 1, 2};
diff --git a/crypto/bytestring/cbs.cc b/crypto/bytestring/cbs.cc
index 167a0cd..dbaa3e8 100644
--- a/crypto/bytestring/cbs.cc
+++ b/crypto/bytestring/cbs.cc
@@ -215,6 +215,42 @@
   return CBS_get_bytes(cbs, out, split - CBS_data(cbs));
 }
 
+int CBS_get_until_first_of(CBS *cbs, CBS *out, const char *chars) {
+  size_t pos = 0;
+  while (pos < CBS_len(cbs)) {
+    uint8_t c = CBS_data(cbs)[pos];
+    // Special-case for \0 characters. We don't want to match on a null byte,
+    // even though strchr will happily return the \0 at the end of `chars`.
+    if (!c || !strchr(chars, c)) {
+      pos++;
+    } else {
+      break;
+    }
+  }
+  if (pos == CBS_len(cbs)) {
+    return 0;
+  }
+  return CBS_get_bytes(cbs, out, pos);
+}
+
+int CBS_get_until_first_not_of(CBS *cbs, CBS *out, const char *chars) {
+  size_t pos = 0;
+  while (pos < CBS_len(cbs)) {
+    uint8_t c = CBS_data(cbs)[pos];
+    // Special-case for \0 characters. We don't want to match on a null byte,
+    // even though strchr will happily return the \0 at the end of `chars`.
+    if (c && strchr(chars, c)) {
+      pos++;
+    } else {
+      break;
+    }
+  }
+  if (pos == CBS_len(cbs)) {
+    return 0;
+  }
+  return CBS_get_bytes(cbs, out, pos);
+}
+
 int CBS_get_u64_decimal(CBS *cbs, uint64_t *out) {
   uint64_t v = 0;
   int seen_digit = 0;
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index b484710..3e1cfe5 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -161,6 +161,20 @@
 // one. Otherwise, it returns zero and leaves |cbs| unmodified.
 OPENSSL_EXPORT int CBS_get_until_first(CBS *cbs, CBS *out, uint8_t c);
 
+// CBS_get_until_first_of finds the first byte in |cbs| matching one of the
+// characters in |chars|, which is a NUL-terminated C string. If found, it sets
+// |*out| to the text before the match, advances |cbs| over it, and returns one.
+// Otherwise, it returns zero and leaves |cbs| unmodified.
+OPENSSL_EXPORT int CBS_get_until_first_of(CBS *cbs, CBS *out,
+                                          const char *chars);
+
+// CBS_get_until_first_not_of finds the first byte in |cbs| that does not match
+// any of the characters in |chars|, which is a NUL-terminated C string. If
+// found, it sets |*out| to the text before the match, advances |cbs| over it,
+// and returns one. Otherwise, it returns zero and leaves |cbs| unmodified.
+OPENSSL_EXPORT int CBS_get_until_first_not_of(CBS *cbs, CBS *out,
+                                              const char *chars);
+
 // CBS_get_u64_decimal reads a decimal integer from |cbs| and writes it to
 // |*out|. It stops reading at the end of the string, or the first non-digit
 // character. It returns one on success and zero on error. This function behaves
diff --git a/include/openssl/prefix_symbols.h b/include/openssl/prefix_symbols.h
index 9d0b61b..c255e35 100644
--- a/include/openssl/prefix_symbols.h
+++ b/include/openssl/prefix_symbols.h
@@ -575,6 +575,8 @@
 #pragma redefine_extname CBS_get_u8_length_prefixed BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_u8_length_prefixed)
 #pragma redefine_extname CBS_get_ucs2_be BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_ucs2_be)
 #pragma redefine_extname CBS_get_until_first BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_until_first)
+#pragma redefine_extname CBS_get_until_first_not_of BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_until_first_not_of)
+#pragma redefine_extname CBS_get_until_first_of BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_until_first_of)
 #pragma redefine_extname CBS_get_utf32_be BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_utf32_be)
 #pragma redefine_extname CBS_get_utf8 BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_get_utf8)
 #pragma redefine_extname CBS_is_unsigned_asn1_integer BORINGSSL_ADD_USER_LABEL_AND_PREFIX(CBS_is_unsigned_asn1_integer)
@@ -3674,6 +3676,8 @@
 #define CBS_get_u8_length_prefixed BORINGSSL_ADD_PREFIX(CBS_get_u8_length_prefixed)
 #define CBS_get_ucs2_be BORINGSSL_ADD_PREFIX(CBS_get_ucs2_be)
 #define CBS_get_until_first BORINGSSL_ADD_PREFIX(CBS_get_until_first)
+#define CBS_get_until_first_not_of BORINGSSL_ADD_PREFIX(CBS_get_until_first_not_of)
+#define CBS_get_until_first_of BORINGSSL_ADD_PREFIX(CBS_get_until_first_of)
 #define CBS_get_utf32_be BORINGSSL_ADD_PREFIX(CBS_get_utf32_be)
 #define CBS_get_utf8 BORINGSSL_ADD_PREFIX(CBS_get_utf8)
 #define CBS_is_unsigned_asn1_integer BORINGSSL_ADD_PREFIX(CBS_is_unsigned_asn1_integer)