Coverage Report

Created: 2026-09-14 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/logging-log4cxx/src/main/cpp/charsetdecoder.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
#define NOMINMAX /* tell windows not to define min/max macros */
18
#include <log4cxx/private/string_c11.h>
19
#include <log4cxx/logstring.h>
20
#include <log4cxx/helpers/charsetdecoder.h>
21
#include <log4cxx/private/bytebuffer_priv.h>
22
#include <log4cxx/helpers/exception.h>
23
#include <log4cxx/helpers/pool.h>
24
#include <log4cxx/helpers/loglog.h>
25
#include <apr_xlate.h>
26
#if !defined(LOG4CXX)
27
  #define LOG4CXX 1
28
#endif
29
#include <log4cxx/private/log4cxx_private.h>
30
#include <locale.h>
31
#include <apr_portable.h>
32
#include <log4cxx/helpers/stringhelper.h>
33
#include <log4cxx/helpers/transcoder.h>
34
#include <mutex>
35
36
using namespace LOG4CXX_NS;
37
using namespace LOG4CXX_NS::helpers;
38
39
IMPLEMENT_LOG4CXX_OBJECT(CharsetDecoder)
40
41
42
namespace LOG4CXX_NS
43
{
44
namespace helpers
45
{
46
47
#if APR_HAS_XLATE
48
/**
49
 *  Converts from an arbitrary encoding to LogString
50
 *    using apr_xlate.  Requires real iconv implementation,
51
*    apr-iconv will crash in use.
52
 */
53
class APRCharsetDecoder : public CharsetDecoder
54
{
55
  public:
56
    /**
57
     *  Creates a new instance.
58
     *  @param frompage name of source encoding.
59
     */
60
480
    APRCharsetDecoder(const LogString& frompage) : pool()
61
480
    {
62
480
#if LOG4CXX_LOGCHAR_IS_WCHAR
63
480
      const char* topage = "WCHAR_T";
64
480
#endif
65
#if LOG4CXX_LOGCHAR_IS_UTF8
66
      const char* topage = "UTF-8";
67
#endif
68
#if LOG4CXX_LOGCHAR_IS_UNICHAR
69
      const char* topage = "UTF-16";
70
#endif
71
480
      std::string fpage(Transcoder::encodeCharsetName(frompage));
72
480
      apr_status_t stat = apr_xlate_open(&convset,
73
480
          topage,
74
480
          fpage.c_str(),
75
480
          pool.getAPRPool());
76
77
480
      if (stat != APR_SUCCESS)
78
0
      {
79
0
        throw IllegalArgumentException(frompage);
80
0
      }
81
480
    }
82
83
    /**
84
     *  Destructor.
85
     */
86
    virtual ~APRCharsetDecoder()
87
480
    {
88
480
    }
89
90
    virtual log4cxx_status_t decode(ByteBuffer& in_param,
91
      LogString& out)
92
229k
    {
93
229k
      auto& in = in_param.impl();
94
229k
      enum { BUFSIZE = 256 };
95
229k
      logchar buf[BUFSIZE];
96
229k
      const apr_size_t initial_outbytes_left = BUFSIZE * sizeof(logchar);
97
229k
      apr_status_t stat = APR_SUCCESS;
98
99
229k
      if (in.remaining() == 0)
100
477
      {
101
477
        size_t outbytes_left = initial_outbytes_left;
102
477
        {
103
477
          std::lock_guard<std::mutex> lock(mutex);
104
477
          stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
105
477
              NULL, NULL, (char*) buf, &outbytes_left);
106
477
        }
107
477
        out.append(buf, (initial_outbytes_left - outbytes_left) / sizeof(logchar));
108
477
      }
109
229k
      else
110
229k
      {
111
461k
        while (in.remaining() > 0 && stat == APR_SUCCESS)
112
231k
        {
113
231k
          size_t inbytes_left = in.remaining();
114
231k
          size_t initial_inbytes_left = inbytes_left;
115
231k
          apr_size_t outbytes_left = initial_outbytes_left;
116
231k
          {
117
231k
            std::lock_guard<std::mutex> lock(mutex);
118
231k
            stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
119
231k
                in.current(),
120
231k
                &inbytes_left,
121
231k
                (char*) buf,
122
231k
                &outbytes_left);
123
231k
          }
124
231k
          out.append(buf, (initial_outbytes_left - outbytes_left) / sizeof(logchar));
125
231k
          if (inbytes_left == initial_inbytes_left && stat == APR_SUCCESS)
126
0
          {
127
0
            stat = APR_BADCH;
128
0
            break;
129
0
          }
130
231k
          in.increment_position(initial_inbytes_left - inbytes_left);
131
231k
        }
132
229k
      }
133
134
229k
      return stat;
135
229k
    }
136
137
  private:
138
    APRCharsetDecoder(const APRCharsetDecoder&);
139
    APRCharsetDecoder& operator=(const APRCharsetDecoder&);
140
    LOG4CXX_NS::helpers::Pool pool;
141
    std::mutex mutex;
142
    apr_xlate_t* convset;
143
};
144
145
#endif
146
147
#if LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_MBSRTOWCS
148
/**
149
*    Converts from the default multi-byte string to
150
*        LogString using mbstowcs.
151
*
152
*/
153
class MbstowcsCharsetDecoder : public CharsetDecoder
154
{
155
  public:
156
    MbstowcsCharsetDecoder()
157
0
    {
158
0
    }
159
160
    virtual ~MbstowcsCharsetDecoder()
161
0
    {
162
0
    }
163
164
  private:
165
    inline log4cxx_status_t append(LogString& out, const wchar_t* buf)
166
0
    {
167
0
      out.append(buf);
168
0
      return APR_SUCCESS;
169
0
    }
170
171
    virtual log4cxx_status_t decode(ByteBuffer& in_param,
172
      LogString& out)
173
0
    {
174
0
      auto& in = in_param.impl();
175
0
      log4cxx_status_t stat = APR_SUCCESS;
176
0
      enum { BUFSIZE = 256 };
177
0
      wchar_t wbuf[BUFSIZE];
178
0
      char cbuf[BUFSIZE*4];
179
0
180
0
      mbstate_t mbstate;
181
0
      memset(&mbstate, 0, sizeof(mbstate));
182
0
183
0
      while (in.remaining() > 0)
184
0
      {
185
0
        const char* src = in.current();
186
0
187
0
        if (*src == 0)
188
0
        {
189
0
          out.append(1, (logchar) 0);
190
0
          in.increment_position(1);
191
0
        }
192
0
        else
193
0
        {
194
0
          auto available = std::min(sizeof (cbuf) - 1, in.remaining());
195
0
          strncpy(cbuf, src, available);
196
0
          cbuf[available] = 0;
197
0
          src = cbuf;
198
0
          size_t wCharCount = mbsrtowcs(wbuf,
199
0
              &src,
200
0
              BUFSIZE - 1,
201
0
              &mbstate);
202
0
          // mbsrtowcs sets *src to nullptr when it consumes a null wide character.
203
0
          // Performing pointer arithmetic on that nullptr (src - cbuf) is undefined
204
0
          // behaviour, so recover the consumed byte count from the position of the
205
0
          // null that stopped the conversion instead.
206
0
          size_t converted;
207
0
          if (src == nullptr)
208
0
          {
209
0
            size_t nullPos = 0;
210
0
            while (nullPos < available && cbuf[nullPos] != 0)
211
0
            {
212
0
              ++nullPos;
213
0
            }
214
0
            // If the null came from the input bytes, it was consumed too;
215
0
            // if it is the sentinel we wrote at cbuf[available], stop at available.
216
0
            converted = (nullPos < available) ? nullPos + 1 : available;
217
0
          }
218
0
          else
219
0
          {
220
0
            converted = static_cast<size_t>(src - cbuf);
221
0
          }
222
0
          in.increment_position(converted);
223
0
224
0
          if (wCharCount == (size_t) -1) // Illegal byte sequence?
225
0
          {
226
0
            LogString msg(LOG4CXX_STR("Illegal byte sequence at "));
227
0
            msg.append(std::to_wstring(in.position()));
228
0
            msg.append(LOG4CXX_STR(" of "));
229
0
            msg.append(std::to_wstring(in.limit()));
230
0
            LogLog::warn(msg);
231
0
            stat = APR_BADCH;
232
0
            break;
233
0
          }
234
0
          else
235
0
          {
236
0
            // FIX: Check for incomplete sequence infinite loop.
237
0
            // If mbsrtowcs returns success (>=0) but converted 0 bytes while data remains,
238
0
            // we are stuck (e.g. incomplete multibyte char at EOF).
239
0
            if (converted == 0 && in.remaining() > 0)
240
0
            {
241
0
              LogString msg(LOG4CXX_STR("Incomplete multibyte sequence at end of buffer"));
242
0
              LogLog::warn(msg);
243
0
              stat = APR_BADCH;
244
0
              break; // Break the infinite loop
245
0
            }
246
0
247
0
            wbuf[wCharCount] = 0;
248
0
            stat = append(out, wbuf);
249
0
          }
250
0
        }
251
0
      }
252
0
253
0
      return stat;
254
0
    }
255
256
257
258
  private:
259
    MbstowcsCharsetDecoder(const MbstowcsCharsetDecoder&);
260
    MbstowcsCharsetDecoder& operator=(const MbstowcsCharsetDecoder&);
261
};
262
#endif
263
264
265
/**
266
*    Decoder used when the external and internal charsets
267
*    are the same.
268
*
269
*/
270
class TrivialCharsetDecoder : public CharsetDecoder
271
{
272
  public:
273
    TrivialCharsetDecoder()
274
0
    {
275
0
    }
276
277
    virtual ~TrivialCharsetDecoder()
278
0
    {
279
0
    }
280
281
    virtual log4cxx_status_t decode(ByteBuffer& in_param,
282
      LogString& out)
283
0
    {
284
0
      auto& in = in_param.impl();
285
0
      size_t remaining = in.remaining();
286
0
287
0
      if ( remaining > 0)
288
0
      {
289
0
        auto src = in.current();
290
0
        auto count = remaining / sizeof(logchar);
291
0
        out.append(reinterpret_cast<const logchar*>(src), count);
292
0
        in.increment_position(remaining);
293
0
      }
294
0
295
0
      return APR_SUCCESS;
296
0
    }
297
298
299
300
  private:
301
    TrivialCharsetDecoder(const TrivialCharsetDecoder&);
302
    TrivialCharsetDecoder& operator=(const TrivialCharsetDecoder&);
303
};
304
305
/**
306
*    Converts from UTF-8 to LogString
307
*
308
*/
309
class UTF8CharsetDecoder : public CharsetDecoder
310
{
311
  public:
312
    UTF8CharsetDecoder()
313
320
    {
314
320
    }
315
316
    virtual ~UTF8CharsetDecoder()
317
0
    {
318
0
    }
319
320
  private:
321
    virtual log4cxx_status_t decode(ByteBuffer& in_param,
322
      LogString& out)
323
2.02M
    {
324
2.02M
      auto& in = in_param.impl();
325
2.02M
      auto availableByteCount = in.remaining();
326
5.77M
      while (0 < availableByteCount)
327
5.77M
      {
328
5.77M
        auto sv = getUTF8CodePoint(in);
329
5.77M
        auto nextAvailableByteCount = in.remaining();
330
5.77M
        if (sv == 0xFFFF || nextAvailableByteCount == availableByteCount)
331
2.02M
          return APR_BADCH;
332
3.75M
        Transcoder::encode(sv, out);
333
3.75M
        availableByteCount = nextAvailableByteCount;
334
3.75M
      }
335
2.26k
      return APR_SUCCESS;
336
2.02M
    }
337
338
  private:
339
    UTF8CharsetDecoder(const UTF8CharsetDecoder&);
340
    UTF8CharsetDecoder& operator=(const UTF8CharsetDecoder&);
341
};
342
343
/**
344
*    Converts from ISO-8859-1 to LogString.
345
*
346
*/
347
class ISOLatinCharsetDecoder : public CharsetDecoder
348
{
349
  public:
350
    ISOLatinCharsetDecoder()
351
201
    {
352
201
    }
353
354
    virtual ~ISOLatinCharsetDecoder()
355
0
    {
356
0
    }
357
358
  private:
359
    virtual log4cxx_status_t decode(ByteBuffer& in_param,
360
      LogString& out)
361
398
    {
362
398
      auto& in = in_param.impl();
363
398
      auto availableByteCount = in.remaining();
364
398
      auto src = in.current();
365
398
      auto srcEnd = src + availableByteCount;
366
367
999k
      while (src < srcEnd)
368
999k
      {
369
999k
        auto sv = static_cast<unsigned int>(static_cast<unsigned char>(*src++));
370
999k
        Transcoder::encode(sv, out);
371
999k
      }
372
398
      in.increment_position(availableByteCount);
373
374
398
      return APR_SUCCESS;
375
398
    }
376
377
378
379
  private:
380
    ISOLatinCharsetDecoder(const ISOLatinCharsetDecoder&);
381
    ISOLatinCharsetDecoder& operator=(const ISOLatinCharsetDecoder&);
382
};
383
384
385
/**
386
*    Converts from US-ASCII to LogString.
387
*
388
*/
389
class USASCIICharsetDecoder : public CharsetDecoder
390
{
391
  public:
392
    USASCIICharsetDecoder()
393
265
    {
394
265
    }
395
396
    virtual ~USASCIICharsetDecoder()
397
0
    {
398
0
    }
399
400
  private:
401
402
    virtual log4cxx_status_t decode(ByteBuffer& in_param,
403
      LogString& out)
404
341k
    {
405
341k
      auto& in = in_param.impl();
406
341k
      log4cxx_status_t stat = APR_SUCCESS;
407
408
341k
      auto availableByteCount = in.remaining();
409
341k
      auto src = in.current();
410
341k
      auto srcEnd = src + availableByteCount;
411
341k
      size_t byteCount = 0;
412
833k
      while (src < srcEnd)
413
832k
      {
414
832k
        auto sv = static_cast<unsigned int>(*src++);
415
416
832k
        if (sv < 0x80)
417
491k
        {
418
491k
          ++byteCount;
419
491k
          Transcoder::encode(sv, out);
420
491k
        }
421
341k
        else
422
341k
        {
423
341k
          stat = APR_BADCH;
424
341k
          break;
425
341k
        }
426
832k
      }
427
341k
      in.increment_position(byteCount);
428
429
341k
      return stat;
430
341k
    }
431
432
433
434
  private:
435
    USASCIICharsetDecoder(const USASCIICharsetDecoder&);
436
    USASCIICharsetDecoder& operator=(const USASCIICharsetDecoder&);
437
};
438
439
/**
440
 *    Charset decoder that uses current locale settings.
441
 */
442
class LocaleCharsetDecoder : public CharsetDecoder
443
{
444
  public:
445
0
    LocaleCharsetDecoder() : state()
446
0
    {
447
0
    }
448
    log4cxx_status_t decode(ByteBuffer& in_param, LogString& out) override
449
0
    {
450
0
      auto& in = in_param.impl();
451
0
      log4cxx_status_t result = APR_SUCCESS;
452
0
      auto p = in.current();
453
0
      auto availableByteCount = in.remaining();
454
0
      size_t byteCount = 0;
455
0
#if !LOG4CXX_CHARSET_EBCDIC
456
0
      if (std::mbsinit(&this->state)) // ByteBuffer not partially decoded?
457
0
      {
458
        // Copy single byte characters
459
0
        for (; byteCount < availableByteCount && static_cast<unsigned int>(*p) < 0x80; ++byteCount, ++p)
460
0
        {
461
0
          out.append(1, *p);
462
0
        }
463
0
      }
464
0
#endif
465
      // Decode characters that may be represented by multiple bytes
466
0
      while (byteCount < availableByteCount)
467
0
      {
468
0
        wchar_t ch = 0;
469
0
        size_t n = std::mbrtowc(&ch, p, availableByteCount - byteCount, &this->state);
470
0
        if (0 == n) // NULL encountered?
471
0
        {
472
0
          ++byteCount;
473
0
          break;
474
0
        }
475
0
        if (static_cast<std::size_t>(-1) == n) // decoding error?
476
0
        {
477
0
          result = APR_BADCH;
478
0
          break;
479
0
        }
480
0
        if (static_cast<std::size_t>(-2) == n) // incomplete sequence?
481
0
        {
482
0
          break;
483
0
        }
484
0
        Transcoder::encode(static_cast<unsigned int>(ch), out);
485
0
        byteCount += n;
486
0
        p += n;
487
0
      }
488
0
      in.increment_position(byteCount);
489
0
      return result;
490
0
    }
491
492
  private:
493
    std::mbstate_t state;
494
};
495
496
497
498
} // namespace helpers
499
500
}  //namespace log4cxx
501
502
503
CharsetDecoder::CharsetDecoder()
504
1.26k
{
505
1.26k
}
506
507
508
CharsetDecoder::~CharsetDecoder()
509
1.26k
{
510
1.26k
}
511
512
CharsetDecoder* CharsetDecoder::createDefaultDecoder()
513
1
{
514
1
#if LOG4CXX_CHARSET_UTF8
515
#if LOG4CXX_LOGCHAR_IS_UTF8
516
  return new TrivialCharsetDecoder();
517
#else
518
1
  return new UTF8CharsetDecoder();
519
1
#endif
520
#elif LOG4CXX_CHARSET_ISO88591 || defined(_WIN32_WCE)
521
  return new ISOLatinCharsetDecoder();
522
#elif LOG4CXX_CHARSET_USASCII
523
  return new USASCIICharsetDecoder();
524
#elif LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_MBSRTOWCS
525
  return new MbstowcsCharsetDecoder();
526
#else
527
  return new LocaleCharsetDecoder();
528
#endif
529
1
}
530
531
CharsetDecoderPtr CharsetDecoder::getDefaultDecoder()
532
1
{
533
1
  static WideLife<CharsetDecoderPtr> decoder(createDefaultDecoder());
534
535
  //
536
  //  if invoked after static variable destruction
537
  //     (if logging is called in the destructor of a static object)
538
  //     then create a new decoder.
539
  //
540
1
  if (decoder.value() == 0)
541
0
  {
542
0
    return CharsetDecoderPtr( createDefaultDecoder() );
543
0
  }
544
545
1
  return decoder;
546
1
}
547
548
CharsetDecoderPtr CharsetDecoder::getUTF8Decoder()
549
0
{
550
0
  return std::make_shared<UTF8CharsetDecoder>();
551
0
}
552
553
CharsetDecoderPtr CharsetDecoder::getISOLatinDecoder()
554
0
{
555
0
  return std::make_shared<ISOLatinCharsetDecoder>();
556
0
}
557
558
559
CharsetDecoderPtr CharsetDecoder::getDecoder(const LogString& charset)
560
1.26k
{
561
1.26k
  if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("UTF-8"), LOG4CXX_STR("utf-8")) ||
562
946
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("UTF8"), LOG4CXX_STR("utf8")) ||
563
946
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP65001"), LOG4CXX_STR("cp65001")))
564
319
  {
565
#if LOG4CXX_LOGCHAR_IS_UTF8
566
    return std::make_shared<TrivialCharsetDecoder>();
567
#else
568
319
    return std::make_shared<UTF8CharsetDecoder>();
569
319
#endif
570
319
  }
571
946
  else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("C"), LOG4CXX_STR("c")) ||
572
946
    charset == LOG4CXX_STR("646") ||
573
946
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("US-ASCII"), LOG4CXX_STR("us-ascii")) ||
574
681
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO646-US"), LOG4CXX_STR("iso646-US")) ||
575
681
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ANSI_X3.4-1968"), LOG4CXX_STR("ansi_x3.4-1968")) ||
576
681
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP20127"), LOG4CXX_STR("cp20127")))
577
265
  {
578
265
    return std::make_shared<USASCIICharsetDecoder>();
579
265
  }
580
681
  else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO-8859-1"), LOG4CXX_STR("iso-8859-1")) ||
581
480
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO-LATIN-1"), LOG4CXX_STR("iso-latin-1")) ||
582
480
    StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP1252"), LOG4CXX_STR("cp1252")))
583
201
  {
584
201
    return std::make_shared<ISOLatinCharsetDecoder>();
585
201
  }
586
480
  else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("LOCALE"), LOG4CXX_STR("locale")))
587
0
  {
588
0
    return std::make_shared<LocaleCharsetDecoder>();
589
0
  }
590
591
480
#if APR_HAS_XLATE
592
480
  return std::make_shared<APRCharsetDecoder>(charset);
593
#else
594
  throw IllegalArgumentException(charset);
595
#endif
596
1.26k
}
597
598
log4cxx_status_t CharsetDecoder::decode(const char* in, size_t maxByteCount, LogString& out)
599
0
{
600
0
  ByteBuffer buf((char*)in, strnlen_s(in, maxByteCount));
601
0
  return decode(buf, out);
602
0
}
603
604
unsigned int CharsetDecoder::getUTF8CodePoint(ByteBufferPriv& in)
605
19.6M
{
606
19.6M
  auto availableByteCount = in.remaining();
607
19.6M
  if (0 == availableByteCount)
608
0
    return 0xFFFF;
609
610
19.6M
  auto pChar = in.current();
611
19.6M
  auto ch1 = static_cast<unsigned char>(*pChar);
612
19.6M
  if (ch1 <= 0x7F)
613
13.5M
  {
614
13.5M
    in.increment_position(1);
615
13.5M
    return ch1;
616
13.5M
  }
617
618
  //
619
  //   should not have continuation character here
620
  //
621
6.11M
  if ((ch1 & 0xC0) != 0x80 && 1 < availableByteCount)
622
3.28M
  {
623
3.28M
    auto ch2 = static_cast<unsigned char>(*(pChar + 1));
624
3.28M
    if ((ch2 & 0xC0) != 0x80) // not a continuation?
625
2.28M
      return 0xFFFF;
626
627
1.00M
    if ((ch1 & 0xE0) == 0xC0)
628
71.4k
    {
629
71.4k
      unsigned int rv = ((ch1 & 0x1F) << 6) + (ch2 & 0x3F);
630
71.4k
      if (rv >= 0x80)
631
51.1k
      {
632
51.1k
        in.increment_position(2);
633
51.1k
        return rv;
634
51.1k
      }
635
20.3k
      return 0xFFFF;
636
71.4k
    }
637
638
929k
    if (2 < availableByteCount)
639
929k
    {
640
929k
      auto ch3 = static_cast<unsigned char>(*(pChar + 2));
641
929k
      if ((ch3 & 0xC0) != 0x80) // not a continuation?
642
37.9k
        return 0xFFFF;
643
644
891k
      if ((ch1 & 0xF0) == 0xE0)
645
657k
      {
646
657k
        unsigned int rv = ((ch1 & 0x0F) << 12)
647
657k
          + ((ch2 & 0x3F) << 6)
648
657k
          + (ch3 & 0x3F);
649
650
        // RFC 3629 §3 prohibits UTF-8 encodings of the UTF-16 surrogate
651
        // halves (U+D800..U+DFFF); accepting them lets malformed Unicode
652
        // cross the decode boundary into LogString and downstream output.
653
657k
        if (rv < 0x800 || (0xD800 <= rv && rv <= 0xDFFF))
654
2.55k
          return 0xFFFF;
655
656
654k
        in.increment_position(3);
657
654k
        return rv;
658
657k
      }
659
660
234k
      if (3 < availableByteCount)
661
233k
      {
662
233k
        auto ch4 = static_cast<unsigned char>(*(pChar + 3));
663
233k
        if ((ch4 & 0xC0) != 0x80) // not a continuation?
664
10.6k
          return 0xFFFF;
665
666
223k
        unsigned int rv = ((ch1 & 0x07) << 18)
667
223k
          + ((ch2 & 0x3F) << 12)
668
223k
          + ((ch3 & 0x3F) << 6)
669
223k
          + (ch4 & 0x3F);
670
671
        // RFC 3629 §3 caps UTF-8 at U+10FFFF; lead bytes F5..F7 (and
672
        // F4 with an over-high trailer) produce rv > 0x10FFFF, which
673
        // is not a Unicode code point. Without this bound, encodeUTF16
674
        // later silently aliases the bogus value to a valid in-range
675
        // code point — a substitution-collision filter-bypass primitive.
676
        // Lead bytes F8..FF are never valid UTF-8, but the & 0x07 mask
677
        // discards their high bits, so without the (ch1 & 0xF8) == 0xF0
678
        // guard F8 BF BF BF would alias to U+3FFFF instead of being
679
        // rejected.
680
223k
        if ((ch1 & 0xF8) == 0xF0 && rv > 0xFFFF && rv <= 0x10FFFF)
681
200k
        {
682
200k
          in.increment_position(4);
683
200k
          return rv;
684
200k
        }
685
686
223k
      }
687
234k
    }
688
929k
  }
689
2.85M
  return 0xFFFF;
690
6.11M
}