changeset 57:af5e959e2319

sync
author Sebastien Jodogne <s.jodogne@gmail.com>
date Fri, 02 Jan 2026 13:08:44 +0100
parents 861155104c0c
children 29b366c19bf2
files Resources/Orthanc/CMake/AutoGeneratedCode.cmake Resources/Orthanc/CMake/Compiler.cmake Resources/Orthanc/CMake/DownloadOrthancFramework.cmake Resources/Orthanc/CMake/DownloadPackage.cmake Resources/Orthanc/CMake/LibCurlConfiguration.cmake Resources/Orthanc/CMake/OpenSslConfiguration.cmake Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h Resources/Orthanc/Plugins/OrthancPluginException.h Resources/Orthanc/Plugins/OrthancPluginsExports.cmake
diffstat 10 files changed, 991 insertions(+), 291 deletions(-) [+]
line wrap: on
line diff
--- a/Resources/Orthanc/CMake/AutoGeneratedCode.cmake	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/CMake/AutoGeneratedCode.cmake	Fri Jan 02 13:08:44 2026 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
@@ -20,7 +20,7 @@
 # <http://www.gnu.org/licenses/>.
 
 
-set(EMBED_RESOURCES_PYTHON "${CMAKE_CURRENT_LIST_DIR}/../EmbedResources.py"
+set(EMBED_RESOURCES_PYTHON "${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py"
   CACHE INTERNAL "Path to the EmbedResources.py script from Orthanc")
 set(AUTOGENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}/AUTOGENERATED")
 set(AUTOGENERATED_SOURCES)
--- a/Resources/Orthanc/CMake/Compiler.cmake	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/CMake/Compiler.cmake	Fri Jan 02 13:08:44 2026 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
@@ -21,6 +21,18 @@
 
 
 # This file sets all the compiler-related flags
+message(STATUS "CMAKE_CXX_COMPILER_ID is ${CMAKE_CXX_COMPILER_ID}")
+message(STATUS "CMAKE_SYSTEM_NAME is ${CMAKE_SYSTEM_NAME}")
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
+  # Since Orthanc 1.12.7 that allows CMake 4.0, builds for macOS
+  # require the C++ standard to be explicitly set to C++11. Do *not*
+  # do this on GNU/Linux, as third-party system libraries could have
+  # been compiled with higher versions of the C++ standard.
+  set(CMAKE_CXX_STANDARD 11)
+  set(CMAKE_CXX_STANDARD_REQUIRED ON)
+  set(CMAKE_CXX_EXTENSIONS OFF)
+endif()
 
 
 # Save the current compiler flags to the cache every time cmake configures the project
@@ -236,10 +248,15 @@
   # fix this error that appears with recent compilers on MacOS: boost/mpl/aux_/integral_wrapper.hpp:73:31: error: integer value -1 is outside the valid range of values [0, 3] for this enumeration type [-Wenum-constexpr-conversion]
   SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-enum-constexpr-conversion")
 
+  # it seems that some recent MacOS compilers don't set these flags correctly which prevents zlib from building correctly
+  SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LARGEFILE64_SOURCE=1 -D_FILE_OFFSET_BITS=64")
+
   add_definitions(
     -D_XOPEN_SOURCE=1
     )
-  link_libraries(iconv)
+  
+  # Linking with iconv breaks the Universal builds on modern compilers
+  # link_libraries(iconv)
 
 elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
   message("Building using Emscripten (for WebAssembly or asm.js targets)")
--- a/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/CMake/DownloadOrthancFramework.cmake	Fri Jan 02 13:08:44 2026 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
@@ -167,6 +167,16 @@
         set(ORTHANC_FRAMEWORK_MD5 "1e61779ea4a7cd705720bdcfed8a6a73")
       elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.5")
         set(ORTHANC_FRAMEWORK_MD5 "5bb69f092981fdcfc11dec0a0f9a7db3")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.6")
+        set(ORTHANC_FRAMEWORK_MD5 "0e971f32f4f3e4951e0f3b5de49a3da6")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.7")
+        set(ORTHANC_FRAMEWORK_MD5 "f27c27d7a7a694dab1fd7f0a99d9715a")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.8")
+        set(ORTHANC_FRAMEWORK_MD5 "eb1c719234338e8277b80d3453563e9f")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.9")
+        set(ORTHANC_FRAMEWORK_MD5 "66b5a2ee60706c4a502896083b9e1a01")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "1.12.10")
+        set(ORTHANC_FRAMEWORK_MD5 "d5e1ba442104c89a24013cb859a9d6bf")
 
       # Below this point are development snapshots that were used to
       # release some plugin, before an official release of the Orthanc
@@ -199,6 +209,28 @@
         # DICOMweb 1.15 (framework pre-1.12.2)
         set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
         set(ORTHANC_FRAMEWORK_MD5 "ebe8bdf388319f1c9536b2b680451848")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "36cd91a53403")
+        # Advanced storage 0.2.0 (framework pre-1.12.10)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "911105f18a154b5e106985d8fcfcb620")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "9eb77f159b9d")
+        # Advanced storage 0.2.2 (framework pre-1.12.10)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "bd5ba2cec329010b912209345acbdeaf")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "0ebe8cfd9bf7")
+        # Worklists plugin 0.9.0 (framework pre-1.12.10)
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "17a5ca9254e881ab89c93d052d4655cb")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "e0979326ac53")
+        # DICOMweb 1.22 + PG 10.0 (framework post-1.12.10)
+        # for BlockingSharedMessageQueue + fix SetCurrentThreadName from plugins
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "e66a7e996d56063b3abb790bb8f12e8d")
+      elseif (ORTHANC_FRAMEWORK_VERSION STREQUAL "94c7f3784456")
+        # PixelsMasker 0.1.0 (framework post-1.12.10)
+        # for BlockingSharedMessageQueue.WaitEmpty()
+        set(ORTHANC_FRAMEWORK_PRE_RELEASE ON)
+        set(ORTHANC_FRAMEWORK_MD5 "c037cd2ddbe1b65b431692855483161b")
       endif()
     endif()
   endif()
@@ -499,11 +531,18 @@
   
   include(CheckIncludeFile)
   include(CheckIncludeFileCXX)
-  include(FindPythonInterp)
+  
+  if(CMAKE_VERSION VERSION_GREATER "3.11")
+    find_package(Python REQUIRED COMPONENTS Interpreter)
+    set(PYTHON_EXECUTABLE ${Python_EXECUTABLE})
+  else()
+    include(FindPythonInterp)
+    find_package(PythonInterp REQUIRED)
+  endif()
+  
   include(${CMAKE_CURRENT_LIST_DIR}/Compiler.cmake)
   include(${CMAKE_CURRENT_LIST_DIR}/DownloadPackage.cmake)
   include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)
-  set(EMBED_RESOURCES_PYTHON ${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py)
 
   if (ORTHANC_FRAMEWORK_USE_SHARED)
     list(GET CMAKE_FIND_LIBRARY_PREFIXES 0 Prefix)
--- a/Resources/Orthanc/CMake/DownloadPackage.cmake	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/CMake/DownloadPackage.cmake	Fri Jan 02 13:08:44 2026 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
--- a/Resources/Orthanc/CMake/LibCurlConfiguration.cmake	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/CMake/LibCurlConfiguration.cmake	Fri Jan 02 13:08:44 2026 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
@@ -21,9 +21,9 @@
 
 
 if (STATIC_BUILD OR NOT USE_SYSTEM_CURL)
-  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-8.9.0)
-  SET(CURL_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/curl-8.9.0.tar.gz")
-  SET(CURL_MD5 "f9bca5d4d5bac1f04e6c5eb4d0418618")
+  SET(CURL_SOURCES_DIR ${CMAKE_BINARY_DIR}/curl-8.17.0)
+  SET(CURL_URL "https://orthanc.uclouvain.be/downloads/third-party-downloads/curl-8.17.0.tar.gz")
+  SET(CURL_MD5 "71e24b00f40a7503c1d07886e42d6305")
 
   if (IS_DIRECTORY "${CURL_SOURCES_DIR}")
     set(FirstRun OFF)
@@ -36,7 +36,7 @@
   if (FirstRun)
     execute_process(
       COMMAND ${PATCH_EXECUTABLE} -p0 -N -i
-      ${CMAKE_CURRENT_LIST_DIR}/../Patches/curl-8.9.0.patch
+      ${CMAKE_CURRENT_LIST_DIR}/../Patches/curl-8.17.0.patch
       WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
       RESULT_VARIABLE Failure
       )
@@ -55,6 +55,7 @@
   AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vssh CURL_SOURCES)
   AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vtls CURL_SOURCES)
   AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/vquic CURL_SOURCES)
+  AUX_SOURCE_DIRECTORY(${CURL_SOURCES_DIR}/lib/curlx CURL_SOURCES)
   source_group(ThirdParty\\LibCurl REGULAR_EXPRESSION ${CURL_SOURCES_DIR}/.*)
 
   add_definitions(
@@ -112,83 +113,62 @@
       SET(TMP_OS "x86")
     endif()
 
+    # _GNU_SOURCE is required for accept4(), pipe2(), sendmmsg()
     set_property(
       SOURCE ${CURL_SOURCES} APPEND
-      PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H=1;OS=\"${TMP_OS}\""
+      PROPERTY COMPILE_DEFINITIONS "HAVE_CONFIG_H=1;OS=\"${TMP_OS}\";_GNU_SOURCE"
       )
-   
+
+    if(CMAKE_C_COMPILER_TARGET)
+      set(CURL_OS "\"${CMAKE_C_COMPILER_TARGET}\"")
+    else()
+      set(CURL_OS "\"${CMAKE_SYSTEM_NAME}\"")
+    endif()
+
     include(${CURL_SOURCES_DIR}/CMake/Macros.cmake)
 
-    # WARNING: Do *not* reorder the "check_include_file_concat()" below!
-    check_include_file_concat("stdio.h"          HAVE_STDIO_H)
-    check_include_file_concat("inttypes.h"       HAVE_INTTYPES_H)
-    check_include_file_concat("sys/filio.h"      HAVE_SYS_FILIO_H)
-    check_include_file_concat("sys/ioctl.h"      HAVE_SYS_IOCTL_H)
-    check_include_file_concat("sys/param.h"      HAVE_SYS_PARAM_H)
-    check_include_file_concat("sys/poll.h"       HAVE_SYS_POLL_H)
-    check_include_file_concat("sys/resource.h"   HAVE_SYS_RESOURCE_H)
-    check_include_file_concat("sys/select.h"     HAVE_SYS_SELECT_H)
-    check_include_file_concat("sys/socket.h"     HAVE_SYS_SOCKET_H)
-    check_include_file_concat("sys/sockio.h"     HAVE_SYS_SOCKIO_H)
-    check_include_file_concat("sys/stat.h"       HAVE_SYS_STAT_H)
-    check_include_file_concat("sys/time.h"       HAVE_SYS_TIME_H)
-    check_include_file_concat("sys/types.h"      HAVE_SYS_TYPES_H)
-    check_include_file_concat("sys/uio.h"        HAVE_SYS_UIO_H)
-    check_include_file_concat("sys/un.h"         HAVE_SYS_UN_H)
-    check_include_file_concat("sys/utime.h"      HAVE_SYS_UTIME_H)
-    check_include_file_concat("sys/xattr.h"      HAVE_SYS_XATTR_H)
-    check_include_file_concat("alloca.h"         HAVE_ALLOCA_H)
-    check_include_file_concat("arpa/inet.h"      HAVE_ARPA_INET_H)
-    check_include_file_concat("arpa/tftp.h"      HAVE_ARPA_TFTP_H)
-    check_include_file_concat("assert.h"         HAVE_ASSERT_H)
-    check_include_file_concat("crypto.h"         HAVE_CRYPTO_H)
-    check_include_file_concat("des.h"            HAVE_DES_H)
-    check_include_file_concat("err.h"            HAVE_ERR_H)
-    check_include_file_concat("errno.h"          HAVE_ERRNO_H)
-    check_include_file_concat("fcntl.h"          HAVE_FCNTL_H)
-    check_include_file_concat("idn2.h"           HAVE_IDN2_H)
-    check_include_file_concat("ifaddrs.h"        HAVE_IFADDRS_H)
-    check_include_file_concat("io.h"             HAVE_IO_H)
-    check_include_file_concat("krb.h"            HAVE_KRB_H)
-    check_include_file_concat("libgen.h"         HAVE_LIBGEN_H)
-    check_include_file_concat("limits.h"         HAVE_LIMITS_H)
-    check_include_file_concat("locale.h"         HAVE_LOCALE_H)
-    check_include_file_concat("net/if.h"         HAVE_NET_IF_H)
-    check_include_file_concat("netdb.h"          HAVE_NETDB_H)
-    check_include_file_concat("netinet/in.h"     HAVE_NETINET_IN_H)
-    check_include_file_concat("netinet/tcp.h"    HAVE_NETINET_TCP_H)
+    # Detect headers
+
+    # Use check_include_file_concat_curl() for headers required by subsequent
+    # check_include_file_concat_curl() or check_symbol_exists() detections.
+    # Order for these is significant.
+    check_include_file("sys/eventfd.h"    HAVE_SYS_EVENTFD_H)
+    check_include_file("sys/filio.h"      HAVE_SYS_FILIO_H)
+    check_include_file("sys/ioctl.h"      HAVE_SYS_IOCTL_H)
+    check_include_file("sys/param.h"      HAVE_SYS_PARAM_H)
+    check_include_file("sys/poll.h"       HAVE_SYS_POLL_H)
+    check_include_file("sys/resource.h"   HAVE_SYS_RESOURCE_H)
+    check_include_file_concat_curl("sys/select.h"     HAVE_SYS_SELECT_H)
+    check_include_file("sys/sockio.h"     HAVE_SYS_SOCKIO_H)
+    check_include_file_concat_curl("sys/types.h"      HAVE_SYS_TYPES_H)
+    check_include_file("sys/un.h"         HAVE_SYS_UN_H)
+    check_include_file_concat_curl("sys/utime.h"      HAVE_SYS_UTIME_H)  # sys/types.h (AmigaOS)
 
-    check_include_file_concat("pem.h"            HAVE_PEM_H)
-    check_include_file_concat("poll.h"           HAVE_POLL_H)
-    check_include_file_concat("pwd.h"            HAVE_PWD_H)
-    check_include_file_concat("rsa.h"            HAVE_RSA_H)
-    check_include_file_concat("setjmp.h"         HAVE_SETJMP_H)
-    check_include_file_concat("sgtty.h"          HAVE_SGTTY_H)
-    check_include_file_concat("signal.h"         HAVE_SIGNAL_H)
-    check_include_file_concat("ssl.h"            HAVE_SSL_H)
-    check_include_file_concat("stdbool.h"        HAVE_STDBOOL_H)
-    check_include_file_concat("stdint.h"         HAVE_STDINT_H)
-    check_include_file_concat("stdio.h"          HAVE_STDIO_H)
-    check_include_file_concat("stdlib.h"         HAVE_STDLIB_H)
-    check_include_file_concat("string.h"         HAVE_STRING_H)
-    check_include_file_concat("strings.h"        HAVE_STRINGS_H)
-    check_include_file_concat("stropts.h"        HAVE_STROPTS_H)
-    check_include_file_concat("termio.h"         HAVE_TERMIO_H)
-    check_include_file_concat("termios.h"        HAVE_TERMIOS_H)
-    check_include_file_concat("time.h"           HAVE_TIME_H)
-    check_include_file_concat("unistd.h"         HAVE_UNISTD_H)
-    check_include_file_concat("utime.h"          HAVE_UTIME_H)
-    check_include_file_concat("x509.h"           HAVE_X509_H)
-
-    check_include_file_concat("process.h"        HAVE_PROCESS_H)
-    check_include_file_concat("stddef.h"         HAVE_STDDEF_H)
-    check_include_file_concat("dlfcn.h"          HAVE_DLFCN_H)
-    check_include_file_concat("malloc.h"         HAVE_MALLOC_H)
-    check_include_file_concat("memory.h"         HAVE_MEMORY_H)
-    check_include_file_concat("netinet/if_ether.h" HAVE_NETINET_IF_ETHER_H)
-    check_include_file_concat("stdint.h"        HAVE_STDINT_H)
-    check_include_file_concat("sockio.h"        HAVE_SOCKIO_H)
-    check_include_file_concat("sys/utsname.h"   HAVE_SYS_UTSNAME_H)
+    check_include_file_concat_curl("arpa/inet.h"      HAVE_ARPA_INET_H)
+    check_include_file("dirent.h"         HAVE_DIRENT_H)
+    check_include_file("fcntl.h"          HAVE_FCNTL_H)
+    check_include_file_concat_curl("ifaddrs.h"        HAVE_IFADDRS_H)
+    check_include_file("io.h"             HAVE_IO_H)
+    check_include_file_concat_curl("libgen.h"         HAVE_LIBGEN_H)
+    check_include_file("linux/tcp.h"      HAVE_LINUX_TCP_H)
+    check_include_file("locale.h"         HAVE_LOCALE_H)
+    check_include_file_concat_curl("net/if.h"         HAVE_NET_IF_H)  # sys/select.h (e.g. MS-DOS/Watt-32)
+    check_include_file_concat_curl("netdb.h"          HAVE_NETDB_H)
+    check_include_file_concat_curl("netinet/in.h"     HAVE_NETINET_IN_H)
+    check_include_file("netinet/in6.h"    HAVE_NETINET_IN6_H)
+    check_include_file_concat_curl("netinet/tcp.h"    HAVE_NETINET_TCP_H)  # sys/types.h (e.g. Cygwin) netinet/in.h
+    check_include_file_concat_curl("netinet/udp.h"    HAVE_NETINET_UDP_H)  # sys/types.h (e.g. Cygwin)
+    check_include_file("poll.h"           HAVE_POLL_H)
+    check_include_file("pwd.h"            HAVE_PWD_H)
+    check_include_file("stdatomic.h"      HAVE_STDATOMIC_H)
+    check_include_file("stdbool.h"        HAVE_STDBOOL_H)
+    check_include_file("stdint.h"         HAVE_STDINT_H)
+    check_include_file("strings.h"        HAVE_STRINGS_H)
+    check_include_file("stropts.h"        HAVE_STROPTS_H)
+    check_include_file("termio.h"         HAVE_TERMIO_H)
+    check_include_file("termios.h"        HAVE_TERMIOS_H)
+    check_include_file_concat_curl("unistd.h"         HAVE_UNISTD_H)
+    check_include_file("utime.h"          HAVE_UTIME_H)
 
     check_type_size("size_t"  SIZEOF_SIZE_T)
     check_type_size("ssize_t"  SIZEOF_SSIZE_T)
@@ -202,97 +182,76 @@
     check_type_size("off_t"  SIZEOF_OFF_T)
     check_type_size("socklen_t" CURL_SIZEOF_CURL_SOCKLEN_T)
 
-    check_symbol_exists(basename      "${CURL_INCLUDES}" HAVE_BASENAME)
-    check_symbol_exists(socket        "${CURL_INCLUDES}" HAVE_SOCKET)
-    # poll on macOS is unreliable, it first did not exist, then was broken until
-    # fixed in 10.9 only to break again in 10.12.
-    if(NOT APPLE)
-      check_symbol_exists(poll        "${CURL_INCLUDES}" HAVE_POLL)
+    if (SIZEOF_LONG_LONG)
+      set(HAVE_LONGLONG 1)
     endif()
-    check_symbol_exists(select        "${CURL_INCLUDES}" HAVE_SELECT)
-    check_symbol_exists(strdup        "${CURL_INCLUDES}" HAVE_STRDUP)
-    check_symbol_exists(strstr        "${CURL_INCLUDES}" HAVE_STRSTR)
-    check_symbol_exists(strtok_r      "${CURL_INCLUDES}" HAVE_STRTOK_R)
-    check_symbol_exists(strftime      "${CURL_INCLUDES}" HAVE_STRFTIME)
-    check_symbol_exists(uname         "${CURL_INCLUDES}" HAVE_UNAME)
-    check_symbol_exists(strcasecmp    "${CURL_INCLUDES}" HAVE_STRCASECMP)
-    check_symbol_exists(stricmp       "${CURL_INCLUDES}" HAVE_STRICMP)
-    check_symbol_exists(strcmpi       "${CURL_INCLUDES}" HAVE_STRCMPI)
-    check_symbol_exists(strncmpi      "${CURL_INCLUDES}" HAVE_STRNCMPI)
-    check_symbol_exists(alarm         "${CURL_INCLUDES}" HAVE_ALARM)
-    if(NOT HAVE_STRNCMPI)
-      set(HAVE_STRCMPI)
-    endif(NOT HAVE_STRNCMPI)
 
-    check_symbol_exists(gethostbyaddr "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR)
-    check_symbol_exists(gethostbyaddr_r "${CURL_INCLUDES}" HAVE_GETHOSTBYADDR_R)
-    check_symbol_exists(gettimeofday  "${CURL_INCLUDES}" HAVE_GETTIMEOFDAY)
-    check_symbol_exists(inet_addr     "${CURL_INCLUDES}" HAVE_INET_ADDR)
-    check_symbol_exists(inet_ntoa     "${CURL_INCLUDES}" HAVE_INET_NTOA)
-    check_symbol_exists(inet_ntoa_r   "${CURL_INCLUDES}" HAVE_INET_NTOA_R)
-    check_symbol_exists(tcsetattr     "${CURL_INCLUDES}" HAVE_TCSETATTR)
-    check_symbol_exists(tcgetattr     "${CURL_INCLUDES}" HAVE_TCGETATTR)
-    check_symbol_exists(perror        "${CURL_INCLUDES}" HAVE_PERROR)
-    check_symbol_exists(closesocket   "${CURL_INCLUDES}" HAVE_CLOSESOCKET)
-    check_symbol_exists(setvbuf       "${CURL_INCLUDES}" HAVE_SETVBUF)
-    check_symbol_exists(sigsetjmp     "${CURL_INCLUDES}" HAVE_SIGSETJMP)
-    check_symbol_exists(getpass_r     "${CURL_INCLUDES}" HAVE_GETPASS_R)
-    check_symbol_exists(strlcat       "${CURL_INCLUDES}" HAVE_STRLCAT)
-    check_symbol_exists(getpwuid      "${CURL_INCLUDES}" HAVE_GETPWUID)
-    check_symbol_exists(geteuid       "${CURL_INCLUDES}" HAVE_GETEUID)
-    check_symbol_exists(utime         "${CURL_INCLUDES}" HAVE_UTIME)
-    check_symbol_exists(gmtime_r      "${CURL_INCLUDES}" HAVE_GMTIME_R)
-    check_symbol_exists(localtime_r   "${CURL_INCLUDES}" HAVE_LOCALTIME_R)
+    check_function_exists("accept4"       HAVE_ACCEPT4)
+    check_function_exists("fnmatch"       HAVE_FNMATCH)
+    check_symbol_exists("basename"        "${CURL_INCLUDES};string.h" HAVE_BASENAME)  # libgen.h unistd.h
+    check_symbol_exists("opendir"         "dirent.h" HAVE_OPENDIR)
+    check_function_exists("poll"          HAVE_POLL)  # poll.h
+    check_symbol_exists("socket"          "${CURL_INCLUDES}" HAVE_SOCKET)  # winsock2.h sys/socket.h
+    check_symbol_exists("socketpair"      "${CURL_INCLUDES}" HAVE_SOCKETPAIR)  # sys/socket.h
+    check_symbol_exists("recv"            "${CURL_INCLUDES}" HAVE_RECV)  # proto/bsdsocket.h sys/types.h sys/socket.h
+    check_symbol_exists("send"            "${CURL_INCLUDES}" HAVE_SEND)  # proto/bsdsocket.h sys/types.h sys/socket.h
+    check_function_exists("sendmsg"       HAVE_SENDMSG)
+    check_function_exists("sendmmsg"      HAVE_SENDMMSG)
+    check_symbol_exists("select"          "${CURL_INCLUDES}" HAVE_SELECT)  # proto/bsdsocket.h sys/select.h sys/socket.h
+    check_symbol_exists("strdup"          "string.h" HAVE_STRDUP)
+    check_symbol_exists("memrchr"         "string.h" HAVE_MEMRCHR)
+    check_symbol_exists("alarm"           "unistd.h" HAVE_ALARM)
+    check_symbol_exists("fcntl"           "fcntl.h" HAVE_FCNTL)
+    check_function_exists("getppid"       HAVE_GETPPID)
+    check_function_exists("utimes"        HAVE_UTIMES)
 
-    check_symbol_exists(gethostbyname   "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME)
-    check_symbol_exists(gethostbyname_r "${CURL_INCLUDES}" HAVE_GETHOSTBYNAME_R)
+    check_function_exists("gettimeofday"  HAVE_GETTIMEOFDAY)  # sys/time.h
+    check_symbol_exists("closesocket"     "${CURL_INCLUDES}" HAVE_CLOSESOCKET)  # winsock2.h
+    check_symbol_exists("sigsetjmp"       "setjmp.h" HAVE_SIGSETJMP)
+    check_function_exists("getpass_r"     HAVE_GETPASS_R)
+    check_function_exists("getpwuid"      HAVE_GETPWUID)
+    check_function_exists("getpwuid_r"    HAVE_GETPWUID_R)
+    check_function_exists("geteuid"       HAVE_GETEUID)
+    check_function_exists("utime"         HAVE_UTIME)
+    check_symbol_exists("gmtime_r"        "stdlib.h;time.h" HAVE_GMTIME_R)
+
+    check_symbol_exists("gethostbyname_r" "netdb.h" HAVE_GETHOSTBYNAME_R)
+    check_symbol_exists("gethostname"     "${CURL_INCLUDES}" HAVE_GETHOSTNAME)  # winsock2.h unistd.h proto/bsdsocket.h
 
-    check_symbol_exists(signal        "${CURL_INCLUDES}" HAVE_SIGNAL_FUNC)
-    check_symbol_exists(SIGALRM       "${CURL_INCLUDES}" HAVE_SIGNAL_MACRO)
-    if(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO)
-      set(HAVE_SIGNAL 1)
-    endif(HAVE_SIGNAL_FUNC AND HAVE_SIGNAL_MACRO)
-    check_symbol_exists(uname          "${CURL_INCLUDES}" HAVE_UNAME)
-    check_symbol_exists(strtoll        "${CURL_INCLUDES}" HAVE_STRTOLL)
-    check_symbol_exists(_strtoi64      "${CURL_INCLUDES}" HAVE__STRTOI64)
-    check_symbol_exists(strerror_r     "${CURL_INCLUDES}" HAVE_STRERROR_R)
-    check_symbol_exists(siginterrupt   "${CURL_INCLUDES}" HAVE_SIGINTERRUPT)
-    check_symbol_exists(perror         "${CURL_INCLUDES}" HAVE_PERROR)
-    check_symbol_exists(fork           "${CURL_INCLUDES}" HAVE_FORK)
-    check_symbol_exists(getaddrinfo    "${CURL_INCLUDES}" HAVE_GETADDRINFO)
-    check_symbol_exists(freeaddrinfo   "${CURL_INCLUDES}" HAVE_FREEADDRINFO)
-    check_symbol_exists(freeifaddrs    "${CURL_INCLUDES}" HAVE_FREEIFADDRS)
-    check_symbol_exists(pipe           "${CURL_INCLUDES}" HAVE_PIPE)
-    check_symbol_exists(ftruncate      "${CURL_INCLUDES}" HAVE_FTRUNCATE)
-    check_symbol_exists(getprotobyname "${CURL_INCLUDES}" HAVE_GETPROTOBYNAME)
-    check_symbol_exists(getrlimit      "${CURL_INCLUDES}" HAVE_GETRLIMIT)
-    check_symbol_exists(setlocale      "${CURL_INCLUDES}" HAVE_SETLOCALE)
-    check_symbol_exists(setmode        "${CURL_INCLUDES}" HAVE_SETMODE)
-    check_symbol_exists(setrlimit      "${CURL_INCLUDES}" HAVE_SETRLIMIT)
-    check_symbol_exists(fcntl          "${CURL_INCLUDES}" HAVE_FCNTL)
-    check_symbol_exists(ioctl          "${CURL_INCLUDES}" HAVE_IOCTL)
-    check_symbol_exists(setsockopt     "${CURL_INCLUDES}" HAVE_SETSOCKOPT)
+    check_symbol_exists("signal"          "signal.h" HAVE_SIGNAL)
+    check_symbol_exists("strerror_r"      "stdlib.h;string.h" HAVE_STRERROR_R)
+    check_symbol_exists("sigaction"       "signal.h" HAVE_SIGACTION)
+    check_symbol_exists("siginterrupt"    "signal.h" HAVE_SIGINTERRUPT)
+    check_symbol_exists("getaddrinfo"     "${CURL_INCLUDES};stdlib.h;string.h" HAVE_GETADDRINFO)  # ws2tcpip.h sys/socket.h netdb.h
+    check_symbol_exists("getifaddrs"      "${CURL_INCLUDES};stdlib.h" HAVE_GETIFADDRS)  # ifaddrs.h
+    check_symbol_exists("freeaddrinfo"    "${CURL_INCLUDES}" HAVE_FREEADDRINFO)  # ws2tcpip.h sys/socket.h netdb.h
+    check_function_exists("pipe"          HAVE_PIPE)
+    check_function_exists("pipe2"         HAVE_PIPE2)
+    check_function_exists("eventfd"       HAVE_EVENTFD)
+    check_symbol_exists("ftruncate"       "unistd.h" HAVE_FTRUNCATE)
+    check_symbol_exists("getpeername"     "${CURL_INCLUDES}" HAVE_GETPEERNAME)  # winsock2.h unistd.h proto/bsdsocket.h
+    check_symbol_exists("getsockname"     "${CURL_INCLUDES}" HAVE_GETSOCKNAME)  # winsock2.h unistd.h proto/bsdsocket.h
+    check_function_exists("getrlimit"       HAVE_GETRLIMIT)
+    check_function_exists("setlocale"       HAVE_SETLOCALE)
+    check_function_exists("setrlimit"       HAVE_SETRLIMIT)
 
-    if(HAVE_SIZEOF_LONG_LONG)
-      set(HAVE_LONGLONG 1)
-      set(HAVE_LL 1)
-    endif(HAVE_SIZEOF_LONG_LONG)
-
-    check_function_exists(mach_absolute_time HAVE_MACH_ABSOLUTE_TIME)
-    check_function_exists(gethostname HAVE_GETHOSTNAME)
-
-    check_include_file_concat("pthread.h" HAVE_PTHREAD_H)
-    check_symbol_exists(recv "sys/socket.h" HAVE_RECV)
-    check_symbol_exists(send "sys/socket.h" HAVE_SEND)
-
+    if(WIN32)
+      # include wincrypt.h as a workaround for mingw-w64 __MINGW64_VERSION_MAJOR <= 5 header bug */
+      check_symbol_exists("if_nametoindex"  "winsock2.h;wincrypt.h;iphlpapi.h" HAVE_IF_NAMETOINDEX)  # Windows Vista+ non-UWP */
+    else()
+      check_function_exists("if_nametoindex"  HAVE_IF_NAMETOINDEX)  # net/if.h
+      check_function_exists("realpath"        HAVE_REALPATH)
+      check_function_exists("sched_yield"     HAVE_SCHED_YIELD)
+      check_symbol_exists("strcasecmp"      "string.h" HAVE_STRCASECMP)
+      check_symbol_exists("stricmp"         "string.h" HAVE_STRICMP)
+      check_symbol_exists("strcmpi"         "string.h" HAVE_STRCMPI)
+    endif()
     check_struct_has_member("struct sockaddr_un" sun_path "sys/un.h" USE_UNIX_SOCKETS)
 
     list(APPEND CMAKE_REQUIRED_INCLUDES "${CURL_SOURCES_DIR}/include")
     set(CMAKE_EXTRA_INCLUDE_FILES "curl/system.h")
     check_type_size("curl_off_t"  SIZEOF_CURL_OFF_T)
 
-    add_definitions(-DHAVE_GLIBC_STRERROR_R=1)
-
     include(${CURL_SOURCES_DIR}/CMake/OtherTests.cmake)
 
     foreach(CURL_TEST
@@ -304,30 +263,20 @@
         HAVE_IOCTL_FIONBIO
         HAVE_IOCTL_SIOCGIFADDR
         HAVE_SETSOCKOPT_SO_NONBLOCK
-        HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
-        TIME_WITH_SYS_TIME
-        HAVE_O_NONBLOCK
-        HAVE_GETHOSTBYADDR_R_5
-        HAVE_GETHOSTBYADDR_R_7
-        HAVE_GETHOSTBYADDR_R_8
-        HAVE_GETHOSTBYADDR_R_5_REENTRANT
-        HAVE_GETHOSTBYADDR_R_7_REENTRANT
-        HAVE_GETHOSTBYADDR_R_8_REENTRANT
         HAVE_GETHOSTBYNAME_R_3
         HAVE_GETHOSTBYNAME_R_5
         HAVE_GETHOSTBYNAME_R_6
+        HAVE_BOOL_T
+        STDC_HEADERS
+        HAVE_ATOMIC
+        HAVE_SOCKADDR_IN6_SIN6_SCOPE_ID
         HAVE_GETHOSTBYNAME_R_3_REENTRANT
         HAVE_GETHOSTBYNAME_R_5_REENTRANT
         HAVE_GETHOSTBYNAME_R_6_REENTRANT
-        HAVE_SOCKLEN_T
-        HAVE_IN_ADDR_T
-        HAVE_BOOL_T
-        STDC_HEADERS
-        RETSIGTYPE_TEST
-        HAVE_INET_NTOA_R_DECL
-        HAVE_INET_NTOA_R_DECL_REENTRANT
         HAVE_GETADDRINFO
         HAVE_FILE_OFFSET_BITS
+        HAVE_GLIBC_STRERROR_R
+        HAVE_POSIX_STRERROR_R
         )
       curl_internal_test(${CURL_TEST})
     endforeach(CURL_TEST)
--- a/Resources/Orthanc/CMake/OpenSslConfiguration.cmake	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/CMake/OpenSslConfiguration.cmake	Fri Jan 02 13:08:44 2026 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU Lesser General Public License
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.cpp	Fri Jan 02 13:08:44 2026 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -23,9 +23,11 @@
 
 #include "OrthancPluginCppWrapper.h"
 
+#include <cassert>
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/move/unique_ptr.hpp>
 #include <boost/thread.hpp>
+#include <boost/algorithm/string/join.hpp>
 
 
 #include <json/reader.h>
@@ -219,26 +221,17 @@
   }
 
 
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-  MemoryBuffer::MemoryBuffer(const void* buffer,
-                             size_t size)
-  {
-    uint32_t s = static_cast<uint32_t>(size);
-    if (static_cast<size_t>(s) != size)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
-    }
-    else if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
-             OrthancPluginErrorCode_Success)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
-    }
-    else
-    {
-      memcpy(buffer_.data, buffer, size);
-    }
-  }
-#endif
+  MemoryBuffer::~MemoryBuffer()
+  {
+    try
+    {
+      Clear();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      // Don't throw exceptions in destructors
+    }
+  }
 
 
   void MemoryBuffer::Clear()
@@ -252,6 +245,51 @@
   }
 
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void MemoryBuffer::Assign(const void* buffer,
+                            size_t size)
+  {
+    uint32_t s = static_cast<uint32_t>(size);
+    if (static_cast<size_t>(s) != size)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+
+    Clear();
+
+    if (OrthancPluginCreateMemoryBuffer(GetGlobalContext(), &buffer_, s) !=
+        OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+    else
+    {
+      if (size > 0)
+      {
+        memcpy(buffer_.data, buffer, size);
+      }
+    }
+  }
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void MemoryBuffer::Assign(const std::string& s)
+  {
+    Assign(s.empty() ? NULL : s.c_str(), s.size());
+  }
+#endif
+
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+  void MemoryBuffer::AssignJson(const Json::Value& value)
+  {
+    std::string s;
+    WriteFastJson(s, value);
+    Assign(s);
+  }
+#endif
+
+
   void MemoryBuffer::Assign(OrthancPluginMemoryBuffer& other)
   {
     Clear();
@@ -326,6 +364,38 @@
     }
   }
 
+
+#if (HAS_ORTHANC_PLUGIN_PEERS == 1) || (HAS_ORTHANC_PLUGIN_HTTP_CLIENT == 1) || (HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1)
+  static void DecodeHttpHeaders(HttpHeaders& target,
+                                const MemoryBuffer& source)
+  {
+    Json::Value v;
+    source.ToJson(v);
+
+    if (v.type() != Json::objectValue)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+
+    Json::Value::Members members = v.getMemberNames();
+    target.clear();
+
+    for (size_t i = 0; i < members.size(); i++)
+    {
+      const Json::Value& h = v[members[i]];
+      if (h.type() != Json::stringValue)
+      {
+        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+      }
+      else
+      {
+        target[members[i]] = h.asString();
+      }
+    }
+  }
+#endif
+
+
   // helper class to convert std::map of headers to the plugin SDK C structure
   class PluginHttpHeaders
   {
@@ -599,6 +669,19 @@
   }
 
 
+  OrthancString::~OrthancString()
+  {
+    try
+    {
+      Clear();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      // Don't throw exceptions in destructors
+    }
+  }
+
+
   void OrthancString::Assign(char* str)
   {
     Clear();
@@ -672,7 +755,7 @@
   {
     OrthancString str;
     str.Assign(OrthancPluginDicomBufferToJson
-               (GetGlobalContext(), GetData(), GetSize(), format, flags, maxStringLength));
+               (GetGlobalContext(), reinterpret_cast<const char*>(GetData()), GetSize(), format, flags, maxStringLength));
     str.ToJson(target);
   }
 
@@ -1248,6 +1331,20 @@
     }
   }
 
+
+  OrthancImage::~OrthancImage()
+  {
+    try
+    {
+      Clear();
+    }
+    catch (ORTHANC_PLUGINS_EXCEPTION_CLASS&)
+    {
+      // Don't throw exceptions in destructors
+    }
+  }
+
+
   void OrthancImage::UncompressPngImage(const void* data,
                                         size_t size)
   {
@@ -1565,7 +1662,7 @@
     {
       if (!answer.IsEmpty())
       {
-        result.assign(answer.GetData(), answer.GetSize());
+        result.assign(reinterpret_cast<const char*>(answer.GetData()), answer.GetSize());
       }
       return true;
     }
@@ -2051,6 +2148,48 @@
             DoPost(target, index, uri, body, headers));
   }
 
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body,
+                            const HttpHeaders& headers, 
+                            unsigned int timeout) const
+  {
+    MemoryBuffer buffer;
+    HttpHeaders answerHeaders;
+
+    if (DoPost(buffer, answerHeaders, index, uri, body, headers, timeout))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+  bool OrthancPeers::DoPost(Json::Value& target,
+                            HttpHeaders& answerHeaders,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body,
+                            const HttpHeaders& headers, 
+                            unsigned int timeout) const
+  {
+    MemoryBuffer buffer;
+
+    if (DoPost(buffer, answerHeaders, index, uri, body, headers, timeout))
+    {
+      buffer.ToJson(target);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
 
   bool OrthancPeers::DoPost(Json::Value& target,
                             size_t index,
@@ -2098,6 +2237,29 @@
                             const std::string& body,
                             const HttpHeaders& headers) const
   {
+    HttpHeaders answerHeaders;
+    return DoPost(target, answerHeaders, index, uri, body, headers, timeout_);
+  }
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body,
+                            const HttpHeaders& headers,
+                            unsigned int timeout) const
+  {
+    HttpHeaders answerHeaders;
+    return DoPost(target, answerHeaders, index, uri, body, headers, timeout);
+  }
+
+  bool OrthancPeers::DoPost(MemoryBuffer& target,
+                            HttpHeaders& answerHeaders,
+                            size_t index,
+                            const std::string& uri,
+                            const std::string& body,
+                            const HttpHeaders& headers,
+                            unsigned int timeout) const
+  {
     if (index >= index_.size())
     {
       ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_ParameterOutOfRange);
@@ -2110,17 +2272,20 @@
     }
 
     OrthancPlugins::MemoryBuffer answer;
+    OrthancPlugins::MemoryBuffer answerHeadersBuffer;
     uint16_t status;
     PluginHttpHeaders pluginHeaders(headers);
 
     OrthancPluginErrorCode code = OrthancPluginCallPeerApi
-      (GetGlobalContext(), *answer, NULL, &status, peers_,
+      (GetGlobalContext(), *answer, *answerHeadersBuffer, &status, peers_,
        static_cast<uint32_t>(index), OrthancPluginHttpMethod_Post, uri.c_str(),
-       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout_);
+       pluginHeaders.GetSize(), pluginHeaders.GetKeys(), pluginHeaders.GetValues(), body.empty() ? NULL : body.c_str(), body.size(), timeout);
 
     if (code == OrthancPluginErrorCode_Success)
     {
       target.Swap(answer);
+      DecodeHttpHeaders(answerHeaders, answerHeadersBuffer);
+      
       return (status == 200);
     }
     else
@@ -2276,6 +2441,8 @@
   {
     assert(job != NULL);
     OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
+
+    boost::mutex::scoped_lock lock(that.contentMutex_);
     return CopyStringToMemoryBuffer(target, that.content_);
   }
 #else
@@ -2285,7 +2452,10 @@
 
     try
     {
-      return reinterpret_cast<OrthancJob*>(job)->content_.c_str();
+      OrthancJob& that = *reinterpret_cast<OrthancJob*>(job);
+      boost::mutex::scoped_lock lock(that.contentMutex_);
+
+      return that.content_.c_str();
     }
     catch (...)
     {
@@ -2413,6 +2583,8 @@
 
   void OrthancJob::UpdateContent(const Json::Value& content)
   {
+    boost::mutex::scoped_lock lock(contentMutex_);
+
     if (content.type() != Json::objectValue)
     {
       ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_BadFileFormat);
@@ -2555,32 +2727,42 @@
 
         return;
       }
-      else if (state == "Running")
+      else if (state == "Running" ||
+               state == "Pending" ||
+               state == "Paused" ||
+               state == "Retry")
       {
         continue;
       }
-      else if (!status.isMember("ErrorCode") ||
-               status["ErrorCode"].type() != Json::intValue)
+      else if (state == "Failure")
       {
-        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
+        if (!status.isMember("ErrorCode") ||
+            status["ErrorCode"].type() != Json::intValue)
+        {
+          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
+        }
+        else
+        {
+          if (!status.isMember("ErrorDescription") ||
+              status["ErrorDescription"].type() != Json::stringValue)
+          {
+            ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
+          }
+          else
+          {
+  #if HAS_ORTHANC_EXCEPTION == 1
+            throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
+                                            status["ErrorDescription"].asString());
+  #else
+            ORTHANC_PLUGINS_LOG_ERROR("Exception while executing the job: " + status["ErrorDescription"].asString());
+            ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
+  #endif
+          }
+        }
       }
       else
       {
-        if (!status.isMember("ErrorDescription") ||
-            status["ErrorDescription"].type() != Json::stringValue)
-        {
-          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());
-        }
-        else
-        {
-#if HAS_ORTHANC_EXCEPTION == 1
-          throw Orthanc::OrthancException(static_cast<Orthanc::ErrorCode>(status["ErrorCode"].asInt()),
-                                          status["ErrorDescription"].asString());
-#else
-          ORTHANC_PLUGINS_LOG_ERROR("Exception while executing the job: " + status["ErrorDescription"].asString());
-          ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(status["ErrorCode"].asInt());          
-#endif
-        }
+        ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(OrthancPluginErrorCode_InternalError);
       }
     }
   }
@@ -3167,36 +3349,6 @@
   }
 #endif    
 
-
-  static void DecodeHttpHeaders(HttpHeaders& target,
-                                const MemoryBuffer& source)
-  {
-    Json::Value v;
-    source.ToJson(v);
-
-    if (v.type() != Json::objectValue)
-    {
-      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-    }
-
-    Json::Value::Members members = v.getMemberNames();
-    target.clear();
-
-    for (size_t i = 0; i < members.size(); i++)
-    {
-      const Json::Value& h = v[members[i]];
-      if (h.type() != Json::stringValue)
-      {
-        ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
-      }
-      else
-      {
-        target[members[i]] = h.asString();
-      }
-    }
-  }
-
-
   void HttpClient::ExecuteWithoutStream(uint16_t& httpStatus,
                                         HttpHeaders& answerHeaders,
                                         std::string& answerBody,
@@ -4067,6 +4219,16 @@
   }
 #endif
 
+  void GetGetArguments(GetArguments& result, const OrthancPluginHttpRequest* request)
+  {
+    result.clear();
+
+    for (uint32_t i = 0; i < request->getCount; ++i)
+    {
+      result[request->getKeys[i]] = request->getValues[i];
+    }    
+  }
+
   void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request)
   {
     result.clear();
@@ -4077,6 +4239,26 @@
     }    
   }
 
+  void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request)
+  {
+    output.clear();
+    std::vector<std::string> arguments;
+    for (uint32_t i = 0; i < request->getCount; ++i)
+    {
+      if (request->getValues[i] && strlen(request->getValues[i]) > 0)
+      {
+        arguments.push_back(std::string(request->getKeys[i]) + "=" + std::string(request->getValues[i]));
+      }
+      else
+      {
+        arguments.push_back(std::string(request->getKeys[i]));
+      }
+    }
+
+    output = boost::algorithm::join(arguments, "&");
+  }
+
+
 #if !ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 4)
   static void SetPluginProperty(const std::string& pluginIdentifier,
                                 _OrthancPluginProperty property,
@@ -4130,6 +4312,29 @@
     httpStatus_(0)
   {
   }
+
+  RestApiClient::RestApiClient(const char* url,
+                               const OrthancPluginHttpRequest* request) :
+    method_(request->method),
+    path_(url),
+    afterPlugins_(false),
+    httpStatus_(0)
+  {
+    OrthancPlugins::GetHttpHeaders(requestHeaders_, request);
+
+    std::string getArguments;
+    OrthancPlugins::SerializeGetArguments(getArguments, request);
+
+    if (!getArguments.empty())
+    {
+      path_ += "?" + getArguments;
+    }
+
+    if (request->bodySize > 0 && request->body != NULL)
+    {
+      requestBody_.assign(reinterpret_cast<const char*>(request->body), request->bodySize);
+    }
+  }
 #endif
 
 
@@ -4150,6 +4355,15 @@
 
 
 #if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
+  void RestApiClient::SetRequestHeader(const std::string& key,
+                                       const std::string& value)
+  {
+    requestHeaders_[key] = value;
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_GENERIC_CALL_REST_API == 1
   bool RestApiClient::Execute()
   {
     if (requestBody_.size() > 0xffffffffu)
@@ -4195,6 +4409,40 @@
       }
     }
   }
+
+  void RestApiClient::ExecuteAndForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output)
+  {
+    if (Execute())
+    {
+      ForwardAnswer(context, output);
+    }
+  }
+
+  void RestApiClient::ForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output)
+  {
+    if (httpStatus_ == 200)
+    {
+      const char* mimeType = NULL;
+      for (HttpHeaders::const_iterator h = answerHeaders_.begin(); h != answerHeaders_.end(); ++h)
+      {
+        if (h->first == "content-type")
+        {
+          mimeType = h->second.c_str();
+        }
+      }
+      
+      AnswerString(answerBody_, mimeType, output);
+    }
+    else
+    {
+      AnswerHttpError(httpStatus_, output);
+    }
+  }
+
+  bool RestApiClient::GetAnswerJson(Json::Value& output) const
+  {
+    return ReadJson(output, answerBody_);
+  }
 #endif
 
 
