diff options
Diffstat (limited to 'fpdfsdk/javascript/cjs_util.cpp')
-rw-r--r-- | fpdfsdk/javascript/cjs_util.cpp | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/fpdfsdk/javascript/cjs_util.cpp b/fpdfsdk/javascript/cjs_util.cpp new file mode 100644 index 0000000000..6eb52f13fe --- /dev/null +++ b/fpdfsdk/javascript/cjs_util.cpp @@ -0,0 +1,468 @@ +// 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/cjs_util.h" + +#include <time.h> + +#include <algorithm> +#include <cmath> +#include <cwctype> +#include <string> +#include <vector> + +#include "core/fxcrt/fx_extension.h" +#include "fpdfsdk/javascript/JS_Define.h" +#include "fpdfsdk/javascript/cjs_event_context.h" +#include "fpdfsdk/javascript/cjs_eventhandler.h" +#include "fpdfsdk/javascript/cjs_object.h" +#include "fpdfsdk/javascript/cjs_publicmethods.h" +#include "fpdfsdk/javascript/cjs_runtime.h" +#include "fpdfsdk/javascript/js_resources.h" + +#if _FX_OS_ == _FX_OS_ANDROID_ +#include <ctype.h> +#endif + +namespace { + +// Map PDF-style directives to equivalent wcsftime directives. Not +// all have direct equivalents, though. +struct TbConvert { + const wchar_t* lpszJSMark; + const wchar_t* lpszCppMark; +}; + +// Map PDF-style directives lacking direct wcsftime directives to +// the value with which they will be replaced. +struct TbConvertAdditional { + const wchar_t* 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 +}; + +} // namespace + +const JSMethodSpec CJS_Util::MethodSpecs[] = { + {"printd", printd_static}, {"printf", printf_static}, + {"printx", printx_static}, {"scand", scand_static}, + {"byteToChar", byteToChar_static}, {0, 0}}; + +int CJS_Util::ObjDefnID = -1; + +// static +void CJS_Util::DefineJSObjects(CFXJS_Engine* pEngine) { + ObjDefnID = + pEngine->DefineObj("util", FXJSOBJTYPE_STATIC, + JSConstructor<CJS_Util, util>, JSDestructor<CJS_Util>); + DefineMethods(pEngine, ObjDefnID, MethodSpecs); +} + +util::util(CJS_Object* pJSObject) : CJS_EmbedObj(pJSObject) {} + +util::~util() {} + +CJS_Return util::printf(CJS_Runtime* pRuntime, + const std::vector<v8::Local<v8::Value>>& params) { + const size_t iSize = params.size(); + if (iSize < 1) + return CJS_Return(false); + + std::wstring unsafe_fmt_string(pRuntime->ToWideString(params[0]).c_str()); + std::vector<std::wstring> unsafe_conversion_specifiers; + int iOffset = 0; + int iOffend = 0; + unsafe_fmt_string.insert(unsafe_fmt_string.begin(), L'S'); + while (iOffset != -1) { + iOffend = unsafe_fmt_string.find(L"%", iOffset + 1); + std::wstring strSub; + if (iOffend == -1) + strSub = unsafe_fmt_string.substr(iOffset); + else + strSub = unsafe_fmt_string.substr(iOffset, iOffend - iOffset); + unsafe_conversion_specifiers.push_back(strSub); + iOffset = iOffend; + } + + std::wstring c_strResult; + for (size_t iIndex = 0; iIndex < unsafe_conversion_specifiers.size(); + ++iIndex) { + std::wstring c_strFormat = unsafe_conversion_specifiers[iIndex]; + if (iIndex == 0) { + c_strResult = c_strFormat; + continue; + } + + if (iIndex >= iSize) { + c_strResult += c_strFormat; + continue; + } + + WideString strSegment; + switch (ParseDataType(&c_strFormat)) { + case UTIL_INT: + strSegment.Format(c_strFormat.c_str(), + pRuntime->ToInt32(params[iIndex])); + break; + case UTIL_DOUBLE: + strSegment.Format(c_strFormat.c_str(), + pRuntime->ToDouble(params[iIndex])); + break; + case UTIL_STRING: + strSegment.Format(c_strFormat.c_str(), + pRuntime->ToWideString(params[iIndex]).c_str()); + break; + default: + strSegment.Format(L"%ls", c_strFormat.c_str()); + break; + } + c_strResult += strSegment.c_str(); + } + + c_strResult.erase(c_strResult.begin()); + return CJS_Return(pRuntime->NewString(c_strResult.c_str())); +} + +CJS_Return util::printd(CJS_Runtime* pRuntime, + const std::vector<v8::Local<v8::Value>>& params) { + const size_t iSize = params.size(); + if (iSize < 2) + return CJS_Return(false); + + if (params[1].IsEmpty() || !params[1]->IsDate()) + return CJS_Return(JSGetStringFromID(IDS_STRING_JSPRINT1)); + + v8::Local<v8::Date> v8_date = params[1].As<v8::Date>(); + if (v8_date.IsEmpty() || std::isnan(pRuntime->ToDouble(v8_date))) + return CJS_Return(JSGetStringFromID(IDS_STRING_JSPRINT2)); + + double date = JS_LocalTime(pRuntime->ToDouble(v8_date)); + int year = JS_GetYearFromTime(date); + int month = JS_GetMonthFromTime(date) + 1; // One-based. + int day = JS_GetDayFromTime(date); + int hour = JS_GetHourFromTime(date); + int min = JS_GetMinFromTime(date); + int sec = JS_GetSecFromTime(date); + + if (params[0]->IsNumber()) { + WideString swResult; + switch (pRuntime->ToInt32(params[0])) { + case 0: + swResult.Format(L"D:%04d%02d%02d%02d%02d%02d", year, month, day, hour, + min, sec); + break; + case 1: + swResult.Format(L"%04d.%02d.%02d %02d:%02d:%02d", year, month, day, + hour, min, sec); + break; + case 2: + swResult.Format(L"%04d/%02d/%02d %02d:%02d:%02d", year, month, day, + hour, min, sec); + break; + default: + return CJS_Return(JSGetStringFromID(IDS_STRING_JSVALUEERROR)); + } + + return CJS_Return(pRuntime->NewString(swResult.c_str())); + } + + if (params[0]->IsString()) { + // We don't support XFAPicture at the moment. + if (iSize > 2 && pRuntime->ToBoolean(params[2])) + return CJS_Return(JSGetStringFromID(IDS_STRING_JSNOTSUPPORT)); + + // Convert PDF-style format specifiers to wcsftime specifiers. Remove any + // pre-existing %-directives before inserting our own. + std::basic_string<wchar_t> cFormat = + pRuntime->ToWideString(params[0]).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, wcslen(TbConvertTable[i].lpszJSMark), + TbConvertTable[i].lpszCppMark); + iStart = iEnd; + } + } + + if (year < 0) + return CJS_Return(JSGetStringFromID(IDS_STRING_JSVALUEERROR)); + + static const TbConvertAdditional cTableAd[] = { + {L"m", month}, {L"d", day}, + {L"H", hour}, {L"h", hour > 12 ? hour - 12 : hour}, + {L"M", min}, {L"s", sec}, + }; + + for (size_t i = 0; i < FX_ArraySize(cTableAd); ++i) { + WideString sValue; + sValue.Format(L"%d", cTableAd[i].iValue); + + 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, wcslen(cTableAd[i].lpszJSMark), sValue.c_str()); + iStart = iEnd; + } + } + + struct tm time = {}; + time.tm_year = year - 1900; + time.tm_mon = month - 1; + time.tm_mday = day; + time.tm_hour = hour; + time.tm_min = min; + time.tm_sec = sec; + + wchar_t buf[64] = {}; + FXSYS_wcsftime(buf, 64, cFormat.c_str(), &time); + cFormat = buf; + return CJS_Return(pRuntime->NewString(cFormat.c_str())); + } + + return CJS_Return(JSGetStringFromID(IDS_STRING_JSTYPEERROR)); +} + +CJS_Return util::printx(CJS_Runtime* pRuntime, + const std::vector<v8::Local<v8::Value>>& params) { + if (params.size() < 2) + return CJS_Return(JSGetStringFromID(IDS_STRING_JSPARAMERROR)); + + return CJS_Return( + pRuntime->NewString(printx(pRuntime->ToWideString(params[0]), + pRuntime->ToWideString(params[1])) + .c_str())); +} + +enum CaseMode { kPreserveCase, kUpperCase, kLowerCase }; + +static wchar_t TranslateCase(wchar_t input, CaseMode eMode) { + if (eMode == kLowerCase && FXSYS_isupper(input)) + return input | 0x20; + if (eMode == kUpperCase && FXSYS_islower(input)) + return input & ~0x20; + return input; +} + +WideString util::printx(const WideString& wsFormat, + const WideString& wsSource) { + WideString wsResult; + size_t iSourceIdx = 0; + size_t 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 (FXSYS_iswalnum(wsSource[iSourceIdx])) { + wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode); + ++iFormatIdx; + } + ++iSourceIdx; + } else { + ++iFormatIdx; + } + } break; + case 'A': { + if (iSourceIdx < wsSource.GetLength()) { + if (FXSYS_iswalpha(wsSource[iSourceIdx])) { + wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode); + ++iFormatIdx; + } + ++iSourceIdx; + } else { + ++iFormatIdx; + } + } break; + case '9': { + if (iSourceIdx < wsSource.GetLength()) { + if (std::iswdigit(wsSource[iSourceIdx])) { + 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; +} + +CJS_Return util::scand(CJS_Runtime* pRuntime, + const std::vector<v8::Local<v8::Value>>& params) { + if (params.size() < 2) + return CJS_Return(false); + + WideString sFormat = pRuntime->ToWideString(params[0]); + WideString sDate = pRuntime->ToWideString(params[1]); + double dDate = JS_GetDateTime(); + if (sDate.GetLength() > 0) + dDate = CJS_PublicMethods::MakeRegularDate(sDate, sFormat, nullptr); + + if (std::isnan(dDate)) + return CJS_Return(pRuntime->NewUndefined()); + return CJS_Return(pRuntime->NewDate(dDate)); +} + +CJS_Return util::byteToChar(CJS_Runtime* pRuntime, + const std::vector<v8::Local<v8::Value>>& params) { + if (params.size() < 1) + return CJS_Return(JSGetStringFromID(IDS_STRING_JSPARAMERROR)); + + int arg = pRuntime->ToInt32(params[0]); + if (arg < 0 || arg > 255) + return CJS_Return(JSGetStringFromID(IDS_STRING_JSVALUEERROR)); + + WideString wStr(static_cast<wchar_t>(arg)); + return CJS_Return(pRuntime->NewString(wStr.c_str())); +} + +// Ensure that sFormat contains at most one well-understood printf formatting +// directive which is safe to use with a single argument, and return the type +// of argument expected, or -1 otherwise. If -1 is returned, it is NOT safe +// to use sFormat with printf() and it must be copied byte-by-byte. +int util::ParseDataType(std::wstring* sFormat) { + enum State { BEFORE, FLAGS, WIDTH, PRECISION, SPECIFIER, AFTER }; + + int result = -1; + State state = BEFORE; + size_t precision_digits = 0; + size_t i = 0; + while (i < sFormat->length()) { + wchar_t c = (*sFormat)[i]; + switch (state) { + case BEFORE: + if (c == L'%') + state = FLAGS; + break; + case FLAGS: + if (c == L'+' || c == L'-' || c == L'#' || c == L' ') { + // Stay in same state. + } else { + state = WIDTH; + continue; // Re-process same character. + } + break; + case WIDTH: + if (c == L'*') + return -1; + if (std::iswdigit(c)) { + // Stay in same state. + } else if (c == L'.') { + state = PRECISION; + } else { + state = SPECIFIER; + continue; // Re-process same character. + } + break; + case PRECISION: + if (c == L'*') + return -1; + if (std::iswdigit(c)) { + // Stay in same state. + ++precision_digits; + } else { + state = SPECIFIER; + continue; // Re-process same character. + } + break; + case SPECIFIER: + 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') { + result = UTIL_INT; + } else if (c == L'e' || c == L'E' || c == L'f' || c == L'g' || + c == L'G') { + result = UTIL_DOUBLE; + } else if (c == L's' || c == L'S') { + // Map s to S since we always deal internally with wchar_t strings. + // TODO(tsepez): Probably 100% borked. %S is not a standard + // conversion. + (*sFormat)[i] = L'S'; + result = UTIL_STRING; + } else { + return -1; + } + state = AFTER; + break; + case AFTER: + if (c == L'%') + return -1; + // Stay in same state until string exhausted. + break; + } + ++i; + } + // See https://crbug.com/740166 + if (result == UTIL_INT && precision_digits > 2) + return -1; + + return result; +} |