/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * 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-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
 * as published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 **/


#include "PrecompiledHeaders.h"
#include "SystemToolbox.h"


#if defined(_WIN32)
#  include <winsock2.h>      // For GetMacAddresses(), must be included before "windows.h"
#  include <windows.h>

#  include <iphlpapi.h>      // For GetMacAddresses()
#  include <process.h>       // For "_spawnvp()" and "_getpid()"
#  include <stdlib.h>        // For "environ"
#else
#  include <net/if.h>        // For GetMacAddresses()
#  include <netinet/in.h>    // For GetMacAddresses()
#  include <sys/ioctl.h>     // For GetMacAddresses()
#  include <sys/wait.h>      // For "waitpid()"
#  include <unistd.h>        // For "execvp()"
#endif


#if defined(__APPLE__) && defined(__MACH__)
#  include <limits.h>        // PATH_MAX
#  include <mach-o/dyld.h>   // _NSGetExecutablePath
#endif


#if (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
#  include <net/if_dl.h>     // For GetMacAddresses()
#  include <net/if_types.h>  // For GetMacAddresses()
#  include <sys/sysctl.h>    // For GetMacAddresses()
#endif


#if defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
#  include <limits.h>        // PATH_MAX
#  include <signal.h>
#  include <unistd.h>
#endif


#if defined(__OpenBSD__)
#  include <sys/sysctl.h>    // For "sysctl", "CTL_KERN" and "KERN_PROC_ARGS"
#endif


#include "Logging.h"
#include "OrthancException.h"
#include "Toolbox.h"

#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/thread.hpp>

#include <cassert>
#include <string.h>



/*=========================================================================
  The section below comes from the Boost 1.68.0 project:
  https://github.com/boostorg/program_options/blob/boost-1.68.0/src/parsers.cpp
  
  Copyright Vladimir Prus 2002-2004.
  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE_1_0.txt
  or copy at http://www.boost.org/LICENSE_1_0.txt)
  =========================================================================*/

// The 'environ' should be declared in some cases. E.g. Linux man page says:
// (This variable must be declared in the user program, but is declared in 
// the header file unistd.h in case the header files came from libc4 or libc5, 
// and in case they came from glibc and _GNU_SOURCE was defined.) 
// To be safe, declare it here.

// It appears that on Mac OS X the 'environ' variable is not
// available to dynamically linked libraries.
// See: http://article.gmane.org/gmane.comp.lib.boost.devel/103843
// See: http://lists.gnu.org/archive/html/bug-guile/2004-01/msg00013.html
#if defined(__APPLE__) && defined(__DYNAMIC__)
// The proper include for this is crt_externs.h, however it's not
// available on iOS. The right replacement is not known. See
// https://svn.boost.org/trac/boost/ticket/5053
extern "C"
{
  extern char ***_NSGetEnviron(void);
}
#  define environ (*_NSGetEnviron()) 
#else
#  if defined(__MWERKS__)
#    include <crtl.h>
#  else
#    if !defined(_WIN32) || defined(__COMO_VERSION__)
extern char** environ;
#    endif
#  endif
#endif


/*=========================================================================
  End of section from the Boost 1.68.0 project
  =========================================================================*/


namespace Orthanc
{
  static volatile bool finish_;
  static volatile ServerBarrierEvent barrierEvent_;

#if defined(_WIN32)
  static BOOL WINAPI ConsoleControlHandler(DWORD dwCtrlType)
  {
    // http://msdn.microsoft.com/en-us/library/ms683242(v=vs.85).aspx
    finish_ = true;
    return true;
  }
#else
  static void SignalHandler(int signal)
  {
    if (signal == SIGHUP)
    {
      barrierEvent_ = ServerBarrierEvent_Reload;
    }

    finish_ = true;
  }
#endif


  static ServerBarrierEvent ServerBarrierInternal(const bool* stopFlag)
  {
#if defined(_WIN32)
    SetConsoleCtrlHandler(ConsoleControlHandler, true);
#else
    signal(SIGINT, SignalHandler);
    signal(SIGQUIT, SignalHandler);
    signal(SIGTERM, SignalHandler);
    signal(SIGHUP, SignalHandler);
#endif
  
    // Active loop that awakens every 100ms
    finish_ = false;
    barrierEvent_ = ServerBarrierEvent_Stop;
    while (!(*stopFlag || finish_))
    {
      SystemToolbox::USleep(100ull * 1000ull);
    }

#if defined(_WIN32)
    SetConsoleCtrlHandler(ConsoleControlHandler, false);
#else
    signal(SIGINT, NULL);
    signal(SIGQUIT, NULL);
    signal(SIGTERM, NULL);
    signal(SIGHUP, NULL);
#endif

    return barrierEvent_;
  }


