/* -*- 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 <systools/win32/extended_max_path.hxx>
#include <systools/win32/uwinapi.h>
#include <aclapi.h>

#include "file_url.hxx"
#include "filetime.hxx"
#include "file_error.hxx"

#include "path_helper.hxx"

#include <rtl/alloc.h>
#include <rtl/ustring.hxx>
#include <rtl/character.hxx>
#include <sal/log.hxx>
#include <o3tl/char16_t2wchar_t.hxx>

#include <memory>

namespace
{
__int64 getFiletime(FILETIME const& ft)
{
    return (DWORD64(ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
}

void setFiletime(FILETIME& ft, __int64 value)
{
    ft.dwHighDateTime = value >> 32;
    ft.dwLowDateTime = value & 0xFFFFFFFF;
}

__int64 getBaseFileTime()
{
    static const __int64 baseFileTime = []
    {
        const SYSTEMTIME BaseSysTime{ .wYear = 1970,
                                      .wMonth = 1,
                                      .wDayOfWeek = 0,
                                      .wDay = 1,
                                      .wHour = 0,
                                      .wMinute = 0,
                                      .wSecond = 0,
                                      .wMilliseconds = 0 };
        FILETIME BaseFileTime;
        [[maybe_unused]] bool bResult = SystemTimeToFileTime(&BaseSysTime, &BaseFileTime);
        assert(bResult);
        return getFiletime(BaseFileTime);
    }();
    return baseFileTime;
}
}

BOOL TimeValueToFileTime(const TimeValue *cpTimeVal, FILETIME *pFTime)
{
    if (cpTimeVal==nullptr)
        return false;

    __int64 localTime = cpTimeVal->Seconds * __int64(10000000) + cpTimeVal->Nanosec / 100;
    __int64 timeValue = getBaseFileTime() + localTime;

    if (timeValue < 0)
        return false;

    setFiletime(*pFTime, timeValue);
    return true;
}

BOOL FileTimeToTimeValue(const FILETIME* cpFTime, TimeValue* pTimeVal, bool bDuration)
{
    __int64 localTime = getFiletime(*cpFTime);
    if (!bDuration)
        localTime -= getBaseFileTime();

    if (localTime < 0)
        return false;

    pTimeVal->Seconds = static_cast<unsigned long>(localTime / 10000000L);
    pTimeVal->Nanosec = static_cast<unsigned long>((localTime % 10000000L) * 100);
    return true;
}

namespace
{
// Returns whether a given path is only a logical drive pattern or not.
// A logical drive pattern is something like "a:\", "c:\".
// No logical drive pattern is something like "c:\test"
bool systemPathIsLogicalDrivePattern(std::u16string_view path)
{
    // is [A-Za-z]:[/|\]\0
    if (path.length() < 2 || !rtl::isAsciiAlpha(path[0]) || path[1] != ':')
        return false;
    auto rest = path.substr(2);
    return rest.empty() // "c:"
           || rest == u"\\" // "c:\"
           || rest == u"/" // "c:/"
           || rest == u".\\"; // "c:.\"
               // degenerated case returned by the Windows FileOpen dialog
               // when someone enters for instance "x:filename", the Win32
               // API accepts this case
}

// Adds a trailing path separator to the given system path if not
// already there and if the path is not the root path or a logical
// drive alone
void systemPathEnsureSeparator(/*inout*/ OUString& path)
{
    if (!path.endsWith(u"\\") && !path.endsWith(u"/"))
        path += "\\";

    SAL_WARN_IF(!path.endsWith(u"\\"), "sal.osl",
                "systemPathEnsureSeparator: Post condition failed");
}

// Removes the last separator from the given system path if any and
// if the path is not the root path '\'
void systemPathRemoveSeparator(/*inout*/ OUString& path)
{
    if (!systemPathIsLogicalDrivePattern(path) && (path.endsWith(u"\\") || path.endsWith(u"/")))
        path = path.copy(0, path.getLength() - 1);
}

    struct Component
    {
        bool isPresent() const { return begin_ < end_; }

        const sal_Unicode* begin_ = nullptr;
        const sal_Unicode* end_ = nullptr;
    };

    struct UNCComponents
    {
        Component server_;
        Component share_;
        Component resource_;
    };

    bool is_UNC_path(std::u16string_view path) { return path.starts_with(u"\\\\"); }

    UNCComponents parse_UNC_path(std::u16string_view path)
    {
        OSL_PRECOND(is_UNC_path(path), "Precondition violated: No UNC path");
        OSL_PRECOND(path.find('/') == std::u16string_view::npos, "Path must not contain slashes");

        const sal_Unicode* pend = path.data() + path.length();
        const sal_Unicode* ppos = path.data() + 2;
        UNCComponents uncc;

        uncc.server_.begin_ = ppos;
        while ((ppos < pend) && (*ppos != '\\'))
            ppos++;

        uncc.server_.end_ = ppos;

        if (ppos < pend)
        {
            uncc.share_.begin_ = ++ppos;
            while ((ppos < pend) && (*ppos != '\\'))
                ppos++;

            uncc.share_.end_ = ppos;

            if (ppos < pend)
            {
                uncc.resource_.begin_ = ppos + 1;
                uncc.resource_.end_ = pend;
            }
        }

        SAL_WARN_IF(!uncc.server_.isPresent() || !uncc.share_.isPresent(),
            "sal.osl",
            "Postcondition violated: Invalid UNC path detected");
        return uncc;
    }

    bool has_path_parent(std::u16string_view path)
    {
        // Has the given path a parent or are we already there,
        // e.g. 'c:\' or '\\server\share\'?

        bool has_parent = false;
        if (is_UNC_path(path))
        {
            UNCComponents unc_comp = parse_UNC_path(path);
            has_parent = unc_comp.resource_.isPresent();
        }
        else
        {
            has_parent = !systemPathIsLogicalDrivePattern(path);
        }
        return has_parent;
    }
}

oslFileError SAL_CALL osl_acquireVolumeDeviceHandle( oslVolumeDeviceHandle Handle )
{
    if ( Handle )
    {
        rtl_uString_acquire( static_cast<rtl_uString *>(Handle) );
        return osl_File_E_None;
    }
    else
        return osl_File_E_INVAL;
}

oslFileError SAL_CALL osl_releaseVolumeDeviceHandle( oslVolumeDeviceHandle Handle )
{
    if ( Handle )
    {
        rtl_uString_release( static_cast<rtl_uString *>(Handle) );
        return osl_File_E_None;
    }
    else
        return osl_File_E_INVAL;
}

oslFileError SAL_CALL osl_getVolumeDeviceMountPath( oslVolumeDeviceHandle Handle, rtl_uString **pstrPath )
{
    if ( Handle && pstrPath )
    {
        rtl_uString_assign( pstrPath, static_cast<rtl_uString *>(Handle) );
        return osl_File_E_None;
    }
    else
        return osl_File_E_INVAL;
}

#define DIRECTORYITEM_DRIVE     0
#define DIRECTORYITEM_FILE      1
#define DIRECTORYITEM_SERVER    2

namespace {

struct DirectoryItem_Impl
{
    UINT uType = 0;
    union {
        WIN32_FIND_DATAW FindData;
        WCHAR            cDriveString[MAX_PATH];
    };
    OUString        m_sFullPath;
    bool            bFullPathNormalized = false;
    int             nRefCount = 0;
};

}

#define DIRECTORYTYPE_LOCALROOT     0
#define DIRECTORYTYPE_NETROOT       1
#define DIRECTORYTYPE_FILESYSTEM    3

namespace {

struct Directory_Impl
{
    UINT uType = 0;
    union {
        HANDLE  hDirectory = nullptr;
        HANDLE  hEnumDrives;
    };
    OUString    m_sDirectoryPath;
};

typedef struct tagDRIVEENUM
{
    LPCWSTR lpIdent;
    WCHAR   cBuffer[/*('Z' - 'A' + 1) * sizeof("A:\\") + 1*/256];
    LPCWSTR lpCurrent;
} DRIVEENUM, *PDRIVEENUM, *LPDRIVEENUM;

}

static HANDLE OpenLogicalDrivesEnum()
{
    auto xEnum = std::make_unique<DRIVEENUM>();
    DWORD dwNumCopied = GetLogicalDriveStringsW( SAL_N_ELEMENTS(xEnum->cBuffer) - 1, xEnum->cBuffer );

    if ( dwNumCopied && dwNumCopied < SAL_N_ELEMENTS(xEnum->cBuffer) )
    {
        xEnum->lpCurrent = xEnum->cBuffer;
        xEnum->lpIdent = L"tagDRIVEENUM";
    }
    else
    {
        xEnum.reset();
    }
    return xEnum ? static_cast<HANDLE>(xEnum.release()) : INVALID_HANDLE_VALUE;
}

static bool EnumLogicalDrives(HANDLE hEnum, LPWSTR lpBuffer)
{
    LPDRIVEENUM pEnum = static_cast<LPDRIVEENUM>(hEnum);
    if ( !pEnum )
    {
        SetLastError( ERROR_INVALID_HANDLE );
        return false;
    }

    int nLen = wcslen( pEnum->lpCurrent );
    if ( !nLen )
    {
        SetLastError( ERROR_NO_MORE_FILES );
        return false;
    }

    CopyMemory( lpBuffer, pEnum->lpCurrent, (nLen + 1) * sizeof(WCHAR) );
    pEnum->lpCurrent += nLen + 1;
    return true;
}

static bool CloseLogicalDrivesEnum(HANDLE hEnum)
{
    bool        fSuccess = false;
    LPDRIVEENUM pEnum = static_cast<LPDRIVEENUM>(hEnum);

    if ( pEnum )
    {
        delete pEnum;
        fSuccess = true;
    }
    else
        SetLastError( ERROR_INVALID_HANDLE );

    return fSuccess;
}

namespace {

typedef struct tagDIRECTORY
{
    HANDLE           hFind;
    WIN32_FIND_DATAW aFirstData;
} DIRECTORY, *PDIRECTORY, *LPDIRECTORY;

}

static HANDLE OpenDirectory(const OUString& path)
{
    if (path.isEmpty())
        return nullptr;

    std::u16string_view suffix;
    if (!path.endsWith(u"\\"))
        suffix = u"*.*";
    else
        suffix = u"\\*.*";

    std::unique_ptr<WCHAR[]> szFileMask(new (std::nothrow) WCHAR[path.getLength() + suffix.length() + 1]);
    assert(szFileMask); // Don't handle OOM conditions
    WCHAR* pos = std::copy_n(path.getStr(), path.getLength(), szFileMask.get());
    pos = std::copy_n(suffix.data(), suffix.length(), pos);
    *pos = 0;

    auto xDirectory = std::make_unique<DIRECTORY>();
    xDirectory->hFind = FindFirstFileW(szFileMask.get(), &xDirectory->aFirstData);

    if (!IsValidHandle(xDirectory->hFind))
    {
        if ( GetLastError() != ERROR_NO_MORE_FILES )
        {
            xDirectory.reset();
        }
    }

    return static_cast<HANDLE>(xDirectory.release());
}

static bool EnumDirectory(HANDLE hDirectory, LPWIN32_FIND_DATAW pFindData)
{
    LPDIRECTORY pDirectory = static_cast<LPDIRECTORY>(hDirectory);
    if ( !pDirectory )
    {
        SetLastError( ERROR_INVALID_HANDLE );
        return false;
    }

    bool fSuccess = false;
    bool fValid;
    do
    {
        if ( pDirectory->aFirstData.cFileName[0] )
        {
            *pFindData = pDirectory->aFirstData;
            fSuccess = true;
            pDirectory->aFirstData.cFileName[0] = 0;
        }
        else if ( IsValidHandle( pDirectory->hFind ) )
            fSuccess = FindNextFileW( pDirectory->hFind, pFindData );
        else
        {
            fSuccess = false;
            SetLastError( ERROR_NO_MORE_FILES );
        }

        fValid = fSuccess && wcscmp( L".", pFindData->cFileName ) != 0 && wcscmp( L"..", pFindData->cFileName ) != 0;

    } while( fSuccess && !fValid );

    return fSuccess;
}

static bool CloseDirectory(HANDLE hDirectory)
{
    bool        fSuccess = false;
    LPDIRECTORY pDirectory = static_cast<LPDIRECTORY>(hDirectory);

    if (pDirectory)
    {
        if (IsValidHandle(pDirectory->hFind))
            fSuccess = FindClose(pDirectory->hFind);

        delete pDirectory;
    }
    else
        SetLastError(ERROR_INVALID_HANDLE);

    return fSuccess;
}

static oslFileError osl_openLocalRoot(
    rtl_uString *strDirectoryPath, oslDirectory *pDirectory)
{
    if ( !pDirectory )
        return osl_File_E_INVAL;

    *pDirectory = nullptr;

    OUString strSysPath;
    oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDirectoryPath), &strSysPath.pData, false);
    if ( osl_File_E_None != error )
        return error;

    std::unique_ptr<Directory_Impl> pDirImpl(new (std::nothrow) Directory_Impl);
    assert(pDirImpl); // Don't handle OOM conditions
    pDirImpl->m_sDirectoryPath = strSysPath;

    /* Append backslash if necessary */

    /* @@@ToDo
       use function ensure backslash
    */
    sal_uInt32 nLen = pDirImpl->m_sDirectoryPath.getLength();
    if ( nLen && pDirImpl->m_sDirectoryPath[nLen - 1] != L'\\' )
    {
        pDirImpl->m_sDirectoryPath += "\\";
    }

    pDirImpl->uType = DIRECTORYTYPE_LOCALROOT;
    pDirImpl->hEnumDrives = OpenLogicalDrivesEnum();

    /* @@@ToDo
       Use IsValidHandle(...)
    */
    if (pDirImpl->hEnumDrives == INVALID_HANDLE_VALUE)
        return oslTranslateFileError(GetLastError());

    *pDirectory = pDirImpl.release();
    return osl_File_E_None;
}

static oslFileError osl_openFileDirectory(
    rtl_uString *strDirectoryPath, oslDirectory *pDirectory)
{
    if ( !pDirectory )
        return osl_File_E_INVAL;
    *pDirectory = nullptr;

    std::unique_ptr<Directory_Impl> pDirImpl(new (std::nothrow) Directory_Impl);
    assert(pDirImpl); // Don't handle OOM conditions
    pDirImpl->m_sDirectoryPath = strDirectoryPath;

    /* Append backslash if necessary */

    /* @@@ToDo
       use function ensure backslash
    */
    sal_uInt32 nLen = pDirImpl->m_sDirectoryPath.getLength();
    if ( nLen && pDirImpl->m_sDirectoryPath[nLen - 1] != '\\' )
        pDirImpl->m_sDirectoryPath += "\\";

    pDirImpl->uType = DIRECTORYTYPE_FILESYSTEM;
    pDirImpl->hDirectory = OpenDirectory(pDirImpl->m_sDirectoryPath);

    if ( !pDirImpl->hDirectory )
        return oslTranslateFileError(GetLastError());

    *pDirectory = pDirImpl.release();
    return osl_File_E_None;
}

static oslFileError osl_openNetworkServer(
    rtl_uString *strSysDirPath, oslDirectory *pDirectory)
{
    NETRESOURCEW    aNetResource;
    HANDLE          hEnum;
    DWORD           dwError;

    ZeroMemory( &aNetResource, sizeof(aNetResource) );

    aNetResource.lpRemoteName = o3tl::toW(strSysDirPath->buffer);

    dwError = WNetOpenEnumW(
        RESOURCE_GLOBALNET,
        RESOURCETYPE_DISK,
        RESOURCEUSAGE_CONNECTABLE | RESOURCEUSAGE_CONTAINER,
        &aNetResource,
        &hEnum );

    if ( ERROR_SUCCESS == dwError )
    {
        Directory_Impl *pDirImpl = new (std::nothrow) Directory_Impl;
        assert(pDirImpl); // Don't handle OOM conditions
        pDirImpl->uType = DIRECTORYTYPE_NETROOT;
        pDirImpl->hDirectory = hEnum;
        *pDirectory = static_cast<oslDirectory>(pDirImpl);
    }
    return oslTranslateFileError( dwError );
}

static DWORD create_dir_with_callback(
    rtl_uString * dir_path,
    oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc,
    void* pData)
{
    // Create the specified directory and call the
    // user specified callback function. On success
    // the function returns ERROR_SUCCESS else a Win32 error code.

    bool bCreated = CreateDirectoryW( o3tl::toW(rtl_uString_getStr( dir_path )), nullptr );

    if ( bCreated )
    {
        if (aDirectoryCreationCallbackFunc)
        {
            OUString url;
            osl_getFileURLFromSystemPath(dir_path, &(url.pData));
            aDirectoryCreationCallbackFunc(pData, url.pData);
        }
        return ERROR_SUCCESS;
    }
    return GetLastError();
}

static sal_Int32 path_make_parent(rtl_uString* path)
{
    /*  Cut off the last part of the given path to
    get the parent only, e.g. 'c:\dir\subdir' ->
    'c:\dir' or '\\share\sub\dir' -> '\\share\sub'
    @return The position where the path has been cut
    off (this is the position of the last backslash).
    If there are no more parents 0 will be returned,
    e.g. 'c:\' or '\\Share' have no more parents */

    OSL_PRECOND(OUString::unacquired(&path).indexOf('/') == -1, "Path must not contain slashes");
    OSL_PRECOND(has_path_parent(OUString::unacquired(&path)), "Path must have a parent");

    sal_Int32 pos = OUString::unacquired(&path).lastIndexOf('\\');
    assert(pos >= 0);
    *(path->buffer + pos) = 0;
    return pos;
}

static DWORD create_dir_recursively_(
    rtl_uString * dir_path,
    oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc,
    void* pData)
{
    OSL_PRECOND(!OUString::unacquired(&dir_path).endsWith(u"\\"),
        "Path must not end with a backslash");

    DWORD w32_error = create_dir_with_callback(
        dir_path, aDirectoryCreationCallbackFunc, pData);
    if ((w32_error != ERROR_PATH_NOT_FOUND) || !has_path_parent(OUString::unacquired(&dir_path)))
        return w32_error;

    const sal_Int32 oldLen = dir_path->length;
    dir_path->length = path_make_parent(dir_path); // dir_path->buffer[pos] = 0, restore below

    w32_error = create_dir_recursively_(
        dir_path, aDirectoryCreationCallbackFunc, pData);

    dir_path->buffer[dir_path->length] = '\\'; // restore
    dir_path->length = oldLen;

    if (ERROR_SUCCESS != w32_error && ERROR_ALREADY_EXISTS != w32_error)
        return w32_error;

    return create_dir_with_callback(dir_path, aDirectoryCreationCallbackFunc, pData);
}

oslFileError SAL_CALL osl_createDirectoryPath(
    rtl_uString* aDirectoryUrl,
    oslDirectoryCreationCallbackFunc aDirectoryCreationCallbackFunc,
    void* pData)
{
    if (aDirectoryUrl == nullptr)
        return osl_File_E_INVAL;

    OUString sys_path;
    oslFileError osl_error =
        osl_getSystemPathFromFileURL_(OUString::unacquired(&aDirectoryUrl), &sys_path.pData, false);

    if (osl_error != osl_File_E_None)
        return osl_error;

    systemPathRemoveSeparator(sys_path);

    return oslTranslateFileError(create_dir_recursively_(
        sys_path.pData, aDirectoryCreationCallbackFunc, pData));
}

oslFileError SAL_CALL osl_createDirectory(rtl_uString* strPath)
{
    return osl_createDirectoryWithFlags(
        strPath, osl_File_OpenFlag_Read | osl_File_OpenFlag_Write);
}

oslFileError osl_createDirectoryWithFlags(rtl_uString * strPath, sal_uInt32)
{
    OUString strSysPath;
    oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false);

    if ( osl_File_E_None != error )
        return error;

    bool bCreated = CreateDirectoryW(o3tl::toW(strSysPath.getStr()), nullptr);
    if ( !bCreated )
    {
        /*@@@ToDo
          The following case is a hack because the ucb or the webtop had some
          problems with the error code that CreateDirectory returns in
          case the path is only a logical drive, should be removed!
        */

        if ((strSysPath.getLength() == 2 || (strSysPath.getLength() == 3 && strSysPath[2] == '\\'))
            && rtl::isAsciiAlpha(strSysPath[0]) && strSysPath[1] == ':')
            SetLastError( ERROR_ALREADY_EXISTS );

        error = oslTranslateFileError( GetLastError() );
    }

    return error;
}

oslFileError SAL_CALL osl_removeDirectory(rtl_uString* strPath)
{
    OUString strSysPath;
    oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strPath), &strSysPath.pData, false);

    if ( osl_File_E_None == error )
    {
        if (RemoveDirectoryW(o3tl::toW(strSysPath.getStr())))
            error = osl_File_E_None;
        else
            error = oslTranslateFileError( GetLastError() );
    }
    return error;
}

oslFileError SAL_CALL osl_openDirectory(rtl_uString *strDirectoryPath, oslDirectory *pDirectory)
{
    oslFileError    error;

    if ( 0 == rtl_ustr_ascii_compareIgnoreAsciiCase( strDirectoryPath->buffer, "file:///" ) )
        error = osl_openLocalRoot( strDirectoryPath, pDirectory );
    else
    {
        OUString strSysDirectoryPath;
        DWORD       dwPathType;

        error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strDirectoryPath), &strSysDirectoryPath.pData, false);

        if ( osl_File_E_None != error )
                return error;

        dwPathType = IsValidFilePath(strSysDirectoryPath, VALIDATEPATH_NORMAL, nullptr);

        if ( dwPathType & PATHTYPE_IS_SERVER )
            error = osl_openNetworkServer(strSysDirectoryPath.pData, pDirectory);
        else
            error = osl_openFileDirectory(strSysDirectoryPath.pData, pDirectory);
    }
    return error;
}

