/*
 *
 *  Copyright (C) 2001-2024, OFFIS e.V.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  This software and supporting documentation were developed by
 *
 *    OFFIS e.V.
 *    R&D Division Health
 *    Escherweg 2
 *    D-26121 Oldenburg, Germany
 *
 *
 *  As an exception of the above notice, the code for OFStandard::strlcpy
 *  and OFStandard::strlcat in this file have been derived from the BSD
 *  implementation which carries the following copyright notice:
 *
 *  Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 *  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 *  THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 *  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 *  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 *  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 *  The code for OFStandard::atof that is used when DCMTK is compiled
 *  with the macro ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION has been derived
 *  from an implementation that carries the following copyright notice:
 *
 *  Copyright 1988 Regents of the University of California
 *  Permission to use, copy, modify, and distribute this software and
 *  its documentation for any purpose and without fee is hereby granted,
 *  provided that the above copyright notice appear in all copies.  The
 *  University of California makes no representations about the
 *  suitability of this software for any purpose.  It is provided "as
 *  is" without express or implied warranty.
 *
 *
 *  The code for OFStandard::ftoa that is used when DCMTK is compiled
 *  with the macro ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION has been derived
 *  from an implementation that carries the following copyright notice:
 *
 *  Copyright (c) 1988 Regents of the University of California.
 *  All rights reserved.  See COPYRIGHT file for details.
 *
 *  Redistribution and use in source and binary forms are permitted
 *  provided that the above copyright notice and this paragraph are
 *  duplicated in all such forms and that any documentation,
 *  advertising materials, and other materials related to such
 *  distribution and use acknowledge that the software was developed
 *  by the University of California, Berkeley.  The name of the
 *  University may not be used to endorse or promote products derived
 *  from this software without specific prior written permission.
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 *  WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 *
 *
 *  The "Base64" encoder/decoder has been derived from an implementation
 *  with the following copyright notice:
 *
 *  Copyright (c) 1999, Bob Withers - bwit@pobox.com
 *
 *  This code may be freely used for any purpose, either personal or
 *  commercial, provided the authors copyright notice remains intact.
 *
 *
 *  Module: ofstd
 *
 *  Author: Joerg Riesmeier, Marco Eichelberg
 *
 *  Purpose: Class for various helper functions
 *
 */


#include "dcmtk/config/osconfig.h"    /* make sure OS specific configuration is included first */

#ifdef __SUNPRO_CC
BEGIN_EXTERN_C
// SunPro declares vsnprintf() only in <stdio.h>, not in <cstdio>.
#include <stdio.h>
END_EXTERN_C
#else
#include <cstdio>
#endif

#include "dcmtk/ofstd/ofstd.h"
#include "dcmtk/ofstd/ofcond.h"
#include "dcmtk/ofstd/offile.h"
#include "dcmtk/ofstd/ofstream.h"
#include "dcmtk/ofstd/oftuple.h"
#include "dcmtk/ofstd/ofmath.h"
#include "dcmtk/ofstd/ofsockad.h"
#include "dcmtk/ofstd/ofvector.h"
#include "dcmtk/ofstd/ofdiag.h"
#include "dcmtk/ofstd/oftimer.h"


#include <cmath>
#include <clocale>
#include <cstring>       /* for memset() */
#include <sstream>

BEGIN_EXTERN_C
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>    /* for stat() */
#endif
#ifdef HAVE_IO_H
#include <io.h>          /* for access() on Win32 */
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>   /* for opendir() and closedir() */
#endif
#ifdef HAVE_DIRENT_H
#include <dirent.h>      /* for opendir() and closedir() */
#else
#define dirent direct
#ifdef HAVE_SYS_DIR_H
#include <sys/dir.h>
#endif
#endif
#ifdef HAVE_FNMATCH_H
#include <fnmatch.h>     /* for fnmatch() */
#endif
#ifdef HAVE_IEEEFP_H
#include <ieeefp.h>      /* for finite() on Solaris 2.5.1 */
#endif
#ifdef HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
END_EXTERN_C

#ifdef HAVE_WINDOWS_H
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <windows.h>     /* for GetFileAttributes() */
#include <direct.h>      /* for _mkdir() */
#include <lm.h>          /* for NetWkstaUserGetInfo */
#include <ws2tcpip.h>    /* for struct sockaddr_in6 */
#ifndef R_OK /* Windows defines access() but not the constants */
#define W_OK 02 /* Write permission */
#define R_OK 04 /* Read permission */
#define F_OK 00 /* Existence only */
#endif /* !R_OK */

#elif defined(HAVE_WINSOCK_H)
#include <winsock.h>  /* include winsock.h directly i.e. on MacOS */
#endif /* HAVE_WINDOWS_H */

#ifdef _WIN32
#include <process.h>     /* needed for declaration of getpid() */
#endif

#include "dcmtk/ofstd/ofgrp.h"
#include "dcmtk/ofstd/ofpwd.h"
#include "dcmtk/ofstd/ofoption.h"

// check mutually exclusive feature macros for the implementations of OFStandard::atof() and OFStandard::ftoa()

#if defined (ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION) && defined (ENABLE_CSTDIO_BASED_ATOF_IMPLEMENTATION)
#error The macros ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION and ENABLE_CSTDIO_BASED_ATOF_IMPLEMENTATION must not both be defined
#endif

#if defined (ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION) && defined (ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION)
#error The macros ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION and ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION must not both be defined
#endif

#if defined (ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION) && defined (ENABLE_CSTDIO_BASED_ATOF_IMPLEMENTATION)
#error The macros ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION and ENABLE_CSTDIO_BASED_ATOF_IMPLEMENTATION must not both be defined
#endif

#if defined (ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION) && defined (ENABLE_CSTDIO_BASED_FTOA_IMPLEMENTATION)
#error The macros ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION and ENABLE_CSTDIO_BASED_FTOA_IMPLEMENTATION must not both be defined
#endif

#if defined (ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION) && defined (ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION)
#error The macros ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION and ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION must not both be defined
#endif

#if defined (ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION) && defined (ENABLE_CSTDIO_BASED_FTOA_IMPLEMENTATION)
#error The macros ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION and ENABLE_CSTDIO_BASED_FTOA_IMPLEMENTATION must not both be defined
#endif

// define defaults for OFStandard::atof() and OFStandard::ftoa()
#if !defined(ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION) && !defined(ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION) && !defined(ENABLE_CSTDIO_BASED_ATOF_IMPLEMENTATION)
#ifdef _WIN32
// on Windows, the iostream-based implementation of atof is extremely slow,
// and we do have a locale independent version of sscanf. Use this version.
#define ENABLE_CSTDIO_BASED_ATOF_IMPLEMENTATION
#else
// on other platforms, we assume that the iobased-implementation, being the
// cleanest one, is appropriate. This is known to be the case for gcc and clang with glibc.
#define ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION
#endif
#endif

#if !defined(ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION) && !defined(ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION) && !defined(ENABLE_CSTDIO_BASED_FTOA_IMPLEMENTATION)
#ifdef _WIN32
// on Windows, the iostream-based implementation of atof is extremely slow,
// and we do have a locale independent version of sscanf. Use this version.
#define ENABLE_CSTDIO_BASED_ATOF_IMPLEMENTATION
#else
// on other platforms, we assume that the iobased-implementation, being the
// cleanest one, is appropriate. This is known to be the case for gcc and clang with glibc.
#define ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION
#endif
#endif

// maximum number of repetitions for EAI_AGAIN
#define DCMTK_MAX_EAI_AGAIN_REPETITIONS 5

// --- ftoa() processing flags ---

const unsigned int OFStandard::ftoa_format_e  = 0x01;
const unsigned int OFStandard::ftoa_format_f  = 0x02;
const unsigned int OFStandard::ftoa_uppercase = 0x04;
const unsigned int OFStandard::ftoa_alternate = 0x08;
const unsigned int OFStandard::ftoa_leftadj   = 0x10;
const unsigned int OFStandard::ftoa_zeropad   = 0x20;

// --- string functions ---

#ifndef HAVE_STRLCPY
/*
 * Copy src to string dst of size siz.  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz == 0).
 * Returns strlen(src); if retval >= siz, truncation occurred.
 */
size_t OFStandard::my_strlcpy(char *dst, const char *src, size_t siz)
{
  char *d = dst;
  const char *s = src;
  size_t n = siz;

  /* Copy as many bytes as will fit */
  if (n != 0 && --n != 0)
  {
    do
    {
      if ((*d++ = *s++) == 0)
         break;
    } while (--n != 0);
  }

  /* Not enough room in dst, add NUL and traverse rest of src */
  if (n == 0)
  {
     if (siz != 0)
        *d = '\0'; /* NUL-terminate dst */
     while (*s++) /* do nothing */ ;
  }

  return(s - src - 1);    /* count does not include NUL */
}
#endif /* HAVE_STRLCPY */


#ifndef HAVE_STRLCAT
/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
size_t OFStandard::my_strlcat(char *dst, const char *src, size_t siz)
{
  char *d = dst;
  const char *s = src;
  size_t n = siz;
  size_t dlen;

  /* Find the end of dst and adjust bytes left but don't go past end */
  while (n-- != 0 && *d != '\0') d++;
  dlen = d - dst;
  n = siz - dlen;

  if (n == 0) return(dlen + strlen(s));
  while (*s != '\0')
  {
    if (n != 1)
    {
      *d++ = *s;
      n--;
    }
    s++;
  }
  *d = '\0';

  return(dlen + (s - src));       /* count does not include NUL */
}
#endif /* HAVE_STRLCAT */

int OFStandard::snprintf(char *str, size_t size, const char *format, ...)
{
    // we emulate snprintf() via vsnprintf().
    int count;
    va_list ap;
    va_start(ap, format);
    count = OFStandard::vsnprintf(str, size, format, ap);
    va_end(ap);
    return count;
}

int OFStandard::vsnprintf(char *str, size_t size, const char *format, va_list ap)
{
#ifdef _MSC_VER
#if _MSC_VER < 1900
    // Visual Studio versions 2005 to 2013 do not have a C99 compliant
    // vsnprintf(), but they have _snprintf(), which can be used to emulate it.
    int count = -1;

    if (size != 0)
        count = _vsnprintf_s(str, size, _TRUNCATE, format, ap);
    if (count == -1)
        count = _vscprintf(format, ap);

    return count;
#else /* _MSC_VER < 1900 */
    // Visual Studio 2015 and newer has a C99 compliant vsnprintf().
    return ::vsnprintf(str, size, format, ap);
#endif /* _MSC_VER < 1900 */
#else /* _MSC_VER */
#ifdef HAVE_VSNPRINTF
    return ::vsnprintf(str, size, format, ap);
#else /* HAVE_VSNPRINTF */
#ifdef DCMTK_ENABLE_UNSAFE_VSNPRINTF
    // This implementation internally uses vsprintf (which is inherently unsafe).
    // It allocates a buffer that is 1 kByte larger than "size",
    // formats the string into that buffer, and then uses strlcpy() to
    // copy the formatted string into the output buffer, truncating if necessary.
    // This will work in most cases, since few snprintf calls should overrun
    // the provided buffer by more than 1K, but it can be easily abused by
    // a malicious attacker to cause a buffer overrun.
    //
    // Therefore, this implementation should only be used as a "last resort"
    // and we strongly advise against using it in production code.
    // The macro "DCMTK_ENABLE_UNSAFE_VSNPRINTF" must explicitly be defined
    // by the used to enable this implementation.
    int count = -1;
    if (size != 0)
    {
      char *buf = new char[size+1024];
      count = ::vsprintf(buf, format, ap);
      OFStandard::strlcpy(str, buf, size);
      delete[] buf;
    }
    return count;
#warning Using unsafe implementation of vsnprintf(3)
#else /* DCMTK_ENABLE_UNSAFE_VSNPRINTF */
    return -1;
#error vsnprintf(3) not found. Use different compiler or compile with DCMTK_ENABLE_UNSAFE_VSNPRINTF (unsafe!)
#endif /* DCMTK_ENABLE_UNSAFE_VSNPRINTF */
#endif /* HAVE_VSNPRINTF */
#endif /* _MSC_VER */
}

#ifdef HAVE_PROTOTYPE_STRERROR_R
/*
 * convert a given error code to a string. This function wraps the various
 * approaches found on different systems. Internally, the standard function
 * strerror() or strerror_r() is used.
 */
const char *OFStandard::strerror(const int errnum,
                                 char *buf,
                                 const size_t buflen)
{
    const char *result = "";
    if ((buf != NULL) && (buflen > 0))
    {
        // be paranoid and initialize the buffer to empty string
        buf[0] = 0;
        // two incompatible interfaces for strerror_r with different return types exist
#ifdef HAVE_CHARP_STRERROR_R
        // we're using the GNU specific version that returns the result, which may
        // or may not be a pointer to buf
        result = strerror_r(errnum, buf, buflen);
#else
        // we're using the X/OPEN version that always stores the result in buf
        (void) strerror_r(errnum, buf, buflen);
        result = buf;
#endif
    }
    return result;
}
#else
const char *OFStandard::strerror(const int errnum,
                                 char * /*buf*/,
                                 const size_t /*buflen*/)
{
    // we only have strerror() which is thread unsafe on Posix platforms, but thread safe on Windows
    return :: strerror(errnum);
}
#endif


OFString &OFStandard::toUpper(OFString &result,
                              const OFString &value)
{
    result = value;
    return OFStandard::toUpper(result);
}


OFString &OFStandard::toUpper(OFString &value)
{
    const size_t length = value.length();
    unsigned char c;
    for (size_t i = 0; i < length; i++)
    {
        c = value.at(i);
        value.at(i) = OFstatic_cast(char, toupper(c));
    }
    return value;
}


OFString &OFStandard::toLower(OFString &result,
                              const OFString &value)
{
    result = value;
    return OFStandard::toLower(result);
}


OFString &OFStandard::toLower(OFString &value)
{
    const size_t length = value.length();
    unsigned char c;
    for (size_t i = 0; i < length; i++)
    {
        c = value.at(i);
        value.at(i) = OFstatic_cast(char, tolower(c));
    }
    return value;
}


// --- file system functions ---

