/* -*- 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 <sal/config.h>
#include <sal/log.hxx>
 
#include <algorithm>
 
#include <systools/win32/uwinapi.h>
 
#include "file_url.hxx"
#include "file_error.hxx"
 
#include <rtl/alloc.h>
#include <rtl/ustring.hxx>
#include <osl/mutex.h>
#include <o3tl/char16_t2wchar_t.hxx>
 
#include "path_helper.hxx"
 
#define WSTR_SYSTEM_ROOT_PATH               L"\\\\.\\"
#define WSTR_LONG_PATH_PREFIX               L"\\\\?\\"
#define WSTR_LONG_PATH_PREFIX_UNC           L"\\\\?\\UNC\\"
 
// FileURL functions
 
oslMutex g_CurrentDirectoryMutex = nullptr; /* Initialized in dllentry.c */
 
static bool IsValidFilePathComponent(
    sal_Unicode const * lpComponent, sal_Unicode const **lppComponentEnd,
    DWORD dwFlags)
{
        sal_Unicode const * lpComponentEnd = nullptr;
        sal_Unicode const * lpCurrent = lpComponent;
        bool    bValid = true;  /* Assume success */
        sal_Unicode cLast = 0;
 
        /* Path component length must not exceed MAX_PATH even if long path with "\\?\" prefix is used */
 
        while ( !lpComponentEnd && lpCurrent && lpCurrent - lpComponent < MAX_PATH )
        {
            switch ( *lpCurrent )
            {
                /* Both backslash and slash determine the end of a path component */
            case '\0':
            case '/':
            case '\\':
                switch ( cLast )
                {
                    /* Component must not end with '.' or blank and can't be empty */
 
                case '.':
                    if ( dwFlags & VALIDATEPATH_ALLOW_ELLIPSE )
                    {
                        if ( (dwFlags & VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD) ||
                             1 == lpCurrent - lpComponent )
                        {
                            /* Either do allow periods anywhere, or current directory */
                            lpComponentEnd = lpCurrent;
                            break;
                        }
                        else if ( 2 == lpCurrent - lpComponent && '.' == *lpComponent )
                        {
                            /* Parent directory is O.K. */
                            lpComponentEnd = lpCurrent;
                            break;
                        }
                    }
                    SAL_FALLTHROUGH;
                case 0:
                case ' ':
                    if ( dwFlags & VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD )
                        lpComponentEnd = lpCurrent;
                    else
                    {
                        lpComponentEnd = lpCurrent - 1;
                        bValid = false;
                    }
                    break;
                default:
                    lpComponentEnd = lpCurrent;
                    break;
                }
                break;
                /* The following characters are reserved */
            case '?':
            case '*':
            case '<':
            case '>':
            case '\"':
            case '|':
            case ':':
                lpComponentEnd = lpCurrent;
                bValid = false;
                break;
            default:
                /* Characters below ASCII 32 are not allowed */
                if ( *lpCurrent < ' ' )
                {
                    lpComponentEnd = lpCurrent;
                    bValid = false;
                }
                break;
            }
            cLast = *lpCurrent++;
        }
 
        /*  If we don't reached the end of the component the length of the component was to long
            ( See condition of while loop ) */
        if ( !lpComponentEnd )
        {
            bValid = false;
            lpComponentEnd = lpCurrent;
        }
 
        if ( bValid )
        {
            // Empty components are not allowed
            if ( lpComponentEnd - lpComponent < 1 )
                bValid = false;
 
            // If we reached the end of the string nullptr is returned
            else if ( !*lpComponentEnd )
                lpComponentEnd = nullptr;
 
        }
 
        if ( lppComponentEnd )
            *lppComponentEnd = lpComponentEnd;
 
        return bValid;
}
 
static sal_Int32 countInitialSeparators(sal_Unicode const * path) {
    sal_Unicode const * p = path;
    while (*p == '\\' || *p == '/') {
        ++p;
    }
    return p - path;
}
 