static oslFileError osl_getNextNetResource(
    oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 /*uHint*/ )
{
    Directory_Impl      *pDirImpl = static_cast<Directory_Impl *>(Directory);
    DirectoryItem_Impl  *pItemImpl = nullptr;
    BYTE                buffer[16384];
    LPNETRESOURCEW      lpNetResource = reinterpret_cast<LPNETRESOURCEW>(buffer);
    DWORD               dwError, dwCount, dwBufSize;

    if ( !pItem )
        return osl_File_E_INVAL;
    *pItem = nullptr;

    if ( !pDirImpl )
        return osl_File_E_INVAL;

    dwCount = 1;
    dwBufSize = sizeof(buffer);
    dwError = WNetEnumResourceW( pDirImpl->hDirectory, &dwCount, lpNetResource, &dwBufSize );

    switch ( dwError )
    {
        case NO_ERROR:
        case ERROR_MORE_DATA:
        {
            pItemImpl = new (std::nothrow) DirectoryItem_Impl;
            if ( !pItemImpl )
                return osl_File_E_NOMEM;

            pItemImpl->uType = DIRECTORYITEM_DRIVE;
            osl_acquireDirectoryItem( static_cast<oslDirectoryItem>(pItemImpl) );

            wcscpy( pItemImpl->cDriveString, lpNetResource->lpRemoteName );

            *pItem = pItemImpl;
        }
        return osl_File_E_None;
        case ERROR_NO_MORE_ITEMS:
            return osl_File_E_NOENT;
        default:
            return oslTranslateFileError( dwError );
    }
}

static oslFileError osl_getNextDrive(
    oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 /*uHint*/ )
{
    Directory_Impl      *pDirImpl = static_cast<Directory_Impl *>(Directory);

    if ( !pItem )
        return osl_File_E_INVAL;
    *pItem = nullptr;

    if ( !pDirImpl )
        return osl_File_E_INVAL;

    std::unique_ptr<DirectoryItem_Impl> pItemImpl(new (std::nothrow) DirectoryItem_Impl);
    if ( !pItemImpl )
        return osl_File_E_NOMEM;

    pItemImpl->uType = DIRECTORYITEM_DRIVE;
    osl_acquireDirectoryItem(pItemImpl.get());
    if (!EnumLogicalDrives(pDirImpl->hEnumDrives, pItemImpl->cDriveString))
        return oslTranslateFileError(GetLastError());

    *pItem = pItemImpl.release();
    return osl_File_E_None;
}

static oslFileError osl_getNextFileItem(
    oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 /*uHint*/)
{
    Directory_Impl      *pDirImpl = static_cast<Directory_Impl *>(Directory);

    if ( !pItem )
        return osl_File_E_INVAL;
    *pItem = nullptr;

    if ( !pDirImpl )
        return osl_File_E_INVAL;

    std::unique_ptr<DirectoryItem_Impl> pItemImpl(new (std::nothrow) DirectoryItem_Impl);
    if ( !pItemImpl )
        return osl_File_E_NOMEM;

    if (!EnumDirectory(pDirImpl->hDirectory, &pItemImpl->FindData))
        return oslTranslateFileError( GetLastError() );

    pItemImpl->uType = DIRECTORYITEM_FILE;
    pItemImpl->nRefCount = 1;

    pItemImpl->m_sFullPath = pDirImpl->m_sDirectoryPath + o3tl::toU(pItemImpl->FindData.cFileName);

    pItemImpl->bFullPathNormalized = true;
    *pItem = pItemImpl.release();
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_getNextDirectoryItem(
    oslDirectory Directory, oslDirectoryItem *pItem, sal_uInt32 uHint)
{
    Directory_Impl      *pDirImpl = static_cast<Directory_Impl *>(Directory);

    /* Assume failure */

    if ( !pItem )
        return osl_File_E_INVAL;
    *pItem = nullptr;

    if ( !pDirImpl )
        return osl_File_E_INVAL;

    switch ( pDirImpl->uType )
    {
    case DIRECTORYTYPE_LOCALROOT:
        return osl_getNextDrive( Directory, pItem, uHint );
    case DIRECTORYTYPE_NETROOT:
        return osl_getNextNetResource( Directory, pItem, uHint );
    case DIRECTORYTYPE_FILESYSTEM:
        return osl_getNextFileItem( Directory, pItem, uHint );
    default:
        return osl_File_E_INVAL;
    }
}

oslFileError SAL_CALL osl_closeDirectory(oslDirectory Directory)
{
    Directory_Impl  *pDirImpl = static_cast<Directory_Impl *>(Directory);
    oslFileError    eError = osl_File_E_INVAL;

    if ( pDirImpl )
    {
        switch ( pDirImpl->uType )
        {
        case DIRECTORYTYPE_FILESYSTEM:
            eError = CloseDirectory( pDirImpl->hDirectory ) ? osl_File_E_None : oslTranslateFileError( GetLastError() );
            break;
        case DIRECTORYTYPE_LOCALROOT:
            eError = CloseLogicalDrivesEnum( pDirImpl->hEnumDrives ) ? osl_File_E_None : oslTranslateFileError( GetLastError() );
            break;
        case DIRECTORYTYPE_NETROOT:
            {
                DWORD err = WNetCloseEnum(pDirImpl->hDirectory);
                eError = (err == NO_ERROR) ? osl_File_E_None : oslTranslateFileError(err);
            }
            break;
        default:
            OSL_FAIL( "Invalid directory type" );
            break;
        }

        delete pDirImpl;
    }
    return eError;
}

namespace {

/* Different types of paths */
enum PATHTYPE
{
    PATHTYPE_SYNTAXERROR = 0,
    PATHTYPE_NETROOT,
    PATHTYPE_NETSERVER,
    PATHTYPE_VOLUME,
    PATHTYPE_FILE
};

}

oslFileError SAL_CALL osl_getDirectoryItem(rtl_uString *strFilePath, oslDirectoryItem *pItem)
{
    oslFileError    error = osl_File_E_None;
    OUString strSysFilePath;
    PATHTYPE        type = PATHTYPE_FILE;
    DWORD           dwPathType;

    /* Assume failure */

    if ( !pItem )
        return osl_File_E_INVAL;

    *pItem = nullptr;

    error = osl_getSystemPathFromFileURL_(OUString::unacquired(&strFilePath), &strSysFilePath.pData, false);

    if ( osl_File_E_None != error )
            return error;

    dwPathType = IsValidFilePath( strSysFilePath, VALIDATEPATH_NORMAL, nullptr );

    if ( dwPathType & PATHTYPE_IS_VOLUME )
        type = PATHTYPE_VOLUME;
    else if ( dwPathType & PATHTYPE_IS_SERVER )
        type = PATHTYPE_NETSERVER;
    else
        type = PATHTYPE_FILE;

    switch ( type )
    {
    case PATHTYPE_NETSERVER:
        {
            DirectoryItem_Impl* pItemImpl = new (std::nothrow) DirectoryItem_Impl;

            if ( !pItemImpl )
                return osl_File_E_NOMEM;

            pItemImpl->uType = DIRECTORYITEM_SERVER;

            osl_acquireDirectoryItem(pItemImpl);
            pItemImpl->m_sFullPath = strSysFilePath;

            // Assign a title anyway
            {
                int iSrc = 2;
                int iDst = 0;

                while( iSrc < strSysFilePath.getLength() && strSysFilePath[iSrc] && strSysFilePath[iSrc] != '\\')
                {
                    pItemImpl->FindData.cFileName[iDst++] = strSysFilePath[iSrc++];
                }
            }

            *pItem = pItemImpl;
        }
        break;
    case PATHTYPE_VOLUME:
        {
            DirectoryItem_Impl* pItemImpl = new (std::nothrow) DirectoryItem_Impl;

            if ( !pItemImpl )
                return osl_File_E_NOMEM;

            pItemImpl->uType = DIRECTORYITEM_DRIVE;

            osl_acquireDirectoryItem(pItemImpl);

            auto pos = std::copy_n(strSysFilePath.getStr(), strSysFilePath.getLength(), pItemImpl->cDriveString);
            pItemImpl->cDriveString[0] = rtl::toAsciiUpperCase( pItemImpl->cDriveString[0] );

            if (!strSysFilePath.endsWith(u"\\"))
                *pos++ = '\\';
            *pos = 0;

            *pItem = pItemImpl;
        }
        break;
    case PATHTYPE_SYNTAXERROR:
    case PATHTYPE_NETROOT:
    case PATHTYPE_FILE:
        {
            HANDLE              hFind;
            WIN32_FIND_DATAW     aFindData;

            if (!strSysFilePath.isEmpty() && strSysFilePath[strSysFilePath.getLength() - 1] == '\\')
                strSysFilePath = strSysFilePath.copy(0, strSysFilePath.getLength() - 1);

            hFind = FindFirstFileW( o3tl::toW(strSysFilePath.getStr()), &aFindData );

            if ( hFind != INVALID_HANDLE_VALUE )
            {
                DirectoryItem_Impl *pItemImpl = new (std::nothrow) DirectoryItem_Impl;
                if (!pItemImpl)
                    error = osl_File_E_NOMEM;

                if (osl_File_E_None == error)
                {
                    osl_acquireDirectoryItem(static_cast<oslDirectoryItem>(pItemImpl));

                    CopyMemory(&pItemImpl->FindData, &aFindData, sizeof(WIN32_FIND_DATAW));
                    pItemImpl->m_sFullPath = strSysFilePath;

                    pItemImpl->uType = DIRECTORYITEM_FILE;
                    *pItem = pItemImpl;
                }

                FindClose( hFind );
            }
            else
                error = oslTranslateFileError( GetLastError() );
        }
        break;
    }

    return error;
}

oslFileError SAL_CALL osl_acquireDirectoryItem( oslDirectoryItem Item )
{
    DirectoryItem_Impl  *pItemImpl = static_cast<DirectoryItem_Impl *>(Item);

    if ( !pItemImpl )
        return osl_File_E_INVAL;

    pItemImpl->nRefCount++;
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_releaseDirectoryItem( oslDirectoryItem Item )
{
    DirectoryItem_Impl  *pItemImpl = static_cast<DirectoryItem_Impl *>(Item);

    if ( !pItemImpl )
        return osl_File_E_INVAL;

    if ( ! --pItemImpl->nRefCount )
        delete pItemImpl;

    return osl_File_E_None;
}

sal_Bool
SAL_CALL osl_identicalDirectoryItem( oslDirectoryItem a, oslDirectoryItem b)
{
    DirectoryItem_Impl *pA = static_cast<DirectoryItem_Impl *>(a);
    DirectoryItem_Impl *pB = static_cast<DirectoryItem_Impl *>(b);
    if (a == b)
        return true;
    /* same name => same item, unless renaming / moving madness has occurred */
    if (pA->m_sFullPath == pB->m_sFullPath)
        return true;

    // FIXME: as/when/if this is used in anger on Windows we could
    // do better here.

    return false;
}

static bool is_floppy_A_present()
{ return (GetLogicalDrives() & 1); }

static bool is_floppy_B_present()
{ return (GetLogicalDrives() & 2); }

static bool is_floppy_volume_mount_point(const OUString& path)
{
    // determines if a volume mount point shows to a floppy
    // disk by comparing the unique volume names
    static const LPCWSTR FLOPPY_A = L"A:\\";
    static const LPCWSTR FLOPPY_B = L"B:\\";

    OUString p(path);
    systemPathEnsureSeparator(p);

    WCHAR vn[51];
    if (GetVolumeNameForVolumeMountPointW(o3tl::toW(p.getStr()), vn, std::size(vn)))
    {
        WCHAR vnfloppy[51];
        if (is_floppy_A_present() &&
            GetVolumeNameForVolumeMountPointW(FLOPPY_A, vnfloppy, std::size(vnfloppy)) &&
            (0 == wcscmp(vn, vnfloppy)))
            return true;

        if (is_floppy_B_present() &&
            GetVolumeNameForVolumeMountPointW(FLOPPY_B, vnfloppy, std::size(vnfloppy)) &&
            (0 == wcscmp(vn, vnfloppy)))
            return true;
    }
    return false;
}

static bool is_floppy_drive(const OUString& path)
{
    static const LPCWSTR FLOPPY_DRV_LETTERS = L"AaBb";

    // we must take into account that even a floppy
    // drive may be mounted to a directory so checking
    // for the drive letter alone is not sufficient
    // we must compare the unique volume name with
    // that of the available floppy disks

    const sal_Unicode* pszPath = path.getStr();
    return ((wcschr(FLOPPY_DRV_LETTERS, pszPath[0]) && (L':' == pszPath[1])) || is_floppy_volume_mount_point(path));
}

static bool is_volume_mount_point(const OUString& path)
{
    OUString p(path);
    systemPathRemoveSeparator(p);

    if (is_floppy_drive(p))
        return false;

    DWORD fattr = GetFileAttributesW(o3tl::toW(p.getStr()));
    if ((INVALID_FILE_ATTRIBUTES == fattr) ||
        !(FILE_ATTRIBUTE_REPARSE_POINT & fattr))
        return false;

    bool  is_volume_root = false;
    WIN32_FIND_DATAW find_data;
    HANDLE h_find = FindFirstFileW(o3tl::toW(p.getStr()), &find_data);

    if (IsValidHandle(h_find) &&
        (FILE_ATTRIBUTE_REPARSE_POINT & find_data.dwFileAttributes) &&
        (IO_REPARSE_TAG_MOUNT_POINT == find_data.dwReserved0))
    {
        is_volume_root = true;
    }
    if (IsValidHandle(h_find))
        FindClose(h_find);
    return is_volume_root;
}

static UINT get_volume_mount_point_drive_type(const OUString& path)
{
    if (0 == path.getLength())
        return GetDriveTypeW(nullptr);

    OUString p(path);
    systemPathEnsureSeparator(p);

    WCHAR vn[51];
    if (GetVolumeNameForVolumeMountPointW(o3tl::toW(p.getStr()), vn, std::size(vn)))
        return GetDriveTypeW(vn);

    return DRIVE_NO_ROOT_DIR;
}

static bool is_drivetype_request(sal_uInt32 field_mask)
{
    return (field_mask & osl_VolumeInfo_Mask_Attributes);
}

static oslFileError osl_get_drive_type(
    const OUString& path, oslVolumeInfo* pInfo)
{
    // GetDriveType fails on empty volume mount points
    // see Knowledge Base Q244089
    UINT drive_type;
    if (is_volume_mount_point(path))
        drive_type = get_volume_mount_point_drive_type(path);
    else
        drive_type = GetDriveTypeW(o3tl::toW(path.getStr()));

    if (DRIVE_NO_ROOT_DIR == drive_type)
        return oslTranslateFileError(ERROR_INVALID_DRIVE);

    pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes;

    switch (drive_type)
    {
        case DRIVE_CDROM:
            pInfo->uAttributes |= osl_Volume_Attribute_CompactDisc | osl_Volume_Attribute_Removeable;
            break;
        case DRIVE_REMOVABLE:
            pInfo->uAttributes |= osl_Volume_Attribute_Removeable;
            if (is_floppy_drive(path))
                pInfo->uAttributes |= osl_Volume_Attribute_FloppyDisk;
            break;
        case DRIVE_FIXED:
            pInfo->uAttributes |= osl_Volume_Attribute_FixedDisk;
            break;
        case DRIVE_RAMDISK:
            pInfo->uAttributes |= osl_Volume_Attribute_RAMDisk;
            break;
        case DRIVE_REMOTE:
            pInfo->uAttributes |= osl_Volume_Attribute_Remote;
            break;
        case DRIVE_UNKNOWN:
            pInfo->uAttributes = 0;
            break;
        default:
            pInfo->uValidFields &= ~osl_VolumeInfo_Mask_Attributes;
            pInfo->uAttributes = 0;
            break;
    }
    return osl_File_E_None;
}

static bool is_volume_space_info_request(sal_uInt32 field_mask)
{
    return (field_mask &
            (osl_VolumeInfo_Mask_TotalSpace |
             osl_VolumeInfo_Mask_UsedSpace  |
             osl_VolumeInfo_Mask_FreeSpace));
}

static void get_volume_space_information(
    const OUString& path, oslVolumeInfo *pInfo)
{
    bool ret = GetDiskFreeSpaceExW(
        o3tl::toW(path.getStr()),
        reinterpret_cast<PULARGE_INTEGER>(&pInfo->uFreeSpace),
        reinterpret_cast<PULARGE_INTEGER>(&pInfo->uTotalSpace),
        nullptr);

    if (ret)
    {
        pInfo->uUsedSpace    = pInfo->uTotalSpace - pInfo->uFreeSpace;
        pInfo->uValidFields |= osl_VolumeInfo_Mask_TotalSpace |
            osl_VolumeInfo_Mask_UsedSpace |
            osl_VolumeInfo_Mask_FreeSpace;
    }
}

static bool is_filesystem_attributes_request(sal_uInt32 field_mask)
{
    return (field_mask &
            (osl_VolumeInfo_Mask_MaxNameLength |
             osl_VolumeInfo_Mask_MaxPathLength |
             osl_VolumeInfo_Mask_FileSystemName |
             osl_VolumeInfo_Mask_FileSystemCaseHandling));
}

static oslFileError get_filesystem_attributes(
    const OUString& path, sal_uInt32 field_mask, oslVolumeInfo* pInfo)
{
    pInfo->uAttributes = 0;

    // osl_get_drive_type must be called first because
    // this function resets osl_VolumeInfo_Mask_Attributes
    // on failure
    if (is_drivetype_request(field_mask))
    {
        oslFileError osl_error = osl_get_drive_type(path, pInfo);
        if (osl_File_E_None != osl_error)
            return osl_error;
    }
    if (is_filesystem_attributes_request(field_mask))
    {
        /* the following two parameters can not be longer than MAX_PATH+1 */
        WCHAR vn[MAX_PATH+1];
        WCHAR fsn[MAX_PATH+1];

        DWORD serial;
        DWORD mcl;
        DWORD flags;

        LPCWSTR pszPath = o3tl::toW(path.getStr());
        if (GetVolumeInformationW(pszPath, vn, MAX_PATH+1, &serial, &mcl, &flags, fsn, MAX_PATH+1))
        {
            // Currently sal does not use this value, instead MAX_PATH is used
            pInfo->uValidFields   |= osl_VolumeInfo_Mask_MaxNameLength;
            pInfo->uMaxNameLength  = mcl;

            // Should the uMaxPathLength be set to 32767, "\\?\" prefix allows it
            pInfo->uValidFields   |= osl_VolumeInfo_Mask_MaxPathLength;
            pInfo->uMaxPathLength  = MAX_PATH;

            pInfo->uValidFields   |= osl_VolumeInfo_Mask_FileSystemName;
            rtl_uString_newFromStr(&pInfo->ustrFileSystemName, o3tl::toU(fsn));

            // volumes (even NTFS) will always be considered case
            // insensitive because the Win32 API is not able to
            // deal with case sensitive volumes see M$ Knowledge Base
            // article 100625 that's why we never set the attribute
            // osl_Volume_Attribute_Case_Sensitive

            if (flags & FS_CASE_IS_PRESERVED)
                pInfo->uAttributes |= osl_Volume_Attribute_Case_Is_Preserved;

            pInfo->uValidFields |= osl_VolumeInfo_Mask_Attributes;
        }
    }
    return osl_File_E_None;
}

static bool path_get_parent(OUString& path)
{
    OSL_PRECOND(path.lastIndexOf('/') == -1, "Path must not have slashes");

    if (!has_path_parent(path))
    {
        sal_Int32 i = path.lastIndexOf('\\');
        if (-1 < i)
        {
            path = path.copy(0, i);
            return true;
        }
    }
    return false;
}

static void path_travel_to_volume_root(const OUString& system_path, OUString& volume_root)
{
    OUString sys_path(system_path);

    while(!is_volume_mount_point(sys_path) && path_get_parent(sys_path))
        /**/;

    volume_root = sys_path;
    systemPathEnsureSeparator(volume_root);
}

oslFileError SAL_CALL osl_getVolumeInformation(
    rtl_uString *ustrURL, oslVolumeInfo *pInfo, sal_uInt32 uFieldMask )
{
    if (!pInfo)
        return osl_File_E_INVAL;

    OUString system_path;
    oslFileError error = osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrURL), &system_path.pData, false);

    if (osl_File_E_None != error)
        return error;

    OUString volume_root;
    path_travel_to_volume_root(system_path, volume_root);

    pInfo->uValidFields = 0;

    if ((error = get_filesystem_attributes(volume_root, uFieldMask, pInfo)) != osl_File_E_None)
        return error;

    if (is_volume_space_info_request(uFieldMask))
        get_volume_space_information(volume_root, pInfo);

    if (uFieldMask & osl_VolumeInfo_Mask_DeviceHandle)
    {
        error = osl_getFileURLFromSystemPath(volume_root.pData, reinterpret_cast<rtl_uString**>(&pInfo->pDeviceHandle));
        if (error != osl_File_E_None)
            return error;
        pInfo->uValidFields |= osl_VolumeInfo_Mask_DeviceHandle;
    }

    return osl_File_E_None;
}

