Home | History | Annotate | Line # | Download | only in util
      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