# HG changeset patch
# Node ID 6d9be253399b6c2fc9e2ad27acc3363785c79496
# Parent  83a288f437bd5ce8d1ce069ae28211f0f0f45d95
diff --git a/hasl/haslgs2header.c b/hasl/haslgs2header.c
--- a/hasl/haslgs2header.c
+++ b/hasl/haslgs2header.c
@@ -44,6 +44,9 @@
 	char *authzid;
 };
 
+G_DEFINE_QUARK(hasl-gs2-header-parse-error,
+               hasl_gs2_header_parse_error)
+
 G_DEFINE_TYPE(HaslGs2Header, hasl_gs2_header, G_TYPE_OBJECT)
 
 /******************************************************************************
@@ -192,6 +195,106 @@
 	return g_object_new(HASL_TYPE_GS2_HEADER, NULL);
 }
 
+HaslGs2Header *
+hasl_gs2_header_parse(const char *str, GError **error) {
+	HaslGs2Header *header = NULL;
+	GError *local_error = NULL;
+	GMatchInfo *info = NULL;
+	GRegex *regex = NULL;
+	const char *pattern = "^(?:(?P<nonstd>F),)?"
+	                      "(?P<cb>[npy])(?:=(?P<cbname>[^,]*))?,"
+	                      "(?:a=(?P<authzid>[^,]+))?,";
+	gboolean matched = FALSE;
+
+	g_return_val_if_fail(str != NULL, NULL);
+
+	regex = g_regex_new(pattern, G_REGEX_DEFAULT, G_REGEX_MATCH_DEFAULT,
+	                    &local_error);
+	if(local_error != NULL) {
+		g_propagate_error(error, local_error);
+		return NULL;
+	}
+
+	matched = g_regex_match(regex, str, G_REGEX_MATCH_DEFAULT, &info);
+	if(matched) {
+		char *value = NULL;
+		gboolean cbname_required = FALSE;
+
+		header = hasl_gs2_header_new();
+
+		/* Handle the standard mechanism field. */
+		value = g_match_info_fetch_named(info, "nonstd");
+		if(value[0] == 'F') {
+			hasl_gs2_header_set_standard_mechanism(header, FALSE);
+		}
+		g_clear_pointer(&value, g_free);
+
+		/* Handle the channel binding field. */
+		value = g_match_info_fetch_named(info, "cb");
+		switch(value[0]) {
+		case 'n':
+			hasl_gs2_header_set_channel_binding(header,
+			                                    HaslGs2HeaderChannelBindingClientNotSupported);
+			break;
+		case 'p':
+			hasl_gs2_header_set_channel_binding(header,
+			                                    HaslGs2HeaderChannelBindingClientSupported);
+			cbname_required = TRUE;
+			break;
+		case 'y':
+			hasl_gs2_header_set_channel_binding(header,
+			                                    HaslGs2HeaderChannelBindingClientSupportedServerUnknown);
+			break;
+
+		}
+		g_clear_pointer(&value, g_free);
+
+		/* Handle the cbname field. */
+		value = g_match_info_fetch_named(info, "cbname");
+		if(cbname_required) {
+			if(value[0] == '\0') {
+				g_clear_pointer(&value, g_free);
+				g_clear_object(&header);
+				g_clear_pointer(&regex, g_regex_unref);
+				g_clear_pointer(&info, g_match_info_unref);
+
+				g_set_error_literal(error, HASL_GS2_HEADER_PARSE_ERROR,
+				                    HASL_GS2_HEADER_PARSE_ERROR_CBNAME_NOT_SPECIFIED,
+				                    "cbname was required but not specified.");
+
+				return NULL;
+			}
+
+			hasl_gs2_header_set_channel_binding_name(header, value);
+		} else if(value[0] != '\0') {
+			g_clear_pointer(&value, g_free);
+			g_clear_object(&header);
+			g_clear_pointer(&regex, g_regex_unref);
+			g_clear_pointer(&info, g_match_info_unref);
+
+			g_set_error_literal(error,
+			                    HASL_GS2_HEADER_PARSE_ERROR,
+			                    HASL_GS2_HEADER_PARSE_ERROR_CBNAME_UNEXPECTED,
+			                    "cbname was specified when it wasn't expected");
+
+			return NULL;
+		}
+		g_clear_pointer(&value, g_free);
+
+		/* Handle the authzid field. */
+		value = g_match_info_fetch_named(info, "authzid");
+		if(value != NULL && value[0] != '\0') {
+			hasl_gs2_header_set_authzid(header, value);
+		}
+		g_clear_pointer(&value, g_free);
+	}
+
+	g_clear_pointer(&regex, g_regex_unref);
+	g_clear_pointer(&info, g_match_info_unref);
+
+	return header;
+}
+
 char *
 hasl_gs2_header_to_string(HaslGs2Header *header, GError **error) {
 	GString *str = NULL;
diff --git a/hasl/haslgs2header.h b/hasl/haslgs2header.h
--- a/hasl/haslgs2header.h
+++ b/hasl/haslgs2header.h
@@ -27,30 +27,65 @@
 
 /**
  * HaslGs2HeaderChannelBinding:
- * @HaslGs2HeaderChannelBindingClientNotSupported: The client doesn't support
- *                                                 channel binding.
- * @HaslGs2HeaderChannelBindingClientSupported: The client supports channel
- *                                              binding and has specified a
- *                                              name.
- * @HaslGs2HeaderChannelBindingClientSupportedServerUnknown: The client
- *                                                           supports
- *                                                           channel binding,
- *                                                           doesn't know if
- *                                                           the server does.
+ * @HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_NOT_SUPPORTED: The client doesn't
+ *  support channel binding. This is the 'n' value.
+ * @HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED: The client supports
+ *  channel binding and has specified a name. This is the 'p' value.
+ * @HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED_SERVER_UNKNOWN: The client
+ *  supports channel binding, doesn't know if the server does. This is the 'y'
+ *  value.
+ * @HaslGs2HeaderChannelBindingClientNotSupported: deprecated use
+ *  HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_NOT_SUPPORTED.
+ * @HaslGs2HeaderChannelBindingClientSupported: deprecated use
+ *  HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED.
+ * @HaslGs2HeaderChannelBindingClientSupportedServerUnknown: deprecated use
+ *  HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED_SERVER_UNKNOWN.
  *
  * This enum tells the header about channel bindings for this connection. See
- * https://datatracker.ietf.org/doc/html/rfc5801#section-4 for more
- * information.
+ * https://www.rfc-editor.org/rfc/rfc5801.html#section-4 for more information.
  *
  * Since: 0.3
  */
 typedef enum {
-	HaslGs2HeaderChannelBindingClientNotSupported,
-	HaslGs2HeaderChannelBindingClientSupported,
-	HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
+	HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_NOT_SUPPORTED HASL_AVAILABLE_ENUMERATOR_IN_0_5,
+	HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED HASL_AVAILABLE_ENUMERATOR_IN_0_5,
+	HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED_SERVER_UNKNOWN HASL_AVAILABLE_ENUMERATOR_IN_0_5,
+	HaslGs2HeaderChannelBindingClientNotSupported HASL_DEPRECATED_ENUMERATOR_IN_0_5_FOR(HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_NOT_SUPPORTED) = HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_NOT_SUPPORTED,
+	HaslGs2HeaderChannelBindingClientSupported HASL_DEPRECATED_ENUMERATOR_IN_0_5_FOR(HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED) = HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED,
+	HaslGs2HeaderChannelBindingClientSupportedServerUnknown HASL_DEPRECATED_ENUMERATOR_IN_0_5_FOR(HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED_SERVER_UNKNOWN) = HASL_GS2_HEADER_CHANNEL_BINDING_CLIENT_SUPPORTED_SERVER_UNKNOWN,
 } HaslGs2HeaderChannelBinding;
 
-#define HASL_GS2_HEADER_DOMAIN (g_quark_from_static_string("hash-gs2-header"))
+#define HASL_GS2_HEADER_DOMAIN (g_quark_from_static_string("hasl-gs2-header"))
+
+#define HASL_GS2_HEADER_PARSE_ERROR hasl_gs2_header_parse_error_quark()
+
+/**
+ * hasl_gs2_header_parse_error_quark:
+ *
+ * The error domain to identify errors when parsing gs2 headers.
+ *
+ * Returns: The error domain.
+ *
+ * Since: 0.5
+ */
+HASL_AVAILABLE_IN_0_5
+GQuark hasl_gs2_header_parse_error_quark(void);
+
+/**
+ * HaslGs2HeaderParseError:
+ * @HASL_GS2_HEADER_PARSE_ERROR_CBNAME_NOT_SPECIFIED: the channel binding
+ *  setting was not specified
+ * @HASL_GS2_HEADER_PARSE_ERROR_CBNAME_UNEXPECTED: the channel binding setting
+ *  was specified but was unexpected
+ *
+ * Error codes returned by gs2 header parse.
+ *
+ * Since: 0.5
+ */
+typedef enum {
+	HASL_GS2_HEADER_PARSE_ERROR_CBNAME_NOT_SPECIFIED HASL_AVAILABLE_ENUMERATOR_IN_0_5,
+	HASL_GS2_HEADER_PARSE_ERROR_CBNAME_UNEXPECTED HASL_AVAILABLE_ENUMERATOR_IN_0_5,
+} HaslGs2HeaderParseError;
 
 /**
  * HaslGs2Header:
@@ -81,6 +116,20 @@
 HaslGs2Header *hasl_gs2_header_new(void);
 
 /**
+ * hasl_gs2_header_parse:
+ * @str: the string to parse
+ * @error: (out) (nullable): a return address for a #GError
+ *
+ * Parses a string into a GS2 Header.
+ *
+ * Returns: (transfer full) (nullable): The new header if found.
+ *
+ * Since: 0.5
+ */
+HASL_AVAILABLE_IN_0_5
+HaslGs2Header *hasl_gs2_header_parse(const char *str, GError **error);
+
+/**
  * hasl_gs2_header_to_string:
  * @header: The instance.
  * @error: (out): A return address for an error.
diff --git a/hasl/haslversion.h b/hasl/haslversion.h
--- a/hasl/haslversion.h
+++ b/hasl/haslversion.h
@@ -67,14 +67,20 @@
 
 #ifdef HASL_DISABLE_DEPRECATION_WARNINGS
 #define HASL_DEPRECATED _HASL_EXTERN
+#define HASL_DEPRECATED_ENUMERATOR
+#define HASL_DEPRECATED_ENUMERATOR_FOR(f)
 #define HASL_DEPRECATED_FOR(f) _HASL_EXTERN
 #define HASL_UNAVAILABLE(maj, min) _HASL_EXTERN
+#define HASL_UNAVAILABLE_ENUMERATOR(maj, min)
 #define HASL_UNAVAILABLE_STATIC_INLINE(maj, min)
 #define HASL_UNAVAILABLE_TYPE(maj, min)
 #else
 #define HASL_DEPRECATED G_DEPRECATED _HASL_EXTERN
+#define HASL_DEPRECATED_ENUMERATOR G_DEPRECATED
+#define HASL_DEPRECATED_ENUMERATOR_FOR(f) G_DEPRECATED_FOR(f)
 #define HASL_DEPRECATED_FOR(f) G_DEPRECATED_FOR(f) _HASL_EXTERN
 #define HASL_UNAVAILABLE(maj, min) G_UNAVAILABLE(maj, min) _HASL_EXTERN
+#define HASL_UNAVAILABLE_ENUMERATOR(maj, min) G_UNAVAILABLE(maj,min)
 #define HASL_UNAVAILABLE_STATIC_INLINE(maj, min) G_UNAVAILABLE(maj, min)
 #define HASL_UNAVAILABLE_TYPE(maj, min) G_UNAVAILABLE(maj, min)
 #endif
@@ -196,8 +202,12 @@
 
 #if HASL_VERSION_MAX_ALLOWED < HASL_VERSION_0_5
 #define HASL_AVAILABLE_IN_0_5 HASL_UNAVAILABLE(0, 5)
+#define HASL_AVAILABLE_ENUMERATOR_IN_0_5 HASL_UNAVAILABLE_ENUMERATOR(0, 5)
+#define HASL_DEPRECATED_ENUMERATOR_IN_0_5_FOR(f) HASL_DEPRECATED_ENUMERATOR_FOR(f)
 #else
 #define HASL_AVAILABLE_IN_0_5 _HASL_EXTERN
+#define HASL_AVAILABLE_ENUMERATOR_IN_0_5
+#define HASL_DEPRECATED_ENUMERATOR_IN_0_5_FOR(f)
 #endif
 
 G_BEGIN_DECLS
diff --git a/hasl/reference/hasl.toml.in b/hasl/reference/hasl.toml.in
--- a/hasl/reference/hasl.toml.in
+++ b/hasl/reference/hasl.toml.in
@@ -54,3 +54,15 @@
 [[object]]
 name = "UNAVAILABLE_TYPE"
 hidden = true
+
+[[object]]
+name = "UNAVAILABLE_ENUMERATOR"
+hidden = true
+
+[[object]]
+name = "DEPRECATED_ENUMERATOR_FOR"
+hidden = true
+
+[[object]]
+pattern = "DEPRECATED_ENUMERATOR_IN_*"
+hidden = true
diff --git a/hasl/tests/test-gs2-header.c b/hasl/tests/test-gs2-header.c
--- a/hasl/tests/test-gs2-header.c
+++ b/hasl/tests/test-gs2-header.c
@@ -223,6 +223,144 @@
 }
 
 /******************************************************************************
+ * Parse tests
+ *****************************************************************************/
+static inline void
+test_hasl_gs2_parse(const char *input,
+                    gboolean expected_standard_mechanism,
+                    HaslGs2HeaderChannelBinding expected_binding,
+                    const char *expected_cbname,
+                    const char *expected_authzid)
+{
+	HaslGs2Header *header = NULL;
+	HaslGs2HeaderChannelBinding binding = 0;
+	GError *error = NULL;
+	const char *cbname = NULL;
+	const char *authzid = NULL;
+
+	header = hasl_gs2_header_parse(input, &error);
+	g_assert_no_error(error);
+
+	g_assert_true(HASL_IS_GS2_HEADER(header));
+
+	if(expected_standard_mechanism) {
+		g_assert_true(hasl_gs2_header_get_standard_mechanism(header));
+	} else {
+		g_assert_false(hasl_gs2_header_get_standard_mechanism(header));
+	}
+
+	binding = hasl_gs2_header_get_channel_binding(header);
+	g_assert_cmpuint(binding, ==, expected_binding);
+
+	cbname = hasl_gs2_header_get_channel_binding_name(header);
+	g_assert_cmpstr(cbname, ==, expected_cbname);
+
+	authzid = hasl_gs2_header_get_authzid(header);
+	g_assert_cmpstr(authzid, ==, expected_authzid);
+
+	g_assert_finalize_object(header);
+}
+
+static void
+test_hasl_gs2_header_parse_rfc5801_no_binding(void) {
+	test_hasl_gs2_parse("n,,",
+	                    TRUE,
+	                    HaslGs2HeaderChannelBindingClientNotSupported,
+	                    NULL,
+	                    NULL);
+}
+
+static void
+test_hasl_gs2_header_parse_rfc5801_no_binding_authzid(void) {
+	test_hasl_gs2_parse("n,a=someuser,",
+	                    TRUE,
+	                    HaslGs2HeaderChannelBindingClientNotSupported,
+	                    NULL,
+	                    "someuser");
+}
+
+static void
+test_hasl_gs2_header_parse_rfc5801_nonstd_no_binding(void) {
+	test_hasl_gs2_parse("F,n,,",
+	                    FALSE,
+	                    HaslGs2HeaderChannelBindingClientNotSupported,
+	                    NULL,
+	                    NULL);
+}
+
+static void
+test_hasl_gs2_header_parse_rfc5801_binding_tls_unique(void) {
+	test_hasl_gs2_parse("p=tls-unique,,",
+	                    TRUE,
+	                    HaslGs2HeaderChannelBindingClientSupported,
+	                    "tls-unique",
+	                    NULL);
+}
+
+static void
+test_hasl_gs2_header_parse_rfc5801_binding_tls_unique_authzid(void) {
+	test_hasl_gs2_parse("p=tls-unique,a=someuser,",
+	                    TRUE,
+	                    HaslGs2HeaderChannelBindingClientSupported,
+	                    "tls-unique",
+	                    "someuser");
+}
+
+static void
+test_hasl_gs2_header_parse_rfc5801_binding_tls_server_end_point(void) {
+	test_hasl_gs2_parse("p=tls-server-end-point,,",
+	                    TRUE,
+	                    HaslGs2HeaderChannelBindingClientSupported,
+	                    "tls-server-end-point",
+	                    NULL);
+}
+
+static void
+test_hasl_gs2_header_parse_rfc5801_binding_authzid(void) {
+	test_hasl_gs2_parse("y,a=someuser,",
+	                    TRUE,
+	                    HaslGs2HeaderChannelBindingClientSupportedServerUnknown,
+	                    NULL,
+	                    "someuser");
+}
+
+static void
+test_hasl_gs2_header_parse_missing_cbname(void) {
+	HaslGs2Header *header = NULL;
+	GError *error = NULL;
+
+	header = hasl_gs2_header_parse("p,,", &error);
+	g_assert_error(error, HASL_GS2_HEADER_PARSE_ERROR,
+	               HASL_GS2_HEADER_PARSE_ERROR_CBNAME_NOT_SPECIFIED);
+	g_clear_error(&error);
+	g_assert_null(header);
+
+	header = hasl_gs2_header_parse("p=,,", &error);
+	g_assert_error(error, HASL_GS2_HEADER_PARSE_ERROR,
+	               HASL_GS2_HEADER_PARSE_ERROR_CBNAME_NOT_SPECIFIED);
+	g_clear_error(&error);
+	g_assert_null(header);
+}
+
+static void
+test_hasl_gs2_header_parse_unexpected_cbname(void) {
+	HaslGs2Header *header = NULL;
+	GError *error = NULL;
+
+	header = hasl_gs2_header_parse("y=foo,,", &error);
+	g_assert_error(error, HASL_GS2_HEADER_PARSE_ERROR,
+	               HASL_GS2_HEADER_PARSE_ERROR_CBNAME_UNEXPECTED);
+	g_clear_error(&error);
+	g_assert_null(header);
+
+	header = hasl_gs2_header_parse("n=bar,,", &error);
+	g_assert_error(error, HASL_GS2_HEADER_PARSE_ERROR,
+	               HASL_GS2_HEADER_PARSE_ERROR_CBNAME_UNEXPECTED);
+	g_clear_error(&error);
+	g_assert_null(header);
+}
+
+/******************************************************************************
  * Main
  *****************************************************************************/
 int
@@ -250,5 +388,25 @@
 	g_test_add_func("/hasl/gs2-header/to-string/authzid",
 	                test_hasl_gs2_header_to_string_authzid);
 
+	g_test_add_func("/hasl/gs2-header/parse/rfc5801/no-binding",
+	                test_hasl_gs2_header_parse_rfc5801_no_binding);
+	g_test_add_func("/hasl/gs2-header/parse/rfc5801/no-binding-authzid",
+	                test_hasl_gs2_header_parse_rfc5801_no_binding_authzid);
+	g_test_add_func("/hasl/gs2-header/parse/rfc5801/nonstd-no-binding",
+	                test_hasl_gs2_header_parse_rfc5801_nonstd_no_binding);
+	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-tls-unique",
+	                test_hasl_gs2_header_parse_rfc5801_binding_tls_unique);
+	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-tls-unique-authzid",
+	                test_hasl_gs2_header_parse_rfc5801_binding_tls_unique_authzid);
+	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-tls-server-end-point",
+	                test_hasl_gs2_header_parse_rfc5801_binding_tls_server_end_point);
+	g_test_add_func("/hasl/gs2-header/parse/rfc5801/binding-authzid",
+	                test_hasl_gs2_header_parse_rfc5801_binding_authzid);
+
+	g_test_add_func("/hasl/gs2-hedaer/parse/missing-cbname",
+	                test_hasl_gs2_header_parse_missing_cbname);
+	g_test_add_func("/hasl/gs2-hedaer/parse/unexpected-cbname",
+	                test_hasl_gs2_header_parse_unexpected_cbname);
+
 	return g_test_run();
 }