OFBool OFStandard::pathExists(const OFFilename &pathName)
{
    OFBool result = OFFalse;
    /* check for valid path name (avoid NULL or empty string) */
    if (!pathName.isEmpty())
    {
#ifdef HAVE_ACCESS
        /* check existence with "access()" */
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (pathName.usesWideChars())
            result = (_waccess(pathName.getWideCharPointer(), F_OK) == 0);
        else
#endif
            result = (access(pathName.getCharPointer(), F_OK) == 0);
#else /* HAVE_ACCESS */
#ifdef HAVE_WINDOWS_H
        /* get file attributes */
        DWORD fileAttr;
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (pathName.usesWideChars())
            fileAttr = GetFileAttributesW(pathName.getWideCharPointer());
        else
#endif
            fileAttr = GetFileAttributes(pathName.getCharPointer());
        result = (fileAttr != 0xffffffff);
#else /* HAVE_WINDOWS_H */
#ifdef HAVE_SYS_STAT_H
        /* check existence with "stat()" */
        struct stat stat_buf;
        result = (stat(pathName.getCharPointer(), &stat_buf) == 0);
#else
        /* try to open the given "file" (or directory) in read-only mode */
        OFFile file;
        result = file.fopen(pathName, "r");
        file.fclose();
#endif /* HAVE_SYS_STAT_H */
#endif /* HAVE_WINDOWS_H */
#endif /* HAVE_ACCESS */
    }
    return result;
}


OFBool OFStandard::fileExists(const OFFilename &fileName)
{
    OFBool result = OFFalse;
    /* check for valid file name (avoid NULL or empty string) */
    if (!fileName.isEmpty())
    {
#ifdef HAVE_WINDOWS_H
        /* get file attributes */
        DWORD fileAttr;
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (fileName.usesWideChars())
            fileAttr = GetFileAttributesW(fileName.getWideCharPointer());
        else
#endif
            fileAttr = GetFileAttributesA(fileName.getCharPointer());
        if (fileAttr != 0xffffffff)
        {
            /* check file type (not a directory?) */
            result = ((fileAttr & FILE_ATTRIBUTE_DIRECTORY) == 0);
        }
#else /* HAVE_WINDOWS_H */
        /* check whether path exists (but does not point to a directory) */
        result = pathExists(fileName.getCharPointer()) && !dirExists(fileName.getCharPointer());
#endif /* HAVE_WINDOWS_H */
    }
    return result;
}


OFBool OFStandard::dirExists(const OFFilename &dirName)
{
    OFBool result = OFFalse;
    /* check for valid directory name (avoid NULL or empty string) */
    if (!dirName.isEmpty())
    {
#ifdef HAVE_WINDOWS_H
        /* get file attributes of the directory */
        DWORD fileAttr;
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (dirName.usesWideChars())
            fileAttr = GetFileAttributesW(dirName.getWideCharPointer());
        else
#endif
            fileAttr = GetFileAttributesA(dirName.getCharPointer());
        if (fileAttr != 0xffffffff)
        {
            /* check file type (is a directory?) */
            result = ((fileAttr & FILE_ATTRIBUTE_DIRECTORY) != 0);
        }
#else /* HAVE_WINDOWS_H */
        /* try to open the given directory */
        DIR *dirPtr = opendir(dirName.getCharPointer());
        if (dirPtr != NULL)
        {
            result = OFTrue;
            closedir(dirPtr);
        }
#endif /* HAVE_WINDOWS_H */
    }
    return result;
}


OFBool OFStandard::isReadable(const OFFilename &pathName)
{
    OFBool result = OFFalse;
    /* check for valid path name (avoid NULL or empty string) */
    if (!pathName.isEmpty())
    {
#ifdef HAVE_ACCESS
        /* check whether the path is readable using "access()" */
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (pathName.usesWideChars())
            result = (_waccess(pathName.getWideCharPointer(), R_OK) == 0);
        else
#endif
            result = (access(pathName.getCharPointer(), R_OK) == 0);
#else /* HAVE_ACCESS */
        /* try to open the given "file" (or directory) in read-only mode */
        OFFile file;
        result = file.fopen(pathName, "r");
#endif /* HAVE_ACCESS */
}
    return result;
}


OFBool OFStandard::isWriteable(const OFFilename &pathName)
{
    OFBool result = OFFalse;
    /* check for valid path name (avoid NULL or empty string) */
    if (!pathName.isEmpty())
    {
#ifdef HAVE_ACCESS
        /* check whether the path is writable using "access()" */
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (pathName.usesWideChars())
            result = (_waccess(pathName.getWideCharPointer(), W_OK) == 0);
        else
#endif
            result = (access(pathName.getCharPointer(), W_OK) == 0);
#else /* HAVE_ACCESS */
        /* try to open the given "file" (or directory) in write mode */
        OFFile file;
        result = file.fopen(pathName, "w");
#endif /* HAVE_ACCESS */
    }
    return result;
}


OFString &OFStandard::getDirNameFromPath(OFString &result,
                                         const OFString &pathName,
                                         const OFBool assumeDirName)
{
    OFFilename resultFilename;
    /* call the real function */
    getDirNameFromPath(resultFilename, pathName, assumeDirName);
    /* convert result into a string object */
    result = OFSTRING_GUARD(resultFilename.getCharPointer());
    return result;
}


OFFilename &OFStandard::getDirNameFromPath(OFFilename &result,
                                           const OFFilename &pathName,
                                           const OFBool assumeDirName)
{
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
    /* check whether to use the wide-char version of the API function */
    if (pathName.usesWideChars())
    {
        const wchar_t *strValue = pathName.getWideCharPointer();
        const wchar_t *strPos = wcsrchr(strValue, L'\\' /* WIDE_PATH_SEPARATOR */);

        // Windows accepts both backslash and forward slash as path separators.
        const wchar_t *strPos2 = wcsrchr(strValue, L'/');

        // if strPos2 points to a character closer to the end of the string, use this instead of strPos
        if ((strPos == NULL) || ((strPos2 != NULL) && (strPos2 > strPos))) strPos = strPos2;

        /* path separator found? */
        if (strPos == NULL)
        {
            if (assumeDirName)
                result = pathName;
            else
                result.clear();
        } else {
            wchar_t *tmpString = new wchar_t[strPos - strValue + 1];
            wcsncpy(tmpString, strValue, strPos - strValue);
            tmpString[strPos - strValue] = L'\0';
            result.set(tmpString, OFTrue /*convert*/);
            delete[] tmpString;
        }
    } else
#endif
    /* otherwise, use the conventional 8-bit characters version */
    {
        const char *strValue = pathName.getCharPointer();
        const char *strPos = strrchr(strValue, PATH_SEPARATOR);

#ifdef _WIN32
        // Windows accepts both backslash and forward slash as path separators.
        const char *strPos2 = strrchr(strValue, '/');

        // if strPos2 points to a character closer to the end of the string, use this instead of strPos
        if ((strPos == NULL) || ((strPos2 != NULL) && (strPos2 > strPos))) strPos = strPos2;
#endif

        /* path separator found? */
        if (strPos == NULL)
        {
            if (assumeDirName)
                result = pathName;
            else
                result.clear();
        } else
            result.set(OFString(strValue, strPos - strValue));
    }
    return result;
}


OFString &OFStandard::getFilenameFromPath(OFString &result,
                                          const OFString &pathName,
                                          const OFBool assumeFilename)
{
    OFFilename resultFilename;
    /* call the real function */
    getFilenameFromPath(resultFilename, pathName, assumeFilename);
    /* convert result into a string object */
    result = OFSTRING_GUARD(resultFilename.getCharPointer());
    return result;
}


OFFilename &OFStandard::getFilenameFromPath(OFFilename &result,
                                            const OFFilename &pathName,
                                            const OFBool assumeFilename)
{
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
    /* check whether to use the wide-char version of the API function */
    if (pathName.usesWideChars())
    {
        const wchar_t *strValue = pathName.getWideCharPointer();
        const wchar_t *strPos = wcsrchr(strValue, L'\\' /* WIDE_PATH_SEPARATOR */);

        // Windows accepts both backslash and forward slash as path separators.
        const wchar_t *strPos2 = wcsrchr(strValue, L'/');

        // if strPos2 points to a character closer to the end of the string, use this instead of strPos
        if ((strPos == NULL) || ((strPos2 != NULL) && (strPos2 > strPos))) strPos = strPos2;

        /* path separator found? */
        if (strPos == NULL)
        {
            if (assumeFilename)
                result = pathName;
            else
                result.clear();
        } else {
            wchar_t *tmpString = new wchar_t[wcslen(strPos)];
            wcscpy(tmpString, strPos + 1);
            result.set(tmpString, OFTrue /*convert*/);
            delete[] tmpString;
        }
    } else
#endif
    /* otherwise, use the conventional 8-bit characters version */
    {
        const char *strValue = pathName.getCharPointer();
        const char *strPos = strrchr(strValue, PATH_SEPARATOR);

#ifdef _WIN32
        // Windows accepts both backslash and forward slash as path separators.
        const char *strPos2 = strrchr(strValue, '/');

        // if strPos2 points to a character closer to the end of the string, use this instead of strPos
        if ((strPos == NULL) || ((strPos2 != NULL) && (strPos2 > strPos))) strPos = strPos2;
#endif

        /* path separator found? */
        if (strPos == NULL)
        {
            if (assumeFilename)
                result = pathName;
            else
                result.clear();
        } else
            result.set(OFString(strPos + 1));
    }
    return result;
}


OFString &OFStandard::normalizeDirName(OFString &result,
                                       const OFString &dirName,
                                       const OFBool allowEmptyDirName)
{
    OFFilename resultFilename;
    /* call the real function */
    normalizeDirName(resultFilename, dirName, allowEmptyDirName);
    /* convert result into a string object */
    result = OFSTRING_GUARD(resultFilename.getCharPointer());
    return result;
}


OFFilename &OFStandard::normalizeDirName(OFFilename &result,
                                         const OFFilename &dirName,
                                         const OFBool allowEmptyDirName)
{
    /* remove trailing path separators (keep it if appearing at the beginning of the string) */
    /* TODO: do we need to check for absolute path containing Windows drive name, e.g. "c:\"? */
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
    /* check whether to use the wide-char version of the API function */
    if (dirName.usesWideChars())
    {
        const wchar_t *strValue = dirName.getWideCharPointer();
        size_t strLength = (strValue == NULL) ? 0 : wcslen(strValue);
        // Windows accepts both backslash and forward slash as path separators.
        while ((strLength > 1) && ((strValue[strLength - 1] == L'\\' /* WIDE_PATH_SEPARATOR */) ||
              (strValue[strLength - 1] == L'/' )))
            --strLength;
        /* avoid "." as a directory name, use empty string instead */
        if (allowEmptyDirName && ((strLength == 0) || ((strLength == 1) && (strValue[0] == L'.'))))
            result.clear();
        /* avoid empty directory name (use "." instead) */
        else if (!allowEmptyDirName && (strLength == 0))
            result.set(L".", OFTrue /*convert*/);
        /* copy resulting string (omit trailing backslashes) */
        else {
            wchar_t *tmpString = new wchar_t[strLength + 1];
            wcsncpy(tmpString, strValue, strLength);
            tmpString[strLength] = L'\0';
            result.set(tmpString, OFTrue /*convert*/);
            delete[] tmpString;
        }
    } else
#endif
    /* otherwise, use the conventional 8-bit characters version */
    {
        const char *strValue = dirName.getCharPointer();
        size_t strLength = (strValue == NULL) ? 0 : strlen(strValue);
#ifdef _WIN32
        // Windows accepts both backslash and forward slash as path separators.
        while ((strLength > 1) && ((strValue[strLength - 1] == PATH_SEPARATOR) ||
              (strValue[strLength - 1] == '/' )))
            --strLength;
#else
        while ((strLength > 1) && (strValue[strLength - 1] == PATH_SEPARATOR))
            --strLength;
#endif
        /* avoid "." as a directory name, use empty string instead */
        if (allowEmptyDirName && ((strLength == 0) || ((strLength == 1) && (strValue[0] == '.'))))
            result.clear();
        /* avoid empty directory name (use "." instead) */
        else if (!allowEmptyDirName && (strLength == 0))
            result.set(".");
        /* copy resulting string (omit trailing backslashes) */
        else
            result.set(OFString(strValue, strLength));
    }
    return result;
}


OFString &OFStandard::combineDirAndFilename(OFString &result,
                                            const OFString &dirName,
                                            const OFString &fileName,
                                            const OFBool allowEmptyDirName)
{
    OFFilename resultFilename;
    /* call the real function */
    combineDirAndFilename(resultFilename, dirName, fileName, allowEmptyDirName);
    /* convert result into a string object */
    result = OFSTRING_GUARD(resultFilename.getCharPointer());
    return result;
}