DWORD IsValidFilePath(rtl_uString *path, DWORD dwFlags, rtl_uString **corrected)
{
        sal_Unicode const * lpszPath = path->buffer;
        sal_Unicode const * lpComponent = lpszPath;
        bool    bValid = true;
        DWORD   dwPathType = PATHTYPE_ERROR;
        sal_Int32 nLength = rtl_uString_getLength( path );
 
        if ( dwFlags & VALIDATEPATH_ALLOW_RELATIVE )
            dwFlags |= VALIDATEPATH_ALLOW_ELLIPSE;
 
        if ( !lpszPath )
            bValid = false;
 
        DWORD   dwCandidatPathType = PATHTYPE_ERROR;
 
        if ( 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( path->buffer, nLength, o3tl::toU(WSTR_LONG_PATH_PREFIX_UNC), SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX_UNC) - 1, SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX_UNC) - 1 ) )
        {
            /* This is long path in UNC notation */
            lpComponent = lpszPath + SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX_UNC) - 1;
            dwCandidatPathType = PATHTYPE_ABSOLUTE_UNC | PATHTYPE_IS_LONGPATH;
        }
        else if ( 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( path->buffer, nLength, o3tl::toU(WSTR_LONG_PATH_PREFIX), SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1, SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1 ) )
        {
            /* This is long path */
            lpComponent = lpszPath + SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1;
 
            if ( iswalpha( lpComponent[0] ) && ':' == lpComponent[1] )
            {
                lpComponent += 2;
                dwCandidatPathType = PATHTYPE_ABSOLUTE_LOCAL | PATHTYPE_IS_LONGPATH;
            }
        }
        else if ( 2 == countInitialSeparators( lpszPath ) )
        {
            /* The UNC path notation */
            lpComponent = lpszPath + 2;
            dwCandidatPathType = PATHTYPE_ABSOLUTE_UNC;
        }
        else if ( iswalpha( lpszPath[0] ) && ':' == lpszPath[1] )
        {
            /* Local path verification. Must start with <drive>: */
            lpComponent = lpszPath + 2;
            dwCandidatPathType = PATHTYPE_ABSOLUTE_LOCAL;
        }
 
        if ( ( dwCandidatPathType & PATHTYPE_MASK_TYPE ) == PATHTYPE_ABSOLUTE_UNC )
        {
            bValid = IsValidFilePathComponent( lpComponent, &lpComponent, VALIDATEPATH_ALLOW_ELLIPSE );
 
            /* So far we have a valid servername. Now let's see if we also have a network resource */
 
            dwPathType = dwCandidatPathType;
 
            if ( bValid )
            {
                if ( lpComponent &&  !*++lpComponent )
                    lpComponent = nullptr;
 
                if ( !lpComponent )
                {
                    dwPathType |= PATHTYPE_IS_SERVER;
                }
                else
                {
                    /* Now test the network resource */
 
                    bValid = IsValidFilePathComponent( lpComponent, &lpComponent, 0 );
 
                    /* If we now reached the end of the path, everything is O.K. */
 
                    if ( bValid && (!lpComponent || !*++lpComponent ) )
                    {
                        lpComponent = nullptr;
                        dwPathType |= PATHTYPE_IS_VOLUME;
                    }
                }
            }
        }
        else if (  ( dwCandidatPathType & PATHTYPE_MASK_TYPE ) == PATHTYPE_ABSOLUTE_LOCAL )
        {
            if ( 1 == countInitialSeparators( lpComponent ) )
                lpComponent++;
            else if ( *lpComponent )
                bValid = false;
 
            dwPathType = dwCandidatPathType;
 
            /* Now we are behind the backslash or it was a simple drive without backslash */
 
            if ( bValid && !*lpComponent )
            {
                lpComponent = nullptr;
                dwPathType |= PATHTYPE_IS_VOLUME;
            }
        }
        else if ( dwFlags & VALIDATEPATH_ALLOW_RELATIVE )
        {
            /* Can be a relative path */
            lpComponent = lpszPath;
 
            /* Relative path can start with a backslash */
 
            if ( 1 == countInitialSeparators( lpComponent ) )
            {
                lpComponent++;
                if ( !*lpComponent )
                    lpComponent = nullptr;
            }
 
            dwPathType = PATHTYPE_RELATIVE;
        }
        else
        {
            /* Anything else is an error */
            bValid = false;
            lpComponent = lpszPath;
        }
 
        /* Now validate each component of the path */
        rtl_uString * lastCorrected = path;
        while ( bValid && lpComponent )
        {
            // Correct path by merging consecutive slashes:
            if (*lpComponent == '\\' && corrected != nullptr) {
                sal_Int32 i = lpComponent - lpszPath;
                rtl_uString_newReplaceStrAt(corrected, lastCorrected, i, 1, nullptr);
                    //TODO: handle out-of-memory
                lastCorrected = *corrected;
                lpszPath = (*corrected)->buffer;
                lpComponent = lpszPath + i;
            }
 
            bValid = IsValidFilePathComponent( lpComponent, &lpComponent, dwFlags | VALIDATEPATH_ALLOW_INVALID_SPACE_AND_PERIOD);
 
            if ( bValid && lpComponent )
            {
                lpComponent++;
 
                /* If the string behind the backslash is empty, we've done */
 
                if ( !*lpComponent )
                    lpComponent = nullptr;
            }
        }
 
        /* The path can be longer than MAX_PATH only in case it has the longpath prefix */
        if ( bValid && !( dwPathType &  PATHTYPE_IS_LONGPATH ) && rtl_ustr_getLength( lpszPath ) >= MAX_PATH )
        {
            bValid = false;
        }
 
        return bValid ? dwPathType : PATHTYPE_ERROR;
}
 
static sal_Int32 PathRemoveFileSpec(LPWSTR lpPath, LPWSTR lpFileName, sal_Int32 nFileBufLen )
{
    sal_Int32 nRemoved = 0;
 
    if ( nFileBufLen )
    {
        lpFileName[0] = 0;
        LPWSTR  lpLastBkSlash = wcsrchr( lpPath, '\\' );
        LPWSTR  lpLastSlash = wcsrchr( lpPath, '/' );
        LPWSTR  lpLastDelimiter = std::max(lpLastSlash, lpLastBkSlash);
 
        if ( lpLastDelimiter )
        {
                sal_Int32 nDelLen = wcslen( lpLastDelimiter );
                if ( 1 == nDelLen )
                {
                    if ( lpLastDelimiter > lpPath && *(lpLastDelimiter - 1) != ':' )
                    {
                        *lpLastDelimiter = 0;
                        *lpFileName = 0;
                        nRemoved = nDelLen;
                    }
                }
                else if ( nDelLen && nDelLen - 1 < nFileBufLen )
                {
                    wcscpy( lpFileName, lpLastDelimiter + 1 );
                    *(++lpLastDelimiter) = 0;
                    nRemoved = nDelLen - 1;
                }
        }
    }
 
    return nRemoved;
}
 
// Undocumented in SHELL32.DLL ordinal 32
static LPWSTR PathAddBackslash(LPWSTR lpPath, sal_uInt32 nBufLen)
{
    LPWSTR  lpEndPath = nullptr;
 
    if ( lpPath )
    {
            std::size_t nLen = wcslen(lpPath);
 
            if ( !nLen || ( lpPath[nLen-1] != '\\' && lpPath[nLen-1] != '/' && nLen < nBufLen - 1 ) )
            {
                lpEndPath = lpPath + nLen;
                *lpEndPath++ = '\\';
                *lpEndPath = 0;
            }
    }
    return lpEndPath;
}
 
// Same as GetLongPathName but also 95/NT4
static DWORD GetCaseCorrectPathNameEx(
    LPWSTR  lpszPath,   // path buffer to convert
    sal_uInt32 cchBuffer,      // size of path buffer
    DWORD   nSkipLevels,
    bool bCheckExistence )
{
        ::osl::LongPathBuffer< WCHAR > szFile( MAX_PATH + 1 );
        sal_Int32 nRemoved = PathRemoveFileSpec( lpszPath, szFile, MAX_PATH + 1 );
        sal_Int32 nLastStepRemoved = nRemoved;
        while ( nLastStepRemoved && szFile[0] == 0 )
        {
            // remove separators
            nLastStepRemoved = PathRemoveFileSpec( lpszPath, szFile, MAX_PATH + 1 );
            nRemoved += nLastStepRemoved;
        }
 
        if ( nRemoved )
        {
            bool bSkipThis = false;
 
            if ( 0 == wcscmp( szFile, L".." ) )
            {
                bSkipThis = true;
                nSkipLevels += 1;
            }
            else if ( 0 == wcscmp( szFile, L"." ) )
            {
                bSkipThis = true;
            }
            else if ( nSkipLevels )
            {
                bSkipThis = true;
                nSkipLevels--;
            }
            else
                bSkipThis = false;
 
            if ( !GetCaseCorrectPathNameEx( lpszPath, cchBuffer, nSkipLevels, bCheckExistence ) )
                return 0;
 
            PathAddBackslash( lpszPath, cchBuffer );
 
            /* Analyze parent if not only a trailing backslash was cutted but a real file spec */
            if ( !bSkipThis )
            {
                if ( bCheckExistence )
                {
                    ::osl::LongPathBuffer< WCHAR > aShortPath( MAX_LONG_PATH );
                    wcscpy( aShortPath, lpszPath );
                    wcscat( aShortPath, szFile );
 
                    WIN32_FIND_DATAW aFindFileData;
                    HANDLE  hFind = FindFirstFileW( aShortPath, &aFindFileData );
 
                    if ( IsValidHandle(hFind) )
                    {
                        wcscat( lpszPath, aFindFileData.cFileName[0] ? aFindFileData.cFileName : aFindFileData.cAlternateFileName );
 
                        FindClose( hFind );
                    }
                    else
                        lpszPath[0] = 0;
                }
                else
                {
                    /* add the segment name back */
                    wcscat( lpszPath, szFile );
                }
            }
        }
        else
        {
            /* File specification can't be removed therefore the short path is either a drive
               or a network share. If still levels to skip are left, the path specification
               tries to travel below the file system root */
            if ( nSkipLevels )
                    lpszPath[0] = 0;
            else
                _wcsupr( lpszPath );
        }
 
        return wcslen( lpszPath );
}
 
