blob: a00d6b2be54738b2766e34ef22145dd29a3bc93a [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.
*/
#define NOMINMAX /* tell windows not to define min/max macros */
#include <log4cxx/private/string_c11.h>
#include <log4cxx/logstring.h>
#include <log4cxx/helpers/charsetdecoder.h>
#include <log4cxx/private/bytebuffer_priv.h>
#include <log4cxx/helpers/exception.h>
#include <log4cxx/helpers/pool.h>
#include <log4cxx/helpers/loglog.h>
#include <apr_xlate.h>
#if !defined(LOG4CXX)
#define LOG4CXX 1
#endif
#include <log4cxx/private/log4cxx_private.h>
#include <locale.h>
#include <apr_portable.h>
#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/helpers/transcoder.h>
#include <mutex>
using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
IMPLEMENT_LOG4CXX_OBJECT(CharsetDecoder)
namespace LOG4CXX_NS
{
namespace helpers
{
#if APR_HAS_XLATE
/**
* Converts from an arbitrary encoding to LogString
* using apr_xlate. Requires real iconv implementation,
* apr-iconv will crash in use.
*/
class APRCharsetDecoder : public CharsetDecoder
{
public:
/**
* Creates a new instance.
* @param frompage name of source encoding.
*/
APRCharsetDecoder(const LogString& frompage) : pool()
{
#if LOG4CXX_LOGCHAR_IS_WCHAR
const char* topage = "WCHAR_T";
#endif
#if LOG4CXX_LOGCHAR_IS_UTF8
const char* topage = "UTF-8";
#endif
std::string fpage(Transcoder::encodeCharsetName(frompage));
apr_status_t stat = apr_xlate_open(&convset,
topage,
fpage.c_str(),
pool.getAPRPool());
if (stat != APR_SUCCESS)
{
throw IllegalArgumentException(frompage);
}
}
/**
* Destructor.
*/
virtual ~APRCharsetDecoder()
{
}
virtual log4cxx_status_t decode(ByteBuffer& in_param,
LogString& out)
{
auto& in = in_param.impl();
enum { BUFSIZE = 256 };
logchar buf[BUFSIZE];
const apr_size_t initial_outbytes_left = BUFSIZE * sizeof(logchar);
apr_status_t stat = APR_SUCCESS;
if (in.remaining() == 0)
{
size_t outbytes_left = initial_outbytes_left;
{
std::lock_guard<std::mutex> lock(mutex);
stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
NULL, NULL, (char*) buf, &outbytes_left);
}
out.append(buf, (initial_outbytes_left - outbytes_left) / sizeof(logchar));
}
else
{
while (in.remaining() > 0 && stat == APR_SUCCESS)
{
size_t inbytes_left = in.remaining();
size_t initial_inbytes_left = inbytes_left;
apr_size_t outbytes_left = initial_outbytes_left;
{
std::lock_guard<std::mutex> lock(mutex);
stat = apr_xlate_conv_buffer((apr_xlate_t*) convset,
in.current(),
&inbytes_left,
(char*) buf,
&outbytes_left);
}
out.append(buf, (initial_outbytes_left - outbytes_left) / sizeof(logchar));
if (inbytes_left == initial_inbytes_left && stat == APR_SUCCESS)
{
stat = APR_BADCH;
break;
}
in.increment_position(initial_inbytes_left - inbytes_left);
}
}
return stat;
}
private:
APRCharsetDecoder(const APRCharsetDecoder&);
APRCharsetDecoder& operator=(const APRCharsetDecoder&);
LOG4CXX_NS::helpers::Pool pool;
std::mutex mutex;
apr_xlate_t* convset;
};
#endif
#if LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_MBSRTOWCS
/**
* Converts from the default multi-byte string to
* LogString using mbstowcs.
*
*/
class MbstowcsCharsetDecoder : public CharsetDecoder
{
public:
MbstowcsCharsetDecoder()
{
}
virtual ~MbstowcsCharsetDecoder()
{
}
private:
inline log4cxx_status_t append(LogString& out, const wchar_t* buf)
{
out.append(buf);
return APR_SUCCESS;
}
virtual log4cxx_status_t decode(ByteBuffer& in_param,
LogString& out)
{
auto& in = in_param.impl();
log4cxx_status_t stat = APR_SUCCESS;
enum { BUFSIZE = 256 };
wchar_t wbuf[BUFSIZE];
char cbuf[BUFSIZE*4];
mbstate_t mbstate;
memset(&mbstate, 0, sizeof(mbstate));
while (in.remaining() > 0)
{
const char* src = in.current();
if (*src == 0)
{
out.append(1, (logchar) 0);
in.increment_position(1);
}
else
{
auto available = std::min(sizeof (cbuf) - 1, in.remaining());
strncpy(cbuf, src, available);
cbuf[available] = 0;
src = cbuf;
size_t wCharCount = mbsrtowcs(wbuf,
&src,
BUFSIZE - 1,
&mbstate);
// mbsrtowcs sets *src to nullptr when it consumes a null wide character.
// Performing pointer arithmetic on that nullptr (src - cbuf) is undefined
// behaviour, so recover the consumed byte count from the position of the
// null that stopped the conversion instead.
size_t converted;
if (src == nullptr)
{
size_t nullPos = 0;
while (nullPos < available && cbuf[nullPos] != 0)
{
++nullPos;
}
// If the null came from the input bytes, it was consumed too;
// if it is the sentinel we wrote at cbuf[available], stop at available.
converted = (nullPos < available) ? nullPos + 1 : available;
}
else
{
converted = static_cast<size_t>(src - cbuf);
}
in.increment_position(converted);
if (wCharCount == (size_t) -1) // Illegal byte sequence?
{
LogString msg(LOG4CXX_STR("Illegal byte sequence at "));
msg.append(std::to_wstring(in.position()));
msg.append(LOG4CXX_STR(" of "));
msg.append(std::to_wstring(in.limit()));
LogLog::warn(msg);
stat = APR_BADCH;
break;
}
else
{
// FIX: Check for incomplete sequence infinite loop.
// If mbsrtowcs returns success (>=0) but converted 0 bytes while data remains,
// we are stuck (e.g. incomplete multibyte char at EOF).
if (converted == 0 && in.remaining() > 0)
{
LogString msg(LOG4CXX_STR("Incomplete multibyte sequence at end of buffer"));
LogLog::warn(msg);
stat = APR_BADCH;
break; // Break the infinite loop
}
wbuf[wCharCount] = 0;
stat = append(out, wbuf);
}
}
}
return stat;
}
private:
MbstowcsCharsetDecoder(const MbstowcsCharsetDecoder&);
MbstowcsCharsetDecoder& operator=(const MbstowcsCharsetDecoder&);
};
#endif
/**
* Decoder used when the external and internal charsets
* are the same.
*
*/
class TrivialCharsetDecoder : public CharsetDecoder
{
public:
TrivialCharsetDecoder()
{
}
virtual ~TrivialCharsetDecoder()
{
}
virtual log4cxx_status_t decode(ByteBuffer& in_param,
LogString& out)
{
auto& in = in_param.impl();
size_t remaining = in.remaining();
if ( remaining > 0)
{
auto src = in.current();
auto count = remaining / sizeof(logchar);
out.append(reinterpret_cast<const logchar*>(src), count);
in.increment_position(remaining);
}
return APR_SUCCESS;
}
private:
TrivialCharsetDecoder(const TrivialCharsetDecoder&);
TrivialCharsetDecoder& operator=(const TrivialCharsetDecoder&);
};
/**
* Converts from UTF-8 to LogString
*
*/
class UTF8CharsetDecoder : public CharsetDecoder
{
public:
UTF8CharsetDecoder()
{
}
virtual ~UTF8CharsetDecoder()
{
}
private:
virtual log4cxx_status_t decode(ByteBuffer& in_param,
LogString& out)
{
auto& in = in_param.impl();
auto availableByteCount = in.remaining();
while (0 < availableByteCount)
{
auto sv = getUTF8CodePoint(in);
auto nextAvailableByteCount = in.remaining();
if (sv == 0xFFFF || nextAvailableByteCount == availableByteCount)
return APR_BADCH;
Transcoder::encode(sv, out);
availableByteCount = nextAvailableByteCount;
}
return APR_SUCCESS;
}
private:
UTF8CharsetDecoder(const UTF8CharsetDecoder&);
UTF8CharsetDecoder& operator=(const UTF8CharsetDecoder&);
};
/**
* Converts from ISO-8859-1 to LogString.
*
*/
class ISOLatinCharsetDecoder : public CharsetDecoder
{
public:
ISOLatinCharsetDecoder()
{
}
virtual ~ISOLatinCharsetDecoder()
{
}
private:
virtual log4cxx_status_t decode(ByteBuffer& in_param,
LogString& out)
{
auto& in = in_param.impl();
auto availableByteCount = in.remaining();
auto src = in.current();
auto srcEnd = src + availableByteCount;
while (src < srcEnd)
{
auto sv = static_cast<unsigned int>(static_cast<unsigned char>(*src++));
Transcoder::encode(sv, out);
}
in.increment_position(availableByteCount);
return APR_SUCCESS;
}
private:
ISOLatinCharsetDecoder(const ISOLatinCharsetDecoder&);
ISOLatinCharsetDecoder& operator=(const ISOLatinCharsetDecoder&);
};
/**
* Converts from US-ASCII to LogString.
*
*/
class USASCIICharsetDecoder : public CharsetDecoder
{
public:
USASCIICharsetDecoder()
{
}
virtual ~USASCIICharsetDecoder()
{
}
private:
virtual log4cxx_status_t decode(ByteBuffer& in_param,
LogString& out)
{
auto& in = in_param.impl();
log4cxx_status_t stat = APR_SUCCESS;
auto availableByteCount = in.remaining();
auto src = in.current();
auto srcEnd = src + availableByteCount;
size_t byteCount = 0;
while (src < srcEnd)
{
auto sv = static_cast<unsigned int>(*src++);
if (sv < 0x80)
{
++byteCount;
Transcoder::encode(sv, out);
}
else
{
stat = APR_BADCH;
break;
}
}
in.increment_position(byteCount);
return stat;
}
private:
USASCIICharsetDecoder(const USASCIICharsetDecoder&);
USASCIICharsetDecoder& operator=(const USASCIICharsetDecoder&);
};
/**
* Charset decoder that uses current locale settings.
*/
class LocaleCharsetDecoder : public CharsetDecoder
{
public:
LocaleCharsetDecoder() : state()
{
}
log4cxx_status_t decode(ByteBuffer& in_param, LogString& out) override
{
auto& in = in_param.impl();
log4cxx_status_t result = APR_SUCCESS;
auto p = in.current();
auto availableByteCount = in.remaining();
size_t byteCount = 0;
#if !LOG4CXX_CHARSET_EBCDIC
if (std::mbsinit(&this->state)) // ByteBuffer not partially decoded?
{
// Copy single byte characters
for (; byteCount < availableByteCount && static_cast<unsigned int>(*p) < 0x80; ++byteCount, ++p)
{
out.append(1, *p);
}
}
#endif
// Decode characters that may be represented by multiple bytes
while (byteCount < availableByteCount)
{
wchar_t ch = 0;
size_t n = std::mbrtowc(&ch, p, availableByteCount - byteCount, &this->state);
if (0 == n) // NULL encountered?
{
++byteCount;
break;
}
if (static_cast<std::size_t>(-1) == n) // decoding error?
{
result = APR_BADCH;
break;
}
if (static_cast<std::size_t>(-2) == n) // incomplete sequence?
{
break;
}
Transcoder::encode(static_cast<unsigned int>(ch), out);
byteCount += n;
p += n;
}
in.increment_position(byteCount);
return result;
}
private:
std::mbstate_t state;
};
} // namespace helpers
} //namespace log4cxx
CharsetDecoder::CharsetDecoder()
{
}
CharsetDecoder::~CharsetDecoder()
{
}
CharsetDecoder* CharsetDecoder::createDefaultDecoder()
{
#if LOG4CXX_CHARSET_UTF8
#if LOG4CXX_LOGCHAR_IS_UTF8
return new TrivialCharsetDecoder();
#else
return new UTF8CharsetDecoder();
#endif
#elif LOG4CXX_CHARSET_ISO88591 || defined(_WIN32_WCE)
return new ISOLatinCharsetDecoder();
#elif LOG4CXX_CHARSET_USASCII
return new USASCIICharsetDecoder();
#elif LOG4CXX_LOGCHAR_IS_WCHAR && LOG4CXX_HAS_MBSRTOWCS
return new MbstowcsCharsetDecoder();
#else
return new LocaleCharsetDecoder();
#endif
}
CharsetDecoderPtr CharsetDecoder::getDefaultDecoder()
{
static WideLife<CharsetDecoderPtr> decoder(createDefaultDecoder());
//
// if invoked after static variable destruction
// (if logging is called in the destructor of a static object)
// then create a new decoder.
//
if (decoder.value() == 0)
{
return CharsetDecoderPtr( createDefaultDecoder() );
}
return decoder;
}
CharsetDecoderPtr CharsetDecoder::getUTF8Decoder()
{
return std::make_shared<UTF8CharsetDecoder>();
}
CharsetDecoderPtr CharsetDecoder::getISOLatinDecoder()
{
return std::make_shared<ISOLatinCharsetDecoder>();
}
CharsetDecoderPtr CharsetDecoder::getDecoder(const LogString& charset)
{
if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("UTF-8"), LOG4CXX_STR("utf-8")) ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("UTF8"), LOG4CXX_STR("utf8")) ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP65001"), LOG4CXX_STR("cp65001")))
{
#if LOG4CXX_LOGCHAR_IS_UTF8
return std::make_shared<TrivialCharsetDecoder>();
#else
return std::make_shared<UTF8CharsetDecoder>();
#endif
}
else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("C"), LOG4CXX_STR("c")) ||
charset == LOG4CXX_STR("646") ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("US-ASCII"), LOG4CXX_STR("us-ascii")) ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO646-US"), LOG4CXX_STR("iso646-US")) ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ANSI_X3.4-1968"), LOG4CXX_STR("ansi_x3.4-1968")) ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP20127"), LOG4CXX_STR("cp20127")))
{
return std::make_shared<USASCIICharsetDecoder>();
}
else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO-8859-1"), LOG4CXX_STR("iso-8859-1")) ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("ISO-LATIN-1"), LOG4CXX_STR("iso-latin-1")) ||
StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("CP1252"), LOG4CXX_STR("cp1252")))
{
return std::make_shared<ISOLatinCharsetDecoder>();
}
else if (StringHelper::equalsIgnoreCase(charset, LOG4CXX_STR("LOCALE"), LOG4CXX_STR("locale")))
{
return std::make_shared<LocaleCharsetDecoder>();
}
#if APR_HAS_XLATE
return std::make_shared<APRCharsetDecoder>(charset);
#else
throw IllegalArgumentException(charset);
#endif
}
log4cxx_status_t CharsetDecoder::decode(const char* in, size_t maxByteCount, LogString& out)
{
ByteBuffer buf((char*)in, strnlen_s(in, maxByteCount));
return decode(buf, out);
}
unsigned int CharsetDecoder::getUTF8CodePoint(ByteBufferPriv& in)
{
auto availableByteCount = in.remaining();
if (0 == availableByteCount)
return 0xFFFF;
auto pChar = in.current();
auto ch1 = static_cast<unsigned char>(*pChar);
if (ch1 <= 0x7F)
{
in.increment_position(1);
return ch1;
}
//
// should not have continuation character here
//
if ((ch1 & 0xC0) != 0x80 && 1 < availableByteCount)
{
auto ch2 = static_cast<unsigned char>(*(pChar + 1));
if ((ch2 & 0xC0) != 0x80) // not a continuation?
return 0xFFFF;
if ((ch1 & 0xE0) == 0xC0)
{
unsigned int rv = ((ch1 & 0x1F) << 6) + (ch2 & 0x3F);
if (rv >= 0x80)
{
in.increment_position(2);
return rv;
}
return 0xFFFF;
}
if (2 < availableByteCount)
{
auto ch3 = static_cast<unsigned char>(*(pChar + 2));
if ((ch3 & 0xC0) != 0x80) // not a continuation?
return 0xFFFF;
if ((ch1 & 0xF0) == 0xE0)
{
unsigned int rv = ((ch1 & 0x0F) << 12)
+ ((ch2 & 0x3F) << 6)
+ (ch3 & 0x3F);
// RFC 3629 §3 prohibits UTF-8 encodings of the UTF-16 surrogate
// halves (U+D800..U+DFFF); accepting them lets malformed Unicode
// cross the decode boundary into LogString and downstream output.
if (rv < 0x800 || (0xD800 <= rv && rv <= 0xDFFF))
return 0xFFFF;
in.increment_position(3);
return rv;
}
if (3 < availableByteCount)
{
auto ch4 = static_cast<unsigned char>(*(pChar + 3));
if ((ch4 & 0xC0) != 0x80) // not a continuation?
return 0xFFFF;
unsigned int rv = ((ch1 & 0x07) << 18)
+ ((ch2 & 0x3F) << 12)
+ ((ch3 & 0x3F) << 6)
+ (ch4 & 0x3F);
// RFC 3629 §3 caps UTF-8 at U+10FFFF; lead bytes F5..F7 (and
// F4 with an over-high trailer) produce rv > 0x10FFFF, which
// is not a Unicode code point. Without this bound, encodeUTF16
// later silently aliases the bogus value to a valid in-range
// code point — a substitution-collision filter-bypass primitive.
// Lead bytes F8..FF are never valid UTF-8, but the & 0x07 mask
// discards their high bits, so without the (ch1 & 0xF8) == 0xF0
// guard F8 BF BF BF would alias to U+3FFFF instead of being
// rejected.
if ((ch1 & 0xF8) == 0xF0 && rv > 0xFFFF && rv <= 0x10FFFF)
{
in.increment_position(4);
return rv;
}
}
}
}
return 0xFFFF;
}