add fetch to dependencies of gbuildtojson / Rdb target
[LibreOffice.git] / sal / rtl / bootstrap.cxx
blob04e0a4d0ed14e95634eadf48796fe9a7f0c328e8
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/malformeduriexception.hxx>
37 #include <rtl/uri.hxx>
38 #include <sal/log.hxx>
39 #include <o3tl/lru_map.hxx>
40 #include <o3tl/string_view.hxx>
42 #include <utility>
43 #include <vector>
44 #include <algorithm>
45 #include <cstddef>
46 #include <string_view>
47 #include <unordered_map>
49 #ifdef ANDROID
50 #include <osl/detail/android-bootstrap.h>
51 #endif
53 #ifdef EMSCRIPTEN
54 #include <osl/detail/emscripten-bootstrap.h>
55 #endif
57 #ifdef IOS
58 #include <premac.h>
59 #import <Foundation/Foundation.h>
60 #include <postmac.h>
61 #endif
63 using osl::DirectoryItem;
64 using osl::FileStatus;
66 namespace
69 struct Bootstrap_Impl;
71 constexpr std::u16string_view VND_SUN_STAR_PATHNAME = u"vnd.sun.star.pathname:";
73 bool isPathnameUrl(std::u16string_view url)
75 return o3tl::matchIgnoreAsciiCase(url, VND_SUN_STAR_PATHNAME);
78 bool resolvePathnameUrl(OUString * url)
80 OSL_ASSERT(url);
81 if (!isPathnameUrl(*url) ||
82 (osl::FileBase::getFileURLFromSystemPath(
83 url->copy(VND_SUN_STAR_PATHNAME.size()), *url) ==
84 osl::FileBase::E_None))
86 return true;
88 *url = OUString();
89 return false;
92 enum class LookupMode {
93 NORMAL, URE_BOOTSTRAP,
94 URE_BOOTSTRAP_EXPANSION };
96 struct ExpandRequestLink {
97 ExpandRequestLink const * next;
98 Bootstrap_Impl const * file;
99 OUString key;
102 OUString expandMacros(
103 Bootstrap_Impl const * file, std::u16string_view text, LookupMode mode,
104 ExpandRequestLink const * requestStack);
106 OUString recursivelyExpandMacros(
107 Bootstrap_Impl const * file, std::u16string_view text, LookupMode mode,
108 Bootstrap_Impl const * requestFile, OUString const & requestKey,
109 ExpandRequestLink const * requestStack)
111 for (; requestStack; requestStack = requestStack->next) {
112 if (requestStack->file == requestFile &&
113 requestStack->key == requestKey)
115 return "***RECURSION DETECTED***";
118 ExpandRequestLink link = { requestStack, requestFile, requestKey };
119 return expandMacros(file, text, mode, &link);
122 struct rtl_bootstrap_NameValue
124 OUString sName;
125 OUString sValue;
127 rtl_bootstrap_NameValue()
129 rtl_bootstrap_NameValue(OUString name, OUString value )
130 : sName(std::move( name )),
131 sValue(std::move( value ))
135 } // end namespace
137 typedef std::vector<rtl_bootstrap_NameValue> NameValueVector;
139 static bool find(
140 NameValueVector const & vector, OUString const & key,
141 OUString * value)
143 OSL_ASSERT(value);
144 auto i = std::find_if(vector.begin(), vector.end(),
145 [&key](const rtl_bootstrap_NameValue& rNameValue) { return rNameValue.sName == key; });
146 if (i != vector.end())
148 *value = i->sValue;
149 return true;
151 return false;
154 namespace
156 NameValueVector rtl_bootstrap_set_vector;
159 static bool getFromCommandLineArgs(
160 OUString const & key, OUString * value )
162 OSL_ASSERT(value);
164 static NameValueVector nameValueVector = []()
166 NameValueVector tmp;
168 sal_Int32 nArgCount = osl_getCommandArgCount();
169 for(sal_Int32 i = 0; i < nArgCount; ++ i)
171 OUString pArg;
172 osl_getCommandArg( i, &pArg.pData );
173 if( (pArg.startsWith("-") || pArg.startsWith("/") ) &&
174 pArg.match("env:", 1) )
176 sal_Int32 nIndex = pArg.indexOf( '=' );
178 if( nIndex >= 0 )
180 rtl_bootstrap_NameValue nameValue;
181 nameValue.sName = pArg.copy( 5, nIndex - 5 );
182 nameValue.sValue = pArg.copy( nIndex+1 );
184 if( i == nArgCount-1 &&
185 nameValue.sValue.getLength() &&
186 nameValue.sValue[nameValue.sValue.getLength()-1] == 13 )
188 // avoid the 13 linefeed for the last argument,
189 // when the executable is started from a script,
190 // that was edited on windows
191 nameValue.sValue = nameValue.sValue.copy(0,nameValue.sValue.getLength()-1);
194 tmp.push_back( nameValue );
198 return tmp;
199 }();
201 return find(nameValueVector, key, value);
204 static void getExecutableDirectory_Impl(rtl_uString ** ppDirURL)
206 OUString fileName;
207 osl_getExecutableFile(&(fileName.pData));
209 sal_Int32 nDirEnd = fileName.lastIndexOf('/');
210 OSL_ENSURE(nDirEnd >= 0, "Cannot locate executable directory");
212 rtl_uString_newFromStr_WithLength(ppDirURL,fileName.getStr(),nDirEnd);
215 static OUString & getIniFileName_Impl()
217 static OUString aStaticName = []() {
218 OUString fileName;
220 #if defined IOS
221 // On iOS hardcode the inifile as "rc" in the .app
222 // directory. Apps are self-contained anyway, there is no
223 // possibility to have several "applications" in the same
224 // installation location with different inifiles.
225 const char *inifile = [[@"vnd.sun.star.pathname:" stringByAppendingString: [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent: @"rc"]] UTF8String];
226 fileName = OUString(inifile, strlen(inifile), RTL_TEXTENCODING_UTF8);
227 resolvePathnameUrl(&fileName);
228 #elif defined ANDROID
229 // Apps are self-contained on Android, too, can as well hardcode
230 // it as "rc" in the "/assets" directory, i.e. inside the app's
231 // .apk (zip) archive as the /assets/rc file.
232 fileName = OUString("vnd.sun.star.pathname:/assets/rc");
233 resolvePathnameUrl(&fileName);
234 #elif defined(EMSCRIPTEN)
235 fileName = OUString("vnd.sun.star.pathname:/instdir/program/sofficerc");
236 resolvePathnameUrl(&fileName);
237 #else
238 if (getFromCommandLineArgs("INIFILENAME", &fileName))
240 resolvePathnameUrl(&fileName);
242 else
244 osl_getExecutableFile(&(fileName.pData));
246 // get rid of a potential executable extension
247 OUString progExt = ".bin";
248 if (fileName.getLength() > progExt.getLength()
249 && o3tl::equalsIgnoreAsciiCase(fileName.subView(fileName.getLength() - progExt.getLength()), progExt))
251 fileName = fileName.copy(0, fileName.getLength() - progExt.getLength());
254 progExt = ".exe";
255 if (fileName.getLength() > progExt.getLength()
256 && o3tl::equalsIgnoreAsciiCase(fileName.subView(fileName.getLength() - progExt.getLength()), progExt))
258 fileName = fileName.copy(0, fileName.getLength() - progExt.getLength());
261 // append config file suffix
262 fileName += SAL_CONFIGFILE("");
264 #ifdef MACOSX
265 // We keep only executables in the MacOS folder, and all
266 // rc files in LIBO_ETC_FOLDER (typically "Resources").
267 sal_Int32 off = fileName.lastIndexOf( "/MacOS/" );
268 if (off != -1)
269 fileName = fileName.replaceAt(off + 1, strlen("MacOS"), u"" LIBO_ETC_FOLDER);
270 #endif
272 #endif
274 return fileName;
275 }();
277 return aStaticName;
280 // ensure the given file url has no final slash
282 static void EnsureNoFinalSlash (OUString & url)
284 sal_Int32 i = url.getLength();
286 if (i > 0 && url[i - 1] == '/')
287 url = url.copy(0, i - 1);
290 namespace {
292 struct Bootstrap_Impl
294 sal_Int32 _nRefCount;
295 Bootstrap_Impl * _base_ini;
297 NameValueVector _nameValueVector;
298 OUString _iniName;
300 explicit Bootstrap_Impl (OUString const & rIniName);
301 ~Bootstrap_Impl();
303 static void * operator new (std::size_t n)
304 { return malloc (sal_uInt32(n)); }
305 static void operator delete (void * p , std::size_t)
306 { free (p); }
308 bool getValue(
309 OUString const & key, rtl_uString ** value,
310 rtl_uString * defaultValue, LookupMode mode, bool override,
311 ExpandRequestLink const * requestStack) const;
312 bool getDirectValue(
313 OUString const & key, rtl_uString ** value, LookupMode mode,
314 ExpandRequestLink const * requestStack) const;
315 bool getAmbienceValue(
316 OUString const & key, rtl_uString ** value, LookupMode mode,
317 ExpandRequestLink const * requestStack) const;
318 void expandValue(
319 rtl_uString ** value, OUString const & text, LookupMode mode,
320 Bootstrap_Impl const * requestFile, OUString const & requestKey,
321 ExpandRequestLink const * requestStack) const;
326 Bootstrap_Impl::Bootstrap_Impl( OUString const & rIniName )
327 : _nRefCount( 0 ),
328 _base_ini( nullptr ),
329 _iniName (rIniName)
331 OUString base_ini(getIniFileName_Impl());
332 // normalize path
333 FileStatus status( osl_FileStatus_Mask_FileURL );
334 DirectoryItem dirItem;
335 if (DirectoryItem::get(base_ini, dirItem) == DirectoryItem::E_None &&
336 dirItem.getFileStatus(status) == DirectoryItem::E_None)
338 base_ini = status.getFileURL();
339 if (rIniName != base_ini)
341 _base_ini = static_cast< Bootstrap_Impl * >(
342 rtl_bootstrap_args_open(base_ini.pData));
345 SAL_INFO("sal.bootstrap", "Bootstrap_Impl(): sFile=" << _iniName);
346 oslFileHandle handle;
347 if (!_iniName.isEmpty() &&
348 osl_openFile(_iniName.pData, &handle, osl_File_OpenFlag_Read) == osl_File_E_None)
350 rtl::ByteSequence seq;
352 while (osl_readLine(handle , reinterpret_cast<sal_Sequence **>(&seq)) == osl_File_E_None)
354 OString line(reinterpret_cast<const char *>(seq.getConstArray()), seq.getLength());
355 sal_Int32 nIndex = line.indexOf('=');
356 if (nIndex >= 1)
358 struct rtl_bootstrap_NameValue nameValue;
359 nameValue.sName = OStringToOUString(o3tl::trim(line.subView(0,nIndex)), RTL_TEXTENCODING_ASCII_US);
360 nameValue.sValue = OStringToOUString(o3tl::trim(line.subView(nIndex+1)), RTL_TEXTENCODING_UTF8);
362 SAL_INFO("sal.bootstrap", "pushing: name=" << nameValue.sName << " value=" << nameValue.sValue);
364 _nameValueVector.push_back(nameValue);
367 osl_closeFile(handle);
369 else
371 SAL_INFO( "sal.bootstrap", "couldn't open file: " << _iniName );
375 Bootstrap_Impl::~Bootstrap_Impl()
377 if (_base_ini)
378 rtl_bootstrap_args_close( _base_ini );
381 namespace {
383 Bootstrap_Impl * get_static_bootstrap_handle()
385 static Bootstrap_Impl* s_handle = []() {
386 OUString iniName(getIniFileName_Impl());
387 Bootstrap_Impl* that = static_cast<Bootstrap_Impl*>(rtl_bootstrap_args_open(iniName.pData));
388 if (!that)
390 that = new Bootstrap_Impl(iniName);
391 ++that->_nRefCount;
393 return that;
394 }();
396 return s_handle;
399 struct FundamentalIniData
401 rtlBootstrapHandle ini;
403 FundamentalIniData()
405 OUString uri;
406 ini =
407 (get_static_bootstrap_handle()->getValue(
408 "URE_BOOTSTRAP", &uri.pData, nullptr, LookupMode::NORMAL, false,
409 nullptr)
410 && resolvePathnameUrl(&uri))
411 ? rtl_bootstrap_args_open(uri.pData) : nullptr;
414 ~FundamentalIniData() { rtl_bootstrap_args_close(ini); }
416 FundamentalIniData(const FundamentalIniData&) = delete;
417 FundamentalIniData& operator=(const FundamentalIniData&) = delete;
420 FundamentalIniData& FundamentalIni()
422 static FundamentalIniData SINGLETON;
423 return SINGLETON;
428 bool Bootstrap_Impl::getValue(
429 OUString const & key, rtl_uString ** value, rtl_uString * defaultValue,
430 LookupMode mode, bool override, ExpandRequestLink const * requestStack)
431 const
433 if (mode == LookupMode::NORMAL && key == "URE_BOOTSTRAP")
434 mode = LookupMode::URE_BOOTSTRAP;
436 if (override && getDirectValue(key, value, mode, requestStack))
437 return true;
439 if (key == "_OS")
441 rtl_uString_assign(
442 value, OUString(RTL_OS).pData);
443 return true;
446 if (key == "_ARCH")
448 rtl_uString_assign(
449 value, OUString(RTL_ARCH).pData);
450 return true;
453 if (key == "_CPPU_ENV")
455 rtl_uString_assign(
456 value,
457 (OUString(
458 SAL_STRINGIFY(CPPU_ENV)).
459 pData));
460 return true;
463 #if defined ANDROID || defined EMSCRIPTEN
464 if (key == "APP_DATA_DIR")
466 const char *app_data_dir = lo_get_app_data_dir();
467 rtl_uString_assign(
468 value, OUString(app_data_dir, strlen(app_data_dir), RTL_TEXTENCODING_UTF8).pData);
469 return true;
471 #endif
473 #ifdef IOS
474 if (key == "APP_DATA_DIR")
476 const char *app_data_dir = [[[[NSBundle mainBundle] bundlePath] stringByAddingPercentEncodingWithAllowedCharacters: [NSCharacterSet URLPathAllowedCharacterSet]] UTF8String];
477 rtl_uString_assign(
478 value, OUString(app_data_dir, strlen(app_data_dir), RTL_TEXTENCODING_UTF8).pData);
479 return true;
481 #endif
483 if (key == "ORIGIN")
485 rtl_uString_assign(
486 value,
487 _iniName.copy(
488 0, std::max<sal_Int32>(0, _iniName.lastIndexOf('/'))).pData);
489 return true;
492 if (getAmbienceValue(key, value, mode, requestStack))
493 return true;
495 if (key == "SYSUSERCONFIG")
497 OUString v;
498 bool b = osl::Security().getConfigDir(v);
499 EnsureNoFinalSlash(v);
500 rtl_uString_assign(value, v.pData);
501 return b;
504 if (key == "SYSUSERHOME")
506 OUString v;
507 bool b = osl::Security().getHomeDir(v);
508 EnsureNoFinalSlash(v);
509 rtl_uString_assign(value, v.pData);
510 return b;
513 if (key == "SYSBINDIR")
515 getExecutableDirectory_Impl(value);
516 return true;
519 if (_base_ini != nullptr && _base_ini->getDirectValue(key, value, mode, requestStack))
520 return true;
522 if (!override && getDirectValue(key, value, mode, requestStack))
523 return true;
525 if (mode == LookupMode::NORMAL)
527 FundamentalIniData const & d = FundamentalIni();
528 Bootstrap_Impl const * b = static_cast<Bootstrap_Impl const *>(d.ini);
529 if (b != nullptr && b != this && b->getDirectValue(key, value, mode, requestStack))
530 return true;
533 if (defaultValue != nullptr)
535 rtl_uString_assign(value, defaultValue);
536 return true;
539 rtl_uString_new(value);
540 return false;
543 bool Bootstrap_Impl::getDirectValue(
544 OUString const & key, rtl_uString ** value, LookupMode mode,
545 ExpandRequestLink const * requestStack) const
547 OUString v;
548 if (find(_nameValueVector, key, &v))
550 expandValue(value, v, mode, this, key, requestStack);
551 return true;
554 return false;
557 bool Bootstrap_Impl::getAmbienceValue(
558 OUString const & key, rtl_uString ** value, LookupMode mode,
559 ExpandRequestLink const * requestStack) const
561 OUString v;
562 bool f;
565 osl::MutexGuard g(osl::Mutex::getGlobalMutex());
566 f = find(rtl_bootstrap_set_vector, key, &v);
569 if (f || getFromCommandLineArgs(key, &v) ||
570 osl_getEnvironment(key.pData, &v.pData) == osl_Process_E_None)
572 expandValue(value, v, mode, nullptr, key, requestStack);
573 return true;
576 return false;
579 void Bootstrap_Impl::expandValue(
580 rtl_uString ** value, OUString const & text, LookupMode mode,
581 Bootstrap_Impl const * requestFile, OUString const & requestKey,
582 ExpandRequestLink const * requestStack) const
584 rtl_uString_assign(
585 value,
586 (mode == LookupMode::URE_BOOTSTRAP && isPathnameUrl(text) ?
587 text :
588 recursivelyExpandMacros(
589 this, text,
590 (mode == LookupMode::URE_BOOTSTRAP ?
591 LookupMode::URE_BOOTSTRAP_EXPANSION : mode),
592 requestFile, requestKey, requestStack)).pData);
595 namespace {
597 typedef std::unordered_map< OUString, Bootstrap_Impl * > bootstrap_map_t;
598 bootstrap_map_t bootstrap_map;
602 rtlBootstrapHandle SAL_CALL rtl_bootstrap_args_open(rtl_uString * pIniName)
604 static o3tl::lru_map<OUString,OUString> fileUrlLookupCache(16);
606 OUString originalIniName( pIniName );
607 OUString iniName;
609 osl::ResettableMutexGuard guard(osl::Mutex::getGlobalMutex());
610 auto cacheIt = fileUrlLookupCache.find(originalIniName);
611 bool foundInCache = cacheIt != fileUrlLookupCache.end();
612 if (foundInCache)
613 iniName = cacheIt->second;
614 guard.clear();
616 // normalize path
617 if (!foundInCache)
619 FileStatus status(osl_FileStatus_Mask_FileURL);
620 DirectoryItem dirItem;
621 if (DirectoryItem::get(originalIniName, dirItem) != DirectoryItem::E_None ||
622 dirItem.getFileStatus(status) != DirectoryItem::E_None)
624 return nullptr;
626 iniName = status.getFileURL();
629 guard.reset();
630 if (!foundInCache)
631 fileUrlLookupCache.insert({originalIniName, iniName});
632 Bootstrap_Impl * that;
633 auto iFind(bootstrap_map.find(iniName));
634 if (iFind == bootstrap_map.end())
636 guard.clear();
637 that = new Bootstrap_Impl(iniName);
638 guard.reset();
639 iFind = bootstrap_map.find(iniName);
640 if (iFind == bootstrap_map.end())
642 ++that->_nRefCount;
643 ::std::pair< bootstrap_map_t::iterator, bool > insertion(
644 bootstrap_map.emplace(iniName, that));
645 OSL_ASSERT(insertion.second);
647 else
649 Bootstrap_Impl * obsolete = that;
650 that = iFind->second;
651 ++that->_nRefCount;
652 guard.clear();
653 delete obsolete;
656 else
658 that = iFind->second;
659 ++that->_nRefCount;
661 return static_cast< rtlBootstrapHandle >( that );
664 void SAL_CALL rtl_bootstrap_args_close(rtlBootstrapHandle handle) SAL_THROW_EXTERN_C()
666 if (!handle)
667 return;
669 Bootstrap_Impl * that = static_cast< Bootstrap_Impl * >( handle );
671 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
672 OSL_ASSERT(bootstrap_map.find(that->_iniName)->second == that);
673 --that->_nRefCount;
675 if (that->_nRefCount != 0)
676 return;
678 std::size_t const nLeaking = 8; // only hold up to 8 files statically
679 if (bootstrap_map.size() > nLeaking)
681 ::std::size_t erased = bootstrap_map.erase( that->_iniName );
682 if (erased != 1) {
683 OSL_ASSERT( false );
685 delete that;
689 sal_Bool SAL_CALL rtl_bootstrap_get_from_handle(
690 rtlBootstrapHandle handle,
691 rtl_uString * pName,
692 rtl_uString ** ppValue,
693 rtl_uString * pDefault
696 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
698 bool found = false;
699 if(ppValue && pName)
701 if (!handle)
702 handle = get_static_bootstrap_handle();
704 found = static_cast< Bootstrap_Impl * >(handle)->getValue(
705 pName, ppValue, pDefault, LookupMode::NORMAL, false, nullptr );
708 return found;
711 void SAL_CALL rtl_bootstrap_get_iniName_from_handle (
712 rtlBootstrapHandle handle,
713 rtl_uString ** ppIniName
716 if(!ppIniName)
717 return;
719 if(handle)
721 Bootstrap_Impl * pImpl = static_cast<Bootstrap_Impl*>(handle);
722 rtl_uString_assign(ppIniName, pImpl->_iniName.pData);
724 else
726 const OUString & iniName = getIniFileName_Impl();
727 rtl_uString_assign(ppIniName, iniName.pData);
731 void SAL_CALL rtl_bootstrap_setIniFileName (
732 rtl_uString * pName
735 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
736 OUString & file = getIniFileName_Impl();
737 file = pName;
740 sal_Bool SAL_CALL rtl_bootstrap_get (
741 rtl_uString * pName,
742 rtl_uString ** ppValue,
743 rtl_uString * pDefault
746 return rtl_bootstrap_get_from_handle(nullptr, pName, ppValue, pDefault);
749 void SAL_CALL rtl_bootstrap_set (
750 rtl_uString * pName,
751 rtl_uString * pValue
754 const OUString name(pName);
755 const OUString value(pValue);
757 osl::MutexGuard guard(osl::Mutex::getGlobalMutex());
759 for (auto & item : rtl_bootstrap_set_vector)
761 if (item.sName == name)
763 item.sValue = value;
764 return;
768 SAL_INFO("sal.bootstrap", "explicitly setting: name=" << name << " value=" <<value);
770 rtl_bootstrap_set_vector.emplace_back(name, value);
773 void SAL_CALL rtl_bootstrap_expandMacros_from_handle(
774 rtlBootstrapHandle handle,
775 rtl_uString ** macro)
777 if (!handle)
778 handle = get_static_bootstrap_handle();
780 OUString expanded(expandMacros(static_cast< Bootstrap_Impl * >(handle),
781 OUString::unacquired(macro),
782 LookupMode::NORMAL, nullptr));
783 rtl_uString_assign(macro, expanded.pData);
786 void SAL_CALL rtl_bootstrap_expandMacros(rtl_uString ** macro)
788 rtl_bootstrap_expandMacros_from_handle(nullptr, macro);
791 void rtl_bootstrap_encode(rtl_uString const * value, rtl_uString ** encoded)
793 OSL_ASSERT(value);
794 OUStringBuffer b(value->length+5);
795 for (sal_Int32 i = 0; i < value->length; ++i)
797 sal_Unicode c = value->buffer[i];
798 if (c == '$' || c == '\\')
799 b.append('\\');
801 b.append(c);
804 rtl_uString_assign(encoded, b.makeStringAndClear().pData);
807 namespace {
809 int hex(sal_Unicode c)
811 return
812 c >= '0' && c <= '9' ? c - '0' :
813 c >= 'A' && c <= 'F' ? c - 'A' + 10 :
814 c >= 'a' && c <= 'f' ? c - 'a' + 10 : -1;
817 sal_Unicode read(std::u16string_view text, std::size_t * pos, bool * escaped)
819 OSL_ASSERT(pos && *pos < text.length() && escaped);
820 sal_Unicode c = text[(*pos)++];
821 if (c == '\\')
823 int n1, n2, n3, n4;
824 if (*pos < text.length() - 4 && text[*pos] == 'u' &&
825 ((n1 = hex(text[*pos + 1])) >= 0) &&
826 ((n2 = hex(text[*pos + 2])) >= 0) &&
827 ((n3 = hex(text[*pos + 3])) >= 0) &&
828 ((n4 = hex(text[*pos + 4])) >= 0))
830 *pos += 5;
831 *escaped = true;
832 return static_cast< sal_Unicode >(
833 (n1 << 12) | (n2 << 8) | (n3 << 4) | n4);
836 if (*pos < text.length())
838 *escaped = true;
839 return text[(*pos)++];
843 *escaped = false;
844 return c;
847 OUString lookup(
848 Bootstrap_Impl const * file, LookupMode mode, bool override,
849 OUString const & key, ExpandRequestLink const * requestStack)
851 OUString v;
852 (file == nullptr ? get_static_bootstrap_handle() : file)->getValue(
853 key, &v.pData, nullptr, mode, override, requestStack);
854 return v;
857 OUString expandMacros(
858 Bootstrap_Impl const * file, std::u16string_view text, LookupMode mode,
859 ExpandRequestLink const * requestStack)
861 SAL_INFO("sal.bootstrap", "expandMacros called with: " << OUString(text));
862 OUStringBuffer buf(2048);
864 for (std::size_t i = 0; i < text.length();)
866 bool escaped;
867 sal_Unicode c = read(text, &i, &escaped);
868 if (escaped || c != '$')
870 buf.append(c);
872 else
874 if (i < text.length() && text[i] == '{')
876 ++i;
877 std::size_t p = i;
878 sal_Int32 nesting = 0;
879 OUString seg[3];
880 int n = 0;
882 while (i < text.length())
884 std::size_t j = i;
885 c = read(text, &i, &escaped);
887 if (!escaped)
889 switch (c)
891 case '{':
892 ++nesting;
893 break;
894 case '}':
895 if (nesting == 0)
897 seg[n++] = text.substr(p, j - p);
898 goto done;
900 else
902 --nesting;
904 break;
905 case ':':
906 if (nesting == 0 && n < 2)
908 seg[n++] = text.substr(p, j - p);
909 p = i;
911 break;
915 done:
916 for (int j = 0; j < n; ++j)
918 seg[j] = expandMacros(file, seg[j], mode, requestStack);
921 if (n == 3 && seg[0] != ".override" && seg[1].isEmpty())
923 // For backward compatibility, treat ${file::key} the
924 // same as just ${file:key}:
925 seg[1] = seg[2];
926 n = 2;
929 if (n == 1)
931 buf.append(lookup(file, mode, false, seg[0], requestStack));
933 else if (n == 2)
935 rtl::Bootstrap b(seg[0]);
936 Bootstrap_Impl * f = static_cast< Bootstrap_Impl * >(b.getHandle());
937 buf.append(lookup(f, mode, false, seg[1], requestStack));
939 else if (n == 3 && seg[0] == ".override")
941 rtl::Bootstrap b(seg[1]);
942 Bootstrap_Impl * f = static_cast< Bootstrap_Impl * >(b.getHandle());
943 buf.append(lookup(f, mode, f != nullptr, seg[2], requestStack));
945 else
947 // Going through osl::Profile, this code erroneously
948 // does not recursively expand macros in the resulting
949 // replacement text (and if it did, it would fail to
950 // detect cycles that pass through here):
951 buf.append(
952 OStringToOUString(
953 osl::Profile(seg[0]).readString(
954 OUStringToOString(
955 seg[1], RTL_TEXTENCODING_UTF8),
956 OUStringToOString(
957 seg[2], RTL_TEXTENCODING_UTF8),
958 OString()),
959 RTL_TEXTENCODING_UTF8));
962 else
964 OUStringBuffer kbuf(sal_Int32(text.length()));
965 for (; i < text.length();)
967 std::size_t j = i;
968 c = read(text, &j, &escaped);
969 if (!escaped &&
970 (c == ' ' || c == '$' || c == '-' || c == '/' ||
971 c == ';' || c == '\\'))
973 break;
976 kbuf.append(c);
977 i = j;
980 buf.append(
981 lookup(
982 file, mode, false, kbuf.makeStringAndClear(),
983 requestStack));
988 OUString result(buf.makeStringAndClear());
989 SAL_INFO("sal.bootstrap", "expandMacros result: " << result);
991 return result;
996 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */