Coverage Report

Created: 2026-06-15 06:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/optionconverter.cpp
Line
Count
Source
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one or more
3
 * contributor license agreements.  See the NOTICE file distributed with
4
 * this work for additional information regarding copyright ownership.
5
 * The ASF licenses this file to You under the Apache License, Version 2.0
6
 * (the "License"); you may not use this file except in compliance with
7
 * the License.  You may obtain a copy of the License at
8
 *
9
 *      http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
18
#include <log4cxx/logstring.h>
19
#include <log4cxx/spi/loggerfactory.h>
20
#include <log4cxx/spi/loggerrepository.h>
21
#include <log4cxx/appenderskeleton.h>
22
#include <log4cxx/helpers/optionconverter.h>
23
#include <algorithm>
24
#include <cctype>
25
#include <ctype.h>
26
#include <log4cxx/helpers/stringhelper.h>
27
#include <log4cxx/helpers/exception.h>
28
#include <stdlib.h>
29
#include <cerrno>
30
#include <limits>
31
#include <log4cxx/helpers/properties.h>
32
#include <log4cxx/helpers/loglog.h>
33
#include <log4cxx/level.h>
34
#include <log4cxx/helpers/object.h>
35
#include <log4cxx/helpers/class.h>
36
#include <log4cxx/helpers/loader.h>
37
#include <log4cxx/helpers/system.h>
38
#include <log4cxx/propertyconfigurator.h>
39
#include <log4cxx/helpers/transcoder.h>
40
#include <log4cxx/file.h>
41
#include <log4cxx/xml/domconfigurator.h>
42
#include <log4cxx/logmanager.h>
43
#if !defined(LOG4CXX)
44
  #define LOG4CXX 1
45
#endif
46
#include <log4cxx/helpers/aprinitializer.h>
47
#include <log4cxx/helpers/filewatchdog.h>
48
#include <log4cxx/helpers/singletonholder.h>
49
50
namespace
51
{
52
using namespace LOG4CXX_NS;
53
54
void skipWhitespace(char*& p)
55
0
{
56
0
  while (std::isspace(static_cast<unsigned char>(*p)))
57
0
  {
58
0
    ++p;
59
0
  }
60
0
}
61
62
/// For recursion checking
63
struct LogStringChain
64
{
65
  const LogString& item;
66
  const LogStringChain* parent;
67
};
68
69
/// Is \c item referenced in \c path
70
bool isRecursiveReference(const LogString& newkey, const LogStringChain* path)
71
0
{
72
0
  bool result = false;
73
0
  if (path->item == newkey)
74
0
    result = true;
75
0
  else if (path->parent)
76
0
    result = isRecursiveReference(newkey, path->parent);
77
0
  return result;
78
0
}
79
80
LogString substVarsSafely(const LogString& val, helpers::Properties& props, const LogStringChain* path = 0)
81
5
{
82
5
  LogString sbuf;
83
5
  const logchar delimStartArray[] = { 0x24, 0x7B, 0 }; // '$', '{'
84
5
  const LogString delimStart(delimStartArray);
85
5
  const logchar delimStop = 0x7D; // '}';
86
5
  const size_t DELIM_START_LEN = 2;
87
5
  const size_t DELIM_STOP_LEN = 1;
88
89
5
  size_t i = 0;
90
91
5
  while (true)
92
5
  {
93
5
    size_t j = val.find(delimStart, i);
94
95
5
    if (j == val.npos)
96
5
    {
97
      // no more variables
98
5
      if (i == 0)
99
5
      {
100
        // this is a simple string
101
5
        return val;
102
5
      }
103
0
      else
104
0
      {
105
        // add the tail string which contails no variables and return the result.
106
0
        sbuf.append(val.substr(i, val.length() - i));
107
0
        return sbuf;
108
0
      }
109
5
    }
110
0
    else
111
0
    {
112
0
      sbuf.append(val.substr(i, j - i));
113
0
      size_t k = val.find(delimStop, j);
114
115
0
      if (k == val.npos)
116
0
      {
117
0
        LogString msg(1, (logchar) 0x22 /* '\"' */);
118
0
        msg.append(val);
119
0
        msg.append(LOG4CXX_STR("\" has no closing brace. Opening brace at position "));
120
0
        helpers::Pool p;
121
0
        helpers::StringHelper::toString(j, msg);
122
0
        msg.append(1, (logchar) 0x2E /* '.' */);
123
0
        throw helpers::IllegalArgumentException(msg);
124
0
      }
125
0
      else
126
0
      {
127
0
        j += DELIM_START_LEN;
128
0
        LogString key = val.substr(j, k - j);
129
0
        if (path && isRecursiveReference(key, path))
130
0
        {
131
0
          LogString msg(LOG4CXX_STR("The variable ${"));
132
0
          msg.append(key);
133
0
          msg.append(LOG4CXX_STR("} is used recursively"));
134
0
          throw helpers::IllegalArgumentException(msg);
135
0
        }
136
137
        // first try in System properties
138
0
        LogString replacement(helpers::OptionConverter::getSystemProperty(key, LogString()));
139
140
        // then try props parameter
141
0
        if (replacement.empty())
142
0
        {
143
0
          replacement = props.getProperty(key);
144
0
        }
145
146
0
        if (!replacement.empty())
147
0
        {
148
          // Do variable substitution on the replacement string
149
          // such that we can solve "Hello ${x2}" as "Hello p1"
150
          // the where the properties are
151
          // x1=p1
152
          // x2=${x1}
153
0
          LogStringChain current{ key, path };
154
0
          LogString recursiveReplacement = substVarsSafely(replacement, props, &current);
155
0
          sbuf.append(recursiveReplacement);
156
0
        }
157
158
0
        i = k + DELIM_STOP_LEN;
159
0
      }
160
0
    }
161
5
  }
162
5
}
163
164
} // namespace
165
166
namespace LOG4CXX_NS
167
{
168
169
class ConfiguratorWatchdog  : public helpers::FileWatchdog
170
{
171
  spi::ConfiguratorPtr m_config;
172
  public:
173
    ConfiguratorWatchdog(const spi::ConfiguratorPtr& config, const File& filename)
174
0
        : helpers::FileWatchdog(filename)
175
0
        , m_config(config)
176
0
    {
177
0
    }
178
179
    /**
180
    Call PropertyConfigurator#doConfigure(const String& configFileName,
181
    const spi::LoggerRepositoryPtr& hierarchy) with the
182
    <code>filename</code> to reconfigure log4cxx.
183
    */
184
    void doOnChange() override
185
0
    {
186
0
        m_config->doConfigure(file(), LogManager::getLoggerRepository());
187
0
    }
188
189
  static void startWatching(const spi::ConfiguratorPtr& config, const File& filename, long delay)
190
0
  {
191
0
    using WatchdogHolder = helpers::SingletonHolder<ConfiguratorWatchdog>;
192
0
    auto pHolder = helpers::APRInitializer::getOrAddUnique<WatchdogHolder>
193
0
      ( [&config, &filename]() -> helpers::ObjectPtr
194
0
        { return std::make_shared<WatchdogHolder>(config, filename); }
195
0
      );
196
0
    auto& dog = pHolder->value();
197
0
    dog.m_config = config;
198
0
    dog.setFile(filename);
199
0
    dog.setDelay(delay);
200
0
    dog.start();
201
0
  }
202
};
203
204
}
205
206
using namespace LOG4CXX_NS;
207
using namespace LOG4CXX_NS::helpers;
208
using namespace LOG4CXX_NS::spi;
209
210
211
LogString OptionConverter::convertSpecialChars(const LogString& s)
212
1
{
213
1
  logchar c;
214
1
  LogString sbuf;
215
216
1
  LogString::const_iterator i = s.begin();
217
218
24
  while (i != s.end())
219
23
  {
220
23
    c = *i++;
221
222
23
    if (i != s.end() && c == 0x5C /* '\\' */)
223
0
    {
224
0
      c =  *i++;
225
226
0
      switch (c)
227
0
      {
228
0
        case 0x6E: //'n'
229
0
          c = 0x0A;
230
0
          break;
231
232
0
        case 0x72: //'r'
233
0
          c = 0x0D;
234
0
          break;
235
236
0
        case 0x74: //'t'
237
0
          c = 0x09;
238
0
          break;
239
240
0
        case 0x66: //'f'
241
0
          c = 0x0C;
242
0
          break;
243
244
0
        default:
245
0
          break;
246
0
      }
247
0
    }
248
249
23
    sbuf.append(1, c);
250
23
  }
251
252
1
  return sbuf;
253
1
}
254
255
256
bool OptionConverter::toBoolean(const LogString& value, bool dEfault)
257
2
{
258
2
  if (value.length() >= 4)
259
2
  {
260
2
    if (StringHelper::equalsIgnoreCase(value.substr(0, 4),
261
2
        LOG4CXX_STR("TRUE"), LOG4CXX_STR("true")))
262
1
    {
263
1
      return true;
264
1
    }
265
2
  }
266
267
1
  if (dEfault && value.length() >= 5)
268
0
  {
269
0
    if (StringHelper::equalsIgnoreCase(value.substr(0, 5),
270
0
        LOG4CXX_STR("FALSE"), LOG4CXX_STR("false")))
271
0
    {
272
0
      return false;
273
0
    }
274
0
  }
275
276
1
  return dEfault;
277
1
}
278
279
int OptionConverter::toInt(const LogString& value, int dEfault)
280
0
{
281
0
  LogString trimmed(StringHelper::trim(value));
282
283
0
  if (trimmed.empty())
284
0
  {
285
0
    return dEfault;
286
0
  }
287
288
0
  LOG4CXX_ENCODE_CHAR(cvalue, trimmed);
289
290
0
  char* endptr = nullptr;
291
0
  errno = 0;
292
0
  long long parsed = strtoll(cvalue.c_str(), &endptr, 10);
293
294
0
  if (endptr == cvalue.c_str() || *endptr != '\0' || errno == ERANGE ||
295
0
    parsed < (std::numeric_limits<int>::min)() ||
296
0
    parsed > (std::numeric_limits<int>::max)())
297
0
  {
298
0
    return dEfault;
299
0
  }
300
301
0
  return static_cast<int>(parsed);
302
0
}
303
304
long OptionConverter::toFileSize(const LogString& s, long dEfault)
305
0
{
306
0
  LogString trimmed(StringHelper::trim(s));
307
0
  if (trimmed.empty())
308
0
  {
309
0
    return dEfault;
310
0
  }
311
312
0
  long long multiplier = 1;
313
0
  LOG4CXX_ENCODE_CHAR(cvalue, trimmed);
314
0
  char* endptr;
315
0
  errno = 0;
316
0
  long long parsed = strtoll(cvalue.c_str(), &endptr, 10);
317
318
0
  if (endptr == cvalue.c_str() || errno == ERANGE || parsed < 0)
319
0
  {
320
0
    return dEfault;
321
0
  }
322
323
0
  skipWhitespace(endptr);
324
0
  if (*endptr != '\0')
325
0
  {
326
0
    const char unit = static_cast<char>(std::toupper(static_cast<unsigned char>(*endptr)));
327
0
    if (unit == 'K')
328
0
    {
329
0
      multiplier = 1024;
330
0
      ++endptr;
331
0
    }
332
0
    else if (unit == 'M')
333
0
    {
334
0
      multiplier = 1024 * 1024;
335
0
      ++endptr;
336
0
    }
337
0
    else if (unit == 'G')
338
0
    {
339
0
      multiplier = 1024 * 1024 * 1024;
340
0
      ++endptr;
341
0
    }
342
343
0
    if (static_cast<char>(std::toupper(static_cast<unsigned char>(*endptr))) != 'B')
344
0
    {
345
0
      return dEfault;
346
0
    }
347
0
    ++endptr;
348
0
    skipWhitespace(endptr);
349
0
    if (*endptr != '\0')
350
0
    {
351
0
      return dEfault;
352
0
    }
353
0
  }
354
355
0
  if (multiplier != 0 && parsed > std::numeric_limits<long long>::max() / multiplier)
356
0
  {
357
0
    return dEfault;
358
0
  }
359
360
0
  long long scaled = parsed * multiplier;
361
0
  if (scaled > std::numeric_limits<long>::max())
362
0
  {
363
0
    return dEfault;
364
0
  }
365
366
0
  return static_cast<long>(scaled);
367
0
}
368
369
LogString OptionConverter::findAndSubst(const LogString& key, Properties& props)
370
8
{
371
8
  LogString value(props.getProperty(key));
372
373
8
  if (value.empty())
374
3
  {
375
3
    return value;
376
3
  }
377
378
5
  try
379
5
  {
380
5
    return substVars(value, props);
381
5
  }
382
5
  catch (IllegalArgumentException& e)
383
5
  {
384
0
    LogLog::error(((LogString) LOG4CXX_STR("Bad option value ["))
385
0
      + value + LOG4CXX_STR("]."), e);
386
0
    return value;
387
0
  }
388
5
}
389
390
LogString OptionConverter::substVars(const LogString& val, Properties& props)
391
5
{
392
5
  return substVarsSafely(val, props);
393
5
}
394
395
LogString OptionConverter::getSystemProperty(const LogString& key, const LogString& def)
396
2
{
397
2
  if (!key.empty())
398
2
  {
399
2
    LogString value(System::getProperty(key));
400
401
2
    if (!value.empty())
402
0
    {
403
0
      return value;
404
0
    }
405
2
  }
406
407
2
  return def;
408
2
}
409
410
LevelPtr OptionConverter::toLevel(const LogString& value,
411
  const LevelPtr& defaultValue)
412
1
{
413
1
  size_t hashIndex = value.find(LOG4CXX_STR("#"));
414
415
1
  if (hashIndex == LogString::npos)
416
1
  {
417
    // no class name specified : use standard Level class
418
1
    if (value.empty())
419
0
    {
420
0
      return defaultValue;
421
0
    }
422
1
    else
423
1
    {
424
1
      return Level::toLevelLS(value, defaultValue);
425
1
    }
426
1
  }
427
428
0
  LogString clazz = value.substr(hashIndex + 1);
429
0
  LogString levelName = value.substr(0, hashIndex);
430
431
  // This is degenerate case but you never know.
432
0
  if (levelName.empty() || clazz.empty())
433
0
  {
434
0
    return Level::toLevelLS(value, defaultValue);
435
0
  }
436
0
  if (LogLog::isDebugEnabled())
437
0
  {
438
0
    LogLog::debug(LOG4CXX_STR("Desired ") + Level::getStaticClass().getName()
439
0
        + LOG4CXX_STR(" sub-class: [") + clazz + LOG4CXX_STR("]"));
440
0
  }
441
442
0
  try
443
0
  {
444
    // Note: the dynamic_cast could fail across DLL boundaries.
445
    // However, without the dynamic_cast a poorly formed XML file
446
    // could attempt to load an invalid class as a filter, causing
447
    // a crash.  If it can't be converted, a std::bad_cast should be
448
    // thrown(and caught by the exception handler below)
449
0
    const Level::LevelClass& levelClass =
450
0
      dynamic_cast<const Level::LevelClass&>(Loader::loadClass(clazz));
451
0
    return levelClass.toLevel(levelName);
452
0
  }
453
0
  catch (Exception& oops)
454
0
  {
455
0
    LogLog::error(LOG4CXX_STR("Could not create ") + Level::getStaticClass().getName() + LOG4CXX_STR(" sub-class"), oops);
456
0
  }
457
0
  catch (const std::bad_cast&)
458
0
  {
459
0
    LogLog::warn(
460
0
      LOG4CXX_STR("class [") + clazz + LOG4CXX_STR("] unable to be converted to "
461
0
      "Level::LevelClass"));
462
0
  }
463
0
  catch (...)
464
0
  {
465
0
    LogLog::warn(
466
0
      LOG4CXX_STR("class [") + clazz + LOG4CXX_STR("], level [") + levelName +
467
0
      LOG4CXX_STR("] conversion) failed."));
468
0
  }
469
470
0
  return defaultValue;
471
0
}
472
473
474
ObjectPtr OptionConverter::instantiateByKey(Properties& props, const LogString& key,
475
  const Class& superClass, const ObjectPtr& defaultValue)
