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