OFFilename &OFStandard::combineDirAndFilename(OFFilename &result,
                                              const OFFilename &dirName,
                                              const OFFilename &fileName,
                                              const OFBool allowEmptyDirName)
{
    // # might use system function realpath() in the future to resolve paths including ".."
    // # or combinations of absolute paths in both 'dirName' and 'fileName'
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
    /* check whether to use the wide-char version of the API function */
    if (dirName.usesWideChars() || fileName.usesWideChars())
    {
        const wchar_t *strValue = fileName.getWideCharPointer();
        size_t strLength = (strValue == NULL) ? 0 : wcslen(strValue);
        /* check whether 'fileName' contains absolute path */
        /* (this check also covers UNC syntax, e.g. "\\server\...") */
        // Windows accepts both backslash and forward slash as path separators.
        if ((strLength > 0) && ((strValue[0] == L'\\' /* WIDE_PATH_SEPARATOR */) || (strValue[0] == L'/')))
        {
            result.set(strValue, OFTrue /*convert*/);
            return result;
        }
#ifdef HAVE_WINDOWS_H
        else if (strLength >= 3)
        {
            /* check for absolute path containing Windows drive name, e.g. "c:\..." */
            const wchar_t c = strValue[0];
            if (((c >= L'A') && (c <= L'Z')) || ((c >= L'a') && (c <= L'z')))
            {
                // Windows accepts both backslash and forward slash as path separators.
                if ((strValue[1] == L':') && ((strValue[2] == L'\\' /* WIDE_PATH_SEPARATOR */))||(strValue[2] == L'/'))
                {
                    result.set(strValue, OFTrue /*convert*/);
                    return result;
                }
            }
        }
#endif
        /* we only get here, if we don't have an absolute directory in "fileName" */
        /* now normalize the directory name */
        normalizeDirName(result, dirName, allowEmptyDirName);
        /* do some extra checks on a special case */
        if (!result.isEmpty() && !result.usesWideChars())
        {
            /* make sure that wide-char version exists */
            OFFilename tmpDirName(result);
            result.set(tmpDirName.getCharPointer(), OFTrue /*convert*/);
        }
        /* check file name (ignore empty string and ".") */
        if ((strLength > 1) || ((strLength == 1) && (strValue[0] != L'.')))
        {
            if (result.isEmpty())
                result.set(strValue, OFTrue /*convert*/);
            else {
                const wchar_t *resValue = result.getWideCharPointer();
                const size_t resLength = wcslen(resValue); /* should never be 0 */
                wchar_t *tmpString = new wchar_t[strLength + resLength + 1 + 1];
                wcscpy(tmpString, resValue);
                /* add path separator (if required) ... */
                // Windows accepts both backslash and forward slash as path separators.
                if ((resValue[resLength - 1] != L'\\' /* WIDE_PATH_SEPARATOR */) && (resValue[resLength - 1] != L'/'))
                {
                    tmpString[resLength] = L'\\' /* WIDE_PATH_SEPARATOR */;
                    tmpString[resLength + 1] = L'\0';
                }
                /* ...and file name */
                wcscat(tmpString, strValue);
                result.set(tmpString, OFTrue /*convert*/);
                delete[] tmpString;
            }
        }
    } else
#endif
    /* otherwise, use the conventional 8-bit characters version */
    {
        const char *strValue = fileName.getCharPointer();
        size_t strLength = (strValue == NULL) ? 0 : strlen(strValue);
        /* check whether 'fileName' contains absolute path */
        /* (this check also covers UNC syntax, e.g. "\\server\...") */
#ifdef _WIN32
        // Windows accepts both backslash and forward slash as path separators.
        if ((strLength > 0) && ((strValue[0] == PATH_SEPARATOR) || (strValue[0] == '/')))
#else
        if ((strLength > 0) && (strValue[0] == PATH_SEPARATOR))
#endif
        {
            result.set(strValue);
            return result;
        }
#ifdef HAVE_WINDOWS_H
        else if (strLength >= 3)
        {
            /* check for absolute path containing Windows drive name, e.g. "c:\..." */
            const char c = strValue[0];
            if (((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')))
            {
                if ((strValue[1] == ':') && ((strValue[2] == '\\') || (strValue[2] == '/')))
                {
                    result.set(strValue);
                    return result;
                }
            }
        }
#endif
        /* we only get here, if we don't have an absolute directory in "fileName" */
        /* now normalize the directory name */
        normalizeDirName(result, dirName, allowEmptyDirName);
        /* check file name (ignore empty string and ".") */
        if ((strLength > 1) || ((strLength == 1) && (strValue[0] != '.')))
        {
            if (result.isEmpty())
                result.set(strValue);
            else {
                const char *resValue = result.getCharPointer();
                const size_t resLength = strlen(resValue); /* should never be 0 */
                const size_t buflen = strLength + resLength + 1 + 1;
                char *tmpString = new char[buflen];
                OFStandard::strlcpy(tmpString, resValue, buflen);
                /* add path separator (if required) ... */
#ifdef _WIN32
                // Windows accepts both backslash and forward slash as path separators.
                if ((resValue[resLength - 1] != PATH_SEPARATOR) && (resValue[resLength - 1] != '/'))
#else
                if (resValue[resLength - 1] != PATH_SEPARATOR)
#endif
                {
                    tmpString[resLength] = PATH_SEPARATOR;
                    tmpString[resLength + 1] = '\0';
                }
                /* ...and file name */
                OFStandard::strlcat(tmpString, strValue, buflen);
                result.set(tmpString);
                delete[] tmpString;
            }
        }
    }
    return result;
}


OFCondition OFStandard::removeRootDirFromPathname(OFFilename &result,
                                                  const OFFilename &rootDir,
                                                  const OFFilename &pathName,
                                                  const OFBool allowLeadingPathSeparator)
{
    OFCondition status = EC_IllegalParameter;
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
    /* check whether to use the wide-char version of the API function */
    if (rootDir.usesWideChars() || pathName.usesWideChars())
    {
        const wchar_t *rootValue = rootDir.getWideCharPointer();
        const wchar_t *pathValue = pathName.getWideCharPointer();
        const size_t rootLength = (rootValue == NULL) ? 0 : wcslen(rootValue);
        const size_t pathLength = (pathValue == NULL) ? 0 : wcslen(pathValue);
        /* check for empty strings */
        if ((rootLength == 0) && (pathLength == 0))
        {
            result.set("", OFTrue /*convert*/);
            status = EC_Normal;
        }
        /* check for empty root dir */
        else if (rootLength == 0)
        {
            result.set(pathValue, OFTrue /*convert*/);
            status = EC_Normal;
        }
        /* check for "compatible" length */
        else if (rootLength <= pathLength)
        {
            /* check for same prefix */
            if (wcsncmp(rootValue, pathValue, rootLength) == 0)
            {
                /* create temporary buffer for destination string */
                wchar_t *tmpString = new wchar_t[pathLength - rootLength + 1];
                /* remove root dir prefix from path name */
                wcscpy(tmpString, pathValue + rootLength);
                /* remove leading path separator (if present) */
                if (!allowLeadingPathSeparator && ((tmpString[0] == PATH_SEPARATOR) || (tmpString[0] == '/')))
                    result.set(tmpString + 1, OFTrue /*convert*/);
                else
                    result.set(tmpString, OFTrue /*convert*/);
                delete[] tmpString;
                status = EC_Normal;
            }
        }
    } else
#endif
    /* otherwise, use the conventional 8-bit characters version */
    {
        const char *rootValue = rootDir.getCharPointer();
        const char *pathValue = pathName.getCharPointer();
        const size_t rootLength = (rootValue == NULL) ? 0 : strlen(rootValue);
        const size_t pathLength = (pathValue == NULL) ? 0 : strlen(pathValue);
        /* check for empty strings */
        if ((rootLength == 0) && (pathLength == 0))
        {
            result.set("");
            status = EC_Normal;
        }
        /* check for empty root dir */
        else if (rootLength == 0)
        {
            result.set(pathValue);
            status = EC_Normal;
        }
        /* check for "compatible" length */
        else if (rootLength <= pathLength)
        {
            /* check for same prefix */
            if (strncmp(rootValue, pathValue, rootLength) == 0)
            {
                /* create temporary buffer for destination string */
                size_t buflen = pathLength - rootLength + 1;
                char *tmpString = new char[buflen];
                /* remove root dir prefix from path name */
                OFStandard::strlcpy(tmpString, pathValue + rootLength, buflen);
                /* remove leading path separator (if present) */
#ifdef _WIN32
                // Windows accepts both backslash and forward slash as path separators.
                if (!allowLeadingPathSeparator && ((tmpString[0] == PATH_SEPARATOR) || (tmpString[0] == '/')))
#else
                if (!allowLeadingPathSeparator && (tmpString[0] == PATH_SEPARATOR))
#endif
                    result.set(tmpString + 1);
                else
                    result.set(tmpString);
                delete[] tmpString;
                status = EC_Normal;
            }
        }
    }
    /* return empty string in case of error */
    if (status.bad())
        result.clear();
    return status;
}


OFFilename &OFStandard::appendFilenameExtension(OFFilename &result,
                                                const OFFilename &fileName,
                                                const OFFilename &fileExtension)
{
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
    /* check whether to use the wide-char version of the API function */
    if (fileName.usesWideChars())
    {
        OFFilename fileExt(fileExtension);
        /* convert file extension to wide chars (if needed) */
        if (!fileExt.isEmpty() && !fileExt.usesWideChars())
            fileExt.set(fileExtension.getCharPointer(), OFTrue /*convert*/);
        const wchar_t *namValue = fileName.getWideCharPointer();
        const wchar_t *extValue = fileExt.getWideCharPointer();
        size_t namLength = (namValue == NULL) ? 0 : wcslen(namValue);
        size_t extLength = (extValue == NULL) ? 0 : wcslen(extValue);
        /* create temporary buffer for destination string */
        wchar_t *tmpString = new wchar_t[namLength + extLength + 1];
        wcscpy(tmpString, namValue);
        if (extValue != NULL)
            wcscat(tmpString, extValue);
        result.set(tmpString, OFTrue /*convert*/);
        delete[] tmpString;
    } else
#endif
    /* otherwise, use the conventional 8-bit characters version */
    {
        const char *namValue = fileName.getCharPointer();
        const char *extValue = fileExtension.getCharPointer();
        size_t namLength = (namValue == NULL) ? 0 : strlen(namValue);
        size_t extLength = (extValue == NULL) ? 0 : strlen(extValue);
        /* create temporary buffer for destination string */
        size_t buflen = namLength + extLength + 1;
        char *tmpString = new char[buflen];
        OFStandard::strlcpy(tmpString, (namValue == NULL) ? "" : namValue, buflen);
        if (extValue != NULL)
            OFStandard::strlcat(tmpString, extValue, buflen);
        result.set(tmpString);
        delete[] tmpString;
    }
    return result;
}


size_t OFStandard::searchDirectoryRecursively(const OFString &directory,
                                              OFList<OFString> &fileList,
                                              const OFString &pattern,
                                              const OFString &dirPrefix,
                                              const OFBool recurse)
{
    OFList<OFFilename> filenameList;
    /* call the real function */
    const size_t result = searchDirectoryRecursively(directory, filenameList, pattern, dirPrefix, recurse);
    /* copy all list entries to reference parameter */
    OFListIterator(OFFilename) iter = filenameList.begin();
    OFListIterator(OFFilename) last = filenameList.end();
    while (iter != last)
    {
        fileList.push_back(OFSTRING_GUARD((*iter).getCharPointer()));
        ++iter;
    }
    return result;
}


size_t OFStandard::searchDirectoryRecursively(const OFFilename &directory,
                                              OFList<OFFilename> &fileList,
                                              const OFFilename &pattern,
                                              const OFFilename &dirPrefix,
                                              const OFBool recurse)
{
    const size_t initialSize = fileList.size();
    OFFilename dirName, pathName, tmpString;
    combineDirAndFilename(dirName, dirPrefix, directory);
#ifdef HAVE_WINDOWS_H
    /* check whether given directory exists */
    if (dirExists(dirName))
    {
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (dirName.usesWideChars())
        {
            HANDLE handle;
            WIN32_FIND_DATAW data;
            /* check whether file pattern is given */
            if (!pattern.isEmpty())
            {
                /* first, search for matching files on this directory level */
                handle = FindFirstFileW(combineDirAndFilename(tmpString, dirName, pattern, OFTrue /*allowEmptyDirName*/).getWideCharPointer(), &data);
                if (handle != INVALID_HANDLE_VALUE)
                {
                    do {
                        /* avoid leading "." */
                        if (wcscmp(dirName.getWideCharPointer(), L".") == 0)
                            pathName.set(data.cFileName, OFTrue /*convert*/);
                        else
                            combineDirAndFilename(pathName, directory, data.cFileName, OFTrue /*allowEmptyDirName*/);
                        /* ignore directories and the like */
                        if (fileExists(combineDirAndFilename(tmpString, dirPrefix, pathName, OFTrue /*allowEmptyDirName*/)))
                            fileList.push_back(pathName);
                    } while (FindNextFileW(handle, &data));
                    FindClose(handle);
                }
            }
            /* then search for _any_ file/directory entry */
            handle = FindFirstFileW(combineDirAndFilename(tmpString, dirName, L"*.*", OFTrue /*allowEmptyDirName*/).getWideCharPointer(), &data);
            if (handle != INVALID_HANDLE_VALUE)
            {
                do {
                    /* filter out current and parent directory */
                    if ((wcscmp(data.cFileName, L".") != 0) && (wcscmp(data.cFileName, L"..") != 0))
                    {
                        /* avoid leading "." */
                        if (wcscmp(dirName.getWideCharPointer(), L".") == 0)
                            pathName.set(data.cFileName, OFTrue /*convert*/);
                        else
                            combineDirAndFilename(pathName, directory, data.cFileName, OFTrue /*allowEmptyDirName*/);
                        if (dirExists(combineDirAndFilename(tmpString, dirPrefix, pathName, OFTrue /*allowEmptyDirName*/)))
                        {
                            /* recursively search sub directories */
                            if (recurse)
                                searchDirectoryRecursively(pathName, fileList, pattern, dirPrefix, recurse);
                        }
                        else if (pattern.isEmpty())
                        {
                            /* add filename to the list (if no pattern is given) */
                            fileList.push_back(pathName);
                        }
                    }
                } while (FindNextFileW(handle, &data));
                FindClose(handle);
            }
        } else
#endif /* defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32) */
        /* otherwise, use the conventional 8-bit characters version */
        {
            HANDLE handle;
            WIN32_FIND_DATAA data;
            /* check whether file pattern is given */
            if (!pattern.isEmpty())
            {
                /* first, search for matching files on this directory level */
                handle = FindFirstFileA(combineDirAndFilename(tmpString, dirName, pattern, OFTrue /*allowEmptyDirName*/).getCharPointer(), &data);
                if (handle != INVALID_HANDLE_VALUE)
                {
                    do {
                        /* avoid leading "." */
                        if (strcmp(dirName.getCharPointer(), ".") == 0)
                            pathName.set(data.cFileName);
                        else
                            combineDirAndFilename(pathName, directory, data.cFileName, OFTrue /*allowEmptyDirName*/);
                        /* ignore directories and the like */
                        if (fileExists(combineDirAndFilename(tmpString, dirPrefix, pathName, OFTrue /*allowEmptyDirName*/)))
                            fileList.push_back(pathName);
                    } while (FindNextFileA(handle, &data));
                    FindClose(handle);
                }
            }
            /* then search for _any_ file/directory entry */
            handle = FindFirstFileA(combineDirAndFilename(tmpString, dirName, "*.*", OFTrue /*allowEmptyDirName*/).getCharPointer(), &data);
            if (handle != INVALID_HANDLE_VALUE)
            {
                do {
                    /* filter out current and parent directory */
                    if ((strcmp(data.cFileName, ".") != 0) && (strcmp(data.cFileName, "..") != 0))
                    {
                        /* avoid leading "." */
                        if (strcmp(dirName.getCharPointer(), ".") == 0)
                            pathName.set(data.cFileName);
                        else
                            combineDirAndFilename(pathName, directory, data.cFileName, OFTrue /*allowEmptyDirName*/);
                        if (dirExists(combineDirAndFilename(tmpString, dirPrefix, pathName, OFTrue /*allowEmptyDirName*/)))
                        {
                            /* recursively search sub directories */
                            if (recurse)
                                searchDirectoryRecursively(pathName, fileList, pattern, dirPrefix, recurse);
                        }
                        else if (pattern.isEmpty())
                        {
                            /* add filename to the list (if no pattern is given) */
                            fileList.push_back(pathName);
                        }
                    }
                } while (FindNextFileA(handle, &data));
                FindClose(handle);
            }
        }
    }
#else /* HAVE_WINDOWS_H */
    /* try to open the directory */
    DIR *dirPtr = opendir(dirName.getCharPointer());
    if (dirPtr != NULL)
    {
        struct dirent *entry = NULL;
#if defined(HAVE_READDIR_R) && !defined(READDIR_IS_THREADSAFE)
        dirent d = {};
        while (!readdir_r(dirPtr, &d, &entry) && entry)
#else
        while ((entry = readdir(dirPtr)) != NULL)
#endif
        {
            /* filter out current (".") and parent directory ("..") */
            if ((strcmp(entry->d_name, ".") != 0) && (strcmp(entry->d_name, "..") != 0))
            {
                /* avoid leading "." */
                if (strcmp(dirName.getCharPointer(), ".") == 0)
                    pathName = entry->d_name;
                else
                    combineDirAndFilename(pathName, directory, entry->d_name, OFTrue /*allowEmptyDirName*/);
                if (dirExists(combineDirAndFilename(tmpString, dirPrefix, pathName, OFTrue /*allowEmptyDirName*/)))
                {
                    /* recursively search sub directories */
                    if (recurse)
                        searchDirectoryRecursively(pathName, fileList, pattern, dirPrefix, recurse);
                } else {
#ifdef HAVE_FNMATCH_H
                    /* check whether filename matches pattern */
                    if ((pattern.isEmpty()) || (fnmatch(pattern.getCharPointer(), entry->d_name, FNM_PATHNAME) == 0))
#else
                        /* no pattern matching, sorry :-/ */
#endif
                        fileList.push_back(pathName);
                }
            }
        }
        closedir(dirPtr);
    }
#endif /* HAVE_WINDOWS_H */
    /* return number of added files */
    return fileList.size() - initialSize;
}


OFCondition OFStandard::createDirectory(const OFFilename &dirName,
                                        const OFFilename &rootDir)
{
    OFCondition status = EC_Normal;
    /* first, check whether the directory already exists */
    if (!dirExists(dirName))
    {
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        /* check whether to use the wide-char version of the API function */
        if (dirName.usesWideChars())
        {
            /* then, check whether the given prefix can be skipped */
            size_t pos = 0;
            const wchar_t *dirValue = dirName.getWideCharPointer();
            const wchar_t *rootValue = rootDir.getWideCharPointer();
            size_t dirLength = (dirValue == NULL) ? 0 : wcslen(dirValue);
            size_t rootLength = (rootValue == NULL) ? 0 : wcslen(rootValue);
            /* check for absolute path containing Windows drive name, e. g. "c:\",
             * is not required since the root directory should always exist */
#ifdef _WIN32
            // Windows accepts both backslash and forward slash as path separators.
            if ((dirLength > 1) && ((dirValue[dirLength - 1] == L'\\' /* WIDE_PATH_SEPARATOR */) || (dirValue[dirLength - 1] == L'/')))
#else
            if ((dirLength > 1) && (dirValue[dirLength - 1] == L'\\' /* WIDE_PATH_SEPARATOR */))
#endif
            {
                /* ignore trailing path separator */
                --dirLength;
            }
#ifdef _WIN32
            // Windows accepts both backslash and forward slash as path separators.
            if ((rootLength > 1) && ((rootValue[rootLength - 1] == L'\\' /* WIDE_PATH_SEPARATOR */) || (rootValue[rootLength - 1] == L'/')))
#else
            if ((rootLength > 1) && (rootValue[rootLength - 1] == L'\\' /* WIDE_PATH_SEPARATOR */))
#endif
            {
                /* ignore trailing path separator */
                --rootLength;
            }
            /* check for "compatible" length */
            if ((rootLength > 0) && (rootLength < dirLength))
            {
                /* check for common prefix */
                if (wcsncmp(dirValue, rootValue, rootLength) == 0)
                {
                    /* check whether root directory really exists */
                    if (dirExists(rootDir))
                    {
                        /* start searching after the common prefix */
                        pos = rootLength;
                    }
                }
            }
            /* and finally, iterate over all subsequent subdirectories */
            do {
                /* search for next path separator */
                do {
                    ++pos;
#ifdef _WIN32
                // Windows accepts both backslash and forward slash as path separators.
                } while ((dirValue[pos] != L'\\' /* WIDE_PATH_SEPARATOR */) && (dirValue[pos] != L'/') && (dirValue[pos] != '\0'));
#else
                } while ((dirValue[pos] != L'\\' /* WIDE_PATH_SEPARATOR */) && (dirValue[pos] != L'\0'));
#endif

                /* get name of current directory component */
                wchar_t *subDir = new wchar_t[pos + 1];
                wcsncpy(subDir, dirValue, pos /*num*/);
                subDir[pos] = L'\0';
                if (!dirExists(subDir))
                {
                    /* and create the directory component (if not already existing) */
                    if (_wmkdir(subDir) == -1)
                    {
                        char errBuf[256];
                        OFString message("Cannot create directory: ");
                        message.append(strerror(errno, errBuf, sizeof(errBuf)));
                        status = makeOFCondition(0, EC_CODE_CannotCreateDirectory, OF_error, message.c_str());
                        /* exit the loop */
                        break;
                    }
                }
                delete[] subDir;
            } while (pos < dirLength);
        } else
#endif
        /* otherwise, use the conventional 8-bit characters version */
        {
            /* then, check whether the given prefix can be skipped */
            size_t pos = 0;
            const char *dirValue = dirName.getCharPointer();
            const char *rootValue = rootDir.getCharPointer();
            size_t dirLength = (dirValue == NULL) ? 0 : strlen(dirValue);
            size_t rootLength = (rootValue == NULL) ? 0 : strlen(rootValue);
            /* check for absolute path containing Windows drive name, e. g. "c:\",
             * is not required since the root directory should always exist */
#ifdef _WIN32
            // Windows accepts both backslash and forward slash as path separators.
            if ((dirLength > 1) && ((dirValue[dirLength - 1] == PATH_SEPARATOR) || (dirValue[dirLength - 1] == '/')))
#else
            if ((dirLength > 1) && (dirValue[dirLength - 1] == PATH_SEPARATOR))
#endif
            {
                /* ignore trailing path separator */
                --dirLength;
            }
#ifdef _WIN32
            // Windows accepts both backslash and forward slash as path separators.
            if ((rootLength > 1) && ((rootValue[rootLength - 1] == PATH_SEPARATOR) || (rootValue[rootLength - 1] == '/')))
#else
            if ((rootLength > 1) && (rootValue[rootLength - 1] == PATH_SEPARATOR))
#endif
            {
                /* ignore trailing path separator */
                --rootLength;
            }
            /* check for "compatible" length */
            if ((rootLength > 0) && (rootLength < dirLength))
            {
                /* check for common prefix */
                if (strncmp(dirValue, rootValue, rootLength) == 0)
                {
                    /* check whether root directory really exists */
                    if (dirExists(rootDir))
                    {
                        /* start searching after the common prefix */
                        pos = rootLength;
                    }
                }
            }
            /* and finally, iterate over all subsequent subdirectories */
            do {
                /* search for next path separator */
                do {
                    ++pos;
#ifdef _WIN32
                // Windows accepts both backslash and forward slash as path separators.
                } while ((dirValue[pos] != PATH_SEPARATOR) && (dirValue[pos] != '/') && (dirValue[pos] != '\0'));
#else
                } while ((dirValue[pos] != PATH_SEPARATOR) && (dirValue[pos] != '\0'));
#endif
                /* get name of current directory component */
                char *subDir = new char[pos + 1];
                strlcpy(subDir, dirValue, pos + 1 /*size*/);
                if (!dirExists(subDir))
                {
                    /* and create the directory component (if not already existing) */
#ifdef HAVE_WINDOWS_H
                    if (_mkdir(subDir) == -1)
#else
                    if (mkdir(subDir, S_IRWXU | S_IRWXG | S_IRWXO) == -1)
#endif
                    {
                        char errBuf[256];
                        OFString message("Cannot create directory: ");
                        message.append(strerror(errno, errBuf, sizeof(errBuf)));
                        status = makeOFCondition(0, EC_CODE_CannotCreateDirectory, OF_error, message.c_str());
                        /* exit the loop */
                        break;
                    }
                }
                delete[] subDir;
            } while (pos < dirLength);
        }
    }
    return status;
}


#define COPY_FILE_BUFFER_SIZE 4096

OFBool OFStandard::copyFile(const OFFilename &sourceFilename,
                            const OFFilename &destFilename)
{
    OFBool status = OFFalse;
    /* avoid NULL or empty string passed to fopen() */
    if (!sourceFilename.isEmpty() && !destFilename.isEmpty())
    {
        /* open input file */
        OFFile sourceFile;
        if (sourceFile.fopen(sourceFilename, "rb"))
        {
            /* create output file */
            OFFile destFile;
            if (destFile.fopen(destFilename, "wb"))
            {
                size_t numRead = 0;
                size_t numWrite = 0;
                Uint8 buffer[COPY_FILE_BUFFER_SIZE];
                /* read and write data in chunks */
                do {
                    numRead = sourceFile.fread(buffer, 1, COPY_FILE_BUFFER_SIZE);
                } while ((numRead > 0) && ((numWrite = destFile.fwrite(buffer, 1, numRead)) == numRead));
                /* check for any errors */
                if ((sourceFile.error() == 0) && (destFile.error() == 0) && (destFile.fclose() == 0))
                    status = OFTrue;
            }
        }
    }
    return status;
}


OFBool OFStandard::deleteFile(const OFFilename &filename)
{
    int err = -1;
    /* avoid NULL or empty string passed to unlink() */
    if (!filename.isEmpty())
    {
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        if (filename.usesWideChars())
            err = _wunlink(filename.getWideCharPointer());
        else
#endif
            err = unlink(filename.getCharPointer());
    }
    return (err == 0);
}


OFBool OFStandard::renameFile(const OFFilename &oldFilename,
                              const OFFilename &newFilename)
{
    int err = -1;
    /* avoid NULL or empty strings passed to rename() */
    if (!oldFilename.isEmpty() && !newFilename.isEmpty())
    {
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        if (oldFilename.usesWideChars() && newFilename.usesWideChars())
            err = _wrename(oldFilename.getWideCharPointer(), newFilename.getWideCharPointer());
        else {
            const char *oldName = oldFilename.getCharPointer();
            const char *newName = newFilename.getCharPointer();
            /* avoid passing invalid values to rename() */
            if ((oldName != NULL) && (newName != NULL))
                err = rename(oldName, newName);
        }
#else
        err = rename(oldFilename.getCharPointer(), newFilename.getCharPointer());
#endif
    }
    return (err == 0);
}


size_t OFStandard::getFileSize(const OFFilename &filename)
{
    size_t fileSize = 0;
    /* avoid NULL or empty strings passed to stat() */
    if (!filename.isEmpty())
    {
#if defined(WIDE_CHAR_FILE_IO_FUNCTIONS) && defined(_WIN32)
        if (filename.usesWideChars())
        {
            struct _stat64i32 fileStat;
            if (_wstat(filename.getWideCharPointer(), &fileStat) == 0)
                fileSize = OFstatic_cast(size_t, fileStat.st_size);
        } else
#endif
        {
            struct stat fileStat;
            if (stat(filename.getCharPointer(), &fileStat) == 0)
                fileSize = OFstatic_cast(size_t, fileStat.st_size);
        }
    }
    return fileSize;
}


OFBool OFStandard::checkForMarkupConversion(const OFString &sourceString,
                                            const OFBool convertNonASCII,
                                            const size_t maxLength)
{
    OFBool result = OFFalse;
    size_t pos = 0;
    const size_t strLen = sourceString.length();
    /* determine maximum number of characters to be converted */
    const size_t length = (maxLength == 0) ? strLen : ((strLen < maxLength) ? strLen : maxLength);
    /* check for characters to be converted */
    while (pos < length)
    {
        const size_t c = OFstatic_cast(unsigned char, sourceString.at(pos));
        if ((c == '<') || (c == '>') || (c == '&') || (c == '"') || (c == '\'') ||
            (c == 0) || /* a NULL byte should never be added to the output */
            (c == 10) || (c == 13) || (convertNonASCII && ((c < 32) || (c >= 127))))
        {
            /* return on the first character that needs to be converted */
            result = OFTrue;
            break;
        }
        ++pos;
    }
    return result;
}


OFCondition OFStandard::convertToMarkupStream(STD_NAMESPACE ostream &out,
                                              const OFString &sourceString,
                                              const OFBool convertNonASCII,
                                              const E_MarkupMode markupMode,
                                              const OFBool newlineAllowed,
                                              const size_t maxLength)
{
    size_t pos = 0;
    const size_t strLen = sourceString.length();
    /* determine maximum number of characters to be converted */
    const size_t length = (maxLength == 0) ? strLen : ((strLen < maxLength) ? strLen : maxLength);
    /* replace HTML/XHTML/XML reserved characters */
    while (pos < length)
    {
        const char c = sourceString.at(pos);
        /* less than */
        if (c == '<')
            out << "&lt;";
        /* greater than */
        else if (c == '>')
            out << "&gt;";
        /* ampersand */
        else if (c == '&')
            out << "&amp;";
        /* quotation mark */
        else if (c == '"')
        {
            /* entity "&quot;" is not defined in HTML 3.2 */
            if (markupMode == MM_HTML32)
                out << "&#34;";
            else
                out << "&quot;";
        }
        /* apostrophe */
        else if (c == '\'')
        {
            /* entity "&apos;" is not defined in HTML */
            if ((markupMode == MM_HTML) || (markupMode == MM_HTML32))
                out << "&#39;";
            else
                out << "&apos;";
        }
        /* newline: LF, CR, LF CR, CR LF */
        else if ((c == '\012') || (c == '\015'))
        {
            if (markupMode == MM_XML)
            {
                /* encode CR and LF exactly as specified */
                if (c == '\012')
                    out << "&#10;";    // '\n'
                else
                    out << "&#13;";    // '\r'
            } else {  /* HTML/XHTML mode */
                /* skip next character if it belongs to the newline sequence */
                if (((c == '\012') && (sourceString[pos + 1] == '\015')) || ((c == '\015') && (sourceString[pos + 1] == '\012')))
                    ++pos;
                if (newlineAllowed)
                {
                    if (markupMode == MM_XHTML)
                        out << "<br />\n";
                    else
                        out << "<br>\n";
                } else
                    out << "&para;";
            }
        } else {
            const size_t charValue = OFstatic_cast(unsigned char, c);
            /* other character: ... */
            if ((convertNonASCII || (markupMode == MM_HTML32)) && ((charValue < 32) || (charValue >= 127)))
            {
                /* convert < #32 and >= #127 to Unicode (ISO Latin-1) */
                out << "&#" << charValue << ";";
            }
            else if (charValue != 0)
            {
                /* just append (if not a NULL byte) */
                out << c;
            }
        }
        ++pos;
    }
    return EC_Normal;
}


const OFString &OFStandard::convertToMarkupString(const OFString &sourceString,
                                                  OFString &markupString,
                                                  const OFBool convertNonASCII,
                                                  const E_MarkupMode markupMode,
                                                  const OFBool newlineAllowed,
                                                  const size_t maxLength)
{
    OFStringStream stream;
    /* call stream variant of convert to markup */
    if (OFStandard::convertToMarkupStream(stream, sourceString, convertNonASCII, markupMode, newlineAllowed, maxLength).good())
    {
        stream << OFStringStream_ends;
        /* convert string stream into a character string */
        OFSTRINGSTREAM_GETSTR(stream, buffer_str)
        markupString.assign(buffer_str);
        OFSTRINGSTREAM_FREESTR(buffer_str)
    } else
        markupString.clear();
    return markupString;
}


OFBool OFStandard::checkForOctalConversion(const OFString &sourceString,
                                           const size_t maxLength)
{
    OFBool result = OFFalse;
    size_t pos = 0;
    const size_t strLen = sourceString.length();
    /* determine maximum number of characters to be converted */
    const size_t length = (maxLength == 0) ? strLen : ((strLen < maxLength) ? strLen : maxLength);
    /* check for characters to be converted */
    while (pos < length)
    {
        const size_t c = OFstatic_cast(unsigned char, sourceString.at(pos));
        if ((c < 32) || (c >= 127))
        {
            /* return on the first character that needs to be converted */
            result = OFTrue;
            break;
        }
        ++pos;
    }
    return result;
}


OFCondition OFStandard::convertToOctalStream(STD_NAMESPACE ostream &out,
                                             const OFString &sourceString,
                                             const size_t maxLength)
{
    size_t pos = 0;
    const size_t strLen = sourceString.length();
    /* determine maximum number of characters to be converted */
    const size_t length = (maxLength == 0) ? strLen : ((strLen < maxLength) ? strLen : maxLength);
    /* switch to octal mode for numbers */
    out << STD_NAMESPACE oct << STD_NAMESPACE setfill('0');
    while (pos < length)
    {
        const char c = sourceString.at(pos);
        const size_t charValue = OFstatic_cast(unsigned char, c);
        /* replace non-ASCII characters */
        if ((charValue < 32) || (charValue >= 127))
            out << '\\' << STD_NAMESPACE setw(3) << charValue;
        else
            out << c;
        ++pos;
    }
    /* reset i/o manipulators */
    out << STD_NAMESPACE dec << STD_NAMESPACE setfill(' ');
    return EC_Normal;
}


const OFString &OFStandard::convertToOctalString(const OFString &sourceString,
                                                 OFString &octalString,
                                                 const size_t maxLength)
{
    OFStringStream stream;
    /* call stream variant of convert to octal notation */
    if (OFStandard::convertToOctalStream(stream, sourceString, maxLength).good())
    {
        stream << OFStringStream_ends;
        /* convert string stream into a character string */
        OFSTRINGSTREAM_GETSTR(stream, buffer_str)
        octalString.assign(buffer_str);
        OFSTRINGSTREAM_FREESTR(buffer_str)
    } else
        octalString.clear();
    return octalString;
}


// Base64 translation table as described in RFC 2045 (MIME)
static const char enc_base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

OFCondition OFStandard::encodeBase64(STD_NAMESPACE ostream &out,
                                     const unsigned char *data,
                                     const size_t length,
                                     const size_t width)
{
    OFCondition status = EC_IllegalParameter;
    /* check data buffer to be encoded */
    if (data != NULL)
    {
        unsigned char c;
        size_t w = 0;
        /* iterate over all data elements */
        for (size_t i = 0; i < length; i++)
        {
            /* encode first 6 bits */
            out << enc_base64[(data[i] >> 2) & 0x3f];
            /* insert line break (if width > 0) */
            if (++w == width)
            {
                out << OFendl;
                w = 0;
            }
            /* encode remaining 2 bits of the first byte and 4 bits of the second byte */
            c = (data[i] << 4) & 0x3f;
            if (++i < length)
                c |= (data[i] >> 4) & 0x0f;
            out << enc_base64[c];
            /* insert line break (if width > 0) */
            if (++w == width)
            {
                out << OFendl;
                w = 0;
            }
            /* encode remaining 4 bits of the second byte and 2 bits of the third byte */
            if (i < length)
            {
                c = (data[i] << 2) & 0x3f;
                if (++i < length)
                    c |= (data[i] >> 6) & 0x03;
                out << enc_base64[c];
            } else {
                i++;
                /* append fill char */
                out << '=';
            }
            /* insert line break (if width > 0) */
            if (++w == width)
            {
                out << OFendl;
                w = 0;
            }
            /* encode remaining 6 bits of the third byte */
            if (i < length)
                out << enc_base64[data[i] & 0x3f];
            else /* append fill char */
                out << '=';
            /* insert line break (if width > 0) */
            if (++w == width)
            {
                out << OFendl;
                w = 0;
            }
        }
        /* flush stream */
        out.flush();
        status = EC_Normal;
    }
    return status;
}


const OFString &OFStandard::encodeBase64(const unsigned char *data,
                                         const size_t length,
                                         OFString &result,
                                         const size_t width)
{
    OFStringStream stream;
    /* call stream variant of base64 encoder */
    if (OFStandard::encodeBase64(stream, data, length, width).good())
    {
        stream << OFStringStream_ends;
        /* convert string stream into a character string */
        OFSTRINGSTREAM_GETSTR(stream, buffer_str)
        result.assign(buffer_str);
        OFSTRINGSTREAM_FREESTR(buffer_str)
    } else
        result.clear();
    return result;
}


// Base64 decoding table: maps #43..#122 to #0..#63 (255 means invalid)
static const unsigned char dec_base64[] =
  { 62, 255, 255, 255, 63,                                                                                  // '+' .. '/'
    52, 53, 54, 55, 56, 57, 58, 59, 60, 61,                                                                 // '0' .. '9'
    255, 255, 255, 255, 255, 255, 255,                                                                      // ':' .. '@'
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,           // 'A' .. 'Z'
    255, 255, 255, 255, 255, 255,                                                                           // '[' .. '`'
    26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51  // 'a' .. 'z'
  };

size_t OFStandard::decodeBase64(const OFString &data,
                                unsigned char *&result)
{
    size_t count = 0;
    /* search for fill char to determine the real length of the input string */
    const size_t fillPos = data.find('=');
    const size_t length = (fillPos != OFString_npos) ? fillPos : data.length();
    /* check data buffer to be decoded */
    if (length > 0)
    {
        /* allocate sufficient memory for the decoded data */
        result = new unsigned char[((length + 3) / 4) * 3];
        if (result != NULL)
        {
            unsigned char c1 = 0;
            unsigned char c2 = 0;
            /* iterate over all data elements */
            for (size_t i = 0; i < length; i++)
            {
                /* skip invalid characters and assign first decoded char */
                while ((i < length) && ((data.at(i) < '+') || (data.at(i) > 'z') || ((c1 = dec_base64[data.at(i) - '+']) > 63)))
                    i++;
                if (++i < length)
                {
                    /* skip invalid characters and assign second decoded char */
                    while ((i < length) && ((data.at(i) < '+') || (data.at(i) > 'z') || ((c2 = dec_base64[data.at(i) - '+']) > 63)))
                        i++;
                    if (i < length)
                    {
                        /* decode first byte */
                        result[count++] = OFstatic_cast(unsigned char, (c1 << 2) | ((c2 >> 4) & 0x3));
                        if (++i < length)
                        {
                            /* skip invalid characters and assign third decoded char */
                            while ((i < length) && ((data.at(i) < '+') || (data.at(i) > 'z') || ((c1 = dec_base64[data.at(i) - '+']) > 63)))
                                i++;
                            if (i < length)
                            {
                                /* decode second byte */
                                result[count++] = OFstatic_cast(unsigned char, ((c2 << 4) & 0xf0) | ((c1 >> 2) & 0xf));
                                if (++i < length)
                                {
                                    /* skip invalid characters and assign fourth decoded char */
                                    while ((i < length) && ((data.at(i) < '+') || (data.at(i) > 'z') || ((c2 = dec_base64[data.at(i) - '+']) > 63)))
                                        i++;
                                    /* decode third byte */
                                    if (i < length)
                                        result[count++] = OFstatic_cast(unsigned char, ((c1 << 6) & 0xc0) | c2);
                                }
                            }
                        }
                    }
                }
            }
            /* delete buffer if no data has been written to the output */
            if (count == 0)
                delete[] result;
        }
    } else
        result = NULL;
    return count;
}

#ifndef ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION

double OFStandard::atof(const char *s, OFBool *success)
{
  double d = 0.0;
  if (success) *success = OFFalse;
  if (s)
  {
    // convert input to a string object
    STD_NAMESPACE string ss(s);

    // erase leading whitespace
    ss.erase(0, ss.find_first_not_of("\t "));

    // handle NaN as a special case, since iostream does not.
    // sscanf may or may not handle this case internally, depending on the version of the C standard implemented. NaN and inf will be supported in C99.
    if ((ss.length() >= 3) && (ss[0] == 'n' || ss[0] == 'N') && (ss[1] == 'a' || ss[1] == 'A') && (ss[2] == 'n' || ss[2] == 'N'))
    {
        if (success) *success = OFTrue;
        return OFnumeric_limits<double>::quiet_NaN();
    }

    // handle positive infinity as a special case, since iostream does not
    if ((ss.length() >= 3) && (ss[0] == 'i' || ss[0] == 'I') && (ss[1] == 'n' || ss[1] == 'N') && (ss[2] == 'f' || ss[2] == 'F'))
    {
        if (success) *success = OFTrue;
        return OFnumeric_limits<double>::infinity();
    }

    // handle negative infinity as a special case, since iostream does not
    if ((ss.length() >= 4) && (ss[0] == '-') && (ss[1] == 'i' || ss[1] == 'I') && (ss[2] == 'n' || ss[2] == 'N') && (ss[3] == 'f' || ss[3] == 'F'))
    {
        if (success) *success = OFTrue;
        return -OFnumeric_limits<double>::infinity();
    }

#ifdef ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION

    // create an input string stream
    STD_NAMESPACE istringstream iss(s);

    // create a locale object for the C locale and activate it in the stream
    STD_NAMESPACE locale mylocale("C");
    iss.imbue(mylocale);

    // convert string to double and set success flag
    if ((iss >> d) && success) *success = OFTrue;

#else /* ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION */

#ifdef _WIN32

    // Windows has a sscanf version where we can explicitly pass a locale
    _locale_t localeInfo = _create_locale(LC_NUMERIC, "C");
    if (_sscanf_l(ss.c_str(),"%lf",localeInfo,&d) == 1 && success ) *success = OFTrue;
    _free_locale(localeInfo);

#else /* _WIN32 */

    // handle the case that the decimal separator expected by sscanf is not "."
    size_t separator_pos = ss.find('.');
    if (separator_pos != STD_NAMESPACE string::npos)
    {
        struct lconv *loc = localeconv();
        if (loc && loc->decimal_point && (0 != strcmp(".", loc->decimal_point)))
        {
          // current locale is using a different decimal separator.
          // Replace "." by the separator expected by sscanf.
          ss.erase(separator_pos, 1);
          ss.insert(separator_pos, loc->decimal_point);
        }
    }

    // note that there is a race condition here. If another thread calls
    // setlocale() between our calls to localeconv() and sscanf(), then
    // things may go wrong, i.e. the conversion may yield an incorrect result
    // because the separator character expected by sscanf has suddenly changed.
    if (sscanf(ss.c_str(),"%lf",&d) == 1 && success ) *success = OFTrue;

#endif /* _WIN32 */

#endif /* ENABLE_IOSTREAM_BASED_ATOF_IMPLEMENTATION */

  }
  return d;
}

#else

// Old implementation of OFStandard::atof(). This implementation may produce
// rounding errors (see DCMTK issue #1100) and has thus been replaced by
// a new implementation based on std::istringstream.

/* Largest possible base 10 exponent.  Any exponent larger than this will
 * already produce underflow or overflow, so there's no need to worry
 * about additional digits.
 */
#define ATOF_MAXEXPONENT 511

/* Table giving binary powers of 10.  Entry is 10^2^i.
 * Used to convert decimal exponents into floating-point numbers.
 */
static const double atof_powersOf10[] =
{
    10.,
    100.,
    1.0e4,
    1.0e8,
    1.0e16,
    1.0e32,
    1.0e64,
    1.0e128,
    1.0e256
};

double OFStandard::atof(const char *s, OFBool *success)
{
    if (success) *success = OFFalse;
    const char *p = s;
    char c;
    int sign = 0;
    int expSign = 0;
    double fraction;
    int exponent = 0; // Exponent read from "EX" field.
    int old_exponent = 0;
    const char *pExp; // Temporarily holds location of exponent in string.

    /* Exponent that derives from the fractional part.  Under normal
     * circumstances, it is the negative of the number of digits in F.
     * However, if I is very long, the last digits of I get dropped
     * (otherwise a long I with a large negative exponent could cause an
     * unnecessary overflow on I alone).  In this case, fracExp is
     * incremented one for each dropped digit.
     */
    int fracExp = 0;

    // Strip off leading blanks and check for a sign.
    while (OFStandard::isspace(*p)) ++p;

    if (*p == '-')
    {
        sign = 1;
        ++p;
    }
    else
    {
        if (*p == '+') ++p;
    }

    //Check for special cases like NaN
    if ((p[0] == 'n' || p[0] == 'N') && (p[1] == 'a' || p[1] == 'A') && (p[2] == 'n' || p[2] == 'N')) {
        if (success) *success = OFTrue;
        return OFnumeric_limits<double>::quiet_NaN();
    }

    if ((p[0] == 'i' || p[0] == 'I') && (p[1] == 'n' || p[1] == 'N') && (p[2] == 'f' || p[2] == 'F')) {
        if (success) *success = OFTrue;
        return sign ? -OFnumeric_limits<double>::infinity() : OFnumeric_limits<double>::infinity();
    }
    // Count the number of digits in the mantissa (including the decimal
    // point), and also locate the decimal point.

    int decPt = -1; // Number of mantissa digits BEFORE decimal point.
    int mantSize;     // Number of digits in mantissa.
    for (mantSize = 0; ; ++mantSize)
    {
        c = *p;
        if (!isdigit(OFstatic_cast(unsigned char, c)))
        {
            if ((c != '.') || (decPt >= 0)) break;
            decPt = mantSize;
        }
        ++p;
    }

    /*
     * Now suck up the digits in the mantissa.  Use two integers to
     * collect 9 digits each (this is faster than using floating-point).
     * If the mantissa has more than 18 digits, ignore the extras, since
     * they can't affect the value anyway.
     */

    pExp = p;
    p -= mantSize;
    if (decPt < 0)
      decPt = mantSize;
      else mantSize -= 1; // One of the digits was the point

    if (mantSize > 18)
    {
        fracExp = decPt - 18;
        mantSize = 18;
    }
    else
    {
        fracExp = decPt - mantSize;
    }

    if (mantSize == 0)
    {
      // subject sequence does not have expected form.
      // return 0 and leave success flag set to false
      return 0.0;
    }
    else
    {
        int frac1 = 0;
        for ( ; mantSize > 9; mantSize -= 1)
        {
            c = *p;
            ++p;
            if (c == '.')
            {
                c = *p;
                ++p;
            }
            frac1 = 10*frac1 + (c - '0');
        }
        int frac2 = 0;
        for (; mantSize > 0; mantSize -= 1)
        {
            c = *p;
            ++p;
            if (c == '.')
            {
                c = *p;
                ++p;
            }
            frac2 = 10*frac2 + (c - '0');
        }
        fraction = (1.0e9 * frac1) + frac2;
    }

    // Skim off the exponent.
    p = pExp;
    if ((*p == 'E') || (*p == 'e'))
    {
        ++p;
        if (*p == '-')
        {
            expSign = 1;
            ++p;
        }
        else
        {
            if (*p == '+') ++p;
            expSign = 0;
        }
        while (isdigit(OFstatic_cast(unsigned char, *p)))
        {
            old_exponent = exponent;
            exponent = exponent * 10 + (*p - '0');
            ++p;
            if (exponent < old_exponent)
            {
              // overflow of the exponent. We cannot represent this number in an integer
              // and also not in a double, where the exponent must not be larger than 308.
              if (expSign)
              {
                // negative exponent. return 0 and leave success flag set to false
                return 0.0;
              }
              else
              {
                // positive exponent. return plus/minus HUGE_VAL, depending on the sign bit
                if (sign) return -HUGE_VAL; else return HUGE_VAL;
              }
            }
        }
    }

    if (expSign)
       exponent = fracExp - exponent;
       else exponent = fracExp + exponent;

    /*
     * Generate a floating-point number that represents the exponent.
     * Do this by processing the exponent one bit at a time to combine
     * many powers of 2 of 10. Then combine the exponent with the
     * fraction.
     */

    if (exponent < 0)
    {
        expSign = 1;
        exponent = -exponent;
    }
    else expSign = 0;

    if (exponent > ATOF_MAXEXPONENT) exponent = ATOF_MAXEXPONENT;
    double dblExp = 1.0;
    for (const double *d = atof_powersOf10; exponent != 0; exponent >>= 1, ++d)
    {
        if (exponent & 01) dblExp *= *d;
    }

    if (expSign)
      fraction /= dblExp;
      else fraction *= dblExp;

    if (success) *success = OFTrue;
    if (sign) return -fraction;
    return fraction;
}

#endif /* ENABLE_OLD_OFSTD_ATOF_IMPLEMENTATION */

/* binary "and" mask for format flags */
#define FTOA_FORMAT_MASK 0x03
/* default precision is 6 digits */
#define FTOA_DEFPREC 6
/* 11-bit exponent (VAX G floating point) is 308 decimal digits */
#define FTOA_MAXEXP          308
/* 128 bit fraction takes up 39 decimal digits; max reasonable precision */
#define FTOA_MAXFRACT        39
/* internal buffer size for ftoa code */
#define FTOA_BUFSIZE         (FTOA_MAXEXP+FTOA_MAXFRACT+1)

#ifndef ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION

#ifndef ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION

static void ftoa_convert(
  char *dst,
  size_t siz,
  double val,
  unsigned int flags,
  int width,
  int prec)
{
  // this version of the function uses snprintf to format the output string.

  char buf[FTOA_BUFSIZE];
  OFString s("%"); // this will become the format string
  unsigned char fmtch = 'G';

  // check if val is NAN
  if (OFMath::isnan(val))
  {
    OFStandard::strlcpy(dst, "nan", siz);
    return;
  }

  // check if val is infinity
  if (OFMath::isinf(val))
  {
    if (val < 0)
        OFStandard::strlcpy(dst, "-inf", siz);
        else OFStandard::strlcpy(dst, "inf", siz);
    return;
  }

  // determine format character
  if (flags & OFStandard::ftoa_uppercase)
  {
    if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_e) fmtch = 'E';
    else if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_f) fmtch = 'f'; // there is no uppercase for 'f'
    else
    {
      fmtch = 'G';
    }
  }
  else
  {
    if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_e) fmtch = 'e';
    else if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_f) fmtch = 'f';
    else
    {
      fmtch = 'g';
    }
  }

  if (flags & OFStandard::ftoa_alternate) s += "#";
  if (flags & OFStandard::ftoa_leftadj) s += "-";
  if (flags & OFStandard::ftoa_zeropad) s += "0";
  if (width > 0)
  {
    OFStandard::snprintf(buf, sizeof(buf), "%d", width);
    s += buf;
  }
  if (prec >= 0)
  {
    OFStandard::snprintf(buf, sizeof(buf), ".%d", prec);
    s += buf;
  }
  s += fmtch;

#ifdef _WIN32

#ifdef HAVE__SET_OUTPUT_FORMAT
  // The old Microsoft Visual C Runtime (MSVCRT) used in VS 2013 and older
  // prints 3 exponent digits by default.  This call changes this.
  // This function does not exist anymore in the Universal C Runtime
  // used by VS 2015 or newer, but is still used by MinGW64.
  _set_output_format(_TWO_DIGIT_EXPONENT);
#endif

  // Windows has an sprintf version where we can explicitly pass a locale
  _locale_t localeInfo = _create_locale(LC_NUMERIC, "C");
  _snprintf_s_l(dst, siz, _TRUNCATE, s.c_str(), localeInfo, val);
  _free_locale(localeInfo);

#else /* _WIN32 */

  // On other platforms, we use snprintf() and fix the decimal separator afterwards
  OFStandard::snprintf(dst, siz, s.c_str(), val);

  // adjust for the decimal separator of the locale, which may be different from '.'
  // Since the locale may change at any time, we try doing this without actually accessing the current locale info
  char *c = dst;
  size_t c_len = siz; // remaining buffer size
  OFBool replaced = OFFalse;
  while (*c)
  {
    if (*c == 0) return; // end of string
    if (*c == '.') return; // decimal separator is '.', nothing to do
    if (*c == ',') // decimal separator is ','; adjust and return
    {
      *c = '.';
      return;
    }

    // since the string is null terminated, c+1 must exist if *c is not null
    if ((*c == OFstatic_cast(char, 0xd9) && *(c+1) == OFstatic_cast(char, 0xab)) ||
        (*c == OFstatic_cast(char, 0xab) && *(c+1) == OFstatic_cast(char, 0xd9))) // decimal separator is <U066B> in big endian or little endian byte order
    {
      *c = '.'; // replace first byte with '.'
      ++c;
      --c_len;
      // c_len cannot be zero at this point, but we check anyway
      if (c_len > 0) memmove(c, c+1,  c_len - 1); // move rest of the string one byte ahead. memmove works with overlapping memory areas.
      return;
    }

    if ((*c >= '0' && *c <= '9') ||
       (*c == '-') || (*c == '+') ||
       (*c == 'E') || (*c == 'e') || (*c == ' '))
    {
      // not a decimal separator, skip
      ++c;
      --c_len;
    }
    else
    {
      // unknown character. We assume this is a decimal separator.
      // We also assume that it might be a multi-byte UTF-8 sequence. We replace the
      // first unknown character with '.', then continue and delete all other unknown characters.
      if (replaced)
      {
        // this is not the first unknown character. Delete character, then continue.
        if (c_len > 0) memmove(c, c+1,  c_len - 1); // move rest of the string one byte ahead. memmove works with overlapping memory areas.
        // we do not advance c here, continue at this position with the now shorter remaining string
      }
      else
      {
        // this is the first unknown character. Replace with '.'
        *c = '.';
        ++c;
        --c_len;
        replaced = OFTrue;
      }
    }
  }
#endif /* _WIN32 */
}

#else /* ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION */

static void ftoa_convert(
  char *dst,
  size_t siz,
  double val,
  unsigned int flags,
  int width,
  int prec)
{
  // if target string is NULL or zero bytes long, bail out.
  if (!dst || !siz) return;

  // check if val is NAN
  if (OFMath::isnan(val))
  {
    OFStandard::strlcpy(dst, "nan", siz);
    return;
  }

  // check if val is infinity
  if (OFMath::isinf(val))
  {
    if (val < 0)
        OFStandard::strlcpy(dst, "-inf", siz);
        else OFStandard::strlcpy(dst, "inf", siz);
    return;
  }

  // create an output string stream
  STD_NAMESPACE ostringstream oss;

  // create a locale object for the C locale and activate it in the stream
  STD_NAMESPACE locale mylocale("C");
  oss.imbue(mylocale);

  // set width
  if (width > 0) oss << STD_NAMESPACE setw(width);

  // set adjustment
  if (flags & OFStandard::ftoa_leftadj) oss << STD_NAMESPACE left;

  // set precision
  if (prec < 0) prec = FTOA_DEFPREC;
  oss << STD_NAMESPACE setprecision(prec);

  // set uppercase
  if (flags & OFStandard::ftoa_uppercase) oss << STD_NAMESPACE uppercase;

  // set alternate form
  if (flags & OFStandard::ftoa_alternate) oss << STD_NAMESPACE showpoint;

  // set zero padding
  if (flags & OFStandard::ftoa_zeropad) oss << STD_NAMESPACE setfill('0') << STD_NAMESPACE internal;

  // set scientific vs fixed format
  if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_e) oss << STD_NAMESPACE scientific;
  else if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_f) oss << STD_NAMESPACE fixed;

  // insert the value into the string stream
  oss << val;

  // create a string object and store the stream content
  STD_NAMESPACE string os;
  os = oss.str();

  // copy string into target buffer
  OFStandard::strlcpy(dst, os.c_str(), siz);

  return;
}

