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