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