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