This is an automated email from the ASF dual-hosted git repository.
swebb2066 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git
The following commit(s) were added to refs/heads/master by this push:
new acaf3886 Encode an invalid codepoint using a charset-specific
character (#730)
acaf3886 is described below
commit acaf3886293423198b5ef0da3e1c1244d7e7d805
Author: Stephen Webb <[email protected]>
AuthorDate: Wed Aug 19 09:29:54 2026 +1000
Encode an invalid codepoint using a charset-specific character (#730)
* Centralises decoding logic and uses the Unicode replacement character
codepoint to indicate a failure.
* UTF-8 encoding is available (using CharsetEncoder::getUTF8Encoder()) when
Log4cxx is built using LOG4CXX_CHARSET=utf-8. (Peviously, when Log4cxx was
built using LOG4CXX_CHARSET=utf-8, the CharsetEncoder::getUTF8Encoder()
function returned a TrivialCharsetEncoder.)
* The Telnet appender now uses the Unicode replacement character codepoint
to indicate a message encoding failure. Old behaviour is obtained by explicitly
configuring "Encoding" to "US-ASCII".
* Document the 'CharsetEncoder::encode' method behaviour and preconditions
* Remove CharsetEncoder::flush in the next ABI version
---
.github/workflows/abi-compatibility.yml | 2 -
.github/workflows/log4cxx-cpp11.yml | 3 +-
.github/workflows/log4cxx-macos.yml | 2 +-
.github/workflows/log4cxx-msys2.yml | 2 +-
.github/workflows/log4cxx-ubuntu.yml | 32 +--
.github/workflows/package_code.yml | 2 +-
.github/workflows/sonarcloud.yml | 1 -
src/fuzzers/cpp/TranscoderFuzzer.cpp | 2 +-
src/main/cpp/charsetencoder.cpp | 256 +++++++++++++++++-----
src/main/cpp/jsonlayout.cpp | 23 +-
src/main/cpp/outputstreamwriter.cpp | 1 -
src/main/cpp/telnetappender.cpp | 19 +-
src/main/cpp/transcoder.cpp | 135 ++++++------
src/main/cpp/transform.cpp | 46 +---
src/main/include/log4cxx/helpers/charsetencoder.h | 35 +--
src/main/include/log4cxx/helpers/transcoder.h | 51 ++++-
src/test/cpp/helpers/charsetencodertestcase.cpp | 7 +-
src/test/cpp/helpers/transcodertestcase.cpp | 59 ++---
18 files changed, 391 insertions(+), 287 deletions(-)
diff --git a/.github/workflows/abi-compatibility.yml
b/.github/workflows/abi-compatibility.yml
index fd5d1c06..894e890c 100644
--- a/.github/workflows/abi-compatibility.yml
+++ b/.github/workflows/abi-compatibility.yml
@@ -27,7 +27,6 @@ jobs:
job:
name: abi-check
runs-on: ubuntu-latest
- timeout-minutes: 38
strategy:
fail-fast: false
@@ -39,7 +38,6 @@ jobs:
- name: 'Configure Dependencies - Ubuntu'
run: |
- sudo apt-get update
sudo apt-get install -y g++-11 libapr1-dev libaprutil1-dev elfutils
vtable-dumper universal-ctags
- name: 'run CMake'
diff --git a/.github/workflows/log4cxx-cpp11.yml
b/.github/workflows/log4cxx-cpp11.yml
index e2f3412b..16d8928f 100644
--- a/.github/workflows/log4cxx-cpp11.yml
+++ b/.github/workflows/log4cxx-cpp11.yml
@@ -20,7 +20,6 @@ jobs:
job:
name: log4cxx-cpp11
runs-on: ubuntu-latest
- timeout-minutes: 38
steps:
- uses: actions/checkout@v6
with:
@@ -29,7 +28,6 @@ jobs:
- name: 'Configure Dependencies - Ubuntu'
run: |
- sudo apt-get update
sudo apt-get install -y libapr1-dev libaprutil1-dev libesmtp-dev
- name: 'run cmake - *nix'
@@ -42,6 +40,7 @@ jobs:
- name: run unit tests
shell: pwsh
+ timeout-minutes: 10
run: |
cd main
cd build
diff --git a/.github/workflows/log4cxx-macos.yml
b/.github/workflows/log4cxx-macos.yml
index b7ad1812..b46cd04c 100644
--- a/.github/workflows/log4cxx-macos.yml
+++ b/.github/workflows/log4cxx-macos.yml
@@ -20,7 +20,6 @@ jobs:
job:
name: ${{ matrix.os }}-${{ matrix.cxx }}-build-and-test
runs-on: ${{ matrix.os }}
- timeout-minutes: 38
strategy:
fail-fast: false
matrix:
@@ -98,6 +97,7 @@ jobs:
cmake --build .
- name: run unit tests
+ timeout-minutes: 10
run: |
cd main
cd build
diff --git a/.github/workflows/log4cxx-msys2.yml
b/.github/workflows/log4cxx-msys2.yml
index a75ed766..c859deca 100644
--- a/.github/workflows/log4cxx-msys2.yml
+++ b/.github/workflows/log4cxx-msys2.yml
@@ -20,7 +20,6 @@ jobs:
job:
name: ${{ matrix.env }}-${{ matrix.cxx }}-build-and-test
runs-on: windows-2022
- timeout-minutes: 30
strategy:
fail-fast: false
matrix:
@@ -74,6 +73,7 @@ jobs:
- name: 'run unit tests'
shell: msys2 {0}
+ timeout-minutes: 10
run: |
cd log4cxx/build
ctest -C Debug --output-on-failure
diff --git a/.github/workflows/log4cxx-ubuntu.yml
b/.github/workflows/log4cxx-ubuntu.yml
index 11367e19..91234878 100644
--- a/.github/workflows/log4cxx-ubuntu.yml
+++ b/.github/workflows/log4cxx-ubuntu.yml
@@ -20,14 +20,13 @@ jobs:
job:
name: ${{ matrix.os }}-${{ matrix.cxx }}-build-and-test
runs-on: ${{ matrix.os }}
- timeout-minutes: 38
strategy:
fail-fast: false
matrix:
- name: [ubuntu22-gcc, ubuntu22-clang, ubuntu24-gcc, ubuntu24-clang]
+ name: [ubuntu24-gcc, ubuntu24-clang, ubuntu26-gcc, ubuntu26-clang]
include:
- - name: ubuntu22-gcc
- os: ubuntu-22.04
+ - name: ubuntu24-gcc
+ os: ubuntu24.04
cxx: g++
fmt: OFF
qt: ON
@@ -39,8 +38,8 @@ jobs:
fuzzers: OFF
logchar: utf-8
next_abi: OFF
- - name: ubuntu22-clang
- os: ubuntu-22.04
+ - name: ubuntu24-clang
+ os: ubuntu24.04
cxx: clang++
fmt: OFF
qt: OFF
@@ -52,8 +51,8 @@ jobs:
fuzzers: ON
logchar: utf-8
next_abi: OFF
- - name: ubuntu24-gcc
- os: ubuntu-24.04
+ - name: ubuntu26-gcc
+ os: ubuntu-26.04
cxx: g++
fmt: ON
qt: ON
@@ -65,10 +64,10 @@ jobs:
fuzzers: OFF
logchar: utf-8
next_abi: ON
- - name: ubuntu24-clang
- os: ubuntu-24.04
+ - name: ubuntu26-clang
+ os: ubuntu-26.04
cxx: clang++
- fmt: ON
+ fmt: OFF
qt: OFF
qt6: OFF
odbc: OFF
@@ -87,22 +86,12 @@ jobs:
- name: 'Configure Dependencies'
run: |
- sudo apt-get update
sudo apt-get install -y libapr1-dev libaprutil1-dev
if [ ${{ matrix.fmt }} == ON ]; then sudo apt-get install -y
libfmt-dev; fi
if [ ${{ matrix.odbc }} == ON ]; then sudo apt-get install -y
unixodbc-dev; fi
if [ ${{ matrix.qt }} == ON ] && [ ${{ matrix.qt6 }} == OFF ]; then
sudo apt-get install -y qtbase5-dev; fi
if [ ${{ matrix.qt }} == ON ] && [ ${{ matrix.qt6 }} == ON ]; then
sudo apt-get install -y qt6-base-dev; fi
- - name: WORKAROUND FOR https://github.com/actions/runner-images/issues/8659
- if: |
- matrix.name=='ubuntu22-clang'
- run: |
- echo -e "TEMPORARY WORKAROUND FOR GITHUB RUNNER BUG #8659\n\nRemoving
GCC 13 as it breaks Clang14"
- sudo rm -f
/etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list
- sudo apt-get update
- sudo apt-get install -y --allow-downgrades libc6=2.35-*
libc6-dev=2.35-* libstdc++6=12.3.0-* libgcc-s1=12.3.0-*
-
- name: 'run cmake - posix'
run: |
ROOT=`pwd`
@@ -141,6 +130,7 @@ jobs:
fi
- name: run unit tests
+ timeout-minutes: 10
run: |
ROOT=`pwd`
cd $ROOT/build
diff --git a/.github/workflows/package_code.yml
b/.github/workflows/package_code.yml
index f512c29f..7857c74b 100644
--- a/.github/workflows/package_code.yml
+++ b/.github/workflows/package_code.yml
@@ -80,10 +80,10 @@ jobs:
- name: 'Configure Dependencies'
run: |
- sudo apt-get update
sudo apt-get install -y libapr1-dev libaprutil1-dev
- name: 'Test the archive'
+ timeout-minutes: 10
run: |
VERSION=`ls apache-log4cxx-*.tar.gz | sed -Ee
's/.*apache-log4cxx-([0-9]*)\.([0-9]*)\.([0-9]*).*/\1.\2.\3/'`
echo "Extracting files from apache-log4cxx-$VERSION.tar.gz..."
diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml
index 1fbe2aa4..3cd2b8dc 100644
--- a/.github/workflows/sonarcloud.yml
+++ b/.github/workflows/sonarcloud.yml
@@ -51,7 +51,6 @@ jobs:
- name: 'Configure Dependencies - Ubuntu'
run: |
- sudo apt-get update
sudo apt-get install -y libapr1-dev libaprutil1-dev
- name: Install Build Wrapper
diff --git a/src/fuzzers/cpp/TranscoderFuzzer.cpp
b/src/fuzzers/cpp/TranscoderFuzzer.cpp
index 12850a37..6ccf0188 100644
--- a/src/fuzzers/cpp/TranscoderFuzzer.cpp
+++ b/src/fuzzers/cpp/TranscoderFuzzer.cpp
@@ -136,7 +136,7 @@ namespace
if (CharsetDecoder::isError(stat))
{
- out.append(1, (logchar) Transcoder::LOSSCHAR);
+ Transcoder::encode(Transcoder::LOSSCHAR, out);
buf.increment_position(1);
}
else if (buf.position() == before)
diff --git a/src/main/cpp/charsetencoder.cpp b/src/main/cpp/charsetencoder.cpp
index ce93dc9d..eb789a2d 100644
--- a/src/main/cpp/charsetencoder.cpp
+++ b/src/main/cpp/charsetencoder.cpp
@@ -35,6 +35,12 @@
#include <stdlib.h>
#endif
+#if 15 < LOG4CXX_ABI_VERSION
+#define LOG4CXX_16_VIRTUAL_SPECIFIER override
+#else
+#define LOG4CXX_16_VIRTUAL_SPECIFIER
+#endif
+
using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
@@ -82,7 +88,7 @@ class APRCharsetEncoder : public CharsetEncoder
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
apr_status_t stat;
size_t outbytes_left = out.remaining();
@@ -116,6 +122,19 @@ class APRCharsetEncoder : public CharsetEncoder
return stat;
}
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (codePoint <= 0x10FFFF)
+ Transcoder::encodeUTF8(codePoint, out);
+ else
+ result = APR_BADARG;
+ return result;
+ }
+
private:
APRCharsetEncoder(const APRCharsetEncoder&);
APRCharsetEncoder& operator=(const APRCharsetEncoder&);
@@ -141,7 +160,7 @@ class WcstombsCharsetEncoder : public CharsetEncoder
*/
log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
log4cxx_status_t stat = APR_SUCCESS;
@@ -203,7 +222,25 @@ class WcstombsCharsetEncoder : public CharsetEncoder
return stat;
}
-
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (codePoint <= 0x10FFFF && MB_LEN_MAX <=
out.remaining())
+ {
+ auto ch = static_cast<wchar_t>(codePoint);
+ auto converted = wcstombs(out.current(), &ch,
1);
+ if (static_cast<std::size_t>(-1) == converted)
+ result = APR_BADARG;
+ else
+ out.increment_position(converted);
+ }
+ else
+ result = APR_BADARG;
+ return result;
+ }
private:
WcstombsCharsetEncoder(const WcstombsCharsetEncoder&);
@@ -224,7 +261,7 @@ class USASCIICharsetEncoder : public CharsetEncoder
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
log4cxx_status_t stat = APR_SUCCESS;
@@ -251,6 +288,23 @@ class USASCIICharsetEncoder : public CharsetEncoder
return stat;
}
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (out.remaining() < 1)
+ result = APR_BADARG;
+ else if (codePoint <= 0x7F)
+ out.put(static_cast<char>(codePoint));
+ else if (Transcoder::LOSSCHAR == codePoint)
+ out.put('?');
+ else
+ result = APR_BADARG;
+ return result;
+ }
+
private:
USASCIICharsetEncoder(const USASCIICharsetEncoder&);
USASCIICharsetEncoder& operator=(const USASCIICharsetEncoder&);
@@ -268,33 +322,43 @@ class ISOLatinCharsetEncoder : public CharsetEncoder
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
log4cxx_status_t stat = APR_SUCCESS;
- if (iter != in.end())
+ while (out.remaining() > 0 && iter != in.end())
{
- while (out.remaining() > 0 && iter != in.end())
+ LogString::const_iterator prev(iter);
+ unsigned int sv = Transcoder::decode(in, iter);
+ if (sv <= 0xFF)
+ out.put(static_cast<char>(sv));
+ else
{
- LogString::const_iterator prev(iter);
- unsigned int sv =
Transcoder::decode(in, iter);
-
- if (sv <= 0xFF)
- {
- out.put((char) sv);
- }
- else
- {
- iter = prev;
- stat = APR_BADARG;
- break;
- }
+ iter = prev;
+ stat = APR_BADARG;
+ break;
}
}
-
return stat;
}
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (out.remaining() < 1)
+ result = APR_BADARG;
+ else if (codePoint <= 0xFF)
+ out.put(static_cast<char>(codePoint));
+ else if (Transcoder::LOSSCHAR == codePoint)
+ out.put('?');
+ else
+ result = APR_BADARG;
+ return result;
+ }
+
private:
ISOLatinCharsetEncoder(const ISOLatinCharsetEncoder&);
ISOLatinCharsetEncoder& operator=(const
ISOLatinCharsetEncoder&);
@@ -313,7 +377,7 @@ class TrivialCharsetEncoder : public CharsetEncoder
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
if (iter != in.end())
{
@@ -334,14 +398,28 @@ class TrivialCharsetEncoder : public CharsetEncoder
return APR_SUCCESS;
}
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (out.remaining() < 1)
+ result = APR_BADARG;
+ else if (codePoint <= 0xFF)
+ out.put(static_cast<char>(codePoint));
+ else if (Transcoder::LOSSCHAR == codePoint)
+ out.put('?');
+ else
+ result = APR_BADARG;
+ return result;
+ }
+
private:
TrivialCharsetEncoder(const TrivialCharsetEncoder&);
TrivialCharsetEncoder& operator=(const TrivialCharsetEncoder&);
};
-#if LOG4CXX_LOGCHAR_IS_UTF8
-typedef TrivialCharsetEncoder UTF8CharsetEncoder;
-#else
/**
* Converts a LogString to UTF-8.
*/
@@ -354,28 +432,34 @@ class UTF8CharsetEncoder : public CharsetEncoder
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
while (iter != in.end() && out.remaining() >= 8)
{
- unsigned int sv = Transcoder::decode(in, iter);
-
- if (sv == 0xFFFF)
- {
- return APR_BADARG;
- }
-
+ auto sv = Transcoder::getCodePoint(in, iter);
Transcoder::encodeUTF8(sv, out);
}
return APR_SUCCESS;
}
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (codePoint <= 0x10FFFF)
+ Transcoder::encodeUTF8(codePoint, out);
+ else
+ result = APR_BADARG;
+ return result;
+ }
+
private:
UTF8CharsetEncoder(const UTF8CharsetEncoder&);
UTF8CharsetEncoder& operator=(const UTF8CharsetEncoder&);
};
-#endif
/**
* Encodes a LogString to UTF16-BE.
@@ -389,23 +473,30 @@ class UTF16BECharsetEncoder : public CharsetEncoder
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
while (iter != in.end() && out.remaining() >= 4)
{
- unsigned int sv = Transcoder::decode(in, iter);
-
- if (sv == 0xFFFF)
- {
- return APR_BADARG;
- }
-
+ auto sv = Transcoder::getCodePoint(in, iter);
Transcoder::encodeUTF16BE(sv, out);
}
return APR_SUCCESS;
}
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (codePoint <= 0x10FFFF)
+ Transcoder::encodeUTF16BE(codePoint, out);
+ else
+ result = APR_BADARG;
+ return result;
+ }
+
private:
UTF16BECharsetEncoder(const UTF16BECharsetEncoder&);
UTF16BECharsetEncoder& operator=(const UTF16BECharsetEncoder&);
@@ -424,22 +515,29 @@ class UTF16LECharsetEncoder : public CharsetEncoder
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
- ByteBuffer& out)
+ ByteBuffer& out) override
{
while (iter != in.end() && out.remaining() >= 4)
{
- unsigned int sv = Transcoder::decode(in, iter);
-
- if (sv == 0xFFFF)
- {
- return APR_BADARG;
- }
-
+ auto sv = Transcoder::getCodePoint(in, iter);
Transcoder::encodeUTF16LE(sv, out);
}
return APR_SUCCESS;
}
+
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (codePoint <= 0x10FFFF)
+ Transcoder::encodeUTF16LE(codePoint, out);
+ else
+ result = APR_BADARG;
+ return result;
+ }
private:
UTF16LECharsetEncoder(const UTF16LECharsetEncoder&);
UTF16LECharsetEncoder& operator=(const UTF16LECharsetEncoder&);
@@ -479,10 +577,7 @@ class LocaleCharsetEncoder : public CharsetEncoder
// Encode characters that may require multiple bytes
while (nextCodePoint != in.end() && byteCount <
availableByteCount && MB_CUR_MAX <= (availableByteCount - byteCount))
{
- LogString::const_iterator lastCodePoint =
nextCodePoint;
- auto ch = Transcoder::decode(in, nextCodePoint);
- if (nextCodePoint == lastCodePoint) // invalid
input sequence?
- nextCodePoint = in.end();
+ auto ch = Transcoder::getCodePoint(in,
nextCodePoint);
auto n = std::wcrtomb(current, ch,
&this->state);
if (static_cast<std::size_t>(-1) == n) // not a
valid wide character?
{
@@ -496,6 +591,25 @@ class LocaleCharsetEncoder : public CharsetEncoder
return result;
}
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ */
+ log4cxx_status_t encode(unsigned int codePoint, ByteBuffer&
out) LOG4CXX_16_VIRTUAL_SPECIFIER
+ {
+ apr_status_t result = APR_SUCCESS;
+ if (MB_CUR_MAX <= out.remaining())
+ {
+ auto n = std::wcrtomb(out.current(), codePoint,
&this->state);
+ if (static_cast<std::size_t>(-1) == n) // not a
valid wide character?
+ result = APR_BADARG;
+ else
+ out.increment_position(n);
+ }
+ else
+ result = APR_BADARG;
+ return result;
+ }
+
private:
std::mbstate_t state;
};
@@ -535,7 +649,11 @@ CharsetEncoderPtr CharsetEncoder::getDefaultEncoder()
CharsetEncoder* CharsetEncoder::createDefaultEncoder()
{
#if LOG4CXX_CHARSET_UTF8
+#if LOG4CXX_LOGCHAR_IS_UTF8
+ return new TrivialCharsetEncoder();
+#else
return new UTF8CharsetEncoder();
+#endif
#elif LOG4CXX_CHARSET_ISO88591
return new ISOLatinCharsetEncoder();
#elif LOG4CXX_CHARSET_USASCII
@@ -604,10 +722,11 @@ void CharsetEncoder::reset()
{
}
+#if LOG4CXX_ABI_VERSION <= 15
void CharsetEncoder::flush(ByteBuffer& /* out */ )
{
}
-
+#endif
void CharsetEncoder::encode(CharsetEncoderPtr& enc,
const LogString& src,
@@ -633,7 +752,32 @@ void CharsetEncoder::encode(CharsetEncoderPtr& enc,
#else
#error logchar is unrecognized
#endif
- dst.put(Transcoder::LOSSCHAR);
+#if 15 < LOG4CXX_ABI_VERSION
+ enc->encode(Transcoder::LOSSCHAR, dst);
+#else // LOG4CXX_ABI_VERSION <= 15
+ if (auto p = dynamic_cast<TrivialCharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+ else if (auto p = dynamic_cast<UTF8CharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+ else if (auto p =
dynamic_cast<LocaleCharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+ else if (auto p =
dynamic_cast<USASCIICharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+ else if (auto p =
dynamic_cast<ISOLatinCharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+ else if (auto p =
dynamic_cast<UTF16BECharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+ else if (auto p =
dynamic_cast<UTF16LECharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+#if LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_WCSTOMBS
+ else if (auto p =
dynamic_cast<WcstombsCharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+#endif // LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_WCSTOMBS
+#if APR_HAS_XLATE
+ else if (auto p = dynamic_cast<APRCharsetEncoder*>(enc.get()))
+ p->encode(Transcoder::LOSSCHAR, dst);
+#endif // APR_HAS_XLATE
+#endif // LOG4CXX_ABI_VERSION <= 15
}
}
diff --git a/src/main/cpp/jsonlayout.cpp b/src/main/cpp/jsonlayout.cpp
index 6f5e1b86..a66c8a77 100644
--- a/src/main/cpp/jsonlayout.cpp
+++ b/src/main/cpp/jsonlayout.cpp
@@ -223,27 +223,10 @@ void JSONLayout::appendItem(const LogString& input,
LogString& buf)
for (auto nextCodePoint = start; input.end() != nextCodePoint; )
{
auto lastCodePoint = nextCodePoint;
- auto ch = Transcoder::decode(input, nextCodePoint);
- if (nextCodePoint == lastCodePoint) // failed to decode input?
- {
- // Skip the undecodable run and keep escaping the
remaining input
- // instead of discarding it; the run collapses to one
replacement.
- for (++nextCodePoint; nextCodePoint != input.end();
++nextCodePoint)
- {
- auto probe = nextCodePoint;
- Transcoder::decode(input, probe);
- if (probe != nextCodePoint) // next unit starts
a decodable sequence
- break;
- }
- ch = 0xFFFD; // The Unicode replacement character
- }
- else if ((0xD800 <= ch && ch <= 0xDFFF) || 0x10FFFF < ch)
- {
- ch = 0xFFFD; // The Unicode replacement character
- }
- else if (0x22 == ch || 0x5c == ch) // double quote or backslash?
+ auto ch = Transcoder::getCodePoint(input, nextCodePoint);
+ if (0x22 == ch || 0x5c == ch) // double quote or backslash?
;
- else if (0x20 <= ch) // not a control character?
+ else if (0x20 <= ch && 0xFFFD != ch) // not a control character
or the replacement character?
continue;
if (start != lastCodePoint)
diff --git a/src/main/cpp/outputstreamwriter.cpp
b/src/main/cpp/outputstreamwriter.cpp
index fbbbd222..51d0f427 100644
--- a/src/main/cpp/outputstreamwriter.cpp
+++ b/src/main/cpp/outputstreamwriter.cpp
@@ -118,7 +118,6 @@ void OutputStreamWriter::write(
LOG4CXX_WRITE_WRITER_FORMAL_PARAMETERS )
}
CharsetEncoder::encode(m_priv->enc, str, iter, buf);
- m_priv->enc->flush(buf);
buf.flip();
m_priv->out->write(buf);
}
diff --git a/src/main/cpp/telnetappender.cpp b/src/main/cpp/telnetappender.cpp
index 96845a26..9dd071ba 100644
--- a/src/main/cpp/telnetappender.cpp
+++ b/src/main/cpp/telnetappender.cpp
@@ -255,7 +255,7 @@ void TelnetAppender::write(ByteBuffer& buf)
void TelnetAppender::writeStatus(const SocketPtr& socket, const LogString&
msg, Pool& p)
{
- size_t bytesSize = msg.size() * 2;
+ size_t bytesSize = msg.size() * 2 + 10;
char* bytes = p.pstralloc(bytesSize);
LogString::const_iterator msgIter(msg.begin());
@@ -263,7 +263,7 @@ void TelnetAppender::writeStatus(const SocketPtr& socket,
const LogString& msg,
while (msgIter != msg.end())
{
- _priv->encoder->encode(msg, msgIter, buf);
+ CharsetEncoder::encode(_priv->encoder, msg, msgIter, buf);
buf.flip();
socket->write(buf);
buf.clear();
@@ -282,7 +282,7 @@ void TelnetAppender::append(
LOG4CXX_APPEND_FORMAL_PARAMETERS )
else
msg = event->getRenderedMessage();
msg.append(LOG4CXX_STR("\r\n"));
- size_t bytesSize = msg.size() * 2;
+ size_t bytesSize = msg.size() * 2 + 10;
char* bytes = tempPool.pstralloc(bytesSize);
LogString::const_iterator msgIter(msg.begin());
@@ -292,21 +292,10 @@ void TelnetAppender::append(
LOG4CXX_APPEND_FORMAL_PARAMETERS )
while (msgIter != msg.end())
{
- log4cxx_status_t stat = _priv->encoder->encode(msg,
msgIter, buf);
+ CharsetEncoder::encode(_priv->encoder, msg, msgIter,
buf);
buf.flip();
write(buf);
buf.clear();
-
- if (CharsetEncoder::isError(stat))
- {
- LogString unrepresented(1, 0x3F /* '?' */);
- LogString::const_iterator
unrepresentedIter(unrepresented.begin());
- stat = _priv->encoder->encode(unrepresented,
unrepresentedIter, buf);
- buf.flip();
- write(buf);
- buf.clear();
- msgIter++;
- }
}
}
}
diff --git a/src/main/cpp/transcoder.cpp b/src/main/cpp/transcoder.cpp
index 12a0c568..bf71a302 100644
--- a/src/main/cpp/transcoder.cpp
+++ b/src/main/cpp/transcoder.cpp
@@ -39,34 +39,14 @@
using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
-
void Transcoder::decodeUTF8(const std::string& src, LogString& dst)
{
std::string::const_iterator iter = src.begin();
while (iter != src.end())
{
- std::string::const_iterator start = iter;
- unsigned int sv = decode(src, iter);
-
- if (sv != 0xFFFF)
- {
- encode(sv, dst);
- }
- else
- {
- dst.append(1, LOSSCHAR);
-
- // decode() returns 0xFFFF both for a decode error
(iter left at
- // start) and for a successfully decoded U+FFFF (iter
already
- // advanced past EF BF BF). Only advance here in the
former case,
- // otherwise the byte following U+FFFF is skipped and,
at end of
- // input, iter is pushed past src.end().
- if (iter == start)
- {
- iter++;
- }
- }
+ auto sv = getCodePoint(src, iter);
+ encode(sv, dst);
}
}
@@ -79,17 +59,8 @@ void Transcoder::encodeUTF8(const LogString& src,
std::string& dst)
while (iter != src.end())
{
- unsigned int sv = decode(src, iter);
-
- if (sv != 0xFFFF)
- {
- encode(sv, dst);
- }
- else
- {
- dst.append(1, LOSSCHAR);
- iter++;
- }
+ unsigned int sv = getCodePoint(src, iter);
+ encode(sv, dst);
}
#endif
@@ -226,6 +197,36 @@ unsigned int Transcoder::decode(const std::string& src,
return result;
}
+ template <typename T>
+unsigned int decodeCodePoint(const typename std::basic_string<T>& str,
typename std::basic_string<T>::const_iterator& nextCodePoint)
+{
+ auto lastCodePoint = nextCodePoint;
+ auto ch = Transcoder::decode(str, nextCodePoint);
+ if (nextCodePoint == lastCodePoint) // failed to decode input?
+ {
+ // Skip the undecodable run and keep escaping the remaining
input
+ // instead of discarding it; the run collapses to one
replacement.
+ for (++nextCodePoint; nextCodePoint != str.end();
++nextCodePoint)
+ {
+ auto probe = nextCodePoint;
+ Transcoder::decode(str, probe);
+ if (probe != nextCodePoint) // next unit starts a
decodable sequence
+ break;
+ }
+ ch = 0xFFFD; // The Unicode replacement character
+ }
+ else if ( (0xD800 <= ch && ch <= 0xDFFF) // UTF-16 surrogate-range
+ || 0xFFFF == ch || 0x10FFFF < ch)
+ {
+ ch = 0xFFFD; // The Unicode replacement character
+ }
+ return ch;
+}
+
+unsigned int Transcoder::getCodePoint(const std::string& str,
std::string::const_iterator& nextCodePoint)
+{
+ return decodeCodePoint<char>(str, nextCodePoint);
+}
void Transcoder::encode(unsigned int sv, std::string& dst)
{
@@ -234,6 +235,11 @@ void Transcoder::encode(unsigned int sv, std::string& dst)
dst.append(tmp, bytes);
}
+/// Does \c str contain the Unicode replacement character
+bool Transcoder::hasReplacementCharacter(const std::string& str)
+{
+ return str.npos != str.find("\xEF\xBF\xBD");
+}
void Transcoder::decode(const std::string& src, LogString& dst)
{
@@ -265,7 +271,7 @@ void Transcoder::decode(const std::string& src, LogString&
dst)
if (CharsetDecoder::isError(stat))
{
- dst.append(1, LOSSCHAR);
+ encode(LOSSCHAR, dst);
buf.increment_position(1);
}
}
@@ -323,7 +329,7 @@ void Transcoder::encode(const LogString& src, std::string&
dst)
if (CharsetEncoder::isError(stat))
{
- dst.append(1, LOSSCHAR);
+ encode(LOSSCHAR, dst);
iter++;
}
}
@@ -413,21 +419,8 @@ void Transcoder::decode(const std::wstring& src,
LogString& dst)
while (i != src.end())
{
- std::wstring::const_iterator start = i;
- unsigned int cp = decode(src, i);
-
- if (cp != 0xFFFF)
- {
- encode(cp, dst);
- }
- else
- {
- dst.append(1, LOSSCHAR);
- if (i == start)
- {
- i++;
- }
- }
+ auto cp = getCodePoint(src, i);
+ encode(cp, dst);
}
#endif
@@ -441,21 +434,8 @@ void Transcoder::encode(const LogString& src,
std::wstring& dst)
for (LogString::const_iterator i = src.begin(); i != src.end();)
{
- LogString::const_iterator start = i;
- unsigned int cp = Transcoder::decode(src, i);
-
- if (cp != 0xFFFF)
- {
- encode(cp, dst);
- }
- else
- {
- dst.append(1, LOSSCHAR);
- if (i == start)
- {
- i++;
- }
- }
+ unsigned int cp = getCodePoint(src, i);
+ encode(cp, dst);
}
#endif
@@ -487,6 +467,10 @@ unsigned int Transcoder::decode(const std::wstring& in,
#endif
}
+unsigned int Transcoder::getCodePoint(const std::wstring& str,
std::wstring::const_iterator& nextCodePoint)
+{
+ return decodeCodePoint<wchar_t>(str, nextCodePoint);
+}
void Transcoder::encode(unsigned int sv, std::wstring& dst)
{
@@ -506,6 +490,11 @@ void Transcoder::encode(unsigned int sv, std::wstring& dst)
#endif
}
+/// Does \c str contain the Unicode replacement character
+bool Transcoder::hasReplacementCharacter(const std::wstring& str)
+{
+ return str.npos != str.find(LOSSCHAR);
+}
#endif
@@ -529,7 +518,7 @@ void Transcoder::decode(const std::basic_string<UniChar>&
src, LogString& dst)
}
else
{
- dst.append(1, LOSSCHAR);
+ encode(LOSSCHAR, dst);
if (i == start)
{
i++;
@@ -575,11 +564,21 @@ unsigned int Transcoder::decode(const
std::basic_string<UniChar>& in,
return decodeUTF16(in, iter);
}
+unsigned int Transcoder::getCodePoint(const std::basic_string<UniChar>& str,
std::basic_string<UniChar>::const_iterator& nextCodePoint)
+{
+ return decodeCodePoint<UniChar>(str, nextCodePoint);
+}
+
void Transcoder::encode(unsigned int sv, std::basic_string<UniChar>& dst)
{
encodeUTF16(sv, dst);
}
+/// Does \c str contain the Unicode replacement character
+bool Transcoder::hasReplacementCharacter(const std::basic_string<UniChar>& str)
+{
+ return str.npos != str.find(LOSSCHAR);
+}
#endif
#if LOG4CXX_CFSTRING_API
@@ -610,7 +609,7 @@ void Transcoder::decode(const CFStringRef& src, LogString&
dst)
}
else
{
- dst.append(1, LOSSCHAR);
+ encode(LOSSCHAR, dst);
if (i == start)
{
i++;
@@ -672,7 +671,7 @@ std::string Transcoder::encodeCharsetName(const LogString&
val)
}
else
{
- out.append(1, LOSSCHAR);
+ out.append(1, '?');
}
}
diff --git a/src/main/cpp/transform.cpp b/src/main/cpp/transform.cpp
index e5cec926..7ede9a6f 100644
--- a/src/main/cpp/transform.cpp
+++ b/src/main/cpp/transform.cpp
@@ -43,31 +43,14 @@ void appendValidCharacters(LogString& buf, const LogString&
input, CharProcessor
for (auto nextCodePoint = start; input.end() != nextCodePoint; )
{
auto lastCodePoint = nextCodePoint;
- auto ch = Transcoder::decode(input, nextCodePoint);
- if (nextCodePoint == lastCodePoint) // failed to decode input?
- {
- // Skip the undecodable run and keep escaping the
remaining input
- // instead of discarding it; the run collapses to one
replacement.
- for (++nextCodePoint; nextCodePoint != input.end();
++nextCodePoint)
- {
- auto probe = nextCodePoint;
- Transcoder::decode(input, probe);
- if (probe != nextCodePoint) // next unit starts
a decodable sequence
- break;
- }
- }
- else if (0xD800 <= ch && ch <= 0xDFFF)
- {
- // RFC 3629 §3 explicitly forbids surrogate-half values
in UTF-8
- ch = 0xFFFF;
- }
- else if (((0x20 <= ch && ch <= 0xD7FF) &&
+ auto ch = Transcoder::getCodePoint(input, nextCodePoint);
+ if (((0x20 <= ch && ch <= 0xD7FF) &&
specials[0] != ch &&
specials[1] != ch &&
specials[2] != ch &&
specials[3] != ch) ||
(0x9 == ch || 0xA == ch || 0xD == ch) ||
- (0xE000 <= ch && ch <= 0xFFFD) ||
+ (0xE000 <= ch && ch < 0xFFFD) ||
(0x10000 <= ch && ch <= 0x10FFFF))
{
LogString escaped;
@@ -104,8 +87,8 @@ void appendValidCharacters(LogString& buf, const LogString&
input, CharProcessor
buf.append(LOG4CXX_STR(">"));
break;
- case 0xFFFF: // invalid sequence
- Transform::appendCharacterReference(buf,
0xFFFD); // The Unicode replacement character
+ case 0xFFFD: // The Unicode replacement character
+ Transform::appendCharacterReference(buf,
0xFFFD);
break;
default:
@@ -147,21 +130,8 @@ void Transform::appendEscapingCDATA(
{
bool cdataEnd = false;
auto lastCodePoint = nextCodePoint;
- auto ch = Transcoder::decode(input, nextCodePoint);
- if (nextCodePoint == lastCodePoint) // failed to decode input?
- {
- // Skip the undecodable run and keep escaping the
remaining input
- // instead of discarding it; the run collapses to one
replacement.
- for (++nextCodePoint; nextCodePoint != input.end();
++nextCodePoint)
- {
- auto probe = nextCodePoint;
- Transcoder::decode(input, probe);
- if (probe != nextCodePoint) // next unit starts
a decodable sequence
- break;
- }
- ch = 0xFFFD; // The Unicode replacement character
- }
- else if (CDATA_END[0] == ch && input.end() != nextCodePoint)
+ auto ch = Transcoder::getCodePoint(input, nextCodePoint);
+ if (CDATA_END[0] == ch && input.end() != nextCodePoint)
{
lastCodePoint = nextCodePoint;
if (CDATA_END[1] != Transcoder::decode(input,
nextCodePoint) ||
@@ -176,7 +146,7 @@ void Transform::appendEscapingCDATA(
}
else if ((0x20 <= ch && ch <= 0xD7FF) ||
(0x9 == ch || 0xA == ch || 0xD == ch) ||
- (0xE000 <= ch && ch <= 0xFFFD) ||
+ (0xE000 <= ch && ch < 0xFFFD) ||
(0x10000 <= ch && ch <= 0x10FFFF))
{
continue;
diff --git a/src/main/include/log4cxx/helpers/charsetencoder.h
b/src/main/include/log4cxx/helpers/charsetencoder.h
index e4c0b348..44b7b5dd 100644
--- a/src/main/include/log4cxx/helpers/charsetencoder.h
+++ b/src/main/include/log4cxx/helpers/charsetencoder.h
@@ -31,8 +31,8 @@ class CharsetEncoder;
LOG4CXX_PTR_DEF(CharsetEncoder);
/**
-* An engine to transform LogStrings into bytes
-* for the specific character set.
+* An abstract base for classes that transform LogString codepoints
+* to the byte sequence representation of the codepoints.
*/
class LOG4CXX_EXPORT CharsetEncoder : public Object
{
@@ -75,37 +75,46 @@ class LOG4CXX_EXPORT CharsetEncoder : public Object
static CharsetEncoderPtr getUTF8Encoder();
/**
- * Encodes a string replacing unmappable
- * characters with escape sequences.
- *
- */
+ * Use \c enc to encode into \c out codepoints of \c src until
\c out is full or an invalid value is encountered.
+ * If encoding is stopped by an invalid value,
+ * a replacement character is added to \c out
+ * and \c iter is advanced past the invalid value.
+ * @pre \c iter is a valid, dereferenceable iterator.
+ * @pre \c iter and the end of \c src are in the same sequence.
+ */
static void encode(CharsetEncoderPtr& enc,
const LogString& src,
LogString::const_iterator& iter,
ByteBuffer& dst);
/**
- * Encodes as many characters from the input string as possible
- * to the output buffer.
- * @param in input string
- * @param iter position in string to start.
- * @param out output buffer.
- * @return APR_SUCCESS unless a character can not be
represented in
- * the encoding.
+ * Encode into \c out codepoints of \c in until \c out is full
or an invalid value is encountered.
+ * @pre \c iter is a valid, dereferenceable iterator.
+ * @pre \c iter and the end of \c in are in the same sequence.
+ * @return APR_SUCCESS unless an invalid value is encountered
*/
virtual log4cxx_status_t encode(const LogString& in,
LogString::const_iterator& iter,
ByteBuffer& out) = 0;
+#if 15 < LOG4CXX_ABI_VERSION
+ /**
+ * Add onto \c out an encoded equivalent of \c codePoint.
+ * @return APR_SUCCESS unless \c codePoint cannot be
represented by this charset or \c out is full.
+ */
+ virtual log4cxx_status_t encode(unsigned int codePoint,
ByteBuffer& out) = 0;
+#endif
/**
* Resets any internal state.
*/
virtual void reset();
+#if LOG4CXX_ABI_VERSION <= 15
/**
* Flushes the encoder.
*/
virtual void flush(ByteBuffer& out);
+#endif
/**
* Determines if the return value from encode indicates
diff --git a/src/main/include/log4cxx/helpers/transcoder.h
b/src/main/include/log4cxx/helpers/transcoder.h
index 68cf8388..51ed6b96 100644
--- a/src/main/include/log4cxx/helpers/transcoder.h
+++ b/src/main/include/log4cxx/helpers/transcoder.h
@@ -33,8 +33,6 @@ class Pool;
class LOG4CXX_EXPORT Transcoder
{
public:
-
-
/**
* Append the UTF-8 characters in \c src onto \c dst.
*/
@@ -71,16 +69,30 @@ class LOG4CXX_EXPORT Transcoder
* @pre \c iter and the end of \c str are in the same
sequence.
* @param str contains the code point to which \c iter refers.
* @param iter the start of the current code point.
- * @return the code point value or 0xFFFF if not a valid
sequence.
+ * @return if a valid sequence, the decoded value; otherwise,
0xFFFF and leave \c iter unchanged.
*/
static unsigned int decode(const std::string& str,
std::string::const_iterator& iter);
+ /**
+ * Increment \c pCodePoint past one \c str code point.
+ * @pre \c pCodePoint is a valid, dereferenceable iterator.
+ * @pre \c pCodePoint and the end of \c str are in the same
sequence.
+ * @post \c <code>[old_pCodePoint = pCodePoint]
(old_pCodePoint < pCodePoint)</code> // \c pCodePoint is always advanced
+ * @param str contains the code point to which \c pCodePoint
refers.
+ * @param pCodePoint the start of the current code point.
+ * @return the code point value or 0xFFFD if not a valid
sequence; \c pCodePoint is always advanced.
+ */
+ static unsigned int getCodePoint(const std::string& str,
std::string::const_iterator& pCodePoint);
+
/**
* Append the UTF8 equivalent to \c ch onto \c dst.
*/
static void encode(unsigned int ch, std::string& dst);
+ /// Does \c str contain the Unicode replacement character
+ static bool hasReplacementCharacter(const std::string& str);
+
/**
* Append the LogString equivalent of \c src onto \c dst.
*/
@@ -126,16 +138,29 @@ class LOG4CXX_EXPORT Transcoder
* @pre \c iter and the end of \c str are in the same
sequence.
* @param str contains the code point to which \c iter refers.
* @param iter the start of the current code point.
- * @return the code point value or 0xFFFF if not a valid
sequence.
+ * @return if a valid sequence, the decoded value; otherwise,
0xFFFF and leave \c iter unchanged.
*/
static unsigned int decode(const std::wstring& str,
std::wstring::const_iterator& iter);
+ /**
+ * Increment \c pCodePoint past one \c str code point.
+ * @pre \c pCodePoint is a valid, dereferenceable iterator.
+ * @pre \c pCodePoint and the end of \c str are in the same
sequence.
+ * @post \c <code>[old_pCodePoint = pCodePoint]
(old_pCodePoint < pCodePoint)</code> // \c pCodePoint is always advanced
+ * @param str contains the code point to which \c pCodePoint
refers.
+ * @param pCodePoint the start of the current code point.
+ * @return the code point value or 0xFFFD if not a valid
sequence; \c pCodePoint is always advanced.
+ */
+ static unsigned int getCodePoint(const std::wstring& str,
std::wstring::const_iterator& pCodePoint);
+
/**
* Append the wchar_t equivalent to \c ch onto \c dst.
*/
static void encode(unsigned int ch, std::wstring& dst);
+ /// Does \c str contain the Unicode replacement character
+ static bool hasReplacementCharacter(const std::wstring& str);
#endif
@@ -155,16 +180,29 @@ class LOG4CXX_EXPORT Transcoder
* @pre \c iter and the end of \c str are in the same
sequence.
* @param str contains the code point to which \c iter refers.
* @param iter the start of the current code point.
- * @return the code point value or 0xFFFF if not a valid
sequence.
+ * @return if a valid sequence, the decoded value; otherwise,
0xFFFF and leave \c iter unchanged.
*/
static unsigned int decode(const std::basic_string<UniChar>&
str,
std::basic_string<UniChar>::const_iterator& iter);
+ /**
+ * Increment \c pCodePoint past one \c str code point.
+ * @pre \c pCodePoint is a valid, dereferenceable iterator.
+ * @pre \c pCodePoint and the end of \c str are in the same
sequence.
+ * @post \c <code>[old_pCodePoint = pCodePoint]
(old_pCodePoint < pCodePoint)</code> // \c pCodePoint is always advanced
+ * @param str contains the code point to which \c pCodePoint
refers.
+ * @param pCodePoint the start of the current code point.
+ * @return the code point value or 0xFFFD if not a valid
sequence; \c pCodePoint is always advanced.
+ */
+ static unsigned int getCodePoint(const
std::basic_string<UniChar>& str, std::basic_string<UniChar>::const_iterator&
pCodePoint);
+
/**
* Append the UniChar equivalent to \c ch onto \c dst.
*/
static void encode(unsigned int ch, std::basic_string<UniChar>&
dst);
+ /// Does \c str contain the Unicode replacement character
+ static bool hasReplacementCharacter(const
std::basic_string<UniChar>& str);
#endif
#if LOG4CXX_CFSTRING_API
@@ -176,8 +214,7 @@ class LOG4CXX_EXPORT Transcoder
static CFStringRef encode(const LogString& src);
#endif
- enum { LOSSCHAR = 0x3F };
-
+ enum { LOSSCHAR = 0xFFFD }; // Unicode replacement character
/**
* The logchar equivalent to \c ch.
*/
diff --git a/src/test/cpp/helpers/charsetencodertestcase.cpp
b/src/test/cpp/helpers/charsetencodertestcase.cpp
index 8d1430e4..db67fb44 100644
--- a/src/test/cpp/helpers/charsetencodertestcase.cpp
+++ b/src/test/cpp/helpers/charsetencodertestcase.cpp
@@ -243,8 +243,8 @@ public:
/**
* Regression test: write malformed UTF-8 through OutputStreamWriter
- * using a non-trivial encoder and assert the replacement character
- * is emitted (Transcoder::LOSSCHAR). This is deterministic and does
+ * using a non-trivial encoder and assert the encoder specific
+ * replacement character is emitted. This is deterministic and does
* not rely on process crash.
*/
void utf8Recovery()
@@ -276,8 +276,7 @@ public:
LOGUNIT_ASSERT_EQUAL(1, read);
LOGUNIT_ASSERT_EQUAL((size_t)1, buf.position());
- LOGUNIT_ASSERT_EQUAL((unsigned char) Transcoder::LOSSCHAR,
- (unsigned char) raw[0]);
+ LOGUNIT_ASSERT_EQUAL('?', raw[0]);
}
class ThreadPackage
diff --git a/src/test/cpp/helpers/transcodertestcase.cpp
b/src/test/cpp/helpers/transcodertestcase.cpp
index 18e7070c..6b808995 100644
--- a/src/test/cpp/helpers/transcodertestcase.cpp
+++ b/src/test/cpp/helpers/transcodertestcase.cpp
@@ -200,17 +200,23 @@ public:
LOGUNIT_ASSERT_EQUAL(std::wstring(L"Hello"),
encoded.substr(BUFSIZE - 3));
}
+ // Test invalid codepoints
void encode3_1()
{
- // Test invalid multibyte string
- LogString greeting;
- greeting.push_back( logchar(0xff) );
- std::wstring encoded;
- Transcoder::encode(greeting, encoded);
+ unsigned int replacementCharacter = Transcoder::LOSSCHAR;
+ std::wstring encodedD800(1, 0xD800);
+ std::wstring::const_iterator iD800 = encodedD800.begin();
+ LOGUNIT_ASSERT_EQUAL(replacementCharacter,
Transcoder::getCodePoint(encodedD800, iD800));
+
+ std::wstring encodedDFFF(1, 0xDFFF);
+ std::wstring::const_iterator iDFFF = encodedDFFF.begin();
+ LOGUNIT_ASSERT_EQUAL(replacementCharacter,
Transcoder::getCodePoint(encodedDFFF, iDFFF));
- std::wstring expected;
- expected.push_back( log4cxx::helpers::Transcoder::LOSSCHAR );
- LOGUNIT_ASSERT_EQUAL(encoded, expected );
+#if defined(__STDC_ISO_10646__)
+ std::wstring encoded110000(1, 0x110000);
+ std::wstring::const_iterator i110000 = encoded110000.begin();
+ LOGUNIT_ASSERT_EQUAL(replacementCharacter,
Transcoder::getCodePoint(encoded110000, i110000));
+#endif
}
#endif
@@ -306,7 +312,7 @@ public:
std::string src(1, char(0x80));
LogString out;
Transcoder::decodeUTF8(src, out);
- LOGUNIT_ASSERT_EQUAL(LogString(1, Transcoder::LOSSCHAR), out);
+ LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
void testDecodeUTF8_3()
@@ -314,7 +320,7 @@ public:
std::string src("\xC2");
LogString out;
Transcoder::decodeUTF8(src, out);
- LOGUNIT_ASSERT_EQUAL(LogString(1, Transcoder::LOSSCHAR), out);
+ LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
void testDecodeUTF8_4()
@@ -333,8 +339,7 @@ public:
* (U+D800..U+DFFF). The three-byte sequences ED A0 80 .. ED BF BF must
* not decode to the corresponding surrogate code points: doing so lets
* lone surrogates enter LogString and be re-emitted by JSON/XML
layouts,
- * propagating malformed Unicode past the parsing boundary. Each byte of
- * the invalid sequence is replaced with Transcoder::LOSSCHAR.
+ * propagating malformed Unicode past the parsing boundary.
*/
void testDecodeUTF8_RejectSurrogate()
{
@@ -342,12 +347,7 @@ public:
std::string src("\xED\xA0\x80");
LogString out;
Transcoder::decodeUTF8(src, out);
-
- LogString expected;
- expected.append(1, Transcoder::LOSSCHAR);
- expected.append(1, Transcoder::LOSSCHAR);
- expected.append(1, Transcoder::LOSSCHAR);
- LOGUNIT_ASSERT_EQUAL(expected, out);
+ LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
/**
@@ -364,11 +364,7 @@ public:
std::string src("\xE0\xA0\x80");
LogString out;
Transcoder::decodeUTF8(src, out);
-
- LogString expected;
- Transcoder::encode(0x0800, expected);
- LOGUNIT_ASSERT_EQUAL(expected, out);
- LOGUNIT_ASSERT(out.find(Transcoder::LOSSCHAR) ==
LogString::npos);
+ LOGUNIT_ASSERT(!Transcoder::hasReplacementCharacter(out));
}
/**
@@ -392,8 +388,7 @@ public:
std::string src(c.bytes, c.len);
LogString out;
Transcoder::decodeUTF8(src, out);
- bool hasLoss = out.find(Transcoder::LOSSCHAR) !=
LogString::npos;
- LOGUNIT_ASSERT_EQUAL(c.reject, hasLoss);
+ LOGUNIT_ASSERT_EQUAL(c.reject,
Transcoder::hasReplacementCharacter(out));
}
}
@@ -412,10 +407,7 @@ public:
LogString out;
Transcoder::decodeUTF8(src, out);
- LogString expected;
- for (int i = 0; i < 4; ++i)
- expected.append(1, Transcoder::LOSSCHAR);
- LOGUNIT_ASSERT_EQUAL(expected, out);
+ LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
/**
@@ -438,8 +430,7 @@ public:
std::string src(c.bytes, c.len);
LogString out;
Transcoder::decodeUTF8(src, out);
- bool hasLoss = out.find(Transcoder::LOSSCHAR) !=
LogString::npos;
- LOGUNIT_ASSERT_EQUAL(c.reject, hasLoss);
+ LOGUNIT_ASSERT_EQUAL(c.reject,
Transcoder::hasReplacementCharacter(out));
}
}
@@ -448,8 +439,7 @@ public:
* branch masks the lead byte with 0x07, discarding those high bits, so
* F8 BF BF BF used to slip past the U+10FFFF bound and decode to
U+3FFFF
* (and FB/FC likewise to other in-range planes) — the same aliasing
- * filter-bypass that the F5..F7 rejection guards against. Each byte of
an
- * invalid lead sequence must be replaced with Transcoder::LOSSCHAR.
+ * filter-bypass that the F5..F7 rejection guards against.
*/
void testDecodeUTF8_RejectInvalidLeadByte()
{
@@ -466,8 +456,7 @@ public:
std::string src(c.bytes, c.len);
LogString out;
Transcoder::decodeUTF8(src, out);
- bool hasLoss = out.find(Transcoder::LOSSCHAR) !=
LogString::npos;
- LOGUNIT_ASSERT_EQUAL(c.reject, hasLoss);
+ LOGUNIT_ASSERT_EQUAL(c.reject,
Transcoder::hasReplacementCharacter(out));
}
}