1 /* $NetBSD: valid_uri_scheme.c,v 1.2 2025/02/25 19:15:52 christos Exp $ */ 2 3 /*++ 4 /* NAME 5 /* valid_uri_scheme 3 6 /* SUMMARY 7 /* validate scheme:// prefix 8 /* SYNOPSIS 9 /* #include <valid_uri_scheme.h> 10 /* 11 /* int valid_uri_scheme(const char *str) 12 /* DESCRIPTION 13 /* valid_uri_scheme() takes a null-terminated string and returns 14 /* the length of a valid scheme:// prefix, or zero if no valid 15 /* prefix was found. 16 /* 17 /* This function requires that input is encoded in ASCII or UTF-8. 18 /* LICENSE 19 /* .ad 20 /* .fi 21 /* The Secure Mailer license must be distributed with this software. 22 /* AUTHOR(S) 23 /* Wietse Venema 24 /* porcupine.org 25 /*--*/ 26 27 /* 28 * System library. 29 */ 30 #include <sys_defs.h> 31 #include <ctype.h> 32 #include <stdlib.h> 33 34 /* 35 * Utility library. 36 */ 37 #include <valid_uri_scheme.h> 38 #include <msg.h> 39 #include <msg_vstream.h> 40 #include <stringops.h> 41 42 /* valid_uri_scheme - predicate that string starts with scheme:// */ 43 44 ssize_t valid_uri_scheme(const char *str) 45 { 46 const char *cp = str; 47 int ch = *cp++; 48 49 /* Per RFC 3986, a valid scheme starts with ALPHA. */ 50 if (!ISALPHA(ch)) 51 return (0); 52 53 while ((ch = *cp++) != 0) { 54 /* A valid scheme continues with ALPHA | DIGIT | '+' | '-'. */ 55 if (ISALNUM(ch) || ch == '+' || ch == '-') 56 continue; 57 /* A valid scheme is followed by "://". */ 58 if (ch == ':' && *cp++ == '/' && *cp++ == '/') 59 return (cp - str); 60 /* Not a valid scheme. */ 61 break; 62 } 63 /* Not a valid scheme. */ 64 return (0); 65 } 66 67 #ifdef TEST 68 69 typedef struct TEST_CASE { 70 const char *label; 71 const char *input; 72 const ssize_t want; 73 } TEST_CASE; 74 75 #define PASS (0) 76 #define FAIL (1) 77 78 static const TEST_CASE test_cases[] = { 79 {"accepts_alpha_scheme", "abcd://blah", sizeof("abcd://") - 1}, 80 {"accepts_mixed_scheme", "a-bcd+123://blah", sizeof("a-bcd+123://") - 1}, 81 {"rejects_minus_first", "-bcd+123://blah'", 0}, 82 {"rejects_plus_first", "+123://blah", 0}, 83 {"rejects_digit_first", "123://blah", 0}, 84 {"rejects_other_first", "?123://blah", 0}, 85 {"rejects_other_middle", "abcd?123://blah", 0}, 86 {"rejects_other_end", "abcd-123?://blah", 0}, 87 {"rejects_non_scheme", "inet:host:port", 0}, 88 {"rejects_no_colon", "inet", 0}, 89 {"rejects_colon_slash", "abcd:/blah", 0}, 90 {"rejects_empty", "", 0}, 91 {0,} 92 }; 93 94 static int test_validate_scheme(const TEST_CASE *tp) 95 { 96 int got; 97 98 got = valid_uri_scheme(tp->input); 99 if (got != tp->want) { 100 msg_warn("got '%ld', want '%ld'", (long) got, (long) tp->want); 101 return (FAIL); 102 } 103 return (PASS); 104 } 105 106 int main(int argc, char **argv) 107 { 108 const TEST_CASE *tp; 109 int pass = 0; 110 int fail = 0; 111 112 msg_vstream_init(sane_basename((VSTRING *) 0, argv[0]), VSTREAM_ERR); 113 114 for (tp = test_cases; tp->label != 0; tp++) { 115 int test_failed; 116 117 msg_info("RUN %s", tp->label); 118 test_failed = test_validate_scheme(tp); 119 if (test_failed) { 120 msg_info("FAIL %s", tp->label); 121 fail++; 122 } else { 123 msg_info("PASS %s", tp->label); 124 pass++; 125 } 126 } 127 msg_info("PASS=%d FAIL=%d", pass, fail); 128 exit(fail != 0); 129 } 130 131 #endif 132