• 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/xfa/cfxjse_formcalc_context.h"
8 
9 #include <ctype.h>
10 #include <math.h>
11 #include <stdint.h>
12 #include <stdlib.h>
13 
14 #include <algorithm>
15 #include <array>
16 #include <limits>
17 #include <memory>
18 #include <optional>
19 #include <utility>
20 #include <vector>
21 
22 #include "core/fxcrt/cfx_datetime.h"
23 #include "core/fxcrt/check_op.h"
24 #include "core/fxcrt/code_point_view.h"
25 #include "core/fxcrt/compiler_specific.h"
26 #include "core/fxcrt/containers/contains.h"
27 #include "core/fxcrt/data_vector.h"
28 #include "core/fxcrt/fx_extension.h"
29 #include "core/fxcrt/fx_random.h"
30 #include "core/fxcrt/fx_safe_types.h"
31 #include "core/fxcrt/numerics/safe_conversions.h"
32 #include "core/fxcrt/span_util.h"
33 #include "core/fxcrt/widetext_buffer.h"
34 #include "fxjs/fxv8.h"
35 #include "fxjs/xfa/cfxjse_class.h"
36 #include "fxjs/xfa/cfxjse_context.h"
37 #include "fxjs/xfa/cfxjse_engine.h"
38 #include "fxjs/xfa/cfxjse_value.h"
39 #include "fxjs/xfa/cjx_object.h"
40 #include "v8/include/v8-container.h"
41 #include "v8/include/v8-function-callback.h"
42 #include "v8/include/v8-local-handle.h"
43 #include "v8/include/v8-object.h"
44 #include "v8/include/v8-primitive.h"
45 #include "xfa/fgas/crt/cfgas_decimal.h"
46 #include "xfa/fxfa/cxfa_ffnotify.h"
47 #include "xfa/fxfa/formcalc/cxfa_fmparser.h"
48 #include "xfa/fxfa/formcalc/cxfa_fmtojavascriptdepth.h"
49 #include "xfa/fxfa/parser/cxfa_document.h"
50 #include "xfa/fxfa/parser/cxfa_localevalue.h"
51 #include "xfa/fxfa/parser/cxfa_node.h"
52 #include "xfa/fxfa/parser/cxfa_thisproxy.h"
53 #include "xfa/fxfa/parser/cxfa_timezoneprovider.h"
54 #include "xfa/fxfa/parser/gced_locale_iface.h"
55 #include "xfa/fxfa/parser/xfa_utils.h"
56 
57 using pdfium::fxjse::kClassTag;
58 using pdfium::fxjse::kFuncTag;
59 
60 namespace {
61 
62 // Maximum number of characters Acrobat can fit in a text box.
63 constexpr int kMaxCharCount = 15654908;
64 
65 const double kFinancialPrecision = 0.00000001;
66 
67 constexpr std::array<wchar_t, 16> kStrCode = {
68     {L'0', L'1', L'2', L'3', L'4', L'5', L'6', L'7', L'8', L'9', L'a', L'b',
69      L'c', L'd', L'e', L'f'}};
70 
71 struct XFA_FMHtmlReserveCode {
72   uint16_t m_uCode;
73   // Inline string data reduces size for small strings.
74   const char m_htmlReserve[10];
75 };
76 
77 // Sorted by |m_htmlReserve|.
78 const XFA_FMHtmlReserveCode kReservesForDecode[] = {
79     {198, "AElig"},   {193, "Aacute"},   {194, "Acirc"},    {192, "Agrave"},
80     {913, "Alpha"},   {197, "Aring"},    {195, "Atilde"},   {196, "Auml"},
81     {914, "Beta"},    {199, "Ccedil"},   {935, "Chi"},      {8225, "Dagger"},
82     {916, "Delta"},   {208, "ETH"},      {201, "Eacute"},   {202, "Ecirc"},
83     {200, "Egrave"},  {917, "Epsilon"},  {919, "Eta"},      {203, "Euml"},
84     {915, "Gamma"},   {922, "Kappa"},    {923, "Lambda"},   {924, "Mu"},
85     {209, "Ntilde"},  {925, "Nu"},       {338, "OElig"},    {211, "Oacute"},
86     {212, "Ocirc"},   {210, "Ograve"},   {937, "Omega"},    {927, "Omicron"},
87     {216, "Oslash"},  {213, "Otilde"},   {214, "Ouml"},     {934, "Phi"},
88     {928, "Pi"},      {936, "Psi"},      {929, "Rho"},      {352, "Scaron"},
89     {931, "Sigma"},   {222, "THORN"},    {932, "Tau"},      {920, "Theta"},
90     {218, "Uacute"},  {219, "Ucirc"},    {217, "Ugrave"},   {933, "Upsilon"},
91     {220, "Uuml"},    {926, "Xi"},       {221, "Yacute"},   {376, "Yuml"},
92     {918, "Zeta"},    {225, "aacute"},   {226, "acirc"},    {180, "acute"},
93     {230, "aelig"},   {224, "agrave"},   {8501, "alefsym"}, {945, "alpha"},
94     {38, "amp"},      {8743, "and"},     {8736, "ang"},     {39, "apos"},
95     {229, "aring"},   {8776, "asymp"},   {227, "atilde"},   {228, "auml"},
96     {8222, "bdquo"},  {946, "beta"},     {166, "brvbar"},   {8226, "bull"},
97     {8745, "cap"},    {231, "ccedil"},   {184, "cedil"},    {162, "cent"},
98     {967, "chi"},     {710, "circ"},     {9827, "clubs"},   {8773, "cong"},
99     {169, "copy"},    {8629, "crarr"},   {8746, "cup"},     {164, "current"},
100     {8659, "dArr"},   {8224, "dagger"},  {8595, "darr"},    {176, "deg"},
101     {948, "delta"},   {9830, "diams"},   {247, "divide"},   {233, "eacute"},
102     {234, "ecirc"},   {232, "egrave"},   {8709, "empty"},   {8195, "emsp"},
103     {8194, "ensp"},   {949, "epsilon"},  {8801, "equiv"},   {951, "eta"},
104     {240, "eth"},     {235, "euml"},     {8364, "euro"},    {8707, "exist"},
105     {402, "fnof"},    {8704, "forall"},  {189, "frac12"},   {188, "frac14"},
106     {190, "frac34"},  {8260, "frasl"},   {947, "gamma"},    {8805, "ge"},
107     {62, "gt"},       {8660, "hArr"},    {8596, "harr"},    {9829, "hearts"},
108     {8230, "hellip"}, {237, "iacute"},   {238, "icirc"},    {161, "iexcl"},
109     {236, "igrave"},  {8465, "image"},   {8734, "infin"},   {8747, "int"},
110     {953, "iota"},    {191, "iquest"},   {8712, "isin"},    {239, "iuml"},
111     {954, "kappa"},   {8656, "lArr"},    {205, "lacute"},   {955, "lambda"},
112     {9001, "lang"},   {171, "laquo"},    {8592, "larr"},    {8968, "lceil"},
113     {206, "lcirc"},   {8220, "ldquo"},   {8804, "le"},      {8970, "lfloor"},
114     {204, "lgrave"},  {921, "lota"},     {8727, "lowast"},  {9674, "loz"},
115     {8206, "lrm"},    {8249, "lsaquo"},  {8216, "lsquo"},   {60, "lt"},
116     {207, "luml"},    {175, "macr"},     {8212, "mdash"},   {181, "micro"},
117     {183, "middot"},  {8722, "minus"},   {956, "mu"},       {8711, "nabla"},
118     {160, "nbsp"},    {8211, "ndash"},   {8800, "ne"},      {8715, "ni"},
119     {172, "not"},     {8713, "notin"},   {8836, "nsub"},    {241, "ntilde"},
120     {957, "nu"},      {243, "oacute"},   {244, "ocirc"},    {339, "oelig"},
121     {242, "ograve"},  {8254, "oline"},   {969, "omega"},    {959, "omicron"},
122     {8853, "oplus"},  {8744, "or"},      {170, "ordf"},     {186, "ordm"},
123     {248, "oslash"},  {245, "otilde"},   {8855, "otimes"},  {246, "ouml"},
124     {182, "para"},    {8706, "part"},    {8240, "permil"},  {8869, "perp"},
125     {966, "phi"},     {960, "pi"},       {982, "piv"},      {177, "plusmn"},
126     {8242, "prime"},  {8719, "prod"},    {8733, "prop"},    {968, "psi"},
127     {163, "pund"},    {34, "quot"},      {8658, "rArr"},    {8730, "radic"},
128     {9002, "rang"},   {187, "raquo"},    {8594, "rarr"},    {8969, "rceil"},
129     {8476, "real"},   {174, "reg"},      {8971, "rfloor"},  {961, "rho"},
130     {8207, "rlm"},    {8250, "rsaquo"},  {8217, "rsquo"},   {353, "saron"},
131     {8218, "sbquo"},  {8901, "sdot"},    {167, "sect"},     {173, "shy"},
132     {963, "sigma"},   {962, "sigmaf"},   {8764, "sim"},     {9824, "spades"},
133     {8834, "sub"},    {8838, "sube"},    {8721, "sum"},     {8835, "sup"},
134     {185, "sup1"},    {178, "sup2"},     {179, "sup3"},     {8839, "supe"},
135     {223, "szlig"},   {964, "tau"},      {8221, "tdquo"},   {8756, "there4"},
136     {952, "theta"},   {977, "thetasym"}, {8201, "thinsp"},  {254, "thorn"},
137     {732, "tilde"},   {215, "times"},    {8482, "trade"},   {8657, "uArr"},
138     {250, "uacute"},  {8593, "uarr"},    {251, "ucirc"},    {249, "ugrave"},
139     {168, "uml"},     {978, "upsih"},    {965, "upsilon"},  {252, "uuml"},
140     {8472, "weierp"}, {958, "xi"},       {253, "yacute"},   {165, "yen"},
141     {255, "yuml"},    {950, "zeta"},     {8205, "zwj"},     {8204, "zwnj"},
142 };
143 
144 // Sorted by |m_uCode|.
145 const XFA_FMHtmlReserveCode kReservesForEncode[] = {
146     {34, "quot"},     {38, "amp"},      {39, "apos"},      {60, "lt"},
147     {62, "gt"},       {160, "nbsp"},    {161, "iexcl"},    {162, "cent"},
148     {163, "pund"},    {164, "current"}, {165, "yen"},      {166, "brvbar"},
149     {167, "sect"},    {168, "uml"},     {169, "copy"},     {170, "ordf"},
150     {171, "laquo"},   {172, "not"},     {173, "shy"},      {174, "reg"},
151     {175, "macr"},    {176, "deg"},     {177, "plusmn"},   {178, "sup2"},
152     {179, "sup3"},    {180, "acute"},   {181, "micro"},    {182, "para"},
153     {183, "middot"},  {184, "cedil"},   {185, "sup1"},     {186, "ordm"},
154     {187, "raquo"},   {188, "frac14"},  {189, "frac12"},   {190, "frac34"},
155     {191, "iquest"},  {192, "Agrave"},  {193, "Aacute"},   {194, "Acirc"},
156     {195, "Atilde"},  {196, "Auml"},    {197, "Aring"},    {198, "AElig"},
157     {199, "Ccedil"},  {200, "Egrave"},  {201, "Eacute"},   {202, "Ecirc"},
158     {203, "Euml"},    {204, "lgrave"},  {205, "lacute"},   {206, "lcirc"},
159     {207, "luml"},    {208, "ETH"},     {209, "Ntilde"},   {210, "Ograve"},
160     {211, "Oacute"},  {212, "Ocirc"},   {213, "Otilde"},   {214, "Ouml"},
161     {215, "times"},   {216, "Oslash"},  {217, "Ugrave"},   {218, "Uacute"},
162     {219, "Ucirc"},   {220, "Uuml"},    {221, "Yacute"},   {222, "THORN"},
163     {223, "szlig"},   {224, "agrave"},  {225, "aacute"},   {226, "acirc"},
164     {227, "atilde"},  {228, "auml"},    {229, "aring"},    {230, "aelig"},
165     {231, "ccedil"},  {232, "egrave"},  {233, "eacute"},   {234, "ecirc"},
166     {235, "euml"},    {236, "igrave"},  {237, "iacute"},   {238, "icirc"},
167     {239, "iuml"},    {240, "eth"},     {241, "ntilde"},   {242, "ograve"},
168     {243, "oacute"},  {244, "ocirc"},   {245, "otilde"},   {246, "ouml"},
169     {247, "divide"},  {248, "oslash"},  {249, "ugrave"},   {250, "uacute"},
170     {251, "ucirc"},   {252, "uuml"},    {253, "yacute"},   {254, "thorn"},
171     {255, "yuml"},    {338, "OElig"},   {339, "oelig"},    {352, "Scaron"},
172     {353, "saron"},   {376, "Yuml"},    {402, "fnof"},     {710, "circ"},
173     {732, "tilde"},   {913, "Alpha"},   {914, "Beta"},     {915, "Gamma"},
174     {916, "Delta"},   {917, "Epsilon"}, {918, "Zeta"},     {919, "Eta"},
175     {920, "Theta"},   {921, "lota"},    {922, "Kappa"},    {923, "Lambda"},
176     {924, "Mu"},      {925, "Nu"},      {926, "Xi"},       {927, "Omicron"},
177     {928, "Pi"},      {929, "Rho"},     {931, "Sigma"},    {932, "Tau"},
178     {933, "Upsilon"}, {934, "Phi"},     {935, "Chi"},      {936, "Psi"},
179     {937, "Omega"},   {945, "alpha"},   {946, "beta"},     {947, "gamma"},
180     {948, "delta"},   {949, "epsilon"}, {950, "zeta"},     {951, "eta"},
181     {952, "theta"},   {953, "iota"},    {954, "kappa"},    {955, "lambda"},
182     {956, "mu"},      {957, "nu"},      {958, "xi"},       {959, "omicron"},
183     {960, "pi"},      {961, "rho"},     {962, "sigmaf"},   {963, "sigma"},
184     {964, "tau"},     {965, "upsilon"}, {966, "phi"},      {967, "chi"},
185     {968, "psi"},     {969, "omega"},   {977, "thetasym"}, {978, "upsih"},
186     {982, "piv"},     {8194, "ensp"},   {8195, "emsp"},    {8201, "thinsp"},
187     {8204, "zwnj"},   {8205, "zwj"},    {8206, "lrm"},     {8207, "rlm"},
188     {8211, "ndash"},  {8212, "mdash"},  {8216, "lsquo"},   {8217, "rsquo"},
189     {8218, "sbquo"},  {8220, "ldquo"},  {8221, "tdquo"},   {8222, "bdquo"},
190     {8224, "dagger"}, {8225, "Dagger"}, {8226, "bull"},    {8230, "hellip"},
191     {8240, "permil"}, {8242, "prime"},  {8249, "lsaquo"},  {8250, "rsaquo"},
192     {8254, "oline"},  {8260, "frasl"},  {8364, "euro"},    {8465, "image"},
193     {8472, "weierp"}, {8476, "real"},   {8482, "trade"},   {8501, "alefsym"},
194     {8592, "larr"},   {8593, "uarr"},   {8594, "rarr"},    {8595, "darr"},
195     {8596, "harr"},   {8629, "crarr"},  {8656, "lArr"},    {8657, "uArr"},
196     {8658, "rArr"},   {8659, "dArr"},   {8660, "hArr"},    {8704, "forall"},
197     {8706, "part"},   {8707, "exist"},  {8709, "empty"},   {8711, "nabla"},
198     {8712, "isin"},   {8713, "notin"},  {8715, "ni"},      {8719, "prod"},
199     {8721, "sum"},    {8722, "minus"},  {8727, "lowast"},  {8730, "radic"},
200     {8733, "prop"},   {8734, "infin"},  {8736, "ang"},     {8743, "and"},
201     {8744, "or"},     {8745, "cap"},    {8746, "cup"},     {8747, "int"},
202     {8756, "there4"}, {8764, "sim"},    {8773, "cong"},    {8776, "asymp"},
203     {8800, "ne"},     {8801, "equiv"},  {8804, "le"},      {8805, "ge"},
204     {8834, "sub"},    {8835, "sup"},    {8836, "nsub"},    {8838, "sube"},
205     {8839, "supe"},   {8853, "oplus"},  {8855, "otimes"},  {8869, "perp"},
206     {8901, "sdot"},   {8968, "lceil"},  {8969, "rceil"},   {8970, "lfloor"},
207     {8971, "rfloor"}, {9001, "lang"},   {9002, "rang"},    {9674, "loz"},
208     {9824, "spades"}, {9827, "clubs"},  {9829, "hearts"},  {9830, "diams"},
209 };
210 
211 const FXJSE_FUNCTION_DESCRIPTOR kFormCalcFunctions[] = {
212     {kFuncTag, "Abs", CFXJSE_FormCalcContext::Abs},
213     {kFuncTag, "Avg", CFXJSE_FormCalcContext::Avg},
214     {kFuncTag, "Ceil", CFXJSE_FormCalcContext::Ceil},
215     {kFuncTag, "Count", CFXJSE_FormCalcContext::Count},
216     {kFuncTag, "Floor", CFXJSE_FormCalcContext::Floor},
217     {kFuncTag, "Max", CFXJSE_FormCalcContext::Max},
218     {kFuncTag, "Min", CFXJSE_FormCalcContext::Min},
219     {kFuncTag, "Mod", CFXJSE_FormCalcContext::Mod},
220     {kFuncTag, "Round", CFXJSE_FormCalcContext::Round},
221     {kFuncTag, "Sum", CFXJSE_FormCalcContext::Sum},
222     {kFuncTag, "Date", CFXJSE_FormCalcContext::Date},
223     {kFuncTag, "Date2Num", CFXJSE_FormCalcContext::Date2Num},
224     {kFuncTag, "DateFmt", CFXJSE_FormCalcContext::DateFmt},
225     {kFuncTag, "IsoDate2Num", CFXJSE_FormCalcContext::IsoDate2Num},
226     {kFuncTag, "IsoTime2Num", CFXJSE_FormCalcContext::IsoTime2Num},
227     {kFuncTag, "LocalDateFmt", CFXJSE_FormCalcContext::LocalDateFmt},
228     {kFuncTag, "LocalTimeFmt", CFXJSE_FormCalcContext::LocalTimeFmt},
229     {kFuncTag, "Num2Date", CFXJSE_FormCalcContext::Num2Date},
230     {kFuncTag, "Num2GMTime", CFXJSE_FormCalcContext::Num2GMTime},
231     {kFuncTag, "Num2Time", CFXJSE_FormCalcContext::Num2Time},
232     {kFuncTag, "Time", CFXJSE_FormCalcContext::Time},
233     {kFuncTag, "Time2Num", CFXJSE_FormCalcContext::Time2Num},
234     {kFuncTag, "TimeFmt", CFXJSE_FormCalcContext::TimeFmt},
235     {kFuncTag, "Apr", CFXJSE_FormCalcContext::Apr},
236     {kFuncTag, "Cterm", CFXJSE_FormCalcContext::CTerm},
237     {kFuncTag, "FV", CFXJSE_FormCalcContext::FV},
238     {kFuncTag, "Ipmt", CFXJSE_FormCalcContext::IPmt},
239     {kFuncTag, "NPV", CFXJSE_FormCalcContext::NPV},
240     {kFuncTag, "Pmt", CFXJSE_FormCalcContext::Pmt},
241     {kFuncTag, "PPmt", CFXJSE_FormCalcContext::PPmt},
242     {kFuncTag, "PV", CFXJSE_FormCalcContext::PV},
243     {kFuncTag, "Rate", CFXJSE_FormCalcContext::Rate},
244     {kFuncTag, "Term", CFXJSE_FormCalcContext::Term},
245     {kFuncTag, "Choose", CFXJSE_FormCalcContext::Choose},
246     {kFuncTag, "Exists", CFXJSE_FormCalcContext::Exists},
247     {kFuncTag, "HasValue", CFXJSE_FormCalcContext::HasValue},
248     {kFuncTag, "Oneof", CFXJSE_FormCalcContext::Oneof},
249     {kFuncTag, "Within", CFXJSE_FormCalcContext::Within},
250     {kFuncTag, "If", CFXJSE_FormCalcContext::If},
251     {kFuncTag, "Eval", CFXJSE_FormCalcContext::Eval},
252     {kFuncTag, "Translate", CFXJSE_FormCalcContext::eval_translation},
253     {kFuncTag, "Ref", CFXJSE_FormCalcContext::Ref},
254     {kFuncTag, "UnitType", CFXJSE_FormCalcContext::UnitType},
255     {kFuncTag, "UnitValue", CFXJSE_FormCalcContext::UnitValue},
256     {kFuncTag, "At", CFXJSE_FormCalcContext::At},
257     {kFuncTag, "Concat", CFXJSE_FormCalcContext::Concat},
258     {kFuncTag, "Decode", CFXJSE_FormCalcContext::Decode},
259     {kFuncTag, "Encode", CFXJSE_FormCalcContext::Encode},
260     {kFuncTag, "Format", CFXJSE_FormCalcContext::Format},
261     {kFuncTag, "Left", CFXJSE_FormCalcContext::Left},
262     {kFuncTag, "Len", CFXJSE_FormCalcContext::Len},
263     {kFuncTag, "Lower", CFXJSE_FormCalcContext::Lower},
264     {kFuncTag, "Ltrim", CFXJSE_FormCalcContext::Ltrim},
265     {kFuncTag, "Parse", CFXJSE_FormCalcContext::Parse},
266     {kFuncTag, "Replace", CFXJSE_FormCalcContext::Replace},
267     {kFuncTag, "Right", CFXJSE_FormCalcContext::Right},
268     {kFuncTag, "Rtrim", CFXJSE_FormCalcContext::Rtrim},
269     {kFuncTag, "Space", CFXJSE_FormCalcContext::Space},
270     {kFuncTag, "Str", CFXJSE_FormCalcContext::Str},
271     {kFuncTag, "Stuff", CFXJSE_FormCalcContext::Stuff},
272     {kFuncTag, "Substr", CFXJSE_FormCalcContext::Substr},
273     {kFuncTag, "Uuid", CFXJSE_FormCalcContext::Uuid},
274     {kFuncTag, "Upper", CFXJSE_FormCalcContext::Upper},
275     {kFuncTag, "WordNum", CFXJSE_FormCalcContext::WordNum},
276     {kFuncTag, "Get", CFXJSE_FormCalcContext::Get},
277     {kFuncTag, "Post", CFXJSE_FormCalcContext::Post},
278     {kFuncTag, "Put", CFXJSE_FormCalcContext::Put},
279     {kFuncTag, "pos_op", CFXJSE_FormCalcContext::positive_operator},
280     {kFuncTag, "neg_op", CFXJSE_FormCalcContext::negative_operator},
281     {kFuncTag, "log_or_op", CFXJSE_FormCalcContext::logical_or_operator},
282     {kFuncTag, "log_and_op", CFXJSE_FormCalcContext::logical_and_operator},
283     {kFuncTag, "log_not_op", CFXJSE_FormCalcContext::logical_not_operator},
284     {kFuncTag, "eq_op", CFXJSE_FormCalcContext::equality_operator},
285     {kFuncTag, "neq_op", CFXJSE_FormCalcContext::notequality_operator},
286     {kFuncTag, "lt_op", CFXJSE_FormCalcContext::less_operator},
287     {kFuncTag, "le_op", CFXJSE_FormCalcContext::lessequal_operator},
288     {kFuncTag, "gt_op", CFXJSE_FormCalcContext::greater_operator},
289     {kFuncTag, "ge_op", CFXJSE_FormCalcContext::greaterequal_operator},
290     {kFuncTag, "plus_op", CFXJSE_FormCalcContext::plus_operator},
291     {kFuncTag, "minus_op", CFXJSE_FormCalcContext::minus_operator},
292     {kFuncTag, "mul_op", CFXJSE_FormCalcContext::multiple_operator},
293     {kFuncTag, "div_op", CFXJSE_FormCalcContext::divide_operator},
294     {kFuncTag, "asgn_val_op", CFXJSE_FormCalcContext::assign_value_operator},
295     {kFuncTag, "dot_acc", CFXJSE_FormCalcContext::dot_accessor},
296     {kFuncTag, "dotdot_acc", CFXJSE_FormCalcContext::dotdot_accessor},
297     {kFuncTag, "concat_obj", CFXJSE_FormCalcContext::concat_fm_object},
298     {kFuncTag, "is_obj", CFXJSE_FormCalcContext::is_fm_object},
299     {kFuncTag, "is_ary", CFXJSE_FormCalcContext::is_fm_array},
300     {kFuncTag, "get_val", CFXJSE_FormCalcContext::get_fm_value},
301     {kFuncTag, "get_jsobj", CFXJSE_FormCalcContext::get_fm_jsobj},
302     {kFuncTag, "var_filter", CFXJSE_FormCalcContext::fm_var_filter},
303 };
304 
305 const uint8_t kAltTableDate[] = {
306     255, 255, 255, 3,   9,   255, 255, 255, 255, 255, 255,
307     255, 2,   255, 255, 255, 255, 255, 255, 255, 255, 255,
308     255, 255, 1,   255, 255, 255, 255, 255, 255, 255, 255,
309 };
310 static_assert(std::size(kAltTableDate) == L'a' - L'A' + 1,
311               "Invalid kAltTableDate size.");
312 
313 const uint8_t kAltTableTime[] = {
314     14,  255, 255, 3,   9,   255, 255, 15,  255, 255, 255,
315     255, 6,   255, 255, 255, 255, 255, 7,   255, 255, 255,
316     255, 255, 1,   17,  255, 255, 255, 255, 255, 255, 255,
317 };
318 static_assert(std::size(kAltTableTime) == L'a' - L'A' + 1,
319               "Invalid kAltTableTime size.");
320 
AlternateDateTimeSymbols(WideString * pPattern,const WideString & wsAltSymbols,bool bIsDate)321 void AlternateDateTimeSymbols(WideString* pPattern,
322                               const WideString& wsAltSymbols,
323                               bool bIsDate) {
324   const uint8_t* pAltTable = bIsDate ? kAltTableDate : kAltTableTime;
325   int32_t nLength = pPattern->GetLength();
326   bool bInConstRange = false;
327   bool bEscape = false;
328   int32_t i = 0;
329   UNSAFE_TODO({
330     while (i < nLength) {
331       wchar_t wc = (*pPattern)[i];
332       if (wc == L'\'') {
333         bInConstRange = !bInConstRange;
334         if (bEscape) {
335           i++;
336         } else {
337           pPattern->Delete(i);
338           nLength--;
339         }
340         bEscape = !bEscape;
341         continue;
342       }
343       if (!bInConstRange && wc >= L'A' && wc <= L'a') {
344         uint8_t nAlt = pAltTable[wc - L'A'];
345         if (nAlt != 255) {
346           pPattern->SetAt(i, wsAltSymbols[nAlt]);
347         }
348       }
349       i++;
350       bEscape = false;
351     }
352   });
353 }
354 
PatternStringType(ByteStringView bsPattern)355 std::pair<bool, CXFA_LocaleValue::ValueType> PatternStringType(
356     ByteStringView bsPattern) {
357   WideString wsPattern = WideString::FromUTF8(bsPattern);
358   if (wsPattern.First(8).EqualsASCII("datetime")) {
359     return {true, CXFA_LocaleValue::ValueType::kDateTime};
360   }
361   if (wsPattern.First(4).EqualsASCII("date")) {
362     auto pos = wsPattern.Find(L"time");
363     if (pos.has_value() && pos.value() != 0) {
364       return {true, CXFA_LocaleValue::ValueType::kDateTime};
365     }
366     return {true, CXFA_LocaleValue::ValueType::kDate};
367   }
368   if (wsPattern.First(4).EqualsASCII("time")) {
369     return {true, CXFA_LocaleValue::ValueType::kTime};
370   }
371   if (wsPattern.First(4).EqualsASCII("text")) {
372     return {true, CXFA_LocaleValue::ValueType::kText};
373   }
374   if (wsPattern.First(3).EqualsASCII("num")) {
375     if (wsPattern.Substr(4, 7).EqualsASCII("integer")) {
376       return {true, CXFA_LocaleValue::ValueType::kInteger};
377     }
378     if (wsPattern.Substr(4, 7).EqualsASCII("decimal")) {
379       return {true, CXFA_LocaleValue::ValueType::kDecimal};
380     }
381     if (wsPattern.Substr(4, 8).EqualsASCII("currency")) {
382       return {true, CXFA_LocaleValue::ValueType::kFloat};
383     }
384     if (wsPattern.Substr(4, 7).EqualsASCII("percent")) {
385       return {true, CXFA_LocaleValue::ValueType::kFloat};
386     }
387     return {true, CXFA_LocaleValue::ValueType::kFloat};
388   }
389 
390   CXFA_LocaleValue::ValueType type = CXFA_LocaleValue::ValueType::kNull;
391   wsPattern.MakeLower();
392   const wchar_t* pData = wsPattern.c_str();
393   int32_t iLength = wsPattern.GetLength();
394   int32_t iIndex = 0;
395   bool bSingleQuotation = false;
396   UNSAFE_TODO({
397     while (iIndex < iLength) {
398       wchar_t wsPatternChar = pData[iIndex];
399       if (wsPatternChar == 0x27) {
400         bSingleQuotation = !bSingleQuotation;
401         iIndex++;
402         continue;
403       }
404       if (bSingleQuotation) {
405         iIndex++;
406         continue;
407       }
408 
409       if (wsPatternChar == 'h' || wsPatternChar == 'k') {
410         return {false, CXFA_LocaleValue::ValueType::kTime};
411       }
412       if (wsPatternChar == 'x' || wsPatternChar == 'o' ||
413           wsPatternChar == '0') {
414         return {false, CXFA_LocaleValue::ValueType::kText};
415       }
416       if (wsPatternChar == 'v' || wsPatternChar == '8' ||
417           wsPatternChar == '$') {
418         return {false, CXFA_LocaleValue::ValueType::kFloat};
419       }
420       if (wsPatternChar == 'y' || wsPatternChar == 'j') {
421         iIndex++;
422         wchar_t timePatternChar;
423         while (iIndex < iLength) {
424           timePatternChar = pData[iIndex];
425           if (timePatternChar == 0x27) {
426             bSingleQuotation = !bSingleQuotation;
427             iIndex++;
428             continue;
429           }
430           if (!bSingleQuotation && timePatternChar == 't') {
431             return {false, CXFA_LocaleValue::ValueType::kDateTime};
432           }
433           iIndex++;
434         }
435         return {false, CXFA_LocaleValue::ValueType::kDate};
436       }
437 
438       if (wsPatternChar == 'a') {
439         type = CXFA_LocaleValue::ValueType::kText;
440       } else if (wsPatternChar == 'z' || wsPatternChar == 's' ||
441                  wsPatternChar == 'e' || wsPatternChar == ',' ||
442                  wsPatternChar == '.') {
443         type = CXFA_LocaleValue::ValueType::kFloat;
444       }
445       iIndex++;
446     }
447     return {false, type};
448   });
449 }
450 
ToFormCalcContext(CFXJSE_HostObject * pHostObj)451 CFXJSE_FormCalcContext* ToFormCalcContext(CFXJSE_HostObject* pHostObj) {
452   return pHostObj ? pHostObj->AsFormCalcContext() : nullptr;
453 }
454 
LocaleFromString(CXFA_Document * pDoc,CXFA_LocaleMgr * pMgr,ByteStringView bsLocale)455 GCedLocaleIface* LocaleFromString(CXFA_Document* pDoc,
456                                   CXFA_LocaleMgr* pMgr,
457                                   ByteStringView bsLocale) {
458   if (!bsLocale.IsEmpty())
459     return pMgr->GetLocaleByName(WideString::FromUTF8(bsLocale));
460 
461   CXFA_Node* pThisNode = ToNode(pDoc->GetScriptContext()->GetThisObject());
462   return pThisNode->GetLocale();
463 }
464 
FormatFromString(LocaleIface * pLocale,ByteStringView bsFormat)465 WideString FormatFromString(LocaleIface* pLocale, ByteStringView bsFormat) {
466   if (!bsFormat.IsEmpty())
467     return WideString::FromUTF8(bsFormat);
468 
469   return pLocale->GetDatePattern(LocaleIface::DateTimeSubcategory::kDefault);
470 }
471 
SubCategoryFromInt(int32_t iStyle)472 LocaleIface::DateTimeSubcategory SubCategoryFromInt(int32_t iStyle) {
473   switch (iStyle) {
474     case 1:
475       return LocaleIface::DateTimeSubcategory::kShort;
476     case 3:
477       return LocaleIface::DateTimeSubcategory::kLong;
478     case 4:
479       return LocaleIface::DateTimeSubcategory::kFull;
480     case 0:
481     case 2:
482     default:
483       return LocaleIface::DateTimeSubcategory::kMedium;
484   }
485 }
486 
GetLocalDateTimeFormat(CXFA_Document * pDoc,int32_t iStyle,ByteStringView bsLocale,bool bStandard,bool bIsDate)487 ByteString GetLocalDateTimeFormat(CXFA_Document* pDoc,
488                                   int32_t iStyle,
489                                   ByteStringView bsLocale,
490                                   bool bStandard,
491                                   bool bIsDate) {
492   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
493   LocaleIface* pLocale = LocaleFromString(pDoc, pMgr, bsLocale);
494   if (!pLocale)
495     return ByteString();
496 
497   LocaleIface::DateTimeSubcategory category = SubCategoryFromInt(iStyle);
498   WideString wsLocal = bIsDate ? pLocale->GetDatePattern(category)
499                                : pLocale->GetTimePattern(category);
500   if (!bStandard)
501     AlternateDateTimeSymbols(&wsLocal, pLocale->GetDateTimeSymbols(), bIsDate);
502   return wsLocal.ToUTF8();
503 }
504 
IsWhitespace(char c)505 bool IsWhitespace(char c) {
506   return c == 0x20 || c == 0x09 || c == 0x0B || c == 0x0C || c == 0x0A ||
507          c == 0x0D;
508 }
509 
IsPartOfNumber(char ch)510 bool IsPartOfNumber(char ch) {
511   return isdigit(ch) || ch == '-' || ch == '.';
512 }
513 
IsPartOfNumberW(wchar_t ch)514 bool IsPartOfNumberW(wchar_t ch) {
515   return FXSYS_IsDecimalDigit(ch) || ch == L'-' || ch == L'.';
516 }
517 
GUIDString(bool bSeparator)518 ByteString GUIDString(bool bSeparator) {
519   std::array<uint8_t, 16> data;
520   FX_Random_GenerateMT(fxcrt::reinterpret_span<uint32_t, uint8_t>(data));
521   data[6] = (data[6] & 0x0F) | 0x40;
522 
523   ByteString bsGUID;
524   {
525     // Span's lifetime must end before ReleaseBuffer() below.
526     pdfium::span<char> pBuf = bsGUID.GetBuffer(40);
527     size_t out_index = 0;
528     for (size_t i = 0; i < 16; ++i, out_index += 2) {
529       if (bSeparator && (i == 4 || i == 6 || i == 8 || i == 10)) {
530         pBuf[out_index++] = L'-';
531       }
532       FXSYS_IntToTwoHexChars(data[i], &pBuf[out_index]);
533     }
534   }
535   bsGUID.ReleaseBuffer(bSeparator ? 36 : 32);
536   return bsGUID;
537 }
538 
GetLocalTimeZone(int32_t * pHour,int32_t * pMin,int32_t * pSec)539 void GetLocalTimeZone(int32_t* pHour, int32_t* pMin, int32_t* pSec) {
540   time_t now;
541   FXSYS_time(&now);
542 
543   struct tm* pGmt = gmtime(&now);
544   struct tm* pLocal = FXSYS_localtime(&now);
545   *pHour = pLocal->tm_hour - pGmt->tm_hour;
546   *pMin = pLocal->tm_min - pGmt->tm_min;
547   *pSec = pLocal->tm_sec - pGmt->tm_sec;
548 }
549 
HTMLSTR2Code(const WideString & pData,uint32_t * iCode)550 bool HTMLSTR2Code(const WideString& pData, uint32_t* iCode) {
551   auto cmpFunc = [](const XFA_FMHtmlReserveCode& iter, ByteStringView val) {
552     return strcmp(val.unterminated_c_str(), iter.m_htmlReserve) > 0;
553   };
554   if (!pData.IsASCII())
555     return false;
556   ByteString temp = pData.ToASCII();
557   const XFA_FMHtmlReserveCode* result = std::lower_bound(
558       std::begin(kReservesForDecode), std::end(kReservesForDecode),
559       temp.AsStringView(), cmpFunc);
560   if (result != std::end(kReservesForDecode) &&
561       !strcmp(temp.c_str(), result->m_htmlReserve)) {
562     *iCode = result->m_uCode;
563     return true;
564   }
565   return false;
566 }
567 
HTMLCode2STR(uint32_t iCode,WideString * wsHTMLReserve)568 bool HTMLCode2STR(uint32_t iCode, WideString* wsHTMLReserve) {
569   auto cmpFunc = [](const XFA_FMHtmlReserveCode iter, const uint32_t& val) {
570     return iter.m_uCode < val;
571   };
572   const XFA_FMHtmlReserveCode* result =
573       std::lower_bound(std::begin(kReservesForEncode),
574                        std::end(kReservesForEncode), iCode, cmpFunc);
575   if (result != std::end(kReservesForEncode) && result->m_uCode == iCode) {
576     *wsHTMLReserve = WideString::FromASCII(result->m_htmlReserve);
577     return true;
578   }
579   return false;
580 }
581 
DecodeURL(const WideString & wsURL)582 WideString DecodeURL(const WideString& wsURL) {
583   const wchar_t* pData = wsURL.c_str();
584   size_t iLen = wsURL.GetLength();
585   WideTextBuffer wsResultBuf;
586   UNSAFE_TODO({
587     for (size_t i = 0; i < iLen; ++i) {
588       wchar_t ch = pData[i];
589       if ('%' != ch) {
590         wsResultBuf.AppendChar(ch);
591         continue;
592       }
593       wchar_t chTemp = 0;
594       int32_t iCount = 0;
595       while (iCount < 2) {
596         if (++i >= iLen) {
597           return WideString();
598         }
599         chTemp *= 16;
600         ch = pData[i];
601         if (!FXSYS_IsWideHexDigit(ch)) {
602           return WideString();
603         }
604         chTemp += FXSYS_WideHexCharToInt(ch);
605         ++iCount;
606       }
607       wsResultBuf.AppendChar(chTemp);
608     }
609     return wsResultBuf.MakeString();
610   });
611 }
612 
DecodeMLInternal(const WideString & wsHTML,bool bIsHTML)613 WideString DecodeMLInternal(const WideString& wsHTML, bool bIsHTML) {
614   const wchar_t* pData = wsHTML.c_str();
615   size_t iLen = wsHTML.GetLength();
616   WideTextBuffer wsResultBuf;
617   UNSAFE_TODO({
618     for (size_t i = 0; i < iLen; ++i) {
619       wchar_t ch = pData[i];
620       if (ch != '&') {
621         wsResultBuf.AppendChar(ch);
622         continue;
623       }
624 
625       if (++i >= iLen)
626         break;
627       ch = pData[i];
628       if (ch == '#') {
629         if (++i >= iLen) {
630           break;
631         }
632         ch = pData[i];
633         if (ch != 'x' && ch != 'X') {
634           return WideString();
635         }
636         if (++i >= iLen)
637           break;
638         ch = pData[i];
639         uint32_t iCode = 0;
640         while (ch != ';' && i < iLen) {
641           iCode *= 16;
642           if (!FXSYS_IsWideHexDigit(ch)) {
643             return WideString();
644           }
645           iCode += FXSYS_WideHexCharToInt(ch);
646           if (++i >= iLen) {
647             break;
648           }
649           ch = pData[i];
650         }
651         wsResultBuf.AppendChar(iCode);
652         continue;
653       }
654 
655       wchar_t szBuffer[9];
656       size_t iStrIndex = 0;
657       while (ch != ';' && i < iLen) {
658         if (iStrIndex < 8) {
659           szBuffer[iStrIndex++] = ch;
660         }
661         if (++i >= iLen) {
662           break;
663         }
664         ch = pData[i];
665       }
666       szBuffer[iStrIndex] = 0;
667       if (bIsHTML) {
668         uint32_t iData = 0;
669         if (HTMLSTR2Code(szBuffer, &iData)) {
670           wsResultBuf.AppendChar((wchar_t)iData);
671         }
672       } else {
673         if (wcscmp(szBuffer, L"quot") == 0) {
674           wsResultBuf.AppendChar('"');
675         } else if (wcscmp(szBuffer, L"amp") == 0) {
676           wsResultBuf.AppendChar('&');
677         } else if (wcscmp(szBuffer, L"apos") == 0) {
678           wsResultBuf.AppendChar('\'');
679         } else if (wcscmp(szBuffer, L"lt") == 0) {
680           wsResultBuf.AppendChar('<');
681         } else if (wcscmp(szBuffer, L"gt") == 0) {
682           wsResultBuf.AppendChar('>');
683         }
684       }
685     }
686     return wsResultBuf.MakeString();
687   });
688 }
689 
DecodeHTML(const WideString & wsHTML)690 WideString DecodeHTML(const WideString& wsHTML) {
691   return DecodeMLInternal(wsHTML, true);
692 }
693 
DecodeXML(const WideString & wsXML)694 WideString DecodeXML(const WideString& wsXML) {
695   return DecodeMLInternal(wsXML, false);
696 }
697 
EncodeURL(const ByteString & bsURL)698 WideString EncodeURL(const ByteString& bsURL) {
699   static constexpr char32_t kStrUnsafe[] = {' ', '<', '>', '"', '#',
700                                             '%', '{', '}', '|', '\\',
701                                             '^', '~', '[', ']', '`'};
702   static constexpr char32_t kStrReserved[] = {';', '/', '?', ':',
703                                               '@', '=', '&'};
704 
705   WideString wsURL = WideString::FromUTF8(bsURL.AsStringView());
706   WideTextBuffer wsResultBuf;
707   std::array<wchar_t, 3> encode_buffer = {L'%'};  // Starts with %.
708   for (char32_t ch : pdfium::CodePointView(wsURL.AsStringView())) {
709     if (ch <= 0x1f || (ch >= 0x7f && ch <= 0xff) ||
710         pdfium::Contains(kStrUnsafe, ch) ||
711         pdfium::Contains(kStrReserved, ch)) {
712       int32_t iIndex = ch / 16;
713       encode_buffer[1] = kStrCode[iIndex];
714       encode_buffer[2] = kStrCode[ch - iIndex * 16];
715       wsResultBuf << WideStringView(encode_buffer);
716       continue;
717     }
718     if (ch >= 0x20 && ch <= 0x7e) {
719       wsResultBuf.AppendChar(ch);
720       continue;
721     }
722     const wchar_t iRadix = 16;
723     WideString wsBuffer;
724     while (ch >= iRadix) {
725       wchar_t tmp = kStrCode[ch % iRadix];
726       ch /= iRadix;
727       wsBuffer += tmp;
728     }
729     wsBuffer += kStrCode[ch];
730     int32_t iLen = wsBuffer.GetLength();
731     if (iLen < 2) {
732       break;
733     }
734 
735     int32_t iIndex = 0;
736     if (iLen % 2 != 0) {
737       encode_buffer[1] = '0';
738       encode_buffer[2] = wsBuffer[iLen - 1];
739       iIndex = iLen - 2;
740     } else {
741       encode_buffer[1] = wsBuffer[iLen - 1];
742       encode_buffer[2] = wsBuffer[iLen - 2];
743       iIndex = iLen - 3;
744     }
745     wsResultBuf << WideStringView(encode_buffer);
746     while (iIndex > 0) {
747       encode_buffer[1] = wsBuffer[iIndex];
748       encode_buffer[2] = wsBuffer[iIndex - 1];
749       iIndex -= 2;
750       wsResultBuf << WideStringView(encode_buffer);
751     }
752   }
753   return wsResultBuf.MakeString();
754 }
755 
EncodeHTML(const ByteString & bsHTML)756 WideString EncodeHTML(const ByteString& bsHTML) {
757   WideString wsHTML = WideString::FromUTF8(bsHTML.AsStringView());
758   std::array<wchar_t, 8> encode_buffer;
759   encode_buffer[0] = '&';
760   encode_buffer[1] = '#';
761   encode_buffer[2] = 'x';
762   WideTextBuffer wsResultBuf;
763   for (char32_t ch : pdfium::CodePointView(wsHTML.AsStringView())) {
764     WideString htmlReserve;
765     if (HTMLCode2STR(ch, &htmlReserve)) {
766       wsResultBuf.AppendChar(L'&');
767       wsResultBuf << htmlReserve;
768       wsResultBuf.AppendChar(L';');
769     } else if (ch >= 32 && ch <= 126) {
770       wsResultBuf.AppendChar(static_cast<wchar_t>(ch));
771     } else if (ch < 256) {
772       int32_t iIndex = ch / 16;
773       encode_buffer[3] = kStrCode[iIndex];
774       encode_buffer[4] = kStrCode[ch - iIndex * 16];
775       encode_buffer[5] = ';';
776       wsResultBuf << WideStringView(encode_buffer).First(6);
777     } else if (ch < 65536) {
778       int32_t iBigByte = ch / 256;
779       int32_t iLittleByte = ch % 256;
780       encode_buffer[3] = kStrCode[iBigByte / 16];
781       encode_buffer[4] = kStrCode[iBigByte % 16];
782       encode_buffer[5] = kStrCode[iLittleByte / 16];
783       encode_buffer[6] = kStrCode[iLittleByte % 16];
784       encode_buffer[7] = ';';
785       wsResultBuf << WideStringView(encode_buffer).First(8);
786     } else {
787       // TODO(tsepez): Handle codepoint not in BMP.
788     }
789   }
790   return wsResultBuf.MakeString();
791 }
792 
EncodeXML(const ByteString & bsXML)793 WideString EncodeXML(const ByteString& bsXML) {
794   WideString wsXML = WideString::FromUTF8(bsXML.AsStringView());
795   WideTextBuffer wsResultBuf;
796   std::array<wchar_t, 8> encode_buffer;
797   encode_buffer[0] = '&';
798   encode_buffer[1] = '#';
799   encode_buffer[2] = 'x';
800   for (char32_t ch : pdfium::CodePointView(wsXML.AsStringView())) {
801     switch (ch) {
802       case '"':
803         wsResultBuf.AppendChar('&');
804         wsResultBuf << WideStringView(L"quot");
805         wsResultBuf.AppendChar(';');
806         break;
807       case '&':
808         wsResultBuf.AppendChar('&');
809         wsResultBuf << WideStringView(L"amp");
810         wsResultBuf.AppendChar(';');
811         break;
812       case '\'':
813         wsResultBuf.AppendChar('&');
814         wsResultBuf << WideStringView(L"apos");
815         wsResultBuf.AppendChar(';');
816         break;
817       case '<':
818         wsResultBuf.AppendChar('&');
819         wsResultBuf << WideStringView(L"lt");
820         wsResultBuf.AppendChar(';');
821         break;
822       case '>':
823         wsResultBuf.AppendChar('&');
824         wsResultBuf << WideStringView(L"gt");
825         wsResultBuf.AppendChar(';');
826         break;
827       default: {
828         if (ch >= 32 && ch <= 126) {
829           wsResultBuf.AppendChar(static_cast<wchar_t>(ch));
830         } else if (ch < 256) {
831           int32_t iIndex = ch / 16;
832           encode_buffer[3] = kStrCode[iIndex];
833           encode_buffer[4] = kStrCode[ch - iIndex * 16];
834           encode_buffer[5] = ';';
835           wsResultBuf << WideStringView(encode_buffer).First(6);
836         } else if (ch < 65536) {
837           int32_t iBigByte = ch / 256;
838           int32_t iLittleByte = ch % 256;
839           encode_buffer[3] = kStrCode[iBigByte / 16];
840           encode_buffer[4] = kStrCode[iBigByte % 16];
841           encode_buffer[5] = kStrCode[iLittleByte / 16];
842           encode_buffer[6] = kStrCode[iLittleByte % 16];
843           encode_buffer[7] = ';';
844           wsResultBuf << WideStringView(encode_buffer).First(8);
845         } else {
846           // TODO(tsepez): Handle codepoint not in BMP.
847         }
848         break;
849       }
850     }
851   }
852   return wsResultBuf.MakeString();
853 }
854 
TrillionUS(ByteStringView bsData)855 ByteString TrillionUS(ByteStringView bsData) {
856   static const char kUnits[][6] = {"zero", "one", "two",   "three", "four",
857                                    "five", "six", "seven", "eight", "nine"};
858   static const char kCapUnits[][6] = {"Zero", "One", "Two",   "Three", "Four",
859                                       "Five", "Six", "Seven", "Eight", "Nine"};
860   static const char kTens[][10] = {
861       "Ten",     "Eleven",  "Twelve",    "Thirteen", "Fourteen",
862       "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
863   static const char kLastTens[][8] = {"Twenty", "Thirty",  "Forty",  "Fifty",
864                                       "Sixty",  "Seventy", "Eighty", "Ninety"};
865   static const char kComm[][11] = {" Hundred ", " Thousand ", " Million ",
866                                    " Billion ", "Trillion"};
867   const char* pData = bsData.unterminated_c_str();
868   int32_t iLength = bsData.GetLength();
869   int32_t iComm = 0;
870   if (iLength > 12)
871     iComm = 4;
872   else if (iLength > 9)
873     iComm = 3;
874   else if (iLength > 6)
875     iComm = 2;
876   else if (iLength > 3)
877     iComm = 1;
878 
879   int32_t iFirstCount = iLength % 3;
880   if (iFirstCount == 0)
881     iFirstCount = 3;
882 
883   ByteString strBuf;
884   int32_t iIndex = 0;
885   UNSAFE_TODO({
886     if (iFirstCount == 3) {
887       if (pData[iIndex] != '0') {
888         strBuf += kCapUnits[pData[iIndex] - '0'];
889         strBuf += kComm[0];
890       }
891       if (pData[iIndex + 1] == '0') {
892         strBuf += kCapUnits[pData[iIndex + 2] - '0'];
893       } else {
894         if (pData[iIndex + 1] > '1') {
895           strBuf += kLastTens[pData[iIndex + 1] - '2'];
896           strBuf += "-";
897           strBuf += kUnits[pData[iIndex + 2] - '0'];
898         } else if (pData[iIndex + 1] == '1') {
899           strBuf += kTens[pData[iIndex + 2] - '0'];
900         } else if (pData[iIndex + 1] == '0') {
901           strBuf += kCapUnits[pData[iIndex + 2] - '0'];
902         }
903       }
904       iIndex += 3;
905     } else if (iFirstCount == 2) {
906       if (pData[iIndex] == '0') {
907         strBuf += kCapUnits[pData[iIndex + 1] - '0'];
908       } else {
909         if (pData[iIndex] > '1') {
910           strBuf += kLastTens[pData[iIndex] - '2'];
911           strBuf += "-";
912           strBuf += kUnits[pData[iIndex + 1] - '0'];
913         } else if (pData[iIndex] == '1') {
914           strBuf += kTens[pData[iIndex + 1] - '0'];
915         } else if (pData[iIndex] == '0') {
916           strBuf += kCapUnits[pData[iIndex + 1] - '0'];
917         }
918       }
919       iIndex += 2;
920     } else if (iFirstCount == 1) {
921       strBuf += kCapUnits[pData[iIndex] - '0'];
922       ++iIndex;
923     }
924     if (iLength > 3 && iFirstCount > 0) {
925       strBuf += kComm[iComm];
926       --iComm;
927     }
928     while (iIndex < iLength) {
929       if (pData[iIndex] != '0') {
930         strBuf += kCapUnits[pData[iIndex] - '0'];
931         strBuf += kComm[0];
932       }
933       if (pData[iIndex + 1] == '0') {
934         strBuf += kCapUnits[pData[iIndex + 2] - '0'];
935       } else {
936         if (pData[iIndex + 1] > '1') {
937           strBuf += kLastTens[pData[iIndex + 1] - '2'];
938           strBuf += "-";
939           strBuf += kUnits[pData[iIndex + 2] - '0'];
940         } else if (pData[iIndex + 1] == '1') {
941           strBuf += kTens[pData[iIndex + 2] - '0'];
942         } else if (pData[iIndex + 1] == '0') {
943           strBuf += kCapUnits[pData[iIndex + 2] - '0'];
944         }
945       }
946       if (iIndex < iLength - 3) {
947         strBuf += kComm[iComm];
948         --iComm;
949       }
950       iIndex += 3;
951     }
952     return strBuf;
953   });
954 }
955 
WordUS(ByteStringView bsData,int32_t iStyle)956 ByteString WordUS(ByteStringView bsData, int32_t iStyle) {
957   if (iStyle < 0 || iStyle > 2)
958     return ByteString();
959 
960   int32_t iLength = bsData.GetLength();
961   ByteString strBuf;
962   int32_t iIndex = 0;
963   while (iIndex < iLength) {
964     if (bsData[iIndex] == '.')
965       break;
966     ++iIndex;
967   }
968   int32_t iInteger = iIndex;
969   iIndex = 0;
970   while (iIndex < iInteger) {
971     int32_t iCount = (iInteger - iIndex) % 12;
972     if (!iCount && iInteger - iIndex > 0)
973       iCount = 12;
974 
975     strBuf += TrillionUS(bsData.Substr(iIndex, iCount));
976     iIndex += iCount;
977     if (iIndex < iInteger)
978       strBuf += " Trillion ";
979   }
980 
981   if (iStyle > 0)
982     strBuf += " Dollars";
983 
984   if (iStyle > 1 && iInteger < iLength) {
985     strBuf += " And ";
986     iIndex = iInteger + 1;
987     while (iIndex < iLength) {
988       int32_t iCount = (iLength - iIndex) % 12;
989       if (!iCount && iLength - iIndex > 0)
990         iCount = 12;
991 
992       strBuf += TrillionUS(bsData.Substr(iIndex, iCount));
993       iIndex += iCount;
994       if (iIndex < iLength)
995         strBuf += " Trillion ";
996     }
997     strBuf += " Cents";
998   }
999   return strBuf;
1000 }
1001 
GetObjectDefaultValue(v8::Isolate * pIsolate,v8::Local<v8::Object> pObject)1002 v8::Local<v8::Value> GetObjectDefaultValue(v8::Isolate* pIsolate,
1003                                            v8::Local<v8::Object> pObject) {
1004   CXFA_Node* pNode = ToNode(CFXJSE_Engine::ToObject(pIsolate, pObject));
1005   if (!pNode)
1006     return fxv8::NewNullHelper(pIsolate);
1007 
1008   v8::Local<v8::Value> value;
1009   pNode->JSObject()->ScriptSomDefaultValue(pIsolate, &value, false,
1010                                            XFA_Attribute::Unknown);
1011   return value;
1012 }
1013 
SetObjectDefaultValue(v8::Isolate * pIsolate,v8::Local<v8::Object> pObject,v8::Local<v8::Value> hNewValue)1014 bool SetObjectDefaultValue(v8::Isolate* pIsolate,
1015                            v8::Local<v8::Object> pObject,
1016                            v8::Local<v8::Value> hNewValue) {
1017   CXFA_Node* pNode = ToNode(CFXJSE_Engine::ToObject(pIsolate, pObject));
1018   if (!pNode)
1019     return false;
1020 
1021   pNode->JSObject()->ScriptSomDefaultValue(pIsolate, &hNewValue, true,
1022                                            XFA_Attribute::Unknown);
1023   return true;
1024 }
1025 
GetExtractedValue(v8::Isolate * pIsolate,v8::Local<v8::Value> pValue)1026 v8::Local<v8::Value> GetExtractedValue(v8::Isolate* pIsolate,
1027                                        v8::Local<v8::Value> pValue) {
1028   if (pValue.IsEmpty())
1029     return v8::Local<v8::Value>();
1030 
1031   if (fxv8::IsArray(pValue)) {
1032     v8::Local<v8::Array> arr = pValue.As<v8::Array>();
1033     uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
1034     if (iLength < 3)
1035       return fxv8::NewUndefinedHelper(pIsolate);
1036 
1037     v8::Local<v8::Value> propertyValue =
1038         fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);
1039     v8::Local<v8::Value> jsValue =
1040         fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 2);
1041     if (!fxv8::IsObject(jsValue))
1042       return fxv8::NewUndefinedHelper(pIsolate);
1043 
1044     v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
1045     if (fxv8::IsNull(propertyValue))
1046       return GetObjectDefaultValue(pIsolate, jsObjectValue);
1047 
1048     ByteString bsName =
1049         fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
1050     return fxv8::ReentrantGetObjectPropertyHelper(pIsolate, jsObjectValue,
1051                                                   bsName.AsStringView());
1052   }
1053 
1054   if (fxv8::IsObject(pValue))
1055     return GetObjectDefaultValue(pIsolate, pValue.As<v8::Object>());
1056 
1057   return pValue;
1058 }
1059 
GetSimpleValue(const v8::FunctionCallbackInfo<v8::Value> & info,uint32_t index)1060 v8::Local<v8::Value> GetSimpleValue(
1061     const v8::FunctionCallbackInfo<v8::Value>& info,
1062     uint32_t index) {
1063   DCHECK(index < static_cast<uint32_t>(info.Length()));
1064   return GetExtractedValue(info.GetIsolate(), info[index]);
1065 }
1066 
ValueIsNull(v8::Isolate * pIsolate,v8::Local<v8::Value> arg)1067 bool ValueIsNull(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
1068   v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
1069   return extracted.IsEmpty() || fxv8::IsNull(extracted);
1070 }
1071 
ValueToInteger(v8::Isolate * pIsolate,v8::Local<v8::Value> arg)1072 int32_t ValueToInteger(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
1073   v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
1074   if (extracted.IsEmpty())
1075     return 0;
1076 
1077   if (fxv8::IsObject(extracted) || fxv8::IsArray(extracted))
1078     return ValueToInteger(pIsolate, extracted);
1079 
1080   if (fxv8::IsString(extracted)) {
1081     ByteString bsValue = fxv8::ReentrantToByteStringHelper(pIsolate, extracted);
1082     return FXSYS_atoi(bsValue.c_str());
1083   }
1084 
1085   return fxv8::ReentrantToInt32Helper(pIsolate, extracted);
1086 }
1087 
ValueToFloat(v8::Isolate * pIsolate,v8::Local<v8::Value> arg)1088 float ValueToFloat(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
1089   v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
1090   if (extracted.IsEmpty())
1091     return 0.0f;
1092 
1093   if (fxv8::IsUndefined(extracted))
1094     return 0.0f;
1095 
1096   if (fxv8::IsObject(extracted) || fxv8::IsArray(extracted))
1097     return ValueToFloat(pIsolate, extracted);
1098 
1099   if (fxv8::IsString(extracted)) {
1100     ByteString bsValue = fxv8::ReentrantToByteStringHelper(pIsolate, extracted);
1101     return strtof(bsValue.c_str(), nullptr);
1102   }
1103 
1104   return fxv8::ReentrantToFloatHelper(pIsolate, extracted);
1105 }
1106 
ValueToDouble(v8::Isolate * pIsolate,v8::Local<v8::Value> arg)1107 double ValueToDouble(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
1108   v8::Local<v8::Value> extracted = GetExtractedValue(pIsolate, arg);
1109   if (extracted.IsEmpty())
1110     return 0.0;
1111 
1112   if (fxv8::IsUndefined(extracted))
1113     return 0.0;
1114 
1115   if (fxv8::IsObject(extracted) || fxv8::IsArray(extracted))
1116     return ValueToDouble(pIsolate, extracted);
1117 
1118   if (fxv8::IsString(extracted)) {
1119     ByteString bsValue = fxv8::ReentrantToByteStringHelper(pIsolate, extracted);
1120     return strtod(bsValue.c_str(), nullptr);
1121   }
1122 
1123   return fxv8::ReentrantToDoubleHelper(pIsolate, extracted);
1124 }
1125 
ExtractDouble(v8::Isolate * pIsolate,v8::Local<v8::Value> src)1126 std::optional<double> ExtractDouble(v8::Isolate* pIsolate,
1127                                     v8::Local<v8::Value> src) {
1128   if (src.IsEmpty())
1129     return 0.0;
1130 
1131   if (!fxv8::IsArray(src))
1132     return ValueToDouble(pIsolate, src);
1133 
1134   v8::Local<v8::Array> arr = src.As<v8::Array>();
1135   uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
1136   if (iLength < 3)
1137     return std::nullopt;
1138 
1139   v8::Local<v8::Value> propertyValue =
1140       fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);
1141   v8::Local<v8::Value> jsValue =
1142       fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 2);
1143   if (fxv8::IsNull(propertyValue) || !fxv8::IsObject(jsValue))
1144     return ValueToDouble(pIsolate, jsValue);
1145 
1146   ByteString bsName =
1147       fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
1148   return ValueToDouble(
1149       pIsolate, fxv8::ReentrantGetObjectPropertyHelper(
1150                     pIsolate, jsValue.As<v8::Object>(), bsName.AsStringView()));
1151 }
1152 
ValueToUTF8String(v8::Isolate * pIsolate,v8::Local<v8::Value> arg)1153 ByteString ValueToUTF8String(v8::Isolate* pIsolate, v8::Local<v8::Value> arg) {
1154   if (arg.IsEmpty())
1155     return ByteString();
1156 
1157   if (fxv8::IsNull(arg) || fxv8::IsUndefined(arg))
1158     return ByteString();
1159 
1160   if (fxv8::IsBoolean(arg))
1161     return fxv8::ReentrantToBooleanHelper(pIsolate, arg) ? "1" : "0";
1162 
1163   return fxv8::ReentrantToByteStringHelper(pIsolate, arg);
1164 }
1165 
SimpleValueCompare(v8::Isolate * pIsolate,v8::Local<v8::Value> firstValue,v8::Local<v8::Value> secondValue)1166 bool SimpleValueCompare(v8::Isolate* pIsolate,
1167                         v8::Local<v8::Value> firstValue,
1168                         v8::Local<v8::Value> secondValue) {
1169   if (firstValue.IsEmpty())
1170     return false;
1171 
1172   if (fxv8::IsString(firstValue)) {
1173     const ByteString first = ValueToUTF8String(pIsolate, firstValue);
1174     const ByteString second = ValueToUTF8String(pIsolate, secondValue);
1175     return first == second;
1176   }
1177   if (fxv8::IsNumber(firstValue)) {
1178     const float first = ValueToFloat(pIsolate, firstValue);
1179     const float second = ValueToFloat(pIsolate, secondValue);
1180     return first == second;
1181   }
1182   if (fxv8::IsBoolean(firstValue)) {
1183     const bool first = fxv8::ReentrantToBooleanHelper(pIsolate, firstValue);
1184     const bool second = fxv8::ReentrantToBooleanHelper(pIsolate, secondValue);
1185     return first == second;
1186   }
1187   return fxv8::IsNull(firstValue) && fxv8::IsNull(secondValue);
1188 }
1189 
UnfoldArgs(const v8::FunctionCallbackInfo<v8::Value> & info)1190 v8::LocalVector<v8::Value> UnfoldArgs(
1191     const v8::FunctionCallbackInfo<v8::Value>& info) {
1192   v8::LocalVector<v8::Value> results(info.GetIsolate());
1193   v8::Isolate* pIsolate = info.GetIsolate();
1194   for (int i = 1; i < info.Length(); ++i) {
1195     v8::Local<v8::Value> arg = info[i];
1196     if (fxv8::IsArray(arg)) {
1197       v8::Local<v8::Array> arr = arg.As<v8::Array>();
1198       uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
1199       if (iLength < 3)
1200         continue;
1201 
1202       v8::Local<v8::Value> propertyValue =
1203           fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);
1204 
1205       for (uint32_t j = 2; j < iLength; j++) {
1206         v8::Local<v8::Value> jsValue =
1207             fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, j);
1208 
1209         if (!fxv8::IsObject(jsValue)) {
1210           results.push_back(fxv8::NewUndefinedHelper(pIsolate));
1211         } else if (fxv8::IsNull(propertyValue)) {
1212           results.push_back(
1213               GetObjectDefaultValue(pIsolate, jsValue.As<v8::Object>()));
1214         } else {
1215           ByteString bsName =
1216               fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
1217           results.push_back(fxv8::ReentrantGetObjectPropertyHelper(
1218               pIsolate, jsValue.As<v8::Object>(), bsName.AsStringView()));
1219         }
1220       }
1221     } else if (fxv8::IsObject(arg)) {
1222       results.push_back(GetObjectDefaultValue(pIsolate, arg.As<v8::Object>()));
1223     } else {
1224       results.push_back(arg);
1225     }
1226   }
1227   return results;
1228 }
1229 
1230 // Returns empty value on failure.
GetObjectForName(CFXJSE_HostObject * pHostObject,ByteStringView bsAccessorName)1231 v8::Local<v8::Value> GetObjectForName(CFXJSE_HostObject* pHostObject,
1232                                       ByteStringView bsAccessorName) {
1233   CXFA_Document* pDoc = ToFormCalcContext(pHostObject)->GetDocument();
1234   if (!pDoc)
1235     return v8::Local<v8::Value>();
1236 
1237   CFXJSE_Engine* pScriptContext = pDoc->GetScriptContext();
1238   std::optional<CFXJSE_Engine::ResolveResult> maybeResult =
1239       pScriptContext->ResolveObjects(
1240           pScriptContext->GetThisObject(),
1241           WideString::FromUTF8(bsAccessorName).AsStringView(),
1242           Mask<XFA_ResolveFlag>{
1243               XFA_ResolveFlag::kChildren, XFA_ResolveFlag::kProperties,
1244               XFA_ResolveFlag::kSiblings, XFA_ResolveFlag::kParent});
1245   if (!maybeResult.has_value() ||
1246       maybeResult.value().type != CFXJSE_Engine::ResolveResult::Type::kNodes ||
1247       maybeResult.value().objects.empty()) {
1248     return v8::Local<v8::Value>();
1249   }
1250   return pScriptContext->GetOrCreateJSBindingFromMap(
1251       maybeResult.value().objects.front().Get());
1252 }
1253 
ResolveObjects(CFXJSE_HostObject * pHostObject,v8::Local<v8::Value> pRefValue,ByteStringView bsSomExp,bool bDotAccessor,bool bHasNoResolveName)1254 std::optional<CFXJSE_Engine::ResolveResult> ResolveObjects(
1255     CFXJSE_HostObject* pHostObject,
1256     v8::Local<v8::Value> pRefValue,
1257     ByteStringView bsSomExp,
1258     bool bDotAccessor,
1259     bool bHasNoResolveName) {
1260   CXFA_Document* pDoc = ToFormCalcContext(pHostObject)->GetDocument();
1261   if (!pDoc)
1262     return std::nullopt;
1263 
1264   v8::Isolate* pIsolate = ToFormCalcContext(pHostObject)->GetIsolate();
1265   WideString wsSomExpression = WideString::FromUTF8(bsSomExp);
1266   CFXJSE_Engine* pScriptContext = pDoc->GetScriptContext();
1267   CXFA_Object* pNode = nullptr;
1268   Mask<XFA_ResolveFlag> dwFlags;
1269   if (bDotAccessor) {
1270     if (fxv8::IsNull(pRefValue)) {
1271       pNode = pScriptContext->GetThisObject();
1272       dwFlags = {XFA_ResolveFlag::kSiblings, XFA_ResolveFlag::kParent};
1273     } else {
1274       pNode = CFXJSE_Engine::ToObject(pIsolate, pRefValue);
1275       if (!pNode)
1276         return std::nullopt;
1277 
1278       if (bHasNoResolveName) {
1279         WideString wsName;
1280         if (CXFA_Node* pXFANode = pNode->AsNode()) {
1281           std::optional<WideString> ret =
1282               pXFANode->JSObject()->TryAttribute(XFA_Attribute::Name, false);
1283           if (ret.has_value())
1284             wsName = ret.value();
1285         }
1286         if (wsName.IsEmpty())
1287           wsName = L"#" + WideString::FromASCII(pNode->GetClassName());
1288 
1289         wsSomExpression = wsName + wsSomExpression;
1290         dwFlags = XFA_ResolveFlag::kSiblings;
1291       } else {
1292         dwFlags = (bsSomExp == "*")
1293                       ? Mask<XFA_ResolveFlag>{XFA_ResolveFlag::kChildren}
1294                       : Mask<XFA_ResolveFlag>{XFA_ResolveFlag::kChildren,
1295                                               XFA_ResolveFlag::kAttributes,
1296                                               XFA_ResolveFlag::kProperties};
1297       }
1298     }
1299   } else {
1300     pNode = CFXJSE_Engine::ToObject(pIsolate, pRefValue);
1301     dwFlags = XFA_ResolveFlag::kAnyChild;
1302   }
1303   return pScriptContext->ResolveObjects(pNode, wsSomExpression.AsStringView(),
1304                                         dwFlags);
1305 }
1306 
ParseResolveResult(CFXJSE_HostObject * pHostObject,const CFXJSE_Engine::ResolveResult & resolveNodeRS,v8::Local<v8::Value> pParentValue,bool * bAttribute)1307 v8::LocalVector<v8::Value> ParseResolveResult(
1308     CFXJSE_HostObject* pHostObject,
1309     const CFXJSE_Engine::ResolveResult& resolveNodeRS,
1310     v8::Local<v8::Value> pParentValue,
1311     bool* bAttribute) {
1312   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pHostObject);
1313   v8::Isolate* pIsolate = pContext->GetIsolate();
1314   v8::LocalVector<v8::Value> resultValues(pIsolate);
1315 
1316   if (resolveNodeRS.type == CFXJSE_Engine::ResolveResult::Type::kNodes) {
1317     *bAttribute = false;
1318     CFXJSE_Engine* pScriptContext = pContext->GetDocument()->GetScriptContext();
1319     for (auto& pObject : resolveNodeRS.objects) {
1320       resultValues.push_back(
1321           pScriptContext->GetOrCreateJSBindingFromMap(pObject.Get()));
1322     }
1323     return resultValues;
1324   }
1325 
1326   *bAttribute = true;
1327   if (resolveNodeRS.script_attribute.callback &&
1328       resolveNodeRS.script_attribute.eValueType == XFA_ScriptType::Object) {
1329     for (auto& pObject : resolveNodeRS.objects) {
1330       v8::Local<v8::Value> pValue;
1331       CJX_Object* jsObject = pObject->JSObject();
1332       (*resolveNodeRS.script_attribute.callback)(
1333           pIsolate, jsObject, &pValue, false,
1334           resolveNodeRS.script_attribute.attribute);
1335       resultValues.push_back(pValue);
1336       *bAttribute = false;
1337     }
1338   }
1339   if (*bAttribute && fxv8::IsObject(pParentValue))
1340     resultValues.push_back(pParentValue);
1341 
1342   return resultValues;
1343 }
1344 
1345 // Returns 0 if the provided `arg` is an invalid payment period count.
GetValidatedPaymentPeriods(v8::Isolate * isolate,v8::Local<v8::Value> arg)1346 int GetValidatedPaymentPeriods(v8::Isolate* isolate, v8::Local<v8::Value> arg) {
1347   double periods = ValueToDouble(isolate, arg);
1348   if (periods < 1 ||
1349       periods > static_cast<double>(std::numeric_limits<int32_t>::max())) {
1350     return 0;
1351   }
1352 
1353   return static_cast<int>(periods);
1354 }
1355 
1356 }  // namespace
1357 
1358 const FXJSE_CLASS_DESCRIPTOR kFormCalcDescriptor = {
1359     kClassTag,                      // tag
1360     "XFA_FormCalcClass",            // name
1361     kFormCalcFunctions,             // methods
1362     nullptr,                        // dynamic prop type
1363     nullptr,                        // dynamic prop getter
1364     nullptr,                        // dynamic prop setter
1365     nullptr,                        // dynamic prop method call
1366 };
1367 
1368 // static
Abs(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1369 void CFXJSE_FormCalcContext::Abs(
1370     CFXJSE_HostObject* pThis,
1371     const v8::FunctionCallbackInfo<v8::Value>& info) {
1372   if (info.Length() != 1) {
1373     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Abs");
1374     return;
1375   }
1376 
1377   if (ValueIsNull(info.GetIsolate(), info[0])) {
1378     info.GetReturnValue().SetNull();
1379     return;
1380   }
1381   double dValue = ValueToDouble(info.GetIsolate(), info[0]);
1382   if (dValue < 0)
1383     dValue = -dValue;
1384 
1385   info.GetReturnValue().Set(dValue);
1386 }
1387 
1388 // static
Avg(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1389 void CFXJSE_FormCalcContext::Avg(
1390     CFXJSE_HostObject* pThis,
1391     const v8::FunctionCallbackInfo<v8::Value>& info) {
1392   uint32_t uCount = 0;
1393   double dSum = 0.0;
1394   auto fn = [&uCount, &dSum](v8::Isolate* pIsolate,
1395                              v8::Local<v8::Value> pValue) {
1396     dSum += ValueToDouble(pIsolate, pValue);
1397     uCount++;
1398   };
1399   if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/false))
1400     return;
1401 
1402   if (uCount == 0) {
1403     info.GetReturnValue().SetNull();
1404     return;
1405   }
1406   info.GetReturnValue().Set(dSum / uCount);
1407 }
1408 
1409 // static
Ceil(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1410 void CFXJSE_FormCalcContext::Ceil(
1411     CFXJSE_HostObject* pThis,
1412     const v8::FunctionCallbackInfo<v8::Value>& info) {
1413   if (info.Length() != 1) {
1414     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Ceil");
1415     return;
1416   }
1417 
1418   v8::Local<v8::Value> argValue = GetSimpleValue(info, 0);
1419   if (ValueIsNull(info.GetIsolate(), argValue)) {
1420     info.GetReturnValue().SetNull();
1421     return;
1422   }
1423 
1424   info.GetReturnValue().Set(ceil(ValueToFloat(info.GetIsolate(), argValue)));
1425 }
1426 
1427 // static
Count(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1428 void CFXJSE_FormCalcContext::Count(
1429     CFXJSE_HostObject* pThis,
1430     const v8::FunctionCallbackInfo<v8::Value>& info) {
1431   uint32_t iCount = 0;
1432   auto fn = [&iCount](v8::Isolate* pIsolate, v8::Local<v8::Value> pvalue) {
1433     ++iCount;
1434   };
1435   if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true))
1436     return;
1437 
1438   info.GetReturnValue().Set(iCount);
1439 }
1440 
1441 // static
Floor(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1442 void CFXJSE_FormCalcContext::Floor(
1443     CFXJSE_HostObject* pThis,
1444     const v8::FunctionCallbackInfo<v8::Value>& info) {
1445   if (info.Length() != 1) {
1446     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Floor");
1447     return;
1448   }
1449 
1450   v8::Local<v8::Value> argValue = GetSimpleValue(info, 0);
1451   if (ValueIsNull(info.GetIsolate(), argValue)) {
1452     info.GetReturnValue().SetNull();
1453     return;
1454   }
1455 
1456   info.GetReturnValue().Set(floor(ValueToFloat(info.GetIsolate(), argValue)));
1457 }
1458 
1459 // static
Max(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1460 void CFXJSE_FormCalcContext::Max(
1461     CFXJSE_HostObject* pThis,
1462     const v8::FunctionCallbackInfo<v8::Value>& info) {
1463   uint32_t uCount = 0;
1464   double dMaxValue = 0.0;
1465   auto fn = [&uCount, &dMaxValue](v8::Isolate* pIsolate,
1466                                   v8::Local<v8::Value> pValue) {
1467     ++uCount;
1468     double dValue = ValueToDouble(pIsolate, pValue);
1469     dMaxValue = uCount == 1 ? dValue : std::max(dMaxValue, dValue);
1470   };
1471   if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true))
1472     return;
1473 
1474   if (uCount == 0) {
1475     info.GetReturnValue().SetNull();
1476     return;
1477   }
1478   info.GetReturnValue().Set(dMaxValue);
1479 }
1480 
1481 // static
Min(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1482 void CFXJSE_FormCalcContext::Min(
1483     CFXJSE_HostObject* pThis,
1484     const v8::FunctionCallbackInfo<v8::Value>& info) {
1485   uint32_t uCount = 0;
1486   double dMinValue = 0.0;
1487   auto fn = [&uCount, &dMinValue](v8::Isolate* pIsolate,
1488                                   v8::Local<v8::Value> pValue) {
1489     ++uCount;
1490     double dValue = ValueToDouble(pIsolate, pValue);
1491     dMinValue = uCount == 1 ? dValue : std::min(dMinValue, dValue);
1492   };
1493   if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true))
1494     return;
1495 
1496   if (uCount == 0) {
1497     info.GetReturnValue().SetNull();
1498     return;
1499   }
1500   info.GetReturnValue().Set(dMinValue);
1501 }
1502 
1503 // static
Mod(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1504 void CFXJSE_FormCalcContext::Mod(
1505     CFXJSE_HostObject* pThis,
1506     const v8::FunctionCallbackInfo<v8::Value>& info) {
1507   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
1508   if (info.Length() != 2) {
1509     pContext->ThrowParamCountMismatchException("Mod");
1510     return;
1511   }
1512 
1513   if (fxv8::IsNull(info[0]) || fxv8::IsNull(info[1])) {
1514     info.GetReturnValue().SetNull();
1515     return;
1516   }
1517 
1518   std::optional<double> maybe_dividend =
1519       ExtractDouble(info.GetIsolate(), info[0]);
1520   std::optional<double> maybe_divisor =
1521       ExtractDouble(info.GetIsolate(), info[1]);
1522   if (!maybe_dividend.has_value() || !maybe_divisor.has_value()) {
1523     pContext->ThrowArgumentMismatchException();
1524     return;
1525   }
1526 
1527   double dividend = maybe_dividend.value();
1528   double divisor = maybe_divisor.value();
1529   if (divisor == 0.0) {
1530     pContext->ThrowDivideByZeroException();
1531     return;
1532   }
1533 
1534   info.GetReturnValue().Set(dividend -
1535                             divisor * static_cast<int32_t>(dividend / divisor));
1536 }
1537 
1538 // static
Round(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1539 void CFXJSE_FormCalcContext::Round(
1540     CFXJSE_HostObject* pThis,
1541     const v8::FunctionCallbackInfo<v8::Value>& info) {
1542   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
1543   int32_t argc = info.Length();
1544   if (argc < 1 || argc > 2) {
1545     pContext->ThrowParamCountMismatchException("Round");
1546     return;
1547   }
1548 
1549   if (fxv8::IsNull(info[0])) {
1550     info.GetReturnValue().SetNull();
1551     return;
1552   }
1553 
1554   std::optional<double> maybe_value = ExtractDouble(info.GetIsolate(), info[0]);
1555   if (!maybe_value.has_value()) {
1556     pContext->ThrowArgumentMismatchException();
1557     return;
1558   }
1559 
1560   double dValue = maybe_value.value();
1561   uint8_t uPrecision = 0;
1562   if (argc > 1) {
1563     if (fxv8::IsNull(info[1])) {
1564       info.GetReturnValue().SetNull();
1565       return;
1566     }
1567     std::optional<double> maybe_precision =
1568         ExtractDouble(info.GetIsolate(), info[1]);
1569     if (!maybe_precision.has_value()) {
1570       pContext->ThrowArgumentMismatchException();
1571       return;
1572     }
1573     double dPrecision = maybe_precision.value();
1574     uPrecision = static_cast<uint8_t>(std::clamp(dPrecision, 0.0, 12.0));
1575   }
1576 
1577   CFGAS_Decimal decimalValue(static_cast<float>(dValue), uPrecision);
1578   info.GetReturnValue().Set(decimalValue.ToDouble());
1579 }
1580 
1581 // static
Sum(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1582 void CFXJSE_FormCalcContext::Sum(
1583     CFXJSE_HostObject* pThis,
1584     const v8::FunctionCallbackInfo<v8::Value>& info) {
1585   uint32_t uCount = 0;
1586   double dSum = 0.0;
1587   auto fn = [&uCount, &dSum](v8::Isolate* pIsolate,
1588                              v8::Local<v8::Value> pValue) {
1589     ++uCount;
1590     dSum += ValueToDouble(pIsolate, pValue);
1591   };
1592   if (!ToFormCalcContext(pThis)->ApplyToExpansion(fn, info, /*bStrict=*/true))
1593     return;
1594 
1595   if (uCount == 0) {
1596     info.GetReturnValue().SetNull();
1597     return;
1598   }
1599   info.GetReturnValue().Set(dSum);
1600 }
1601 
1602 // static
Date(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1603 void CFXJSE_FormCalcContext::Date(
1604     CFXJSE_HostObject* pThis,
1605     const v8::FunctionCallbackInfo<v8::Value>& info) {
1606   if (info.Length() != 0) {
1607     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Date");
1608     return;
1609   }
1610 
1611   time_t currentTime;
1612   FXSYS_time(&currentTime);
1613   struct tm* pTmStruct = gmtime(&currentTime);
1614 
1615   info.GetReturnValue().Set(DateString2Num(
1616       ByteString::Format("%d%02d%02d", pTmStruct->tm_year + 1900,
1617                          pTmStruct->tm_mon + 1, pTmStruct->tm_mday)
1618           .AsStringView()));
1619 }
1620 
1621 // static
Date2Num(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1622 void CFXJSE_FormCalcContext::Date2Num(
1623     CFXJSE_HostObject* pThis,
1624     const v8::FunctionCallbackInfo<v8::Value>& info) {
1625   int32_t argc = info.Length();
1626   if (argc < 1 || argc > 3) {
1627     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Date2Num");
1628     return;
1629   }
1630 
1631   v8::Local<v8::Value> dateValue = GetSimpleValue(info, 0);
1632   if (ValueIsNull(info.GetIsolate(), dateValue)) {
1633     info.GetReturnValue().SetNull();
1634     return;
1635   }
1636 
1637   ByteString bsDate = ValueToUTF8String(info.GetIsolate(), dateValue);
1638   ByteString bsFormat;
1639   if (argc > 1) {
1640     v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
1641     if (ValueIsNull(info.GetIsolate(), formatValue)) {
1642       info.GetReturnValue().SetNull();
1643       return;
1644     }
1645     bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
1646   }
1647 
1648   ByteString bsLocale;
1649   if (argc > 2) {
1650     v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
1651     if (ValueIsNull(info.GetIsolate(), localeValue)) {
1652       info.GetReturnValue().SetNull();
1653       return;
1654     }
1655     bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
1656   }
1657 
1658   ByteString bsIsoDate =
1659       Local2IsoDate(pThis, bsDate.AsStringView(), bsFormat.AsStringView(),
1660                     bsLocale.AsStringView());
1661   info.GetReturnValue().Set(DateString2Num(bsIsoDate.AsStringView()));
1662 }
1663 
1664 // static
DateFmt(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1665 void CFXJSE_FormCalcContext::DateFmt(
1666     CFXJSE_HostObject* pThis,
1667     const v8::FunctionCallbackInfo<v8::Value>& info) {
1668   int32_t argc = info.Length();
1669   if (argc > 2) {
1670     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Date2Num");
1671     return;
1672   }
1673 
1674   int32_t iStyle = 0;
1675   if (argc > 0) {
1676     v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
1677     if (fxv8::IsNull(infotyle)) {
1678       info.GetReturnValue().SetNull();
1679       return;
1680     }
1681 
1682     iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
1683     if (iStyle < 0 || iStyle > 4)
1684       iStyle = 0;
1685   }
1686 
1687   ByteString bsLocale;
1688   if (argc > 1) {
1689     v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
1690     if (fxv8::IsNull(argLocale)) {
1691       info.GetReturnValue().SetNull();
1692       return;
1693     }
1694     bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
1695   }
1696 
1697   ByteString bsFormat =
1698       GetStandardDateFormat(pThis, iStyle, bsLocale.AsStringView());
1699   info.GetReturnValue().Set(
1700       fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
1701 }
1702 
1703 // static
IsoDate2Num(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1704 void CFXJSE_FormCalcContext::IsoDate2Num(
1705     CFXJSE_HostObject* pThis,
1706     const v8::FunctionCallbackInfo<v8::Value>& info) {
1707   if (info.Length() != 1) {
1708     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("IsoDate2Num");
1709     return;
1710   }
1711   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
1712   if (fxv8::IsNull(argOne)) {
1713     info.GetReturnValue().SetNull();
1714     return;
1715   }
1716   ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
1717   info.GetReturnValue().Set(DateString2Num(bsArg.AsStringView()));
1718 }
1719 
1720 // static
IsoTime2Num(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1721 void CFXJSE_FormCalcContext::IsoTime2Num(
1722     CFXJSE_HostObject* pThis,
1723     const v8::FunctionCallbackInfo<v8::Value>& info) {
1724   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
1725   if (info.Length() != 1) {
1726     pContext->ThrowParamCountMismatchException("IsoTime2Num");
1727     return;
1728   }
1729 
1730   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
1731   if (ValueIsNull(info.GetIsolate(), argOne)) {
1732     info.GetReturnValue().SetNull();
1733     return;
1734   }
1735 
1736   CXFA_Document* pDoc = pContext->GetDocument();
1737   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
1738   ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
1739   auto pos = bsArg.Find('T', 0);
1740   if (!pos.has_value() || pos.value() == bsArg.GetLength() - 1) {
1741     info.GetReturnValue().Set(0);
1742     return;
1743   }
1744   bsArg = bsArg.Last(bsArg.GetLength() - (pos.value() + 1));
1745 
1746   CXFA_LocaleValue timeValue(CXFA_LocaleValue::ValueType::kTime,
1747                              WideString::FromUTF8(bsArg.AsStringView()), pMgr);
1748   if (!timeValue.IsValid()) {
1749     info.GetReturnValue().Set(0);
1750     return;
1751   }
1752 
1753   CFX_DateTime uniTime = timeValue.GetTime();
1754   int32_t hour = uniTime.GetHour();
1755   int32_t min = uniTime.GetMinute();
1756   int32_t second = uniTime.GetSecond();
1757   int32_t milSecond = uniTime.GetMillisecond();
1758 
1759   // TODO(dsinclair): See if there is other time conversion code in pdfium and
1760   //   consolidate.
1761   int32_t mins = hour * 60 + min;
1762   mins -= pMgr->GetDefLocale()->GetTimeZoneInMinutes();
1763   while (mins > 1440)
1764     mins -= 1440;
1765   while (mins < 0)
1766     mins += 1440;
1767   hour = mins / 60;
1768   min = mins % 60;
1769 
1770   info.GetReturnValue().Set(hour * 3600000 + min * 60000 + second * 1000 +
1771                             milSecond + 1);
1772 }
1773 
1774 // static
LocalDateFmt(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1775 void CFXJSE_FormCalcContext::LocalDateFmt(
1776     CFXJSE_HostObject* pThis,
1777     const v8::FunctionCallbackInfo<v8::Value>& info) {
1778   int32_t argc = info.Length();
1779   if (argc > 2) {
1780     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("LocalDateFmt");
1781     return;
1782   }
1783 
1784   int32_t iStyle = 0;
1785   if (argc > 0) {
1786     v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
1787     if (fxv8::IsNull(infotyle)) {
1788       info.GetReturnValue().SetNull();
1789       return;
1790     }
1791     iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
1792     if (iStyle > 4 || iStyle < 0)
1793       iStyle = 0;
1794   }
1795 
1796   ByteString bsLocale;
1797   if (argc > 1) {
1798     v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
1799     if (fxv8::IsNull(argLocale)) {
1800       info.GetReturnValue().SetNull();
1801       return;
1802     }
1803     bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
1804   }
1805 
1806   ByteString bsFormat =
1807       GetLocalDateFormat(pThis, iStyle, bsLocale.AsStringView(), false);
1808   info.GetReturnValue().Set(
1809       fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
1810 }
1811 
1812 // static
LocalTimeFmt(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1813 void CFXJSE_FormCalcContext::LocalTimeFmt(
1814     CFXJSE_HostObject* pThis,
1815     const v8::FunctionCallbackInfo<v8::Value>& info) {
1816   int32_t argc = info.Length();
1817   if (argc > 2) {
1818     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("LocalTimeFmt");
1819     return;
1820   }
1821 
1822   int32_t iStyle = 0;
1823   if (argc > 0) {
1824     v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
1825     if (fxv8::IsNull(infotyle)) {
1826       info.GetReturnValue().SetNull();
1827       return;
1828     }
1829     iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
1830     if (iStyle > 4 || iStyle < 0)
1831       iStyle = 0;
1832   }
1833 
1834   ByteString bsLocale;
1835   if (argc > 1) {
1836     v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
1837     if (fxv8::IsNull(argLocale)) {
1838       info.GetReturnValue().SetNull();
1839       return;
1840     }
1841     bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
1842   }
1843 
1844   ByteString bsFormat =
1845       GetLocalTimeFormat(pThis, iStyle, bsLocale.AsStringView(), false);
1846   info.GetReturnValue().Set(
1847       fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
1848 }
1849 
1850 // static
Num2Date(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1851 void CFXJSE_FormCalcContext::Num2Date(
1852     CFXJSE_HostObject* pThis,
1853     const v8::FunctionCallbackInfo<v8::Value>& info) {
1854   int32_t argc = info.Length();
1855   if (argc < 1 || argc > 3) {
1856     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Num2Date");
1857     return;
1858   }
1859 
1860   v8::Local<v8::Value> dateValue = GetSimpleValue(info, 0);
1861   if (ValueIsNull(info.GetIsolate(), dateValue)) {
1862     info.GetReturnValue().SetNull();
1863     return;
1864   }
1865   int32_t dDate =
1866       static_cast<int32_t>(ValueToFloat(info.GetIsolate(), dateValue));
1867   if (dDate < 1) {
1868     info.GetReturnValue().SetNull();
1869     return;
1870   }
1871 
1872   ByteString bsFormat;
1873   if (argc > 1) {
1874     v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
1875     if (ValueIsNull(info.GetIsolate(), formatValue)) {
1876       info.GetReturnValue().SetNull();
1877       return;
1878     }
1879     bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
1880   }
1881 
1882   ByteString bsLocale;
1883   if (argc > 2) {
1884     v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
1885     if (ValueIsNull(info.GetIsolate(), localeValue)) {
1886       info.GetReturnValue().SetNull();
1887       return;
1888     }
1889     bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
1890   }
1891 
1892   int32_t iYear = 1900;
1893   int32_t iMonth = 1;
1894   int32_t iDay = 1;
1895   int32_t i = 0;
1896   while (dDate > 0) {
1897     if (iMonth == 2) {
1898       if ((!((iYear + i) % 4) && ((iYear + i) % 100)) || !((iYear + i) % 400)) {
1899         if (dDate > 29) {
1900           ++iMonth;
1901           if (iMonth > 12) {
1902             iMonth = 1;
1903             ++i;
1904           }
1905           iDay = 1;
1906           dDate -= 29;
1907         } else {
1908           iDay += static_cast<int32_t>(dDate) - 1;
1909           dDate = 0;
1910         }
1911       } else {
1912         if (dDate > 28) {
1913           ++iMonth;
1914           if (iMonth > 12) {
1915             iMonth = 1;
1916             ++i;
1917           }
1918           iDay = 1;
1919           dDate -= 28;
1920         } else {
1921           iDay += static_cast<int32_t>(dDate) - 1;
1922           dDate = 0;
1923         }
1924       }
1925     } else if (iMonth < 8) {
1926       if ((iMonth % 2 == 0)) {
1927         if (dDate > 30) {
1928           ++iMonth;
1929           if (iMonth > 12) {
1930             iMonth = 1;
1931             ++i;
1932           }
1933           iDay = 1;
1934           dDate -= 30;
1935         } else {
1936           iDay += static_cast<int32_t>(dDate) - 1;
1937           dDate = 0;
1938         }
1939       } else {
1940         if (dDate > 31) {
1941           ++iMonth;
1942           if (iMonth > 12) {
1943             iMonth = 1;
1944             ++i;
1945           }
1946           iDay = 1;
1947           dDate -= 31;
1948         } else {
1949           iDay += static_cast<int32_t>(dDate) - 1;
1950           dDate = 0;
1951         }
1952       }
1953     } else {
1954       if (iMonth % 2 != 0) {
1955         if (dDate > 30) {
1956           ++iMonth;
1957           if (iMonth > 12) {
1958             iMonth = 1;
1959             ++i;
1960           }
1961           iDay = 1;
1962           dDate -= 30;
1963         } else {
1964           iDay += static_cast<int32_t>(dDate) - 1;
1965           dDate = 0;
1966         }
1967       } else {
1968         if (dDate > 31) {
1969           ++iMonth;
1970           if (iMonth > 12) {
1971             iMonth = 1;
1972             ++i;
1973           }
1974           iDay = 1;
1975           dDate -= 31;
1976         } else {
1977           iDay += static_cast<int32_t>(dDate) - 1;
1978           dDate = 0;
1979         }
1980       }
1981     }
1982   }
1983 
1984   ByteString bsLocalDate = IsoDate2Local(
1985       pThis,
1986       ByteString::Format("%d%02d%02d", iYear + i, iMonth, iDay).AsStringView(),
1987       bsFormat.AsStringView(), bsLocale.AsStringView());
1988   info.GetReturnValue().Set(
1989       fxv8::NewStringHelper(info.GetIsolate(), bsLocalDate.AsStringView()));
1990 }
1991 
1992 // static
Num2GMTime(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)1993 void CFXJSE_FormCalcContext::Num2GMTime(
1994     CFXJSE_HostObject* pThis,
1995     const v8::FunctionCallbackInfo<v8::Value>& info) {
1996   int32_t argc = info.Length();
1997   if (argc < 1 || argc > 3) {
1998     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Num2GMTime");
1999     return;
2000   }
2001 
2002   v8::Local<v8::Value> timeValue = GetSimpleValue(info, 0);
2003   if (fxv8::IsNull(timeValue)) {
2004     info.GetReturnValue().SetNull();
2005     return;
2006   }
2007   int32_t iTime =
2008       static_cast<int32_t>(ValueToFloat(info.GetIsolate(), timeValue));
2009   if (abs(iTime) < 1.0) {
2010     info.GetReturnValue().SetNull();
2011     return;
2012   }
2013 
2014   ByteString bsFormat;
2015   if (argc > 1) {
2016     v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
2017     if (fxv8::IsNull(formatValue)) {
2018       info.GetReturnValue().SetNull();
2019       return;
2020     }
2021     bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
2022   }
2023 
2024   ByteString bsLocale;
2025   if (argc > 2) {
2026     v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
2027     if (fxv8::IsNull(localeValue)) {
2028       info.GetReturnValue().SetNull();
2029       return;
2030     }
2031     bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
2032   }
2033 
2034   ByteString bsGMTTime = Num2AllTime(pThis, iTime, bsFormat.AsStringView(),
2035                                      bsLocale.AsStringView(), true);
2036   info.GetReturnValue().Set(
2037       fxv8::NewStringHelper(info.GetIsolate(), bsGMTTime.AsStringView()));
2038 }
2039 
2040 // static
Num2Time(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2041 void CFXJSE_FormCalcContext::Num2Time(
2042     CFXJSE_HostObject* pThis,
2043     const v8::FunctionCallbackInfo<v8::Value>& info) {
2044   int32_t argc = info.Length();
2045   if (argc < 1 || argc > 3) {
2046     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Num2Time");
2047     return;
2048   }
2049 
2050   v8::Local<v8::Value> timeValue = GetSimpleValue(info, 0);
2051   if (fxv8::IsNull(timeValue)) {
2052     info.GetReturnValue().SetNull();
2053     return;
2054   }
2055   float fTime = ValueToFloat(info.GetIsolate(), timeValue);
2056   if (fabs(fTime) < 1.0) {
2057     info.GetReturnValue().SetNull();
2058     return;
2059   }
2060 
2061   ByteString bsFormat;
2062   if (argc > 1) {
2063     v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
2064     if (fxv8::IsNull(formatValue)) {
2065       info.GetReturnValue().SetNull();
2066       return;
2067     }
2068     bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
2069   }
2070 
2071   ByteString bsLocale;
2072   if (argc > 2) {
2073     v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
2074     if (fxv8::IsNull(localeValue)) {
2075       info.GetReturnValue().SetNull();
2076       return;
2077     }
2078     bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
2079   }
2080 
2081   ByteString bsLocalTime =
2082       Num2AllTime(pThis, static_cast<int32_t>(fTime), bsFormat.AsStringView(),
2083                   bsLocale.AsStringView(), false);
2084   info.GetReturnValue().Set(
2085       fxv8::NewStringHelper(info.GetIsolate(), bsLocalTime.AsStringView()));
2086 }
2087 
2088 // static
Time(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2089 void CFXJSE_FormCalcContext::Time(
2090     CFXJSE_HostObject* pThis,
2091     const v8::FunctionCallbackInfo<v8::Value>& info) {
2092   if (info.Length() != 0) {
2093     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Time");
2094     return;
2095   }
2096 
2097   time_t now;
2098   FXSYS_time(&now);
2099   struct tm* pGmt = gmtime(&now);
2100   info.GetReturnValue().Set(
2101       (pGmt->tm_hour * 3600 + pGmt->tm_min * 60 + pGmt->tm_sec) * 1000);
2102 }
2103 
2104 // static
Time2Num(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2105 void CFXJSE_FormCalcContext::Time2Num(
2106     CFXJSE_HostObject* pThis,
2107     const v8::FunctionCallbackInfo<v8::Value>& info) {
2108   int32_t argc = info.Length();
2109   if (argc < 1 || argc > 3) {
2110     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Time2Num");
2111     return;
2112   }
2113 
2114   ByteString bsTime;
2115   v8::Local<v8::Value> timeValue = GetSimpleValue(info, 0);
2116   if (ValueIsNull(info.GetIsolate(), timeValue)) {
2117     info.GetReturnValue().SetNull();
2118     return;
2119   }
2120   bsTime = ValueToUTF8String(info.GetIsolate(), timeValue);
2121 
2122   ByteString bsFormat;
2123   if (argc > 1) {
2124     v8::Local<v8::Value> formatValue = GetSimpleValue(info, 1);
2125     if (ValueIsNull(info.GetIsolate(), formatValue)) {
2126       info.GetReturnValue().SetNull();
2127       return;
2128     }
2129     bsFormat = ValueToUTF8String(info.GetIsolate(), formatValue);
2130   }
2131 
2132   ByteString bsLocale;
2133   if (argc > 2) {
2134     v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
2135     if (ValueIsNull(info.GetIsolate(), localeValue)) {
2136       info.GetReturnValue().SetNull();
2137       return;
2138     }
2139     bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
2140   }
2141 
2142   CXFA_Document* pDoc = ToFormCalcContext(pThis)->GetDocument();
2143   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
2144   GCedLocaleIface* pLocale = nullptr;
2145   if (!bsLocale.IsEmpty()) {
2146     pLocale =
2147         pMgr->GetLocaleByName(WideString::FromUTF8(bsLocale.AsStringView()));
2148   }
2149   if (!pLocale) {
2150     CXFA_Node* pThisNode = ToNode(pDoc->GetScriptContext()->GetThisObject());
2151     pLocale = pThisNode->GetLocale();
2152   }
2153 
2154   WideString wsFormat;
2155   if (bsFormat.IsEmpty()) {
2156     wsFormat =
2157         pLocale->GetTimePattern(LocaleIface::DateTimeSubcategory::kDefault);
2158   } else {
2159     wsFormat = WideString::FromUTF8(bsFormat.AsStringView());
2160   }
2161   wsFormat = L"time{" + wsFormat + L"}";
2162   CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kTime,
2163                                WideString::FromUTF8(bsTime.AsStringView()),
2164                                wsFormat, pLocale, pMgr);
2165   if (!localeValue.IsValid()) {
2166     info.GetReturnValue().Set(0);
2167     return;
2168   }
2169 
2170   CFX_DateTime uniTime = localeValue.GetTime();
2171   int32_t hour = uniTime.GetHour();
2172   int32_t minute = uniTime.GetMinute();
2173   const int32_t second = uniTime.GetSecond();
2174   const int32_t millisecond = uniTime.GetMillisecond();
2175 
2176   constexpr int kMinutesInDay = 24 * 60;
2177   int32_t minutes_with_tz =
2178       hour * 60 + minute - CXFA_TimeZoneProvider().GetTimeZoneInMinutes();
2179   minutes_with_tz %= kMinutesInDay;
2180   if (minutes_with_tz < 0)
2181     minutes_with_tz += kMinutesInDay;
2182 
2183   hour = minutes_with_tz / 60;
2184   minute = minutes_with_tz % 60;
2185   info.GetReturnValue().Set(hour * 3600000 + minute * 60000 + second * 1000 +
2186                             millisecond + 1);
2187 }
2188 
2189 // static
TimeFmt(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2190 void CFXJSE_FormCalcContext::TimeFmt(
2191     CFXJSE_HostObject* pThis,
2192     const v8::FunctionCallbackInfo<v8::Value>& info) {
2193   int32_t argc = info.Length();
2194   if (argc > 2) {
2195     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("TimeFmt");
2196     return;
2197   }
2198 
2199   int32_t iStyle = 0;
2200   if (argc > 0) {
2201     v8::Local<v8::Value> infotyle = GetSimpleValue(info, 0);
2202     if (fxv8::IsNull(infotyle)) {
2203       info.GetReturnValue().SetNull();
2204       return;
2205     }
2206     iStyle = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), infotyle));
2207     if (iStyle > 4 || iStyle < 0)
2208       iStyle = 0;
2209   }
2210 
2211   ByteString bsLocale;
2212   if (argc > 1) {
2213     v8::Local<v8::Value> argLocale = GetSimpleValue(info, 1);
2214     if (fxv8::IsNull(argLocale)) {
2215       info.GetReturnValue().SetNull();
2216       return;
2217     }
2218     bsLocale = ValueToUTF8String(info.GetIsolate(), argLocale);
2219   }
2220 
2221   ByteString bsFormat =
2222       GetStandardTimeFormat(pThis, iStyle, bsLocale.AsStringView());
2223   info.GetReturnValue().Set(
2224       fxv8::NewStringHelper(info.GetIsolate(), bsFormat.AsStringView()));
2225 }
2226 
2227 // static
Local2IsoDate(CFXJSE_HostObject * pThis,ByteStringView bsDate,ByteStringView bsFormat,ByteStringView bsLocale)2228 ByteString CFXJSE_FormCalcContext::Local2IsoDate(CFXJSE_HostObject* pThis,
2229                                                  ByteStringView bsDate,
2230                                                  ByteStringView bsFormat,
2231                                                  ByteStringView bsLocale) {
2232   CXFA_Document* pDoc = ToFormCalcContext(pThis)->GetDocument();
2233   if (!pDoc)
2234     return ByteString();
2235 
2236   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
2237   GCedLocaleIface* pLocale = LocaleFromString(pDoc, pMgr, bsLocale);
2238   if (!pLocale)
2239     return ByteString();
2240 
2241   WideString wsFormat = FormatFromString(pLocale, bsFormat);
2242   CFX_DateTime dt =
2243       CXFA_LocaleValue(CXFA_LocaleValue::ValueType::kDate,
2244                        WideString::FromUTF8(bsDate), wsFormat, pLocale, pMgr)
2245           .GetDate();
2246 
2247   return ByteString::Format("%4d-%02d-%02d", dt.GetYear(), dt.GetMonth(),
2248                             dt.GetDay());
2249 }
2250 
2251 // static
IsoDate2Local(CFXJSE_HostObject * pThis,ByteStringView bsDate,ByteStringView bsFormat,ByteStringView bsLocale)2252 ByteString CFXJSE_FormCalcContext::IsoDate2Local(CFXJSE_HostObject* pThis,
2253                                                  ByteStringView bsDate,
2254                                                  ByteStringView bsFormat,
2255                                                  ByteStringView bsLocale) {
2256   CXFA_Document* pDoc = ToFormCalcContext(pThis)->GetDocument();
2257   if (!pDoc)
2258     return ByteString();
2259 
2260   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
2261   GCedLocaleIface* pLocale = LocaleFromString(pDoc, pMgr, bsLocale);
2262   if (!pLocale)
2263     return ByteString();
2264 
2265   WideString wsFormat = FormatFromString(pLocale, bsFormat);
2266   WideString wsRet;
2267   CXFA_LocaleValue(CXFA_LocaleValue::ValueType::kDate,
2268                    WideString::FromUTF8(bsDate), pMgr)
2269       .FormatPatterns(wsRet, wsFormat, pLocale, XFA_ValuePicture::kDisplay);
2270   return wsRet.ToUTF8();
2271 }
2272 
2273 // static
IsoTime2Local(CFXJSE_HostObject * pThis,ByteStringView bsTime,ByteStringView bsFormat,ByteStringView bsLocale)2274 ByteString CFXJSE_FormCalcContext::IsoTime2Local(CFXJSE_HostObject* pThis,
2275                                                  ByteStringView bsTime,
2276                                                  ByteStringView bsFormat,
2277                                                  ByteStringView bsLocale) {
2278   CXFA_Document* pDoc = ToFormCalcContext(pThis)->GetDocument();
2279   if (!pDoc)
2280     return ByteString();
2281 
2282   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
2283   GCedLocaleIface* pLocale = LocaleFromString(pDoc, pMgr, bsLocale);
2284   if (!pLocale)
2285     return ByteString();
2286 
2287   WideString wsFormat = {
2288       L"time{", FormatFromString(pLocale, bsFormat).AsStringView(), L"}"};
2289   CXFA_LocaleValue widgetValue(CXFA_LocaleValue::ValueType::kTime,
2290                                WideString::FromUTF8(bsTime), pMgr);
2291   WideString wsRet;
2292   widgetValue.FormatPatterns(wsRet, wsFormat, pLocale,
2293                              XFA_ValuePicture::kDisplay);
2294   return wsRet.ToUTF8();
2295 }
2296 
2297 // static
GetLocalDateFormat(CFXJSE_HostObject * pThis,int32_t iStyle,ByteStringView bsLocale,bool bStandard)2298 ByteString CFXJSE_FormCalcContext::GetLocalDateFormat(CFXJSE_HostObject* pThis,
2299                                                       int32_t iStyle,
2300                                                       ByteStringView bsLocale,
2301                                                       bool bStandard) {
2302   CXFA_Document* pDoc = ToFormCalcContext(pThis)->GetDocument();
2303   if (!pDoc)
2304     return ByteString();
2305 
2306   return GetLocalDateTimeFormat(pDoc, iStyle, bsLocale, bStandard,
2307                                 /*bIsDate=*/true);
2308 }
2309 
2310 // static
GetLocalTimeFormat(CFXJSE_HostObject * pThis,int32_t iStyle,ByteStringView bsLocale,bool bStandard)2311 ByteString CFXJSE_FormCalcContext::GetLocalTimeFormat(CFXJSE_HostObject* pThis,
2312                                                       int32_t iStyle,
2313                                                       ByteStringView bsLocale,
2314                                                       bool bStandard) {
2315   CXFA_Document* pDoc = ToFormCalcContext(pThis)->GetDocument();
2316   if (!pDoc)
2317     return ByteString();
2318 
2319   return GetLocalDateTimeFormat(pDoc, iStyle, bsLocale, bStandard,
2320                                 /*bIsDate=*/false);
2321 }
2322 
2323 // static
GetStandardDateFormat(CFXJSE_HostObject * pThis,int32_t iStyle,ByteStringView bsLocale)2324 ByteString CFXJSE_FormCalcContext::GetStandardDateFormat(
2325     CFXJSE_HostObject* pThis,
2326     int32_t iStyle,
2327     ByteStringView bsLocale) {
2328   return GetLocalDateFormat(pThis, iStyle, bsLocale, true);
2329 }
2330 
2331 // static
GetStandardTimeFormat(CFXJSE_HostObject * pThis,int32_t iStyle,ByteStringView bsLocale)2332 ByteString CFXJSE_FormCalcContext::GetStandardTimeFormat(
2333     CFXJSE_HostObject* pThis,
2334     int32_t iStyle,
2335     ByteStringView bsLocale) {
2336   return GetLocalTimeFormat(pThis, iStyle, bsLocale, true);
2337 }
2338 
2339 // static
Num2AllTime(CFXJSE_HostObject * pThis,int32_t iTime,ByteStringView bsFormat,ByteStringView bsLocale,bool bGM)2340 ByteString CFXJSE_FormCalcContext::Num2AllTime(CFXJSE_HostObject* pThis,
2341                                                int32_t iTime,
2342                                                ByteStringView bsFormat,
2343                                                ByteStringView bsLocale,
2344                                                bool bGM) {
2345   int32_t iHour = 0;
2346   int32_t iMin = 0;
2347   int32_t iSec = 0;
2348   iHour = static_cast<int>(iTime) / 3600000;
2349   iMin = (static_cast<int>(iTime) - iHour * 3600000) / 60000;
2350   iSec = (static_cast<int>(iTime) - iHour * 3600000 - iMin * 60000) / 1000;
2351 
2352   if (!bGM) {
2353     int32_t iZoneHour;
2354     int32_t iZoneMin;
2355     int32_t iZoneSec;
2356     GetLocalTimeZone(&iZoneHour, &iZoneMin, &iZoneSec);
2357     iHour += iZoneHour;
2358     iMin += iZoneMin;
2359     iSec += iZoneSec;
2360   }
2361 
2362   return IsoTime2Local(
2363       pThis,
2364       ByteString::Format("%02d:%02d:%02d", iHour, iMin, iSec).AsStringView(),
2365       bsFormat, bsLocale);
2366 }
2367 
2368 // static
Apr(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2369 void CFXJSE_FormCalcContext::Apr(
2370     CFXJSE_HostObject* pThis,
2371     const v8::FunctionCallbackInfo<v8::Value>& info) {
2372   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2373   if (info.Length() != 3) {
2374     pContext->ThrowParamCountMismatchException("Apr");
2375     return;
2376   }
2377 
2378   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2379   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2380   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2381   if (ValueIsNull(info.GetIsolate(), argOne) ||
2382       ValueIsNull(info.GetIsolate(), argTwo) ||
2383       ValueIsNull(info.GetIsolate(), argThree)) {
2384     info.GetReturnValue().SetNull();
2385     return;
2386   }
2387 
2388   double nPrincipal = ValueToDouble(info.GetIsolate(), argOne);
2389   double nPayment = ValueToDouble(info.GetIsolate(), argTwo);
2390   int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
2391   if (nPrincipal <= 0 || nPayment <= 0 || nPeriods == 0) {
2392     pContext->ThrowArgumentMismatchException();
2393     return;
2394   }
2395 
2396   double r = 2 * (nPeriods * nPayment - nPrincipal) / (nPeriods * nPrincipal);
2397   double nTemp = 1;
2398   for (int32_t i = 0; i < nPeriods; ++i)
2399     nTemp *= (1 + r);
2400 
2401   double nRet = r * nTemp / (nTemp - 1) - nPayment / nPrincipal;
2402   while (fabs(nRet) > kFinancialPrecision) {
2403     double nDerivative =
2404         ((nTemp + r * nPeriods * (nTemp / (1 + r))) * (nTemp - 1) -
2405          (r * nTemp * nPeriods * (nTemp / (1 + r)))) /
2406         ((nTemp - 1) * (nTemp - 1));
2407     if (nDerivative == 0) {
2408       info.GetReturnValue().SetNull();
2409       return;
2410     }
2411 
2412     r = r - nRet / nDerivative;
2413     nTemp = 1;
2414     for (int32_t i = 0; i < nPeriods; ++i) {
2415       nTemp *= (1 + r);
2416     }
2417     nRet = r * nTemp / (nTemp - 1) - nPayment / nPrincipal;
2418   }
2419   info.GetReturnValue().Set(r * 12);
2420 }
2421 
2422 // static
CTerm(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2423 void CFXJSE_FormCalcContext::CTerm(
2424     CFXJSE_HostObject* pThis,
2425     const v8::FunctionCallbackInfo<v8::Value>& info) {
2426   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2427   if (info.Length() != 3) {
2428     pContext->ThrowParamCountMismatchException("CTerm");
2429     return;
2430   }
2431 
2432   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2433   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2434   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2435   if (ValueIsNull(info.GetIsolate(), argOne) ||
2436       ValueIsNull(info.GetIsolate(), argTwo) ||
2437       ValueIsNull(info.GetIsolate(), argThree)) {
2438     info.GetReturnValue().SetNull();
2439     return;
2440   }
2441 
2442   float nRate = ValueToFloat(info.GetIsolate(), argOne);
2443   float nFutureValue = ValueToFloat(info.GetIsolate(), argTwo);
2444   float nInitAmount = ValueToFloat(info.GetIsolate(), argThree);
2445   if ((nRate <= 0) || (nFutureValue <= 0) || (nInitAmount <= 0)) {
2446     pContext->ThrowArgumentMismatchException();
2447     return;
2448   }
2449 
2450   info.GetReturnValue().Set(log((float)(nFutureValue / nInitAmount)) /
2451                             log((float)(1 + nRate)));
2452 }
2453 
2454 // static
FV(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2455 void CFXJSE_FormCalcContext::FV(
2456     CFXJSE_HostObject* pThis,
2457     const v8::FunctionCallbackInfo<v8::Value>& info) {
2458   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2459   if (info.Length() != 3) {
2460     pContext->ThrowParamCountMismatchException("FV");
2461     return;
2462   }
2463 
2464   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2465   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2466   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2467   if (ValueIsNull(info.GetIsolate(), argOne) ||
2468       ValueIsNull(info.GetIsolate(), argTwo) ||
2469       ValueIsNull(info.GetIsolate(), argThree)) {
2470     info.GetReturnValue().SetNull();
2471     return;
2472   }
2473 
2474   double nAmount = ValueToDouble(info.GetIsolate(), argOne);
2475   double nRate = ValueToDouble(info.GetIsolate(), argTwo);
2476   int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
2477   if (nAmount <= 0 || nRate < 0 || nPeriods == 0) {
2478     pContext->ThrowArgumentMismatchException();
2479     return;
2480   }
2481 
2482   double dResult = 0;
2483   if (nRate) {
2484     double nTemp = 1;
2485     for (int i = 0; i < nPeriods; ++i) {
2486       nTemp *= 1 + nRate;
2487     }
2488     dResult = nAmount * (nTemp - 1) / nRate;
2489   } else {
2490     dResult = nAmount * nPeriods;
2491   }
2492 
2493   info.GetReturnValue().Set(dResult);
2494 }
2495 
2496 // static
IPmt(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2497 void CFXJSE_FormCalcContext::IPmt(
2498     CFXJSE_HostObject* pThis,
2499     const v8::FunctionCallbackInfo<v8::Value>& info) {
2500   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2501   if (info.Length() != 5) {
2502     pContext->ThrowParamCountMismatchException("IPmt");
2503     return;
2504   }
2505 
2506   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2507   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2508   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2509   v8::Local<v8::Value> argFour = GetSimpleValue(info, 3);
2510   v8::Local<v8::Value> argFive = GetSimpleValue(info, 4);
2511   if (ValueIsNull(info.GetIsolate(), argOne) ||
2512       ValueIsNull(info.GetIsolate(), argTwo) ||
2513       ValueIsNull(info.GetIsolate(), argThree) ||
2514       ValueIsNull(info.GetIsolate(), argFour) ||
2515       ValueIsNull(info.GetIsolate(), argFive)) {
2516     info.GetReturnValue().SetNull();
2517     return;
2518   }
2519 
2520   float nPrincipalAmount = ValueToFloat(info.GetIsolate(), argOne);
2521   float nRate = ValueToFloat(info.GetIsolate(), argTwo);
2522   float nPayment = ValueToFloat(info.GetIsolate(), argThree);
2523   float nFirstMonth = ValueToFloat(info.GetIsolate(), argFour);
2524   float nNumberOfMonths = ValueToFloat(info.GetIsolate(), argFive);
2525   if ((nPrincipalAmount <= 0) || (nRate <= 0) || (nPayment <= 0) ||
2526       (nFirstMonth < 0) || (nNumberOfMonths < 0)) {
2527     pContext->ThrowArgumentMismatchException();
2528     return;
2529   }
2530 
2531   float nRateOfMonth = nRate / 12;
2532   int32_t iNums = static_cast<int32_t>(
2533       (log10((float)(nPayment / nPrincipalAmount)) -
2534        log10((float)(nPayment / nPrincipalAmount - nRateOfMonth))) /
2535       log10((float)(1 + nRateOfMonth)));
2536   int32_t iEnd =
2537       std::min(static_cast<int32_t>(nFirstMonth + nNumberOfMonths - 1), iNums);
2538 
2539   if (nPayment < nPrincipalAmount * nRateOfMonth) {
2540     info.GetReturnValue().Set(0);
2541     return;
2542   }
2543 
2544   int32_t i = 0;
2545   for (i = 0; i < nFirstMonth - 1; ++i)
2546     nPrincipalAmount -= nPayment - nPrincipalAmount * nRateOfMonth;
2547 
2548   float nSum = 0;
2549   for (; i < iEnd; ++i) {
2550     nSum += nPrincipalAmount * nRateOfMonth;
2551     nPrincipalAmount -= nPayment - nPrincipalAmount * nRateOfMonth;
2552   }
2553   info.GetReturnValue().Set(nSum);
2554 }
2555 
2556 // static
NPV(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2557 void CFXJSE_FormCalcContext::NPV(
2558     CFXJSE_HostObject* pThis,
2559     const v8::FunctionCallbackInfo<v8::Value>& info) {
2560   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2561   int32_t argc = info.Length();
2562   if (argc < 2) {
2563     pContext->ThrowParamCountMismatchException("NPV");
2564     return;
2565   }
2566 
2567   v8::Local<v8::Value> argValue = GetSimpleValue(info, 0);
2568   if (ValueIsNull(info.GetIsolate(), argValue)) {
2569     info.GetReturnValue().SetNull();
2570     return;
2571   }
2572 
2573   double nRate = ValueToDouble(info.GetIsolate(), argValue);
2574   if (nRate <= 0) {
2575     pContext->ThrowArgumentMismatchException();
2576     return;
2577   }
2578 
2579   std::vector<double> data;
2580   for (int32_t i = 1; i < argc; i++) {
2581     argValue = GetSimpleValue(info, i);
2582     if (ValueIsNull(info.GetIsolate(), argValue)) {
2583       info.GetReturnValue().SetNull();
2584       return;
2585     }
2586     data.push_back(ValueToDouble(info.GetIsolate(), argValue));
2587   }
2588 
2589   double nSum = 0;
2590   double nDivisor = 1.0 + nRate;
2591   while (!data.empty()) {
2592     nSum += data.back();
2593     nSum /= nDivisor;
2594     data.pop_back();
2595   }
2596   info.GetReturnValue().Set(nSum);
2597 }
2598 
2599 // static
Pmt(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2600 void CFXJSE_FormCalcContext::Pmt(
2601     CFXJSE_HostObject* pThis,
2602     const v8::FunctionCallbackInfo<v8::Value>& info) {
2603   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2604   if (info.Length() != 3) {
2605     pContext->ThrowParamCountMismatchException("Pmt");
2606     return;
2607   }
2608 
2609   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2610   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2611   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2612   if (ValueIsNull(info.GetIsolate(), argOne) ||
2613       ValueIsNull(info.GetIsolate(), argTwo) ||
2614       ValueIsNull(info.GetIsolate(), argThree)) {
2615     info.GetReturnValue().SetNull();
2616     return;
2617   }
2618 
2619   double nPrincipal = ValueToDouble(info.GetIsolate(), argOne);
2620   double nRate = ValueToDouble(info.GetIsolate(), argTwo);
2621   int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
2622   if (nPrincipal <= 0 || nRate <= 0 || nPeriods == 0) {
2623     pContext->ThrowArgumentMismatchException();
2624     return;
2625   }
2626 
2627   double nSum = pow(1.0 + nRate, nPeriods);
2628   info.GetReturnValue().Set((nPrincipal * nRate * nSum) / (nSum - 1));
2629 }
2630 
2631 // static
PPmt(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2632 void CFXJSE_FormCalcContext::PPmt(
2633     CFXJSE_HostObject* pThis,
2634     const v8::FunctionCallbackInfo<v8::Value>& info) {
2635   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2636   if (info.Length() != 5) {
2637     pContext->ThrowParamCountMismatchException("PPmt");
2638     return;
2639   }
2640 
2641   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2642   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2643   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2644   v8::Local<v8::Value> argFour = GetSimpleValue(info, 3);
2645   v8::Local<v8::Value> argFive = GetSimpleValue(info, 4);
2646   if (ValueIsNull(info.GetIsolate(), argOne) ||
2647       ValueIsNull(info.GetIsolate(), argTwo) ||
2648       ValueIsNull(info.GetIsolate(), argThree) ||
2649       ValueIsNull(info.GetIsolate(), argFour) ||
2650       ValueIsNull(info.GetIsolate(), argFive)) {
2651     info.GetReturnValue().SetNull();
2652     return;
2653   }
2654 
2655   float nPrincipalAmount = ValueToFloat(info.GetIsolate(), argOne);
2656   float nRate = ValueToFloat(info.GetIsolate(), argTwo);
2657   float nPayment = ValueToFloat(info.GetIsolate(), argThree);
2658   float nFirstMonth = ValueToFloat(info.GetIsolate(), argFour);
2659   float nNumberOfMonths = ValueToFloat(info.GetIsolate(), argFive);
2660   if ((nPrincipalAmount <= 0) || (nRate <= 0) || (nPayment <= 0) ||
2661       (nFirstMonth < 0) || (nNumberOfMonths < 0)) {
2662     pContext->ThrowArgumentMismatchException();
2663     return;
2664   }
2665 
2666   float nRateOfMonth = nRate / 12;
2667   int32_t iNums = static_cast<int32_t>(
2668       (log10((float)(nPayment / nPrincipalAmount)) -
2669        log10((float)(nPayment / nPrincipalAmount - nRateOfMonth))) /
2670       log10((float)(1 + nRateOfMonth)));
2671   int32_t iEnd =
2672       std::min(static_cast<int32_t>(nFirstMonth + nNumberOfMonths - 1), iNums);
2673   if (nPayment < nPrincipalAmount * nRateOfMonth) {
2674     pContext->ThrowArgumentMismatchException();
2675     return;
2676   }
2677 
2678   int32_t i = 0;
2679   for (i = 0; i < nFirstMonth - 1; ++i)
2680     nPrincipalAmount -= nPayment - nPrincipalAmount * nRateOfMonth;
2681 
2682   float nTemp = 0;
2683   float nSum = 0;
2684   for (; i < iEnd; ++i) {
2685     nTemp = nPayment - nPrincipalAmount * nRateOfMonth;
2686     nSum += nTemp;
2687     nPrincipalAmount -= nTemp;
2688   }
2689   info.GetReturnValue().Set(nSum);
2690 }
2691 
2692 // static
PV(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2693 void CFXJSE_FormCalcContext::PV(
2694     CFXJSE_HostObject* pThis,
2695     const v8::FunctionCallbackInfo<v8::Value>& info) {
2696   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2697   if (info.Length() != 3) {
2698     pContext->ThrowParamCountMismatchException("PV");
2699     return;
2700   }
2701 
2702   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2703   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2704   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2705   if (ValueIsNull(info.GetIsolate(), argOne) ||
2706       ValueIsNull(info.GetIsolate(), argTwo) ||
2707       ValueIsNull(info.GetIsolate(), argThree)) {
2708     info.GetReturnValue().SetNull();
2709     return;
2710   }
2711 
2712   double nAmount = ValueToDouble(info.GetIsolate(), argOne);
2713   double nRate = ValueToDouble(info.GetIsolate(), argTwo);
2714   int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
2715   if (nAmount <= 0 || nRate < 0 || nPeriods == 0) {
2716     pContext->ThrowArgumentMismatchException();
2717     return;
2718   }
2719 
2720   double nTemp = 1 / pow(1.0 + nRate, nPeriods);
2721   info.GetReturnValue().Set(nAmount * ((1.0 - nTemp) / nRate));
2722 }
2723 
2724 // static
Rate(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2725 void CFXJSE_FormCalcContext::Rate(
2726     CFXJSE_HostObject* pThis,
2727     const v8::FunctionCallbackInfo<v8::Value>& info) {
2728   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2729   if (info.Length() != 3) {
2730     pContext->ThrowParamCountMismatchException("Rate");
2731     return;
2732   }
2733 
2734   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2735   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2736   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2737   if (ValueIsNull(info.GetIsolate(), argOne) ||
2738       ValueIsNull(info.GetIsolate(), argTwo) ||
2739       ValueIsNull(info.GetIsolate(), argThree)) {
2740     info.GetReturnValue().SetNull();
2741     return;
2742   }
2743 
2744   float nFuture = ValueToFloat(info.GetIsolate(), argOne);
2745   float nPresent = ValueToFloat(info.GetIsolate(), argTwo);
2746   int nPeriods = GetValidatedPaymentPeriods(info.GetIsolate(), argThree);
2747   if (nFuture <= 0 || nPresent < 0 || nPeriods == 0) {
2748     pContext->ThrowArgumentMismatchException();
2749     return;
2750   }
2751 
2752   info.GetReturnValue().Set(powf(nFuture / nPresent, 1.0f / nPeriods) - 1.0f);
2753 }
2754 
2755 // static
Term(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2756 void CFXJSE_FormCalcContext::Term(
2757     CFXJSE_HostObject* pThis,
2758     const v8::FunctionCallbackInfo<v8::Value>& info) {
2759   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2760   if (info.Length() != 3) {
2761     pContext->ThrowParamCountMismatchException("Term");
2762     return;
2763   }
2764 
2765   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2766   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
2767   v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
2768   if (ValueIsNull(info.GetIsolate(), argOne) ||
2769       ValueIsNull(info.GetIsolate(), argTwo) ||
2770       ValueIsNull(info.GetIsolate(), argThree)) {
2771     info.GetReturnValue().SetNull();
2772     return;
2773   }
2774 
2775   float nMount = ValueToFloat(info.GetIsolate(), argOne);
2776   float nRate = ValueToFloat(info.GetIsolate(), argTwo);
2777   float nFuture = ValueToFloat(info.GetIsolate(), argThree);
2778   if ((nMount <= 0) || (nRate <= 0) || (nFuture <= 0)) {
2779     pContext->ThrowArgumentMismatchException();
2780     return;
2781   }
2782 
2783   info.GetReturnValue().Set(log((float)(nFuture / nMount * nRate) + 1) /
2784                             log((float)(1 + nRate)));
2785 }
2786 
2787 // static
Choose(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2788 void CFXJSE_FormCalcContext::Choose(
2789     CFXJSE_HostObject* pThis,
2790     const v8::FunctionCallbackInfo<v8::Value>& info) {
2791   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2792   int32_t argc = info.Length();
2793   if (argc < 2) {
2794     pContext->ThrowParamCountMismatchException("Choose");
2795     return;
2796   }
2797 
2798   if (ValueIsNull(info.GetIsolate(), info[0])) {
2799     info.GetReturnValue().SetNull();
2800     return;
2801   }
2802 
2803   int32_t iIndex =
2804       static_cast<int32_t>(ValueToFloat(info.GetIsolate(), info[0]));
2805   if (iIndex < 1) {
2806     info.GetReturnValue().SetEmptyString();
2807     return;
2808   }
2809 
2810   bool bFound = false;
2811   bool bStopCounterFlags = false;
2812   int32_t iArgIndex = 1;
2813   int32_t iValueIndex = 0;
2814   while (!bFound && !bStopCounterFlags && (iArgIndex < argc)) {
2815     v8::Local<v8::Value> argIndexValue = info[iArgIndex];
2816     if (fxv8::IsArray(argIndexValue)) {
2817       v8::Local<v8::Array> arr = argIndexValue.As<v8::Array>();
2818       uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
2819       if (iLength > 3)
2820         bStopCounterFlags = true;
2821 
2822       iValueIndex += (iLength - 2);
2823       if (iValueIndex >= iIndex) {
2824         v8::Local<v8::Value> propertyValue =
2825             fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 1);
2826         v8::Local<v8::Value> jsValue = fxv8::ReentrantGetArrayElementHelper(
2827             info.GetIsolate(), arr, (iLength - 1) - (iValueIndex - iIndex));
2828         v8::Local<v8::Value> newPropertyValue;
2829         if (fxv8::IsObject(jsValue)) {
2830           v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
2831           if (fxv8::IsNull(propertyValue)) {
2832             newPropertyValue =
2833                 GetObjectDefaultValue(info.GetIsolate(), jsObjectValue);
2834           } else {
2835             ByteString bsName = fxv8::ReentrantToByteStringHelper(
2836                 info.GetIsolate(), propertyValue);
2837             newPropertyValue = fxv8::ReentrantGetObjectPropertyHelper(
2838                 info.GetIsolate(), jsObjectValue, bsName.AsStringView());
2839           }
2840         }
2841         ByteString bsChosen =
2842             ValueToUTF8String(info.GetIsolate(), newPropertyValue);
2843         info.GetReturnValue().Set(
2844             fxv8::NewStringHelper(info.GetIsolate(), bsChosen.AsStringView()));
2845         bFound = true;
2846       }
2847     } else {
2848       iValueIndex++;
2849       if (iValueIndex == iIndex) {
2850         ByteString bsChosen =
2851             ValueToUTF8String(info.GetIsolate(), argIndexValue);
2852         info.GetReturnValue().Set(
2853             fxv8::NewStringHelper(info.GetIsolate(), bsChosen.AsStringView()));
2854         bFound = true;
2855       }
2856     }
2857     iArgIndex++;
2858   }
2859   if (!bFound)
2860     info.GetReturnValue().SetEmptyString();
2861 }
2862 
2863 // static
Exists(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2864 void CFXJSE_FormCalcContext::Exists(
2865     CFXJSE_HostObject* pThis,
2866     const v8::FunctionCallbackInfo<v8::Value>& info) {
2867   if (info.Length() != 1) {
2868     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Exists");
2869     return;
2870   }
2871   info.GetReturnValue().Set(fxv8::IsObject(info[0]));
2872 }
2873 
2874 // static
HasValue(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2875 void CFXJSE_FormCalcContext::HasValue(
2876     CFXJSE_HostObject* pThis,
2877     const v8::FunctionCallbackInfo<v8::Value>& info) {
2878   if (info.Length() != 1) {
2879     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("HasValue");
2880     return;
2881   }
2882 
2883   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2884   if (!fxv8::IsString(argOne)) {
2885     info.GetReturnValue().Set(
2886         static_cast<int>(fxv8::IsNumber(argOne) || fxv8::IsBoolean(argOne)));
2887     return;
2888   }
2889 
2890   ByteString bsValue =
2891       fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argOne);
2892   bsValue.TrimWhitespaceFront();
2893   info.GetReturnValue().Set(static_cast<int>(!bsValue.IsEmpty()));
2894 }
2895 
2896 // static
Oneof(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2897 void CFXJSE_FormCalcContext::Oneof(
2898     CFXJSE_HostObject* pThis,
2899     const v8::FunctionCallbackInfo<v8::Value>& info) {
2900   if (info.Length() < 2) {
2901     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Oneof");
2902     return;
2903   }
2904 
2905   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2906   for (const auto& value : UnfoldArgs(info)) {
2907     if (SimpleValueCompare(info.GetIsolate(), argOne, value)) {
2908       info.GetReturnValue().Set(1);
2909       return;
2910     }
2911   }
2912   info.GetReturnValue().Set(0);
2913 }
2914 
2915 // static
Within(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2916 void CFXJSE_FormCalcContext::Within(
2917     CFXJSE_HostObject* pThis,
2918     const v8::FunctionCallbackInfo<v8::Value>& info) {
2919   if (info.Length() != 3) {
2920     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Within");
2921     return;
2922   }
2923 
2924   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
2925   if (fxv8::IsNull(argOne)) {
2926     info.GetReturnValue().SetUndefined();
2927     return;
2928   }
2929 
2930   v8::Local<v8::Value> argLow = GetSimpleValue(info, 1);
2931   v8::Local<v8::Value> argHigh = GetSimpleValue(info, 2);
2932   if (fxv8::IsNumber(argOne)) {
2933     float oneNumber = ValueToFloat(info.GetIsolate(), argOne);
2934     float lowNumber = ValueToFloat(info.GetIsolate(), argLow);
2935     float heightNumber = ValueToFloat(info.GetIsolate(), argHigh);
2936     info.GetReturnValue().Set(static_cast<int>((oneNumber >= lowNumber) &&
2937                                                (oneNumber <= heightNumber)));
2938     return;
2939   }
2940 
2941   ByteString bsOne = ValueToUTF8String(info.GetIsolate(), argOne);
2942   ByteString bsLow = ValueToUTF8String(info.GetIsolate(), argLow);
2943   ByteString bsHeight = ValueToUTF8String(info.GetIsolate(), argHigh);
2944   info.GetReturnValue().Set(
2945       static_cast<int>((bsOne.Compare(bsLow.AsStringView()) >= 0) &&
2946                        (bsOne.Compare(bsHeight.AsStringView()) <= 0)));
2947 }
2948 
2949 // static
If(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2950 void CFXJSE_FormCalcContext::If(
2951     CFXJSE_HostObject* pThis,
2952     const v8::FunctionCallbackInfo<v8::Value>& info) {
2953   if (info.Length() != 3) {
2954     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("If");
2955     return;
2956   }
2957 
2958   const bool condition = fxv8::ReentrantToBooleanHelper(
2959       info.GetIsolate(), GetSimpleValue(info, 0));
2960 
2961   info.GetReturnValue().Set(GetSimpleValue(info, condition ? 1 : 2));
2962 }
2963 
2964 // static
Eval(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)2965 void CFXJSE_FormCalcContext::Eval(
2966     CFXJSE_HostObject* pThis,
2967     const v8::FunctionCallbackInfo<v8::Value>& info) {
2968   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
2969   if (info.Length() != 1) {
2970     pContext->ThrowParamCountMismatchException("Eval");
2971     return;
2972   }
2973 
2974   v8::Isolate* pIsolate = pContext->GetIsolate();
2975   v8::Local<v8::Value> scriptValue = GetSimpleValue(info, 0);
2976   ByteString bsUtf8Script = ValueToUTF8String(info.GetIsolate(), scriptValue);
2977   if (bsUtf8Script.IsEmpty()) {
2978     info.GetReturnValue().SetNull();
2979     return;
2980   }
2981 
2982   WideString wsCalcScript = WideString::FromUTF8(bsUtf8Script.AsStringView());
2983   std::optional<WideTextBuffer> wsJavaScriptBuf =
2984       CFXJSE_FormCalcContext::Translate(pContext->GetDocument()->GetHeap(),
2985                                         wsCalcScript.AsStringView());
2986   if (!wsJavaScriptBuf.has_value()) {
2987     pContext->ThrowCompilerErrorException();
2988     return;
2989   }
2990   std::unique_ptr<CFXJSE_Context> pNewContext =
2991       CFXJSE_Context::Create(pIsolate, nullptr, nullptr, nullptr);
2992 
2993   ByteString bsScript = FX_UTF8Encode(wsJavaScriptBuf.value().AsStringView());
2994   CFXJSE_Context::ExecutionResult result = pNewContext->ExecuteScript(
2995       bsScript.AsStringView(), v8::Local<v8::Object>());
2996 
2997   info.GetReturnValue().Set(result.value->DirectGetValue());
2998 }
2999 
3000 // static
Ref(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3001 void CFXJSE_FormCalcContext::Ref(
3002     CFXJSE_HostObject* pThis,
3003     const v8::FunctionCallbackInfo<v8::Value>& info) {
3004   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
3005   if (info.Length() != 1) {
3006     pContext->ThrowParamCountMismatchException("Ref");
3007     return;
3008   }
3009 
3010   v8::Local<v8::Value> argOne = info[0];
3011   if (fxv8::IsBoolean(argOne) || fxv8::IsString(argOne) ||
3012       fxv8::IsNumber(argOne)) {
3013     info.GetReturnValue().Set(argOne);
3014     return;
3015   }
3016 
3017   v8::LocalVector<v8::Value> values(info.GetIsolate(), 3);
3018   int intVal = 3;
3019   if (fxv8::IsNull(argOne)) {
3020     // TODO(dsinclair): Why is this 4 when the others are all 3?
3021     intVal = 4;
3022     values[2] = fxv8::NewNullHelper(info.GetIsolate());
3023   } else if (fxv8::IsArray(argOne)) {
3024     v8::Local<v8::Array> arr = argOne.As<v8::Array>();
3025     v8::Local<v8::Value> propertyValue =
3026         fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 1);
3027     v8::Local<v8::Value> jsObjectValue =
3028         fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2);
3029     if (!fxv8::IsNull(propertyValue) || fxv8::IsNull(jsObjectValue)) {
3030       pContext->ThrowArgumentMismatchException();
3031       return;
3032     }
3033     values[2] = jsObjectValue;
3034   } else if (fxv8::IsObject(argOne)) {
3035     values[2] = argOne;
3036   } else {
3037     pContext->ThrowArgumentMismatchException();
3038     return;
3039   }
3040 
3041   values[0] = fxv8::NewNumberHelper(info.GetIsolate(), intVal);
3042   values[1] = fxv8::NewNullHelper(info.GetIsolate());
3043   info.GetReturnValue().Set(fxv8::NewArrayHelper(info.GetIsolate(), values));
3044 }
3045 
3046 // static
UnitType(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3047 void CFXJSE_FormCalcContext::UnitType(
3048     CFXJSE_HostObject* pThis,
3049     const v8::FunctionCallbackInfo<v8::Value>& info) {
3050   if (info.Length() != 1) {
3051     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("UnitType");
3052     return;
3053   }
3054 
3055   v8::Local<v8::Value> unitspanValue = GetSimpleValue(info, 0);
3056   if (fxv8::IsNull(unitspanValue)) {
3057     info.GetReturnValue().SetNull();
3058     return;
3059   }
3060 
3061   ByteString bsUnitspan = ValueToUTF8String(info.GetIsolate(), unitspanValue);
3062   if (bsUnitspan.IsEmpty()) {
3063     info.GetReturnValue().SetEmptyString();
3064     return;
3065   }
3066 
3067   enum XFA_FormCalc_VALUETYPE_ParserStatus {
3068     VALUETYPE_START,
3069     VALUETYPE_HAVEINVALIDCHAR,
3070     VALUETYPE_HAVEDIGIT,
3071     VALUETYPE_HAVEDIGITWHITE,
3072     VALUETYPE_ISCM,
3073     VALUETYPE_ISMM,
3074     VALUETYPE_ISPT,
3075     VALUETYPE_ISMP,
3076     VALUETYPE_ISIN,
3077   };
3078   bsUnitspan.MakeLower();
3079   WideString wsType = WideString::FromUTF8(bsUnitspan.AsStringView());
3080   const wchar_t* pData = wsType.c_str();
3081   int32_t u = 0;
3082   int32_t uLen = wsType.GetLength();
3083   UNSAFE_TODO({
3084     while (IsWhitespace(pData[u])) {
3085       u++;
3086     }
3087 
3088     XFA_FormCalc_VALUETYPE_ParserStatus eParserStatus = VALUETYPE_START;
3089     wchar_t typeChar;
3090     // TODO(dsinclair): Cleanup this parser, figure out what the various checks
3091     //    are for.
3092     while (u < uLen) {
3093       typeChar = pData[u];
3094       if (IsWhitespace(typeChar)) {
3095         if (eParserStatus != VALUETYPE_HAVEDIGIT &&
3096             eParserStatus != VALUETYPE_HAVEDIGITWHITE) {
3097           eParserStatus = VALUETYPE_ISIN;
3098           break;
3099         }
3100         eParserStatus = VALUETYPE_HAVEDIGITWHITE;
3101       } else if (IsPartOfNumberW(typeChar)) {
3102         if (eParserStatus == VALUETYPE_HAVEDIGITWHITE) {
3103           eParserStatus = VALUETYPE_ISIN;
3104           break;
3105         }
3106         eParserStatus = VALUETYPE_HAVEDIGIT;
3107       } else if ((typeChar == 'c' || typeChar == 'p') && (u + 1 < uLen)) {
3108         wchar_t nextChar = pData[u + 1];
3109         if ((eParserStatus == VALUETYPE_START ||
3110              eParserStatus == VALUETYPE_HAVEDIGIT ||
3111              eParserStatus == VALUETYPE_HAVEDIGITWHITE) &&
3112             !IsPartOfNumberW(nextChar)) {
3113           eParserStatus = (typeChar == 'c') ? VALUETYPE_ISCM : VALUETYPE_ISPT;
3114           break;
3115         }
3116         eParserStatus = VALUETYPE_HAVEINVALIDCHAR;
3117       } else if (typeChar == 'm' && (u + 1 < uLen)) {
3118         wchar_t nextChar = pData[u + 1];
3119         if ((eParserStatus == VALUETYPE_START ||
3120              eParserStatus == VALUETYPE_HAVEDIGIT ||
3121              eParserStatus == VALUETYPE_HAVEDIGITWHITE) &&
3122             !IsPartOfNumberW(nextChar)) {
3123           eParserStatus = VALUETYPE_ISMM;
3124           if (nextChar == 'p' || ((u + 5 < uLen) && pData[u + 1] == 'i' &&
3125                                   pData[u + 2] == 'l' && pData[u + 3] == 'l' &&
3126                                   pData[u + 4] == 'i' && pData[u + 5] == 'p')) {
3127             eParserStatus = VALUETYPE_ISMP;
3128           }
3129           break;
3130         }
3131       } else {
3132         eParserStatus = VALUETYPE_HAVEINVALIDCHAR;
3133       }
3134       u++;
3135     }
3136     switch (eParserStatus) {
3137       case VALUETYPE_ISCM:
3138         info.GetReturnValue().Set(
3139             fxv8::NewStringHelper(info.GetIsolate(), "cm"));
3140         break;
3141       case VALUETYPE_ISMM:
3142         info.GetReturnValue().Set(
3143             fxv8::NewStringHelper(info.GetIsolate(), "mm"));
3144         break;
3145       case VALUETYPE_ISPT:
3146         info.GetReturnValue().Set(
3147             fxv8::NewStringHelper(info.GetIsolate(), "pt"));
3148         break;
3149       case VALUETYPE_ISMP:
3150         info.GetReturnValue().Set(
3151             fxv8::NewStringHelper(info.GetIsolate(), "mp"));
3152         break;
3153       default:
3154         info.GetReturnValue().Set(
3155             fxv8::NewStringHelper(info.GetIsolate(), "in"));
3156         break;
3157     }
3158   });
3159 }
3160 
3161 // static
UnitValue(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3162 void CFXJSE_FormCalcContext::UnitValue(
3163     CFXJSE_HostObject* pThis,
3164     const v8::FunctionCallbackInfo<v8::Value>& info) {
3165   int32_t argc = info.Length();
3166   if (argc < 1 || argc > 2) {
3167     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("UnitValue");
3168     return;
3169   }
3170 
3171   v8::Local<v8::Value> unitspanValue = GetSimpleValue(info, 0);
3172   if (fxv8::IsNull(unitspanValue)) {
3173     info.GetReturnValue().SetNull();
3174     return;
3175   }
3176 
3177   ByteString bsUnitspan = ValueToUTF8String(info.GetIsolate(), unitspanValue);
3178   const char* pData = bsUnitspan.c_str();
3179   if (!pData) {
3180     info.GetReturnValue().Set(0);
3181     return;
3182   }
3183 
3184   UNSAFE_TODO({
3185     size_t u = 0;
3186     while (IsWhitespace(pData[u])) {
3187       ++u;
3188     }
3189 
3190     while (u < bsUnitspan.GetLength()) {
3191       if (!IsPartOfNumber(pData[u])) {
3192         break;
3193       }
3194       ++u;
3195     }
3196 
3197     char* pTemp = nullptr;
3198     double dFirstNumber = strtod(pData, &pTemp);
3199     while (IsWhitespace(pData[u])) {
3200       ++u;
3201     }
3202 
3203     size_t uLen = bsUnitspan.GetLength();
3204     ByteString bsFirstUnit;
3205     while (u < uLen) {
3206       if (pData[u] == ' ') {
3207         break;
3208       }
3209 
3210       bsFirstUnit += pData[u];
3211       ++u;
3212     }
3213     bsFirstUnit.MakeLower();
3214 
3215     ByteString bsUnit;
3216     if (argc > 1) {
3217       v8::Local<v8::Value> unitValue = GetSimpleValue(info, 1);
3218       ByteString bsUnitTemp = ValueToUTF8String(info.GetIsolate(), unitValue);
3219       const char* pChar = bsUnitTemp.c_str();
3220       size_t uVal = 0;
3221       while (IsWhitespace(pChar[uVal])) {
3222         ++uVal;
3223       }
3224 
3225       while (uVal < bsUnitTemp.GetLength()) {
3226         if (!isdigit(pChar[uVal]) && pChar[uVal] != '.') {
3227           break;
3228         }
3229         ++uVal;
3230       }
3231       while (IsWhitespace(pChar[uVal])) {
3232         ++uVal;
3233       }
3234 
3235       size_t uValLen = bsUnitTemp.GetLength();
3236       while (uVal < uValLen) {
3237         if (pChar[uVal] == ' ') {
3238           break;
3239         }
3240 
3241         bsUnit += pChar[uVal];
3242         ++uVal;
3243       }
3244       bsUnit.MakeLower();
3245     } else {
3246       bsUnit = bsFirstUnit;
3247     }
3248 
3249     double dResult = 0;
3250     if (bsFirstUnit == "in" || bsFirstUnit == "inches") {
3251       if (bsUnit == "mm" || bsUnit == "millimeters") {
3252         dResult = dFirstNumber * 25.4;
3253       } else if (bsUnit == "cm" || bsUnit == "centimeters") {
3254         dResult = dFirstNumber * 2.54;
3255       } else if (bsUnit == "pt" || bsUnit == "points") {
3256         dResult = dFirstNumber / 72;
3257       } else if (bsUnit == "mp" || bsUnit == "millipoints") {
3258         dResult = dFirstNumber / 72000;
3259       } else {
3260         dResult = dFirstNumber;
3261       }
3262     } else if (bsFirstUnit == "mm" || bsFirstUnit == "millimeters") {
3263       if (bsUnit == "mm" || bsUnit == "millimeters") {
3264         dResult = dFirstNumber;
3265       } else if (bsUnit == "cm" || bsUnit == "centimeters") {
3266         dResult = dFirstNumber / 10;
3267       } else if (bsUnit == "pt" || bsUnit == "points") {
3268         dResult = dFirstNumber / 25.4 / 72;
3269       } else if (bsUnit == "mp" || bsUnit == "millipoints") {
3270         dResult = dFirstNumber / 25.4 / 72000;
3271       } else {
3272         dResult = dFirstNumber / 25.4;
3273       }
3274     } else if (bsFirstUnit == "cm" || bsFirstUnit == "centimeters") {
3275       if (bsUnit == "mm" || bsUnit == "millimeters") {
3276         dResult = dFirstNumber * 10;
3277       } else if (bsUnit == "cm" || bsUnit == "centimeters") {
3278         dResult = dFirstNumber;
3279       } else if (bsUnit == "pt" || bsUnit == "points") {
3280         dResult = dFirstNumber / 2.54 / 72;
3281       } else if (bsUnit == "mp" || bsUnit == "millipoints") {
3282         dResult = dFirstNumber / 2.54 / 72000;
3283       } else {
3284         dResult = dFirstNumber / 2.54;
3285       }
3286     } else if (bsFirstUnit == "pt" || bsFirstUnit == "points") {
3287       if (bsUnit == "mm" || bsUnit == "millimeters") {
3288         dResult = dFirstNumber / 72 * 25.4;
3289       } else if (bsUnit == "cm" || bsUnit == "centimeters") {
3290         dResult = dFirstNumber / 72 * 2.54;
3291       } else if (bsUnit == "pt" || bsUnit == "points") {
3292         dResult = dFirstNumber;
3293       } else if (bsUnit == "mp" || bsUnit == "millipoints") {
3294         dResult = dFirstNumber * 1000;
3295       } else {
3296         dResult = dFirstNumber / 72;
3297       }
3298     } else if (bsFirstUnit == "mp" || bsFirstUnit == "millipoints") {
3299       if (bsUnit == "mm" || bsUnit == "millimeters") {
3300         dResult = dFirstNumber / 72000 * 25.4;
3301       } else if (bsUnit == "cm" || bsUnit == "centimeters") {
3302         dResult = dFirstNumber / 72000 * 2.54;
3303       } else if (bsUnit == "pt" || bsUnit == "points") {
3304         dResult = dFirstNumber / 1000;
3305       } else if (bsUnit == "mp" || bsUnit == "millipoints") {
3306         dResult = dFirstNumber;
3307       } else {
3308         dResult = dFirstNumber / 72000;
3309       }
3310     }
3311     info.GetReturnValue().Set(dResult);
3312   });
3313 }
3314 
3315 // static
At(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3316 void CFXJSE_FormCalcContext::At(
3317     CFXJSE_HostObject* pThis,
3318     const v8::FunctionCallbackInfo<v8::Value>& info) {
3319   if (info.Length() != 2) {
3320     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("At");
3321     return;
3322   }
3323 
3324   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3325   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3326   if (ValueIsNull(info.GetIsolate(), argOne) ||
3327       ValueIsNull(info.GetIsolate(), argTwo)) {
3328     info.GetReturnValue().SetNull();
3329     return;
3330   }
3331 
3332   ByteString stringTwo = ValueToUTF8String(info.GetIsolate(), argTwo);
3333   if (stringTwo.IsEmpty()) {
3334     info.GetReturnValue().Set(1);
3335     return;
3336   }
3337 
3338   ByteString stringOne = ValueToUTF8String(info.GetIsolate(), argOne);
3339   auto pos = stringOne.Find(stringTwo.AsStringView());
3340   info.GetReturnValue().Set(
3341       static_cast<int>(pos.has_value() ? pos.value() + 1 : 0));
3342 }
3343 
3344 // static
Concat(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3345 void CFXJSE_FormCalcContext::Concat(
3346     CFXJSE_HostObject* pThis,
3347     const v8::FunctionCallbackInfo<v8::Value>& info) {
3348   int32_t argc = info.Length();
3349   if (argc < 1) {
3350     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Concat");
3351     return;
3352   }
3353 
3354   ByteString bsResult;
3355   bool bAllNull = true;
3356   for (int32_t i = 0; i < argc; i++) {
3357     v8::Local<v8::Value> value = GetSimpleValue(info, i);
3358     if (ValueIsNull(info.GetIsolate(), value))
3359       continue;
3360 
3361     bAllNull = false;
3362     bsResult += ValueToUTF8String(info.GetIsolate(), value);
3363   }
3364 
3365   if (bAllNull) {
3366     info.GetReturnValue().SetNull();
3367     return;
3368   }
3369   info.GetReturnValue().Set(
3370       fxv8::NewStringHelper(info.GetIsolate(), bsResult.AsStringView()));
3371 }
3372 
3373 // static
Decode(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3374 void CFXJSE_FormCalcContext::Decode(
3375     CFXJSE_HostObject* pThis,
3376     const v8::FunctionCallbackInfo<v8::Value>& info) {
3377   int32_t argc = info.Length();
3378   if (argc < 1 || argc > 2) {
3379     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Decode");
3380     return;
3381   }
3382 
3383   if (argc == 1) {
3384     v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3385     if (ValueIsNull(info.GetIsolate(), argOne)) {
3386       info.GetReturnValue().SetNull();
3387       return;
3388     }
3389 
3390     WideString decoded = DecodeURL(WideString::FromUTF8(
3391         ValueToUTF8String(info.GetIsolate(), argOne).AsStringView()));
3392     auto result = FX_UTF8Encode(decoded.AsStringView());
3393     info.GetReturnValue().Set(
3394         fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3395     return;
3396   }
3397 
3398   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3399   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3400   if (ValueIsNull(info.GetIsolate(), argOne) ||
3401       ValueIsNull(info.GetIsolate(), argTwo)) {
3402     info.GetReturnValue().SetNull();
3403     return;
3404   }
3405 
3406   ByteString bsToDecode = ValueToUTF8String(info.GetIsolate(), argOne);
3407   ByteString bsIdentify = ValueToUTF8String(info.GetIsolate(), argTwo);
3408   WideString decoded;
3409 
3410   WideString wsToDecode = WideString::FromUTF8(bsToDecode.AsStringView());
3411 
3412   if (bsIdentify.EqualNoCase("html"))
3413     decoded = DecodeHTML(wsToDecode);
3414   else if (bsIdentify.EqualNoCase("xml"))
3415     decoded = DecodeXML(wsToDecode);
3416   else
3417     decoded = DecodeURL(wsToDecode);
3418 
3419   auto result = FX_UTF8Encode(decoded.AsStringView());
3420   info.GetReturnValue().Set(
3421       fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3422 }
3423 
3424 // static
Encode(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3425 void CFXJSE_FormCalcContext::Encode(
3426     CFXJSE_HostObject* pThis,
3427     const v8::FunctionCallbackInfo<v8::Value>& info) {
3428   int32_t argc = info.Length();
3429   if (argc < 1 || argc > 2) {
3430     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Encode");
3431     return;
3432   }
3433 
3434   if (argc == 1) {
3435     v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3436     if (ValueIsNull(info.GetIsolate(), argOne)) {
3437       info.GetReturnValue().SetNull();
3438       return;
3439     }
3440     WideString encoded =
3441         EncodeURL(ValueToUTF8String(info.GetIsolate(), argOne));
3442     auto result = FX_UTF8Encode(encoded.AsStringView());
3443     info.GetReturnValue().Set(
3444         fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3445     return;
3446   }
3447 
3448   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3449   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3450   if (ValueIsNull(info.GetIsolate(), argOne) ||
3451       ValueIsNull(info.GetIsolate(), argTwo)) {
3452     info.GetReturnValue().SetNull();
3453     return;
3454   }
3455 
3456   ByteString bsToEncode = ValueToUTF8String(info.GetIsolate(), argOne);
3457   ByteString bsIdentify = ValueToUTF8String(info.GetIsolate(), argTwo);
3458   WideString encoded;
3459   if (bsIdentify.EqualNoCase("html"))
3460     encoded = EncodeHTML(bsToEncode);
3461   else if (bsIdentify.EqualNoCase("xml"))
3462     encoded = EncodeXML(bsToEncode);
3463   else
3464     encoded = EncodeURL(bsToEncode);
3465 
3466   auto result = FX_UTF8Encode(encoded.AsStringView());
3467   info.GetReturnValue().Set(
3468       fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3469 }
3470 
3471 // static
Format(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3472 void CFXJSE_FormCalcContext::Format(
3473     CFXJSE_HostObject* pThis,
3474     const v8::FunctionCallbackInfo<v8::Value>& info) {
3475   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
3476   if (info.Length() < 2) {
3477     pContext->ThrowParamCountMismatchException("Format");
3478     return;
3479   }
3480 
3481   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3482   ByteString bsPattern = ValueToUTF8String(info.GetIsolate(), argOne);
3483 
3484   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3485   ByteString bsValue = ValueToUTF8String(info.GetIsolate(), argTwo);
3486 
3487   CXFA_Document* pDoc = pContext->GetDocument();
3488   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
3489   CXFA_Node* pThisNode = ToNode(pDoc->GetScriptContext()->GetThisObject());
3490   GCedLocaleIface* pLocale = pThisNode->GetLocale();
3491   WideString wsPattern = WideString::FromUTF8(bsPattern.AsStringView());
3492   WideString wsValue = WideString::FromUTF8(bsValue.AsStringView());
3493   auto [bPatternIsString, dwPatternType] =
3494       PatternStringType(bsPattern.AsStringView());
3495   if (!bPatternIsString) {
3496     switch (dwPatternType) {
3497       case CXFA_LocaleValue::ValueType::kDateTime: {
3498         auto iTChar = wsPattern.Find(L'T');
3499         if (!iTChar.has_value()) {
3500           info.GetReturnValue().SetEmptyString();
3501           return;
3502         }
3503         auto wsDatePattern = WideString::FromASCII("date{");
3504         wsDatePattern += wsPattern.First(iTChar.value()) + L"} ";
3505 
3506         auto wsTimePattern = WideString::FromASCII("time{");
3507         wsTimePattern +=
3508             wsPattern.Last(wsPattern.GetLength() - (iTChar.value() + 1)) + L"}";
3509         wsPattern = wsDatePattern + wsTimePattern;
3510       } break;
3511       case CXFA_LocaleValue::ValueType::kDate: {
3512         wsPattern = L"date{" + wsPattern + L"}";
3513       } break;
3514       case CXFA_LocaleValue::ValueType::kTime: {
3515         wsPattern = L"time{" + wsPattern + L"}";
3516       } break;
3517       case CXFA_LocaleValue::ValueType::kText: {
3518         wsPattern = L"text{" + wsPattern + L"}";
3519       } break;
3520       case CXFA_LocaleValue::ValueType::kFloat: {
3521         wsPattern = L"num{" + wsPattern + L"}";
3522       } break;
3523       default: {
3524         WideString wsTestPattern = L"num{" + wsPattern + L"}";
3525         CXFA_LocaleValue tempLocaleValue(CXFA_LocaleValue::ValueType::kFloat,
3526                                          wsValue, wsTestPattern, pLocale, pMgr);
3527         if (tempLocaleValue.IsValid()) {
3528           wsPattern = std::move(wsTestPattern);
3529           dwPatternType = CXFA_LocaleValue::ValueType::kFloat;
3530         } else {
3531           wsPattern = L"text{" + wsPattern + L"}";
3532           dwPatternType = CXFA_LocaleValue::ValueType::kText;
3533         }
3534       } break;
3535     }
3536   }
3537   CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
3538                                pMgr);
3539   WideString wsRet;
3540   if (!localeValue.FormatPatterns(wsRet, wsPattern, pLocale,
3541                                   XFA_ValuePicture::kDisplay)) {
3542     info.GetReturnValue().SetEmptyString();
3543     return;
3544   }
3545   info.GetReturnValue().Set(
3546       fxv8::NewStringHelper(info.GetIsolate(), wsRet.ToUTF8().AsStringView()));
3547 }
3548 
3549 // static
Left(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3550 void CFXJSE_FormCalcContext::Left(
3551     CFXJSE_HostObject* pThis,
3552     const v8::FunctionCallbackInfo<v8::Value>& info) {
3553   if (info.Length() != 2) {
3554     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Left");
3555     return;
3556   }
3557 
3558   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3559   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3560   if ((ValueIsNull(info.GetIsolate(), argOne)) ||
3561       (ValueIsNull(info.GetIsolate(), argTwo))) {
3562     info.GetReturnValue().SetNull();
3563     return;
3564   }
3565 
3566   ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
3567   int32_t count = std::max(0, ValueToInteger(info.GetIsolate(), argTwo));
3568   info.GetReturnValue().Set(fxv8::NewStringHelper(
3569       info.GetIsolate(), bsSource.First(count).AsStringView()));
3570 }
3571 
3572 // static
Len(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3573 void CFXJSE_FormCalcContext::Len(
3574     CFXJSE_HostObject* pThis,
3575     const v8::FunctionCallbackInfo<v8::Value>& info) {
3576   if (info.Length() != 1) {
3577     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Len");
3578     return;
3579   }
3580 
3581   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3582   if (ValueIsNull(info.GetIsolate(), argOne)) {
3583     info.GetReturnValue().SetNull();
3584     return;
3585   }
3586 
3587   ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
3588   info.GetReturnValue().Set(static_cast<int>(bsSource.GetLength()));
3589 }
3590 
3591 // static
Lower(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3592 void CFXJSE_FormCalcContext::Lower(
3593     CFXJSE_HostObject* pThis,
3594     const v8::FunctionCallbackInfo<v8::Value>& info) {
3595   int32_t argc = info.Length();
3596   if (argc < 1 || argc > 2) {
3597     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Lower");
3598     return;
3599   }
3600 
3601   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3602   if (ValueIsNull(info.GetIsolate(), argOne)) {
3603     info.GetReturnValue().SetNull();
3604     return;
3605   }
3606 
3607   WideTextBuffer szLowBuf;
3608   ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
3609   WideString wsArg = WideString::FromUTF8(bsArg.AsStringView());
3610   for (wchar_t ch : wsArg) {
3611     if ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0xC0 && ch <= 0xDE))
3612       ch += 32;
3613     else if (ch == 0x100 || ch == 0x102 || ch == 0x104)
3614       ch += 1;
3615     szLowBuf.AppendChar(ch);
3616   }
3617   auto result = FX_UTF8Encode(szLowBuf.AsStringView());
3618   info.GetReturnValue().Set(
3619       fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3620 }
3621 
3622 // static
Ltrim(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3623 void CFXJSE_FormCalcContext::Ltrim(
3624     CFXJSE_HostObject* pThis,
3625     const v8::FunctionCallbackInfo<v8::Value>& info) {
3626   if (info.Length() != 1) {
3627     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Ltrim");
3628     return;
3629   }
3630 
3631   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3632   if (ValueIsNull(info.GetIsolate(), argOne)) {
3633     info.GetReturnValue().SetNull();
3634     return;
3635   }
3636 
3637   ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
3638   bsSource.TrimWhitespaceFront();
3639   info.GetReturnValue().Set(
3640       fxv8::NewStringHelper(info.GetIsolate(), bsSource.AsStringView()));
3641 }
3642 
3643 // static
Parse(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3644 void CFXJSE_FormCalcContext::Parse(
3645     CFXJSE_HostObject* pThis,
3646     const v8::FunctionCallbackInfo<v8::Value>& info) {
3647   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
3648   if (info.Length() != 2) {
3649     pContext->ThrowParamCountMismatchException("Parse");
3650     return;
3651   }
3652 
3653   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3654   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3655   if (ValueIsNull(info.GetIsolate(), argTwo)) {
3656     info.GetReturnValue().SetNull();
3657     return;
3658   }
3659 
3660   ByteString bsPattern = ValueToUTF8String(info.GetIsolate(), argOne);
3661   ByteString bsValue = ValueToUTF8String(info.GetIsolate(), argTwo);
3662   CXFA_Document* pDoc = pContext->GetDocument();
3663   CXFA_LocaleMgr* pMgr = pDoc->GetLocaleMgr();
3664   CXFA_Node* pThisNode = ToNode(pDoc->GetScriptContext()->GetThisObject());
3665   GCedLocaleIface* pLocale = pThisNode->GetLocale();
3666   WideString wsPattern = WideString::FromUTF8(bsPattern.AsStringView());
3667   WideString wsValue = WideString::FromUTF8(bsValue.AsStringView());
3668   auto [bPatternIsString, dwPatternType] =
3669       PatternStringType(bsPattern.AsStringView());
3670   if (bPatternIsString) {
3671     CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
3672                                  pMgr);
3673     if (!localeValue.IsValid()) {
3674       info.GetReturnValue().SetEmptyString();
3675       return;
3676     }
3677     auto result = localeValue.GetValue().ToUTF8();
3678     info.GetReturnValue().Set(
3679         fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3680     return;
3681   }
3682 
3683   switch (dwPatternType) {
3684     case CXFA_LocaleValue::ValueType::kDateTime: {
3685       auto iTChar = wsPattern.Find(L'T');
3686       if (!iTChar.has_value()) {
3687         info.GetReturnValue().SetEmptyString();
3688         return;
3689       }
3690       WideString wsDatePattern(L"date{" + wsPattern.First(iTChar.value()) +
3691                                L"} ");
3692       WideString wsTimePattern(
3693           L"time{" +
3694           wsPattern.Last(wsPattern.GetLength() - (iTChar.value() + 1)) + L"}");
3695       wsPattern = wsDatePattern + wsTimePattern;
3696       CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
3697                                    pMgr);
3698       if (!localeValue.IsValid()) {
3699         info.GetReturnValue().SetEmptyString();
3700         return;
3701       }
3702       auto result = localeValue.GetValue().ToUTF8();
3703       info.GetReturnValue().Set(
3704           fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3705       return;
3706     }
3707     case CXFA_LocaleValue::ValueType::kDate: {
3708       wsPattern = L"date{" + wsPattern + L"}";
3709       CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
3710                                    pMgr);
3711       if (!localeValue.IsValid()) {
3712         info.GetReturnValue().SetEmptyString();
3713         return;
3714       }
3715       auto result = localeValue.GetValue().ToUTF8();
3716       info.GetReturnValue().Set(
3717           fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3718       return;
3719     }
3720     case CXFA_LocaleValue::ValueType::kTime: {
3721       wsPattern = L"time{" + wsPattern + L"}";
3722       CXFA_LocaleValue localeValue(dwPatternType, wsValue, wsPattern, pLocale,
3723                                    pMgr);
3724       if (!localeValue.IsValid()) {
3725         info.GetReturnValue().SetEmptyString();
3726         return;
3727       }
3728       auto result = localeValue.GetValue().ToUTF8();
3729       info.GetReturnValue().Set(
3730           fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3731       return;
3732     }
3733     case CXFA_LocaleValue::ValueType::kText: {
3734       wsPattern = L"text{" + wsPattern + L"}";
3735       CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kText, wsValue,
3736                                    wsPattern, pLocale, pMgr);
3737       if (!localeValue.IsValid()) {
3738         info.GetReturnValue().SetEmptyString();
3739         return;
3740       }
3741       auto result = localeValue.GetValue().ToUTF8();
3742       info.GetReturnValue().Set(
3743           fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3744       return;
3745     }
3746     case CXFA_LocaleValue::ValueType::kFloat: {
3747       wsPattern = L"num{" + wsPattern + L"}";
3748       CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kFloat, wsValue,
3749                                    wsPattern, pLocale, pMgr);
3750       if (!localeValue.IsValid()) {
3751         info.GetReturnValue().SetEmptyString();
3752         return;
3753       }
3754       info.GetReturnValue().Set(localeValue.GetDoubleNum());
3755       return;
3756     }
3757     default: {
3758       {
3759         WideString wsTestPattern = L"num{" + wsPattern + L"}";
3760         CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kFloat,
3761                                      wsValue, wsTestPattern, pLocale, pMgr);
3762         if (localeValue.IsValid()) {
3763           info.GetReturnValue().Set(localeValue.GetDoubleNum());
3764           return;
3765         }
3766       }
3767 
3768       {
3769         WideString wsTestPattern = L"text{" + wsPattern + L"}";
3770         CXFA_LocaleValue localeValue(CXFA_LocaleValue::ValueType::kText,
3771                                      wsValue, wsTestPattern, pLocale, pMgr);
3772         if (localeValue.IsValid()) {
3773           auto result = localeValue.GetValue().ToUTF8();
3774           info.GetReturnValue().Set(
3775               fxv8::NewStringHelper(info.GetIsolate(), result.AsStringView()));
3776           return;
3777         }
3778       }
3779       info.GetReturnValue().SetEmptyString();
3780       return;
3781     }
3782   }
3783 }
3784 
3785 // static
Replace(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3786 void CFXJSE_FormCalcContext::Replace(
3787     CFXJSE_HostObject* pThis,
3788     const v8::FunctionCallbackInfo<v8::Value>& info) {
3789   int32_t argc = info.Length();
3790   if (argc < 2 || argc > 3) {
3791     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Replace");
3792     return;
3793   }
3794 
3795   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3796   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3797   ByteString bsOne;
3798   ByteString bsTwo;
3799   if (!ValueIsNull(info.GetIsolate(), argOne) &&
3800       !ValueIsNull(info.GetIsolate(), argTwo)) {
3801     bsOne = ValueToUTF8String(info.GetIsolate(), argOne);
3802     bsTwo = ValueToUTF8String(info.GetIsolate(), argTwo);
3803   }
3804 
3805   ByteString bsThree;
3806   if (argc > 2) {
3807     v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
3808     bsThree = ValueToUTF8String(info.GetIsolate(), argThree);
3809   }
3810 
3811   bsOne.Replace(bsTwo.AsStringView(), bsThree.AsStringView());
3812   info.GetReturnValue().Set(
3813       fxv8::NewStringHelper(info.GetIsolate(), bsOne.AsStringView()));
3814 }
3815 
3816 // static
Right(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3817 void CFXJSE_FormCalcContext::Right(
3818     CFXJSE_HostObject* pThis,
3819     const v8::FunctionCallbackInfo<v8::Value>& info) {
3820   if (info.Length() != 2) {
3821     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Right");
3822     return;
3823   }
3824 
3825   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3826   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
3827   if ((ValueIsNull(info.GetIsolate(), argOne)) ||
3828       (ValueIsNull(info.GetIsolate(), argTwo))) {
3829     info.GetReturnValue().SetNull();
3830     return;
3831   }
3832 
3833   ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
3834   int32_t count = std::max(0, ValueToInteger(info.GetIsolate(), argTwo));
3835   info.GetReturnValue().Set(fxv8::NewStringHelper(
3836       info.GetIsolate(), bsSource.Last(count).AsStringView()));
3837 }
3838 
3839 // static
Rtrim(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3840 void CFXJSE_FormCalcContext::Rtrim(
3841     CFXJSE_HostObject* pThis,
3842     const v8::FunctionCallbackInfo<v8::Value>& info) {
3843   if (info.Length() != 1) {
3844     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Rtrim");
3845     return;
3846   }
3847 
3848   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3849   if (ValueIsNull(info.GetIsolate(), argOne)) {
3850     info.GetReturnValue().SetNull();
3851     return;
3852   }
3853 
3854   ByteString bsSource = ValueToUTF8String(info.GetIsolate(), argOne);
3855   bsSource.TrimWhitespaceBack();
3856   info.GetReturnValue().Set(
3857       fxv8::NewStringHelper(info.GetIsolate(), bsSource.AsStringView()));
3858 }
3859 
3860 // static
Space(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3861 void CFXJSE_FormCalcContext::Space(
3862     CFXJSE_HostObject* pThis,
3863     const v8::FunctionCallbackInfo<v8::Value>& info) {
3864   if (info.Length() != 1) {
3865     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Space");
3866     return;
3867   }
3868 
3869   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
3870   if (fxv8::IsNull(argOne)) {
3871     info.GetReturnValue().SetNull();
3872     return;
3873   }
3874 
3875   int count = std::max(0, ValueToInteger(info.GetIsolate(), argOne));
3876   if (count > kMaxCharCount) {
3877     ToFormCalcContext(pThis)->ThrowException("String too long.");
3878     return;
3879   }
3880   DataVector<char> space_string(count, ' ');
3881   info.GetReturnValue().Set(
3882       fxv8::NewStringHelper(info.GetIsolate(), ByteStringView(space_string)));
3883 }
3884 
3885 // static
Str(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)3886 void CFXJSE_FormCalcContext::Str(
3887     CFXJSE_HostObject* pThis,
3888     const v8::FunctionCallbackInfo<v8::Value>& info) {
3889   int32_t argc = info.Length();
3890   if (argc < 1 || argc > 3) {
3891     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Str");
3892     return;
3893   }
3894 
3895   v8::Local<v8::Value> numberValue = GetSimpleValue(info, 0);
3896   if (fxv8::IsNull(numberValue)) {
3897     info.GetReturnValue().SetNull();
3898     return;
3899   }
3900   float fNumber = ValueToFloat(info.GetIsolate(), numberValue);
3901 
3902   constexpr int32_t kDefaultWidth = 10;
3903   int32_t iWidth = kDefaultWidth;
3904   if (argc > 1) {
3905     v8::Local<v8::Value> widthValue = GetSimpleValue(info, 1);
3906     iWidth = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), widthValue));
3907     if (iWidth > kMaxCharCount) {
3908       ToFormCalcContext(pThis)->ThrowException("String too long.");
3909       return;
3910     }
3911   }
3912 
3913   constexpr int32_t kDefaultPrecision = 0;
3914   int32_t iPrecision = kDefaultPrecision;
3915   if (argc > 2) {
3916     constexpr int32_t kMaxPrecision = 15;
3917     v8::Local<v8::Value> precision_value = GetSimpleValue(info, 2);
3918     iPrecision = std::max(0, static_cast<int32_t>(ValueToFloat(
3919                                  info.GetIsolate(), precision_value)));
3920     iPrecision = std::min(iPrecision, kMaxPrecision);
3921   }
3922 
3923   ByteString bsFormat = "%";
3924   if (iPrecision) {
3925     bsFormat += ".";
3926     bsFormat += ByteString::FormatInteger(iPrecision);
3927   }
3928   bsFormat += "f";
3929   ByteString bsNumber = ByteString::Format(bsFormat.c_str(), fNumber);
3930 
3931   const char* pData = bsNumber.c_str();
3932   int32_t iLength = bsNumber.GetLength();
3933   int32_t u = 0;
3934   UNSAFE_TODO({
3935     while (u < iLength) {
3936       if (pData[u] == '.') {
3937         break;
3938       }
3939 
3940       ++u;
3941     }
3942 
3943     if (u > iWidth || (iPrecision + u) >= iWidth) {
3944       DataVector<char> stars(std::max(iWidth, 0), '*');
3945       info.GetReturnValue().Set(
3946           fxv8::NewStringHelper(info.GetIsolate(), ByteStringView(stars)));
3947       return;
3948     }
3949 
3950     ByteString resultBuf;
3951     if (u == iLength) {
3952       if (iLength > iWidth) {
3953         int32_t i = 0;
3954         while (i < iWidth) {
3955           resultBuf += '*';
3956           ++i;
3957         }
3958       } else {
3959         int32_t i = 0;
3960         while (i < iWidth - iLength) {
3961           resultBuf += ' ';
3962           ++i;
3963         }
3964         resultBuf += pData;
3965       }
3966       info.GetReturnValue().Set(
3967           fxv8::NewStringHelper(info.GetIsolate(), resultBuf.AsStringView()));
3968       return;
3969     }
3970 
3971     int32_t iLeavingSpace = iWidth - u - iPrecision;
3972     if (iPrecision != 0) {
3973       iLeavingSpace--;
3974     }
3975 
3976     int32_t i = 0;
3977     while (i < iLeavingSpace) {
3978       resultBuf += ' ';
3979       ++i;
3980     }
3981     i = 0;
3982     while (i < u) {
3983       resultBuf += pData[i];
3984       ++i;
3985     }
3986     if (iPrecision != 0) {
3987       resultBuf += '.';
3988     }
3989 
3990     u++;
3991     i = 0;
3992     while (u < iLength) {
3993       if (i >= iPrecision) {
3994         break;
3995       }
3996 
3997       resultBuf += pData[u];
3998       ++i;
3999       ++u;
4000     }
4001     while (i < iPrecision) {
4002       resultBuf += '0';
4003       ++i;
4004     }
4005     info.GetReturnValue().Set(
4006         fxv8::NewStringHelper(info.GetIsolate(), resultBuf.AsStringView()));
4007   });
4008 }
4009 
4010 // static
Stuff(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4011 void CFXJSE_FormCalcContext::Stuff(
4012     CFXJSE_HostObject* pThis,
4013     const v8::FunctionCallbackInfo<v8::Value>& info) {
4014   int32_t argc = info.Length();
4015   if (argc < 3 || argc > 4) {
4016     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Stuff");
4017     return;
4018   }
4019 
4020   v8::Local<v8::Value> sourceValue = GetSimpleValue(info, 0);
4021   v8::Local<v8::Value> startValue = GetSimpleValue(info, 1);
4022   v8::Local<v8::Value> deleteValue = GetSimpleValue(info, 2);
4023   if (fxv8::IsNull(sourceValue) || fxv8::IsNull(startValue) ||
4024       fxv8::IsNull(deleteValue)) {
4025     info.GetReturnValue().SetNull();
4026     return;
4027   }
4028 
4029   int32_t iStart = 1;  // one-based character indexing.
4030   int32_t iDelete = 0;
4031   ByteString bsSource = ValueToUTF8String(info.GetIsolate(), sourceValue);
4032   int32_t iLength = pdfium::checked_cast<int32_t>(bsSource.GetLength());
4033   if (iLength) {
4034     iStart = std::clamp(
4035         static_cast<int32_t>(ValueToFloat(info.GetIsolate(), startValue)), 1,
4036         iLength);
4037     iDelete = std::clamp(
4038         static_cast<int32_t>(ValueToFloat(info.GetIsolate(), deleteValue)), 0,
4039         iLength - iStart + 1);
4040   }
4041 
4042   ByteString bsInsert;
4043   if (argc > 3) {
4044     v8::Local<v8::Value> insertValue = GetSimpleValue(info, 3);
4045     bsInsert = ValueToUTF8String(info.GetIsolate(), insertValue);
4046   }
4047 
4048   --iStart;  // now zero-based.
4049   ByteString bsResult = {bsSource.AsStringView().First(iStart),
4050                          bsInsert.AsStringView(),
4051                          bsSource.AsStringView().Substr(iStart + iDelete)};
4052   info.GetReturnValue().Set(
4053       fxv8::NewStringHelper(info.GetIsolate(), bsResult.AsStringView()));
4054 }
4055 
4056 // static
Substr(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4057 void CFXJSE_FormCalcContext::Substr(
4058     CFXJSE_HostObject* pThis,
4059     const v8::FunctionCallbackInfo<v8::Value>& info) {
4060   if (info.Length() != 3) {
4061     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Substr");
4062     return;
4063   }
4064 
4065   v8::Local<v8::Value> string_value = GetSimpleValue(info, 0);
4066   v8::Local<v8::Value> start_value = GetSimpleValue(info, 1);
4067   v8::Local<v8::Value> end_value = GetSimpleValue(info, 2);
4068   if (ValueIsNull(info.GetIsolate(), string_value) ||
4069       ValueIsNull(info.GetIsolate(), start_value) ||
4070       ValueIsNull(info.GetIsolate(), end_value)) {
4071     info.GetReturnValue().SetNull();
4072     return;
4073   }
4074 
4075   ByteString bsSource = ValueToUTF8String(info.GetIsolate(), string_value);
4076   size_t iLength = bsSource.GetLength();
4077   if (iLength == 0) {
4078     info.GetReturnValue().SetEmptyString();
4079     return;
4080   }
4081 
4082   // |start_value| is 1-based. Assume first character if |start_value| is less
4083   // than 1, per spec. Subtract 1 since |iStart| is 0-based.
4084   size_t iStart =
4085       std::max(ValueToInteger(info.GetIsolate(), start_value), 1) - 1;
4086   if (iStart >= iLength) {
4087     info.GetReturnValue().SetEmptyString();
4088     return;
4089   }
4090 
4091   // Negative values are treated as 0. Can't clamp() due to sign mismatches.
4092   size_t iCount = std::max(ValueToInteger(info.GetIsolate(), end_value), 0);
4093   iCount = std::min(iCount, iLength - iStart);
4094   info.GetReturnValue().Set(fxv8::NewStringHelper(
4095       info.GetIsolate(), bsSource.Substr(iStart, iCount).AsStringView()));
4096 }
4097 
4098 // static
Uuid(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4099 void CFXJSE_FormCalcContext::Uuid(
4100     CFXJSE_HostObject* pThis,
4101     const v8::FunctionCallbackInfo<v8::Value>& info) {
4102   int32_t argc = info.Length();
4103   if (argc < 0 || argc > 1) {
4104     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Uuid");
4105     return;
4106   }
4107 
4108   int32_t iNum = 0;
4109   if (argc > 0) {
4110     v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4111     iNum = static_cast<int32_t>(ValueToFloat(info.GetIsolate(), argOne));
4112   }
4113   info.GetReturnValue().Set(fxv8::NewStringHelper(
4114       info.GetIsolate(), GUIDString(!!iNum).AsStringView()));
4115 }
4116 
4117 // static
Upper(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4118 void CFXJSE_FormCalcContext::Upper(
4119     CFXJSE_HostObject* pThis,
4120     const v8::FunctionCallbackInfo<v8::Value>& info) {
4121   int32_t argc = info.Length();
4122   if (argc < 1 || argc > 2) {
4123     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("Upper");
4124     return;
4125   }
4126 
4127   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4128   if (ValueIsNull(info.GetIsolate(), argOne)) {
4129     info.GetReturnValue().SetNull();
4130     return;
4131   }
4132 
4133   ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
4134   WideString wsArg = WideString::FromUTF8(bsArg.AsStringView());
4135   WideString upperStringBuf;
4136   upperStringBuf.Reserve(wsArg.GetLength());
4137   for (wchar_t ch : wsArg) {
4138     if ((ch >= 0x61 && ch <= 0x7A) || (ch >= 0xE0 && ch <= 0xFE))
4139       ch -= 32;
4140     else if (ch == 0x101 || ch == 0x103 || ch == 0x105)
4141       ch -= 1;
4142 
4143     upperStringBuf += ch;
4144   }
4145   info.GetReturnValue().Set(fxv8::NewStringHelper(
4146       info.GetIsolate(),
4147       FX_UTF8Encode(upperStringBuf.AsStringView()).AsStringView()));
4148 }
4149 
4150 // static
WordNum(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4151 void CFXJSE_FormCalcContext::WordNum(
4152     CFXJSE_HostObject* pThis,
4153     const v8::FunctionCallbackInfo<v8::Value>& info) {
4154   int32_t argc = info.Length();
4155   if (argc < 1 || argc > 3) {
4156     ToFormCalcContext(pThis)->ThrowParamCountMismatchException("WordNum");
4157     return;
4158   }
4159 
4160   v8::Local<v8::Value> numberValue = GetSimpleValue(info, 0);
4161   if (fxv8::IsNull(numberValue)) {
4162     info.GetReturnValue().SetNull();
4163     return;
4164   }
4165   float fNumber = ValueToFloat(info.GetIsolate(), numberValue);
4166 
4167   int32_t iIdentifier = 0;
4168   if (argc > 1) {
4169     v8::Local<v8::Value> identifierValue = GetSimpleValue(info, 1);
4170     if (fxv8::IsNull(identifierValue)) {
4171       info.GetReturnValue().SetNull();
4172       return;
4173     }
4174     iIdentifier =
4175         static_cast<int32_t>(ValueToFloat(info.GetIsolate(), identifierValue));
4176   }
4177 
4178   ByteString bsLocale;
4179   if (argc > 2) {
4180     v8::Local<v8::Value> localeValue = GetSimpleValue(info, 2);
4181     if (fxv8::IsNull(localeValue)) {
4182       info.GetReturnValue().SetNull();
4183       return;
4184     }
4185     bsLocale = ValueToUTF8String(info.GetIsolate(), localeValue);
4186   }
4187 
4188   if (isnan(fNumber) || fNumber < 0.0f || fNumber > 922337203685477550.0f) {
4189     info.GetReturnValue().Set(fxv8::NewStringHelper(info.GetIsolate(), "*"));
4190     return;
4191   }
4192   ByteString bsFormatted = ByteString::Format("%.2f", fNumber);
4193   ByteString bsWorded = WordUS(bsFormatted.AsStringView(), iIdentifier);
4194   info.GetReturnValue().Set(
4195       fxv8::NewStringHelper(info.GetIsolate(), bsWorded.AsStringView()));
4196 }
4197 
4198 // static
Get(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4199 void CFXJSE_FormCalcContext::Get(
4200     CFXJSE_HostObject* pThis,
4201     const v8::FunctionCallbackInfo<v8::Value>& info) {
4202   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4203   if (info.Length() != 1) {
4204     pContext->ThrowParamCountMismatchException("Get");
4205     return;
4206   }
4207 
4208   CXFA_Document* pDoc = pContext->GetDocument();
4209   if (!pDoc)
4210     return;
4211 
4212   CXFA_FFApp::CallbackIface* pAppProvider = pDoc->GetNotify()->GetAppProvider();
4213   if (!pAppProvider)
4214     return;
4215 
4216   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4217   ByteString bsUrl = ValueToUTF8String(info.GetIsolate(), argOne);
4218   RetainPtr<IFX_SeekableReadStream> pFile =
4219       pAppProvider->DownloadURL(WideString::FromUTF8(bsUrl.AsStringView()));
4220   if (!pFile)
4221     return;
4222 
4223   FX_FILESIZE size = pFile->GetSize();
4224   DataVector<uint8_t> data_buf(size);
4225 
4226   // TODO(tsepez): check return value?
4227   (void)pFile->ReadBlockAtOffset(data_buf, 0);
4228   info.GetReturnValue().Set(
4229       fxv8::NewStringHelper(info.GetIsolate(), ByteStringView(data_buf)));
4230 }
4231 
4232 // static
Post(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4233 void CFXJSE_FormCalcContext::Post(
4234     CFXJSE_HostObject* pThis,
4235     const v8::FunctionCallbackInfo<v8::Value>& info) {
4236   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4237   int32_t argc = info.Length();
4238   if (argc < 2 || argc > 5) {
4239     pContext->ThrowParamCountMismatchException("Post");
4240     return;
4241   }
4242 
4243   CXFA_Document* pDoc = pContext->GetDocument();
4244   if (!pDoc)
4245     return;
4246 
4247   CXFA_FFApp::CallbackIface* pAppProvider = pDoc->GetNotify()->GetAppProvider();
4248   if (!pAppProvider)
4249     return;
4250 
4251   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4252   ByteString bsURL = ValueToUTF8String(info.GetIsolate(), argOne);
4253 
4254   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
4255   ByteString bsData = ValueToUTF8String(info.GetIsolate(), argTwo);
4256 
4257   ByteString bsContentType;
4258   if (argc > 2) {
4259     v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
4260     bsContentType = ValueToUTF8String(info.GetIsolate(), argThree);
4261   }
4262 
4263   ByteString bsEncode;
4264   if (argc > 3) {
4265     v8::Local<v8::Value> argFour = GetSimpleValue(info, 3);
4266     bsEncode = ValueToUTF8String(info.GetIsolate(), argFour);
4267   }
4268 
4269   ByteString bsHeader;
4270   if (argc > 4) {
4271     v8::Local<v8::Value> argFive = GetSimpleValue(info, 4);
4272     bsHeader = ValueToUTF8String(info.GetIsolate(), argFive);
4273   }
4274 
4275   WideString decodedResponse;
4276   if (!pAppProvider->PostRequestURL(
4277           WideString::FromUTF8(bsURL.AsStringView()),
4278           WideString::FromUTF8(bsData.AsStringView()),
4279           WideString::FromUTF8(bsContentType.AsStringView()),
4280           WideString::FromUTF8(bsEncode.AsStringView()),
4281           WideString::FromUTF8(bsHeader.AsStringView()), decodedResponse)) {
4282     pContext->ThrowServerDeniedException();
4283     return;
4284   }
4285   info.GetReturnValue().Set(fxv8::NewStringHelper(
4286       info.GetIsolate(), decodedResponse.ToUTF8().AsStringView()));
4287 }
4288 
4289 // static
Put(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4290 void CFXJSE_FormCalcContext::Put(
4291     CFXJSE_HostObject* pThis,
4292     const v8::FunctionCallbackInfo<v8::Value>& info) {
4293   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4294   int32_t argc = info.Length();
4295   if (argc < 2 || argc > 3) {
4296     pContext->ThrowParamCountMismatchException("Put");
4297     return;
4298   }
4299 
4300   CXFA_Document* pDoc = pContext->GetDocument();
4301   if (!pDoc)
4302     return;
4303 
4304   CXFA_FFApp::CallbackIface* pAppProvider = pDoc->GetNotify()->GetAppProvider();
4305   if (!pAppProvider)
4306     return;
4307 
4308   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4309   ByteString bsURL = ValueToUTF8String(info.GetIsolate(), argOne);
4310 
4311   v8::Local<v8::Value> argTwo = GetSimpleValue(info, 1);
4312   ByteString bsData = ValueToUTF8String(info.GetIsolate(), argTwo);
4313 
4314   ByteString bsEncode;
4315   if (argc > 2) {
4316     v8::Local<v8::Value> argThree = GetSimpleValue(info, 2);
4317     bsEncode = ValueToUTF8String(info.GetIsolate(), argThree);
4318   }
4319   if (!pAppProvider->PutRequestURL(
4320           WideString::FromUTF8(bsURL.AsStringView()),
4321           WideString::FromUTF8(bsData.AsStringView()),
4322           WideString::FromUTF8(bsEncode.AsStringView()))) {
4323     pContext->ThrowServerDeniedException();
4324     return;
4325   }
4326   info.GetReturnValue().SetEmptyString();
4327 }
4328 
4329 // static
assign_value_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4330 void CFXJSE_FormCalcContext::assign_value_operator(
4331     CFXJSE_HostObject* pThis,
4332     const v8::FunctionCallbackInfo<v8::Value>& info) {
4333   v8::Isolate* pIsolate = info.GetIsolate();
4334   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4335   if (info.Length() != 2) {
4336     pContext->ThrowCompilerErrorException();
4337     return;
4338   }
4339   ByteStringView bsFuncName("asgn_val_op");
4340   v8::Local<v8::Value> lValue = info[0];
4341   v8::Local<v8::Value> rValue = GetSimpleValue(info, 1);
4342   if (fxv8::IsArray(lValue)) {
4343     v8::Local<v8::Array> arr = lValue.As<v8::Array>();
4344     uint32_t iLeftLength = fxv8::GetArrayLengthHelper(arr);
4345     v8::Local<v8::Value> propertyValue =
4346         fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, 1);
4347     for (uint32_t i = 2; i < iLeftLength; i++) {
4348       v8::Local<v8::Value> jsValue =
4349           fxv8::ReentrantGetArrayElementHelper(pIsolate, arr, i);
4350       if (!fxv8::IsObject(jsValue)) {
4351         pContext->ThrowNoDefaultPropertyException(bsFuncName);
4352         return;
4353       }
4354       v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
4355       if (fxv8::IsNull(propertyValue)) {
4356         if (!SetObjectDefaultValue(pIsolate, jsObjectValue, rValue)) {
4357           pContext->ThrowNoDefaultPropertyException(bsFuncName);
4358           return;
4359         }
4360       } else {
4361         fxv8::ReentrantPutObjectPropertyHelper(
4362             pIsolate, jsObjectValue,
4363             fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue)
4364                 .AsStringView(),
4365             rValue);
4366       }
4367     }
4368   } else if (fxv8::IsObject(lValue)) {
4369     if (!SetObjectDefaultValue(pIsolate, lValue.As<v8::Object>(), rValue)) {
4370       pContext->ThrowNoDefaultPropertyException(bsFuncName);
4371       return;
4372     }
4373   }
4374   info.GetReturnValue().Set(rValue);
4375 }
4376 
4377 // static
logical_or_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4378 void CFXJSE_FormCalcContext::logical_or_operator(
4379     CFXJSE_HostObject* pThis,
4380     const v8::FunctionCallbackInfo<v8::Value>& info) {
4381   if (info.Length() != 2) {
4382     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4383     return;
4384   }
4385 
4386   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4387   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4388   if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
4389     info.GetReturnValue().SetNull();
4390     return;
4391   }
4392 
4393   float first = ValueToFloat(info.GetIsolate(), argFirst);
4394   float second = ValueToFloat(info.GetIsolate(), argSecond);
4395   info.GetReturnValue().Set(static_cast<int>(first || second));
4396 }
4397 
4398 // static
logical_and_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4399 void CFXJSE_FormCalcContext::logical_and_operator(
4400     CFXJSE_HostObject* pThis,
4401     const v8::FunctionCallbackInfo<v8::Value>& info) {
4402   if (info.Length() != 2) {
4403     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4404     return;
4405   }
4406 
4407   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4408   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4409   if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
4410     info.GetReturnValue().SetNull();
4411     return;
4412   }
4413 
4414   float first = ValueToFloat(info.GetIsolate(), argFirst);
4415   float second = ValueToFloat(info.GetIsolate(), argSecond);
4416   info.GetReturnValue().Set(static_cast<int>(first && second));
4417 }
4418 
4419 // static
equality_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4420 void CFXJSE_FormCalcContext::equality_operator(
4421     CFXJSE_HostObject* pThis,
4422     const v8::FunctionCallbackInfo<v8::Value>& info) {
4423   if (info.Length() != 2) {
4424     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4425     return;
4426   }
4427 
4428   if (fm_ref_equal(pThis, info)) {
4429     info.GetReturnValue().Set(1);
4430     return;
4431   }
4432 
4433   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4434   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4435   if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
4436     info.GetReturnValue().Set(
4437         static_cast<int>(fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)));
4438     return;
4439   }
4440 
4441   if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
4442     info.GetReturnValue().Set(static_cast<int>(
4443         fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst) ==
4444         fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond)));
4445     return;
4446   }
4447 
4448   double first = ValueToDouble(info.GetIsolate(), argFirst);
4449   double second = ValueToDouble(info.GetIsolate(), argSecond);
4450   info.GetReturnValue().Set(static_cast<int>(first == second));
4451 }
4452 
4453 // static
notequality_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4454 void CFXJSE_FormCalcContext::notequality_operator(
4455     CFXJSE_HostObject* pThis,
4456     const v8::FunctionCallbackInfo<v8::Value>& info) {
4457   if (info.Length() != 2) {
4458     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4459     return;
4460   }
4461 
4462   if (fm_ref_equal(pThis, info)) {
4463     info.GetReturnValue().Set(0);
4464     return;
4465   }
4466 
4467   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4468   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4469   if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
4470     info.GetReturnValue().Set(
4471         static_cast<int>(!fxv8::IsNull(argFirst) || !fxv8::IsNull(argSecond)));
4472     return;
4473   }
4474 
4475   if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
4476     info.GetReturnValue().Set(static_cast<int>(
4477         fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst) !=
4478         fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond)));
4479     return;
4480   }
4481 
4482   double first = ValueToDouble(info.GetIsolate(), argFirst);
4483   double second = ValueToDouble(info.GetIsolate(), argSecond);
4484   info.GetReturnValue().Set(static_cast<int>(first != second));
4485 }
4486 
4487 // static
fm_ref_equal(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4488 bool CFXJSE_FormCalcContext::fm_ref_equal(
4489     CFXJSE_HostObject* pThis,
4490     const v8::FunctionCallbackInfo<v8::Value>& info) {
4491   v8::Local<v8::Value> argFirst = info[0];
4492   v8::Local<v8::Value> argSecond = info[1];
4493   if (!fxv8::IsArray(argFirst) || !fxv8::IsArray(argSecond))
4494     return false;
4495 
4496   v8::Local<v8::Array> firstArr = argFirst.As<v8::Array>();
4497   v8::Local<v8::Array> secondArr = argSecond.As<v8::Array>();
4498   v8::Local<v8::Value> firstFlag =
4499       fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), firstArr, 0);
4500   v8::Local<v8::Value> secondFlag =
4501       fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), secondArr, 0);
4502   if (fxv8::ReentrantToInt32Helper(info.GetIsolate(), firstFlag) != 3 ||
4503       fxv8::ReentrantToInt32Helper(info.GetIsolate(), secondFlag) != 3) {
4504     return false;
4505   }
4506 
4507   v8::Local<v8::Value> firstValue =
4508       fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), firstArr, 2);
4509   v8::Local<v8::Value> secondValue =
4510       fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), secondArr, 2);
4511 
4512   if (fxv8::IsNull(firstValue) || fxv8::IsNull(secondValue))
4513     return false;
4514 
4515   return FXJSE_RetrieveObjectBinding(firstValue) ==
4516          FXJSE_RetrieveObjectBinding(secondValue);
4517 }
4518 
4519 // static
less_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4520 void CFXJSE_FormCalcContext::less_operator(
4521     CFXJSE_HostObject* pThis,
4522     const v8::FunctionCallbackInfo<v8::Value>& info) {
4523   if (info.Length() != 2) {
4524     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4525     return;
4526   }
4527 
4528   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4529   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4530   if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
4531     info.GetReturnValue().Set(0);
4532     return;
4533   }
4534 
4535   if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
4536     ByteString bs1 =
4537         fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
4538     ByteString bs2 =
4539         fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
4540     info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) < 0);
4541     return;
4542   }
4543 
4544   double first = ValueToDouble(info.GetIsolate(), argFirst);
4545   double second = ValueToDouble(info.GetIsolate(), argSecond);
4546   info.GetReturnValue().Set(static_cast<int>(first < second));
4547 }
4548 
4549 // static
lessequal_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4550 void CFXJSE_FormCalcContext::lessequal_operator(
4551     CFXJSE_HostObject* pThis,
4552     const v8::FunctionCallbackInfo<v8::Value>& info) {
4553   if (info.Length() != 2) {
4554     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4555     return;
4556   }
4557 
4558   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4559   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4560   if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
4561     info.GetReturnValue().Set(
4562         static_cast<int>(fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)));
4563     return;
4564   }
4565 
4566   if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
4567     auto bs1 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
4568     auto bs2 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
4569     info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) <= 0);
4570     return;
4571   }
4572 
4573   double first = ValueToDouble(info.GetIsolate(), argFirst);
4574   double second = ValueToDouble(info.GetIsolate(), argSecond);
4575   info.GetReturnValue().Set(static_cast<int>(first <= second));
4576 }
4577 
4578 // static
greater_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4579 void CFXJSE_FormCalcContext::greater_operator(
4580     CFXJSE_HostObject* pThis,
4581     const v8::FunctionCallbackInfo<v8::Value>& info) {
4582   if (info.Length() != 2) {
4583     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4584     return;
4585   }
4586 
4587   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4588   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4589   if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
4590     info.GetReturnValue().Set(0);
4591     return;
4592   }
4593 
4594   if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
4595     auto bs1 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
4596     auto bs2 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
4597     info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) > 0);
4598     return;
4599   }
4600 
4601   double first = ValueToDouble(info.GetIsolate(), argFirst);
4602   double second = ValueToDouble(info.GetIsolate(), argSecond);
4603   info.GetReturnValue().Set(static_cast<int>(first > second));
4604 }
4605 
4606 // static
greaterequal_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4607 void CFXJSE_FormCalcContext::greaterequal_operator(
4608     CFXJSE_HostObject* pThis,
4609     const v8::FunctionCallbackInfo<v8::Value>& info) {
4610   if (info.Length() != 2) {
4611     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4612     return;
4613   }
4614 
4615   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4616   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4617   if (fxv8::IsNull(argFirst) || fxv8::IsNull(argSecond)) {
4618     info.GetReturnValue().Set(
4619         static_cast<int>(fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)));
4620     return;
4621   }
4622 
4623   if (fxv8::IsString(argFirst) && fxv8::IsString(argSecond)) {
4624     auto bs1 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argFirst);
4625     auto bs2 = fxv8::ReentrantToByteStringHelper(info.GetIsolate(), argSecond);
4626     info.GetReturnValue().Set(bs1.Compare(bs2.AsStringView()) >= 0);
4627     return;
4628   }
4629 
4630   double first = ValueToDouble(info.GetIsolate(), argFirst);
4631   double second = ValueToDouble(info.GetIsolate(), argSecond);
4632   info.GetReturnValue().Set(static_cast<int>(first >= second));
4633 }
4634 
4635 // static
plus_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4636 void CFXJSE_FormCalcContext::plus_operator(
4637     CFXJSE_HostObject* pThis,
4638     const v8::FunctionCallbackInfo<v8::Value>& info) {
4639   if (info.Length() != 2) {
4640     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4641     return;
4642   }
4643 
4644   if (ValueIsNull(info.GetIsolate(), info[0]) &&
4645       ValueIsNull(info.GetIsolate(), info[1])) {
4646     info.GetReturnValue().SetNull();
4647     return;
4648   }
4649 
4650   const double first = ValueToDouble(info.GetIsolate(), info[0]);
4651   const double second = ValueToDouble(info.GetIsolate(), info[1]);
4652   info.GetReturnValue().Set(first + second);
4653 }
4654 
4655 // static
minus_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4656 void CFXJSE_FormCalcContext::minus_operator(
4657     CFXJSE_HostObject* pThis,
4658     const v8::FunctionCallbackInfo<v8::Value>& info) {
4659   if (info.Length() != 2) {
4660     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4661     return;
4662   }
4663 
4664   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4665   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4666   if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
4667     info.GetReturnValue().SetNull();
4668     return;
4669   }
4670 
4671   double first = ValueToDouble(info.GetIsolate(), argFirst);
4672   double second = ValueToDouble(info.GetIsolate(), argSecond);
4673   info.GetReturnValue().Set(first - second);
4674 }
4675 
4676 // static
multiple_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4677 void CFXJSE_FormCalcContext::multiple_operator(
4678     CFXJSE_HostObject* pThis,
4679     const v8::FunctionCallbackInfo<v8::Value>& info) {
4680   if (info.Length() != 2) {
4681     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4682     return;
4683   }
4684 
4685   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4686   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4687   if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
4688     info.GetReturnValue().SetNull();
4689     return;
4690   }
4691 
4692   double first = ValueToDouble(info.GetIsolate(), argFirst);
4693   double second = ValueToDouble(info.GetIsolate(), argSecond);
4694   info.GetReturnValue().Set(first * second);
4695 }
4696 
4697 // static
divide_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4698 void CFXJSE_FormCalcContext::divide_operator(
4699     CFXJSE_HostObject* pThis,
4700     const v8::FunctionCallbackInfo<v8::Value>& info) {
4701   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4702   if (info.Length() != 2) {
4703     pContext->ThrowCompilerErrorException();
4704     return;
4705   }
4706 
4707   v8::Local<v8::Value> argFirst = GetSimpleValue(info, 0);
4708   v8::Local<v8::Value> argSecond = GetSimpleValue(info, 1);
4709   if (fxv8::IsNull(argFirst) && fxv8::IsNull(argSecond)) {
4710     info.GetReturnValue().SetNull();
4711     return;
4712   }
4713 
4714   double second = ValueToDouble(info.GetIsolate(), argSecond);
4715   if (second == 0.0) {
4716     pContext->ThrowDivideByZeroException();
4717     return;
4718   }
4719 
4720   double first = ValueToDouble(info.GetIsolate(), argFirst);
4721   info.GetReturnValue().Set(first / second);
4722 }
4723 
4724 // static
positive_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4725 void CFXJSE_FormCalcContext::positive_operator(
4726     CFXJSE_HostObject* pThis,
4727     const v8::FunctionCallbackInfo<v8::Value>& info) {
4728   if (info.Length() != 1) {
4729     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4730     return;
4731   }
4732 
4733   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4734   if (fxv8::IsNull(argOne)) {
4735     info.GetReturnValue().SetNull();
4736     return;
4737   }
4738   info.GetReturnValue().Set(0.0 + ValueToDouble(info.GetIsolate(), argOne));
4739 }
4740 
4741 // static
negative_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4742 void CFXJSE_FormCalcContext::negative_operator(
4743     CFXJSE_HostObject* pThis,
4744     const v8::FunctionCallbackInfo<v8::Value>& info) {
4745   if (info.Length() != 1) {
4746     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4747     return;
4748   }
4749 
4750   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4751   if (fxv8::IsNull(argOne)) {
4752     info.GetReturnValue().SetNull();
4753     return;
4754   }
4755   info.GetReturnValue().Set(0.0 - ValueToDouble(info.GetIsolate(), argOne));
4756 }
4757 
4758 // static
logical_not_operator(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4759 void CFXJSE_FormCalcContext::logical_not_operator(
4760     CFXJSE_HostObject* pThis,
4761     const v8::FunctionCallbackInfo<v8::Value>& info) {
4762   if (info.Length() != 1) {
4763     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4764     return;
4765   }
4766 
4767   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4768   if (fxv8::IsNull(argOne)) {
4769     info.GetReturnValue().SetNull();
4770     return;
4771   }
4772 
4773   double first = ValueToDouble(info.GetIsolate(), argOne);
4774   info.GetReturnValue().Set((first == 0.0) ? 1 : 0);
4775 }
4776 
4777 // static
dot_accessor(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4778 void CFXJSE_FormCalcContext::dot_accessor(
4779     CFXJSE_HostObject* pThis,
4780     const v8::FunctionCallbackInfo<v8::Value>& info) {
4781   DotAccessorCommon(pThis, info, /*bDotAccessor=*/true);
4782 }
4783 
4784 // static
dotdot_accessor(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4785 void CFXJSE_FormCalcContext::dotdot_accessor(
4786     CFXJSE_HostObject* pThis,
4787     const v8::FunctionCallbackInfo<v8::Value>& info) {
4788   DotAccessorCommon(pThis, info, /*bDotAccessor=*/false);
4789 }
4790 
4791 // static
eval_translation(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4792 void CFXJSE_FormCalcContext::eval_translation(
4793     CFXJSE_HostObject* pThis,
4794     const v8::FunctionCallbackInfo<v8::Value>& info) {
4795   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4796   if (info.Length() != 1) {
4797     pContext->ThrowParamCountMismatchException("Eval");
4798     return;
4799   }
4800 
4801   v8::Local<v8::Value> argOne = GetSimpleValue(info, 0);
4802   ByteString bsArg = ValueToUTF8String(info.GetIsolate(), argOne);
4803   if (bsArg.IsEmpty()) {
4804     pContext->ThrowArgumentMismatchException();
4805     return;
4806   }
4807 
4808   WideString wsCalcScript = WideString::FromUTF8(bsArg.AsStringView());
4809   std::optional<WideTextBuffer> wsJavaScriptBuf =
4810       CFXJSE_FormCalcContext::Translate(pContext->GetDocument()->GetHeap(),
4811                                         wsCalcScript.AsStringView());
4812   if (!wsJavaScriptBuf.has_value()) {
4813     pContext->ThrowCompilerErrorException();
4814     return;
4815   }
4816   info.GetReturnValue().Set(fxv8::NewStringHelper(
4817       info.GetIsolate(),
4818       FX_UTF8Encode(wsJavaScriptBuf.value().AsStringView()).AsStringView()));
4819 }
4820 
4821 // static
is_fm_object(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4822 void CFXJSE_FormCalcContext::is_fm_object(
4823     CFXJSE_HostObject* pThis,
4824     const v8::FunctionCallbackInfo<v8::Value>& info) {
4825   const bool result = info.Length() == 1 && fxv8::IsObject(info[0]);
4826   info.GetReturnValue().Set(result);
4827 }
4828 
4829 // static
is_fm_array(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4830 void CFXJSE_FormCalcContext::is_fm_array(
4831     CFXJSE_HostObject* pThis,
4832     const v8::FunctionCallbackInfo<v8::Value>& info) {
4833   const bool result = info.Length() == 1 && fxv8::IsArray(info[0]);
4834   info.GetReturnValue().Set(result);
4835 }
4836 
4837 // static
get_fm_value(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4838 void CFXJSE_FormCalcContext::get_fm_value(
4839     CFXJSE_HostObject* pThis,
4840     const v8::FunctionCallbackInfo<v8::Value>& info) {
4841   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4842   if (info.Length() != 1) {
4843     pContext->ThrowCompilerErrorException();
4844     return;
4845   }
4846 
4847   v8::Local<v8::Value> argOne = info[0];
4848   if (fxv8::IsArray(argOne)) {
4849     v8::Local<v8::Array> arr = argOne.As<v8::Array>();
4850     v8::Local<v8::Value> propertyValue =
4851         fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 1);
4852     v8::Local<v8::Value> jsValue =
4853         fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2);
4854     if (!fxv8::IsObject(jsValue)) {
4855       info.GetReturnValue().Set(fxv8::NewUndefinedHelper(info.GetIsolate()));
4856       return;
4857     }
4858     v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
4859     if (fxv8::IsNull(propertyValue)) {
4860       info.GetReturnValue().Set(
4861           GetObjectDefaultValue(info.GetIsolate(), jsObjectValue));
4862       return;
4863     }
4864     ByteString bsName =
4865         fxv8::ReentrantToByteStringHelper(info.GetIsolate(), propertyValue);
4866     info.GetReturnValue().Set(fxv8::ReentrantGetObjectPropertyHelper(
4867         info.GetIsolate(), jsObjectValue, bsName.AsStringView()));
4868     return;
4869   }
4870 
4871   if (fxv8::IsObject(argOne)) {
4872     v8::Local<v8::Object> obj = argOne.As<v8::Object>();
4873     info.GetReturnValue().Set(GetObjectDefaultValue(info.GetIsolate(), obj));
4874     return;
4875   }
4876 
4877   info.GetReturnValue().Set(argOne);
4878 }
4879 
4880 // static
get_fm_jsobj(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4881 void CFXJSE_FormCalcContext::get_fm_jsobj(
4882     CFXJSE_HostObject* pThis,
4883     const v8::FunctionCallbackInfo<v8::Value>& info) {
4884   if (info.Length() != 1) {
4885     ToFormCalcContext(pThis)->ThrowCompilerErrorException();
4886     return;
4887   }
4888 
4889   v8::Local<v8::Value> argOne = info[0];
4890   if (!fxv8::IsArray(argOne)) {
4891     info.GetReturnValue().Set(argOne);
4892     return;
4893   }
4894 
4895   v8::Local<v8::Array> arr = argOne.As<v8::Array>();
4896   info.GetReturnValue().Set(
4897       fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2));
4898 }
4899 
4900 // static
fm_var_filter(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4901 void CFXJSE_FormCalcContext::fm_var_filter(
4902     CFXJSE_HostObject* pThis,
4903     const v8::FunctionCallbackInfo<v8::Value>& info) {
4904   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
4905   if (info.Length() != 1) {
4906     pContext->ThrowCompilerErrorException();
4907     return;
4908   }
4909 
4910   v8::Local<v8::Value> argOne = info[0];
4911   if (!fxv8::IsArray(argOne)) {
4912     info.GetReturnValue().Set(GetSimpleValue(info, 0));
4913     return;
4914   }
4915 
4916   v8::Local<v8::Array> arr = argOne.As<v8::Array>();
4917   v8::Local<v8::Value> flagsValue =
4918       fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 0);
4919   int32_t iFlags = fxv8::ReentrantToInt32Helper(info.GetIsolate(), flagsValue);
4920   if (iFlags != 3 && iFlags != 4) {
4921     info.GetReturnValue().Set(GetSimpleValue(info, 0));
4922     return;
4923   }
4924 
4925   if (iFlags == 4) {
4926     v8::LocalVector<v8::Value> values(info.GetIsolate(), 3);
4927     values[0] = fxv8::NewNumberHelper(info.GetIsolate(), 3);
4928     values[1] = fxv8::NewNullHelper(info.GetIsolate());
4929     values[2] = fxv8::NewNullHelper(info.GetIsolate());
4930     info.GetReturnValue().Set(fxv8::NewArrayHelper(info.GetIsolate(), values));
4931     return;
4932   }
4933 
4934   v8::Local<v8::Value> objectValue =
4935       fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, 2);
4936   if (fxv8::IsNull(objectValue)) {
4937     pContext->ThrowCompilerErrorException();
4938     return;
4939   }
4940   info.GetReturnValue().Set(argOne);
4941 }
4942 
4943 // static
concat_fm_object(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info)4944 void CFXJSE_FormCalcContext::concat_fm_object(
4945     CFXJSE_HostObject* pThis,
4946     const v8::FunctionCallbackInfo<v8::Value>& info) {
4947   v8::Isolate* pIsolate = ToFormCalcContext(pThis)->GetIsolate();
4948   v8::LocalVector<v8::Value> returnValues(pIsolate);
4949   for (int i = 0; i < info.Length(); ++i) {
4950     if (fxv8::IsArray(info[i])) {
4951       v8::Local<v8::Array> arr = info[i].As<v8::Array>();
4952       uint32_t length = fxv8::GetArrayLengthHelper(arr);
4953       for (uint32_t j = 2; j < length; j++) {
4954         returnValues.push_back(
4955             fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, j));
4956       }
4957     }
4958     returnValues.push_back(info[i]);
4959   }
4960   info.GetReturnValue().Set(fxv8::NewArrayHelper(pIsolate, returnValues));
4961 }
4962 
4963 // static
GenerateSomExpression(ByteStringView bsName,int32_t iIndexFlags,int32_t iIndexValue,bool bIsStar)4964 ByteString CFXJSE_FormCalcContext::GenerateSomExpression(ByteStringView bsName,
4965                                                          int32_t iIndexFlags,
4966                                                          int32_t iIndexValue,
4967                                                          bool bIsStar) {
4968   if (bIsStar)
4969     return ByteString(bsName, "[*]");
4970 
4971   // `iIndexFlags` values are the same as enum class
4972   // `CXFA_FMIndexExpression::AccessorIndex` values.
4973   if (iIndexFlags == 0)
4974     return ByteString(bsName);
4975 
4976   if (iIndexFlags == 1 || iIndexValue == 0) {
4977     return ByteString(bsName, "[") + ByteString::FormatInteger(iIndexValue) +
4978            "]";
4979   }
4980 
4981   const bool bNegative = iIndexValue < 0;
4982   ByteString bsSomExp(bsName);
4983   if (iIndexFlags == 2) {
4984     bsSomExp += bNegative ? "[-" : "[+";
4985   } else {
4986     DCHECK_EQ(iIndexFlags, 3);
4987     bsSomExp += bNegative ? "[" : "[-";
4988   }
4989 
4990   FX_SAFE_INT32 safe_index = iIndexValue;
4991   if (bNegative)
4992     safe_index = -safe_index;
4993   bsSomExp += ByteString::FormatInteger(safe_index.ValueOrDefault(0));
4994   bsSomExp += "]";
4995   return bsSomExp;
4996 }
4997 
Translate(cppgc::Heap * pHeap,WideStringView wsFormcalc)4998 std::optional<WideTextBuffer> CFXJSE_FormCalcContext::Translate(
4999     cppgc::Heap* pHeap,
5000     WideStringView wsFormcalc) {
5001   if (wsFormcalc.IsEmpty())
5002     return WideTextBuffer();
5003 
5004   CXFA_FMLexer lexer(wsFormcalc);
5005   CXFA_FMParser parser(pHeap, &lexer);
5006   CXFA_FMAST* ast = parser.Parse();
5007   if (!ast || parser.HasError())
5008     return std::nullopt;
5009 
5010   CXFA_FMToJavaScriptDepth::Reset();
5011   std::optional<WideTextBuffer> wsJavaScript = ast->ToJavaScript();
5012   if (!wsJavaScript.has_value())
5013     return std::nullopt;
5014 
5015   if (CXFA_IsTooBig(wsJavaScript.value()))
5016     return std::nullopt;
5017 
5018   return wsJavaScript;
5019 }
5020 
CFXJSE_FormCalcContext(v8::Isolate * pIsolate,CFXJSE_Context * pScriptContext,CXFA_Document * pDoc)5021 CFXJSE_FormCalcContext::CFXJSE_FormCalcContext(v8::Isolate* pIsolate,
5022                                                CFXJSE_Context* pScriptContext,
5023                                                CXFA_Document* pDoc)
5024     : m_pIsolate(pIsolate), m_pDocument(pDoc) {
5025   m_Value.Reset(m_pIsolate,
5026                 NewBoundV8Object(
5027                     m_pIsolate, CFXJSE_Class::Create(
5028                                     pScriptContext, &kFormCalcDescriptor, false)
5029                                     ->GetTemplate(m_pIsolate)));
5030 }
5031 
5032 CFXJSE_FormCalcContext::~CFXJSE_FormCalcContext() = default;
5033 
AsFormCalcContext()5034 CFXJSE_FormCalcContext* CFXJSE_FormCalcContext::AsFormCalcContext() {
5035   return this;
5036 }
5037 
GlobalPropertyGetter()5038 v8::Local<v8::Value> CFXJSE_FormCalcContext::GlobalPropertyGetter() {
5039   return v8::Local<v8::Value>::New(m_pIsolate, m_Value);
5040 }
5041 
5042 // static
DotAccessorCommon(CFXJSE_HostObject * pThis,const v8::FunctionCallbackInfo<v8::Value> & info,bool bDotAccessor)5043 void CFXJSE_FormCalcContext::DotAccessorCommon(
5044     CFXJSE_HostObject* pThis,
5045     const v8::FunctionCallbackInfo<v8::Value>& info,
5046     bool bDotAccessor) {
5047   CFXJSE_FormCalcContext* pContext = ToFormCalcContext(pThis);
5048   v8::Isolate* pIsolate = pContext->GetIsolate();
5049   int32_t argc = info.Length();
5050   if (argc < 4 || argc > 5) {
5051     pContext->ThrowCompilerErrorException();
5052     return;
5053   }
5054 
5055   bool bIsStar = true;
5056   int32_t iIndexValue = 0;
5057   if (argc > 4) {
5058     bIsStar = false;
5059     iIndexValue = ValueToInteger(info.GetIsolate(), info[4]);
5060   }
5061 
5062   const ByteString bsName =
5063       fxv8::ReentrantToByteStringHelper(info.GetIsolate(), info[2]);
5064   const bool bHasNoResolveName = bDotAccessor && bsName.IsEmpty();
5065   ByteString bsSomExp = GenerateSomExpression(
5066       bsName.AsStringView(),
5067       fxv8::ReentrantToInt32Helper(info.GetIsolate(), info[3]), iIndexValue,
5068       bIsStar);
5069 
5070   v8::Local<v8::Value> argAccessor = info[0];
5071   if (fxv8::IsArray(argAccessor)) {
5072     v8::Local<v8::Array> arr = argAccessor.As<v8::Array>();
5073     uint32_t iLength = fxv8::GetArrayLengthHelper(arr);
5074     if (iLength < 3) {
5075       pContext->ThrowArgumentMismatchException();
5076       return;
5077     }
5078 
5079     // TODO(crbug.com/pdfium/2090) - doublecheck use of std::vector
5080     std::vector<v8::LocalVector<v8::Value>> resolveValues(
5081         iLength - 2, v8::LocalVector<v8::Value>(info.GetIsolate()));
5082 
5083     bool bAttribute = false;
5084     bool bAllEmpty = true;
5085     for (uint32_t i = 2; i < iLength; i++) {
5086       v8::Local<v8::Value> hJSObjValue =
5087           fxv8::ReentrantGetArrayElementHelper(info.GetIsolate(), arr, i);
5088       std::optional<CFXJSE_Engine::ResolveResult> maybeResult =
5089           ResolveObjects(pThis, hJSObjValue, bsSomExp.AsStringView(),
5090                          bDotAccessor, bHasNoResolveName);
5091       if (maybeResult.has_value()) {
5092         resolveValues[i - 2] = ParseResolveResult(pThis, maybeResult.value(),
5093                                                   hJSObjValue, &bAttribute);
5094         bAllEmpty = bAllEmpty && resolveValues[i - 2].empty();
5095       }
5096     }
5097     if (bAllEmpty) {
5098       pContext->ThrowPropertyNotInObjectException(bsName.AsStringView(),
5099                                                   bsSomExp.AsStringView());
5100       return;
5101     }
5102 
5103     v8::LocalVector<v8::Value> values(pIsolate);
5104     values.push_back(fxv8::NewNumberHelper(pIsolate, 1));
5105     values.push_back(
5106         bAttribute ? fxv8::NewStringHelper(pIsolate, bsName.AsStringView())
5107                          .As<v8::Value>()
5108                    : fxv8::NewNullHelper(pIsolate).As<v8::Value>());
5109     for (uint32_t i = 0; i < iLength - 2; i++) {
5110       for (size_t j = 0; j < resolveValues[i].size(); j++)
5111         values.push_back(resolveValues[i][j]);
5112     }
5113     info.GetReturnValue().Set(fxv8::NewArrayHelper(pIsolate, values));
5114     return;
5115   }
5116 
5117   std::optional<CFXJSE_Engine::ResolveResult> maybeResult;
5118   ByteString bsAccessorName =
5119       fxv8::ReentrantToByteStringHelper(info.GetIsolate(), info[1]);
5120   if (fxv8::IsObject(argAccessor) ||
5121       (fxv8::IsNull(argAccessor) && bsAccessorName.IsEmpty())) {
5122     maybeResult = ResolveObjects(pThis, argAccessor, bsSomExp.AsStringView(),
5123                                  bDotAccessor, bHasNoResolveName);
5124   } else if (!fxv8::IsObject(argAccessor) && !bsAccessorName.IsEmpty()) {
5125     v8::Local<v8::Value> obj =
5126         GetObjectForName(pThis, bsAccessorName.AsStringView());
5127     if (!obj.IsEmpty()) {
5128       argAccessor = obj;
5129       maybeResult = ResolveObjects(pThis, argAccessor, bsSomExp.AsStringView(),
5130                                    bDotAccessor, bHasNoResolveName);
5131     }
5132   }
5133   if (!maybeResult.has_value()) {
5134     pContext->ThrowPropertyNotInObjectException(bsName.AsStringView(),
5135                                                 bsSomExp.AsStringView());
5136     return;
5137   }
5138 
5139   bool bAttribute = false;
5140   v8::LocalVector<v8::Value> resolveValues =
5141       ParseResolveResult(pThis, maybeResult.value(), argAccessor, &bAttribute);
5142 
5143   v8::LocalVector<v8::Value> values(pIsolate, resolveValues.size() + 2);
5144   values[0] = fxv8::NewNumberHelper(pIsolate, 1);
5145   values[1] = bAttribute
5146                   ? fxv8::NewStringHelper(pIsolate, bsName.AsStringView())
5147                         .As<v8::Value>()
5148                   : fxv8::NewNullHelper(pIsolate).As<v8::Value>();
5149 
5150   for (size_t i = 0; i < resolveValues.size(); i++)
5151     values[i + 2] = resolveValues[i];
5152 
5153   info.GetReturnValue().Set(fxv8::NewArrayHelper(pIsolate, values));
5154 }
5155 
5156 // static
IsIsoDateFormat(ByteStringView bsData,int32_t * pYear,int32_t * pMonth,int32_t * pDay)5157 bool CFXJSE_FormCalcContext::IsIsoDateFormat(ByteStringView bsData,
5158                                              int32_t* pYear,
5159                                              int32_t* pMonth,
5160                                              int32_t* pDay) {
5161   pdfium::span<const char> pData = bsData.span();
5162 
5163   int32_t& iYear = *pYear;
5164   int32_t& iMonth = *pMonth;
5165   int32_t& iDay = *pDay;
5166 
5167   iYear = 0;
5168   iMonth = 1;
5169   iDay = 1;
5170 
5171   if (pData.size() < 4) {
5172     return false;
5173   }
5174 
5175   std::array<char, 5> szYear = {};
5176   for (int32_t i = 0; i < 4; ++i) {
5177     if (!isdigit(pData[i])) {
5178       return false;
5179     }
5180     szYear[i] = pData[i];
5181   }
5182   iYear = FXSYS_atoi(szYear.data());
5183   if (pData.size() == 4) {
5184     return true;
5185   }
5186 
5187   int32_t iStyle = pData[4] == '-' ? 1 : 0;
5188   size_t iPosOff = iStyle == 0 ? 4 : 5;
5189   if (!isdigit(pData[iPosOff]) || !isdigit(pData[iPosOff + 1])) {
5190     return false;
5191   }
5192 
5193   char szBuffer[3] = {};
5194   szBuffer[0] = pData[iPosOff];
5195   szBuffer[1] = pData[iPosOff + 1];
5196   iMonth = FXSYS_atoi(szBuffer);
5197   if (iMonth > 12 || iMonth < 1) {
5198     return false;
5199   }
5200 
5201   if (iStyle == 0) {
5202     iPosOff += 2;
5203     if (pData.size() == 6) {
5204       return true;
5205     }
5206   } else {
5207     iPosOff += 3;
5208     if (pData.size() == 7) {
5209       return true;
5210     }
5211   }
5212   if (!isdigit(pData[iPosOff]) || !isdigit(pData[iPosOff + 1])) {
5213     return false;
5214   }
5215 
5216   szBuffer[0] = pData[iPosOff];
5217   szBuffer[1] = pData[iPosOff + 1];
5218   iDay = FXSYS_atoi(szBuffer);
5219   if (iPosOff + 2 < pData.size()) {
5220     return false;
5221   }
5222 
5223   if (iMonth == 2) {
5224     bool bIsLeap = (!(iYear % 4) && (iYear % 100)) || !(iYear % 400);
5225     return iDay <= (bIsLeap ? 29 : 28);
5226   }
5227 
5228   if (iMonth < 8) {
5229     return iDay <= (iMonth % 2 == 0 ? 30 : 31);
5230   }
5231   return iDay <= (iMonth % 2 == 0 ? 31 : 30);
5232 }
5233 
5234 // static
IsIsoTimeFormat(ByteStringView bsData)5235 bool CFXJSE_FormCalcContext::IsIsoTimeFormat(ByteStringView bsData) {
5236   enum State { kHour, kMinute, kSecond, kZoneHour, kZoneMinute, kFinished };
5237 
5238   pdfium::span<const char> pData = bsData.span();
5239   if (pData.empty()) {
5240     return false;
5241   }
5242 
5243   size_t iZone = 0;
5244   size_t i = 0;
5245   while (i < pData.size()) {
5246     if (!isdigit(pData[i]) && pData[i] != ':') {
5247       iZone = i;
5248       break;
5249     }
5250     ++i;
5251   }
5252   if (i == pData.size()) {
5253     iZone = pData.size();
5254   }
5255 
5256   char szBuffer[3] = {};  // Last char always stays NUL for termination.
5257   State state = kHour;
5258   size_t iIndex = 0;
5259   while (iIndex + 1 < iZone) {
5260     szBuffer[0] = pData[iIndex];
5261     szBuffer[1] = pData[iIndex + 1];
5262     if (!isdigit(szBuffer[0]) || !isdigit(szBuffer[1])) {
5263       return false;
5264     }
5265     int32_t value = FXSYS_atoi(szBuffer);
5266     if (state == kHour) {
5267       if (value >= 24) {
5268         return false;
5269       }
5270       state = kMinute;
5271     } else if (state == kMinute) {
5272       if (value >= 60) {
5273         return false;
5274       }
5275       state = kSecond;
5276     } else if (state == kSecond) {
5277       // Allow leap second.
5278       if (value > 60) {
5279         return false;
5280       }
5281       state = kFinished;
5282     } else {
5283       return false;
5284     }
5285     iIndex += 2;
5286     if (iIndex < iZone && pData[iIndex] == ':') {
5287       ++iIndex;
5288     }
5289   }
5290 
5291   if (iIndex < pData.size() && pData[iIndex] == '.') {
5292     constexpr int kSubSecondLength = 3;
5293     if (iIndex + kSubSecondLength >= pData.size()) {
5294       return false;
5295     }
5296 
5297     ++iIndex;
5298     std::array<char, kSubSecondLength + 1> szMilliSeconds = {};
5299     for (int j = 0; j < kSubSecondLength; ++j) {
5300       char c = pData[iIndex + j];
5301       if (!isdigit(c)) {
5302         return false;
5303       }
5304       szMilliSeconds[j] = c;
5305     }
5306     if (FXSYS_atoi(szMilliSeconds.data()) >= 1000) {
5307       return false;
5308     }
5309     iIndex += kSubSecondLength;
5310   }
5311 
5312   if (iIndex < pData.size() && FXSYS_towlower(pData[iIndex]) == 'z') {
5313     return true;
5314   }
5315 
5316   if (iIndex < pData.size()) {
5317     if (pData[iIndex] == '+') {
5318       ++iIndex;
5319     } else if (pData[iIndex] == '-') {
5320       ++iIndex;
5321     }
5322   }
5323   state = kZoneHour;
5324   while (iIndex + 1 < pData.size()) {
5325     szBuffer[0] = pData[iIndex];
5326     szBuffer[1] = pData[iIndex + 1];
5327     if (!isdigit(szBuffer[0]) || !isdigit(szBuffer[1])) {
5328       return false;
5329     }
5330     int32_t value = FXSYS_atoi(szBuffer);
5331     if (state == kZoneHour) {
5332       if (value >= 24) {
5333         return false;
5334       }
5335       state = kZoneMinute;
5336     } else if (state == kZoneMinute) {
5337       if (value >= 60) {
5338         return false;
5339       }
5340       state = kFinished;
5341     } else {
5342       return false;
5343     }
5344     iIndex += 2;
5345     if (iIndex < pData.size() && pData[iIndex] == ':') {
5346       ++iIndex;
5347     }
5348   }
5349 
5350   // Success if all input was processed.
5351   return iIndex == pData.size();
5352 }
5353 
IsIsoDateTimeFormat(ByteStringView bsData,int32_t * pYear,int32_t * pMonth,int32_t * pDay)5354 bool CFXJSE_FormCalcContext::IsIsoDateTimeFormat(ByteStringView bsData,
5355                                                  int32_t* pYear,
5356                                                  int32_t* pMonth,
5357                                                  int32_t* pDay) {
5358   *pYear = 0;
5359   *pMonth = 0;
5360   *pDay = 0;
5361 
5362   size_t iIndex = 0;
5363   while (iIndex < bsData.GetLength()) {
5364     if (bsData[iIndex] == 'T' || bsData[iIndex] == 't') {
5365       break;
5366     }
5367     ++iIndex;
5368   }
5369   if (iIndex == bsData.GetLength() || (iIndex != 8 && iIndex != 10)) {
5370     return false;
5371   }
5372 
5373   ByteStringView date_part = bsData.First(iIndex);
5374   ByteStringView time_part = bsData.Substr(iIndex + 1);
5375   return IsIsoDateFormat(date_part, pYear, pMonth, pDay) &&
5376          IsIsoTimeFormat(time_part);
5377 }
5378 
5379 // static
DateString2Num(ByteStringView bsDate)5380 int32_t CFXJSE_FormCalcContext::DateString2Num(ByteStringView bsDate) {
5381   int32_t iYear = 0;
5382   int32_t iMonth = 0;
5383   int32_t iDay = 0;
5384   if (bsDate.GetLength() <= 10) {
5385     if (!IsIsoDateFormat(bsDate, &iYear, &iMonth, &iDay)) {
5386       return 0;
5387     }
5388   } else {
5389     if (!IsIsoDateTimeFormat(bsDate, &iYear, &iMonth, &iDay)) {
5390       return 0;
5391     }
5392   }
5393 
5394   float dDays = 0;
5395   int32_t i = 1;
5396   if (iYear < 1900) {
5397     return 0;
5398   }
5399 
5400   while (iYear - i >= 1900) {
5401     dDays +=
5402         ((!((iYear - i) % 4) && ((iYear - i) % 100)) || !((iYear - i) % 400))
5403             ? 366
5404             : 365;
5405     ++i;
5406   }
5407   i = 1;
5408   while (i < iMonth) {
5409     if (i == 2) {
5410       dDays += ((!(iYear % 4) && (iYear % 100)) || !(iYear % 400)) ? 29 : 28;
5411     } else if (i <= 7) {
5412       dDays += (i % 2 == 0) ? 30 : 31;
5413     } else {
5414       dDays += (i % 2 == 0) ? 31 : 30;
5415     }
5416 
5417     ++i;
5418   }
5419   i = 0;
5420   while (iDay - i > 0) {
5421     ++dDays;
5422     ++i;
5423   }
5424   return static_cast<int32_t>(dDays);
5425 }
5426 
ApplyToExpansion(std::function<void (v8::Isolate *,v8::Local<v8::Value>)> fn,const v8::FunctionCallbackInfo<v8::Value> & info,bool bStrict)5427 bool CFXJSE_FormCalcContext::ApplyToExpansion(
5428     std::function<void(v8::Isolate*, v8::Local<v8::Value>)> fn,
5429     const v8::FunctionCallbackInfo<v8::Value>& info,
5430     bool bStrict) {
5431   v8::Isolate* pIsolate = info.GetIsolate();
5432   for (int32_t i = 0; i < info.Length(); i++) {
5433     v8::Local<v8::Value> argValue = info[i];
5434     if (fxv8::IsArray(argValue)) {
5435       if (!ApplyToArray(pIsolate, fn, argValue.As<v8::Array>()) && bStrict) {
5436         ThrowArgumentMismatchException();
5437         return false;
5438       }
5439     } else if (fxv8::IsObject(argValue)) {
5440       ApplyToObject(pIsolate, fn, argValue.As<v8::Object>());
5441     } else if (!fxv8::IsNull(argValue)) {
5442       fn(pIsolate, argValue);
5443     }
5444   }
5445   return true;
5446 }
5447 
ApplyToArray(v8::Isolate * pIsolate,std::function<void (v8::Isolate *,v8::Local<v8::Value>)> fn,v8::Local<v8::Array> pArray)5448 bool CFXJSE_FormCalcContext::ApplyToArray(
5449     v8::Isolate* pIsolate,
5450     std::function<void(v8::Isolate*, v8::Local<v8::Value>)> fn,
5451     v8::Local<v8::Array> pArray) {
5452   uint32_t iLength = fxv8::GetArrayLengthHelper(pArray);
5453   if (iLength < 3)
5454     return false;
5455 
5456   v8::Local<v8::Value> propertyValue =
5457       fxv8::ReentrantGetArrayElementHelper(pIsolate, pArray, 1);
5458 
5459   ByteString bsName;
5460   const bool nullprop = fxv8::IsNull(propertyValue);
5461   if (!nullprop)
5462     bsName = fxv8::ReentrantToByteStringHelper(pIsolate, propertyValue);
5463 
5464   for (uint32_t j = 2; j < iLength; j++) {
5465     v8::Local<v8::Value> jsValue =
5466         fxv8::ReentrantGetArrayElementHelper(pIsolate, pArray, j);
5467     if (!fxv8::IsObject(jsValue))
5468       continue;
5469 
5470     v8::Local<v8::Object> jsObjectValue = jsValue.As<v8::Object>();
5471     v8::Local<v8::Value> newPropertyValue =
5472         nullprop ? GetObjectDefaultValue(pIsolate, jsObjectValue)
5473                  : fxv8::ReentrantGetObjectPropertyHelper(
5474                        pIsolate, jsObjectValue, bsName.AsStringView());
5475     if (!fxv8::IsNull(newPropertyValue))
5476       fn(pIsolate, newPropertyValue);
5477   }
5478   return true;
5479 }
5480 
ApplyToObject(v8::Isolate * pIsolate,std::function<void (v8::Isolate *,v8::Local<v8::Value>)> fn,v8::Local<v8::Object> pObject)5481 void CFXJSE_FormCalcContext::ApplyToObject(
5482     v8::Isolate* pIsolate,
5483     std::function<void(v8::Isolate*, v8::Local<v8::Value>)> fn,
5484     v8::Local<v8::Object> pObject) {
5485   v8::Local<v8::Value> newPropertyValue =
5486       GetObjectDefaultValue(pIsolate, pObject);
5487   if (!fxv8::IsNull(newPropertyValue))
5488     fn(pIsolate, newPropertyValue);
5489 }
5490 
ThrowNoDefaultPropertyException(ByteStringView name) const5491 void CFXJSE_FormCalcContext::ThrowNoDefaultPropertyException(
5492     ByteStringView name) const {
5493   ByteString msg(name);
5494   msg += " doesn't have a default property.";
5495   ThrowException(msg.AsStringView());
5496 }
5497 
ThrowCompilerErrorException() const5498 void CFXJSE_FormCalcContext::ThrowCompilerErrorException() const {
5499   ThrowException("Compiler error.");
5500 }
5501 
ThrowDivideByZeroException() const5502 void CFXJSE_FormCalcContext::ThrowDivideByZeroException() const {
5503   ThrowException("Divide by zero.");
5504 }
5505 
ThrowServerDeniedException() const5506 void CFXJSE_FormCalcContext::ThrowServerDeniedException() const {
5507   ThrowException("Server does not permit operation.");
5508 }
5509 
ThrowPropertyNotInObjectException(ByteStringView name,ByteStringView exp) const5510 void CFXJSE_FormCalcContext::ThrowPropertyNotInObjectException(
5511     ByteStringView name,
5512     ByteStringView exp) const {
5513   ByteString msg("An attempt was made to reference property '");
5514   msg += name;
5515   msg += "' of a non-object in SOM expression ";
5516   msg += exp;
5517   msg += ".";
5518   ThrowException(msg.AsStringView());
5519 }
5520 
ThrowParamCountMismatchException(ByteStringView method) const5521 void CFXJSE_FormCalcContext::ThrowParamCountMismatchException(
5522     ByteStringView method) const {
5523   ByteString msg("Incorrect number of parameters calling method '");
5524   msg += method;
5525   msg += "'.";
5526   ThrowException(msg.AsStringView());
5527 }
5528 
ThrowArgumentMismatchException() const5529 void CFXJSE_FormCalcContext::ThrowArgumentMismatchException() const {
5530   ThrowException("Argument mismatch in property or function argument.");
5531 }
5532 
ThrowException(ByteStringView str) const5533 void CFXJSE_FormCalcContext::ThrowException(ByteStringView str) const {
5534   DCHECK(!str.IsEmpty());
5535   FXJSE_ThrowMessage(GetIsolate(), str);
5536 }
5537