Revision control
++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:significant
++
++ #include "qv4globalobject_p.h"
++
++ #include <private/qv4alloca_p.h>
++ #include <private/qv4codegen_p.h>
++ #include <private/qv4context_p.h>
++ #include <private/qv4function_p.h>
++ #include <private/qv4mm_p.h>
++ #include <private/qv4scopedvalue_p.h>
++ #include <private/qv4script_p.h>
++ #include <private/qv4stackframe_p.h>
++ #include <private/qv4string_p.h>
++ #include <private/qv4value_p.h>
++
++ #include <wtf/MathExtras.h>
++
++ #include <QtCore/private/qlocale_tools_p.h>
++ #include <QtCore/private/qtools_p.h>
++
++ #include <QtCore/qdebug.h>
++ #include <QtCore/qstring.h>
++
++ #include <iostream>
++
++ using namespace QV4;
++ using QtMiscUtils::toHexUpper;
++ using QtMiscUtils::fromHex;
++
++ static QString escape(const QString &input)
++ {
++ QString output;
++ output.reserve(input.size() * 3);
++ const int length = input.size();
++ for (int i = 0; i < length; ++i) {
++ ushort uc = input.at(i).unicode();
++ if (uc < 0x100) {
++ if ( (uc > 0x60 && uc < 0x7B)
++ || (uc > 0x3F && uc < 0x5B)
++ || (uc > 0x2C && uc < 0x3A)
++ || (uc == 0x2A)
++ || (uc == 0x2B)
++ || (uc == 0x5F)) {
++ output.append(QChar(uc));
++ } else {
++ output.append(u'%');
++ output.append(QLatin1Char(toHexUpper(uc >> 4)));
++ output.append(QLatin1Char(toHexUpper(uc)));
++ }
++ } else {
++ output.append(u'%');
++ output.append(u'u');
++ output.append(QLatin1Char(toHexUpper(uc >> 12)));
++ output.append(QLatin1Char(toHexUpper(uc >> 8)));
++ output.append(QLatin1Char(toHexUpper(uc >> 4)));
++ output.append(QLatin1Char(toHexUpper(uc)));
++ }
++ }
++ return output;
++ }
++
++ static QString unescape(const QString &input)
++ {
++ QString result;
++ result.reserve(input.size());
++ int i = 0;
++ const int length = input.size();
++ while (i < length) {
++ QChar c = input.at(i++);
++ if ((c == u'%') && (i + 1 < length)) {
++ QChar a = input.at(i);
++ if ((a == u'u') && (i + 4 < length)) {
++ int d3 = fromHex(input.at(i+1).unicode());
++ int d2 = fromHex(input.at(i+2).unicode());
++ int d1 = fromHex(input.at(i+3).unicode());
++ int d0 = fromHex(input.at(i+4).unicode());
++ if ((d3 != -1) && (d2 != -1) && (d1 != -1) && (d0 != -1)) {
++ ushort uc = ushort((d3 << 12) | (d2 << 8) | (d1 << 4) | d0);
++ result.append(QChar(uc));
++ i += 5;
++ } else {
++ result.append(c);
++ }
++ } else {
++ int d1 = fromHex(a.unicode());
++ int d0 = fromHex(input.at(i+1).unicode());
++ if ((d1 != -1) && (d0 != -1)) {
++ c = QChar((d1 << 4) | d0);
++ i += 2;
++ }
++ result.append(c);
++ }
++ } else {
++ result.append(c);
++ }
++ }
++ return result;
++ }
++
++ static const char uriReserved[] = ";/?:@&=+$,#";
++ static const char uriUnescaped[] = "-_.!~*'()";
++ static const char uriUnescapedReserved[] = "-_.!~*'();/?:@&=+$,#";
++
++ static void addEscapeSequence(QString &output, uchar ch)
++ {
++ output.append(QLatin1Char('%'));
++ output.append(QLatin1Char(toHexUpper(ch >> 4)));
++ output.append(QLatin1Char(toHexUpper(ch & 0xf)));
++ }
++
++ static QString encode(const QString &input, const char *unescapedSet, bool *ok)
++ {
++ *ok = true;
++ QString output;
++ const int length = input.size();
++ int i = 0;
++ while (i < length) {
++ const QChar c = input.at(i);
++ bool escape = true;
++ if ((c.unicode() >= 'a' && c.unicode() <= 'z') ||
++ (c.unicode() >= 'A' && c.unicode() <= 'Z') ||
++ (c.unicode() >= '0' && c.unicode() <= '9')) {
++ escape = false;
++ } else {
++ const char *r = unescapedSet;
++ while (*r) {
++ if (*r == c.unicode()) {
++ escape = false;
++ break;
++ }
++ ++r;
++ }
++ }
++ if (escape) {
++ uint uc = c.unicode();
++ if ((uc >= 0xDC00) && (uc <= 0xDFFF)) {
++ *ok = false;
++ break;
++ }
++ if (!((uc < 0xD800) || (uc > 0xDBFF))) {
++ ++i;
++ if (i == length) {
++ *ok = false;
++ break;
++ }
++ const uint uc2 = input.at(i).unicode();
++ if ((uc2 < 0xDC00) || (uc2 > 0xDFFF)) {
++ *ok = false;
++ break;
++ }
++ uc = ((uc - 0xD800) * 0x400) + (uc2 - 0xDC00) + 0x10000;
++ }
++ if (uc < 0x80) {
++ addEscapeSequence(output, (uchar)uc);
++ } else {
++ if (uc < 0x0800) {
++ addEscapeSequence(output, 0xc0 | ((uchar) (uc >> 6)));
++ } else {
++
++ if (QChar::requiresSurrogates(uc)) {
++ addEscapeSequence(output, 0xf0 | ((uchar) (uc >> 18)));
++ addEscapeSequence(output, 0x80 | (((uchar) (uc >> 12)) & 0x3f));
++ } else {
++ addEscapeSequence(output, 0xe0 | (((uchar) (uc >> 12)) & 0x3f));
++ }
++ addEscapeSequence(output, 0x80 | (((uchar) (uc >> 6)) & 0x3f));
++ }
++ addEscapeSequence(output, 0x80 | ((uchar) (uc&0x3f)));
++ }
++ } else {
++ output.append(c);
++ }
++ ++i;
++ }
++ if (i != length)
++ *ok = false;
++ return output;
++ }
++
++ enum DecodeMode {
++ DecodeAll,
++ DecodeNonReserved
++ };
++
++ static QString decode(const QString &input, DecodeMode decodeMode, bool *ok)
++ {
++ *ok = true;
++ QString output;
++ output.reserve(input.size());
++ const int length = input.size();
++ int i = 0;
++ const QChar percent = QLatin1Char('%');
++ while (i < length) {
++ const QChar ch = input.at(i);
++ if (ch == percent) {
++ int start = i;
++ if (i + 2 >= length)
++ goto error;
++
++ int d1 = fromHex(input.at(i+1).unicode());
++ int d0 = fromHex(input.at(i+2).unicode());
++ if ((d1 == -1) || (d0 == -1))
++ goto error;
++
++ int b = (d1 << 4) | d0;
++ i += 2;
++ if (b & 0x80) {
++ int uc;
++ int min_uc;
++ int need;
++ if ((b & 0xe0) == 0xc0) {
++ uc = b & 0x1f;
++ need = 1;
++ min_uc = 0x80;
++ } else if ((b & 0xf0) == 0xe0) {
++ uc = b & 0x0f;
++ need = 2;
++ min_uc = 0x800;
++ } else if ((b & 0xf8) == 0xf0) {
++ uc = b & 0x07;
++ need = 3;
++ min_uc = 0x10000;
++ } else {
++ goto error;
++ }
++
++ if (i + (3 * need) >= length)
++ goto error;
++
++ for (int j = 0; j < need; ++j) {
++ ++i;
++ if (input.at(i) != percent)
++ goto error;
++
++ d1 = fromHex(input.at(i+1).unicode());
++ d0 = fromHex(input.at(i+2).unicode());
++ if ((d1 == -1) || (d0 == -1))
++ goto error;
++
++ b = (d1 << 4) | d0;
++ if ((b & 0xC0) != 0x80)
++ goto error;
++
++ i += 2;
++ uc = (uc << 6) + (b & 0x3f);
++ }
++ if (uc < min_uc)
++ goto error;
++
++ if (uc < 0x10000) {
++ output.append(QChar(uc));
++ } else {
++ if (uc > 0x10FFFF)
++ goto error;
++
++ ushort l = ushort(((uc - 0x10000) & 0x3FF) + 0xDC00);
++ ushort h = ushort((((uc - 0x10000) >> 10) & 0x3FF) + 0xD800);
++ output.append(QChar(h));
++ output.append(QChar(l));
++ }
++ } else {
++ if (decodeMode == DecodeNonReserved && b <= 0x40) {
++ const char *r = uriReserved;
++ while (*r) {
++ if (*r == b)
++ break;
++ ++r;
++ }
++ if (*r)
++ output.append(QStringView{input}.mid(start, i - start + 1));
++ else
++ output.append(QChar(b));
++ } else {
++ output.append(QChar(b));
++ }
++ }
++ } else {
++ output.append(ch);
++ }
++ ++i;
++ }
++ if (i != length)
++ *ok = false;
++ return output;
++ error:
++ *ok = false;
++ return QString();
++ }
++
++ DEFINE_OBJECT_VTABLE(EvalFunction);
++
++ void Heap::EvalFunction::init(QV4::ExecutionEngine *engine)
++ {
++ Scope s(engine);
++ Heap::FunctionObject::init(engine, s.engine->id_eval());
++ ScopedFunctionObject f(s, this);
++ f->defineReadonlyConfigurableProperty(s.engine->id_length(), Value::fromInt32(1));
++ }
++
++ static ExecutionContext *evalContext(QV4::ExecutionEngine *v4, bool directCall)
++ {
++ // In case of !directCall, the context for eval should be the global scope
++ if (!directCall)
++ return v4->scriptContext();
++
++ // Otherwise there has to be a current stack frame. We need to be called from somewhere.
++ Q_ASSERT(v4->currentStackFrame);
++ return v4->currentContext();
++ }
++
++ ReturnedValue EvalFunction::evalCall(const Value *, const Value *argv, int argc, bool directCall) const
++ {
++ if (argc < 1)
++ return Encode::undefined();
++
++ ExecutionEngine *v4 = engine();
++ const Function *v4Function = v4->currentStackFrame
++ ? v4->currentStackFrame->v4Function
++ : v4->globalCode;
++ const bool isStrict = v4Function && v4Function->isStrict();
++
++ Scope scope(v4);
++
++ ScopedContext ctx(scope, evalContext(v4, directCall));
++
++ String *scode = argv[0].stringValue();
++ if (!scode)
++ return argv[0].asReturnedValue();
++
++ const QString code = scode->toQString();
++ bool inheritContext = !isStrict;
++
++ Script script(ctx, QV4::Compiler::ContextType::Eval, code, QStringLiteral("eval code"));
++ script.setStrictMode(directCall && isStrict);
++ script.setInheritContext(inheritContext);
++ script.parse();
++ if (v4->hasException)
++ return Encode::undefined();
++
++ Function *function = script.function();
++ if (!function)
++ return Encode::undefined();
++ function->kind = Function::Eval;
++
++ ScopedValue thisObject(scope, directCall
++ ? scope.engine->currentStackFrame->thisObject()
++ : scope.engine->globalObject->asReturnedValue());
++ if (function->isStrict() || isStrict) {
++ ScopedFunctionObject e(scope, FunctionObject::createScriptFunction(ctx, function));
++ return checkedResult(v4, e->call(thisObject, nullptr, 0));
++ }
++
++ return checkedResult(v4, function->call(thisObject, nullptr, 0, ctx));
++ }
++
++
++ ReturnedValue EvalFunction::virtualCall(const FunctionObject *f, const Value *thisObject, const Value *argv, int argc)
++ {
++ // indirect call
++ return static_cast<const EvalFunction *>(f)->evalCall(thisObject, argv, argc, false);
++ }
++
++
++ static inline int toInt(const QChar &qc, int R)
++ {
++ ushort c = qc.unicode();
++ int v = -1;
++ if (c >= '0' && c <= '9')
++ v = c - '0';
++ else if (c >= 'A' && c <= 'Z')
++ v = c - 'A' + 10;
++ else if (c >= 'a' && c <= 'z')
++ v = c - 'a' + 10;
++ if (v >= 0 && v < R)
++ return v;
++ else
++ return -1;
++ }
++
++ // parseInt [15.1.2.2]
++ ReturnedValue GlobalFunctions::method_parseInt(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ Scope scope(b);
++ ScopedValue inputString(scope, argc ? argv[0] : Value::undefinedValue());
++ ScopedValue radix(scope, argc > 1 ? argv[1] : Value::undefinedValue());
++ int R = radix->isUndefined() ? 0 : radix->toInt32();
++
++ // [15.1.2.2] step by step:
++ QString trimmed = inputString->toQString().trimmed(); // 1 + 2
++ CHECK_EXCEPTION();
++
++ const QChar *pos = trimmed.constData();
++ const QChar *end = pos + trimmed.size();
++
++ int sign = 1; // 3
++ if (pos != end) {
++ if (*pos == QLatin1Char('-'))
++ sign = -1; // 4
++ if (*pos == QLatin1Char('-') || *pos == QLatin1Char('+'))
++ ++pos; // 5
++ }
++ bool stripPrefix = true; // 7
++ if (R) { // 8
++ if (R < 2 || R > 36)
++ RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); // 8a
++ if (R != 16)
++ stripPrefix = false; // 8b
++ } else { // 9
++ R = 10; // 9a
++ }
++ if (stripPrefix) { // 10
++ if ((end - pos >= 2)
++ && (pos[0] == QLatin1Char('0'))
++ && (pos[1] == QLatin1Char('x') || pos[1] == QLatin1Char('X'))) { // 10a
++ pos += 2;
++ R = 16;
++ }
++ }
++ // 11: Z is progressively built below
++ // 13: this is handled by the toInt function
++ if (pos == end) // 12
++ RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN()));
++ bool overflow = false;
++ qint64 v_overflow = 0;
++ unsigned overflow_digit_count = 0;
++ int d = toInt(*pos++, R);
++ if (d == -1)
++ RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN()));
++ qint64 v = d;
++ while (pos != end) {
++ d = toInt(*pos++, R);
++ if (d == -1)
++ break;
++ if (overflow) {
++ if (overflow_digit_count == 0) {
++ v_overflow = v;
++ v = 0;
++ }
++ ++overflow_digit_count;
++ v = v * R + d;
++ } else {
++ qint64 vNew = v * R + d;
++ if (vNew < v) {
++ overflow = true;
++ --pos;
++ } else {
++ v = vNew;
++ }
++ }
++ }
++
++ if (overflow) {
++ double result = (double) v_overflow * pow(static_cast<double>(R), static_cast<double>(overflow_digit_count));
++ result += v;
++ RETURN_RESULT(Encode(sign * result));
++ } else {
++ RETURN_RESULT(Encode(sign * (double) v)); // 15
++ }
++ }
++
++ // parseFloat [15.1.2.3]
++ ReturnedValue GlobalFunctions::method_parseFloat(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ Scope scope(b);
++ // [15.1.2.3] step by step:
++ ScopedString inputString(scope, argc ? argv[0] : Value::undefinedValue(), ScopedString::Convert);
++ CHECK_EXCEPTION();
++
++ QString trimmed = inputString->toQString().trimmed(); // 2
++
++ // 4:
++ if (trimmed.startsWith(QLatin1String("Infinity"))
++ || trimmed.startsWith(QLatin1String("+Infinity")))
++ RETURN_RESULT(Encode(Q_INFINITY));
++ if (trimmed.startsWith(QLatin1String("-Infinity")))
++ RETURN_RESULT(Encode(-Q_INFINITY));
++ QByteArray ba = trimmed.toLatin1();
++ bool ok;
++ const char *begin = ba.constData();
++ const char *end = nullptr;
++ double d = qstrtod(begin, &end, &ok);
++ if (end - begin == 0)
++ RETURN_RESULT(Encode(std::numeric_limits<double>::quiet_NaN())); // 3
++ else
++ RETURN_RESULT(Encode(d));
++ }
++
++ /// isNaN [15.1.2.4]
++ ReturnedValue GlobalFunctions::method_isNaN(const FunctionObject *, const Value *, const Value *argv, int argc)
++ {
++ if (!argc)
++ // undefined gets converted to NaN
++ RETURN_RESULT(Encode(true));
++
++ if (argv[0].integerCompatible())
++ RETURN_RESULT(Encode(false));
++
++ double d = argv[0].toNumber();
++ RETURN_RESULT(Encode((bool)std::isnan(d)));
++ }
++
++ /// isFinite [15.1.2.5]
++ ReturnedValue GlobalFunctions::method_isFinite(const FunctionObject *, const Value *, const Value *argv, int argc)
++ {
++ if (!argc)
++ // undefined gets converted to NaN
++ RETURN_RESULT(Encode(false));
++
++ if (argv[0].integerCompatible())
++ RETURN_RESULT(Encode(true));
++
++ double d = argv[0].toNumber();
++ RETURN_RESULT(Encode((bool)std::isfinite(d)));
++ }
++
++ /// decodeURI [15.1.3.1]
++ ReturnedValue GlobalFunctions::method_decodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ if (argc == 0)
++ RETURN_UNDEFINED();
++
++ ExecutionEngine *v4 = b->engine();
++ QString uriString = argv[0].toQString();
++ bool ok;
++ QString out = decode(uriString, DecodeNonReserved, &ok);
++ if (!ok) {
++ Scope scope(v4);
++ ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence")));
++ RETURN_RESULT(scope.engine->throwURIError(s));
++ }
++
++ RETURN_RESULT(v4->newString(out));
++ }
++
++ /// decodeURIComponent [15.1.3.2]
++ ReturnedValue GlobalFunctions::method_decodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ if (argc == 0)
++ RETURN_UNDEFINED();
++
++ ExecutionEngine *v4 = b->engine();
++ QString uriString = argv[0].toQString();
++ bool ok;
++ QString out = decode(uriString, DecodeAll, &ok);
++ if (!ok) {
++ Scope scope(v4);
++ ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence")));
++ RETURN_RESULT(scope.engine->throwURIError(s));
++ }
++
++ RETURN_RESULT(v4->newString(out));
++ }
++
++ /// encodeURI [15.1.3.3]
++ ReturnedValue GlobalFunctions::method_encodeURI(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ if (argc == 0)
++ RETURN_UNDEFINED();
++
++ ExecutionEngine *v4 = b->engine();
++ QString uriString = argv[0].toQString();
++ bool ok;
++ QString out = encode(uriString, uriUnescapedReserved, &ok);
++ if (!ok) {
++ Scope scope(v4);
++ ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence")));
++ RETURN_RESULT(scope.engine->throwURIError(s));
++ }
++
++ RETURN_RESULT(v4->newString(out));
++ }
++
++ /// encodeURIComponent [15.1.3.4]
++ ReturnedValue GlobalFunctions::method_encodeURIComponent(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ if (argc == 0)
++ RETURN_UNDEFINED();
++
++ ExecutionEngine *v4 = b->engine();
++ QString uriString = argv[0].toQString();
++ bool ok;
++ QString out = encode(uriString, uriUnescaped, &ok);
++ if (!ok) {
++ Scope scope(v4);
++ ScopedString s(scope, scope.engine->newString(QStringLiteral("malformed URI sequence")));
++ RETURN_RESULT(scope.engine->throwURIError(s));
++ }
++
++ RETURN_RESULT(v4->newString(out));
++ }
++
++ ReturnedValue GlobalFunctions::method_escape(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ if (!argc)
++ RETURN_RESULT(v4->newString(QStringLiteral("undefined")));
++
++ QString str = argv[0].toQString();
++ RETURN_RESULT(v4->newString(escape(str)));
++ }
++
++ ReturnedValue GlobalFunctions::method_unescape(const FunctionObject *b, const Value *, const Value *argv, int argc)
++ {
++ ExecutionEngine *v4 = b->engine();
++ if (!argc)
++ RETURN_RESULT(v4->newString(QStringLiteral("undefined")));
++
++ QString str = argv[0].toQString();
++ RETURN_RESULT(v4->newString(unescape(str)));
++ }