#endif /* ENABLE_IOSTREAM_BASED_FTOA_IMPLEMENTATION */

void OFStandard::ftoa(
  char *dst,
  size_t siz,
  double val,
  unsigned int flags,
  int width,
  int prec)
{
  // special handling for g/G format and precision -2
  if ((prec == -2) && ((flags & FTOA_FORMAT_MASK) == 0))
  {
    // first attempt conversion with precision=16
    ftoa_convert(dst, siz, val, flags, width, 16);

    // and check if round-trip is exact
    OFBool success = OFFalse;
    double d = OFStandard::atof(dst, &success);
    if (!success || d != val)
    {
      // really need precision 17 (DBL_DECIMAL_DIG)
      ftoa_convert(dst, siz, val, flags, width, prec);
    }
  }
  else
  {
    ftoa_convert(dst, siz, val, flags, width, prec);
  }
}

#else /* ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION */

#define FTOA_TODIGIT(c)      ((c) - '0')
#define FTOA_TOCHAR(n)       ((n) + '0')

/** internal helper class that maintains a string buffer
 *  to which characters can be written. If the string buffer
 *  gets full, additional characters are discarded.
 *  The string buffer does not guarantee zero termination.
 */
class FTOAStringBuffer
{
public:
  /** constructor
   *  @param theSize desired size of string buffer, in bytes
   */
  FTOAStringBuffer(unsigned long theSize)
  : buf_(NULL)
  , offset_(0)
  , size_(theSize)
  {
    if (size_ > 0) buf_ = new char[size_];
  }

  /// destructor
  ~FTOAStringBuffer()
  {
    delete[] buf_;
  }

  /** add one character to string buffer. Never overwrites
   *  buffer boundary.
   *  @param c character to add
   */
  inline void put(unsigned char c)
  {
    if (buf_ && (offset_ < size_)) buf_[offset_++] = c;
  }

  // return pointer to string buffer
  const char *getBuffer() const
  {
    return buf_;
  }

private:
  /// pointer to string buffer
  char *buf_;

  /// current offset within buffer
  unsigned long offset_;

  /// size of buffer
  unsigned long size_;

  /// private undefined copy constructor
  FTOAStringBuffer(const FTOAStringBuffer &old);

  /// private undefined assignment operator
  FTOAStringBuffer &operator=(const FTOAStringBuffer &obj);
};


/** writes the given format character and exponent to output string p.
 *  @param p pointer to target string
 *  @param exponent exponent to print
 *  @param fmtch format character
 *  @return pointer to next unused character in output string
 */
