2595 lines
79 KiB
C
2595 lines
79 KiB
C
/*
|
|
* $Id$
|
|
*
|
|
Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.
|
|
|
|
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.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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.
|
|
*/
|
|
|
|
|
|
#include "httpd.h"
|
|
#include "../cmn/mem-prv.h"
|
|
#include <qse/cmn/str.h>
|
|
#include <qse/cmn/fmt.h>
|
|
#include <qse/cmn/hton.h>
|
|
|
|
typedef struct task_proxy_arg_t task_proxy_arg_t;
|
|
struct task_proxy_arg_t
|
|
{
|
|
const qse_httpd_rsrc_proxy_t* rsrc;
|
|
qse_htre_t* req;
|
|
};
|
|
|
|
typedef struct task_proxy_t task_proxy_t;
|
|
struct task_proxy_t
|
|
{
|
|
#define PROXY_INIT_FAILED (1u << 0)
|
|
#define PROXY_RAW (1u << 1)
|
|
#define PROXY_TRANSPARENT (1u << 2)
|
|
#define PROXY_DNS_SERVER (1u << 3) /* dns server address specified */
|
|
#define PROXY_URS_SERVER (1u << 4) /* urs server address specified */
|
|
#define PROXY_OUTBAND_PEER_NAME (1u << 5) /* the peer_name pointer points to
|
|
a separate memory chunk outside
|
|
the task_proxy_t chunk. explicit
|
|
deallocatin is required */
|
|
#define PROXY_RESOLVE_PEER_NAME (1u << 6)
|
|
#define PROXY_PEER_NAME_RESOLVING (1u << 7)
|
|
#define PROXY_PEER_NAME_RESOLVED (1u << 8)
|
|
#define PROXY_PEER_NAME_UNRESOLVED (1u << 9)
|
|
#define PROXY_REWRITE_URL (1u << 10)
|
|
#define PROXY_URL_REWRITING (1u << 11)
|
|
#define PROXY_URL_PREREWRITTEN (1u << 12) /* URL has been prerewritten in prerewrite(). */
|
|
#define PROXY_URL_REWRITTEN (1u << 13)
|
|
#define PROXY_URL_REDIRECTED (1u << 14)
|
|
#define PROXY_X_FORWARDED (1u << 15) /* Add X-Forwarded-For and X-Forwarded-Proto */
|
|
#define PROXY_VIA (1u << 16) /* Via: added to the request */
|
|
#define PROXY_VIA_RETURNING (1u << 17) /* Via: added to the response */
|
|
#define PROXY_ALLOW_UPGRADE (1u << 18)
|
|
#define PROXY_UPGRADE_REQUESTED (1u << 19)
|
|
#define PROXY_PROTOCOL_SWITCHED (1u << 20)
|
|
#define PROXY_GOT_BAD_REQUEST (1u << 21)
|
|
unsigned int flags;
|
|
qse_httpd_t* httpd;
|
|
qse_httpd_client_t* client;
|
|
|
|
int method;
|
|
qse_http_version_t version;
|
|
int keepalive; /* taken from the request */
|
|
|
|
qse_httpd_task_t* task;
|
|
qse_mchar_t* url_to_rewrite;
|
|
qse_size_t qpath_pos_in_reqfwdbuf; /* position where qpath begins */
|
|
qse_size_t qpath_len_in_reqfwdbuf; /* length of qpath + qparams */
|
|
|
|
qse_httpd_dns_server_t dns_server;
|
|
qse_httpd_urs_server_t urs_server;
|
|
qse_mchar_t* pseudonym;
|
|
qse_htrd_t* peer_htrd;
|
|
|
|
qse_httpd_mod_t* dns_preresolve_mod;
|
|
qse_mchar_t* peer_name;
|
|
qse_uint16_t peer_port;
|
|
|
|
qse_httpd_peer_t* peer; /* it points to static_peer initially. it can get changed to something else */
|
|
qse_httpd_peer_t static_peer;
|
|
#define PROXY_PEER_OPEN (1 << 0)
|
|
#define PROXY_PEER_CONNECTED (1 << 1)
|
|
int peer_status;
|
|
|
|
#define PROXY_REQ_FWDERR (1 << 0)
|
|
#define PROXY_REQ_FWDCHUNKED (1 << 1)
|
|
int reqflags;
|
|
qse_htre_t* req; /* set to original client request associated with this if necessary */
|
|
qse_mbs_t* reqfwdbuf; /* content from the request */
|
|
|
|
qse_mbs_t* res;
|
|
qse_size_t res_consumed;
|
|
qse_size_t res_pending;
|
|
#define PROXY_RES_CLIENT_DISCON (1 << 0) /* disconnect client after task */
|
|
#define PROXY_RES_CLIENT_CHUNK (1 << 1) /* chunk chunked output to client */
|
|
#define PROXY_RES_PEER_CLOSE (1 << 2) /* read peer until close.
|
|
* no chunk nor no size specified */
|
|
#define PROXY_RES_PEER_CHUNK (1 << 4) /* peer's output is chunked */
|
|
#define PROXY_RES_PEER_LENGTH (1 << 5) /* peer's output is set with
|
|
* the content-length */
|
|
#define PROXY_RES_PEER_LENGTH_FAKE (1 << 6) /* peer_output_length is fake */
|
|
#define PROXY_RES_EVER_SENTBACK (1 << 7) /* any single byte sent back to a client */
|
|
#define PROXY_RES_AWAIT_100 (1 << 10) /* waiting for 100 continue */
|
|
#define PROXY_RES_AWAIT_RESHDR (1 << 11) /* waiting for response header */
|
|
#define PROXY_RES_AWAIT_RESCON (1 << 12) /* waiting for response content.
|
|
* used iif PROXY_RES_CLIENT_CHUNK
|
|
* is on */
|
|
#define PROXY_RES_RECEIVED_100 (1 << 13) /* got 100 continue */
|
|
#define PROXY_RES_RECEIVED_RESHDR (1 << 14) /* got response header at least */
|
|
#define PROXY_RES_RECEIVED_RESCON (1 << 15) /* finished getting the response
|
|
* content fully. used iif
|
|
* PROXY_RES_CLIENT_CHUNK is on */
|
|
int resflags;
|
|
|
|
qse_size_t peer_output_length;
|
|
qse_size_t peer_output_received;
|
|
|
|
qse_mchar_t buf[MAX_SEND_SIZE];
|
|
qse_size_t buflen;
|
|
};
|
|
|
|
typedef struct proxy_peer_htrd_xtn_t proxy_peer_htrd_xtn_t;
|
|
struct proxy_peer_htrd_xtn_t
|
|
{
|
|
task_proxy_t* proxy;
|
|
qse_httpd_client_t* client;
|
|
qse_httpd_task_t* task;
|
|
};
|
|
|
|
/* ----------------------------------------------------------------- */
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
#define DBGOUT_PROXY_ERROR(proxy, msg) \
|
|
do { \
|
|
qse_mchar_t tmp1[128], tmp2[128]; \
|
|
qse_nwadtombs (&(proxy)->peer->nwad, tmp1, QSE_COUNTOF(tmp1), QSE_NWADTOMBS_ALL); \
|
|
qse_nwadtombs (&(proxy)->client->remote_addr, tmp2, QSE_COUNTOF(tmp2), QSE_NWADTOMBS_ALL); \
|
|
HTTPD_DBGOUT3 ("Proxy error with peer [%hs] client [%hs] - %hs\n", tmp1, tmp2, msg); \
|
|
} while(0)
|
|
#else
|
|
#define DBGOUT_PROXY_ERROR(proxy, msg)
|
|
#endif
|
|
|
|
static int proxy_add_header_to_buffer (
|
|
task_proxy_t* proxy, qse_mbs_t* buf, const qse_mchar_t* key, const qse_htre_hdrval_t* val)
|
|
{
|
|
QSE_ASSERT (val != QSE_NULL);
|
|
|
|
do
|
|
{
|
|
if (qse_mbs_cat (buf, key) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (buf, QSE_MT(": ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (buf, val->ptr) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (buf, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
val = val->next;
|
|
}
|
|
while (val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_add_header_to_buffer_with_extra_data (
|
|
task_proxy_t* proxy, qse_mbs_t* buf, const qse_mchar_t* key, const qse_htre_hdrval_t* val,
|
|
const qse_mchar_t* fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
QSE_ASSERT (val != QSE_NULL);
|
|
|
|
/* NOTE: append the extra data to each value */
|
|
do
|
|
{
|
|
va_start (ap, fmt);
|
|
if (qse_mbs_cat (buf, key) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (buf, QSE_MT(": ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (buf, val->ptr) == (qse_size_t)-1 ||
|
|
(fmt && qse_mbs_vfcat (buf, fmt, ap) == (qse_size_t)-1) ||
|
|
qse_mbs_cat (buf, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
va_end (ap);
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
va_end (ap);
|
|
val = val->next;
|
|
}
|
|
while (val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_capture_peer_header (qse_htre_t* req, const qse_mchar_t* key, const qse_htre_hdrval_t* val, void* ctx)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)ctx;
|
|
|
|
#if 0
|
|
if (!(proxy->httpd->opt.trait & QSE_HTTPD_PROXYNOVIA) && !(proxy->flags & PROXY_VIA_RETURNING))
|
|
{
|
|
if (qse_mbscasecmp (key, QSE_MT("Via")) == 0)
|
|
{
|
|
qse_mchar_t extra[128];
|
|
const qse_mchar_t* pseudonym;
|
|
|
|
proxy->flags |= PROXY_VIA_RETURNING;
|
|
|
|
if (proxy->pseudonym[0])
|
|
{
|
|
pseudonym = proxy->pseudonym;
|
|
}
|
|
else
|
|
{
|
|
qse_nwadtombs (&proxy->client->local_addr, extra, QSE_COUNTOF(extra), QSE_NWADTOMBS_ALL);
|
|
pseudonym = extra;
|
|
}
|
|
|
|
return proxy_add_header_to_buffer_with_extra_data (
|
|
proxy, proxy->res, key, val,
|
|
QSE_MT(", %d.%d %hs (%hs)"),
|
|
(int)proxy->version.major,
|
|
(int)proxy->version.minor,
|
|
pseudonym,
|
|
qse_httpd_getname(proxy->httpd));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (qse_mbscasecmp (key, QSE_MT("Connection")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Transfer-Encoding")) != 0)
|
|
{
|
|
return proxy_add_header_to_buffer (proxy, proxy->res, key, val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_capture_peer_trailer (qse_htre_t* req, const qse_mchar_t* key, const qse_htre_hdrval_t* val, void* ctx)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)ctx;
|
|
|
|
if (qse_mbscasecmp (key, QSE_MT("Transfer-Encoding")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Content-Length")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Connection")) != 0)
|
|
{
|
|
return proxy_add_header_to_buffer (proxy, proxy->res, key, val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_capture_client_header (qse_htre_t* req, const qse_mchar_t* key, const qse_htre_hdrval_t* val, void* ctx)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)ctx;
|
|
|
|
/* EXPERIMENTAL: REMOVE HEADERS.
|
|
* FOR EXAMPLE, You can remove Referer or forge it to give analysis systems harder time */
|
|
if (qse_mbscasecmp (key, QSE_MT("Transfer-Encoding")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Content-Length")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Proxy-Connection")) != 0 /* EXPERIMENTAL */ /* &&
|
|
qse_mbscasecmp (key, QSE_MT("Referer")) != 0*/)
|
|
{
|
|
return proxy_add_header_to_buffer (proxy, proxy->reqfwdbuf, key, val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_capture_client_trailer (qse_htre_t* req, const qse_mchar_t* key, const qse_htre_hdrval_t* val, void* ctx)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)ctx;
|
|
|
|
if (qse_mbscasecmp (key, QSE_MT("Transfer-Encoding")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Content-Length")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Connection")) != 0 &&
|
|
qse_mbscasecmp (key, QSE_MT("Proxy-Connection")) != 0)
|
|
{
|
|
return proxy_add_header_to_buffer (proxy, proxy->reqfwdbuf, key, val);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_snatch_client_input_raw (
|
|
qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx)
|
|
{
|
|
/* it is a callback function set to the client-side htrd reader
|
|
* when the raw proxying is enabled. raw proxying doesn't parse
|
|
* requests. */
|
|
|
|
qse_httpd_task_t* task;
|
|
task_proxy_t* proxy;
|
|
|
|
task = (qse_httpd_task_t*)ctx;
|
|
proxy = (task_proxy_t*)task->ctx;
|
|
|
|
/* this function is never called with ptr of QSE_NULL
|
|
* because this callback is set manually after the request
|
|
* has been discarded or completed in task_init_proxy() and
|
|
* qse_htre_completecontent() or qse_htre_discardcontent() is
|
|
* not called again. Unlike proxy_snatch_client_input(),
|
|
* it doesn't care about EOF indicated by ptr of QSE_NULL. */
|
|
if (ptr && !(proxy->reqflags & PROXY_REQ_FWDERR))
|
|
{
|
|
if (qse_mbs_ncat (proxy->reqfwdbuf, ptr, len) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
task->trigger.v[0].mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_snatch_client_input (
|
|
qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx)
|
|
{
|
|
/* it is a callback function set to the client-side htrd reader
|
|
* when the normal proxying is enabled. normal proxying requires
|
|
* request parsing. */
|
|
|
|
qse_httpd_task_t* task;
|
|
task_proxy_t* proxy;
|
|
|
|
task = (qse_httpd_task_t*)ctx;
|
|
proxy = (task_proxy_t*)task->ctx;
|
|
|
|
if (ptr == QSE_NULL)
|
|
{
|
|
/*
|
|
* this callback is called with ptr of QSE_NULL
|
|
* when the request is completed or discarded.
|
|
* and this indicates that there's nothing more to read
|
|
* from the client side. this can happen when the end of
|
|
* a request is seen or when an error occurs
|
|
*/
|
|
QSE_ASSERT (len == 0);
|
|
|
|
if (proxy->reqflags & PROXY_REQ_FWDCHUNKED)
|
|
{
|
|
/* add the 0-sized chunk and trailers */
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("0\r\n")) == (qse_size_t)-1 ||
|
|
qse_htre_walktrailers (req, proxy_capture_client_trailer, proxy) <= -1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/* mark the there's nothing to read form the client side */
|
|
qse_htre_unsetconcb (proxy->req);
|
|
proxy->req = QSE_NULL;
|
|
|
|
/* since there is no more to read from the client side.
|
|
* the relay trigger is not needed any more. */
|
|
task->trigger.cmask &= ~QSE_HTTPD_TASK_TRIGGER_READ;
|
|
|
|
if (QSE_MBS_LEN(proxy->reqfwdbuf) > 0 &&
|
|
(proxy->peer_status & PROXY_PEER_CONNECTED) &&
|
|
!(task->trigger.v[0].mask & QSE_HTTPD_TASK_TRIGGER_WRITE))
|
|
{
|
|
/* there's nothing more to read from the client side.
|
|
* there's something to forward in the forwarding buffer.
|
|
* but no write trigger is set. add the write trigger
|
|
* for task invocation. */
|
|
task->trigger.v[0].mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
}
|
|
else if (!(proxy->reqflags & PROXY_REQ_FWDERR))
|
|
{
|
|
/* we can write to the peer if a forwarding error
|
|
* didn't occur previously. we store data from the client side
|
|
* to the forwaring buffer only if there's no such previous
|
|
* error. if an error occurred, we simply drop the data. */
|
|
if (proxy->reqflags & PROXY_REQ_FWDCHUNKED)
|
|
{
|
|
qse_mchar_t buf[64];
|
|
qse_fmtuintmaxtombs (
|
|
buf, QSE_COUNTOF(buf), len,
|
|
16 | QSE_FMTUINTMAXTOMBS_UPPERCASE,
|
|
-1, QSE_MT('\0'), QSE_NULL);
|
|
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, buf) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1 ||
|
|
qse_mbs_ncat (proxy->reqfwdbuf, ptr, len) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (qse_mbs_ncat (proxy->reqfwdbuf, ptr, len) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
task->trigger.v[0].mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_snatch_peer_output (
|
|
qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx)
|
|
{
|
|
/* this is a content callback function called by the peer
|
|
* response reader (proxy->peer_htrd). */
|
|
|
|
qse_httpd_task_t* task;
|
|
task_proxy_t* proxy;
|
|
|
|
task = (qse_httpd_task_t*)ctx;
|
|
proxy = (task_proxy_t*)task->ctx;
|
|
|
|
/* this callback is enabled if and only if the output back to
|
|
* the client should be chunked */
|
|
QSE_ASSERT (proxy->resflags & PROXY_RES_CLIENT_CHUNK);
|
|
|
|
/* TODO: better condition for compaction??? */
|
|
if (proxy->res_pending > 0 && proxy->res_consumed > 0)
|
|
{
|
|
qse_mbs_del (proxy->res, 0, proxy->res_consumed);
|
|
proxy->res_consumed = 0;
|
|
}
|
|
|
|
if (ptr == QSE_NULL)
|
|
{
|
|
/* content completed. got the entire response */
|
|
|
|
QSE_ASSERT (len == 0);
|
|
|
|
if (qse_mbs_cat (proxy->res, QSE_MT("0\r\n")) == (qse_size_t)-1 ||
|
|
qse_htre_walktrailers (req, proxy_capture_peer_trailer, proxy) <= -1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
proxy->resflags &= ~PROXY_RES_AWAIT_RESCON;
|
|
proxy->resflags |= PROXY_RES_RECEIVED_RESCON;
|
|
}
|
|
else
|
|
{
|
|
/* append the peer response content to the response buffer */
|
|
qse_mchar_t buf[64];
|
|
qse_fmtuintmaxtombs (
|
|
buf, QSE_COUNTOF(buf), len,
|
|
16 | QSE_FMTUINTMAXTOMBS_UPPERCASE,
|
|
-1, QSE_MT('\0'), QSE_NULL);
|
|
|
|
if (qse_mbs_cat (proxy->res, buf) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1 ||
|
|
qse_mbs_ncat (proxy->res, ptr, len) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
proxy->res_pending = QSE_MBS_LEN(proxy->res) - proxy->res_consumed;
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_htrd_peek_peer_output (qse_htrd_t* htrd, qse_htre_t* res)
|
|
{
|
|
proxy_peer_htrd_xtn_t* xtn;
|
|
task_proxy_t* proxy;
|
|
qse_httpd_t* httpd;
|
|
int res_code;
|
|
|
|
xtn = (proxy_peer_htrd_xtn_t*) qse_htrd_getxtn (htrd);
|
|
proxy = xtn->proxy;
|
|
httpd = proxy->httpd;
|
|
|
|
QSE_ASSERT (!(res->state & QSE_HTRE_DISCARDED));
|
|
|
|
if (proxy->resflags & PROXY_RES_RECEIVED_RESHDR)
|
|
{
|
|
/* this peek handler is being called again.
|
|
* this can happen if qse_htrd_feed() is fed with
|
|
* multiple responses in task_main_proxy_2 (). */
|
|
httpd->errnum = QSE_HTTPD_EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
res_code = qse_htre_getscodeval(res);
|
|
if ((proxy->resflags & PROXY_RES_AWAIT_100) && res_code == 100)
|
|
{
|
|
/* TODO: check if the request contained Expect... */
|
|
|
|
/* 100 continue */
|
|
proxy->resflags &= ~PROXY_RES_AWAIT_100;
|
|
proxy->resflags |= PROXY_RES_RECEIVED_100;
|
|
|
|
if (qse_mbs_cat (proxy->res, qse_htre_getverstr(res)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT(" ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, qse_htre_getscodestr(res)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT(" ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, qse_htre_getsmesg(res)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n\r\n")) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
|
|
/* i don't relay any headers and contents in '100 continue'
|
|
* back to the client */
|
|
qse_htre_discardcontent (res);
|
|
}
|
|
else
|
|
{
|
|
int keepalive;
|
|
|
|
/* add initial line and headers to proxy->res */
|
|
proxy->resflags &= ~PROXY_RES_AWAIT_100;
|
|
proxy->resflags &= ~PROXY_RES_AWAIT_RESHDR;
|
|
proxy->resflags |= PROXY_RES_RECEIVED_RESHDR;
|
|
|
|
keepalive = proxy->keepalive;
|
|
if (res->flags & QSE_HTRE_ATTR_LENGTH)
|
|
{
|
|
/* the response from the peer is length based */
|
|
proxy->resflags |= PROXY_RES_PEER_LENGTH;
|
|
proxy->peer_output_length = res->attr.content_length;
|
|
}
|
|
else if (res->state & QSE_HTRE_COMPLETED)
|
|
{
|
|
/* the response from the peer is chunked or
|
|
* should be read until disconnection.
|
|
* but the whole response has already been
|
|
* received. so i dont' have to do complex
|
|
* chunking or something when returning the
|
|
* response back to the client. */
|
|
proxy->resflags |= PROXY_RES_PEER_LENGTH | PROXY_RES_PEER_LENGTH_FAKE;
|
|
proxy->peer_output_length = qse_htre_getcontentlen(res);
|
|
}
|
|
else
|
|
{
|
|
if (proxy->method == QSE_HTTP_HEAD || (res_code >= 100 && res_code <= 199) ||
|
|
res_code == 204 || res_code == 304)
|
|
{
|
|
/* RFC 2616.
|
|
* 1.Any response message which "MUST NOT" include a message-body (such
|
|
* as the 1xx, 204, and 304 responses and any response to a HEAD
|
|
* request) is always terminated by the first empty line after the
|
|
* header fields, regardless of the entity-header fields present in
|
|
* the message.
|
|
*
|
|
* Google chrome looks to be very strict in interpreting the above
|
|
* clause in that it suffers when it gets a 304 response with
|
|
* chunked transfer-encoding and the content containting the last
|
|
* chunk(0\r\n\r\n) only.
|
|
*
|
|
* Other browsers like firefox and opera didn't have this problem.
|
|
*
|
|
* The hack here assumes that the actual response from the peer
|
|
* contains no message-body(content) either.
|
|
*/
|
|
|
|
/* Force the length to be zero as if Content-Length: 0 is in the
|
|
* original peer response */
|
|
proxy->resflags |= PROXY_RES_PEER_LENGTH | PROXY_RES_PEER_LENGTH_FAKE;
|
|
proxy->peer_output_length = 0;
|
|
}
|
|
else if (qse_comparehttpversions (&proxy->version, &qse_http_v11) >= 0)
|
|
{
|
|
/* client supports chunking */
|
|
|
|
/* chunk response when writing back to client */
|
|
proxy->resflags |= PROXY_RES_CLIENT_CHUNK;
|
|
|
|
if (res->flags & QSE_HTRE_ATTR_CHUNKED)
|
|
{
|
|
/* mark the peer output is chunked */
|
|
proxy->resflags |= PROXY_RES_PEER_CHUNK;
|
|
}
|
|
else
|
|
{
|
|
/* no chunk, no size. i should read
|
|
* the peer's output until it closes connection */
|
|
proxy->resflags |= PROXY_RES_PEER_CLOSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* client doesn't support chunking */
|
|
keepalive = 0;
|
|
|
|
/* mark that the connection to client should be closed */
|
|
proxy->resflags |= PROXY_RES_CLIENT_DISCON;
|
|
/* and push the actual disconnection task */
|
|
if (qse_httpd_entaskdisconnect (httpd, xtn->client, xtn->task) == QSE_NULL) return -1;
|
|
|
|
if (res->flags & QSE_HTRE_ATTR_CHUNKED)
|
|
proxy->resflags |= PROXY_RES_PEER_CHUNK;
|
|
else
|
|
proxy->resflags |= PROXY_RES_PEER_CLOSE;
|
|
}
|
|
}
|
|
|
|
/* begin initial line */
|
|
if (proxy->resflags & PROXY_RES_CLIENT_CHUNK &&
|
|
qse_comparehttpversions (&res->version, &qse_http_v11) < 0)
|
|
{
|
|
qse_mchar_t major[32], minor[32];
|
|
|
|
qse_fmtuintmaxtombs (major, QSE_COUNTOF(major), proxy->version.major, 10, -1, QSE_MT('\0'), QSE_NULL);
|
|
qse_fmtuintmaxtombs (minor, QSE_COUNTOF(minor), proxy->version.minor, 10, -1, QSE_MT('\0'), QSE_NULL);
|
|
|
|
if (qse_mbs_cat (proxy->res, QSE_MT("HTTP/")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, major) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT(".")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, minor) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (qse_mbs_cat (proxy->res, qse_htre_getverstr(res)) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (qse_mbs_cat (proxy->res, QSE_MT(" ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, qse_htre_getscodestr(res)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT(" ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, qse_htre_getsmesg(res)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
/* end initial line */
|
|
|
|
if (proxy->resflags & PROXY_RES_PEER_LENGTH_FAKE)
|
|
{
|
|
/* length should be added by force.
|
|
* let me add Content-Length event if it's 0
|
|
* for less code */
|
|
if (qse_mbs_cat (proxy->res, QSE_MT("Content-Length: ")) == (qse_size_t)-1 ||
|
|
qse_mbs_fcat (proxy->res, QSE_MT("%zu"), (qse_size_t)proxy->peer_output_length) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
else if (proxy->resflags & PROXY_RES_CLIENT_CHUNK)
|
|
{
|
|
if (qse_mbs_cat (proxy->res, QSE_MT("Transfer-Encoding: chunked\r\n")) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((proxy->flags & PROXY_UPGRADE_REQUESTED) && qse_htre_getscodeval(res) == 101)
|
|
{
|
|
if (qse_mbs_cat (proxy->res, QSE_MT("Connection: Upgrade\r\n")) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (qse_mbs_cat (proxy->res, (keepalive? QSE_MT("Connection: keep-alive\r\n"): QSE_MT("Connection: close\r\n"))) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (qse_htre_walkheaders (res, proxy_capture_peer_header, proxy) <= -1) return -1;
|
|
|
|
if (!(httpd->opt.trait & QSE_HTTPD_PROXYNOVIA) && !(proxy->flags & PROXY_VIA_RETURNING) && qse_htre_getscodeval(res) != 100)
|
|
{
|
|
/* add the Via: header into the response if it is not 100. */
|
|
qse_size_t tmp;
|
|
qse_mchar_t extra[128];
|
|
const qse_mchar_t* pseudonym;
|
|
qse_http_version_t v;
|
|
|
|
proxy->flags |= PROXY_VIA_RETURNING;
|
|
|
|
v = *qse_htre_getversion(res);
|
|
if (proxy->pseudonym[0])
|
|
{
|
|
pseudonym = proxy->pseudonym;
|
|
}
|
|
else
|
|
{
|
|
qse_nwadtombs (&proxy->client->local_addr, extra, QSE_COUNTOF(extra), QSE_NWADTOMBS_ALL);
|
|
pseudonym = extra;
|
|
}
|
|
|
|
tmp = qse_mbs_fcat (
|
|
proxy->res, QSE_MT("Via: %d.%d %hs (%hs)\r\n"),
|
|
(int)v.major, (int)v.minor,
|
|
pseudonym, qse_httpd_getname(httpd));
|
|
if (tmp == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
/* end of headers */
|
|
|
|
if (qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1) return -1;
|
|
|
|
/* content body begins here */
|
|
proxy->peer_output_received = qse_htre_getcontentlen(res);
|
|
if ((proxy->resflags & PROXY_RES_PEER_LENGTH) &&
|
|
proxy->peer_output_received > proxy->peer_output_length)
|
|
{
|
|
DBGOUT_PROXY_ERROR (proxy, "Redundant output from peer");
|
|
httpd->errnum = QSE_HTTPD_EINVAL; /* TODO: change it to a better error code */
|
|
return -1;
|
|
}
|
|
|
|
if (proxy->peer_output_received > 0)
|
|
{
|
|
/* the initial part of the content body has been received
|
|
* along with the header. it needs to be added to the result
|
|
* buffer. */
|
|
if (proxy->resflags & PROXY_RES_CLIENT_CHUNK)
|
|
{
|
|
qse_mchar_t buf[64];
|
|
qse_fmtuintmaxtombs (
|
|
buf, QSE_COUNTOF(buf),
|
|
proxy->peer_output_received,
|
|
16 | QSE_FMTUINTMAXTOMBS_UPPERCASE,
|
|
-1, QSE_MT('\0'), QSE_NULL);
|
|
|
|
if (qse_mbs_cat (proxy->res, buf) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1 ||
|
|
qse_mbs_ncat (proxy->res, qse_htre_getcontentptr(res), qse_htre_getcontentlen(res)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->res, QSE_MT("\r\n")) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (qse_mbs_ncat (proxy->res, qse_htre_getcontentptr(res), qse_htre_getcontentlen(res)) == (qse_size_t)-1)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (proxy->resflags & PROXY_RES_CLIENT_CHUNK)
|
|
{
|
|
/* arrange to store further contents received to proxy->res */
|
|
qse_htre_setconcb (res, proxy_snatch_peer_output, xtn->task);
|
|
}
|
|
|
|
if (proxy->flags & PROXY_UPGRADE_REQUESTED)
|
|
{
|
|
QSE_ASSERT (proxy->req != QSE_NULL);
|
|
|
|
if (qse_htre_getscodeval(res) == 101)
|
|
{
|
|
if (proxy->resflags & PROXY_RES_PEER_LENGTH)
|
|
{
|
|
/* the response to 'Upgrade' must not contain contents */
|
|
if (proxy->peer_output_length > 0) goto no_upgrade;
|
|
else proxy->resflags &= ~PROXY_RES_PEER_LENGTH;
|
|
}
|
|
|
|
/* Unlike raw proxying entasked for CONNECT for which disconnection
|
|
* is supposed to be scheduled by the caller, protocol upgrade
|
|
* can be requested over a normal http stream. A stream whose
|
|
* protocol has been switched must not be sustained after the
|
|
* task is over. */
|
|
if (qse_httpd_entaskdisconnect (httpd, proxy->client, xtn->task) == QSE_NULL) return -1;
|
|
proxy->flags |= PROXY_PROTOCOL_SWITCHED;
|
|
}
|
|
else
|
|
{
|
|
no_upgrade:
|
|
/* the update request is not granted. restore the reader
|
|
* to the original state so that HTTP packets can be handled
|
|
* later on. */
|
|
qse_htrd_undummify (proxy->client->htrd);
|
|
qse_htre_unsetconcb (proxy->req);
|
|
proxy->req = QSE_NULL;
|
|
}
|
|
|
|
/* let the reader accept data to be fed */
|
|
qse_htrd_resume (proxy->client->htrd);
|
|
|
|
/*task->trigger.v[0].mask &= ~QSE_HTTPD_TASK_TRIGGER_WRITE;*/ /* peer */
|
|
proxy->task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_READ; /* client-side */
|
|
}
|
|
}
|
|
|
|
|
|
proxy->res_pending = QSE_MBS_LEN(proxy->res) - proxy->res_consumed;
|
|
return 0;
|
|
}
|
|
|
|
static int proxy_htrd_handle_peer_output (qse_htrd_t* htrd, qse_htre_t* req)
|
|
{
|
|
/* finished reading response from the peer */
|
|
return 0;
|
|
}
|
|
|
|
static qse_htrd_recbs_t proxy_peer_htrd_cbs =
|
|
{
|
|
proxy_htrd_peek_peer_output,
|
|
proxy_htrd_handle_peer_output
|
|
};
|
|
|
|
static void proxy_forward_client_input_to_peer (qse_httpd_t* httpd, qse_httpd_task_t* task)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
|
|
QSE_ASSERT (proxy->reqfwdbuf != QSE_NULL);
|
|
|
|
if (QSE_MBS_LEN(proxy->reqfwdbuf) > 0)
|
|
{
|
|
/* there is something to forward in the forwarding buffer. */
|
|
|
|
if (proxy->reqflags & PROXY_REQ_FWDERR)
|
|
{
|
|
/* a forwarding error has occurred previously.
|
|
* clear the forwarding buffer */
|
|
qse_mbs_clear (proxy->reqfwdbuf);
|
|
}
|
|
else
|
|
{
|
|
/* normal forwarding */
|
|
qse_ssize_t n;
|
|
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.peer.send (
|
|
httpd, proxy->peer,
|
|
QSE_MBS_PTR(proxy->reqfwdbuf),
|
|
QSE_MBS_LEN(proxy->reqfwdbuf)
|
|
);
|
|
|
|
if (n <= -1)
|
|
{
|
|
if (httpd->errnum != QSE_HTTPD_EAGAIN)
|
|
{
|
|
DBGOUT_PROXY_ERROR (proxy, "Send failure to peer");
|
|
|
|
proxy->reqflags |= PROXY_REQ_FWDERR;
|
|
qse_mbs_clear (proxy->reqfwdbuf);
|
|
if (proxy->req)
|
|
{
|
|
qse_htre_discardcontent (proxy->req);
|
|
/* NOTE: proxy->req may be set to QSE_NULL
|
|
* in proxy_snatch_client_input() triggered by
|
|
* qse_htre_discardcontent() */
|
|
}
|
|
|
|
task->trigger.v[0].mask &= ~QSE_HTTPD_TASK_TRIGGER_WRITE; /* peer */
|
|
}
|
|
}
|
|
else if (n > 0)
|
|
{
|
|
/* TODO: improve performance.. instead of copying the remaining part
|
|
to the head all the time.. grow the buffer to a certain limit. */
|
|
qse_mbs_del (proxy->reqfwdbuf, 0, n);
|
|
if (QSE_MBS_LEN(proxy->reqfwdbuf) <= 0)
|
|
{
|
|
/* the forwarding buffer is emptied after sending. */
|
|
if (!proxy->req) goto done;
|
|
task->trigger.v[0].mask &= ~QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (!proxy->req)
|
|
{
|
|
done:
|
|
/* there is nothing to read from the client side and
|
|
* there is nothing more to forward in the forwarding buffer.
|
|
* clear the read and write triggers.
|
|
*/
|
|
task->trigger.v[0].mask &= ~QSE_HTTPD_TASK_TRIGGER_WRITE; /* peer */
|
|
task->trigger.cmask &= ~QSE_HTTPD_TASK_TRIGGER_READ; /* client-side */
|
|
}
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static void adjust_peer_name_and_port (task_proxy_t* proxy)
|
|
{
|
|
qse_mchar_t* colon;
|
|
colon = qse_mbschr (proxy->peer_name, QSE_MT(':'));
|
|
if (colon)
|
|
{
|
|
/* handle a port number after the colon sign */
|
|
*colon = QSE_MT('\0');
|
|
proxy->peer_port = qse_mbstoui (colon + 1, 10, QSE_NULL);
|
|
/* TODO: check if there is a garbage after the port number.
|
|
* check if the port number has overflown */
|
|
}
|
|
else
|
|
{
|
|
if (proxy->flags & PROXY_RAW) proxy->peer_port = QSE_HTTPD_DEFAULT_SECURE_PORT;
|
|
else
|
|
{
|
|
if (proxy->peer->flags & QSE_HTTPD_PEER_SECURE)
|
|
proxy->peer_port = QSE_HTTPD_DEFAULT_SECURE_PORT;
|
|
else
|
|
proxy->peer_port = QSE_HTTPD_DEFAULT_PORT;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int task_init_proxy (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
task_proxy_t* proxy;
|
|
task_proxy_arg_t* arg;
|
|
qse_size_t len;
|
|
const qse_mchar_t* ptr;
|
|
|
|
proxy = (task_proxy_t*)qse_httpd_gettaskxtn (httpd, task);
|
|
arg = (task_proxy_arg_t*)task->ctx;
|
|
|
|
QSE_MEMSET (proxy, 0, QSE_SIZEOF(*proxy));
|
|
proxy->httpd = httpd;
|
|
proxy->client = client;
|
|
|
|
proxy->method = qse_htre_getqmethodtype(arg->req);
|
|
proxy->version = *qse_htre_getversion(arg->req);
|
|
proxy->keepalive = (arg->req->flags & QSE_HTRE_ATTR_KEEPALIVE);
|
|
|
|
proxy->task = task; /* needed for url rewriting */
|
|
|
|
proxy->pseudonym = (qse_mchar_t*)(proxy + 1);
|
|
if (arg->rsrc->pseudonym)
|
|
{
|
|
len = qse_mbscpy (proxy->pseudonym, arg->rsrc->pseudonym);
|
|
}
|
|
else
|
|
{
|
|
proxy->pseudonym[0] = QSE_MT('\0');
|
|
len = 0;
|
|
}
|
|
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_RAW) proxy->flags |= PROXY_RAW;
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_TRANSPARENT) proxy->flags |= PROXY_TRANSPARENT;
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_X_FORWARDED) proxy->flags |= PROXY_X_FORWARDED;
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_ALLOW_UPGRADE) proxy->flags |= PROXY_ALLOW_UPGRADE;
|
|
|
|
proxy->peer = &proxy->static_peer;
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_DST_SECURE) proxy->peer->flags |= QSE_HTTPD_PEER_SECURE;
|
|
|
|
proxy->peer->local = arg->rsrc->src.nwad;
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_DST_STR)
|
|
{
|
|
/* the destination given is a string.
|
|
* arrange to make a DNS query in task_main_proxy() */
|
|
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_ENABLE_DNS)
|
|
{
|
|
/* dns service is enabled. carry on with the arrangement */
|
|
|
|
proxy->peer_name = proxy->pseudonym + len + 1;
|
|
qse_mbscpy (proxy->peer_name, arg->rsrc->dst.str);
|
|
adjust_peer_name_and_port (proxy);
|
|
proxy->dns_preresolve_mod = arg->rsrc->dns_preresolve_mod;
|
|
|
|
proxy->flags |= PROXY_RESOLVE_PEER_NAME;
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_DNS_SERVER)
|
|
{
|
|
/* dns server specified */
|
|
proxy->flags |= PROXY_DNS_SERVER;
|
|
proxy->dns_server = arg->rsrc->dns_server;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* dns service is requried to resolve the destination.
|
|
* but it's not enabled */
|
|
httpd->errnum = QSE_HTTPD_ENODNS;
|
|
goto oops;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
proxy->peer->nwad = arg->rsrc->dst.nwad;
|
|
}
|
|
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_ENABLE_URS)
|
|
{
|
|
int x;
|
|
|
|
if (arg->rsrc->urs_prerewrite_mod && arg->rsrc->urs_prerewrite_mod->urs_prerewrite)
|
|
x = arg->rsrc->urs_prerewrite_mod->urs_prerewrite (arg->rsrc->urs_prerewrite_mod, client, arg->req, arg->rsrc->host, &proxy->url_to_rewrite);
|
|
else
|
|
x = httpd->opt.scb.urs.prerewrite (httpd, client, arg->req, arg->rsrc->host, &proxy->url_to_rewrite);
|
|
if (x <= -1) goto oops;
|
|
|
|
/* enable url rewriting */
|
|
proxy->flags |= PROXY_REWRITE_URL;
|
|
if (x == 0)
|
|
{
|
|
/* prerewrite() indicates that proxy->url_to_rewrite is the final
|
|
* rewriting result and no further rewriting is required */
|
|
proxy->flags |= PROXY_URL_PREREWRITTEN;
|
|
}
|
|
|
|
if (arg->rsrc->flags & QSE_HTTPD_RSRC_PROXY_URS_SERVER)
|
|
{
|
|
/* urs server address specified */
|
|
proxy->flags |= PROXY_URS_SERVER;
|
|
proxy->urs_server = arg->rsrc->urs_server;
|
|
}
|
|
}
|
|
|
|
proxy->req = QSE_NULL;
|
|
/* --------------------------------------------------------------------
|
|
* TODO: compose headers to send to peer and push them to fwdbuf...
|
|
* TODO: also change the content length check logic below...
|
|
* -------------------------------------------------------------------- */
|
|
|
|
/* TODO: DETERMINE THIS SIZE */
|
|
len = MAX_SEND_SIZE;
|
|
|
|
proxy->reqfwdbuf = qse_mbs_open (qse_httpd_getmmgr(httpd), 0, (len < 512? 512: len));
|
|
if (proxy->reqfwdbuf == QSE_NULL) goto nomem_oops;
|
|
|
|
if (proxy->flags & PROXY_RAW)
|
|
{
|
|
/* TODO: when connect is attempted, no keep-alive must be hornored.
|
|
* when connection fails, it returns failure page followed by close.... */
|
|
|
|
/* the caller must make sure that the actual content is discarded or completed
|
|
* and the following data is treated as contents */
|
|
QSE_ASSERT (arg->req->state & (QSE_HTRE_DISCARDED | QSE_HTRE_COMPLETED));
|
|
/*QSE_ASSERT (qse_htrd_getopt(client->htrd) & QSE_HTRD_DUMMY);*/
|
|
|
|
proxy->req = arg->req;
|
|
qse_htre_setconcb (proxy->req, proxy_snatch_client_input_raw, task);
|
|
}
|
|
else
|
|
{
|
|
int snatch_needed = 0;
|
|
|
|
/* compose a request to send to the peer using the request
|
|
* received from the client */
|
|
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, qse_htre_getqmethodname(arg->req)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT(" ")) == (qse_size_t)-1) goto nomem_oops;
|
|
|
|
proxy->qpath_pos_in_reqfwdbuf = QSE_STR_LEN(proxy->reqfwdbuf);
|
|
if (arg->req->flags & QSE_HTRE_QPATH_PERDEC)
|
|
{
|
|
/* the query path has been percent-decoded. get the original qpath*/
|
|
|
|
#if 0
|
|
/* percent-encoding back doesn't work all the time because
|
|
* some characters not encoded in the original request may get
|
|
* encoded. some picky servers has thrown errors for such requests */
|
|
qse_mchar_t* qpath, * qpath_enc;
|
|
qse_size_t x;
|
|
|
|
qpath = qse_htre_getqpath(arg->req);
|
|
qpath_enc = qse_perenchttpstrdup (QSE_PERENCHTTPSTR_KEEP_SLASH, qpath, qse_httpd_getmmgr(httpd));
|
|
if (qpath_enc == QSE_NULL) goto nomem_oops;
|
|
|
|
|
|
x = qse_mbs_cat (proxy->reqfwdbuf, qpath_enc);
|
|
if (qpath != qpath_enc) QSE_MMGR_FREE (qse_httpd_getmmgr(httpd), qpath_enc);
|
|
|
|
if (x == (qse_size_t)-1) goto nomem_oops;
|
|
#else
|
|
/* using the original query path minimizes the chance of side-effects */
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, qse_htre_getorgqpath(arg->req)) == (qse_size_t)-1) goto nomem_oops;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* the query path doesn't require encoding or it's never decoded */
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, qse_htre_getqpath(arg->req)) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
|
|
if (qse_htre_getqparam(arg->req))
|
|
{
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("?")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, qse_htre_getqparam(arg->req)) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
|
|
/* length must include the parameters also */
|
|
proxy->qpath_len_in_reqfwdbuf = QSE_STR_LEN(proxy->reqfwdbuf) - proxy->qpath_pos_in_reqfwdbuf;
|
|
|
|
#if 0
|
|
{
|
|
/* EXPERIMENTAL */
|
|
/* KT FILTERING WORKAROUND POC. KT seems to check the Host: the first packet
|
|
* only.I add 1500 byte space octets between the URL and the HTTP version string.
|
|
* the header is likely to be placed in the second packet. it seems to work. */
|
|
qse_mchar_t spc[2000];
|
|
QSE_MEMSET (spc, QSE_MT(' '), QSE_COUNTOF(spc));
|
|
qse_mbs_ncat (proxy->reqfwdbuf, spc, QSE_COUNTOF(spc));
|
|
}
|
|
#endif
|
|
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT(" ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, qse_htre_getverstr(arg->req)) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1 ||
|
|
qse_htre_walkheaders (arg->req, proxy_capture_client_header, proxy) <= -1) goto nomem_oops;
|
|
|
|
if ((proxy->flags & (PROXY_TRANSPARENT | PROXY_X_FORWARDED)) == PROXY_X_FORWARDED)
|
|
{
|
|
qse_mchar_t extra[128];
|
|
|
|
/* client's ip address */
|
|
qse_nwadtombs (&proxy->client->remote_addr, extra, QSE_COUNTOF(extra), QSE_NWADTOMBS_ADDR);
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("X-Forwarded-For: ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, extra) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
|
|
/* client's protocol*/
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("X-Forwarded-Proto: ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, ((client->status & QSE_HTTPD_CLIENT_SECURE)? QSE_MT("https"): QSE_MT("http"))) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
|
|
/* TODO: support the Forwarded header in RFC7239.
|
|
* Forwarded: for=xxx;by=xxx;prot=xxxx */
|
|
}
|
|
|
|
proxy->resflags |= PROXY_RES_AWAIT_RESHDR;
|
|
if ((arg->req->flags & QSE_HTRE_ATTR_EXPECT100) &&
|
|
qse_comparehttpversions (&arg->req->version, &qse_http_v11) >= 0)
|
|
{
|
|
proxy->resflags |= PROXY_RES_AWAIT_100;
|
|
}
|
|
|
|
if (!(httpd->opt.trait & QSE_HTTPD_PROXYNOVIA) && !(proxy->flags & PROXY_VIA))
|
|
{
|
|
/* add the Via: header into the request */
|
|
qse_size_t tmp;
|
|
qse_mchar_t extra[128];
|
|
const qse_mchar_t* pseudonym;
|
|
|
|
proxy->flags |= PROXY_VIA;
|
|
|
|
if (proxy->pseudonym[0])
|
|
{
|
|
pseudonym = proxy->pseudonym;
|
|
}
|
|
else
|
|
{
|
|
qse_nwadtombs (&proxy->client->local_addr, extra, QSE_COUNTOF(extra), QSE_NWADTOMBS_ALL);
|
|
pseudonym = extra;
|
|
}
|
|
tmp = qse_mbs_fcat (
|
|
proxy->reqfwdbuf, QSE_MT("Via: %d.%d %hs (%hs)\r\n"),
|
|
(int)proxy->version.major, (int)proxy->version.minor,
|
|
pseudonym, qse_httpd_getname(httpd));
|
|
if (tmp == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
|
|
if (arg->req->state & QSE_HTRE_DISCARDED)
|
|
{
|
|
/* no content to add */
|
|
if ((arg->req->flags & QSE_HTRE_ATTR_LENGTH) ||
|
|
(arg->req->flags & QSE_HTRE_ATTR_CHUNKED))
|
|
{
|
|
/* i don't add chunk traiers if the
|
|
* request content has been discarded */
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("Content-Length: 0\r\n\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
else
|
|
{
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
}
|
|
else if (arg->req->state & QSE_HTRE_COMPLETED)
|
|
{
|
|
if (arg->req->flags & QSE_HTRE_ATTR_CHUNKED)
|
|
{
|
|
/* add trailers if any */
|
|
if (qse_htre_walktrailers (arg->req, proxy_capture_client_trailer, proxy) <= -1) goto nomem_oops;
|
|
}
|
|
|
|
len = qse_htre_getcontentlen(arg->req);
|
|
if (len > 0 || (arg->req->flags & QSE_HTRE_ATTR_LENGTH) ||
|
|
(arg->req->flags & QSE_HTRE_ATTR_CHUNKED))
|
|
{
|
|
qse_mchar_t buf[64];
|
|
|
|
qse_fmtuintmaxtombs (
|
|
buf, QSE_COUNTOF(buf), len,
|
|
10, -1, QSE_MT('\0'), QSE_NULL);
|
|
|
|
/* force-insert content-length. content-length is added
|
|
* even if the original request dones't contain it */
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("Content-Length: ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, buf) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
|
|
if (len > 0)
|
|
{
|
|
/* content */
|
|
ptr = qse_htre_getcontentptr(arg->req);
|
|
if (qse_mbs_ncat (proxy->reqfwdbuf, ptr, len) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
}
|
|
else if (arg->req->flags & QSE_HTRE_ATTR_LENGTH)
|
|
{
|
|
/* the Content-Length header field is contained in the request. */
|
|
qse_mchar_t buf[64];
|
|
qse_fmtuintmaxtombs (
|
|
buf, QSE_COUNTOF(buf),
|
|
arg->req->attr.content_length,
|
|
10, -1, QSE_MT('\0'), QSE_NULL);
|
|
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("Content-Length: ")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, buf) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
|
|
len = qse_htre_getcontentlen(arg->req);
|
|
if (len > 0)
|
|
{
|
|
/* content received so far */
|
|
ptr = qse_htre_getcontentptr(arg->req);
|
|
if (qse_mbs_ncat (proxy->reqfwdbuf, ptr, len) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
|
|
snatch_needed = 1;
|
|
}
|
|
else
|
|
{
|
|
/* if this request is not chunked nor not length based,
|
|
* the state should be QSE_HTRE_COMPLETED. so only a
|
|
* chunked request should reach here */
|
|
QSE_ASSERT (arg->req->flags & QSE_HTRE_ATTR_CHUNKED);
|
|
|
|
proxy->reqflags |= PROXY_REQ_FWDCHUNKED;
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("Transfer-Encoding: chunked\r\n")) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1 /* end of header */) goto nomem_oops;
|
|
|
|
len = qse_htre_getcontentlen(arg->req);
|
|
if (len > 0)
|
|
{
|
|
qse_mchar_t buf[64];
|
|
|
|
qse_fmtuintmaxtombs (
|
|
buf, QSE_COUNTOF(buf), len,
|
|
16 | QSE_FMTUINTMAXTOMBS_UPPERCASE,
|
|
-1, QSE_MT('\0'), QSE_NULL);
|
|
|
|
ptr = qse_htre_getcontentptr(arg->req);
|
|
|
|
/* chunk length and chunk content */
|
|
if (qse_mbs_cat (proxy->reqfwdbuf, buf) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1 ||
|
|
qse_mbs_ncat (proxy->reqfwdbuf, ptr, len) == (qse_size_t)-1 ||
|
|
qse_mbs_cat (proxy->reqfwdbuf, QSE_MT("\r\n")) == (qse_size_t)-1) goto nomem_oops;
|
|
}
|
|
|
|
snatch_needed = 1;
|
|
}
|
|
|
|
if ((proxy->flags & PROXY_ALLOW_UPGRADE) && qse_htre_getheaderval(arg->req, QSE_MT("Upgrade")))
|
|
{
|
|
/* Upgrade: is found in the request header */
|
|
const qse_htre_hdrval_t* hv;
|
|
hv = qse_htre_getheaderval(arg->req, QSE_MT("Connection"));
|
|
while (hv)
|
|
{
|
|
if (qse_mbscaseword (hv->ptr, QSE_MT("Upgrade"), QSE_MT(','))) break;
|
|
hv = hv->next;
|
|
}
|
|
|
|
if (!hv) goto no_upgrade;
|
|
|
|
if (snatch_needed)
|
|
{
|
|
/* The upgrade request can't have contents.
|
|
* Not allowing contents makes implementation easier. */
|
|
httpd->errnum = QSE_HTTPD_EBADREQ;
|
|
proxy->flags |= PROXY_GOT_BAD_REQUEST;
|
|
goto oops;
|
|
}
|
|
|
|
/* cause feeding of client data to fail. i do this because
|
|
* it's unknown if upgrade will get granted or not.
|
|
* if it's granted, the client input should be treated
|
|
* as an octet string. If not, it should still be handled
|
|
* as HTTP. */
|
|
qse_htrd_suspend (client->htrd);
|
|
|
|
/* prearrange to not parse client input when feeding is resumed */
|
|
qse_htrd_dummify (client->htrd);
|
|
|
|
proxy->flags |= PROXY_UPGRADE_REQUESTED;
|
|
proxy->req = arg->req;
|
|
|
|
/* prearrange to capature client input when feeding is resumed */
|
|
qse_htre_setconcb (proxy->req, proxy_snatch_client_input_raw, proxy->task);
|
|
}
|
|
else
|
|
{
|
|
no_upgrade:
|
|
if (snatch_needed)
|
|
{
|
|
/* set up a callback to be called when the request content
|
|
* is fed to the htrd reader. qse_htre_addcontent() that
|
|
* htrd calls invokes this callback. */
|
|
proxy->req = arg->req;
|
|
qse_htre_setconcb (proxy->req, proxy_snatch_client_input, task);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* no triggers yet since the main loop doesn't allow me to set
|
|
* triggers in the task initializer. however the main task handler
|
|
* will be invoked so long as the client handle is writable by
|
|
* the main loop. */
|
|
#if 0
|
|
qse_printf (QSE_T("GOING TO PROXY [%hs]\n"), QSE_MBS_PTR(proxy->reqfwdbuf));
|
|
#endif
|
|
task->ctx = proxy;
|
|
return 0;
|
|
|
|
nomem_oops:
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
/* goto oops */
|
|
|
|
oops:
|
|
/* since a new task can't be added in the initializer,
|
|
* i mark that initialization failed and let task_main_proxy()
|
|
* add an error task */
|
|
|
|
if (proxy->url_to_rewrite)
|
|
{
|
|
qse_httpd_freemem (httpd, proxy->url_to_rewrite);
|
|
proxy->url_to_rewrite = QSE_NULL;
|
|
proxy->flags &= ~PROXY_REWRITE_URL;
|
|
}
|
|
|
|
if (proxy->reqfwdbuf)
|
|
{
|
|
qse_mbs_close (proxy->reqfwdbuf);
|
|
proxy->reqfwdbuf = QSE_NULL;
|
|
}
|
|
|
|
proxy->flags |= PROXY_INIT_FAILED;
|
|
task->ctx = proxy;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static void task_fini_proxy (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
|
|
if (proxy->peer_status & PROXY_PEER_OPEN)
|
|
{
|
|
int reuse = 0;
|
|
|
|
/* check if the peer connection can be reused */
|
|
if (!(proxy->flags & (PROXY_RAW | PROXY_UPGRADE_REQUESTED | PROXY_PROTOCOL_SWITCHED)) &&
|
|
!(proxy->resflags & PROXY_RES_PEER_CLOSE))
|
|
{
|
|
qse_mchar_t tmpch;
|
|
|
|
/* check if the peer connection dropped connection or
|
|
* sending excessive data. don't reuse such a connection */
|
|
if (httpd->opt.scb.peer.recv (httpd, proxy->peer, &tmpch, 1) <= -1 &&
|
|
httpd->errnum == QSE_HTTPD_EAGAIN) reuse = 1;
|
|
}
|
|
|
|
if (reuse && qse_httpd_cacheproxypeer (httpd, client, proxy->peer))
|
|
{
|
|
/* cache a reusable peer connection */
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
qse_mchar_t tmp[128];
|
|
|
|
qse_nwadtombs (&proxy->peer->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Cached peer [%hs] - %zd\n", tmp, (qse_size_t)proxy->peer->handle);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
qse_mchar_t tmp[128];
|
|
|
|
qse_nwadtombs (&proxy->peer->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Closing peer [%hs] - %zd\n", tmp, (qse_size_t)proxy->peer->handle);
|
|
#endif
|
|
|
|
httpd->opt.scb.peer.close (httpd, proxy->peer);
|
|
|
|
if (proxy->peer->flags & QSE_HTTPD_PEER_CACHED)
|
|
{
|
|
QSE_ASSERT (proxy->peer != &proxy->static_peer);
|
|
qse_httpd_freemem (httpd, proxy->peer);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((proxy->flags & (PROXY_UPGRADE_REQUESTED | PROXY_PROTOCOL_SWITCHED)) == PROXY_UPGRADE_REQUESTED)
|
|
{
|
|
/* upgrade requested but protocol switching not completed yet.
|
|
* this can happen because dummification is performed before
|
|
* the 101 response is received. */
|
|
|
|
/* no harm to call this multiple times */
|
|
qse_htrd_undummify (proxy->client->htrd);
|
|
}
|
|
|
|
if (proxy->res) qse_mbs_close (proxy->res);
|
|
if (proxy->peer_htrd) qse_htrd_close (proxy->peer_htrd);
|
|
if (proxy->reqfwdbuf) qse_mbs_close (proxy->reqfwdbuf);
|
|
if (proxy->req) qse_htre_unsetconcb (proxy->req);
|
|
if (proxy->url_to_rewrite) qse_httpd_freemem (httpd, proxy->url_to_rewrite);
|
|
if (proxy->flags & PROXY_OUTBAND_PEER_NAME) qse_httpd_freemem (httpd, proxy->peer_name);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
static int task_main_proxy_5 (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
qse_ssize_t n;
|
|
|
|
#if 0
|
|
printf ("task_main_proxy_5 trigger[0].mask=%d trigger[1].mask=%d trigger[2].mask=%d\n",
|
|
task->trigger.v[0].mask, task->trigger.v[1].mask, task->trigger.cmask);
|
|
#endif
|
|
|
|
proxy_forward_client_input_to_peer (httpd, task);
|
|
|
|
if (/*(task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) && */ proxy->buflen > 0)
|
|
{
|
|
/* write to the client socket as long as there's something.
|
|
* it's safe to do so as the socket is non-blocking.
|
|
* i commented out the check in the 'if' condition above */
|
|
|
|
/* TODO: check if proxy outputs more than content-length if it is set... */
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.client.send (httpd, client, proxy->buf, proxy->buflen);
|
|
if (n <= -1)
|
|
{
|
|
if (httpd->errnum != QSE_HTTPD_EAGAIN)
|
|
{
|
|
/* can't return internal server error any more... */
|
|
DBGOUT_PROXY_ERROR (proxy, "Send failrue to client");
|
|
return -1;
|
|
}
|
|
}
|
|
else if (n > 0)
|
|
{
|
|
QSE_MEMCPY (&proxy->buf[0], &proxy->buf[n], proxy->buflen - n);
|
|
proxy->buflen -= n;
|
|
}
|
|
}
|
|
|
|
/* if forwarding didn't finish, something is not really right...
|
|
* so long as the output from peer is finished, no more forwarding
|
|
* is performed */
|
|
return (proxy->buflen > 0 || proxy->req ||
|
|
(proxy->reqfwdbuf && QSE_MBS_LEN(proxy->reqfwdbuf) > 0))? 1: 0;
|
|
}
|
|
|
|
static int task_main_proxy_4 (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
qse_ssize_t n;
|
|
|
|
#if 0
|
|
printf ("task_main_proxy_4 trigger[0].mask=%d trigger[1].mask=%d trigger.cmask=%d\n",
|
|
task->trigger.v[0].mask, task->trigger.v[1].mask, task->trigger.cmask);
|
|
#endif
|
|
|
|
proxy_forward_client_input_to_peer (httpd, task);
|
|
|
|
if ((task->trigger.v[0].mask & QSE_HTTPD_TASK_TRIGGER_READABLE) &&
|
|
proxy->buflen < QSE_SIZEOF(proxy->buf))
|
|
{
|
|
reread:
|
|
/* reading from the peer */
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.peer.recv (
|
|
httpd, proxy->peer,
|
|
&proxy->buf[proxy->buflen],
|
|
QSE_SIZEOF(proxy->buf) - proxy->buflen
|
|
);
|
|
if (n <= -1)
|
|
{
|
|
/* can't return internal server error any more... */
|
|
if (httpd->errnum != QSE_HTTPD_EAGAIN)
|
|
{
|
|
DBGOUT_PROXY_ERROR (proxy, "Recv failure from peer");
|
|
return -1;
|
|
}
|
|
|
|
/* carry on as if recv was't called at all */
|
|
}
|
|
else if (n == 0)
|
|
{
|
|
/* peer closed connection */
|
|
if (proxy->resflags & PROXY_RES_PEER_LENGTH)
|
|
{
|
|
QSE_ASSERT (!(proxy->flags & PROXY_RAW));
|
|
|
|
if (proxy->peer_output_received < proxy->peer_output_length)
|
|
{
|
|
DBGOUT_PROXY_ERROR (proxy, "Premature content end");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
task->main = task_main_proxy_5;
|
|
|
|
/* nothing to read from peer. set the mask to 0 */
|
|
task->trigger.v[0].mask = 0;
|
|
|
|
/* arrange to be called if the client side is writable */
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
|
|
if (proxy->flags & PROXY_RAW)
|
|
{
|
|
/* peer connection has been closed.
|
|
* so no more forwarding from the client to the peer
|
|
* is possible. get rid of the content callback on the
|
|
* client side. */
|
|
qse_htre_unsetconcb (proxy->req);
|
|
proxy->req = QSE_NULL;
|
|
}
|
|
else if (proxy->flags & PROXY_PROTOCOL_SWITCHED)
|
|
{
|
|
qse_htrd_undummify (proxy->client->htrd);
|
|
qse_htre_unsetconcb (proxy->req);
|
|
proxy->req = QSE_NULL;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
proxy->buflen += n;
|
|
proxy->peer_output_received += n;
|
|
|
|
if (proxy->resflags & PROXY_RES_PEER_LENGTH)
|
|
{
|
|
QSE_ASSERT (!(proxy->flags & PROXY_RAW));
|
|
|
|
if (proxy->peer_output_received > proxy->peer_output_length)
|
|
{
|
|
/* proxy returning too much data... something is wrong in PROXY */
|
|
DBGOUT_PROXY_ERROR (proxy, "Redundant output from peer");
|
|
return -1;
|
|
}
|
|
else if (proxy->peer_output_received == proxy->peer_output_length)
|
|
{
|
|
/* proxy has finished reading all */
|
|
task->main = task_main_proxy_5;
|
|
task->trigger.v[0].mask = 0;
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (proxy->buflen)
|
|
{
|
|
/* the main loop invokes the task function only if the client
|
|
* side is writable. it should be safe to write whenever
|
|
* this task function is called. even if it's not writable,
|
|
* it should still be ok as the client socket is non-blocking. */
|
|
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.client.send (httpd, client, proxy->buf, proxy->buflen);
|
|
if (n <= -1)
|
|
{
|
|
if (httpd->errnum != QSE_HTTPD_EAGAIN)
|
|
{
|
|
/* can't return internal server error any more... */
|
|
DBGOUT_PROXY_ERROR (proxy, "Send failure to client");
|
|
return -1;
|
|
}
|
|
}
|
|
else if (n > 0)
|
|
{
|
|
QSE_MEMCPY (&proxy->buf[0], &proxy->buf[n], proxy->buflen - n);
|
|
proxy->buflen -= n;
|
|
}
|
|
}
|
|
|
|
if (proxy->peer->flags & QSE_HTTPD_PEER_PENDING)
|
|
{
|
|
/* this QSE_HTTPD_CLIENT_PENDING thing is a dirty hack for SSL.
|
|
* In SSL, data is transmitted in a record. a record can be
|
|
* as large as 16K bytes since its length field is 2 bytes.
|
|
* If SSL_read() has a record but it's given a smaller buffer
|
|
* than the actual record, the next call to select() won't return.
|
|
* there is no data to read at the socket layer. SSL_pending() can
|
|
* tell you the amount of data in the SSL buffer. I try to consume
|
|
* the pending data if the client.recv handler has set QSE_HTTPD_CLIENT_PENDING. */
|
|
|
|
/* BUG BUG BUG.
|
|
* it jumps back to read more. If the client-side is not writable,
|
|
* unnecessary loop is made between this 'goto' and the target label.
|
|
* HOW SHOULD I SOLVE THIS? USE A BIG BUFFER AS LARGE AS 16K? */
|
|
/*if (proxy->buflen < QSE_SIZEOF(proxy->buf))*/ goto reread;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int task_main_proxy_3 (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
/* let's send up the http initial line and headers before
|
|
* attempting to read the reset of content. it may already
|
|
* include some contents as well received together with
|
|
* the header. */
|
|
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
|
|
#if 0
|
|
printf ("task_main_proxy_3 trigger[0].mask=%d trigger[1].mask=%d trigger[2].mask=%d\n",
|
|
task->trigger.v[0].mask, task->trigger.v[1].mask, task->trigger.cmask);
|
|
#endif
|
|
|
|
proxy_forward_client_input_to_peer (httpd, task);
|
|
|
|
if (/*(task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) &&*/ proxy->res_pending > 0)
|
|
{
|
|
/* the client socket is non-blocking. so attempt to send
|
|
* so long as there's something to send regardless of writability
|
|
* of the client socket. see the check commented out in the 'if'
|
|
* condition above.*/
|
|
|
|
qse_ssize_t n;
|
|
qse_size_t count;
|
|
|
|
count = proxy->res_pending;
|
|
if (count > MAX_SEND_SIZE) count = MAX_SEND_SIZE;
|
|
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.client.send (
|
|
httpd, client,
|
|
&QSE_MBS_CHAR(proxy->res,proxy->res_consumed),
|
|
count
|
|
);
|
|
if (n <= -1)
|
|
{
|
|
if (httpd->errnum != QSE_HTTPD_EAGAIN)
|
|
{
|
|
DBGOUT_PROXY_ERROR (proxy, "Send failure to client");
|
|
return -1;
|
|
}
|
|
}
|
|
else if (n > 0)
|
|
{
|
|
proxy->resflags |= PROXY_RES_EVER_SENTBACK;
|
|
proxy->res_consumed += n;
|
|
proxy->res_pending -= n;
|
|
|
|
if (proxy->res_pending <= 0)
|
|
{
|
|
/* all data received from the peer so far(including those injected)
|
|
* have been sent back to the client-side */
|
|
|
|
qse_mbs_clear (proxy->res);
|
|
proxy->res_consumed = 0;
|
|
|
|
if (proxy->flags & PROXY_PROTOCOL_SWITCHED)
|
|
{
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_READ;
|
|
goto read_more;
|
|
}
|
|
|
|
if ((proxy->resflags & PROXY_RES_CLIENT_CHUNK) ||
|
|
((proxy->resflags & PROXY_RES_PEER_LENGTH) && proxy->peer_output_received >= proxy->peer_output_length))
|
|
{
|
|
/* received all contents */
|
|
task->main = task_main_proxy_5;
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
else
|
|
{
|
|
read_more:
|
|
/* there are still more to read from the peer.
|
|
* arrange to read the remaining contents from the peer */
|
|
task->main = task_main_proxy_4;
|
|
/* nothing to write in proxy->res. so clear WRITE from the
|
|
* client side */
|
|
task->trigger.cmask &= ~QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1; /* more work to do */
|
|
}
|
|
|
|
static int task_main_proxy_2 (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
int http_errnum = 500;
|
|
|
|
proxy_forward_client_input_to_peer (httpd, task);
|
|
|
|
if (/*(task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) && */ proxy->res_pending > 0)
|
|
{
|
|
/* the 'if' condition becomes true only if '100 Continue'
|
|
* is received without an actual reply in a previous call to
|
|
* qse_htrd_feed() far below. Since the actual reply is not
|
|
* received yet, i just want to read more while relaying
|
|
* '100 Continue' to the client.
|
|
*
|
|
* attempt to write to the client regardless of writability of
|
|
* the cleint socket as it is non-blocking. see the check commented
|
|
* out in the 'if' condition above. */
|
|
|
|
qse_ssize_t n;
|
|
qse_size_t count;
|
|
|
|
QSE_ASSERT ((proxy->resflags & PROXY_RES_AWAIT_RESHDR) ||
|
|
(proxy->resflags & PROXY_RES_CLIENT_CHUNK));
|
|
|
|
count = proxy->res_pending;
|
|
if (count > MAX_SEND_SIZE) count = MAX_SEND_SIZE;
|
|
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.client.send (
|
|
httpd, client,
|
|
QSE_MBS_CPTR(proxy->res,proxy->res_consumed),
|
|
count
|
|
);
|
|
if (n <= -1)
|
|
{
|
|
if (httpd->errnum != QSE_HTTPD_EAGAIN)
|
|
{
|
|
DBGOUT_PROXY_ERROR (proxy, "Send failure to client");
|
|
goto oops;
|
|
}
|
|
}
|
|
else if (n > 0)
|
|
{
|
|
proxy->resflags |= PROXY_RES_EVER_SENTBACK;
|
|
proxy->res_consumed += n;
|
|
proxy->res_pending -= n;
|
|
|
|
if (proxy->res_pending <= 0)
|
|
{
|
|
/* '100 Continue' and payload received together
|
|
* has all been relayed back. no need for writability
|
|
* check of the client side as there's nothing to write.
|
|
* when something is read from the peer and proxy->res
|
|
* becomes loaded, this cmask is added with WRITE
|
|
* in the 'if' block below that takes care of reading
|
|
* from the peer. */
|
|
task->trigger.cmask &= ~QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (task->trigger.v[0].mask & QSE_HTTPD_TASK_TRIGGER_READABLE)
|
|
{
|
|
qse_ssize_t n;
|
|
|
|
/* there is something to read from peer */
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.peer.recv (
|
|
httpd, proxy->peer,
|
|
&proxy->buf[proxy->buflen],
|
|
QSE_SIZEOF(proxy->buf) - proxy->buflen
|
|
);
|
|
if (n <= -1)
|
|
{
|
|
if (httpd->errnum != QSE_HTTPD_EAGAIN)
|
|
{
|
|
DBGOUT_PROXY_ERROR (proxy, "Recv failure from peer");
|
|
goto oops;
|
|
}
|
|
}
|
|
else if (n == 0)
|
|
{
|
|
if (!(proxy->resflags & PROXY_RES_RECEIVED_RESHDR))
|
|
{
|
|
/* end of output from peer before it has seen a header.
|
|
* the proxy peer must be bad. */
|
|
DBGOUT_PROXY_ERROR (proxy, "Premature header end from peer");
|
|
if (!(proxy->resflags & PROXY_RES_RECEIVED_100)) http_errnum = 502;
|
|
goto oops;
|
|
}
|
|
else
|
|
{
|
|
QSE_ASSERT (proxy->resflags & PROXY_RES_CLIENT_CHUNK);
|
|
|
|
if (proxy->resflags & PROXY_RES_PEER_CLOSE)
|
|
{
|
|
/* i should stop the reader manually since the
|
|
* end of content is indicated by close in this
|
|
* case. call qse_htrd_halt() for this. */
|
|
qse_htrd_halt (proxy->peer_htrd);
|
|
task->main = task_main_proxy_3;
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
return 1;
|
|
}
|
|
|
|
/* premature eof from the peer */
|
|
DBGOUT_PROXY_ERROR (proxy, "No chunked content from peer");
|
|
goto oops;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
proxy->buflen += n;
|
|
}
|
|
|
|
if (proxy->buflen > 0)
|
|
{
|
|
if (qse_htrd_feed (proxy->peer_htrd, proxy->buf, proxy->buflen) <= -1)
|
|
{
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
HTTPD_DBGOUT3 ("Failed to feed proxy peer response to handler - %d [%.*hs]\n",
|
|
qse_htrd_geterrnum(proxy->peer_htrd), (int)proxy->buflen, proxy->buf);
|
|
#endif
|
|
goto oops;
|
|
}
|
|
|
|
proxy->buflen = 0;
|
|
}
|
|
|
|
if (proxy->flags & PROXY_PROTOCOL_SWITCHED)
|
|
{
|
|
task->trigger.cmask = QSE_HTTPD_TASK_TRIGGER_READ;
|
|
if (QSE_MBS_LEN(proxy->res) > 0) task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
task->main = task_main_proxy_3;
|
|
}
|
|
else if (QSE_MBS_LEN(proxy->res) > 0)
|
|
{
|
|
if (proxy->resflags & PROXY_RES_RECEIVED_RESCON)
|
|
{
|
|
/* received the contents in full */
|
|
QSE_ASSERT (proxy->resflags & PROXY_RES_CLIENT_CHUNK);
|
|
task->main = task_main_proxy_3;
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
else if (proxy->resflags & PROXY_RES_AWAIT_RESCON)
|
|
{
|
|
/* waiting for contents */
|
|
QSE_ASSERT (proxy->resflags & PROXY_RES_CLIENT_CHUNK);
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
else if (proxy->resflags & PROXY_RES_RECEIVED_RESHDR)
|
|
{
|
|
/* the actual response header has been received
|
|
* with or without '100 continue'. you can
|
|
* check it with proxy->resflags & PROXY_RES_RECEIVED_100 */
|
|
|
|
if (proxy->resflags & PROXY_RES_CLIENT_CHUNK)
|
|
{
|
|
proxy->resflags |= PROXY_RES_AWAIT_RESCON;
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
else
|
|
{
|
|
#if 0
|
|
qse_printf (QSE_T("TRAILING DATA=%d, [%hs]\n"), (int)QSE_MBS_LEN(proxy->res), QSE_MBS_CPTR(proxy->res,proxy->res_consumed));
|
|
#endif
|
|
/* switch to the next phase */
|
|
task->main = task_main_proxy_3;
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
}
|
|
else if (proxy->resflags & PROXY_RES_RECEIVED_100)
|
|
{
|
|
/* 100 continue has been received but
|
|
* the actual response has not. */
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
else
|
|
{
|
|
/* anything to do? */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* complete headers not seen yet. i need to be called again */
|
|
return 1;
|
|
|
|
oops:
|
|
if (proxy->resflags & PROXY_RES_EVER_SENTBACK) return -1;
|
|
return (qse_httpd_entaskerrorwithmvk (httpd, client, task, http_errnum, proxy->method, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0;
|
|
}
|
|
|
|
static int task_main_proxy_1 (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
/* scheduling of this function is made in task_main_proxy() if
|
|
* the connection to the peer isn't established. this function should
|
|
* check the connection state to the peer. */
|
|
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
int http_errnum = 500;
|
|
|
|
/* wait for peer to get connected */
|
|
if (task->trigger.v[0].mask & QSE_HTTPD_TASK_TRIGGER_READABLE ||
|
|
task->trigger.v[0].mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE)
|
|
{
|
|
int n;
|
|
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.peer.connected (httpd, proxy->peer);
|
|
if (n <= -1)
|
|
{
|
|
/* TODO: translate more error codes to http error codes... */
|
|
|
|
if (httpd->errnum == QSE_HTTPD_ENOENT) http_errnum = 404;
|
|
else if (httpd->errnum == QSE_HTTPD_EACCES ||
|
|
httpd->errnum == QSE_HTTPD_ECONN) http_errnum = 403;
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&proxy->peer->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT1 ("Cannnot connect to peer [%hs]\n", tmp);
|
|
}
|
|
#endif
|
|
|
|
goto oops;
|
|
}
|
|
|
|
if (n >= 1)
|
|
{
|
|
/* connected to the peer now */
|
|
proxy->peer_status |= PROXY_PEER_CONNECTED;
|
|
if (!(proxy->flags & PROXY_UPGRADE_REQUESTED) && proxy->req)
|
|
{
|
|
/* need to read from the client-side as
|
|
* the content has not been received in full.
|
|
*
|
|
* proxy->req is set to the original request when snatching is
|
|
* required. it's also set to the original request when protocol
|
|
* upgrade is requested. however, a upgrade request containing
|
|
* contents is treated as a bad request. so i don't arrange
|
|
* to read from the client side when PROXY_UPGRADE_REQUESTED
|
|
* is on. */
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_READ;
|
|
}
|
|
|
|
task->trigger.v[0].mask &= ~QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
if (QSE_MBS_LEN(proxy->reqfwdbuf) > 0)
|
|
{
|
|
/* forward the initial part of the input to the peer */
|
|
proxy_forward_client_input_to_peer (httpd, task);
|
|
if (QSE_MBS_LEN(proxy->reqfwdbuf) > 0)
|
|
{
|
|
/* there are still more to forward in the buffer
|
|
* request the task invocation when the peer
|
|
* is writable */
|
|
task->trigger.v[0].mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
}
|
|
|
|
if (proxy->flags & PROXY_RAW)
|
|
{
|
|
/* inject http response */
|
|
if (qse_mbs_fmt (proxy->res, QSE_MT("HTTP/%d.%d 200 Connection established\r\n\r\n"),
|
|
(int)proxy->version.major, (int)proxy->version.minor) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
goto oops;
|
|
}
|
|
proxy->res_pending = QSE_MBS_LEN(proxy->res) - proxy->res_consumed;
|
|
|
|
/* arrange to be called if the client side is writable.
|
|
* it must write the injected response. */
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
task->main = task_main_proxy_3;
|
|
}
|
|
else
|
|
{
|
|
task->main = task_main_proxy_2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
|
|
oops:
|
|
return (qse_httpd_entaskerrorwithmvk (httpd, client, task, http_errnum, proxy->method, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0;
|
|
}
|
|
|
|
static void on_peer_name_resolved (qse_httpd_t* httpd, const qse_mchar_t* name, const qse_nwad_t* nwad, void* ctx)
|
|
{
|
|
qse_httpd_task_t* task = (qse_httpd_task_t*)ctx;
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
|
|
QSE_ASSERT (proxy->flags & PROXY_RESOLVE_PEER_NAME);
|
|
QSE_ASSERT (proxy->flags & PROXY_PEER_NAME_RESOLVING);
|
|
QSE_ASSERT (!(proxy->flags & (PROXY_PEER_NAME_RESOLVED | PROXY_PEER_NAME_UNRESOLVED)));
|
|
|
|
proxy->flags &= ~(PROXY_RESOLVE_PEER_NAME | PROXY_PEER_NAME_RESOLVING);
|
|
|
|
if (nwad)
|
|
{
|
|
/* resolved successfully */
|
|
|
|
proxy->peer->nwad = *nwad;
|
|
qse_setnwadport (&proxy->peer->nwad, qse_hton16(proxy->peer_port));
|
|
|
|
if (proxy->peer->local.type == QSE_NWAD_NX)
|
|
proxy->peer->local.type = proxy->peer->nwad.type;
|
|
|
|
proxy->flags |= PROXY_PEER_NAME_RESOLVED;
|
|
}
|
|
else
|
|
{
|
|
/* resolution failure. */
|
|
proxy->flags |= PROXY_INIT_FAILED | PROXY_PEER_NAME_UNRESOLVED;
|
|
}
|
|
|
|
if (qse_httpd_activatetasktrigger (httpd, proxy->client, task) <= -1)
|
|
{
|
|
proxy->flags |= PROXY_INIT_FAILED;
|
|
}
|
|
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
if (proxy->flags & PROXY_PEER_NAME_RESOLVED)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&proxy->peer->nwad, tmp, 128, QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Peer name [%hs] resolved to [%hs]\n", name, tmp);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void on_url_rewritten (qse_httpd_t* httpd, const qse_mchar_t* url, const qse_mchar_t* new_url, void* ctx)
|
|
{
|
|
qse_httpd_task_t* task = (qse_httpd_task_t*)ctx;
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
|
|
if (new_url)
|
|
{
|
|
qse_nwad_t nwad;
|
|
|
|
proxy->flags &= ~(PROXY_REWRITE_URL | PROXY_URL_REWRITING);
|
|
|
|
HTTPD_DBGOUT2 ("URL rewritten from [%hs] to [%hs]\n", url, new_url);
|
|
if (new_url[0] == QSE_MT('\0'))
|
|
{
|
|
/* no change. carry on */
|
|
}
|
|
else if (qse_mbstonwad (new_url, &nwad) >= 0)
|
|
{
|
|
/* if a network address is returned, change the peer address only */
|
|
/* TODO: prevent proxying to self */
|
|
|
|
if (qse_getnwadport(&nwad) == 0)
|
|
{
|
|
/* i don't care if new_url is X.X.X.X:0 or just X.X.X.X */
|
|
qse_setnwadport (&nwad, qse_hton16(QSE_HTTPD_DEFAULT_PORT));
|
|
}
|
|
|
|
proxy->peer->nwad = nwad;
|
|
proxy->flags |= PROXY_URL_REWRITTEN;
|
|
proxy->flags &= ~PROXY_RESOLVE_PEER_NAME; /* skip dns */
|
|
}
|
|
else if (new_url[0] >= QSE_MT('0') && new_url[0] <= QSE_MT('9'))
|
|
{
|
|
/* check if it begins with redirection code followed by a colon */
|
|
int redir_code = 0;
|
|
qse_httpd_rsrc_reloc_t reloc;
|
|
const qse_mchar_t* nuptr = new_url;
|
|
do
|
|
{
|
|
redir_code = redir_code * 10 + (*nuptr - QSE_MT('0'));
|
|
nuptr++;
|
|
}
|
|
while (*nuptr >= QSE_MT('0') && *nuptr <= QSE_MT('9'));
|
|
if (*nuptr != QSE_MT(':'))
|
|
{
|
|
/* no colon is found after digits. it's probably a normal url */
|
|
goto normal_url;
|
|
}
|
|
if (redir_code != 301 && redir_code != 302 && redir_code != 303 &&
|
|
redir_code != 307 && redir_code != 308) redir_code = 302;
|
|
nuptr++;
|
|
|
|
/* relocation code is given explictly, no slash appending is needed.
|
|
* use qse_httpd_entask_status() rather than qse_httpd_entaskreloc(). */
|
|
reloc.flags = 0;
|
|
reloc.target = nuptr;
|
|
|
|
if (qse_httpd_entask_status (
|
|
httpd, proxy->client, proxy->task, redir_code, &reloc,
|
|
proxy->method, &proxy->version, proxy->keepalive) == QSE_NULL)
|
|
{
|
|
goto fail;
|
|
}
|
|
|
|
proxy->flags |= PROXY_URL_REDIRECTED;
|
|
}
|
|
else
|
|
{
|
|
normal_url:
|
|
if (proxy->flags & PROXY_RAW)
|
|
{
|
|
qse_mchar_t* tmp;
|
|
|
|
QSE_ASSERT (QSE_STR_LEN(proxy->reqfwdbuf) == 0);
|
|
|
|
tmp = qse_mbsdup (new_url, qse_httpd_getmmgr(httpd));
|
|
if (tmp == QSE_NULL)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
proxy->flags |= PROXY_RESOLVE_PEER_NAME | PROXY_OUTBAND_PEER_NAME;
|
|
proxy->peer_name = tmp;
|
|
adjust_peer_name_and_port (proxy);
|
|
}
|
|
else
|
|
{
|
|
int proto_len;
|
|
|
|
QSE_ASSERT (QSE_STR_LEN(proxy->reqfwdbuf) > 0);
|
|
|
|
/* TODO: Host rewriting?? */
|
|
/* TODO: Host rewriting - to support it, headers must be made available thru request cloning.
|
|
* the request may not be valid after task_init_proxy */
|
|
|
|
if (qse_mbszcasecmp (new_url, QSE_MT("http://"), (proto_len = 7)) == 0 ||
|
|
qse_mbszcasecmp (new_url, QSE_MT("https://"), (proto_len = 8)) == 0)
|
|
{
|
|
const qse_mchar_t* host;
|
|
|
|
host = new_url + proto_len;
|
|
if (host[0] != QSE_MT('/') && host[0] != QSE_MT('\0'))
|
|
{
|
|
const qse_mchar_t* slash;
|
|
qse_mchar_t* tmp;
|
|
|
|
slash = qse_mbschr (host, QSE_MT('/'));
|
|
if (slash)
|
|
{
|
|
tmp = qse_mbsxdup (host, slash - host, qse_httpd_getmmgr(httpd));
|
|
new_url = slash;
|
|
}
|
|
else
|
|
{
|
|
tmp = qse_mbsdup (host, qse_httpd_getmmgr(httpd));
|
|
new_url = QSE_MT("/");
|
|
}
|
|
|
|
if (tmp == QSE_NULL)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
|
|
goto fail;
|
|
}
|
|
|
|
/* TODO: antything todo when http is rewritten to HTTPS or vice versa */
|
|
if (proto_len == 8)
|
|
proxy->peer->flags |= QSE_HTTPD_PEER_SECURE;
|
|
else
|
|
proxy->peer->flags &= ~QSE_HTTPD_PEER_SECURE;
|
|
|
|
if (qse_mbstonwad (tmp, &nwad) <= -1)
|
|
{
|
|
proxy->flags |= PROXY_RESOLVE_PEER_NAME | PROXY_OUTBAND_PEER_NAME;
|
|
proxy->peer_name = tmp;
|
|
adjust_peer_name_and_port (proxy);
|
|
}
|
|
else
|
|
{
|
|
if (qse_getnwadport(&nwad) == 0)
|
|
{
|
|
/* i don't care if tmp is X.X.X.X:0 or just X.X.X.X */
|
|
qse_setnwadport (&nwad, qse_hton16(proto_len == 8? QSE_HTTPD_DEFAULT_SECURE_PORT: QSE_HTTPD_DEFAULT_PORT));
|
|
}
|
|
|
|
proxy->peer->nwad = nwad;
|
|
proxy->flags |= PROXY_URL_REWRITTEN;
|
|
proxy->flags &= ~PROXY_RESOLVE_PEER_NAME; /* skip dns */
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&proxy->peer->nwad, tmp, 128, QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT4 ("Peer name resolved to [%hs] in url rewriting. new_url [%hs] %d %d\n",
|
|
tmp, new_url,
|
|
(int)proxy->qpath_pos_in_reqfwdbuf,
|
|
(int)proxy->qpath_len_in_reqfwdbuf
|
|
);
|
|
}
|
|
#endif
|
|
|
|
/* the temporary string is not used. kill it */
|
|
qse_httpd_freemem (httpd, tmp);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (qse_mbs_amend (proxy->reqfwdbuf, proxy->qpath_pos_in_reqfwdbuf, proxy->qpath_len_in_reqfwdbuf, new_url) == (qse_size_t)-1)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
proxy->flags |= PROXY_URL_REWRITTEN;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fail:
|
|
/* url rewriting failed */
|
|
proxy->flags |= PROXY_INIT_FAILED;
|
|
}
|
|
|
|
if (qse_httpd_activatetasktrigger (httpd, proxy->client, task) <= -1)
|
|
{
|
|
proxy->flags |= PROXY_INIT_FAILED;
|
|
}
|
|
}
|
|
|
|
static int task_main_proxy (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
task_proxy_t* proxy = (task_proxy_t*)task->ctx;
|
|
proxy_peer_htrd_xtn_t* xtn;
|
|
int http_errnum = 500;
|
|
int n;
|
|
qse_httpd_peer_t* peer_from_cache;
|
|
|
|
if (proxy->flags & PROXY_INIT_FAILED)
|
|
{
|
|
if (proxy->flags & PROXY_GOT_BAD_REQUEST)
|
|
{
|
|
http_errnum = 400; /* 400 Bad Request */
|
|
proxy->keepalive = 0; /* force Connect: close in the response */
|
|
}
|
|
else if (proxy->flags & PROXY_PEER_NAME_UNRESOLVED) http_errnum = 404; /* 404 Not Found */
|
|
goto oops;
|
|
}
|
|
|
|
if (proxy->flags & PROXY_URL_REDIRECTED) return 0; /* URL redirected. task finished */
|
|
|
|
if (proxy->flags & PROXY_REWRITE_URL)
|
|
{
|
|
if (proxy->flags & PROXY_URL_PREREWRITTEN)
|
|
{
|
|
/* proxy->url_to_rewrite is the final rewritten URL by prerewrite().
|
|
* pass QSE_NULL as the second parameter to on_url_rewritten() to
|
|
* indicate that the original URL is not known */
|
|
on_url_rewritten (httpd, QSE_NULL, (proxy->url_to_rewrite? proxy->url_to_rewrite: QSE_MT("")), task);
|
|
}
|
|
else
|
|
{
|
|
if (!(proxy->flags & PROXY_URL_REWRITING))
|
|
{
|
|
/* note that url_to_rewrite is URL + extra information. */
|
|
proxy->flags |= PROXY_URL_REWRITING; /* to prevent double calls */
|
|
|
|
if (qse_httpd_rewriteurl (httpd, proxy->url_to_rewrite, on_url_rewritten,
|
|
((proxy->flags & PROXY_URS_SERVER)? &proxy->urs_server: QSE_NULL), task) <= -1) goto oops;
|
|
|
|
if (proxy->flags & PROXY_INIT_FAILED) goto oops;
|
|
|
|
if ((proxy->flags & PROXY_REWRITE_URL) &&
|
|
qse_httpd_inactivatetasktrigger (httpd, client, task) <= -1) goto oops;
|
|
}
|
|
}
|
|
|
|
return 1; /* not finished yet */
|
|
}
|
|
|
|
if (proxy->flags & PROXY_RESOLVE_PEER_NAME)
|
|
{
|
|
/* arrange to resolve a host name and return */
|
|
int x;
|
|
|
|
QSE_ASSERT (proxy->peer_name != QSE_NULL);
|
|
|
|
if (proxy->flags & PROXY_PEER_NAME_RESOLVING)
|
|
{
|
|
return 1; /* not finished yet */
|
|
}
|
|
|
|
if (proxy->dns_preresolve_mod && proxy->dns_preresolve_mod->dns_preresolve)
|
|
x = proxy->dns_preresolve_mod->dns_preresolve (proxy->dns_preresolve_mod, client, proxy->peer_name, &proxy->peer->nwad);
|
|
else
|
|
x = httpd->opt.scb.dns.preresolve (httpd, client, proxy->peer_name, &proxy->peer->nwad);
|
|
if (x <= -1) goto oops;
|
|
|
|
if (x == 0)
|
|
{
|
|
/* preresolve() indicates that proxy->peer->nwad contains the
|
|
* final address. no actual dns resolution is required */
|
|
proxy->flags |= PROXY_PEER_NAME_RESOLVED;
|
|
proxy->flags &= ~PROXY_RESOLVE_PEER_NAME;
|
|
qse_setnwadport (&proxy->peer->nwad, qse_hton16(proxy->peer_port));
|
|
}
|
|
else
|
|
{
|
|
/* this function can be called more than once if a socket
|
|
* descriptor appears multiple times in the event result
|
|
* of a single event polling cycle in the main loop.
|
|
* e.g.) the mux implementation doesn't collapse multiple events
|
|
* for a socket descriptor into 1 event.
|
|
*
|
|
* if this happens, qse_http_resolvename() can be called
|
|
* multiple times. set this flag to prevent double resolution.
|
|
*/
|
|
proxy->flags |= PROXY_PEER_NAME_RESOLVING;
|
|
|
|
x = qse_httpd_resolvename (httpd, proxy->peer_name, on_peer_name_resolved, ((proxy->flags & PROXY_DNS_SERVER)? &proxy->dns_server: QSE_NULL), task);
|
|
if (x <= -1) goto oops;
|
|
}
|
|
|
|
/* if the name could be resolved without sending a request
|
|
* in qse_httpd_resolvename(), on_peer_name_resolve would be
|
|
* called. */
|
|
if (proxy->flags & PROXY_INIT_FAILED)
|
|
{
|
|
if (proxy->flags & PROXY_PEER_NAME_UNRESOLVED) http_errnum = 404; /* 404 Not Found */
|
|
goto oops;
|
|
}
|
|
|
|
/* peer name is not resolved yet. */
|
|
if (!(proxy->flags & PROXY_PEER_NAME_RESOLVED) &&
|
|
qse_httpd_inactivatetasktrigger (httpd, client, task) <= -1) goto oops;
|
|
|
|
return 1; /* not finished yet */
|
|
}
|
|
|
|
if (!(proxy->flags & PROXY_RAW))
|
|
{
|
|
/* set up a http reader to read a response from the peer */
|
|
proxy->peer_htrd = qse_htrd_open (qse_httpd_getmmgr(httpd), QSE_SIZEOF(proxy_peer_htrd_xtn_t));
|
|
if (proxy->peer_htrd == QSE_NULL) goto oops;
|
|
xtn = (proxy_peer_htrd_xtn_t*) qse_htrd_getxtn (proxy->peer_htrd);
|
|
xtn->proxy = proxy;
|
|
xtn->client = client;
|
|
xtn->task = task;
|
|
qse_htrd_setrecbs (proxy->peer_htrd, &proxy_peer_htrd_cbs);
|
|
qse_htrd_setopt (proxy->peer_htrd, QSE_HTRD_RESPONSE | QSE_HTRD_TRAILERS);
|
|
}
|
|
|
|
proxy->res = qse_mbs_open (qse_httpd_getmmgr(httpd), 0, 256);
|
|
if (proxy->res == QSE_NULL) goto oops;
|
|
proxy->res_consumed = 0;
|
|
proxy->res_pending = 0;
|
|
|
|
/* get a cached peer connection */
|
|
peer_from_cache = qse_httpd_decacheproxypeer (
|
|
httpd, client, &proxy->peer->nwad,
|
|
&proxy->peer->local, (proxy->peer->flags & QSE_HTTPD_PEER_SECURE));
|
|
if (peer_from_cache)
|
|
{
|
|
qse_mchar_t tmpch;
|
|
|
|
QSE_ASSERT (peer_from_cache->flags & QSE_HTTPD_PEER_CACHED);
|
|
|
|
/* test if the cached connection is still ok */
|
|
if (httpd->opt.scb.peer.recv (httpd, peer_from_cache, &tmpch, 1) <= -1 &&
|
|
httpd->errnum == QSE_HTTPD_EAGAIN)
|
|
{
|
|
/* this connection seems to be ok. it didn't return EOF nor any data.
|
|
* A valid connection can't return data yes as no request has been sent.*/
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&peer_from_cache->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Decached peer [%hs] - %zd\n", tmp, (qse_size_t)peer_from_cache->handle);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
/* the cached connection seems to be stale or invalid */
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&peer_from_cache->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Decached and closed stale peer [%hs] - %zd\n", tmp, (qse_size_t)peer_from_cache->handle);
|
|
}
|
|
#endif
|
|
httpd->opt.scb.peer.close (httpd, peer_from_cache);
|
|
qse_httpd_freemem (httpd, peer_from_cache);
|
|
|
|
peer_from_cache = QSE_NULL;
|
|
}
|
|
}
|
|
|
|
if (peer_from_cache)
|
|
{
|
|
proxy->peer = peer_from_cache; /* switch the peer pointer to the one acquired from the cache */
|
|
n = 1; /* act as if it just got connected */
|
|
}
|
|
else
|
|
{
|
|
proxy->peer->client = client;
|
|
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
n = httpd->opt.scb.peer.open (httpd, proxy->peer);
|
|
if (n <= -1)
|
|
{
|
|
/* TODO: translate more error codes to http error codes... */
|
|
if (httpd->errnum == QSE_HTTPD_ENOENT) http_errnum = 404;
|
|
else if (httpd->errnum == QSE_HTTPD_EACCES ||
|
|
httpd->errnum == QSE_HTTPD_ECONN) http_errnum = 403;
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&proxy->peer->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT1 ("Cannnot open peer [%hs]\n", tmp);
|
|
}
|
|
#endif
|
|
|
|
goto oops;
|
|
}
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&proxy->peer->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Opened peer [%hs] - %zd\n", tmp, (qse_size_t)proxy->peer->handle);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
proxy->peer_status |= PROXY_PEER_OPEN;
|
|
task->trigger.v[0].mask = QSE_HTTPD_TASK_TRIGGER_READ;
|
|
task->trigger.v[0].handle = proxy->peer->handle;
|
|
/*task->trigger.cmask = QSE_HTTPD_TASK_TRIGGER_READ;*/
|
|
task->trigger.cmask = 0;
|
|
|
|
if (n == 0)
|
|
{
|
|
/* peer not connected yet */
|
|
/*task->trigger.cmask &= ~QSE_HTTPD_TASK_TRIGGER_READ;*/
|
|
task->trigger.v[0].mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
task->main = task_main_proxy_1;
|
|
}
|
|
else
|
|
{
|
|
/* peer connected already */
|
|
proxy->peer_status |= PROXY_PEER_CONNECTED;
|
|
if (!(proxy->flags & PROXY_UPGRADE_REQUESTED) && proxy->req)
|
|
{
|
|
/* need to read from the client-side as
|
|
* the content has not been received in full.
|
|
*
|
|
* proxy->req is set to the original request when snatching is
|
|
* required. it's also set to the original request when protocol
|
|
* upgrade is requested. however, a upgrade request containing
|
|
* contents is treated as a bad request. so i don't arrange
|
|
* to read from the client side when PROXY_UPGRADE_REQUESTED
|
|
* is on.
|
|
*/
|
|
task->trigger.cmask = QSE_HTTPD_TASK_TRIGGER_READ;
|
|
}
|
|
|
|
if (QSE_MBS_LEN(proxy->reqfwdbuf) > 0)
|
|
{
|
|
proxy_forward_client_input_to_peer (httpd, task);
|
|
if (QSE_MBS_LEN(proxy->reqfwdbuf) > 0)
|
|
{
|
|
task->trigger.v[0].mask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
}
|
|
}
|
|
|
|
if (proxy->flags & PROXY_RAW)
|
|
{
|
|
/* inject http response */
|
|
if (qse_mbs_fmt (proxy->res, QSE_MT("HTTP/%d.%d 200 Connection established\r\n\r\n"),
|
|
(int)proxy->version.major, (int)proxy->version.minor) == (qse_size_t)-1)
|
|
{
|
|
proxy->httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
goto oops;
|
|
}
|
|
proxy->res_pending = QSE_MBS_LEN(proxy->res) - proxy->res_consumed;
|
|
|
|
/* arrange to be called if the client side is writable.
|
|
* it must write the injected response. */
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
task->main = task_main_proxy_3;
|
|
}
|
|
else
|
|
{
|
|
task->main = task_main_proxy_2;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
|
|
oops:
|
|
if (proxy->res)
|
|
{
|
|
qse_mbs_close (proxy->res);
|
|
proxy->res = QSE_NULL;
|
|
}
|
|
if (proxy->peer_htrd)
|
|
{
|
|
qse_htrd_close (proxy->peer_htrd);
|
|
proxy->peer_htrd = QSE_NULL;
|
|
}
|
|
|
|
return (qse_httpd_entaskerrorwithmvk (
|
|
httpd, client, task, http_errnum,
|
|
proxy->method, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
|
|
qse_httpd_task_t* qse_httpd_entaskproxy (
|
|
qse_httpd_t* httpd,
|
|
qse_httpd_client_t* client,
|
|
qse_httpd_task_t* pred,
|
|
const qse_httpd_rsrc_proxy_t* proxy,
|
|
qse_htre_t* req)
|
|
{
|
|
qse_httpd_task_t task;
|
|
task_proxy_arg_t arg;
|
|
qse_size_t xtnsize = 0;
|
|
|
|
arg.rsrc = proxy;
|
|
arg.req = req;
|
|
|
|
if (proxy->pseudonym)
|
|
xtnsize += qse_mbslen (proxy->pseudonym) + 1;
|
|
else
|
|
xtnsize += 1;
|
|
|
|
if (proxy->flags & QSE_HTTPD_RSRC_PROXY_DST_STR)
|
|
xtnsize += qse_mbslen (proxy->dst.str) + 1;
|
|
|
|
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
|
|
task.init = task_init_proxy;
|
|
task.fini = task_fini_proxy;
|
|
task.main = task_main_proxy;
|
|
task.ctx = &arg;
|
|
|
|
return qse_httpd_entask (
|
|
httpd, client, pred, &task, QSE_SIZEOF(task_proxy_t) + xtnsize
|
|
);
|
|
}
|
|
|