summaryrefslogtreecommitdiffstats
path: root/sal/osl/unx/file_url.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sal/osl/unx/file_url.cxx')
-rw-r--r--sal/osl/unx/file_url.cxx986
1 files changed, 986 insertions, 0 deletions
diff --git a/sal/osl/unx/file_url.cxx b/sal/osl/unx/file_url.cxx
new file mode 100644
index 000000000..5e99d5306
--- /dev/null
+++ b/sal/osl/unx/file_url.cxx
@@ -0,0 +1,986 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include "file_url.hxx"
+
+#include "system.hxx"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <stdexcept>
+#include <string_view>
+#include <type_traits>
+#include <limits.h>
+#include <errno.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include <o3tl/safeint.hxx>
+#include <osl/file.hxx>
+#include <osl/security.hxx>
+#include <osl/socket.h>
+#include <osl/diagnose.h>
+#include <osl/thread.h>
+#include <osl/process.h>
+
+#include <rtl/character.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/uri.h>
+#include <rtl/uri.hxx>
+#include <rtl/ustring.hxx>
+#include <rtl/ustrbuf.h>
+#include <rtl/ustrbuf.hxx>
+#include <rtl/textcvt.h>
+#include <sal/log.hxx>
+
+#include <uri_internal.hxx>
+
+#include "file_error_transl.hxx"
+#include "file_path_helper.hxx"
+
+#include "uunxapi.hxx"
+
+/** @file
+
+ General note
+
+ This file contains the part that handles File URLs.
+
+ File URLs as scheme specific notion of URIs
+ (RFC2396) may be handled platform independent, but
+ will not in osl which is considered wrong.
+ Future version of osl should handle File URLs this
+ way. In rtl/uri there is already a URI parser etc.
+ so this code should be consolidated.
+
+*/
+
+using namespace osl;
+
+namespace {
+
+// A slightly modified version of Pchar in rtl/source/uri.c, but without
+// encoding slashes:
+const sal_Bool uriCharClass[128] = {
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, false, false, false, false, false, false, false,
+ false, true, false, false, true, false, true, true, // !"#$%&'
+ true, true, true, true, true, true, true, true, // ()*+,-./
+ true, true, true, true, true, true, true, true, // 01234567
+ true, true, true, false, false, true, false, false, // 89:;<=>?
+ true, true, true, true, true, true, true, true, // @ABCDEFG
+ true, true, true, true, true, true, true, true, // HIJKLMNO
+ true, true, true, true, true, true, true, true, // PQRSTUVW
+ true, true, true, false, false, false, false, true, // XYZ[\]^_
+ false, true, true, true, true, true, true, true, // `abcdefg
+ true, true, true, true, true, true, true, true, // hijklmno
+ true, true, true, true, true, true, true, true, // pqrstuvw
+ true, true, true, false, false, false, true, false}; // xyz{|}~
+
+}
+
+oslFileError SAL_CALL osl_getCanonicalName( rtl_uString* ustrFileURL, rtl_uString** pustrValidURL )
+{
+ OSL_FAIL("osl_getCanonicalName not implemented");
+
+ rtl_uString_newFromString(pustrValidURL, ustrFileURL);
+ return osl_File_E_None;
+}
+
+namespace {
+
+ class UnicodeToTextConverter_Impl
+ {
+ rtl_UnicodeToTextConverter m_converter;
+
+ UnicodeToTextConverter_Impl()
+ : m_converter (rtl_createUnicodeToTextConverter (osl_getThreadTextEncoding()))
+ {}
+
+ ~UnicodeToTextConverter_Impl()
+ {
+ rtl_destroyUnicodeToTextConverter (m_converter);
+ }
+ public:
+ static UnicodeToTextConverter_Impl & getInstance()
+ {
+ static UnicodeToTextConverter_Impl g_theConverter;
+ return g_theConverter;
+ }
+
+ sal_Size convert(
+ sal_Unicode const * pSrcBuf, sal_Size nSrcChars, char * pDstBuf, sal_Size nDstBytes,
+ sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtChars)
+ {
+ OSL_ASSERT(m_converter != nullptr);
+ return rtl_convertUnicodeToText (
+ m_converter, nullptr, pSrcBuf, nSrcChars, pDstBuf, nDstBytes, nFlags, pInfo, pSrcCvtChars);
+ }
+ };
+
+bool convert(OUStringBuffer const & in, OStringBuffer * append) {
+ assert(append != nullptr);
+ for (sal_Size nConvert = in.getLength();;) {
+ auto const oldLen = append->getLength();
+ auto n = std::min(
+ std::max(nConvert, sal_Size(PATH_MAX)),
+ sal_Size(std::numeric_limits<sal_Int32>::max() - oldLen));
+ // approximation of required converted size
+ auto s = append->appendUninitialized(n);
+ sal_uInt32 info;
+ sal_Size converted;
+ //TODO: context, for reliable treatment of DESTBUFFERTOSMALL:
+ n = UnicodeToTextConverter_Impl::getInstance().convert(
+ in.getStr() + in.getLength() - nConvert, nConvert, s, n,
+ (RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR
+ | RTL_UNICODETOTEXT_FLAGS_FLUSH),
+ &info, &converted);
+ if ((info & RTL_UNICODETOTEXT_INFO_ERROR) != 0) {
+ return false;
+ }
+ append->setLength(oldLen + n);
+ assert(converted <= nConvert);
+ nConvert -= converted;
+ assert((nConvert == 0) == ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0));
+ if ((info & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL) == 0) {
+ break;
+ }
+ }
+ return true;
+}
+
+bool decodeFromUtf8(std::u16string_view text, OString * result) {
+ assert(result != nullptr);
+ auto p = text.data();
+ auto const end = p + text.size();
+ OUStringBuffer ubuf(static_cast<int>(text.size()));
+ OStringBuffer bbuf(PATH_MAX);
+ while (p < end) {
+ rtl::uri::detail::EscapeType t;
+ sal_uInt32 c = rtl::uri::detail::readUcs4(&p, end, true, RTL_TEXTENCODING_UTF8, &t);
+ switch (t) {
+ case rtl::uri::detail::EscapeNo:
+ if (c == '%') {
+ return false;
+ }
+ [[fallthrough]];
+ case rtl::uri::detail::EscapeChar:
+ if (rtl::isSurrogate(c)) {
+ return false;
+ }
+ ubuf.appendUtf32(c);
+ break;
+ case rtl::uri::detail::EscapeOctet:
+ if (!convert(ubuf, &bbuf)) {
+ return false;
+ }
+ ubuf.setLength(0);
+ assert(c <= 0xFF);
+ bbuf.append(char(c));
+ break;
+ }
+ }
+ if (!convert(ubuf, &bbuf)) {
+ return false;
+ }
+ *result = bbuf.makeStringAndClear();
+ return true;
+}
+
+template<typename T> oslFileError getSystemPathFromFileUrl(
+ OUString const & url, T * path, bool resolveHome)
+{
+ assert(path != nullptr);
+ // For compatibility with assumptions in other parts of the code base,
+ // assume that anything starting with a slash is a system path instead of a
+ // (relative) file URL (except if it starts with two slashes, in which case
+ // it is a relative URL with an authority component):
+ if (url.isEmpty()
+ || (url[0] == '/' && (url.getLength() == 1 || url[1] != '/')))
+ {
+ return osl_File_E_INVAL;
+ }
+ // Check for non file scheme:
+ sal_Int32 i = 0;
+ if (rtl::isAsciiAlpha(url[0])) {
+ for (sal_Int32 j = 1; j != url.getLength(); ++j) {
+ auto c = url[j];
+ if (c == ':') {
+ if (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
+ url.pData->buffer, j,
+ RTL_CONSTASCII_STRINGPARAM("file"))
+ != 0)
+ {
+ return osl_File_E_INVAL;
+ }
+ i = j + 1;
+ break;
+ }
+ if (!rtl::isAsciiAlphanumeric(c) && c != '+' && c != '-'
+ && c != '.')
+ {
+ break;
+ }
+ }
+ }
+ // Handle query or fragment:
+ if (url.indexOf('?', i) != -1 || url.indexOf('#', i) != -1)
+ return osl_File_E_INVAL;
+ // Handle authority, supporting a host of "localhost", "127.0.0.1", or the exact value (e.g.,
+ // not supporting an additional final dot, for simplicity) reported by osl_getLocalHostname
+ // (and, in each case, ignoring case of ASCII letters):
+ if (url.getLength() - i >= 2 && url[i] == '/' && url[i + 1] == '/')
+ {
+ i += 2;
+ sal_Int32 j = url.indexOf('/', i);
+ if (j == -1)
+ j = url.getLength();
+ if (j != i
+ && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
+ url.pData->buffer + i, j - i,
+ RTL_CONSTASCII_STRINGPARAM("localhost"))
+ != 0)
+ && (rtl_ustr_ascii_compareIgnoreAsciiCase_WithLengths(
+ url.pData->buffer + i, j - i,
+ RTL_CONSTASCII_STRINGPARAM("127.0.0.1"))
+ != 0))
+ {
+ OUString hostname;
+ if (osl_getLocalHostname(&hostname.pData) != osl_Socket_Ok
+ || (rtl_ustr_compareIgnoreAsciiCase_WithLength(
+ url.pData->buffer + i, j - i, hostname.getStr(), hostname.getLength())
+ != 0))
+ {
+ return osl_File_E_INVAL;
+ }
+ }
+ i = j;
+ }
+ // Handle empty path:
+ if (i == url.getLength())
+ {
+ *path = "/";
+ return osl_File_E_None;
+ }
+ // Path must not contain %2F:
+ if (url.indexOf("%2F", i) != -1 || url.indexOf("%2f", i) != -1)
+ return osl_File_E_INVAL;
+
+ if constexpr (std::is_same_v<T, rtl::OString>) {
+ if (!decodeFromUtf8(std::u16string_view(url).substr(i), path)) {
+ return osl_File_E_INVAL;
+ }
+ } else if constexpr (std::is_same_v<T, rtl::OUString>) {
+ *path = rtl::Uri::decode(
+ url.copy(i), rtl_UriDecodeWithCharset, RTL_TEXTENCODING_UTF8);
+ } else {
+ static_assert(std::is_same_v<T, rtl::OString> || std::is_same_v<T, rtl::OUString>);
+ }
+ // Path must not contain %2F:
+ if (path->indexOf('\0') != -1)
+ return osl_File_E_INVAL;
+
+ // Handle ~ notation:
+ if (resolveHome && path->getLength() >= 2 && (*path)[1] == '~')
+ {
+ sal_Int32 j = path->indexOf('/', 2);
+ if (j == -1)
+ j = path->getLength();
+
+ if (j == 2)
+ {
+ OUString home;
+ if (!osl::Security().getHomeDir(home))
+ {
+ SAL_WARN("sal.file", "osl::Security::getHomeDir failed");
+ return osl_File_E_INVAL;
+ }
+
+ i = url.indexOf('/', i + 1);
+
+ if (i == -1)
+ i = url.getLength();
+ else
+ ++i;
+
+ //TODO: cheesy way of ensuring home's path ends in slash:
+ if (!home.isEmpty() && home[home.getLength() - 1] != '/')
+ home += "/";
+ try
+ {
+ home = rtl::Uri::convertRelToAbs(home, url.copy(i));
+ }
+ catch (rtl::MalformedUriException & e)
+ {
+ SAL_WARN("sal.file", "rtl::MalformedUriException " << e.getMessage());
+ return osl_File_E_INVAL;
+ }
+ return getSystemPathFromFileUrl(home, path, false);
+ }
+ // FIXME: replace ~user with user's home directory
+ return osl_File_E_INVAL;
+ }
+ return osl_File_E_None;
+}
+
+}
+
+oslFileError SAL_CALL osl_getSystemPathFromFileURL( rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath )
+{
+ OUString path;
+ oslFileError e;
+ try
+ {
+ e = getSystemPathFromFileUrl(
+ OUString::unacquired(&ustrFileURL), &path, true);
+ }
+ catch (std::length_error &)
+ {
+ e = osl_File_E_RANGE;
+ }
+
+ if (e == osl_File_E_None)
+ rtl_uString_assign(pustrSystemPath, path.pData);
+
+ return e;
+}
+
+oslFileError SAL_CALL osl_getFileURLFromSystemPath( rtl_uString *ustrSystemPath, rtl_uString **pustrFileURL )
+{
+ static const sal_Unicode pDoubleSlash[2] = { '/', '/' };
+
+ rtl_uString *pTmp = nullptr;
+ sal_Int32 nIndex;
+
+ if( ustrSystemPath->length == 0 )
+ return osl_File_E_INVAL;
+
+ /* temporary hack: if already file url, return ustrSystemPath */
+
+ if( rtl_ustr_ascii_shortenedCompare_WithLength( ustrSystemPath->buffer, ustrSystemPath->length,"file:", 5 ) == 0 )
+ return osl_File_E_INVAL;
+
+ /* check if system path starts with ~ or ~user and replace it with the appropriate home dir */
+ if( ustrSystemPath->buffer[0] == '~' )
+ {
+ /* check if another user is specified */
+ if( ( ustrSystemPath->length == 1 ) ||
+ ( ustrSystemPath->buffer[1] == '/' ) )
+ {
+ /* osl_getHomeDir returns file URL */
+ oslSecurity pSecurity = osl_getCurrentSecurity();
+ osl_getHomeDir( pSecurity , &pTmp );
+ osl_freeSecurityHandle( pSecurity );
+
+ if (!pTmp)
+ return osl_File_E_INVAL;
+
+ /* remove "file://" prefix */
+ rtl_uString_newFromStr_WithLength( &pTmp, pTmp->buffer + 7, pTmp->length - 7 );
+
+ /* replace '~' in original string */
+ rtl_uString_newReplaceStrAt( &pTmp, ustrSystemPath, 0, 1, pTmp );
+ }
+ else
+ {
+ /* FIXME: replace ~user with users home directory */
+ return osl_File_E_INVAL;
+ }
+ }
+
+ /* check if initial string contains repeated '/' characters */
+ nIndex = rtl_ustr_indexOfStr_WithLength( ustrSystemPath->buffer, ustrSystemPath->length, pDoubleSlash, 2 );
+ if( nIndex != -1 )
+ {
+ sal_Int32 nSrcIndex;
+ sal_Int32 nDeleted = 0;
+
+ /* if pTmp is not already allocated, copy ustrSystemPath for modification */
+ if( pTmp == nullptr )
+ rtl_uString_newFromString( &pTmp, ustrSystemPath );
+
+ /* adapt index to pTmp */
+ nIndex += pTmp->length - ustrSystemPath->length;
+
+ /* replace repeated '/' characters with a single '/' */
+ for( nSrcIndex = nIndex + 1; nSrcIndex < pTmp->length; nSrcIndex++ )
+ {
+ if( (pTmp->buffer[nSrcIndex] == '/') && (pTmp->buffer[nIndex] == '/') )
+ nDeleted++;
+ else
+ pTmp->buffer[++nIndex] = pTmp->buffer[nSrcIndex];
+ }
+
+ /* adjust length member */
+ pTmp->length -= nDeleted;
+ }
+
+ if( pTmp == nullptr )
+ rtl_uString_assign( &pTmp, ustrSystemPath );
+
+ /* file URLs must be URI encoded */
+ rtl_uriEncode( pTmp, uriCharClass, rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8, pustrFileURL );
+
+ rtl_uString_release( pTmp );
+
+ /* absolute urls should start with 'file://' */
+ if( (*pustrFileURL)->buffer[0] == '/' )
+ {
+ rtl_uString *pProtocol = nullptr;
+
+ rtl_uString_newFromAscii( &pProtocol, "file://" );
+ rtl_uString_newConcat( pustrFileURL, pProtocol, *pustrFileURL );
+ rtl_uString_release( pProtocol );
+ }
+
+ return osl_File_E_None;
+}
+
+/*
+ * relative URLs are not accepted
+ */
+oslFileError osl_getSystemPathFromFileURL_Ex(
+ rtl_uString *ustrFileURL, rtl_uString **pustrSystemPath)
+{
+ rtl_uString* temp = nullptr;
+ oslFileError osl_error = osl_getSystemPathFromFileURL(ustrFileURL, &temp);
+
+ if (osl_error == osl_File_E_None)
+ {
+ if (temp->buffer[0] == '/')
+ {
+ *pustrSystemPath = temp;
+ }
+ else
+ {
+ rtl_uString_release(temp);
+ osl_error = osl_File_E_INVAL;
+ }
+ }
+
+ return osl_error;
+}
+
+namespace
+{
+
+ /** Helper function, return a pointer to the final '\0'
+ of a string
+ */
+
+ sal_Unicode* ustrtoend(sal_Unicode* pStr)
+ {
+ return (pStr + rtl_ustr_getLength(pStr));
+ }
+
+ sal_Unicode* ustrchrcat(const sal_Unicode chr, sal_Unicode* d)
+ {
+ sal_Unicode* p = ustrtoend(d);
+ *p++ = chr;
+ *p = 0;
+ return d;
+ }
+
+ bool _islastchr(sal_Unicode* pStr, sal_Unicode Chr)
+ {
+ sal_Unicode* p = ustrtoend(pStr);
+ if (p > pStr)
+ p--;
+ return (*p == Chr);
+ }
+
+ /**
+ Remove the last part of a path, a path that has
+ only a '/' or no '/' at all will be returned
+ unmodified
+ */
+
+ sal_Unicode* _rmlastpathtoken(sal_Unicode* aPath)
+ {
+ /* we may always skip -2 because we
+ may at least stand on a '/' but
+ either there is no other character
+ before this '/' or it's another
+ character than the '/'
+ */
+ sal_Unicode* p = ustrtoend(aPath) - 2;
+
+ /* move back to the next path separator
+ or to the start of the string */
+ while ((p > aPath) && (*p != '/'))
+ p--;
+
+ if (p >= aPath)
+ {
+ if (*p == '/')
+ {
+ p++;
+ *p = '\0';
+ }
+ else
+ {
+ *p = '\0';
+ }
+ }
+
+ return aPath;
+ }
+
+ oslFileError _osl_resolvepath(
+ /*inout*/ sal_Unicode* path,
+ /*inout*/ bool* failed)
+ {
+ oslFileError ferr = osl_File_E_None;
+
+ if (!*failed)
+ {
+ char unresolved_path[PATH_MAX];
+ if (!UnicodeToText(unresolved_path, sizeof(unresolved_path), path, rtl_ustr_getLength(path)))
+ return oslTranslateFileError(ENAMETOOLONG);
+
+ char resolved_path[PATH_MAX];
+ if (realpath(unresolved_path, resolved_path))
+ {
+ if (!TextToUnicode(resolved_path, strlen(resolved_path), path, PATH_MAX))
+ return oslTranslateFileError(ENAMETOOLONG);
+ }
+ else
+ {
+ if (EACCES == errno || ENOTDIR == errno || ENOENT == errno)
+ *failed = true;
+ else
+ ferr = oslTranslateFileError(errno);
+ }
+ }
+
+ return ferr;
+ }
+
+ /**
+ Works even with non existing paths. The resulting path must not exceed
+ PATH_MAX else osl_File_E_NAMETOOLONG is the result
+ */
+
+ oslFileError osl_getAbsoluteFileURL_impl_(const OUString& unresolved_path, OUString& resolved_path)
+ {
+ /* the given unresolved path must not exceed PATH_MAX */
+ if (unresolved_path.getLength() >= (PATH_MAX - 2))
+ return oslTranslateFileError(ENAMETOOLONG);
+
+ sal_Unicode path_resolved_so_far[PATH_MAX];
+ const sal_Unicode* punresolved = unresolved_path.getStr();
+ sal_Unicode* presolvedsf = path_resolved_so_far;
+
+ /* reserve space for leading '/' and trailing '\0'
+ do not exceed this limit */
+ sal_Unicode* sentinel = path_resolved_so_far + PATH_MAX - 2;
+
+ /* if realpath fails with error ENOTDIR, EACCES or ENOENT
+ we will not call it again, because _osl_realpath should also
+ work with non existing directories etc. */
+ bool realpath_failed = false;
+ oslFileError ferr;
+
+ path_resolved_so_far[0] = '\0';
+
+ while (*punresolved != '\0')
+ {
+ /* ignore '/.' , skip one part back when '/..' */
+ if ((*punresolved == '.') && (*presolvedsf == '/'))
+ {
+ if (*(punresolved + 1) == '\0')
+ {
+ punresolved++;
+ continue;
+ }
+ if (*(punresolved + 1) == '/')
+ {
+ punresolved += 2;
+ continue;
+ }
+ if ((*(punresolved + 1) == '.') && (*(punresolved + 2) == '\0' || (*(punresolved + 2) == '/')))
+ {
+ _rmlastpathtoken(path_resolved_so_far);
+
+ presolvedsf = ustrtoend(path_resolved_so_far) - 1;
+
+ if (*(punresolved + 2) == '/')
+ punresolved += 3;
+ else
+ punresolved += 2;
+
+ continue;
+ }
+
+ /* a file or directory name may start with '.' */
+ if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
+ return oslTranslateFileError(ENAMETOOLONG);
+
+ ustrchrcat(*punresolved++, path_resolved_so_far);
+
+ if (*punresolved == '\0' && !realpath_failed)
+ {
+ ferr = _osl_resolvepath(
+ path_resolved_so_far,
+ &realpath_failed);
+
+ if (ferr != osl_File_E_None)
+ return ferr;
+ }
+ }
+ else if (*punresolved == '/')
+ {
+ if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
+ return oslTranslateFileError(ENAMETOOLONG);
+
+ ustrchrcat(*punresolved++, path_resolved_so_far);
+
+ if (!realpath_failed)
+ {
+ ferr = _osl_resolvepath(
+ path_resolved_so_far,
+ &realpath_failed);
+
+ if (ferr != osl_File_E_None)
+ return ferr;
+
+ if (!_islastchr(path_resolved_so_far, '/'))
+ {
+ if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
+ return oslTranslateFileError(ENAMETOOLONG);
+
+ ustrchrcat('/', path_resolved_so_far);
+ }
+ }
+ }
+ else // any other character
+ {
+ if ((presolvedsf = ustrtoend(path_resolved_so_far)) > sentinel)
+ return oslTranslateFileError(ENAMETOOLONG);
+
+ ustrchrcat(*punresolved++, path_resolved_so_far);
+
+ if (*punresolved == '\0' && !realpath_failed)
+ {
+ ferr = _osl_resolvepath(
+ path_resolved_so_far,
+ &realpath_failed);
+
+ if (ferr != osl_File_E_None)
+ return ferr;
+ }
+ }
+ }
+
+ sal_Int32 len = rtl_ustr_getLength(path_resolved_so_far);
+
+ OSL_ASSERT(len < PATH_MAX);
+
+ resolved_path = OUString(path_resolved_so_far, len);
+
+ return osl_File_E_None;
+ }
+
+}
+
+oslFileError osl_getAbsoluteFileURL(
+ rtl_uString* ustrBaseDirURL,
+ rtl_uString* ustrRelativeURL,
+ rtl_uString** pustrAbsoluteURL)
+{
+ /* Work around the below call to getSystemPathFromFileURL rejecting input
+ that starts with "/" (for whatever reason it behaves that way; but
+ changing that would start to break lots of tests at least) */
+ OUString relUrl(ustrRelativeURL);
+ if (relUrl.startsWith("//"))
+ relUrl = "file:" + relUrl;
+ else if (relUrl.startsWith("/"))
+ relUrl = "file://" + relUrl;
+
+ OUString unresolved_path;
+
+ FileBase::RC frc = FileBase::getSystemPathFromFileURL(relUrl, unresolved_path);
+ if (frc != FileBase::E_None)
+ return oslFileError(frc);
+
+ if (systemPathIsRelativePath(unresolved_path))
+ {
+ OUString base_path;
+ oslFileError rc = osl_getSystemPathFromFileURL_Ex(ustrBaseDirURL, &base_path.pData);
+ if (rc != osl_File_E_None)
+ return rc;
+
+ unresolved_path = systemPathMakeAbsolutePath(base_path, unresolved_path);
+ }
+
+ OUString resolved_path;
+ oslFileError rc = osl_getAbsoluteFileURL_impl_(unresolved_path, resolved_path);
+ if (rc == osl_File_E_None)
+ {
+ rc = osl_getFileURLFromSystemPath(resolved_path.pData, pustrAbsoluteURL);
+ OSL_ASSERT(osl_File_E_None == rc);
+ }
+
+ return rc;
+}
+
+namespace osl::detail {
+
+ /**
+ No separate error code if unicode to text conversion or getenv fails because for the
+ caller there is no difference why a file could not be found in $PATH
+ */
+ bool find_in_PATH(const OUString& file_path, OUString& result)
+ {
+ bool bfound = false;
+ OUString path("PATH");
+ OUString env_path;
+
+ if (osl_getEnvironment(path.pData, &env_path.pData) == osl_Process_E_None)
+ bfound = osl::searchPath(file_path, env_path, result);
+
+ return bfound;
+ }
+}
+
+namespace
+{
+ /**
+ No separate error code if unicode to text conversion or getcwd fails because for the
+ caller there is no difference why a file could not be found in CDW
+ */
+ bool find_in_CWD(const OUString& file_path, OUString& result)
+ {
+ bool bfound = false;
+ OUString cwd_url;
+
+ if (osl_getProcessWorkingDir(&cwd_url.pData) == osl_Process_E_None)
+ {
+ OUString cwd;
+ FileBase::getSystemPathFromFileURL(cwd_url, cwd);
+ bfound = osl::searchPath(file_path, cwd, result);
+ }
+ return bfound;
+ }
+
+ bool find_in_searchPath(const OUString& file_path, rtl_uString* search_path, OUString& result)
+ {
+ return (search_path && osl::searchPath(file_path, OUString(search_path), result));
+ }
+
+}
+
+oslFileError osl_searchFileURL(rtl_uString* ustrFilePath, rtl_uString* ustrSearchPath, rtl_uString** pustrURL)
+{
+ OSL_PRECOND(ustrFilePath && pustrURL, "osl_searchFileURL: invalid parameter");
+
+ FileBase::RC rc;
+ OUString file_path;
+
+ // try to interpret search path as file url else assume it's a system path list
+ rc = FileBase::getSystemPathFromFileURL(ustrFilePath, file_path);
+ if (rc == FileBase::E_INVAL)
+ file_path = ustrFilePath;
+ else if (rc != FileBase::E_None)
+ return oslFileError(rc);
+
+ bool bfound = false;
+ OUString result;
+
+ if (find_in_searchPath(file_path, ustrSearchPath, result) ||
+ osl::detail::find_in_PATH(file_path, result) ||
+ find_in_CWD(file_path, result))
+ {
+ OUString resolved;
+
+ if (osl::realpath(result, resolved))
+ {
+ oslFileError osl_error = osl_getFileURLFromSystemPath(resolved.pData, pustrURL);
+ SAL_WARN_IF(osl_File_E_None != osl_error, "sal.file", "osl_getFileURLFromSystemPath failed");
+ bfound = true;
+ }
+ }
+ return bfound ? osl_File_E_None : osl_File_E_NOENT;
+}
+
+oslFileError FileURLToPath(char * buffer, size_t bufLen, rtl_uString* ustrFileURL)
+{
+ OString strSystemPath;
+ oslFileError osl_error = osl::detail::convertUrlToPathname(
+ OUString::unacquired(&ustrFileURL), &strSystemPath);
+
+ if(osl_error != osl_File_E_None)
+ return osl_error;
+
+ osl_systemPathRemoveSeparator(strSystemPath.pData);
+
+ if (o3tl::make_unsigned(strSystemPath.getLength()) >= bufLen) {
+ return osl_File_E_OVERFLOW;
+ }
+ std::strcpy(buffer, strSystemPath.getStr());
+
+ return osl_error;
+}
+
+int UnicodeToText( char * buffer, size_t bufLen, const sal_Unicode * uniText, sal_Int32 uniTextLen )
+{
+ sal_uInt32 nInfo = 0;
+ sal_Size nSrcChars = 0;
+
+ sal_Size nDestBytes = UnicodeToTextConverter_Impl::getInstance().convert (
+ uniText, uniTextLen, buffer, bufLen,
+ OUSTRING_TO_OSTRING_CVTFLAGS | RTL_UNICODETOTEXT_FLAGS_FLUSH, &nInfo, &nSrcChars);
+
+ if( nInfo & RTL_UNICODETOTEXT_INFO_DESTBUFFERTOSMALL )
+ {
+ errno = EOVERFLOW;
+ return 0;
+ }
+
+ /* ensure trailing '\0' */
+ buffer[nDestBytes] = '\0';
+ return nDestBytes;
+}
+
+namespace
+{
+ class TextToUnicodeConverter_Impl
+ {
+ rtl_TextToUnicodeConverter m_converter;
+
+ TextToUnicodeConverter_Impl()
+ : m_converter (rtl_createTextToUnicodeConverter (osl_getThreadTextEncoding()))
+ {}
+
+ ~TextToUnicodeConverter_Impl()
+ {
+ rtl_destroyTextToUnicodeConverter (m_converter);
+ }
+
+ public:
+ static TextToUnicodeConverter_Impl & getInstance()
+ {
+ static TextToUnicodeConverter_Impl g_theConverter;
+ return g_theConverter;
+ }
+
+ sal_Size convert(
+ char const * pSrcBuf, sal_Size nSrcBytes, sal_Unicode * pDstBuf, sal_Size nDstChars,
+ sal_uInt32 nFlags, sal_uInt32 * pInfo, sal_Size * pSrcCvtBytes)
+ {
+ OSL_ASSERT(m_converter != nullptr);
+ return rtl_convertTextToUnicode (
+ m_converter, nullptr, pSrcBuf, nSrcBytes, pDstBuf, nDstChars, nFlags, pInfo, pSrcCvtBytes);
+ }
+ };
+}
+
+int TextToUnicode(
+ const char* text,
+ size_t text_buffer_size,
+ sal_Unicode* unic_text,
+ sal_Int32 unic_text_buffer_size)
+{
+ sal_uInt32 nInfo = 0;
+ sal_Size nSrcChars = 0;
+
+ sal_Size nDestBytes = TextToUnicodeConverter_Impl::getInstance().convert(
+ text, text_buffer_size, unic_text, unic_text_buffer_size,
+ OSTRING_TO_OUSTRING_CVTFLAGS | RTL_TEXTTOUNICODE_FLAGS_FLUSH, &nInfo, &nSrcChars);
+
+ if (nInfo & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL)
+ {
+ errno = EOVERFLOW;
+ return 0;
+ }
+
+ /* ensure trailing '\0' */
+ unic_text[nDestBytes] = '\0';
+ return nDestBytes;
+}
+
+oslFileError osl::detail::convertUrlToPathname(OUString const & url, OString * pathname) {
+ assert(pathname != nullptr);
+ oslFileError e;
+ try {
+ e = getSystemPathFromFileUrl(url, pathname, true);
+ } catch (std::length_error &) {
+ e = osl_File_E_RANGE;
+ }
+ if (e == osl_File_E_None && !pathname->startsWith("/")) {
+ e = osl_File_E_INVAL;
+ }
+ return e;
+}
+
+oslFileError osl::detail::convertPathnameToUrl(OString const & pathname, OUString * url) {
+ assert(url != nullptr);
+ OUStringBuffer buf(10+pathname.getLength());
+ buf.append("file:");
+ if (pathname.startsWith("/")) {
+ buf.append("//");
+ // so if pathname should ever start with "//" that isn't mistaken for an authority
+ // component
+ }
+ for (sal_Size convert = pathname.getLength();;) {
+ auto n = std::max(convert, sal_Size(PATH_MAX)); // approximation of required converted size
+ OUStringBuffer ubuf(static_cast<int>(n));
+ auto s = ubuf.appendUninitialized(n);
+ sal_uInt32 info;
+ sal_Size converted;
+ //TODO: context, for reliable treatment of DESTBUFFERTOSMALL:
+ n = TextToUnicodeConverter_Impl::getInstance().convert(
+ pathname.getStr() + pathname.getLength() - convert, convert, s, n,
+ (RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR | RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR
+ | RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR | RTL_TEXTTOUNICODE_FLAGS_FLUSH),
+ &info, &converted);
+ ubuf.setLength(n);
+ buf.append(
+ rtl::Uri::encode(
+ ubuf.makeStringAndClear(), uriCharClass, rtl_UriEncodeIgnoreEscapes,
+ RTL_TEXTENCODING_UTF8));
+ assert(converted <= convert);
+ convert -= converted;
+ if ((info & RTL_TEXTTOUNICODE_INFO_ERROR) != 0) {
+ assert(convert > 0);
+ //TODO: see writeEscapeOctet in sal/rtl/uri.cxx
+ buf.append("%");
+ unsigned char c = pathname[pathname.getLength() - convert];
+ assert(c >= 0x80);
+ static sal_Unicode const aHex[16]
+ = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46 }; /* '0'--'9', 'A'--'F' */
+ buf.append(aHex[c >> 4]);
+ buf.append(aHex[c & 15]);
+ --convert;
+ continue;
+ }
+ assert((convert == 0) == ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0));
+ if ((info & RTL_TEXTTOUNICODE_INFO_DESTBUFFERTOOSMALL) == 0) {
+ break;
+ }
+ }
+ *url = buf.makeStringAndClear();
+ return osl_File_E_None;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */