• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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