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
ldaptool_fileurl2path(const char * fileurl,char ** localpathp)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
ldaptool_path2fileurl(char * path,char ** urlp)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
ldaptool_berval_from_ldif_value(const char * value,int vlen,struct berval * bvp,int recognize_url_syntax,int always_try_file,int reporterrs)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
ldaptool_fileurlerr2ldaperr(int lderr)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
berval_from_file(const char * path,struct berval * bvp,int reporterrs)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