/*
  This file is part of libmicrohttpd
  Copyright (C) 2014-2024 Karlson2k (Evgeny Grin)

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/
/**
 * @file microhttpd/mhd_sockets.c
 * @brief  Implementation for sockets functions
 * @author Karlson2k (Evgeny Grin)
 */
#include "mhd_sockets.h"

#ifdef MHD_WINSOCK_SOCKETS

/**
 * Return pointer to string description of specified WinSock error
 * @param err the WinSock error code.
 * @return pointer to string description of specified WinSock error.
 */
const char *
MHD_W32_strerror_winsock_ (int err)
{
  switch (err)
  {
  case 0:
    return "No error";
  case WSA_INVALID_HANDLE:
    return "Specified event object handle is invalid";
  case WSA_NOT_ENOUGH_MEMORY:
    return "Insufficient memory available";
  case WSA_INVALID_PARAMETER:
    return "One or more parameters are invalid";
  case WSA_OPERATION_ABORTED:
    return "Overlapped operation aborted";
  case WSA_IO_INCOMPLETE:
    return "Overlapped I/O event object not in signaled state";
  case WSA_IO_PENDING:
    return "Overlapped operations will complete later";
  case WSAEINTR:
    return "Interrupted function call";
  case WSAEBADF:
    return "File handle is not valid";
  case WSAEACCES:
    return "Permission denied";
  case WSAEFAULT:
    return "Bad address";
  case WSAEINVAL:
    return "Invalid argument";
  case WSAEMFILE:
    return "Too many open files";
  case WSAEWOULDBLOCK:
    return "Resource temporarily unavailable";
  case WSAEINPROGRESS:
    return "Operation now in progress";
  case WSAEALREADY:
    return "Operation already in progress";
  case WSAENOTSOCK:
    return "Socket operation on nonsocket";
  case WSAEDESTADDRREQ:
    return "Destination address required";
  case WSAEMSGSIZE:
    return "Message too long";
  case WSAEPROTOTYPE:
    return "Protocol wrong type for socket";
  case WSAENOPROTOOPT:
    return "Bad protocol option";
  case WSAEPROTONOSUPPORT:
    return "Protocol not supported";
  case WSAESOCKTNOSUPPORT:
    return "Socket type not supported";
  case WSAEOPNOTSUPP:
    return "Operation not supported";
  case WSAEPFNOSUPPORT:
    return "Protocol family not supported";
  case WSAEAFNOSUPPORT:
    return "Address family not supported by protocol family";
  case WSAEADDRINUSE:
    return "Address already in use";
  case WSAEADDRNOTAVAIL:
    return "Cannot assign requested address";
  case WSAENETDOWN:
    return "Network is down";
  case WSAENETUNREACH:
    return "Network is unreachable";
  case WSAENETRESET:
    return "Network dropped connection on reset";
  case WSAECONNABORTED:
    return "Software caused connection abort";
  case WSAECONNRESET:
    return "Connection reset by peer";
  case WSAENOBUFS:
    return "No buffer space available";
  case WSAEISCONN:
    return "Socket is already connected";
  case WSAENOTCONN:
    return "Socket is not connected";
  case WSAESHUTDOWN:
    return "Cannot send after socket shutdown";
  case WSAETOOMANYREFS:
    return "Too many references";
  case WSAETIMEDOUT:
    return "Connection timed out";
  case WSAECONNREFUSED:
    return "Connection refused";
  case WSAELOOP:
    return "Cannot translate name";
  case WSAENAMETOOLONG:
    return "Name too long";
  case WSAEHOSTDOWN:
    return "Host is down";
  case WSAEHOSTUNREACH:
    return "No route to host";
  case WSAENOTEMPTY:
    return "Directory not empty";
  case WSAEPROCLIM:
    return "Too many processes";
  case WSAEUSERS:
    return "User quota exceeded";
  case WSAEDQUOT:
    return "Disk quota exceeded";
  case WSAESTALE:
    return "Stale file handle reference";
  case WSAEREMOTE:
    return "Item is remote";
  case WSASYSNOTREADY:
    return "Network subsystem is unavailable";
  case WSAVERNOTSUPPORTED:
    return "Winsock.dll version out of range";
  case WSANOTINITIALISED:
    return "Successful WSAStartup not yet performed";
  case WSAEDISCON:
    return "Graceful shutdown in progress";
  case WSAENOMORE:
    return "No more results";
  case WSAECANCELLED:
    return "Call has been canceled";
  case WSAEINVALIDPROCTABLE:
    return "Procedure call table is invalid";
  case WSAEINVALIDPROVIDER:
    return "Service provider is invalid";
  case WSAEPROVIDERFAILEDINIT:
    return "Service provider failed to initialize";
  case WSASYSCALLFAILURE:
    return "System call failure";
  case WSASERVICE_NOT_FOUND:
    return "Service not found";
  case WSATYPE_NOT_FOUND:
    return "Class type not found";
  case WSA_E_NO_MORE:
    return "No more results";
  case WSA_E_CANCELLED:
    return "Call was canceled";
  case WSAEREFUSED:
    return "Database query was refused";
  case WSAHOST_NOT_FOUND:
    return "Host not found";
  case WSATRY_AGAIN:
    return "Nonauthoritative host not found";
  case WSANO_RECOVERY:
    return "This is a nonrecoverable error";
  case WSANO_DATA:
    return "Valid name, no data record of requested type";
  case WSA_QOS_RECEIVERS:
    return "QoS receivers";
  case WSA_QOS_SENDERS:
    return "QoS senders";
  case WSA_QOS_NO_SENDERS:
    return "No QoS senders";
  case WSA_QOS_NO_RECEIVERS:
    return "QoS no receivers";
  case WSA_QOS_REQUEST_CONFIRMED:
    return "QoS request confirmed";
  case WSA_QOS_ADMISSION_FAILURE:
    return "QoS admission error";
  case WSA_QOS_POLICY_FAILURE:
    return "QoS policy failure";
  case WSA_QOS_BAD_STYLE:
    return "QoS bad style";
  case WSA_QOS_BAD_OBJECT:
    return "QoS bad object";
  case WSA_QOS_TRAFFIC_CTRL_ERROR:
    return "QoS traffic control error";
  case WSA_QOS_GENERIC_ERROR:
    return "QoS generic error";
  case WSA_QOS_ESERVICETYPE:
    return "QoS service type error";
  case WSA_QOS_EFLOWSPEC:
    return "QoS flowspec error";
  case WSA_QOS_EPROVSPECBUF:
    return "Invalid QoS provider buffer";
  case WSA_QOS_EFILTERSTYLE:
    return "Invalid QoS filter style";
  case WSA_QOS_EFILTERTYPE:
    return "Invalid QoS filter type";
  case WSA_QOS_EFILTERCOUNT:
    return "Incorrect QoS filter count";
  case WSA_QOS_EOBJLENGTH:
    return "Invalid QoS object length";
  case WSA_QOS_EFLOWCOUNT:
    return "Incorrect QoS flow count";
  case WSA_QOS_EUNKOWNPSOBJ:
    return "Unrecognized QoS object";
  case WSA_QOS_EPOLICYOBJ:
    return "Invalid QoS policy object";
  case WSA_QOS_EFLOWDESC:
    return "Invalid QoS flow descriptor";
  case WSA_QOS_EPSFLOWSPEC:
    return "Invalid QoS provider-specific flowspec";
  case WSA_QOS_EPSFILTERSPEC:
    return "Invalid QoS provider-specific filterspec";
  case WSA_QOS_ESDMODEOBJ:
    return "Invalid QoS shape discard mode object";
  case WSA_QOS_ESHAPERATEOBJ:
    return "Invalid QoS shaping rate object";
  case WSA_QOS_RESERVED_PETYPE:
    return "Reserved policy QoS element type";
  }
  return "Unknown winsock error";
}