@@ -4251,4 +4499,262 @@
     }
   }
 #endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  KeyValueStore::Iterator::Iterator(OrthancPluginKeysValuesIterator  *iterator) :
+    iterator_(iterator)
+  {
+    if (iterator_ == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  KeyValueStore::Iterator::~Iterator()
+  {
+    OrthancPluginFreeKeysValuesIterator(OrthancPlugins::GetGlobalContext(), iterator_);
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  bool KeyValueStore::Iterator::Next()
+  {
+    uint8_t done;
+    OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorNext(OrthancPlugins::GetGlobalContext(), &done, iterator_);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+    else
+    {
+      return (done != 0);
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  std::string KeyValueStore::Iterator::GetKey() const
+  {
+    const char* s = OrthancPluginKeysValuesIteratorGetKey(OrthancPlugins::GetGlobalContext(), iterator_);
+    if (s == NULL)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(InternalError);
+    }
+    else
+    {
+      return s;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  void KeyValueStore::Iterator::GetValue(std::string& value) const
+  {
+    OrthancPlugins::MemoryBuffer valueBuffer;
+    OrthancPluginErrorCode code = OrthancPluginKeysValuesIteratorGetValue(OrthancPlugins::GetGlobalContext(), *valueBuffer, iterator_);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+    else
+    {
+      valueBuffer.ToString(value);
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  void KeyValueStore::Store(const std::string& key,
+                            const void* value,
+                            size_t valueSize)
+  {
+    if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+
+    OrthancPluginErrorCode code = OrthancPluginStoreKeyValue(OrthancPlugins::GetGlobalContext(), storeId_.c_str(),
+                                                             key.c_str(), value, static_cast<uint32_t>(valueSize));
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  bool KeyValueStore::GetValue(std::string& value,
+                               const std::string& key)
+  {
+    uint8_t found = false;
+    OrthancPlugins::MemoryBuffer valueBuffer;
+    OrthancPluginErrorCode code = OrthancPluginGetKeyValue(OrthancPlugins::GetGlobalContext(), &found,
+                                                           *valueBuffer, storeId_.c_str(), key.c_str());
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+    else if (found)
+    {
+      valueBuffer.ToString(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  void KeyValueStore::DeleteKey(const std::string& key)
+  {
+    OrthancPluginErrorCode code = OrthancPluginDeleteKeyValue(OrthancPlugins::GetGlobalContext(),
+                                                              storeId_.c_str(), key.c_str());
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  KeyValueStore::Iterator* KeyValueStore::CreateIterator()
+  {
+    return new Iterator(OrthancPluginCreateKeysValuesIterator(OrthancPlugins::GetGlobalContext(), storeId_.c_str()));
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_QUEUES == 1
+  void Queue::Enqueue(const void* value,
+                      size_t valueSize)
+  {
+    if (static_cast<size_t>(static_cast<uint32_t>(valueSize)) != valueSize)
+    {
+      ORTHANC_PLUGINS_THROW_EXCEPTION(NotEnoughMemory);
+    }
+
+    OrthancPluginErrorCode code = OrthancPluginEnqueueValue(OrthancPlugins::GetGlobalContext(),
+                                                            queueId_.c_str(), value, static_cast<uint32_t>(valueSize));
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_QUEUES == 1
+  bool Queue::DequeueInternal(std::string& value,
+                              OrthancPluginQueueOrigin origin)
+  {
+    uint8_t found = false;
+    OrthancPlugins::MemoryBuffer valueBuffer;
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+    OrthancPluginErrorCode code = OrthancPluginDequeueValue(OrthancPlugins::GetGlobalContext(), &found,
+                                                            *valueBuffer, queueId_.c_str(), origin);
+#pragma GCC diagnostic pop
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+    else if (found)
+    {
+      valueBuffer.ToString(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_QUEUES == 1
+  uint64_t Queue::GetSize()
+  {
+    uint64_t size = 0;
+    OrthancPluginErrorCode code = OrthancPluginGetQueueSize(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), &size);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+    else
+    {
+      return size;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  bool Queue::ReserveInternal(std::string& value, uint64_t& valueId, OrthancPluginQueueOrigin origin, uint32_t releaseTimeout)
+  {
+    uint8_t found = false;
+    OrthancPlugins::MemoryBuffer valueBuffer;
+
+    OrthancPluginErrorCode code = OrthancPluginReserveQueueValue(OrthancPlugins::GetGlobalContext(), &found,
+                                                                 *valueBuffer, &valueId, queueId_.c_str(), origin, releaseTimeout);
+
+    if (code != OrthancPluginErrorCode_Success)
+    {
+      ORTHANC_PLUGINS_THROW_PLUGIN_ERROR_CODE(code);
+    }
+    else if (found)
+    {
+      valueBuffer.ToString(value);
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  bool Queue::ReserveBack(std::string& value, uint64_t& valueId, uint32_t releaseTimeout)
+  {
+    return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Back, releaseTimeout);
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  bool Queue::ReserveFront(std::string& value, uint64_t& valueId, uint32_t releaseTimeout)
+  {
+    return ReserveInternal(value, valueId, OrthancPluginQueueOrigin_Front, releaseTimeout);
+  }
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+  void Queue::Acknowledge(uint64_t valueId)
+  {
+    OrthancPluginAcknowledgeQueueValue(OrthancPlugins::GetGlobalContext(), queueId_.c_str(), valueId);
+  }
+#endif
 }
--- a/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/Plugins/OrthancPluginCppWrapper.h	Fri Jan 02 13:08:44 2026 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
@@ -28,6 +28,7 @@
 #include <orthanc/OrthancCPlugin.h>
 #include <boost/noncopyable.hpp>
 #include <boost/lexical_cast.hpp>
+#include <boost/thread/mutex.hpp>
 #include <boost/date_time/posix_time/posix_time.hpp>
 #include <json/value.h>
 #include <vector>
@@ -134,6 +135,20 @@
 #  define HAS_ORTHANC_PLUGIN_LOG_MESSAGE  0
 #endif
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 8)
+#  define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES  1
+#  define HAS_ORTHANC_PLUGIN_QUEUES            1
+#else
+#  define HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES  0
+#  define HAS_ORTHANC_PLUGIN_QUEUES            0
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 12, 10)
+#  define HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE   1
+#else
+#  define HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE   0
+#endif
+
 
 // Macro to tag a function as having been deprecated
 #if (__cplusplus >= 201402L)  // C++14
@@ -172,6 +187,8 @@
 {
   typedef std::map<std::string, std::string>  HttpHeaders;
 
+  typedef std::map<std::string, std::string>  GetArguments;
+
   typedef void (*RestCallback) (OrthancPluginRestOutput* output,
                                 const char* url,
                                 const OrthancPluginHttpRequest* request);
@@ -203,23 +220,27 @@
   public:
     MemoryBuffer();
 
-#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
-    // This constructor makes a copy of the given buffer in the memory
-    // handled by the Orthanc core
-    MemoryBuffer(const void* buffer,
-                 size_t size);
-#endif
-
-    ~MemoryBuffer()
-    {
-      Clear();
-    }
+    ~MemoryBuffer();
 
     OrthancPluginMemoryBuffer* operator*()
     {
       return &buffer_;
     }
 
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    // Copy of the given buffer into the memory managed by the Orthanc core
+    void Assign(const void* buffer,
+                size_t size);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void Assign(const std::string& s);
+#endif
+
+#if ORTHANC_PLUGINS_VERSION_IS_ABOVE(1, 7, 0)
+    void AssignJson(const Json::Value& value);
+#endif
+
     // This transfers ownership from "other" to "this"
     void Assign(OrthancPluginMemoryBuffer& other);
 
@@ -227,11 +248,11 @@
 
     OrthancPluginMemoryBuffer Release();
 
-    const char* GetData() const
+    const void* GetData() const
     {
       if (buffer_.size > 0)
       {
-        return reinterpret_cast<const char*>(buffer_.data);
+        return buffer_.data;
       }
       else
       {
@@ -354,10 +375,7 @@
     {
     }
 
-    ~OrthancString()
-    {
-      Clear();
-    }
+    ~OrthancString();
 
     // This transfers ownership, warning: The string must have been
     // allocated by the Orthanc core
@@ -474,10 +492,7 @@
                  uint32_t                  pitch,
                  void*                     buffer);
 
-    ~OrthancImage()
-    {
-      Clear();
-    }
+    ~OrthancImage();
 
     void UncompressPngImage(const void* data,
                             size_t size);
@@ -855,6 +870,21 @@
                 const HttpHeaders& headers) const;
 
     bool DoPost(MemoryBuffer& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body,
+                const HttpHeaders& headers,
+                unsigned int timeout) const;
+
+    bool DoPost(MemoryBuffer& target,
+                HttpHeaders& answerHeaders,
+                size_t index,
+                const std::string& uri,
+                const std::string& body,
+                const HttpHeaders& headers,
+                unsigned int timeout) const;
+
+    bool DoPost(MemoryBuffer& target,
                 const std::string& name,
                 const std::string& uri,
                 const std::string& body,
@@ -867,6 +897,21 @@
                 const HttpHeaders& headers) const;
 
     bool DoPost(Json::Value& target,
+                size_t index,
+                const std::string& uri,
+                const std::string& body,
+                const HttpHeaders& headers,
+                unsigned int timeout) const;
+    
+    bool DoPost(Json::Value& target,
+                HttpHeaders& answerHeaders,
+                size_t index,
+                const std::string& uri,
+                const std::string& body,
+                const HttpHeaders& headers,
+                unsigned int timeout) const;
+
+    bool DoPost(Json::Value& target,
                 const std::string& name,
                 const std::string& uri,
                 const std::string& body,
@@ -899,6 +944,7 @@
   {
   private:
     std::string   jobType_;
+    boost::mutex  contentMutex_;
     std::string   content_;
     bool          hasSerialized_;
     std::string   serialized_;
@@ -973,7 +1019,7 @@
 
 
 #if HAS_ORTHANC_PLUGIN_METRICS == 1
-  inline void SetMetricsValue(char* name,
+  inline void SetMetricsValue(const char* name,
                               float value)
   {
     OrthancPluginSetMetricsValue(GetGlobalContext(), name,
@@ -1399,6 +1445,12 @@
 // helper method to convert Http headers from the plugin SDK to a std::map
 void GetHttpHeaders(HttpHeaders& result, const OrthancPluginHttpRequest* request);
 
+// helper method to re-serialize the get arguments from the SDK into a string
+void SerializeGetArguments(std::string& output, const OrthancPluginHttpRequest* request);
+
+// helper method to convert Get arguments from the plugin SDK to a std::map
+void GetGetArguments(GetArguments& result, const OrthancPluginHttpRequest* request);
+
 #if HAS_ORTHANC_PLUGIN_WEBDAV == 1
   class IWebDavCollection : public boost::noncopyable
   {
@@ -1528,6 +1580,10 @@
 
   public:
     RestApiClient();
+    
+    // used to forward a call from the plugin to the core
+    RestApiClient(const char* url,
+                  const OrthancPluginHttpRequest* request);
 
     void SetMethod(OrthancPluginHttpMethod method)
     {
@@ -1552,6 +1608,9 @@
     void AddRequestHeader(const std::string& key,
                           const std::string& value);
 
+    void SetRequestHeader(const std::string& key,
+                          const std::string& value);
+
     const HttpHeaders& GetRequestHeaders() const
     {
       return requestHeaders_;
@@ -1582,14 +1641,144 @@
       return requestBody_;
     }
 
+    // Execute only
     bool Execute();
 
+    // Forward response as is
+    void ForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output);
+
+    // Execute and forward the response as is
+    void ExecuteAndForwardAnswer(OrthancPluginContext* context, OrthancPluginRestOutput* output);
+
     uint16_t GetHttpStatus() const;
 
     bool LookupAnswerHeader(std::string& value,
                             const std::string& key) const;
 
     const std::string& GetAnswerBody() const;
+
+    bool GetAnswerJson(Json::Value& output) const;
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_KEY_VALUE_STORES == 1
+  class KeyValueStore : public boost::noncopyable
+  {
+  public:
+    class Iterator : public boost::noncopyable
+    {
+    private:
+      OrthancPluginKeysValuesIterator  *iterator_;
+
+    public:
+      explicit Iterator(OrthancPluginKeysValuesIterator *iterator);
+
+      ~Iterator();
+
+      bool Next();
+
+      std::string GetKey() const;
+
+      void GetValue(std::string& target) const;
+    };
+
+  private:
+    std::string storeId_;
+
+  public:
+    explicit KeyValueStore(const std::string& storeId) :
+      storeId_(storeId)
+    {
+    }
+
+    const std::string& GetStoreId() const
+    {
+      return storeId_;
+    }
+
+    void Store(const std::string& key,
+               const void* value,
+               size_t valueSize);
+
+    void Store(const std::string& key,
+               const std::string& value)
+    {
+      Store(key, value.empty() ? NULL : value.c_str(), value.size());
+    }
+
+    bool GetValue(std::string& value,
+                  const std::string& key);
+
+    void DeleteKey(const std::string& key);
+
+    Iterator* CreateIterator();
+  };
+#endif
+
+
+#if HAS_ORTHANC_PLUGIN_QUEUES == 1
+  class Queue : public boost::noncopyable
+  {
+  private:
+    std::string queueId_;
+
+    bool DequeueInternal(std::string& value, OrthancPluginQueueOrigin origin);
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    bool ReserveInternal(std::string& value, uint64_t& valueId, OrthancPluginQueueOrigin origin, uint32_t releaseTimeout);
+#endif
+
+  public:
+    explicit Queue(const std::string& queueId) :
+      queueId_(queueId)
+    {
+    }
+
+    const std::string& GetQueueId() const
+    {
+      return queueId_;
+    }
+
+    void Enqueue(const void* value,
+                 size_t valueSize);
+
+    void Enqueue(const std::string& value)
+    {
+      Enqueue(value.empty() ? NULL : value.c_str(), value.size());
+    }
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    // Use ReserveBack() instead
+    ORTHANC_PLUGIN_DEPRECATED
+#endif
+    bool DequeueBack(std::string& value)
+    {
+      return DequeueInternal(value, OrthancPluginQueueOrigin_Back);
+    }
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    // Use ReserveFront() instead
+    ORTHANC_PLUGIN_DEPRECATED
+#endif
+    bool DequeueFront(std::string& value)
+    {
+      return DequeueInternal(value, OrthancPluginQueueOrigin_Front);
+    }
+
+    uint64_t GetSize();
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    bool ReserveBack(std::string& value, uint64_t& valueId, uint32_t releaseTimeout);
+#endif
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    bool ReserveFront(std::string& value, uint64_t& valueId, uint32_t releaseTimeout);
+#endif
+
+#if HAS_ORTHANC_PLUGIN_RESERVE_QUEUE_VALUE == 1
+    void Acknowledge(uint64_t valueId);
+#endif
   };
 #endif
 }
--- a/Resources/Orthanc/Plugins/OrthancPluginException.h	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/Plugins/OrthancPluginException.h	Fri Jan 02 13:08:44 2026 +0100
@@ -3,8 +3,8 @@
  * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
  * Department, University Hospital of Liege, Belgium
  * Copyright (C) 2017-2023 Osimis S.A., Belgium
- * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
- * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+ * Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+ * Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
  *
  * This program is free software: you can redistribute it and/or
  * modify it under the terms of the GNU General Public License as
--- a/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Fri Jan 17 13:09:12 2025 +0100
+++ b/Resources/Orthanc/Plugins/OrthancPluginsExports.cmake	Fri Jan 02 13:08:44 2026 +0100
@@ -2,8 +2,8 @@
 # Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 # Department, University Hospital of Liege, Belgium
 # Copyright (C) 2017-2023 Osimis S.A., Belgium
-# Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
-# Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
+# Copyright (C) 2024-2026 Orthanc Team SRL, Belgium
+# Copyright (C) 2021-2026 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 #
 # This program is free software: you can redistribute it and/or
 # modify it under the terms of the GNU General Public License as