/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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 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 VarNameToIndexMap; VarNameToIndexMap m_aPreDefVarMap; // Mapping from pre-def variable names to enum for array access PredefinedPathVariables m_aPreDefVars; // All predefined variables std::vector 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(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 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(this) ); } aResult = rText; } else { // variable in text but unknown! if ( bSubstRequired ) { throw NoSuchElementException( "Unknown variable found!", static_cast(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 , 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 "$("")" or "". 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(this)); } PreDefVariable nIndex = pNTOIIter->second; return m_aPreDefVars.m_FixedVar[static_cast(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(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 const & context): instance( static_cast(new SubstitutePathVariables(context))) { } css::uno::Reference instance; }; struct Singleton: public rtl::StaticWithArg< Instance, css::uno::Reference, Singleton> {}; } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface * com_sun_star_comp_framework_PathSubstitution_get_implementation( css::uno::XComponentContext *context, css::uno::Sequence const &) { return cppu::acquire(static_cast( Singleton::get(context).instance.get())); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */