# HG changeset patch
# Node ID efaddb319116b71108ed8d66de6445c2e52cf93e
# Parent  daf24ed751fe7dbfc7115563cfbcbf5c3c7f1758
diff --git a/ChangeLog b/ChangeLog
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,6 @@
+0.8.0: ????-??-??
+  * Nothing yet, be the first!
+
 0.7.1: 2024-09-02
   * Keep the raw CTCP message in params (RR 3457) (Gary Kramlich)
 
diff --git a/ibis/ibissource.c b/ibis/ibissource.c
--- a/ibis/ibissource.c
+++ b/ibis/ibissource.c
@@ -87,3 +87,64 @@
 	g_clear_pointer(&info, g_match_info_unref);
 	g_clear_pointer(&regex, g_regex_unref);
 }
+
+/**
+ * ibis_source_find_offset: (skip)
+ * @source: The source.
+ * @prefixes: The prefixes.
+ *
+ * Finds offset where the prefixes end and the source starts.
+ *
+ * Returns: The pointer in @source where the source starts and @prefixes ends.
+ */
+static const char *
+ibis_source_find_offset(const char *source, const char *prefixes) {
+	int source_offset = 0;
+
+	g_return_val_if_fail(!ibis_str_is_empty(source), NULL);
+	g_return_val_if_fail(!ibis_str_is_empty(prefixes), NULL);
+
+	/* Prefixes are supposed to be in order of rank from high to low. So we
+	 * walk prefixes checking if the current index of source matches the
+	 * current prefix.
+	 */
+	for(int i = 0; prefixes[i] != '\0'; i++) {
+		if(source[source_offset] == prefixes[i]) {
+			source_offset += 1;
+		}
+
+		if(source[source_offset] == '\0') {
+			break;
+		}
+	}
+
+	return source + source_offset;
+}
+
+char *
+ibis_source_strip_prefix(const char *source, const char *prefixes) {
+	const char *offset = NULL;
+
+	g_return_val_if_fail(!ibis_str_is_empty(source), NULL);
+	g_return_val_if_fail(!ibis_str_is_empty(prefixes), NULL);
+
+	offset = ibis_source_find_offset(source, prefixes);
+
+	return g_strdup(offset);
+}
+
+char *
+ibis_source_get_prefix(const char *source, const char *prefixes) {
+	const char *offset = NULL;
+
+	g_return_val_if_fail(!ibis_str_is_empty(source), NULL);
+	g_return_val_if_fail(!ibis_str_is_empty(prefixes), NULL);
+
+	offset = ibis_source_find_offset(source, prefixes);
+
+	if(offset - source > 0) {
+		return g_strndup(source, offset - source);
+	}
+
+	return NULL;
+}
diff --git a/ibis/ibissource.h b/ibis/ibissource.h
--- a/ibis/ibissource.h
+++ b/ibis/ibissource.h
@@ -51,6 +51,51 @@
 IBIS_AVAILABLE_IN_0_1
 void ibis_source_parse(const char *source, char **nick, char **user, char **host);
 
+/**
+ * ibis_source_strip_prefix:
+ * @source: The source to strip.
+ * @prefixes: The list of prefixes that should be stripped.
+ *
+ * Removes any characters from the start of @source that are in @prefixes until
+ * a character that is not in @prefixes is found.
+ *
+ * If for some reason, @source is a single character that's in @prefixes, an
+ * empty string will be returned.
+ *
+ * @prefixes should be in descending order, from the prefix that gives the most
+ * privileges to the prefix that gives the least. The server will send them in
+ * this order. See [PREFIX
+ * Parameter](https://modern.ircdocs.horse/#prefix-parameter) for more
+ * information.
+ *
+ * Returns: (transfer full): The source with the prefix removed.
+ *
+ * Since: 0.8
+ */
+IBIS_AVAILABLE_IN_0_8
+char *ibis_source_strip_prefix(const char *source, const char *prefixes);
+
+/**
+ * ibis_source_get_prefix:
+ * @source: The source.
+ * @prefixes: The list of valid prefixes.
+ *
+ * Gets the prefixes from @source.
+ *
+ * @prefixes should be in descending order, from the prefix that gives the most
+ * privileges to the prefix that gives the least. The server will send them in
+ * this order. See [PREFIX
+ * Parameter](https://modern.ircdocs.horse/#prefix-parameter) for more
+ * information.
+ *
+ * Returns: (transfer full) (nullable): The prefixes from @source if found,
+ *          otherwise %NULL.
+ *
+ * Since: 0.8
+ */
+IBIS_AVAILABLE_IN_0_8
+char *ibis_source_get_prefix(const char *source, const char *prefixes);
+
 G_END_DECLS
 
 #endif /* IBIS_SOURCE_H */
