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 #include <config_features.h>
20 #include <config_folders.h>
21 
22 #include <rtl/bootstrap.h>
23 #include <rtl/bootstrap.hxx>
24 #include <osl/diagnose.h>
25 #include <osl/module.h>
26 #include <osl/process.h>
27 #include <osl/file.hxx>
28 #include <osl/mutex.hxx>
29 #include <osl/profile.hxx>
30 #include <osl/security.hxx>
31 #include <rtl/alloc.h>
32 #include <rtl/string.hxx>
33 #include <rtl/ustrbuf.hxx>
34 #include <rtl/ustring.hxx>
35 #include <rtl/byteseq.hxx>
36 #include <rtl/instance.hxx>
37 #include <rtl/malformeduriexception.hxx>
38 #include <rtl/uri.hxx>
39 #include <sal/log.hxx>
40 
41 #include <vector>
42 #include <algorithm>
43 #include <unordered_map>
44 
45 #ifdef ANDROID
46 #include <osl/detail/android-bootstrap.h>
47 #endif
48 
49 #ifdef IOS
50 #include <premac.h>
51 #import <Foundation/Foundation.h>
52 #include <postmac.h>
53 #endif
54 
55 using osl::DirectoryItem;
56 using osl::FileStatus;
57 
58 namespace
59 {
60 
61 struct Bootstrap_Impl;
62 
63 char const VND_SUN_STAR_PATHNAME[] = "vnd.sun.star.pathname:";
64 
isPathnameUrl(OUString const & url)65 bool isPathnameUrl(OUString const & url)
66 {
67     return url.matchIgnoreAsciiCase(VND_SUN_STAR_PATHNAME);
68 }
69 
resolvePathnameUrl(OUString * url)70 bool resolvePathnameUrl(OUString * url)
71 {
72     OSL_ASSERT(url);
73     if (!isPathnameUrl(*url) ||
74         (osl::FileBase::getFileURLFromSystemPath(
75             url->copy(RTL_CONSTASCII_LENGTH(VND_SUN_STAR_PATHNAME)), *url) ==
76          osl::FileBase::E_None))
77     {
78         return true;
79     }
80     *url = OUString();
81     return false;
82 }
83 
84 enum LookupMode {
85     LOOKUP_MODE_NORMAL, LOOKUP_MODE_URE_BOOTSTRAP,
86     LOOKUP_MODE_URE_BOOTSTRAP_EXPANSION };
87 
88 struct ExpandRequestLink {
89     ExpandRequestLink const * next;
90     Bootstrap_Impl const * file;
91     OUString key;
92 };
93 
94 OUString expandMacros(
95     Bootstrap_Impl const * file, OUString const & text, LookupMode mode,
96     ExpandRequestLink const * requestStack);
97 
recursivelyExpandMacros(Bootstrap_Impl const * file,OUString const & text,LookupMode mode,Bootstrap_Impl const * requestFile,OUString const & requestKey,ExpandRequestLink const * requestStack)98 OUString recursivelyExpandMacros(
99     Bootstrap_Impl const * file, OUString const & text, LookupMode mode,
100     Bootstrap_Impl const * requestFile, OUString const & requestKey,
101     ExpandRequestLink const * requestStack)
102 {
103     for (; requestStack; requestStack = requestStack->next) {
104         if (requestStack->file == requestFile &&
105             requestStack->key == requestKey)
106         {
107             return "***RECURSION DETECTED***";
108         }
109     }
110     ExpandRequestLink link = { requestStack, requestFile, requestKey };
111     return expandMacros(file, text, mode, &link);
112 }
113 
114 struct rtl_bootstrap_NameValue
115 {
116     OUString sName;
117     OUString sValue;
118 
rtl_bootstrap_NameValue__anon7f121f7c0111::rtl_bootstrap_NameValue119     rtl_bootstrap_NameValue()
120         {}
rtl_bootstrap_NameValue__anon7f121f7c0111::rtl_bootstrap_NameValue121     rtl_bootstrap_NameValue(OUString const & name, OUString const & value )
122         : sName( name ),
123           sValue( value )
124         {}
125 };
126 
127 } // end namespace
128 
129 typedef std::vector<rtl_bootstrap_NameValue> NameValueVector;
130 
find(NameValueVector const & vector,OUString const & key,OUString * value)131 static bool find(
132     NameValueVector const & vector, OUString const & key,
133     OUString * value)
134 {
135     OSL_ASSERT(value);
136     auto i = std::find_if(vector.begin(), vector.end(),
137         [&key](const rtl_bootstrap_NameValue& rNameValue) { return rNameValue.sName == key; });
138     if (i != vector.end())
139     {
140         *value = i->sValue;
141         return true;
142     }
143     return false;
144 }
145 
146 namespace
147 {
148     struct rtl_bootstrap_set_vector :
149         public rtl::Static< NameValueVector, rtl_bootstrap_set_vector > {};
150 }
151 
getFromCommandLineArgs(OUString const & key,OUString * value)152 static bool getFromCommandLineArgs(
153     OUString const & key, OUString * value )
154 {
155     OSL_ASSERT(value);
156 
157     static NameValueVector nameValueVector = [&]()
158     {
159         NameValueVector tmp;
160 
161         sal_Int32 nArgCount = osl_getCommandArgCount();
162         for(sal_Int32 i = 0; i < nArgCount; ++ i)
163         {
164             OUString pArg;
165             osl_getCommandArg( i, &pArg.pData );
166             if( (pArg.startsWith("-") || pArg.startsWith("/") ) &&
167                 pArg.match("env:", 1) )
168             {
169                 sal_Int32 nIndex = pArg.indexOf( '=' );
170 
171                 if( nIndex >= 0 )
172                 {
173                     rtl_bootstrap_NameValue nameValue;
174                     nameValue.sName = pArg.copy( 5, nIndex - 5  );
175                     nameValue.sValue = pArg.copy( nIndex+1 );
176 
177                     if( i == nArgCount-1 &&
178                         nameValue.sValue.getLength() &&
179                         nameValue.sValue[nameValue.sValue.getLength()-1] == 13 )
180                     {
181                         // avoid the 13 linefeed for the last argument,
182                         // when the executable is started from a script,
183                         // that was edited on windows
184                         nameValue.sValue = nameValue.sValue.copy(0,nameValue.sValue.getLength()-1);
185                     }
186 
187                     tmp.push_back( nameValue );
188                 }
189             }
190         };
191         return tmp;
192     }();
193 
194     return find(nameValueVector, key, value);
195 }
196 
getExecutableDirectory_Impl(rtl_uString ** ppDirURL)197 static void getExecutableDirectory_Impl(rtl_uString ** ppDirURL)
198 {
199     OUString fileName;
200     osl_getExecutableFile(&(fileName.pData));
201 
202     sal_Int32 nDirEnd = fileName.lastIndexOf('/');
203     OSL_ENSURE(nDirEnd >= 0, "Cannot locate executable directory");
204 
205     rtl_uString_newFromStr_WithLength(ppDirURL,fileName.getStr(),nDirEnd);
206 }
207 
getIniFileName_Impl()208 static OUString & getIniFileName_Impl()
209 {
210     static OUString aStaticName = []() {
211         OUString fileName;
212 
213 #if defined IOS
214         // On iOS hardcode the inifile as "rc" in the .app
215         // directory. Apps are self-contained anyway, there is no
216         // possibility to have several "applications" in the same
217         // installation location with different inifiles.
218         const char *inifile = [[@"vnd.sun.star.pathname:" stringByAppendingString: [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"rc"]] UTF8String];
219         fileName = OUString(inifile, strlen(inifile), RTL_TEXTENCODING_UTF8);
220         resolvePathnameUrl(&fileName);
221 #elif defined ANDROID
222         // Apps are self-contained on Android, too, can as well hardcode
223         // it as "rc" in the "/assets" directory, i.e.  inside the app's
224         // .apk (zip) archive as the /assets/rc file.
225         fileName = OUString("vnd.sun.star.pathname:/assets/rc");
226         resolvePathnameUrl(&fileName);
227 #else
228         if (getFromCommandLineArgs("INIFILENAME", &fileName))
229         {
230             resolvePathnameUrl(&fileName);
231         }
232         else
233         {
234             osl_getExecutableFile(&(fileName.pData));
235 
236             // get rid of a potential executable extension
237             OUString progExt = ".bin";
238             if (fileName.getLength() > progExt.getLength()
239                 && fileName.copy(fileName.getLength() - progExt.getLength()).equalsIgnoreAsciiCase(progExt))
240             {
241                 fileName = fileName.copy(0, fileName.getLength() - progExt.getLength());
242             }
243 
244             progExt = ".exe";
245             if (fileName.getLength() > progExt.getLength()
246                 && fileName.copy(fileName.getLength() - progExt.getLength()).equalsIgnoreAsciiCase(progExt))
247             {
248                 fileName = fileName.copy(0, fileName.getLength() - progExt.getLength());
249             }
250 
251             // append config file suffix
252             fileName += SAL_CONFIGFILE("");
253 
254 #ifdef MACOSX
255             // We keep only executables in the MacOS folder, and all
256             // rc files in LIBO_ETC_FOLDER (typically "Resources").
257             sal_Int32 off = fileName.lastIndexOf( "/MacOS/" );
258             if (off != -1)
259                 fileName = fileName.replaceAt(off + 1, strlen("MacOS"), LIBO_ETC_FOLDER);
260 #endif
261         }
262 #endif
263 
264         return fileName;
265     }();
266 
267     return aStaticName;
268 }
269 
270 // ensure the given file url has no final slash
271 
EnsureNoFinalSlash(OUString & url)272 static void EnsureNoFinalSlash (OUString & url)
273 {
274     sal_Int32 i = url.getLength();
275 
276     if (i > 0 && url[i - 1] == '/')
277         url = url.copy(0, i - 1);
278 }
279 
280 namespace {
281 
282 struct Bootstrap_Impl
283 {
284     sal_Int32 _nRefCount;
285     Bootstrap_Impl * _base_ini;
286 
287     NameValueVector _nameValueVector;
288     OUString      _iniName;
289 
290     explicit Bootstrap_Impl (OUString const & rIniName);
291     ~Bootstrap_Impl();
292 
operator new__anon7f121f7c0611::Bootstrap_Impl293     static void * operator new (std::size_t n)
294         { return malloc (sal_uInt32(n)); }
operator delete__anon7f121f7c0611::Bootstrap_Impl295     static void operator delete (void * p , std::size_t)
296         { free (p); }
297 
298     bool getValue(
299         OUString const & key, rtl_uString ** value,
300         rtl_uString * defaultValue, LookupMode mode, bool override,
301         ExpandRequestLink const * requestStack) const;
302     bool getDirectValue(
303         OUString const & key, rtl_uString ** value, LookupMode mode,
304         ExpandRequestLink const * requestStack) const;
305     bool getAmbienceValue(
306         OUString const & key, rtl_uString ** value, LookupMode mode,
307         ExpandRequestLink const * requestStack) const;
308     void expandValue(
309         rtl_uString ** value, OUString const & text, LookupMode mode,
310         Bootstrap_Impl const * requestFile, OUString const & requestKey,
311         ExpandRequestLink const * requestStack) const;
312 };
313 
314 }
315 
Bootstrap_Impl(OUString const & rIniName)316 Bootstrap_Impl::Bootstrap_Impl( OUString const & rIniName )
317     : _nRefCount( 0 ),
318       _base_ini( nullptr ),
319       _iniName (rIniName)
320 {
321     OUString base_ini(getIniFileName_Impl());
322     // normalize path
323     FileStatus status( osl_FileStatus_Mask_FileURL );
324     DirectoryItem dirItem;
325     if (DirectoryItem::get(base_ini, dirItem) == DirectoryItem::E_None &&
326         dirItem.getFileStatus(status) == DirectoryItem::E_None)
327     {
328         base_ini = status.getFileURL();
329         if (rIniName != base_ini)
330         {
331             _base_ini = static_cast< Bootstrap_Impl * >(
332                 rtl_bootstrap_args_open(base_ini.pData));
333         }
334     }
335     SAL_INFO("sal.bootstrap", "Bootstrap_Impl(): sFile=" << _iniName);
336     oslFileHandle handle;
337     if (!_iniName.isEmpty() &&
338         osl_openFile(_iniName.pData, &handle, osl_File_OpenFlag_Read) == osl_File_E_None)
339     {
340         rtl::ByteSequence seq;
341 
342         while (osl_readLine(handle , reinterpret_cast<sal_Sequence **>(&seq)) == osl_File_E_None)
343         {
344             OString line(reinterpret_cast<const char *>(seq.getConstArray()), seq.getLength());
345             sal_Int32 nIndex = line.indexOf('=');
346             if (nIndex >= 1)
347             {
348                 struct rtl_bootstrap_NameValue nameValue;
349                 nameValue.sName = OStringToOUString(line.copy(0,nIndex).trim(), RTL_TEXTENCODING_ASCII_US);
350                 nameValue.sValue = OStringToOUString(line.copy(nIndex+1).trim(), RTL_TEXTENCODING_UTF8);
351 
352                 SAL_INFO("sal.bootstrap", "pushing: name=" << nameValue.sName << " value=" << nameValue.sValue);
353 
354                 _nameValueVector.push_back(nameValue);
355             }
356         }
357         osl_closeFile(handle);
358     }
359     else
360     {
361         SAL_INFO( "sal.bootstrap", "couldn't open file: " <<  _iniName );
362     }
363 }
364 
~Bootstrap_Impl()365 Bootstrap_Impl::~Bootstrap_Impl()
366 {
367     if (_base_ini)
368         rtl_bootstrap_args_close( _base_ini );
369 }
370 
371 namespace {
372 
get_static_bootstrap_handle()373 Bootstrap_Impl * get_static_bootstrap_handle()
374 {
375     static Bootstrap_Impl* s_handle = []() {
376         OUString iniName(getIniFileName_Impl());
377         Bootstrap_Impl* that = static_cast<Bootstrap_Impl*>(rtl_bootstrap_args_open(iniName.pData));
378         if (!that)
379         {
380             that = new Bootstrap_Impl(iniName);
381             ++that->_nRefCount;
382         }
383         return that;
384     }();
385 
386     return s_handle;
387 }
388 
389 struct FundamentalIniData
390 {
391     rtlBootstrapHandle ini;
392 
FundamentalIniData__anon7f121f7c0711::FundamentalIniData393     FundamentalIniData()
394     {
395         OUString uri;
396         ini =
397             (get_static_bootstrap_handle()->getValue(
398                 "URE_BOOTSTRAP", &uri.pData, nullptr, LOOKUP_MODE_NORMAL, false,
399                 nullptr)
400              && resolvePathnameUrl(&uri))
401             ? rtl_bootstrap_args_open(uri.pData) : nullptr;
402     }
403 
~FundamentalIniData__anon7f121f7c0711::FundamentalIniData404     ~FundamentalIniData() { rtl_bootstrap_args_close(ini); }
405 
406     FundamentalIniData(const FundamentalIniData&) = delete;
407     FundamentalIniData& operator=(const FundamentalIniData&) = delete;
408 };
409 
410 struct FundamentalIni: public rtl::Static< FundamentalIniData, FundamentalIni >
411 {};
412 
413 }
414 
getValue(OUString const & key,rtl_uString ** value,rtl_uString * defaultValue,LookupMode mode,bool override,ExpandRequestLink const * requestStack) const415 bool Bootstrap_Impl::getValue(
416     OUString const & key, rtl_uString ** value, rtl_uString * defaultValue,
417     LookupMode mode, bool override, ExpandRequestLink const * requestStack)
418     const
419 {
420     if (mode == LOOKUP_MODE_NORMAL && key == "URE_BOOTSTRAP")
421         mode = LOOKUP_MODE_URE_BOOTSTRAP;
422 
423     if (override && getDirectValue(key, value, mode, requestStack))
424         return true;
425 
426     if (key == "_OS")
427     {
428         rtl_uString_assign(
429             value, OUString(RTL_OS).pData);
430         return true;
431     }
432 
433     if (key == "_ARCH")
434     {
435         rtl_uString_assign(
436             value, OUString(RTL_ARCH).pData);
437         return true;
438     }
439 
440     if (key == "_CPPU_ENV")
441     {
442         rtl_uString_assign(
443             value,
444             (OUString(
445                 SAL_STRINGIFY(CPPU_ENV)).
446              pData));
447         return true;
448     }
449 
450 #ifdef ANDROID
451     if (key == "APP_DATA_DIR")
452     {
453         const char *app_data_dir = lo_get_app_data_dir();
454         rtl_uString_assign(
455             value, OUString(app_data_dir, strlen(app_data_dir), RTL_TEXTENCODING_UTF8).pData);
456         return true;
457     }
458 #endif
459 
460 #ifdef IOS
461     if (key == "APP_DATA_DIR")
462     {
463         const char *app_data_dir = [[[[NSBundle mainBundle] bundlePath] stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLPathAllowedCharacterSet]] UTF8String];
464         rtl_uString_assign(
465             value, OUString(app_data_dir, strlen(app_data_dir), RTL_TEXTENCODING_UTF8).pData);
466         return true;
467     }
468 #endif
469 
470     if (key == "ORIGIN")
471     {
472         rtl_uString_assign(
473             value,
474             _iniName.copy(
475                 0, std::max<sal_Int32>(0, _iniName.lastIndexOf('/'))).pData);
476         return true;
477     }
478 
479     if (getAmbienceValue(key, value, mode, requestStack))
480         return true;
481 
482     if (key == "SYSUSERCONFIG")
483     {
484         OUString v;
485         bool b = osl::Security().getConfigDir(v);
486         EnsureNoFinalSlash(v);
487         rtl_uString_assign(value, v.pData);
488         return b;
489     }
490 
491     if (key == "SYSUSERHOME")
492     {
493         OUString v;
494         bool b = osl::Security().getHomeDir(v);
495         EnsureNoFinalSlash(v);
496         rtl_uString_assign(value, v.pData);
497         return b;
498     }
499 
500     if (key == "SYSBINDIR")
501     {
502         getExecutableDirectory_Impl(value);
503         return true;
504     }
505 
506     if (_base_ini != nullptr && _base_ini->getDirectValue(key, value, mode, requestStack))
507         return true;
508 
509     if (!override && getDirectValue(key, value, mode, requestStack))
510         return true;
511 
512     if (mode == LOOKUP_MODE_NORMAL)
513     {
514         FundamentalIniData const & d = FundamentalIni::get();
515         Bootstrap_Impl const * b = static_cast<Bootstrap_Impl const *>(d.ini);
516         if (b != nullptr && b != this && b->getDirectValue(key, value, mode, requestStack))
517             return true;
518     }
519 
520     if (defaultValue != nullptr)
521     {
522         rtl_uString_assign(value, defaultValue);
523         return true;
524     }
525 
526     rtl_uString_new(value);
527     return false;
528 }
529 
getDirectValue(OUString const & key,rtl_uString ** value,LookupMode mode,ExpandRequestLink const * requestStack) const530 bool Bootstrap_Impl::getDirectValue(
531     OUString const & key, rtl_uString ** value, LookupMode mode,
532     ExpandRequestLink const * requestStack) const
533 {
534     OUString v;
535     if (find(_nameValueVector, key, &v))
536     {
537         expandValue(value, v, mode, this, key, requestStack);
538         return true;
539     }
540 
541     return false;
542 }
543 
getAmbienceValue(OUString const & key,rtl_uString ** value,LookupMode mode,ExpandRequestLink const * requestStack) const544 bool Bootstrap_Impl::getAmbienceValue(
545     OUString const & key, rtl_uString ** value, LookupMode mode,
546     ExpandRequestLink const * requestStack) const
547 {
548     OUString v;
549     bool f;
550 
551     {
552         osl::MutexGuard g(osl::Mutex::getGlobalMutex());
553         f = find(rtl_bootstrap_set_vector::get(), key, &v);
554     }
555 
556     if (f || getFromCommandLineArgs(key, &v) ||
557         osl_getEnvironment(key.pData, &v.pData) == osl_Process_E_None)
558     {
559         expandValue(value, v, mode, nullptr, key, requestStack);
560         return true;
561     }
562 
563     return false;
564 }
565 
expandValue(rtl_uString ** value,OUString const & text,LookupMode mode,Bootstrap_Impl const * requestFile,OUString const & requestKey,ExpandRequestLink const * requestStack) const566 void Bootstrap_Impl::expandValue(
567     rtl_uString ** value, OUString const & text, LookupMode mode,
568     Bootstrap_Impl const * requestFile, OUString const & requestKey,
569     ExpandRequestLink const * requestStack) const
570 {
571     rtl_uString_assign(
572         value,
573         (mode == LOOKUP_MODE_URE_BOOTSTRAP && isPathnameUrl(text) ?
574          text :
575          recursivelyExpandMacros(
576              this, text,
577              (mode == LOOKUP_MODE_URE_BOOTSTRAP ?
578               LOOKUP_MODE_URE_BOOTSTRAP_EXPANSION : mode),
579              requestFile, requestKey, requestStack)).pData);
580 }
581 
582 namespace {
583 
584 // This map must only be called properly synchronized via some mutex
585 // (e.g., osl::Mutex::getGlobalMutex()):
586 typedef std::unordered_map< OUString, Bootstrap_Impl * > bootstrap_map_t;
587 bootstrap_map_t bootstrap_map;
588 
589 }
590 
rtl_bootstrap_args_open(rtl_uString * pIniName)591 rtlBootstrapHandle SAL_CALL rtl_bootstrap_args_open(rtl_uString * pIniName)
592 {
593     OUString iniName( pIniName );
594 
595     // normalize path
596     FileStatus status(osl_FileStatus_Mask_FileURL);
597     DirectoryItem dirItem;
598     if (DirectoryItem::get(iniName, dirItem) != DirectoryItem::E_None ||
599         dirItem.getFileStatus(status) != DirectoryItem::E_None)
600     {
601         return nullptr;
602     }
603 
604     iniName = status.getFileURL();
605 
606     Bootstrap_Impl * that;
607     osl::ResettableMutexGuard guard(osl::Mutex::getGlobalMutex());
608     auto iFind(bootstrap_map.find(iniName));
609     if (iFind == bootstrap_map.end())
610     {
611         guard.clear();
612         that = new Bootstrap_Impl(iniName);
613         guard.reset();
614         iFind = bootstrap_map.find(iniName);
615         if (iFind == bootstrap_map.end())
616         {
617             ++that->_nRefCount;
618             ::std::pair< bootstrap_map_t::iterator, bool > insertion(
619                 bootstrap_map.emplace(iniName, that));
620             OSL_ASSERT(insertion.second);
621         }
622         else
623         {
624             Bootstrap_Impl * obsolete = that;
625             that = iFind->second;
626             ++that->_nRefCount;
627             guard.clear();
628             delete obsolete;
629         }
630     }
631     else
632     {
633         that = iFind->second;
634         ++that->_nRefCount;
635     }
636     return static_cast< rtlBootstrapHandle >( that );
637 }
638 
rtl_bootstrap_args_close(rtlBootstrapHandle handle)639 void SAL_CALL rtl_bootstrap_args_close(rtlBootstrapHandle handle) SAL_THROW_EXTERN_C()
640 {
641     if (!handle)
642         return;
643 
644     Bootstrap_Impl * that = static_cast< Bootstrap_Impl * >( handle );
645 
646     osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
647     OSL_ASSERT(bootstrap_map.find(that->_iniName)->second == that);
648     --that->_nRefCount;
649 
650     if (that->_nRefCount != 0)
651         return;
652 
653     std::size_t const nLeaking = 8; // only hold up to 8 files statically
654     if (bootstrap_map.size() > nLeaking)
655     {
656         ::std::size_t erased = bootstrap_map.erase( that->_iniName );
657         if (erased != 1) {
658             OSL_ASSERT( false );
659         }
660         delete that;
661     }
662 }
663 
rtl_bootstrap_get_from_handle(rtlBootstrapHandle handle,rtl_uString * pName,rtl_uString ** ppValue,rtl_uString * pDefault)664 sal_Bool SAL_CALL rtl_bootstrap_get_from_handle(
665     rtlBootstrapHandle handle,
666     rtl_uString      * pName,
667     rtl_uString     ** ppValue,
668     rtl_uString      * pDefault
669 )
670 {
671     osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
672 
673     bool found = false;
674     if(ppValue && pName)
675     {
676         if (!handle)
677             handle = get_static_bootstrap_handle();
678 
679         found = static_cast< Bootstrap_Impl * >(handle)->getValue(
680             pName, ppValue, pDefault, LOOKUP_MODE_NORMAL, false, nullptr );
681     }
682 
683     return found;
684 }
685 
rtl_bootstrap_get_iniName_from_handle(rtlBootstrapHandle handle,rtl_uString ** ppIniName)686 void SAL_CALL rtl_bootstrap_get_iniName_from_handle (
687     rtlBootstrapHandle handle,
688     rtl_uString     ** ppIniName
689 )
690 {
691     if(!ppIniName)
692         return;
693 
694     if(handle)
695     {
696         Bootstrap_Impl * pImpl = static_cast<Bootstrap_Impl*>(handle);
697         rtl_uString_assign(ppIniName, pImpl->_iniName.pData);
698     }
699     else
700     {
701         const OUString & iniName = getIniFileName_Impl();
702         rtl_uString_assign(ppIniName, iniName.pData);
703     }
704 }
705 
rtl_bootstrap_setIniFileName(rtl_uString * pName)706 void SAL_CALL rtl_bootstrap_setIniFileName (
707     rtl_uString * pName
708 )
709 {
710     osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
711     OUString & file = getIniFileName_Impl();
712     file = pName;
713 }
714 
rtl_bootstrap_get(rtl_uString * pName,rtl_uString ** ppValue,rtl_uString * pDefault)715 sal_Bool SAL_CALL rtl_bootstrap_get (
716     rtl_uString  * pName,
717     rtl_uString ** ppValue,
718     rtl_uString  * pDefault
719 )
720 {
721     return rtl_bootstrap_get_from_handle(nullptr, pName, ppValue, pDefault);
722 }
723 
rtl_bootstrap_set(rtl_uString * pName,rtl_uString * pValue)724 void SAL_CALL rtl_bootstrap_set (
725     rtl_uString * pName,
726     rtl_uString * pValue
727 )
728 {
729     const OUString name(pName);
730     const OUString value(pValue);
731 
732     osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
733 
734     NameValueVector& r_rtl_bootstrap_set_vector= rtl_bootstrap_set_vector::get();
735     for (auto & item : r_rtl_bootstrap_set_vector)
736     {
737         if (item.sName == name)
738         {
739             item.sValue = value;
740             return;
741         }
742     }
743 
744     SAL_INFO("sal.bootstrap", "explicitly getting: name=" << name << " value=" <<value);
745 
746     r_rtl_bootstrap_set_vector.emplace_back(name, value);
747 }
748 
rtl_bootstrap_expandMacros_from_handle(rtlBootstrapHandle handle,rtl_uString ** macro)749 void SAL_CALL rtl_bootstrap_expandMacros_from_handle(
750     rtlBootstrapHandle handle,
751     rtl_uString     ** macro)
752 {
753     if (!handle)
754         handle = get_static_bootstrap_handle();
755 
756     OUString expanded(expandMacros(static_cast< Bootstrap_Impl * >(handle),
757                                    OUString::unacquired(macro),
758                                    LOOKUP_MODE_NORMAL, nullptr));
759     rtl_uString_assign(macro, expanded.pData);
760 }
761 
rtl_bootstrap_expandMacros(rtl_uString ** macro)762 void SAL_CALL rtl_bootstrap_expandMacros(rtl_uString ** macro)
763 {
764     rtl_bootstrap_expandMacros_from_handle(nullptr, macro);
765 }
766 
rtl_bootstrap_encode(rtl_uString const * value,rtl_uString ** encoded)767 void rtl_bootstrap_encode(rtl_uString const * value, rtl_uString ** encoded)
768 {
769     OSL_ASSERT(value);
770     OUStringBuffer b(value->length+5);
771     for (sal_Int32 i = 0; i < value->length; ++i)
772     {
773         sal_Unicode c = value->buffer[i];
774         if (c == '$' || c == '\\')
775             b.append('\\');
776 
777         b.append(c);
778     }
779 
780     rtl_uString_assign(encoded, b.makeStringAndClear().pData);
781 }
782 
783 namespace {
784 
hex(sal_Unicode c)785 int hex(sal_Unicode c)
786 {
787     return
788         c >= '0' && c <= '9' ? c - '0' :
789         c >= 'A' && c <= 'F' ? c - 'A' + 10 :
790         c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
791 }
792 
read(OUString const & text,sal_Int32 * pos,bool * escaped)793 sal_Unicode read(OUString const & text, sal_Int32 * pos, bool * escaped)
794 {
795     OSL_ASSERT(pos && *pos >= 0 && *pos < text.getLength() && escaped);
796     sal_Unicode c = text[(*pos)++];
797     if (c == '\\')
798     {
799         int n1, n2, n3, n4;
800         if (*pos < text.getLength() - 4 && text[*pos] == 'u' &&
801             ((n1 = hex(text[*pos + 1])) >= 0) &&
802             ((n2 = hex(text[*pos + 2])) >= 0) &&
803             ((n3 = hex(text[*pos + 3])) >= 0) &&
804             ((n4 = hex(text[*pos + 4])) >= 0))
805         {
806             *pos += 5;
807             *escaped = true;
808             return static_cast< sal_Unicode >(
809                 (n1 << 12) | (n2 << 8) | (n3 << 4) | n4);
810         }
811 
812         if (*pos < text.getLength())
813         {
814             *escaped = true;
815             return text[(*pos)++];
816         }
817     }
818 
819     *escaped = false;
820     return c;
821 }
822 
lookup(Bootstrap_Impl const * file,LookupMode mode,bool override,OUString const & key,ExpandRequestLink const * requestStack)823 OUString lookup(
824     Bootstrap_Impl const * file, LookupMode mode, bool override,
825     OUString const & key, ExpandRequestLink const * requestStack)
826 {
827     OUString v;
828     (file == nullptr ? get_static_bootstrap_handle() : file)->getValue(
829         key, &v.pData, nullptr, mode, override, requestStack);
830     return v;
831 }
832 
expandMacros(Bootstrap_Impl const * file,OUString const & text,LookupMode mode,ExpandRequestLink const * requestStack)833 OUString expandMacros(
834     Bootstrap_Impl const * file, OUString const & text, LookupMode mode,
835     ExpandRequestLink const * requestStack)
836 {
837     SAL_INFO("sal.bootstrap", "expandMacros called with: " << text);
838     OUStringBuffer buf(2048);
839 
840     for (sal_Int32 i = 0; i < text.getLength();)
841     {
842         bool escaped;
843         sal_Unicode c = read(text, &i, &escaped);
844         if (escaped || c != '$')
845         {
846             buf.append(c);
847         }
848         else
849         {
850             if (i < text.getLength() && text[i] == '{')
851             {
852                 ++i;
853                 sal_Int32 p = i;
854                 sal_Int32 nesting = 0;
855                 OUString seg[3];
856                 int n = 0;
857 
858                 while (i < text.getLength())
859                 {
860                     sal_Int32 j = i;
861                     c = read(text, &i, &escaped);
862 
863                     if (!escaped)
864                     {
865                         switch (c)
866                         {
867                             case '{':
868                                 ++nesting;
869                                 break;
870                             case '}':
871                                 if (nesting == 0)
872                                 {
873                                     seg[n++] = text.copy(p, j - p);
874                                     goto done;
875                                 }
876                                 else
877                                 {
878                                     --nesting;
879                                 }
880                                 break;
881                             case ':':
882                                 if (nesting == 0 && n < 2)
883                                 {
884                                     seg[n++] = text.copy(p, j - p);
885                                     p = i;
886                                 }
887                                 break;
888                         }
889                     }
890                 }
891             done:
892                 for (int j = 0; j < n; ++j)
893                 {
894                     seg[j] = expandMacros(file, seg[j], mode, requestStack);
895                 }
896 
897                 if (n == 1)
898                 {
899                     buf.append(lookup(file, mode, false, seg[0], requestStack));
900                 }
901                 else if (n == 3 && seg[0] == ".override")
902                 {
903                     rtl::Bootstrap b(seg[1]);
904                     Bootstrap_Impl * f = static_cast< Bootstrap_Impl * >(b.getHandle());
905                     buf.append(lookup(f, mode, f != nullptr, seg[2], requestStack));
906                 }
907                 else
908                 {
909                     if (n == 3 && seg[1].isEmpty())
910                     {
911                         // For backward compatibility, treat ${file::key} the
912                         // same as just ${file:key}:
913                         seg[1] = seg[2];
914                         n = 2;
915                     }
916 
917                     if (n == 2)
918                     {
919                         buf.append(
920                             lookup(
921                                 static_cast< Bootstrap_Impl * >(
922                                     rtl::Bootstrap(seg[0]).getHandle()),
923                                 mode, false, seg[1], requestStack));
924                     }
925                     else
926                     {
927                         // Going through osl::Profile, this code erroneously
928                         // does not recursively expand macros in the resulting
929                         // replacement text (and if it did, it would fail to
930                         // detect cycles that pass through here):
931                         buf.append(
932                             OStringToOUString(
933                                 osl::Profile(seg[0]).readString(
934                                     OUStringToOString(
935                                         seg[1], RTL_TEXTENCODING_UTF8),
936                                     OUStringToOString(
937                                         seg[2], RTL_TEXTENCODING_UTF8),
938                                     OString()),
939                                 RTL_TEXTENCODING_UTF8));
940                     }
941                 }
942             }
943             else
944             {
945                 OUStringBuffer kbuf(text.getLength());
946                 for (; i < text.getLength();)
947                 {
948                     sal_Int32 j = i;
949                     c = read(text, &j, &escaped);
950                     if (!escaped &&
951                         (c == ' ' || c == '$' || c == '-' || c == '/' ||
952                          c == ';' || c == '\\'))
953                     {
954                         break;
955                     }
956 
957                     kbuf.append(c);
958                     i = j;
959                 }
960 
961                 buf.append(
962                     lookup(
963                         file, mode, false, kbuf.makeStringAndClear(),
964                         requestStack));
965             }
966         }
967     }
968 
969     OUString result(buf.makeStringAndClear());
970     SAL_INFO("sal.bootstrap", "expandMacros result: " << result);
971 
972     return result;
973 }
974 
975 }
976 
977 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
978