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
DefineJSObjects(CFXJS_Engine * pEngine)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
util(CJS_Object * pJSObject)77 util::util(CJS_Object* pJSObject) : CJS_EmbedObj(pJSObject) {}
78
~util()79 util::~util() {}
80
printf(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)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
printd(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)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
printx(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)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
TranslateCase(wchar_t input,CaseMode eMode)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
printx(const WideString & wsFormat,const WideString & wsSource)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
scand(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)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
byteToChar(CJS_Runtime * pRuntime,const std::vector<v8::Local<v8::Value>> & params)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.
ParseDataType(std::wstring * sFormat)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