/**
 * Create pair of mutually connected TCP/IP sockets on loopback address
 * @param sockets_pair array to receive resulted sockets
 * @param non_blk if set to non-zero value, sockets created in non-blocking mode
 *                otherwise sockets will be in blocking mode
 * @return non-zero if succeeded, zero otherwise
 */
int
MHD_W32_socket_pair_ (SOCKET sockets_pair[2], int non_blk)
{
  int i;

  if (! sockets_pair)
  {
    WSASetLastError (WSAEFAULT);
    return 0;
  }

#define PAIRMAXTRYIES 800
  for (i = 0; i < PAIRMAXTRYIES; i++)
  {
    struct sockaddr_in listen_addr;
    SOCKET listen_s;
    static const int c_addinlen = sizeof(struct sockaddr_in);   /* help compiler to optimize */
    int addr_len = c_addinlen;
    unsigned long on_val = 1;
    unsigned long off_val = 0;

    listen_s = socket (AF_INET,
                       SOCK_STREAM,
                       IPPROTO_TCP);
    if (INVALID_SOCKET == listen_s)
      break;   /* can't create even single socket */

    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = 0;   /* same as htons(0) */
    listen_addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
    if ( ((0 == bind (listen_s,
                      (struct sockaddr *) &listen_addr,
                      c_addinlen)) &&
          (0 == listen (listen_s,
                        1) ) &&
          (0 == getsockname (listen_s,
                             (struct sockaddr *) &listen_addr,
                             &addr_len))) )
    {
      SOCKET client_s = socket (AF_INET,
                                SOCK_STREAM,
                                IPPROTO_TCP);
      struct sockaddr_in accepted_from_addr;
      struct sockaddr_in client_addr;
      SOCKET server_s;

      if (INVALID_SOCKET == client_s)
      {
        /* try again */
        closesocket (listen_s);
        continue;
      }

      if ( (0 != ioctlsocket (client_s,
                              (int) FIONBIO,
                              &on_val)) ||
           ( (0 != connect (client_s,
                            (struct sockaddr *) &listen_addr,
                            c_addinlen)) &&
             (WSAGetLastError () != WSAEWOULDBLOCK)) )
      {
        /* try again */
        closesocket (listen_s);
        closesocket (client_s);
        continue;
      }

      addr_len = c_addinlen;
      server_s = accept (listen_s,
                         (struct sockaddr *) &accepted_from_addr,
                         &addr_len);
      if (INVALID_SOCKET == server_s)
      {
        /* try again */
        closesocket (listen_s);
        closesocket (client_s);
        continue;
      }

      addr_len = c_addinlen;
      if ( (0 == getsockname (client_s,
                              (struct sockaddr *) &client_addr,
                              &addr_len)) &&
           (accepted_from_addr.sin_port == client_addr.sin_port) &&
           (accepted_from_addr.sin_addr.s_addr ==
            client_addr.sin_addr.s_addr) &&
           (accepted_from_addr.sin_family == client_addr.sin_family) &&
           ( (0 != non_blk) ?
             (0 == ioctlsocket (server_s,
                                (int) FIONBIO,
                                &on_val)) :
             (0 == ioctlsocket (client_s,
                                (int) FIONBIO,
                                &off_val)) ) &&
           (0 == setsockopt (server_s,
                             IPPROTO_TCP,
                             TCP_NODELAY,
                             (const void *) (&on_val),
                             sizeof (on_val))) &&
           (0 == setsockopt (client_s,
                             IPPROTO_TCP,
                             TCP_NODELAY,
                             (const void *) (&on_val),
                             sizeof (on_val))) )
      {
        closesocket (listen_s);
        sockets_pair[0] = server_s;
        sockets_pair[1] = client_s;
        return ! 0;
      }
      closesocket (server_s);
      closesocket (client_s);
    }
    closesocket (listen_s);
  }

  sockets_pair[0] = INVALID_SOCKET;
  sockets_pair[1] = INVALID_SOCKET;
  WSASetLastError (WSAECONNREFUSED);

  return 0;
}


#endif /* MHD_WINSOCK_SOCKETS */


/**
 * Add @a fd to the @a set.  If @a fd is
 * greater than @a max_fd, set @a max_fd to @a fd.
 *
 * @param fd file descriptor to add to the @a set
 * @param set set to modify
 * @param max_fd maximum value to potentially update
 * @param fd_setsize value of FD_SETSIZE
 * @return non-zero if succeeded, zero otherwise
 */
int
MHD_add_to_fd_set_ (MHD_socket fd,
                    fd_set *set,
                    MHD_socket *max_fd,
                    int fd_setsize)
{
  if ( (NULL == set) ||
       (MHD_INVALID_SOCKET == fd) )
    return 0;

#ifndef HAS_FD_SETSIZE_OVERRIDABLE
  (void) fd_setsize;  /* Mute compiler warning */
  fd_setsize = (int) FD_SETSIZE; /* Help compiler to optimise */
#endif /* ! HAS_FD_SETSIZE_OVERRIDABLE */

  if (! MHD_SCKT_FD_FITS_FDSET_SETSIZE_ (fd,
                                         set,
                                         fd_setsize))
    return 0;
  MHD_SCKT_ADD_FD_TO_FDSET_SETSIZE_ (fd,
                                     set,
                                     fd_setsize);
  if ( (NULL != max_fd) &&
       ( (fd > *max_fd) ||
         (MHD_INVALID_SOCKET == *max_fd) ) )
    *max_fd = fd;
  return ! 0;
}


/**
 * Change socket options to be non-blocking.
 *
 * @param sock socket to manipulate
 * @return non-zero if succeeded, zero otherwise
 */
int
MHD_socket_nonblocking_ (MHD_socket sock)
{
#if defined(MHD_POSIX_SOCKETS)
  int flags;

  flags = fcntl (sock,
                 F_GETFL);
  if (-1 == flags)
    return 0;

  if ( ((flags | O_NONBLOCK) != flags) &&
       (0 != fcntl (sock,
                    F_SETFL,
                    flags | O_NONBLOCK)) )
    return 0;
#elif defined(MHD_WINSOCK_SOCKETS)
  unsigned long flags = 1;

  if (0 != ioctlsocket (sock,
                        (int) FIONBIO,
                        &flags))
    return 0;
#endif /* MHD_WINSOCK_SOCKETS */
  return ! 0;
}


/**
 * Change socket options to be non-inheritable.
 *
 * @param sock socket to manipulate
 * @return non-zero if succeeded, zero otherwise
 * @warning Does not set socket error on W32.
 */
int
MHD_socket_noninheritable_ (MHD_socket sock)
{
#if defined(MHD_POSIX_SOCKETS)
  int flags;

  flags = fcntl (sock,
                 F_GETFD);
  if (-1 == flags)
    return 0;

  if ( ((flags | FD_CLOEXEC) != flags) &&
       (0 != fcntl (sock,
                    F_SETFD,
                    flags | FD_CLOEXEC)) )
    return 0;
#elif defined(MHD_WINSOCK_SOCKETS)
  if (! SetHandleInformation ((HANDLE) sock,
                              HANDLE_FLAG_INHERIT,
                              0))
    return 0;
#endif /* MHD_WINSOCK_SOCKETS */
  return ! 0;
}


/**
 * Disable Nagle's algorithm on @a sock.  This is what we do by default for
 * all TCP sockets in MHD, unless the platform does not support the MSG_MORE
 * or MSG_CORK or MSG_NOPUSH options.
 *
 * @param sock socket to manipulate
 * @param on value to use
 * @return 0 on success
 */
