| /* | |
| * 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/logstring.h> | |
| #include <log4cxx/helpers/simpledateformat.h> | |
| #include <apr_time.h> | |
| #include <apr_strings.h> | |
| #include <sstream> | |
| #include <log4cxx/helpers/transcoder.h> | |
| #include <log4cxx/helpers/stringhelper.h> | |
| #include <assert.h> | |
| #if !defined(LOG4CXX) | |
| #define LOG4CXX 1 | |
| #endif | |
| #include <log4cxx/private/log4cxx_private.h> | |
| #include <log4cxx/helpers/pool.h> | |
| using namespace LOG4CXX_NS; | |
| using namespace LOG4CXX_NS::helpers; | |
| using namespace std; | |
| #if LOG4CXX_HAS_STD_LOCALE | |
| #include <locale> | |
| #endif | |
| #if defined(_MSC_VER) && _MSC_VER < 1300 | |
| #define HAS_FACET(locale, type) _HAS(locale, type) | |
| #define USE_FACET(locale, type) _USE(locale, type) | |
| #define PUT_FACET(facet, os, time, spec) facet.put(os, os, time, spec) | |
| #else | |
| #if defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) | |
| #define HAS_FACET(locale, type) std::has_facet(locale, (type*) 0) | |
| #define USE_FACET(locale, type) std::use_facet(locale, (type*) 0) | |
| #else | |
| #define HAS_FACET(locale, type) std::has_facet < type >(locale) | |
| #define USE_FACET(locale, type) std::use_facet < type >(locale) | |
| #endif | |
| #define PUT_FACET(facet, os, time, spec) facet.put(os, os, os.fill(), time, spec) | |
| #endif | |
| namespace LOG4CXX_NS | |
| { | |
| namespace helpers | |
| { | |
| namespace SimpleDateFormatImpl | |
| { | |
| typedef void (*incrementFunction)(tm& time, apr_time_exp_t& apr_time); | |
| /** | |
| * Append \c names[index] only when \c index is within bounds. | |
| * | |
| * The day/month/AM-PM name tables have a fixed size, but the index comes | |
| * directly from an apr_time_exp_t field (tm_wday, tm_mon, tm_hour). APR's | |
| * time-explosion arithmetic can overflow and yield an out-of-range field for | |
| * extreme apr_time_t inputs, in which case the previous code performed an | |
| * out-of-bounds std::vector read (std::vector::operator[] is unchecked). | |
| */ | |
| static inline void appendName(LogString& s, | |
| const std::vector<LogString>& names, int index) | |
| { | |
| if (0 <= index && static_cast<std::vector<LogString>::size_type>(index) < names.size()) | |
| { | |
| s.append(names[index]); | |
| } | |
| } | |
| /** | |
| * Abstract inner class representing one format token | |
| * (one or more instances of a character). | |
| */ | |
| class PatternToken | |
| { | |
| public: | |
| PatternToken() | |
| { | |
| } | |
| virtual ~PatternToken() | |
| { | |
| } | |
| /** | |
| * Sets the time zone. | |
| * @param zone new time zone. | |
| */ | |
| virtual void setTimeZone(const TimeZonePtr& zone) | |
| { | |
| } | |
| /** | |
| * Appends the formatted content to the string. | |
| * @param s string to which format contribution is appended. | |
| * @param date exploded date/time. | |
| * @param p memory pool. | |
| */ | |
| virtual void format(LogString& s, | |
| const apr_time_exp_t& date, | |
| LOG4CXX_NS::helpers::Pool& p) const = 0; | |
| protected: | |
| static void incrementMonth(tm& time, apr_time_exp_t& aprtime) | |
| { | |
| time.tm_mon++; | |
| aprtime.tm_mon++; | |
| } | |
| static void incrementDay(tm& time, apr_time_exp_t& aprtime) | |
| { | |
| time.tm_wday++; | |
| aprtime.tm_wday++; | |
| } | |
| static void incrementHalfDay(tm& time, apr_time_exp_t& aprtime) | |
| { | |
| time.tm_hour += 12; | |
| aprtime.tm_hour += 12; | |
| } | |
| static void renderFacet(const std::locale* locale, | |
| incrementFunction inc, | |
| char spec, | |
| unsigned int wspec, | |
| const char* aprspec, | |
| std::vector<LogString>& values) | |
| { | |
| std::vector<LogString>::iterator valueIter = values.begin(); | |
| tm time; | |
| memset(&time, 0, sizeof(time)); | |
| apr_time_exp_t aprtime; | |
| memset(&aprtime, 0, sizeof(aprtime)); | |
| #if LOG4CXX_HAS_STD_LOCALE | |
| if (locale != NULL) | |
| { | |
| #if LOG4CXX_WCHAR_T_API | |
| if (HAS_FACET(*locale, std::time_put<wchar_t>)) | |
| { | |
| const std::time_put<wchar_t>& facet = USE_FACET(*locale, std::time_put<wchar_t>); | |
| size_t start = 0; | |
| std::basic_ostringstream<wchar_t> os; | |
| for (; valueIter != values.end(); valueIter++) | |
| { | |
| PUT_FACET(facet, os, &time, (char)wspec); | |
| Transcoder::decode(os.str().substr(start), *valueIter); | |
| start = os.str().length(); | |
| (*inc)(time, aprtime); | |
| } | |
| } | |
| else | |
| #endif | |
| if (HAS_FACET(*locale, std::time_put<char>)) | |
| { | |
| const std::time_put<char>& facet = USE_FACET(*locale, std::time_put<char> ); | |
| size_t start = 0; | |
| std::ostringstream os; | |
| for (; valueIter != values.end(); valueIter++) | |
| { | |
| PUT_FACET(facet, os, &time, spec); | |
| Transcoder::decode(os.str().substr(start), *valueIter); | |
| start = os.str().length(); | |
| (*inc)(time, aprtime); | |
| } | |
| } | |
| } | |
| #endif | |
| const size_t BUFSIZE = 256; | |
| char buf[BUFSIZE]; | |
| memset(buf, 0, BUFSIZE); | |
| apr_size_t retsize = 0; | |
| for (; valueIter != values.end(); valueIter++) | |
| { | |
| apr_status_t stat = apr_strftime(buf, &retsize, BUFSIZE, aprspec, &aprtime); | |
| (*inc)(time, aprtime); | |
| if (stat == APR_SUCCESS) | |
| { | |
| Transcoder::decode(std::string(buf, retsize), *valueIter); | |
| } | |
| else | |
| { | |
| valueIter->append(1, (logchar) 0x3F); | |
| } | |
| } | |
| } | |
| private: | |
| /** | |
| * Private copy constructor. | |
| */ | |
| PatternToken(const PatternToken&); | |
| /** | |
| * Private assignment operator. | |
| */ | |
| PatternToken& operator=(const PatternToken&); | |
| }; | |
| class LiteralToken : public PatternToken | |
| { | |
| public: | |
| LiteralToken( logchar ch1, int count1 ) : ch( ch1 ), count( count1 ) | |
| { | |
| } | |
| void format( LogString& s, const apr_time_exp_t&, Pool& /* p */ ) const | |
| { | |
| s.append( count, ch ); | |
| } | |
| private: | |
| logchar ch; | |
| int count; | |
| }; | |
| class EraToken : public PatternToken | |
| { | |
| public: | |
| EraToken( int /* count */, const std::locale* /* locale */ ) | |
| { | |
| } | |
| void format(LogString& s, const apr_time_exp_t& /* tm */, Pool& /* p */ ) const | |
| { | |
| s.append(1, (logchar) 0x41 /* 'A' */); | |
| s.append(1, (logchar) 0x44 /* 'D' */); | |
| } | |
| }; | |
| class NumericToken : public PatternToken | |
| { | |
| public: | |
| NumericToken( size_t width1 ) : width( width1 ) | |
| { | |
| } | |
| virtual int getField( const apr_time_exp_t& tm ) const = 0; | |
| void format( LogString& s, const apr_time_exp_t& tm, Pool& p ) const | |
| { | |
| size_t initialLength = s.length(); | |
| StringHelper::toString( getField( tm ), s ); | |
| size_t finalLength = s.length(); | |
| if ( initialLength + width > finalLength ) | |
| { | |
| s.insert( initialLength, ( initialLength + width ) - finalLength, (logchar) 0x30 /* '0' */); | |
| } | |
| } | |
| private: | |
| size_t width; | |
| }; | |
| class YearToken : public NumericToken | |
| { | |
| public: | |
| YearToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return 1900 + tm.tm_year; | |
| } | |
| }; | |
| class MonthToken : public NumericToken | |
| { | |
| public: | |
| MonthToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_mon + 1; | |
| } | |
| }; | |
| class AbbreviatedMonthNameToken : public PatternToken | |
| { | |
| public: | |
| AbbreviatedMonthNameToken(int, const std::locale* locale) : names( 12 ) | |
| { | |
| renderFacet(locale, PatternToken::incrementMonth, 'b', 0x62, "%b", names); | |
| } | |
| void format(LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const | |
| { | |
| appendName( s, names, tm.tm_mon ); | |
| } | |
| private: | |
| std::vector < LogString > names; | |
| }; | |
| class FullMonthNameToken : public PatternToken | |
| { | |
| public: | |
| FullMonthNameToken( int width, const std::locale* locale) : names( 12 ) | |
| { | |
| renderFacet(locale, PatternToken::incrementMonth, 'B', 0x42, "%B", names); | |
| } | |
| void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const | |
| { | |
| appendName( s, names, tm.tm_mon ); | |
| } | |
| private: | |
| std::vector < LogString > names; | |
| }; | |
| class WeekInYearToken : public NumericToken | |
| { | |
| public: | |
| WeekInYearToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_yday / 7; | |
| } | |
| }; | |
| class WeekInMonthToken : public NumericToken | |
| { | |
| public: | |
| WeekInMonthToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_mday / 7; | |
| } | |
| }; | |
| class DayInMonthToken : public NumericToken | |
| { | |
| public: | |
| DayInMonthToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_mday; | |
| } | |
| }; | |
| class DayInYearToken : public NumericToken | |
| { | |
| public: | |
| DayInYearToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_yday; | |
| } | |
| }; | |
| class DayOfWeekInMonthToken : public NumericToken | |
| { | |
| public: | |
| DayOfWeekInMonthToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& /* tm */ ) const | |
| { | |
| return -1; | |
| } | |
| }; | |
| class AbbreviatedDayNameToken : public PatternToken | |
| { | |
| public: | |
| AbbreviatedDayNameToken( int width, const std::locale* locale) : names( 7 ) | |
| { | |
| renderFacet(locale, PatternToken::incrementDay, 'a', 0x61, "%a", names); | |
| } | |
| void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const | |
| { | |
| appendName( s, names, tm.tm_wday ); | |
| } | |
| private: | |
| std::vector < LogString > names; | |
| }; | |
| class FullDayNameToken : public PatternToken | |
| { | |
| public: | |
| FullDayNameToken( int width, const std::locale* locale) : names( 7 ) | |
| { | |
| renderFacet(locale, PatternToken::incrementDay, 'A', 0x41, "%A", names); | |
| } | |
| void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const | |
| { | |
| appendName( s, names, tm.tm_wday ); | |
| } | |
| private: | |
| std::vector < LogString > names; | |
| }; | |
| class MilitaryHourToken : public NumericToken | |
| { | |
| public: | |
| MilitaryHourToken( int width1, int offset1 ) : NumericToken( width1 ), offset( offset1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_hour + offset; | |
| } | |
| private: | |
| int offset; | |
| }; | |
| class HourToken : public NumericToken | |
| { | |
| public: | |
| HourToken( int width1, int /* offset1 */ ) : NumericToken( width1 ), offset( 0 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return ( ( tm.tm_hour + 12 - offset ) % 12 ) + offset; | |
| } | |
| private: | |
| int offset; | |
| }; | |
| class MinuteToken : public NumericToken | |
| { | |
| public: | |
| MinuteToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_min; | |
| } | |
| }; | |
| class SecondToken : public NumericToken | |
| { | |
| public: | |
| SecondToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_sec; | |
| } | |
| }; | |
| class MillisecondToken : public NumericToken | |
| { | |
| public: | |
| MillisecondToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_usec / 1000; | |
| } | |
| }; | |
| class MicrosecondToken : public NumericToken | |
| { | |
| public: | |
| MicrosecondToken( int width1 ) : NumericToken( width1 ) | |
| { | |
| } | |
| int getField( const apr_time_exp_t& tm ) const | |
| { | |
| return tm.tm_usec; | |
| } | |
| }; | |
| class AMPMToken : public PatternToken | |
| { | |
| public: | |
| AMPMToken( int width, const std::locale* locale) : names( 2 ) | |
| { | |
| renderFacet(locale, PatternToken::incrementHalfDay, 'p', 0x70, "%p", names); | |
| } | |
| void format( LogString& s, const apr_time_exp_t& tm, Pool& /* p */ ) const | |
| { | |
| appendName( s, names, tm.tm_hour / 12 ); | |
| } | |
| private: | |
| std::vector < LogString > names; | |
| }; | |
| class GeneralTimeZoneToken : public PatternToken | |
| { | |
| public: | |
| GeneralTimeZoneToken( int /* width */ ) | |
| { | |
| } | |
| void format( LogString& s, const apr_time_exp_t&, Pool& /* p */ ) const | |
| { | |
| s.append(timeZone->getID()); | |
| } | |
| void setTimeZone( const TimeZonePtr& zone ) | |
| { | |
| timeZone = zone; | |
| } | |
| private: | |
| TimeZonePtr timeZone; | |
| }; | |
| class RFC822TimeZoneToken : public PatternToken | |
| { | |
| public: | |
| RFC822TimeZoneToken( int /* width */ ) | |
| { | |
| } | |
| void format( LogString& s, const apr_time_exp_t& tm, Pool& p ) const | |
| { | |
| if ( tm.tm_gmtoff == 0 ) | |
| { | |
| s.append( 1, (logchar) 0x5A /* 'Z' */ ); | |
| } | |
| else | |
| { | |
| apr_int32_t off = tm.tm_gmtoff; | |
| s.reserve(s.length() + 5); | |
| if ( off < 0 ) | |
| { | |
| s.push_back( '-' ); | |
| off = -off; | |
| }else{ | |
| s.push_back( '+' ); | |
| } | |
| LogString hours; | |
| StringHelper::toString( off / 3600, hours ); | |
| if( hours.size() == 1 ){ | |
| s.push_back( '0' ); | |
| } | |
| s.append(hours); | |
| LogString min; | |
| StringHelper::toString( ( off % 3600 ) / 60, min ); | |
| if( min.size() == 1 ){ | |
| s.push_back( '0' ); | |
| } | |
| s.append(min); | |
| } | |
| } | |
| }; | |
| } | |
| } | |
| } | |
| using namespace LOG4CXX_NS::helpers::SimpleDateFormatImpl; | |
| void SimpleDateFormat::addToken(const logchar spec, const int repeat, const std::locale* locale, | |
| std::vector < PatternToken* >& pattern ) | |
| { | |
| PatternToken* token = NULL; | |
| switch ( spec ) | |
| { | |
| case 0x47: // 'G' | |
| token = ( new EraToken( repeat, locale ) ); | |
| break; | |
| case 0x79: // 'y' | |
| token = ( new YearToken( repeat ) ); | |
| break; | |
| case 0x4D: // 'M' | |
| if ( repeat <= 2 ) | |
| { | |
| token = ( new MonthToken( repeat ) ); | |
| } | |
| else if ( repeat <= 3 ) | |
| { | |
| token = ( new AbbreviatedMonthNameToken( repeat, locale ) ); | |
| } | |
| else | |
| { | |
| token = ( new FullMonthNameToken( repeat, locale ) ); | |
| } | |
| break; | |
| case 0x77: // 'w' | |
| token = ( new WeekInYearToken( repeat ) ); | |
| break; | |
| case 0x57: // 'W' | |
| token = ( new WeekInMonthToken( repeat ) ); | |
| break; | |
| case 0x44: // 'D' | |
| token = ( new DayInYearToken( repeat ) ); | |
| break; | |
| case 0x64: // 'd' | |
| token = ( new DayInMonthToken( repeat ) ); | |
| break; | |
| case 0x46: // 'F' | |
| token = ( new DayOfWeekInMonthToken( repeat ) ); | |
| break; | |
| case 0x45: // 'E' | |
| if ( repeat <= 3 ) | |
| { | |
| token = ( new AbbreviatedDayNameToken( repeat, locale ) ); | |
| } | |
| else | |
| { | |
| token = ( new FullDayNameToken( repeat, locale ) ); | |
| } | |
| break; | |
| case 0x61: // 'a' | |
| token = ( new AMPMToken( repeat, locale ) ); | |
| break; | |
| case 0x48: // 'H' | |
| token = ( new MilitaryHourToken( repeat, 0 ) ); | |
| break; | |
| case 0x6B: // 'k' | |
| token = ( new MilitaryHourToken( repeat, 1 ) ); | |
| break; | |
| case 0x4B: // 'K' | |
| token = ( new HourToken( repeat, 0 ) ); | |
| break; | |
| case 0x68: // 'h' | |
| token = ( new HourToken( repeat, 1 ) ); | |
| break; | |
| case 0x6D: // 'm' | |
| token = ( new MinuteToken( repeat ) ); | |
| break; | |
| case 0x73: // 's' | |
| token = ( new SecondToken( repeat ) ); | |
| break; | |
| case 0x53: // 'S' | |
| if ( repeat == 6 ) | |
| { | |
| token = ( new MicrosecondToken( repeat ) ); | |
| } | |
| else | |
| { | |
| // It would be nice to support patterns with arbitrary | |
| // subsecond precision (like "s.S" or "s.SSSS"), but we | |
| // don't; so this is a back-compatible default. | |
| token = ( new MillisecondToken( repeat ) ); | |
| } | |
| break; | |
| case 0x7A: // 'z' | |
| token = ( new GeneralTimeZoneToken( repeat ) ); | |
| break; | |
| case 0x5A: // 'Z' | |
| token = ( new RFC822TimeZoneToken( repeat ) ); | |
| break; | |
| default: | |
| token = ( new LiteralToken( spec, repeat ) ); | |
| } | |
| assert( token != NULL ); | |
| pattern.push_back( token ); | |
| } | |
| void SimpleDateFormat::parsePattern( const LogString& fmt, const std::locale* locale, | |
| std::vector < PatternToken* >& pattern ) | |
| { | |
| if ( !fmt.empty() ) | |
| { | |
| LogString::const_iterator iter = fmt.begin(); | |
| int repeat = 1; | |
| logchar prevChar = * iter; | |
| for ( iter++; iter != fmt.end(); iter++ ) | |
| { | |
| if ( * iter == prevChar ) | |
| { | |
| repeat++; | |
| } | |
| else | |
| { | |
| addToken( prevChar, repeat, locale, pattern ); | |
| prevChar = * iter; | |
| repeat = 1; | |
| } | |
| } | |
| addToken( prevChar, repeat, locale, pattern ); | |
| } | |
| } | |
| struct SimpleDateFormat::SimpleDateFormatPrivate{ | |
| SimpleDateFormatPrivate() : | |
| timeZone(TimeZone::getDefault()) | |
| {} | |
| /** | |
| * Time zone. | |
| */ | |
| TimeZonePtr timeZone; | |
| /** | |
| * List of tokens. | |
| */ | |
| PatternTokenList pattern; | |
| }; | |
| SimpleDateFormat::SimpleDateFormat( const LogString& fmt ) : m_priv(std::make_unique<SimpleDateFormatPrivate>()) | |
| { | |
| #if LOG4CXX_HAS_STD_LOCALE | |
| std::locale defaultLocale; | |
| parsePattern( fmt, & defaultLocale, m_priv->pattern ); | |
| #else | |
| parsePattern( fmt, NULL, m_priv->pattern ); | |
| #endif | |
| for (auto const& item : m_priv->pattern) | |
| { | |
| item->setTimeZone( m_priv->timeZone ); | |
| } | |
| } | |
| SimpleDateFormat::SimpleDateFormat( const LogString& fmt, const std::locale* locale ) : m_priv(std::make_unique<SimpleDateFormatPrivate>()) | |
| { | |
| parsePattern( fmt, locale, m_priv->pattern ); | |
| for (auto const& item : m_priv->pattern) | |
| { | |
| item->setTimeZone( m_priv->timeZone ); | |
| } | |
| } | |
| SimpleDateFormat::~SimpleDateFormat() | |
| { | |
| for (auto item : m_priv->pattern) | |
| { | |
| delete item; | |
| } | |
| } | |
| void SimpleDateFormat::format( LogString& toAppendTo, log4cxx_time_t tm ) const | |
| { | |
| helpers::Pool tempPool; | |
| apr_time_exp_t exploded; | |
| apr_status_t stat = m_priv->timeZone->explode( & exploded, tm ); | |
| if ( stat == APR_SUCCESS ) | |
| { | |
| for ( PatternTokenList::const_iterator iter = m_priv->pattern.begin(); iter != m_priv->pattern.end(); iter++ ) | |
| { | |
| ( * iter )->format( toAppendTo, exploded, tempPool ); | |
| } | |
| } | |
| } | |
| void SimpleDateFormat::setTimeZone( const TimeZonePtr& zone ) | |
| { | |
| m_priv->timeZone = zone; | |
| } |