xref: /netbsd-src/external/bsd/openldap/dist/libraries/libldap/url.c (revision b5677b36047b601b9addaaa494a58ceae82c2a6c)
1 /* LIBLDAP url.c -- LDAP URL (RFC 4516) related routines */
2 /* $OpenLDAP: pkg/ldap/libraries/libldap/url.c,v 1.94.2.8 2008/02/11 23:41:37 quanah Exp $ */
3 /* This work is part of OpenLDAP Software <http://www.openldap.org/>.
4  *
5  * Copyright 1998-2008 The OpenLDAP Foundation.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted only as authorized by the OpenLDAP
10  * Public License.
11  *
12  * A copy of this license is available in the file LICENSE in the
13  * top-level directory of the distribution or, alternatively, at
14  * <http://www.OpenLDAP.org/license.html>.
15  */
16 /* Portions Copyright (c) 1996 Regents of the University of Michigan.
17  * All rights reserved.
18  */
19 
20 
21 /*
22  *  LDAP URLs look like this:
23  *    ldap[is]://host[:port][/[dn[?[attributes][?[scope][?[filter][?exts]]]]]]
24  *
25  *  where:
26  *   attributes is a comma separated list
27  *   scope is one of these three strings:  base one sub (default=base)
28  *   filter is an string-represented filter as in RFC 4515
29  *
30  *  e.g.,  ldap://host:port/dc=com?o,cn?base?(o=openldap)?extension
31  *
32  *  We also tolerate URLs that look like: <ldapurl> and <URL:ldapurl>
33  */
34 
35 #include "portable.h"
36 
37 #include <stdio.h>
38 
39 #include <ac/stdlib.h>
40 #include <ac/ctype.h>
41 
42 #include <ac/socket.h>
43 #include <ac/string.h>
44 #include <ac/time.h>
45 
46 #include "ldap-int.h"
47 
48 /* local functions */
49 static const char* skip_url_prefix LDAP_P((
50 	const char *url,
51 	int *enclosedp,
52 	const char **scheme ));
53 
54 int ldap_pvt_url_scheme2proto( const char *scheme )
55 {
56 	assert( scheme != NULL );
57 
58 	if( scheme == NULL ) {
59 		return -1;
60 	}
61 
62 	if( strcmp("ldap", scheme) == 0 ) {
63 		return LDAP_PROTO_TCP;
64 	}
65 
66 	if( strcmp("ldapi", scheme) == 0 ) {
67 		return LDAP_PROTO_IPC;
68 	}
69 
70 	if( strcmp("ldaps", scheme) == 0 ) {
71 		return LDAP_PROTO_TCP;
72 	}
73 #ifdef LDAP_CONNECTIONLESS
74 	if( strcmp("cldap", scheme) == 0 ) {
75 		return LDAP_PROTO_UDP;
76 	}
77 #endif
78 
79 	return -1;
80 }
81 
82 int ldap_pvt_url_scheme_port( const char *scheme, int port )
83 {
84 	assert( scheme != NULL );
85 
86 	if( port ) return port;
87 	if( scheme == NULL ) return port;
88 
89 	if( strcmp("ldap", scheme) == 0 ) {
90 		return LDAP_PORT;
91 	}
92 
93 	if( strcmp("ldapi", scheme) == 0 ) {
94 		return -1;
95 	}
96 
97 	if( strcmp("ldaps", scheme) == 0 ) {
98 		return LDAPS_PORT;
99 	}
100 
101 #ifdef LDAP_CONNECTIONLESS
102 	if( strcmp("cldap", scheme) == 0 ) {
103 		return LDAP_PORT;
104 	}
105 #endif
106 
107 	return -1;
108 }
109 
110 int
111 ldap_pvt_url_scheme2tls( const char *scheme )
112 {
113 	assert( scheme != NULL );
114 
115 	if( scheme == NULL ) {
116 		return -1;
117 	}
118 
119 	return strcmp("ldaps", scheme) == 0;
120 }
121 
122 int
123 ldap_is_ldap_url( LDAP_CONST char *url )
124 {
125 	int	enclosed;
126 	const char * scheme;
127 
128 	if( url == NULL ) {
129 		return 0;
130 	}
131 
132 	if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
133 		return 0;
134 	}
135 
136 	return 1;
137 }
138 
139 int
140 ldap_is_ldaps_url( LDAP_CONST char *url )
141 {
142 	int	enclosed;
143 	const char * scheme;
144 
145 	if( url == NULL ) {
146 		return 0;
147 	}
148 
149 	if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
150 		return 0;
151 	}
152 
153 	return strcmp(scheme, "ldaps") == 0;
154 }
155 
156 int
157 ldap_is_ldapi_url( LDAP_CONST char *url )
158 {
159 	int	enclosed;
160 	const char * scheme;
161 
162 	if( url == NULL ) {
163 		return 0;
164 	}
165 
166 	if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
167 		return 0;
168 	}
169 
170 	return strcmp(scheme, "ldapi") == 0;
171 }
172 
173 #ifdef LDAP_CONNECTIONLESS
174 int
175 ldap_is_ldapc_url( LDAP_CONST char *url )
176 {
177 	int	enclosed;
178 	const char * scheme;
179 
180 	if( url == NULL ) {
181 		return 0;
182 	}
183 
184 	if( skip_url_prefix( url, &enclosed, &scheme ) == NULL ) {
185 		return 0;
186 	}
187 
188 	return strcmp(scheme, "cldap") == 0;
189 }
190 #endif
191 
192 static const char*
193 skip_url_prefix(
194 	const char *url,
195 	int *enclosedp,
196 	const char **scheme )
197 {
198 	/*
199  	 * return non-zero if this looks like a LDAP URL; zero if not
200  	 * if non-zero returned, *urlp will be moved past "ldap://" part of URL
201  	 */
202 	const char *p;
203 
204 	if ( url == NULL ) {
205 		return( NULL );
206 	}
207 
208 	p = url;
209 
210 	/* skip leading '<' (if any) */
211 	if ( *p == '<' ) {
212 		*enclosedp = 1;
213 		++p;
214 	} else {
215 		*enclosedp = 0;
216 	}
217 
218 	/* skip leading "URL:" (if any) */
219 	if ( strncasecmp( p, LDAP_URL_URLCOLON, LDAP_URL_URLCOLON_LEN ) == 0 ) {
220 		p += LDAP_URL_URLCOLON_LEN;
221 	}
222 
223 	/* check for "ldap://" prefix */
224 	if ( strncasecmp( p, LDAP_URL_PREFIX, LDAP_URL_PREFIX_LEN ) == 0 ) {
225 		/* skip over "ldap://" prefix and return success */
226 		p += LDAP_URL_PREFIX_LEN;
227 		*scheme = "ldap";
228 		return( p );
229 	}
230 
231 	/* check for "ldaps://" prefix */
232 	if ( strncasecmp( p, LDAPS_URL_PREFIX, LDAPS_URL_PREFIX_LEN ) == 0 ) {
233 		/* skip over "ldaps://" prefix and return success */
234 		p += LDAPS_URL_PREFIX_LEN;
235 		*scheme = "ldaps";
236 		return( p );
237 	}
238 
239 	/* check for "ldapi://" prefix */
240 	if ( strncasecmp( p, LDAPI_URL_PREFIX, LDAPI_URL_PREFIX_LEN ) == 0 ) {
241 		/* skip over "ldapi://" prefix and return success */
242 		p += LDAPI_URL_PREFIX_LEN;
243 		*scheme = "ldapi";
244 		return( p );
245 	}
246 
247 #ifdef LDAP_CONNECTIONLESS
248 	/* check for "cldap://" prefix */
249 	if ( strncasecmp( p, LDAPC_URL_PREFIX, LDAPC_URL_PREFIX_LEN ) == 0 ) {
250 		/* skip over "cldap://" prefix and return success */
251 		p += LDAPC_URL_PREFIX_LEN;
252 		*scheme = "cldap";
253 		return( p );
254 	}
255 #endif
256 
257 	return( NULL );
258 }
259 
260 int
261 ldap_pvt_scope2bv( int scope, struct berval *bv )
262 {
263 	switch ( scope ) {
264 	case LDAP_SCOPE_BASE:
265 		BER_BVSTR( bv, "base" );
266 		break;
267 
268 	case LDAP_SCOPE_ONELEVEL:
269 		BER_BVSTR( bv, "one" );
270 		break;
271 
272 	case LDAP_SCOPE_SUBTREE:
273 		BER_BVSTR( bv, "sub" );
274 		break;
275 
276 	case LDAP_SCOPE_SUBORDINATE:
277 		BER_BVSTR( bv, "subordinate" );
278 		break;
279 
280 	default:
281 		return LDAP_OTHER;
282 	}
283 
284 	return LDAP_SUCCESS;
285 }
286 
287 const char *
288 ldap_pvt_scope2str( int scope )
289 {
290 	struct berval	bv;
291 
292 	if ( ldap_pvt_scope2bv( scope, &bv ) == LDAP_SUCCESS ) {
293 		return bv.bv_val;
294 	}
295 
296 	return NULL;
297 }
298 
299 int
300 ldap_pvt_bv2scope( struct berval *bv )
301 {
302 	static struct {
303 		struct berval	bv;
304 		int		scope;
305 	}	v[] = {
306 		{ BER_BVC( "one" ),		LDAP_SCOPE_ONELEVEL },
307 		{ BER_BVC( "onelevel" ),	LDAP_SCOPE_ONELEVEL },
308 		{ BER_BVC( "base" ),		LDAP_SCOPE_BASE },
309 		{ BER_BVC( "sub" ),		LDAP_SCOPE_SUBTREE },
310 		{ BER_BVC( "subtree" ),		LDAP_SCOPE_SUBTREE },
311 		{ BER_BVC( "subord" ),		LDAP_SCOPE_SUBORDINATE },
312 		{ BER_BVC( "subordinate" ),	LDAP_SCOPE_SUBORDINATE },
313 		{ BER_BVC( "children" ),	LDAP_SCOPE_SUBORDINATE },
314 		{ BER_BVNULL,			-1 }
315 	};
316 	int	i;
317 
318 	for ( i = 0; v[ i ].scope != -1; i++ ) {
319 		if ( ber_bvstrcasecmp( bv, &v[ i ].bv ) == 0 ) {
320 			return v[ i ].scope;
321 		}
322 	}
323 
324 	return( -1 );
325 }
326 
327 int
328 ldap_pvt_str2scope( const char *p )
329 {
330 	struct berval	bv;
331 
332 	ber_str2bv( p, 0, 0, &bv );
333 
334 	return ldap_pvt_bv2scope( &bv );
335 }
336 
337 static const char	hex[] = "0123456789ABCDEF";
338 
339 #define URLESC_NONE	0x0000U
340 #define URLESC_COMMA	0x0001U
341 #define URLESC_SLASH	0x0002U
342 
343 static int
344 hex_escape_len( const char *s, unsigned list )
345 {
346 	int	len;
347 
348 	if ( s == NULL ) {
349 		return 0;
350 	}
351 
352 	for ( len = 0; s[0]; s++ ) {
353 		switch ( s[0] ) {
354 		/* RFC 2396: reserved */
355 		case '?':
356 			len += 3;
357 			break;
358 
359 		case ',':
360 			if ( list & URLESC_COMMA ) {
361 				len += 3;
362 			} else {
363 				len++;
364 			}
365 			break;
366 
367 		case '/':
368 			if ( list & URLESC_SLASH ) {
369 				len += 3;
370 			} else {
371 				len++;
372 			}
373 			break;
374 
375 		case ';':
376 		case ':':
377 		case '@':
378 		case '&':
379 		case '=':
380 		case '+':
381 		case '$':
382 
383 		/* RFC 2396: unreserved mark */
384 		case '-':
385 		case '_':
386 		case '.':
387 		case '!':
388 		case '~':
389 		case '*':
390 		case '\'':
391 		case '(':
392 		case ')':
393 			len++;
394 			break;
395 
396 		/* RFC 2396: unreserved alphanum */
397 		default:
398 			if ( !isalnum( (unsigned char) s[0] ) ) {
399 				len += 3;
400 			} else {
401 				len++;
402 			}
403 			break;
404 		}
405 	}
406 
407 	return len;
408 }
409 
410 static int
411 hex_escape( char *buf, int len, const char *s, unsigned list )
412 {
413 	int	i;
414 	int	pos;
415 
416 	if ( s == NULL ) {
417 		return 0;
418 	}
419 
420 	for ( pos = 0, i = 0; s[i] && pos < len; i++ ) {
421 		int	escape = 0;
422 
423 		switch ( s[i] ) {
424 		/* RFC 2396: reserved */
425 		case '?':
426 			escape = 1;
427 			break;
428 
429 		case ',':
430 			if ( list & URLESC_COMMA ) {
431 				escape = 1;
432 			}
433 			break;
434 
435 		case '/':
436 			if ( list & URLESC_SLASH ) {
437 				escape = 1;
438 			}
439 			break;
440 
441 		case ';':
442 		case ':':
443 		case '@':
444 		case '&':
445 		case '=':
446 		case '+':
447 		case '$':
448 
449 		/* RFC 2396: unreserved mark */
450 		case '-':
451 		case '_':
452 		case '.':
453 		case '!':
454 		case '~':
455 		case '*':
456 		case '\'':
457 		case '(':
458 		case ')':
459 			break;
460 
461 		/* RFC 2396: unreserved alphanum */
462 		default:
463 			if ( !isalnum( (unsigned char) s[i] ) ) {
464 				escape = 1;
465 			}
466 			break;
467 		}
468 
469 		if ( escape ) {
470 			buf[pos++] = '%';
471 			buf[pos++] = hex[ (s[i] >> 4) & 0x0f ];
472 			buf[pos++] = hex[ s[i] & 0x0f ];
473 
474 		} else {
475 			buf[pos++] = s[i];
476 		}
477 	}
478 
479 	buf[pos] = '\0';
480 
481 	return pos;
482 }
483 
484 static int
485 hex_escape_len_list( char **s, unsigned flags )
486 {
487 	int	len;
488 	int	i;
489 
490 	if ( s == NULL ) {
491 		return 0;
492 	}
493 
494 	len = 0;
495 	for ( i = 0; s[i] != NULL; i++ ) {
496 		if ( len ) {
497 			len++;
498 		}
499 		len += hex_escape_len( s[i], flags );
500 	}
501 
502 	return len;
503 }
504 
505 static int
506 hex_escape_list( char *buf, int len, char **s, unsigned flags )
507 {
508 	int	pos;
509 	int	i;
510 
511 	if ( s == NULL ) {
512 		return 0;
513 	}
514 
515 	pos = 0;
516 	for ( i = 0; s[i] != NULL; i++ ) {
517 		int	curlen;
518 
519 		if ( pos ) {
520 			buf[pos++] = ',';
521 			len--;
522 		}
523 		curlen = hex_escape( &buf[pos], len, s[i], flags );
524 		len -= curlen;
525 		pos += curlen;
526 	}
527 
528 	return pos;
529 }
530 
531 static int
532 desc2str_len( LDAPURLDesc *u )
533 {
534 	int		sep = 0;
535 	int		len = 0;
536 	int		is_ipc = 0;
537 	struct berval	scope;
538 
539 	if ( u == NULL || u->lud_scheme == NULL ) {
540 		return -1;
541 	}
542 
543 	if ( !strcmp( "ldapi", u->lud_scheme )) {
544 		is_ipc = 1;
545 	}
546 
547 	if ( u->lud_exts ) {
548 		len += hex_escape_len_list( u->lud_exts, URLESC_COMMA );
549 		if ( !sep ) {
550 			sep = 5;
551 		}
552 	}
553 
554 	if ( u->lud_filter ) {
555 		len += hex_escape_len( u->lud_filter, URLESC_NONE );
556 		if ( !sep ) {
557 			sep = 4;
558 		}
559 	}
560 
561 	if ( ldap_pvt_scope2bv( u->lud_scope, &scope ) == LDAP_SUCCESS ) {
562 		len += scope.bv_len;
563 		if ( !sep ) {
564 			sep = 3;
565 		}
566 	}
567 
568 	if ( u->lud_attrs ) {
569 		len += hex_escape_len_list( u->lud_attrs, URLESC_NONE );
570 		if ( !sep ) {
571 			sep = 2;
572 		}
573 	}
574 
575 	if ( u->lud_dn && u->lud_dn[0] ) {
576 		len += hex_escape_len( u->lud_dn, URLESC_NONE );
577 		if ( !sep ) {
578 			sep = 1;
579 		}
580 	};
581 
582 	len += sep;
583 
584 	if ( u->lud_port ) {
585 		unsigned p = u->lud_port;
586 		if ( p > 65535 )
587 			return -1;
588 
589 		len += (p > 999 ? 5 + (p > 9999) : p > 99 ? 4 : 2 + (p > 9));
590 	}
591 
592 	if ( u->lud_host && u->lud_host[0] ) {
593 		len += hex_escape_len( u->lud_host, URLESC_SLASH );
594 		if ( !is_ipc && strchr( u->lud_host, ':' )) {
595 			len += 2;	/* IPv6, [] */
596 		}
597 	}
598 
599 	len += strlen( u->lud_scheme ) + STRLENOF( "://" );
600 
601 	return len;
602 }
603 
604 static int
605 desc2str( LDAPURLDesc *u, char *s, int len )
606 {
607 	int		i;
608 	int		sep = 0;
609 	int		sofar = 0;
610 	int		is_v6 = 0;
611 	int		is_ipc = 0;
612 	struct berval	scope = BER_BVNULL;
613 
614 	if ( u == NULL ) {
615 		return -1;
616 	}
617 
618 	if ( s == NULL ) {
619 		return -1;
620 	}
621 
622 	if ( u->lud_scheme && !strcmp( "ldapi", u->lud_scheme )) {
623 		is_ipc = 1;
624 	}
625 
626 	ldap_pvt_scope2bv( u->lud_scope, &scope );
627 
628 	if ( u->lud_exts ) {
629 		sep = 5;
630 	} else if ( u->lud_filter ) {
631 		sep = 4;
632 	} else if ( !BER_BVISEMPTY( &scope ) ) {
633 		sep = 3;
634 	} else if ( u->lud_attrs ) {
635 		sep = 2;
636 	} else if ( u->lud_dn && u->lud_dn[0] ) {
637 		sep = 1;
638 	}
639 
640 	if ( !is_ipc && u->lud_host && strchr( u->lud_host, ':' )) {
641 		is_v6 = 1;
642 	}
643 
644 	if ( u->lud_port ) {
645 		sofar = sprintf( s, "%s://%s%s%s:%d", u->lud_scheme,
646 				is_v6 ? "[" : "",
647 				u->lud_host ? u->lud_host : "",
648 				is_v6 ? "]" : "",
649 				u->lud_port );
650 		len -= sofar;
651 
652 	} else {
653 		sofar = sprintf( s, "%s://", u->lud_scheme );
654 		len -= sofar;
655 		if ( u->lud_host && u->lud_host[0] ) {
656 			if ( is_v6 ) {
657 				s[sofar++] = '[';
658 				len--;
659 			}
660 			i = hex_escape( &s[sofar], len, u->lud_host, URLESC_SLASH );
661 			sofar += i;
662 			len -= i;
663 			if ( is_v6 ) {
664 				s[sofar++] = ']';
665 				len--;
666 			}
667 		}
668 	}
669 
670 	assert( len >= 0 );
671 
672 	if ( sep < 1 ) {
673 		goto done;
674 	}
675 
676 	s[sofar++] = '/';
677 	len--;
678 
679 	assert( len >= 0 );
680 
681 	if ( u->lud_dn && u->lud_dn[0] ) {
682 		i = hex_escape( &s[sofar], len, u->lud_dn, URLESC_NONE );
683 		sofar += i;
684 		len -= i;
685 
686 		assert( len >= 0 );
687 	}
688 
689 	if ( sep < 2 ) {
690 		goto done;
691 	}
692 	s[sofar++] = '?';
693 	len--;
694 
695 	assert( len >= 0 );
696 
697 	i = hex_escape_list( &s[sofar], len, u->lud_attrs, URLESC_NONE );
698 	sofar += i;
699 	len -= i;
700 
701 	assert( len >= 0 );
702 
703 	if ( sep < 3 ) {
704 		goto done;
705 	}
706 	s[sofar++] = '?';
707 	len--;
708 
709 	assert( len >= 0 );
710 
711 	if ( !BER_BVISNULL( &scope ) ) {
712 		strcpy( &s[sofar], scope.bv_val );
713 		sofar += scope.bv_len;
714 		len -= scope.bv_len;
715 	}
716 
717 	assert( len >= 0 );
718 
719 	if ( sep < 4 ) {
720 		goto done;
721 	}
722 	s[sofar++] = '?';
723 	len--;
724 
725 	assert( len >= 0 );
726 
727 	i = hex_escape( &s[sofar], len, u->lud_filter, URLESC_NONE );
728 	sofar += i;
729 	len -= i;
730 
731 	assert( len >= 0 );
732 
733 	if ( sep < 5 ) {
734 		goto done;
735 	}
736 	s[sofar++] = '?';
737 	len--;
738 
739 	assert( len >= 0 );
740 
741 	i = hex_escape_list( &s[sofar], len, u->lud_exts, URLESC_COMMA );
742 	sofar += i;
743 	len -= i;
744 
745 	assert( len >= 0 );
746 
747 done:
748 	if ( len < 0 ) {
749 		return -1;
750 	}
751 
752 	return sofar;
753 }
754 
755 char *
756 ldap_url_desc2str( LDAPURLDesc *u )
757 {
758 	int	len;
759 	char	*s;
760 
761 	if ( u == NULL ) {
762 		return NULL;
763 	}
764 
765 	len = desc2str_len( u );
766 	if ( len < 0 ) {
767 		return NULL;
768 	}
769 
770 	/* allocate enough to hex escape everything -- overkill */
771 	s = LDAP_MALLOC( len + 1 );
772 
773 	if ( s == NULL ) {
774 		return NULL;
775 	}
776 
777 	if ( desc2str( u, s, len ) != len ) {
778 		LDAP_FREE( s );
779 		return NULL;
780 	}
781 
782 	s[len] = '\0';
783 
784 	return s;
785 }
786 
787 int
788 ldap_url_parse_ext( LDAP_CONST char *url_in, LDAPURLDesc **ludpp, unsigned flags )
789 {
790 /*
791  *  Pick apart the pieces of an LDAP URL.
792  */
793 
794 	LDAPURLDesc	*ludp;
795 	char	*p, *q, *r;
796 	int		i, enclosed, proto, is_v6 = 0;
797 	const char *scheme = NULL;
798 	const char *url_tmp;
799 	char *url;
800 
801 	int	check_dn = 1;
802 
803 	if( url_in == NULL || ludpp == NULL ) {
804 		return LDAP_URL_ERR_PARAM;
805 	}
806 
807 #ifndef LDAP_INT_IN_KERNEL
808 	/* Global options may not be created yet
809 	 * We can't test if the global options are initialized
810 	 * because a call to LDAP_INT_GLOBAL_OPT() will try to allocate
811 	 * the options and cause infinite recursion
812 	 */
813 	Debug( LDAP_DEBUG_TRACE, "ldap_url_parse_ext(%s)\n", url_in, 0, 0 );
814 #endif
815 
816 	*ludpp = NULL;	/* pessimistic */
817 
818 	url_tmp = skip_url_prefix( url_in, &enclosed, &scheme );
819 
820 	if ( url_tmp == NULL ) {
821 		return LDAP_URL_ERR_BADSCHEME;
822 	}
823 
824 	assert( scheme != NULL );
825 
826 	proto = ldap_pvt_url_scheme2proto( scheme );
827 	if ( proto == -1 ) {
828 		return LDAP_URL_ERR_BADSCHEME;
829 	}
830 
831 	/* make working copy of the remainder of the URL */
832 	url = LDAP_STRDUP( url_tmp );
833 	if ( url == NULL ) {
834 		return LDAP_URL_ERR_MEM;
835 	}
836 
837 	if ( enclosed ) {
838 		p = &url[strlen(url)-1];
839 
840 		if( *p != '>' ) {
841 			LDAP_FREE( url );
842 			return LDAP_URL_ERR_BADENCLOSURE;
843 		}
844 
845 		*p = '\0';
846 	}
847 
848 	/* allocate return struct */
849 	ludp = (LDAPURLDesc *)LDAP_CALLOC( 1, sizeof( LDAPURLDesc ));
850 
851 	if ( ludp == NULL ) {
852 		LDAP_FREE( url );
853 		return LDAP_URL_ERR_MEM;
854 	}
855 
856 	ludp->lud_next = NULL;
857 	ludp->lud_host = NULL;
858 	ludp->lud_port = 0;
859 	ludp->lud_dn = NULL;
860 	ludp->lud_attrs = NULL;
861 	ludp->lud_scope = ( flags & LDAP_PVT_URL_PARSE_NODEF_SCOPE ) ? LDAP_SCOPE_BASE : LDAP_SCOPE_DEFAULT;
862 	ludp->lud_filter = NULL;
863 	ludp->lud_exts = NULL;
864 
865 	ludp->lud_scheme = LDAP_STRDUP( scheme );
866 
867 	if ( ludp->lud_scheme == NULL ) {
868 		LDAP_FREE( url );
869 		ldap_free_urldesc( ludp );
870 		return LDAP_URL_ERR_MEM;
871 	}
872 
873 	/* scan forward for '/' that marks end of hostport and begin. of dn */
874 	p = strchr( url, '/' );
875 	q = NULL;
876 
877 	if( p != NULL ) {
878 		/* terminate hostport; point to start of dn */
879 		*p++ = '\0';
880 	} else {
881 		/* check for Novell kludge, see below */
882 		p = strchr( url, '?' );
883 		if ( p ) {
884 			*p++ = '\0';
885 			q = p;
886 			p = NULL;
887 		}
888 	}
889 
890 	if ( proto != LDAP_PROTO_IPC ) {
891 		/* IPv6 syntax with [ip address]:port */
892 		if ( *url == '[' ) {
893 			r = strchr( url, ']' );
894 			if ( r == NULL ) {
895 				LDAP_FREE( url );
896 				ldap_free_urldesc( ludp );
897 				return LDAP_URL_ERR_BADURL;
898 			}
899 			*r++ = '\0';
900 			q = strchr( r, ':' );
901 			if ( q && q != r ) {
902 				LDAP_FREE( url );
903 				ldap_free_urldesc( ludp );
904 				return LDAP_URL_ERR_BADURL;
905 			}
906 			is_v6 = 1;
907 		} else {
908 			q = strchr( url, ':' );
909 		}
910 
911 		if ( q != NULL ) {
912 			char	*next;
913 
914 			*q++ = '\0';
915 			ldap_pvt_hex_unescape( q );
916 
917 			if( *q == '\0' ) {
918 				LDAP_FREE( url );
919 				ldap_free_urldesc( ludp );
920 				return LDAP_URL_ERR_BADURL;
921 			}
922 
923 			ludp->lud_port = strtol( q, &next, 10 );
924 			if ( next == q || next[0] != '\0' ) {
925 				LDAP_FREE( url );
926 				ldap_free_urldesc( ludp );
927 				return LDAP_URL_ERR_BADURL;
928 			}
929 			/* check for Novell kludge */
930 			if ( !p ) {
931 				if ( *next != '\0' ) {
932 					q = &next[1];
933 				} else {
934 					q = NULL;
935 				}
936 			}
937 		}
938 
939 		if ( ( flags & LDAP_PVT_URL_PARSE_DEF_PORT ) && ludp->lud_port == 0 ) {
940 			if ( strcmp( ludp->lud_scheme, "ldaps" ) == 0 ) {
941 				ludp->lud_port = LDAPS_PORT;
942 			} else {
943 				ludp->lud_port = LDAP_PORT;
944 			}
945 		}
946 	}
947 
948 	ldap_pvt_hex_unescape( url );
949 
950 	/* If [ip address]:port syntax, url is [ip and we skip the [ */
951 	ludp->lud_host = LDAP_STRDUP( url + is_v6 );
952 
953 	if( ludp->lud_host == NULL ) {
954 		LDAP_FREE( url );
955 		ldap_free_urldesc( ludp );
956 		return LDAP_URL_ERR_MEM;
957 	}
958 
959 	if ( ( flags & LDAP_PVT_URL_PARSE_NOEMPTY_HOST )
960 		&& ludp->lud_host != NULL
961 		&& *ludp->lud_host == '\0' )
962 	{
963 		LDAP_FREE( ludp->lud_host );
964 		ludp->lud_host = NULL;
965 	}
966 
967 	/*
968 	 * Kludge.  ldap://111.222.333.444:389??cn=abc,o=company
969 	 *
970 	 * On early Novell releases, search references/referrals were returned
971 	 * in this format, i.e., the dn was kind of in the scope position,
972 	 * but the required slash is missing. The whole thing is illegal syntax,
973 	 * but we need to account for it. Fortunately it can't be confused with
974 	 * anything real.
975 	 */
976 	if( (p == NULL) && (q != NULL) && (*q == '?') ) {
977 		/* ? immediately followed by question */
978 		q++;
979 		if( *q != '\0' ) {
980 			/* parse dn part */
981 			ldap_pvt_hex_unescape( q );
982 			ludp->lud_dn = LDAP_STRDUP( q );
983 
984 		} else if ( !( flags & LDAP_PVT_URL_PARSE_NOEMPTY_DN ) ) {
985 			ludp->lud_dn = LDAP_STRDUP( "" );
986 
987 		} else {
988 			check_dn = 0;
989 		}
990 
991 		if ( check_dn && ludp->lud_dn == NULL ) {
992 			LDAP_FREE( url );
993 			ldap_free_urldesc( ludp );
994 			return LDAP_URL_ERR_MEM;
995 		}
996 	}
997 
998 	if( p == NULL ) {
999 		LDAP_FREE( url );
1000 		*ludpp = ludp;
1001 		return LDAP_URL_SUCCESS;
1002 	}
1003 
1004 	/* scan forward for '?' that may marks end of dn */
1005 	q = strchr( p, '?' );
1006 
1007 	if( q != NULL ) {
1008 		/* terminate dn part */
1009 		*q++ = '\0';
1010 	}
1011 
1012 	if( *p != '\0' ) {
1013 		/* parse dn part */
1014 		ldap_pvt_hex_unescape( p );
1015 		ludp->lud_dn = LDAP_STRDUP( p );
1016 
1017 	} else if ( !( flags & LDAP_PVT_URL_PARSE_NOEMPTY_DN ) ) {
1018 		ludp->lud_dn = LDAP_STRDUP( "" );
1019 
1020 	} else {
1021 		check_dn = 0;
1022 	}
1023 
1024 	if( check_dn && ludp->lud_dn == NULL ) {
1025 		LDAP_FREE( url );
1026 		ldap_free_urldesc( ludp );
1027 		return LDAP_URL_ERR_MEM;
1028 	}
1029 
1030 	if( q == NULL ) {
1031 		/* no more */
1032 		LDAP_FREE( url );
1033 		*ludpp = ludp;
1034 		return LDAP_URL_SUCCESS;
1035 	}
1036 
1037 	/* scan forward for '?' that may marks end of attributes */
1038 	p = q;
1039 	q = strchr( p, '?' );
1040 
1041 	if( q != NULL ) {
1042 		/* terminate attributes part */
1043 		*q++ = '\0';
1044 	}
1045 
1046 	if( *p != '\0' ) {
1047 		/* parse attributes */
1048 		ldap_pvt_hex_unescape( p );
1049 		ludp->lud_attrs = ldap_str2charray( p, "," );
1050 
1051 		if( ludp->lud_attrs == NULL ) {
1052 			LDAP_FREE( url );
1053 			ldap_free_urldesc( ludp );
1054 			return LDAP_URL_ERR_BADATTRS;
1055 		}
1056 	}
1057 
1058 	if ( q == NULL ) {
1059 		/* no more */
1060 		LDAP_FREE( url );
1061 		*ludpp = ludp;
1062 		return LDAP_URL_SUCCESS;
1063 	}
1064 
1065 	/* scan forward for '?' that may marks end of scope */
1066 	p = q;
1067 	q = strchr( p, '?' );
1068 
1069 	if( q != NULL ) {
1070 		/* terminate the scope part */
1071 		*q++ = '\0';
1072 	}
1073 
1074 	if( *p != '\0' ) {
1075 		/* parse the scope */
1076 		ldap_pvt_hex_unescape( p );
1077 		ludp->lud_scope = ldap_pvt_str2scope( p );
1078 
1079 		if( ludp->lud_scope == -1 ) {
1080 			LDAP_FREE( url );
1081 			ldap_free_urldesc( ludp );
1082 			return LDAP_URL_ERR_BADSCOPE;
1083 		}
1084 	}
1085 
1086 	if ( q == NULL ) {
1087 		/* no more */
1088 		LDAP_FREE( url );
1089 		*ludpp = ludp;
1090 		return LDAP_URL_SUCCESS;
1091 	}
1092 
1093 	/* scan forward for '?' that may marks end of filter */
1094 	p = q;
1095 	q = strchr( p, '?' );
1096 
1097 	if( q != NULL ) {
1098 		/* terminate the filter part */
1099 		*q++ = '\0';
1100 	}
1101 
1102 	if( *p != '\0' ) {
1103 		/* parse the filter */
1104 		ldap_pvt_hex_unescape( p );
1105 
1106 		if( ! *p ) {
1107 			/* missing filter */
1108 			LDAP_FREE( url );
1109 			ldap_free_urldesc( ludp );
1110 			return LDAP_URL_ERR_BADFILTER;
1111 		}
1112 
1113 		ludp->lud_filter = LDAP_STRDUP( p );
1114 
1115 		if( ludp->lud_filter == NULL ) {
1116 			LDAP_FREE( url );
1117 			ldap_free_urldesc( ludp );
1118 			return LDAP_URL_ERR_MEM;
1119 		}
1120 	}
1121 
1122 	if ( q == NULL ) {
1123 		/* no more */
1124 		LDAP_FREE( url );
1125 		*ludpp = ludp;
1126 		return LDAP_URL_SUCCESS;
1127 	}
1128 
1129 	/* scan forward for '?' that may marks end of extensions */
1130 	p = q;
1131 	q = strchr( p, '?' );
1132 
1133 	if( q != NULL ) {
1134 		/* extra '?' */
1135 		LDAP_FREE( url );
1136 		ldap_free_urldesc( ludp );
1137 		return LDAP_URL_ERR_BADURL;
1138 	}
1139 
1140 	/* parse the extensions */
1141 	ludp->lud_exts = ldap_str2charray( p, "," );
1142 
1143 	if( ludp->lud_exts == NULL ) {
1144 		LDAP_FREE( url );
1145 		ldap_free_urldesc( ludp );
1146 		return LDAP_URL_ERR_BADEXTS;
1147 	}
1148 
1149 	for( i=0; ludp->lud_exts[i] != NULL; i++ ) {
1150 		ldap_pvt_hex_unescape( ludp->lud_exts[i] );
1151 
1152 		if( *ludp->lud_exts[i] == '!' ) {
1153 			/* count the number of critical extensions */
1154 			ludp->lud_crit_exts++;
1155 		}
1156 	}
1157 
1158 	if( i == 0 ) {
1159 		/* must have 1 or more */
1160 		LDAP_FREE( url );
1161 		ldap_free_urldesc( ludp );
1162 		return LDAP_URL_ERR_BADEXTS;
1163 	}
1164 
1165 	/* no more */
1166 	*ludpp = ludp;
1167 	LDAP_FREE( url );
1168 	return LDAP_URL_SUCCESS;
1169 }
1170 
1171 int
1172 ldap_url_parse( LDAP_CONST char *url_in, LDAPURLDesc **ludpp )
1173 {
1174 	return ldap_url_parse_ext( url_in, ludpp, LDAP_PVT_URL_PARSE_HISTORIC );
1175 }
1176 
1177 LDAPURLDesc *
1178 ldap_url_dup ( LDAPURLDesc *ludp )
1179 {
1180 	LDAPURLDesc *dest;
1181 
1182 	if ( ludp == NULL ) {
1183 		return NULL;
1184 	}
1185 
1186 	dest = LDAP_MALLOC( sizeof(LDAPURLDesc) );
1187 	if (dest == NULL)
1188 		return NULL;
1189 
1190 	*dest = *ludp;
1191 	dest->lud_scheme = NULL;
1192 	dest->lud_host = NULL;
1193 	dest->lud_dn = NULL;
1194 	dest->lud_filter = NULL;
1195 	dest->lud_attrs = NULL;
1196 	dest->lud_exts = NULL;
1197 	dest->lud_next = NULL;
1198 
1199 	if ( ludp->lud_scheme != NULL ) {
1200 		dest->lud_scheme = LDAP_STRDUP( ludp->lud_scheme );
1201 		if (dest->lud_scheme == NULL) {
1202 			ldap_free_urldesc(dest);
1203 			return NULL;
1204 		}
1205 	}
1206 
1207 	if ( ludp->lud_host != NULL ) {
1208 		dest->lud_host = LDAP_STRDUP( ludp->lud_host );
1209 		if (dest->lud_host == NULL) {
1210 			ldap_free_urldesc(dest);
1211 			return NULL;
1212 		}
1213 	}
1214 
1215 	if ( ludp->lud_dn != NULL ) {
1216 		dest->lud_dn = LDAP_STRDUP( ludp->lud_dn );
1217 		if (dest->lud_dn == NULL) {
1218 			ldap_free_urldesc(dest);
1219 			return NULL;
1220 		}
1221 	}
1222 
1223 	if ( ludp->lud_filter != NULL ) {
1224 		dest->lud_filter = LDAP_STRDUP( ludp->lud_filter );
1225 		if (dest->lud_filter == NULL) {
1226 			ldap_free_urldesc(dest);
1227 			return NULL;
1228 		}
1229 	}
1230 
1231 	if ( ludp->lud_attrs != NULL ) {
1232 		dest->lud_attrs = ldap_charray_dup( ludp->lud_attrs );
1233 		if (dest->lud_attrs == NULL) {
1234 			ldap_free_urldesc(dest);
1235 			return NULL;
1236 		}
1237 	}
1238 
1239 	if ( ludp->lud_exts != NULL ) {
1240 		dest->lud_exts = ldap_charray_dup( ludp->lud_exts );
1241 		if (dest->lud_exts == NULL) {
1242 			ldap_free_urldesc(dest);
1243 			return NULL;
1244 		}
1245 	}
1246 
1247 	return dest;
1248 }
1249 
1250 LDAPURLDesc *
1251 ldap_url_duplist (LDAPURLDesc *ludlist)
1252 {
1253 	LDAPURLDesc *dest, *tail, *ludp, *newludp;
1254 
1255 	dest = NULL;
1256 	tail = NULL;
1257 	for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1258 		newludp = ldap_url_dup(ludp);
1259 		if (newludp == NULL) {
1260 			ldap_free_urllist(dest);
1261 			return NULL;
1262 		}
1263 		if (tail == NULL)
1264 			dest = newludp;
1265 		else
1266 			tail->lud_next = newludp;
1267 		tail = newludp;
1268 	}
1269 	return dest;
1270 }
1271 
1272 static int
1273 ldap_url_parselist_int (LDAPURLDesc **ludlist, const char *url, const char *sep, unsigned flags )
1274 
1275 {
1276 	int i, rc;
1277 	LDAPURLDesc *ludp;
1278 	char **urls;
1279 
1280 	assert( ludlist != NULL );
1281 	assert( url != NULL );
1282 
1283 	*ludlist = NULL;
1284 
1285 	if ( sep == NULL ) {
1286 		sep = ", ";
1287 	}
1288 
1289 	urls = ldap_str2charray( url, sep );
1290 	if (urls == NULL)
1291 		return LDAP_URL_ERR_MEM;
1292 
1293 	/* count the URLs... */
1294 	for (i = 0; urls[i] != NULL; i++) ;
1295 	/* ...and put them in the "stack" backward */
1296 	while (--i >= 0) {
1297 		rc = ldap_url_parse_ext( urls[i], &ludp, flags );
1298 		if ( rc != 0 ) {
1299 			ldap_charray_free( urls );
1300 			ldap_free_urllist( *ludlist );
1301 			*ludlist = NULL;
1302 			return rc;
1303 		}
1304 		ludp->lud_next = *ludlist;
1305 		*ludlist = ludp;
1306 	}
1307 	ldap_charray_free( urls );
1308 	return LDAP_URL_SUCCESS;
1309 }
1310 
1311 int
1312 ldap_url_parselist (LDAPURLDesc **ludlist, const char *url )
1313 {
1314 	return ldap_url_parselist_int( ludlist, url, ", ", LDAP_PVT_URL_PARSE_HISTORIC );
1315 }
1316 
1317 int
1318 ldap_url_parselist_ext (LDAPURLDesc **ludlist, const char *url, const char *sep, unsigned flags )
1319 {
1320 	return ldap_url_parselist_int( ludlist, url, sep, flags );
1321 }
1322 
1323 int
1324 ldap_url_parsehosts(
1325 	LDAPURLDesc **ludlist,
1326 	const char *hosts,
1327 	int port )
1328 {
1329 	int i;
1330 	LDAPURLDesc *ludp;
1331 	char **specs, *p;
1332 
1333 	assert( ludlist != NULL );
1334 	assert( hosts != NULL );
1335 
1336 	*ludlist = NULL;
1337 
1338 	specs = ldap_str2charray(hosts, ", ");
1339 	if (specs == NULL)
1340 		return LDAP_NO_MEMORY;
1341 
1342 	/* count the URLs... */
1343 	for (i = 0; specs[i] != NULL; i++) /* EMPTY */;
1344 
1345 	/* ...and put them in the "stack" backward */
1346 	while (--i >= 0) {
1347 		ludp = LDAP_CALLOC( 1, sizeof(LDAPURLDesc) );
1348 		if (ludp == NULL) {
1349 			ldap_charray_free(specs);
1350 			ldap_free_urllist(*ludlist);
1351 			*ludlist = NULL;
1352 			return LDAP_NO_MEMORY;
1353 		}
1354 		ludp->lud_port = port;
1355 		ludp->lud_host = specs[i];
1356 		specs[i] = NULL;
1357 		p = strchr(ludp->lud_host, ':');
1358 		if (p != NULL) {
1359 			/* more than one :, IPv6 address */
1360 			if ( strchr(p+1, ':') != NULL ) {
1361 				/* allow [address] and [address]:port */
1362 				if ( *ludp->lud_host == '[' ) {
1363 					p = LDAP_STRDUP(ludp->lud_host+1);
1364 					/* copied, make sure we free source later */
1365 					specs[i] = ludp->lud_host;
1366 					ludp->lud_host = p;
1367 					p = strchr( ludp->lud_host, ']' );
1368 					if ( p == NULL ) {
1369 						LDAP_FREE(ludp);
1370 						ldap_charray_free(specs);
1371 						return LDAP_PARAM_ERROR;
1372 					}
1373 					*p++ = '\0';
1374 					if ( *p != ':' ) {
1375 						if ( *p != '\0' ) {
1376 							LDAP_FREE(ludp);
1377 							ldap_charray_free(specs);
1378 							return LDAP_PARAM_ERROR;
1379 						}
1380 						p = NULL;
1381 					}
1382 				} else {
1383 					p = NULL;
1384 				}
1385 			}
1386 			if (p != NULL) {
1387 				char	*next;
1388 
1389 				*p++ = 0;
1390 				ldap_pvt_hex_unescape(p);
1391 				ludp->lud_port = strtol( p, &next, 10 );
1392 				if ( next == p || next[0] != '\0' ) {
1393 					LDAP_FREE(ludp);
1394 					ldap_charray_free(specs);
1395 					return LDAP_PARAM_ERROR;
1396 				}
1397 			}
1398 		}
1399 		ldap_pvt_hex_unescape(ludp->lud_host);
1400 		ludp->lud_scheme = LDAP_STRDUP("ldap");
1401 		ludp->lud_next = *ludlist;
1402 		*ludlist = ludp;
1403 	}
1404 
1405 	/* this should be an array of NULLs now */
1406 	/* except entries starting with [ */
1407 	ldap_charray_free(specs);
1408 	return LDAP_SUCCESS;
1409 }
1410 
1411 char *
1412 ldap_url_list2hosts (LDAPURLDesc *ludlist)
1413 {
1414 	LDAPURLDesc *ludp;
1415 	int size;
1416 	char *s, *p, buf[32];	/* big enough to hold a long decimal # (overkill) */
1417 
1418 	if (ludlist == NULL)
1419 		return NULL;
1420 
1421 	/* figure out how big the string is */
1422 	size = 1;	/* nul-term */
1423 	for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1424 		size += strlen(ludp->lud_host) + 1;		/* host and space */
1425 		if (strchr(ludp->lud_host, ':'))        /* will add [ ] below */
1426 			size += 2;
1427 		if (ludp->lud_port != 0)
1428 			size += sprintf(buf, ":%d", ludp->lud_port);
1429 	}
1430 	s = LDAP_MALLOC(size);
1431 	if (s == NULL)
1432 		return NULL;
1433 
1434 	p = s;
1435 	for (ludp = ludlist; ludp != NULL; ludp = ludp->lud_next) {
1436 		if (strchr(ludp->lud_host, ':')) {
1437 			p += sprintf(p, "[%s]", ludp->lud_host);
1438 		} else {
1439 			strcpy(p, ludp->lud_host);
1440 			p += strlen(ludp->lud_host);
1441 		}
1442 		if (ludp->lud_port != 0)
1443 			p += sprintf(p, ":%d", ludp->lud_port);
1444 		*p++ = ' ';
1445 	}
1446 	if (p != s)
1447 		p--;	/* nuke that extra space */
1448 	*p = 0;
1449 	return s;
1450 }
1451 
1452 char *
1453 ldap_url_list2urls(
1454 	LDAPURLDesc *ludlist )
1455 {
1456 	LDAPURLDesc	*ludp;
1457 	int		size, sofar;
1458 	char		*s;
1459 
1460 	if ( ludlist == NULL ) {
1461 		return NULL;
1462 	}
1463 
1464 	/* figure out how big the string is */
1465 	for ( size = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) {
1466 		int	len = desc2str_len( ludp );
1467 		if ( len < 0 ) {
1468 			return NULL;
1469 		}
1470 		size += len + 1;
1471 	}
1472 
1473 	s = LDAP_MALLOC( size );
1474 
1475 	if ( s == NULL ) {
1476 		return NULL;
1477 	}
1478 
1479 	for ( sofar = 0, ludp = ludlist; ludp != NULL; ludp = ludp->lud_next ) {
1480 		int	len;
1481 
1482 		len = desc2str( ludp, &s[sofar], size );
1483 
1484 		if ( len < 0 ) {
1485 			LDAP_FREE( s );
1486 			return NULL;
1487 		}
1488 
1489 		sofar += len;
1490 		size -= len;
1491 
1492 		s[sofar++] = ' ';
1493 		size--;
1494 
1495 		assert( size >= 0 );
1496 	}
1497 
1498 	s[sofar - 1] = '\0';
1499 
1500 	return s;
1501 }
1502 
1503 void
1504 ldap_free_urllist( LDAPURLDesc *ludlist )
1505 {
1506 	LDAPURLDesc *ludp, *next;
1507 
1508 	for (ludp = ludlist; ludp != NULL; ludp = next) {
1509 		next = ludp->lud_next;
1510 		ldap_free_urldesc(ludp);
1511 	}
1512 }
1513 
1514 void
1515 ldap_free_urldesc( LDAPURLDesc *ludp )
1516 {
1517 	if ( ludp == NULL ) {
1518 		return;
1519 	}
1520 
1521 	if ( ludp->lud_scheme != NULL ) {
1522 		LDAP_FREE( ludp->lud_scheme );
1523 	}
1524 
1525 	if ( ludp->lud_host != NULL ) {
1526 		LDAP_FREE( ludp->lud_host );
1527 	}
1528 
1529 	if ( ludp->lud_dn != NULL ) {
1530 		LDAP_FREE( ludp->lud_dn );
1531 	}
1532 
1533 	if ( ludp->lud_filter != NULL ) {
1534 		LDAP_FREE( ludp->lud_filter);
1535 	}
1536 
1537 	if ( ludp->lud_attrs != NULL ) {
1538 		LDAP_VFREE( ludp->lud_attrs );
1539 	}
1540 
1541 	if ( ludp->lud_exts != NULL ) {
1542 		LDAP_VFREE( ludp->lud_exts );
1543 	}
1544 
1545 	LDAP_FREE( ludp );
1546 }
1547 
1548 static int
1549 ldap_int_is_hexpair( char *s )
1550 {
1551 	int	i;
1552 
1553 	for ( i = 0; i < 2; i++ ) {
1554 		if ( s[i] >= '0' && s[i] <= '9' ) {
1555 			continue;
1556 		}
1557 
1558 		if ( s[i] >= 'A' && s[i] <= 'F' ) {
1559 			continue;
1560 		}
1561 
1562 		if ( s[i] >= 'a' && s[i] <= 'f' ) {
1563 			continue;
1564 		}
1565 
1566 		return 0;
1567 	}
1568 
1569 	return 1;
1570 }
1571 
1572 static int
1573 ldap_int_unhex( int c )
1574 {
1575 	return( c >= '0' && c <= '9' ? c - '0'
1576 	    : c >= 'A' && c <= 'F' ? c - 'A' + 10
1577 	    : c - 'a' + 10 );
1578 }
1579 
1580 void
1581 ldap_pvt_hex_unescape( char *s )
1582 {
1583 	/*
1584 	 * Remove URL hex escapes from s... done in place.  The basic concept for
1585 	 * this routine is borrowed from the WWW library HTUnEscape() routine.
1586 	 */
1587 	char	*p,
1588 		*save_s = s;
1589 
1590 	for ( p = s; *s != '\0'; ++s ) {
1591 		if ( *s == '%' ) {
1592 			/*
1593 			 * FIXME: what if '%' is followed
1594 			 * by non-hexpair chars?
1595 			 */
1596 			if ( !ldap_int_is_hexpair( s + 1 ) ) {
1597 				p = save_s;
1598 				break;
1599 			}
1600 
1601 			if ( *++s == '\0' ) {
1602 				break;
1603 			}
1604 			*p = ldap_int_unhex( *s ) << 4;
1605 			if ( *++s == '\0' ) {
1606 				break;
1607 			}
1608 			*p++ += ldap_int_unhex( *s );
1609 		} else {
1610 			*p++ = *s;
1611 		}
1612 	}
1613 
1614 	*p = '\0';
1615 }
1616 
1617