1 // Copyright 2014 The PDFium Authors
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 <math.h>
10 #include <time.h>
11
12 #include <algorithm>
13 #include <string>
14 #include <vector>
15
16 #include "build/build_config.h"
17 #include "core/fxcrt/fx_extension.h"
18 #include "fxjs/cjs_event_context.h"
19 #include "fxjs/cjs_object.h"
20 #include "fxjs/cjs_publicmethods.h"
21 #include "fxjs/cjs_runtime.h"
22 #include "fxjs/fx_date_helpers.h"
23 #include "fxjs/fxv8.h"
24 #include "fxjs/js_define.h"
25 #include "fxjs/js_resources.h"
26 #include "third_party/base/check_op.h"
27 #include "v8/include/v8-date.h"
28
29 #if BUILDFLAG(IS_ANDROID)
30 #include <ctype.h>
31 #endif
32
33 namespace {
34
35 // Map PDF-style directives to equivalent wcsftime directives. Not
36 // all have direct equivalents, though.
37 struct TbConvert {
38 const wchar_t* lpszJSMark;
39 const wchar_t* lpszCppMark;
40 };
41
42 // Map PDF-style directives lacking direct wcsftime directives to
43 // the value with which they will be replaced.
44 struct TbConvertAdditional {
45 wchar_t js_mark;
46 int value;
47 };
48
49 const TbConvert TbConvertTable[] = {
50 {L"mmmm", L"%B"}, {L"mmm", L"%b"}, {L"mm", L"%m"}, {L"dddd", L"%A"},
51 {L"ddd", L"%a"}, {L"dd", L"%d"}, {L"yyyy", L"%Y"}, {L"yy", L"%y"},
52 {L"HH", L"%H"}, {L"hh", L"%I"}, {L"MM", L"%M"}, {L"ss", L"%S"},
53 {L"TT", L"%p"},
54 #if BUILDFLAG(IS_WIN)
55 {L"tt", L"%p"}, {L"h", L"%#I"},
56 #else
57 {L"tt", L"%P"}, {L"h", L"%l"},
58 #endif
59 };
60
61 enum CaseMode { kPreserveCase, kUpperCase, kLowerCase };
62
TranslateCase(wchar_t input,CaseMode eMode)63 wchar_t TranslateCase(wchar_t input, CaseMode eMode) {
64 if (eMode == kLowerCase && FXSYS_iswupper(input))
65 return input | 0x20;
66 if (eMode == kUpperCase && FXSYS_iswlower(input))
67 return input & ~0x20;
68 return input;
69 }
70
71 } // namespace
72
73 const JSMethodSpec CJS_Util::MethodSpecs[] = {
74 {"printd", printd_static},
75 {"printf", printf_static},
76 {"printx", printx_static},
77 {"scand", scand_static},
78 {"byteToChar", byteToChar_static}};
79
80 uint32_t CJS_Util::ObjDefnID = 0;
81 const char CJS_Util::kName[] = "util";
82
83 // static
GetObjDefnID()84 uint32_t CJS_Util::GetObjDefnID() {
85 return ObjDefnID;
86 }
87
88 // static
DefineJSObjects(CFXJS_Engine * pEngine)89 void CJS_Util::DefineJSObjects(CFXJS_Engine* pEngine) {
90 ObjDefnID = pEngine->DefineObj(CJS_Util::kName, FXJSOBJTYPE_STATIC,
91 JSConstructor<CJS_Util>, JSDestructor);
92 DefineMethods(pEngine, ObjDefnID, MethodSpecs);
93 }
94
CJS_Util(v8::Local<v8::Object> pObject,CJS_Runtime * pRuntime)95 CJS_Util::CJS_Util(v8::Local<v8::Object> pObject, CJS_Runtime* pRuntime)
96 : CJS_Object(pObject, pRuntime) {}
97
98 CJS_Util::~CJS_Util() = default;
99
printf(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)100 CJS_Result CJS_Util::printf(CJS_Runtime* pRuntime,
101 const std::vector<v8::Local<v8::Value>>& params) {
102 const size_t num_params = params.size();
103 if (num_params < 1)
104 return CJS_Result::Failure(JSMessage::kParamError);
105
106 // Use 'S' as a sentinel to ensure we always have some text before the first
107 // format specifier.
108 WideString unsafe_fmt_string = L'S' + pRuntime->ToWideString(params[0]);
109 std::vector<WideString> unsafe_conversion_specifiers;
110
111 {
112 size_t offset = 0;
113 while (true) {
114 absl::optional<size_t> offset_end =
115 unsafe_fmt_string.Find(L"%", offset + 1);
116 if (!offset_end.has_value()) {
117 unsafe_conversion_specifiers.push_back(
118 unsafe_fmt_string.Last(unsafe_fmt_string.GetLength() - offset));
119 break;
120 }
121
122 unsafe_conversion_specifiers.push_back(
123 unsafe_fmt_string.Substr(offset, offset_end.value() - offset));
124 offset = offset_end.value();
125 }
126 }
127
128 WideString result = unsafe_conversion_specifiers[0];
129 for (size_t i = 1; i < unsafe_conversion_specifiers.size(); ++i) {
130 WideString fmt = unsafe_conversion_specifiers[i];
131 if (i >= num_params) {
132 result += fmt;
133 continue;
134 }
135
136 WideString segment;
137 switch (ParseDataType(&fmt)) {
138 case DataType::kInt:
139 segment = WideString::Format(fmt.c_str(), pRuntime->ToInt32(params[i]));
140 break;
141 case DataType::kDouble:
142 segment =
143 WideString::Format(fmt.c_str(), pRuntime->ToDouble(params[i]));
144 break;
145 case DataType::kString:
146 segment = WideString::Format(fmt.c_str(),
147 pRuntime->ToWideString(params[i]).c_str());
148 break;
149 default:
150 segment = WideString::Format(L"%ls", fmt.c_str());
151 break;
152 }
153 result += segment;
154 }
155
156 // Remove the 'S' sentinel introduced earlier.
157 DCHECK_EQ(L'S', result[0]);
158 auto result_view = result.AsStringView();
159 return CJS_Result::Success(
160 pRuntime->NewString(result_view.Last(result_view.GetLength() - 1)));
161 }
162
printd(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)163 CJS_Result CJS_Util::printd(CJS_Runtime* pRuntime,
164 const std::vector<v8::Local<v8::Value>>& params) {
165 const size_t iSize = params.size();
166 if (iSize < 2)
167 return CJS_Result::Failure(JSMessage::kParamError);
168
169 if (!fxv8::IsDate(params[1]))
170 return CJS_Result::Failure(JSMessage::kSecondParamNotDateError);
171
172 v8::Local<v8::Date> v8_date = params[1].As<v8::Date>();
173 if (v8_date.IsEmpty() || isnan(pRuntime->ToDouble(v8_date)))
174 return CJS_Result::Failure(JSMessage::kSecondParamInvalidDateError);
175
176 double date = FX_LocalTime(pRuntime->ToDouble(v8_date));
177 int year = FX_GetYearFromTime(date);
178 int month = FX_GetMonthFromTime(date) + 1; // One-based.
179 int day = FX_GetDayFromTime(date);
180 int hour = FX_GetHourFromTime(date);
181 int min = FX_GetMinFromTime(date);
182 int sec = FX_GetSecFromTime(date);
183
184 if (params[0]->IsNumber()) {
185 WideString swResult;
186 switch (pRuntime->ToInt32(params[0])) {
187 case 0:
188 swResult = WideString::Format(L"D:%04d%02d%02d%02d%02d%02d", year,
189 month, day, hour, min, sec);
190 break;
191 case 1:
192 swResult = WideString::Format(L"%04d.%02d.%02d %02d:%02d:%02d", year,
193 month, day, hour, min, sec);
194 break;
195 case 2:
196 swResult = WideString::Format(L"%04d/%02d/%02d %02d:%02d:%02d", year,
197 month, day, hour, min, sec);
198 break;
199 default:
200 return CJS_Result::Failure(JSMessage::kValueError);
201 }
202
203 return CJS_Result::Success(pRuntime->NewString(swResult.AsStringView()));
204 }
205
206 if (!params[0]->IsString())
207 return CJS_Result::Failure(JSMessage::kTypeError);
208
209 // We don't support XFAPicture at the moment.
210 if (iSize > 2 && pRuntime->ToBoolean(params[2]))
211 return CJS_Result::Failure(JSMessage::kNotSupportedError);
212
213 // Convert PDF-style format specifiers to wcsftime specifiers. Remove any
214 // pre-existing %-directives before inserting our own.
215 std::wstring cFormat = pRuntime->ToWideString(params[0]).c_str();
216 cFormat.erase(std::remove(cFormat.begin(), cFormat.end(), '%'),
217 cFormat.end());
218
219 for (size_t i = 0; i < std::size(TbConvertTable); ++i) {
220 size_t nFound = 0;
221 while (true) {
222 nFound = cFormat.find(TbConvertTable[i].lpszJSMark, nFound);
223 if (nFound == std::wstring::npos)
224 break;
225
226 cFormat.replace(nFound, wcslen(TbConvertTable[i].lpszJSMark),
227 TbConvertTable[i].lpszCppMark);
228 }
229 }
230
231 if (year < 0)
232 return CJS_Result::Failure(JSMessage::kValueError);
233
234 const TbConvertAdditional cTableAd[] = {
235 {L'm', month}, {L'd', day},
236 {L'H', hour}, {L'h', hour > 12 ? hour - 12 : hour},
237 {L'M', min}, {L's', sec},
238 };
239
240 for (size_t i = 0; i < std::size(cTableAd); ++i) {
241 size_t nFound = 0;
242 while (true) {
243 nFound = cFormat.find(cTableAd[i].js_mark, nFound);
244 if (nFound == std::wstring::npos)
245 break;
246
247 if (nFound != 0 && cFormat[nFound - 1] == L'%') {
248 ++nFound;
249 continue;
250 }
251 cFormat.replace(nFound, 1,
252 WideString::FormatInteger(cTableAd[i].value).c_str());
253 }
254 }
255
256 struct tm time = {};
257 time.tm_year = year - 1900;
258 time.tm_mon = month - 1;
259 time.tm_mday = day;
260 time.tm_hour = hour;
261 time.tm_min = min;
262 time.tm_sec = sec;
263
264 wchar_t buf[64] = {};
265 FXSYS_wcsftime(buf, 64, cFormat.c_str(), &time);
266 cFormat = buf;
267 return CJS_Result::Success(pRuntime->NewString(cFormat.c_str()));
268 }
269
printx(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)270 CJS_Result CJS_Util::printx(CJS_Runtime* pRuntime,
271 const std::vector<v8::Local<v8::Value>>& params) {
272 if (params.size() < 2)
273 return CJS_Result::Failure(JSMessage::kParamError);
274
275 return CJS_Result::Success(
276 pRuntime->NewString(StringPrintx(pRuntime->ToWideString(params[0]),
277 pRuntime->ToWideString(params[1]))
278 .AsStringView()));
279 }
280
281 // static
StringPrintx(const WideString & wsFormat,const WideString & wsSource)282 WideString CJS_Util::StringPrintx(const WideString& wsFormat,
283 const WideString& wsSource) {
284 WideString wsResult;
285 wsResult.Reserve(wsFormat.GetLength());
286 size_t iSourceIdx = 0;
287 size_t iFormatIdx = 0;
288 CaseMode eCaseMode = kPreserveCase;
289 bool bEscaped = false;
290 while (iFormatIdx < wsFormat.GetLength()) {
291 if (bEscaped) {
292 bEscaped = false;
293 wsResult += wsFormat[iFormatIdx];
294 ++iFormatIdx;
295 continue;
296 }
297 switch (wsFormat[iFormatIdx]) {
298 case '\\': {
299 bEscaped = true;
300 ++iFormatIdx;
301 } break;
302 case '<': {
303 eCaseMode = kLowerCase;
304 ++iFormatIdx;
305 } break;
306 case '>': {
307 eCaseMode = kUpperCase;
308 ++iFormatIdx;
309 } break;
310 case '=': {
311 eCaseMode = kPreserveCase;
312 ++iFormatIdx;
313 } break;
314 case '?': {
315 if (iSourceIdx < wsSource.GetLength()) {
316 wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
317 ++iSourceIdx;
318 }
319 ++iFormatIdx;
320 } break;
321 case 'X': {
322 if (iSourceIdx < wsSource.GetLength()) {
323 if (isascii(wsSource[iSourceIdx]) && isalnum(wsSource[iSourceIdx])) {
324 wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
325 ++iFormatIdx;
326 }
327 ++iSourceIdx;
328 } else {
329 ++iFormatIdx;
330 }
331 } break;
332 case 'A': {
333 if (iSourceIdx < wsSource.GetLength()) {
334 if (isascii(wsSource[iSourceIdx]) && isalpha(wsSource[iSourceIdx])) {
335 wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
336 ++iFormatIdx;
337 }
338 ++iSourceIdx;
339 } else {
340 ++iFormatIdx;
341 }
342 } break;
343 case '9': {
344 if (iSourceIdx < wsSource.GetLength()) {
345 if (FXSYS_IsDecimalDigit(wsSource[iSourceIdx])) {
346 wsResult += wsSource[iSourceIdx];
347 ++iFormatIdx;
348 }
349 ++iSourceIdx;
350 } else {
351 ++iFormatIdx;
352 }
353 } break;
354 case '*': {
355 if (iSourceIdx < wsSource.GetLength()) {
356 wsResult += TranslateCase(wsSource[iSourceIdx], eCaseMode);
357 ++iSourceIdx;
358 } else {
359 ++iFormatIdx;
360 }
361 } break;
362 default: {
363 wsResult += wsFormat[iFormatIdx];
364 ++iFormatIdx;
365 } break;
366 }
367 }
368 return wsResult;
369 }
370
scand(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)371 CJS_Result CJS_Util::scand(CJS_Runtime* pRuntime,
372 const std::vector<v8::Local<v8::Value>>& params) {
373 if (params.size() < 2)
374 return CJS_Result::Failure(JSMessage::kParamError);
375
376 WideString sFormat = pRuntime->ToWideString(params[0]);
377 WideString sDate = pRuntime->ToWideString(params[1]);
378 double dDate = FX_GetDateTime();
379 if (sDate.GetLength() > 0)
380 dDate = CJS_PublicMethods::ParseDateUsingFormat(pRuntime->GetIsolate(),
381 sDate, sFormat, nullptr);
382 if (isnan(dDate))
383 return CJS_Result::Success(pRuntime->NewUndefined());
384
385 return CJS_Result::Success(pRuntime->NewDate(dDate));
386 }
387
byteToChar(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)388 CJS_Result CJS_Util::byteToChar(
389 CJS_Runtime* pRuntime,
390 const std::vector<v8::Local<v8::Value>>& params) {
391 if (params.size() < 1)
392 return CJS_Result::Failure(JSMessage::kParamError);
393
394 int arg = pRuntime->ToInt32(params[0]);
395 if (arg < 0 || arg > 255)
396 return CJS_Result::Failure(JSMessage::kValueError);
397
398 WideString wStr(static_cast<wchar_t>(arg));
399 return CJS_Result::Success(pRuntime->NewString(wStr.AsStringView()));
400 }
401
402 // static
ParseDataType(WideString * sFormat)403 CJS_Util::DataType CJS_Util::ParseDataType(WideString* sFormat) {
404 enum State { kBefore, kFlags, kWidth, kPrecision, kSpecifier, kAfter };
405
406 DataType result = DataType::kInvalid;
407 State state = kBefore;
408 size_t precision_digits = 0;
409 size_t i = 0;
410 while (i < sFormat->GetLength()) {
411 wchar_t c = (*sFormat)[i];
412 switch (state) {
413 case kBefore:
414 if (c == L'%')
415 state = kFlags;
416 break;
417 case kFlags:
418 if (c == L'+' || c == L'-' || c == L'#' || c == L' ') {
419 // Stay in same state.
420 } else {
421 state = kWidth;
422 continue; // Re-process same character.
423 }
424 break;
425 case kWidth:
426 if (c == L'*')
427 return DataType::kInvalid;
428 if (FXSYS_IsDecimalDigit(c)) {
429 // Stay in same state.
430 } else if (c == L'.') {
431 state = kPrecision;
432 } else {
433 state = kSpecifier;
434 continue; // Re-process same character.
435 }
436 break;
437 case kPrecision:
438 if (c == L'*')
439 return DataType::kInvalid;
440 if (FXSYS_IsDecimalDigit(c)) {
441 // Stay in same state.
442 ++precision_digits;
443 } else {
444 state = kSpecifier;
445 continue; // Re-process same character.
446 }
447 break;
448 case kSpecifier:
449 if (c == L'c' || c == L'C' || c == L'd' || c == L'i' || c == L'o' ||
450 c == L'u' || c == L'x' || c == L'X') {
451 result = DataType::kInt;
452 } else if (c == L'e' || c == L'E' || c == L'f' || c == L'g' ||
453 c == L'G') {
454 result = DataType::kDouble;
455 } else if (c == L's' || c == L'S') {
456 // Map s to S since we always deal internally with wchar_t strings.
457 // TODO(tsepez): Probably 100% borked. %S is not a standard
458 // conversion.
459 sFormat->SetAt(i, L'S');
460 result = DataType::kString;
461 } else {
462 return DataType::kInvalid;
463 }
464 state = kAfter;
465 break;
466 case kAfter:
467 if (c == L'%')
468 return DataType::kInvalid;
469 // Stay in same state until string exhausted.
470 break;
471 }
472 ++i;
473 }
474 // See https://crbug.com/740166
475 if (result == DataType::kInt && precision_digits > 2)
476 return DataType::kInvalid;
477
478 return result;
479 }
480