int
MHD_socket_set_nodelay_ (MHD_socket sock,
                         bool on)
{
#ifdef TCP_NODELAY
  {
    const MHD_SCKT_OPT_BOOL_ off_val = 0;
    const MHD_SCKT_OPT_BOOL_ on_val = 1;
    /* Disable Nagle's algorithm for normal buffering */
    return setsockopt (sock,
                       IPPROTO_TCP,
                       TCP_NODELAY,
                       (const void *) ((on) ? &on_val : &off_val),
                       sizeof (on_val));
  }
#else
  (void) sock;
  return 0;
#endif /* TCP_NODELAY */
}


/**
 * Create a listen socket, with noninheritable flag if possible.
 *
 * @param pf protocol family to use
 * @return created socket or MHD_INVALID_SOCKET in case of errors
 */
MHD_socket
MHD_socket_create_listen_ (int pf)
{
  MHD_socket fd;
  int cloexec_set;
#if defined(SOCK_NOSIGPIPE) || defined(MHD_socket_nosignal_)
  int nosigpipe_set;
#endif /* SOCK_NOSIGPIPE ||  MHD_socket_nosignal_ */

#if defined(MHD_POSIX_SOCKETS) && (defined(SOCK_CLOEXEC) || \
  defined(SOCK_NOSIGPIPE) )

  fd = socket (pf,
               SOCK_STREAM | SOCK_CLOEXEC | SOCK_NOSIGPIPE_OR_ZERO,
               0);
  cloexec_set = (SOCK_CLOEXEC_OR_ZERO != 0);
#if defined(SOCK_NOSIGPIPE) || defined(MHD_socket_nosignal_)
  nosigpipe_set = (SOCK_NOSIGPIPE_OR_ZERO != 0);
#endif /* SOCK_NOSIGPIPE ||  MHD_socket_nosignal_ */
#elif defined(MHD_WINSOCK_SOCKETS) && defined(WSA_FLAG_NO_HANDLE_INHERIT)
  fd = WSASocketW (pf,
                   SOCK_STREAM,
                   0,
                   NULL,
                   0,
                   WSA_FLAG_OVERLAPPED | WSA_FLAG_NO_HANDLE_INHERIT);
  cloexec_set = ! 0;
#else  /* No special socket init function / flags */
  fd = MHD_INVALID_SOCKET;
  cloexec_set = 0;
#if defined(SOCK_NOSIGPIPE) || defined(MHD_socket_nosignal_)
  nosigpipe_set = 0;
#endif /* SOCK_NOSIGPIPE ||  MHD_socket_nosignal_ */
#endif /* No special socket init function / flags */
  if (MHD_INVALID_SOCKET == fd)
  {
    fd = socket (pf,
                 SOCK_STREAM,
                 0);
    cloexec_set = 0;
#if defined(SOCK_NOSIGPIPE) || defined(MHD_socket_nosignal_)
    nosigpipe_set = 0;
#endif /* SOCK_NOSIGPIPE ||  MHD_socket_nosignal_ */
  }
  if (MHD_INVALID_SOCKET == fd)
    return MHD_INVALID_SOCKET;

#if defined(MHD_socket_nosignal_)
  if ( (! nosigpipe_set) &&
       (0 == MHD_socket_nosignal_ (fd)) &&
       (0 == MSG_NOSIGNAL_OR_ZERO) )
  {
    /* SIGPIPE disable is possible on this platform
     * (so application expect that it will be disabled),
     * but failed to be disabled here and it is not
     * possible to disable SIGPIPE by MSG_NOSIGNAL. */
    const int err = MHD_socket_get_error_ ();
    (void) MHD_socket_close_ (fd);
    MHD_socket_fset_error_ (err);
    return MHD_INVALID_SOCKET;
  }
#endif /* defined(MHD_socket_nosignal_) */
  if (! cloexec_set)
    (void) MHD_socket_noninheritable_ (fd);

  return fd;
}
