Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / sal / osl / unx / file_url.cxx
blobc69670d6373aa7078e194806c4cd89b6e1a116a1
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include "file_url.hxx"
22 #include "system.hxx"
24 #include <algorithm>
25 #include <cassert>
26 #include <cstring>
27 #include <stdexcept>
28 #include <string_view>
29 #include <type_traits>
30 #include <limits.h>
31 #include <errno.h>
32 #include <strings.h>
33 #include <unistd.h>
35 #include <o3tl/safeint.hxx>
36 #include <osl/file.hxx>
37 #include <osl/security.hxx>
38 #include <osl/socket.h>
39 #include <oslsocket.hxx>
40 #include <osl/diagnose.h>
41 #include <osl/thread.h>
42 #include <osl/process.h>
44 #include <rtl/character.hxx>
45 #include <rtl/strbuf.hxx>
46 #include <rtl/uri.h>
47 #include <rtl/uri.hxx>
48 #include <rtl/ustring.hxx>
49 #include <rtl/ustrbuf.h>
50 #include <rtl/ustrbuf.hxx>
51 #include <rtl/textcvt.h>
52 #include <sal/log.hxx>
54 #include <uri_internal.hxx>
56 #include "file_error_transl.hxx"
57 #include "file_path_helper.hxx"
59 #include "uunxapi.hxx"
61 /** @file
63 General note
65 This file contains the part that handles File URLs.
67 File URLs as scheme specific notion of URIs
68 (RFC2396) may be handled platform independent, but
69 will not in osl which is considered wrong.
70 Future version of osl should handle File URLs this
71 way. In rtl/uri there is already a URI parser etc.
72 so this code should be consolidated.
76 using namespace osl;
78 namespace {
80 // A slightly modified version of Pchar in rtl/source/uri.c, but without
81 // encoding slashes:
82 constexpr auto uriCharClass = rtl::createUriCharClass(
83 u8"!$&'()*+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~");
87 oslFileError SAL_CALL osl_getCanonicalName( rtl_uString* ustrFileURL, rtl_uString** pustrValidURL )
89 OSL_FAIL("osl_getCanonicalName not implemented");
91 rtl_uString_newFromString(pustrValidURL, ustrFileURL);
92 return osl_File_E_None;
95 namespace {
97 class UnicodeToTextConverter_Impl
99 rtl_UnicodeToTextConverter m_converter;
101 UnicodeToTextConverter_Impl()
102 : m_converter (rtl_createUnicodeToTextConverter (osl_getThreadTextEncoding()))
105 ~UnicodeToTextConverter_Impl()
107 rtl_destroyUnicodeToTextConverter (m_converter);
109 public:
110 static UnicodeToTextConverter_Impl & getInstance()
112 static UnicodeToTextConverter_Impl g_theConverter;
113 return g_theConverter;
116 sal_Size convert(
117 sal_Unicode const * pSrcBuf, sal_Size nSrcChars, char * pDstBuf, sal_Size nDstBytes,
118 sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtChars)
120 OSL_ASSERT(m_converter != nullptr);
121 return rtl_convertUnicodeToText (
122 m_converter, nullptr, pSrcBuf, nSrcChars, pDstBuf, nDstBytes, nFlags, pInfo, pSrcCvtChars);
126 bool convert(OUStringBuffer const & in, OStringBuffer * append) {
127 assert(append != nullptr);
128 for (sal_Size nConvert = in.getLength();;) {
129 auto const oldLen = append->getLength();
130 auto n = std::min(
131 std::max(nConvert, sal_Size(PATH_MAX)),
132 sal_Size(std::numeric_limits<sal_Int32>::max() - oldLen));
133 // approximation of required converted size
134 auto s = append->appendUninitialized(n);
135 sal_uInt32 info;
136 sal_Size converted;
137 //TODO: context, for reliable treatment of DESTBUFFERTOSMALL:
138 n = UnicodeToTextConverter_Impl::getInstance().convert(
139 in.getStr() + in.getLength() - nConvert, nConvert, s, n,
140 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
141 | RTL_UNICODETOTEXT_FLAGS_FLUSH),
142 &info, &converted);
143 if ((info & RTL_UNICODETOTEXT_INFO_ERROR) != 0) {
144 return false;
146 append->setLength(oldLen + n);
147 assert(converted <= nConvert);
148 nConvert -= converted;
149 assert((nConvert == 0) == ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0));
150 if ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0) {
151 break;
154 return true;
157 bool decodeFromUtf8(std::u16string_view text, OString * result) {
158 assert(result != nullptr);
159 auto p = text.data();
160 auto const end = p + text.size();
161 OUStringBuffer ubuf(static_cast<int>(text.size()));
162 OStringBuffer bbuf(PATH_MAX);
163 while (p < end) {
164 rtl::uri::detail::EscapeType t;
165 sal_uInt32 c = rtl::uri::detail::readUcs4(&p, end, true, RTL_TEXTENCODING_UTF8, &t);
166 switch (t) {
167 case rtl::uri::detail::EscapeNo:
168 if (c == '%') {
169 return false;
171 [[fallthrough]];
172 case rtl::uri::detail::EscapeChar:
173 if (rtl::isSurrogate(c)) {
174 return false;
176 ubuf.appendUtf32(c);
177 break;
178 case rtl::uri::detail::EscapeOctet:
179 if (!convert(ubuf, &bbuf)) {
180 return false;
182 ubuf.setLength(0);
183 assert(c <= 0xFF);
184 bbuf.append(char(c));
185 break;
188 if (!convert(ubuf, &bbuf)) {
189 return false;
191 *result = bbuf.makeStringAndClear();
192 return true;
195 template<typename T> oslFileError getSystemPathFromFileUrl(
196 OUString const & url, T * path, bool resolveHome)
198 assert(path != nullptr);
199 // For compatibility with assumptions in other parts of the code base,
200 // assume that anything starting with a slash is a system path instead of a
201 // (relative) file URL (except if it starts with two slashes, in which case
202 // it is a relative URL with an authority component):
203 if (url.isEmpty()
204 || (url[0] == '/' && (url.getLength() == 1 || url[1] != '/')))
206 return osl_File_E_INVAL;
208 // Check for non file scheme:
209 sal_Int32 i = 0;
210 if (rtl::isAsciiAlpha(url[0])) {
211 for (sal_Int32 j = 1; j != url.getLength(); ++j) {
212 auto c = url[j];
213 if (c == ':') {
214 if (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
215 url.pData->buffer, j,
216 RTL_CONSTASCII_STRINGPARAM("file"))
217 != 0)
219 return osl_File_E_INVAL;
221 i = j + 1;
222 break;
224 if (!rtl::isAsciiAlphanumeric(c) && c != '+' && c != '-'
225 && c != '.')
227 break;
231 // Handle query or fragment:
232 if (url.indexOf('?', i) != -1 || url.indexOf('#', i) != -1)
233 return osl_File_E_INVAL;
234 // Handle authority, supporting a host of "localhost", "127.0.0.1", or the exact value (e.g.,
235 // not supporting an additional final dot, for simplicity) reported by osl_getLocalHostnameFQDN
236 // (and, in each case, ignoring case of ASCII letters):
237 if (url.getLength() - i >= 2 && url[i] == '/' && url[i + 1] == '/')
239 i += 2;
240 sal_Int32 j = url.indexOf('/', i);
241 if (j == -1)
242 j = url.getLength();
243 if (j != i
244 && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
245 url.pData->buffer + i, j - i,
246 RTL_CONSTASCII_STRINGPARAM("localhost"))
247 != 0)
248 && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
249 url.pData->buffer + i, j - i,
250 RTL_CONSTASCII_STRINGPARAM("127.0.0.1"))
251 != 0))
253 OUString hostname;
254 // The 'file' URI Scheme does imply that we want a FQDN in this case
255 // See https://tools.ietf.org/html/rfc8089#section-3
256 if (osl_getLocalHostnameFQDN(&hostname.pData) != osl_Socket_Ok
257 || (rtl_ustr_compareIgnoreAsciiCase_WithLength(
258 url.pData->buffer + i, j - i, hostname.getStr(), hostname.getLength())
259 != 0))
261 return osl_File_E_INVAL;
264 i = j;
266 // Handle empty path:
267 if (i == url.getLength())
269 *path = "/";
270 return osl_File_E_None;
272 // Path must not contain %2F:
273 if (url.indexOf("%2F", i) != -1 || url.indexOf("%2f", i) != -1)
274 return osl_File_E_INVAL;
276 if constexpr (std::is_same_v<T, rtl::OString>) {
277 if (!decodeFromUtf8(url.subView(i), path)) {
278 return osl_File_E_INVAL;
280 } else if constexpr (std::is_same_v<T, rtl::OUString>) {
281 *path = rtl::Uri::decode(
282 url.copy(i), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
283 } else {
284 static_assert(std::is_same_v<T, rtl::OString> || std::is_same_v<T, rtl::OUString>);
286 // Path must not contain %2F:
287 if (path->indexOf('\0') != -1)
288 return osl_File_E_INVAL;
290 // Handle ~ notation:
291 if (resolveHome && path->getLength() >= 2 && (*path)[1] == '~')
293 sal_Int32 j = path->indexOf('/', 2);
294 if (j == -1)
295 j = path->getLength();
297 if (j == 2)
299 OUString home;
300 if (!osl::Security().getHomeDir(home))
302 SAL_WARN("sal.file", "osl::Security::getHomeDir failed");
303 return osl_File_E_INVAL;
306 i = url.indexOf('/', i + 1);
308 if (i == -1)
309 i = url.getLength();
310 else
311 ++i;
313 //TODO: cheesy way of ensuring home's path ends in slash:
314 if (!home.isEmpty() && home[home.getLength() - 1] != '/')
315 home += "/";
318 home = rtl::Uri::convertRelToAbs(home, url.copy(i));
320 catch (rtl::MalformedUriException & e)
322 SAL_WARN("sal.file", "rtl::MalformedUriException " << e.getMessage());
323 return osl_File_E_INVAL;
325 return getSystemPathFromFileUrl(home, path, false);
327 // FIXME: replace ~user with user's home directory
328 return osl_File_E_INVAL;
330 return osl_File_E_None;
335 oslFileError SAL_CALL osl_getSystemPathFromFileURL( rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath )
337 OUString path;
338 oslFileError e;
341 e = getSystemPathFromFileUrl(
342 OUString::unacquired(&ustrFileURL), &path, true);
344 catch (std::length_error &)
346 e = osl_File_E_RANGE;
349 if (e == osl_File_E_None)
350 rtl_uString_assign(pustrSystemPath, path.pData);
352 return e;
355 oslFileError SAL_CALL osl_getFileURLFromSystemPath( rtl_uString *ustrSystemPath, rtl_uString **pustrFileURL )
357 rtl_uString *pTmp = nullptr;
358 sal_Int32 nIndex;
360 auto const & systemPath = OUString::unacquired(&ustrSystemPath);
362 if( systemPath.isEmpty() )
363 return osl_File_E_INVAL;
365 if( systemPath.startsWith( "file:" ) )
366 return osl_File_E_INVAL;
368 /* check if system path starts with ~ or ~user and replace it with the appropriate home dir */
369 if( systemPath.startsWith("~") )
371 /* check if another user is specified */
372 if( ( systemPath.getLength() == 1 ) ||
373 ( systemPath[1] == '/' ) )
375 /* osl_getHomeDir returns file URL */
376 oslSecurity pSecurity = osl_getCurrentSecurity();
377 osl_getHomeDir( pSecurity , &pTmp );
378 osl_freeSecurityHandle( pSecurity );
380 if (!pTmp)
381 return osl_File_E_INVAL;
383 /* remove "file://" prefix */
384 rtl_uString_newFromStr_WithLength( &pTmp, pTmp->buffer + 7, pTmp->length - 7 );
386 /* replace '~' in original string */
387 rtl_uString_newReplaceStrAt( &pTmp, systemPath.pData, 0, 1, pTmp );
389 else
391 /* FIXME: replace ~user with users home directory */
392 return osl_File_E_INVAL;
396 /* check if initial string contains repeated '/' characters */
397 nIndex = systemPath.indexOf( "//" );
398 if( nIndex != -1 )
400 sal_Int32 nSrcIndex;
401 sal_Int32 nDeleted = 0;
403 /* if pTmp is not already allocated, copy systemPath for modification */
404 if( pTmp == nullptr )
405 rtl_uString_newFromString( &pTmp, systemPath.pData );
407 /* adapt index to pTmp */
408 nIndex += pTmp->length - systemPath.getLength();
410 /* replace repeated '/' characters with a single '/' */
411 for( nSrcIndex = nIndex + 1; nSrcIndex < pTmp->length; nSrcIndex++ )
413 if( (pTmp->buffer[nSrcIndex] == '/') && (pTmp->buffer[nIndex] == '/') )
414 nDeleted++;
415 else
416 pTmp->buffer[++nIndex] = pTmp->buffer[nSrcIndex];
419 /* adjust length member */
420 pTmp->length -= nDeleted;
423 if( pTmp == nullptr )
424 rtl_uString_assign( &pTmp, systemPath.pData );
426 /* file URLs must be URI encoded */
427 rtl_uriEncode( pTmp, uriCharClass.data(), rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8, pustrFileURL );
429 rtl_uString_release( pTmp );
431 /* absolute urls should start with 'file://' */
432 if( (*pustrFileURL)->buffer[0] == '/' )
434 rtl_uString *pProtocol = nullptr;
436 rtl_uString_newFromAscii( &pProtocol, "file://" );
437 rtl_uString_newConcat( pustrFileURL, pProtocol, *pustrFileURL );
438 rtl_uString_release( pProtocol );
441 return osl_File_E_None;
445 * relative URLs are not accepted
447 oslFileError getSystemPathFromFileURL_Ex(
448 rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath)
450 rtl_uString* temp = nullptr;
451 oslFileError osl_error = osl_getSystemPathFromFileURL(ustrFileURL, &temp);
453 if (osl_error == osl_File_E_None)
455 if (temp->buffer[0] == '/')
457 *pustrSystemPath = temp;
459 else
461 rtl_uString_release(temp);
462 osl_error = osl_File_E_INVAL;
466 return osl_error;
469 namespace
472 /** Helper function, return a pointer to the final '\0'
473 of a string
476 sal_Unicode* ustrtoend(sal_Unicode* pStr)
478 return (pStr + rtl_ustr_getLength(pStr));
481 sal_Unicode* ustrchrcat(const sal_Unicode chr, sal_Unicode* d)
483 sal_Unicode* p = ustrtoend(d);
484 *p++ = chr;
485 *p = 0;
486 return d;
489 bool _islastchr(sal_Unicode* pStr, sal_Unicode Chr)
491 sal_Unicode* p = ustrtoend(pStr);
492 if (p > pStr)
493 p--;
494 return (*p == Chr);
498 Remove the last part of a path, a path that has
499 only a '/' or no '/' at all will be returned
500 unmodified
503 sal_Unicode* _rmlastpathtoken(sal_Unicode* aPath)
505 /* we may always skip -2 because we
506 may at least stand on a '/' but
507 either there is no other character
508 before this '/' or it's another
509 character than the '/'
511 sal_Unicode* p = ustrtoend(aPath) - 2;
513 /* move back to the next path separator
514 or to the start of the string */
515 while ((p > aPath) && (*p != '/'))
516 p--;
518 if (p >= aPath)
520 if (*p == '/')
522 p++;
523 *p = '\0';
525 else
527 *p = '\0';
531 return aPath;
534 oslFileError _osl_resolvepath(
535 /*inout*/ sal_Unicode* path,
536 /*inout*/ bool* failed)
538 oslFileError ferr = osl_File_E_None;
540 if (!*failed)
542 char unresolved_path[PATH_MAX];
543 if (!UnicodeToText(unresolved_path, sizeof(unresolved_path), path, rtl_ustr_getLength(path)))
544 return oslTranslateFileError(ENAMETOOLONG);
546 char resolved_path[PATH_MAX];
547 if (realpath(unresolved_path, resolved_path))
549 if (!TextToUnicode(resolved_path, strlen(resolved_path), path, PATH_MAX))
550 return oslTranslateFileError(ENAMETOOLONG);
552 else
554 if (EACCES == errno || ENOTDIR == errno || ENOENT == errno)
555 *failed = true;
556 else
557 ferr = oslTranslateFileError(errno);
561 return ferr;
565 Works even with non existing paths. The resulting path must not exceed
566 PATH_MAX else osl_File_E_NAMETOOLONG is the result
569 oslFileError osl_getAbsoluteFileURL_impl_(const OUString& unresolved_path, OUString& resolved_path)
571 /* the given unresolved path must not exceed PATH_MAX */
572 if (unresolved_path.getLength() >= (PATH_MAX - 2))
573 return oslTranslateFileError(ENAMETOOLONG);
575 sal_Unicode path_resolved_so_far[PATH_MAX];
576 const sal_Unicode* punresolved = unresolved_path.getStr();
577 sal_Unicode* presolvedsf = path_resolved_so_far;
579 /* reserve space for leading '/' and trailing '\0'
580 do not exceed this limit */
581 sal_Unicode* sentinel = path_resolved_so_far + PATH_MAX - 2;
583 /* if realpath fails with error ENOTDIR, EACCES or ENOENT
584 we will not call it again, because _osl_realpath should also
585 work with non existing directories etc. */
586 bool realpath_failed = false;
587 oslFileError ferr;
589 path_resolved_so_far[0] = '\0';
591 while (*punresolved != '\0')
593 /* ignore '/.' , skip one part back when '/..' */
594 if ((*punresolved == '.') && (*presolvedsf == '/'))
596 if (*(punresolved + 1) == '\0')
598 punresolved++;
599 continue;
601 if (*(punresolved + 1) == '/')
603 punresolved += 2;
604 continue;
606 if ((*(punresolved + 1) == '.') && (*(punresolved + 2) == '\0' || (*(punresolved + 2) == '/')))
608 _rmlastpathtoken(path_resolved_so_far);
610 presolvedsf = ustrtoend(path_resolved_so_far) - 1;
612 if (*(punresolved + 2) == '/')
613 punresolved += 3;
614 else
615 punresolved += 2;
617 continue;
620 /* a file or directory name may start with '.' */
621 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
622 return oslTranslateFileError(ENAMETOOLONG);
624 ustrchrcat(*punresolved++, path_resolved_so_far);
626 if (*punresolved == '\0' && !realpath_failed)
628 ferr = _osl_resolvepath(
629 path_resolved_so_far,
630 &realpath_failed);
632 if (ferr != osl_File_E_None)
633 return ferr;
636 else if (*punresolved == '/')
638 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
639 return oslTranslateFileError(ENAMETOOLONG);
641 ustrchrcat(*punresolved++, path_resolved_so_far);
643 if (!realpath_failed)
645 ferr = _osl_resolvepath(
646 path_resolved_so_far,
647 &realpath_failed);
649 if (ferr != osl_File_E_None)
650 return ferr;
652 if (!_islastchr(path_resolved_so_far, '/'))
654 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
655 return oslTranslateFileError(ENAMETOOLONG);
657 ustrchrcat('/', path_resolved_so_far);
661 else // any other character
663 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
664 return oslTranslateFileError(ENAMETOOLONG);
666 ustrchrcat(*punresolved++, path_resolved_so_far);
668 if (*punresolved == '\0' && !realpath_failed)
670 ferr = _osl_resolvepath(
671 path_resolved_so_far,
672 &realpath_failed);
674 if (ferr != osl_File_E_None)
675 return ferr;
680 sal_Int32 len = rtl_ustr_getLength(path_resolved_so_far);
682 OSL_ASSERT(len < PATH_MAX);
684 resolved_path = OUString(path_resolved_so_far, len);
686 return osl_File_E_None;
691 oslFileError osl_getAbsoluteFileURL(
692 rtl_uString* ustrBaseDirURL,
693 rtl_uString* ustrRelativeURL,
694 rtl_uString** pustrAbsoluteURL)
696 /* Work around the below call to getSystemPathFromFileURL rejecting input
697 that starts with "/" (for whatever reason it behaves that way; but
698 changing that would start to break lots of tests at least) */
699 OUString relUrl(ustrRelativeURL);
700 if (relUrl.startsWith("//"))
701 relUrl = "file:" + relUrl;
702 else if (relUrl.startsWith("/"))
703 relUrl = "file://" + relUrl;
705 OUString unresolved_path;
707 FileBase::RC frc = FileBase::getSystemPathFromFileURL(relUrl, unresolved_path);
708 if (frc != FileBase::E_None)
709 return oslFileError(frc);
711 if (systemPathIsRelativePath(unresolved_path))
713 OUString base_path;
714 oslFileError rc = getSystemPathFromFileURL_Ex(ustrBaseDirURL, &base_path.pData);
715 if (rc != osl_File_E_None)
716 return rc;
718 unresolved_path = systemPathMakeAbsolutePath(base_path, unresolved_path);
721 OUString resolved_path;
722 oslFileError rc = osl_getAbsoluteFileURL_impl_(unresolved_path, resolved_path);
723 if (rc == osl_File_E_None)
725 rc = osl_getFileURLFromSystemPath(resolved_path.pData, pustrAbsoluteURL);
726 OSL_ASSERT(osl_File_E_None == rc);
729 return rc;
732 namespace osl::detail {
735 No separate error code if unicode to text conversion or getenv fails because for the
736 caller there is no difference why a file could not be found in $PATH
738 bool find_in_PATH(const OUString& file_path, OUString& result)
740 bool bfound = false;
741 OUString path("PATH");
742 OUString env_path;
744 if (osl_getEnvironment(path.pData, &env_path.pData) == osl_Process_E_None)
745 bfound = osl::searchPath(file_path, env_path, result);
747 return bfound;
751 namespace
754 No separate error code if unicode to text conversion or getcwd fails because for the
755 caller there is no difference why a file could not be found in CDW
757 bool find_in_CWD(const OUString& file_path, OUString& result)
759 bool bfound = false;
760 OUString cwd_url;
762 if (osl_getProcessWorkingDir(&cwd_url.pData) == osl_Process_E_None)
764 OUString cwd;
765 FileBase::getSystemPathFromFileURL(cwd_url, cwd);
766 bfound = osl::searchPath(file_path, cwd, result);
768 return bfound;
771 bool find_in_searchPath(const OUString& file_path, rtl_uString* search_path, OUString& result)
773 return (search_path && osl::searchPath(file_path, OUString(search_path), result));
778 oslFileError osl_searchFileURL(rtl_uString* ustrFilePath, rtl_uString* ustrSearchPath, rtl_uString** pustrURL)
780 OSL_PRECOND(ustrFilePath && pustrURL, "osl_searchFileURL: invalid parameter");
782 FileBase::RC rc;
783 OUString file_path;
785 // try to interpret search path as file url else assume it's a system path list
786 rc = FileBase::getSystemPathFromFileURL(ustrFilePath, file_path);
787 if (rc == FileBase::E_INVAL)
788 file_path = ustrFilePath;
789 else if (rc != FileBase::E_None)
790 return oslFileError(rc);
792 bool bfound = false;
793 OUString result;
795 if (find_in_searchPath(file_path, ustrSearchPath, result) ||
796 osl::detail::find_in_PATH(file_path, result) ||
797 find_in_CWD(file_path, result))
799 OUString resolved;
801 if (osl::realpath(result, resolved))
803 oslFileError osl_error = osl_getFileURLFromSystemPath(resolved.pData, pustrURL);
804 SAL_WARN_IF(osl_File_E_None != osl_error, "sal.file", "osl_getFileURLFromSystemPath failed");
805 bfound = true;
808 return bfound ? osl_File_E_None : osl_File_E_NOENT;
811 oslFileError FileURLToPath(char * buffer, size_t bufLen, rtl_uString* ustrFileURL)
813 OString strSystemPath;
814 oslFileError osl_error = osl::detail::convertUrlToPathname(
815 OUString::unacquired(&ustrFileURL), &strSystemPath);
817 if(osl_error != osl_File_E_None)
818 return osl_error;
820 osl_systemPathRemoveSeparator(strSystemPath.pData);
822 if (o3tl::make_unsigned(strSystemPath.getLength()) >= bufLen) {
823 return osl_File_E_OVERFLOW;
825 std::strcpy(buffer, strSystemPath.getStr());
827 return osl_error;
830 int UnicodeToText( char * buffer, size_t bufLen, const sal_Unicode * uniText, sal_Int32 uniTextLen )
832 sal_uInt32 nInfo = 0;
833 sal_Size nSrcChars = 0;
835 sal_Size nDestBytes = UnicodeToTextConverter_Impl::getInstance().convert (
836 uniText, uniTextLen, buffer, bufLen,
837 OUSTRING_TO_OSTRING_CVTFLAGS | RTL_UNICODETOTEXT_FLAGS_FLUSH, &nInfo, &nSrcChars);
839 if( nInfo & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL )
841 errno = EOVERFLOW;
842 return 0;
845 /* ensure trailing '\0' */
846 buffer[nDestBytes] = '\0';
847 return nDestBytes;
850 namespace
852 class TextToUnicodeConverter_Impl
854 rtl_TextToUnicodeConverter m_converter;
856 TextToUnicodeConverter_Impl()
857 : m_converter (rtl_createTextToUnicodeConverter (osl_getThreadTextEncoding()))
860 ~TextToUnicodeConverter_Impl()
862 rtl_destroyTextToUnicodeConverter (m_converter);
865 public:
866 static TextToUnicodeConverter_Impl & getInstance()
868 static TextToUnicodeConverter_Impl g_theConverter;
869 return g_theConverter;
872 sal_Size convert(
873 char const * pSrcBuf, sal_Size nSrcBytes, sal_Unicode * pDstBuf, sal_Size nDstChars,
874 sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtBytes)
876 OSL_ASSERT(m_converter != nullptr);
877 return rtl_convertTextToUnicode (
878 m_converter, nullptr, pSrcBuf, nSrcBytes, pDstBuf, nDstChars, nFlags, pInfo, pSrcCvtBytes);
883 int TextToUnicode(
884 const char* text,
885 size_t text_buffer_size,
886 sal_Unicode* unic_text,
887 sal_Int32 unic_text_buffer_size)
889 sal_uInt32 nInfo = 0;
890 sal_Size nSrcChars = 0;
892 sal_Size nDestBytes = TextToUnicodeConverter_Impl::getInstance().convert(
893 text, text_buffer_size, unic_text, unic_text_buffer_size,
894 OSTRING_TO_OUSTRING_CVTFLAGS | RTL_TEXTTOUNICODE_FLAGS_FLUSH, &nInfo, &nSrcChars);
896 if (nInfo & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL)
898 errno = EOVERFLOW;
899 return 0;
902 /* ensure trailing '\0' */
903 unic_text[nDestBytes] = '\0';
904 return nDestBytes;
907 oslFileError osl::detail::convertUrlToPathname(OUString const & url, OString * pathname) {
908 assert(pathname != nullptr);
909 oslFileError e;
910 try {
911 e = getSystemPathFromFileUrl(url, pathname, true);
912 } catch (std::length_error &) {
913 e = osl_File_E_RANGE;
915 if (e == osl_File_E_None && !pathname->startsWith("/")) {
916 e = osl_File_E_INVAL;
918 return e;
921 oslFileError osl::detail::convertPathnameToUrl(OString const & pathname, OUString * url) {
922 assert(url != nullptr);
923 OUStringBuffer buf(10+pathname.getLength());
924 buf.append("file:");
925 if (pathname.startsWith("/")) {
926 buf.append("//");
927 // so if pathname should ever start with "//" that isn't mistaken for an authority
928 // component
930 for (sal_Size convert = pathname.getLength();;) {
931 auto n = std::max(convert, sal_Size(PATH_MAX)); // approximation of required converted size
932 OUStringBuffer ubuf(static_cast<int>(n));
933 auto s = ubuf.appendUninitialized(n);
934 sal_uInt32 info;
935 sal_Size converted;
936 //TODO: context, for reliable treatment of DESTBUFFERTOSMALL:
937 n = TextToUnicodeConverter_Impl::getInstance().convert(
938 pathname.getStr() + pathname.getLength() - convert, convert, s, n,
939 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
940 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR | RTL_TEXTTOUNICODE_FLAGS_FLUSH),
941 &info, &converted);
942 ubuf.setLength(n);
943 buf.append(
944 rtl::Uri::encode(
945 ubuf.makeStringAndClear(), uriCharClass.data(), rtl_UriEncodeIgnoreEscapes,
946 RTL_TEXTENCODING_UTF8));
947 assert(converted <= convert);
948 convert -= converted;
949 if ((info & RTL_TEXTTOUNICODE_INFO_ERROR) != 0) {
950 assert(convert > 0);
951 //TODO: see writeEscapeOctet in sal/rtl/uri.cxx
952 buf.append("%");
953 unsigned char c = pathname[pathname.getLength() - convert];
954 assert(c >= 0x80);
955 static sal_Unicode const aHex[16]
956 = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
957 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 }; /* '0'--'9', 'A'--'F' */
958 buf.append(OUStringChar(aHex[c >> 4]) + OUStringChar(aHex[c & 15]));
959 --convert;
960 continue;
962 assert((convert == 0) == ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0));
963 if ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0) {
964 break;
967 *url = buf.makeStringAndClear();
968 return osl_File_E_None;
971 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */