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