1 /* 2 * Copyright 2003 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 ( fstats.st_mode & S_IFDIR ) { 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 !(fstats.st_mode & S_IFDIR)) { /* 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