/* * Copyright 2017 Gary Mills * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is Mozilla Communicator client code, released * March 31, 1998. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998-1999 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ /* * LDAP tools fileurl.c -- functions for handling file URLs. * Used by ldapmodify. */ #include "ldaptool.h" #include "fileurl.h" #include /* for isalpha() */ #ifdef SOLARIS_LDAP_CMD #include #endif /* SOLARIS_LDAP_CMD */ #ifndef SOLARIS_LDAP_CMD #define gettext(s) s #endif static int str_starts_with( const char *s, char *prefix ); static void hex_unescape( char *s ); static int unhex( char c ); static void strcpy_escaped_and_convert( char *s1, char *s2 ); static int berval_from_file( const char *path, struct berval *bvp, int reporterrs ); /* * Convert a file URL to a local path. * * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *localpathp is * set point to an allocated string. If not, an different LDAPTOOL_FILEURL_ * error code is returned. * * See RFCs 1738 and 2396 for a specification for file URLs... but * Netscape Navigator seems to be a bit more lenient in what it will * accept, especially on Windows). * * This function parses file URLs of these three forms: * * file:///path * file:/path * file://localhost/path * file://host/path (rejected with a ...NONLOCAL error) * * On Windows, we convert leading drive letters of the form C| to C: * and if a drive letter is present we strip off the slash that precedes * path. Otherwise, the leading slash is returned. * */ int ldaptool_fileurl2path( const char *fileurl, char **localpathp ) { const char *path; char *pathcopy; /* * Make sure this is a file URL we can handle. */ if ( !str_starts_with( fileurl, "file:" )) { return( LDAPTOOL_FILEURL_NOTAFILEURL ); } path = fileurl + 5; /* skip past "file:" scheme prefix */ if ( *path != '/' ) { return( LDAPTOOL_FILEURL_MISSINGPATH ); } ++path; /* skip past '/' at end of "file:/" */ if ( *path == '/' ) { ++path; /* remainder is now host/path or /path */ if ( *path != '/' ) { /* * Make sure it is for the local host. */ if ( str_starts_with( path, "localhost/" )) { path += 9; } else { return( LDAPTOOL_FILEURL_NONLOCAL ); } } } else { /* URL is of the form file:/path */ --path; } /* * The remainder is now of the form /path. On Windows, skip past the * leading slash if a drive letter is present. */ #ifdef _WINDOWS if ( isalpha( path[1] ) && ( path[2] == '|' || path[2] == ':' )) { ++path; } #endif /* _WINDOWS */ /* * Duplicate the path so we can safely alter it. * Unescape any %HH sequences. */ if (( pathcopy = strdup( path )) == NULL ) { return( LDAPTOOL_FILEURL_NOMEMORY ); } hex_unescape( pathcopy ); #ifdef _WINDOWS /* * Convert forward slashes to backslashes for Windows. Also, * if we see a drive letter / vertical bar combination (e.g., c|) * at the beginning of the path, replace the '|' with a ':'. */ { char *p; for ( p = pathcopy; *p != '\0'; ++p ) { if ( *p == '/' ) { *p = '\\'; } } } if ( isalpha( pathcopy[0] ) && pathcopy[1] == '|' ) { pathcopy[1] = ':'; } #endif /* _WINDOWS */ *localpathp = pathcopy; return( LDAPTOOL_FILEURL_SUCCESS ); } /* * Convert a local path to a file URL. * * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *urlp is * set point to an allocated string. If not, an different LDAPTOOL_FILEURL_ * error code is returned. At present, the only possible error is * LDAPTOOL_FILEURL_NOMEMORY. * * This function produces file URLs of the form file:path. * * On Windows, we convert leading drive letters to C|. * */ int ldaptool_path2fileurl( char *path, char **urlp ) { char *p, *url, *prefix ="file:"; if ( NULL == path ) { path = "/"; } /* * Allocate space for the URL, taking into account that path may * expand during the hex escaping process. */ if (( url = malloc( strlen( prefix ) + 3 * strlen( path ) + 1 )) == NULL ) { return( LDAPTOOL_FILEURL_NOMEMORY ); } strcpy( url, prefix ); p = url + strlen( prefix ); #ifdef _WINDOWS /* * On Windows, convert leading drive letters (e.g., C:) to the correct URL * syntax (e.g., C|). */ if ( isalpha( path[0] ) && path[1] == ':' ) { *p++ = path[0]; *p++ = '|'; path += 2; *p = '\0'; } #endif /* _WINDOWS */ /* * Append the path, encoding any URL-special characters using the %HH * convention. * On Windows, convert backwards slashes in the path to forward ones. */ strcpy_escaped_and_convert( p, path ); *urlp = url; return( LDAPTOOL_FILEURL_SUCCESS ); } /* * Populate *bvp from "value" of length "vlen." * * If recognize_url_syntax is non-zero, :bv_len * and bvp->bv_val are set (the latter is set to malloc'd memory). * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned. */ int ldaptool_berval_from_ldif_value( const char *value, int vlen, struct berval *bvp, int recognize_url_syntax, int always_try_file, int reporterrs ) { int rc = LDAPTOOL_FILEURL_SUCCESS; /* optimistic */ const char *url = NULL; struct stat fstats; /* recognize "attr :< url" syntax if LDIF version is >= 1 */ #ifdef notdef if ( ldaptool_verbose ) { fprintf( stderr, gettext("%s: ldaptool_berval_from_ldif_value: value: %s\n"), ldaptool_progname, value); } #endif if ( recognize_url_syntax && *value == '<' ) { for ( url = value + 1; isspace( *url ); ++url ) { ; /* NULL */ } if (strlen(url) > 7 && strncasecmp(url, "file://", 7) != 0) { /* * We only support file:// URLs for now. */ url = NULL; } } if ( NULL != url ) { char *path; rc = ldaptool_fileurl2path( url, &path ); switch( rc ) { case LDAPTOOL_FILEURL_NOTAFILEURL: if ( reporterrs ) fprintf( stderr, gettext("%s: unsupported URL \"%s\";" " use a file:// URL instead.\n"), ldaptool_progname, url ); break; case LDAPTOOL_FILEURL_MISSINGPATH: if ( reporterrs ) fprintf( stderr, gettext("%s: unable to process URL \"%s\" --" " missing path.\n"), ldaptool_progname, url ); break; case LDAPTOOL_FILEURL_NONLOCAL: if ( reporterrs ) fprintf( stderr, gettext("%s: unable to process URL \"%s\" -- only" " local file:// URLs are supported.\n"), ldaptool_progname, url ); break; case LDAPTOOL_FILEURL_NOMEMORY: if ( reporterrs ) perror( "ldaptool_fileurl2path" ); break; case LDAPTOOL_FILEURL_SUCCESS: if ( stat( path, &fstats ) != 0 ) { if ( reporterrs ) perror( path ); } else if (S_ISDIR(fstats.st_mode)) { if ( reporterrs ) fprintf( stderr, gettext("%s: %s is a directory, not a file\n"), ldaptool_progname, path ); rc = LDAPTOOL_FILEURL_FILEIOERROR; } else { rc = berval_from_file( path, bvp, reporterrs ); } free( path ); break; default: if ( reporterrs ) fprintf( stderr, gettext("%s: unable to process URL \"%s\"" " -- unknown error\n"), ldaptool_progname, url ); } } else if ( always_try_file && (stat( value, &fstats ) == 0) && !S_ISDIR(fstats.st_mode)) { /* get value from file */ rc = berval_from_file( value, bvp, reporterrs ); } else { bvp->bv_len = vlen; if (( bvp->bv_val = (char *)malloc( vlen + 1 )) == NULL ) { if ( reporterrs ) perror( "malloc" ); rc = LDAPTOOL_FILEURL_NOMEMORY; } else { SAFEMEMCPY( bvp->bv_val, value, vlen ); bvp->bv_val[ vlen ] = '\0'; } } return( rc ); } /* * Map an LDAPTOOL_FILEURL_ error code to an LDAP error code (crude). */ int ldaptool_fileurlerr2ldaperr( int lderr ) { int rc; switch( lderr ) { case LDAPTOOL_FILEURL_SUCCESS: rc = LDAP_SUCCESS; break; case LDAPTOOL_FILEURL_NOMEMORY: rc = LDAP_NO_MEMORY; break; default: rc = LDAP_PARAM_ERROR; } return( rc ); } /* * Populate *bvp with the contents of the file named by "path". * * If reporterrs is non-zero, specific error messages are printed to * stderr. * * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and bvp->bv_len * and bvp->bv_val are set (the latter is set to malloc'd memory). * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned. */ static int berval_from_file( const char *path, struct berval *bvp, int reporterrs ) { FILE *fp; long rlen; #if defined( XP_WIN32 ) char mode[20] = "r+b"; #else char mode[20] = "r"; #endif #ifdef SOLARIS_LDAP_CMD if (( fp = fopen( path, mode )) == NULL ) { #else if (( fp = ldaptool_open_file( path, mode )) == NULL ) { #endif /* SOLARIS_LDAP_CMD */ if ( reporterrs ) perror( path ); return( LDAPTOOL_FILEURL_FILEIOERROR ); } if ( fseek( fp, 0L, SEEK_END ) != 0 ) { if ( reporterrs ) perror( path ); fclose( fp ); return( LDAPTOOL_FILEURL_FILEIOERROR ); } bvp->bv_len = ftell( fp ); if (( bvp->bv_val = (char *)malloc( bvp->bv_len + 1 )) == NULL ) { if ( reporterrs ) perror( "malloc" ); fclose( fp ); return( LDAPTOOL_FILEURL_NOMEMORY ); } if ( fseek( fp, 0L, SEEK_SET ) != 0 ) { if ( reporterrs ) perror( path ); fclose( fp ); return( LDAPTOOL_FILEURL_FILEIOERROR ); } rlen = fread( bvp->bv_val, 1, bvp->bv_len, fp ); fclose( fp ); if ( rlen != (long)bvp->bv_len ) { if ( reporterrs ) perror( path ); free( bvp->bv_val ); return( LDAPTOOL_FILEURL_FILEIOERROR ); } bvp->bv_val[ bvp->bv_len ] = '\0'; return( LDAPTOOL_FILEURL_SUCCESS ); } /* * Return a non-zero value if the string s begins with prefix and zero if not. */ static int str_starts_with( const char *s, char *prefix ) { size_t prefix_len; if ( s == NULL || prefix == NULL ) { return( 0 ); } prefix_len = strlen( prefix ); if ( strlen( s ) < prefix_len ) { return( 0 ); } return( strncmp( s, prefix, prefix_len ) == 0 ); } /* * Remove URL hex escapes from s... done in place. The basic concept for * this routine is borrowed from the WWW library HTUnEscape() routine. * * A similar function called nsldapi_hex_unescape can be found in * ../../libraries/libldap/unescape.c */ static void hex_unescape( char *s ) { char *p; for ( p = s; *s != '\0'; ++s ) { if ( *s == '%' ) { if ( *++s != '\0' ) { *p = unhex( *s ) << 4; } if ( *++s != '\0' ) { *p++ += unhex( *s ); } } else { *p++ = *s; } } *p = '\0'; } /* * Return the integer equivalent of one hex digit (in c). * * A similar function can be found in ../../libraries/libldap/unescape.c */ static int unhex( char c ) { return( c >= '0' && c <= '9' ? c - '0' : c >= 'A' && c <= 'F' ? c - 'A' + 10 : c - 'a' + 10 ); } #define HREF_CHAR_ACCEPTABLE( c ) (( c >= '-' && c <= '9' ) || \ ( c >= '@' && c <= 'Z' ) || \ ( c == '_' ) || \ ( c >= 'a' && c <= 'z' )) /* * Like strcat(), except if any URL-special characters are found in s2 * they are escaped using the %HH convention and backslash characters are * converted to forward slashes on Windows. * * Maximum space needed in s1 is 3 * strlen( s2 ) + 1. * * A similar function that does not convert the slashes called * strcat_escaped() can be found in ../../libraries/libldap/tmplout.c */ static void strcpy_escaped_and_convert( char *s1, char *s2 ) { char *p, *q; char *hexdig = "0123456789ABCDEF"; p = s1 + strlen( s1 ); for ( q = s2; *q != '\0'; ++q ) { #ifdef _WINDOWS if ( *q == '\\' ) { *p++ = '/'; } else #endif /* _WINDOWS */ if ( HREF_CHAR_ACCEPTABLE( *q )) { *p++ = *q; } else { *p++ = '%'; *p++ = hexdig[ 0x0F & ((*(unsigned char*)q) >> 4) ]; *p++ = hexdig[ 0x0F & *q ]; } } *p = '\0'; }