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