static oslFileError osl_getDriveInfo(
    oslDirectoryItem Item, oslFileStatus *pStatus, sal_uInt32 uFieldMask)
{
    DirectoryItem_Impl  *pItemImpl = static_cast<DirectoryItem_Impl *>(Item);
    WCHAR               cDrive[3] = L"A:";
    WCHAR               cRoot[4] = L"A:\\";

    if ( !pItemImpl )
        return osl_File_E_INVAL;

    pStatus->uValidFields = 0;

    cDrive[0] = pItemImpl->cDriveString[0];
    cRoot[0] = pItemImpl->cDriveString[0];

    if ( uFieldMask & osl_FileStatus_Mask_FileName )
    {
        if ( pItemImpl->cDriveString[0] == '\\' && pItemImpl->cDriveString[1] == '\\' )
        {
            LPCWSTR lpFirstBkSlash = wcschr( &pItemImpl->cDriveString[2], '\\' );

            if ( lpFirstBkSlash && lpFirstBkSlash[1] )
            {
                LPCWSTR lpLastBkSlash = wcschr( &lpFirstBkSlash[1], '\\' );

                if ( lpLastBkSlash )
                    rtl_uString_newFromStr_WithLength( &pStatus->ustrFileName, o3tl::toU(&lpFirstBkSlash[1]), lpLastBkSlash - lpFirstBkSlash - 1 );
                else
                    rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(&lpFirstBkSlash[1]) );
                pStatus->uValidFields |= osl_FileStatus_Mask_FileName;
            }
        }
        else switch ( GetDriveTypeW( cRoot ) )
        {
            case DRIVE_REMOTE:
            {
                WCHAR szBuffer[1024];
                DWORD const dwBufsizeConst = std::size(szBuffer);
                DWORD dwBufsize = dwBufsizeConst;

                DWORD dwResult = WNetGetConnectionW( cDrive, szBuffer, &dwBufsize );
                if ( NO_ERROR == dwResult )
                {
                    WCHAR szFileName[dwBufsizeConst + 16];

                    swprintf( szFileName, L"%s [%s]", cDrive, szBuffer );
                    rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(szFileName) );
                }
                else
                    rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(cDrive) );
            }
            pStatus->uValidFields |= osl_FileStatus_Mask_FileName;
            break;
            case DRIVE_FIXED:
            {
                WCHAR szVolumeNameBuffer[1024];
                DWORD const dwBufsizeConst = std::size(szVolumeNameBuffer);

                if ( GetVolumeInformationW( cRoot, szVolumeNameBuffer, dwBufsizeConst, nullptr, nullptr, nullptr, nullptr, 0 ) )
                {
                    WCHAR   szFileName[dwBufsizeConst + 16];

                    swprintf( szFileName, L"%s [%s]", cDrive, szVolumeNameBuffer );
                    rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(szFileName) );
                }
                else
                    rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(cDrive) );
            }
            pStatus->uValidFields |= osl_FileStatus_Mask_FileName;
            break;
            case DRIVE_CDROM:
            case DRIVE_REMOVABLE:
                pStatus->uValidFields |= osl_FileStatus_Mask_FileName;
                rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(cRoot) );
                break;
            case DRIVE_UNKNOWN:
            default:
                break;
        }
    }

    pStatus->eType = osl_File_Type_Volume;
    pStatus->uValidFields |= osl_FileStatus_Mask_Type;

    if ( uFieldMask & osl_FileStatus_Mask_FileURL )
    {
        rtl_uString *ustrSystemPath = nullptr;

        rtl_uString_newFromStr( &ustrSystemPath, o3tl::toU(pItemImpl->cDriveString) );
        oslFileError error = osl_getFileURLFromSystemPath( ustrSystemPath, &pStatus->ustrFileURL );
        rtl_uString_release( ustrSystemPath );
        if (error != osl_File_E_None)
            return error;
        pStatus->uValidFields |= osl_FileStatus_Mask_FileURL;
    }
    return osl_File_E_None;
}

