xref: /illumos-gate/usr/src/cmd/ldap/common/fileurl.c (revision 4bc0a2ef)
1 /*
2  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * The contents of this file are subject to the Netscape Public
10  * License Version 1.1 (the "License"); you may not use this file
11  * except in compliance with the License. You may obtain a copy of
12  * the License at http://www.mozilla.org/NPL/
13  *
14  * Software distributed under the License is distributed on an "AS
15  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
16  * implied. See the License for the specific language governing
17  * rights and limitations under the License.
18  *
19  * The Original Code is Mozilla Communicator client code, released
20  * March 31, 1998.
21  *
22  * The Initial Developer of the Original Code is Netscape
23  * Communications Corporation. Portions created by Netscape are
24  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
25  * Rights Reserved.
26  *
27  * Contributor(s):
28  */
29 
30 /*
31  *  LDAP tools fileurl.c -- functions for handling file URLs.
32  *  Used by ldapmodify.
33  */
34 
35 #include "ldaptool.h"
36 #include "fileurl.h"
37 #include <ctype.h>	/* for isalpha() */
38 #ifdef SOLARIS_LDAP_CMD
39 #include <locale.h>
40 #endif	/* SOLARIS_LDAP_CMD */
41 
42 #ifndef SOLARIS_LDAP_CMD
43 #define gettext(s) s
44 #endif
45 
46 static int str_starts_with( const char *s, char *prefix );
47 static void hex_unescape( char *s );
48 static int unhex( char c );
49 static void strcpy_escaped_and_convert( char *s1, char *s2 );
50 static int berval_from_file( const char *path, struct berval *bvp,
51 	int reporterrs );
52 
53 /*
54  * Convert a file URL to a local path.
55  *
56  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *localpathp is
57  * set point to an allocated string.  If not, an different LDAPTOOL_FILEURL_
58  * error code is returned.
59  *
60  * See RFCs 1738 and 2396 for a specification for file URLs... but
61  * Netscape Navigator seems to be a bit more lenient in what it will
62  * accept, especially on Windows).
63  *
64  * This function parses file URLs of these three forms:
65  *
66  *    file:///path
67  *    file:/path
68  *    file://localhost/path
69  *    file://host/path		(rejected with a ...NONLOCAL error)
70  *
71  * On Windows, we convert leading drive letters of the form C| to C:
72  * and if a drive letter is present we strip off the slash that precedes
73  * path.  Otherwise, the leading slash is returned.
74  *
75  */
76 int
77 ldaptool_fileurl2path( const char *fileurl, char **localpathp )
78 {
79     const char	*path;
80     char	*pathcopy;
81 
82     /*
83      * Make sure this is a file URL we can handle.
84      */
85     if ( !str_starts_with( fileurl, "file:" )) {
86 	return( LDAPTOOL_FILEURL_NOTAFILEURL );
87     }
88 
89     path = fileurl + 5;		/* skip past "file:" scheme prefix */
90 
91     if ( *path != '/' ) {
92 	return( LDAPTOOL_FILEURL_MISSINGPATH );
93     }
94 
95     ++path;			/* skip past '/' at end of "file:/" */
96 
97     if ( *path == '/' ) {
98 	++path;			/* remainder is now host/path or /path */
99 	if ( *path != '/' ) {
100 	    /*
101 	     * Make sure it is for the local host.
102 	     */
103 	    if ( str_starts_with( path, "localhost/" )) {
104 		path += 9;
105 	    } else {
106 		return( LDAPTOOL_FILEURL_NONLOCAL );
107 	    }
108 	}
109     } else {		/* URL is of the form file:/path */
110 	--path;
111     }
112 
113     /*
114      * The remainder is now of the form /path.  On Windows, skip past the
115      * leading slash if a drive letter is present.
116      */
117 #ifdef _WINDOWS
118     if ( isalpha( path[1] ) && ( path[2] == '|' || path[2] == ':' )) {
119 	++path;
120     }
121 #endif /* _WINDOWS */
122 
123     /*
124      * Duplicate the path so we can safely alter it.
125      * Unescape any %HH sequences.
126      */
127     if (( pathcopy = strdup( path )) == NULL ) {
128 	return( LDAPTOOL_FILEURL_NOMEMORY );
129     }
130     hex_unescape( pathcopy );
131 
132 #ifdef _WINDOWS
133     /*
134      * Convert forward slashes to backslashes for Windows.  Also,
135      * if we see a drive letter / vertical bar combination (e.g., c|)
136      * at the beginning of the path, replace the '|' with a ':'.
137      */
138     {
139 	char	*p;
140 
141 	for ( p = pathcopy; *p != '\0'; ++p ) {
142 	    if ( *p == '/' ) {
143 		*p = '\\';
144 	    }
145 	}
146     }
147 
148     if ( isalpha( pathcopy[0] ) && pathcopy[1] == '|' ) {
149 	pathcopy[1] = ':';
150     }
151 #endif /* _WINDOWS */
152 
153     *localpathp = pathcopy;
154     return( LDAPTOOL_FILEURL_SUCCESS );
155 }
156 
157 
158 /*
159  * Convert a local path to a file URL.
160  *
161  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *urlp is
162  * set point to an allocated string.  If not, an different LDAPTOOL_FILEURL_
163  * error code is returned.  At present, the only possible error is
164  * LDAPTOOL_FILEURL_NOMEMORY.
165  *
166  * This function produces file URLs of the form file:path.
167  *
168  * On Windows, we convert leading drive letters to C|.
169  *
170  */
171 int
172 ldaptool_path2fileurl( char *path, char **urlp )
173 {
174     char	*p, *url, *prefix ="file:";
175 
176     if ( NULL == path ) {
177 	path = "/";
178     }
179 
180     /*
181      * Allocate space for the URL, taking into account that path may
182      * expand during the hex escaping process.
183      */
184     if (( url = malloc( strlen( prefix ) + 3 * strlen( path ) + 1 )) == NULL ) {
185 	return( LDAPTOOL_FILEURL_NOMEMORY );
186     }
187 
188     strcpy( url, prefix );
189     p = url + strlen( prefix );
190 
191 #ifdef _WINDOWS
192     /*
193      * On Windows, convert leading drive letters (e.g., C:) to the correct URL
194      * syntax (e.g., C|).
195      */
196     if ( isalpha( path[0] ) && path[1] == ':' ) {
197 	*p++ = path[0];
198 	*p++ = '|';
199 	path += 2;
200 	*p = '\0';
201     }
202 #endif /* _WINDOWS */
203 
204     /*
205      * Append the path, encoding any URL-special characters using the %HH
206      * convention.
207      * On Windows, convert backwards slashes in the path to forward ones.
208      */
209     strcpy_escaped_and_convert( p, path );
210 
211     *urlp = url;
212     return( LDAPTOOL_FILEURL_SUCCESS );
213 }
214 
215 
216 /*
217  * Populate *bvp from "value" of length "vlen."
218  *
219  * If recognize_url_syntax is non-zero, :<fileurl is recognized.
220  * If always_try_file is recognized and no file URL was found, an
221  * attempt is made to stat and read the value as if it were the name
222  * of a file.
223  *
224  * If reporterrs is non-zero, specific error messages are printed to
225  * stderr.
226  *
227  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and bvp->bv_len
228  * and bvp->bv_val are set (the latter is set to malloc'd memory).
229  * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned.
230  */
231 int
232 ldaptool_berval_from_ldif_value( const char *value, int vlen,
233 	struct berval *bvp, int recognize_url_syntax, int always_try_file,
234 	int reporterrs )
235 {
236     int	rc = LDAPTOOL_FILEURL_SUCCESS;	/* optimistic */
237     const char	*url = NULL;
238     struct stat	fstats;
239 
240     /* recognize "attr :< url" syntax if LDIF version is >= 1 */
241 
242 #ifdef notdef
243     if ( ldaptool_verbose ) {
244 	fprintf( stderr, gettext("%s: ldaptool_berval_from_ldif_value: value: %s\n"),
245 	    ldaptool_progname, value);
246     }
247 #endif
248 
249     if ( recognize_url_syntax && *value == '<' ) {
250         for ( url = value + 1; isspace( *url ); ++url ) {
251 	    ;	/* NULL */
252 	}
253 
254 	if (strlen(url) > 7 && strncasecmp(url, "file://", 7) != 0) {
255 	    /*
256 	     * We only support file:// URLs for now.
257 	     */
258 	    url = NULL;
259 	}
260     }
261 
262     if ( NULL != url ) {
263 	char		*path;
264 
265 	rc = ldaptool_fileurl2path( url, &path );
266 	switch( rc ) {
267 	case LDAPTOOL_FILEURL_NOTAFILEURL:
268 	    if ( reporterrs ) fprintf( stderr, gettext("%s: unsupported URL \"%s\";"
269 				       " use a file:// URL instead.\n"), ldaptool_progname, url );
270 	    break;
271 
272 	case LDAPTOOL_FILEURL_MISSINGPATH:
273 	    if ( reporterrs ) fprintf( stderr,
274 				       gettext("%s: unable to process URL \"%s\" --"
275 				       " missing path.\n"), ldaptool_progname, url );
276 	    break;
277 
278 	case LDAPTOOL_FILEURL_NONLOCAL:
279 	    if ( reporterrs ) fprintf( stderr,
280 				       gettext("%s: unable to process URL \"%s\" -- only"
281 				       " local file:// URLs are supported.\n"),
282 				       ldaptool_progname, url );
283 	    break;
284 
285 	case LDAPTOOL_FILEURL_NOMEMORY:
286 	    if ( reporterrs ) perror( "ldaptool_fileurl2path" );
287 	    break;
288 
289 	case LDAPTOOL_FILEURL_SUCCESS:
290 	    if ( stat( path, &fstats ) != 0 ) {
291 		if ( reporterrs ) perror( path );
292 	    } else if (S_ISDIR(fstats.st_mode)) {
293 		if ( reporterrs ) fprintf( stderr,
294 					   gettext("%s: %s is a directory, not a file\n"),
295 					   ldaptool_progname, path );
296 		rc = LDAPTOOL_FILEURL_FILEIOERROR;
297 	    } else {
298 		rc = berval_from_file( path, bvp, reporterrs );
299 	    }
300 	    free( path );
301 	    break;
302 
303 	default:
304 	    if ( reporterrs ) fprintf( stderr,
305 				       gettext("%s: unable to process URL \"%s\""
306 				       " -- unknown error\n"), ldaptool_progname, url );
307 	}
308     } else if ( always_try_file && (stat( value, &fstats ) == 0) &&
309 		!S_ISDIR(fstats.st_mode)) {	/* get value from file */
310 	rc = berval_from_file( value, bvp, reporterrs );
311     } else {
312 	bvp->bv_len = vlen;
313 	if (( bvp->bv_val = (char *)malloc( vlen + 1 )) == NULL ) {
314 	    if ( reporterrs ) perror( "malloc" );
315 	    rc = LDAPTOOL_FILEURL_NOMEMORY;
316 	} else {
317 	    SAFEMEMCPY( bvp->bv_val, value, vlen );
318 	    bvp->bv_val[ vlen ] = '\0';
319 	}
320     }
321 
322     return( rc );
323 }
324 
325 
326 /*
327  * Map an LDAPTOOL_FILEURL_ error code to an LDAP error code (crude).
328  */
329 int
330 ldaptool_fileurlerr2ldaperr( int lderr )
331 {
332     int		rc;
333 
334     switch( lderr ) {
335     case LDAPTOOL_FILEURL_SUCCESS:
336 	rc = LDAP_SUCCESS;
337 	break;
338     case LDAPTOOL_FILEURL_NOMEMORY:
339 	rc = LDAP_NO_MEMORY;
340 	break;
341     default:
342 	rc = LDAP_PARAM_ERROR;
343     }
344 
345     return( rc );
346 }
347 
348 
349 /*
350  * Populate *bvp with the contents of the file named by "path".
351  *
352  * If reporterrs is non-zero, specific error messages are printed to
353  * stderr.
354  *
355  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and bvp->bv_len
356  * and bvp->bv_val are set (the latter is set to malloc'd memory).
357  * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned.
358  */
359 
360 static int
361 berval_from_file( const char *path, struct berval *bvp, int reporterrs )
362 {
363     FILE	*fp;
364     long	rlen;
365     int		eof;
366 #if defined( XP_WIN32 )
367     char	mode[20] = "r+b";
368 #else
369     char	mode[20] = "r";
370 #endif
371 
372 #ifdef SOLARIS_LDAP_CMD
373     if (( fp = fopen( path, mode )) == NULL ) {
374 #else
375     if (( fp = ldaptool_open_file( path, mode )) == NULL ) {
376 #endif	/* SOLARIS_LDAP_CMD */
377 	if ( reporterrs ) perror( path );
378 	return( LDAPTOOL_FILEURL_FILEIOERROR );
379     }
380 
381     if ( fseek( fp, 0L, SEEK_END ) != 0 ) {
382 	if ( reporterrs ) perror( path );
383 	fclose( fp );
384 	return( LDAPTOOL_FILEURL_FILEIOERROR );
385     }
386 
387     bvp->bv_len = ftell( fp );
388 
389     if (( bvp->bv_val = (char *)malloc( bvp->bv_len + 1 )) == NULL ) {
390 	if ( reporterrs ) perror( "malloc" );
391 	fclose( fp );
392 	return( LDAPTOOL_FILEURL_NOMEMORY );
393     }
394 
395     if ( fseek( fp, 0L, SEEK_SET ) != 0 ) {
396 	if ( reporterrs ) perror( path );
397 	fclose( fp );
398 	return( LDAPTOOL_FILEURL_FILEIOERROR );
399     }
400 
401     rlen = fread( bvp->bv_val, 1, bvp->bv_len, fp );
402     eof = feof( fp );
403     fclose( fp );
404 
405     if ( rlen != (long)bvp->bv_len ) {
406 	if ( reporterrs ) perror( path );
407 	free( bvp->bv_val );
408 	return( LDAPTOOL_FILEURL_FILEIOERROR );
409     }
410 
411     bvp->bv_val[ bvp->bv_len ] = '\0';
412     return( LDAPTOOL_FILEURL_SUCCESS );
413 }
414 
415 
416 /*
417  * Return a non-zero value if the string s begins with prefix and zero if not.
418  */
419 static int
420 str_starts_with( const char *s, char *prefix )
421 {
422     size_t	prefix_len;
423 
424     if ( s == NULL || prefix == NULL ) {
425 	return( 0 );
426     }
427 
428     prefix_len = strlen( prefix );
429     if ( strlen( s ) < prefix_len ) {
430 	return( 0 );
431     }
432 
433     return( strncmp( s, prefix, prefix_len ) == 0 );
434 }
435 
436 
437 /*
438  * Remove URL hex escapes from s... done in place.  The basic concept for
439  * this routine is borrowed from the WWW library HTUnEscape() routine.
440  *
441  * A similar function called nsldapi_hex_unescape can be found in
442  * ../../libraries/libldap/unescape.c
443  */
444 static void
445 hex_unescape( char *s )
446 {
447 	char	*p;
448 
449 	for ( p = s; *s != '\0'; ++s ) {
450 		if ( *s == '%' ) {
451 			if ( *++s != '\0' ) {
452 				*p = unhex( *s ) << 4;
453 			}
454 			if ( *++s != '\0' ) {
455 				*p++ += unhex( *s );
456 			}
457 		} else {
458 			*p++ = *s;
459 		}
460 	}
461 
462 	*p = '\0';
463 }
464 
465 
466 /*
467  * Return the integer equivalent of one hex digit (in c).
468  *
469  * A similar function can be found in ../../libraries/libldap/unescape.c
470  */
471 static int
472 unhex( char c )
473 {
474 	return( c >= '0' && c <= '9' ? c - '0'
475 	    : c >= 'A' && c <= 'F' ? c - 'A' + 10
476 	    : c - 'a' + 10 );
477 }
478 
479 
480 #define HREF_CHAR_ACCEPTABLE( c )	(( c >= '-' && c <= '9' ) ||	\
481 					 ( c >= '@' && c <= 'Z' ) ||	\
482 					 ( c == '_' ) ||		\
483 					 ( c >= 'a' && c <= 'z' ))
484 
485 /*
486  * Like strcat(), except if any URL-special characters are found in s2
487  * they are escaped using the %HH convention and backslash characters are
488  * converted to forward slashes on Windows.
489  *
490  * Maximum space needed in s1 is 3 * strlen( s2 ) + 1.
491  *
492  * A similar function that does not convert the slashes called
493  * strcat_escaped() can be found in ../../libraries/libldap/tmplout.c
494  */
495 static void
496 strcpy_escaped_and_convert( char *s1, char *s2 )
497 {
498     char	*p, *q;
499     char	*hexdig = "0123456789ABCDEF";
500 
501     p = s1 + strlen( s1 );
502     for ( q = s2; *q != '\0'; ++q ) {
503 #ifdef _WINDOWS
504 	if ( *q == '\\' ) {
505                 *p++ = '/';
506 	} else
507 #endif /* _WINDOWS */
508 
509 	if ( HREF_CHAR_ACCEPTABLE( *q )) {
510 	    *p++ = *q;
511 	} else {
512 	    *p++ = '%';
513 	    *p++ = hexdig[ 0x0F & ((*(unsigned char*)q) >> 4) ];
514 	    *p++ = hexdig[ 0x0F & *q ];
515 	}
516     }
517 
518     *p = '\0';
519 }
520