Line data Source code
1 : #include "fd_url.h"
2 : #include "../../util/cstr/fd_cstr.h"
3 : #include "../../util/log/fd_log.h"
4 :
5 : fd_url_t *
6 : fd_url_parse_cstr( fd_url_t * const url,
7 : char const * const url_str,
8 : ulong const url_str_len,
9 0 : int * opt_err ) {
10 0 : int err_[1];
11 0 : if( !opt_err ) opt_err = err_;
12 0 : *opt_err = FD_URL_SUCCESS;
13 :
14 0 : char const * const url_end = url_str+url_str_len;
15 :
16 0 : char const * const scheme = url_str;
17 0 : ulong scheme_len = 0UL;
18 0 : if( FD_UNLIKELY( url_str_len<8UL ) ) return NULL;
19 0 : if( fd_memeq( scheme, "http://", 7 ) ) {
20 0 : scheme_len = 7;
21 0 : } else if( fd_memeq( scheme, "https://", 8 ) ) {
22 0 : scheme_len = 8;
23 0 : } else {
24 0 : *opt_err = FD_URL_ERR_SCHEME;
25 0 : return NULL;
26 0 : }
27 :
28 0 : char const * const authority = scheme+scheme_len;
29 :
30 : /* Find end of authority */
31 0 : char const * authority_end;
32 0 : for( authority_end = authority;
33 0 : authority_end < url_end &&
34 0 : *authority_end != '/' &&
35 0 : *authority_end != '?' &&
36 0 : *authority_end != '#';
37 0 : authority_end++ ) {
38 0 : if( FD_UNLIKELY( *authority_end=='@' ) ) {
39 0 : *opt_err = FD_URL_ERR_USERINFO;
40 0 : return NULL; /* userinfo not supported */
41 0 : }
42 0 : }
43 0 : ulong const authority_len = (ulong)( authority_end-authority );
44 :
45 : /* Find port number */
46 0 : char const * const host = authority;
47 0 : ulong host_len = authority_len;
48 0 : char const * port = NULL;
49 0 : ulong port_len = 0UL;
50 0 : for( ulong j=0UL; j<authority_len; j++ ) {
51 0 : if( authority[ j ]==':' ) {
52 0 : host_len = j;
53 0 : port = authority +j+1;
54 0 : port_len = authority_len-j-1;
55 0 : break;
56 0 : }
57 0 : }
58 :
59 0 : if( FD_UNLIKELY( host_len>=FD_FQDN_BUF_MAX ) ) {
60 0 : *opt_err = FD_URL_ERR_HOST_OVERSZ;
61 0 : return NULL;
62 0 : }
63 :
64 :
65 0 : *url = (fd_url_t){
66 0 : .scheme = scheme,
67 0 : .scheme_len = scheme_len,
68 0 : .host = host,
69 0 : .host_len = host_len,
70 0 : .port = port,
71 0 : .port_len = port_len,
72 0 : .tail = authority+authority_len,
73 0 : .tail_len = (ulong)( url_end-(authority+authority_len) )
74 0 : };
75 :
76 0 : return url;
77 0 : }
78 :
79 : int
80 : fd_url_parse_endpoint( fd_url_t * url_,
81 : char const * url_str,
82 : ulong url_str_len,
83 : ushort * tcp_port,
84 : _Bool * is_ssl,
85 0 : char const * context ) {
86 0 : char const * ctx = context ? context : "URL";
87 :
88 0 : int url_err[1];
89 0 : fd_url_t * url = fd_url_parse_cstr( url_, url_str, url_str_len, url_err );
90 0 : if( FD_UNLIKELY( !url ) ) {
91 0 : switch( *url_err ) {
92 0 : case FD_URL_ERR_SCHEME:
93 0 : FD_LOG_WARNING(( "Invalid %s `%.*s`: must start with `http://` or `https://`", ctx, (int)url_str_len, url_str ));
94 0 : return -1;
95 0 : case FD_URL_ERR_HOST_OVERSZ:
96 0 : FD_LOG_WARNING(( "Invalid %s `%.*s`: domain name is too long", ctx, (int)url_str_len, url_str ));
97 0 : return -1;
98 0 : case FD_URL_ERR_USERINFO:
99 0 : FD_LOG_WARNING(( "Invalid %s `%.*s`: userinfo is not supported", ctx, (int)url_str_len, url_str ));
100 0 : return -1;
101 0 : default:
102 0 : FD_LOG_WARNING(( "Invalid %s `%.*s`", ctx, (int)url_str_len, url_str ));
103 0 : return -1;
104 0 : }
105 0 : }
106 :
107 : /* fd_url_parse_cstr() already guarantees http:// or https:// */
108 0 : *is_ssl = ( url->scheme_len==8UL );
109 :
110 0 : *tcp_port = *is_ssl ? 443 : 80;
111 0 : if( FD_LIKELY( !url->port ) ) return 0;
112 0 : if( FD_UNLIKELY( !url->port_len || url->port_len>5UL ) ) goto invalid_port;
113 :
114 0 : ulong port_no = 0UL;
115 0 : for( ulong i=0UL; i<url->port_len; i++ ) {
116 0 : uchar c = (uchar)url->port[ i ];
117 0 : if( FD_UNLIKELY( c<(uchar)'0' || c>(uchar)'9' ) ) goto invalid_port;
118 0 : port_no = 10UL*port_no + (ulong)(c-(uchar)'0');
119 0 : }
120 0 : if( FD_UNLIKELY( !port_no || port_no>USHORT_MAX ) ) goto invalid_port;
121 :
122 0 : *tcp_port = (ushort)port_no;
123 :
124 0 : return 0;
125 :
126 0 : invalid_port:
127 0 : FD_LOG_WARNING(( "Invalid %s `%.*s`: invalid port number", ctx, (int)url_str_len, url_str ));
128 0 : return -1;
129 0 : }
130 :
131 :
132 : static inline int
133 0 : fd_hex_unhex( int c ) {
134 0 : if( c>='0' && c<='9' ) return c-'0';
135 0 : if( c>='a' && c<='f' ) return c-'a'+0xa;
136 0 : if( c>='A' && c<='F' ) return c-'A'+0xa;
137 0 : return -1;
138 0 : }
139 :
140 : ulong
141 : fd_url_unescape( char * const msg,
142 0 : ulong const len ) {
143 0 : char * end = msg+len;
144 0 : char * dst = msg;
145 0 : for( char * src=msg; src<end; src++ ) {
146 : /* invariant: dst<=src */
147 0 : if( FD_LIKELY( (*src)!='%' ) ) {
148 0 : *(dst++) = *src;
149 0 : } else {
150 0 : if( FD_UNLIKELY( src+2>=end ) ) return 0UL; /* truncated percent encoding */
151 0 : int hi = fd_hex_unhex( src[1] );
152 0 : int lo = fd_hex_unhex( src[2] );
153 0 : if( FD_UNLIKELY( (hi|lo)<0 ) ) return 0UL; /* invalid hex digit */
154 0 : *(dst++) = (char)( (hi<<4) | lo );
155 0 : src += 2;
156 0 : }
157 0 : }
158 0 : return (ulong)( dst-msg );
159 0 : }
|