static char *ftoa_exponent(char *p, int exponent, char fmtch)
{
  char expbuf[FTOA_MAXEXP];

  *p++ = fmtch;
  if (exponent < 0)
  {
    exponent = -exponent;
    *p++ = '-';
  }
  else *p++ = '+';
  char *t = expbuf + FTOA_MAXEXP;
  if (exponent > 9)
  {
    do
    {
      *--t = OFstatic_cast(char, FTOA_TOCHAR(exponent % 10));
    }
    while ((exponent /= 10) > 9);
    *--t = OFstatic_cast(char, FTOA_TOCHAR(exponent));
    for (; t < expbuf + FTOA_MAXEXP; *p++ = *t++) /* nothing */;
  }
  else
  {
    *p++ = '0';
    *p++ = OFstatic_cast(char, FTOA_TOCHAR(exponent));
  }

  return p;
}


/** round given fraction and adjust text string if round up.
 *  @param fract  fraction to round
 *  @param expon  pointer to exponent, may be NULL
 *  @param start  pointer to start of string to round
 *  @param end    pointer to one char after end of string
 *  @param ch     if fract is zero, this character is interpreted as fraction*10 instead
 *  @param signp  pointer to sign character, '-' or 0.
 *  @return adjusted pointer to start of rounded string, may be start or start-1.
 */
static char *ftoa_round(double fract, int *expon, char *start, char *end, char ch, char *signp)
{
  double tmp;

  if (fract) (void) modf(fract * 10, &tmp);
  else tmp = FTOA_TODIGIT(ch);

  if (tmp > 4)
  {
    for (;; --end)
    {
      if (*end == '.') --end;
      if (++*end <= '9') break;
      *end = '0';
      if (end == start)
      {
        if (expon) /* e/E; increment exponent */
        {
          *end = '1';
          ++*expon;
        }
        else /* f; add extra digit */
        {
          *--end = '1';
          --start;
        }
        break;
      }
    }
  }
  /* ``"%.3f", (double)-0.0004'' gives you a negative 0. */
  else if (*signp == '-')
  {
    for (;; --end)
    {
      if (*end == '.') --end;
      if (*end != '0') break;
      if (end == start) *signp = 0; // suppress negative 0
    }
  }

  return start;
}


/** convert double value to string, without padding
 *  @param val double value to be formatted
 *  @param prec    precision, adjusted for FTOA_MAXFRACT
 *  @param flags   formatting flags
 *  @param signp   pointer to sign character, '-' or 0.
 *  @param fmtch   format character
 *  @param startp  pointer to start of target buffer
 *  @param endp    pointer to one char after end of target buffer
 *  @return
 */
static int ftoa_convert(double val, int prec, int flags, char *signp, char fmtch, char *startp, char *endp)
{
  char *p;
  double fract;
  int dotrim = 0;
  int expcnt = 0;
  int gformat = 0;
  double integer, tmp;

  fract = modf(val, &integer);

  /* get an extra slot for rounding. */
  char *t = ++startp;

  /*
   * get integer portion of val; put into the end of the buffer; the
   * .01 is added for modf(356.0 / 10, &integer) returning .59999999...
   */
  for (p = endp - 1; integer; ++expcnt)
  {
    tmp = modf(integer / 10, &integer);
    *p-- = OFstatic_cast(char, FTOA_TOCHAR(OFstatic_cast(int, (tmp + .01) * 10)));
  }

  switch(fmtch)
  {
    case 'f':
      /* reverse integer into beginning of buffer */
      if (expcnt)
      {
        for (; ++p < endp; *t++ = *p);
      }
      else *t++ = '0';

      /*
       * if precision required or alternate flag set, add in a
       * decimal point.
       */
      if (prec || flags & OFStandard::ftoa_alternate) *t++ = '.';

      /* if requires more precision and some fraction left */
      if (fract)
      {
        if (prec) do
        {
          fract = modf(fract * 10, &tmp);
          *t++ = OFstatic_cast(char, FTOA_TOCHAR(OFstatic_cast(int, tmp)));
        } while (--prec && fract);
        if (fract)
        {
          startp = ftoa_round(fract, OFstatic_cast(int *, NULL), startp, t - 1, OFstatic_cast(char, 0), signp);
        }
      }
      for (; prec--; *t++ = '0');
      break;

    case 'e':
    case 'E':
eformat:
      if (expcnt)
      {
        *t++ = *++p;
        if (prec || flags&OFStandard::ftoa_alternate)
                *t++ = '.';
        /* if requires more precision and some integer left */
        for (; prec && ++p < endp; --prec)
                *t++ = *p;
        /*
         * if done precision and more of the integer component,
         * round using it; adjust fract so we don't re-round
         * later.
         */
        if (!prec && ++p < endp)
        {
          fract = 0;
          startp = ftoa_round(OFstatic_cast(double, 0), &expcnt, startp, t - 1, *p, signp);
        }
        /* adjust expcnt for digit in front of decimal */
        --expcnt;
      }
      /* until first fractional digit, decrement exponent */
      else if (fract)
      {
        /* adjust expcnt for digit in front of decimal */
        for (expcnt = -1;; --expcnt) {
                fract = modf(fract * 10, &tmp);
                if (tmp)
                        break;
        }
        *t++ = OFstatic_cast(char, FTOA_TOCHAR(OFstatic_cast(int, tmp)));
        if (prec || flags&OFStandard::ftoa_alternate) *t++ = '.';
      }
      else
      {
        *t++ = '0';
        if (prec || flags&OFStandard::ftoa_alternate) *t++ = '.';
      }

      /* if requires more precision and some fraction left */
      if (fract)
      {
        if (prec) do
        {
          fract = modf(fract * 10, &tmp);
          *t++ = OFstatic_cast(char, FTOA_TOCHAR(OFstatic_cast(int, tmp)));
        } while (--prec && fract);
        if (fract)
        {
          startp = ftoa_round(fract, &expcnt, startp, t - 1, OFstatic_cast(char, 0), signp);
        }
      }

      /* if requires more precision */
      for (; prec--; *t++ = '0');

      /* unless alternate flag, trim any g/G format trailing 0's */
      if (gformat && !(flags&OFStandard::ftoa_alternate))
      {
        while (t > startp && *--t == '0') /* nothing */;
        if (*t == '.') --t;
        ++t;
      }
      t = ftoa_exponent(t, expcnt, fmtch);
      break;

    case 'g':
    case 'G':
      /* a precision of 0 is treated as a precision of 1. */
      if (!prec) ++prec;
      /*
       * ``The style used depends on the value converted; style e
       * will be used only if the exponent resulting from the
       * conversion is less than -4 or greater than the precision.''
       *      -- ANSI X3J11
       */
      if (expcnt > prec || (!expcnt && fract && fract < .0001))
      {
        /*
         * g/G format counts "significant digits, not digits of
         * precision; for the e/E format, this just causes an
         * off-by-one problem, i.e. g/G considers the digit
         * before the decimal point significant and e/E doesn't
         * count it as precision.
         */
        --prec;
        fmtch = OFstatic_cast(char, fmtch - 2);             /* G->E, g->e */
        gformat = 1;
        goto eformat;
      }

      /*
       * reverse integer into beginning of buffer,
       * note, decrement precision
       */
      if (expcnt)
      {
        for (; ++p < endp; *t++ = *p, --prec);
      }
      else *t++ = '0';
      /*
       * if precision required or alternate flag set, add in a
       * decimal point.  If no digits yet, add in leading 0.
       */
      if (prec || flags&OFStandard::ftoa_alternate)
      {
        dotrim = 1;
        *t++ = '.';
      }
      else dotrim = 0;

      /* if requires more precision and some fraction left */
      if (fract)
      {
        if (prec)
        {
          do
          {
            fract = modf(fract * 10, &tmp);
            *t++ = OFstatic_cast(char, FTOA_TOCHAR(OFstatic_cast(int, tmp)));
          } while(!tmp);
          while (--prec && fract)
          {
            fract = modf(fract * 10, &tmp);
            *t++ = OFstatic_cast(char, FTOA_TOCHAR(OFstatic_cast(int, tmp)));
          }
        }
        if (fract)
        {
          startp = ftoa_round(fract, OFstatic_cast(int *, NULL), startp, t - 1, OFstatic_cast(char, 0), signp);
        }
      }
      /* alternate format, adds 0's for precision, else trim 0's */
      if (flags&OFStandard::ftoa_alternate) for (; prec--; *t++ = '0') /* nothing */;
      else if (dotrim)
      {
        while (t > startp && *--t == '0') /* nothing */;
        if (*t != '.') ++t;
      }
  } /* end switch */

  return OFstatic_cast(int, t - startp);
}

void OFStandard::ftoa(
  char *dst,
  size_t siz,
  double val,
  unsigned int flags,
  int width,
  int prec)
{
  // if target string is NULL or zero bytes long, bail out.
  if (!dst || !siz) return;

  // check if val is NAN
  if (OFMath::isnan(val))
  {
    OFStandard::strlcpy(dst, "nan", siz);
    return;
  }

  // check if val is infinity
  if (OFMath::isinf(val))
  {
    if (val < 0)
        OFStandard::strlcpy(dst, "-inf", siz);
        else OFStandard::strlcpy(dst, "inf", siz);
    return;
  }

  int fpprec = 0;     /* `extra' floating precision in [eEfgG] */
  char softsign = 0;  /* temporary negative sign for floats */
  char buf[FTOA_BUFSIZE];      /* space for %c, %[diouxX], %[eEfgG] */
  char sign = '\0';   /* sign prefix (' ', '+', '-', or \0) */
  int n;
  unsigned char fmtch = 'G';
  FTOAStringBuffer sb(FTOA_BUFSIZE+1);

  // determine format character
  if (flags & OFStandard::ftoa_uppercase)
  {
    if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_e) fmtch = 'E';
    else if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_f) fmtch = 'f'; // there is no uppercase for 'f'
    else fmtch = 'G';
  }
  else
  {
    if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_e) fmtch = 'e';
    else if ((flags & FTOA_FORMAT_MASK) == OFStandard::ftoa_format_f) fmtch = 'f';
    else fmtch = 'g';
  }

  // don't do unrealistic precision; just pad it with zeroes later,
  // so buffer size stays rational.
  if (prec > FTOA_MAXFRACT)
  {
    if ((fmtch != 'g' && fmtch != 'G') || (flags&OFStandard::ftoa_alternate)) fpprec = prec - FTOA_MAXFRACT;
    prec = FTOA_MAXFRACT;
  }
  else if (prec == -1) prec = FTOA_DEFPREC;
  else if (prec == -2) prec = 17;

  /*
   * softsign avoids negative 0 if val is < 0 and
   * no significant digits will be shown
   */
  if (val < 0)
  {
    softsign = '-';
    val = -val;
  }
  else softsign = 0;

  /*
   * ftoa_convert may have to round up past the "start" of the
   * buffer, i.e. ``intf("%.2f", (double)9.999);'';
   * if the first char isn't \0, it did.
   */
  *buf = 0;
  int size = ftoa_convert(val, prec, flags, &softsign, fmtch, buf, buf + sizeof(buf));
  if (softsign) sign = '-';
  char *t = *buf ? buf : buf + 1;

  /* At this point, `t' points to a string which (if not flags&OFStandard::ftoa_leftadj)
   * should be padded out to `width' places.  If flags&OFStandard::ftoa_zeropad, it should
   * first be prefixed by any sign or other prefix; otherwise, it should be
   * blank padded before the prefix is emitted.  After any left-hand
   * padding, print the string proper, then emit zeroes required by any
   * leftover floating precision; finally, if OFStandard::ftoa_leftadj, pad with blanks.
   *
   * compute actual size, so we know how much to pad
   */
  int fieldsz = size + fpprec;
  if (sign) fieldsz++;

  /* right-adjusting blank padding */
  if ((flags & (OFStandard::ftoa_leftadj|OFStandard::ftoa_zeropad)) == 0 && width)
  {
    for (n = fieldsz; n < width; n++) sb.put(' ');
  }

  /* prefix */
  if (sign) sb.put(sign);

  /* right-adjusting zero padding */
  if ((flags & (OFStandard::ftoa_leftadj|OFStandard::ftoa_zeropad)) == OFStandard::ftoa_zeropad)
          for (n = fieldsz; n < width; n++)
                  sb.put('0');

  /* the string or number proper */
  n = size;
  while (--n >= 0) sb.put(*t++);

  /* trailing f.p. zeroes */
  while (--fpprec >= 0) sb.put('0');

  /* left-adjusting padding (always blank) */
  if (flags & OFStandard::ftoa_leftadj)
          for (n = fieldsz; n < width; n++)
                  sb.put(' ');

  /* zero-terminate string */
  sb.put(0);

  /* copy result from char buffer to output array */
  const char *c = sb.getBuffer();
  if (c) OFStandard::strlcpy(dst, c, siz); else *dst = 0;
}

#endif /* ENABLE_OLD_OFSTD_FTOA_IMPLEMENTATION */


unsigned int OFStandard::my_sleep(unsigned int seconds)
{
#ifdef HAVE_WINDOWS_H
  // on Win32 we use the Sleep() system call which expects milliseconds
  Sleep(1000*seconds);
  return 0;
#elif defined(HAVE_SLEEP)
  // just use the original sleep() system call
  return sleep(seconds);
#elif defined(HAVE_USLEEP)
  // usleep() expects microseconds
  (void) usleep(OFstatic_cast(unsigned long, seconds)*1000000UL);
  return 0;
#else
  // don't know how to sleep
  return 0;
#endif
}

void OFStandard::milliSleep(unsigned int millisecs)
{
#ifdef HAVE_WINDOWS_H
  // on Win32 we use the Sleep() system call which expects milliseconds
    Sleep(millisecs);
#elif defined(HAVE_USLEEP)
    // usleep() expects microseconds
    (void) usleep(OFstatic_cast(useconds_t, millisecs * 1000UL));
#else
    struct timeval t;
    t.tv_sec = millisecs / 1000;
    t.tv_usec = (millisecs % 1000) * 1000;
    select(0, NULL, NULL, NULL, &t);
#endif
}


long OFStandard::getProcessID()
{
#ifdef _WIN32
  return _getpid();
#elif defined(HAVE_GETPID)
  return getpid();
#else
  return 0; // Workaround for MAC
#endif
}

const unsigned int OFrandr_max = 0x7fffffff;