476
2
{
477
  // Get the value of the property in string form
478
2
  LogString className(findAndSubst(key, props));
479
480
2
  if (className.empty())
481
0
  {
482
0
    LogLog::error(
483
0
      ((LogString) LOG4CXX_STR("Could not find value for key ")) + key);
484
0
    return defaultValue;
485
0
  }
486
487
  // Trim className to avoid trailing spaces that cause problems.
488
2
  return OptionConverter::instantiateByClassName(
489
2
      StringHelper::trim(className), superClass, defaultValue);
490
2
}
491
492
ObjectPtr OptionConverter::instantiateByClassName(const LogString& className,
493
  const Class& superClass, const ObjectPtr& defaultValue)
494
2
{
495
2
  if (LogLog::isDebugEnabled())
496
0
  {
497
0
    LogLog::debug(LOG4CXX_STR("Desired ") + superClass.getName()
498
0
      + LOG4CXX_STR(" sub-class: [") + className + LOG4CXX_STR("]"));
499
0
  }
500
2
  try
501
2
  {
502
2
    const Class& classObj = Loader::loadClass(className);
503
2
    ObjectPtr newObject =  ObjectPtr(classObj.newInstance());
504
505
2
    if (!newObject->instanceof(superClass))
506
0
    {
507
0
      LogLog::error(LOG4CXX_STR("Not a ") + superClass.getName() + LOG4CXX_STR(" sub-class"));
508
0
      return defaultValue;
509
0
    }
510
511
2
    return newObject;
512
2
  }
513
2
  catch (Exception& e)
514
2
  {
515
0
    LogLog::error(LOG4CXX_STR("Could not create ") + superClass.getName() + LOG4CXX_STR(" sub-class"), e);
516
0
  }
517
518
0
  return defaultValue;
519
2
}
520
521
void OptionConverter::selectAndConfigure(const File& configFileName,
522
  const LogString& _clazz, spi::LoggerRepositoryPtr hierarchy, int delay)
