Back to index

lightning-sunbird  0.9+nobinonly
fileurl.c
Go to the documentation of this file.
00001 /* ***** BEGIN LICENSE BLOCK *****
00002  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00003  *
00004  * The contents of this file are subject to the Mozilla Public License Version
00005  * 1.1 (the "License"); you may not use this file except in compliance with
00006  * the License. You may obtain a copy of the License at
00007  * http://www.mozilla.org/MPL/
00008  *
00009  * Software distributed under the License is distributed on an "AS IS" basis,
00010  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00011  * for the specific language governing rights and limitations under the
00012  * License.
00013  *
00014  * The Original Code is Mozilla Communicator client code, released
00015  * March 31, 1998.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998-1999
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 /*
00039  *  LDAP tools fileurl.c -- functions for handling file URLs.
00040  *  Used by ldapmodify.
00041  */
00042 
00043 #include "ldaptool.h"
00044 #include "fileurl.h"
00045 #include <ctype.h>   /* for isalpha() */
00046 
00047 static int str_starts_with( const char *s, char *prefix );
00048 static void hex_unescape( char *s );
00049 static int unhex( char c );
00050 static void strcpy_escaped_and_convert( char *s1, char *s2 );
00051 static int berval_from_file( const char *path, struct berval *bvp,
00052        int reporterrs );
00053 
00054 /*
00055  * Convert a file URL to a local path.
00056  *
00057  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *localpathp is
00058  * set point to an allocated string.  If not, an different LDAPTOOL_FILEURL_
00059  * error code is returned.
00060  *
00061  * See RFCs 1738 and 2396 for a specification for file URLs... but
00062  * Netscape Navigator seems to be a bit more lenient in what it will
00063  * accept, especially on Windows).
00064  *
00065  * This function parses file URLs of these three forms:
00066  *
00067  *    file:///path
00068  *    file:/path
00069  *    file://localhost/path
00070  *    file://host/path             (rejected with a ...NONLOCAL error)
00071  *
00072  * On Windows, we convert leading drive letters of the form C| to C:
00073  * and if a drive letter is present we strip off the slash that precedes
00074  * path.  Otherwise, the leading slash is returned.
00075  *
00076  */
00077 int
00078 ldaptool_fileurl2path( const char *fileurl, char **localpathp )
00079 {
00080     const char       *path;
00081     char      *pathcopy;
00082 
00083     /*
00084      * Make sure this is a file URL we can handle.
00085      */
00086     if ( !str_starts_with( fileurl, "file:" )) {
00087        return( LDAPTOOL_FILEURL_NOTAFILEURL );
00088     }
00089 
00090     path = fileurl + 5;            /* skip past "file:" scheme prefix */
00091 
00092     if ( *path != '/' ) {
00093        return( LDAPTOOL_FILEURL_MISSINGPATH );
00094     }
00095 
00096     ++path;                 /* skip past '/' at end of "file:/" */
00097 
00098     if ( *path == '/' ) {
00099        ++path;                     /* remainder is now host/path or /path */
00100        if ( *path != '/' ) {
00101            /*
00102             * Make sure it is for the local host.
00103             */
00104            if ( str_starts_with( path, "localhost/" )) {
00105               path += 9;
00106            } else {
00107               return( LDAPTOOL_FILEURL_NONLOCAL );
00108            }
00109        }
00110     } else {         /* URL is of the form file:/path */
00111        --path;
00112     }
00113 
00114     /*
00115      * The remainder is now of the form /path.  On Windows, skip past the
00116      * leading slash if a drive letter is present.
00117      */
00118 #ifdef _WINDOWS
00119     if ( isalpha( path[1] ) && ( path[2] == '|' || path[2] == ':' )) {
00120        ++path;
00121     }
00122 #endif /* _WINDOWS */
00123 
00124     /*
00125      * Duplicate the path so we can safely alter it.
00126      * Unescape any %HH sequences.
00127      */
00128     if (( pathcopy = strdup( path )) == NULL ) {
00129        return( LDAPTOOL_FILEURL_NOMEMORY );
00130     }
00131     hex_unescape( pathcopy );
00132 
00133 #ifdef _WINDOWS
00134     /*
00135      * Convert forward slashes to backslashes for Windows.  Also,
00136      * if we see a drive letter / vertical bar combination (e.g., c|)
00137      * at the beginning of the path, replace the '|' with a ':'.
00138      */
00139     {
00140        char   *p;
00141 
00142        for ( p = pathcopy; *p != '\0'; ++p ) {
00143            if ( *p == '/' ) {
00144               *p = '\\';
00145            }
00146        }
00147     }
00148 
00149     if ( isalpha( pathcopy[0] ) && pathcopy[1] == '|' ) {
00150        pathcopy[1] = ':';
00151     }
00152 #endif /* _WINDOWS */
00153 
00154     *localpathp = pathcopy;
00155     return( LDAPTOOL_FILEURL_SUCCESS );
00156 }
00157 
00158 
00159 /*
00160  * Convert a local path to a file URL.
00161  *
00162  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and *urlp is
00163  * set point to an allocated string.  If not, an different LDAPTOOL_FILEURL_
00164  * error code is returned.  At present, the only possible error is
00165  * LDAPTOOL_FILEURL_NOMEMORY.
00166  *
00167  * This function produces file URLs of the form file:path.
00168  *
00169  * On Windows, we convert leading drive letters to C|.
00170  *
00171  */
00172 int
00173 ldaptool_path2fileurl( char *path, char **urlp )
00174 {
00175     char      *p, *url, *prefix ="file:";
00176 
00177     if ( NULL == path ) {
00178        path = "/";
00179     }
00180 
00181     /*
00182      * Allocate space for the URL, taking into account that path may
00183      * expand during the hex escaping process.
00184      */
00185     if (( url = malloc( strlen( prefix ) + 3 * strlen( path ) + 1 )) == NULL ) {
00186        return( LDAPTOOL_FILEURL_NOMEMORY );
00187     }
00188 
00189     strcpy( url, prefix );
00190     p = url + strlen( prefix );
00191 
00192 #ifdef _WINDOWS
00193     /*
00194      * On Windows, convert leading drive letters (e.g., C:) to the correct URL
00195      * syntax (e.g., C|).
00196      */
00197     if ( isalpha( path[0] ) && path[1] == ':' ) {
00198        *p++ = path[0];
00199        *p++ = '|';
00200        path += 2;
00201        *p = '\0';
00202     }
00203 #endif /* _WINDOWS */
00204 
00205     /*
00206      * Append the path, encoding any URL-special characters using the %HH
00207      * convention.
00208      * On Windows, convert backwards slashes in the path to forward ones.
00209      */
00210     strcpy_escaped_and_convert( p, path );
00211 
00212     *urlp = url;
00213     return( LDAPTOOL_FILEURL_SUCCESS );
00214 }
00215 
00216 
00217 /*
00218  * Populate *bvp from "value" of length "vlen."
00219  *
00220  * If recognize_url_syntax is non-zero, :<fileurl is recognized.
00221  * If always_try_file is recognized and no file URL was found, an
00222  * attempt is made to stat and read the value as if it were the name
00223  * of a file.
00224  *
00225  * If reporterrs is non-zero, specific error messages are printed to
00226  * stderr.
00227  *
00228  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and bvp->bv_len
00229  * and bvp->bv_val are set (the latter is set to malloc'd memory).
00230  * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned.
00231  */
00232 int
00233 ldaptool_berval_from_ldif_value( const char *value, int vlen,
00234        struct berval *bvp, int recognize_url_syntax, int always_try_file,
00235        int reporterrs )
00236 {
00237     int       rc = LDAPTOOL_FILEURL_SUCCESS;     /* optimistic */
00238     struct stat      fstats;
00239 
00240     /* recognize "attr :< url" syntax if LDIF version is >= 1 */
00241 
00242     if ( recognize_url_syntax && *value == '<' ) {
00243         const char   *url;
00244         char         *path;
00245 
00246        for ( url = value + 1; isspace( *url ); ++url ) {
00247            ;  /* NULL */
00248        }
00249 
00250        if (strlen(url) > 7 && strncasecmp(url, "file://", 7) == 0) {
00251          /*
00252           * We only support file:// URLs for now.
00253           */
00254          rc = ldaptool_fileurl2path( url, &path );
00255          switch( rc ) {
00256          case LDAPTOOL_FILEURL_NOTAFILEURL:
00257              if ( reporterrs ) fprintf( stderr, "%s: unsupported URL \"%s\";"
00258                 " use a file:// URL instead.\n", ldaptool_progname, url );
00259              break;
00260 
00261          case LDAPTOOL_FILEURL_MISSINGPATH:
00262              if ( reporterrs ) fprintf( stderr,
00263                   "%s: unable to process URL \"%s\" --"
00264                   " missing path.\n", ldaptool_progname, url );
00265              break;
00266 
00267          case LDAPTOOL_FILEURL_NONLOCAL:
00268              if ( reporterrs ) fprintf( stderr,
00269                   "%s: unable to process URL \"%s\" -- only"
00270                   " local file:// URLs are supported.\n",
00271                   ldaptool_progname, url );
00272              break;
00273 
00274          case LDAPTOOL_FILEURL_NOMEMORY:
00275              if ( reporterrs ) perror( "ldaptool_fileurl2path" );
00276              break;
00277 
00278          case LDAPTOOL_FILEURL_SUCCESS:
00279              if ( stat( path, &fstats ) != 0 ) {
00280                 if ( reporterrs ) perror( path );
00281              } else if ( fstats.st_mode & S_IFDIR ) {   
00282                 if ( reporterrs ) fprintf( stderr,
00283                      "%s: %s is a directory, not a file\n",
00284                      ldaptool_progname, path );
00285                 rc = LDAPTOOL_FILEURL_FILEIOERROR;
00286              } else {
00287                 rc = berval_from_file( path, bvp, reporterrs );
00288              }
00289              free( path );
00290              break;
00291 
00292          default:
00293              if ( reporterrs ) fprintf( stderr,
00294                   "%s: unable to process URL \"%s\""
00295                   " -- unknown error\n", ldaptool_progname, url );
00296          }
00297        }
00298     }
00299     if ( always_try_file && (stat( value, &fstats ) == 0) &&
00300             !(fstats.st_mode & S_IFDIR)) {       /* get value from file */
00301        rc = berval_from_file( value, bvp, reporterrs );
00302     } else {
00303        bvp->bv_len = vlen;
00304        if (( bvp->bv_val = (char *)malloc( vlen + 1 )) == NULL ) {
00305            if ( reporterrs ) perror( "malloc" );
00306            rc = LDAPTOOL_FILEURL_NOMEMORY;
00307        } else {
00308            SAFEMEMCPY( bvp->bv_val, value, vlen );
00309            bvp->bv_val[ vlen ] = '\0';
00310        }
00311     }
00312 
00313     return( rc );
00314 }
00315 
00316 
00317 /*
00318  * Map an LDAPTOOL_FILEURL_ error code to an LDAP error code (crude).
00319  */
00320 int
00321 ldaptool_fileurlerr2ldaperr( int lderr )
00322 {
00323     int              rc;
00324 
00325     switch( lderr ) {
00326     case LDAPTOOL_FILEURL_SUCCESS:
00327        rc = LDAP_SUCCESS;
00328        break;
00329     case LDAPTOOL_FILEURL_NOMEMORY:
00330        rc = LDAP_NO_MEMORY;
00331        break;
00332     default:
00333        rc = LDAP_PARAM_ERROR;
00334     }
00335 
00336     return( rc );
00337 } 
00338 
00339 
00340 /*
00341  * Populate *bvp with the contents of the file named by "path".
00342  *
00343  * If reporterrs is non-zero, specific error messages are printed to
00344  * stderr.
00345  *
00346  * If successful, LDAPTOOL_FILEURL_SUCCESS is returned and bvp->bv_len
00347  * and bvp->bv_val are set (the latter is set to malloc'd memory).
00348  * Upon failure, a different LDAPTOOL_FILEURL_ error code is returned.
00349  */
00350 
00351 static int
00352 berval_from_file( const char *path, struct berval *bvp, int reporterrs )
00353 {
00354     FILE      *fp;
00355     long      rlen;
00356     int              eof;
00357 #if defined( XP_WIN32 )
00358     char      mode[20] = "r+b";
00359 #else
00360     char      mode[20] = "r";
00361 #endif
00362 
00363     if (( fp = fopen( path, mode )) == NULL ) {
00364        if ( reporterrs ) perror( path );
00365        return( LDAPTOOL_FILEURL_FILEIOERROR );
00366     }
00367 
00368     if ( fseek( fp, 0L, SEEK_END ) != 0 ) {
00369        if ( reporterrs ) perror( path );
00370        fclose( fp );
00371        return( LDAPTOOL_FILEURL_FILEIOERROR );
00372     }
00373 
00374     bvp->bv_len = ftell( fp );
00375 
00376     if (( bvp->bv_val = (char *)malloc( bvp->bv_len + 1 )) == NULL ) {
00377        if ( reporterrs ) perror( "malloc" );
00378        fclose( fp );
00379        return( LDAPTOOL_FILEURL_NOMEMORY );
00380     }
00381 
00382     if ( fseek( fp, 0L, SEEK_SET ) != 0 ) {
00383        if ( reporterrs ) perror( path );
00384        fclose( fp );
00385        return( LDAPTOOL_FILEURL_FILEIOERROR );
00386     }
00387 
00388     rlen = fread( bvp->bv_val, 1, bvp->bv_len, fp );
00389     eof = feof( fp );
00390     fclose( fp );
00391 
00392     if ( rlen != (long)bvp->bv_len ) {
00393        if ( reporterrs ) perror( path );
00394        free( bvp->bv_val );
00395        return( LDAPTOOL_FILEURL_FILEIOERROR );
00396     }
00397 
00398     bvp->bv_val[ bvp->bv_len ] = '\0';
00399     return( LDAPTOOL_FILEURL_SUCCESS );
00400 }
00401 
00402 
00403 /*
00404  * Return a non-zero value if the string s begins with prefix and zero if not.
00405  */
00406 static int
00407 str_starts_with( const char *s, char *prefix )
00408 {
00409     size_t    prefix_len;
00410 
00411     if ( s == NULL || prefix == NULL ) {
00412        return( 0 );
00413     }
00414 
00415     prefix_len = strlen( prefix );
00416     if ( strlen( s ) < prefix_len ) {
00417        return( 0 );
00418     }
00419 
00420     return( strncmp( s, prefix, prefix_len ) == 0 );
00421 }
00422 
00423 
00424 /*
00425  * Remove URL hex escapes from s... done in place.  The basic concept for
00426  * this routine is borrowed from the WWW library HTUnEscape() routine.
00427  *
00428  * A similar function called nsldapi_hex_unescape can be found in
00429  * ../../libraries/libldap/unescape.c
00430  */
00431 static void
00432 hex_unescape( char *s )
00433 {
00434        char   *p;
00435 
00436        for ( p = s; *s != '\0'; ++s ) {
00437               if ( *s == '%' ) {
00438                      if ( *++s != '\0' ) {
00439                             *p = unhex( *s ) << 4;
00440                      }
00441                      if ( *++s != '\0' ) {
00442                             *p++ += unhex( *s );
00443                      }
00444               } else {
00445                      *p++ = *s;
00446               }
00447        }
00448 
00449        *p = '\0';
00450 }
00451 
00452 
00453 /*
00454  * Return the integer equivalent of one hex digit (in c).
00455  *
00456  * A similar function can be found in ../../libraries/libldap/unescape.c
00457  */
00458 static int
00459 unhex( char c )
00460 {
00461        return( c >= '0' && c <= '9' ? c - '0'
00462            : c >= 'A' && c <= 'F' ? c - 'A' + 10
00463            : c - 'a' + 10 );
00464 }
00465 
00466 
00467 #define HREF_CHAR_ACCEPTABLE( c )  (( c >= '-' && c <= '9' ) ||       \
00468                                     ( c >= '@' && c <= 'Z' ) ||       \
00469                                     ( c == '_' ) ||            \
00470                                     ( c >= 'a' && c <= 'z' ))
00471 
00472 /*
00473  * Like strcat(), except if any URL-special characters are found in s2
00474  * they are escaped using the %HH convention and backslash characters are
00475  * converted to forward slashes on Windows.
00476  *
00477  * Maximum space needed in s1 is 3 * strlen( s2 ) + 1.
00478  *
00479  * A similar function that does not convert the slashes called
00480  * strcat_escaped() can be found in ../../libraries/libldap/tmplout.c
00481  */
00482 static void
00483 strcpy_escaped_and_convert( char *s1, char *s2 )
00484 {
00485     char      *p, *q;
00486     char      *hexdig = "0123456789ABCDEF";
00487 
00488     p = s1 + strlen( s1 );
00489     for ( q = s2; *q != '\0'; ++q ) {
00490 #ifdef _WINDOWS
00491        if ( *q == '\\' ) {
00492                 *p++ = '/';
00493        } else
00494 #endif /* _WINDOWS */
00495 
00496        if ( HREF_CHAR_ACCEPTABLE( *q )) {
00497            *p++ = *q;
00498        } else {
00499            *p++ = '%';
00500            *p++ = hexdig[ 0x0F & ((*(unsigned char*)q) >> 4) ];
00501            *p++ = hexdig[ 0x0F & *q ];
00502        }
00503     }
00504 
00505     *p = '\0';
00506 }