Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / sal / rtl / bootstrap.cxx
blob7f5d4317636a1c5d3cd16b96bcf6426fd66c889f
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
19 #include <config_features.h>
20 #include <config_folders.h>
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>
41 #include <vector>
42 #include <algorithm>
43 #include <unordered_map>
45 #ifdef ANDROID
46 #include <osl/detail/android-bootstrap.h>
47 #endif
49 #ifdef IOS
50 #include <premac.h>
51 #import <Foundation/Foundation.h>
52 #include <postmac.h>
53 #endif
55 using osl::DirectoryItem;
56 using osl::FileStatus;
58 struct Bootstrap_Impl;
60 namespace
63 static char const VND_SUN_STAR_PATHNAME[] = "vnd.sun.star.pathname:";
65 bool isPathnameUrl(OUString const & url)
67 return url.matchIgnoreAsciiCase(VND_SUN_STAR_PATHNAME);
70 bool resolvePathnameUrl(OUString * url)
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))
78 return true;
80 *url = OUString();
81 return false;
84 enum LookupMode {
85 LOOKUP_MODE_NORMAL, LOOKUP_MODE_URE_BOOTSTRAP,
86 LOOKUP_MODE_URE_BOOTSTRAP_EXPANSION };
88 struct ExpandRequestLink {
89 ExpandRequestLink const * next;
90 Bootstrap_Impl const * file;
91 OUString const key;
94 OUString expandMacros(
95 Bootstrap_Impl const * file, OUString const & text, LookupMode mode,
96 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)
103 for (; requestStack; requestStack = requestStack->next) {
104 if (requestStack->file == requestFile &&
105 requestStack->key == requestKey)
107 return "***RECURSION DETECTED***";
110 ExpandRequestLink link = { requestStack, requestFile, requestKey };
111 return expandMacros(file, text, mode, &link);
114 } // end namespace
116 struct rtl_bootstrap_NameValue
118 OUString sName;
119 OUString sValue;
121 rtl_bootstrap_NameValue()
123 rtl_bootstrap_NameValue(OUString const & name, OUString const & value )
124 : sName( name ),
125 sValue( value )
129 typedef std::vector<rtl_bootstrap_NameValue> NameValueVector;
131 static bool find(
132 NameValueVector const & vector, OUString const & key,
133 OUString * value)
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())
140 *value = i->sValue;
141 return true;
143 return false;
146 namespace
148 struct rtl_bootstrap_set_vector :
149 public rtl::Static< NameValueVector, rtl_bootstrap_set_vector > {};
152 static bool getFromCommandLineArgs(
153 OUString const & key, OUString * value )
155 OSL_ASSERT(value);
157 static NameValueVector nameValueVector = [&]()
159 NameValueVector tmp;
161 sal_Int32 nArgCount = osl_getCommandArgCount();
162 for(sal_Int32 i = 0; i < nArgCount; ++ i)
164 rtl_uString *pArg = nullptr;
165 osl_getCommandArg( i, &pArg );
166 if( (pArg->buffer[0] == '-' || pArg->buffer[0] == '/' ) &&
167 pArg->buffer[1] == 'e' &&
168 pArg->buffer[2] == 'n' &&
169 pArg->buffer[3] == 'v' &&
170 pArg->buffer[4] == ':' )
172 sal_Int32 nIndex = rtl_ustr_indexOfChar( pArg->buffer, '=' );
174 if( nIndex >= 0 )
176 rtl_bootstrap_NameValue nameValue;
177 nameValue.sName = OUString( &(pArg->buffer[5]), nIndex - 5 );
178 nameValue.sValue = OUString( &(pArg->buffer[nIndex+1]) );
180 if( i == nArgCount-1 &&
181 nameValue.sValue.getLength() &&
182 nameValue.sValue[nameValue.sValue.getLength()-1] == 13 )
184 // avoid the 13 linefeed for the last argument,
185 // when the executable is started from a script,
186 // that was edited on windows
187 nameValue.sValue = nameValue.sValue.copy(0,nameValue.sValue.getLength()-1);
190 tmp.push_back( nameValue );
193 rtl_uString_release( pArg );
195 return tmp;
196 }();
198 return find(nameValueVector, key, value);
201 static void getExecutableDirectory_Impl(rtl_uString ** ppDirURL)
203 OUString fileName;
204 osl_getExecutableFile(&(fileName.pData));
206 sal_Int32 nDirEnd = fileName.lastIndexOf('/');
207 OSL_ENSURE(nDirEnd >= 0, "Cannot locate executable directory");
209 rtl_uString_newFromStr_WithLength(ppDirURL,fileName.getStr(),nDirEnd);
212 static OUString & getIniFileName_Impl()
214 static OUString aStaticName = []() {
215 OUString fileName;
217 #if defined IOS
218 // On iOS hardcode the inifile as "rc" in the .app
219 // directory. Apps are self-contained anyway, there is no
220 // possibility to have several "applications" in the same
221 // installation location with different inifiles.
222 const char *inifile = [[@"vnd.sun.star.pathname:" stringByAppendingString: [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"rc"]] UTF8String];
223 fileName = OUString(inifile, strlen(inifile), RTL_TEXTENCODING_UTF8);
224 resolvePathnameUrl(&fileName);
225 #elif defined ANDROID
226 // Apps are self-contained on Android, too, can as well hardcode
227 // it as "rc" in the "/assets" directory, i.e. inside the app's
228 // .apk (zip) archive as the /assets/rc file.
229 fileName = OUString("vnd.sun.star.pathname:/assets/rc");
230 resolvePathnameUrl(&fileName);
231 #else
232 if (getFromCommandLineArgs("INIFILENAME", &fileName))
234 resolvePathnameUrl(&fileName);
236 else
238 osl_getExecutableFile(&(fileName.pData));
240 // get rid of a potential executable extension
241 OUString progExt = ".bin";
242 if (fileName.getLength() > progExt.getLength()
243 && fileName.copy(fileName.getLength() - progExt.getLength()).equalsIgnoreAsciiCase(progExt))
245 fileName = fileName.copy(0, fileName.getLength() - progExt.getLength());
248 progExt = ".exe";
249 if (fileName.getLength() > progExt.getLength()
250 && fileName.copy(fileName.getLength() - progExt.getLength()).equalsIgnoreAsciiCase(progExt))
252 fileName = fileName.copy(0, fileName.getLength() - progExt.getLength());
255 // append config file suffix
256 fileName += SAL_CONFIGFILE("");
258 #ifdef MACOSX
259 // We keep only executables in the MacOS folder, and all
260 // rc files in LIBO_ETC_FOLDER (typically "Resources").
261 sal_Int32 off = fileName.lastIndexOf( "/MacOS/" );
262 if (off != -1)
263 fileName = fileName.replaceAt(off + 1, strlen("MacOS"), LIBO_ETC_FOLDER);
264 #endif
266 #endif
268 return fileName;
269 }();
271 return aStaticName;
274 // ensure the given file url has no final slash
276 static void EnsureNoFinalSlash (OUString & url)
278 sal_Int32 i = url.getLength();
280 if (i > 0 && url[i - 1] == '/')
281 url = url.copy(0, i - 1);
284 struct Bootstrap_Impl
286 sal_Int32 _nRefCount;
287 Bootstrap_Impl * _base_ini;
289 NameValueVector _nameValueVector;
290 OUString const _iniName;
292 explicit Bootstrap_Impl (OUString const & rIniName);
293 ~Bootstrap_Impl();
295 static void * operator new (std::size_t n)
296 { return malloc (sal_uInt32(n)); }
297 static void operator delete (void * p , std::size_t)
298 { free (p); }
300 bool getValue(
301 OUString const & key, rtl_uString ** value,
302 rtl_uString * defaultValue, LookupMode mode, bool override,
303 ExpandRequestLink const * requestStack) const;
304 bool getDirectValue(
305 OUString const & key, rtl_uString ** value, LookupMode mode,
306 ExpandRequestLink const * requestStack) const;
307 bool getAmbienceValue(
308 OUString const & key, rtl_uString ** value, LookupMode mode,
309 ExpandRequestLink const * requestStack) const;
310 void expandValue(
311 rtl_uString ** value, OUString const & text, LookupMode mode,
312 Bootstrap_Impl const * requestFile, OUString const & requestKey,
313 ExpandRequestLink const * requestStack) const;
316 Bootstrap_Impl::Bootstrap_Impl( OUString const & rIniName )
317 : _nRefCount( 0 ),
318 _base_ini( nullptr ),
319 _iniName (rIniName)
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)
328 base_ini = status.getFileURL();
329 if (rIniName != base_ini)
331 _base_ini = static_cast< Bootstrap_Impl * >(
332 rtl_bootstrap_args_open(base_ini.pData));
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)
340 rtl::ByteSequence seq;
342 while (osl_readLine(handle , reinterpret_cast<sal_Sequence **>(&seq)) == osl_File_E_None)
344 OString line(reinterpret_cast<const char *>(seq.getConstArray()), seq.getLength());
345 sal_Int32 nIndex = line.indexOf('=');
346 if (nIndex >= 1)
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);
352 SAL_INFO("sal.bootstrap", "pushing: name=" << nameValue.sName << " value=" << nameValue.sValue);
354 _nameValueVector.push_back(nameValue);
357 osl_closeFile(handle);
359 else
361 SAL_INFO( "sal.bootstrap", "couldn't open file: " << _iniName );
365 Bootstrap_Impl::~Bootstrap_Impl()
367 if (_base_ini)
368 rtl_bootstrap_args_close( _base_ini );
371 namespace {
373 Bootstrap_Impl * get_static_bootstrap_handle()
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)
380 that = new Bootstrap_Impl(iniName);
381 ++that->_nRefCount;
383 return that;
384 }();
386 return s_handle;
389 struct FundamentalIniData
391 rtlBootstrapHandle ini;
393 FundamentalIniData()
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;
404 ~FundamentalIniData() { rtl_bootstrap_args_close(ini); }
406 FundamentalIniData(const FundamentalIniData&) = delete;
407 FundamentalIniData& operator=(const FundamentalIniData&) = delete;
410 struct FundamentalIni: public rtl::Static< FundamentalIniData, FundamentalIni >
415 bool Bootstrap_Impl::getValue(
416 OUString const & key, rtl_uString ** value, rtl_uString * defaultValue,
417 LookupMode mode, bool override, ExpandRequestLink const * requestStack)
418 const
420 if (mode == LOOKUP_MODE_NORMAL && key == "URE_BOOTSTRAP")
421 mode = LOOKUP_MODE_URE_BOOTSTRAP;
423 if (override && getDirectValue(key, value, mode, requestStack))
424 return true;
426 if (key == "_OS")
428 rtl_uString_assign(
429 value, OUString(RTL_OS).pData);
430 return true;
433 if (key == "_ARCH")
435 rtl_uString_assign(
436 value, OUString(RTL_ARCH).pData);
437 return true;
440 if (key == "_CPPU_ENV")
442 rtl_uString_assign(
443 value,
444 (OUString(
445 SAL_STRINGIFY(CPPU_ENV)).
446 pData));
447 return true;
450 #ifdef ANDROID
451 if (key == "APP_DATA_DIR")
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;
458 #endif
460 #ifdef IOS
461 if (key == "APP_DATA_DIR")
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;
468 #endif
470 if (key == "ORIGIN")
472 rtl_uString_assign(
473 value,
474 _iniName.copy(
475 0, std::max<sal_Int32>(0, _iniName.lastIndexOf('/'))).pData);
476 return true;
479 if (getAmbienceValue(key, value, mode, requestStack))
480 return true;
482 if (key == "SYSUSERCONFIG")
484 OUString v;
485 bool b = osl::Security().getConfigDir(v);
486 EnsureNoFinalSlash(v);
487 rtl_uString_assign(value, v.pData);
488 return b;
491 if (key == "SYSUSERHOME")
493 OUString v;
494 bool b = osl::Security().getHomeDir(v);
495 EnsureNoFinalSlash(v);
496 rtl_uString_assign(value, v.pData);
497 return b;
500 if (key == "SYSBINDIR")
502 getExecutableDirectory_Impl(value);
503 return true;
506 if (_base_ini != nullptr && _base_ini->getDirectValue(key, value, mode, requestStack))
507 return true;
509 if (!override && getDirectValue(key, value, mode, requestStack))
510 return true;
512 if (mode == LOOKUP_MODE_NORMAL)
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;
520 if (defaultValue != nullptr)
522 rtl_uString_assign(value, defaultValue);
523 return true;
526 rtl_uString_new(value);
527 return false;
530 bool Bootstrap_Impl::getDirectValue(
531 OUString const & key, rtl_uString ** value, LookupMode mode,
532 ExpandRequestLink const * requestStack) const
534 OUString v;
535 if (find(_nameValueVector, key, &v))
537 expandValue(value, v, mode, this, key, requestStack);
538 return true;
541 return false;
544 bool Bootstrap_Impl::getAmbienceValue(
545 OUString const & key, rtl_uString ** value, LookupMode mode,
546 ExpandRequestLink const * requestStack) const
548 OUString v;
549 bool f;
552 osl::MutexGuard g(osl::Mutex::getGlobalMutex());
553 f = find(rtl_bootstrap_set_vector::get(), key, &v);
556 if (f || getFromCommandLineArgs(key, &v) ||
557 osl_getEnvironment(key.pData, &v.pData) == osl_Process_E_None)
559 expandValue(value, v, mode, nullptr, key, requestStack);
560 return true;
563 return false;
566 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
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);
582 namespace {
584 struct bootstrap_map {
585 typedef std::unordered_map<
586 OUString, Bootstrap_Impl * > t;
588 bootstrap_map(const bootstrap_map&) = delete;
589 bootstrap_map& operator=(const bootstrap_map&) = delete;
591 // get and release must only be called properly synchronized via some mutex
592 // (e.g., osl::Mutex::getGlobalMutex()):
594 static t * get()
596 if (!m_map)
597 m_map = new t;
599 return m_map;
602 static void release()
604 if (m_map != nullptr && m_map->empty())
606 delete m_map;
607 m_map = nullptr;
611 private:
612 static t * m_map;
615 bootstrap_map::t * bootstrap_map::m_map = nullptr;
619 rtlBootstrapHandle SAL_CALL rtl_bootstrap_args_open(rtl_uString * pIniName)
621 OUString iniName( pIniName );
623 // normalize path
624 FileStatus status(osl_FileStatus_Mask_FileURL);
625 DirectoryItem dirItem;
626 if (DirectoryItem::get(iniName, dirItem) != DirectoryItem::E_None ||
627 dirItem.getFileStatus(status) != DirectoryItem::E_None)
629 return nullptr;
632 iniName = status.getFileURL();
634 Bootstrap_Impl * that;
635 osl::ResettableMutexGuard guard(osl::Mutex::getGlobalMutex());
636 bootstrap_map::t* p_bootstrap_map = bootstrap_map::get();
637 bootstrap_map::t::const_iterator iFind(p_bootstrap_map->find(iniName));
638 if (iFind == p_bootstrap_map->end())
640 bootstrap_map::release();
641 guard.clear();
642 that = new Bootstrap_Impl(iniName);
643 guard.reset();
644 p_bootstrap_map = bootstrap_map::get();
645 iFind = p_bootstrap_map->find(iniName);
646 if (iFind == p_bootstrap_map->end())
648 ++that->_nRefCount;
649 ::std::pair< bootstrap_map::t::iterator, bool > insertion(
650 p_bootstrap_map->emplace(iniName, that));
651 OSL_ASSERT(insertion.second);
653 else
655 Bootstrap_Impl * obsolete = that;
656 that = iFind->second;
657 ++that->_nRefCount;
658 guard.clear();
659 delete obsolete;
662 else
664 that = iFind->second;
665 ++that->_nRefCount;
667 return static_cast< rtlBootstrapHandle >( that );
670 void SAL_CALL rtl_bootstrap_args_close(rtlBootstrapHandle handle) SAL_THROW_EXTERN_C()
672 if (!handle)
673 return;
675 Bootstrap_Impl * that = static_cast< Bootstrap_Impl * >( handle );
677 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
678 bootstrap_map::t* p_bootstrap_map = bootstrap_map::get();
679 OSL_ASSERT(p_bootstrap_map->find(that->_iniName)->second == that);
680 --that->_nRefCount;
682 if (that->_nRefCount == 0)
684 std::size_t const nLeaking = 8; // only hold up to 8 files statically
685 if (p_bootstrap_map->size() > nLeaking)
687 ::std::size_t erased = p_bootstrap_map->erase( that->_iniName );
688 if (erased != 1) {
689 OSL_ASSERT( false );
691 delete that;
693 bootstrap_map::release();
697 sal_Bool SAL_CALL rtl_bootstrap_get_from_handle(
698 rtlBootstrapHandle handle,
699 rtl_uString * pName,
700 rtl_uString ** ppValue,
701 rtl_uString * pDefault
704 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
706 bool found = false;
707 if(ppValue && pName)
709 if (!handle)
710 handle = get_static_bootstrap_handle();
712 found = static_cast< Bootstrap_Impl * >(handle)->getValue(
713 pName, ppValue, pDefault, LOOKUP_MODE_NORMAL, false, nullptr );
716 return found;
719 void SAL_CALL rtl_bootstrap_get_iniName_from_handle (
720 rtlBootstrapHandle handle,
721 rtl_uString ** ppIniName
724 if(ppIniName)
726 if(handle)
728 Bootstrap_Impl * pImpl = static_cast<Bootstrap_Impl*>(handle);
729 rtl_uString_assign(ppIniName, pImpl->_iniName.pData);
731 else
733 const OUString & iniName = getIniFileName_Impl();
734 rtl_uString_assign(ppIniName, iniName.pData);
739 void SAL_CALL rtl_bootstrap_setIniFileName (
740 rtl_uString * pName
743 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
744 OUString & file = getIniFileName_Impl();
745 file = pName;
748 sal_Bool SAL_CALL rtl_bootstrap_get (
749 rtl_uString * pName,
750 rtl_uString ** ppValue,
751 rtl_uString * pDefault
754 return rtl_bootstrap_get_from_handle(nullptr, pName, ppValue, pDefault);
757 void SAL_CALL rtl_bootstrap_set (
758 rtl_uString * pName,
759 rtl_uString * pValue
762 const OUString name(pName);
763 const OUString value(pValue);
765 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
767 NameValueVector& r_rtl_bootstrap_set_vector= rtl_bootstrap_set_vector::get();
768 for (auto & item : r_rtl_bootstrap_set_vector)
770 if (item.sName == name)
772 item.sValue = value;
773 return;
777 SAL_INFO("sal.bootstrap", "explicitly getting: name=" << name << " value=" <<value);
779 r_rtl_bootstrap_set_vector.emplace_back(name, value);
782 void SAL_CALL rtl_bootstrap_expandMacros_from_handle(
783 rtlBootstrapHandle handle,
784 rtl_uString ** macro)
786 if (!handle)
787 handle = get_static_bootstrap_handle();
789 OUString expanded(expandMacros(static_cast< Bootstrap_Impl * >(handle),
790 OUString::unacquired(macro),
791 LOOKUP_MODE_NORMAL, nullptr));
792 rtl_uString_assign(macro, expanded.pData);
795 void SAL_CALL rtl_bootstrap_expandMacros(rtl_uString ** macro)
797 rtl_bootstrap_expandMacros_from_handle(nullptr, macro);
800 void rtl_bootstrap_encode(rtl_uString const * value, rtl_uString ** encoded)
802 OSL_ASSERT(value);
803 OUStringBuffer b(value->length+5);
804 for (sal_Int32 i = 0; i < value->length; ++i)
806 sal_Unicode c = value->buffer[i];
807 if (c == '$' || c == '\\')
808 b.append('\\');
810 b.append(c);
813 rtl_uString_assign(encoded, b.makeStringAndClear().pData);
816 namespace {
818 int hex(sal_Unicode c)
820 return
821 c >= '0' && c <= '9' ? c - '0' :
822 c >= 'A' && c <= 'F' ? c - 'A' + 10 :
823 c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
826 sal_Unicode read(OUString const & text, sal_Int32 * pos, bool * escaped)
828 OSL_ASSERT(pos && *pos >= 0 && *pos < text.getLength() && escaped);
829 sal_Unicode c = text[(*pos)++];
830 if (c == '\\')
832 int n1, n2, n3, n4;
833 if (*pos < text.getLength() - 4 && text[*pos] == 'u' &&
834 ((n1 = hex(text[*pos + 1])) >= 0) &&
835 ((n2 = hex(text[*pos + 2])) >= 0) &&
836 ((n3 = hex(text[*pos + 3])) >= 0) &&
837 ((n4 = hex(text[*pos + 4])) >= 0))
839 *pos += 5;
840 *escaped = true;
841 return static_cast< sal_Unicode >(
842 (n1 << 12) | (n2 << 8) | (n3 << 4) | n4);
845 if (*pos < text.getLength())
847 *escaped = true;
848 return text[(*pos)++];
852 *escaped = false;
853 return c;
856 OUString lookup(
857 Bootstrap_Impl const * file, LookupMode mode, bool override,
858 OUString const & key, ExpandRequestLink const * requestStack)
860 OUString v;
861 (file == nullptr ? get_static_bootstrap_handle() : file)->getValue(
862 key, &v.pData, nullptr, mode, override, requestStack);
863 return v;
866 OUString expandMacros(
867 Bootstrap_Impl const * file, OUString const & text, LookupMode mode,
868 ExpandRequestLink const * requestStack)
870 SAL_INFO("sal.bootstrap", "expandMacros called with: " << text);
871 OUStringBuffer buf(2048);
873 for (sal_Int32 i = 0; i < text.getLength();)
875 bool escaped;
876 sal_Unicode c = read(text, &i, &escaped);
877 if (escaped || c != '$')
879 buf.append(c);
881 else
883 if (i < text.getLength() && text[i] == '{')
885 ++i;
886 sal_Int32 p = i;
887 sal_Int32 nesting = 0;
888 OUString seg[3];
889 int n = 0;
891 while (i < text.getLength())
893 sal_Int32 j = i;
894 c = read(text, &i, &escaped);
896 if (!escaped)
898 switch (c)
900 case '{':
901 ++nesting;
902 break;
903 case '}':
904 if (nesting == 0)
906 seg[n++] = text.copy(p, j - p);
907 goto done;
909 else
911 --nesting;
913 break;
914 case ':':
915 if (nesting == 0 && n < 2)
917 seg[n++] = text.copy(p, j - p);
918 p = i;
920 break;
924 done:
925 for (int j = 0; j < n; ++j)
927 seg[j] = expandMacros(file, seg[j], mode, requestStack);
930 if (n == 1)
932 buf.append(lookup(file, mode, false, seg[0], requestStack));
934 else if (n == 3 && seg[0] == ".override")
936 rtl::Bootstrap b(seg[1]);
937 Bootstrap_Impl * f = static_cast< Bootstrap_Impl * >(b.getHandle());
938 buf.append(lookup(f, mode, f != nullptr, seg[2], requestStack));
940 else
942 if (n == 3 && seg[1].isEmpty())
944 // For backward compatibility, treat ${file::key} the
945 // same as just ${file:key}:
946 seg[1] = seg[2];
947 n = 2;
950 if (n == 2)
952 buf.append(
953 lookup(
954 static_cast< Bootstrap_Impl * >(
955 rtl::Bootstrap(seg[0]).getHandle()),
956 mode, false, seg[1], requestStack));
958 else
960 // Going through osl::Profile, this code erroneously
961 // does not recursively expand macros in the resulting
962 // replacement text (and if it did, it would fail to
963 // detect cycles that pass through here):
964 buf.append(
965 OStringToOUString(
966 osl::Profile(seg[0]).readString(
967 OUStringToOString(
968 seg[1], RTL_TEXTENCODING_UTF8),
969 OUStringToOString(
970 seg[2], RTL_TEXTENCODING_UTF8),
971 OString()),
972 RTL_TEXTENCODING_UTF8));
976 else
978 OUStringBuffer kbuf(text.getLength());
979 for (; i < text.getLength();)
981 sal_Int32 j = i;
982 c = read(text, &j, &escaped);
983 if (!escaped &&
984 (c == ' ' || c == '$' || c == '-' || c == '/' ||
985 c == ';' || c == '\\'))
987 break;
990 kbuf.append(c);
991 i = j;
994 buf.append(
995 lookup(
996 file, mode, false, kbuf.makeStringAndClear(),
997 requestStack));
1002 OUString result(buf.makeStringAndClear());
1003 SAL_INFO("sal.bootstrap", "expandMacros result: " << result);
1005 return result;
1010 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */