+int
+ldap_pvt_str2scope( const char *p )
+{
+ struct berval bv;
+
+ ber_str2bv( p, 0, 0, &bv );
+
+ return ldap_pvt_bv2scope( &bv );
+}
+
+static const char hex[] = "0123456789ABCDEF";
+
+#define URLESC_NONE 0x0000U
+#define URLESC_COMMA 0x0001U
+#define URLESC_SLASH 0x0002U
+
+static int
+hex_escape_len( const char *s, unsigned list )
+{
+ int len;
+
+ if ( s == NULL ) {
+ return 0;
+ }
+
+ for ( len = 0; s[0]; s++ ) {
+ switch ( s[0] ) {
+ /* RFC 2396: reserved */
+ case '?':
+ len += 3;
+ break;
+
+ case ',':
+ if ( list & URLESC_COMMA ) {
+ len += 3;
+ } else {
+ len++;
+ }
+ break;
+
+ case '/':
+ if ( list & URLESC_SLASH ) {
+ len += 3;
+ } else {
+ len++;
+ }
+ break;
+
+ case ';':
+ case ':':
+ case '@':
+ case '&':
+ case '=':
+ case '+':
+ case '$':
+
+ /* RFC 2396: unreserved mark */
+ case '-':
+ case '_':
+ case '.':
+ case '!':
+ case '~':
+ case '*':
+ case '\'':
+ case '(':
+ case ')':
+ len++;
+ break;
+
+ /* RFC 2396: unreserved alphanum */
+ default:
+ if ( !isalnum( (unsigned char) s[0] ) ) {
+ len += 3;
+ } else {
+ len++;
+ }
+ break;
+ }
+ }
+
+ return len;
+}
+
+static int
+hex_escape( char *buf, int len, const char *s, unsigned list )
+{
+ int i;
+ int pos;
+
+ if ( s == NULL ) {
+ return 0;
+ }
+
+ for ( pos = 0, i = 0; s[i] && pos < len; i++ ) {
+ int escape = 0;
+
+ switch ( s[i] ) {
+ /* RFC 2396: reserved */
+ case '?':
+ escape = 1;
+ break;
+
+ case ',':
+ if ( list & URLESC_COMMA ) {
+ escape = 1;
+ }
+ break;
+
+ case '/':
+ if ( list & URLESC_SLASH ) {
+ escape = 1;
+ }
+ break;
+
+ case ';':
+ case ':':
+ case '@':
+ case '&':
+ case '=':
+ case '+':
+ case '$':
+
+ /* RFC 2396: unreserved mark */
+ case '-':
+ case '_':
+ case '.':
+ case '!':
+ case '~':
+ case '*':
+ case '\'':
+ case '(':
+ case ')':
+ break;
+
+ /* RFC 2396: unreserved alphanum */
+ default:
+ if ( !isalnum( (unsigned char) s[i] ) ) {
+ escape = 1;
+ }
+ break;
+ }
+
+ if ( escape ) {
+ buf[pos++] = '%';
+ buf[pos++] = hex[ (s[i] >> 4) & 0x0f ];
+ buf[pos++] = hex[ s[i] & 0x0f ];
+
+ } else {
+ buf[pos++] = s[i];
+ }
+ }
+
+ buf[pos] = '\0';
+
+ return pos;
+}
+
+static int
+hex_escape_len_list( char **s, unsigned flags )
+{
+ int len;
+ int i;
+
+ if ( s == NULL ) {
+ return 0;
+ }
+
+ len = 0;
+ for ( i = 0; s[i] != NULL; i++ ) {
+ if ( len ) {
+ len++;
+ }
+ len += hex_escape_len( s[i], flags );
+ }
+
+ return len;
+}
+
+static int
+hex_escape_list( char *buf, int len, char **s, unsigned flags )
+{
+ int pos;
+ int i;
+
+ if ( s == NULL ) {
+ return 0;
+ }
+
+ pos = 0;
+ for ( i = 0; s[i] != NULL; i++ ) {
+ int curlen;
+
+ if ( pos ) {
+ buf[pos++] = ',';
+ len--;
+ }
+ curlen = hex_escape( &buf[pos], len, s[i], flags );
+ len -= curlen;
+ pos += curlen;
+ }
+
+ return pos;
+}
+
+static int
+desc2str_len( LDAPURLDesc *u )
+{
+ int sep = 0;
+ int len = 0;
+ int is_ipc = 0;
+ struct berval scope;
+
+ if ( u == NULL || u->lud_scheme == NULL ) {
+ return -1;
+ }
+
+ if ( !strcmp( "ldapi", u->lud_scheme )) {
+ is_ipc = 1;
+ }
+
+ if ( u->lud_exts ) {
+ len += hex_escape_len_list( u->lud_exts, URLESC_COMMA );
+ if ( !sep ) {
+ sep = 5;
+ }
+ }
+
+ if ( u->lud_filter ) {
+ len += hex_escape_len( u->lud_filter, URLESC_NONE );
+ if ( !sep ) {
+ sep = 4;
+ }
+ }
+
+ if ( ldap_pvt_scope2bv( u->lud_scope, &scope ) == LDAP_SUCCESS ) {
+ len += scope.bv_len;
+ if ( !sep ) {
+ sep = 3;
+ }
+ }
+
+ if ( u->lud_attrs ) {
+ len += hex_escape_len_list( u->lud_attrs, URLESC_NONE );
+ if ( !sep ) {
+ sep = 2;
+ }
+ }
+
+ if ( u->lud_dn && u->lud_dn[0] ) {
+ len += hex_escape_len( u->lud_dn, URLESC_NONE );
+ if ( !sep ) {
+ sep = 1;
+ }
+ };
+
+ len += sep;
+
+ if ( u->lud_port ) {
+ unsigned p = u->lud_port;
+ if ( p > 65535 )
+ return -1;
+
+ len += (p > 999 ? 5 + (p > 9999) : p > 99 ? 4 : 2 + (p > 9));
+ }
+
+ if ( u->lud_host && u->lud_host[0] ) {
+ char *ptr;
+ len += hex_escape_len( u->lud_host, URLESC_SLASH );
+ if ( !is_ipc && ( ptr = strchr( u->lud_host, ':' ))) {
+ if ( strchr( ptr+1, ':' ))
+ len += 2; /* IPv6, [] */
+ }
+ }
+
+ len += strlen( u->lud_scheme ) + STRLENOF( "://" );
+
+ return len;
+}
+
+static int
+desc2str( LDAPURLDesc *u, char *s, int len )
+{
+ int i;
+ int sep = 0;
+ int sofar = 0;
+ int is_v6 = 0;
+ int is_ipc = 0;
+ struct berval scope = BER_BVNULL;
+ char *ptr;
+
+ if ( u == NULL ) {
+ return -1;
+ }
+
+ if ( s == NULL ) {
+ return -1;
+ }
+
+ if ( u->lud_scheme && !strcmp( "ldapi", u->lud_scheme )) {
+ is_ipc = 1;
+ }
+
+ ldap_pvt_scope2bv( u->lud_scope, &scope );
+
+ if ( u->lud_exts ) {
+ sep = 5;
+ } else if ( u->lud_filter ) {
+ sep = 4;
+ } else if ( !BER_BVISEMPTY( &scope ) ) {
+ sep = 3;
+ } else if ( u->lud_attrs ) {
+ sep = 2;
+ } else if ( u->lud_dn && u->lud_dn[0] ) {
+ sep = 1;
+ }
+
+ if ( !is_ipc && u->lud_host && ( ptr = strchr( u->lud_host, ':' ))) {
+ if ( strchr( ptr+1, ':' ))
+ is_v6 = 1;
+ }
+
+ if ( u->lud_port ) {
+ sofar = sprintf( s, "%s://%s%s%s:%d", u->lud_scheme,
+ is_v6 ? "[" : "",
+ u->lud_host ? u->lud_host : "",
+ is_v6 ? "]" : "",
+ u->lud_port );
+ len -= sofar;
+
+ } else {
+ sofar = sprintf( s, "%s://", u->lud_scheme );
+ len -= sofar;
+ if ( u->lud_host && u->lud_host[0] ) {
+ if ( is_v6 ) {
+ s[sofar++] = '[';
+ len--;
+ }
+ i = hex_escape( &s[sofar], len, u->lud_host, URLESC_SLASH );
+ sofar += i;
+ len -= i;
+ if ( is_v6 ) {
+ s[sofar++] = ']';
+ len--;
+ }
+ }
+ }
+
+ assert( len >= 0 );
+
+ if ( sep < 1 ) {
+ goto done;
+ }
+
+ s[sofar++] = '/';
+ len--;
+
+ assert( len >= 0 );
+
+ if ( u->lud_dn && u->lud_dn[0] ) {
+ i = hex_escape( &s[sofar], len, u->lud_dn, URLESC_NONE );
+ sofar += i;
+ len -= i;
+
+ assert( len >= 0 );
+ }
+
+ if ( sep < 2 ) {
+ goto done;
+ }
+ s[sofar++] = '?';
+ len--;
+
+ assert( len >= 0 );
+
+ i = hex_escape_list( &s[sofar], len, u->lud_attrs, URLESC_NONE );
+ sofar += i;
+ len -= i;
+
+ assert( len >= 0 );
+
+ if ( sep < 3 ) {
+ goto done;
+ }
+ s[sofar++] = '?';
+ len--;
+
+ assert( len >= 0 );
+
+ if ( !BER_BVISNULL( &scope ) ) {
+ strcpy( &s[sofar], scope.bv_val );
+ sofar += scope.bv_len;
+ len -= scope.bv_len;
+ }
+
+ assert( len >= 0 );
+
+ if ( sep < 4 ) {
+ goto done;
+ }
+ s[sofar++] = '?';
+ len--;
+
+ assert( len >= 0 );
+
+ i = hex_escape( &s[sofar], len, u->lud_filter, URLESC_NONE );
+ sofar += i;
+ len -= i;
+
+ assert( len >= 0 );
+
+ if ( sep < 5 ) {
+ goto done;
+ }
+ s[sofar++] = '?';
+ len--;
+
+ assert( len >= 0 );
+
+ i = hex_escape_list( &s[sofar], len, u->lud_exts, URLESC_COMMA );
+ sofar += i;
+ len -= i;
+
+ assert( len >= 0 );
+
+done:
+ if ( len < 0 ) {
+ return -1;
+ }
+
+ return sofar;
+}
+
+char *
+ldap_url_desc2str( LDAPURLDesc *u )
+{
+ int len;
+ char *s;
+
+ if ( u == NULL ) {
+ return NULL;
+ }
+
+ len = desc2str_len( u );
+ if ( len < 0 ) {
+ return NULL;
+ }
+
+ /* allocate enough to hex escape everything -- overkill */
+ s = LDAP_MALLOC( len + 1 );
+
+ if ( s == NULL ) {
+ return NULL;
+ }
+
+ if ( desc2str( u, s, len ) != len ) {
+ LDAP_FREE( s );
+ return NULL;
+ }
+
+ s[len] = '\0';
+
+ return s;
+}