523
0
{
524
0
  ConfiguratorPtr configurator;
525
0
  LogString clazz = _clazz;
526
527
0
  LogString filename(configFileName.getPath());
528
529
0
#if LOG4CXX_HAS_DOMCONFIGURATOR
530
0
  if (clazz.empty()
531
0
    && filename.length() > 4
532
0
    && StringHelper::equalsIgnoreCase(
533
0
      filename.substr(filename.length() - 4),
534
0
      LOG4CXX_STR(".XML"), LOG4CXX_STR(".xml")))
535
0
  {
536
0
    clazz = LOG4CXX_NS::xml::DOMConfigurator::getStaticClass().getName();
537
0
  }
538
0
#endif
539
540
0
  if (!clazz.empty())
541
0
  {
542
0
    if (LogLog::isDebugEnabled())
543
0
      LogLog::debug(LOG4CXX_STR("Preferred configurator class: ") + clazz);
544
0
    const Class& clazzObj = Loader::loadClass(clazz);
545
0
    ObjectPtr obj = ObjectPtr(clazzObj.newInstance());
546
0
    configurator = LOG4CXX_NS::cast<Configurator>(obj);
547
548
0
    if (configurator == 0)
549
0
    {
550
0
      LogLog::error(LOG4CXX_STR("Could not instantiate configurator [")
551
0
        + clazz + LOG4CXX_STR("]."));
552
0
      return;
553
0
    }
554
0
  }
555
0
  else
556
0
  {
557
0
    configurator = std::make_shared<PropertyConfigurator>();
558
0
  }
559
560
0
  if (0 < delay)
561
0
    ConfiguratorWatchdog::startWatching(configurator, configFileName, delay);
562
0
  else
563
0
    configurator->doConfigure(configFileName, hierarchy);
564
0
}