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
45static int str_starts_with( const char *s, char *prefix );
46static void hex_unescape( char *s );
47static int unhex( char c );
48static void strcpy_escaped_and_convert( char *s1, char *s2 );
49static 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 */
75int
76ldaptool_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 */
170int
171ldaptool_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 */
230int
231ldaptool_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 */
328int
329ldaptool_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
359static int
360berval_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 */
416static int
417str_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 */
441static void
442hex_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 */
468static int
469unhex( 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 */
492static void
493strcpy_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