blob: 853b01c96c93b8e385da446a21b6b99e252e32f8 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <log4cxx/helpers/transcoder.h>
#include <log4cxx/helpers/bytebuffer.h>
#include "../insertwide.h"
#include "../logunit.h"
#if LOG4CXX_CFSTRING_API
#include <CoreFoundation/CFString.h>
#endif
using namespace log4cxx;
using namespace log4cxx::helpers;
LOGUNIT_CLASS(TranscoderTestCase)
{
LOGUNIT_TEST_SUITE(TranscoderTestCase);
LOGUNIT_TEST(decode1);
#if LOG4CXX_WCHAR_T_API
LOGUNIT_TEST(decode2);
#endif
LOGUNIT_TEST(decode3);
#if LOG4CXX_WCHAR_T_API
LOGUNIT_TEST(decode4);
#endif
#if LOG4CXX_CFSTRING_API
LOGUNIT_TEST(decode5);
#endif
LOGUNIT_TEST(decode7);
LOGUNIT_TEST(decode8);
#if LOG4CXX_WCHAR_T_API
LOGUNIT_TEST(encode1);
#endif
LOGUNIT_TEST(encode2);
#if LOG4CXX_WCHAR_T_API
LOGUNIT_TEST(encode3);
LOGUNIT_TEST(encode3_1);
#endif
LOGUNIT_TEST(encode4);
#if LOG4CXX_WCHAR_T_API
LOGUNIT_TEST(encode5);
#endif
LOGUNIT_TEST(encode6);
#if LOG4CXX_CFSTRING_API
LOGUNIT_TEST(encode7);
#endif
LOGUNIT_TEST(testDecodeUTF8_1);
LOGUNIT_TEST(testDecodeUTF8_2);
LOGUNIT_TEST(testDecodeUTF8_3);
LOGUNIT_TEST(testDecodeUTF8_4);
LOGUNIT_TEST(testDecodeUTF8_RejectSurrogate);
LOGUNIT_TEST(testDecodeUTF8_SurrogateBoundaries);
LOGUNIT_TEST(testDecodeUTF8_U0800);
LOGUNIT_TEST(testDecodeUTF8_RejectAboveMax);
LOGUNIT_TEST(testDecodeUTF8_MaxBoundary);
LOGUNIT_TEST(testDecodeUTF8_RejectInvalidLeadByte);
LOGUNIT_TEST(testDecodeUTF8_FFFF_KeepsFollowingByte);
LOGUNIT_TEST(testEncodeUTF16BE_BMP);
LOGUNIT_TEST(testEncodeUTF16BE_Supplementary);
LOGUNIT_TEST(testEncodeUTF16LE_Supplementary);
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR || defined(WIN32) || defined(_WIN32)
LOGUNIT_TEST(testEncodeWString_FFFF_KeepsFollowingByte);
#endif
#if LOG4CXX_LOGCHAR_IS_UTF8
LOGUNIT_TEST(encodeCharsetName1);
LOGUNIT_TEST(encodeCharsetName2);
LOGUNIT_TEST(encodeCharsetName3);
#endif
LOGUNIT_TEST_SUITE_END();
public:
void decode1()
{
const char* greeting = "Hello, World";
LogString decoded(LOG4CXX_STR("foo\n"));
Transcoder::decode(greeting, decoded);
LOGUNIT_ASSERT_EQUAL((LogString) LOG4CXX_STR("foo\nHello, World"), decoded);
}
#if LOG4CXX_WCHAR_T_API
void decode2()
{
const wchar_t* greeting = L"Hello, World";
LogString decoded(LOG4CXX_STR("foo\n"));
Transcoder::decode(greeting, decoded);
LOGUNIT_ASSERT_EQUAL((LogString) LOG4CXX_STR("foo\nHello, World"), decoded);
}
#endif
void decode3()
{
const char* nothing = "";
LogString decoded(LOG4CXX_STR("foo\n"));
Transcoder::decode(nothing, decoded);
LOGUNIT_ASSERT_EQUAL((LogString) LOG4CXX_STR("foo\n"), decoded);
}
#if LOG4CXX_WCHAR_T_API
void decode4()
{
const wchar_t* nothing = L"";
LogString decoded(LOG4CXX_STR("foo\n"));
Transcoder::decode(nothing, decoded);
LOGUNIT_ASSERT_EQUAL((LogString) LOG4CXX_STR("foo\n"), decoded);
}
#endif
#if LOG4CXX_CFSTRING_API
void decode5()
{
LogString nothing;
LogString decoded(LOG4CXX_STR("foo\n"));
Transcoder::decode(nothing, decoded);
LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("foo\n"), decoded);
}
#endif
enum { BUFSIZE = 255 };
void decode7()
{
//
// normal characters striding over a buffer boundary
//
std::string longMsg(BUFSIZE - 2, 'A');
longMsg.append("Hello");
LogString decoded;
Transcoder::decode(longMsg, decoded);
LOGUNIT_ASSERT_EQUAL((size_t) BUFSIZE + 3, decoded.length());
LOGUNIT_ASSERT_EQUAL(LogString(BUFSIZE - 2, LOG4CXX_STR('A')),
decoded.substr(0, BUFSIZE - 2));
LOGUNIT_ASSERT_EQUAL((LogString) LOG4CXX_STR("Hello"),
decoded.substr(BUFSIZE - 2 ));
}
void decode8()
{
std::string msg("Hello, World.");
LogString actual;
Transcoder::decode(msg, actual);
LogString expected(LOG4CXX_STR("Hello, World."));
LOGUNIT_ASSERT_EQUAL(expected, actual);
}
#if LOG4CXX_WCHAR_T_API
void encode1()
{
const LogString greeting(LOG4CXX_STR("Hello, World"));
std::wstring encoded;
Transcoder::encode(greeting, encoded);
LOGUNIT_ASSERT_EQUAL((std::wstring) L"Hello, World", encoded);
}
#endif
void encode2()
{
const LogString greeting(LOG4CXX_STR("Hello, World"));
std::string encoded;
Transcoder::encode(greeting, encoded);
LOGUNIT_ASSERT_EQUAL((std::string) "Hello, World", encoded);
}
#if LOG4CXX_WCHAR_T_API
void encode3()
{
LogString greeting(BUFSIZE - 3, LOG4CXX_STR('A'));
greeting.append(LOG4CXX_STR("Hello"));
std::wstring encoded;
Transcoder::encode(greeting, encoded);
std::wstring manyAs(BUFSIZE - 3, L'A');
LOGUNIT_ASSERT_EQUAL(manyAs, encoded.substr(0, BUFSIZE - 3));
LOGUNIT_ASSERT_EQUAL(std::wstring(L"Hello"), encoded.substr(BUFSIZE - 3));
}
// Test invalid codepoints
void encode3_1()
{
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));
#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
void encode4()
{
LogString greeting(BUFSIZE - 3, LOG4CXX_STR('A'));
greeting.append(LOG4CXX_STR("Hello"));
std::string encoded;
Transcoder::encode(greeting, encoded);
std::string manyAs(BUFSIZE - 3, 'A');
LOGUNIT_ASSERT_EQUAL(manyAs, encoded.substr(0, BUFSIZE - 3));
LOGUNIT_ASSERT_EQUAL(std::string("Hello"), encoded.substr(BUFSIZE - 3));
}
#if LOG4CXX_WCHAR_T_API
void encode5()
{
// arbitrary, hopefully meaningless, characters from
// Latin, Arabic, Armenian, Bengali, CJK and Cyrillic
const wchar_t greeting[] = { L'A', 0x0605, 0x0530, 0x984, 0x40E3, 0x400, 0 };
//
// decode to LogString (UTF-16 or UTF-8)
//
LogString decoded;
Transcoder::decode(greeting, decoded);
//
// decode to wstring
//
std::wstring encoded;
Transcoder::encode(decoded, encoded);
//
// should be lossless
//
LOGUNIT_ASSERT_EQUAL((std::wstring) greeting, encoded);
}
#endif
void encode6()
{
#if LOG4CXX_LOGCHAR_IS_WCHAR
// arbitrary, hopefully meaningless, characters from
// Latin, Arabic, Armenian, Bengali, CJK and Cyrillic
const logchar greeting[] = { L'A', 0x0605, 0x0530, 0x984, 0x40E3, 0x400, 0 };
#endif
#if LOG4CXX_LOGCHAR_IS_UTF8
const char greeting[] = { 'A',
(char) 0xD8, (char) 0x85,
(char) 0xD4, (char) 0xB0,
(char) 0xE0, (char) 0xCC, (char) 0x84,
(char) 0xE8, (char) 0x87, (char) 0x83,
(char) 0xD0, (char) 0x80,
0
};
#endif
//
// decode to LogString (UTF-16 or UTF-8)
//
LogString decoded;
Transcoder::decode(greeting, decoded);
//
// decode to wstring
//
std::string encoded;
//
// likely 'A\u0605\u0530\u0984\u40E3\u0400'
//
Transcoder::encode(decoded, encoded);
}
#if LOG4CXX_CFSTRING_API
void encode7()
{
const LogString greeting(LOG4CXX_STR("Hello, World"));
CFStringRef encoded = Transcoder::encode(greeting);
LogString decoded;
Transcoder::decode(encoded, decoded);
LOGUNIT_ASSERT_EQUAL(LOG4CXX_STR("Hello, World"), decoded);
}
#endif
void testDecodeUTF8_1()
{
std::string src("a");
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT_EQUAL(LogString(LOG4CXX_STR("a")), out);
}
void testDecodeUTF8_2()
{
std::string src(1, char(0x80));
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
void testDecodeUTF8_3()
{
std::string src("\xC2");
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
void testDecodeUTF8_4()
{
std::string src("\xC2\xA9");
LogString out;
Transcoder::decodeUTF8(src, out);
LogString::const_iterator iter = out.begin();
unsigned int sv = Transcoder::decode(out, iter);
LOGUNIT_ASSERT_EQUAL((unsigned int) 0xA9, sv);
LOGUNIT_ASSERT_EQUAL(true, iter == out.end());
}
/**
* RFC 3629 Β§3 prohibits UTF-8 encodings of the UTF-16 surrogate halves
* (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.
*/
void testDecodeUTF8_RejectSurrogate()
{
// ED A0 80 would encode U+D800 (the smallest high-surrogate).
std::string src("\xED\xA0\x80");
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
/**
* U+0800 (SAMARITAN LETTER ALAF) is the smallest code point that
* legitimately requires a three-byte UTF-8 sequence (E0 A0 80).
* The overlong check in the three-byte branch of Transcoder::decode
* previously used `rv <= 0x800` instead of `rv < 0x800`, so this exact
* code point was rejected as if it were an overlong encoding and the
* caller substituted Transcoder::LOSSCHAR. Any UTF-8 input containing
* the bytes E0 A0 80 was therefore silently corrupted on decode.
*/
void testDecodeUTF8_U0800()
{
std::string src("\xE0\xA0\x80");
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT(!Transcoder::hasReplacementCharacter(out));
}
/**
* Confirm the surrogate-rejection range is exactly U+D800..U+DFFF:
* U+D7FF (ED 9F BF) and U+E000 (EE 80 80) bracket the range and must
* still decode cleanly. The four interior values are each rejected.
*/
void testDecodeUTF8_SurrogateBoundaries()
{
struct { const char* bytes; size_t len; bool reject; } cases[] =
{
{ "\xED\x9F\xBF", 3, false }, // U+D7FF β€” last valid before surrogates
{ "\xED\xA0\x80", 3, true }, // U+D800 β€” high-surrogate min (reject)
{ "\xED\xAF\xBF", 3, true }, // U+DBFF β€” high-surrogate max (reject)
{ "\xED\xB0\x80", 3, true }, // U+DC00 β€” low-surrogate min (reject)
{ "\xED\xBF\xBF", 3, true }, // U+DFFF β€” low-surrogate max (reject)
{ "\xEE\x80\x80", 3, false }, // U+E000 β€” first valid after surrogates
};
for (auto& c : cases)
{
std::string src(c.bytes, c.len);
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT_EQUAL(c.reject, Transcoder::hasReplacementCharacter(out));
}
}
/**
* RFC 3629 Β§3 caps UTF-8 at U+10FFFF. Four-byte sequences with lead F5,
* F6, F7 (and F4 with an over-high trailer) decode to values above the
* Unicode maximum. Without bounds-rejection here, Transcoder::encodeUTF16
* later silently aliases the bogus value to a valid in-range code point
* (e.g. U+110000 collides with U+10000) β€” a substitution-collision
* filter-bypass primitive in wchar builds.
*/
void testDecodeUTF8_RejectAboveMax()
{
// F4 90 80 80 would encode U+110000 (one past the maximum).
std::string src("\xF4\x90\x80\x80");
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT(Transcoder::hasReplacementCharacter(out));
}
/**
* Boundary check around U+10FFFF: the canonical encoding of the
* maximum legal code point (F4 8F BF BF) must decode cleanly; one past
* (F4 90 80 80) and the F5/F6/F7 lead bytes must all be rejected.
*/
void testDecodeUTF8_MaxBoundary()
{
struct { const char* bytes; size_t len; bool reject; } cases[] =
{
{ "\xF4\x8F\xBF\xBF", 4, false }, // U+10FFFF β€” maximum legal code point
{ "\xF4\x90\x80\x80", 4, true }, // U+110000 β€” one past max (reject)
{ "\xF5\x80\x80\x80", 4, true }, // F5 lead: rv = 0x140000 (reject)
{ "\xF6\x80\x80\x80", 4, true }, // F6 lead: rv = 0x180000 (reject)
{ "\xF7\xBF\xBF\xBF", 4, true }, // F7 lead: rv = 0x1FFFFF (reject)
};
for (auto& c : cases)
{
std::string src(c.bytes, c.len);
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT_EQUAL(c.reject, Transcoder::hasReplacementCharacter(out));
}
}
/**
* Lead bytes F8..FF never start a valid UTF-8 sequence. The four-byte
* 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.
*/
void testDecodeUTF8_RejectInvalidLeadByte()
{
struct { const char* bytes; size_t len; bool reject; } cases[] =
{
{ "\xF0\x9F\x98\x80", 4, false }, // U+1F600 β€” valid four-byte
{ "\xF8\xBF\xBF\xBF", 4, true }, // F8 lead: masked to U+3FFFF (reject)
{ "\xFB\xBF\xBF\xBF", 4, true }, // FB lead: masked to U+FFFFF (reject)
{ "\xFC\x8F\xBF\xBF", 4, true }, // FC lead: masked to U+10FFFF (reject)
{ "\xFF\xBF\xBF\xBF", 4, true }, // FF lead (reject)
};
for (auto& c : cases)
{
std::string src(c.bytes, c.len);
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT_EQUAL(c.reject, Transcoder::hasReplacementCharacter(out));
}
}
/**
* U+FFFF (EF BF BF) is a legal three-byte sequence whose scalar value
* collides with the 0xFFFF error sentinel returned by Transcoder::decode.
* decode() consumes all three bytes and returns 0xFFFF, but decodeUTF8
* mistakes that for a decode failure (which leaves the iterator parked)
* and advances the iterator a second time. The byte following U+FFFF is
* therefore silently dropped. Here the trailing 'A' must survive.
*/
void testDecodeUTF8_FFFF_KeepsFollowingByte()
{
std::string src("\xEF\xBF\xBF\x41"); // U+FFFF then 'A'
LogString out;
Transcoder::decodeUTF8(src, out);
LOGUNIT_ASSERT(out.find(LOG4CXX_STR("A")) != LogString::npos);
}
void testEncodeUTF16BE_BMP()
{
char raw[4] = { 0, 0, 0, 0 };
ByteBuffer buf(raw, sizeof(raw));
Transcoder::encodeUTF16BE(0x4E03, buf); // CJK δΈƒ
LOGUNIT_ASSERT_EQUAL((size_t) 2, buf.position());
LOGUNIT_ASSERT_EQUAL((unsigned char) 0x4E, (unsigned char) raw[0]);
LOGUNIT_ASSERT_EQUAL((unsigned char) 0x03, (unsigned char) raw[1]);
}
// U+1F600 (GRINNING FACE) encodes to UTF-16BE as D8 3D DE 00.
// Before the fix the low surrogate's high byte was derived from bits 4-5
// of the code point, yielding 0xDC here instead of 0xDE β€” corrupting the
// pair into two unpaired surrogates.
void testEncodeUTF16BE_Supplementary()
{
char raw[4] = { 0, 0, 0, 0 };
ByteBuffer buf(raw, sizeof(raw));
Transcoder::encodeUTF16BE(0x1F600, buf);
LOGUNIT_ASSERT_EQUAL((size_t) 4, buf.position());
LOGUNIT_ASSERT_EQUAL((unsigned char) 0xD8, (unsigned char) raw[0]);
LOGUNIT_ASSERT_EQUAL((unsigned char) 0x3D, (unsigned char) raw[1]);
LOGUNIT_ASSERT_EQUAL((unsigned char) 0xDE, (unsigned char) raw[2]);
LOGUNIT_ASSERT_EQUAL((unsigned char) 0x00, (unsigned char) raw[3]);
}
void testEncodeUTF16LE_Supplementary()
{
char raw[4] = { 0, 0, 0, 0 };
ByteBuffer buf(raw, sizeof(raw));
Transcoder::encodeUTF16LE(0x1F600, buf);
LOGUNIT_ASSERT_EQUAL((size_t) 4, buf.position());
LOGUNIT_ASSERT_EQUAL((unsigned char) 0x3D, (unsigned char) raw[0]);
LOGUNIT_ASSERT_EQUAL((unsigned char) 0xD8, (unsigned char) raw[1]);
LOGUNIT_ASSERT_EQUAL((unsigned char) 0x00, (unsigned char) raw[2]);
LOGUNIT_ASSERT_EQUAL((unsigned char) 0xDE, (unsigned char) raw[3]);
}
#if LOG4CXX_WCHAR_T_API || LOG4CXX_LOGCHAR_IS_WCHAR || defined(WIN32) || defined(_WIN32)
void testEncodeWString_FFFF_KeepsFollowingByte()
{
LogString src;
#if LOG4CXX_LOGCHAR_IS_UTF8
src = "\xEF\xBF\xBF\x41"; // U+FFFF then 'A'
#else
src.append(1, 0xFFFF);
src.append(1, 0x41);
#endif
std::wstring out;
Transcoder::encode(src, out);
LOGUNIT_ASSERT(out.find(L"A") != std::wstring::npos);
}
#endif
#if LOG4CXX_LOGCHAR_IS_UTF8
void encodeCharsetName1()
{
const logchar utf8[] = { 0x75, 0x74, 0x66, 0x2D, 0x38, 0x00 };
std::string encoded(Transcoder::encodeCharsetName(LogString(utf8)));
LOGUNIT_ASSERT_EQUAL(std::string("utf-8"), encoded);
}
void encodeCharsetName2()
{
logchar lascii[0x60];
char ascii[0x60];
for (int i = 0; i < 0x5F; i++)
{
lascii[i] = i + 0x20;
ascii[i] = i + 0x20;
}
lascii[0x5F] = 0;
ascii[0x5F] = 0;
std::string encoded(Transcoder::encodeCharsetName(LogString(ascii)));
LOGUNIT_ASSERT_EQUAL(std::string(" !\"#$%&'()*+,-./"), encoded.substr(0, 0x10));
if (0x40 == 'A')
{
LOGUNIT_ASSERT_EQUAL(std::string(ascii), encoded);
}
}
void encodeCharsetName3()
{
logchar unsupported[] = { 0x1F, 0x7F, static_cast<logchar>(0x80), static_cast<logchar>(0x81), 0x00 };
std::string encoded(Transcoder::encodeCharsetName(LogString(unsupported)));
LOGUNIT_ASSERT_EQUAL(std::string("????"), encoded);
}
#endif
};
LOGUNIT_TEST_SUITE_REGISTRATION(TranscoderTestCase);