1 /*************************************************************************
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 * Copyright 2008 by Sun Microsystems, Inc.
7 * OpenOffice.org - a multi-platform office productivity suite
9 * $RCSfile: file_url.cxx,v $
12 * This file is part of OpenOffice.org.
14 * OpenOffice.org is free software: you can redistribute it and/or modify
15 * it under the terms of the GNU Lesser General Public License version 3
16 * only, as published by the Free Software Foundation.
18 * OpenOffice.org is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU Lesser General Public License version 3 for more details
22 * (a copy is included in the LICENSE file that accompanied this code).
24 * You should have received a copy of the GNU Lesser General Public License
25 * version 3 along with OpenOffice.org. If not, see
26 * <http://www.openoffice.org/license.html>
27 * for a copy of the LGPLv3 License.
29 ************************************************************************/
31 // MARKER(update_precomp.py): autogen include statement, do not remove
32 #include "precompiled_sal.hxx"
51 #include <osl/security.h>
53 #include <osl/diagnose.h>
54 #include <rtl/ustring.hxx>
55 #include <rtl/ustrbuf.h>
58 #include <osl/thread.h>
60 #include <osl/file.hxx>
61 #include <osl/process.h>
62 #include "file_error_transl.h"
67 #include "file_path_helper.hxx"
69 #ifndef _OSL_UUNXAPI_HXX_
70 #include "uunxapi.hxx"
73 /***************************************************
77 This file contains the part that handles File URLs.
79 File URLs as scheme specific notion of URIs
80 (RFC2396) may be handled platform independend, but
81 will not in osl which is considered wrong.
82 Future version of osl should handle File URLs this
83 way. In rtl/uri there is already an URI parser etc.
84 so this code should be consolidated.
86 **************************************************/
90 /***************************************************
92 **************************************************/
94 extern "C" int UnicodeToText(char *, size_t, const sal_Unicode
*, sal_Int32
);
95 extern "C" int TextToUnicode(const char* text
, size_t text_buffer_size
, sal_Unicode
* unic_text
, sal_Int32 unic_text_buffer_size
);
97 /***************************************************
98 * namespace directives
99 **************************************************/
103 /***************************************************
105 **************************************************/
107 const sal_Unicode UNICHAR_SLASH
= ((sal_Unicode
)'/');
108 const sal_Unicode UNICHAR_COLON
= ((sal_Unicode
)':');
109 const sal_Unicode UNICHAR_DOT
= ((sal_Unicode
)'.');
111 /******************************************************************************
113 * Exported Module Functions
115 *****************************************************************************/
117 /* a slightly modified version of Pchar in rtl/source/uri.c */
118 const sal_Bool uriCharClass
[128] =
120 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* Pchar but without encoding slashes */
121 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
122 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* !"#$%&'()*+,-./ */
123 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, /* 0123456789:;<=>? */
124 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* @ABCDEFGHIJKLMNO */
125 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, /* PQRSTUVWXYZ[\]^_ */
126 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* `abcdefghijklmno */
127 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0 /* pqrstuvwxyz{|}~ */
131 /* check for top wrong usage strings */
133 static sal_Bool findWrongUsage( const sal_Unicode *path, sal_Int32 len )
135 rtl_uString *pTmp = NULL;
138 rtl_uString_newFromStr_WithLength( &pTmp, path, len );
140 rtl_ustr_toAsciiLowerCase_WithLength( pTmp->buffer, pTmp->length );
142 bRet = ( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pTmp->buffer, pTmp->length, "ftp://", 6 ) ) ||
143 ( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pTmp->buffer, pTmp->length, "http://", 7 ) ) ||
144 ( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pTmp->buffer, pTmp->length, "vnd.sun.star", 12 ) ) ||
145 ( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pTmp->buffer, pTmp->length, "private:", 8 ) ) ||
146 ( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pTmp->buffer, pTmp->length, "slot:", 5) );
148 rtl_uString_release( pTmp );
153 /****************************************************************************/
154 /* osl_getSystemPathFromFileURL */
155 /****************************************************************************/
157 oslFileError SAL_CALL
osl_getSystemPathFromFileURL( rtl_uString
*ustrFileURL
, rtl_uString
**pustrSystemPath
)
160 rtl_uString
* pTmp
= NULL
;
162 sal_Unicode encodedSlash
[3] = { '%', '2', 'F' };
163 sal_Unicode protocolDelimiter
[3] = { ':', '/', '/' };
165 /* temporary hack: if already system path, return ustrFileURL */
167 if( (sal_Unicode) '/' == ustrFileURL->buffer[0] )
169 OSL_ENSURE( 0, "osl_getSystemPathFromFileURL: input is already system path" );
170 rtl_uString_assign( pustrSystemPath, ustrFileURL );
171 return osl_File_E_None;
175 /* a valid file url may not start with '/' */
176 if( ( 0 == ustrFileURL
->length
) || ( (sal_Unicode
) '/' == ustrFileURL
->buffer
[0] ) )
178 return osl_File_E_INVAL
;
181 /* Check for non file:// protocols */
183 nIndex
= rtl_ustr_indexOfStr_WithLength( ustrFileURL
->buffer
, ustrFileURL
->length
, protocolDelimiter
, 3 );
184 if ( -1 != nIndex
&& (4 != nIndex
|| 0 != rtl_ustr_ascii_shortenedCompare_WithLength( ustrFileURL
->buffer
, ustrFileURL
->length
,"file", 4 ) ) )
186 return osl_File_E_INVAL
;
189 /* search for encoded slashes (%2F) and decode every single token if we find one */
193 if( -1 != rtl_ustr_indexOfStr_WithLength( ustrFileURL
->buffer
, ustrFileURL
->length
, encodedSlash
, 3 ) )
195 rtl_uString
* ustrPathToken
= NULL
;
196 sal_Int32 nOffset
= 7;
202 /* break url down in '/' devided tokens tokens */
203 nIndex
= rtl_ustr_indexOfChar_WithLength( ustrFileURL
->buffer
+ nOffset
, ustrFileURL
->length
- nOffset
, (sal_Unicode
) '/' );
205 /* copy token to new string */
206 rtl_uString_newFromStr_WithLength( &ustrPathToken
, ustrFileURL
->buffer
+ nOffset
,
207 -1 == nIndex
? ustrFileURL
->length
- nOffset
: nIndex
++ );
210 rtl_uriDecode( ustrPathToken
, rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
, &pTmp
);
212 /* the result should not contain any '/' */
213 if( -1 != rtl_ustr_indexOfChar_WithLength( pTmp
->buffer
, pTmp
->length
, (sal_Unicode
) '/' ) )
215 rtl_uString_release( pTmp
);
216 rtl_uString_release( ustrPathToken
);
218 return osl_File_E_INVAL
;
221 } while( -1 != nIndex
);
223 /* release temporary string and restore index variable */
224 rtl_uString_release( ustrPathToken
);
228 /* protocol and server should not be encoded, so decode the whole string */
229 rtl_uriDecode( ustrFileURL
, rtl_UriDecodeWithCharset
, RTL_TEXTENCODING_UTF8
, &pTmp
);
231 /* check if file protocol specified */
232 /* FIXME: use rtl_ustr_ascii_shortenedCompareIgnoreCase_WithLength when available */
233 if( 7 <= pTmp
->length
)
235 rtl_uString
* pProtocol
= NULL
;
236 rtl_uString_newFromStr_WithLength( &pProtocol
, pTmp
->buffer
, 7 );
238 /* protocol is case insensitive */
239 rtl_ustr_toAsciiLowerCase_WithLength( pProtocol
->buffer
, pProtocol
->length
);
241 if( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pProtocol
->buffer
, pProtocol
->length
,"file://", 7 ) )
244 rtl_uString_release( pProtocol
);
247 /* skip "localhost" or "127.0.0.1" if "file://" is specified */
248 /* FIXME: use rtl_ustr_ascii_shortenedCompareIgnoreCase_WithLength when available */
249 if( nIndex
&& ( 10 <= pTmp
->length
- nIndex
) )
251 rtl_uString
* pServer
= NULL
;
252 rtl_uString_newFromStr_WithLength( &pServer
, pTmp
->buffer
+ nIndex
, 10 );
254 /* server is case insensitive */
255 rtl_ustr_toAsciiLowerCase_WithLength( pServer
->buffer
, pServer
->length
);
257 if( ( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pServer
->buffer
, pServer
->length
,"localhost/", 10 ) ) ||
258 ( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( pServer
->buffer
, pServer
->length
,"127.0.0.1/", 10 ) ) )
260 /* don't exclude the '/' */
264 rtl_uString_release( pServer
);
268 rtl_uString_newFromStr_WithLength( &pTmp
, pTmp
->buffer
+ nIndex
, pTmp
->length
- nIndex
);
270 /* check if system path starts with ~ or ~user and replace it with the appropriate home dir */
271 if( (sal_Unicode
) '~' == pTmp
->buffer
[0] )
273 /* check if another user is specified */
274 if( ( 1 == pTmp
->length
) || ( (sal_Unicode
)'/' == pTmp
->buffer
[1] ) )
276 rtl_uString
*pTmp2
= NULL
;
278 /* osl_getHomeDir returns file URL */
279 osl_getHomeDir( osl_getCurrentSecurity(), &pTmp2
);
281 /* remove "file://" prefix */
282 rtl_uString_newFromStr_WithLength( &pTmp2
, pTmp2
->buffer
+ 7, pTmp2
->length
- 7 );
284 /* replace '~' in original string */
285 rtl_uString_newReplaceStrAt( &pTmp
, pTmp
, 0, 1, pTmp2
);
286 rtl_uString_release( pTmp2
);
291 /* FIXME: replace ~user with users home directory */
292 return osl_File_E_INVAL
;
296 /* temporary check for top 5 wrong usage strings (which are valid but unlikly filenames) */
298 OSL_ASSERT( !findWrongUsage( pTmp->buffer, pTmp->length ) );
301 *pustrSystemPath
= pTmp
;
302 return osl_File_E_None
;
305 /****************************************************************************/
306 /* osl_getFileURLFromSystemPath */
307 /****************************************************************************/
309 oslFileError SAL_CALL
osl_getFileURLFromSystemPath( rtl_uString
*ustrSystemPath
, rtl_uString
**pustrFileURL
)
311 static const sal_Unicode pDoubleSlash
[2] = { '/', '/' };
313 rtl_uString
*pTmp
= NULL
;
316 if( 0 == ustrSystemPath
->length
)
317 return osl_File_E_INVAL
;
319 /* temporary hack: if already file url, return ustrSystemPath */
321 if( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( ustrSystemPath
->buffer
, ustrSystemPath
->length
,"file:", 5 ) )
324 if( 0 == rtl_ustr_ascii_shortenedCompare_WithLength( ustrSystemPath->buffer, ustrSystemPath->length,"file://", 7 ) )
326 OSL_ENSURE( 0, "osl_getFileURLFromSystemPath: input is already file URL" );
327 rtl_uString_assign( pustrFileURL, ustrSystemPath );
331 rtl_uString *pTmp2 = NULL;
333 OSL_ENSURE( 0, "osl_getFileURLFromSystemPath: input is wrong file URL" );
334 rtl_uString_newFromStr_WithLength( pustrFileURL, ustrSystemPath->buffer + 5, ustrSystemPath->length - 5 );
335 rtl_uString_newFromAscii( &pTmp2, "file://" );
336 rtl_uString_newConcat( pustrFileURL, *pustrFileURL, pTmp2 );
337 rtl_uString_release( pTmp2 );
339 return osl_File_E_None;
341 return osl_File_E_INVAL
;
345 /* check if system path starts with ~ or ~user and replace it with the appropriate home dir */
346 if( (sal_Unicode
) '~' == ustrSystemPath
->buffer
[0] )
348 /* check if another user is specified */
349 if( ( 1 == ustrSystemPath
->length
) || ( (sal_Unicode
)'/' == ustrSystemPath
->buffer
[1] ) )
351 /* osl_getHomeDir returns file URL */
352 osl_getHomeDir( osl_getCurrentSecurity(), &pTmp
);
354 /* remove "file://" prefix */
355 rtl_uString_newFromStr_WithLength( &pTmp
, pTmp
->buffer
+ 7, pTmp
->length
- 7 );
357 /* replace '~' in original string */
358 rtl_uString_newReplaceStrAt( &pTmp
, ustrSystemPath
, 0, 1, pTmp
);
363 /* FIXME: replace ~user with users home directory */
364 return osl_File_E_INVAL
;
368 /* check if initial string contains double instances of '/' */
369 nIndex
= rtl_ustr_indexOfStr_WithLength( ustrSystemPath
->buffer
, ustrSystemPath
->length
, pDoubleSlash
, 2 );
373 sal_Int32 nDeleted
= 0;
375 /* if pTmp is not already allocated, copy ustrSystemPath for modification */
377 rtl_uString_newFromString( &pTmp
, ustrSystemPath
);
379 /* adapt index to pTmp */
380 nIndex
+= pTmp
->length
- ustrSystemPath
->length
;
382 /* remove all occurances of '//' */
383 for( nSrcIndex
= nIndex
+ 1; nSrcIndex
< pTmp
->length
; nSrcIndex
++ )
385 if( ((sal_Unicode
) '/' == pTmp
->buffer
[nSrcIndex
]) && ((sal_Unicode
) '/' == pTmp
->buffer
[nIndex
]) )
388 pTmp
->buffer
[++nIndex
] = pTmp
->buffer
[nSrcIndex
];
391 /* adjust length member */
392 pTmp
->length
-= nDeleted
;
396 rtl_uString_assign( &pTmp
, ustrSystemPath
);
398 /* temporary check for top 5 wrong usage strings (which are valid but unlikly filenames) */
400 OSL_ASSERT( !findWrongUsage( pTmp->buffer, pTmp->length ) );
403 /* file URLs must be URI encoded */
404 rtl_uriEncode( pTmp
, uriCharClass
, rtl_UriEncodeIgnoreEscapes
, RTL_TEXTENCODING_UTF8
, pustrFileURL
);
406 rtl_uString_release( pTmp
);
408 /* absolute urls should start with 'file://' */
409 if( (sal_Unicode
)'/' == (*pustrFileURL
)->buffer
[0] )
411 rtl_uString
*pProtocol
= NULL
;
413 rtl_uString_newFromAscii( &pProtocol
, "file://" );
414 rtl_uString_newConcat( pustrFileURL
, pProtocol
, *pustrFileURL
);
415 rtl_uString_release( pProtocol
);
418 return osl_File_E_None
;
421 /****************************************************************************
422 * osl_getSystemPathFromFileURL_Ex - helper function
423 * clients may specify if they want to accept relative
425 ****************************************************************************/
427 oslFileError
osl_getSystemPathFromFileURL_Ex(
428 rtl_uString
*ustrFileURL
, rtl_uString
**pustrSystemPath
, sal_Bool bAllowRelative
)
430 rtl_uString
* temp
= 0;
431 oslFileError osl_error
= osl_getSystemPathFromFileURL(ustrFileURL
, &temp
);
433 if (osl_File_E_None
== osl_error
)
435 if (bAllowRelative
|| (UNICHAR_SLASH
== temp
->buffer
[0]))
437 *pustrSystemPath
= temp
;
441 rtl_uString_release(temp
);
442 osl_error
= osl_File_E_INVAL
;
449 /******************************************************
450 * Resolve the paths if they exist. The resulting
451 * path must not exceed PATH_MAX else
452 * osl_File_E_NAMETOOLONG is the result
453 ******************************************************/
455 static oslFileError
osl_getAbsoluteFileURL_impl_(const rtl::OUString
& unresolved
, rtl::OUString
& resolved
)
457 char unresolved_path
[PATH_MAX
];
458 char resolved_path
[PATH_MAX
];
460 if (!UnicodeToText(unresolved_path
, sizeof(unresolved_path
), unresolved
.getStr(), unresolved
.getLength()))
461 return oslTranslateFileError(OSL_FET_ERROR
, ENAMETOOLONG
);
463 if (realpath(unresolved_path
, resolved_path
))
465 sal_Unicode path
[PATH_MAX
];
466 if (!TextToUnicode(resolved_path
, strlen(resolved_path
), path
, PATH_MAX
))
467 return oslTranslateFileError(OSL_FET_ERROR
, ENAMETOOLONG
);
468 resolved
= rtl::OUString(path
, rtl_ustr_getLength(path
));
469 return osl_File_E_None
;
473 if (EACCES
!= errno
&& ENOTDIR
!= errno
&& ENOENT
!= errno
)
474 return oslTranslateFileError(OSL_FET_ERROR
, errno
);
477 // the 'unresolved' does not exist, let's just copy it to 'resolved'
478 resolved
= unresolved
;
479 return osl_File_E_None
;
482 /******************************************************
483 * osl_getAbsoluteFileURL
484 ******************************************************/
486 oslFileError
osl_getAbsoluteFileURL(rtl_uString
* ustrBaseDirURL
, rtl_uString
* ustrRelativeURL
, rtl_uString
** pustrAbsoluteURL
)
489 rtl::OUString unresolved_path
;
490 static char *allow_symlinks
= getenv( "SAL_ALLOW_LINKOO_SYMLINKS" );
492 rc
= FileBase::getSystemPathFromFileURL(rtl::OUString(ustrRelativeURL
), unresolved_path
);
494 if(FileBase::E_None
!= rc
)
495 return oslFileError(rc
);
497 if (systemPathIsRelativePath(unresolved_path
))
499 rtl::OUString base_path
;
500 rc
= (FileBase::RC
) osl_getSystemPathFromFileURL_Ex(ustrBaseDirURL
, &base_path
.pData
, sal_False
);
502 if (FileBase::E_None
!= rc
)
503 return oslFileError(rc
);
505 rtl::OUString abs_path
;
506 systemPathMakeAbsolutePath(base_path
, unresolved_path
, abs_path
);
508 unresolved_path
= abs_path
;
511 rtl::OUString resolved_path
;
515 rc
= (FileBase::RC
) osl_getAbsoluteFileURL_impl_(unresolved_path
, resolved_path
);
519 // SAL_ALLOW_LINKOO_SYMLINKS environment variable:
520 // for linkoo to work, we need to let the symlinks to the libraries untouched
522 sal_Int32 last_slash
= unresolved_path
.lastIndexOf( UNICHAR_SLASH
);
524 if (last_slash
>= 0 && last_slash
+ 1 < unresolved_path
.getLength())
526 base
= unresolved_path
.copy(last_slash
+1);
527 unresolved_path
= unresolved_path
.copy(0, last_slash
);
530 rc
= (FileBase::RC
) osl_getAbsoluteFileURL_impl_(unresolved_path
, resolved_path
);
532 if (base
.getLength() > 0)
534 resolved_path
+= rtl::OUString( UNICHAR_SLASH
);
535 resolved_path
+= base
;
539 if (FileBase::E_None
== rc
)
541 rc
= (FileBase::RC
) osl_getFileURLFromSystemPath(resolved_path
.pData
, pustrAbsoluteURL
);
542 OSL_ASSERT(FileBase::E_None
== rc
);
545 return oslFileError(rc
);
549 namespace /* private */
552 /*********************************************
553 No separate error code if unicode to text
554 conversion or getenv fails because for the
555 caller there is no difference why a file
556 could not be found in $PATH
557 ********************************************/
559 bool find_in_PATH(const rtl::OUString
& file_path
, rtl::OUString
& result
)
562 rtl::OUString path
= rtl::OUString::createFromAscii("PATH");
563 rtl::OUString env_path
;
565 if (osl_Process_E_None
== osl_getEnvironment(path
.pData
, &env_path
.pData
))
566 bfound
= osl::searchPath(file_path
, env_path
, result
);
571 /*********************************************
572 No separate error code if unicode to text
573 conversion or getcwd fails because for the
574 caller there is no difference why a file
575 could not be found in CDW
576 ********************************************/
578 bool find_in_CWD(const rtl::OUString
& file_path
, rtl::OUString
& result
)
581 rtl::OUString cwd_url
;
583 if (osl_Process_E_None
== osl_getProcessWorkingDir(&cwd_url
.pData
))
586 FileBase::getSystemPathFromFileURL(cwd_url
, cwd
);
587 bfound
= osl::searchPath(file_path
, cwd
, result
);
592 /*********************************************
594 ********************************************/
596 bool find_in_searchPath(const rtl::OUString
& file_path
, rtl_uString
* search_path
, rtl::OUString
& result
)
598 return (search_path
&& osl::searchPath(file_path
, rtl::OUString(search_path
), result
));
601 } // end namespace private
604 /****************************************************************************
606 ***************************************************************************/
608 oslFileError
osl_searchFileURL(rtl_uString
* ustrFilePath
, rtl_uString
* ustrSearchPath
, rtl_uString
** pustrURL
)
610 OSL_PRECOND(ustrFilePath
&& pustrURL
, "osl_searchFileURL: invalid parameter");
613 rtl::OUString file_path
;
615 // try to interpret search path as file url else assume it's a system path list
616 rc
= FileBase::getSystemPathFromFileURL(rtl::OUString(ustrFilePath
), file_path
);
617 if ((FileBase::E_None
!= rc
) && (FileBase::E_INVAL
== rc
))
618 file_path
= ustrFilePath
;
619 else if (FileBase::E_None
!= rc
)
620 return oslFileError(rc
);
623 rtl::OUString result
;
625 if (find_in_searchPath(file_path
, ustrSearchPath
, result
) ||
626 find_in_PATH(file_path
, result
) ||
627 find_in_CWD(file_path
, result
))
629 rtl::OUString resolved
;
631 if (osl::realpath(result
, resolved
))
633 #if OSL_DEBUG_LEVEL > 0
634 oslFileError osl_error
=
636 osl_getFileURLFromSystemPath(resolved
.pData
, pustrURL
);
637 OSL_ASSERT(osl_File_E_None
== osl_error
);
641 return bfound
? osl_File_E_None
: osl_File_E_NOENT
;
645 /****************************************************************************
647 ***************************************************************************/
649 oslFileError
FileURLToPath(char * buffer
, size_t bufLen
, rtl_uString
* ustrFileURL
)
651 rtl_uString
* ustrSystemPath
= NULL
;
652 oslFileError osl_error
= osl_getSystemPathFromFileURL(ustrFileURL
, &ustrSystemPath
);
654 if(osl_File_E_None
!= osl_error
)
657 osl_systemPathRemoveSeparator(ustrSystemPath
);
659 /* convert unicode path to text */
660 if(!UnicodeToText( buffer
, bufLen
, ustrSystemPath
->buffer
, ustrSystemPath
->length
))
661 osl_error
= oslTranslateFileError(OSL_FET_ERROR
, errno
);
663 rtl_uString_release(ustrSystemPath
);