enabled the http file handler to accept POST, PUT, DELETE
This commit is contained in:
parent
7ec3ba3ab7
commit
855e0fb88e
@ -19,7 +19,7 @@ rpm: dist-gzip
|
||||
cp @PACKAGE_NAME@-@PACKAGE_VERSION@.tar.gz "@abs_builddir@/pkgs/RPM/SOURCES"
|
||||
rpmbuild --define "_topdir @abs_builddir@/pkgs/RPM" -ba @abs_builddir@/pkgs/hio.spec --target=@build_cpu@
|
||||
|
||||
docker:
|
||||
docker: all
|
||||
mkdir -p data
|
||||
rm -rf data/*
|
||||
tar -cvf hio-webs.tar bin/hio-webs data
|
||||
|
@ -165,7 +165,7 @@ am__DIST_COMMON = $(srcdir)/Dockerfile.in $(srcdir)/Makefile.in \
|
||||
$(top_srcdir)/ac/config.guess $(top_srcdir)/ac/config.sub \
|
||||
$(top_srcdir)/ac/install-sh $(top_srcdir)/ac/ltmain.sh \
|
||||
$(top_srcdir)/ac/missing $(top_srcdir)/pkgs/hio.spec.in \
|
||||
ac/ar-lib ac/compile ac/config.guess ac/config.sub \
|
||||
ac/ar-lib ac/compile ac/config.guess ac/config.sub ac/depcomp \
|
||||
ac/install-sh ac/ltmain.sh ac/missing
|
||||
DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
|
||||
distdir = $(PACKAGE)-$(VERSION)
|
||||
@ -846,7 +846,7 @@ rpm: dist-gzip
|
||||
cp @PACKAGE_NAME@-@PACKAGE_VERSION@.tar.gz "@abs_builddir@/pkgs/RPM/SOURCES"
|
||||
rpmbuild --define "_topdir @abs_builddir@/pkgs/RPM" -ba @abs_builddir@/pkgs/hio.spec --target=@build_cpu@
|
||||
|
||||
docker:
|
||||
docker: all
|
||||
mkdir -p data
|
||||
rm -rf data/*
|
||||
tar -cvf hio-webs.tar bin/hio-webs data
|
||||
|
10
bin/t01.c
10
bin/t01.c
@ -961,7 +961,7 @@ if (hio_htre_getcontentlen(req) > 0)
|
||||
hio_htre_discardcontent (req);
|
||||
/* 411 Length Required - can't keep alive. Force disconnect */
|
||||
req->flags &= ~HIO_HTRE_ATTR_KEEPALIVE; /* to cause sendstatus() to close */
|
||||
if (hio_svc_htts_sendstatus(htts, csck, req, 411, HIO_NULL) <= -1) goto oops;
|
||||
if (hio_svc_htts_sendstatus(htts, csck, req, HIO_HTTP_STATUS_LENGTH_REQUIRED, HIO_NULL) <= -1) goto oops;
|
||||
}
|
||||
else
|
||||
|
||||
@ -970,13 +970,13 @@ if (hio_htre_getcontentlen(req) > 0)
|
||||
const hio_bch_t* qpath = hio_htre_getqpath(req);
|
||||
int x;
|
||||
if (hio_comp_bcstr_limited(qpath, "/thr/", 5, 1) == 0)
|
||||
x = hio_svc_htts_dothr(htts, csck, req, on_htts_thr_request, HIO_NULL);
|
||||
x = hio_svc_htts_dothr(htts, csck, req, on_htts_thr_request, HIO_NULL, 0);
|
||||
else if (hio_comp_bcstr_limited(qpath, "/txt/", 5, 1) == 0)
|
||||
x = hio_svc_htts_dotxt(htts, csck, req, 200, "text/plain", qpath);
|
||||
x = hio_svc_htts_dotxt(htts, csck, req, HIO_HTTP_STATUS_OK, "text/plain", qpath, 0);
|
||||
else if (hio_comp_bcstr_limited(qpath, "/cgi/", 5, 1) == 0)
|
||||
x = hio_svc_htts_docgi(htts, csck, req, "", qpath + 4);
|
||||
x = hio_svc_htts_docgi(htts, csck, req, "", qpath + 4, 0);
|
||||
else
|
||||
x = hio_svc_htts_dofile(htts, csck, req, "", qpath, "text/plain");
|
||||
x = hio_svc_htts_dofile(htts, csck, req, "", qpath, "text/plain", 0);
|
||||
if (x <= -1) goto oops;
|
||||
}
|
||||
#if 0
|
||||
|
20
bin/t06.c
20
bin/t06.c
@ -45,7 +45,7 @@ static void on_htts_thr_request (hio_t* hio, hio_dev_thr_iopair_t* iop, hio_svc_
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf (fp, "Status: 200\r\n");
|
||||
fprintf (fp, "Status: %d\r\n", HIO_HTTP_STATUS_OK);
|
||||
fprintf (fp, "Content-Type: text/html\r\n\r\n");
|
||||
|
||||
fprintf (fp, "request path = %s\n", tfi->req_path);
|
||||
@ -82,13 +82,13 @@ static void on_htts_thr2_request (hio_t* hio, hio_dev_thr_iopair_t* iop, hio_svc
|
||||
sf = fopen(&tfi->req_path[5], "r");
|
||||
if (!sf)
|
||||
{
|
||||
fprintf (fp, "Status: 404\r\n\r\n");
|
||||
fprintf (fp, "Status: %d\r\n\r\n", HIO_HTTP_STATUS_NOT_FOUND);
|
||||
}
|
||||
else
|
||||
{
|
||||
char buf[4096];
|
||||
|
||||
fprintf (fp, "Status: 200\r\n");
|
||||
fprintf (fp, "Status: %d\r\n", HIO_HTTP_STATUS_OK);
|
||||
fprintf (fp, "Content-Type: text/html\r\n\r\n");
|
||||
|
||||
while (!feof(sf) && !ferror(sf))
|
||||
@ -188,7 +188,7 @@ if (hio_htre_getcontentlen(req) > 0)
|
||||
hio_htre_discardcontent (req);
|
||||
/* 411 Length Required - can't keep alive. Force disconnect */
|
||||
req->flags &= ~HIO_HTRE_ATTR_KEEPALIVE; /* to cause sendstatus() to close */
|
||||
if (hio_svc_htts_sendstatus(htts, csck, req, 411, HIO_NULL) <= -1) goto oops;
|
||||
if (hio_svc_htts_sendstatus(htts, csck, req, HIO_HTTP_STATUS_LENGTH_REQUIRED, HIO_NULL) <= -1) goto oops;
|
||||
}
|
||||
else
|
||||
|
||||
@ -197,21 +197,21 @@ if (hio_htre_getcontentlen(req) > 0)
|
||||
const hio_bch_t* qpath = hio_htre_getqpath(req);
|
||||
int x;
|
||||
if (hio_comp_bcstr_limited(qpath, "/thr/", 5, 1) == 0)
|
||||
x = hio_svc_htts_dothr(htts, csck, req, on_htts_thr_request, HIO_NULL);
|
||||
x = hio_svc_htts_dothr(htts, csck, req, on_htts_thr_request, HIO_NULL, HIO_SVC_HTTS_THR_NO_100_CONTINUE);
|
||||
else if (hio_comp_bcstr_limited(qpath, "/thr2/", 6, 1) == 0)
|
||||
x = hio_svc_htts_dothr(htts, csck, req, on_htts_thr2_request, HIO_NULL);
|
||||
x = hio_svc_htts_dothr(htts, csck, req, on_htts_thr2_request, HIO_NULL, HIO_SVC_HTTS_THR_NO_100_CONTINUE);
|
||||
else if (hio_comp_bcstr_limited(qpath, "/txt/", 5, 1) == 0)
|
||||
x = hio_svc_htts_dotxt(htts, csck, req, 200, "text/plain", qpath);
|
||||
x = hio_svc_htts_dotxt(htts, csck, req, HIO_HTTP_STATUS_OK, "text/plain", qpath, 0);
|
||||
else if (hio_comp_bcstr_limited(qpath, "/cgi/", 5, 1) == 0)
|
||||
x = hio_svc_htts_docgi(htts, csck, req, "", qpath + 4);
|
||||
x = hio_svc_htts_docgi(htts, csck, req, "", qpath + 4, 0);
|
||||
else if (hio_comp_bcstr_limited(qpath, "/fcgi/", 5, 1) == 0)
|
||||
{
|
||||
hio_skad_t fcgis_addr;
|
||||
hio_bcstrtoskad(hio, "127.0.0.1:9000", &fcgis_addr);
|
||||
x = hio_svc_htts_dofcgi(htts, csck, req, &fcgis_addr);
|
||||
x = hio_svc_htts_dofcgi(htts, csck, req, &fcgis_addr, 0);
|
||||
}
|
||||
else
|
||||
x = hio_svc_htts_dofile(htts, csck, req, "", qpath, "text/plain");
|
||||
x = hio_svc_htts_dofile(htts, csck, req, "", qpath, "text/plain", 0);
|
||||
if (x <= -1) goto oops;
|
||||
}
|
||||
#if 0
|
||||
|
11
bin/webs.c
11
bin/webs.c
@ -21,21 +21,22 @@ static int process_http_request (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_
|
||||
mth = hio_htre_getqmethodtype(req);
|
||||
qpath = hio_htre_getqpath(req);
|
||||
|
||||
if (mth == HIO_HTTP_GET || mth == HIO_HTTP_POST)
|
||||
|
||||
// if (mth == HIO_HTTP_GET || mth == HIO_HTTP_POST)
|
||||
{
|
||||
/* TODO: proper mime-type */
|
||||
const hio_bch_t* dot;
|
||||
hio_bch_t mt[128];
|
||||
|
||||
dot = hio_rfind_bchar_in_bcstr(qpath, '.');
|
||||
hio_fmttobcstr (hio, mt, HIO_COUNTOF(mt), "text/%hs", ((dot && dot[1] != '\0')? &dot[1]: "plain")); /* TODO: error check */
|
||||
if (hio_svc_htts_dofile(htts, csck, req, ext->docroot, qpath, mt) <= -1) goto oops;
|
||||
if (hio_svc_htts_dofile(htts, csck, req, ext->docroot, qpath, mt, 0) <= -1) goto oops;
|
||||
}
|
||||
#if 0
|
||||
else
|
||||
{
|
||||
if (hio_svc_htts_dotxt(htts, csck, req, 403, "text/plain", hio_http_status_to_bcstr(403)) <= -1) goto oops;
|
||||
if (hio_svc_htts_dotxt(htts, csck, req, HIO_HTTP_STATUS_FORBIDDEN, "text/plain", hio_http_status_to_bcstr(403), 0) <= -1) goto oops;
|
||||
}
|
||||
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
oops:
|
||||
|
@ -92,9 +92,37 @@ enum hio_http_method_t
|
||||
HIO_HTTP_UNSUBSCRIBE,
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef enum hio_http_method_t hio_http_method_t;
|
||||
|
||||
enum hio_http_status_t
|
||||
{
|
||||
HIO_HTTP_STATUS_CONTINUE = 100,
|
||||
HIO_HTTP_STATUS_SWITCH_PROTOCOL = 101,
|
||||
|
||||
HIO_HTTP_STATUS_OK = 200,
|
||||
HIO_HTTP_STATUS_CREATED = 201,
|
||||
HIO_HTTP_STATUS_ACCEPTED = 202,
|
||||
HIO_HTTP_STATUS_NON_AUTHORITATIVE = 203,
|
||||
HIO_HTTP_STATUS_NO_CONTENT = 204,
|
||||
HIO_HTTP_STATUS_RESET_CONTENT = 205,
|
||||
HIO_HTTP_STATUS_PARTIAL_CONTENT = 206,
|
||||
|
||||
HIO_HTTP_STATUS_MOVED_PERMANENTLY = 301,
|
||||
HIO_HTTP_STATUS_NOT_MODIFIED = 304,
|
||||
|
||||
HIO_HTTP_STATUS_BAD_REQUEST = 400,
|
||||
HIO_HTTP_STATUS_FORBIDDEN = 403,
|
||||
HIO_HTTP_STATUS_NOT_FOUND = 404,
|
||||
HIO_HTTP_STATUS_METHOD_NOT_ALLOWED = 405,
|
||||
HIO_HTTP_STATUS_LENGTH_REQUIRED = 411,
|
||||
HIO_HTTP_STATUS_RANGE_NOT_SATISFIABLE = 416,
|
||||
HIO_HTTP_STATUS_EXPECTATION_FAILED = 417,
|
||||
|
||||
HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR = 500,
|
||||
|
||||
};
|
||||
typedef enum hio_http_status_t hio_http_status_t;
|
||||
|
||||
/*
|
||||
* You should not manipulate an object of the #hio_htre_t
|
||||
* type directly since it's complex. Use #hio_htrd_t to
|
||||
|
@ -137,6 +137,30 @@ typedef void (*hio_svc_htts_thr_func_t) (
|
||||
|
||||
/* -------------------------------------------------------------- */
|
||||
|
||||
|
||||
enum hio_svc_htts_cgi_option_t
|
||||
{
|
||||
HIO_SVC_HTTS_CGI_NO_100_CONTINUE = (1 << 0)
|
||||
};
|
||||
|
||||
enum hio_svc_htts_file_option_t
|
||||
{
|
||||
HIO_SVC_HTTS_FILE_NO_100_CONTINUE = (1 << 0),
|
||||
HIO_SVC_HTTS_FILE_READ_ONLY = (1 << 1)
|
||||
};
|
||||
|
||||
enum hio_svc_htts_thr_option_t
|
||||
{
|
||||
HIO_SVC_HTTS_THR_NO_100_CONTINUE = (1 << 0)
|
||||
};
|
||||
|
||||
#if 0
|
||||
enum hio_svc_htts_txt_option_t
|
||||
{
|
||||
/* no option yet */
|
||||
};
|
||||
#endif
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
@ -307,14 +331,16 @@ HIO_EXPORT int hio_svc_htts_docgi (
|
||||
hio_dev_sck_t* csck,
|
||||
hio_htre_t* req,
|
||||
const hio_bch_t* docroot,
|
||||
const hio_bch_t* script
|
||||
const hio_bch_t* script,
|
||||
int options
|
||||
);
|
||||
|
||||
HIO_EXPORT int hio_svc_htts_dofcgi (
|
||||
hio_svc_htts_t* htts,
|
||||
hio_dev_sck_t* csck,
|
||||
hio_htre_t* req,
|
||||
const hio_skad_t* fcgis_addr
|
||||
const hio_skad_t* fcgis_addr,
|
||||
int options
|
||||
);
|
||||
|
||||
HIO_EXPORT int hio_svc_htts_dofile (
|
||||
@ -323,7 +349,8 @@ HIO_EXPORT int hio_svc_htts_dofile (
|
||||
hio_htre_t* req,
|
||||
const hio_bch_t* docroot,
|
||||
const hio_bch_t* filepath,
|
||||
const hio_bch_t* mime_type
|
||||
const hio_bch_t* mime_type,
|
||||
int options
|
||||
);
|
||||
|
||||
HIO_EXPORT int hio_svc_htts_dothr (
|
||||
@ -331,7 +358,8 @@ HIO_EXPORT int hio_svc_htts_dothr (
|
||||
hio_dev_sck_t* csck,
|
||||
hio_htre_t* req,
|
||||
hio_svc_htts_thr_func_t func,
|
||||
void* ctx
|
||||
void* ctx,
|
||||
int options
|
||||
);
|
||||
|
||||
HIO_EXPORT int hio_svc_htts_dotxt (
|
||||
@ -340,7 +368,8 @@ HIO_EXPORT int hio_svc_htts_dotxt (
|
||||
hio_htre_t* req,
|
||||
int status_code,
|
||||
const hio_bch_t* content_type,
|
||||
const hio_bch_t* content_text
|
||||
const hio_bch_t* content_text,
|
||||
int options
|
||||
);
|
||||
|
||||
HIO_EXPORT hio_svc_htts_rsrc_t* hio_svc_htts_rsrc_make (
|
||||
|
@ -58,6 +58,7 @@ struct cgi_t
|
||||
{
|
||||
HIO_SVC_HTTS_RSRC_HEADER;
|
||||
|
||||
int options;
|
||||
hio_oow_t num_pending_writes_to_client;
|
||||
hio_oow_t num_pending_writes_to_peer;
|
||||
hio_dev_pro_t* peer;
|
||||
@ -69,6 +70,7 @@ struct cgi_t
|
||||
unsigned int keep_alive: 1;
|
||||
unsigned int req_content_length_unlimited: 1;
|
||||
unsigned int ever_attempted_to_write_to_client: 1;
|
||||
unsigned int client_eof_detected: 1;
|
||||
unsigned int client_disconnected: 1;
|
||||
unsigned int client_htrd_recbs_changed: 1;
|
||||
hio_oow_t req_content_length; /* client request content length */
|
||||
@ -160,7 +162,7 @@ static int cgi_write_last_chunk_to_client (cgi_t* cgi)
|
||||
{
|
||||
if (!cgi->ever_attempted_to_write_to_client)
|
||||
{
|
||||
if (cgi_send_final_status_to_client(cgi, 500, 0) <= -1) return -1;
|
||||
if (cgi_send_final_status_to_client(cgi, HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR, 0) <= -1) return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -226,7 +228,7 @@ static HIO_INLINE void cgi_mark_over (cgi_t* cgi, int over_bits)
|
||||
hio_dev_pro_halt (cgi->peer);
|
||||
}
|
||||
|
||||
if (cgi->keep_alive)
|
||||
if (cgi->keep_alive && !cgi->client_eof_detected)
|
||||
{
|
||||
/* how to arrange to delete this cgi object and put the socket back to the normal waiting state??? */
|
||||
HIO_ASSERT (cgi->htts->hio, cgi->client->rsrc == (hio_svc_htts_rsrc_t*)cgi);
|
||||
@ -379,6 +381,7 @@ static int peer_on_read (hio_dev_pro_t* pro, hio_dev_pro_sid_t sid, const void*
|
||||
if (dlen == 0)
|
||||
{
|
||||
HIO_DEBUG3 (hio, "HTTS(%p) - EOF from peer %p(pid=%u)\n", cgi->client->htts, pro, (unsigned int)pro->child_pid);
|
||||
cgi->client_eof_detected = 1;
|
||||
|
||||
if (!(cgi->over & CGI_OVER_READ_FROM_PEER))
|
||||
{
|
||||
@ -404,7 +407,7 @@ static int peer_on_read (hio_dev_pro_t* pro, hio_dev_pro_sid_t sid, const void*
|
||||
if (!cgi->ever_attempted_to_write_to_client &&
|
||||
!(cgi->over & CGI_OVER_WRITE_TO_CLIENT))
|
||||
{
|
||||
cgi_send_final_status_to_client (cgi, 500, 1); /* don't care about error because it jumps to oops below anyway */
|
||||
cgi_send_final_status_to_client (cgi, HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR, 1); /* don't care about error because it jumps to oops below anyway */
|
||||
}
|
||||
|
||||
goto oops;
|
||||
@ -921,7 +924,7 @@ static int peer_on_fork (hio_dev_pro_t* pro, void* fork_ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_bch_t* docroot, const hio_bch_t* script)
|
||||
int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_bch_t* docroot, const hio_bch_t* script, int options)
|
||||
{
|
||||
hio_t* hio = htts->hio;
|
||||
hio_svc_htts_cli_t* cli = hio_dev_sck_getxtn(csck);
|
||||
@ -953,6 +956,7 @@ int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
cgi = (cgi_t*)hio_svc_htts_rsrc_make(htts, HIO_SIZEOF(*cgi), cgi_on_kill);
|
||||
if (HIO_UNLIKELY(!cgi)) goto oops;
|
||||
|
||||
cgi->options = options;
|
||||
cgi->client = cli;
|
||||
/*cgi->num_pending_writes_to_client = 0;
|
||||
cgi->num_pending_writes_to_peer = 0;*/
|
||||
@ -973,7 +977,7 @@ int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
|
||||
if (access(mi.cmd, X_OK) == -1)
|
||||
{
|
||||
cgi_send_final_status_to_client (cgi, 403, 1); /* 403 Forbidden */
|
||||
cgi_send_final_status_to_client (cgi, HIO_HTTP_STATUS_FORBIDDEN, 1); /* 403 Forbidden */
|
||||
goto oops; /* TODO: must not go to oops. just destroy the cgi and finalize the request .. */
|
||||
}
|
||||
|
||||
@ -999,7 +1003,7 @@ int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
* option 2. send 411 Length Required immediately
|
||||
* option 3. set Content-Length to -1 and use EOF to indicate the end of content [Non-Standard] */
|
||||
|
||||
if (cgi_send_final_status_to_client(cgi, 411, 1) <= -1) goto oops;
|
||||
if (cgi_send_final_status_to_client(cgi, HIO_HTTP_STATUS_LENGTH_REQUIRED, 1) <= -1) goto oops;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -1007,7 +1011,8 @@ int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
{
|
||||
/* TODO: Expect: 100-continue? who should handle this? cgi? or the http server? */
|
||||
/* CAN I LET the cgi SCRIPT handle this? */
|
||||
if (hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
if (!(options & HIO_SVC_HTTS_CGI_NO_100_CONTINUE) &&
|
||||
hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
(cgi->req_content_length_unlimited || cgi->req_content_length > 0))
|
||||
{
|
||||
/*
|
||||
@ -1026,7 +1031,7 @@ int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
hio_bch_t msgbuf[64];
|
||||
hio_oow_t msglen;
|
||||
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d 100 Continue\r\n\r\n", cgi->req_version.major, cgi->req_version.minor);
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d %d %hs\r\n\r\n", cgi->req_version.major, cgi->req_version.minor, HIO_HTTP_STATUS_CONTINUE, hio_http_status_to_bcstr(HIO_HTTP_STATUS_CONTINUE));
|
||||
if (cgi_write_to_client(cgi, msgbuf, msglen) <= -1) goto oops;
|
||||
cgi->ever_attempted_to_write_to_client = 0; /* reset this as it's polluted for 100 continue */
|
||||
}
|
||||
@ -1034,7 +1039,7 @@ int hio_svc_htts_docgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
else if (req->flags & HIO_HTRE_ATTR_EXPECT)
|
||||
{
|
||||
/* 417 Expectation Failed */
|
||||
cgi_send_final_status_to_client(cgi, 417, 1);
|
||||
cgi_send_final_status_to_client(cgi, HIO_HTTP_STATUS_EXPECTATION_FAILED, 1);
|
||||
goto oops;
|
||||
}
|
||||
|
||||
|
@ -497,7 +497,7 @@ static void fcgi_on_kill (fcgi_t* fcgi)
|
||||
#endif
|
||||
}
|
||||
|
||||
int hio_svc_htts_dofcgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_skad_t* fcgis_addr)
|
||||
int hio_svc_htts_dofcgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_skad_t* fcgis_addr, int options)
|
||||
{
|
||||
hio_t* hio = htts->hio;
|
||||
hio_svc_htts_cli_t* cli = hio_dev_sck_getxtn(csck);
|
||||
@ -565,7 +565,7 @@ int hio_svc_htts_dofcgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
* option 2. send 411 Length Required immediately
|
||||
* option 3. set Content-Length to -1 and use EOF to indicate the end of content [Non-Standard] */
|
||||
|
||||
if (cgi_send_final_status_to_client(cgi, 411, 1) <= -1) goto oops;
|
||||
if (cgi_send_final_status_to_client(cgi, HIO_HTTP_STATUS_LENGTH_REQUIRED, 1) <= -1) goto oops;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -573,7 +573,8 @@ int hio_svc_htts_dofcgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
{
|
||||
/* TODO: Expect: 100-continue? who should handle this? cgi? or the http server? */
|
||||
/* CAN I LET the cgi SCRIPT handle this? */
|
||||
if (hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
if (!(options & HIO_SVC_HTTS_CGI_NO_100_CONTINUE) &&
|
||||
hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
(fcgi->req_content_length_unlimited || fcgi->req_content_length > 0))
|
||||
{
|
||||
/*
|
||||
@ -592,7 +593,7 @@ int hio_svc_htts_dofcgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
hio_bch_t msgbuf[64];
|
||||
hio_oow_t msglen;
|
||||
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d 100 Continue\r\n\r\n", fcgi->req_version.major, fcgi->req_version.minor);
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d %d %hs\r\n\r\n", fcgi->req_version.major, fcgi->req_version.minor, HIO_HTTP_STATUS_CONTINUE, hio_http_status_to_bcstr(HIO_HTTP_STATUS_CONTINUE));
|
||||
if (fcgi_write_to_client(fcgi, msgbuf, msglen) <= -1) goto oops;
|
||||
fcgi->ever_attempted_to_write_to_client = 0; /* reset this as it's polluted for 100 continue */
|
||||
}
|
||||
@ -600,7 +601,7 @@ int hio_svc_htts_dofcgi (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
else if (req->flags & HIO_HTRE_ATTR_EXPECT)
|
||||
{
|
||||
/* 417 Expectation Failed */
|
||||
fcgi_send_final_status_to_client(fcgi, 417, 1);
|
||||
fcgi_send_final_status_to_client(fcgi, HIO_HTTP_STATUS_EXPECTATION_FAILED, 1);
|
||||
goto oops;
|
||||
}
|
||||
|
||||
|
258
lib/http-file.c
258
lib/http-file.c
@ -43,8 +43,6 @@ enum file_res_mode_t
|
||||
};
|
||||
typedef enum file_res_mode_t file_res_mode_t;
|
||||
|
||||
#define FILE_PENDING_IO_THRESHOLD 5
|
||||
|
||||
#define FILE_OVER_READ_FROM_CLIENT (1 << 0)
|
||||
#define FILE_OVER_READ_FROM_PEER (1 << 1)
|
||||
#define FILE_OVER_WRITE_TO_CLIENT (1 << 2)
|
||||
@ -55,6 +53,7 @@ struct file_t
|
||||
{
|
||||
HIO_SVC_HTTS_RSRC_HEADER;
|
||||
|
||||
int options;
|
||||
hio_oow_t num_pending_writes_to_client;
|
||||
hio_oow_t num_pending_writes_to_peer;
|
||||
int sendfile_ok;
|
||||
@ -98,7 +97,7 @@ static void file_halt_participating_devices (file_t* file)
|
||||
HIO_ASSERT (file->client->htts->hio, file->client != HIO_NULL);
|
||||
HIO_ASSERT (file->client->htts->hio, file->client->sck != HIO_NULL);
|
||||
|
||||
HIO_DEBUG3 (file->client->htts->hio, "HTTS(%p) - file(c=%d,p=%d) Halting participating devices in file state\n", file->client->htts, (int)file->client->sck->hnd, (int)file->peer);
|
||||
HIO_DEBUG3 (file->client->htts->hio, "HTTS(%p) - file(c=%d,p=%d) Halting participating devices\n", file->client->htts, (int)file->client->sck->hnd, (int)file->peer);
|
||||
|
||||
/* only the client socket device.
|
||||
* the peer side is just a file descriptor - no hio-managed device */
|
||||
@ -116,11 +115,6 @@ static int file_write_to_client (file_t* file, const void* data, hio_iolen_t dle
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (file->num_pending_writes_to_client > FILE_PENDING_IO_THRESHOLD)
|
||||
{
|
||||
/* STOP READING */
|
||||
/*if (hio_dev_pro_read(file->peer, HIO_DEV_PRO_OUT, 0) <= -1) return -1;*/
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -135,11 +129,6 @@ static int file_sendfile_to_client (file_t* file, hio_foff_t foff, hio_iolen_t l
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (file->num_pending_writes_to_client > FILE_PENDING_IO_THRESHOLD)
|
||||
{
|
||||
/* STOP READING */
|
||||
/*if (hio_dev_pro_read(file->peer, HIO_DEV_PRO_OUT, 0) <= -1) return -1;*/
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -248,10 +237,20 @@ static int file_write_to_peer (file_t* file, const void* data, hio_iolen_t dlen)
|
||||
}
|
||||
else
|
||||
{
|
||||
hio_iolen_t pos, rem, n;
|
||||
if (file->req_method == HIO_HTTP_GET) return 0;
|
||||
if (file->peer <= -1) return 0; /* peer open proabably failed */
|
||||
|
||||
HIO_ASSERT (hio, file->peer >= 0);
|
||||
return write(file->peer, data, dlen) <= -1? -1: 0;
|
||||
/* TODO: async file io -> create a file device?? */
|
||||
pos = 0;
|
||||
rem = dlen;
|
||||
while (rem > 0)
|
||||
{
|
||||
n = write(file->peer, &((const hio_uint8_t*)data)[pos], rem);
|
||||
if (n <= -1) return -1;
|
||||
rem -= n;
|
||||
pos += n;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -334,9 +333,10 @@ static int file_client_on_read (hio_dev_sck_t* sck, const void* buf, hio_iolen_t
|
||||
goto oops;
|
||||
}
|
||||
|
||||
if (!file->peer)
|
||||
if (file->peer <= -1)
|
||||
{
|
||||
/* the peer is gone */
|
||||
/* the peer is gone or not even opened */
|
||||
HIO_DEBUG3 (cli->htts->hio, "HTTS(%p) - file(c=%d,p=%d) read on client, no peer to write\n", file->client->htts, (int)sck->hnd, file->peer);
|
||||
goto oops; /* do what? just return 0? */
|
||||
}
|
||||
|
||||
@ -344,12 +344,14 @@ static int file_client_on_read (hio_dev_sck_t* sck, const void* buf, hio_iolen_t
|
||||
{
|
||||
/* EOF on the client side. arrange to close */
|
||||
HIO_DEBUG3 (cli->htts->hio, "HTTS(%p) - file(c=%d,p=%d) EOF detected on client\n", file->client->htts, (int)sck->hnd, file->peer);
|
||||
file->client_eof_detected = 1;
|
||||
|
||||
if (!(file->over & FILE_OVER_READ_FROM_CLIENT)) /* if this is true, EOF is received without file_client_htrd_poke() */
|
||||
{
|
||||
if (file_write_to_peer(file, HIO_NULL, 0) <= -1) goto oops;
|
||||
file->client_eof_detected = 1;
|
||||
int n;
|
||||
n = file_write_to_peer(file, HIO_NULL, 0);
|
||||
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT);
|
||||
if (n <= -1) goto oops;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -422,7 +424,6 @@ oops:
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* --------------------------------------------------------------------- */
|
||||
|
||||
static int file_client_htrd_poke (hio_htrd_t* htrd, hio_htre_t* req)
|
||||
@ -438,7 +439,7 @@ static int file_client_htrd_poke (hio_htrd_t* htrd, hio_htre_t* req)
|
||||
|
||||
if (file->req_method != HIO_HTTP_GET)
|
||||
{
|
||||
if (file_send_final_status_to_client(file, 200, 0) <= -1) return -1;
|
||||
if (file_send_final_status_to_client(file, HIO_HTTP_STATUS_OK, 0) <= -1) return -1;
|
||||
}
|
||||
|
||||
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT);
|
||||
@ -476,7 +477,7 @@ static int file_send_header_to_client (file_t* file, int status_code, int force_
|
||||
if (!force_close) force_close = !file->keep_alive;
|
||||
|
||||
content_length = file->end_offset - file->start_offset + 1;
|
||||
if (status_code == 200 && file->total_size != content_length) status_code = 206;
|
||||
if (status_code == HIO_HTTP_STATUS_OK && file->total_size != content_length) status_code = HIO_HTTP_STATUS_PARTIAL_CONTENT;
|
||||
|
||||
if (hio_becs_fmt(cli->sbuf, "HTTP/%d.%d %d %hs\r\nServer: %hs\r\nDate: %s\r\nConnection: %hs\r\nAccept-Ranges: bytes\r\nContent-Type: %hs\r\n",
|
||||
file->req_version.major, file->req_version.minor,
|
||||
@ -485,7 +486,7 @@ static int file_send_header_to_client (file_t* file, int status_code, int force_
|
||||
(force_close? "close": "keep-alive"), mime_type) == (hio_oow_t)-1) return -1;
|
||||
|
||||
if (file->req_method == HIO_HTTP_GET && hio_becs_fcat(cli->sbuf, "ETag: %hs\r\n", file->peer_etag) == (hio_oow_t)-1) return -1;
|
||||
if (status_code == 206 && hio_becs_fcat(cli->sbuf, "Content-Ranges: bytes %ju-%ju/%ju\r\n", (hio_uintmax_t)file->start_offset, (hio_uintmax_t)file->end_offset, (hio_uintmax_t)file->total_size) == (hio_oow_t)-1) return -1;
|
||||
if (status_code == HIO_HTTP_STATUS_PARTIAL_CONTENT && hio_becs_fcat(cli->sbuf, "Content-Ranges: bytes %ju-%ju/%ju\r\n", (hio_uintmax_t)file->start_offset, (hio_uintmax_t)file->end_offset, (hio_uintmax_t)file->total_size) == (hio_oow_t)-1) return -1;
|
||||
|
||||
/* ----- */
|
||||
// TODO: Allow-Contents
|
||||
@ -566,7 +567,12 @@ static int file_send_contents_to_client (file_t* file)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
|
||||
#define ERRNO_TO_STATUS_CODE(x) ( \
|
||||
((x) == ENOENT)? HIO_HTTP_STATUS_NOT_FOUND: \
|
||||
((x) == EPERM || (x) == EACCES)? HIO_HTTP_STATUS_FORBIDDEN: HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR \
|
||||
)
|
||||
|
||||
static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req, int* error_status)
|
||||
{
|
||||
struct stat st;
|
||||
const hio_htre_hdrval_t* tmp;
|
||||
@ -574,14 +580,14 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
|
||||
|
||||
if (fstat(file->peer, &st) <= -1)
|
||||
{
|
||||
file_send_final_status_to_client (file, 500, 1);
|
||||
*error_status = ERRNO_TO_STATUS_CODE(errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((st.st_mode & S_IFMT) != S_IFREG)
|
||||
{
|
||||
/* TODO: support directory listing if S_IFDIR? still disallow special files. */
|
||||
file_send_final_status_to_client (file, 403, 1); /* forbidden */
|
||||
*error_status = HIO_HTTP_STATUS_FORBIDDEN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -617,7 +623,7 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
|
||||
if (hio_parse_http_range_bcstr(tmp->ptr, &range) <= -1)
|
||||
{
|
||||
range_not_satisifiable:
|
||||
file_send_final_status_to_client (file, 416, 1); /* 406 Requested Range Not Satisfiable */
|
||||
*error_status = HIO_HTTP_STATUS_RANGE_NOT_SATISFIABLE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -646,7 +652,13 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
|
||||
}
|
||||
|
||||
if (file->start_offset > 0)
|
||||
lseek(file->peer, file->start_offset, SEEK_SET);
|
||||
{
|
||||
if (lseek(file->peer, file->start_offset, SEEK_SET) <= -1)
|
||||
{
|
||||
*error_status = ERRNO_TO_STATUS_CODE(errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
@ -660,86 +672,39 @@ static HIO_INLINE int process_range_header (file_t* file, hio_htre_t* req)
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define ERRNO_TO_STATUS_CODE(x) ( \
|
||||
((x) == ENOENT)? 404: \
|
||||
((x) == EPERM || (x) == EACCES)? 403: 500 \
|
||||
)
|
||||
|
||||
static int open_peer (file_t* file, const hio_bch_t* actual_file)
|
||||
static int open_peer_with_mode (file_t* file, const hio_bch_t* actual_file, int flags, int* error_status)
|
||||
{
|
||||
switch (file->req_method)
|
||||
{
|
||||
case HIO_HTTP_GET:
|
||||
case HIO_HTTP_HEAD:
|
||||
{
|
||||
int flags;
|
||||
|
||||
if (access(actual_file, R_OK) == -1)
|
||||
{
|
||||
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1); /* 404 not found 403 Forbidden */
|
||||
return -1;
|
||||
}
|
||||
|
||||
flags = O_RDONLY | O_NONBLOCK;
|
||||
#if defined(O_CLOEXEC)
|
||||
flags |= O_NONBLOCK;
|
||||
#if defined(O_CLOEXEC)
|
||||
flags |= O_CLOEXEC;
|
||||
#endif
|
||||
#if defined(O_LARGEFILE)
|
||||
#endif
|
||||
#if defined(O_LARGEFILE)
|
||||
flags |= O_LARGEFILE;
|
||||
#endif
|
||||
file->peer = open(actual_file, flags);
|
||||
|
||||
if (HIO_UNLIKELY(file->peer <= -1))
|
||||
{
|
||||
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
case HIO_HTTP_PUT:
|
||||
case HIO_HTTP_POST:
|
||||
/* TOOD: this is destructive. jump to default if not allowed by flags... */
|
||||
file->peer = open(actual_file, O_WRONLY | O_TRUNC | O_CREAT | O_NONBLOCK | O_CLOEXEC, 0644);
|
||||
if (HIO_UNLIKELY(file->peer <= -1))
|
||||
{
|
||||
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case HIO_HTTP_PATCH:
|
||||
/* TOOD: this is destructive. jump to default if not allowed by flags... */
|
||||
file->peer = open(actual_file, O_WRONLY | O_NONBLOCK | O_CLOEXEC, 0644);
|
||||
if (HIO_UNLIKELY(file->peer <= -1))
|
||||
{
|
||||
file_send_final_status_to_client (file, ERRNO_TO_STATUS_CODE(errno), 1);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
case HIO_HTTP_DELETE:
|
||||
/* TODO: */
|
||||
#endif
|
||||
file->peer = open(actual_file, flags, 0644);
|
||||
if (HIO_UNLIKELY(file->peer <= -1))
|
||||
{
|
||||
*error_status = ERRNO_TO_STATUS_CODE(errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
file_send_final_status_to_client (file, 405, 1); /* 405: method not allowed */
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static HIO_INLINE void fadvise_on_peer (file_t* file)
|
||||
static HIO_INLINE void set_tcp_cork (hio_dev_sck_t* sck)
|
||||
{
|
||||
#if defined(HAVE_POSIX_FADVISE)
|
||||
if (file->req_method == HIO_HTTP_GET)
|
||||
posix_fadvise (file->peer, file->start_offset, file->end_offset - file->start_offset + 1, POSIX_FADV_SEQUENTIAL);
|
||||
#if defined(TCP_CORK)
|
||||
int tcp_cork = 1;
|
||||
#if defined(SOL_TCP)
|
||||
hio_dev_sck_setsockopt (sck, SOL_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
|
||||
#elif defined(IPPROTO_TCP)
|
||||
hio_dev_sck_setsockopt (sck, IPPROTO_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_bch_t* docroot, const hio_bch_t* filepath, const hio_bch_t* mime_type)
|
||||
int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, const hio_bch_t* docroot, const hio_bch_t* filepath, const hio_bch_t* mime_type, int options)
|
||||
{
|
||||
/* TODO: ETag, Last-Modified... */
|
||||
|
||||
@ -747,6 +712,7 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
hio_svc_htts_cli_t* cli = hio_dev_sck_getxtn(csck);
|
||||
file_t* file = HIO_NULL;
|
||||
hio_bch_t* actual_file = HIO_NULL;
|
||||
int status_code;
|
||||
|
||||
/* ensure that you call this function before any contents is received */
|
||||
HIO_ASSERT (hio, hio_htre_getcontentlen(req) == 0);
|
||||
@ -760,6 +726,7 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
file = (file_t*)hio_svc_htts_rsrc_make(htts, HIO_SIZEOF(*file), file_on_kill);
|
||||
if (HIO_UNLIKELY(!file)) goto oops;
|
||||
|
||||
file->options = options;
|
||||
file->client = cli;
|
||||
file->sendfile_ok = hio_dev_sck_sendfileok(cli->sck);
|
||||
/*file->num_pending_writes_to_client = 0;
|
||||
@ -781,11 +748,6 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
file->peer_tmridx = HIO_TMRIDX_INVALID;
|
||||
file->peer = -1;
|
||||
|
||||
if (open_peer(file, actual_file) <= -1 ||
|
||||
process_range_header(file, req) <= -1) goto oops;
|
||||
|
||||
fadvise_on_peer (file);
|
||||
|
||||
#if !defined(FILE_ALLOW_UNLIMITED_REQ_CONTENT_LENGTH)
|
||||
if (file->req_content_length_unlimited)
|
||||
{
|
||||
@ -794,21 +756,21 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
/* option 1. buffer contents. if it gets too large, send 413 Request Entity Too Large.
|
||||
* option 2. send 411 Length Required immediately
|
||||
* option 3. set Content-Length to -1 and use EOF to indicate the end of content [Non-Standard] */
|
||||
|
||||
if (file_send_final_status_to_client(file, 411, 1) <= -1) goto oops;
|
||||
if (file_send_final_status_to_client(file, HIO_HTTP_STATUS_LENGTH_REQUIRED, 1) <= -1) goto oops;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (req->flags & HIO_HTRE_ATTR_EXPECT100)
|
||||
{
|
||||
if (hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
if (!(options & HIO_SVC_HTTS_FILE_NO_100_CONTINUE) &&
|
||||
hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
(file->req_content_length_unlimited || file->req_content_length > 0) &&
|
||||
(file->req_method != HIO_HTTP_GET && file->req_method != HIO_HTTP_HEAD))
|
||||
{
|
||||
hio_bch_t msgbuf[64];
|
||||
hio_oow_t msglen;
|
||||
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d 100 Continue\r\n\r\n", file->req_version.major, file->req_version.minor);
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d %d %hs\r\n\r\n", file->req_version.major, file->req_version.minor, HIO_HTTP_STATUS_CONTINUE, hio_http_status_to_bcstr(HIO_HTTP_STATUS_CONTINUE));
|
||||
if (file_write_to_client(file, msgbuf, msglen) <= -1) goto oops;
|
||||
file->ever_attempted_to_write_to_client = 0; /* reset this as it's polluted for 100 continue */
|
||||
}
|
||||
@ -816,7 +778,7 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
else if (req->flags & HIO_HTRE_ATTR_EXPECT)
|
||||
{
|
||||
/* 417 Expectation Failed */
|
||||
file_send_final_status_to_client (file, 417, 1);
|
||||
file_send_final_status_to_client (file, HIO_HTTP_STATUS_EXPECTATION_FAILED, 1);
|
||||
goto oops;
|
||||
}
|
||||
|
||||
@ -843,9 +805,15 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
else
|
||||
{
|
||||
/* no content to be uploaded from the client */
|
||||
#if 0
|
||||
/* indicate EOF to the peer and disable input wathching from the client */
|
||||
if (file_write_to_peer(file, HIO_NULL, 0) <= -1) goto oops;
|
||||
HIO_ASSERT (hio, file->over | FILE_OVER_WRITE_TO_PEER); /* must be set by the call to file_write_to_peer() above */
|
||||
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT);
|
||||
#else
|
||||
/* no peer is open yet. so simply set the mars forcibly instead of calling file_write_to_peer() with null data */
|
||||
file_mark_over (file, FILE_OVER_READ_FROM_CLIENT | FILE_OVER_WRITE_TO_PEER);
|
||||
#endif
|
||||
}
|
||||
#if defined(FILE_ALLOW_UNLIMITED_REQ_CONTENT_LENGTH)
|
||||
}
|
||||
@ -863,34 +831,74 @@ int hio_svc_htts_dofile (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t*
|
||||
file->res_mode_to_cli = FILE_RES_MODE_CLOSE;
|
||||
}
|
||||
|
||||
if (file->req_method == HIO_HTTP_GET)
|
||||
switch (file->req_method)
|
||||
{
|
||||
case HIO_HTTP_GET:
|
||||
if (open_peer_with_mode(file, actual_file, O_RDONLY, &status_code) <= -1) goto done_with_status_code;
|
||||
if (process_range_header(file, req, &status_code) <= -1) goto done_with_status_code;
|
||||
|
||||
if (file->etag_match)
|
||||
{
|
||||
/* 304 not modified */
|
||||
if (file_send_final_status_to_client(file, 304, 0) <= -1) goto oops;
|
||||
file_mark_over (file, FILE_OVER_READ_FROM_PEER | FILE_OVER_WRITE_TO_PEER);
|
||||
status_code = HIO_HTTP_STATUS_NOT_MODIFIED;
|
||||
goto done_with_status_code;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* normal full transfer */
|
||||
#if defined(TCP_CORK)
|
||||
int tcp_cork = 1;
|
||||
#if defined(SOL_TCP)
|
||||
hio_dev_sck_setsockopt(file->client->sck, SOL_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
|
||||
#elif defined(IPPROTO_TCP)
|
||||
hio_dev_sck_setsockopt(file->client->sck, IPPROTO_TCP, TCP_CORK, &tcp_cork, HIO_SIZEOF(tcp_cork));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (file_send_header_to_client(file, 200, 0, mime_type) <= -1 ||
|
||||
/* normal full transfer */
|
||||
#if defined(HAVE_POSIX_FADVISE)
|
||||
posix_fadvise (file->peer, file->start_offset, file->end_offset - file->start_offset + 1, POSIX_FADV_SEQUENTIAL);
|
||||
#endif
|
||||
set_tcp_cork (file->client->sck);
|
||||
|
||||
if (file_send_header_to_client(file, HIO_HTTP_STATUS_OK, 0, mime_type) <= -1 ||
|
||||
file_send_contents_to_client(file) <= -1) goto oops;
|
||||
}
|
||||
}
|
||||
else if (file->req_method == HIO_HTTP_HEAD)
|
||||
|
||||
break;
|
||||
|
||||
case HIO_HTTP_HEAD:
|
||||
if (open_peer_with_mode(file, actual_file, O_RDONLY, &status_code) <= -1) goto done_with_status_code;
|
||||
if (process_range_header(file, req, &status_code) <= -1) goto done_with_status_code;
|
||||
status_code = HIO_HTTP_STATUS_OK;
|
||||
goto done_with_status_code;
|
||||
|
||||
case HIO_HTTP_POST:
|
||||
case HIO_HTTP_PUT:
|
||||
if (file->options & HIO_SVC_HTTS_FILE_READ_ONLY)
|
||||
{
|
||||
if (file_send_header_to_client(file, 200, 0, mime_type) <= -1) goto oops;
|
||||
status_code = HIO_HTTP_STATUS_METHOD_NOT_ALLOWED;
|
||||
goto done_with_status_code;
|
||||
}
|
||||
|
||||
if (open_peer_with_mode(file, actual_file, O_WRONLY | O_TRUNC | O_CREAT, &status_code) <= -1) goto done_with_status_code;
|
||||
|
||||
/* the client input must be written to the peer side */
|
||||
file_mark_over (file, FILE_OVER_READ_FROM_PEER);
|
||||
break;
|
||||
|
||||
case HIO_HTTP_DELETE:
|
||||
if (file->options & HIO_SVC_HTTS_FILE_READ_ONLY)
|
||||
{
|
||||
status_code = HIO_HTTP_STATUS_METHOD_NOT_ALLOWED;
|
||||
goto done_with_status_code;
|
||||
}
|
||||
|
||||
if (unlink(actual_file) <= -1)
|
||||
{
|
||||
if (errno != EISDIR || (errno == EISDIR && rmdir(actual_file) <= -1))
|
||||
{
|
||||
status_code = ERRNO_TO_STATUS_CODE(errno);
|
||||
goto done_with_status_code;
|
||||
}
|
||||
}
|
||||
|
||||
status_code = HIO_HTTP_STATUS_OK;
|
||||
goto done_with_status_code;
|
||||
|
||||
default:
|
||||
status_code = HIO_HTTP_STATUS_METHOD_NOT_ALLOWED;
|
||||
done_with_status_code:
|
||||
if (file_send_final_status_to_client(file, status_code, 0) <= -1) goto oops;
|
||||
file_mark_over (file, FILE_OVER_READ_FROM_PEER | FILE_OVER_WRITE_TO_PEER);
|
||||
break;
|
||||
}
|
||||
|
||||
/* TODO: store current input watching state and use it when destroying the file data */
|
||||
|
@ -59,6 +59,7 @@ struct thr_state_t
|
||||
{
|
||||
HIO_SVC_HTTS_RSRC_HEADER;
|
||||
|
||||
int options;
|
||||
hio_oow_t num_pending_writes_to_client;
|
||||
hio_oow_t num_pending_writes_to_peer;
|
||||
hio_dev_thr_t* peer;
|
||||
@ -70,6 +71,7 @@ struct thr_state_t
|
||||
unsigned int keep_alive: 1;
|
||||
unsigned int req_content_length_unlimited: 1;
|
||||
unsigned int ever_attempted_to_write_to_client: 1;
|
||||
unsigned int client_eof_detected: 1;
|
||||
unsigned int client_disconnected: 1;
|
||||
unsigned int client_htrd_recbs_changed: 1;
|
||||
hio_oow_t req_content_length; /* client request content length */
|
||||
@ -160,7 +162,7 @@ static int thr_state_write_last_chunk_to_client (thr_state_t* thr_state)
|
||||
{
|
||||
if (!thr_state->ever_attempted_to_write_to_client)
|
||||
{
|
||||
if (thr_state_send_final_status_to_client(thr_state, 500, 0) <= -1) return -1;
|
||||
if (thr_state_send_final_status_to_client(thr_state, HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR, 0) <= -1) return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -225,7 +227,7 @@ static HIO_INLINE void thr_state_mark_over (thr_state_t* thr_state, int over_bit
|
||||
hio_dev_thr_halt (thr_state->peer);
|
||||
}
|
||||
|
||||
if (thr_state->keep_alive)
|
||||
if (thr_state->keep_alive && !thr_state->client_eof_detected)
|
||||
{
|
||||
/* how to arrange to delete this thr_state object and put the socket back to the normal waiting state??? */
|
||||
HIO_ASSERT (thr_state->htts->hio, thr_state->client->rsrc == (hio_svc_htts_rsrc_t*)thr_state);
|
||||
@ -396,7 +398,7 @@ static int thr_peer_on_read (hio_dev_thr_t* thr, const void* data, hio_iolen_t d
|
||||
if (!thr_state->ever_attempted_to_write_to_client &&
|
||||
!(thr_state->over & THR_STATE_OVER_WRITE_TO_CLIENT))
|
||||
{
|
||||
thr_state_send_final_status_to_client (thr_state, 500, 1); /* don't care about error because it jumps to oops below anyway */
|
||||
thr_state_send_final_status_to_client (thr_state, HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR, 1); /* don't care about error because it jumps to oops below anyway */
|
||||
}
|
||||
|
||||
goto oops;
|
||||
@ -452,7 +454,7 @@ static int thr_peer_htrd_peek (hio_htrd_t* htrd, hio_htre_t* req)
|
||||
thr_state_t* thr_state = thr_peer->state;
|
||||
hio_svc_htts_cli_t* cli = thr_state->client;
|
||||
hio_bch_t dtbuf[64];
|
||||
int status_code = 200;
|
||||
int status_code = HIO_HTTP_STATUS_OK;
|
||||
|
||||
if (req->attr.content_length)
|
||||
{
|
||||
@ -693,11 +695,14 @@ static int thr_client_on_read (hio_dev_sck_t* sck, const void* buf, hio_iolen_t
|
||||
{
|
||||
/* EOF on the client side. arrange to close */
|
||||
HIO_DEBUG3 (hio, "HTTPS(%p) - EOF from client %p(hnd=%d)\n", thr_state->client->htts, sck, (int)sck->hnd);
|
||||
thr_state->client_eof_detected = 1;
|
||||
|
||||
if (!(thr_state->over & THR_STATE_OVER_READ_FROM_CLIENT)) /* if this is true, EOF is received without thr_client_htrd_poke() */
|
||||
{
|
||||
if (thr_state_write_to_peer(thr_state, HIO_NULL, 0) <= -1) goto oops;
|
||||
int n;
|
||||
n = thr_state_write_to_peer(thr_state, HIO_NULL, 0);
|
||||
thr_state_mark_over (thr_state, THR_STATE_OVER_READ_FROM_CLIENT);
|
||||
if (n <= -1) goto oops;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -812,7 +817,7 @@ static int thr_capture_request_header (hio_htre_t* req, const hio_bch_t* key, co
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hio_svc_htts_dothr (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, hio_svc_htts_thr_func_t func, void* ctx)
|
||||
int hio_svc_htts_dothr (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, hio_svc_htts_thr_func_t func, void* ctx, int options)
|
||||
{
|
||||
hio_t* hio = htts->hio;
|
||||
hio_svc_htts_cli_t* cli = hio_dev_sck_getxtn(csck);
|
||||
@ -857,6 +862,7 @@ int hio_svc_htts_dothr (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
thr_state = (thr_state_t*)hio_svc_htts_rsrc_make(htts, HIO_SIZEOF(*thr_state), thr_state_on_kill);
|
||||
if (HIO_UNLIKELY(!thr_state)) goto oops;
|
||||
|
||||
thr_state->options = options;
|
||||
thr_state->client = cli;
|
||||
/*thr_state->num_pending_writes_to_client = 0;
|
||||
thr_state->num_pending_writes_to_peer = 0;*/
|
||||
@ -902,7 +908,7 @@ int hio_svc_htts_dothr (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
* option 2. send 411 Length Required immediately
|
||||
* option 3. set Content-Length to -1 and use EOF to indicate the end of content [Non-Standard] */
|
||||
|
||||
if (thr_state_send_final_status_to_client(thr_state, 411, 1) <= -1) goto oops;
|
||||
if (thr_state_send_final_status_to_client(thr_state, HIO_HTTP_STATUS_LENGTH_REQUIRED, 1) <= -1) goto oops;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -910,7 +916,8 @@ int hio_svc_htts_dothr (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
{
|
||||
/* TODO: Expect: 100-continue? who should handle this? thr? or the http server? */
|
||||
/* CAN I LET the thr SCRIPT handle this? */
|
||||
if (hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
if (!(options & HIO_SVC_HTTS_THR_NO_100_CONTINUE) &&
|
||||
hio_comp_http_version_numbers(&req->version, 1, 1) >= 0 &&
|
||||
(thr_state->req_content_length_unlimited || thr_state->req_content_length > 0))
|
||||
{
|
||||
/*
|
||||
@ -929,7 +936,7 @@ int hio_svc_htts_dothr (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
hio_bch_t msgbuf[64];
|
||||
hio_oow_t msglen;
|
||||
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d 100 Continue\r\n\r\n", thr_state->req_version.major, thr_state->req_version.minor);
|
||||
msglen = hio_fmttobcstr(hio, msgbuf, HIO_COUNTOF(msgbuf), "HTTP/%d.%d %d %hs\r\n\r\n", thr_state->req_version.major, thr_state->req_version.minor, HIO_HTTP_STATUS_CONTINUE, hio_http_status_to_bcstr(HIO_HTTP_STATUS_CONTINUE));
|
||||
if (thr_state_write_to_client(thr_state, msgbuf, msglen) <= -1) goto oops;
|
||||
thr_state->ever_attempted_to_write_to_client = 0; /* reset this as it's polluted for 100 continue */
|
||||
}
|
||||
@ -937,7 +944,7 @@ int hio_svc_htts_dothr (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
else if (req->flags & HIO_HTRE_ATTR_EXPECT)
|
||||
{
|
||||
/* 417 Expectation Failed */
|
||||
thr_state_send_final_status_to_client(thr_state, 417, 1);
|
||||
thr_state_send_final_status_to_client(thr_state, HIO_HTTP_STATUS_EXPECTATION_FAILED, 1);
|
||||
goto oops;
|
||||
}
|
||||
|
||||
|
@ -33,6 +33,7 @@ struct txt_t
|
||||
{
|
||||
HIO_SVC_HTTS_RSRC_HEADER;
|
||||
|
||||
int options;
|
||||
hio_oow_t num_pending_writes_to_client;
|
||||
hio_svc_htts_cli_t* client;
|
||||
hio_http_version_t req_version; /* client request */
|
||||
@ -40,6 +41,7 @@ struct txt_t
|
||||
unsigned int over: 2; /* must be large enough to accomodate TXT_OVER_ALL */
|
||||
unsigned int keep_alive: 1;
|
||||
unsigned int req_content_length_unlimited: 1;
|
||||
unsigned int client_eof_detected: 1;
|
||||
unsigned int client_disconnected: 1;
|
||||
unsigned int client_htrd_recbs_changed: 1;
|
||||
hio_oow_t req_content_length; /* client request content length */
|
||||
@ -133,7 +135,7 @@ static HIO_INLINE void txt_mark_over (txt_t* txt, int over_bits)
|
||||
if (old_over != TXT_OVER_ALL && txt->over == TXT_OVER_ALL)
|
||||
{
|
||||
/* ready to stop */
|
||||
if (txt->keep_alive)
|
||||
if (txt->keep_alive && !txt->client_eof_detected)
|
||||
{
|
||||
/* how to arrange to delete this txt object and put the socket back to the normal waiting state??? */
|
||||
HIO_ASSERT (txt->htts->hio, txt->client->rsrc == (hio_svc_htts_rsrc_t*)txt);
|
||||
@ -248,6 +250,7 @@ static int txt_client_on_read (hio_dev_sck_t* sck, const void* buf, hio_iolen_t
|
||||
{
|
||||
/* EOF on the client side. arrange to close */
|
||||
HIO_DEBUG3 (hio, "HTTPS(%p) - EOF from client %p(hnd=%d)\n", txt->client->htts, sck, (int)sck->hnd);
|
||||
txt->client_eof_detected = 1;
|
||||
|
||||
if (!(txt->over & TXT_OVER_READ_FROM_CLIENT)) /* if this is true, EOF is received without txt_client_htrd_poke() */
|
||||
{
|
||||
@ -316,7 +319,7 @@ oops:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hio_svc_htts_dotxt (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, int status_code, const hio_bch_t* content_type, const hio_bch_t* content_text)
|
||||
int hio_svc_htts_dotxt (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* req, int status_code, const hio_bch_t* content_type, const hio_bch_t* content_text, int options)
|
||||
{
|
||||
hio_t* hio = htts->hio;
|
||||
hio_svc_htts_cli_t* cli = hio_dev_sck_getxtn(csck);
|
||||
@ -328,6 +331,7 @@ int hio_svc_htts_dotxt (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
txt = (txt_t*)hio_svc_htts_rsrc_make(htts, HIO_SIZEOF(*txt), txt_on_kill);
|
||||
if (HIO_UNLIKELY(!txt)) goto oops;
|
||||
|
||||
txt->options = options;
|
||||
txt->client = cli;
|
||||
/*txt->num_pending_writes_to_client = 0;*/
|
||||
txt->req_version = *hio_htre_getversion(req);
|
||||
@ -350,7 +354,7 @@ int hio_svc_htts_dotxt (hio_svc_htts_t* htts, hio_dev_sck_t* csck, hio_htre_t* r
|
||||
else if (req->flags & HIO_HTRE_ATTR_EXPECT)
|
||||
{
|
||||
/* 417 Expectation Failed */
|
||||
txt_send_final_status_to_client(txt, 417, HIO_NULL, HIO_NULL, 1);
|
||||
txt_send_final_status_to_client(txt, HIO_HTTP_STATUS_EXPECTATION_FAILED, HIO_NULL, HIO_NULL, 1);
|
||||
goto oops;
|
||||
}
|
||||
|
||||
|
46
lib/http.c
46
lib/http.c
@ -46,50 +46,50 @@ const hio_bch_t* hio_http_status_to_bcstr (int code)
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case 100: msg = "Continue"; break;
|
||||
case 101: msg = "Switching Protocols"; break;
|
||||
case HIO_HTTP_STATUS_CONTINUE: msg = "Continue"; break;
|
||||
case HIO_HTTP_STATUS_SWITCH_PROTOCOL: msg = "Switching Protocols"; break;
|
||||
|
||||
case 200: msg = "OK"; break;
|
||||
case 201: msg = "Created"; break;
|
||||
case 202: msg = "Accepted"; break;
|
||||
case 203: msg = "Non-Authoritative Information"; break;
|
||||
case 204: msg = "No Content"; break;
|
||||
case 205: msg = "Reset Content"; break;
|
||||
case 206: msg = "Partial Content"; break;
|
||||
case HIO_HTTP_STATUS_OK: msg = "OK"; break;
|
||||
case HIO_HTTP_STATUS_CREATED: msg = "Created"; break;
|
||||
case HIO_HTTP_STATUS_ACCEPTED: msg = "Accepted"; break;
|
||||
case HIO_HTTP_STATUS_NON_AUTHORITATIVE: msg = "Non-Authoritative Information"; break;
|
||||
case HIO_HTTP_STATUS_NO_CONTENT: msg = "No Content"; break;
|
||||
case HIO_HTTP_STATUS_RESET_CONTENT: msg = "Reset Content"; break;
|
||||
case HIO_HTTP_STATUS_PARTIAL_CONTENT: msg = "Partial Content"; break;
|
||||
|
||||
case 300: msg = "Multiple Choices"; break;
|
||||
case 301: msg = "Moved Permanently"; break;
|
||||
case HIO_HTTP_STATUS_MOVED_PERMANENTLY: msg = "Moved Permanently"; break;
|
||||
case 302: msg = "Found"; break;
|
||||
case 303: msg = "See Other"; break;
|
||||
case 304: msg = "Not Modified"; break;
|
||||
case HIO_HTTP_STATUS_NOT_MODIFIED: msg = "Not Modified"; break;
|
||||
case 305: msg = "Use Proxy"; break;
|
||||
case 307: msg = "Temporary Redirect"; break;
|
||||
case 308: msg = "Permanent Redirect"; break;
|
||||
|
||||
case 400: msg = "Bad Request"; break;
|
||||
case HIO_HTTP_STATUS_BAD_REQUEST: msg = "Bad Request"; break;
|
||||
case 401: msg = "Unauthorized"; break;
|
||||
case 402: msg = "Payment Required"; break;
|
||||
case 403: msg = "Forbidden"; break;
|
||||
case 404: msg = "Not Found"; break;
|
||||
case 405: msg = "Method Not Allowed"; break;
|
||||
case HIO_HTTP_STATUS_FORBIDDEN: msg = "Forbidden"; break;
|
||||
case HIO_HTTP_STATUS_NOT_FOUND: msg = "Not Found"; break;
|
||||
case HIO_HTTP_STATUS_METHOD_NOT_ALLOWED: msg = "Method Not Allowed"; break;
|
||||
case 406: msg = "Not Acceptable"; break;
|
||||
case 407: msg = "Proxy Authentication Required"; break;
|
||||
case 408: msg = "Request Timeout"; break;
|
||||
case 409: msg = "Conflict"; break;
|
||||
case 410: msg = "Gone"; break;
|
||||
case 411: msg = "Length Required"; break;
|
||||
case HIO_HTTP_STATUS_LENGTH_REQUIRED: msg = "Length Required"; break;
|
||||
case 412: msg = "Precondition Failed"; break;
|
||||
case 413: msg = "Request Entity Too Large"; break;
|
||||
case 414: msg = "Request-URI Too Long"; break;
|
||||
case 415: msg = "Unsupported Media Type"; break;
|
||||
case 416: msg = "Requested Range Not Satisfiable"; break;
|
||||
case 417: msg = "Expectation Failed"; break;
|
||||
case HIO_HTTP_STATUS_RANGE_NOT_SATISFIABLE: msg = "Requested Range Not Satisfiable"; break;
|
||||
case HIO_HTTP_STATUS_EXPECTATION_FAILED: msg = "Expectation Failed"; break;
|
||||
case 426: msg = "Upgrade Required"; break;
|
||||
case 428: msg = "Precondition Required"; break;
|
||||
case 429: msg = "Too Many Requests"; break;
|
||||
case 431: msg = "Request Header Fields Too Large"; break;
|
||||
|
||||
case 500: msg = "Internal Server Error"; break;
|
||||
case HIO_HTTP_STATUS_INTERNAL_SERVER_ERROR: msg = "Internal Server Error"; break;
|
||||
case 501: msg = "Not Implemented"; break;
|
||||
case 502: msg = "Bad Gateway"; break;
|
||||
case 503: msg = "Service Unavailable"; break;
|
||||
@ -217,13 +217,7 @@ int hio_parse_http_range_bcstr (const hio_bch_t* str, hio_http_range_t* range)
|
||||
hio_foff_t from, to;
|
||||
int type = HIO_HTTP_RANGE_PROPER;
|
||||
|
||||
if (str[0] != 'b' ||
|
||||
str[1] != 'y' ||
|
||||
str[2] != 't' ||
|
||||
str[3] != 'e' ||
|
||||
str[4] != 's' ||
|
||||
str[5] != '=') return -1;
|
||||
|
||||
if (str[0] != 'b' || str[1] != 'y' || str[2] != 't' || str[3] != 'e' || str[4] != 's' || str[5] != '=') return -1;
|
||||
str += 6;
|
||||
|
||||
from = to = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user