Home | History | Annotate | Download | only in fxjs
      1 // Copyright 2014 PDFium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
      6 
      7 #include "fxjs/cjs_util.h"
      8 
      9 #include <time.h>
     10 
     11 #include <algorithm>
     12 #include <cmath>
     13 #include <cwctype>
     14 #include <string>
     15 #include <vector>
     16 
     17 #include "core/fxcrt/fx_extension.h"
     18 #include "fxjs/JS_Define.h"
     19 #include "fxjs/cjs_event_context.h"
     20 #include "fxjs/cjs_eventhandler.h"
     21 #include "fxjs/cjs_object.h"
     22 #include "fxjs/cjs_publicmethods.h"
     23 #include "fxjs/cjs_runtime.h"
     24 #include "fxjs/js_resources.h"
     25 
     26 #if _FX_OS_ == _FX_OS_ANDROID_
     27 #include <ctype.h>
     28 #endif
     29 
     30 namespace {
     31 
     32 // Map PDF-style directives to equivalent wcsftime directives. Not
     33 // all have direct equivalents, though.
     34 struct TbConvert {
     35   const wchar_t* lpszJSMark;
     36   const wchar_t* lpszCppMark;
     37 };
     38 
     39 // Map PDF-style directives lacking direct wcsftime directives to
     40 // the value with which they will be replaced.
     41 struct TbConvertAdditional {
     42   const wchar_t* lpszJSMark;
     43   int iValue;
     44 };
     45 
     46 const TbConvert TbConvertTable[] = {
     47     {L"mmmm", L"%B"}, {L"mmm", L"%b"}, {L"mm", L"%m"},   {L"dddd", L"%A"},
     48     {L"ddd", L"%a"},  {L"dd", L"%d"},  {L"yyyy", L"%Y"}, {L"yy", L"%y"},
     49     {L"HH", L"%H"},   {L"hh", L"%I"},  {L"MM", L"%M"},   {L"ss", L"%S"},
     50     {L"TT", L"%p"},
     51 #if defined(_WIN32)
     52     {L"tt", L"%p"},   {L"h", L"%#I"},
     53 #else
     54     {L"tt", L"%P"},   {L"h", L"%l"},
     55 #endif
     56 };
     57 
     58 }  // namespace
     59 
     60 const JSMethodSpec CJS_Util::MethodSpecs[] = {
     61     {"printd", printd_static},
     62     {"printf", printf_static},
     63     {"printx", printx_static},
     64     {"scand", scand_static},
     65     {"byteToChar", byteToChar_static}};
     66 
     67 int CJS_Util::ObjDefnID = -1;
     68 
     69 // static
     70 void CJS_Util::DefineJSObjects(CFXJS_Engine* pEngine) {
     71   ObjDefnID =
     72       pEngine->DefineObj("util", FXJSOBJTYPE_STATIC,
     73                          JSConstructor<CJS_Util, util>, JSDestructor<CJS_Util>);
     74   DefineMethods(pEngine, ObjDefnID, MethodSpecs, FX_ArraySize(MethodSpecs));
     75 }
     76 
     77 util::util(CJS_Object* pJSObject) : CJS_EmbedObj(pJSObject) {}
     78 
     79 util::~util() {}
     80 
     81 CJS_Return util::printf(CJS_Runtime* pRuntime,
     82                         const std::vector<v8::Local<v8::Value>>& params) {
     83   const size_t iSize = params.size();
     84   if (iSize < 1)
     85     return CJS_Return(false);
     86 
     87   std::wstring unsafe_fmt_string(pRuntime->ToWideString(params[0]).c_str());
     88   std::vector<std::wstring> unsafe_conversion_specifiers;
     89   int iOffset = 0;
     90   int iOffend = 0;
     91   unsafe_fmt_string.insert(unsafe_fmt_string.begin(), L'S');
     92   while (iOffset != -1) {
     93     iOffend = unsafe_fmt_string.find(L"%", iOffset + 1);
     94     std::wstring strSub;
     95     if (iOffend == -1)
     96       strSub = unsafe_fmt_string.substr(iOffset);
     97     else
     98       strSub = unsafe_fmt_string.substr(iOffset, iOffend - iOffset);
     99     unsafe_conversion_specifiers.push_back(strSub);
    100     iOffset = iOffend;
    101   }
    102 
    103   std::wstring c_strResult;
    104   for (size_t iIndex = 0; iIndex < unsafe_conversion_specifiers.size();
    105        ++iIndex) {
    106     std::wstring c_strFormat = unsafe_conversion_specifiers[iIndex];
    107     if (iIndex == 0) {
    108       c_strResult = c_strFormat;
    109       continue;
    110     }
    111 
    112     if (iIndex >= iSize) {
    113       c_strResult += c_strFormat;
    114       continue;
    115     }
    116 
    117     WideString strSegment;
    118     switch (ParseDataType(&c_strFormat)) {
    119       case UTIL_INT:
    120         strSegment = WideString::Format(c_strFormat.c_str(),
    121                                         pRuntime->ToInt32(params[iIndex]));
    122         break;
    123       case UTIL_DOUBLE:
    124         strSegment = WideString::Format(c_strFormat.c_str(),
    125                                         pRuntime->ToDouble(params[iIndex]));
    126         break;
    127       case UTIL_STRING:
    128         strSegment =
    129             WideString::Format(c_strFormat.c_str(),
    130                                pRuntime->ToWideString(params[iIndex]).c_str());
    131         break;
    132       default:
    133         strSegment = WideString::Format(L"%ls", c_strFormat.c_str());
    134         break;
    135     }
    136     c_strResult += strSegment.c_str();
    137   }
    138 
    139   c_strResult.erase(c_strResult.begin());
    140   return CJS_Return(pRuntime->NewString(c_strResult.c_str()));
    141 }
    142 
    143 CJS_Return util::printd(CJS_Runtime* pRuntime,
    144                         const std::vector<v8::Local<v8::Value>>& params) {
    145   const size_t iSize = params.size();
    146   if (iSize < 2)
    147     return CJS_Return(false);
    148 
    149   if (params[1].IsEmpty() || !params[1]->IsDate())
    150     return CJS_Return(JSGetStringFromID(JSMessage::kSecondParamNotDateError));
    151 
    152   v8::Local<v8::Date> v8_date = params[1].As<v8::Date>();
    153   if (v8_date.IsEmpty() || std::isnan(pRuntime->ToDouble(v8_date))) {
    154     return CJS_Return(
    155         JSGetStringFromID(JSMessage::kSecondParamInvalidDateError));
    156   }
    157 
    158   double date = JS_LocalTime(pRuntime->ToDouble(v8_date));
    159   int year = JS_GetYearFromTime(date);
    160   int month = JS_GetMonthFromTime(date) + 1;  // One-based.
    161   int day = JS_GetDayFromTime(date);
    162   int hour = JS_GetHourFromTime(date);
    163   int min = JS_GetMinFromTime(date);
    164   int sec = JS_GetSecFromTime(date);
    165 
    166   if (params[0]->IsNumber()) {
    167     WideString swResult;
    168     switch (pRuntime->ToInt32(params[0])) {
    169       case 0:
    170         swResult = WideString::Format(L"D:%04d%02d%02d%02d%02d%02d", year,
    171                                       month, day, hour, min, sec);
    172         break;
    173       case 1:
    174         swResult = WideString::Format(L"%04d.%02d.%02d %02d:%02d:%02d", year,
    175                                       month, day, hour, min, sec);
    176         break;
    177       case 2:
    178         swResult = WideString::Format(L"%04d/%02d/%02d %02d:%02d:%02d", year,
    179                                       month, day, hour, min, sec);
    180         break;
    181       default:
    182         return CJS_Return(JSGetStringFromID(JSMessage::kValueError));
    183     }
    184 
    185     return CJS_Return(pRuntime->NewString(swResult.c_str()));
    186   }
    187 
    188   if (params[0]->IsString()) {
    189     // We don't support XFAPicture at the moment.
    190     if (iSize > 2 && pRuntime->ToBoolean(params[2]))
    191       return CJS_Return(JSGetStringFromID(JSMessage::kNotSupportedError));
    192 
    193     // Convert PDF-style format specifiers to wcsftime specifiers. Remove any
    194     // pre-existing %-directives before inserting our own.
    195     std::basic_string<wchar_t> cFormat =
    196         pRuntime->ToWideString(params[0]).c_str();
    197     cFormat.erase(std::remove(cFormat.begin(), cFormat.end(), '%'),
    198                   cFormat.end());
    199 
    200     for (size_t i = 0; i < FX_ArraySize(TbConvertTable); ++i) {
    201       int iStart = 0;
    202       int iEnd;
    203       while ((iEnd = cFormat.find(TbConvertTable[i].lpszJSMark, iStart)) !=
    204              -1) {
    205         cFormat.replace(iEnd, wcslen(TbConvertTable[i].lpszJSMark),
    206                         TbConvertTable[i].lpszCppMark);
    207         iStart = iEnd;
    208       }
    209     }
    210 
    211     if (year < 0)
    212       return CJS_Return(JSGetStringFromID(JSMessage::kValueError));
    213 
    214     static const TbConvertAdditional cTableAd[] = {
    215         {L"m", month}, {L"d", day},
    216         {L"H", hour},  {L"h", hour > 12 ? hour - 12 : hour},
    217         {L"M", min},   {L"s", sec},
    218     };
    219 
    220     for (size_t i = 0; i < FX_ArraySize(cTableAd); ++i) {
    221       int iStart = 0;
    222       int iEnd;
    223       while ((iEnd = cFormat.find(cTableAd[i].lpszJSMark, iStart)) != -1) {
    224         if (iEnd > 0) {
    225           if (cFormat[iEnd - 1] == L'%') {
    226             iStart = iEnd + 1;
    227             continue;
    228           }
    229         }
    230         cFormat.replace(iEnd, wcslen(cTableAd[i].lpszJSMark),
    231                         WideString::Format(L"%d", cTableAd[i].iValue).c_str());
    232         iStart = iEnd;
    233       }
    234     }
    235 
    236     struct tm time = {};
    237     time.tm_year = year - 1900;
    238     time.tm_mon = month - 1;
    239     time.tm_mday = day;
    240     time.tm_hour = hour;
    241     time.tm_min = min;
    242     time.tm_sec = sec;
    243 
    244     wchar_t buf[64] = {};
    245     FXSYS_wcsftime(buf, 64, cFormat.c_str(), &time);
    246     cFormat = buf;
    247     return CJS_Return(pRuntime->NewString(cFormat.c_str()));
    248   }
    249 
    250   return CJS_Return(JSGetStringFromID(JSMessage::kTypeError));
    251 }
    252 
    253 CJS_Return util::printx(CJS_Runtime* pRuntime,
    254                         const std::vector<v8::Local<v8::Value>>& params) {
    255   if (params.size() < 2)
    256     return CJS_Return(JSGetStringFromID(JSMessage::kParamError));
    257 
    258   return CJS_Return(
    259       pRuntime->NewString(printx(pRuntime->ToWideString(params[0]),
    260                                  pRuntime->ToWideString(params[1]))
    261                               .c_str()));
    262 }
    263 
    264 enum CaseMode { kPreserveCase, kUpperCase, kLowerCase };
    265 
    266 static wchar_t TranslateCase(wchar_t input, CaseMode eMode) {
    267   if (eMode == kLowerCase && FXSYS_isupper(input))
    268     return input | 0x20;
    269   if (eMode == kUpperCase && FXSYS_islower(input))
    270     return input & ~0x20;
    271   return input;
    272 }
    273 
    274 WideString util::printx(const WideString& wsFormat,
    275                         const WideString& wsSource) {
    276   WideString wsResult;
    277   size_t iSourceIdx = 0;
    278   size_t iFormatIdx = 0;
    279   CaseMode eCaseMode = kPreserveCase;
    280   bool bEscaped = false;
    281   while (iFormatIdx < wsFormat.GetLength()) {
    282     if (bEscaped) {
    283       bEscaped = false;
    284       wsResult += wsFormat[iFormatIdx];
    285       ++iFormatIdx;
    286       continue;
    287     }
    288     switch (wsFormat[iFormatIdx]) {
    289       case '\\': {
    290         bEscaped = true;
    291         ++iFormatIdx;
    292       } break;
    293       case '<': {
    294         eCaseMode = kLowerCase;
    295         ++iFormatIdx;
    296       } break;
    297       case '>': {
    298         eCaseMode = kUpperCase;
    299         ++iFormatIdx;
    300       } break;
    301       case '=': {
    302         eCaseMode = kPreserveCase;
    303         ++iFormatIdx;
    304       } break;
    305       case '?': {
    306         if (iSourceIdx < wsSource.GetLength()) {
    307           wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
    308           ++iSourceIdx;
    309         }
    310         ++iFormatIdx;
    311       } break;
    312       case 'X': {
    313         if (iSourceIdx < wsSource.GetLength()) {
    314           if (FXSYS_iswalnum(wsSource[iSourceIdx])) {
    315             wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
    316             ++iFormatIdx;
    317           }
    318           ++iSourceIdx;
    319         } else {
    320           ++iFormatIdx;
    321         }
    322       } break;
    323       case 'A': {
    324         if (iSourceIdx < wsSource.GetLength()) {
    325           if (FXSYS_iswalpha(wsSource[iSourceIdx])) {
    326             wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
    327             ++iFormatIdx;
    328           }
    329           ++iSourceIdx;
    330         } else {
    331           ++iFormatIdx;
    332         }
    333       } break;
    334       case '9': {
    335         if (iSourceIdx < wsSource.GetLength()) {
    336           if (std::iswdigit(wsSource[iSourceIdx])) {
    337             wsResult += wsSource[iSourceIdx];
    338             ++iFormatIdx;
    339           }
    340           ++iSourceIdx;
    341         } else {
    342           ++iFormatIdx;
    343         }
    344       } break;
    345       case '*': {
    346         if (iSourceIdx < wsSource.GetLength()) {
    347           wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
    348           ++iSourceIdx;
    349         } else {
    350           ++iFormatIdx;
    351         }
    352       } break;
    353       default: {
    354         wsResult += wsFormat[iFormatIdx];
    355         ++iFormatIdx;
    356       } break;
    357     }
    358   }
    359   return wsResult;
    360 }
    361 
    362 CJS_Return util::scand(CJS_Runtime* pRuntime,
    363                        const std::vector<v8::Local<v8::Value>>& params) {
    364   if (params.size() < 2)
    365     return CJS_Return(false);
    366 
    367   WideString sFormat = pRuntime->ToWideString(params[0]);
    368   WideString sDate = pRuntime->ToWideString(params[1]);
    369   double dDate = JS_GetDateTime();
    370   if (sDate.GetLength() > 0)
    371     dDate = CJS_PublicMethods::MakeRegularDate(sDate, sFormat, nullptr);
    372 
    373   if (std::isnan(dDate))
    374     return CJS_Return(pRuntime->NewUndefined());
    375   return CJS_Return(pRuntime->NewDate(dDate));
    376 }
    377 
    378 CJS_Return util::byteToChar(CJS_Runtime* pRuntime,
    379                             const std::vector<v8::Local<v8::Value>>& params) {
    380   if (params.size() < 1)
    381     return CJS_Return(JSGetStringFromID(JSMessage::kParamError));
    382 
    383   int arg = pRuntime->ToInt32(params[0]);
    384   if (arg < 0 || arg > 255)
    385     return CJS_Return(JSGetStringFromID(JSMessage::kValueError));
    386 
    387   WideString wStr(static_cast<wchar_t>(arg));
    388   return CJS_Return(pRuntime->NewString(wStr.c_str()));
    389 }
    390 
    391 // Ensure that sFormat contains at most one well-understood printf formatting
    392 // directive which is safe to use with a single argument, and return the type
    393 // of argument expected, or -1 otherwise. If -1 is returned, it is NOT safe
    394 // to use sFormat with printf() and it must be copied byte-by-byte.
    395 int util::ParseDataType(std::wstring* sFormat) {
    396   enum State { BEFORE, FLAGS, WIDTH, PRECISION, SPECIFIER, AFTER };
    397 
    398   int result = -1;
    399   State state = BEFORE;
    400   size_t precision_digits = 0;
    401   size_t i = 0;
    402   while (i < sFormat->length()) {
    403     wchar_t c = (*sFormat)[i];
    404     switch (state) {
    405       case BEFORE:
    406         if (c == L'%')
    407           state = FLAGS;
    408         break;
    409       case FLAGS:
    410         if (c == L'+' || c == L'-' || c == L'#' || c == L' ') {
    411           // Stay in same state.
    412         } else {
    413           state = WIDTH;
    414           continue;  // Re-process same character.
    415         }
    416         break;
    417       case WIDTH:
    418         if (c == L'*')
    419           return -1;
    420         if (std::iswdigit(c)) {
    421           // Stay in same state.
    422         } else if (c == L'.') {
    423           state = PRECISION;
    424         } else {
    425           state = SPECIFIER;
    426           continue;  // Re-process same character.
    427         }
    428         break;
    429       case PRECISION:
    430         if (c == L'*')
    431           return -1;
    432         if (std::iswdigit(c)) {
    433           // Stay in same state.
    434           ++precision_digits;
    435         } else {
    436           state = SPECIFIER;
    437           continue;  // Re-process same character.
    438         }
    439         break;
    440       case SPECIFIER:
    441         if (c == L'c' || c == L'C' || c == L'd' || c == L'i' || c == L'o' ||
    442             c == L'u' || c == L'x' || c == L'X') {
    443           result = UTIL_INT;
    444         } else if (c == L'e' || c == L'E' || c == L'f' || c == L'g' ||
    445                    c == L'G') {
    446           result = UTIL_DOUBLE;
    447         } else if (c == L's' || c == L'S') {
    448           // Map s to S since we always deal internally with wchar_t strings.
    449           // TODO(tsepez): Probably 100% borked. %S is not a standard
    450           // conversion.
    451           (*sFormat)[i] = L'S';
    452           result = UTIL_STRING;
    453         } else {
    454           return -1;
    455         }
    456         state = AFTER;
    457         break;
    458       case AFTER:
    459         if (c == L'%')
    460           return -1;
    461         // Stay in same state until string exhausted.
    462         break;
    463     }
    464     ++i;
    465   }
    466   // See https://crbug.com/740166
    467   if (result == UTIL_INT && precision_digits > 2)
    468     return -1;
    469 
    470   return result;
    471 }
    472