//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2014-2016 ArangoDB GmbH, Cologne, Germany /// Copyright 2004-2014 triAGENS GmbH, Cologne, Germany /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is ArangoDB GmbH, Cologne, Germany /// /// @author Jan Steemann //////////////////////////////////////////////////////////////////////////////// #include "v8-vpack.h" #include "Basics/Exceptions.h" #include "V8/v8-utils.h" #include #include //////////////////////////////////////////////////////////////////////////////// /// @brief converts a VelocyValueType::String into a V8 object //////////////////////////////////////////////////////////////////////////////// static v8::Handle ObjectVPackString(v8::Isolate* isolate, VPackSlice const& slice) { arangodb::velocypack::ValueLength l; char const* val = slice.getString(l); return TRI_V8_PAIR_STRING(val, l); } //////////////////////////////////////////////////////////////////////////////// /// @brief converts a VelocyValueType::Object into a V8 object //////////////////////////////////////////////////////////////////////////////// static v8::Handle ObjectVPackObject(v8::Isolate* isolate, VPackSlice const& slice) { TRI_ASSERT(slice.isObject()); v8::Handle object = v8::Object::New(isolate); if (object.IsEmpty()) { return v8::Undefined(isolate); } VPackObjectIterator it(slice); while (it.valid()) { v8::Handle val = TRI_VPackToV8(isolate, it.value()); if (!val.IsEmpty()) { auto k = ObjectVPackString(isolate, it.key()); object->ForceSet(k, val); } it.next(); } return object; } //////////////////////////////////////////////////////////////////////////////// /// @brief converts a VelocyValueType::Array into a V8 object //////////////////////////////////////////////////////////////////////////////// static v8::Handle ObjectVPackArray(v8::Isolate* isolate, VPackSlice const& slice) { TRI_ASSERT(slice.isArray()); v8::Handle object = v8::Array::New(isolate, static_cast(slice.length())); if (object.IsEmpty()) { return v8::Undefined(isolate); } uint32_t j = 0; VPackArrayIterator it(slice); while (it.valid()) { v8::Handle val = TRI_VPackToV8(isolate, it.value()); if (!val.IsEmpty()) { object->Set(j++, val); } it.next(); } return object; } //////////////////////////////////////////////////////////////////////////////// /// @brief converts a VPack value into a V8 object //////////////////////////////////////////////////////////////////////////////// v8::Handle TRI_VPackToV8(v8::Isolate* isolate, VPackSlice const& slice) { switch (slice.type()) { case VPackValueType::Null: return v8::Null(isolate); case VPackValueType::Bool: return v8::Boolean::New(isolate, slice.getBool()); case VPackValueType::Double: return v8::Number::New(isolate, slice.getDouble()); case VPackValueType::Int: return v8::Number::New(isolate, static_cast(slice.getInt())); case VPackValueType::UInt: return v8::Number::New(isolate, static_cast(slice.getUInt())); case VPackValueType::SmallInt: return v8::Number::New(isolate, static_cast(slice.getSmallInt())); case VPackValueType::String: return ObjectVPackString(isolate, slice); case VPackValueType::Object: return ObjectVPackObject(isolate, slice); case VPackValueType::Array: return ObjectVPackArray(isolate, slice); case VPackValueType::None: default: return v8::Undefined(isolate); } } struct BuilderContext { BuilderContext(v8::Isolate* isolate, VPackBuilder& builder, bool keepTopLevelOpen) : isolate(isolate), builder(builder), keepTopLevelOpen(keepTopLevelOpen) {} v8::Isolate* isolate; VPackBuilder& builder; std::unordered_set seenHashes; std::vector> seenObjects; bool keepTopLevelOpen; }; //////////////////////////////////////////////////////////////////////////////// /// @brief adds a VPackValue to either an array or an object //////////////////////////////////////////////////////////////////////////////// static void AddValue(BuilderContext& context, std::string const& attributeName, bool inObject, VPackValue const& value) { if (inObject) { context.builder.add(attributeName, value); } else { context.builder.add(value); } } //////////////////////////////////////////////////////////////////////////////// /// @brief convert a V8 value to a VPack value //////////////////////////////////////////////////////////////////////////////// static int V8ToVPack(BuilderContext& context, v8::Handle const parameter, std::string const& attributeName, bool inObject) { v8::Isolate* isolate = context.isolate; v8::HandleScope scope(isolate); if (parameter->IsNull()) { AddValue(context, attributeName, inObject, VPackValue(VPackValueType::Null)); return TRI_ERROR_NO_ERROR; } if (parameter->IsBoolean()) { v8::Handle booleanParameter = parameter->ToBoolean(); AddValue(context, attributeName, inObject, VPackValue(booleanParameter->Value())); return TRI_ERROR_NO_ERROR; } if (parameter->IsNumber()) { v8::Handle numberParameter = parameter->ToNumber(); AddValue(context, attributeName, inObject, VPackValue(numberParameter->Value())); return TRI_ERROR_NO_ERROR; } if (parameter->IsString()) { v8::Handle stringParameter = parameter->ToString(); TRI_Utf8ValueNFC str(TRI_UNKNOWN_MEM_ZONE, stringParameter); if (*str == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } AddValue(context, attributeName, inObject, VPackValue(*str)); return TRI_ERROR_NO_ERROR; } if (parameter->IsArray()) { v8::Handle array = v8::Handle::Cast(parameter); AddValue(context, attributeName, inObject, VPackValue(VPackValueType::Array)); uint32_t const n = array->Length(); for (uint32_t i = 0; i < n; ++i) { // get address of next element int res = V8ToVPack(context, array->Get(i), "", false); if (res != TRI_ERROR_NO_ERROR) { return res; } } context.builder.close(); return TRI_ERROR_NO_ERROR; } if (parameter->IsObject()) { if (parameter->IsBooleanObject()) { AddValue(context, attributeName, inObject, VPackValue(v8::Handle::Cast(parameter) ->BooleanValue())); return TRI_ERROR_NO_ERROR; } if (parameter->IsNumberObject()) { AddValue(context, attributeName, inObject, VPackValue(v8::Handle::Cast(parameter) ->NumberValue())); return TRI_ERROR_NO_ERROR; } if (parameter->IsStringObject()) { v8::Handle stringParameter(parameter->ToString()); TRI_Utf8ValueNFC str(TRI_UNKNOWN_MEM_ZONE, stringParameter); if (*str == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } AddValue(context, attributeName, inObject, VPackValue(*str)); return TRI_ERROR_NO_ERROR; } if (parameter->IsRegExp() || parameter->IsFunction() || parameter->IsExternal()) { return TRI_ERROR_BAD_PARAMETER; } v8::Handle o = parameter->ToObject(); // first check if the object has a "toJSON" function v8::Handle toJsonString = TRI_V8_PAIR_STRING("toJSON", 6); if (o->Has(toJsonString)) { // call it if yes v8::Handle func = o->Get(toJsonString); if (func->IsFunction()) { v8::Handle toJson = v8::Handle::Cast(func); v8::Handle args; v8::Handle converted = toJson->Call(o, 0, &args); if (!converted.IsEmpty()) { // return whatever toJSON returned TRI_Utf8ValueNFC str(TRI_UNKNOWN_MEM_ZONE, converted->ToString()); if (*str == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } // this passes ownership for the utf8 string to the JSON object AddValue(context, attributeName, inObject, VPackValue(*str)); return TRI_ERROR_NO_ERROR; } } // fall-through intentional } int hash = o->GetIdentityHash(); if (context.seenHashes.find(hash) != context.seenHashes.end()) { // LOG(TRACE) << "found hash " << hash; for (auto& it : context.seenObjects) { if (parameter->StrictEquals(it)) { // object is recursive return TRI_ERROR_BAD_PARAMETER; } } } else { context.seenHashes.emplace(hash); } context.seenObjects.emplace_back(o); v8::Handle names = o->GetOwnPropertyNames(); uint32_t const n = names->Length(); AddValue(context, attributeName, inObject, VPackValue(VPackValueType::Object)); for (uint32_t i = 0; i < n; ++i) { // process attribute name v8::Handle key = names->Get(i); TRI_Utf8ValueNFC str(TRI_UNKNOWN_MEM_ZONE, key); if (*str == nullptr) { return TRI_ERROR_OUT_OF_MEMORY; } int res = V8ToVPack(context, o->Get(key), *str, true); if (res != TRI_ERROR_NO_ERROR) { // to mimic behavior of previous ArangoDB versions, we need to silently // ignore this error // a better solution would be: // return res; } } context.seenObjects.pop_back(); if (!context.keepTopLevelOpen || !context.seenObjects.empty()) { context.builder.close(); } return TRI_ERROR_NO_ERROR; } return TRI_ERROR_BAD_PARAMETER; } //////////////////////////////////////////////////////////////////////////////// /// @brief convert a V8 value to VPack value //////////////////////////////////////////////////////////////////////////////// int TRI_V8ToVPack(v8::Isolate* isolate, VPackBuilder& builder, v8::Handle const value, bool keepTopLevelOpen) { BuilderContext context(isolate, builder, keepTopLevelOpen); int res = V8ToVPack(context, value, "", false); return res; }