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