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