//////////////////////////////////////////////////////////////////////////////// /// 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 Dr. Frank Celler //////////////////////////////////////////////////////////////////////////////// #include "v8-utils.h" #include "Basics/Common.h" #ifdef _WIN32 #include // must be before windows.h #include #include #include #include #include #include "Basics/win-utils.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "unicode/normalizer2.h" #include "ApplicationFeatures/ApplicationServer.h" #include "ApplicationFeatures/HttpEndpointProvider.h" #include "ApplicationFeatures/V8SecurityFeature.h" #include "Basics/Exceptions.h" #include "Basics/FileResultString.h" #include "Basics/FileUtils.h" #include "Basics/Nonce.h" #include "Basics/Result.h" #include "Basics/ScopeGuard.h" #include "Basics/StaticStrings.h" #include "Basics/StringBuffer.h" #include "Basics/StringUtils.h" #include "Basics/Thread.h" #include "Basics/Utf8Helper.h" #include "Basics/build.h" #include "Basics/debugging.h" #include "Basics/error.h" #include "Basics/files.h" #include "Basics/memory.h" #include "Basics/operating-system.h" #include "Basics/process-utils.h" #include "Basics/socket-utils.h" #include "Basics/system-compiler.h" #include "Basics/system-functions.h" #include "Basics/terminal-utils.h" #include "Basics/threads.h" #include "Basics/tri-strings.h" #include "Basics/tri-zip.h" #include "Basics/voc-errors.h" #include "Endpoint/Endpoint.h" #include "Logger/LogLevel.h" #include "Logger/LogMacros.h" #include "Logger/LogTopic.h" #include "Logger/Logger.h" #include "Logger/LoggerStream.h" #include "Random/UniformCharacter.h" #include "Rest/CommonDefines.h" #include "Rest/GeneralRequest.h" #include "Rest/GeneralResponse.h" #include "SimpleHttpClient/GeneralClientConnection.h" #include "SimpleHttpClient/SimpleHttpClient.h" #include "SimpleHttpClient/SimpleHttpResult.h" #include "Ssl/SslInterface.h" #include "Ssl/ssl-helper.h" #include "V8/v8-buffer.h" #include "V8/v8-conv.h" #include "V8/v8-globals.h" #include "V8/v8-vpack.h" #ifdef TRI_HAVE_UNISTD_H #include #endif #include #include #include using namespace arangodb; using namespace arangodb::application_features; using namespace arangodb::basics; using namespace arangodb::httpclient; using namespace arangodb::rest; /// @brief Random string generators namespace { static UniformCharacter JSAlphaNumGenerator( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"); static UniformCharacter JSNumGenerator("0123456789"); static UniformCharacter JSSaltGenerator( "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*(){}" "[]:;<>,.?/|"); } // namespace /// @brief Converts an object to a UTF-8-encoded and normalized character array. TRI_Utf8ValueNFC::TRI_Utf8ValueNFC(v8::Isolate* isolate, v8::Handle const obj) : _str(nullptr), _length(0) { v8::String::Value str(isolate, obj); _str = TRI_normalize_utf16_to_NFC(*str, (size_t)str.length(), &_length); } //////////////////////////////////////////////////////////////////////////////// /// @brief Destroys a normalized string object //////////////////////////////////////////////////////////////////////////////// TRI_Utf8ValueNFC::~TRI_Utf8ValueNFC() { TRI_Free(_str); } //////////////////////////////////////////////////////////////////////////////// /// @brief create a Javascript error object //////////////////////////////////////////////////////////////////////////////// static void CreateErrorObject(v8::Isolate* isolate, int errorNumber, std::string const& message) noexcept { try { TRI_GET_GLOBALS(); if (errorNumber == TRI_ERROR_OUT_OF_MEMORY) { LOG_TOPIC("532c3", ERR, arangodb::Logger::FIXME) << "encountered out-of-memory error in context #" << v8g->_id; } v8::Handle errorMessage = TRI_V8_STD_STRING(isolate, message); if (errorMessage.IsEmpty()) { isolate->ThrowException(v8::Object::New(isolate)); return; } v8::Handle err = v8::Exception::Error(errorMessage); if (err.IsEmpty()) { isolate->ThrowException(v8::Object::New(isolate)); return; } v8::Handle errorObject = err->ToObject(TRI_IGETC).FromMaybe(v8::Local()); if (errorObject.IsEmpty()) { isolate->ThrowException(v8::Object::New(isolate)); return; } errorObject->Set(TRI_V8_STD_STRING(isolate, StaticStrings::ErrorNum), v8::Number::New(isolate, errorNumber)); errorObject->Set(TRI_V8_STD_STRING(isolate, StaticStrings::ErrorMessage), errorMessage); TRI_GET_GLOBAL(ArangoErrorTempl, v8::ObjectTemplate); v8::Handle ArangoError = ArangoErrorTempl->NewInstance(); if (!ArangoError.IsEmpty()) { errorObject->SetPrototype(TRI_IGETC, ArangoError).FromMaybe(false); // Ignore error } isolate->ThrowException(errorObject); } catch (...) { // must not throw from here, as a C++ exception must not escape the // C++ bindings that are called by V8. if it does, the program will crash } } //////////////////////////////////////////////////////////////////////////////// /// @brief reads/execute a file into/in the current context //////////////////////////////////////////////////////////////////////////////// static bool LoadJavaScriptFile(v8::Isolate* isolate, char const* filename, bool stripShebang, bool execute, bool useGlobalContext) { v8::HandleScope handleScope(isolate); auto& server = application_features::ApplicationServer::server(); V8SecurityFeature& v8security = server.getFeature(); if (!v8security.isAllowedToAccessPath(isolate, filename, FSAccessType::READ)) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN, std::string("not allowed to read files in this path: ") + filename); } size_t length; char* content = TRI_SlurpFile(filename, &length); if (content == nullptr) { LOG_TOPIC("790a5", ERR, arangodb::Logger::FIXME) << "cannot load JavaScript file '" << filename << "': " << TRI_last_error(); return false; } auto guard = scopeGuard([&content] { TRI_FreeString(content); }); size_t bangOffset = 0; if (stripShebang) { if (strncmp(content, "#!", 2) == 0) { // shebang char const* endOfBang = strchr(content, '\n'); if (endOfBang != nullptr) { bangOffset = size_t(endOfBang - content + 1); TRI_ASSERT(bangOffset <= length); length -= bangOffset; } } } if (useGlobalContext) { constexpr char const* prologue = "(function() { "; constexpr char const* epilogue = "/* end-of-file */ })()"; char* contentWrapper = TRI_Concatenate3String(prologue, content + bangOffset, epilogue); TRI_FreeString(content); length += strlen(prologue) + strlen(epilogue); content = contentWrapper; // shebang already handled here bangOffset = 0; } if (content == nullptr) { LOG_TOPIC("89c6f", ERR, arangodb::Logger::FIXME) << "cannot load JavaScript file '" << filename << "': " << TRI_errno_string(TRI_ERROR_OUT_OF_MEMORY); return false; } v8::Handle name = TRI_V8_STRING(isolate, filename); v8::Handle source = TRI_V8_PAIR_STRING(isolate, content + bangOffset, (int)length); v8::TryCatch tryCatch(isolate); v8::ScriptOrigin scriptOrigin(name); v8::Handle script = v8::Script::Compile(TRI_IGETC, source, &scriptOrigin).FromMaybe(v8::Local()); if (tryCatch.HasCaught()) { TRI_LogV8Exception(isolate, &tryCatch); return false; } // compilation failed, print errors that happened during compilation if (script.IsEmpty()) { LOG_TOPIC("6fffa", ERR, arangodb::Logger::FIXME) << "cannot load JavaScript file '" << filename << "': compilation failed."; return false; } if (execute) { // execute script v8::Handle result = script->Run(TRI_IGETC).FromMaybe( v8::Local()); // TODO: do we want a better default fail here? if (tryCatch.HasCaught()) { TRI_LogV8Exception(isolate, &tryCatch); return false; } if (result.IsEmpty()) { return false; } } LOG_TOPIC("fe6a4", TRACE, arangodb::Logger::FIXME) << "loaded JavaScript file: '" << filename << "'"; return true; } //////////////////////////////////////////////////////////////////////////////// /// @brief reads all files from a directory into the current context //////////////////////////////////////////////////////////////////////////////// static bool LoadJavaScriptDirectory(v8::Isolate* isolate, char const* path, bool stripShebang, bool execute, bool useGlobalContext) { v8::HandleScope scope(isolate); auto& server = application_features::ApplicationServer::server(); V8SecurityFeature& v8security = server.getFeature(); LOG_TOPIC("65c8d", TRACE, arangodb::Logger::FIXME) << "loading JavaScript directory: '" << path << "'"; std::vector files = TRI_FilesDirectory(path); bool result = true; for (auto const& filename : files) { if (!StringUtils::isSuffix(filename, ".js")) { continue; } v8::TryCatch tryCatch(isolate); std::string full = FileUtils::buildFilename(path, filename); if (!v8security.isAllowedToAccessPath(isolate, full, FSAccessType::READ)) { THROW_ARANGO_EXCEPTION_MESSAGE(TRI_ERROR_FORBIDDEN, std::string("not allowed to read files in this path: ") + full); } bool ok = LoadJavaScriptFile(isolate, full.c_str(), stripShebang, execute, useGlobalContext); result = result && ok; if (!ok) { if (tryCatch.CanContinue()) { TRI_LogV8Exception(isolate, &tryCatch); } else { TRI_GET_GLOBALS(); v8g->_canceled = true; } } } return result; } //////////////////////////////////////////////////////////////////////////////// /// @brief returns the program options //////////////////////////////////////////////////////////////////////////////// static void JS_Options(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 0) { TRI_V8_THROW_EXCEPTION_USAGE("options()"); } auto& server = application_features::ApplicationServer::server(); V8SecurityFeature& v8security = server.getFeature(); auto filter = [&v8security, isolate](std::string const& name) { if (name.find("passwd") != std::string::npos || name.find("password") != std::string::npos || name.find("secret") != std::string::npos) { return false; } return v8security.shouldExposeStartupOption(isolate, name); }; VPackBuilder builder = server.options(filter); auto result = TRI_VPackToV8(isolate, builder.slice()); TRI_V8_RETURN(result); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief decodes a base64-encoded string /// /// @FUN{internal.base64Decode(@FA{value})} /// /// Base64-decodes the string @FA{value}. //////////////////////////////////////////////////////////////////////////////// static void JS_Base64Decode(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("base64Decode()"); } try { std::string const value = TRI_ObjectToString(isolate, args[0]); std::string const base64 = StringUtils::decodeBase64(value); TRI_V8_RETURN_STD_STRING(base64); } catch (...) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_errno(), TRI_last_error()); } TRI_ASSERT(false); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief encodes a string as base64 /// /// @FUN{internal.base64Encode(@FA{value})} /// /// Base64-encodes the string @FA{value}. //////////////////////////////////////////////////////////////////////////////// static void JS_Base64Encode(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::HandleScope scope(isolate); if (args.Length() != 1) { TRI_V8_THROW_EXCEPTION_USAGE("base64Encode()"); } try { std::string const&& value = TRI_ObjectToString(isolate, args[0]); std::string const&& base64 = StringUtils::encodeBase64(value); TRI_V8_RETURN_STD_STRING(base64); } catch (...) { TRI_V8_THROW_EXCEPTION_MESSAGE(TRI_errno(), TRI_last_error()); } TRI_ASSERT(false); TRI_V8_TRY_CATCH_END } //////////////////////////////////////////////////////////////////////////////// /// @brief parses a Javascript snippet, but does not execute it /// /// @FUN{internal.parse(@FA{script})} /// /// Parses the @FA{script} code, but does not execute it. /// Will return *true* if the code does not have a parse error, and throw /// an exception otherwise. //////////////////////////////////////////////////////////////////////////////// static void JS_Parse(v8::FunctionCallbackInfo const& args) { TRI_V8_TRY_CATCH_BEGIN(isolate); v8::Local context = isolate->GetCurrentContext(); v8::HandleScope scope(isolate); if (args.Length() < 1) { TRI_V8_THROW_EXCEPTION_USAGE("parse(