1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_folders.h>
21 
22 #include <comphelper/lok.hxx>
23 #include <cppuhelper/basemutex.hxx>
24 #include <cppuhelper/compbase.hxx>
25 #include <cppuhelper/supportsservice.hxx>
26 
27 #include <unotools/bootstrap.hxx>
28 #include <unotools/configmgr.hxx>
29 #include <osl/file.hxx>
30 #include <osl/security.hxx>
31 #include <osl/thread.hxx>
32 #include <i18nlangtag/languagetag.hxx>
33 #include <tools/urlobj.hxx>
34 #include <rtl/ustrbuf.hxx>
35 #include <rtl/bootstrap.hxx>
36 #include <sal/log.hxx>
37 
38 #include <officecfg/Office/Paths.hxx>
39 
40 #include <com/sun/star/lang/XServiceInfo.hpp>
41 #include <com/sun/star/container/NoSuchElementException.hpp>
42 #include <com/sun/star/container/XHierarchicalNameAccess.hpp>
43 #include <com/sun/star/util/XStringSubstitution.hpp>
44 
45 #include <unordered_map>
46 
47 using namespace com::sun::star::uno;
48 using namespace com::sun::star::container;
49 
50 namespace {
51 
52 enum PreDefVariable
53 {
54     PREDEFVAR_INST,
55     PREDEFVAR_PROG,
56     PREDEFVAR_USER,
57     PREDEFVAR_WORK,
58     PREDEFVAR_HOME,
59     PREDEFVAR_TEMP,
60     PREDEFVAR_PATH,
61     PREDEFVAR_USERNAME,
62     PREDEFVAR_LANGID,
63     PREDEFVAR_VLANG,
64     PREDEFVAR_INSTPATH,
65     PREDEFVAR_PROGPATH,
66     PREDEFVAR_USERPATH,
67     PREDEFVAR_INSTURL,
68     PREDEFVAR_PROGURL,
69     PREDEFVAR_USERURL,
70     PREDEFVAR_WORKDIRURL,
71     // New variable of hierarchy service (#i32656#)
72     PREDEFVAR_BASEINSTURL,
73     PREDEFVAR_USERDATAURL,
74     PREDEFVAR_BRANDBASEURL,
75     PREDEFVAR_COUNT
76 };
77 
78 struct FixedVariable
79 {
80     const char*     pVarName;
81     bool            bAbsPath;
82 };
83 
84 // Table with all fixed/predefined variables supported.
85 static const FixedVariable aFixedVarTable[PREDEFVAR_COUNT] =
86 {
87     { "$(inst)",         true  }, // PREDEFVAR_INST
88     { "$(prog)",         true  }, // PREDEFVAR_PROG
89     { "$(user)",         true  }, // PREDEFVAR_USER
90     { "$(work)",         true  }, // PREDEFVAR_WORK, special variable
91                                   //  (transient)
92     { "$(home)",         true  }, // PREDEFVAR_HOME
93     { "$(temp)",         true  }, // PREDEFVAR_TEMP
94     { "$(path)",         true  }, // PREDEFVAR_PATH
95     { "$(username)",     false }, // PREDEFVAR_USERNAME
96     { "$(langid)",       false }, // PREDEFVAR_LANGID
97     { "$(vlang)",        false }, // PREDEFVAR_VLANG
98     { "$(instpath)",     true  }, // PREDEFVAR_INSTPATH
99     { "$(progpath)",     true  }, // PREDEFVAR_PROGPATH
100     { "$(userpath)",     true  }, // PREDEFVAR_USERPATH
101     { "$(insturl)",      true  }, // PREDEFVAR_INSTURL
102     { "$(progurl)",      true  }, // PREDEFVAR_PROGURL
103     { "$(userurl)",      true  }, // PREDEFVAR_USERURL
104     { "$(workdirurl)",   true  }, // PREDEFVAR_WORKDIRURL, special variable
105                                   //  (transient) and don't use for
106                                   //  resubstitution
107     { "$(baseinsturl)",  true  }, // PREDEFVAR_BASEINSTURL
108     { "$(userdataurl)",  true  }, // PREDEFVAR_USERDATAURL
109     { "$(brandbaseurl)", true  }  // PREDEFVAR_BRANDBASEURL
110 };
111 
112 struct PredefinedPathVariables
113 {
114     // Predefined variables supported by substitute variables
115     LanguageType    m_eLanguageType;               // Language type of Office
116     OUString   m_FixedVar[ PREDEFVAR_COUNT ];      // Variable value access by PreDefVariable
117     OUString   m_FixedVarNames[ PREDEFVAR_COUNT ]; // Variable name access by PreDefVariable
118 };
119 
120 struct ReSubstFixedVarOrder
121 {
122     sal_Int32       nVarValueLength;
123     PreDefVariable  eVariable;
124 
125     bool operator< ( const ReSubstFixedVarOrder& aFixedVarOrder ) const
126     {
127         // Reverse operator< to have high to low ordering
128         return ( nVarValueLength > aFixedVarOrder.nVarValueLength );
129     }
130 };
131 
132 typedef ::cppu::WeakComponentImplHelper<
133     css::util::XStringSubstitution,
134     css::lang::XServiceInfo > SubstitutePathVariables_BASE;
135 
136 class SubstitutePathVariables : private cppu::BaseMutex,
137                                 public SubstitutePathVariables_BASE
138 {
139 public:
140     explicit SubstitutePathVariables(const css::uno::Reference< css::uno::XComponentContext >& xContext);
141 
142     virtual OUString SAL_CALL getImplementationName() override
143     {
144         return OUString("com.sun.star.comp.framework.PathSubstitution");
145     }
146 
147     virtual sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override
148     {
149         return cppu::supportsService(this, ServiceName);
150     }
151 
152     virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override
153     {
154         return {"com.sun.star.util.PathSubstitution"};
155     }
156 
157     // XStringSubstitution
158     virtual OUString SAL_CALL substituteVariables( const OUString& aText, sal_Bool bSubstRequired ) override;
159     virtual OUString SAL_CALL reSubstituteVariables( const OUString& aText ) override;
160     virtual OUString SAL_CALL getSubstituteVariableValue( const OUString& variable ) override;
161 
162 protected:
163     void            SetPredefinedPathVariables();
164 
165     // Special case (transient) values can change during runtime!
166     // Don't store them in the pre defined struct
167     OUString   GetWorkPath() const;
168     OUString   GetWorkVariableValue() const;
169     OUString   GetPathVariableValue() const;
170 
171     OUString   GetHomeVariableValue() const;
172 
173     // XStringSubstitution implementation methods
174     /// @throws css::container::NoSuchElementException
175     /// @throws css::uno::RuntimeException
176     OUString impl_substituteVariable( const OUString& aText, bool bSustRequired );
177     /// @throws css::uno::RuntimeException
178     OUString impl_reSubstituteVariables( const OUString& aText );
179     /// @throws css::container::NoSuchElementException
180     /// @throws css::uno::RuntimeException
181     OUString const & impl_getSubstituteVariableValue( const OUString& variable );
182 
183 private:
184     typedef std::unordered_map<OUString, PreDefVariable>
185         VarNameToIndexMap;
186 
187     VarNameToIndexMap            m_aPreDefVarMap;         // Mapping from pre-def variable names to enum for array access
188     PredefinedPathVariables      m_aPreDefVars;           // All predefined variables
189     std::vector<ReSubstFixedVarOrder> m_aReSubstFixedVarOrder; // To speed up resubstitution fixed variables (order for lookup)
190     css::uno::Reference< css::uno::XComponentContext > m_xContext;
191 };
192 
193 SubstitutePathVariables::SubstitutePathVariables( const Reference< XComponentContext >& xContext ) :
194     SubstitutePathVariables_BASE(m_aMutex),
195     m_xContext( xContext )
196 {
197     SetPredefinedPathVariables();
198 
199     // Init the predefined/fixed variable to index hash map
200     for ( int i = 0; i < PREDEFVAR_COUNT; i++ )
201     {
202         // Store variable name into struct of predefined/fixed variables
203         m_aPreDefVars.m_FixedVarNames[i] = OUString::createFromAscii( aFixedVarTable[i].pVarName );
204 
205         // Create hash map entry
206         m_aPreDefVarMap.emplace( m_aPreDefVars.m_FixedVarNames[i], PreDefVariable(i) );
207     }
208 
209     // Sort predefined/fixed variable to path length
210     for ( int i = 0; i < PREDEFVAR_COUNT; i++ )
211     {
212         if (( i != PREDEFVAR_WORKDIRURL ) && ( i != PREDEFVAR_PATH ))
213         {
214             // Special path variables, don't include into automatic resubstitution search!
215             // $(workdirurl) is not allowed to resubstitute! This variable is the value of path settings entry
216             // and it could be possible that it will be resubstituted by itself!!
217             // Example: WORK_PATH=c:\test, $(workdirurl)=WORK_PATH => WORK_PATH=$(workdirurl) and this cannot be substituted!
218             ReSubstFixedVarOrder aFixedVar;
219             aFixedVar.eVariable       = PreDefVariable(i);
220             aFixedVar.nVarValueLength = m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(aFixedVar.eVariable)].getLength();
221             m_aReSubstFixedVarOrder.push_back( aFixedVar );
222         }
223     }
224     sort(m_aReSubstFixedVarOrder.begin(),m_aReSubstFixedVarOrder.end());
225 }
226 
227 // XStringSubstitution
228 OUString SAL_CALL SubstitutePathVariables::substituteVariables( const OUString& aText, sal_Bool bSubstRequired )
229 {
230     osl::MutexGuard g(rBHelper.rMutex);
231     return impl_substituteVariable( aText, bSubstRequired );
232 }
233 
234 OUString SAL_CALL SubstitutePathVariables::reSubstituteVariables( const OUString& aText )
235 {
236     osl::MutexGuard g(rBHelper.rMutex);
237     return impl_reSubstituteVariables( aText );
238 }
239 
240 OUString SAL_CALL SubstitutePathVariables::getSubstituteVariableValue( const OUString& aVariable )
241 {
242     osl::MutexGuard g(rBHelper.rMutex);
243     return impl_getSubstituteVariableValue( aVariable );
244 }
245 
246 OUString SubstitutePathVariables::GetWorkPath() const
247 {
248     OUString aWorkPath;
249     css::uno::Reference< css::container::XHierarchicalNameAccess > xPaths(officecfg::Office::Paths::Paths::get(m_xContext), css::uno::UNO_QUERY_THROW);
250     if (!(xPaths->getByHierarchicalName("['Work']/WritePath") >>= aWorkPath))
251         // fallback in case config layer does not return an usable work dir value.
252         aWorkPath = GetWorkVariableValue();
253 
254     return aWorkPath;
255 }
256 
257 OUString SubstitutePathVariables::GetWorkVariableValue() const
258 {
259     OUString aWorkPath;
260     boost::optional<OUString> x(officecfg::Office::Paths::Variables::Work::get(m_xContext));
261     if (!x)
262     {
263         // fallback to $HOME in case platform dependent config layer does not return
264         // an usable work dir value.
265         osl::Security aSecurity;
266         aSecurity.getHomeDir( aWorkPath );
267     }
268     else
269         aWorkPath = x.get();
270     return aWorkPath;
271 }
272 
273 OUString SubstitutePathVariables::GetHomeVariableValue() const
274 {
275     osl::Security   aSecurity;
276     OUString   aHomePath;
277 
278     aSecurity.getHomeDir( aHomePath );
279     return aHomePath;
280 }
281 
282 OUString SubstitutePathVariables::GetPathVariableValue() const
283 {
284     OUString aRetStr;
285     const char* pEnv = getenv( "PATH" );
286 
287     if ( pEnv )
288     {
289         const int PATH_EXTEND_FACTOR = 120;
290         OUString       aTmp;
291         OUString       aPathList( pEnv, strlen( pEnv ), osl_getThreadTextEncoding() );
292         OUStringBuffer aPathStrBuffer( aPathList.getLength() * PATH_EXTEND_FACTOR / 100 );
293 
294         bool      bAppendSep = false;
295         sal_Int32 nToken = 0;
296         do
297         {
298             OUString sToken = aPathList.getToken(0, SAL_PATHSEPARATOR, nToken);
299             if (!sToken.isEmpty() &&
300                 osl::FileBase::getFileURLFromSystemPath( sToken, aTmp ) ==
301                 osl::FileBase::RC::E_None )
302             {
303                 if ( bAppendSep )
304                     aPathStrBuffer.append( ";" ); // Office uses ';' as path separator
305                 aPathStrBuffer.append( aTmp );
306                 bAppendSep = true;
307             }
308         }
309         while(nToken>=0);
310 
311         aRetStr = aPathStrBuffer.makeStringAndClear();
312     }
313 
314     return aRetStr;
315 }
316 
317 OUString SubstitutePathVariables::impl_substituteVariable( const OUString& rText, bool bSubstRequired )
318 {
319     // This is maximal recursive depth supported!
320     const sal_Int32 nMaxRecursiveDepth = 8;
321 
322     OUString   aWorkText = rText;
323     OUString   aResult;
324 
325     // Use vector with strings to detect endless recursions!
326     std::vector< OUString > aEndlessRecursiveDetector;
327 
328     // Search for first occurrence of "$(...".
329     sal_Int32   nDepth = 0;
330     bool        bSubstitutionCompleted = false;
331     sal_Int32   nPosition = aWorkText.indexOf( "$(" );
332     sal_Int32   nLength = 0; // = count of letters from "$(" to ")" in string
333     bool        bVarNotSubstituted = false;
334 
335     // Have we found any variable like "$(...)"?
336     if ( nPosition != -1 )
337     {
338         // Yes; Get length of found variable.
339         // If no ")" was found - nLength is set to 0 by default! see before.
340         sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
341         if ( nEndPosition != -1 )
342             nLength = nEndPosition - nPosition + 1;
343     }
344 
345     // Is there something to replace ?
346     bool bWorkRetrieved       = false;
347     bool bWorkDirURLRetrieved = false;
348     while ( !bSubstitutionCompleted && nDepth < nMaxRecursiveDepth )
349     {
350         while ( ( nPosition != -1 ) && ( nLength > 3 ) ) // "$(" ")"
351         {
352             // YES; Get the next variable for replace.
353             sal_Int32     nReplaceLength  = 0;
354             OUString aReplacement;
355             OUString aSubString      = aWorkText.copy( nPosition, nLength );
356             OUString aSubVarString;
357 
358             // Path variables are not case sensitive!
359             aSubVarString = aSubString.toAsciiLowerCase();
360             VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( aSubVarString );
361             if ( pNTOIIter != m_aPreDefVarMap.end() )
362             {
363                 // Fixed/Predefined variable found
364                 PreDefVariable nIndex = pNTOIIter->second;
365 
366                 // Determine variable value and length from array/table
367                 if ( nIndex == PREDEFVAR_WORK && !bWorkRetrieved )
368                 {
369                     // Transient value, retrieve it again
370                     m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkVariableValue();
371                     bWorkRetrieved = true;
372                 }
373                 else if ( nIndex == PREDEFVAR_WORKDIRURL && !bWorkDirURLRetrieved )
374                 {
375                     // Transient value, retrieve it again
376                     m_aPreDefVars.m_FixedVar[ nIndex ] = GetWorkPath();
377                     bWorkDirURLRetrieved = true;
378                 }
379 
380                 // Check preconditions to substitute path variables.
381                 // 1. A path variable can only be substituted if it follows a ';'!
382                 // 2. It's located exactly at the start of the string being substituted!
383                 if (( aFixedVarTable[ int( nIndex ) ].bAbsPath && (( nPosition == 0 ) || (( nPosition > 0 ) && ( aWorkText[nPosition-1] == ';')))) ||
384                     ( !aFixedVarTable[ int( nIndex ) ].bAbsPath ))
385                 {
386                     aReplacement = m_aPreDefVars.m_FixedVar[ nIndex ];
387                     nReplaceLength = nLength;
388                 }
389             }
390 
391             // Have we found something to replace?
392             if ( nReplaceLength > 0 )
393             {
394                 // Yes ... then do it.
395                 aWorkText = aWorkText.replaceAt( nPosition, nReplaceLength, aReplacement );
396             }
397             else
398             {
399                 // Variable not known
400                 bVarNotSubstituted = true;
401                 nPosition += nLength;
402             }
403 
404             // Step after replaced text! If no text was replaced (unknown variable!),
405             // length of aReplacement is 0 ... and we don't step then.
406             nPosition += aReplacement.getLength();
407 
408             // We must control index in string before call something at OUString!
409             // The OUString-implementation don't do it for us :-( but the result is not defined otherwise.
410             if ( nPosition + 1 > aWorkText.getLength() )
411             {
412                 // Position is out of range. Break loop!
413                 nPosition = -1;
414                 nLength = 0;
415             }
416             else
417             {
418                 // Else; Position is valid. Search for next variable to replace.
419                 nPosition = aWorkText.indexOf( "$(", nPosition );
420                 // Have we found any variable like "$(...)"?
421                 if ( nPosition != -1 )
422                 {
423                     // Yes; Get length of found variable. If no ")" was found - nLength must set to 0!
424                     nLength = 0;
425                     sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
426                     if ( nEndPosition != -1 )
427                         nLength = nEndPosition - nPosition + 1;
428                 }
429             }
430         }
431 
432         nPosition = aWorkText.indexOf( "$(" );
433         if ( nPosition == -1 )
434         {
435             bSubstitutionCompleted = true;
436             break; // All variables are substituted
437         }
438         else
439         {
440             // Check for recursion
441             const sal_uInt32 nCount = aEndlessRecursiveDetector.size();
442             for ( sal_uInt32 i=0; i < nCount; i++ )
443             {
444                 if ( aEndlessRecursiveDetector[i] == aWorkText )
445                 {
446                     if ( bVarNotSubstituted )
447                         break; // Not all variables could be substituted!
448                     else
449                     {
450                         nDepth = nMaxRecursiveDepth;
451                         break; // Recursion detected!
452                     }
453                 }
454             }
455 
456             aEndlessRecursiveDetector.push_back( aWorkText );
457 
458             // Initialize values for next
459             sal_Int32 nEndPosition = aWorkText.indexOf( ')', nPosition );
460             if ( nEndPosition != -1 )
461                 nLength = nEndPosition - nPosition + 1;
462             bVarNotSubstituted = false;
463             ++nDepth;
464         }
465     }
466 
467     // Fill return value with result
468     if ( bSubstitutionCompleted )
469     {
470         // Substitution successful!
471         aResult = aWorkText;
472     }
473     else
474     {
475         // Substitution not successful!
476         if ( nDepth == nMaxRecursiveDepth )
477         {
478             // recursion depth reached!
479             if ( bSubstRequired )
480             {
481                 throw NoSuchElementException( "Endless recursion detected. Cannot substitute variables!", static_cast<cppu::OWeakObject *>(this) );
482             }
483             aResult = rText;
484         }
485         else
486         {
487             // variable in text but unknown!
488             if ( bSubstRequired )
489             {
490                 throw NoSuchElementException( "Unknown variable found!", static_cast<cppu::OWeakObject *>(this) );
491             }
492             aResult = aWorkText;
493         }
494     }
495 
496     return aResult;
497 }
498 
499 OUString SubstitutePathVariables::impl_reSubstituteVariables( const OUString& rURL )
500 {
501     OUString aURL;
502 
503     INetURLObject aUrl( rURL );
504     if ( !aUrl.HasError() )
505         aURL = aUrl.GetMainURL( INetURLObject::DecodeMechanism::NONE );
506     else
507     {
508         // Convert a system path to a UCB compliant URL before resubstitution
509         OUString aTemp;
510         if ( osl::FileBase::getFileURLFromSystemPath( rURL, aTemp ) == osl::FileBase::E_None )
511         {
512             aURL = INetURLObject( aTemp ).GetMainURL( INetURLObject::DecodeMechanism::NONE );
513             if( aURL.isEmpty() )
514                 return rURL;
515         }
516         else
517         {
518             // rURL is not a valid URL nor a osl system path. Give up and return error!
519             return rURL;
520         }
521     }
522 
523     // Get transient predefined path variable $(work) value before starting resubstitution
524     m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue();
525 
526     for (;;)
527     {
528         bool bVariableFound = false;
529 
530         for (auto const & i: m_aReSubstFixedVarOrder)
531         {
532             OUString aValue = m_aPreDefVars.m_FixedVar[i.eVariable];
533             sal_Int32 nPos = aURL.indexOf( aValue );
534             if ( nPos >= 0 )
535             {
536                 bool bMatch = true;
537                 if ( !aFixedVarTable[i.eVariable].bAbsPath )
538                 {
539                     // Special path variables as they can occur in the middle of a path. Only match if they
540                     // describe a whole directory and not only a substring of a directory!
541                     // (Ideally, all substitutions should stick to syntactical
542                     // boundaries within the given URL, like not covering only
543                     // part of a URL path segment; however, at least when saving
544                     // an Impress document, one URL that is passed in is of the
545                     // form <file:///.../share/palette%3Bfile:///.../user/
546                     // config/standard.sob>, re-substituted to
547                     // <$(inst)/share/palette%3B$(user)/config/standard.sob>.)
548                     const sal_Unicode* pStr = aURL.getStr();
549 
550                     if ( nPos > 0 )
551                         bMatch = ( aURL[ nPos-1 ] == '/' );
552 
553                     if ( bMatch )
554                     {
555                         if ( nPos + aValue.getLength() < aURL.getLength() )
556                             bMatch = ( pStr[ nPos + aValue.getLength() ] == '/' );
557                     }
558                 }
559 
560                 if ( bMatch )
561                 {
562                     aURL = aURL.replaceAt(
563                         nPos, aValue.getLength(),
564                         m_aPreDefVars.m_FixedVarNames[i.eVariable]);
565                     bVariableFound = true; // Resubstitution not finished yet!
566                     break;
567                 }
568             }
569         }
570 
571         if ( !bVariableFound )
572         {
573             return aURL;
574         }
575     }
576 }
577 
578 // This method support both request schemes "$("<varname>")" or "<varname>".
579 OUString const & SubstitutePathVariables::impl_getSubstituteVariableValue( const OUString& rVariable )
580 {
581     OUString aVariable;
582 
583     sal_Int32 nPos = rVariable.indexOf( "$(" );
584     if ( nPos == -1 )
585     {
586         // Prepare variable name before hash map access
587         aVariable = "$(" + rVariable + ")";
588     }
589 
590     VarNameToIndexMap::const_iterator pNTOIIter = m_aPreDefVarMap.find( ( nPos == -1 ) ? aVariable : rVariable );
591 
592     // Fixed/Predefined variable
593     if ( pNTOIIter == m_aPreDefVarMap.end() )
594     {
595         throw NoSuchElementException("Unknown variable!", static_cast<cppu::OWeakObject *>(this));
596     }
597     PreDefVariable nIndex = pNTOIIter->second;
598     return m_aPreDefVars.m_FixedVar[static_cast<sal_Int32>(nIndex)];
599 }
600 
601 void SubstitutePathVariables::SetPredefinedPathVariables()
602 {
603 
604     m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] = "$BRAND_BASE_DIR";
605     rtl::Bootstrap::expandMacros(
606         m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL]);
607 
608     // Get inspath and userpath from bootstrap mechanism in every case as file URL
609     ::utl::Bootstrap::PathStatus aState;
610     OUString              sVal;
611 
612     aState = utl::Bootstrap::locateUserData( sVal );
613     //There can be the valid case that there is no user installation.
614     //TODO: Is that still the case? (With OOo 3.4, "unopkg sync" was run as part
615     // of the setup. Then no user installation was required.)
616     //Therefore we do not assert here.
617     // It's not possible to detect when an empty value would actually be used.
618     // (note: getenv is a hack to detect if we're running in a unit test)
619     // Also, it's okay to have an empty user installation path in case of LOK
620     if (aState == ::utl::Bootstrap::PATH_EXISTS || getenv("SRC_ROOT") ||
621         (comphelper::LibreOfficeKit::isActive() && aState == ::utl::Bootstrap::PATH_VALID))
622     {
623         m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ] = sVal;
624     }
625 
626     // Set $(inst), $(instpath), $(insturl)
627     m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ] = m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL];
628     m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTURL ]    = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
629     m_aPreDefVars.m_FixedVar[ PREDEFVAR_INST ]       = m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
630     // New variable of hierarchy service (#i32656#)
631     m_aPreDefVars.m_FixedVar[ PREDEFVAR_BASEINSTURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_INSTPATH ];
632 
633     // Set $(user), $(userpath), $(userurl)
634     m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERURL ]    = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
635     m_aPreDefVars.m_FixedVar[ PREDEFVAR_USER ]       = m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
636     // New variable of hierarchy service (#i32656#)
637     m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERDATAURL ]= m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERPATH ];
638 
639     // Detect the program directory
640     // Set $(prog), $(progpath), $(progurl)
641     INetURLObject aProgObj(
642         m_aPreDefVars.m_FixedVar[PREDEFVAR_BRANDBASEURL] );
643     if ( !aProgObj.HasError() && aProgObj.insertName( LIBO_BIN_FOLDER ) )
644     {
645         m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ] = aProgObj.GetMainURL(INetURLObject::DecodeMechanism::NONE);
646         m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGURL ]  = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ];
647         m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROG ]     = m_aPreDefVars.m_FixedVar[ PREDEFVAR_PROGPATH ];
648     }
649 
650     // Set $(username)
651     OUString aSystemUser;
652     ::osl::Security aSecurity;
653     aSecurity.getUserName( aSystemUser, false );
654     m_aPreDefVars.m_FixedVar[ PREDEFVAR_USERNAME ]   = aSystemUser;
655 
656     // Detect the language type of the current office
657     m_aPreDefVars.m_eLanguageType = LANGUAGE_ENGLISH_US;
658     OUString aLocaleStr( utl::ConfigManager::getUILocale() );
659     m_aPreDefVars.m_eLanguageType = LanguageTag::convertToLanguageTypeWithFallback( aLocaleStr );
660     // We used to have an else branch here with a SAL_WARN, but that
661     // always fired in some unit tests when this code was built with
662     // debug=t, so it seems fairly pointless, especially as
663     // m_aPreDefVars.m_eLanguageType has been initialized to a
664     // default value above anyway.
665 
666     // Set $(vlang)
667     m_aPreDefVars.m_FixedVar[ PREDEFVAR_VLANG ] = aLocaleStr;
668 
669     // Set $(langid)
670     m_aPreDefVars.m_FixedVar[ PREDEFVAR_LANGID ] = OUString::number( static_cast<sal_uInt16>(m_aPreDefVars.m_eLanguageType) );
671 
672     // Set the other pre defined path variables
673     // Set $(work)
674     m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORK ] = GetWorkVariableValue();
675     m_aPreDefVars.m_FixedVar[ PREDEFVAR_HOME ] = GetHomeVariableValue();
676 
677     // Set $(workdirurl) this is the value of the path PATH_WORK which doesn't make sense
678     // anymore because the path settings service has this value! It can deliver this value more
679     // quickly than the substitution service!
680     m_aPreDefVars.m_FixedVar[ PREDEFVAR_WORKDIRURL ] = GetWorkPath();
681 
682     // Set $(path) variable
683     m_aPreDefVars.m_FixedVar[ PREDEFVAR_PATH ] = GetPathVariableValue();
684 
685     // Set $(temp)
686     OUString aTmp;
687     osl::FileBase::getTempDirURL( aTmp );
688     m_aPreDefVars.m_FixedVar[ PREDEFVAR_TEMP ] = aTmp;
689 }
690 
691 struct Instance {
692     explicit Instance(
693         css::uno::Reference<css::uno::XComponentContext> const & context):
694         instance(
695             static_cast<cppu::OWeakObject *>(new SubstitutePathVariables(context)))
696     {
697     }
698 
699     css::uno::Reference<css::uno::XInterface> instance;
700 };
701 
702 struct Singleton:
703     public rtl::StaticWithArg<
704         Instance, css::uno::Reference<css::uno::XComponentContext>, Singleton>
705 {};
706 
707 }
708 
709 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface *
710 com_sun_star_comp_framework_PathSubstitution_get_implementation(
711     css::uno::XComponentContext *context,
712     css::uno::Sequence<css::uno::Any> const &)
713 {
714     return cppu::acquire(static_cast<cppu::OWeakObject *>(
715                 Singleton::get(context).instance.get()));
716 }
717 
718 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
719