DWORD GetCaseCorrectPathName(
    LPCWSTR lpszShortPath,  // file name
    LPWSTR  lpszLongPath,   // path buffer
    sal_uInt32 cchBuffer,      // size of path buffer
    bool bCheckExistence
)
{
    /* Special handling for "\\.\" as system root */
    if ( lpszShortPath && 0 == wcscmp( lpszShortPath, WSTR_SYSTEM_ROOT_PATH ) )
    {
        if ( cchBuffer >= SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) )
        {
            wcscpy( lpszLongPath, WSTR_SYSTEM_ROOT_PATH );
            return SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1;
        }
        else
        {
            return SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1;
        }
    }
    else if ( lpszShortPath )
    {
        if ( wcslen( lpszShortPath ) <= cchBuffer )
        {
            wcscpy( lpszLongPath, lpszShortPath );
            return GetCaseCorrectPathNameEx( lpszLongPath, cchBuffer, 0, bCheckExistence );
        }
    }
 
    return 0;
}
 
static bool osl_decodeURL_( rtl_String* strUTF8, rtl_uString** pstrDecodedURL )
{
    sal_Char        *pBuffer;
    const sal_Char  *pSrcEnd;
    const sal_Char  *pSrc;
    sal_Char        *pDest;
    sal_Int32       nSrcLen;
    bool        bValidEncoded = true;   /* Assume success */
 
    /* The resulting decoded string length is shorter or equal to the source length */
 
    nSrcLen = rtl_string_getLength(strUTF8);
    pBuffer = static_cast<sal_Char*>(malloc((nSrcLen + 1) * sizeof(sal_Char)));
 
    pDest = pBuffer;
    pSrc = rtl_string_getStr(strUTF8);
    pSrcEnd = pSrc + nSrcLen;
 
    /* Now decode the URL what should result in an UTF8 string */
    while ( bValidEncoded && pSrc < pSrcEnd )
    {
        switch ( *pSrc )
        {
        case '%':
            {
                sal_Char    aToken[3];
                sal_Char    aChar;
 
                pSrc++;
                aToken[0] = *pSrc++;
                aToken[1] = *pSrc++;
                aToken[2] = 0;
 
                aChar = static_cast<sal_Char>(strtoul( aToken, nullptr, 16 ));
 
                /* The chars are path delimiters and must not be encoded */
 
                if ( 0 == aChar || '\\' == aChar || '/' == aChar || ':' == aChar )
                    bValidEncoded = false;
                else
                    *pDest++ = aChar;
            }
            break;
        case '\0':
        case '#':
        case '?':
            bValidEncoded = false;
            break;
        default:
            *pDest++ = *pSrc++;
            break;
        }
    }
 
    *pDest++ = 0;
 
    if ( bValidEncoded )
    {
        rtl_string2UString( pstrDecodedURL, pBuffer, rtl_str_getLength(pBuffer), RTL_TEXTENCODING_UTF8, OSTRING_TO_OUSTRING_CVTFLAGS );
        OSL_ASSERT(*pstrDecodedURL != nullptr);
    }
 
    free( pBuffer );
 
    return bValidEncoded;
}
 
static void osl_encodeURL_( rtl_uString *strURL, rtl_String **pstrEncodedURL )
{
    /* Encode non ascii characters within the URL */
 
    rtl_String      *strUTF8 = nullptr;
    sal_Char        *pszEncodedURL;
    const sal_Char  *pURLScan;
    sal_Char        *pURLDest;
    sal_Int32       nURLScanLen;
    sal_Int32       nURLScanCount;
 
    rtl_uString2String( &strUTF8, rtl_uString_getStr( strURL ), rtl_uString_getLength( strURL ), RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS );
 
    pszEncodedURL = static_cast<sal_Char*>(malloc( (rtl_string_getLength( strUTF8 ) * 3 + 1)  * sizeof(sal_Char) ));
 
    pURLDest = pszEncodedURL;
    pURLScan = rtl_string_getStr( strUTF8 );
    nURLScanLen = rtl_string_getLength( strUTF8 );
    nURLScanCount = 0;
 
    while ( nURLScanCount < nURLScanLen )
    {
        sal_Char cCurrent = *pURLScan;
        switch ( cCurrent )
        {
        default:
            if (!( ( cCurrent >= 'a' && cCurrent <= 'z' ) || ( cCurrent >= 'A' && cCurrent <= 'Z' ) || ( cCurrent >= '0' && cCurrent <= '9' ) ) )
            {
                sprintf( pURLDest, "%%%02X", static_cast<unsigned char>(cCurrent) );
                pURLDest += 3;
                break;
            }
            SAL_FALLTHROUGH;
        case '!':
        case '\'':
        case '(':
        case ')':
        case '*':
        case '-':
        case '.':
        case '_':
        case '~':
        case '$':
        case '&':
        case '+':
        case ',':
        case '=':
        case '@':
        case ':':
        case '/':
        case '\\':
        case '|':
            *pURLDest++ = cCurrent;
            break;
        case 0:
            break;
        }
 
        pURLScan++;
        nURLScanCount++;
    }
 
    *pURLDest = 0;
 
    rtl_string_release( strUTF8 );
    rtl_string_newFromStr( pstrEncodedURL, pszEncodedURL );
    free( pszEncodedURL );
}
 