diff --git a/ibis/ibisversion.h b/ibis/ibisversion.h
--- a/ibis/ibisversion.h
+++ b/ibis/ibisversion.h
@@ -293,6 +293,30 @@
 #define IBIS_AVAILABLE_TYPE_IN_0_7
 #endif
 
+/**
+ * IBIS_VERSION_0_8:
+ *
+ * A macro that evaluates to the 0.8 version of ibis, in a format that can be
+ * used by the C pre-processor.
+ *
+ * Since: 0.8
+ */
+#define IBIS_VERSION_0_8 (G_ENCODE_VERSION(0, 8))
+
+#if IBIS_VERSION_MAX_ALLOWED < IBIS_VERSION_0_8
+#define IBIS_AVAILABLE_IN_0_8 IBIS_UNAVAILABLE(0, 8)
+#define IBIS_AVAILABLE_STATIC_INLINE_IN_0_8 IBIS_UNAVAILABLE_STATIC_INLINE(0, 8)
+#define IBIS_AVAILABLE_MACRO_IN_0_8 IBIS_UNAVAILABLE_MACRO(0, 8)
+#define IBIS_AVAILABLE_ENUMERATOR_IN_0_8 IBIS_UNAVAILABLE_ENUMERATOR(0, 8)
+#define IBIS_AVAILABLE_TYPE_IN_0_8 IBIS_UNAVAILABLE_TYPE(0, 8)
+#else
+#define IBIS_AVAILABLE_IN_0_8 _IBIS_EXTERN
+#define IBIS_AVAILABLE_STATIC_INLINE_IN_0_8
+#define IBIS_AVAILABLE_MACRO_IN_0_8
+#define IBIS_AVAILABLE_ENUMERATOR_IN_0_8
+#define IBIS_AVAILABLE_TYPE_IN_0_8
+#endif
+
 G_BEGIN_DECLS
 
 /**
diff --git a/ibis/tests/test_source.c b/ibis/tests/test_source.c
--- a/ibis/tests/test_source.c
+++ b/ibis/tests/test_source.c
@@ -25,7 +25,7 @@
 #include <ibis.h>
 
 /******************************************************************************
- * Tests
+ * Parse Tests
  *****************************************************************************/
 static void
 test_ibis_source_parse_return_address_required(void) {
@@ -143,11 +143,107 @@
 }
 
 /******************************************************************************
+ * Strip Prefix Tests
+ *****************************************************************************/
+static void
+test_ibis_source_strip_prefix_none(void) {
+	char *result = NULL;
+
+	result = ibis_source_strip_prefix("irene", "@+");
+	g_assert_cmpstr(result, ==, "irene");
+	g_clear_pointer(&result, g_free);
+}
+
+static void
+test_ibis_source_strip_prefix_single(void) {
+	char *result = NULL;
+
+	result = ibis_source_strip_prefix("+irene", "@+");
+	g_assert_cmpstr(result, ==, "irene");
+	g_clear_pointer(&result, g_free);
+}
+
+static void
+test_ibis_source_strip_prefix_multiple(void) {
+	char *result = NULL;
+
+	result = ibis_source_strip_prefix("@+irene", "@+");
+	g_assert_cmpstr(result, ==, "irene");
+	g_clear_pointer(&result, g_free);
+}
+
+static void
+test_ibis_source_strip_prefix_out_of_order(void) {
+	char *result = NULL;
+
+	result = ibis_source_strip_prefix("+@irene", "@+");
+	g_assert_cmpstr(result, ==, "@irene");
+	g_clear_pointer(&result, g_free);
+}
+
+static void
+test_ibis_source_strip_prefix_prefix_only(void) {
+	char *result = NULL;
+
+	result = ibis_source_strip_prefix("@", "@+");
+	g_assert_cmpstr(result, ==, "");
+	g_free(result);
+}
+
+/******************************************************************************
+ * Get Prefix Tests
+ *****************************************************************************/
+static void
+test_ibis_source_get_prefix_none(void) {
+	char *result = NULL;
+
+	result = ibis_source_get_prefix("irene", "@+");
+	g_assert_cmpstr(result, ==, NULL);
+}
+
+static void
+test_ibis_source_get_prefix_single(void) {
+	char *result = NULL;
+
+	result = ibis_source_get_prefix("+irene", "@+");
+	g_assert_cmpstr(result, ==, "+");
+	g_clear_pointer(&result, g_free);
+}
+
+static void
+test_ibis_source_get_prefix_multiple(void) {
+	char *result = NULL;
+
+	result = ibis_source_get_prefix("@+irene", "@+");
+	g_assert_cmpstr(result, ==, "@+");
+	g_clear_pointer(&result, g_free);
+}
+
+static void
+test_ibis_source_get_prefix_out_of_order(void) {
+	char *result = NULL;
+
+	result = ibis_source_get_prefix("+@irene", "@+");
+	g_assert_cmpstr(result, ==, "+");
+	g_clear_pointer(&result, g_free);
+}
+
+static void
+test_ibis_source_get_prefix_prefix_only(void) {
+	char *result = NULL;
+
+	result = ibis_source_get_prefix("+", "@+");
+	g_assert_cmpstr(result, ==, "+");
+	g_clear_pointer(&result, g_free);
+}
+
+/******************************************************************************
  * Main
  *****************************************************************************/
 int
 main(int argc, char *argv[]) {
 	g_test_init(&argc, &argv, NULL);
+	g_test_set_nonfatal_assertions();
 
 	g_test_add_func("/ibis/source/parse/return-address-required",
 	                test_ibis_source_parse_return_address_required);
@@ -157,5 +253,27 @@
 	g_test_add_func("/ibis/source/parse/baddies",
 	                test_ibis_source_parse_baddies);
 
+	g_test_add_func("/ibis/source/strip-prefix/none",
+	                test_ibis_source_strip_prefix_none);
+	g_test_add_func("/ibis/source/strip-prefix/single",
+	                test_ibis_source_strip_prefix_single);
+	g_test_add_func("/ibis/source/strip-prefix/multiple",
+	                test_ibis_source_strip_prefix_multiple);
+	g_test_add_func("/ibis/source/strip-prefix/out-of-order",
+	                test_ibis_source_strip_prefix_out_of_order);
+	g_test_add_func("/ibis/source/strip-prefix/prefix-only",
+	                test_ibis_source_strip_prefix_prefix_only);
+
+	g_test_add_func("/ibis/source/get-prefix/none",
+	                test_ibis_source_get_prefix_none);
+	g_test_add_func("/ibis/source/get-prefix/single",
+	                test_ibis_source_get_prefix_single);
+	g_test_add_func("/ibis/source/get-prefix/multiple",
+	                test_ibis_source_get_prefix_multiple);
+	g_test_add_func("/ibis/source/get-prefix/out-of-order",
+	                test_ibis_source_get_prefix_out_of_order);
+	g_test_add_func("/ibis/source/get-prefix/prefix-only",
+	                test_ibis_source_get_prefix_prefix_only);
+
 	return g_test_run();
 }
diff --git a/meson.build b/meson.build
--- a/meson.build
+++ b/meson.build
@@ -2,7 +2,7 @@
   default_options : ['c_std=c17', 'warning_level=2'],
   license : 'LGPL-2.1-or-later',
   meson_version : '>=1.0.0',
-  version : '0.7.1')
+  version : '0.8.0-dev')
 
 toplevel_inc = include_directories('.')
 
