/* -*- 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 <config_folders.h>
 
#include <comphelper/lok.hxx>
#include <cppuhelper/basemutex.hxx>
#include <cppuhelper/compbase.hxx>
#include <cppuhelper/supportsservice.hxx>
 
#include <unotools/bootstrap.hxx>
#include <unotools/configmgr.hxx>
#include <osl/file.hxx>
#include <osl/security.hxx>
#include <osl/thread.hxx>
#include <i18nlangtag/languagetag.hxx>
#include <tools/urlobj.hxx>
#include <rtl/ustrbuf.hxx>
#include <rtl/bootstrap.hxx>
#include <sal/log.hxx>
 
#include <officecfg/Office/Paths.hxx>
 
#include <com/sun/star/lang/XServiceInfo.hpp>
#include <com/sun/star/container/NoSuchElementException.hpp>
#include <com/sun/star/container/XHierarchicalNameAccess.hpp>
#include <com/sun/star/util/XStringSubstitution.hpp>
 
#include <unordered_map>
 
using namespace com::sun::star::uno;
using namespace com::sun::star::container;
 
namespace {
 
enum PreDefVariable
{
    PREDEFVAR_INST,
    PREDEFVAR_PROG,
    PREDEFVAR_USER,
    PREDEFVAR_WORK,
    PREDEFVAR_HOME,
    PREDEFVAR_TEMP,
    PREDEFVAR_PATH,
    PREDEFVAR_USERNAME,
    PREDEFVAR_LANGID,
    PREDEFVAR_VLANG,
    PREDEFVAR_INSTPATH,
    PREDEFVAR_PROGPATH,
    PREDEFVAR_USERPATH,
    PREDEFVAR_INSTURL,
    PREDEFVAR_PROGURL,
    PREDEFVAR_USERURL,
    PREDEFVAR_WORKDIRURL,
    // New variable of hierarchy service (#i32656#)
    PREDEFVAR_BASEINSTURL,
    PREDEFVAR_USERDATAURL,
    PREDEFVAR_BRANDBASEURL,
    PREDEFVAR_COUNT
};
 
struct FixedVariable
{
    const char*     pVarName;
    bool            bAbsPath;
};
 
// Table with all fixed/predefined variables supported.
static const FixedVariable aFixedVarTable[PREDEFVAR_COUNT] =
{
    { "$(inst)",         true  }, // PREDEFVAR_INST
    { "$(prog)",         true  }, // PREDEFVAR_PROG
    { "$(user)",         true  }, // PREDEFVAR_USER
    { "$(work)",         true  }, // PREDEFVAR_WORK, special variable
                                  //  (transient)
    { "$(home)",         true  }, // PREDEFVAR_HOME
    { "$(temp)",         true  }, // PREDEFVAR_TEMP
    { "$(path)",         true  }, // PREDEFVAR_PATH
    { "$(username)",     false }, // PREDEFVAR_USERNAME
    { "$(langid)",       false }, // PREDEFVAR_LANGID
    { "$(vlang)",        false }, // PREDEFVAR_VLANG
    { "$(instpath)",     true  }, // PREDEFVAR_INSTPATH
    { "$(progpath)",     true  }, // PREDEFVAR_PROGPATH
    { "$(userpath)",     true  }, // PREDEFVAR_USERPATH
    { "$(insturl)",      true  }, // PREDEFVAR_INSTURL
    { "$(progurl)",      true  }, // PREDEFVAR_PROGURL
    { "$(userurl)",      true  }, // PREDEFVAR_USERURL
    { "$(workdirurl)",   true  }, // PREDEFVAR_WORKDIRURL, special variable
                                  //  (transient) and don't use for
                                  //  resubstitution
    { "$(baseinsturl)",  true  }, // PREDEFVAR_BASEINSTURL
    { "$(userdataurl)",  true  }, // PREDEFVAR_USERDATAURL
    { "$(brandbaseurl)", true  }  // PREDEFVAR_BRANDBASEURL
};
 
struct PredefinedPathVariables
{
    // Predefined variables supported by substitute variables
    LanguageType    m_eLanguageType;               // Language type of Office
    OUString   m_FixedVar[ PREDEFVAR_COUNT ];      // Variable value access by PreDefVariable
    OUString   m_FixedVarNames[ PREDEFVAR_COUNT ]; // Variable name access by PreDefVariable
};
 
struct ReSubstFixedVarOrder
{
    sal_Int32       nVarValueLength;
    PreDefVariable  eVariable;
 
    bool operator< ( const ReSubstFixedVarOrder& aFixedVarOrder ) const
    {
        // Reverse operator< to have high to low ordering
        return ( nVarValueLength > aFixedVarOrder.nVarValueLength );
    }
};
 
typedef ::cppu::WeakComponentImplHelper<
    css::util::XStringSubstitution,
    css::lang::XServiceInfo > SubstitutePathVariables_BASE;
 
class SubstitutePathVariables : private cppu::BaseMutex,
                                public SubstitutePathVariables_BASE
{
public:
    explicit SubstitutePathVariables(const css::uno::Reference< css::uno::XComponentContext >& xContext);
 
    virtual OUString SAL_CALL getImplementationName() override
    {
        return OUString("com.sun.star.comp.framework.PathSubstitution");
    }
 
    virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
    {
        return cppu::supportsService(this, ServiceName);
    }
 
    virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
    {
        return {"com.sun.star.util.PathSubstitution"};
    }
 
    // XStringSubstitution
    virtual OUString SAL_CALL substituteVariables( const OUString& aText, sal_Bool bSubstRequired ) override;
    virtual OUString SAL_CALL reSubstituteVariables( const OUString& aText ) override;
    virtual OUString SAL_CALL getSubstituteVariableValue( const OUString& variable ) override;
 
protected:
    void            SetPredefinedPathVariables();
 
    // Special case (transient) values can change during runtime!
    // Don't store them in the pre defined struct
    OUString   GetWorkPath() const;
    OUString   GetWorkVariableValue() const;
    OUString   GetPathVariableValue() const;
 
    OUString   GetHomeVariableValue() const;
 
    // XStringSubstitution implementation methods
    /// @throws css::container::NoSuchElementException
    /// @throws css::uno::RuntimeException
    OUString impl_substituteVariable( const OUString& aText, bool bSustRequired );
    /// @throws css::uno::RuntimeException
    OUString impl_reSubstituteVariables( const OUString& aText );
    /// @throws css::container::NoSuchElementException
    /// @throws css::uno::RuntimeException
    OUString const & impl_getSubstituteVariableValue( const OUString& variable );
 
private:
    typedef std::unordered_map<OUString, PreDefVariable>
        VarNameToIndexMap;
 
    VarNameToIndexMap            m_aPreDefVarMap;         // Mapping from pre-def variable names to enum for array access
    PredefinedPathVariables      m_aPreDefVars;           // All predefined variables
    std::vector<ReSubstFixedVarOrder> m_aReSubstFixedVarOrder; // To speed up resubstitution fixed variables (order for lookup)
    css::uno::Reference< css::uno::XComponentContext > m_xContext;
};
 
SubstitutePathVariables::SubstitutePathVariables( const Reference< XComponentContext >& xContext ) :
    SubstitutePathVariables_BASE(m_aMutex),
    m_xContext( xContext )
{
    SetPredefinedPathVariables();
 
    // Init the predefined/fixed variable to index hash map
    for ( int i = 0; i < PREDEFVAR_COUNT; i++ )
    {
        // Store variable name into struct of predefined/fixed variables
        m_aPreDefVars.m_FixedVarNames[i] = OUString::createFromAscii( aFixedVarTable[i].pVarName );
 
        // Create hash map entry
        m_aPreDefVarMap.emplace( m_aPreDefVars.m_FixedVarNames[i], PreDefVariable(i) );
    }
 
    // Sort predefined/fixed variable to path length
    for ( int i = 0; i < PREDEFVAR_COUNT; i++ )
    {
        if (( i != PREDEFVAR_WORKDIRURL ) && ( i != PREDEFVAR_PATH ))
        {
            // Special path variables, don't include into automatic resubstitution search!
            // $(workdirurl) is not allowed to resubstitute! This variable is the value of path settings entry
            // and it could be possible that it will be resubstituted by itself!!
            // Example: WORK_PATH=c:\test, $(workdirurl)=WORK_PATH => WORK_PATH=$(workdirurl) and this cannot be substituted!
            ReSubstFixedVarOrder aFixedVar;
            aFixedVar.eVariable       = PreDefVariable(i);
            aFixedVar.nVarValueLength = m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(aFixedVar.eVariable)].getLength();
            m_aReSubstFixedVarOrder.push_back( aFixedVar );
        }
    }
    sort(m_aReSubstFixedVarOrder.begin(),m_aReSubstFixedVarOrder.end());
}
 
// XStringSubstitution
OUString SAL_CALL SubstitutePathVariables::substituteVariables( const OUString& aText, sal_Bool bSubstRequired )
{
    osl::MutexGuard g(rBHelper.rMutex);
    return impl_substituteVariable( aText, bSubstRequired );
}
 
OUString SAL_CALL SubstitutePathVariables::reSubstituteVariables( const OUString& aText )
{
    osl::MutexGuard g(rBHelper.rMutex);
    return impl_reSubstituteVariables( aText );
}
 
OUString SAL_CALL SubstitutePathVariables::getSubstituteVariableValue( const OUString& aVariable )
{
    osl::MutexGuard g(rBHelper.rMutex);
    return impl_getSubstituteVariableValue( aVariable );
}
 
OUString SubstitutePathVariables::GetWorkPath() const
{
    OUString aWorkPath;
    css::uno::Reference< css::container::XHierarchicalNameAccess > xPaths(officecfg::Office::Paths::Paths::get(m_xContext), css::uno::UNO_QUERY_THROW);
    if (!(xPaths->getByHierarchicalName("['Work']/WritePath") >>= aWorkPath))
        // fallback in case config layer does not return an usable work dir value.
        aWorkPath = GetWorkVariableValue();
 
    return aWorkPath;
}
 
OUString SubstitutePathVariables::GetWorkVariableValue() const
{
    OUString aWorkPath;
    boost::optional<OUString> x(officecfg::Office::Paths::Variables::Work::get(m_xContext));
    if (!x)
    {
        // fallback to $HOME in case platform dependent config layer does not return
        // an usable work dir value.
        osl::Security aSecurity;
        aSecurity.getHomeDir( aWorkPath );
    }
    else
        aWorkPath = x.get();
    return aWorkPath;
}
 
OUString SubstitutePathVariables::GetHomeVariableValue() const
{
    osl::Security   aSecurity;
    OUString   aHomePath;
 
    aSecurity.getHomeDir( aHomePath );
    return aHomePath;
}
 
OUString SubstitutePathVariables::GetPathVariableValue() const
{
    OUString aRetStr;
    const char* pEnv = getenv( "PATH" );
 
    if ( pEnv )
    {
        const int PATH_EXTEND_FACTOR = 120;
        OUString       aTmp;
        OUString       aPathList( pEnv, strlen( pEnv ), osl_getThreadTextEncoding() );
        OUStringBuffer aPathStrBuffer( aPathList.getLength() * PATH_EXTEND_FACTOR / 100 );
 
        bool      bAppendSep = false;
        sal_Int32 nToken = 0;
        do
        {
            OUString sToken = aPathList.getToken(0, SAL_PATHSEPARATOR, nToken);
            if (!sToken.isEmpty() &&
                osl::FileBase::getFileURLFromSystemPath( sToken, aTmp ) ==
                osl::FileBase::RC::E_None )
            {
                if ( bAppendSep )
                    aPathStrBuffer.append( ";" ); // Office uses ';' as path separator
                aPathStrBuffer.append( aTmp );
                bAppendSep = true;
            }
        }
        while(nToken>=0);
 
        aRetStr = aPathStrBuffer.makeStringAndClear();
    }
 
    return aRetStr;
}
 
OUString SubstitutePathVariables::impl_substituteVariable( const OUString& rText, bool bSubstRequired )
{
    // This is maximal recursive depth supported!
    const sal_Int32 nMaxRecursiveDepth = 8;
 
    OUString   aWorkText = rText;
    OUString   aResult;
 
    // Use vector with strings to detect endless recursions!
    std::vector< OUString > aEndlessRecursiveDetector;
 
    // Search for first occurrence of "$(...".
    sal_Int32   nDepth = 0;
    bool        bSubstitutionCompleted = false;
    sal_Int32   nPosition = aWorkText.indexOf( "$(" );
    sal_Int32   nLength = 0; // = count of letters from "$(" to ")" in string
    bool        bVarNotSubstituted = false;
 
    // Have we found any variable like "$(...)"?
    if ( nPosition != -1 )
    {
        // Yes; Get length of found variable.
        // If no ")" was found - nLength is set to 0 by default! see before.
        sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
        if ( nEndPosition != -1 )
            nLength = nEndPosition - nPosition + 1;
    }
 
    // Is there something to replace ?
    bool bWorkRetrieved       = false;
    bool bWorkDirURLRetrieved = false;
    while ( !bSubstitutionCompleted && nDepth < nMaxRecursiveDepth )
    {
        while ( ( nPosition != -1 ) && ( nLength > 3 ) ) // "$(" ")"
        {
            // YES; Get the next variable for replace.
            sal_Int32     nReplaceLength  = 0;
            OUString aReplacement;
            OUString aSubString      = aWorkText.copy( nPosition, nLength );
            OUString aSubVarString;
 
            // Path variables are not case sensitive!
            aSubVarString = aSubString.toAsciiLowerCase();
            VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( aSubVarString );
            if ( pNTOIIter != m_aPreDefVarMap.end() )
            {
                // Fixed/Predefined variable found
                PreDefVariable nIndex = pNTOIIter->second;
 
                // Determine variable value and length from array/table
                if ( nIndex == PREDEFVAR_WORK && !bWorkRetrieved )
                {
                    // Transient value, retrieve it again
                    m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkVariableValue();
                    bWorkRetrieved = true;
                }
                else if ( nIndex == PREDEFVAR_WORKDIRURL && !bWorkDirURLRetrieved )
                {
                    // Transient value, retrieve it again
                    m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkPath();
                    bWorkDirURLRetrieved = true;
                }
 
                // Check preconditions to substitute path variables.
                // 1. A path variable can only be substituted if it follows a ';'!
                // 2. It's located exactly at the start of the string being substituted!
                if (( aFixedVarTable[ int( nIndex ) ].bAbsPath && (( nPosition == 0 ) || (( nPosition > 0 ) && ( aWorkText[nPosition-1] == ';')))) ||
                    ( !aFixedVarTable[ int( nIndex ) ].bAbsPath ))
                {
                    aReplacement = m_aPreDefVars.m_FixedVar[ nIndex ];
                    nReplaceLength = nLength;
                }
            }
 
            // Have we found something to replace?
            if ( nReplaceLength > 0 )
            {
                // Yes ... then do it.
                aWorkText = aWorkText.replaceAt( nPosition, nReplaceLength, aReplacement );
            }
            else
            {
                // Variable not known
                bVarNotSubstituted = true;
                nPosition += nLength;
            }
 
            // Step after replaced text! If no text was replaced (unknown variable!),
            // length of aReplacement is 0 ... and we don't step then.
            nPosition += aReplacement.getLength();
 
            // We must control index in string before call something at OUString!
            // The OUString-implementation don't do it for us :-( but the result is not defined otherwise.
            if ( nPosition + 1 > aWorkText.getLength() )
            {
                // Position is out of range. Break loop!
                nPosition = -1;
                nLength = 0;
            }
            else
            {
                // Else; Position is valid. Search for next variable to replace.
                nPosition = aWorkText.indexOf( "$(", nPosition );
                // Have we found any variable like "$(...)"?
                if ( nPosition != -1 )
                {
                    // Yes; Get length of found variable. If no ")" was found - nLength must set to 0!
                    nLength = 0;
                    sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
                    if ( nEndPosition != -1 )
                        nLength = nEndPosition - nPosition + 1;
                }
            }
        }
 
        nPosition = aWorkText.indexOf( "$(" );
        if ( nPosition == -1 )
        {
            bSubstitutionCompleted = true;
            break; // All variables are substituted
        }
        else
        {
            // Check for recursion
            const sal_uInt32 nCount = aEndlessRecursiveDetector.size();
            for ( sal_uInt32 i=0; i < nCount; i++ )
            {
                if ( aEndlessRecursiveDetector[i] == aWorkText )
                {
                    if ( bVarNotSubstituted )
                        break; // Not all variables could be substituted!
                    else
                    {
                        nDepth = nMaxRecursiveDepth;
                        break; // Recursion detected!
                    }
                }
            }
 
            aEndlessRecursiveDetector.push_back( aWorkText );
 
            // Initialize values for next
            sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
            if ( nEndPosition != -1 )
                nLength = nEndPosition - nPosition + 1;
            bVarNotSubstituted = false;
            ++nDepth;
        }
    }
 
    // Fill return value with result
    if ( bSubstitutionCompleted )
    {
        // Substitution successful!
        aResult = aWorkText;
    }
    else
    {
        // Substitution not successful!
        if ( nDepth == nMaxRecursiveDepth )
        {
            // recursion depth reached!
            if ( bSubstRequired )
            {
                throw NoSuchElementException( "Endless recursion detected. Cannot substitute variables!", static_cast<cppu::OWeakObject *>(this) );
            }
            aResult = rText;
        }
        else
        {
            // variable in text but unknown!
            if ( bSubstRequired )
            {
                throw NoSuchElementException( "Unknown variable found!", static_cast<cppu::OWeakObject *>(this) );
            }
            aResult = aWorkText;
        }
    }
 
    return aResult;
}
 
OUString SubstitutePathVariables::impl_reSubstituteVariables( const OUString& rURL )
{
    OUString aURL;
 
    INetURLObject aUrl( rURL );
    if ( !aUrl.HasError() )
        aURL = aUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE );
    else
    {
        // Convert a system path to a UCB compliant URL before resubstitution
        OUString aTemp;
        if ( osl::FileBase::getFileURLFromSystemPath( rURL, aTemp ) == osl::FileBase::E_None )
        {
            aURL = INetURLObject( aTemp ).GetMainURL( INetURLObject::DecodeMechanism::NONE );
            if( aURL.isEmpty() )
                return rURL;
        }
        else
        {
            // rURL is not a valid URL nor a osl system path. Give up and return error!
            return rURL;
        }
    }
 
    // Get transient predefined path variable $(work) value before starting resubstitution
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue();
 
    for (;;)
    {
        bool bVariableFound = false;
 
        for (auto const & i: m_aReSubstFixedVarOrder)
        {
            OUString aValue = m_aPreDefVars.m_FixedVar[i.eVariable];
            sal_Int32 nPos = aURL.indexOf( aValue );
            if ( nPos >= 0 )
            {
                bool bMatch = true;
                if ( !aFixedVarTable[i.eVariable].bAbsPath )
                {
                    // Special path variables as they can occur in the middle of a path. Only match if they
                    // describe a whole directory and not only a substring of a directory!
                    // (Ideally, all substitutions should stick to syntactical
                    // boundaries within the given URL, like not covering only
                    // part of a URL path segment; however, at least when saving
                    // an Impress document, one URL that is passed in is of the
                    // form <file:///.../share/palette%3Bfile:///.../user/
                    // config/standard.sob>, re-substituted to
                    // <$(inst)/share/palette%3B$(user)/config/standard.sob>.)
                    const sal_Unicode* pStr = aURL.getStr();
 
                    if ( nPos > 0 )
                        bMatch = ( aURL[ nPos-1 ] == '/' );
 
                    if ( bMatch )
                    {
                        if ( nPos + aValue.getLength() < aURL.getLength() )
                            bMatch = ( pStr[ nPos + aValue.getLength() ] == '/' );
                    }
                }
 
                if ( bMatch )
                {
                    aURL = aURL.replaceAt(
                        nPos, aValue.getLength(),
                        m_aPreDefVars.m_FixedVarNames[i.eVariable]);
                    bVariableFound = true; // Resubstitution not finished yet!
                    break;
                }
            }
        }
 
        if ( !bVariableFound )
        {
            return aURL;
        }
    }
}
 
// This method support both request schemes "$("<varname>")" or "<varname>".
OUString const & SubstitutePathVariables::impl_getSubstituteVariableValue( const OUString& rVariable )
{
    OUString aVariable;
 
    sal_Int32 nPos = rVariable.indexOf( "$(" );
    if ( nPos == -1 )
    {
        // Prepare variable name before hash map access
        aVariable = "$(" + rVariable + ")";
    }
 
    VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( ( nPos == -1 ) ? aVariable : rVariable );
 
    // Fixed/Predefined variable
    if ( pNTOIIter == m_aPreDefVarMap.end() )
    {
        throw NoSuchElementException("Unknown variable!", static_cast<cppu::OWeakObject *>(this));
    }
    PreDefVariable nIndex = pNTOIIter->second;
    return m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(nIndex)];
}
 
void SubstitutePathVariables::SetPredefinedPathVariables()
{
 
    m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] = "$BRAND_BASE_DIR";
    rtl::Bootstrap::expandMacros(
        m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL]);
 
    // Get inspath and userpath from bootstrap mechanism in every case as file URL
    ::utl::Bootstrap::PathStatus aState;
    OUString              sVal;
 
    aState = utl::Bootstrap::locateUserData( sVal );
    //There can be the valid case that there is no user installation.
    //TODO: Is that still the case? (With OOo 3.4, "unopkg sync" was run as part
    // of the setup. Then no user installation was required.)
    //Therefore we do not assert here.
    // It's not possible to detect when an empty value would actually be used.
    // (note: getenv is a hack to detect if we're running in a unit test)
    // Also, it's okay to have an empty user installation path in case of LOK
    if (aState == ::utl::Bootstrap::PATH_EXISTS || getenv("SRC_ROOT") ||
        (comphelper::LibreOfficeKit::isActive() && aState == ::utl::Bootstrap::PATH_VALID))
    {
        m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ] = sVal;
    }
 
    // Set $(inst), $(instpath), $(insturl)
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ] = m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL];
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTURL ]    = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_INST ]       = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
    // New variable of hierarchy service (#i32656#)
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_BASEINSTURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
 
    // Set $(user), $(userpath), $(userurl)
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERURL ]    = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_USER ]       = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
    // New variable of hierarchy service (#i32656#)
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERDATAURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
 
    // Detect the program directory
    // Set $(prog), $(progpath), $(progurl)
    INetURLObject aProgObj(
        m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] );
    if ( !aProgObj.HasError() && aProgObj.insertName( LIBO_BIN_FOLDER ) )
    {
        m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ] = aProgObj.GetMainURL(INetURLObject::DecodeMechanism::NONE);
        m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGURL ]  = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ];
        m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROG ]     = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ];
    }
 
    // Set $(username)
    OUString aSystemUser;
    ::osl::Security aSecurity;
    aSecurity.getUserName( aSystemUser, false );
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERNAME ]   = aSystemUser;
 
    // Detect the language type of the current office
    m_aPreDefVars.m_eLanguageType = LANGUAGE_ENGLISH_US;
    OUString aLocaleStr( utl::ConfigManager::getLocale() );
    m_aPreDefVars.m_eLanguageType = LanguageTag::convertToLanguageTypeWithFallback( aLocaleStr );
    // We used to have an else branch here with a SAL_WARN, but that
    // always fired in some unit tests when this code was built with
    // debug=t, so it seems fairly pointless, especially as
    // m_aPreDefVars.m_eLanguageType has been initialized to a
    // default value above anyway.
 
    // Set $(vlang)
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_VLANG ] = aLocaleStr;
 
    // Set $(langid)
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_LANGID ] = OUString::number( static_cast<sal_uInt16>(m_aPreDefVars.m_eLanguageType) );
 
    // Set the other pre defined path variables
    // Set $(work)
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue();
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_HOME ] = GetHomeVariableValue();
 
    // Set $(workdirurl) this is the value of the path PATH_WORK which doesn't make sense
    // anymore because the path settings service has this value! It can deliver this value more
    // quickly than the substitution service!
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORKDIRURL ] = GetWorkPath();
 
    // Set $(path) variable
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_PATH ] = GetPathVariableValue();
 
    // Set $(temp)
    OUString aTmp;
    osl::FileBase::getTempDirURL( aTmp );
    m_aPreDefVars.m_FixedVar[ PREDEFVAR_TEMP ] = aTmp;
}
 
struct Instance {
    explicit Instance(
        css::uno::Reference<css::uno::XComponentContext> const & context):
        instance(
            static_cast<cppu::OWeakObject *>(new SubstitutePathVariables(context)))
    {
    }
 
    css::uno::Reference<css::uno::XInterface> instance;
};
 
struct Singleton:
    public rtl::StaticWithArg<
        Instance, css::uno::Reference<css::uno::XComponentContext>, Singleton>
{};
 
}
 
extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
com_sun_star_comp_framework_PathSubstitution_get_implementation(
    css::uno::XComponentContext *context,
    css::uno::Sequence<css::uno::Any> const &)
{
    return cppu::acquire(static_cast<cppu::OWeakObject *>(
                Singleton::get(context).instance.get()));
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V560 A part of conditional expression is always true: !bSubstitutionCompleted.