static oslFileError osl_getServerInfo(
    oslDirectoryItem Item, oslFileStatus *pStatus, sal_uInt32 uFieldMask )
{
    DirectoryItem_Impl  *pItemImpl = static_cast<DirectoryItem_Impl *>(Item);
    if ( !pItemImpl )
        return osl_File_E_INVAL;

    pStatus->uValidFields = 0;
    pStatus->eType = osl_File_Type_Directory;
    pStatus->uValidFields |= osl_FileStatus_Mask_Type;

    if ( uFieldMask & osl_FileStatus_Mask_FileURL )
    {
        oslFileError error = osl_getFileURLFromSystemPath( pItemImpl->m_sFullPath.pData, &pStatus->ustrFileURL );
        if (error != osl_File_E_None)
            return error;
        pStatus->uValidFields |= osl_FileStatus_Mask_FileURL;
    }
    return osl_File_E_None;
}

oslFileError SAL_CALL osl_getFileStatus(
    oslDirectoryItem Item,
    oslFileStatus *pStatus,
    sal_uInt32 uFieldMask )
{
    DirectoryItem_Impl  *pItemImpl = static_cast<DirectoryItem_Impl *>(Item);

    if ( !pItemImpl )
        return osl_File_E_INVAL;

    switch ( pItemImpl->uType  )
    {
    case DIRECTORYITEM_DRIVE:
        return osl_getDriveInfo( Item, pStatus, uFieldMask );
    case DIRECTORYITEM_SERVER:
        return osl_getServerInfo( Item, pStatus, uFieldMask );
    default:
        break;
    }

    OUString sFullPath(pItemImpl->m_sFullPath);

    // Prefix long paths, windows API calls expect this prefix
    // (only local paths starting with something like C: or D:)
    if (sFullPath.getLength() >= MAX_PATH && isalpha(sFullPath[0]) && sFullPath[1] == ':')
        sFullPath = "\\\\?\\" + sFullPath;

    if ( uFieldMask & osl_FileStatus_Mask_Validate )
    {
        HANDLE  hFind = FindFirstFileW( o3tl::toW(sFullPath.getStr() ), &pItemImpl->FindData );

        if ( hFind != INVALID_HANDLE_VALUE )
            FindClose( hFind );
        else
            return oslTranslateFileError( GetLastError() );

        uFieldMask &= ~ osl_FileStatus_Mask_Validate;
    }

    /* If no fields to retrieve left ignore pStatus */
    if ( !uFieldMask )
        return osl_File_E_None;

    /* Otherwise, this must be a valid pointer */
    if ( !pStatus )
        return osl_File_E_INVAL;

    if ( pStatus->uStructSize != sizeof(oslFileStatus) )
        return osl_File_E_INVAL;

    pStatus->uValidFields = 0;

    /* File time stamps */

    if ( (uFieldMask & osl_FileStatus_Mask_ModifyTime) &&
        FileTimeToTimeValue( &pItemImpl->FindData.ftLastWriteTime, &pStatus->aModifyTime ) )
        pStatus->uValidFields |= osl_FileStatus_Mask_ModifyTime;

    if ( (uFieldMask & osl_FileStatus_Mask_AccessTime) &&
        FileTimeToTimeValue( &pItemImpl->FindData.ftLastAccessTime, &pStatus->aAccessTime ) )
        pStatus->uValidFields |= osl_FileStatus_Mask_AccessTime;

    if ( (uFieldMask & osl_FileStatus_Mask_CreationTime) &&
        FileTimeToTimeValue( &pItemImpl->FindData.ftCreationTime, &pStatus->aCreationTime ) )
        pStatus->uValidFields |= osl_FileStatus_Mask_CreationTime;

    /* Most of the fields are already set, regardless of required fields */

    rtl_uString_newFromStr( &pStatus->ustrFileName, o3tl::toU(pItemImpl->FindData.cFileName) );
    pStatus->uValidFields |= osl_FileStatus_Mask_FileName;

    if ((FILE_ATTRIBUTE_REPARSE_POINT & pItemImpl->FindData.dwFileAttributes) &&
        (IO_REPARSE_TAG_MOUNT_POINT == pItemImpl->FindData.dwReserved0))
        pStatus->eType = osl_File_Type_Volume;
    else if (pItemImpl->FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
        pStatus->eType = osl_File_Type_Directory;
    else
        pStatus->eType = osl_File_Type_Regular;

    pStatus->uValidFields |= osl_FileStatus_Mask_Type;

    pStatus->uAttributes = pItemImpl->FindData.dwFileAttributes;
    // tdf#157448: RO attribute is ignored for directories on Windows:
    // https://learn.microsoft.com/en-us/windows/desktop/FileIO/file-attribute-constants
    if (pStatus->uAttributes & FILE_ATTRIBUTE_DIRECTORY)
        pStatus->uAttributes &= ~sal_uInt64(FILE_ATTRIBUTE_READONLY);

    // tdf#150118: if there is no Read Only attribute set, lets check if the user has write access on the file
    if ( (pStatus->uAttributes & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_DIRECTORY)) == 0 )
    {
        HANDLE hProcessToken = nullptr;
        OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hProcessToken);

        HANDLE hImpersonationToken = nullptr;
        DuplicateToken(hProcessToken, SecurityImpersonation, &hImpersonationToken);

        PSECURITY_DESCRIPTOR pSD = nullptr;
        // https://learn.microsoft.com/en-us/windows/win32/api/aclapi/nf-aclapi-getnamedsecurityinfow
        DWORD aResult = GetNamedSecurityInfoW(
            o3tl::toW(sFullPath.getStr()), SE_FILE_OBJECT,
            OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
            nullptr, nullptr, nullptr, nullptr, &pSD);

        if (aResult == ERROR_SUCCESS)
        {
            GENERIC_MAPPING mapping
                = { FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE, FILE_ALL_ACCESS };
            DWORD grantedAccess = 0;
            BOOL accessStatus = TRUE;
            PRIVILEGE_SET privSet;
            DWORD privSetSize = sizeof(privSet);

            // https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-accesscheck
            BOOL bResult = AccessCheck(pSD, hImpersonationToken, FILE_GENERIC_WRITE, &mapping,
                                       &privSet, &privSetSize, &grantedAccess, &accessStatus);

            if (bResult)
            {
                if (!accessStatus)
                {
                    // User does NOT have write access: set Read Only attribute
                    pStatus->uAttributes |= sal_uInt64(FILE_ATTRIBUTE_READONLY);
                }
            }
            else
            {
                SAL_WARN("sal.osl", "AccessCheck API failed with: " << GetLastError());
            }
            LocalFree(pSD); // free memory
        }
        else
        {
            SAL_WARN("sal.osl", "GetNamedSecurityInfoW API failed with: " << aResult);
        }
        CloseHandle(hImpersonationToken); // free memory
        CloseHandle(hProcessToken); // free memory
    }

    pStatus->uValidFields |= osl_FileStatus_Mask_Attributes;

    pStatus->uFileSize = static_cast<sal_uInt64>(pItemImpl->FindData.nFileSizeLow) + (static_cast<sal_uInt64>(pItemImpl->FindData.nFileSizeHigh) << 32);
    pStatus->uValidFields |= osl_FileStatus_Mask_FileSize;

    if ( uFieldMask & osl_FileStatus_Mask_LinkTargetURL )
    {
        oslFileError error = osl_getFileURLFromSystemPath( sFullPath.pData, &pStatus->ustrLinkTargetURL );
        if (error != osl_File_E_None)
            return error;

        pStatus->uValidFields |= osl_FileStatus_Mask_LinkTargetURL;
    }

    if ( uFieldMask & osl_FileStatus_Mask_FileURL )
    {
        if ( !pItemImpl->bFullPathNormalized )
        {
            osl::LongPathBuffer<sal_Unicode> aBuffer(EXTENDED_MAX_PATH);
            sal_uInt32 nNewLen = GetLongPathNameW(o3tl::toW(sFullPath.getStr()), o3tl::toW(aBuffer),
                                                 aBuffer.getBufSizeInSymbols());

            if ( nNewLen )
            {
                /* Capitalizes drive name (single letter). Windows file paths are processed
                case-sensitively. While parsing a path, function osl_DirectoryItem has case
                PATHTYPE_VOLUME for drives, and capitalizes them. That can be overwritten by
                function osl_getFileStatus, in it win32 api GetLongPathNameW does no
                capitalization. Thus it needs to be postprocessed.*/
                sal_Int32 nIndex = rtl_ustr_indexOfChar(aBuffer, ':');
                if (nIndex > 0) {
                    aBuffer[nIndex - 1] = rtl::toAsciiUpperCase(aBuffer[nIndex - 1]);
                }

                pItemImpl->m_sFullPath = OUString(&*aBuffer, nNewLen);
                sFullPath = pItemImpl->m_sFullPath;
                pItemImpl->bFullPathNormalized = true;
            }
        }

        oslFileError error = osl_getFileURLFromSystemPath( sFullPath.pData, &pStatus->ustrFileURL );
        if (error != osl_File_E_None)
            return error;
        pStatus->uValidFields |= osl_FileStatus_Mask_FileURL;
    }

    return osl_File_E_None;
}