oslFileError osl_getSystemPathFromFileURL_( rtl_uString *strURL, rtl_uString **pustrPath, bool bAllowRelative )
{
    rtl_String          *strUTF8 = nullptr;
    rtl_uString         *strDecodedURL = nullptr;
    rtl_uString         *strTempPath = nullptr;
    sal_uInt32          nDecodedLen;
    bool            bValidEncoded;
    oslFileError        nError = osl_File_E_INVAL;  /* Assume failure */
 
    /*  If someone hasn't encoded the complete URL we convert it to UTF8 now to prevent from
        having a mixed encoded URL later */
 
    rtl_uString2String( &strUTF8, rtl_uString_getStr( strURL ), rtl_uString_getLength( strURL ), RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS );
 
    /* If the length of strUTF8 and strURL differs it indicates that the URL was not correct encoded */
 
    SAL_WARN_IF(
        strUTF8->length != strURL->length &&
        0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( strURL->buffer, strURL->length, "file:\\\\", 7 )
        , "sal.osl"
        ,"osl_getSystemPathFromFileURL: \"" << rtl::OUString(strURL) << "\" is not encoded !!!");
 
    bValidEncoded = osl_decodeURL_( strUTF8, &strDecodedURL );
 
    /* Release the encoded UTF8 string */
    rtl_string_release( strUTF8 );
 
    if ( bValidEncoded )
    {
        /* Replace backslashes and pipes */
 
        rtl_uString_newReplace( &strDecodedURL, strDecodedURL, '/', '\\' );
        rtl_uString_newReplace( &strDecodedURL, strDecodedURL, '|', ':' );
 
        const sal_Unicode *pDecodedURL = rtl_uString_getStr( strDecodedURL );
        nDecodedLen = rtl_uString_getLength( strDecodedURL );
 
        /* Must start with "file://" */
        if ( 0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\", 7 ) )
        {
            sal_uInt32  nSkip;
 
            if ( 0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\\\", 8 ) )
                nSkip = 8;
            else if (
                0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\localhost\\", 17 ) ||
                0 == rtl_ustr_ascii_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL, nDecodedLen, "file:\\\\127.0.0.1\\", 17 )
                      )
                nSkip = 17;
            else
                nSkip = 5;
 
            /* Indicates local root */
            if ( nDecodedLen == nSkip )
                rtl_uString_newFromStr_WithLength( &strTempPath, o3tl::toU(WSTR_SYSTEM_ROOT_PATH), SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1 );
            else
            {
                /* do not separate the directory and file case, so the maximal path length without prefix is MAX_PATH-12 */
                if ( nDecodedLen - nSkip <= MAX_PATH - 12 )
                {
                    rtl_uString_newFromStr_WithLength( &strTempPath, pDecodedURL + nSkip, nDecodedLen - nSkip );
                }
                else
                {
                    ::osl::LongPathBuffer< sal_Unicode > aBuf( MAX_LONG_PATH );
                    sal_uInt32 nNewLen = GetCaseCorrectPathName( o3tl::toW(pDecodedURL) + nSkip,
                                                                 o3tl::toW(aBuf),
                                                                 aBuf.getBufSizeInSymbols(),
                                                                 false );
 
                    if ( nNewLen <= MAX_PATH - 12
                      || 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL + nSkip, nDecodedLen - nSkip, o3tl::toU(WSTR_SYSTEM_ROOT_PATH), SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1, SAL_N_ELEMENTS(WSTR_SYSTEM_ROOT_PATH) - 1 )
                      || 0 == rtl_ustr_shortenedCompareIgnoreAsciiCase_WithLength( pDecodedURL + nSkip, nDecodedLen - nSkip, o3tl::toU(WSTR_LONG_PATH_PREFIX), SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1, SAL_N_ELEMENTS(WSTR_LONG_PATH_PREFIX) - 1 ) )
                    {
                        rtl_uString_newFromStr_WithLength( &strTempPath, aBuf, nNewLen );
                    }
                    else if ( pDecodedURL[nSkip] == '\\' && pDecodedURL[nSkip+1] == '\\' )
                    {
                        /* it should be an UNC path, use the according prefix */
                        rtl_uString *strSuffix = nullptr;
                        rtl_uString *strPrefix = nullptr;
                        rtl_uString_newFromStr_WithLength( &strPrefix, o3tl::toU(WSTR_LONG_PATH_PREFIX_UNC), SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX_UNC ) - 1 );
                        rtl_uString_newFromStr_WithLength( &strSuffix, aBuf + 2, nNewLen - 2 );
 
                        rtl_uString_newConcat( &strTempPath, strPrefix, strSuffix );
 
                        rtl_uString_release( strPrefix );
                        rtl_uString_release( strSuffix );
                    }
                    else
                    {
                        rtl_uString *strSuffix = nullptr;
                        rtl_uString *strPrefix = nullptr;
                        rtl_uString_newFromStr_WithLength( &strPrefix, o3tl::toU(WSTR_LONG_PATH_PREFIX), SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX ) - 1 );
                        rtl_uString_newFromStr_WithLength( &strSuffix, aBuf, nNewLen );
 
                        rtl_uString_newConcat( &strTempPath, strPrefix, strSuffix );
 
                        rtl_uString_release( strPrefix );
                        rtl_uString_release( strSuffix );
                    }
                }
            }
 
            if ( IsValidFilePath( strTempPath, VALIDATEPATH_ALLOW_ELLIPSE, &strTempPath ) )
                nError = osl_File_E_None;
        }
        else if ( bAllowRelative )  /* This maybe a relative file URL */
        {
            /* In future the relative path could be converted to absolute if it is too long */
            rtl_uString_assign( &strTempPath, strDecodedURL );
 
            if ( IsValidFilePath( strTempPath, VALIDATEPATH_ALLOW_RELATIVE | VALIDATEPATH_ALLOW_ELLIPSE, &strTempPath ) )
                nError = osl_File_E_None;
        }
        else
          SAL_INFO_IF(nError, "sal.osl",
              "osl_getSystemPathFromFileURL: \"" << rtl::OUString(strURL) << "\" is not an absolute FileURL");
 
    }
 
    if ( strDecodedURL )
        rtl_uString_release( strDecodedURL );
 
    if ( osl_File_E_None == nError )
        rtl_uString_assign( pustrPath, strTempPath );
 
    if ( strTempPath )
        rtl_uString_release( strTempPath );
 
    SAL_INFO_IF(nError, "sal.osl",
        "osl_getSystemPathFromFileURL: \"" << rtl::OUString(strURL) << "\" is not a FileURL");
 
    return nError;
}
 