int OFrand_r(unsigned int &seed)
{
  unsigned long val = OFstatic_cast(unsigned long, seed);
  val = val * 1103515245 + 12345;
  seed = OFstatic_cast(unsigned int, val %(OFstatic_cast(unsigned long, 0x80000000)));
  return OFstatic_cast(int, seed);
}

void OFStandard::trimString(const char*& pBegin, const char*& pEnd)
{
  assert(pBegin <= pEnd);
  while(pBegin != pEnd && (*pBegin == ' ' || !*pBegin))
    ++pBegin;
  while(pBegin != pEnd && (*(pEnd-1) == ' ' || !*(pEnd-1)))
    --pEnd;
}

void OFStandard::trimString( const char*& str, size_t& size )
{
    const char* end = str + size;
    trimString( str, end );
    size = end - str;
}

#define MAX_NAME 65536

#ifdef HAVE_GETHOSTBYNAME_R
#ifndef HAVE_PROTOTYPE_GETHOSTBYNAME_R
extern "C" {
    int gethostbyname_r(const char *name, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
}
#endif
#endif

#ifdef HAVE_GETHOSTBYADDR_R
#ifndef HAVE_PROTOTYPE_GETHOSTBYADDR_R
extern "C" {
    int gethostbyaddr_r(const void *addr, socklen_t len, int type, struct hostent *ret, char *buf, size_t buflen, struct hostent **result, int *h_errnop);
}
#endif
#endif

OFString OFStandard::getHostnameByAddress(const char* addr, int len, int type)
{
  OFString result;

  // We have getaddrinfo(). In this case we also presume that we have
  // getnameinfo(), since both functions were introduced together.
  // This is the preferred implementation, being thread-safe and protocol independent.
  OFSockAddr sas;

  // a DNS name must be shorter than 256 characters, so this should be enough
  char hostname[512];
  hostname[0] = '\0';
  socklen_t nameinfo_len;

  if (type == AF_INET)
  {
    if (len != sizeof(struct in_addr)) return result; // invalid address length
    struct sockaddr_in *sa4 = sas.getSockaddr_in();
    sa4->sin_family = AF_INET;
    memcpy(&sa4->sin_addr, addr, len);
    nameinfo_len = sizeof(struct sockaddr_in);
  }
  else if (type == AF_INET6)
  {
    if (len != sizeof(struct in6_addr)) return result; // invalid address length
    struct sockaddr_in6 *sa6 = sas.getSockaddr_in6();
    sa6->sin6_family = AF_INET6;
    memcpy(&sa6->sin6_addr, addr, len);
    nameinfo_len = sizeof(struct sockaddr_in6);
  }
  else return result; // unknown network type, not supported by getnameinfo()

  int err = EAI_AGAIN;
  int rep = DCMTK_MAX_EAI_AGAIN_REPETITIONS;
  struct sockaddr *sa = sas.getSockaddr();

  // perform reverse DNS lookup. Repeat while we receive temporary failures.
  while ((EAI_AGAIN == err) && (rep-- > 0)) err = getnameinfo(sa, nameinfo_len, hostname, 512, NULL, 0, 0);
  if ((err == 0) && (hostname[0] != '\0')) result = hostname;

  return result;
}


void OFStandard::getAddressByHostname(const char *name, int protocolFamily, OFSockAddr& result)
{
  result.clear();
  if (NULL == name) return;

  struct addrinfo *result_list = NULL;
  int err = EAI_AGAIN;
  int rep = DCMTK_MAX_EAI_AGAIN_REPETITIONS;

  // filter for the DNS lookup
  ::addrinfo hint = {};
  hint.ai_family = protocolFamily;

  // perform DNS lookup. Repeat while we receive temporary failures.
  while ((EAI_AGAIN == err) && (rep-- > 0)) err = getaddrinfo(name, NULL, &hint, &result_list);

  if (0 == err)
  {
    if (result_list && result_list->ai_addr)
    {
      // DNS lookup successfully completed.
      struct sockaddr *result_sa = result.getSockaddr();
      memcpy(result_sa, result_list->ai_addr, result_list->ai_addrlen);
    }
    freeaddrinfo(result_list);
  }
}



#ifdef HAVE_GRP_H
OFStandard::OFGroup OFStandard::getGrNam( const char* name )
{
#ifdef HAVE_GETGRNAM_R
    unsigned size = 32;
    char* tmp = new char[size];
    group* res = NULL;
    group buf;
    while( getgrnam_r( name, &buf, tmp, size, &res ) == ERANGE )
    {
        delete[] tmp;
        if( size >= MAX_NAME )
            return NULL;
        tmp = new char[size*=2];
    }
    OFGroup g( res );
    delete[] tmp;
    return g;
#elif defined HAVE_GETGRNAM
    return OFGroup( getgrnam( name ) );
#else
    return OFGroup( NULL );
#endif
}
#endif // HAVE_GRP_H

#ifdef HAVE_PWD_H
OFStandard::OFPasswd OFStandard::getPwNam( const char* name )
{
#ifdef HAVE_GETPWNAM_R
    unsigned size = 32;
    char* tmp = new char[size];
    passwd* res = NULL;
    passwd buf;
    while( getpwnam_r( name, &buf, tmp, size, &res ) == ERANGE )
    {
        delete[] tmp;
        if( size >= MAX_NAME )
            return NULL;
        tmp = new char[size*=2];
    }
    OFPasswd p( res );
    delete[] tmp;
    return p;
#elif defined HAVE_GETPWNAM
    return OFPasswd( getpwnam( name ) );
#else
    return OFPasswd( NULL );
#endif
}
#endif // HAVE_PWD_H

#ifdef HAVE_GRP_H
OFStandard::OFGroup::OFGroup()
: gr_name()
, gr_passwd()
, gr_mem()
, gr_gid()
, ok( OFFalse )
{
}

OFStandard::OFGroup::OFGroup( group* const g )
: gr_name()
, gr_passwd()
, gr_mem()
, gr_gid()
, ok( g != NULL )
{
    if( ok )
    {
        gr_name   = g->gr_name;
        gr_passwd = g->gr_passwd;
        gr_gid    = g->gr_gid;
        for( char** m = g->gr_mem; *m; ++m )
            gr_mem.push_back( *m );
    }
}

OFBool OFStandard::OFGroup::operator!() const { return !ok; }
OFStandard::OFGroup::operator OFBool() const { return ok; }

#endif // #ifdef HAVE_GRP_H

#ifdef HAVE_PWD_H
OFStandard::OFPasswd::OFPasswd()
: pw_name()
, pw_passwd()
, pw_gecos()
, pw_dir()
, pw_shell()
, pw_uid()
, pw_gid()
, ok( OFFalse )
{
}

OFStandard::OFPasswd::OFPasswd( passwd* const p )
: pw_name()
, pw_passwd()
, pw_gecos()
, pw_dir()
, pw_shell()
, pw_uid()
, pw_gid()
, ok( p != NULL )
{
    if( ok )
    {
        pw_name   = p->pw_name;
        pw_passwd = p->pw_passwd;
        pw_uid    = p->pw_uid;
        pw_gid    = p->pw_gid;
#ifdef HAVE_PASSWD_GECOS
        pw_gecos  = p->pw_gecos;
#endif
        pw_dir    = p->pw_dir;
        pw_shell  = p->pw_shell;
    }
}

OFBool OFStandard::OFPasswd::operator!() const { return !ok; }
OFStandard::OFPasswd::operator OFBool() const { return ok; }

#endif // HAVE_PWD_H

OFCondition OFStandard::dropPrivileges()
{
#if defined(HAVE_SETUID) && defined(HAVE_GETUID)
  if ((setuid(getuid()) != 0) && (errno != EPERM))
  {
    /* setuid returning nonzero means that the setuid() operation has failed.
     * An errno code of EPERM means that the application was never running with root
     * privileges, i.e. was not installed with setuid root, which is safe and harmless.
     * Other error codes (in particular EAGAIN) signal a problem. Most likely the
     * calling user has already reached the maximum number of permitted processes.
     * In this case the application should rather terminate than continue with
     * full root privileges.
     */
    return EC_setuidFailed;
  }
#endif
  return EC_Normal;
}


#ifndef HAVE_CXX11
DCMTK_OFSTD_EXPORT OFnullptr_t OFnullptr;
DCMTK_OFSTD_EXPORT OFnullopt_t OFnullopt;
#endif


#ifndef HAVE_STL_TUPLE
static const OFignore_t OFignore_value;
DCMTK_OFSTD_EXPORT const OFignore_t& OFignore = OFignore_value;
OFtuple<> OFmake_tuple() { return OFtuple<>(); }
OFtuple<> OFtie() { return OFtuple<>(); }
#endif


OFString OFStandard::getUserName()
{
#ifdef _WIN32
    WKSTA_USER_INFO_0 *userinfo;
    if( NetWkstaUserGetInfo( OFnullptr, 0, OFreinterpret_cast( LPBYTE*, &userinfo ) ) != NERR_Success )
        return "<no-user-information-available>";
    // Convert the Unicode full name to ANSI.
    const WCHAR* const name = OFstatic_cast( WCHAR*, userinfo->wkui0_username );
    OFVector<char> buf( wcslen( name ) * 2 );
    WideCharToMultiByte
    (
        CP_ACP,
        0,
        name,
        -1,
        &*buf.begin(),
        OFstatic_cast(int, buf.size()),
        OFnullptr,
        OFnullptr
    );
    return &*buf.begin();
#elif defined(HAVE_GETLOGIN_R)
    // use getlogin_r instead of getlogin
    char buf[513];
    if( getlogin_r( buf, 512 ) != 0 )
        return "<no-utmp-entry>";
    buf[512] = 0;
    return buf;
#elif defined(HAVE_GETLOGIN)
    // thread unsafe
    if( const char* s = getlogin() )
        return s;
    return "<no-utmp-entry>";
#elif defined(HAVE_CUSERID)
    char buf[L_cuserid];
    return cuserid( buf );
#else
    return "<unknown-user>";
#endif
}

OFString OFStandard::getHostName()
{
#ifdef HAVE_UNAME
    struct utsname n;
    uname( &n );
    return n.nodename;
#else
    char buf[513];
    gethostname( buf, 512 );
    buf[512] = 0;
    return buf;
#endif
}

void OFStandard::initializeNetwork()
{
#ifdef HAVE_WINSOCK_H
    WSAData winSockData;
    /* we need at least version 1.1 */
    WORD winSockVersionNeeded = MAKEWORD( 1, 1 );
    WSAStartup(winSockVersionNeeded, &winSockData);
#endif
}

void OFStandard::shutdownNetwork()
{
#ifdef HAVE_WINSOCK_H
    WSACleanup();
#endif
}

OFerror_code OFStandard::getLastSystemErrorCode()
{
#ifdef _WIN32
    return OFerror_code( GetLastError(), OFsystem_category() );
#else
    return OFerror_code( errno, OFsystem_category() );
#endif
}

OFerror_code OFStandard::getLastNetworkErrorCode()
{
#ifdef HAVE_WINSOCK_H
    return OFerror_code( WSAGetLastError(), OFsystem_category() );
#else
    return OFerror_code( errno, OFsystem_category() );
#endif
}


void OFStandard::forceSleep(Uint32 seconds)
{
    OFTimer timer;
    double elapsed = timer.getDiff();
    while (elapsed < OFstatic_cast(double, seconds))
    {
        // Use ceiling since otherwise we could wait too short
        OFStandard::sleep(OFstatic_cast(unsigned int, ceil(seconds - elapsed)));
        elapsed = timer.getDiff();
    }
}


void OFStandard::sanitizeFilename(OFString& fname)
{
    const size_t len = fname.length();
    for (size_t i = 0; i < len; ++i)
    {
#ifdef _WIN32
        if ((fname[i] == PATH_SEPARATOR) || (fname[i] == '/')) fname[i] = '_';
#else
        if (fname[i] == PATH_SEPARATOR) fname[i] = '_';
#endif
    }
}


void OFStandard::sanitizeFilename(char *fname)
{
    if (fname)
    {
        char *c = fname;
        while (*c)
        {
#ifdef _WIN32
            if ((*c == PATH_SEPARATOR) || (*c == '/')) *c = '_';
#else
            if (*c == PATH_SEPARATOR) *c = '_';
#endif
            ++c;
        }
    }
}


OFString OFStandard::getDefaultSupportDataDir()
{
#ifdef HAVE_WINDOWS_H
  char buf[MAX_PATH+1];
  memset(buf, 0, sizeof(buf));
  (void) ExpandEnvironmentStringsA(DEFAULT_SUPPORT_DATA_DIR, buf, sizeof(buf));
  return buf;
#else
  return DEFAULT_SUPPORT_DATA_DIR;
#endif
}


OFString OFStandard::getDefaultConfigurationDir()
{
#ifdef HAVE_WINDOWS_H
  char buf[MAX_PATH+1];
  memset(buf, 0, sizeof(buf));
  (void) ExpandEnvironmentStringsA(DEFAULT_CONFIGURATION_DIR, buf, sizeof(buf));
  return buf;
#else
  return DEFAULT_CONFIGURATION_DIR;
#endif
}


bool OFStandard::isspace(char ch)
{
    // This matches every whitespace character in the default locale,
    // as documented in https://en.cppreference.com/w/cpp/string/byte/isspace
    switch (ch)
    {
      case ' ':
      case '\f':
      case '\n':
      case '\r':
      case '\t':
      case '\v':
        return true;
      default:
        return false;
    }
}

#include DCMTK_DIAGNOSTIC_IGNORE_STRICT_ALIASING_WARNING

// black magic:
// The C++ standard says that std::in_place should not be called as a function,
// but the linker says we still need a function body. Normally, we would mark
// it as [[noreturn]] and be done, but that's not available pre C++11.
// Therefore, we need a return statement to silence 'missing return statement...'
// style warnings. However, OFin_place_tag is a forward declared struct with
// no actual definition, so, we cannot return an actual OFin_place_tag object.
// Instead, we cast some pointer to it although that is actually bullshit, but
// the code will never be executed anyway. Prior versions of this code returned
// a casted nullptr, but some compilers are just too smart and return a warning
// for that, so, now we cast a pointer to OFnullptr into an OFin_place_tag
// instead to silence the warnings.
DCMTK_OFSTD_EXPORT OFin_place_tag OFin_place() { return *reinterpret_cast<const OFin_place_tag*>(&OFnullptr); }
