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