Avoid potential negative array index access to cached text.
[LibreOffice.git] / sal / osl / unx / file_url.cxx
blobbe98df95f14ecdaeed68d45e3155a944fbd0f664
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 <algorithm>
23 #include <cassert>
24 #include <cstring>
25 #include <stdexcept>
26 #include <string_view>
27 #include <limits.h>
28 #include <errno.h>
30 #include <o3tl/safeint.hxx>
31 #include <osl/file.hxx>
32 #include <osl/security.hxx>
33 #include <osl/socket.h>
34 #include <oslsocket.hxx>
35 #include <osl/diagnose.h>
36 #include <osl/thread.h>
37 #include <osl/process.h>
39 #include <rtl/character.hxx>
40 #include <rtl/strbuf.hxx>
41 #include <rtl/uri.h>
42 #include <rtl/uri.hxx>
43 #include <rtl/ustring.hxx>
44 #include <rtl/ustrbuf.hxx>
45 #include <rtl/textcvt.h>
46 #include <sal/log.hxx>
48 #include <uri_internal.hxx>
50 #include "file_error_transl.hxx"
51 #include "file_path_helper.hxx"
53 #include "uunxapi.hxx"
55 /** @file
57 General note
59 This file contains the part that handles File URLs.
61 File URLs as scheme specific notion of URIs
62 (RFC2396) may be handled platform independent, but
63 will not in osl which is considered wrong.
64 Future version of osl should handle File URLs this
65 way. In rtl/uri there is already a URI parser etc.
66 so this code should be consolidated.
70 using namespace osl;
72 namespace {
74 // A slightly modified version of Pchar in rtl/source/uri.c, but without
75 // encoding slashes:
76 constexpr auto uriCharClass = rtl::createUriCharClass(
77 u8"!$&'()*+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~");
81 oslFileError SAL_CALL osl_getCanonicalName( rtl_uString* ustrFileURL, rtl_uString** pustrValidURL )
83 OSL_FAIL("osl_getCanonicalName not implemented");
85 rtl_uString_newFromString(pustrValidURL, ustrFileURL);
86 return osl_File_E_None;
89 namespace {
91 class UnicodeToTextConverter_Impl
93 rtl_UnicodeToTextConverter m_converter;
95 UnicodeToTextConverter_Impl()
96 : m_converter (rtl_createUnicodeToTextConverter (osl_getThreadTextEncoding()))
99 ~UnicodeToTextConverter_Impl()
101 rtl_destroyUnicodeToTextConverter (m_converter);
103 public:
104 static UnicodeToTextConverter_Impl & getInstance()
106 static UnicodeToTextConverter_Impl g_theConverter;
107 return g_theConverter;
110 sal_Size convert(
111 sal_Unicode const * pSrcBuf, sal_Size nSrcChars, char * pDstBuf, sal_Size nDstBytes,
112 sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtChars)
114 OSL_ASSERT(m_converter != nullptr);
115 return rtl_convertUnicodeToText (
116 m_converter, nullptr, pSrcBuf, nSrcChars, pDstBuf, nDstBytes, nFlags, pInfo, pSrcCvtChars);
120 bool convert(OUStringBuffer const & in, OStringBuffer * append) {
121 assert(append != nullptr);
122 for (sal_Size nConvert = in.getLength();;) {
123 auto const oldLen = append->getLength();
124 auto n = std::min(
125 std::max(nConvert, sal_Size(PATH_MAX)),
126 sal_Size(std::numeric_limits<sal_Int32>::max() - oldLen));
127 // approximation of required converted size
128 auto s = append->appendUninitialized(n);
129 sal_uInt32 info;
130 sal_Size converted;
131 //TODO: context, for reliable treatment of DESTBUFFERTOSMALL:
132 n = UnicodeToTextConverter_Impl::getInstance().convert(
133 in.getStr() + in.getLength() - nConvert, nConvert, s, n,
134 (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
135 | RTL_UNICODETOTEXT_FLAGS_FLUSH),
136 &info, &converted);
137 if ((info & RTL_UNICODETOTEXT_INFO_ERROR) != 0) {
138 return false;
140 append->setLength(oldLen + n);
141 assert(converted <= nConvert);
142 nConvert -= converted;
143 assert((nConvert == 0) == ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0));
144 if ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0) {
145 break;
148 return true;
151 bool decodeFromUtf8(std::u16string_view text, OString * result) {
152 assert(result != nullptr);
153 auto p = text.data();
154 auto const end = p + text.size();
155 OUStringBuffer ubuf(static_cast<int>(text.size()));
156 OStringBuffer bbuf(PATH_MAX);
157 while (p < end) {
158 rtl::uri::detail::EscapeType t;
159 sal_uInt32 c = rtl::uri::detail::readUcs4(&p, end, true, RTL_TEXTENCODING_UTF8, &t);
160 switch (t) {
161 case rtl::uri::detail::EscapeNo:
162 if (c == '%') {
163 return false;
165 [[fallthrough]];
166 case rtl::uri::detail::EscapeChar:
167 if (rtl::isSurrogate(c)) {
168 return false;
170 ubuf.appendUtf32(c);
171 break;
172 case rtl::uri::detail::EscapeOctet:
173 if (!convert(ubuf, &bbuf)) {
174 return false;
176 ubuf.setLength(0);
177 assert(c <= 0xFF);
178 bbuf.append(char(c));
179 break;
182 if (!convert(ubuf, &bbuf)) {
183 return false;
185 *result = bbuf.makeStringAndClear();
186 return true;
189 template<typename T> oslFileError getSystemPathFromFileUrl(
190 OUString const & url, T * path, bool resolveHome)
192 assert(path != nullptr);
193 // For compatibility with assumptions in other parts of the code base,
194 // assume that anything starting with a slash is a system path instead of a
195 // (relative) file URL (except if it starts with two slashes, in which case
196 // it is a relative URL with an authority component):
197 if (url.isEmpty()
198 || (url[0] == '/' && (url.getLength() == 1 || url[1] != '/')))
200 return osl_File_E_INVAL;
202 // Check for non file scheme:
203 sal_Int32 i = 0;
204 if (rtl::isAsciiAlpha(url[0])) {
205 for (sal_Int32 j = 1; j != url.getLength(); ++j) {
206 auto c = url[j];
207 if (c == ':') {
208 if (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
209 url.pData->buffer, j,
210 RTL_CONSTASCII_STRINGPARAM("file"))
211 != 0)
213 return osl_File_E_INVAL;
215 i = j + 1;
216 break;
218 if (!rtl::isAsciiAlphanumeric(c) && c != '+' && c != '-'
219 && c != '.')
221 break;
225 // Handle query or fragment:
226 if (url.indexOf('?', i) != -1 || url.indexOf('#', i) != -1)
227 return osl_File_E_INVAL;
228 // Handle authority, supporting a host of "localhost", "127.0.0.1", or the exact value (e.g.,
229 // not supporting an additional final dot, for simplicity) reported by osl_getLocalHostnameFQDN
230 // (and, in each case, ignoring case of ASCII letters):
231 if (url.getLength() - i >= 2 && url[i] == '/' && url[i + 1] == '/')
233 i += 2;
234 sal_Int32 j = url.indexOf('/', i);
235 if (j == -1)
236 j = url.getLength();
237 if (j != i
238 && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
239 url.pData->buffer + i, j - i,
240 RTL_CONSTASCII_STRINGPARAM("localhost"))
241 != 0)
242 && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
243 url.pData->buffer + i, j - i,
244 RTL_CONSTASCII_STRINGPARAM("127.0.0.1"))
245 != 0))
247 OUString hostname;
248 // The 'file' URI Scheme does imply that we want a FQDN in this case
249 // See https://tools.ietf.org/html/rfc8089#section-3
250 if (osl_getLocalHostnameFQDN(&hostname.pData) != osl_Socket_Ok
251 || (rtl_ustr_compareIgnoreAsciiCase_WithLength(
252 url.pData->buffer + i, j - i, hostname.getStr(), hostname.getLength())
253 != 0))
255 return osl_File_E_INVAL;
258 i = j;
260 // Handle empty path:
261 if (i == url.getLength())
263 *path = "/";
264 return osl_File_E_None;
266 // Path must not contain %2F:
267 if (url.indexOf("%2F", i) != -1 || url.indexOf("%2f", i) != -1)
268 return osl_File_E_INVAL;
270 if constexpr (std::is_same_v<T, rtl::OString>) {
271 if (!decodeFromUtf8(url.subView(i), path)) {
272 return osl_File_E_INVAL;
274 } else if constexpr (std::is_same_v<T, rtl::OUString>) {
275 *path = rtl::Uri::decode(
276 url.copy(i), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
277 } else {
278 static_assert(std::is_same_v<T, rtl::OString> || std::is_same_v<T, rtl::OUString>);
280 // Path must not contain %2F:
281 if (path->indexOf('\0') != -1)
282 return osl_File_E_INVAL;
284 // Handle ~ notation:
285 if (resolveHome && path->getLength() >= 2 && (*path)[1] == '~')
287 sal_Int32 j = path->indexOf('/', 2);
288 if (j == -1)
289 j = path->getLength();
291 if (j == 2)
293 OUString home;
294 if (!osl::Security().getHomeDir(home))
296 SAL_WARN("sal.file", "osl::Security::getHomeDir failed");
297 return osl_File_E_INVAL;
300 i = url.indexOf('/', i + 1);
302 if (i == -1)
303 i = url.getLength();
304 else
305 ++i;
307 //TODO: cheesy way of ensuring home's path ends in slash:
308 if (!home.isEmpty() && home[home.getLength() - 1] != '/')
309 home += "/";
312 home = rtl::Uri::convertRelToAbs(home, url.copy(i));
314 catch (rtl::MalformedUriException & e)
316 SAL_WARN("sal.file", "rtl::MalformedUriException " << e.getMessage());
317 return osl_File_E_INVAL;
319 return getSystemPathFromFileUrl(home, path, false);
321 // FIXME: replace ~user with user's home directory
322 return osl_File_E_INVAL;
324 return osl_File_E_None;
329 oslFileError SAL_CALL osl_getSystemPathFromFileURL( rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath )
331 OUString path;
332 oslFileError e;
335 e = getSystemPathFromFileUrl(
336 OUString::unacquired(&ustrFileURL), &path, true);
338 catch (std::length_error &)
340 e = osl_File_E_RANGE;
343 if (e == osl_File_E_None)
344 rtl_uString_assign(pustrSystemPath, path.pData);
346 return e;
349 oslFileError SAL_CALL osl_getFileURLFromSystemPath( rtl_uString *ustrSystemPath, rtl_uString **pustrFileURL )
351 rtl_uString *pTmp = nullptr;
352 sal_Int32 nIndex;
354 auto const & systemPath = OUString::unacquired(&ustrSystemPath);
356 if( systemPath.isEmpty() )
357 return osl_File_E_INVAL;
359 if( systemPath.startsWith( "file:" ) )
360 return osl_File_E_INVAL;
362 /* check if system path starts with ~ or ~user and replace it with the appropriate home dir */
363 if( systemPath.startsWith("~") )
365 /* check if another user is specified */
366 if( ( systemPath.getLength() == 1 ) ||
367 ( systemPath[1] == '/' ) )
369 /* osl_getHomeDir returns file URL */
370 oslSecurity pSecurity = osl_getCurrentSecurity();
371 osl_getHomeDir( pSecurity , &pTmp );
372 osl_freeSecurityHandle( pSecurity );
374 if (!pTmp)
375 return osl_File_E_INVAL;
377 /* remove "file://" prefix */
378 rtl_uString_newFromStr_WithLength( &pTmp, pTmp->buffer + 7, pTmp->length - 7 );
380 /* replace '~' in original string */
381 rtl_uString_newReplaceStrAt( &pTmp, systemPath.pData, 0, 1, pTmp );
383 else
385 /* FIXME: replace ~user with users home directory */
386 return osl_File_E_INVAL;
390 /* check if initial string contains repeated '/' characters */
391 nIndex = systemPath.indexOf( "//" );
392 if( nIndex != -1 )
394 sal_Int32 nSrcIndex;
395 sal_Int32 nDeleted = 0;
397 /* if pTmp is not already allocated, copy systemPath for modification */
398 if( pTmp == nullptr )
399 rtl_uString_newFromString( &pTmp, systemPath.pData );
401 /* adapt index to pTmp */
402 nIndex += pTmp->length - systemPath.getLength();
404 /* replace repeated '/' characters with a single '/' */
405 for( nSrcIndex = nIndex + 1; nSrcIndex < pTmp->length; nSrcIndex++ )
407 if( (pTmp->buffer[nSrcIndex] == '/') && (pTmp->buffer[nIndex] == '/') )
408 nDeleted++;
409 else
410 pTmp->buffer[++nIndex] = pTmp->buffer[nSrcIndex];
413 /* adjust length member */
414 pTmp->length -= nDeleted;
417 if( pTmp == nullptr )
418 rtl_uString_assign( &pTmp, systemPath.pData );
420 /* file URLs must be URI encoded */
421 rtl_uriEncode( pTmp, uriCharClass.data(), rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8, pustrFileURL );
423 rtl_uString_release( pTmp );
425 /* absolute urls should start with 'file://' */
426 if( (*pustrFileURL)->buffer[0] == '/' )
428 rtl_uString *pProtocol = nullptr;
430 rtl_uString_newFromAscii( &pProtocol, "file://" );
431 rtl_uString_newConcat( pustrFileURL, pProtocol, *pustrFileURL );
432 rtl_uString_release( pProtocol );
435 return osl_File_E_None;
439 * relative URLs are not accepted
441 oslFileError getSystemPathFromFileURL_Ex(
442 rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath)
444 rtl_uString* temp = nullptr;
445 oslFileError osl_error = osl_getSystemPathFromFileURL(ustrFileURL, &temp);
447 if (osl_error == osl_File_E_None)
449 if (temp->buffer[0] == '/')
451 *pustrSystemPath = temp;
453 else
455 rtl_uString_release(temp);
456 osl_error = osl_File_E_INVAL;
460 return osl_error;
463 namespace
466 /** Helper function, return a pointer to the final '\0'
467 of a string
470 sal_Unicode* ustrtoend(sal_Unicode* pStr)
472 return (pStr + rtl_ustr_getLength(pStr));
475 sal_Unicode* ustrchrcat(const sal_Unicode chr, sal_Unicode* d)
477 sal_Unicode* p = ustrtoend(d);
478 *p++ = chr;
479 *p = 0;
480 return d;
483 bool _islastchr(sal_Unicode* pStr, sal_Unicode Chr)
485 sal_Unicode* p = ustrtoend(pStr);
486 if (p > pStr)
487 p--;
488 return (*p == Chr);
492 Remove the last part of a path, a path that has
493 only a '/' or no '/' at all will be returned
494 unmodified
497 sal_Unicode* _rmlastpathtoken(sal_Unicode* aPath)
499 /* we may always skip -2 because we
500 may at least stand on a '/' but
501 either there is no other character
502 before this '/' or it's another
503 character than the '/'
505 sal_Unicode* p = ustrtoend(aPath) - 2;
507 /* move back to the next path separator
508 or to the start of the string */
509 while ((p > aPath) && (*p != '/'))
510 p--;
512 if (p >= aPath)
514 if (*p == '/')
516 p++;
517 *p = '\0';
519 else
521 *p = '\0';
525 return aPath;
528 oslFileError _osl_resolvepath(
529 /*inout*/ sal_Unicode* path,
530 /*inout*/ bool* failed)
532 oslFileError ferr = osl_File_E_None;
534 if (!*failed)
536 char unresolved_path[PATH_MAX];
537 if (!UnicodeToText(unresolved_path, sizeof(unresolved_path), path, rtl_ustr_getLength(path)))
538 return oslTranslateFileError(ENAMETOOLONG);
540 char resolved_path[PATH_MAX];
541 if (realpath(unresolved_path, resolved_path))
543 if (!TextToUnicode(resolved_path, strlen(resolved_path), path, PATH_MAX))
544 return oslTranslateFileError(ENAMETOOLONG);
546 else
548 if (EACCES == errno || ENOTDIR == errno || ENOENT == errno)
549 *failed = true;
550 else
551 ferr = oslTranslateFileError(errno);
555 return ferr;
559 Works even with non existing paths. The resulting path must not exceed
560 PATH_MAX else osl_File_E_NAMETOOLONG is the result
563 oslFileError osl_getAbsoluteFileURL_impl_(const OUString& unresolved_path, OUString& resolved_path)
565 /* the given unresolved path must not exceed PATH_MAX */
566 if (unresolved_path.getLength() >= (PATH_MAX - 2))
567 return oslTranslateFileError(ENAMETOOLONG);
569 sal_Unicode path_resolved_so_far[PATH_MAX];
570 const sal_Unicode* punresolved = unresolved_path.getStr();
571 sal_Unicode* presolvedsf = path_resolved_so_far;
573 /* reserve space for leading '/' and trailing '\0'
574 do not exceed this limit */
575 sal_Unicode* sentinel = path_resolved_so_far + PATH_MAX - 2;
577 /* if realpath fails with error ENOTDIR, EACCES or ENOENT
578 we will not call it again, because _osl_realpath should also
579 work with non existing directories etc. */
580 bool realpath_failed = false;
581 oslFileError ferr;
583 path_resolved_so_far[0] = '\0';
585 while (*punresolved != '\0')
587 /* ignore '/.' , skip one part back when '/..' */
588 if ((*punresolved == '.') && (*presolvedsf == '/'))
590 if (*(punresolved + 1) == '\0')
592 punresolved++;
593 continue;
595 if (*(punresolved + 1) == '/')
597 punresolved += 2;
598 continue;
600 if ((*(punresolved + 1) == '.') && (*(punresolved + 2) == '\0' || (*(punresolved + 2) == '/')))
602 _rmlastpathtoken(path_resolved_so_far);
604 presolvedsf = ustrtoend(path_resolved_so_far) - 1;
606 if (*(punresolved + 2) == '/')
607 punresolved += 3;
608 else
609 punresolved += 2;
611 continue;
614 /* a file or directory name may start with '.' */
615 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
616 return oslTranslateFileError(ENAMETOOLONG);
618 ustrchrcat(*punresolved++, path_resolved_so_far);
620 if (*punresolved == '\0' && !realpath_failed)
622 ferr = _osl_resolvepath(
623 path_resolved_so_far,
624 &realpath_failed);
626 if (ferr != osl_File_E_None)
627 return ferr;
630 else if (*punresolved == '/')
632 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
633 return oslTranslateFileError(ENAMETOOLONG);
635 ustrchrcat(*punresolved++, path_resolved_so_far);
637 if (!realpath_failed)
639 ferr = _osl_resolvepath(
640 path_resolved_so_far,
641 &realpath_failed);
643 if (ferr != osl_File_E_None)
644 return ferr;
646 if (!_islastchr(path_resolved_so_far, '/'))
648 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
649 return oslTranslateFileError(ENAMETOOLONG);
651 ustrchrcat('/', path_resolved_so_far);
655 else // any other character
657 if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
658 return oslTranslateFileError(ENAMETOOLONG);
660 ustrchrcat(*punresolved++, path_resolved_so_far);
662 if (*punresolved == '\0' && !realpath_failed)
664 ferr = _osl_resolvepath(
665 path_resolved_so_far,
666 &realpath_failed);
668 if (ferr != osl_File_E_None)
669 return ferr;
674 sal_Int32 len = rtl_ustr_getLength(path_resolved_so_far);
676 OSL_ASSERT(len < PATH_MAX);
678 resolved_path = OUString(path_resolved_so_far, len);
680 return osl_File_E_None;
685 oslFileError osl_getAbsoluteFileURL(
686 rtl_uString* ustrBaseDirURL,
687 rtl_uString* ustrRelativeURL,
688 rtl_uString** pustrAbsoluteURL)
690 /* Work around the below call to getSystemPathFromFileURL rejecting input
691 that starts with "/" (for whatever reason it behaves that way; but
692 changing that would start to break lots of tests at least) */
693 OUString relUrl(ustrRelativeURL);
694 if (relUrl.startsWith("//"))
695 relUrl = "file:" + relUrl;
696 else if (relUrl.startsWith("/"))
697 relUrl = "file://" + relUrl;
699 OUString unresolved_path;
701 FileBase::RC frc = FileBase::getSystemPathFromFileURL(relUrl, unresolved_path);
702 if (frc != FileBase::E_None)
703 return oslFileError(frc);
705 if (systemPathIsRelativePath(unresolved_path))
707 OUString base_path;
708 oslFileError rc = getSystemPathFromFileURL_Ex(ustrBaseDirURL, &base_path.pData);
709 if (rc != osl_File_E_None)
710 return rc;
712 unresolved_path = systemPathMakeAbsolutePath(base_path, unresolved_path);
715 OUString resolved_path;
716 oslFileError rc = osl_getAbsoluteFileURL_impl_(unresolved_path, resolved_path);
717 if (rc == osl_File_E_None)
719 rc = osl_getFileURLFromSystemPath(resolved_path.pData, pustrAbsoluteURL);
720 OSL_ASSERT(osl_File_E_None == rc);
723 return rc;
726 namespace osl::detail {
729 No separate error code if unicode to text conversion or getenv fails because for the
730 caller there is no difference why a file could not be found in $PATH
732 bool find_in_PATH(const OUString& file_path, OUString& result)
734 bool bfound = false;
735 OUString path("PATH");
736 OUString env_path;
738 if (osl_getEnvironment(path.pData, &env_path.pData) == osl_Process_E_None)
739 bfound = osl::searchPath(file_path, env_path, result);
741 return bfound;
745 namespace
748 No separate error code if unicode to text conversion or getcwd fails because for the
749 caller there is no difference why a file could not be found in CDW
751 bool find_in_CWD(const OUString& file_path, OUString& result)
753 bool bfound = false;
754 OUString cwd_url;
756 if (osl_getProcessWorkingDir(&cwd_url.pData) == osl_Process_E_None)
758 OUString cwd;
759 FileBase::getSystemPathFromFileURL(cwd_url, cwd);
760 bfound = osl::searchPath(file_path, cwd, result);
762 return bfound;
765 bool find_in_searchPath(const OUString& file_path, rtl_uString* search_path, OUString& result)
767 return (search_path && osl::searchPath(file_path, OUString(search_path), result));
772 oslFileError osl_searchFileURL(rtl_uString* ustrFilePath, rtl_uString* ustrSearchPath, rtl_uString** pustrURL)
774 OSL_PRECOND(ustrFilePath && pustrURL, "osl_searchFileURL: invalid parameter");
776 FileBase::RC rc;
777 OUString file_path;
779 // try to interpret search path as file url else assume it's a system path list
780 rc = FileBase::getSystemPathFromFileURL(ustrFilePath, file_path);
781 if (rc == FileBase::E_INVAL)
782 file_path = ustrFilePath;
783 else if (rc != FileBase::E_None)
784 return oslFileError(rc);
786 bool bfound = false;
787 OUString result;
789 if (find_in_searchPath(file_path, ustrSearchPath, result) ||
790 osl::detail::find_in_PATH(file_path, result) ||
791 find_in_CWD(file_path, result))
793 OUString resolved;
795 if (osl::realpath(result, resolved))
797 oslFileError osl_error = osl_getFileURLFromSystemPath(resolved.pData, pustrURL);
798 SAL_WARN_IF(osl_File_E_None != osl_error, "sal.file", "osl_getFileURLFromSystemPath failed");
799 bfound = true;
802 return bfound ? osl_File_E_None : osl_File_E_NOENT;
805 oslFileError FileURLToPath(char * buffer, size_t bufLen, rtl_uString* ustrFileURL)
807 OString strSystemPath;
808 oslFileError osl_error = osl::detail::convertUrlToPathname(
809 OUString::unacquired(&ustrFileURL), &strSystemPath);
811 if(osl_error != osl_File_E_None)
812 return osl_error;
814 osl_systemPathRemoveSeparator(strSystemPath.pData);
816 if (o3tl::make_unsigned(strSystemPath.getLength()) >= bufLen) {
817 return osl_File_E_OVERFLOW;
819 std::strcpy(buffer, strSystemPath.getStr());
821 return osl_error;
824 int UnicodeToText( char * buffer, size_t bufLen, const sal_Unicode * uniText, sal_Int32 uniTextLen )
826 sal_uInt32 nInfo = 0;
827 sal_Size nSrcChars = 0;
829 sal_Size nDestBytes = UnicodeToTextConverter_Impl::getInstance().convert (
830 uniText, uniTextLen, buffer, bufLen,
831 OUSTRING_TO_OSTRING_CVTFLAGS | RTL_UNICODETOTEXT_FLAGS_FLUSH, &nInfo, &nSrcChars);
833 if( nInfo & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL )
835 errno = EOVERFLOW;
836 return 0;
839 /* ensure trailing '\0' */
840 buffer[nDestBytes] = '\0';
841 return nDestBytes;
844 namespace
846 class TextToUnicodeConverter_Impl
848 rtl_TextToUnicodeConverter m_converter;
850 TextToUnicodeConverter_Impl()
851 : m_converter (rtl_createTextToUnicodeConverter (osl_getThreadTextEncoding()))
854 ~TextToUnicodeConverter_Impl()
856 rtl_destroyTextToUnicodeConverter (m_converter);
859 public:
860 static TextToUnicodeConverter_Impl & getInstance()
862 static TextToUnicodeConverter_Impl g_theConverter;
863 return g_theConverter;
866 sal_Size convert(
867 char const * pSrcBuf, sal_Size nSrcBytes, sal_Unicode * pDstBuf, sal_Size nDstChars,
868 sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtBytes)
870 OSL_ASSERT(m_converter != nullptr);
871 return rtl_convertTextToUnicode (
872 m_converter, nullptr, pSrcBuf, nSrcBytes, pDstBuf, nDstChars, nFlags, pInfo, pSrcCvtBytes);
877 int TextToUnicode(
878 const char* text,
879 size_t text_buffer_size,
880 sal_Unicode* unic_text,
881 sal_Int32 unic_text_buffer_size)
883 sal_uInt32 nInfo = 0;
884 sal_Size nSrcChars = 0;
886 sal_Size nDestBytes = TextToUnicodeConverter_Impl::getInstance().convert(
887 text, text_buffer_size, unic_text, unic_text_buffer_size,
888 OSTRING_TO_OUSTRING_CVTFLAGS | RTL_TEXTTOUNICODE_FLAGS_FLUSH, &nInfo, &nSrcChars);
890 if (nInfo & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL)
892 errno = EOVERFLOW;
893 return 0;
896 /* ensure trailing '\0' */
897 unic_text[nDestBytes] = '\0';
898 return nDestBytes;
901 oslFileError osl::detail::convertUrlToPathname(OUString const & url, OString * pathname) {
902 assert(pathname != nullptr);
903 oslFileError e;
904 try {
905 e = getSystemPathFromFileUrl(url, pathname, true);
906 } catch (std::length_error &) {
907 e = osl_File_E_RANGE;
909 if (e == osl_File_E_None && !pathname->startsWith("/")) {
910 e = osl_File_E_INVAL;
912 return e;
915 oslFileError osl::detail::convertPathnameToUrl(OString const & pathname, OUString * url) {
916 assert(url != nullptr);
917 OUStringBuffer buf(10+pathname.getLength());
918 buf.append("file:");
919 if (pathname.startsWith("/")) {
920 buf.append("//");
921 // so if pathname should ever start with "//" that isn't mistaken for an authority
922 // component
924 for (sal_Size convert = pathname.getLength();;) {
925 auto n = std::max(convert, sal_Size(PATH_MAX)); // approximation of required converted size
926 OUStringBuffer ubuf(static_cast<int>(n));
927 auto s = ubuf.appendUninitialized(n);
928 sal_uInt32 info;
929 sal_Size converted;
930 //TODO: context, for reliable treatment of DESTBUFFERTOSMALL:
931 n = TextToUnicodeConverter_Impl::getInstance().convert(
932 pathname.getStr() + pathname.getLength() - convert, convert, s, n,
933 (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
934 | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR | RTL_TEXTTOUNICODE_FLAGS_FLUSH),
935 &info, &converted);
936 ubuf.setLength(n);
937 buf.append(
938 rtl::Uri::encode(
939 ubuf.makeStringAndClear(), uriCharClass.data(), rtl_UriEncodeIgnoreEscapes,
940 RTL_TEXTENCODING_UTF8));
941 assert(converted <= convert);
942 convert -= converted;
943 if ((info & RTL_TEXTTOUNICODE_INFO_ERROR) != 0) {
944 assert(convert > 0);
945 //TODO: see writeEscapeOctet in sal/rtl/uri.cxx
946 buf.append("%");
947 unsigned char c = pathname[pathname.getLength() - convert];
948 assert(c >= 0x80);
949 static sal_Unicode const aHex[16]
950 = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
951 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 }; /* '0'--'9', 'A'--'F' */
952 buf.append(OUStringChar(aHex[c >> 4]) + OUStringChar(aHex[c & 15]));
953 --convert;
954 continue;
956 assert((convert == 0) == ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0));
957 if ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0) {
958 break;
961 *url = buf.makeStringAndClear();
962 return osl_File_E_None;
965 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */