// Copyright 2014 PDFium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com #include "fpdfsdk/javascript/util.h" #include <time.h> #include <algorithm> #include <string> #include <vector> #include "core/fxcrt/fx_ext.h" #include "fpdfsdk/javascript/JS_Define.h" #include "fpdfsdk/javascript/JS_EventHandler.h" #include "fpdfsdk/javascript/JS_Object.h" #include "fpdfsdk/javascript/JS_Value.h" #include "fpdfsdk/javascript/PublicMethods.h" #include "fpdfsdk/javascript/cjs_event_context.h" #include "fpdfsdk/javascript/cjs_runtime.h" #include "fpdfsdk/javascript/resource.h" #if _FX_OS_ == _FX_ANDROID_ #include <ctype.h> #endif JSConstSpec CJS_Util::ConstSpecs[] = {{0, JSConstSpec::Number, 0, 0}}; JSPropertySpec CJS_Util::PropertySpecs[] = {{0, 0, 0}}; JSMethodSpec CJS_Util::MethodSpecs[] = { {"printd", printd_static}, {"printf", printf_static}, {"printx", printx_static}, {"scand", scand_static}, {"byteToChar", byteToChar_static}, {0, 0}}; IMPLEMENT_JS_CLASS(CJS_Util, util) #define UTIL_INT 0 #define UTIL_DOUBLE 1 #define UTIL_STRING 2 namespace { // Map PDF-style directives to equivalent wcsftime directives. Not // all have direct equivalents, though. struct TbConvert { const FX_WCHAR* lpszJSMark; const FX_WCHAR* lpszCppMark; }; // Map PDF-style directives lacking direct wcsftime directives to // the value with which they will be replaced. struct TbConvertAdditional { const FX_WCHAR* lpszJSMark; int iValue; }; const TbConvert TbConvertTable[] = { {L"mmmm", L"%B"}, {L"mmm", L"%b"}, {L"mm", L"%m"}, {L"dddd", L"%A"}, {L"ddd", L"%a"}, {L"dd", L"%d"}, {L"yyyy", L"%Y"}, {L"yy", L"%y"}, {L"HH", L"%H"}, {L"hh", L"%I"}, {L"MM", L"%M"}, {L"ss", L"%S"}, {L"TT", L"%p"}, #if defined(_WIN32) {L"tt", L"%p"}, {L"h", L"%#I"}, #else {L"tt", L"%P"}, {L"h", L"%l"}, #endif }; int ParseDataType(std::wstring* sFormat) { bool bPercent = false; for (size_t i = 0; i < sFormat->length(); ++i) { wchar_t c = (*sFormat)[i]; if (c == L'%') { bPercent = true; continue; } if (bPercent) { if (c == L'c' || c == L'C' || c == L'd' || c == L'i' || c == L'o' || c == L'u' || c == L'x' || c == L'X') { return UTIL_INT; } if (c == L'e' || c == L'E' || c == L'f' || c == L'g' || c == L'G') { return UTIL_DOUBLE; } if (c == L's' || c == L'S') { // Map s to S since we always deal internally // with wchar_t strings. (*sFormat)[i] = L'S'; return UTIL_STRING; } if (c == L'.' || c == L'+' || c == L'-' || c == L'#' || c == L' ' || FXSYS_iswdigit(c)) { continue; } break; } } return -1; } } // namespace util::util(CJS_Object* pJSObject) : CJS_EmbedObj(pJSObject) {} util::~util() {} bool util::printf(CJS_Runtime* pRuntime, const std::vector<CJS_Value>& params, CJS_Value& vRet, CFX_WideString& sError) { int iSize = params.size(); if (iSize < 1) return false; std::wstring c_ConvChar(params[0].ToCFXWideString(pRuntime).c_str()); std::vector<std::wstring> c_strConvers; int iOffset = 0; int iOffend = 0; c_ConvChar.insert(c_ConvChar.begin(), L'S'); while (iOffset != -1) { iOffend = c_ConvChar.find(L"%", iOffset + 1); std::wstring strSub; if (iOffend == -1) strSub = c_ConvChar.substr(iOffset); else strSub = c_ConvChar.substr(iOffset, iOffend - iOffset); c_strConvers.push_back(strSub); iOffset = iOffend; } std::wstring c_strResult; std::wstring c_strFormat; for (int iIndex = 0; iIndex < (int)c_strConvers.size(); iIndex++) { c_strFormat = c_strConvers[iIndex]; if (iIndex == 0) { c_strResult = c_strFormat; continue; } CFX_WideString strSegment; if (iIndex >= iSize) { c_strResult += c_strFormat; continue; } switch (ParseDataType(&c_strFormat)) { case UTIL_INT: strSegment.Format(c_strFormat.c_str(), params[iIndex].ToInt(pRuntime)); break; case UTIL_DOUBLE: strSegment.Format(c_strFormat.c_str(), params[iIndex].ToDouble(pRuntime)); break; case UTIL_STRING: strSegment.Format(c_strFormat.c_str(), params[iIndex].ToCFXWideString(pRuntime).c_str()); break; default: strSegment.Format(L"%S", c_strFormat.c_str()); break; } c_strResult += strSegment.GetBuffer(strSegment.GetLength() + 1); } c_strResult.erase(c_strResult.begin()); vRet = CJS_Value(pRuntime, c_strResult.c_str()); return true; } bool util::printd(CJS_Runtime* pRuntime, const std::vector<CJS_Value>& params, CJS_Value& vRet, CFX_WideString& sError) { int iSize = params.size(); if (iSize < 2) return false; CJS_Value p1 = params[0]; CJS_Value p2 = params[1]; CJS_Date jsDate; if (!p2.ConvertToDate(pRuntime, jsDate)) { sError = JSGetStringFromID(IDS_STRING_JSPRINT1); return false; } if (!jsDate.IsValidDate(pRuntime)) { sError = JSGetStringFromID(IDS_STRING_JSPRINT2); return false; } if (p1.GetType() == CJS_Value::VT_number) { CFX_WideString swResult; switch (p1.ToInt(pRuntime)) { case 0: swResult.Format(L"D:%04d%02d%02d%02d%02d%02d", jsDate.GetYear(pRuntime), jsDate.GetMonth(pRuntime) + 1, jsDate.GetDay(pRuntime), jsDate.GetHours(pRuntime), jsDate.GetMinutes(pRuntime), jsDate.GetSeconds(pRuntime)); break; case 1: swResult.Format(L"%04d.%02d.%02d %02d:%02d:%02d", jsDate.GetYear(pRuntime), jsDate.GetMonth(pRuntime) + 1, jsDate.GetDay(pRuntime), jsDate.GetHours(pRuntime), jsDate.GetMinutes(pRuntime), jsDate.GetSeconds(pRuntime)); break; case 2: swResult.Format(L"%04d/%02d/%02d %02d:%02d:%02d", jsDate.GetYear(pRuntime), jsDate.GetMonth(pRuntime) + 1, jsDate.GetDay(pRuntime), jsDate.GetHours(pRuntime), jsDate.GetMinutes(pRuntime), jsDate.GetSeconds(pRuntime)); break; default: sError = JSGetStringFromID(IDS_STRING_JSVALUEERROR); return false; } vRet = CJS_Value(pRuntime, swResult.c_str()); return true; } if (p1.GetType() == CJS_Value::VT_string) { if (iSize > 2 && params[2].ToBool(pRuntime)) { sError = JSGetStringFromID(IDS_STRING_JSNOTSUPPORT); return false; // currently, it doesn't support XFAPicture. } // Convert PDF-style format specifiers to wcsftime specifiers. Remove any // pre-existing %-directives before inserting our own. std::basic_string<wchar_t> cFormat = p1.ToCFXWideString(pRuntime).c_str(); cFormat.erase(std::remove(cFormat.begin(), cFormat.end(), '%'), cFormat.end()); for (size_t i = 0; i < FX_ArraySize(TbConvertTable); ++i) { int iStart = 0; int iEnd; while ((iEnd = cFormat.find(TbConvertTable[i].lpszJSMark, iStart)) != -1) { cFormat.replace(iEnd, FXSYS_wcslen(TbConvertTable[i].lpszJSMark), TbConvertTable[i].lpszCppMark); iStart = iEnd; } } int iYear = jsDate.GetYear(pRuntime); int iMonth = jsDate.GetMonth(pRuntime); int iDay = jsDate.GetDay(pRuntime); int iHour = jsDate.GetHours(pRuntime); int iMin = jsDate.GetMinutes(pRuntime); int iSec = jsDate.GetSeconds(pRuntime); TbConvertAdditional cTableAd[] = { {L"m", iMonth + 1}, {L"d", iDay}, {L"H", iHour}, {L"h", iHour > 12 ? iHour - 12 : iHour}, {L"M", iMin}, {L"s", iSec}, }; for (size_t i = 0; i < FX_ArraySize(cTableAd); ++i) { wchar_t tszValue[16]; CFX_WideString sValue; sValue.Format(L"%d", cTableAd[i].iValue); memcpy(tszValue, (wchar_t*)sValue.GetBuffer(sValue.GetLength() + 1), (sValue.GetLength() + 1) * sizeof(wchar_t)); int iStart = 0; int iEnd; while ((iEnd = cFormat.find(cTableAd[i].lpszJSMark, iStart)) != -1) { if (iEnd > 0) { if (cFormat[iEnd - 1] == L'%') { iStart = iEnd + 1; continue; } } cFormat.replace(iEnd, FXSYS_wcslen(cTableAd[i].lpszJSMark), tszValue); iStart = iEnd; } } struct tm time = {}; time.tm_year = iYear - 1900; time.tm_mon = iMonth; time.tm_mday = iDay; time.tm_hour = iHour; time.tm_min = iMin; time.tm_sec = iSec; wchar_t buf[64] = {}; wcsftime(buf, 64, cFormat.c_str(), &time); cFormat = buf; vRet = CJS_Value(pRuntime, cFormat.c_str()); return true; } sError = JSGetStringFromID(IDS_STRING_JSTYPEERROR); return false; } bool util::printx(CJS_Runtime* pRuntime, const std::vector<CJS_Value>& params, CJS_Value& vRet, CFX_WideString& sError) { if (params.size() < 2) { sError = JSGetStringFromID(IDS_STRING_JSPARAMERROR); return false; } vRet = CJS_Value(pRuntime, printx(params[0].ToCFXWideString(pRuntime), params[1].ToCFXWideString(pRuntime)) .c_str()); return true; } enum CaseMode { kPreserveCase, kUpperCase, kLowerCase }; static FX_WCHAR TranslateCase(FX_WCHAR input, CaseMode eMode) { if (eMode == kLowerCase && input >= 'A' && input <= 'Z') return input | 0x20; if (eMode == kUpperCase && input >= 'a' && input <= 'z') return input & ~0x20; return input; } CFX_WideString util::printx(const CFX_WideString& wsFormat, const CFX_WideString& wsSource) { CFX_WideString wsResult; FX_STRSIZE iSourceIdx = 0; FX_STRSIZE iFormatIdx = 0; CaseMode eCaseMode = kPreserveCase; bool bEscaped = false; while (iFormatIdx < wsFormat.GetLength()) { if (bEscaped) { bEscaped = false; wsResult += wsFormat[iFormatIdx]; ++iFormatIdx; continue; } switch (wsFormat[iFormatIdx]) { case '\\': { bEscaped = true; ++iFormatIdx; } break; case '<': { eCaseMode = kLowerCase; ++iFormatIdx; } break; case '>': { eCaseMode = kUpperCase; ++iFormatIdx; } break; case '=': { eCaseMode = kPreserveCase; ++iFormatIdx; } break; case '?': { if (iSourceIdx < wsSource.GetLength()) { wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode); ++iSourceIdx; } ++iFormatIdx; } break; case 'X': { if (iSourceIdx < wsSource.GetLength()) { if ((wsSource[iSourceIdx] >= '0' && wsSource[iSourceIdx] <= '9') || (wsSource[iSourceIdx] >= 'a' && wsSource[iSourceIdx] <= 'z') || (wsSource[iSourceIdx] >= 'A' && wsSource[iSourceIdx] <= 'Z')) { wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode); ++iFormatIdx; } ++iSourceIdx; } else { ++iFormatIdx; } } break; case 'A': { if (iSourceIdx < wsSource.GetLength()) { if ((wsSource[iSourceIdx] >= 'a' && wsSource[iSourceIdx] <= 'z') || (wsSource[iSourceIdx] >= 'A' && wsSource[iSourceIdx] <= 'Z')) { wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode); ++iFormatIdx; } ++iSourceIdx; } else { ++iFormatIdx; } } break; case '9': { if (iSourceIdx < wsSource.GetLength()) { if (wsSource[iSourceIdx] >= '0' && wsSource[iSourceIdx] <= '9') { wsResult += wsSource[iSourceIdx]; ++iFormatIdx; } ++iSourceIdx; } else { ++iFormatIdx; } } break; case '*': { if (iSourceIdx < wsSource.GetLength()) { wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode); ++iSourceIdx; } else { ++iFormatIdx; } } break; default: { wsResult += wsFormat[iFormatIdx]; ++iFormatIdx; } break; } } return wsResult; } bool util::scand(CJS_Runtime* pRuntime, const std::vector<CJS_Value>& params, CJS_Value& vRet, CFX_WideString& sError) { int iSize = params.size(); if (iSize < 2) return false; CFX_WideString sFormat = params[0].ToCFXWideString(pRuntime); CFX_WideString sDate = params[1].ToCFXWideString(pRuntime); double dDate = JS_GetDateTime(); if (sDate.GetLength() > 0) { dDate = CJS_PublicMethods::MakeRegularDate(sDate, sFormat, nullptr); } if (!JS_PortIsNan(dDate)) { vRet = CJS_Value(pRuntime, CJS_Date(pRuntime, dDate)); } else { vRet.SetNull(pRuntime); } return true; } bool util::byteToChar(CJS_Runtime* pRuntime, const std::vector<CJS_Value>& params, CJS_Value& vRet, CFX_WideString& sError) { if (params.size() < 1) { sError = JSGetStringFromID(IDS_STRING_JSPARAMERROR); return false; } int arg = params[0].ToInt(pRuntime); if (arg < 0 || arg > 255) { sError = JSGetStringFromID(IDS_STRING_JSVALUEERROR); return false; } CFX_WideString wStr(static_cast<FX_WCHAR>(arg)); vRet = CJS_Value(pRuntime, wStr.c_str()); return true; }