oslFileError SAL_CALL osl_setFileAttributes(
    rtl_uString *ustrFileURL,
    sal_uInt64 uAttributes )
{
    oslFileError    error;
    OUString ustrSysPath;
    DWORD           dwFileAttributes;
    bool            fSuccess;

    // Converts the normalized path into a systempath
    error = osl_getSystemPathFromFileURL_(OUString::unacquired(&ustrFileURL), &ustrSysPath.pData, false);

    if ( osl_File_E_None != error )
        return error;

    dwFileAttributes = GetFileAttributesW(o3tl::toW(ustrSysPath.getStr()));

    if ( DWORD(-1) != dwFileAttributes )
    {
        dwFileAttributes &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN);

        if ( uAttributes & osl_File_Attribute_ReadOnly )
            dwFileAttributes |= FILE_ATTRIBUTE_READONLY;

        if ( uAttributes & osl_File_Attribute_Hidden )
            dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;

        fSuccess = SetFileAttributesW(o3tl::toW(ustrSysPath.getStr()), dwFileAttributes);
    }
    else
    {
        fSuccess = false;
    }

    if ( !fSuccess )
        error = oslTranslateFileError( GetLastError() );

    return error;
}

oslFileError SAL_CALL osl_setFileTime(
    rtl_uString *filePath,
    const TimeValue *aCreationTime,
    const TimeValue *aLastAccessTime,
    const TimeValue *aLastWriteTime)
{
    oslFileError error;
    OUString sysPath;
    FILETIME *lpCreationTime=nullptr;
    FILETIME *lpLastAccessTime=nullptr;
    FILETIME *lpLastWriteTime=nullptr;
    FILETIME ftCreationTime;
    FILETIME ftLastAccessTime;
    FILETIME ftLastWriteTime;
    HANDLE hFile;
    bool fSuccess;

    error=osl_getSystemPathFromFileURL_(OUString::unacquired(&filePath), &sysPath.pData, false);

    if (error==osl_File_E_INVAL)
        return error;

    hFile=CreateFileW(o3tl::toW(sysPath.getStr()), GENERIC_WRITE, 0, nullptr , OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);

    if (hFile==INVALID_HANDLE_VALUE)
        return osl_File_E_NOENT;

    if (TimeValueToFileTime(aCreationTime, &ftCreationTime))
        lpCreationTime=&ftCreationTime;

    if (TimeValueToFileTime(aLastAccessTime, &ftLastAccessTime))
        lpLastAccessTime=&ftLastAccessTime;

    if (TimeValueToFileTime(aLastWriteTime, &ftLastWriteTime))
        lpLastWriteTime=&ftLastWriteTime;

    fSuccess=SetFileTime(hFile, lpCreationTime, lpLastAccessTime, lpLastWriteTime);

    CloseHandle(hFile);

    if (!fSuccess)
        return osl_File_E_INVAL;
    else
        return osl_File_E_None;
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