oslFileError osl_getFileURLFromSystemPath( rtl_uString* strPath, rtl_uString** pstrURL )
{
    oslFileError nError = osl_File_E_INVAL; /* Assume failure */
    rtl_uString *strTempURL = nullptr;
    DWORD dwPathType = PATHTYPE_ERROR;
 
    if (strPath)
        dwPathType = IsValidFilePath(strPath, VALIDATEPATH_ALLOW_RELATIVE, nullptr);
 
    if (dwPathType)
    {
        rtl_uString *strTempPath = nullptr;
 
        if ( dwPathType & PATHTYPE_IS_LONGPATH )
        {
            rtl_uString *strBuffer = nullptr;
            sal_uInt32 nIgnore = 0;
            sal_uInt32 nLength = 0;
 
            /* the path has the longpath prefix, lets remove it */
            switch ( dwPathType & PATHTYPE_MASK_TYPE )
            {
                case PATHTYPE_ABSOLUTE_UNC:
                    nIgnore = SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX_UNC ) - 1;
                    OSL_ENSURE( nIgnore == 8, "Unexpected long path UNC prefix!" );
 
                    /* generate the normal UNC path */
                    nLength = rtl_uString_getLength( strPath );
                    rtl_uString_newFromStr_WithLength( &strBuffer, strPath->buffer + nIgnore - 2, nLength - nIgnore + 2 );
                    strBuffer->buffer[0] = '\\';
 
                    rtl_uString_newReplace( &strTempPath, strBuffer, '\\', '/' );
                    rtl_uString_release( strBuffer );
                    break;
 
                case PATHTYPE_ABSOLUTE_LOCAL:
                    nIgnore = SAL_N_ELEMENTS( WSTR_LONG_PATH_PREFIX ) - 1;
                    OSL_ENSURE( nIgnore == 4, "Unexpected long path prefix!" );
 
                    /* generate the normal path */
                    nLength = rtl_uString_getLength( strPath );
                    rtl_uString_newFromStr_WithLength( &strBuffer, strPath->buffer + nIgnore, nLength - nIgnore );
 
                    rtl_uString_newReplace( &strTempPath, strBuffer, '\\', '/' );
                    rtl_uString_release( strBuffer );
                    break;
 
                default:
                    OSL_FAIL( "Unexpected long path format!" );
                    rtl_uString_newReplace( &strTempPath, strPath, '\\', '/' );
                    break;
            }
        }
        else
        {
            /* Replace backslashes */
            rtl_uString_newReplace( &strTempPath, strPath, '\\', '/' );
        }
 
        switch ( dwPathType & PATHTYPE_MASK_TYPE )
        {
        case PATHTYPE_RELATIVE:
            rtl_uString_assign( &strTempURL, strTempPath );
            nError = osl_File_E_None;
            break;
        case PATHTYPE_ABSOLUTE_UNC:
            rtl_uString_newFromAscii( &strTempURL, "file:" );
            rtl_uString_newConcat( &strTempURL, strTempURL, strTempPath );
            nError = osl_File_E_None;
            break;
        case PATHTYPE_ABSOLUTE_LOCAL:
            rtl_uString_newFromAscii( &strTempURL, "file:///" );
            rtl_uString_newConcat( &strTempURL, strTempURL, strTempPath );
            nError = osl_File_E_None;
            break;
        default:
            break;
        }
 
        /* Release temp path */
        rtl_uString_release( strTempPath );
    }
 
    if ( osl_File_E_None == nError )
    {
        rtl_String  *strEncodedURL = nullptr;
 
        /* Encode the URL */
        osl_encodeURL_( strTempURL, &strEncodedURL );
 
        /* Provide URL via unicode string */
        rtl_string2UString( pstrURL, rtl_string_getStr(strEncodedURL), rtl_string_getLength(strEncodedURL), RTL_TEXTENCODING_ASCII_US, OUSTRING_TO_OSTRING_CVTFLAGS );
        OSL_ASSERT(*pstrURL != nullptr);
        rtl_string_release( strEncodedURL );
    }
 
    /* Release temp URL */
    if ( strTempURL )
        rtl_uString_release( strTempURL );
 
    SAL_INFO_IF(nError, "sal.osl",
        "osl_getFileURLFromSystemPath: \"" << rtl::OUString(strPath) << "\" is not a systemPath");
    return nError;
}
 
