xref: /illumos-gate/usr/src/cmd/ldap/common/fileurl.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
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