  ServerBarrierEvent SystemToolbox::ServerBarrier(const bool& stopFlag)
  {
    return ServerBarrierInternal(&stopFlag);
  }


  ServerBarrierEvent SystemToolbox::ServerBarrier()
  {
    const bool stopFlag = false;
    return ServerBarrierInternal(&stopFlag);
  }


  void SystemToolbox::USleep(uint64_t microSeconds)
  {
#if defined(_WIN32)
    ::Sleep(static_cast<DWORD>(microSeconds / static_cast<uint64_t>(1000)));
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__native_client__)
    usleep(microSeconds);
#else
#error Support your platform here
#endif
  }


  static std::streamsize GetStreamSize(std::istream& f)
  {
    // http://www.cplusplus.com/reference/iostream/istream/tellg/
    f.seekg(0, std::ios::end);
    std::streamsize size = f.tellg();
    f.seekg(0, std::ios::beg);

    return size;
  }


  void SystemToolbox::ReadFile(std::string& content,
                               const boost::filesystem::path& path,
                               bool log)
  {
    if (!IsRegularFile(path))
    {
      throw OrthancException(ErrorCode_RegularFileExpected, 
                             "Not a regular file or file not found: " + PathToUtf8(path), log);
    }

    try
    {
      boost::filesystem::ifstream f;
      f.open(path, std::ifstream::in | std::ifstream::binary);
      if (!f.good())
      {
        throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path), log);
      }

      std::streamsize size = GetStreamSize(f);
      content.resize(static_cast<size_t>(size));

      if (static_cast<std::streamsize>(content.size()) != size)
      {
        throw OrthancException(ErrorCode_InternalError,
                               "Reading a file that is too large for a 32bit architecture");
      }
    
      if (size != 0)
      {
        f.read(&content[0], size);
      }

      f.close();
    }
    catch (boost::filesystem::filesystem_error&)
    {
      throw OrthancException(ErrorCode_InexistentFile, 
                             "File not found: " + PathToUtf8(path));
    }
    catch (...)  // To catch "std::system_error&" in C++11
    {
      throw OrthancException(ErrorCode_InexistentFile, 
                             "File not found: " + PathToUtf8(path));
    }
  }


  bool SystemToolbox::ReadHeader(std::string& header,
                                 const boost::filesystem::path& path,
                                 size_t headerSize)
  {
    if (!IsRegularFile(path))
    {
      throw OrthancException(ErrorCode_RegularFileExpected, 
                             "Not a regular file or file not found: " + PathToUtf8(path));
    }

    try
    {
      boost::filesystem::ifstream f;
      f.open(path, std::ifstream::in | std::ifstream::binary);
      if (!f.good())
      {
        throw OrthancException(ErrorCode_InexistentFile,
                               "File not found: " + PathToUtf8(path));
      }

      bool full = true;

      {
        std::streamsize fileSize = GetStreamSize(f);
        if (fileSize <= 0)
        {
          headerSize = 0;
          full = false;
        }
        else if (static_cast<size_t>(fileSize) < headerSize)
        {
          headerSize = static_cast<size_t>(fileSize);  // Truncate to the size of the file
          full = false;
        }
      }

      header.resize(headerSize);
      if (headerSize != 0)
      {
        f.read(&header[0], static_cast<std::streamsize>(headerSize));
      }

      f.close();

      return full;
    }
    catch (boost::filesystem::filesystem_error&)
    {
      throw OrthancException(ErrorCode_InexistentFile, 
                             "File not found: " + PathToUtf8(path));
    }
    catch (...)  // To catch "std::system_error&" in C++11
    {
      throw OrthancException(ErrorCode_InexistentFile, 
                             "File not found: " + PathToUtf8(path));
    }
  }

  
  void SystemToolbox::WriteFile(const void *content, 
                                size_t size, 
                                const boost::filesystem::path& path,
                                bool callFsync)
  {
    try
    {
      boost::iostreams::stream<boost::iostreams::file_descriptor_sink> f;

      f.open(path, std::ofstream::out | std::ofstream::binary);
      if (!f.good())
      {
        throw OrthancException(ErrorCode_CannotWriteFile);
      }

      if (size != 0)
      {
        if (size > static_cast<size_t>(std::numeric_limits<std::streamsize>::max()))
        {
          throw OrthancException(ErrorCode_CannotWriteFile, "Size too large to fit in a streamsize");
        }

        f.write(reinterpret_cast<const char*>(content), static_cast<std::streamsize>(size));

        if (!f.good())
        {
          f.close();
          throw OrthancException(ErrorCode_CannotWriteFile);
        }
      }

      if (callFsync)
      {
        // https://stackoverflow.com/a/23826489/881731
        f.flush();

        bool success;

        /**
         * "f->handle()" corresponds to "FILE*" (aka "HANDLE") on
         * Microsoft Windows, and to "int" (file descriptor) on other
         * systems:
         * https://github.com/boostorg/iostreams/blob/develop/include/boost/iostreams/detail/file_handle.hpp
         **/

#if defined(_WIN32)
        // https://docs.microsoft.com/fr-fr/windows/win32/api/fileapi/nf-fileapi-flushfilebuffers
        success = (::FlushFileBuffers(f->handle()) != 0);
#elif (_POSIX_C_SOURCE >= 199309L || _XOPEN_SOURCE >= 500)
        success = (::fdatasync(f->handle()) == 0);
#else
        success = (::fsync(f->handle()) == 0);
#endif

        if (!success)
        {
          throw OrthancException(ErrorCode_CannotWriteFile, "Cannot force flush to disk");
        }
      }

      f.close();
    }
    catch (boost::filesystem::filesystem_error&)
    {
      throw OrthancException(ErrorCode_CannotWriteFile);
    }
    catch (...)  // To catch "std::system_error&" in C++11
    {
      throw OrthancException(ErrorCode_CannotWriteFile);
    }
  }


  void SystemToolbox::WriteFile(const std::string& content,
                                const boost::filesystem::path& path) 
  { 
    WriteFile(content.size() > 0 ? content.c_str() : NULL, 
              content.size(), path, false /* don't automatically call fsync */); 
  }


  void SystemToolbox::RemoveFile(const boost::filesystem::path& path)
  {
    if (boost::filesystem::exists(path))
    {
      if (IsRegularFile(path))
      {
        boost::filesystem::remove(path);
      }
      else
      {
        throw OrthancException(ErrorCode_RegularFileExpected, 
                               "Not a regular file or file not found: " + PathToUtf8(path));
      }
    }
  }


  uint64_t SystemToolbox::GetFileSize(const boost::filesystem::path& path)
  {
    try
    {
      return static_cast<uint64_t>(boost::filesystem::file_size(path));
    }
    catch (boost::filesystem::filesystem_error&)
    {
      throw OrthancException(ErrorCode_InexistentFile,
                             "File not found: " + PathToUtf8(path));
    }
    catch (...)  // To catch "std::system_error&" in C++11
    {
      throw OrthancException(ErrorCode_InexistentFile, 
                             "File not found: " + PathToUtf8(path));
    }
  }


#if ORTHANC_ENABLE_MD5 == 1
  void SystemToolbox::ComputeStreamMD5(std::string& result,
                                       std::istream& inputStream)
  {
    Toolbox::MD5Context context;

    const size_t bufferSize = 1024;
    char buffer[bufferSize];

    while (inputStream.good())
    {
      inputStream.read(buffer, bufferSize);
      std::streamsize bytesRead = inputStream.gcount();

      if (bytesRead > 0)
      {
        context.Append(buffer, bytesRead);
      }
    }

    context.Export(result);
  }
  

  void SystemToolbox::ComputeFileMD5(std::string& result,
                                     const boost::filesystem::path& path)
  {
    boost::filesystem::ifstream fileStream;
    fileStream.open(path, std::ifstream::in | std::ifstream::binary);

    if (!fileStream.good())
    {
      throw OrthancException(ErrorCode_InexistentFile, "File not found: " + PathToUtf8(path));
    }

    ComputeStreamMD5(result, fileStream);
  }


  bool SystemToolbox::CompareFilesMD5(const boost::filesystem::path& path1,
                                      const boost::filesystem::path& path2)
  {
    if (GetFileSize(path1) != GetFileSize(path2))
    {
      return false;
    }
    else
    {
      std::string path1md5, path2md5;
    
      ComputeFileMD5(path1md5, path1);
      ComputeFileMD5(path2md5, path2);

      return path1md5 == path2md5;
    }
  }