oslFileError SAL_CALL osl_getSystemPathFromFileURL(
    rtl_uString *ustrURL, rtl_uString **pustrPath)
{
    return osl_getSystemPathFromFileURL_( ustrURL, pustrPath, true );
}
 
oslFileError SAL_CALL osl_searchFileURL(
    rtl_uString *ustrFileName,
    rtl_uString *ustrSystemSearchPath,
    rtl_uString **pustrPath)
{
    rtl_uString     *ustrUNCPath = nullptr;
    rtl_uString     *ustrSysPath = nullptr;
    oslFileError    error;
 
    /* First try to interpret the file name as an URL even a relative one */
    error = osl_getSystemPathFromFileURL_( ustrFileName, &ustrUNCPath, true );
 
    /* So far we either have an UNC path or something invalid
       Now create a system path */
    if ( osl_File_E_None == error )
        error = osl_getSystemPathFromFileURL_( ustrUNCPath, &ustrSysPath, true );
 
    if ( osl_File_E_None == error )
    {
        DWORD   nBufferLength;
        DWORD   dwResult;
        LPWSTR  lpBuffer = nullptr;
        LPWSTR  lpszFilePart;
 
        /* Repeat calling SearchPath ...
           Start with MAX_PATH for the buffer. In most cases this
           will be enough and does not force the loop to run twice */
        dwResult = MAX_PATH;
 
        do
        {
            /* If search path is empty use a nullptr pointer instead according to MSDN documentation of SearchPath */
            LPCWSTR lpszSearchPath = ustrSystemSearchPath && ustrSystemSearchPath->length ? o3tl::toW(ustrSystemSearchPath->buffer) : nullptr;
            LPCWSTR lpszSearchFile = o3tl::toW(ustrSysPath->buffer);
 
            /* Allocate space for buffer according to previous returned count of required chars */
            /* +1 is not necessary if we follow MSDN documentation but for robustness we do so */
            nBufferLength = dwResult + 1;
            lpBuffer = lpBuffer ?
                static_cast<LPWSTR>(realloc(lpBuffer, nBufferLength * sizeof(WCHAR))) :
                static_cast<LPWSTR>(malloc(nBufferLength * sizeof(WCHAR)));
 
            dwResult = SearchPathW( lpszSearchPath, lpszSearchFile, nullptr, nBufferLength, lpBuffer, &lpszFilePart );
        } while ( dwResult && dwResult >= nBufferLength );
 
        /*  ... until an error occurs or buffer is large enough.
            dwResult == nBufferLength can not happen according to documentation but lets be robust ;-) */
 
        if ( dwResult )
        {
            rtl_uString_newFromStr( &ustrSysPath, o3tl::toU(lpBuffer) );
            error = osl_getFileURLFromSystemPath( ustrSysPath, pustrPath );
        }
        else
        {
            WIN32_FIND_DATAW aFindFileData;
            HANDLE  hFind;
 
            /* something went wrong, perhaps the path was absolute */
            error = oslTranslateFileError( GetLastError() );
 
            hFind = FindFirstFileW( o3tl::toW(ustrSysPath->buffer), &aFindFileData );
 
            if ( IsValidHandle(hFind) )
            {
                error = osl_getFileURLFromSystemPath( ustrSysPath, pustrPath );
                FindClose( hFind );
            }
        }
 
        free( lpBuffer );
    }
 
    if ( ustrSysPath )
        rtl_uString_release( ustrSysPath );
 
    if ( ustrUNCPath )
        rtl_uString_release( ustrUNCPath );
 
    return error;
}
 
