qse/qse/lib/net/httpd-std.c

2470 lines
58 KiB
C

/*
* $Id$
*
Copyright 2006-2012 Chung, Hyung-Hwan.
This file is part of QSE.
QSE is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
QSE 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 QSE. If not, see <htrd://www.gnu.org/licenses/>.
*/
#include "httpd.h"
#include "../cmn/mem.h"
#include <qse/cmn/hton.h>
#include <qse/cmn/nwif.h>
#include <qse/cmn/mbwc.h>
#include <qse/cmn/str.h>
#include <qse/cmn/uri.h>
#include <qse/cmn/alg.h>
#include <qse/cmn/fmt.h>
#include <qse/cmn/path.h>
#include <qse/cmn/mux.h>
#include <qse/cmn/dir.h>
#include <qse/cmn/fio.h>
#if defined(_WIN32)
# include <winsock2.h>
# include <ws2tcpip.h> /* sockaddr_in6 */
# include <windows.h>
# define EPOCH_DIFF_YEARS (QSE_EPOCH_YEAR-QSE_EPOCH_YEAR_WIN)
# define EPOCH_DIFF_DAYS ((qse_long_t)EPOCH_DIFF_YEARS*365+EPOCH_DIFF_YEARS/4-3)
# define EPOCH_DIFF_SECS ((qse_long_t)EPOCH_DIFF_DAYS*24*60*60)
#elif defined(__OS2__)
# include <types.h>
# include <sys/socket.h>
# include <netinet/in.h>
# include <sys/ioctl.h>
# include <nerrno.h>
# if defined(TCPV40HDRS)
# include <sys/select.h>
# else
# include <unistd.h>
# endif
# define INCL_DOSERRORS
# define INCL_DOSFILEMGR
# include <os2.h>
#elif defined(__DOS__)
/* TODO */
#else
# include "../cmn/syscall.h"
# include <sys/socket.h>
# include <netinet/in.h>
# if defined(HAVE_SYS_SENDFILE_H)
# include <sys/sendfile.h>
# endif
# if defined(HAVE_SYS_EPOLL_H)
# include <sys/epoll.h>
# endif
# if defined(__linux__)
# include <limits.h>
# include <linux/netfilter_ipv4.h> /* SO_ORIGINAL_DST */
# if !defined(IP_TRANSPARENT)
# define IP_TRANSPARENT 19
# endif
# endif
#endif
#if defined(HAVE_SSL)
# include <openssl/ssl.h>
# include <openssl/err.h>
# include <openssl/engine.h>
#endif
#include <qse/cmn/stdio.h> /* TODO: remove this */
#define DEFAULT_PORT 80
#define DEFAULT_SECURE_PORT 443
enum server_xtn_cfg_id_t
{
SERVER_XTN_CFG_DOCROOT = 0,
SERVER_XTN_CFG_REALM,
SERVER_XTN_CFG_AUTH, /* basic auth */
SERVER_XTN_CFG_DIRCSS, /* can't be too long due to internal buffer size */
SERVER_XTN_CFG_ERRCSS,
SERVER_XTN_CFG_MAX
};
typedef struct server_xtn_t server_xtn_t;
struct server_xtn_t
{
qse_mchar_t* cfg[SERVER_XTN_CFG_MAX];
union
{
void* a[4];
struct
{
qse_httpd_server_cbstd_t* cbstd;
qse_httpd_server_cgistd_t* cgistd;
qse_httpd_server_mimestd_t* mimestd;
qse_httpd_server_idxstd_t* idxstd;
} s;
} cfg2;
/* private */
qse_httpd_server_predetach_t predetach;
};
/* ------------------------------------------------------------------- */
#if defined(_WIN32)
static qse_httpd_errnum_t syserr_to_errnum (DWORD e)
{
switch (e)
{
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_OUTOFMEMORY:
return QSE_HTTPD_ENOMEM;
case ERROR_INVALID_PARAMETER:
case ERROR_INVALID_HANDLE:
case ERROR_INVALID_NAME:
return QSE_HTTPD_EINVAL;
case ERROR_ACCESS_DENIED:
case ERROR_SHARING_VIOLATION:
return QSE_HTTPD_EACCES;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
return QSE_HTTPD_ENOENT;
case ERROR_ALREADY_EXISTS:
case ERROR_FILE_EXISTS:
return QSE_HTTPD_EEXIST;
default:
return QSE_HTTPD_ESYSERR;
}
}
#elif defined(__OS2__)
static qse_httpd_errnum_t syserr_to_errnum (APIRET e)
{
switch (e)
{
case ERROR_NOT_ENOUGH_MEMORY:
return QSE_HTTPD_ENOMEM;
case ERROR_INVALID_PARAMETER:
case ERROR_INVALID_HANDLE:
case ERROR_INVALID_NAME:
return QSE_HTTPD_EINVAL;
case ERROR_ACCESS_DENIED:
case ERROR_SHARING_VIOLATION:
return QSE_HTTPD_EACCES;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
return QSE_HTTPD_ENOENT;
case ERROR_ALREADY_EXISTS:
return QSE_HTTPD_EEXIST;
default:
return QSE_HTTPD_ESYSERR;
}
}
#elif defined(__DOS__)
static qse_httpd_errnum_t syserr_to_errnum (int e)
{
switch (e)
{
case ENOMEM:
return QSE_HTTPD_ENOMEM;
case EINVAL:
return QSE_HTTPD_EINVAL;
case EACCES:
return QSE_HTTPD_EACCES;
case ENOENT:
return QSE_HTTPD_ENOENT;
case EEXIST:
return QSE_HTTPD_EEXIST;
default:
return QSE_HTTPD_ESYSERR;
}
}
#else
static qse_httpd_errnum_t syserr_to_errnum (int e)
{
switch (e)
{
case ENOMEM:
return QSE_HTTPD_ENOMEM;
case EINVAL:
return QSE_HTTPD_EINVAL;
case EACCES:
case ECONNREFUSED:
return QSE_HTTPD_EACCES;
case ENOENT:
case ENOTDIR:
/* ENOTDIR can be returned in this situation.
* i want to access /tmp/t1.cgi/abc/def
* while /tmp/t1.cgi is an existing file.
* I'm not sure if it is really good to translate
* ENOTDIR to QSE_HTTPD_ENOENT.
*/
return QSE_HTTPD_ENOENT;
case EEXIST:
return QSE_HTTPD_EEXIST;
case EINTR:
return QSE_HTTPD_EINTR;
case EAGAIN:
/*case EWOULDBLOCK:*/
return QSE_HTTPD_EAGAIN;
default:
return QSE_HTTPD_ESYSERR;
}
}
#endif
static qse_httpd_errnum_t muxerr_to_errnum (qse_mux_errnum_t e)
{
switch (e)
{
case QSE_MUX_ENOMEM:
return QSE_HTTPD_ENOMEM;
case QSE_MUX_EINVAL:
return QSE_HTTPD_EINVAL;
case QSE_MUX_EACCES:
return QSE_HTTPD_EACCES;
case QSE_MUX_ENOENT:
return QSE_HTTPD_ENOENT;
case QSE_MUX_EEXIST:
return QSE_HTTPD_EEXIST;
case QSE_MUX_EINTR:
return QSE_HTTPD_EINTR;
default:
return QSE_HTTPD_ESYSERR;
}
}
static qse_httpd_errnum_t fioerr_to_errnum (qse_fio_errnum_t e)
{
switch (e)
{
case QSE_FIO_ENOMEM:
return QSE_HTTPD_ENOMEM;
case QSE_FIO_EINVAL:
return QSE_HTTPD_EINVAL;
case QSE_FIO_EACCES:
return QSE_HTTPD_EACCES;
case QSE_FIO_ENOENT:
return QSE_HTTPD_ENOENT;
case QSE_FIO_EEXIST:
return QSE_HTTPD_EEXIST;
case QSE_FIO_EINTR:
return QSE_HTTPD_EINTR;
default:
return QSE_HTTPD_ESYSERR;
}
}
/* ------------------------------------------------------------------- */
#define MAX_SEND_SIZE 4096
static qse_ssize_t __send_file (
int out_fd, qse_ubi_t in_fd, qse_foff_t* offset, qse_size_t count)
{
#if defined(HAVE_SENDFILE) && defined(HAVE_SENDFILE64)
qse_ubi_t infd = qse_fio_gethandleasubi (in_fd.ptr);
#if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64)
return sendfile64 (out_fd, infd.i, offset, count);
#else
return sendfile (out_fd, infd.i, offset, count);
#endif
#elif defined(HAVE_SENDFILE)
qse_ubi_t infd = qse_fio_gethandleasubi (in_fd.ptr);
return sendfile (out_fd, infd.i, offset, count);
#elif defined(HAVE_SENDFILE64)
qse_ubi_t infd = qse_fio_gethandleasubi (in_fd.ptr);
return sendfile64 (out_fd, in_fd.i, offset, count);
#elif defined(HAVE_SENDFILEV) || defined(HAVE_SENDFILEV64)
#if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64)
struct sendfilevec64 vec;
#else
struct sendfilevec vec;
#endif
size_t xfer;
ssize_t n;
vec.sfv_fd = in_fd.i;
vec.sfv_flag = 0;
if (offset)
{
vec.sfv_off = *offset;
}
else
{
vec.sfv_off = QSE_LSEEK (in_fd.i, 0, SEEK_CUR);
if (vec.sfv_off == (off_t)-1) return (qse_ssize_t)-1;
}
vec.sfv_len = count;
#if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64)
n = sendfilev64 (out_fd, &vec, 1, &xfer);
#else
n = sendfilev (out_fd, &vec, 1, &xfer);
#endif
if (offset) *offset = *offset + xfer;
/* TODO: xfer contains number of byte written even on failure
on success xfer == n.
on failure xfer != n.
*/
return n;
#else
qse_mchar_t buf[MAX_SEND_SIZE];
qse_ssize_t n;
#if 0
if (offset && QSE_LSEEK (in_fd.i, *offset, SEEK_SET) != *offset)
return (qse_ssize_t)-1;
if (count > QSE_COUNTOF(buf)) count = QSE_COUNTOF(buf);
n = QSE_READ (in_fd.i, buf, count);
if (n == (qse_ssize_t)-1 || n == 0) return n;
#endif
if (offset && qse_fio_seek (in_fd.ptr, *offset, QSE_FIO_BEGIN) != *offset)
return (qse_ssize_t)-1;
if (count > QSE_COUNTOF(buf)) count = QSE_COUNTOF(buf);
n = qse_fio_read (in_fd.ptr, buf, count);
if (n == (qse_ssize_t)-1 || n == 0) return n;
n = send (out_fd, buf, n, 0);
if (n > 0 && offset) *offset = *offset + n;
return n;
#endif
}
/* ------------------------------------------------------------------- */
#if defined(HAVE_SSL)
static qse_ssize_t __send_file_ssl (
SSL* out, qse_ubi_t in_fd, qse_foff_t* offset, qse_size_t count)
{
qse_mchar_t buf[MAX_SEND_SIZE];
qse_ssize_t n;
#if 0
if (offset && QSE_LSEEK (in_fd.i, *offset, SEEK_SET) != *offset)
return (qse_ssize_t)-1;
if (count > QSE_COUNTOF(buf)) count = QSE_COUNTOF(buf);
n = QSE_READ (in_fd.i, buf, count);
if (n == (qse_ssize_t)-1 || n == 0) return n;
#endif
if (offset && qse_fio_seek (in_fd.ptr, *offset, QSE_FIO_BEGIN) != *offset)
return (qse_ssize_t)-1;
if (count > QSE_COUNTOF(buf)) count = QSE_COUNTOF(buf);
n = qse_fio_read (in_fd.ptr, buf, count);
if (n == (qse_ssize_t)-1 || n == 0) return n;
n = SSL_write (out, buf, count);
if (n > 0 && offset) *offset = *offset + n;
return n;
}
#endif
/* ------------------------------------------------------------------- */
typedef struct httpd_xtn_t httpd_xtn_t;
struct httpd_xtn_t
{
#if defined(HAVE_SSL)
SSL_CTX* ssl_ctx;
#endif
qse_httpd_ecb_t ecb;
};
#if defined(HAVE_SSL)
static int init_xtn_ssl (
httpd_xtn_t* xtn,
const qse_mchar_t* pemfile,
const qse_mchar_t* keyfile/*,
const qse_mchar_t* chainfile*/)
{
SSL_CTX* ctx;
ctx = SSL_CTX_new (SSLv23_server_method());
if (ctx == QSE_NULL) return -1;
/*SSL_CTX_set_info_callback(ctx,ssl_info_callback);*/
if (SSL_CTX_use_certificate_file (ctx, pemfile, SSL_FILETYPE_PEM) == 0 ||
SSL_CTX_use_PrivateKey_file (ctx, keyfile, SSL_FILETYPE_PEM) == 0 ||
SSL_CTX_check_private_key (ctx) == 0 /*||
SSL_CTX_use_certificate_chain_file (ctx, chainfile) == 0*/)
{
qse_mchar_t buf[128];
ERR_error_string_n(ERR_get_error(), buf, QSE_COUNTOF(buf));
/* TODO: logging */
qse_fprintf (QSE_STDERR, QSE_T("Error: %hs\n"), buf);
SSL_CTX_free (ctx);
return -1;
}
/* TODO: CRYPTO_set_id_callback (); */
/* TODO: CRYPTO_set_locking_callback (); */
SSL_CTX_set_read_ahead (ctx, 0);
xtn->ssl_ctx = ctx;
return 0;
}
static void fini_xtn_ssl (httpd_xtn_t* xtn)
{
/* TODO: CRYPTO_set_id_callback (QSE_NULL); */
/* TODO: CRYPTO_set_locking_callback (QSE_NULL); */
SSL_CTX_free (xtn->ssl_ctx);
}
#endif
/* ------------------------------------------------------------------- */
static void cleanup_standard_httpd (qse_httpd_t* httpd)
{
#if defined(HAVE_SSL)
httpd_xtn_t* xtn;
xtn = (httpd_xtn_t*)qse_httpd_getxtn (httpd);
if (xtn->ssl_ctx) fini_xtn_ssl (xtn);
#endif
}
qse_httpd_t* qse_httpd_openstd (qse_size_t xtnsize)
{
return qse_httpd_openstdwithmmgr (QSE_MMGR_GETDFL(), xtnsize);
}
qse_httpd_t* qse_httpd_openstdwithmmgr (qse_mmgr_t* mmgr, qse_size_t xtnsize)
{
qse_httpd_t* httpd;
httpd_xtn_t* xtn;
httpd = qse_httpd_open (mmgr, QSE_SIZEOF(httpd_xtn_t) + xtnsize);
if (httpd == QSE_NULL) return QSE_NULL;
xtn = (httpd_xtn_t*)qse_httpd_getxtn (httpd);
#if defined(HAVE_SSL)
/*init_xtn_ssl (xtn, "http01.pem", "http01.key");*/
#endif
xtn->ecb.close = cleanup_standard_httpd;
qse_httpd_pushecb (httpd, &xtn->ecb);
return httpd;
}
void* qse_httpd_getxtnstd (qse_httpd_t* httpd)
{
return (void*)((httpd_xtn_t*)QSE_XTN(httpd) + 1);
}
/* ------------------------------------------------------------------- */
static int server_open (qse_httpd_t* httpd, qse_httpd_server_t* server)
{
int fd = -1, flag;
qse_skad_t addr;
int addrsize;
addrsize = qse_nwadtoskad (&server->nwad, &addr);
if (addrsize <= -1)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL);
return -1;
}
fd = socket (qse_skadfamily(&addr), SOCK_STREAM, IPPROTO_TCP);
if (fd <= -1) goto oops;
#if defined(FD_CLOEXEC)
flag = fcntl (fd, F_GETFD);
if (flag >= 0) fcntl (fd, F_SETFD, flag | FD_CLOEXEC);
#endif
#if defined(SO_REUSEADDR)
flag = 1;
setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (void*)&flag, QSE_SIZEOF(flag));
#endif
/* TODO: linux. use capset() to set required capabilities just in case */
#if defined(IP_TRANSPARENT)
/* remove the ip routing restriction that a packet can only
* be sent using a local ip address. this option is useful
* if transparency is achieved with TPROXY */
/*
ip rule add fwmark 0x1/0x1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
iptables -t mangle -N DIVERT
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j DIVERT
iptables -t mangle -A DIVERT -j MARK --set-mark 0x1/0x1
iptables -t mangle -A DIVERT -j ACCEPT
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-port 8000
----------------------------------------------------------------------
if the socket is bound to 99.99.99.99:8000, you may do...
iptables -t mangle -A PREROUTING -p tcp --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-ip 99.99.99.99 --on-port 8000
iptables -t mangle -A PREROUTING -p tcp ! -s 127.0.0.0/255.0.0.0 --dport 80 -j TPROXY --tproxy-mark 0x1/0x1 --on-ip 0.0.0.0 --on-port 8000
IP_TRANSPRENT is needed for:
- accepting TPROXYed connections
- binding to a non-local IP address (IP address the local system doesn't have)
- using a non-local IP address as a source
-
*/
flag = 1;
setsockopt (fd, SOL_IP, IP_TRANSPARENT, &flag, QSE_SIZEOF(flag));
#endif
if (server->flags & QSE_HTTPD_SERVER_BINDTONWIF)
{
#if defined(SO_BINDTODEVICE)
qse_mchar_t tmp[64];
qse_size_t len;
len = qse_nwifindextombs (server->nwif, tmp, QSE_COUNTOF(tmp));
if (len <= 0 || setsockopt (fd, SOL_SOCKET, SO_BINDTODEVICE, tmp, len) <= -1)
{
/* TODO: logging ... */
goto oops;
}
#endif
}
/* Solaris 8 returns EINVAL if QSE_SIZEOF(addr) is passed in as the
* address size for AF_INET. */
/*if (bind (s, (struct sockaddr*)&addr, QSE_SIZEOF(addr)) <= -1) goto oops_esocket;*/
if (bind (fd, (struct sockaddr*)&addr, addrsize) <= -1)
{
#if defined(IPV6_V6ONLY) && defined(EADDRINUSE)
if (errno == EADDRINUSE && qse_skadfamily(&addr) == AF_INET6)
{
int on = 1;
setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
if (bind (fd, (struct sockaddr*)&addr, addrsize) <= -1) goto oops;
}
else goto oops;
#else
goto oops;
#endif
}
if (listen (fd, 10) <= -1) goto oops;
#if defined(O_NONBLOCK)
flag = fcntl (fd, F_GETFL);
if (flag >= 0) fcntl (fd, F_SETFL, flag | O_NONBLOCK);
#endif
server->handle.i = fd;
return 0;
oops:
#if defined(_WIN32)
qse_httpd_seterrnum (httpd, syserr_to_errnum(WSAGetLastError()));
if (fd != INVALID_SOCKET) closesocket (fd);
#elif defined(__OS2__)
qse_httpd_seterrnum (httpd, syserr_to_errnum(sock_errno()));
if (fd >= 0) soclose (fd);
#elif defined(__DOS__)
/* TODO: */
#else
qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
if (fd >= 0) QSE_CLOSE (fd);
#endif
return -1;
}
static void server_close (qse_httpd_t* httpd, qse_httpd_server_t* server)
{
#if defined(_WIN32)
closesocket (server->handle.i);
#elif defined(__OS2__)
soclose (server->handle.i);
#elif defined(__DOS__)
/* TODO: */
#else
QSE_CLOSE (server->handle.i);
#endif
}
static int server_accept (
qse_httpd_t* httpd, qse_httpd_server_t* server, qse_httpd_client_t* client)
{
qse_skad_t addr;
#if defined(HAVE_SOCKLEN_T)
socklen_t addrlen;
#else
int addrlen;
#endif
int fd, flag;
addrlen = QSE_SIZEOF(addr);
fd = accept (server->handle.i, (struct sockaddr*)&addr, &addrlen);
if (fd <= -1)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum (errno));
return -1;
}
#if 0
if (fd >= FD_SETSIZE)
{
qse_fprintf (QSE_STDERR, QSE_T("Error: too many client?\n"));
/*TODO: qse_httpd_seterrnum (httpd, QSE_HTTPD_EXXXXX);*/
QSE_CLOSE (fd);
return -1;
}
#endif
#if defined(FD_CLOEXEC)
flag = fcntl (fd, F_GETFD);
if (flag >= 0) fcntl (fd, F_SETFD, flag | FD_CLOEXEC);
#endif
#if defined(O_NONBLOCK)
flag = fcntl (fd, F_GETFL);
if (flag >= 0) fcntl (fd, F_SETFL, flag | O_NONBLOCK);
#endif
if (qse_skadtonwad (&addr, &client->remote_addr) <= -1)
{
/* TODO: logging */
client->remote_addr = server->nwad;
}
addrlen = QSE_SIZEOF(addr);
if (getsockname (fd, (struct sockaddr*)&addr, &addrlen) <= -1 ||
qse_skadtonwad (&addr, &client->local_addr) <= -1)
{
/* TODO: logging */
client->local_addr = server->nwad;
}
#if defined(SO_ORIGINAL_DST)
/* if REDIRECT is used, SO_ORIGINAL_DST returns the original
* destination. If TPROXY is used, getsockname() above returns
* the original address. */
addrlen = QSE_SIZEOF(addr);
if (getsockopt (fd, SOL_IP, SO_ORIGINAL_DST, (char*)&addr, &addrlen) <= -1 ||
qse_skadtonwad (&addr, &client->orgdst_addr) <= -1)
{
client->orgdst_addr = client->local_addr;
}
#else
client->orgdst_addr = client->local_addr;
#endif
#if 0
client->initial_ifindex = resolve_ifindex (fd, client->local_addr);
if (client->ifindex <= -1)
{
/* the local_address is not one of a local address.
* it's probably proxied. */
}
#endif
client->handle.i = fd;
return 0;
}
/* ------------------------------------------------------------------- */
static int peer_open (qse_httpd_t* httpd, qse_httpd_peer_t* peer)
{
qse_skad_t connaddr, bindaddr;
int connaddrsize, bindaddrsize;
int connected = 1;
#if defined(_WIN32)
SOCKET fd = -1;
unsigned long cmd;
#elif defined(__OS2__)
int fd = -1;
int flag;
#elif defined(__DOS__)
int fd = -1;
int flag;
#else
int fd = -1;
int flag;
#endif
connaddrsize = qse_nwadtoskad (&peer->nwad, &connaddr);
bindaddrsize = qse_nwadtoskad (&peer->local, &bindaddr);
if (connaddrsize <= -1 || bindaddrsize <= -1)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL);
return -1;
}
fd = socket (qse_skadfamily(&connaddr), SOCK_STREAM, IPPROTO_TCP);
if (fd <= -1) goto oops;
#if defined(IP_TRANSPARENT)
flag = 1;
setsockopt (fd, SOL_IP, IP_TRANSPARENT, &flag, QSE_SIZEOF(flag));
#endif
if (bind (fd, (struct sockaddr*)&bindaddr, bindaddrsize) <= -1)
{
/* i won't care about binding faiulre */
/* TODO: some logging for this failure though */
}
#if defined(_WIN32)
cmd = 1;
if (ioctlsocket(fd, FIONBIO, &cmd) == SOCKET_ERROR) goto oops;
if (connect (fd, (struct sockaddr*)&connaddr, connaddrsize) <= -1)
{
if (WSAGetLastError() != WSAEWOULDBLOCK) goto oops;
connected = 0;
}
cmd = 0;
if (ioctlsocket(fd, FIONBIO, &cmd) == SOCKET_ERROR) goto oops;
#elif defined(__OS2__)
/* TODO: */
#elif defined(__DOS__)
/* TODO: */
#else
#if defined(FD_CLOEXEC)
flag = fcntl (fd, F_GETFD);
if (flag >= 0) fcntl (fd, F_SETFD, flag | FD_CLOEXEC);
#endif
flag = fcntl (fd, F_GETFL);
if (flag >= 0) fcntl (fd, F_SETFL, flag | O_NONBLOCK);
if (connect (fd, (struct sockaddr*)&connaddr, connaddrsize) <= -1)
{
if (errno != EINPROGRESS) goto oops;
connected = 0;
}
/* restore flags */
if (fcntl (fd, F_SETFL, flag) <= -1) goto oops;
#endif
peer->handle.i = fd;
return connected;
oops:
#if defined(_WIN32)
qse_httpd_seterrnum (httpd, syserr_to_errnum(WSAGetLastError()));
if (fd != INVALID_SOCKET) closesocket (fd);
#elif defined(__OS2__)
qse_httpd_seterrnum (httpd, syserr_to_errnum(sock_errno()));
if (fd >= 0) soclose (fd);
#elif defined(__DOS__)
/* TODO: */
#else
qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
if (fd >= 0) QSE_CLOSE (fd);
#endif
return -1;
}
static void peer_close (qse_httpd_t* httpd, qse_httpd_peer_t* peer)
{
#if defined(_WIN32)
closesocket (peer->handle.i);
#elif defined(__OS2__)
soclose (peer->handle.i);
#elif defined(__DOS__)
/* TODO: */
#else
QSE_CLOSE (peer->handle.i);
#endif
}
static int peer_connected (qse_httpd_t* httpd, qse_httpd_peer_t* peer)
{
#if defined(_WIN32)
int len;
DWORD ret;
len = QSE_SIZEOF(ret);
if (getsockopt (peer->handle.i, SOL_SOCKET, SO_ERROR, (char*)&ret, &len) == SOCKET_ERROR)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum (ret));
return -1;
}
if (ret == WSAEWOULDBLOCK) return 0;
if (ret != 0)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum (ret));
return -1;
}
return 1; /* connection completed */
#elif defined(__OS2__)
/* TODO */
httpd->errnum = QSE_HTTPD_ENOIMPL;
return -1;
#elif defined(__DOS__)
/* TODO */
httpd->errnum = QSE_HTTPD_ENOIMPL;
return -1;
#else
#if defined(HAVE_SOCKLEN_T)
socklen_t len;
#else
int len;
#endif
int ret;
len = QSE_SIZEOF(ret);
if (getsockopt (peer->handle.i, SOL_SOCKET, SO_ERROR, &ret, &len) <= -1)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum (ret));
return -1;
}
if (ret == EINPROGRESS) return 0;
if (ret != 0)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum (ret));
return -1;
}
return 1; /* connection completed */
#endif
}
static qse_ssize_t peer_recv (
qse_httpd_t* httpd, qse_httpd_peer_t* peer,
qse_mchar_t* buf, qse_size_t bufsize)
{
qse_ssize_t ret = recv (peer->handle.i, buf, bufsize, 0);
if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
return ret;
}
static qse_ssize_t peer_send (
qse_httpd_t* httpd, qse_httpd_peer_t* peer,
const qse_mchar_t* buf, qse_size_t bufsize)
{
qse_ssize_t ret = send (peer->handle.i, buf, bufsize, 0);
if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
return ret;
}
/* ------------------------------------------------------------------- */
typedef struct mux_xtn_t mux_xtn_t;
struct mux_xtn_t
{
qse_httpd_t* httpd;
qse_httpd_muxcb_t cbfun;
};
static void dispatch_muxcb (qse_mux_t* mux, const qse_mux_evt_t* evt)
{
mux_xtn_t* xtn;
qse_ubi_t ubi;
xtn = qse_mux_getxtn (mux);
ubi.i = evt->hnd;
xtn->cbfun (xtn->httpd, mux, ubi, evt->mask, evt->data);
}
static void* mux_open (qse_httpd_t* httpd, qse_httpd_muxcb_t cbfun)
{
qse_mux_t* mux;
mux_xtn_t* xtn;
mux = qse_mux_open (httpd->mmgr, QSE_SIZEOF(*xtn), dispatch_muxcb, 256);
if (!mux)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR);
return QSE_NULL;
}
xtn = qse_mux_getxtn (mux);
xtn->httpd = httpd;
xtn->cbfun = cbfun;
return mux;
}
static void mux_close (qse_httpd_t* httpd, void* vmux)
{
qse_mux_close ((qse_mux_t*)vmux);
}
static int mux_addhnd (
qse_httpd_t* httpd, void* vmux, qse_ubi_t handle, int mask, void* data)
{
qse_mux_evt_t evt;
evt.hnd = handle.i;
evt.mask = 0;
if (mask & QSE_HTTPD_MUX_READ) evt.mask |= QSE_MUX_IN;
if (mask & QSE_HTTPD_MUX_WRITE) evt.mask |= QSE_MUX_OUT;
evt.data = data;
if (qse_mux_insert ((qse_mux_t*)vmux, &evt) <= -1)
{
qse_httpd_seterrnum (httpd, muxerr_to_errnum(qse_mux_geterrnum((qse_mux_t*)vmux)));
return -1;
}
return 0;
}
static int mux_delhnd (qse_httpd_t* httpd, void* vmux, qse_ubi_t handle)
{
qse_mux_evt_t evt;
evt.hnd = handle.i;
if (qse_mux_delete ((qse_mux_t*)vmux, &evt) <= -1)
{
qse_httpd_seterrnum (httpd, muxerr_to_errnum(qse_mux_geterrnum((qse_mux_t*)vmux)));
return -1;
}
return 0;
}
static int mux_poll (qse_httpd_t* httpd, void* vmux, const qse_ntime_t* tmout)
{
if (qse_mux_poll ((qse_mux_t*)vmux, tmout) <= -1)
{
qse_httpd_seterrnum (httpd, muxerr_to_errnum(qse_mux_geterrnum((qse_mux_t*)vmux)));
return -1;
}
return 0;
}
static int mux_readable (qse_httpd_t* httpd, qse_ubi_t handle, const qse_ntime_t* tmout)
{
#if defined(__OS2__) && !defined(TCPV40HDRS)
long tv;
tv = tmout? QSE_SECNSEC_TO_MSEC (tmout->sec, tmout->nsec): -1;
return os2_select (&handle.i, 1, 0, 0, tv);
#else
fd_set r;
struct timeval tv, * tvp;
FD_ZERO (&r);
FD_SET (handle.i, &r);
if (tmout)
{
tv.tv_sec = tmout->sec;
tv.tv_usec = tmout->nsec;
tvp = &tv;
}
else tvp = QSE_NULL;
return select (handle.i + 1, &r, QSE_NULL, QSE_NULL, tvp);
#endif
}
static int mux_writable (qse_httpd_t* httpd, qse_ubi_t handle, const qse_ntime_t* tmout)
{
#if defined(__OS2__) && !defined(TCPV40HDRS)
long tv;
tv = tmout? QSE_SECNSEC_TO_MSEC (tmout->sec, tmout->nsec): -1;
return os2_select (&handle.i, 0, 1, 0, tv);
#else
fd_set w;
struct timeval tv, * tvp;
FD_ZERO (&w);
FD_SET (handle.i, &w);
if (tmout)
{
tv.tv_sec = tmout->sec;
tv.tv_usec = tmout->nsec;
tvp = &tv;
}
else tvp = QSE_NULL;
return select (handle.i + 1, QSE_NULL, &w, QSE_NULL, tvp);
#endif
}
/* ------------------------------------------------------------------- */
static int stat_file (
qse_httpd_t* httpd, const qse_mchar_t* path,
qse_httpd_stat_t* hst, int regonly)
{
#if defined(_WIN32)
HANDLE fh;
WIN32_FIND_DATAA fdata;
ULARGE_INTEGER li;
if ((path[0] == QSE_MT('/') && path[1] == QSE_MT('\0')) ||
(qse_ismbsdriveabspath(path) && qse_mbslen(path) == 3))
{
/* the root directory doesn't work well with FindFirstFile()
* if it's not appened with an wildcard letter like C:\*.*.
* since i'm not interested in the files under the root
* directory, let me just hard-code it to indicate that
* it is a directory.
*/
QSE_MEMSET (hst, 0, QSE_SIZEOF(*hst));
hst->isdir = 1;
}
else
{
/* TODO: hst->dev can be set to the drive letter's index. */
/* fail if the path name contains a wilecard letter */
if (qse_mbspbrk (path, QSE_MT("?*")) != QSE_NULL) return -1;
fh = FindFirstFileA (path, &fdata);
if (fh == INVALID_HANDLE_VALUE)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum(GetLastError()));
return -1;
}
QSE_MEMSET (hst, 0, QSE_SIZEOF(*hst));
if (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) hst->isdir = 1;
hst->size = ((qse_foff_t)fdata.nFileSizeHigh << 32) | fdata.nFileSizeLow;
li.LowPart = fdata.ftLastWriteTime.dwLowDateTime;
li.HighPart = fdata.ftLastWriteTime.dwHighDateTime;
/* li.QuadPart is in the 100-nanosecond intervals */
hst->mtime.sec = (li.QuadPart / (QSE_NSECS_PER_SEC / 100)) - EPOCH_DIFF_SECS;
hst->mtime.nsec = (li.QuadPart % (QSE_NSECS_PER_SEC / 100)) * 100;
FindClose (fh);
}
return 0;
#elif defined(__OS2__)
APIRET rc;
qse_btime_t bt;
qse_ntime_t nt;
FILESTATUS3L ffb;
rc = DosQueryPathInfo (path, FIL_STANDARDL, &ffb, QSE_SIZEOF(ffb));
if (rc != NO_ERROR)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum(rc));
return -1;
}
QSE_MEMSET (&bt, 0, QSE_SIZEOF(bt));
bt.mday = ffb.fdateLastWrite.day;
bt.mon = ffb.fdateLastWrite.month - 1;
bt.year = ffb.fdateLastWrite.year + 80;
bt.hour = ffb.ftimeLastWrite.hours;
bt.min = ffb.ftimeLastWrite.minutes;
bt.min = ffb.ftimeLastWrite.twosecs * 2;
bt.isdst = -1;
if (qse_timelocal (&bt, &nt) <= -1) return -1;
QSE_MEMSET (hst, 0, QSE_SIZEOF(*hst));
if (ffb.attrFile & FILE_DIRECTORY) hst->isdir = 1;
hst->size = ((qse_foff_t)ffb.cbFile.ulHi << 32) | ffb.cbFile.ulLo;
hst->mtime = nt;
if (path[0] != QSE_MT('\0') && path[1] == QSE_MT(':'))
{
if (path[0] >= QSE_MT('a') && path[0] <= QSE_MT('z'))
hst->dev = path[0] - QSE_MT('a');
else if (path[0] >= QSE_MT('A') && path[0] <= QSE_MT('Z'))
hst->dev = path[0] - QSE_MT('A');
}
else
{
ULONG num, map;
if (DosQueryCurrentDisk (&num, &map) == NO_ERROR) hst->dev = num - 1;
}
return 0;
#elif defined(__DOS__)
/* TODO: */
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL);
return -1;
#else
qse_stat_t st;
/* TODO: lstat? or stat? */
if (QSE_STAT (path, &st) <= -1)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
return -1;
}
/* stating for a file. it should be a regular file.
* i don't allow other file types. */
if (regonly && !S_ISREG(st.st_mode))
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_EACCES);
return -1;
}
QSE_MEMSET (hst, 0, QSE_SIZEOF(*hst));
hst->isdir = S_ISDIR(st.st_mode);
hst->dev = st.st_dev;
hst->ino = st.st_ino;
hst->size = st.st_size;
#if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC)
hst->mtime.sec = st.st_mtim.tv_sec;
hst->mtime.nsec = st.st_mtim.tv_nsec;
#elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC)
hst->mtime.sec = st.st_mtimespec.tv_sec;
hst->mtime.nsec = st.st_mtimespec.tv_nsec;
#else
hst->mtime.sec = st.st_mtime;
hst->mtime.nsec = 0;
#endif
return 0;
#endif
}
/* ------------------------------------------------------------------- */
static int file_stat (
qse_httpd_t* httpd, const qse_mchar_t* path, qse_httpd_stat_t* hst)
{
/* this callback is not required to be a general stat function
* for a file. it is mainly used to get a file size and timestamps
* of a regular file. so it should fail for a non-regular file.
* note that 1 passes 1 to stat_file for it */
return stat_file (httpd, path, hst, 1);
}
static int file_ropen (
qse_httpd_t* httpd, const qse_mchar_t* path, qse_ubi_t* handle)
{
qse_fio_t* fio;
fio = QSE_MMGR_ALLOC (httpd->mmgr, QSE_SIZEOF(*fio));
if (fio == QSE_NULL)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
return -1;
}
if (qse_fio_init (
fio, httpd->mmgr, (const qse_char_t*)path,
QSE_FIO_READ | QSE_FIO_MBSPATH, 0) <= -1)
{
qse_httpd_seterrnum (httpd, fioerr_to_errnum(qse_fio_geterrnum(fio)));
QSE_MMGR_FREE (httpd->mmgr, fio);
return -1;
}
handle->ptr = fio;
qse_printf (QSE_T("opened rfile [%hs][%p][%p]\n"), path, handle->ptr, fio->handle);
return 0;
}
static int file_wopen (
qse_httpd_t* httpd, const qse_mchar_t* path,
qse_ubi_t* handle)
{
qse_fio_t* fio;
fio = QSE_MMGR_ALLOC (httpd->mmgr, QSE_SIZEOF(*fio));
if (fio == QSE_NULL)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
return -1;
}
if (qse_fio_init (
fio, httpd->mmgr, (const qse_char_t*)path,
QSE_FIO_WRITE | QSE_FIO_CREATE |
QSE_FIO_TRUNCATE | QSE_FIO_MBSPATH, 0644) <= -1)
{
qse_httpd_seterrnum (httpd, fioerr_to_errnum(qse_fio_geterrnum(fio)));
QSE_MMGR_FREE (httpd->mmgr, fio);
return -1;
}
handle->ptr = fio;
qse_printf (QSE_T("opened wfile [%hs][%p][%p]\n"), path, handle->ptr, fio->handle);
return 0;
}
static void file_close (qse_httpd_t* httpd, qse_ubi_t handle)
{
qse_printf (QSE_T("closed file....%p\n"), handle.ptr);
qse_fio_fini (handle.ptr);
QSE_MMGR_FREE (httpd->mmgr, handle.ptr);
}
static qse_ssize_t file_read (
qse_httpd_t* httpd, qse_ubi_t handle,
qse_mchar_t* buf, qse_size_t len)
{
qse_ssize_t n;
n = qse_fio_read (handle.ptr, buf, len);
if (n <= -1) qse_httpd_seterrnum (httpd, fioerr_to_errnum(qse_fio_geterrnum(handle.ptr)));
return n;
}
static qse_ssize_t file_write (
qse_httpd_t* httpd, qse_ubi_t handle,
const qse_mchar_t* buf, qse_size_t len)
{
qse_ssize_t n;
n = qse_fio_write (handle.ptr, buf, len);
if (n <= -1) qse_httpd_seterrnum (httpd, fioerr_to_errnum(qse_fio_geterrnum(handle.ptr)));
return n;
}
/* ------------------------------------------------------------------- */
typedef struct dir_t dir_t;
struct dir_t
{
qse_mchar_t* path;
qse_dir_t* dp;
};
static int dir_open (qse_httpd_t* httpd, const qse_mchar_t* path, qse_ubi_t* handle)
{
dir_t* d;
d = QSE_MMGR_ALLOC (httpd->mmgr, QSE_SIZEOF(*d));
if (d == QSE_NULL)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
return -1;
}
d->path = qse_mbsdup (path, httpd->mmgr);
if (d->path == QSE_NULL)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
QSE_MMGR_FREE (httpd->mmgr, d);
return -1;
}
d->dp = qse_dir_open (httpd->mmgr, 0,
(const qse_char_t*)d->path, QSE_DIR_MBSPATH | QSE_DIR_SORT);
if (d->dp == QSE_NULL)
{
qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
QSE_MMGR_FREE (httpd->mmgr, d->path);
QSE_MMGR_FREE (httpd->mmgr, d);
return -1;
}
qse_printf (QSE_T("OPENED DIRECTORY [%hs]\n"), path);
handle->ptr = d;
return 0;
}
static void dir_close (qse_httpd_t* httpd, qse_ubi_t handle)
{
dir_t* d;
d = (dir_t*)handle.ptr;
qse_dir_close (d->dp);
QSE_MMGR_FREE (httpd->mmgr, d->path);
QSE_MMGR_FREE (httpd->mmgr, d);
}
static int dir_read (qse_httpd_t* httpd, qse_ubi_t handle, qse_httpd_dirent_t* dirent)
{
dir_t* d;
qse_dir_ent_t de;
qse_mchar_t* fpath;
int n;
d = (dir_t*)handle.ptr;
n = qse_dir_read (d->dp, &de);
if (n <= -1)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR);
return -1;
}
else if (n == 0) return 0;
/* i assume that d->path ends with a slash */
fpath = qse_mbsdup2 (d->path, (const qse_mchar_t*)de.name, httpd->mmgr);
if (fpath == QSE_NULL)
{
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
return -1;
}
n = stat_file (httpd, fpath, &dirent->stat, 0);
QSE_MMGR_FREE (httpd->mmgr, fpath);
if (n <= -1) QSE_MEMSET (dirent, 0, QSE_SIZEOF(*dirent));
dirent->name = (const qse_mchar_t*)de.name;
return 1;
}
/* ------------------------------------------------------------------- */
static void client_close (
qse_httpd_t* httpd, qse_httpd_client_t* client)
{
#if defined(_WIN32)
/* TODO: */
#elif defined(__OS2__)
/* TODO: */
#elif defined(__DOS__)
/* TODO: */
#else
QSE_CLOSE (client->handle.i);
#endif
}
static void client_shutdown (
qse_httpd_t* httpd, qse_httpd_client_t* client)
{
#if defined(SHUT_RDWR)
shutdown (client->handle.i, SHUT_RDWR);
#else
shutdown (client->handle.i, 2);
#endif
}
static qse_ssize_t client_recv (
qse_httpd_t* httpd, qse_httpd_client_t* client,
qse_mchar_t* buf, qse_size_t bufsize)
{
if (client->status & CLIENT_SECURE)
{
#if defined(HAVE_SSL)
int ret = SSL_read (client->handle2.ptr, buf, bufsize);
if (ret <= -1)
{
if (SSL_get_error(client->handle2.ptr,ret) == SSL_ERROR_WANT_READ)
qse_httpd_seterrnum (httpd, QSE_HTTPD_EAGAIN);
else
qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR);
}
if (SSL_pending (client->handle2.ptr) > 0)
client->status |= CLIENT_PENDING;
else
client->status &= ~CLIENT_PENDING;
return ret;
#else
return -1;
#endif
}
else
{
qse_ssize_t ret;
ret = recv (client->handle.i, buf, bufsize, 0);
if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
return ret;
}
}
static qse_ssize_t client_send (
qse_httpd_t* httpd, qse_httpd_client_t* client,
const qse_mchar_t* buf, qse_size_t bufsize)
{
if (client->status & CLIENT_SECURE)
{
#if defined(HAVE_SSL)
int ret = SSL_write (client->handle2.ptr, buf, bufsize);
if (ret <= -1)
{
if (SSL_get_error(client->handle2.ptr,ret) == SSL_ERROR_WANT_WRITE)
qse_httpd_seterrnum (httpd, QSE_HTTPD_EAGAIN);
else
qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR);
}
return ret;
#else
return -1;
#endif
}
else
{
qse_ssize_t ret = send (client->handle.i, buf, bufsize, 0);
if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno));
return ret;
}
}
static qse_ssize_t client_sendfile (
qse_httpd_t* httpd, qse_httpd_client_t* client,
qse_ubi_t handle, qse_foff_t* offset, qse_size_t count)
{
if (client->status & CLIENT_SECURE)
{
#if defined(HAVE_SSL)
return __send_file_ssl (client->handle2.ptr, handle, offset, count);
#else
return -1;
#endif
}
else
{
return __send_file (client->handle.i, handle, offset, count);
}
}
static int client_accepted (qse_httpd_t* httpd, qse_httpd_client_t* client)
{
if (client->status & CLIENT_SECURE)
{
#if defined(HAVE_SSL)
int ret;
SSL* ssl;
httpd_xtn_t* xtn;
xtn = (httpd_xtn_t*) qse_httpd_getxtn (httpd);
if (!xtn->ssl_ctx)
{
/* delayed initialization of ssl */
if (init_xtn_ssl (xtn, "http01.pem", "http01.key") <= -1)
{
return -1;
}
}
QSE_ASSERT (xtn->ssl_ctx != QSE_NULL);
if (client->handle2.ptr)
{
ssl = client->handle2.ptr;
}
else
{
ssl = SSL_new (xtn->ssl_ctx);
if (ssl == QSE_NULL) return -1;
client->handle2.ptr = ssl;
qse_printf (QSE_T("SSL ACCEPTING %d\n"), client->handle.i);
qse_fflush (QSE_STDOUT);
if (SSL_set_fd (ssl, client->handle.i) == 0)
{
/* don't free ssl here since client_closed()
* will free it */
return -1;
}
SSL_set_read_ahead (ssl, 0);
}
ret = SSL_accept (ssl);
if (ret <= 0)
{
if (SSL_get_error(ssl,ret) == SSL_ERROR_WANT_READ)
{
/* handshaking isn't complete. */
return 0;
}
qse_fprintf (QSE_STDERR, QSE_T("Error: SSL ACCEPT ERROR\n"));
/* SSL_free (ssl); */
return -1;
}
qse_printf (QSE_T("SSL ACCEPTED %d\n"), client->handle.i);
qse_fflush (QSE_STDOUT);
#else
qse_fprintf (QSE_STDERR, QSE_T("Error: NO SSL SUPPORT\n"));
return -1;
#endif
}
return 1; /* accept completed */
}
static void client_closed (qse_httpd_t* httpd, qse_httpd_client_t* client)
{
if (client->status & CLIENT_SECURE)
{
#if defined(HAVE_SSL)
if (client->handle2.ptr)
{
SSL_shutdown ((SSL*)client->handle2.ptr); /* is this needed? */
SSL_free ((SSL*)client->handle2.ptr);
}
#endif
}
}
/* ------------------------------------------------------------------- */
static qse_htb_walk_t walk (qse_htb_t* htb, qse_htb_pair_t* pair, void* ctx)
{
qse_htre_hdrval_t* val;
val = QSE_HTB_VPTR(pair);
while (val)
{
qse_printf (QSE_T("HEADER OK %d[%hs] %d[%hs]\n"), (int)QSE_HTB_KLEN(pair), QSE_HTB_KPTR(pair), (int)val->len, val->ptr);
val = val->next;
}
return QSE_HTB_WALK_FORWARD;
}
static int process_request (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req, int peek)
{
int method;
qse_httpd_task_t* task;
int content_received;
server_xtn_t* server_xtn;
server_xtn = (server_xtn_t*)qse_httpd_getserverxtn (httpd, client->server);
method = qse_htre_getqmethodtype(req);
content_received = (qse_htre_getcontentlen(req) > 0);
/* percent-decode the query path to the original buffer
* since i'm not gonna need it in the original form
* any more. once it's decoded in the peek mode,
* the decoded query path is made available in the
* non-peek mode as well */
if (peek) qse_perdechttpstr (qse_htre_getqpath(req), qse_htre_getqpath(req));
qse_printf (QSE_T("================================\n"));
qse_printf (QSE_T("[%lu] %hs REQUEST ==> [%hs] version[%d.%d %hs] method[%hs]\n"),
(unsigned long)time(NULL),
(peek? QSE_MT("PEEK"): QSE_MT("HANDLE")),
qse_htre_getqpath(req),
qse_htre_getmajorversion(req),
qse_htre_getminorversion(req),
qse_htre_getverstr(req),
qse_htre_getqmethodname(req)
);
if (qse_htre_getqparam(req))
qse_printf (QSE_T("PARAMS ==> [%hs]\n"), qse_htre_getqparam(req));
qse_htb_walk (&req->hdrtab, walk, QSE_NULL);
if (qse_htre_getcontentlen(req) > 0)
{
qse_printf (QSE_T("CONTENT [%.*S]\n"), (int)qse_htre_getcontentlen(req), qse_htre_getcontentptr(req));
}
if (peek)
{
if (method != QSE_HTTP_POST && method != QSE_HTTP_PUT)
{
/* i'll discard request contents if the method is none of
* post and put */
qse_httpd_discardcontent (httpd, req);
}
if ((req->attr.flags & QSE_HTRE_ATTR_EXPECT100) &&
(req->version.major > 1 ||
(req->version.major == 1 && req->version.minor >= 1)) &&
!content_received)
{
/* TODO: check method.... */
/* "expect" in the header, version 1.1 or higher,
* and no content received yet */
/* TODO: determine if to return 100-continue or other errors */
if (qse_httpd_entaskcontinue (
httpd, client, QSE_NULL, req) == QSE_NULL) return -1;
}
}
if (method == QSE_HTTP_GET || method == QSE_HTTP_POST)
{
if (peek)
{
qse_httpd_rsrc_t rsrc;
if (method == QSE_HTTP_POST &&
!(req->attr.flags & QSE_HTRE_ATTR_LENGTH) &&
!(req->attr.flags & QSE_HTRE_ATTR_CHUNKED))
{
req->attr.flags &= ~QSE_HTRE_ATTR_KEEPALIVE;
qse_httpd_discardcontent (httpd, req);
task = qse_httpd_entaskerr (httpd, client, QSE_NULL, 411, req);
if (task)
{
/* 411 Length Required - can't keep alive. Force disconnect */
task = qse_httpd_entaskdisconnect (httpd, client, QSE_NULL);
}
}
else if (server_xtn->cfg2.s.cbstd->makersrc (httpd, client, req, &rsrc) <= -1)
{
qse_httpd_discardcontent (httpd, req);
task = qse_httpd_entaskerr (httpd, client, QSE_NULL, 500, req);
}
else
{
task = qse_httpd_entaskrsrc (httpd, client, QSE_NULL, &rsrc, req);
if (server_xtn->cfg2.s.cbstd->freersrc)
server_xtn->cfg2.s.cbstd->freersrc (httpd, client, req, &rsrc);
}
if (task == QSE_NULL) goto oops;
}
}
else
{
if (peek)
{
qse_httpd_discardcontent (httpd, req);
}
else
{
task = qse_httpd_entaskerr (httpd, client, QSE_NULL, 405, req);
if (task == QSE_NULL) goto oops;
}
}
if (!(req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE))
{
if (!peek)
{
task = qse_httpd_entaskdisconnect (httpd, client, QSE_NULL);
if (task == QSE_NULL) goto oops;
}
}
return 0;
oops:
/*qse_httpd_markbadclient (httpd, client);*/
return -1;
}
static int peek_request (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req)
{
return process_request (httpd, client, req, 1);
}
static int handle_request (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req)
{
return process_request (httpd, client, req, 0);
}
static int format_err (
qse_httpd_t* httpd, qse_httpd_client_t* client, int code, qse_mchar_t* buf, int bufsz)
{
int n;
server_xtn_t* server_xtn;
const qse_mchar_t* css, * msg;
server_xtn = qse_httpd_getserverxtn (httpd, client->server);
css = server_xtn->cfg[SERVER_XTN_CFG_ERRCSS];
if (!css) css = QSE_MT("");
msg = qse_httpstatustombs(code);
/* TODO: use my own version of snprintf replacement */
n = snprintf (buf, bufsz,
QSE_MT("<html><head>%s<title>%s</title></head><body><div class='header'>HTTP ERROR</div><div class='body'>%d %s</div><div class='footer'>%s</div></body></html>"),
css, msg, code, msg, qse_httpd_getname(httpd));
if (n < 0 || n >= bufsz)
{
httpd->errnum = QSE_HTTPD_ENOBUF;
return -1;
}
return n;
}
static int format_dir (
qse_httpd_t* httpd, qse_httpd_client_t* client,
const qse_mchar_t* qpath, const qse_httpd_dirent_t* dirent,
qse_mchar_t* buf, int bufsz)
{
/* TODO: page encoding?? utf-8??? or derive name from cmgr or current locale??? */
/* TODO: html escaping of ctx->qpath.ptr */
int n;
server_xtn_t* server_xtn;
server_xtn = qse_httpd_getserverxtn (httpd, client->server);
if (dirent == QSE_NULL)
{
if (qpath)
{
/* header */
const qse_mchar_t* css;
int is_root = (qse_mbscmp (qpath, QSE_MT("/")) == 0);
css = server_xtn->cfg[SERVER_XTN_CFG_DIRCSS];
if (!css) css = QSE_MT("");
/* TODO: html escaping of qpath */
n = snprintf (buf, bufsz,
QSE_MT("<html><head>%s</head><body><div class='header'>%s</div><div class='body'><table>%s"), css, qpath,
(is_root? QSE_MT(""): QSE_MT("<tr><td class='name'><a href='../'>..</a></td><td class='time'></td><td class='size'></td></tr>"))
);
}
else
{
/* footer */
n = snprintf (buf, bufsz, QSE_MT("</table></div><div class='footer'>%s</div></body></html>"), qse_httpd_getname(httpd));
}
}
else
{
/* main entry */
qse_mchar_t* encname;
qse_btime_t bt;
qse_mchar_t tmbuf[32];
qse_mchar_t fszbuf[64];
/* TODO: better buffer management in case there are
* a lot of file names to escape. */
encname = qse_perenchttpstrdup (dirent->name, httpd->mmgr);
if (encname == QSE_NULL)
{
httpd->errnum = QSE_HTTPD_ENOMEM;
return -1;
}
qse_localtime (&dirent->stat.mtime, &bt);
snprintf (tmbuf, QSE_COUNTOF(tmbuf),
QSE_MT("%04d-%02d-%02d %02d:%02d:%02d"),
bt.year + QSE_BTIME_YEAR_BASE, bt.mon + 1, bt.mday,
bt.hour, bt.min, bt.sec);
if (dirent->stat.isdir)
{
fszbuf[0] = QSE_MT('\0');
}
else
{
qse_fmtuintmaxtombs (
fszbuf, QSE_COUNTOF(fszbuf),
dirent->stat.size, 10, -1, QSE_MT('\0'), QSE_NULL
);
}
n = snprintf (
buf, bufsz,
QSE_MT("<tr><td class='name'><a href='%s%s'>%s%s</a></td><td class='time'>%s</td><td class='size'>%s</td></tr>"),
encname,
(dirent->stat.isdir? QSE_MT("/"): QSE_MT("")),
dirent->name, /* TODO: html escaping for entry name */
(dirent->stat.isdir? QSE_MT("/"): QSE_MT("")),
tmbuf, fszbuf
);
if (encname != dirent->name) QSE_MMGR_FREE (httpd->mmgr, encname);
}
if (n <= -1 || n >= bufsz)
{
httpd->errnum = QSE_HTTPD_ENOBUF;
return -1;
}
return n;
}
static qse_httpd_scb_t httpd_system_callbacks =
{
/* server */
{ server_open,
server_close,
server_accept },
{ peer_open,
peer_close,
peer_connected,
peer_recv,
peer_send },
/* multiplexer */
{ mux_open,
mux_close,
mux_addhnd,
mux_delhnd,
mux_poll,
mux_readable,
mux_writable
},
/* file operation */
{ file_stat,
file_ropen,
file_wopen,
file_close,
file_read,
file_write
},
/* directory operation */
{ dir_open,
dir_close,
dir_read
},
/* client connection */
{ client_close,
client_shutdown,
client_recv,
client_send,
client_sendfile,
client_accepted,
client_closed }
};
static qse_httpd_rcb_t httpd_request_callbacks =
{
peek_request,
handle_request,
format_err,
format_dir
};
/* ------------------------------------------------------------------- */
static void free_resource (
qse_httpd_t* httpd, qse_httpd_client_t* client,
qse_htre_t* req, qse_httpd_rsrc_t* target)
{
const qse_mchar_t* qpath = qse_htre_getqpath(req);
switch (target->type)
{
case QSE_HTTPD_RSRC_CGI:
if (target->u.cgi.suffix)
QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.suffix);
if (target->u.cgi.script != qpath)
QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.script);
if (target->u.cgi.path != qpath)
QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.path);
break;
case QSE_HTTPD_RSRC_DIR:
if (target->u.dir.path != qpath)
QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.dir.path);
break;
case QSE_HTTPD_RSRC_FILE:
if (target->u.cgi.path != qpath)
QSE_MMGR_FREE (httpd->mmgr, (qse_mchar_t*)target->u.cgi.path);
break;
default:
/* nothing to do */
break;
}
}
static qse_mchar_t* merge_paths (
qse_httpd_t* httpd, const qse_mchar_t* base, const qse_mchar_t* path)
{
qse_mchar_t* xpath;
const qse_mchar_t* ta[4];
qse_size_t idx = 0;
ta[idx++] = base;
ta[idx++] = QSE_MT("/");
ta[idx++] = path;
ta[idx++] = QSE_NULL;
xpath = qse_mbsadup (ta, QSE_NULL, httpd->mmgr);
if (xpath == QSE_NULL)
{
httpd->errnum = QSE_HTTPD_ENOMEM;
return QSE_NULL;
}
qse_canonmbspath (xpath, xpath, 0);
return xpath;
}
static int attempt_cgi (
qse_httpd_t* httpd, const qse_mchar_t* docroot,
qse_mchar_t* xpath, const qse_mchar_t* qpath, const qse_mchar_t* idxfile,
qse_httpd_server_cgistd_t cgistd[], qse_httpd_rsrc_t* target)
{
qse_mchar_t* ext;
qse_mchar_t* script, * suffix;
qse_size_t i;
if (idxfile)
{
for (i = 0; cgistd[i].ext; i++)
{
if (qse_mbsend (idxfile, cgistd[i].ext))
{
script = merge_paths (httpd, qpath, idxfile);
if (script == QSE_NULL) return -1;
target->type = QSE_HTTPD_RSRC_CGI;
target->u.cgi.nph = cgistd[i].nph;
target->u.cgi.path = xpath;
target->u.cgi.script = script;
target->u.cgi.suffix = QSE_NULL;
target->u.cgi.docroot = docroot;
return 1;
}
}
}
else
{
for (i = 0; cgistd[i].ext; i++)
{
/* TODO: attempt other segments if qpath is like
* /abc/x.cgi/y.cgi/ttt. currently, it tries x.cgi only.
* x.cgi could be a directory name .
*/
ext = qse_mbsstr (qpath, cgistd[i].ext);
if (ext && (ext[cgistd[i].len] == QSE_MT('/') ||
ext[cgistd[i].len] == QSE_MT('\0')))
{
if (ext[cgistd[i].len] == QSE_MT('/'))
{
/* it has a path suffix */
script = qse_mbsxdup (qpath, ext - qpath + cgistd[i].len, httpd->mmgr);
suffix = qse_mbsdup (&ext[cgistd[i].len], httpd->mmgr);
if (script == QSE_NULL || suffix == QSE_NULL)
{
if (suffix) QSE_MMGR_FREE (httpd->mmgr, suffix);
if (script) QSE_MMGR_FREE (httpd->mmgr, script);
httpd->errnum = QSE_HTTPD_ENOMEM;
return -1;
}
/* drop the suffix part */
xpath[qse_mbslen(xpath) - qse_mbslen(suffix)] = QSE_MT('\0');
}
else
{
/* it has no path suffix */
script = qpath;
suffix = QSE_NULL;
}
target->type = QSE_HTTPD_RSRC_CGI;
target->u.cgi.nph = cgistd[i].nph;
target->u.cgi.path = xpath;
target->u.cgi.script = script;
target->u.cgi.suffix = suffix;
target->u.cgi.docroot = docroot;
return 1;
}
}
}
return 0;
}
static int make_resource (
qse_httpd_t* httpd, qse_httpd_client_t* client,
qse_htre_t* req, qse_httpd_rsrc_t* target)
{
server_xtn_t* server_xtn;
const qse_mchar_t* qpath;
const qse_mchar_t* idxfile;
qse_mchar_t* xpath;
qse_httpd_stat_t st;
qse_size_t i;
int n, stx;
qpath = qse_htre_getqpath(req);
QSE_MEMSET (target, 0, QSE_SIZEOF(*target));
qse_printf (QSE_T(">>> MAKING RESOURCE [%hs]\n"), qpath);
server_xtn = qse_httpd_getserverxtn (httpd, client->server);
if (server_xtn->cfg[SERVER_XTN_CFG_REALM] &&
server_xtn->cfg[SERVER_XTN_CFG_AUTH])
{
const qse_htre_hdrval_t* auth;
auth = qse_htre_getheaderval (req, QSE_MT("Authorization"));
if (auth)
{
while (auth->next) auth = auth->next;
if (qse_mbszcasecmp(auth->ptr, QSE_MT("Basic "), 6) == 0)
{
if (qse_mbscmp (&auth->ptr[6], server_xtn->cfg[SERVER_XTN_CFG_AUTH]) == 0) goto auth_ok;
}
}
target->type = QSE_HTTPD_RSRC_AUTH;
target->u.auth.realm = server_xtn->cfg[SERVER_XTN_CFG_REALM];
return 0;
}
auth_ok:
idxfile = QSE_NULL;
xpath = merge_paths (httpd, server_xtn->cfg[SERVER_XTN_CFG_DOCROOT], qpath);
if (xpath == QSE_NULL) return -1;
stx = stat_file (httpd, xpath, &st, 0);
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
if (stx <= -1)
{
/* this OS may fail in stat_file() if the path contains the trailing
* separator. it 's beause of the way FindFirstFile() or DosQueryPathInfo()
* is ussed in stat_file(). let me work around it here. */
qse_size_t pl = qse_mbslen(xpath);
if (pl > 1 && xpath[pl - 1] == QSE_MT('/'))
{
xpath[pl-1] = QSE_MT('\0');
stx = stat_file (httpd, xpath, &st, 0);
xpath[pl-1] = QSE_MT('/');
}
}
#endif
if (stx >= 0 && st.isdir)
{
/* it is a directory */
if (server_xtn->cfg2.s.idxstd)
{
/* try to locate an index file */
for (i = 0; server_xtn->cfg2.s.idxstd[i].name; i++)
{
qse_mchar_t* tpath;
tpath = merge_paths (httpd, xpath, server_xtn->cfg2.s.idxstd[i].name);
if (tpath == QSE_NULL)
{
QSE_MMGR_FREE (httpd->mmgr, xpath);
return -1;
}
if (httpd->scb->file.stat (httpd, tpath, &st) >= 0 && !st.isdir)
{
/* the index file is found */
QSE_MMGR_FREE (httpd->mmgr, xpath);
xpath = tpath;
idxfile = server_xtn->cfg2.s.idxstd[i].name;
goto attempt_file;
}
QSE_MMGR_FREE (httpd->mmgr, tpath);
}
}
target->type = QSE_HTTPD_RSRC_DIR;
target->u.dir.path = xpath;
}
else
{
attempt_file:
if (server_xtn->cfg2.s.cgistd)
{
/* check if the request can resolve to a cgi script */
n = attempt_cgi (httpd, server_xtn->cfg[SERVER_XTN_CFG_DOCROOT],
xpath, qpath, idxfile, server_xtn->cfg2.s.cgistd, target);
if (n <= -1)
{
QSE_MMGR_FREE (httpd->mmgr, xpath);
return -1;
}
if (n >= 1) return 0;
}
/* fall back to a normal file. */
target->type = QSE_HTTPD_RSRC_FILE;
target->u.file.path = xpath;
target->u.file.mime = QSE_NULL;
if (server_xtn->cfg2.s.mimestd)
{
for (i = 0; server_xtn->cfg2.s.mimestd[i].ext; i++)
{
/* TODO: require the table sorted and so the binary search */
if (qse_mbsend (qpath, server_xtn->cfg2.s.mimestd[i].ext))
target->u.file.mime = server_xtn->cfg2.s.mimestd[i].type;
}
}
}
return 0;
}
static qse_httpd_server_cbstd_t server_cbstd =
{
make_resource,
free_resource
};
/* ------------------------------------------------------------------- */
static void predetach_server (qse_httpd_t* httpd, qse_httpd_server_t* server)
{
server_xtn_t* server_xtn;
qse_size_t i;
server_xtn = (server_xtn_t*) qse_httpd_getserverxtn (httpd, server);
if (server_xtn->predetach) server_xtn->predetach (httpd, server);
for (i = QSE_COUNTOF(server_xtn->cfg); i > 0; )
{
if (server_xtn->cfg[--i])
{
QSE_MMGR_FREE (httpd->mmgr, server_xtn->cfg[i]);
server_xtn->cfg[i] = QSE_NULL;
}
}
}
qse_httpd_server_t* qse_httpd_attachserverstd (
qse_httpd_t* httpd, const qse_char_t* uri,
qse_httpd_server_predetach_t predetach, qse_size_t xtnsize)
{
qse_httpd_server_t server;
qse_httpd_server_t* xserver;
server_xtn_t* server_xtn;
qse_mxstr_t ba;
qse_size_t balen2;
qse_uint16_t default_port;
qse_uri_t xuri;
static qse_httpd_server_cgistd_t server_cgistd[] =
{
{ QSE_MT(".cgi"), 4, 0 },
{ QSE_MT(".nph"), 4, 1 },
{ QSE_NULL, 0, 0 }
};
static qse_httpd_server_mimestd_t server_mimestd[] =
{
{ QSE_MT(".html"), QSE_MT("text/html") },
{ QSE_MT(".htm"), QSE_MT("text/htm") },
{ QSE_MT(".txt"), QSE_MT("text/plain") },
{ QSE_MT(".log"), QSE_MT("text/plain") },
{ QSE_MT(".css"), QSE_MT("text/css") },
{ QSE_MT(".xml"), QSE_MT("text/xml") },
{ QSE_MT(".js"), QSE_MT("application/javascript") },
{ QSE_MT(".jpg"), QSE_MT("image/jpeg") },
{ QSE_MT(".png"), QSE_MT("image/png") },
{ QSE_MT(".mp4"), QSE_MT("video/mp4") },
{ QSE_MT(".mp3"), QSE_MT("audio/mpeg") },
{ QSE_MT(".c"), QSE_MT("text/plain") },
{ QSE_MT(".h"), QSE_MT("text/plain") },
{ QSE_MT(".cpp"), QSE_MT("text/plain") },
{ QSE_MT(".hpp"), QSE_MT("text/plain") },
{ QSE_NULL, QSE_NULL }
};
#if defined(QSE_CHAR_IS_MCHAR)
qse_mcstr_t tmp[4] =
{
{ QSE_MT(""), 0 },
{ QSE_MT(":"), 1 },
{ QSE_MT(""), 0 },
{ QSE_NULL, 0 }
};
#else
qse_wcstr_t tmp[4] =
{
{ QSE_WT(""), 0 },
{ QSE_WT(":"), 1 },
{ QSE_WT(""), 0 },
{ QSE_NULL, 0 }
};
#endif
QSE_MEMSET (&server, 0, QSE_SIZEOF(server));
if (qse_strtouri (uri, &xuri, QSE_STRTOURI_NOQUERY) <= -1) goto invalid;
if (qse_strxcasecmp (xuri.scheme.ptr, xuri.scheme.len, QSE_T("http")) == 0)
{
default_port = DEFAULT_PORT;
}
else if (qse_strxcasecmp (xuri.scheme.ptr, xuri.scheme.len, QSE_T("https")) == 0)
{
server.flags |= QSE_HTTPD_SERVER_SECURE;
default_port = DEFAULT_SECURE_PORT;
}
else goto invalid;
if (qse_strntonwad (
xuri.host.ptr,
xuri.host.len + (xuri.port.ptr? (xuri.port.len + 1): 0),
&server.nwad) <= -1) goto invalid;
if (server.nwad.type == QSE_NWAD_IN4)
{
if (server.nwad.u.in4.port == 0)
server.nwad.u.in4.port = qse_hton16(default_port);
}
else if (server.nwad.type == QSE_NWAD_IN6)
{
if (server.nwad.u.in6.port == 0)
server.nwad.u.in6.port = qse_hton16(default_port);
}
xserver = qse_httpd_attachserver (
httpd, &server, predetach_server, QSE_SIZEOF(*server_xtn) + xtnsize);
if (xserver == QSE_NULL) return QSE_NULL;
server_xtn = qse_httpd_getserverxtn (httpd, xserver);
if (!xuri.path.ptr)
{
/* the path part is not specified */
#if defined(QSE_CHAR_IS_MCHAR)
xuri.path.ptr = QSE_MT("/");
#else
xuri.path.ptr = QSE_WT("/");
#endif
xuri.path.len = 1;
}
if (xuri.auth.user.ptr)
{
tmp[0].ptr = xuri.auth.user.ptr;
tmp[0].len = xuri.auth.user.len;
}
if (xuri.auth.pass.ptr)
{
tmp[2].ptr = xuri.auth.pass.ptr;
tmp[2].len = xuri.auth.pass.len;
}
#if defined(QSE_CHAR_IS_MCHAR)
if (qse_ismbsdriveabspath((const qse_mchar_t*)xuri.path.ptr + 1))
server_xtn->cfg[SERVER_XTN_CFG_DOCROOT] = qse_mbsxdup ((const qse_mchar_t*)xuri.path.ptr + 1, xuri.path.len - 1, httpd->mmgr);
else
server_xtn->cfg[SERVER_XTN_CFG_DOCROOT] = qse_mbsxdup (xuri.path.ptr, xuri.path.len, httpd->mmgr);
if (xuri.frag.ptr) server_xtn->cfg[SERVER_XTN_CFG_REALM] = qse_mbsxdup (xuri.frag.ptr, xuri.frag.len, httpd->mmgr);
ba.ptr = qse_mcstradup (tmp, &ba.len, httpd->mmgr);
#else
if (qse_iswcsdriveabspath((const qse_wchar_t*)xuri.path.ptr + 1))
server_xtn->cfg[SERVER_XTN_CFG_DOCROOT] = qse_wcsntombsdup ((const qse_wchar_t*)xuri.path.ptr + 1, xuri.path.len - 1, QSE_NULL, httpd->mmgr);
else
server_xtn->cfg[SERVER_XTN_CFG_DOCROOT] = qse_wcsntombsdup (xuri.path.ptr, xuri.path.len, QSE_NULL, httpd->mmgr);
if (xuri.frag.ptr) server_xtn->cfg[SERVER_XTN_CFG_REALM] = qse_wcsntombsdup (xuri.frag.ptr, xuri.frag.len, QSE_NULL, httpd->mmgr);
ba.ptr = qse_wcsnatombsdup (tmp, &ba.len, httpd->mmgr);
#endif
if ((!server_xtn->cfg[SERVER_XTN_CFG_DOCROOT]) ||
(xuri.frag.ptr && !server_xtn->cfg[SERVER_XTN_CFG_REALM]) ||
!ba.ptr)
{
if (ba.ptr) QSE_MMGR_FREE (httpd->mmgr, ba.ptr);
goto nomem_after_attach;
}
balen2 = ((ba.len / 3) + 1) * 4;
server_xtn->cfg[SERVER_XTN_CFG_AUTH] = QSE_MMGR_ALLOC (
httpd->mmgr, (balen2 + 1) * QSE_SIZEOF(qse_mchar_t));
if (!server_xtn->cfg[SERVER_XTN_CFG_AUTH])
{
QSE_MMGR_FREE (httpd->mmgr, ba.ptr);
goto nomem_after_attach;
}
qse_enbase64 (
ba.ptr, ba.len,
server_xtn->cfg[SERVER_XTN_CFG_AUTH],
balen2,
&balen2
);
QSE_MMGR_FREE (httpd->mmgr, ba.ptr);
(server_xtn->cfg[SERVER_XTN_CFG_AUTH])[balen2] = QSE_MT('\0');
server_xtn->predetach = predetach;
server_xtn->cfg2.s.cbstd = &server_cbstd;
server_xtn->cfg2.s.cgistd = server_cgistd;
server_xtn->cfg2.s.mimestd = server_mimestd;
server_xtn->cfg2.s.idxstd = QSE_NULL;
return xserver;
invalid:
httpd->errnum = QSE_HTTPD_EINVAL;
return QSE_NULL;
nomem_after_attach:
qse_httpd_detachserver (httpd, xserver);
httpd->errnum = QSE_HTTPD_ENOMEM;
return QSE_NULL;
}
int qse_httpd_getserveroptstd (
qse_httpd_t* httpd, qse_httpd_server_t* server,
qse_httpd_server_optstd_t id, void* value)
{
server_xtn_t* server_xtn;
server_xtn = qse_httpd_getserverxtn (httpd, server);
switch (id)
{
case QSE_HTTPD_SERVER_DOCROOT:
case QSE_HTTPD_SERVER_REALM:
case QSE_HTTPD_SERVER_AUTH:
case QSE_HTTPD_SERVER_ERRCSS:
case QSE_HTTPD_SERVER_DIRCSS:
*(qse_mchar_t**)value = server_xtn->cfg[id - QSE_HTTPD_SERVER_DOCROOT];
return 0;
case QSE_HTTPD_SERVER_CBSTD:
case QSE_HTTPD_SERVER_CGISTD:
case QSE_HTTPD_SERVER_MIMESTD:
case QSE_HTTPD_SERVER_IDXSTD:
*(qse_mchar_t**)value = (void*)server_xtn->cfg2.a[id - QSE_HTTPD_SERVER_CBSTD];
return 0;
}
httpd->errnum = QSE_HTTPD_EINVAL;
return -1;
}
int qse_httpd_setserveroptstd (
qse_httpd_t* httpd, qse_httpd_server_t* server,
qse_httpd_server_optstd_t id, const void* value)
{
server_xtn_t* server_xtn;
qse_mchar_t* mctmp;
server_xtn = qse_httpd_getserverxtn (httpd, server);
switch (id)
{
case QSE_HTTPD_SERVER_DOCROOT:
case QSE_HTTPD_SERVER_REALM:
case QSE_HTTPD_SERVER_AUTH:
case QSE_HTTPD_SERVER_ERRCSS:
case QSE_HTTPD_SERVER_DIRCSS:
mctmp = (qse_mchar_t*)value;
if(mctmp)
{
mctmp = qse_mbsdup ((qse_mchar_t*)mctmp, httpd->mmgr);
if (mctmp == QSE_NULL)
{
httpd->errnum = QSE_HTTPD_ENOMEM;
return -1;
}
}
if (server_xtn->cfg[id - QSE_HTTPD_SERVER_DOCROOT])
{
QSE_MMGR_FREE (httpd->mmgr,
server_xtn->cfg[id - QSE_HTTPD_SERVER_DOCROOT]);
}
server_xtn->cfg[id - QSE_HTTPD_SERVER_DOCROOT] = mctmp;
return 0;
case QSE_HTTPD_SERVER_CBSTD:
case QSE_HTTPD_SERVER_CGISTD:
case QSE_HTTPD_SERVER_MIMESTD:
case QSE_HTTPD_SERVER_IDXSTD:
server_xtn->cfg2.a[id - QSE_HTTPD_SERVER_CBSTD] = value;
return 0;
}
httpd->errnum = QSE_HTTPD_EINVAL;
return -1;
}
void* qse_httpd_getserverxtnstd (qse_httpd_t* httpd, qse_httpd_server_t* server)
{
server_xtn_t* xtn = qse_httpd_getserverxtn (httpd, server);
return (void*)(xtn + 1);
}
/* ------------------------------------------------------------------- */
int qse_httpd_loopstd (qse_httpd_t* httpd, const qse_ntime_t* tmout)
{
return qse_httpd_loop (
httpd, &httpd_system_callbacks,
&httpd_request_callbacks, tmout);
}