• 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/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