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