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
30namespace {
31
32// Map PDF-style directives to equivalent wcsftime directives. Not
33// all have direct equivalents, though.
34struct 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.
41struct TbConvertAdditional {
42  const wchar_t* lpszJSMark;
43  int iValue;
44};
45
46const 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
60const 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
67int CJS_Util::ObjDefnID = -1;
68
69// static
70void 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
77util::util(CJS_Object* pJSObject) : CJS_EmbedObj(pJSObject) {}
78
79util::~util() {}
80
81CJS_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
143CJS_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
253CJS_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
264enum CaseMode { kPreserveCase, kUpperCase, kLowerCase };
265
266static 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
274WideString 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
362CJS_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
378CJS_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.
395int 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