# HG changeset patch # User Sebastien Jodogne # Date 1478697274 -3600 # Node ID 9220cf4a63d5690153c7ca440d8ae3e4bbc4a934 # Parent 4b7e0244881fc51b377230d3ad5a9c026f383c0d sync diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/Enumerations.cpp --- a/Framework/Orthanc/Core/Enumerations.cpp Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/Enumerations.cpp Wed Nov 09 14:14:34 2016 +0100 @@ -64,7 +64,7 @@ return "Parameter out of range"; case ErrorCode_NotEnoughMemory: - return "Not enough memory"; + return "The server hosting Orthanc is running out of memory"; case ErrorCode_BadParameterType: return "Bad type for a parameter"; @@ -156,6 +156,9 @@ case ErrorCode_NotAcceptable: return "Cannot send a response which is acceptable according to the Accept HTTP header"; + case ErrorCode_NullPointer: + return "Cannot handle a NULL pointer"; + case ErrorCode_SQLiteNotOpened: return "SQLite: The database is not opened"; diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/Enumerations.h --- a/Framework/Orthanc/Core/Enumerations.h Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/Enumerations.h Wed Nov 09 14:14:34 2016 +0100 @@ -52,7 +52,7 @@ ErrorCode_Plugin = 1 /*!< Error encountered within the plugin engine */, ErrorCode_NotImplemented = 2 /*!< Not implemented yet */, ErrorCode_ParameterOutOfRange = 3 /*!< Parameter out of range */, - ErrorCode_NotEnoughMemory = 4 /*!< Not enough memory */, + ErrorCode_NotEnoughMemory = 4 /*!< The server hosting Orthanc is running out of memory */, ErrorCode_BadParameterType = 5 /*!< Bad type for a parameter */, ErrorCode_BadSequenceOfCalls = 6 /*!< Bad sequence of calls */, ErrorCode_InexistentItem = 7 /*!< Accessing an inexistent item */, @@ -83,6 +83,7 @@ ErrorCode_StorageAreaPlugin = 32 /*!< Error in the plugin implementing a custom storage area */, ErrorCode_EmptyRequest = 33 /*!< The request is empty */, ErrorCode_NotAcceptable = 34 /*!< Cannot send a response which is acceptable according to the Accept HTTP header */, + ErrorCode_NullPointer = 35 /*!< Cannot handle a NULL pointer */, ErrorCode_SQLiteNotOpened = 1000 /*!< SQLite: The database is not opened */, ErrorCode_SQLiteAlreadyOpened = 1001 /*!< SQLite: Connection is already open */, ErrorCode_SQLiteCannotOpen = 1002 /*!< SQLite: Unable to open the database */, diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/HttpClient.cpp --- a/Framework/Orthanc/Core/HttpClient.cpp Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/HttpClient.cpp Wed Nov 09 14:14:34 2016 +0100 @@ -44,7 +44,7 @@ #include -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 // For OpenSSL initialization and finalization # include # include @@ -54,7 +54,7 @@ #endif -#if ORTHANC_PKCS11_ENABLED == 1 +#if ORTHANC_ENABLE_PKCS11 == 1 # include "Pkcs11.h" #endif @@ -161,7 +161,7 @@ return timeout_; } -#if ORTHANC_PKCS11_ENABLED == 1 +#if ORTHANC_ENABLE_PKCS11 == 1 bool IsPkcs11Initialized() { boost::mutex::scoped_lock lock(mutex_); @@ -435,7 +435,7 @@ CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_HEADERDATA, &headerParameters)); } -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 // Setup HTTPS-related options if (verifyPeers_) @@ -461,7 +461,7 @@ if (pkcs11Enabled_) { -#if ORTHANC_PKCS11_ENABLED == 1 +#if ORTHANC_ENABLE_PKCS11 == 1 if (GlobalParameters::GetInstance().IsPkcs11Initialized()) { CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLENGINE, Pkcs11::GetEngineIdentifier())); @@ -480,7 +480,7 @@ } else if (!clientCertificateFile_.empty()) { -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERTTYPE, "PEM")); CheckCode(curl_easy_setopt(pimpl_->curl_, CURLOPT_SSLCERT, clientCertificateFile_.c_str())); @@ -671,7 +671,7 @@ void HttpClient::ConfigureSsl(bool httpsVerifyPeers, const std::string& httpsVerifyCertificates) { -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 if (httpsVerifyPeers) { if (httpsVerifyCertificates.empty()) @@ -696,7 +696,7 @@ void HttpClient::GlobalInitialize() { -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 CheckCode(curl_global_init(CURL_GLOBAL_ALL)); #else CheckCode(curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_SSL)); @@ -708,7 +708,7 @@ { curl_global_cleanup(); -#if ORTHANC_PKCS11_ENABLED == 1 +#if ORTHANC_ENABLE_PKCS11 == 1 Pkcs11::Finalize(); #endif } @@ -796,7 +796,7 @@ const std::string& pin, bool verbose) { -#if ORTHANC_PKCS11_ENABLED == 1 +#if ORTHANC_ENABLE_PKCS11 == 1 LOG(INFO) << "Initializing PKCS#11 using " << module << (pin.empty() ? " (no PIN provided)" : " (PIN is provided)"); GlobalParameters::GetInstance().InitializePkcs11(module, pin, verbose); @@ -809,7 +809,7 @@ void HttpClient::InitializeOpenSsl() { -#if ORTHANC_SSL_ENABLED == 1 +#if ORTHANC_ENABLE_SSL == 1 // https://wiki.openssl.org/index.php/Library_Initialization SSL_library_init(); SSL_load_error_strings(); @@ -821,7 +821,7 @@ void HttpClient::FinalizeOpenSsl() { - #if ORTHANC_SSL_ENABLED == 1 + #if ORTHANC_ENABLE_SSL == 1 // Finalize OpenSSL // https://wiki.openssl.org/index.php/Library_Initialization#Cleanup FIPS_mode_set(0); diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/Logging.cpp --- a/Framework/Orthanc/Core/Logging.cpp Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/Logging.cpp Wed Nov 09 14:14:34 2016 +0100 @@ -318,113 +318,123 @@ return; } - LogLevel l = StringToLogLevel(level); - - if ((l == LogLevel_Info && !loggingContext_->infoEnabled_) || - (l == LogLevel_Trace && !loggingContext_->traceEnabled_)) + try { - // This logging level is disabled, directly exit and unlock - // the mutex to speed-up things. The stream is set to "/dev/null" - lock_.unlock(); - return; - } + LogLevel l = StringToLogLevel(level); + + if ((l == LogLevel_Info && !loggingContext_->infoEnabled_) || + (l == LogLevel_Trace && !loggingContext_->traceEnabled_)) + { + // This logging level is disabled, directly exit and unlock + // the mutex to speed-up things. The stream is set to "/dev/null" + lock_.unlock(); + return; + } - // Compute the header of the line, temporary release the lock as - // this is a time-consuming operation - lock_.unlock(); - std::string header; + // Compute the header of the line, temporary release the lock as + // this is a time-consuming operation + lock_.unlock(); + std::string header; - { - boost::filesystem::path path(file); - boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); - boost::posix_time::time_duration duration = now.time_of_day(); + { + boost::filesystem::path path(file); + boost::posix_time::ptime now = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration duration = now.time_of_day(); - /** - From Google Log documentation: + /** + From Google Log documentation: - "Log lines have this form: + "Log lines have this form: - Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... + Lmmdd hh:mm:ss.uuuuuu threadid file:line] msg... - where the fields are defined as follows: + where the fields are defined as follows: - L A single character, representing the log level (eg 'I' for INFO) - mm The month (zero padded; ie May is '05') - dd The day (zero padded) - hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds - threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux) - file The file name - line The line number - msg The user-supplied message" + L A single character, representing the log level (eg 'I' for INFO) + mm The month (zero padded; ie May is '05') + dd The day (zero padded) + hh:mm:ss.uuuuuu Time in hours, minutes and fractional seconds + threadid The space-padded thread ID as returned by GetTID() (this matches the PID on Linux) + file The file name + line The line number + msg The user-supplied message" - In this implementation, "threadid" is not printed. - **/ + In this implementation, "threadid" is not printed. + **/ - char date[32]; - sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ", - level[0], - now.date().month().as_number(), - now.date().day().as_number(), - duration.hours(), - duration.minutes(), - duration.seconds(), - static_cast(duration.fractional_seconds())); + char date[32]; + sprintf(date, "%c%02d%02d %02d:%02d:%02d.%06d ", + level[0], + now.date().month().as_number(), + now.date().day().as_number(), + duration.hours(), + duration.minutes(), + duration.seconds(), + static_cast(duration.fractional_seconds())); - header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast(line) + "] "; - } + header = std::string(date) + path.filename().string() + ":" + boost::lexical_cast(line) + "] "; + } - // The header is computed, we now re-lock the mutex to access - // the stream objects. Pay attention that "loggingContext_", - // "infoEnabled_" or "traceEnabled_" might have changed while - // the mutex was unlocked. - lock_.lock(); + // The header is computed, we now re-lock the mutex to access + // the stream objects. Pay attention that "loggingContext_", + // "infoEnabled_" or "traceEnabled_" might have changed while + // the mutex was unlocked. + lock_.lock(); + + if (loggingContext_.get() == NULL) + { + fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); + return; + } - if (loggingContext_.get() == NULL) - { - fprintf(stderr, "ERROR: Trying to log a message after the finalization of the logging engine\n"); - return; - } + switch (l) + { + case LogLevel_Error: + stream_ = loggingContext_->error_; + break; - switch (l) - { - case LogLevel_Error: - stream_ = loggingContext_->error_; - break; + case LogLevel_Warning: + stream_ = loggingContext_->warning_; + break; - case LogLevel_Warning: - stream_ = loggingContext_->warning_; - break; + case LogLevel_Info: + if (loggingContext_->infoEnabled_) + { + stream_ = loggingContext_->info_; + } + + break; - case LogLevel_Info: - if (loggingContext_->infoEnabled_) - { - stream_ = loggingContext_->info_; - } + case LogLevel_Trace: + if (loggingContext_->traceEnabled_) + { + stream_ = loggingContext_->info_; + } - break; + break; - case LogLevel_Trace: - if (loggingContext_->traceEnabled_) - { - stream_ = loggingContext_->info_; - } + default: + throw OrthancException(ErrorCode_InternalError); + } - break; - - default: - throw OrthancException(ErrorCode_InternalError); - } + if (stream_ == &null_) + { + // The logging is disabled for this level. The stream is the + // "null_" member of this object, so we can release the global + // mutex. + lock_.unlock(); + } - if (stream_ == &null_) - { - // The logging is disabled for this level. The stream is the - // "null_" member of this object, so we can release the global - // mutex. - lock_.unlock(); + (*stream_) << header; } - - (*stream_) << header; + catch (...) + { + // Something is going really wrong, probably running out of + // memory. Fallback to a degraded mode. + stream_ = loggingContext_->error_; + (*stream_) << "E???? ??:??:??.?????? ] "; + } } diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/Logging.h --- a/Framework/Orthanc/Core/Logging.h Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/Logging.h Wed Nov 09 14:14:34 2016 +0100 @@ -110,6 +110,12 @@ { return (*stream_) << message; } + + // This overload fixes build problems with Visual Studio 2015 + std::ostream& operator<< (const char* message) + { + return (*stream_) << message; + } }; } } diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/PrecompiledHeaders.h --- a/Framework/Orthanc/Core/PrecompiledHeaders.h Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/PrecompiledHeaders.h Wed Nov 09 14:14:34 2016 +0100 @@ -48,7 +48,7 @@ #include -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 #include #endif diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/Toolbox.cpp --- a/Framework/Orthanc/Core/Toolbox.cpp Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/Toolbox.cpp Wed Nov 09 14:14:34 2016 +0100 @@ -103,7 +103,7 @@ #endif -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 #include "ChunkedBuffer.h" #include #endif @@ -844,6 +844,23 @@ } + bool Toolbox::IsAsciiString(const void* data, + size_t size) + { + const uint8_t* p = reinterpret_cast(data); + + for (size_t i = 0; i < size; i++, p++) + { + if (*p > 127 || (*p != 0 && iscntrl(*p))) + { + return false; + } + } + + return true; + } + + std::string Toolbox::ConvertToAscii(const std::string& source) { std::string result; @@ -1190,7 +1207,7 @@ #endif -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 class ChunkedBufferWriter : public pugi::xml_writer { private: diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Core/Toolbox.h --- a/Framework/Orthanc/Core/Toolbox.h Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Core/Toolbox.h Wed Nov 09 14:14:34 2016 +0100 @@ -163,6 +163,9 @@ std::string ConvertFromUtf8(const std::string& source, Encoding targetEncoding); + bool IsAsciiString(const void* data, + size_t size); + std::string ConvertToAscii(const std::string& source); std::string StripSpaces(const std::string& source); @@ -195,7 +198,7 @@ bool IsExistingFile(const std::string& path); #endif -#if ORTHANC_PUGIXML_ENABLED == 1 +#if ORTHANC_ENABLE_PUGIXML == 1 void JsonToXml(std::string& target, const Json::Value& source, const std::string& rootElement = "root", diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake --- a/Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/CMake/BoostConfiguration.cmake Wed Nov 09 14:14:34 2016 +0100 @@ -210,7 +210,8 @@ ${BOOST_SOURCES_DIR} ) - source_group(ThirdParty\\Boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) + source_group(ThirdParty\\boost REGULAR_EXPRESSION ${BOOST_SOURCES_DIR}/.*) + else() add_definitions( -DBOOST_HAS_LOCALE=1 diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/CMake/DownloadPackage.cmake --- a/Framework/Orthanc/Resources/CMake/DownloadPackage.cmake Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/CMake/DownloadPackage.cmake Wed Nov 09 14:14:34 2016 +0100 @@ -15,12 +15,18 @@ ## Setup the patch command-line tool ## -if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") - set(PATCH_EXECUTABLE ${CMAKE_SOURCE_DIR}/Resources/ThirdParty/patch/patch.exe) -else () - find_program(PATCH_EXECUTABLE patch) - if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND") - message(FATAL_ERROR "Please install the 'patch' standard command-line tool") +if (NOT ORTHANC_DISABLE_PATCH) + if ("${CMAKE_HOST_SYSTEM_NAME}" STREQUAL "Windows") + set(PATCH_EXECUTABLE ${CMAKE_CURRENT_LIST_DIR}/../ThirdParty/patch/patch.exe) + if (NOT EXISTS ${PATCH_EXECUTABLE}) + message(FATAL_ERROR "Unable to find the patch.exe tool that is shipped with Orthanc") + endif() + + else () + find_program(PATCH_EXECUTABLE patch) + if (${PATCH_EXECUTABLE} MATCHES "PATCH_EXECUTABLE-NOTFOUND") + message(FATAL_ERROR "Please install the 'patch' standard command-line tool") + endif() endif() endif() diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/CMake/LibIconvConfiguration.cmake --- a/Framework/Orthanc/Resources/CMake/LibIconvConfiguration.cmake Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/CMake/LibIconvConfiguration.cmake Wed Nov 09 14:14:34 2016 +0100 @@ -48,3 +48,5 @@ ${LIBICONV_SOURCES_DIR}/libcharset/lib/localcharset.c ${LIBICONV_SOURCES_DIR}/libcharset/lib/relocatable.c ) + +source_group(ThirdParty\\libiconv REGULAR_EXPRESSION ${LIBICONV_SOURCES_DIR}/.*) diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake --- a/Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/CMake/LibJpegConfiguration.cmake Wed Nov 09 14:14:34 2016 +0100 @@ -81,6 +81,8 @@ ${LIBJPEG_SOURCES_DIR}/jconfig.h COPYONLY ) + source_group(ThirdParty\\libjpeg REGULAR_EXPRESSION ${LIBJPEG_SOURCES_DIR}/.*) + else() include(FindJPEG) diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake --- a/Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/CMake/LibPngConfiguration.cmake Wed Nov 09 14:14:34 2016 +0100 @@ -46,7 +46,7 @@ -DPNG_IMPEXP= ) - source_group(ThirdParty\\Libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) + source_group(ThirdParty\\libpng REGULAR_EXPRESSION ${LIBPNG_SOURCES_DIR}/.*) else() include(FindPNG) diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake --- a/Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/CMake/OpenSslConfiguration.cmake Wed Nov 09 14:14:34 2016 +0100 @@ -216,6 +216,8 @@ endif() endif() + source_group(ThirdParty\\OpenSSL REGULAR_EXPRESSION ${OPENSSL_SOURCES_DIR}/.*) + else() include(FindOpenSSL) diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake --- a/Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/CMake/ZlibConfiguration.cmake Wed Nov 09 14:14:34 2016 +0100 @@ -27,10 +27,10 @@ ${ZLIB_SOURCES_DIR}/zutil.c ) + source_group(ThirdParty\\zlib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*) + else() include(FindZLIB) include_directories(${ZLIB_INCLUDE_DIRS}) link_libraries(${ZLIB_LIBRARIES}) endif() - -source_group(ThirdParty\\ZLib REGULAR_EXPRESSION ${ZLIB_SOURCES_DIR}/.*) diff -r 4b7e0244881f -r 9220cf4a63d5 Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h --- a/Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h Wed Oct 26 12:14:03 2016 +0200 +++ b/Framework/Orthanc/Resources/ThirdParty/VisualStudio/stdint.h Wed Nov 09 14:14:34 2016 +0100 @@ -1,7 +1,7 @@ // ISO C9x compliant stdint.h for Microsoft Visual Studio // Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 // -// Copyright (c) 2006-2008 Alexander Chemeris +// Copyright (c) 2006-2013 Alexander Chemeris // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: @@ -13,8 +13,9 @@ // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // -// 3. The name of the author may be used to endorse or promote products -// derived from this software without specific prior written permission. +// 3. Neither the name of the product nor the names of its contributors may +// be used to endorse or promote products derived from this software +// without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF @@ -40,6 +41,10 @@ #pragma once #endif +#if _MSC_VER >= 1600 // [ +#include +#else // ] _MSC_VER >= 1600 [ + #include // For Visual Studio 6 in C++ mode and for many Visual Studio versions when @@ -238,10 +243,17 @@ #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants -#define INTMAX_C INT64_C -#define UINTMAX_C UINT64_C +// These #ifndef's are needed to prevent collisions with . +// Check out Issue 9 for the details. +#ifndef INTMAX_C // [ +# define INTMAX_C INT64_C +#endif // INTMAX_C ] +#ifndef UINTMAX_C // [ +# define UINTMAX_C UINT64_C +#endif // UINTMAX_C ] #endif // __STDC_CONSTANT_MACROS ] +#endif // _MSC_VER >= 1600 ] #endif // _MSC_STDINT_H_ ]