#endif


  void SystemToolbox::MakeDirectory(const boost::filesystem::path& path)
  {
    if (boost::filesystem::exists(path))
    {
      if (!boost::filesystem::is_directory(path))
      {
        throw OrthancException(ErrorCode_DirectoryOverFile);
      }
    }
    else
    {
      if (!boost::filesystem::create_directories(path))
      {
        throw OrthancException(ErrorCode_MakeDirectory);
      }
    }
  }


#if defined(_WIN32)
  static std::string GetPathToExecutableInternal()
  {
    // Yes, this is ugly, but there is no simple way to get the 
    // required buffer size, so we use a big constant
    std::vector<char> buffer(32768);
    /*int bytes =*/ GetModuleFileNameA(NULL, &buffer[0], static_cast<DWORD>(buffer.size() - 1));
    return std::string(&buffer[0]);
  }

#elif defined(__linux__) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
  static std::string GetPathToExecutableInternal()
  {
    // NOTE: For FreeBSD, using KERN_PROC_PATHNAME might be a better alternative

    std::vector<char> buffer(PATH_MAX + 1);
    ssize_t bytes = readlink("/proc/self/exe", &buffer[0], buffer.size() - 1);
    if (bytes == 0)
    {
      throw OrthancException(ErrorCode_PathToExecutable);
    }

    return std::string(&buffer[0]);
  }

#elif defined(__APPLE__) && defined(__MACH__)
  static std::string GetPathToExecutableInternal()
  {
    char pathbuf[PATH_MAX + 1];
    unsigned int  bufsize = static_cast<int>(sizeof(pathbuf));

    _NSGetExecutablePath( pathbuf, &bufsize);

    return std::string(pathbuf);
  }

#elif defined(__OpenBSD__)
  static std::string GetPathToExecutableInternal()
  {
    // This is an adapted version of the patch proposed in issue #64
    // without an explicit call to "malloc()" to prevent memory leak
    // https://orthanc.uclouvain.be/bugs/show_bug.cgi?id=64
    // https://stackoverflow.com/q/31494901/881731

    const int mib[4] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };

    size_t len;
    if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1) 
    {
      throw OrthancException(ErrorCode_PathToExecutable);
    }

    std::string tmp;
    tmp.resize(len);

    char** buffer = reinterpret_cast<char**>(&tmp[0]);

    if (sysctl(mib, 4, buffer, &len, NULL, 0) == -1) 
    {
      throw OrthancException(ErrorCode_PathToExecutable);
    }
    else
    {
      return std::string(buffer[0]);
    }
  }

#else
#error Support your platform here
#endif


#ifdef _WIN32
  std::wstring SystemToolbox::Utf8ToWString(const std::string& str)
  {
    if (str.empty())
    {
      return std::wstring();
    }

    int sizeNeeded = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), NULL, 0);

    std::wstring wstr(sizeNeeded, 0);
    MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int) str.size(), &wstr[0], sizeNeeded);

    return wstr;
  }

  std::string SystemToolbox::WStringToUtf8(const std::wstring &wstr)
  {
    if (wstr.empty())
    {
      return std::string();
    }

    int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int) wstr.size(), NULL, 0, NULL, NULL);

    std::string str(sizeNeeded, 0);
    WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), (int) wstr.size(), &str[0], sizeNeeded, NULL, NULL);

    return str;
  }

  std::wstring SystemToolbox::WStringFromCharPtr(const char *str)
  {
    if (str == NULL)
    {
      return std::wstring();
    }

    int sizeNeeded = MultiByteToWideChar(CP_ACP, 0, str, -1, NULL, 0);

    std::wstring wstr(sizeNeeded, 0);
    MultiByteToWideChar(CP_UTF8, 0, str, -1, &wstr[0], sizeNeeded);

    return wstr;

  }
#endif

  boost::filesystem::path SystemToolbox::PathFromUtf8(const std::string& utf8)
  {
#if defined(_WIN32) && !defined(__MINGW32__)  // non-ASCII paths are not supported when building with MinGW
    return boost::filesystem::path(Utf8ToWString(utf8));
#else
    return boost::filesystem::path(utf8); // POSIX: std::string is UTF-8
#endif
  }

  std::string SystemToolbox::PathToUtf8(const boost::filesystem::path& p)
  {
#if defined(_WIN32) && !defined(__MINGW32__)  // non-ASCII paths are not supported when building with MinGW
    return WStringToUtf8(p.wstring());
#else
    return p.string(); // POSIX: already UTF-8
#endif
  }


  boost::filesystem::path SystemToolbox::GetPathToExecutable()
  {
    boost::filesystem::path p(PathFromUtf8(GetPathToExecutableInternal()));
    return boost::filesystem::absolute(p);
  }


  boost::filesystem::path SystemToolbox::GetDirectoryOfExecutable()
  {
    boost::filesystem::path p(PathFromUtf8(GetPathToExecutableInternal()));
    return boost::filesystem::absolute(p.parent_path());
  }


  void SystemToolbox::ExecuteSystemCommand(const std::string& command,
                                           const std::vector<std::string>& arguments)
  {
    // Convert the arguments as a C array
    std::vector<char*>  args(arguments.size() + 2);

    args.front() = const_cast<char*>(command.c_str());

    for (size_t i = 0; i < arguments.size(); i++)
    {
      args[i + 1] = const_cast<char*>(arguments[i].c_str());
    }

    args.back() = NULL;

    int status;

#if defined(_WIN32)
    // http://msdn.microsoft.com/en-us/library/275khfab.aspx
    status = static_cast<int>(_spawnvp(_P_OVERLAY, command.c_str(), &args[0]));

#else
    int pid = fork();

    if (pid == -1)
    {
      // Error in fork()
      throw OrthancException(ErrorCode_SystemCommand, "Cannot fork a child process");
    }
    else if (pid == 0)
    {
      // Execute the system command in the child process
      execvp(command.c_str(), &args[0]);

      // We should never get here
      _exit(1);
    }
    else
    {
      // Wait for the system command to exit
      waitpid(pid, &status, 0);
    }
#endif

    if (status != 0)
    {
      throw OrthancException(ErrorCode_SystemCommand,
                             "System command failed with status code " +
                             boost::lexical_cast<std::string>(status));
    }
  }


  int SystemToolbox::GetProcessId()
  {
#if defined(_WIN32)
    return static_cast<int>(_getpid());
#else
    return static_cast<int>(getpid());
#endif
  }


  bool SystemToolbox::IsRegularFile(const boost::filesystem::path& path)
  {
    try
    {
      if (boost::filesystem::exists(path))
      {
        boost::filesystem::file_status status = boost::filesystem::status(path);
        return (status.type() == boost::filesystem::regular_file ||
                status.type() == boost::filesystem::reparse_file);   // Fix BitBucket issue #11
      }
    }
    catch (boost::filesystem::filesystem_error&)  // NOLINT(bugprone-empty-catch)
    {
    }

    return false;
  }


  FILE* SystemToolbox::OpenFile(const boost::filesystem::path& path,
                                FileMode mode)
  {
#if defined(_WIN32)
    const wchar_t *m;
    switch (mode)
    {
      case FileMode_ReadBinary:
        m = L"rb";
        break;

      case FileMode_WriteBinary:
        m = L"wb";
        break;

      default:
        throw OrthancException(ErrorCode_ParameterOutOfRange);
    }

    return _wfopen(path.wstring().c_str(), m);
#else
    const char *m;
    switch (mode)
    {
      case FileMode_ReadBinary:
        m = "rb";
        break;

      case FileMode_WriteBinary:
        m = "wb";
        break;

      default:
        throw OrthancException(ErrorCode_ParameterOutOfRange);
    }

    return fopen(path.c_str(), m);
#endif
  }


  static boost::posix_time::ptime GetNow(bool utc)
  {
    if (utc)
    {
      return boost::posix_time::second_clock::universal_time();
    }
    else
    {
      return boost::posix_time::second_clock::local_time();
    }
  }


  std::string SystemToolbox::GetNowIsoString(bool utc)
  {
    return boost::posix_time::to_iso_string(GetNow(utc));
  }

  
  void SystemToolbox::GetNowDicom(std::string& date,
                                  std::string& time,
                                  bool utc)
  {
    boost::posix_time::ptime now = GetNow(utc);
    tm tm = boost::posix_time::to_tm(now);

    char s[32];
    sprintf(s, "%04d%02d%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
    date.assign(s);

    // TODO milliseconds
    sprintf(s, "%02d%02d%02d.%06d", tm.tm_hour, tm.tm_min, tm.tm_sec, 0);
    time.assign(s);
  }

  
  unsigned int SystemToolbox::GetHardwareConcurrency()
  {
    // Get the number of available hardware threads (e.g. number of
    // CPUs or cores or hyperthreading units)
    unsigned int threads = boost::thread::hardware_concurrency();
    
    if (threads == 0)
    {
      return 1;
    }
    else
    {
      return threads;
    }
  }

  bool SystemToolbox::IsContentCompressible(MimeType mime)
  {
    switch (mime)
    {
      case MimeType_Css:
      case MimeType_Html:
      case MimeType_JavaScript:
      case MimeType_Json:
      case MimeType_Pam:
      case MimeType_Pdf:
      case MimeType_PlainText:
      case MimeType_WebAssembly:
      case MimeType_Xml:
      case MimeType_PrometheusText:
      case MimeType_DicomWebJson:
      case MimeType_DicomWebXml:
        return true;
      default: // for all other (JPEG, DICOM, binary, ...)
        return false;
    }
  }
  
  bool SystemToolbox::IsContentCompressible(const std::string& contentType)
  {
    if (contentType.empty())
    {
      return false;
    }

    if (contentType.find(MIME_JSON) != std::string::npos ||
        contentType.find(MIME_XML) != std::string::npos ||
        contentType.find(MIME_DICOM_WEB_JSON) != std::string::npos ||
        contentType.find(MIME_DICOM_WEB_XML) != std::string::npos ||
        contentType.find(MIME_PDF) != std::string::npos ||
        contentType.find(MIME_CSS) != std::string::npos ||
        contentType.find(MIME_HTML) != std::string::npos ||
        contentType.find(MIME_JAVASCRIPT) != std::string::npos ||
        contentType.find(MIME_PLAIN_TEXT) != std::string::npos ||
        contentType.find(MIME_WEB_ASSEMBLY) != std::string::npos ||
        contentType.find(MIME_XML_2) != std::string::npos)
    {
      return true;
    }

    return false;
  }


  MimeType SystemToolbox::AutodetectMimeType(const boost::filesystem::path& path)
  {
    std::string extension = path.extension().string();
    Toolbox::ToLowerCase(extension);

    // http://en.wikipedia.org/wiki/Mime_types
    // Text types
    if (extension == ".txt")
    {
      return MimeType_PlainText;
    }
    else if (extension == ".html")
    {
      return MimeType_Html;
    }
    else if (extension == ".xml")
    {
      return MimeType_Xml;
    }
    else if (extension == ".css")
    {
      return MimeType_Css;
    }

    // Application types
    else if (extension == ".js")
    {
      return MimeType_JavaScript;
    }
    else if (extension == ".json" ||
             extension == ".nmf"  /* manifest */)
    {
      return MimeType_Json;
    }
    else if (extension == ".pdf")
    {
      return MimeType_Pdf;
    }
    else if (extension == ".wasm")
    {
      return MimeType_WebAssembly;
    }
    else if (extension == ".nexe")
    {
      return MimeType_NaCl;
    }
    else if (extension == ".pexe")
    {
      return MimeType_PNaCl;
    }

    // Images types
    else if (extension == ".dcm")
    {
      return MimeType_Dicom;
    }
    else if (extension == ".jpg" ||
             extension == ".jpeg")
    {
      return MimeType_Jpeg;
    }
    else if (extension == ".gif")
    {
      return MimeType_Gif;
    }
    else if (extension == ".png")
    {
      return MimeType_Png;
    }
    else if (extension == ".pam")
    {
      return MimeType_Pam;
    }
    else if (extension == ".svg")
    {
      return MimeType_Svg;
    }

    // Various types
    else if (extension == ".woff")
    {
      return MimeType_Woff;
    }
    else if (extension == ".woff2")
    {
      return MimeType_Woff2;
    }
    else if (extension == ".ico")
    {
      return MimeType_Ico;
    }
    else if (extension == ".gz")
    {
      return MimeType_Gzip;
    }
    else if (extension == ".zip")
    {
      return MimeType_Zip;
    }
    else if (extension == ".mtl")
    {
      return MimeType_Mtl;
    }
    else if (extension == ".obj")
    {
      return MimeType_Obj;
    }
    else if (extension == ".stl")
    {
      return MimeType_Stl;
    }

    // Default type
    else
    {
      LOG(INFO) << "Unknown MIME type for extension \"" << extension << "\"";
      return MimeType_Binary;
    }
  }


  void SystemToolbox::GetEnvironmentVariables(std::map<std::string, std::string>& env)
  {
    env.clear();
    
    for (char **p = environ; *p != NULL; p++)
    {
      std::string v(*p);
      size_t pos = v.find('=');

      if (pos != std::string::npos)
      {
        std::string key = v.substr(0, pos);
        std::string value = v.substr(pos + 1);
        env[key] = value;
      } 
    }
  }


  boost::filesystem::path SystemToolbox::InterpretRelativePath(const boost::filesystem::path& baseDirectory,
                                                               const boost::filesystem::path& relativePath)
  {
    /**
       The following lines should be equivalent to this one: 

       return (baseDirectory / relativePath);

       However, for some unknown reason, some versions of Boost do not
       make the proper path resolution when "baseDirectory" is an
       absolute path. So, a hack is used below.
    **/

    if (relativePath.is_absolute())
    {
      return relativePath;
    }
    else
    {
      return baseDirectory / relativePath;
    }
  }


  void SystemToolbox::ReadFileRange(std::string& content,                              
                                    const boost::filesystem::path& path,
                                    uint64_t start,  // Inclusive
                                    uint64_t end,    // Exclusive
                                    bool throwIfOverflow)
  {
    if (start > end)
    {
      throw OrthancException(ErrorCode_ParameterOutOfRange);
    }
    
    if (!IsRegularFile(path))
    {
      throw OrthancException(ErrorCode_RegularFileExpected, 
                             "Not a regular file or file not found: " + PathToUtf8(path));
    }

    boost::filesystem::ifstream f;
    f.open(path, std::ifstream::in | std::ifstream::binary);
    if (!f.good())
    {
      throw OrthancException(ErrorCode_InexistentFile,
                             "File not found: " + PathToUtf8(path));
    }

    uint64_t fileSize = static_cast<uint64_t>(GetStreamSize(f));
    if (end > fileSize)
    {
      if (throwIfOverflow)
      {
        throw OrthancException(ErrorCode_ParameterOutOfRange,
                               "Reading beyond the end of a file");
      }
      else
      {
        end = fileSize;
      }
    }

    if (start <= end)
    {
      content.resize(static_cast<size_t>(end - start));

      if (static_cast<uint64_t>(content.size()) != (end - start))
      {
        throw OrthancException(ErrorCode_InternalError,
                               "Reading a file that is too large for a 32bit architecture");
      }

      if (!content.empty())
      {
        f.seekg(static_cast<off_t>(start), std::ios::beg);
        f.read(&content[0], static_cast<std::streamsize>(content.size()));
      }
    }
    else
    {
      content.clear();
    }

    f.close();
  }


#if defined(_WIN32)
  void SystemToolbox::GetMacAddresses(std::set<std::string>& target)
  {
    target.clear();
    
    // 15Ko is the recommanded size to start with
    std::vector<char> buffer(15 * 1024);

    for (unsigned int iteration = 0; iteration < 3; iteration++)
    {
      ULONG outBufLen = static_cast<ULONG>(buffer.size());
      DWORD result = GetAdaptersAddresses
        (AF_UNSPEC, 0, NULL, 
         reinterpret_cast<IP_ADAPTER_ADDRESSES*>(&buffer[0]), &outBufLen);

      if (result == NO_ERROR)
      {
        IP_ADAPTER_ADDRESSES* current =
          reinterpret_cast<IP_ADAPTER_ADDRESSES*>(&buffer[0]); 

        while (current != NULL)
        {
          if (current->PhysicalAddressLength == 6 &&
              (current->PhysicalAddress[0] != 0 ||
               current->PhysicalAddress[1] != 0 ||
               current->PhysicalAddress[2] != 0 ||
               current->PhysicalAddress[3] != 0 ||
               current->PhysicalAddress[4] != 0 ||
               current->PhysicalAddress[5] != 0))
          {
            char tmp[32];
            sprintf(tmp, "%02x:%02x:%02x:%02x:%02x:%02x",
                    (unsigned char) current->PhysicalAddress[0],
                    (unsigned char) current->PhysicalAddress[1],
                    (unsigned char) current->PhysicalAddress[2],
                    (unsigned char) current->PhysicalAddress[3],
                    (unsigned char) current->PhysicalAddress[4],
                    (unsigned char) current->PhysicalAddress[5]);
            target.insert(tmp);
          }

          current = current->Next;
        }
        
        return;
      }     
      else if (result != ERROR_BUFFER_OVERFLOW || 
               iteration >= 3 ||
               outBufLen == 0)
      {
        return;
      }
      else
      {
        buffer.resize(outBufLen);
        iteration++;
      }
    }
  }

#else
  namespace
  {
    class SocketRaii : public boost::noncopyable
    {
    private:
      int socket_;

    public:
      SocketRaii()
      {
        socket_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
      }

      ~SocketRaii()
      {
        if (socket_ != -1)
        {
          close(socket_);
        }
      }

      int GetDescriptor() const
      {
        return socket_;
      }
    };


    class NetworkInterfaces : public boost::noncopyable
    {
    private:
      struct if_nameindex* list_;
      struct if_nameindex* current_;

    public:
      NetworkInterfaces()
      {
        list_ = if_nameindex();
        current_ = list_;
      }

      ~NetworkInterfaces()
      {
        if (list_ != NULL)
        {
          if_freenameindex(list_);
        }
      }

      bool IsDone() const
      {
        return (current_ == NULL ||
                (current_->if_index == 0 &&
                 current_->if_name == NULL));
      }

      const char* GetCurrentName() const
      {
        assert(!IsDone());
        return current_->if_name;
      }

      unsigned int GetCurrentIndex() const
      {
        assert(!IsDone());
        return current_->if_index;
      }

      void Next()
      {
        assert(!IsDone());
        current_++;
      }
    };
  }


  void SystemToolbox::GetMacAddresses(std::set<std::string>& target)
  {
    target.clear();

    SocketRaii socket;
    
    if (socket.GetDescriptor() != 1)
    {
      NetworkInterfaces interfaces;

      while (!interfaces.IsDone())
      {
#if (defined(__APPLE__) && defined(__MACH__)) || defined(__FreeBSD_kernel__) || defined(__FreeBSD__)
        int mib[6];
        mib[0] = CTL_NET;
        mib[1] = AF_ROUTE;
        mib[2] = 0;
        mib[3] = AF_LINK;
        mib[4] = NET_RT_IFLIST;
        mib[5] = interfaces.GetCurrentIndex();

        size_t len;
        if (sysctl(mib, 6, NULL, &len, NULL, 0) == 0 &&
            len > 0)
        {
          std::string tmp;
          tmp.resize(len);
          if (sysctl(mib, 6, &tmp[0], &len, NULL, 0) == 0)
          {
            struct if_msghdr* ifm = reinterpret_cast<struct if_msghdr*>(&tmp[0]);
            struct sockaddr_dl* sdl = reinterpret_cast<struct sockaddr_dl*>(ifm + 1);

            if (sdl->sdl_type == IFT_ETHER)  // Only consider Ethernet interfaces
            {
              const unsigned char* mac = reinterpret_cast<const unsigned char*>(LLADDR(sdl));
              char tmp[32];
              sprintf(tmp, "%02x:%02x:%02x:%02x:%02x:%02x",
                      mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
              target.insert(tmp);
            }
          }
        }

#else
        struct ifreq ifr;
        if (strlen(interfaces.GetCurrentName()) > (IF_NAMESIZE - 1))
        {
          throw OrthancException(ErrorCode_InternalError, std::string("Network interface name too long: '") + interfaces.GetCurrentName() + "'");
        }

        strcpy(ifr.ifr_name, interfaces.GetCurrentName());  // NOLINT(clang-analyzer-security.insecureAPI.strcpy)
          
        if (ioctl(socket.GetDescriptor(), SIOCGIFFLAGS, &ifr) == 0 &&
            !(ifr.ifr_flags & IFF_LOOPBACK) && // ignore loopback interface
            ioctl(socket.GetDescriptor(), SIOCGIFHWADDR, &ifr) == 0)
        {
          const unsigned char* mac = reinterpret_cast<const unsigned char*>(ifr.ifr_hwaddr.sa_data);
            
          char tmp[32];
          sprintf(tmp, "%02x:%02x:%02x:%02x:%02x:%02x",
                  mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
          target.insert(tmp);
        }
#endif
        
        interfaces.Next();
      }
    }
  }

#endif
}