oslFileError SAL_CALL osl_getAbsoluteFileURL( rtl_uString* ustrBaseURL, rtl_uString* ustrRelativeURL, rtl_uString** pustrAbsoluteURL )
{
    oslFileError    eError;
    rtl_uString     *ustrRelSysPath = nullptr;
    rtl_uString     *ustrBaseSysPath = nullptr;
 
    if ( ustrBaseURL && ustrBaseURL->length )
    {
        eError = osl_getSystemPathFromFileURL_( ustrBaseURL, &ustrBaseSysPath, false );
        OSL_ENSURE( osl_File_E_None == eError, "osl_getAbsoluteFileURL called with relative or invalid base URL" );
 
        eError = osl_getSystemPathFromFileURL_( ustrRelativeURL, &ustrRelSysPath, true );
    }
    else
    {
        eError = osl_getSystemPathFromFileURL_( ustrRelativeURL, &ustrRelSysPath, false );
        OSL_ENSURE( osl_File_E_None == eError, "osl_getAbsoluteFileURL called with empty base URL and/or invalid relative URL" );
    }
 
    if ( !eError )
    {
        ::osl::LongPathBuffer< sal_Unicode > aBuffer( MAX_LONG_PATH );
        ::osl::LongPathBuffer< sal_Unicode > aCurrentDir( MAX_LONG_PATH );
        LPWSTR  lpFilePart = nullptr;
        DWORD   dwResult;
 
/*@@@ToDo
  Bad, bad hack, this only works if the base path
  really exists which is not necessary according
  to RFC2396
  The whole FileURL implementation should be merged
  with the rtl/uri class.
*/
        if ( ustrBaseSysPath )
        {
            osl_acquireMutex( g_CurrentDirectoryMutex );
 
            GetCurrentDirectoryW( aCurrentDir.getBufSizeInSymbols(), o3tl::toW(aCurrentDir) );
            SetCurrentDirectoryW( o3tl::toW(ustrBaseSysPath->buffer) );
        }
 
        dwResult = GetFullPathNameW( o3tl::toW(ustrRelSysPath->buffer), aBuffer.getBufSizeInSymbols(), o3tl::toW(aBuffer), &lpFilePart );
 
        if ( ustrBaseSysPath )
        {
            SetCurrentDirectoryW( o3tl::toW(aCurrentDir) );
 
            osl_releaseMutex( g_CurrentDirectoryMutex );
        }
 
        if ( dwResult )
        {
            if ( dwResult >= aBuffer.getBufSizeInSymbols() )
                eError = osl_File_E_INVAL;
            else
            {
                rtl_uString *ustrAbsSysPath = nullptr;
 
                rtl_uString_newFromStr( &ustrAbsSysPath, aBuffer );
 
                eError = osl_getFileURLFromSystemPath( ustrAbsSysPath, pustrAbsoluteURL );
 
                if ( ustrAbsSysPath )
                    rtl_uString_release( ustrAbsSysPath );
            }
        }
        else
            eError = oslTranslateFileError( GetLastError() );
    }
 
    if ( ustrBaseSysPath )
        rtl_uString_release( ustrBaseSysPath );
 
    if ( ustrRelSysPath )
        rtl_uString_release( ustrRelSysPath );
 
    return  eError;
}
 
oslFileError SAL_CALL osl_getCanonicalName( rtl_uString *strRequested, rtl_uString **strValid )
{
    rtl_uString_newFromString(strValid, strRequested);
    return osl_File_E_None;
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V769 The 'pDest' pointer in the 'pDest ++' expression could be nullptr. In such case, resulting value will be senseless and it should not be used. Check lines: 524, 496.

V547 Expression '!lpszPath' is always false.

V575 The potential null pointer is passed into 'sprintf' function. Inspect the first argument. Check lines: 579, 564.