From 9ddf68b7f49b629d06962fbd3489479ac7178b97 Mon Sep 17 00:00:00 2001 From: yann300 Date: Tue, 16 Jun 2015 12:58:52 +0200 Subject: [PATCH] - array validation in QML - compliant with multi dim/dynamic array --- mix/ContractCallDataEncoder.cpp | 116 +++++++++++++++++++++++--------- mix/ContractCallDataEncoder.h | 5 +- mix/qml/js/InputValidator.js | 113 +++++++++++++++++++++++++++---- 3 files changed, 187 insertions(+), 47 deletions(-) diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index 07ab8dd73..08e764955 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -20,15 +20,19 @@ * Ethereum IDE client. */ -#include +#include #include #include +#include +#include +#include #include #include #include "QVariableDeclaration.h" #include "QVariableDefinition.h" #include "QFunctionDefinition.h" #include "ContractCallDataEncoder.h" +using namespace std; using namespace dev; using namespace dev::solidity; using namespace dev::mix; @@ -38,13 +42,18 @@ bytes ContractCallDataEncoder::encodedData() bytes r(m_encodedData); size_t headerSize = m_encodedData.size() & ~0x1fUL; //ignore any prefix that is not 32-byte aligned //apply offsets - for (auto const& p: m_offsetMap) + for (auto const& p: m_dynamicOffsetMap) + { + vector_ref offsetRef(m_dynamicData.data() + p.first, 32); + toBigEndian>(p.second + headerSize, offsetRef); //add header size minus signature hash + } + for (auto const& p: m_staticOffsetMap) { vector_ref offsetRef(r.data() + p.first, 32); toBigEndian>(p.second + headerSize, offsetRef); //add header size minus signature hash } - - r.insert(r.end(), m_dynamicData.begin(), m_dynamicData.end()); + if (m_dynamicData.size() > 0) + r.insert(r.end(), m_dynamicData.begin(), m_dynamicData.end()); return r; } @@ -54,54 +63,95 @@ void ContractCallDataEncoder::encode(QFunctionDefinition const* _function) m_encodedData.insert(m_encodedData.end(), hash.begin(), hash.end()); } +void ContractCallDataEncoder::encodeArray(QJsonArray const& _array, QList _dim, SolidityType const& _type, bytes& _content) +{ + size_t offsetStart = _content.size(); + if (_dim[0] == -1) + { + bytes count = bytes(32); + toBigEndian((u256)_array.size(), count); + _content += count; //reserved space for count + } + _dim.pop_front(); + + int k = 0; + for (QJsonValue const& c: _array) + { + if (c.isArray()) + { + if (_dim[0] == -1) + { + m_dynamicOffsetMap.push_back(std::make_pair(m_dynamicData.size() + offsetStart + 32 + k * 32, + m_dynamicData.size() + _content.size())); + } + encodeArray(c.toArray(), _dim, _type, _content); + } + else + { + // encode single item + if (c.isDouble()) + encodeSingleItem(QString::number(c.toDouble()), _type, _content); + else if (c.isString()) + encodeSingleItem(c.toString(), _type, _content); + } + k++; + } +} + void ContractCallDataEncoder::encode(QVariant const& _data, SolidityType const& _type) { - u256 count = 1; QStringList strList; + vector dim; if (_type.array) { - if (_data.type() == QVariant::String) - strList = _data.toString().split(",", QString::SkipEmptyParts); //TODO: proper parsing - else - strList = _data.toStringList(); - count = strList.count(); + QList dim = extractDimension(_type. name); + bytes content; + QJsonDocument jsonResponse = QJsonDocument::fromJson(_data.toString().toUtf8()); + QJsonArray jsonObject = jsonResponse.array(); + size_t size = m_encodedData.size(); + if (dim[0] == -1) + { + m_encodedData += bytes(32); // reserve space for offset + m_staticOffsetMap.push_back(std::make_pair(size, m_dynamicData.size())); + } + encodeArray(jsonObject, dim, _type, content); + if (!_type.dynamicSize) + m_encodedData.insert(m_encodedData.end(), content.begin(), content.end()); + else + m_dynamicData.insert(m_dynamicData.end(), content.begin(), content.end()); } - else - strList.append(_data.toString()); - - if (_type.dynamicSize) + else if (_type.dynamicSize && _type.type == SolidityType::Type::Bytes) { bytes empty(32); size_t sizePos = m_dynamicData.size(); m_dynamicData += empty; //reserve space for count - if (_type.type == SolidityType::Type::Bytes) - count = encodeSingleItem(_data.toString(), _type, m_dynamicData); - else - { - count = strList.count(); - for (auto const& item: strList) - encodeSingleItem(item, _type, m_dynamicData); - } + u256 count = encodeSingleItem(_data.toString(), _type, m_dynamicData); vector_ref sizeRef(m_dynamicData.data() + sizePos, 32); toBigEndian(count, sizeRef); - m_offsetMap.push_back(std::make_pair(m_encodedData.size(), sizePos)); + m_staticOffsetMap.push_back(std::make_pair(m_encodedData.size(), sizePos)); m_encodedData += empty; //reserve space for offset } else + encodeSingleItem(_data.toString(), _type, m_encodedData); +} + +QList ContractCallDataEncoder::extractDimension(QString const& _type) +{ + QList dim; + QRegExp dimExtract("(\\[[^\\]]*\\])"); + int pos = dimExtract.indexIn(_type); + while (pos != -1) { - if (_type.array) - count = _type.count; - int c = static_cast(count); - if (strList.size() > c) - strList.erase(strList.begin() + c, strList.end()); + QString d = dimExtract.cap(0); + pos += d.length(); + pos = dimExtract.indexIn(_type, pos); + if (d == "[]") + dim.push_front(-1); else - while (strList.size() < c) - strList.append(QString()); - - for (auto const& item: strList) - encodeSingleItem(item, _type, m_encodedData); + dim.push_front(d.replace("[", "").replace("]", "").toInt()); } + return dim; } unsigned ContractCallDataEncoder::encodeSingleItem(QString const& _data, SolidityType const& _type, bytes& _dest) diff --git a/mix/ContractCallDataEncoder.h b/mix/ContractCallDataEncoder.h index 2707845ae..629e46b0d 100644 --- a/mix/ContractCallDataEncoder.h +++ b/mix/ContractCallDataEncoder.h @@ -69,11 +69,14 @@ private: QString toString(bool _b); QString toString(dev::bytes const& _b); bool asString(dev::bytes const& _b, QString& _str); + QList extractDimension(QString const& _type); + void encodeArray(QJsonArray const& _array, QList _dim, SolidityType const& _type, bytes& _content); private: bytes m_encodedData; bytes m_dynamicData; - std::vector> m_offsetMap; + std::vector> m_dynamicOffsetMap; + std::vector> m_staticOffsetMap; }; } diff --git a/mix/qml/js/InputValidator.js b/mix/qml/js/InputValidator.js index 37185b898..8a8ef1715 100644 --- a/mix/qml/js/InputValidator.js +++ b/mix/qml/js/InputValidator.js @@ -1,27 +1,20 @@ Qt.include("QEtherHelper.js") var nbRegEx = new RegExp('^[0-9]+$'); +var arrayRegEx = new RegExp('\\[[^\\]]*\\]', "g"); +var capturenbRegEx = new RegExp("[0-9]+"); + function validate(model, values) { var inError = []; for (var k in model) { + init() if (values[model[k].name]) { var type = model[k].type.name; - var res; - if (isContractType(type)) - res = validateAddress(type, values[model[k].name]); - else if (type.indexOf("int") !== -1) - res = validateInt(type, values[model[k].name]); - else if (type.indexOf("bytes") !== -1) - res = validateBytes(type, values[model[k].name]); - else if (type.indexOf("bool") !== -1) - res = validateBool(type, values[model[k].name]); - else if (type.indexOf("address") !== -1) - res = validateAddress(type, values[model[k].name]); - else - res.valid = true; + var value = values[model[k].name]; + var res = check(type, value) if (!res.valid) inError.push({ type: type, value: values, message: res.message }); } @@ -29,6 +22,100 @@ function validate(model, values) return inError; } +function init() +{ + nbRegEx = new RegExp('^[0-9]+$'); + arrayRegEx = new RegExp('\\[[^\\]]*\\]', "g"); + capturenbRegEx = new RegExp("[0-9]+"); +} + +function check(type, value) +{ + var res = { valid: true, message : "" } + if (isContractType(type)) + res = validateAddress(type, value); + else if (isArray(type)) + res = validateArray(type, value); + else if (type.indexOf("int") !== -1) + res = validateInt(type, value); + else if (type.indexOf("bytes") !== -1) + res = validateBytes(type, value); + else if (type.indexOf("bool") !== -1) + res = validateBool(type, value); + else if (type.indexOf("address") !== -1) + res = validateAddress(type, value); + else + { + res.valid = true + res.message = "" + } + return res; +} + +function isArray(_type) +{ + if (!arrayRegEx.test(_type)) + return false + else + return (_type.indexOf("int") !== -1 || _type.indexOf("bytes") !== -1 || _type.indexOf("bool") !== -1 || _type.indexOf("adress") !== -1) +} + +function checkArrayRecursively(_type, _dim, _array) +{ + if (_array instanceof Array) + { + if (_dim.length === 0) + return { valid: false, message: "Your input contains too many dimensions" } + var size = -1 + var infinite = false + if (_dim === "[]") + infinite = true + else + size = parseInt(capturenbRegEx.exec(_dim[0])) + if (_array.length > size && !infinite) + return { valid: false, message: "Array size does not correspond. Should be " + _dim[0] } + if (_array.length > 0) + { + var _newdim = _dim.slice(0) + _newdim.splice(0, 1) + for (var i = 0; i < _array.length; i++) + { + var ret = checkArrayRecursively(_type, _newdim, _array[i]) + if (!ret.valid) + return ret + } + } + return { valid: true, message: "" } + } + else + { + if (_dim.length > 0) + return { valid: false, message: "Your input contains too few dimensions" } + if (typeof(_array) === "number") + _array = '' + _array + '' + return check(_type, _array) + } +} + +function validateArray(_type, _value) +{ + try + { + _value = JSON.parse(_value) + } + catch (e) + { + return { valid: false, message: "Input must be JSON formatted like [1,5,3,9] or [[4,9],[4,9],[4,9],[4,9]]" } + } + var dim = _type.match(arrayRegEx) + dim.reverse(); + for (var k = 0; k < dim.length; k++) + { + _type = _type.replace(dim[k], "") + } + return checkArrayRecursively(_type, dim, _value) +} + function isContractType(_type) { for (var k in Object.keys(codeModel.contracts))