diff --git a/mio/bin/Makefile.am b/mio/bin/Makefile.am index 94b80ca..01f96ae 100644 --- a/mio/bin/Makefile.am +++ b/mio/bin/Makefile.am @@ -14,7 +14,7 @@ LDFLAGS_ALL_COMMON = -L$(abs_builddir) -L$(abs_builddir)/../lib -L$(libdir) ################################################## CPPFLAGS_LIB_COMMON = $(CPPFLAGS_ALL_COMMON) -LDFLAGS_LIB_COMMON = $(LDFLAGS_ALL_COMMON) -version-info 1:0:0 -no-undefined +LDFLAGS_LIB_COMMON = $(LDFLAGS_ALL_COMMON) -no-undefined LIBADD_LIB_COMMON = $(LIBM) bin_PROGRAMS = mio-execd diff --git a/mio/bin/Makefile.in b/mio/bin/Makefile.in index ac9f312..6830e16 100644 --- a/mio/bin/Makefile.in +++ b/mio/bin/Makefile.in @@ -359,7 +359,7 @@ LDFLAGS_ALL_COMMON = -L$(abs_builddir) -L$(abs_builddir)/../lib -L$(libdir) # MAIN LIBRARY ################################################## CPPFLAGS_LIB_COMMON = $(CPPFLAGS_ALL_COMMON) -LDFLAGS_LIB_COMMON = $(LDFLAGS_ALL_COMMON) -version-info 1:0:0 -no-undefined +LDFLAGS_LIB_COMMON = $(LDFLAGS_ALL_COMMON) -no-undefined LIBADD_LIB_COMMON = $(LIBM) mio_execd_SOURCES = execd.c mio_execd_CPPFLAGS = $(CPPFLAGS_LIB_COMMON) diff --git a/mio/lib/htrd.c b/mio/lib/htrd.c new file mode 100644 index 0000000..10e5559 --- /dev/null +++ b/mio/lib/htrd.c @@ -0,0 +1,1735 @@ +/* + Copyright (c) 2016-2020 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 +#include +#include +#include "../cmn/mem-prv.h" + +static const mio_bch_t NUL = '\0'; + +/* for htrd->fed.s.flags */ +#define CONSUME_UNTIL_CLOSE (1 << 0) + +/* for htrd->flags */ +#define FEEDING_SUSPENDED (1 << 0) +#define FEEDING_DUMMIFIED (1 << 1) + +static MIO_INLINE int is_whspace_octet (mio_bch_t c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static MIO_INLINE int is_space_octet (mio_bch_t c) +{ + return c == ' ' || c == '\t' || c == '\r'; +} + +static MIO_INLINE int is_purespace_octet (mio_bch_t c) +{ + return c == ' ' || c == '\t'; +} + +static MIO_INLINE int is_upalpha_octet (mio_bch_t c) +{ + return c >= 'A' && c <= 'Z'; +} + +static MIO_INLINE int is_loalpha_octet (mio_bch_t c) +{ + return c >= 'a' && c <= 'z'; +} + +static MIO_INLINE int is_alpha_octet (mio_bch_t c) +{ + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +static MIO_INLINE int is_digit_octet (mio_bch_t c) +{ + return c >= '0' && c <= '9'; +} + +static MIO_INLINE int is_xdigit_octet (mio_bch_t c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); +} + +static MIO_INLINE int digit_to_num (mio_bch_t c) +{ + if (c >= '0' && c <= '9') return c - '0'; + return -1; +} + +static MIO_INLINE int xdigit_to_num (mio_bch_t c) +{ + return MIO_MXDIGITTONUM (c); +} + + +static MIO_INLINE int push_to_buffer (mio_htrd_t* htrd, mio_htob_t* octb, const mio_bch_t* ptr, mio_size_t len) +{ + if (mio_mbs_ncat (octb, ptr, len) == (mio_size_t)-1) + { + htrd->errnum = MIO_HTRD_ENOMEM; + return -1; + } + return 0; +} + +static MIO_INLINE int push_content (mio_htrd_t* htrd, const mio_bch_t* ptr, mio_size_t len) +{ + MIO_ASSERT (len > 0); + + if (mio_htre_addcontent(&htrd->re, ptr, len) <= -1) + { + htrd->errnum = MIO_HTRD_ENOMEM; + return -1; + } + + /* mio_htre_addcontent() returns 1 on full success and 0 if adding is + * skipped. i treat both as success */ + return 0; +} + +static MIO_INLINE void clear_feed (mio_htrd_t* htrd) +{ + /* clear necessary part of the request/response before + * reading the next request/response */ + htrd->clean = 1; + mio_htre_clear (&htrd->re); + + mio_mbs_clear (&htrd->fed.b.tra); + mio_mbs_clear (&htrd->fed.b.raw); + + MIO_MEMSET (&htrd->fed.s, 0, MIO_SIZEOF(htrd->fed.s)); +} + +mio_htrd_t* mio_htrd_open (mio_mmgr_t* mmgr, mio_size_t xtnsize) +{ + mio_htrd_t* htrd; + + htrd = (mio_htrd_t*)MIO_MMGR_ALLOC(mmgr, MIO_SIZEOF(mio_htrd_t) + xtnsize); + if (htrd) + { + if (mio_htrd_init (htrd, mmgr) <= -1) + { + MIO_MMGR_FREE (mmgr, htrd); + return MIO_NULL; + } + else MIO_MEMSET (MIO_XTN(htrd), 0, xtnsize); + } + return htrd; +} + +void mio_htrd_close (mio_htrd_t* htrd) +{ + mio_htrd_fini (htrd); + MIO_MMGR_FREE (htrd->mmgr, htrd); +} + +int mio_htrd_init (mio_htrd_t* htrd, mio_mmgr_t* mmgr) +{ + MIO_MEMSET (htrd, 0, MIO_SIZEOF(*htrd)); + htrd->mmgr = mmgr; + htrd->option = MIO_HTRD_REQUEST | MIO_HTRD_RESPONSE; + +#if 0 + mio_mbs_init (&htrd->tmp.qparam, htrd->mmgr, 0); +#endif + mio_mbs_init (&htrd->fed.b.raw, htrd->mmgr, 0); + mio_mbs_init (&htrd->fed.b.tra, htrd->mmgr, 0); + + if (mio_htre_init (&htrd->re, mmgr) <= -1) + { + mio_mbs_fini (&htrd->fed.b.tra); + mio_mbs_fini (&htrd->fed.b.raw); +#if 0 + mio_mbs_fini (&htrd->tmp.qparam); +#endif + return -1; + } + + htrd->clean = 1; + return 0; +} + +void mio_htrd_fini (mio_htrd_t* htrd) +{ + mio_htre_fini (&htrd->re); + + mio_mbs_fini (&htrd->fed.b.tra); + mio_mbs_fini (&htrd->fed.b.raw); +#if 0 + mio_mbs_fini (&htrd->tmp.qparam); +#endif +} + +static mio_bch_t* parse_initial_line (mio_htrd_t* htrd, mio_bch_t* line) +{ + mio_bch_t* p = line; + mio_mcstr_t tmp; + +#if 0 + /* ignore leading spaces excluding crlf */ + while (is_space_octet(*p)) p++; +#endif + + /* the method should start with an alphabet */ + if (!is_alpha_octet(*p)) goto badre; + + /* get the method name */ + tmp.ptr = p; + do { p++; } while (is_alpha_octet(*p)); + tmp.len = p - tmp.ptr; + + htrd->re.type = MIO_HTRE_Q; + if (htrd->option & MIO_HTRD_REQUEST) + { + /* method name must be followed by space */ + if (!is_space_octet(*p)) goto badre; + + *p = '\0'; /* null-terminate the method name */ + + htrd->re.u.q.method.type = mio_mcstrtohttpmethod (&tmp); + htrd->re.u.q.method.name = tmp.ptr; + } + else if ((htrd->option & MIO_HTRD_RESPONSE) && + mio_mbsxcmp (tmp.ptr, tmp.len, "HTTP") == 0) + { + /* it begins with HTTP. it may be a response */ + htrd->re.type = MIO_HTRE_S; + } + else goto badre; + + if (htrd->re.type == MIO_HTRE_S) + { + /* response */ + int n, status; + + if (*p == '/' && p[1] != '\0' && p[2] == '.') + { + int q = digit_to_num(p[1]); + int w = digit_to_num(p[3]); + if (q >= 0 && w >= 0) + { + htrd->re.version.major = q; + htrd->re.version.minor = w; + p += 4; + } + else goto badre; + } + else goto badre; + + /* version must be followed by space */ + if (!is_space_octet(*p)) goto badre; + + *p = '\0'; /* null-terminate version string */ + htrd->re.verstr = tmp.ptr; + + /* skip spaces */ + do p++; while (is_space_octet(*p)); + + n = digit_to_num(*p); + if (n <= -1) goto badre; + + tmp.ptr = p; + status = 0; + do + { + status = status * 10 + n; + p++; + } + while ((n = digit_to_num(*p)) >= 0); + + if (!is_space_octet(*p)) goto badre; + *p = '\0'; /* null-terminate the status code */ + + htrd->re.u.s.code.val = status; + htrd->re.u.s.code.str = tmp.ptr; + + /* i don't treat the following weird messages as bad message: + * no status message follows the status code + */ + + /* skip spaces */ + do p++; while (is_space_octet(*p)); + + tmp.ptr = p; + tmp.len = 0; + while (*p != '\0' && *p != '\n') + { + if (!is_space_octet(*p)) tmp.len = p - tmp.ptr + 1; + p++; + } + + /* if the line does not end with a new line, it is a bad request */ + if (*p != '\n') goto badre; + + /* null-terminate the message */ + ((mio_bch_t*)tmp.ptr)[tmp.len] = '\0'; + htrd->re.u.s.mesg = tmp.ptr; + } + else + { +#if 0 + mio_bch_t* out; +#endif + mio_mcstr_t param; + + /* skip spaces */ + do p++; while (is_space_octet(*p)); + + /* process the url part */ + tmp.ptr = p; /* remember the beginning of path*/ + param.ptr = MIO_NULL; +#if 0 + out = p; + while (*p != '\0' && !is_space_octet(*p)) + { + if (*p == '%' && param.ptr == MIO_NULL) + { + /* decode percent-encoded charaters in the + * path part. if we're in the parameter string + * part, we don't decode them. */ + + int q = xdigit_to_num(*(p+1)); + int w = xdigit_to_num(*(p+2)); + + if (q >= 0 && w >= 0) + { + int t = (q << 4) + w; + if (t == 0) + { + /* percent enconding contains a null character */ + goto badre; + } + + *out++ = t; + p += 3; + + htrd->re.flags |= MIO_HTRE_QPATH_PERDEC; + } + else *out++ = *p++; + } + else if (*p == '?') + { + if (param.ptr == MIO_NULL) + { + /* ? must be explicit to be an argument instroducer. + * %3f is just a literal. */ + tmp.len = out - tmp.ptr; + *out++ = '\0'; /* null-terminate the path part */ + param.ptr = out; + p++; + } + else *out++ = *p++; + } + else *out++ = *p++; + } + + /* the url must be followed by a space */ + if (!is_space_octet(*p)) goto badre; + + /* null-terminate the url part though we know the length */ + *out = '\0'; + + if (htrd->re.flags & MIO_HTRE_QPATH_PERDEC) + { + /* TODO: build the original qpath */ + htrd->re.orgpqath.ptr = XXX; + htrd->re.orgpath.len = XXXX; + } + + if (param.ptr) + { + param.len = out - param.ptr; + htrd->re.u.q.path = tmp; + htrd->re.u.q.param = param; + } + else + { + tmp.len = out - tmp.ptr; + htrd->re.u.q.path = tmp; + htrd->re.u.q.param.ptr = MIO_NULL; + htrd->re.u.q.param.len = 0; + } +#else + while (*p != '\0' && !is_space_octet(*p)) + { + if (*p == '?' && param.ptr == MIO_NULL) + { + tmp.len = p - tmp.ptr; /* length of the path part */ + *p++ = '\0'; /* null-terminate the path part */ + param.ptr = p; + } + else p++; + } + + /* the url must be followed by a space */ + if (!is_space_octet(*p)) goto badre; + param.len = p - param.ptr; /* length of the param part */ + *p = '\0'; /* null-terminate the path or param part */ + + if (param.ptr) + { + htrd->re.u.q.path = tmp; + htrd->re.u.q.param = param; + } + else + { + htrd->re.u.q.path = tmp; + htrd->re.u.q.param.ptr = MIO_NULL; + htrd->re.u.q.param.len = 0; + } +#endif + + if (htrd->option & MIO_HTRD_CANONQPATH) + { + mio_bch_t* qpath = htrd->re.u.q.path.ptr; + + /* if the url begins with xxx://, + * skip xxx:/ and canonicalize from the second slash */ + while (is_alpha_octet(*qpath)) qpath++; + if (mio_mbszcmp (qpath, "://", 3) == 0) + { + qpath = qpath + 2; /* set the position to the second / in :// */ + htrd->re.u.q.path.len = mio_canonmbspath (qpath, qpath, 0); + htrd->re.u.q.path.len += qpath - htrd->re.u.q.path.ptr; + } + else + { + qpath = htrd->re.u.q.path.ptr; + htrd->re.u.q.path.len = mio_canonmbspath (qpath, qpath, 0); + } + } + + /* skip spaces after the url part */ + do { p++; } while (is_space_octet(*p)); + + tmp.ptr = p; + /* check protocol version */ + if ((p[0] == 'H' || p[0] == 'h') && + (p[1] == 'T' || p[1] == 't') && + (p[2] == 'T' || p[2] == 't') && + (p[3] == 'P' || p[3] == 'p') && + p[4] == '/' && p[6] == '.') + { + int q = digit_to_num(p[5]); + int w = digit_to_num(p[7]); + if (q >= 0 && w >= 0) + { + htrd->re.version.major = q; + htrd->re.version.minor = w; + p += 8; + } + else goto badre; + } + else goto badre; + + tmp.len = p - tmp.ptr; + + /* skip trailing spaces on the line */ + while (is_space_octet(*p)) p++; + + /* if the line does not end with a new line, it is a bad request */ + if (*p != '\n') goto badre; + + ((mio_bch_t*)tmp.ptr)[tmp.len] = '\0'; + htrd->re.verstr = tmp.ptr; + } + + /* adjust Connection: Keep-Alive for HTTP 1.1 or later. + * this is initial. it can be adjusted further in capture_connection(). */ + if (htrd->re.version.major > 1 || + (htrd->re.version.major == 1 && htrd->re.version.minor >= 1)) + { + htrd->re.flags |= MIO_HTRE_ATTR_KEEPALIVE; + } + + return ++p; + +badre: + htrd->errnum = MIO_HTRD_EBADRE; + return MIO_NULL; +} + +void mio_htrd_clear (mio_htrd_t* htrd) +{ + clear_feed (htrd); + htrd->flags = 0; +} + +mio_mmgr_t* mio_htrd_getmmgr (mio_htrd_t* htrd) +{ + return htrd->mmgr; +} + +void* mio_htrd_getxtn (mio_htrd_t* htrd) +{ + return MIO_XTN (htrd); +} + + +mio_htrd_errnum_t mio_htrd_geterrnum (mio_htrd_t* htrd) +{ + return htrd->errnum; +} + +int mio_htrd_getopt (mio_htrd_t* htrd) +{ + return htrd->option; +} + +void mio_htrd_setopt (mio_htrd_t* htrd, int opts) +{ + htrd->option = opts; +} + +const mio_htrd_recbs_t* mio_htrd_getrecbs (mio_htrd_t* htrd) +{ + return htrd->recbs; +} + +void mio_htrd_setrecbs (mio_htrd_t* htrd, const mio_htrd_recbs_t* recbs) +{ + htrd->recbs = recbs; +} + +static int capture_connection (mio_htrd_t* htrd, mio_htb_pair_t* pair) +{ + mio_htre_hdrval_t* val; + + val = MIO_HTB_VPTR(pair); + while (val->next) val = val->next; + + /* The value for Connection: may get comma-separated. + * so use mio_mbscaseword() instead of mio_mbscmp(). */ + + if (mio_mbscaseword (val->ptr, "close", ',')) + { + htrd->re.flags &= ~MIO_HTRE_ATTR_KEEPALIVE; + return 0; + } + + if (mio_mbscaseword (val->ptr, "keep-alive", ',')) + { + htrd->re.flags |= MIO_HTRE_ATTR_KEEPALIVE; + return 0; + } + + /* Basically i don't care about other values. + * but for HTTP 1.0, other values will set connection to 'close'. + * + * Other values include even Keep-Alive specified multiple times. + * Connection: Keep-Alive + * Connection: Keep-Alive + * For the second Keep-Alive, this function sees 'Keep-Alive,Keep-Alive' + * That's because values of the same keys are concatenated. + */ + if (htrd->re.version.major < 1 || + (htrd->re.version.major == 1 && htrd->re.version.minor <= 0)) + { + htrd->re.flags &= ~MIO_HTRE_ATTR_KEEPALIVE; + } + return 0; +} + +static int capture_content_length (mio_htrd_t* htrd, mio_htb_pair_t* pair) +{ + mio_size_t len = 0, off = 0, tmp; + const mio_bch_t* ptr; + mio_htre_hdrval_t* val; + + /* get the last content_length */ + val = MIO_HTB_VPTR(pair); + while (val->next) val = val->next; + + ptr = val->ptr; + while (off < val->len) + { + int num = digit_to_num (ptr[off]); + if (num <= -1) + { + /* the length contains a non-digit */ + htrd->errnum = MIO_HTRD_EBADRE; + return -1; + } + + tmp = len * 10 + num; + if (tmp < len) + { + /* the length has overflown */ + htrd->errnum = MIO_HTRD_EBADRE; + return -1; + } + + len = tmp; + off++; + } + + if (off == 0) + { + /* no length was provided */ + htrd->errnum = MIO_HTRD_EBADRE; + return -1; + } + + if ((htrd->re.flags & MIO_HTRE_ATTR_CHUNKED) && len > 0) + { + /* content-length is greater than 0 + * while transfer-encoding: chunked is specified. */ + htrd->errnum = MIO_HTRD_EBADRE; + return -1; + } + + htrd->re.flags |= MIO_HTRE_ATTR_LENGTH; + htrd->re.attr.content_length = len; + return 0; +} + +static int capture_expect (mio_htrd_t* htrd, mio_htb_pair_t* pair) +{ + mio_htre_hdrval_t* val; + + /* Expect is included */ + htrd->re.flags |= MIO_HTRE_ATTR_EXPECT; + + val = MIO_HTB_VPTR(pair); + while (val) + { + /* Expect: 100-continue is included */ + if (mio_mbscasecmp(val->ptr, "100-continue") == 0) htrd->re.flags |= MIO_HTRE_ATTR_EXPECT100; + val = val->next; + } + + return 0; +} + +static int capture_status (mio_htrd_t* htrd, mio_htb_pair_t* pair) +{ + mio_htre_hdrval_t* val; + + val = MIO_HTB_VPTR(pair); + while (val->next) val = val->next; + + htrd->re.attr.status = val->ptr; + return 0; +} + +static int capture_transfer_encoding (mio_htrd_t* htrd, mio_htb_pair_t* pair) +{ + int n; + mio_htre_hdrval_t* val; + + val = MIO_HTB_VPTR(pair); + while (val->next) val = val->next; + + n = mio_mbscasecmp(val->ptr, "chunked"); + if (n == 0) + { + /* if (htrd->re.attr.content_length > 0) */ + if (htrd->re.flags & MIO_HTRE_ATTR_LENGTH) + { + /* both content-length and 'transfer-encoding: chunked' are specified. */ + goto badre; + } + + htrd->re.flags |= MIO_HTRE_ATTR_CHUNKED; + return 0; + } + + /* other encoding type not supported yet */ +badre: + htrd->errnum = MIO_HTRD_EBADRE; + return -1; +} + +static MIO_INLINE int capture_key_header (mio_htrd_t* htrd, mio_htb_pair_t* pair) +{ + static struct + { + const mio_bch_t* ptr; + mio_size_t len; + int (*handler) (mio_htrd_t*, mio_htb_pair_t*); + } hdrtab[] = + { + { "Connection", 10, capture_connection }, + { "Content-Length", 14, capture_content_length }, + { "Expect", 6, capture_expect }, + { "Status", 6, capture_status }, + { "Transfer-Encoding", 17, capture_transfer_encoding } + }; + + int n; + mio_size_t mid, count, base = 0; + + /* perform binary search */ + for (count = MIO_COUNTOF(hdrtab); count > 0; count /= 2) + { + mid = base + count / 2; + + n = mio_mbsxncasecmp ( + MIO_HTB_KPTR(pair), MIO_HTB_KLEN(pair), + hdrtab[mid].ptr, hdrtab[mid].len + ); + + if (n == 0) + { + /* bingo! */ + return hdrtab[mid].handler (htrd, pair); + } + + if (n > 0) { base = mid + 1; count--; } + } + + /* No callback functions were interested in this header field. */ + return 0; +} + +struct hdr_cbserter_ctx_t +{ + mio_htrd_t* htrd; + void* vptr; + mio_size_t vlen; +}; + +static mio_htb_pair_t* hdr_cbserter ( + mio_htb_t* htb, mio_htb_pair_t* pair, + void* kptr, mio_size_t klen, void* ctx) +{ + struct hdr_cbserter_ctx_t* tx = (struct hdr_cbserter_ctx_t*)ctx; + + if (pair == MIO_NULL) + { + /* the key is new. let's create a new pair. */ + mio_htb_pair_t* p; + mio_htre_hdrval_t *val; + + val = MIO_MMGR_ALLOC (htb->mmgr, MIO_SIZEOF(*val)); + if (val == MIO_NULL) + { + tx->htrd->errnum = MIO_HTRD_ENOMEM; + return MIO_NULL; + } + + MIO_MEMSET (val, 0, MIO_SIZEOF(*val)); + val->ptr = tx->vptr; + val->len = tx->vlen; + val->next = MIO_NULL; + + p = mio_htb_allocpair (htb, kptr, klen, val, 0); + if (p == MIO_NULL) + { + MIO_MMGR_FREE (htb->mmgr, val); + tx->htrd->errnum = MIO_HTRD_ENOMEM; + } + else + { + if (capture_key_header (tx->htrd, p) <= -1) + { + /* Destroy the pair created here + * as it is not added to the hash table yet */ + mio_htb_freepair (htb, p); + p = MIO_NULL; + } + } + + return p; + } + else + { + /* RFC2616 + * Multiple message-header fields with the same field-name + * MAY be present in a message if and only if the entire + * field-value for that header field is defined as a + * comma-separated list [i.e., #(values)]. It MUST be possible + * to combine the multiple header fields into one + * "field-name: field-value" pair, without changing the semantics + * of the message, by appending each subsequent field-value + * to the first, each separated by a comma. The order in which + * header fields with the same field-name are received is therefore + * significant to the interpretation of the combined field value, + * and thus a proxy MUST NOT change the order of these field values + * when a message is forwarded. + + * RFC6265 defines the syntax for Set-Cookie and Cookie. + * this seems to be conflicting with RFC2616. + * + * Origin servers SHOULD NOT fold multiple Set-Cookie header fields + * into a single header field. The usual mechanism for folding HTTP + * headers fields (i.e., as defined in [RFC2616]) might change the + * semantics of the Set-Cookie header field because the %x2C (",") + * character is used by Set-Cookie in a way that conflicts with + * such folding. + * + * So i just maintain the list of values for a key instead of + * folding them. + */ + + mio_htre_hdrval_t* val; + mio_htre_hdrval_t* tmp; + + val = (mio_htre_hdrval_t*) MIO_MMGR_ALLOC ( + tx->htrd->mmgr, MIO_SIZEOF(*val)); + if (val == MIO_NULL) + { + tx->htrd->errnum = MIO_HTRD_ENOMEM; + return MIO_NULL; + } + + MIO_MEMSET (val, 0, MIO_SIZEOF(*val)); + val->ptr = tx->vptr; + val->len = tx->vlen; + val->next = MIO_NULL; + +/* TODO: doubly linked list for speed-up??? */ + tmp = MIO_HTB_VPTR(pair); + MIO_ASSERT (tmp != MIO_NULL); + + /* find the tail */ + while (tmp->next) tmp = tmp->next; + /* append it to the list*/ + tmp->next = val; + + if (capture_key_header (tx->htrd, pair) <= -1) return MIO_NULL; + return pair; + } +} + +mio_bch_t* parse_header_field (mio_htrd_t* htrd, mio_bch_t* line, mio_htb_t* tab) +{ + mio_bch_t* p = line, * last; + struct + { + mio_bch_t* ptr; + mio_size_t len; + } name, value; + +#if 0 + /* ignore leading spaces excluding crlf */ + while (is_space_octet(*p)) p++; +#endif + + MIO_ASSERT (!is_whspace_octet(*p)); + + /* check the field name */ + name.ptr = last = p; + while (*p != '\0' && *p != '\n' && *p != ':') + { + if (!is_space_octet(*p++)) last = p; + } + name.len = last - name.ptr; + + if (*p != ':') + { + if (!(htrd->option & MIO_HTRD_STRICT)) + { + while (is_space_octet(*p)) p++; + if (*p == '\n') + { + /* ignore a line without a colon */ + p++; + return p; + } + } + goto badhdr; + } + *last = '\0'; + + /* skip the colon and spaces after it */ + do { p++; } while (is_space_octet(*p)); + + value.ptr = last = p; + while (*p != '\0' && *p != '\n') + { + if (!is_space_octet(*p++)) last = p; + } + + value.len = last - value.ptr; + if (*p != '\n') goto badhdr; /* not ending with a new line */ + + /* peep at the beginning of the next line to check if it is + * the continuation */ + if (is_purespace_octet (*++p)) + { + /* RFC: HTTP/1.0 headers may be folded onto multiple lines if + * each continuation line begins with a space or horizontal tab. + * All linear whitespace, including folding, has the same semantics + * as SP. */ + mio_bch_t* cpydst; + + cpydst = p - 1; + if (*(cpydst-1) == '\r') cpydst--; + + /* process all continued lines */ + do + { + while (*p != '\0' && *p != '\n') + { + *cpydst = *p++; + if (!is_space_octet(*cpydst++)) last = cpydst; + } + + value.len = last - value.ptr; + if (*p != '\n') goto badhdr; + + if (*(cpydst-1) == '\r') cpydst--; + } + while (is_purespace_octet(*++p)); + } + *last = '\0'; + + /* insert the new field to the header table */ + { + struct hdr_cbserter_ctx_t ctx; + + ctx.htrd = htrd; + ctx.vptr = value.ptr; + ctx.vlen = value.len; + + htrd->errnum = MIO_HTRD_ENOERR; + if (mio_htb_cbsert ( + tab, name.ptr, name.len, + hdr_cbserter, &ctx) == MIO_NULL) + { + if (htrd->errnum == MIO_HTRD_ENOERR) + htrd->errnum = MIO_HTRD_ENOMEM; + return MIO_NULL; + } + } + + return p; + +badhdr: + htrd->errnum = MIO_HTRD_EBADHDR; + return MIO_NULL; +} + +static MIO_INLINE int parse_initial_line_and_headers ( + mio_htrd_t* htrd, const mio_bch_t* req, mio_size_t rlen) +{ + mio_bch_t* p; + + /* add the actual request */ + if (push_to_buffer (htrd, &htrd->fed.b.raw, req, rlen) <= -1) return -1; + + /* add the terminating null for easier parsing */ + if (push_to_buffer (htrd, &htrd->fed.b.raw, &NUL, 1) <= -1) return -1; + + p = MIO_MBS_PTR(&htrd->fed.b.raw); + +#if 0 + if (htrd->option & MIO_HTRD_SKIPEMPTYLINES) + while (is_whspace_octet(*p)) p++; + else +#endif + while (is_space_octet(*p)) p++; + + MIO_ASSERT (*p != '\0'); + + /* parse the initial line */ + if (!(htrd->option & MIO_HTRD_SKIPINITIALLINE)) + { + p = parse_initial_line (htrd, p); + if (p == MIO_NULL) return -1; + } + + /* parse header fields */ + do + { + while (is_whspace_octet(*p)) p++; + if (*p == '\0') break; + + /* TODO: return error if protocol is 0.9. + * HTTP/0.9 must not get headers... */ + + p = parse_header_field (htrd, p, &htrd->re.hdrtab); + if (p == MIO_NULL) return -1; + } + while (1); + + return 0; +} + +/* chunk parsing phases */ +#define GET_CHUNK_DONE 0 +#define GET_CHUNK_LEN 1 +#define GET_CHUNK_DATA 2 +#define GET_CHUNK_CRLF 3 +#define GET_CHUNK_TRAILERS 4 + +static const mio_bch_t* getchunklen (mio_htrd_t* htrd, const mio_bch_t* ptr, mio_size_t len) +{ + const mio_bch_t* end = ptr + len; + + /* this function must be called in the GET_CHUNK_LEN context */ + MIO_ASSERT (htrd->fed.s.chunk.phase == GET_CHUNK_LEN); + + if (htrd->fed.s.chunk.count <= 0) + { + /* skip leading spaces if the first character of + * the chunk length has not been read yet */ + while (ptr < end && is_space_octet(*ptr)) ptr++; + } + + while (ptr < end) + { + int n = xdigit_to_num (*ptr); + if (n <= -1) break; + + htrd->fed.s.chunk.len = htrd->fed.s.chunk.len * 16 + n; + htrd->fed.s.chunk.count++; + ptr++; + } + + /* skip trailing spaces if the length has been read */ + while (ptr < end && is_space_octet(*ptr)) ptr++; + + if (ptr < end) + { + if (*ptr == '\n') + { + /* the chunk length line ended properly */ + + if (htrd->fed.s.chunk.count <= 0) + { + /* empty line - no more chunk */ + htrd->fed.s.chunk.phase = GET_CHUNK_DONE; + } + else if (htrd->fed.s.chunk.len <= 0) + { + /* length explicity specified to 0 + get trailing headers .... */ + htrd->fed.s.chunk.phase = GET_CHUNK_TRAILERS; + } + else + { + /* ready to read the chunk data... */ + htrd->fed.s.chunk.phase = GET_CHUNK_DATA; + } + + htrd->fed.s.need = htrd->fed.s.chunk.len; + ptr++; + } + else + { + htrd->errnum = MIO_HTRD_EBADRE; + return MIO_NULL; + } + } + + return ptr; +} + +static const mio_bch_t* get_trailing_headers ( + mio_htrd_t* htrd, const mio_bch_t* req, const mio_bch_t* end) +{ + const mio_bch_t* ptr = req; + + while (ptr < end) + { + register mio_bch_t b = *ptr++; + switch (b) + { + case '\0': + /* guarantee that the request does not contain a null + * character */ + htrd->errnum = MIO_HTRD_EBADRE; + return MIO_NULL; + + case '\n': + if (htrd->fed.s.crlf <= 1) + { + htrd->fed.s.crlf = 2; + break; + } + else + { + mio_bch_t* p; + + MIO_ASSERT (htrd->fed.s.crlf <= 3); + htrd->fed.s.crlf = 0; + + if (push_to_buffer ( + htrd, &htrd->fed.b.tra, req, ptr - req) <= -1) + return MIO_NULL; + if (push_to_buffer ( + htrd, &htrd->fed.b.tra, &NUL, 1) <= -1) + return MIO_NULL; + + p = MIO_MBS_PTR(&htrd->fed.b.tra); + + do + { + while (is_whspace_octet(*p)) p++; + if (*p == '\0') break; + + /* TODO: return error if protocol is 0.9. + * HTTP/0.9 must not get headers... */ + + p = parse_header_field ( + htrd, p, + ((htrd->option & MIO_HTRD_TRAILERS)? &htrd->re.trailers: &htrd->re.hdrtab) + ); + if (p == MIO_NULL) return MIO_NULL; + } + while (1); + + htrd->fed.s.chunk.phase = GET_CHUNK_DONE; + goto done; + } + + case '\r': + if (htrd->fed.s.crlf == 0 || htrd->fed.s.crlf == 2) + htrd->fed.s.crlf++; + else htrd->fed.s.crlf = 1; + break; + + default: + /* mark that neither CR nor LF was seen */ + htrd->fed.s.crlf = 0; + break; + } + } + + if (push_to_buffer (htrd, &htrd->fed.b.tra, req, ptr - req) <= -1) + return MIO_NULL; + +done: + return ptr; +} + +/* feed the percent encoded string */ +int mio_htrd_feed (mio_htrd_t* htrd, const mio_bch_t* req, mio_size_t len) +{ + const mio_bch_t* end = req + len; + const mio_bch_t* ptr = req; + int header_completed_during_this_feed = 0; + mio_size_t avail; + + MIO_ASSERT (len > 0); + + if (htrd->flags & FEEDING_SUSPENDED) + { + htrd->errnum = MIO_HTRD_ESUSPENDED; + return -1; + } + + /*if (htrd->option & MIO_HTRD_DUMMY)*/ + if (htrd->flags & FEEDING_DUMMIFIED) + { + /* treat everything as contents. + * i don't care about headers or whatsoever. */ + return push_content (htrd, req, len); + } + + /* does this goto drop code maintainability? */ + if (htrd->fed.s.need > 0) + { + /* we're in need of as many octets as htrd->fed.s.need + * for contents body. make a proper jump to resume + * content handling */ + goto content_resume; + } + + switch (htrd->fed.s.chunk.phase) + { + case GET_CHUNK_LEN: + goto dechunk_resume; + + case GET_CHUNK_DATA: + /* this won't be reached as htrd->fed.s.need + * is greater than 0 if GET_CHUNK_DATA is true */ + goto content_resume; + + case GET_CHUNK_CRLF: + goto dechunk_crlf; + + case GET_CHUNK_TRAILERS: + goto dechunk_get_trailers; + } + + htrd->clean = 0; /* mark that htrd is in need of some data */ + + while (ptr < end) + { + register mio_bch_t b = *ptr++; + +#if 0 + if (htrd->option & MIO_HTRD_SKIPEMPTYLINES && + htrd->fed.s.plen <= 0 && is_whspace_octet(b)) + { + /* let's drop leading whitespaces across multiple + * lines */ + req++; + continue; + } +#endif + + switch (b) + { + case '\0': + /* guarantee that the request does not contain + * a null character */ + htrd->errnum = MIO_HTRD_EBADRE; + return -1; + + case '\n': + { + if (htrd->fed.s.crlf <= 1) + { + /* htrd->fed.s.crlf == 0 + * => CR was not seen + * htrd->fed.s.crlf == 1 + * => CR was seen + * whatever the current case is, + * mark the first LF is seen here. + */ + htrd->fed.s.crlf = 2; + } + else + { + /* htrd->fed.s.crlf == 2 + * => no 2nd CR before LF + * htrd->fed.s.crlf == 3 + * => 2nd CR before LF + */ + + /* we got a complete request header. */ + MIO_ASSERT (htrd->fed.s.crlf <= 3); + + /* reset the crlf state */ + htrd->fed.s.crlf = 0; + /* reset the raw request length */ + htrd->fed.s.plen = 0; + + if (parse_initial_line_and_headers (htrd, req, ptr - req) <= -1) return -1; + + /* compelete request header is received */ + header_completed_during_this_feed = 1; + if (htrd->option & MIO_HTRD_PEEKONLY) + { + /* when MIO_HTRD_PEEKONLY is set, + * the peek callback is invoked once + * a complete header is seen. the caller + * should not feed more data by calling + * this function again once the callback is + * invoked. the trailing data is appended + * to the content buffer. + * + * NOTE: if the current feed that completed + * the header contains the next request, + * the next request is treated as if it + * belongs to the current request. + * + * In priciple, this option was added for + * reading CGI outputs. So it comes with + * awkwardity described above. + */ + if (ptr < end && push_content (htrd, ptr, end - ptr) <= -1) return -1; + + /* i don't really know if it is really completed + * with content. MIO_HTRD_PEEKONLY is not compatible + * with the completed state. anyway, let me complete + * it. */ + mio_htre_completecontent (&htrd->re); + + /* this jump is only to invoke the peek + * callback. this function should not be fed + * more. */ + goto feedme_more; + } + + /* carry on processing content body fed together with the header */ + if (htrd->re.flags & MIO_HTRE_ATTR_CHUNKED) + { + /* transfer-encoding: chunked */ + MIO_ASSERT (!(htrd->re.flags & MIO_HTRE_ATTR_LENGTH)); + + dechunk_start: + htrd->fed.s.chunk.phase = GET_CHUNK_LEN; + htrd->fed.s.chunk.len = 0; + htrd->fed.s.chunk.count = 0; + + dechunk_resume: + ptr = getchunklen (htrd, ptr, end - ptr); + if (ptr == MIO_NULL) return -1; + + if (htrd->fed.s.chunk.phase == GET_CHUNK_LEN) + { + /* still in the GET_CHUNK_LEN state. + * the length has been partially read. */ + goto feedme_more; + } + else if (htrd->fed.s.chunk.phase == GET_CHUNK_TRAILERS) + { + /* this state is reached after the + * last chunk length 0 is read. The next + * empty line immediately completes + * a content body. so i need to adjust + * this crlf status to 2 as if a trailing + * header line has been read. */ + htrd->fed.s.crlf = 2; + + dechunk_get_trailers: + ptr = get_trailing_headers (htrd, ptr, end); + if (ptr == MIO_NULL) return -1; + + if (htrd->fed.s.chunk.phase == GET_CHUNK_TRAILERS) + { + /* still in the same state. + * the trailers have not been processed fully */ + goto feedme_more; + } + } + } + else + { + /* we need to read as many octets as + * Content-Length */ + if ((htrd->option & MIO_HTRD_RESPONSE) && + !(htrd->re.flags & MIO_HTRE_ATTR_LENGTH) && + !(htrd->re.flags & MIO_HTRE_ATTR_KEEPALIVE)) + { + /* for a response, no content-length and + * no chunk are specified and 'connection' + * is to close. i must read until the + * connection is closed. however, there isn't + * any good way to know when to stop from + * within this function. so the caller + * can call mio_htrd_halt() for this. */ + + /* set this to the maximum in a type safe way + * assuming it's unsigned. the problem of + * the current implementation is that + * it can't receive more than */ + htrd->fed.s.need = 0; + htrd->fed.s.need = ~htrd->fed.s.need; + htrd->fed.s.flags |= CONSUME_UNTIL_CLOSE; + } + else if ((htrd->option & MIO_HTRD_RESPONSE) && + !(htrd->re.flags & MIO_HTRE_ATTR_LENGTH) && + (htrd->re.flags & MIO_HTRE_ATTR_KEEPALIVE)) + { + /* + * what the hell! + * no content-length, but keep-alive and not chunked. + * there's no way to know how large the contents is. + * + * For a request 'http://php.net/manual/en/function.curl-strerror.php' containing the following header fields: + * If-Modified-Since: Fri, 31 Oct 2014 11:12:47 GMT + * Accept-Encoding: gzip, deflate + * + * the service gave this response as of this writing: + * +HTTP/1.1 304 Not Modified +Server: nginx/1.6.2 +Date: Tue, 04 Nov 2014 15:45:46 GMT +Connection: keep-alive +Vary: Accept-Encoding +Set-Cookie: LAST_LANG=en; expires=Wed, 04-Nov-2015 15:45:46 GMT; Max-Age=31536000; path=/; domain=.php.net +Set-Cookie: COUNTRY=KOR%2C220.121.110.171; expires=Tue, 11-Nov-2014 15:45:46 GMT; Max-Age=604800; path=/; domain=.php.net + +XXXXXXXX + * + * XXXXXXX is some compressed garbage included in the contents-body. + * why does the service behave this way? is it a server bug or am i doing anything wrong? + * + * <> + * i decided to drop whatever trailing data avaiable + * after the header fields for this feeding. + * if more contents are fed in later, it will still + * end up with a bad request error. */ + ptr = end; + htrd->fed.s.need = 0; + } + else + { + htrd->fed.s.need = htrd->re.attr.content_length; + } + } + + if (htrd->fed.s.need > 0) + { + /* content-length or chunked data length + * specified */ + content_resume: + avail = end - ptr; + if (avail <= 0) + { + /* we didn't get a complete content yet */ + + /* avail can be 0 if data fed ends with + * a chunk length withtout actual data. + * so i check if avail is greater than 0 + * in order not to push empty content. */ + goto feedme_more; + } + else if (avail < htrd->fed.s.need) + { + /* the data is not as large as needed */ + if (push_content (htrd, ptr, avail) <= -1) return -1; + + if (!(htrd->fed.s.flags & CONSUME_UNTIL_CLOSE)) + { + /* i don't decrement htrd->fed.s.need + * if i should read until connection is closed. + * well, unless set your own callback, + * push_content() above will fail + * if too much has been received already */ + htrd->fed.s.need -= avail; + } + + /* we didn't get a complete content yet */ + goto feedme_more; + } + else + { + /* we got all or more than needed */ + if (push_content (htrd, ptr, htrd->fed.s.need) <= -1) return -1; + ptr += htrd->fed.s.need; + if (!(htrd->fed.s.flags & CONSUME_UNTIL_CLOSE)) + htrd->fed.s.need = 0; + } + } + + if (htrd->fed.s.chunk.phase == GET_CHUNK_DATA) + { + MIO_ASSERT (htrd->fed.s.need == 0); + htrd->fed.s.chunk.phase = GET_CHUNK_CRLF; + + dechunk_crlf: + while (ptr < end && is_space_octet(*ptr)) ptr++; + if (ptr < end) + { + if (*ptr == '\n') + { + /* end of chunk data. */ + ptr++; + + /* more octets still available. + * let it decode the next chunk + */ + if (ptr < end) goto dechunk_start; + + /* no more octets available after + * chunk data. the chunk state variables + * need to be reset when a jump is made + * to dechunk_resume upon the next call + */ + htrd->fed.s.chunk.phase = GET_CHUNK_LEN; + htrd->fed.s.chunk.len = 0; + htrd->fed.s.chunk.count = 0; + + goto feedme_more; + } + else + { + /* redundant character ... */ + htrd->errnum = MIO_HTRD_EBADRE; + return -1; + } + } + else + { + /* data not enough */ + goto feedme_more; + } + } + + /* the content has been received fully */ + mio_htre_completecontent (&htrd->re); + + if (header_completed_during_this_feed && htrd->recbs->peek) + { + /* the peek handler has not been executed. + * this can happen if this function is fed with + * at least the ending part of a complete header + * plus complete content body and the header + * of the next request. */ + int n; + htrd->errnum = MIO_HTRD_ENOERR; + n = htrd->recbs->peek (htrd, &htrd->re); + if (n <= -1) + { + if (htrd->errnum == MIO_HTRD_ENOERR) + htrd->errnum = MIO_HTRD_ERECBS; + /* need to clear request on error? + clear_feed (htrd); */ + return -1; + } + + header_completed_during_this_feed = 0; + } + + if (htrd->recbs->poke) + { + int n; + htrd->errnum = MIO_HTRD_ENOERR; + n = htrd->recbs->poke (htrd, &htrd->re); + if (n <= -1) + { + if (htrd->errnum == MIO_HTRD_ENOERR) + htrd->errnum = MIO_HTRD_ERECBS; + /* need to clear request on error? + clear_feed (htrd); */ + return -1; + } + } + +#if 0 +mio_printf (MIO_T("CONTENT_LENGTH %d, RAW HEADER LENGTH %d\n"), + (int)MIO_MBS_LEN(&htrd->re.content), + (int)MIO_MBS_LEN(&htrd->fed.b.raw)); +#endif + + clear_feed (htrd); + if (ptr >= end) return 0; /* no more feeds to handle */ + + if (htrd->flags & FEEDING_SUSPENDED) + { + htrd->errnum = MIO_HTRD_ESUSPENDED; + return -1; + } + + /*if (htrd->option & MIO_HTRD_DUMMY)*/ + if (htrd->flags & FEEDING_DUMMIFIED) + { + /* once the mode changes to RAW in a callback, + * left-over is pushed as contents */ + if (ptr < end) + return push_content (htrd, ptr, end - ptr); + else + return 0; + } + + + /* let ptr point to the next character to LF or + * the optional contents */ + req = ptr; + + /* since there are more to handle, i mark that + * htrd is in need of some data. this may + * not be really compatible with SKIPEMPTYLINES. + * SHOULD I simply remove the option? */ + htrd->clean = 0; + } + break; + } + + case '\r': + if (htrd->fed.s.crlf == 0 || htrd->fed.s.crlf == 2) + htrd->fed.s.crlf++; + else htrd->fed.s.crlf = 1; + break; + + default: + /* increment length of a request in raw + * excluding crlf */ + htrd->fed.s.plen++; + /* mark that neither CR nor LF was seen */ + htrd->fed.s.crlf = 0; + } + } + + if (ptr > req) + { + /* enbuffer the incomplete request */ + if (push_to_buffer (htrd, &htrd->fed.b.raw, req, ptr - req) <= -1) return -1; + } + +feedme_more: + if (header_completed_during_this_feed && htrd->recbs->peek) + { + int n; + htrd->errnum = MIO_HTRD_ENOERR; + n = htrd->recbs->peek (htrd, &htrd->re); + if (n <= -1) + { + if (htrd->errnum == MIO_HTRD_ENOERR) + htrd->errnum = MIO_HTRD_ERECBS; + /* need to clear request on error? + clear_feed (htrd); */ + return -1; + } + } + + return 0; +} + +int mio_htrd_halt (mio_htrd_t* htrd) +{ + if (htrd->fed.s.flags & CONSUME_UNTIL_CLOSE || !htrd->clean) + { + mio_htre_completecontent (&htrd->re); + + if (htrd->recbs->poke) + { + int n; + htrd->errnum = MIO_HTRD_ENOERR; + n = htrd->recbs->poke (htrd, &htrd->re); + if (n <= -1) + { + if (htrd->errnum == MIO_HTRD_ENOERR) + htrd->errnum = MIO_HTRD_ERECBS; + /* need to clear request on error? + clear_feed (htrd); */ + return -1; + } + } + + clear_feed (htrd); + } + + return 0; +} + +void mio_htrd_suspend (mio_htrd_t* htrd) +{ + htrd->flags |= FEEDING_SUSPENDED; +} + +void mio_htrd_resume (mio_htrd_t* htrd) +{ + htrd->flags &= ~FEEDING_SUSPENDED; +} + +void mio_htrd_dummify (mio_htrd_t* htrd) +{ + htrd->flags |= FEEDING_DUMMIFIED; +} + +void mio_htrd_undummify (mio_htrd_t* htrd) +{ + htrd->flags &= ~FEEDING_DUMMIFIED; +} + +#if 0 +int mio_htrd_scanqparam (mio_htrd_t* htrd, const mio_mcstr_t* cstr) +{ + mio_mcstr_t key, val; + const mio_bch_t* p, * end; + mio_bch_t* out; + + if (cstr == MIO_NULL) cstr = mio_htre_getqparamcstr(&htrd->re); + + p = cstr->ptr; + if (p == MIO_NULL) return 0; /* no param string to scan */ + + end = p + cstr->len; + + /* a key and a value pair including two terminating null + * can't exceed the the qparamstrlen + 2. only +1 below as there is + * one more space for an internal terminating null */ + mio_mbs_setlen (&htrd->tmp.qparam, cstr->len + 1); + + /* let out point to the beginning of the qparam buffer. + * the loop below emits percent-decode key and value to this buffer. */ + out = MIO_MBS_PTR(&htrd->tmp.qparam); + + key.ptr = out; key.len = 0; + val.ptr = MIO_NULL; val.len = 0; + + do + { + if (p >= end || *p == '&' || *p == ';') + { + MIO_ASSERT (key.ptr != MIO_NULL); + + *out++ = '\0'; + if (val.ptr == MIO_NULL) + { + if (key.len == 0) + { + /* both key and value are empty. + * we don't need to do anything */ + goto next_octet; + } + + val.ptr = out; + *out++ = '\0'; + MIO_ASSERT (val.len == 0); + } + + MIO_ASSERTX ( + htrd->recbs->qparamstr != MIO_NULL, + "set request parameter string callback before scanning" + ); + + htrd->errnum = MIO_HTRD_ENOERR; + if (htrd->recbs->qparamstr (htrd, &key, &val) <= -1) + { + if (htrd->errnum == MIO_HTRD_ENOERR) + htrd->errnum = MIO_HTRD_ERECBS; + return -1; + } + + next_octet: + if (p >= end) break; + p++; + + out = MIO_MBS_PTR(&htrd->tmp.qparam); + key.ptr = out; key.len = 0; + val.ptr = MIO_NULL; val.len = 0; + } + else if (*p == '=') + { + *out++ = '\0'; p++; + + val.ptr = out; + /*val.len = 0; */ + } + else + { + if (*p == '%' && p + 2 <= end) + { + int q = xdigit_to_num(*(p+1)); + if (q >= 0) + { + int w = xdigit_to_num(*(p+2)); + if (w >= 0) + { + /* unlike the path part, we don't care if it + * contains a null character */ + *out++ = ((q << 4) + w); + p += 3; + goto next; + } + } + } + + *out++ = *p++; + + next: + if (val.ptr) val.len++; + else key.len++; + } + } + while (1); + + mio_mbs_clear (&htrd->tmp.qparam); + return 0; +} +#endif diff --git a/mio/lib/htre.c b/mio/lib/htre.c new file mode 100644 index 0000000..a535d57 --- /dev/null +++ b/mio/lib/htre.c @@ -0,0 +1,320 @@ +/* + * $Id$ + * + Copyright (c) 2016-2020 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 +#include "mio-prv.h" + +static void free_hdrval (mio_htb_t* htb, void* vptr, mio_size_t vlen) +{ + mio_htre_hdrval_t* val; + mio_htre_hdrval_t* tmp; + + val = vptr; + while (val) + { + tmp = val; + val = val->next; + MIO_MMGR_FREE (htb->mmgr, tmp); + } +} + +int mio_htre_init (mio_htre_t* re, mio_mmgr_t* mmgr) +{ + static mio_htb_style_t style = + { + { + MIO_HTB_COPIER_DEFAULT, + MIO_HTB_COPIER_DEFAULT + }, + { + MIO_HTB_FREEER_DEFAULT, + free_hdrval + }, + MIO_HTB_COMPER_DEFAULT, + MIO_HTB_KEEPER_DEFAULT, + MIO_HTB_SIZER_DEFAULT, + MIO_HTB_HASHER_DEFAULT + }; + + MIO_MEMSET (re, 0, MIO_SIZEOF(*re)); + re->mmgr = mmgr; + + if (mio_htb_init (&re->hdrtab, mmgr, 60, 70, 1, 1) <= -1) return -1; + if (mio_htb_init (&re->trailers, mmgr, 20, 70, 1, 1) <= -1) return -1; + + mio_htb_setstyle (&re->hdrtab, &style); + mio_htb_setstyle (&re->trailers, &style); + + mio_mbs_init (&re->content, mmgr, 0); +#if 0 + mio_mbs_init (&re->iniline, mmgr, 0); +#endif + + return 0; +} + +void mio_htre_fini (mio_htre_t* re) +{ +#if 0 + mio_mbs_fini (&re->iniline); +#endif + mio_mbs_fini (&re->content); + mio_htb_fini (&re->trailers); + mio_htb_fini (&re->hdrtab); + + if (re->orgqpath.buf) + MIO_MMGR_FREE (re->mmgr, re->orgqpath.buf); +} + +void mio_htre_clear (mio_htre_t* re) +{ + if (!(re->state & MIO_HTRE_COMPLETED) && + !(re->state & MIO_HTRE_DISCARDED)) + { + if (re->concb) + { + re->concb (re, MIO_NULL, 0, re->concb_ctx); /* indicate end of content */ + mio_htre_unsetconcb (re); + } + } + + re->state = 0; + re->flags = 0; + + re->orgqpath.ptr = MIO_NULL; + re->orgqpath.len = 0; + + MIO_MEMSET (&re->version, 0, MIO_SIZEOF(re->version)); + MIO_MEMSET (&re->attr, 0, MIO_SIZEOF(re->attr)); + + mio_htb_clear (&re->hdrtab); + mio_htb_clear (&re->trailers); + + mio_mbs_clear (&re->content); +#if 0 + mio_mbs_clear (&re->iniline); +#endif +} + +const mio_htre_hdrval_t* mio_htre_getheaderval ( + const mio_htre_t* re, const mio_mchar_t* name) +{ + mio_htb_pair_t* pair; + pair = mio_htb_search (&re->hdrtab, name, mio_mbslen(name)); + if (pair == MIO_NULL) return MIO_NULL; + return MIO_HTB_VPTR(pair); +} + +const mio_htre_hdrval_t* mio_htre_gettrailerval ( + const mio_htre_t* re, const mio_mchar_t* name) +{ + mio_htb_pair_t* pair; + pair = mio_htb_search (&re->trailers, name, mio_mbslen(name)); + if (pair == MIO_NULL) return MIO_NULL; + return MIO_HTB_VPTR(pair); +} + +struct header_walker_ctx_t +{ + mio_htre_t* re; + mio_htre_header_walker_t walker; + void* ctx; + int ret; +}; + +static mio_htb_walk_t walk_headers ( + mio_htb_t* htb, mio_htb_pair_t* pair, void* ctx) +{ + struct header_walker_ctx_t* hwctx = (struct header_walker_ctx_t*)ctx; + if (hwctx->walker (hwctx->re, MIO_HTB_KPTR(pair), MIO_HTB_VPTR(pair), hwctx->ctx) <= -1) + { + hwctx->ret = -1; + return MIO_HTB_WALK_STOP; + } + return MIO_HTB_WALK_FORWARD; +} + +int mio_htre_walkheaders ( + mio_htre_t* re, mio_htre_header_walker_t walker, void* ctx) +{ + struct header_walker_ctx_t hwctx; + hwctx.re = re; + hwctx.walker = walker; + hwctx.ctx = ctx; + hwctx.ret = 0; + mio_htb_walk (&re->hdrtab, walk_headers, &hwctx); + return hwctx.ret; +} + +int mio_htre_walktrailers ( + mio_htre_t* re, mio_htre_header_walker_t walker, void* ctx) +{ + struct header_walker_ctx_t hwctx; + hwctx.re = re; + hwctx.walker = walker; + hwctx.ctx = ctx; + hwctx.ret = 0; + mio_htb_walk (&re->trailers, walk_headers, &hwctx); + return hwctx.ret; +} + +int mio_htre_addcontent ( + mio_htre_t* re, const mio_mchar_t* ptr, mio_size_t len) +{ + /* see comments in mio_htre_discardcontent() */ + + if (re->state & (MIO_HTRE_COMPLETED | MIO_HTRE_DISCARDED)) return 0; /* skipped */ + + if (re->concb) + { + /* if the callback is set, the content goes to the callback. */ + if (re->concb (re, ptr, len, re->concb_ctx) <= -1) return -1; + } + else + { + /* if the callback is not set, the contents goes to the internal buffer */ + if (mio_mbs_ncat (&re->content, ptr, len) == (mio_size_t)-1) return -1; + } + + return 1; /* added successfully */ +} + +void mio_htre_completecontent (mio_htre_t* re) +{ + /* see comments in mio_htre_discardcontent() */ + + if (!(re->state & MIO_HTRE_COMPLETED) && + !(re->state & MIO_HTRE_DISCARDED)) + { + re->state |= MIO_HTRE_COMPLETED; + if (re->concb) + { + /* indicate end of content */ + re->concb (re, MIO_NULL, 0, re->concb_ctx); + } + } +} + +void mio_htre_discardcontent (mio_htre_t* re) +{ + /* you can't discard this if it's completed. + * you can't complete this if it's discarded + * you can't add contents to this if it's completed or discarded + */ + + if (!(re->state & MIO_HTRE_COMPLETED) && + !(re->state & MIO_HTRE_DISCARDED)) + { + re->state |= MIO_HTRE_DISCARDED; + + /* mio_htre_addcontent()... + * mio_thre_setconcb()... + * mio_htre_discardcontent()... <-- POINT A. + * + * at point A, the content must contain something + * and concb is also set. for simplicity, + * clear the content buffer and invoke the callback + * + * likewise, you may produce many weird combinations + * of these functions. however, these functions are + * designed to serve a certain usage pattern not including + * weird combinations. + */ + mio_mbs_clear (&re->content); + if (re->concb) + { + /* indicate end of content */ + re->concb (re, MIO_NULL, 0, re->concb_ctx); + } + } +} + +void mio_htre_unsetconcb (mio_htre_t* re) +{ + re->concb = MIO_NULL; + re->concb_ctx = MIO_NULL; +} + +void mio_htre_setconcb (mio_htre_t* re, mio_htre_concb_t concb, void* ctx) +{ + re->concb = concb; + re->concb_ctx = ctx; +} + +int mio_htre_perdecqpath (mio_htre_t* re) +{ + mio_size_t dec_count; + + /* percent decode the query path*/ + + if (re->type != MIO_HTRE_Q || (re->flags & MIO_HTRE_QPATH_PERDEC)) return -1; + + MIO_ASSERT (re->orgqpath.len <= 0); + MIO_ASSERT (re->orgqpath.ptr == MIO_NULL); + + if (mio_isperencedhttpstr(re->u.q.path.ptr)) + { + /* the string is percent-encoded. keep the original request + * in a separately allocated buffer */ + + if (re->orgqpath.buf && re->u.q.path.len <= re->orgqpath.capa) + { + re->orgqpath.len = mio_mbscpy (re->orgqpath.buf, re->u.q.path.ptr); + re->orgqpath.ptr = re->orgqpath.buf; + } + else + { + if (re->orgqpath.buf) + { + MIO_MMGR_FREE (re->mmgr, re->orgqpath.buf); + re->orgqpath.capa = 0; + } + + re->orgqpath.buf = mio_mbsxdup (re->u.q.path.ptr, re->u.q.path.len, re->mmgr); + if (!re->orgqpath.buf) return -1; + re->orgqpath.capa = re->u.q.path.len; + + re->orgqpath.ptr = re->orgqpath.buf; + re->orgqpath.len = re->orgqpath.capa; + + /* orgqpath.buf and orgqpath.ptr are the same here. the caller + * is free to change orgqpath.ptr to point to a differnt position + * in the buffer. */ + } + } + + re->u.q.path.len = mio_perdechttpstr (re->u.q.path.ptr, re->u.q.path.ptr, &dec_count); + if (dec_count > 0) + { + /* this assertion is to ensure that mio_isperencedhttpstr() + * returned true when dec_count is greater than 0 */ + MIO_ASSERT (re->orgqpath.buf != MIO_NULL); + MIO_ASSERT (re->orgqpath.ptr != MIO_NULL); + re->flags |= MIO_HTRE_QPATH_PERDEC; + } + + return 0; +} diff --git a/mio/lib/http.c b/mio/lib/http.c new file mode 100644 index 0000000..4336e87 --- /dev/null +++ b/mio/lib/http.c @@ -0,0 +1,534 @@ +/* + Copyright (c) 2016-2020 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 +#include "mio-prv.h" + +int mio_comparehttpversions ( + const mio_http_version_t* v1, + const mio_http_version_t* v2) +{ + if (v1->major == v2->major) return v1->minor - v2->minor; + return v1->major - v2->major; +} + +const mio_mchar_t* mio_httpstatustombs (int code) +{ + const mio_mchar_t* msg; + + switch (code) + { + case 100: msg = "Continue"; break; + case 101: 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 300: msg = "Multiple Choices"; break; + case 301: msg = "Moved Permanently"; break; + case 302: msg = "Found"; break; + case 303: msg = "See Other"; break; + case 304: 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 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 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 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 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 501: msg = "Not Implemented"; break; + case 502: msg = "Bad Gateway"; break; + case 503: msg = "Service Unavailable"; break; + case 504: msg = "Gateway Timeout"; break; + case 505: msg = "HTTP Version Not Supported"; break; + + default: msg = "Unknown Error"; break; + } + + return msg; +} + +const mio_mchar_t* mio_httpmethodtombs (mio_http_method_t type) +{ + /* keep this table in the same order as mio_httpd_method_t enumerators */ + static mio_mchar_t* names[] = + { + "OTHER", + + "HEAD", + "GET", + "POST", + "PUT", + "DELETE", + "OPTIONS", + "TRACE", + "CONNECT" + }; + + return (type < 0 || type >= MIO_COUNTOF(names))? MIO_NULL: names[type]; +} + +struct mtab_t +{ + const mio_mchar_t* name; + mio_http_method_t type; +}; + +static struct mtab_t mtab[] = +{ + /* keep this table sorted by name for binary search */ + { "CONNECT", MIO_HTTP_CONNECT }, + { "DELETE", MIO_HTTP_DELETE }, + { "GET", MIO_HTTP_GET }, + { "HEAD", MIO_HTTP_HEAD }, + { "OPTIONS", MIO_HTTP_OPTIONS }, + { "POST", MIO_HTTP_POST }, + { "PUT", MIO_HTTP_PUT }, + { "TRACE", MIO_HTTP_TRACE } +}; + +mio_http_method_t mio_mbstohttpmethod (const mio_mchar_t* name) +{ + /* perform binary search */ + + /* declaring left, right, mid to be of int is ok + * because we know mtab is small enough. */ + int left = 0, right = MIO_COUNTOF(mtab) - 1, mid; + + while (left <= right) + { + int n; + struct mtab_t* entry; + + /*mid = (left + right) / 2;*/ + mid = left + (right - left) / 2; + entry = &mtab[mid]; + + n = mio_mbscmp (name, entry->name); + if (n < 0) + { + /* if left, right, mid were of mio_size_t, + * you would need the following line. + if (mid == 0) break; + */ + right = mid - 1; + } + else if (n > 0) left = mid + 1; + else return entry->type; + } + + return MIO_HTTP_OTHER; +} + +mio_http_method_t mio_mcstrtohttpmethod (const mio_mcstr_t* name) +{ + /* perform binary search */ + + /* declaring left, right, mid to be of int is ok + * because we know mtab is small enough. */ + int left = 0, right = MIO_COUNTOF(mtab) - 1, mid; + + while (left <= right) + { + int n; + struct mtab_t* entry; + + /*mid = (left + right) / 2;*/ + mid = left + (right - left) / 2; + entry = &mtab[mid]; + + n = mio_mbsxcmp (name->ptr, name->len, entry->name); + if (n < 0) + { + /* if left, right, mid were of mio_size_t, + * you would need the following line. + if (mid == 0) break; + */ + right = mid - 1; + } + else if (n > 0) left = mid + 1; + else return entry->type; + } + + return MIO_HTTP_OTHER; +} + +int mio_parsehttprange (const mio_mchar_t* str, mio_http_range_t* range) +{ + /* NOTE: this function does not support a range set + * like bytes=1-20,30-50 */ + + mio_http_range_int_t from, to; + int type = MIO_HTTP_RANGE_PROPER; + + if (str[0] != 'b' || + str[1] != 'y' || + str[2] != 't' || + str[3] != 'e' || + str[4] != 's' || + str[5] != '=') return -1; + + str += 6; + + from = 0; + if (MIO_ISMDIGIT(*str)) + { + do + { + from = from * 10 + (*str - '0'); + str++; + } + while (MIO_ISMDIGIT(*str)); + } + else type = MIO_HTTP_RANGE_SUFFIX; + + if (*str != '-') return -1; + str++; + + if (MIO_ISMDIGIT(*str)) + { + to = 0; + do + { + to = to * 10 + (*str - '0'); + str++; + } + while (MIO_ISMDIGIT(*str)); + } + else to = MIO_TYPE_MAX(mio_http_range_int_t); + + if (from > to) return -1; + + range->type = type; + range->from = from; + range->to = to; + return 0; +} + +typedef struct mname_t mname_t; +struct mname_t +{ + const mio_mchar_t* s; + const mio_mchar_t* l; +}; + +static mname_t wday_name[] = +{ + { "Sun", "Sunday" }, + { "Mon", "Monday" }, + { "Tue", "Tuesday" }, + { "Wed", "Wednesday" }, + { "Thu", "Thursday" }, + { "Fri", "Friday" }, + { "Sat", "Saturday" } +}; + +static mname_t mon_name[] = +{ + { "Jan", "January" }, + { "Feb", "February" }, + { "Mar", "March" }, + { "Apr", "April" }, + { "May", "May" }, + { "Jun", "June" }, + { "Jul", "July" }, + { "Aug", "August" }, + { "Sep", "September" }, + { "Oct", "October" }, + { "Nov", "November" }, + { "Dec", "December" } +}; + +int mio_parsehttptime (const mio_mchar_t* str, mio_ntime_t* nt) +{ + mio_btime_t bt; + const mio_mchar_t* word; + mio_size_t wlen, i; + + /* TODO: support more formats */ + + MIO_MEMSET (&bt, 0, MIO_SIZEOF(bt)); + + /* weekday */ + while (MIO_ISMSPACE(*str)) str++; + for (word = str; MIO_ISMALPHA(*str); str++); + wlen = str - word; + for (i = 0; i < MIO_COUNTOF(wday_name); i++) + { + if (mio_mbsxcmp (word, wlen, wday_name[i].s) == 0) + { + bt.wday = i; + break; + } + } + if (i >= MIO_COUNTOF(wday_name)) return -1; + + /* comma - i'm just loose as i don't care if it doesn't exist */ + while (MIO_ISMSPACE(*str)) str++; + if (*str == ',') str++; + + /* day */ + while (MIO_ISMSPACE(*str)) str++; + if (!MIO_ISMDIGIT(*str)) return -1; + do bt.mday = bt.mday * 10 + *str++ - '0'; while (MIO_ISMDIGIT(*str)); + + /* month */ + while (MIO_ISMSPACE(*str)) str++; + for (word = str; MIO_ISMALPHA(*str); str++); + wlen = str - word; + for (i = 0; i < MIO_COUNTOF(mon_name); i++) + { + if (mio_mbsxcmp (word, wlen, mon_name[i].s) == 0) + { + bt.mon = i; + break; + } + } + if (i >= MIO_COUNTOF(mon_name)) return -1; + + /* year */ + while (MIO_ISMSPACE(*str)) str++; + if (!MIO_ISMDIGIT(*str)) return -1; + do bt.year = bt.year * 10 + *str++ - '0'; while (MIO_ISMDIGIT(*str)); + bt.year -= MIO_BTIME_YEAR_BASE; + + /* hour */ + while (MIO_ISMSPACE(*str)) str++; + if (!MIO_ISMDIGIT(*str)) return -1; + do bt.hour = bt.hour * 10 + *str++ - '0'; while (MIO_ISMDIGIT(*str)); + if (*str != ':') return -1; + str++; + + /* min */ + while (MIO_ISMSPACE(*str)) str++; + if (!MIO_ISMDIGIT(*str)) return -1; + do bt.min = bt.min * 10 + *str++ - '0'; while (MIO_ISMDIGIT(*str)); + if (*str != ':') return -1; + str++; + + /* sec */ + while (MIO_ISMSPACE(*str)) str++; + if (!MIO_ISMDIGIT(*str)) return -1; + do bt.sec = bt.sec * 10 + *str++ - '0'; while (MIO_ISMDIGIT(*str)); + + /* GMT */ + while (MIO_ISMSPACE(*str)) str++; + for (word = str; MIO_ISMALPHA(*str); str++); + wlen = str - word; + if (mio_mbsxcmp (word, wlen, "GMT" != 0) return -1; + + while (MIO_ISMSPACE(*str)) str++; + if (*str != '\0') return -1; + + return mio_timegm (&bt, nt); +} + +mio_mchar_t* mio_fmthttptime (const mio_ntime_t* nt, mio_mchar_t* buf, mio_size_t bufsz) +{ + mio_btime_t bt; + + mio_gmtime (nt, &bt); + + mio_mbsxfmt ( + buf, bufsz, + "%s, %d %s %d %02d:%02d:%02d GMT", + wday_name[bt.wday].s, + bt.mday, + mon_name[bt.mon].s, + bt.year + MIO_BTIME_YEAR_BASE, + bt.hour, bt.min, bt.sec + ); + + return buf; +} + +int mio_isperencedhttpstr (const mio_mchar_t* str) +{ + const mio_mchar_t* p = str; + + while (*p != '\0') + { + if (*p == '%' && *(p + 1) != '\0' && *(p + 2) != '\0') + { + int q = MIO_MXDIGITTONUM (*(p + 1)); + if (q >= 0) + { + /* return true if the first valid percent-encoded sequence is found */ + int w = MIO_MXDIGITTONUM (*(p + 2)); + if (w >= 0) return 1; + } + } + + p++; + } + + return 1; +} + +mio_size_t mio_perdechttpstr (const mio_mchar_t* str, mio_mchar_t* buf, mio_size_t* ndecs) +{ + const mio_mchar_t* p = str; + mio_mchar_t* out = buf; + mio_size_t dec_count = 0; + + while (*p != '\0') + { + if (*p == '%' && *(p + 1) != '\0' && *(p + 2) != '\0') + { + int q = MIO_MXDIGITTONUM (*(p + 1)); + if (q >= 0) + { + int w = MIO_MXDIGITTONUM (*(p + 2)); + if (w >= 0) + { + /* we don't care if it contains a null character */ + *out++ = ((q << 4) + w); + p += 3; + dec_count++; + continue; + } + } + } + + *out++ = *p++; + } + + *out = '\0'; + if (ndecs) *ndecs = dec_count; + return out - buf; +} + +#define IS_UNRESERVED(c) \ + (((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= 'a' && (c) <= 'z') || \ + ((c) >= '0' && (c) <= '9') || \ + (c) == '-' || (c) == '_' || \ + (c) == '.' || (c) == '~') + +#define TO_HEX(v) ("0123456789ABCDEF"[(v) & 15]) + +mio_size_t mio_perenchttpstr (int opt, const mio_mchar_t* str, mio_mchar_t* buf, mio_size_t* nencs) +{ + const mio_mchar_t* p = str; + mio_mchar_t* out = buf; + mio_size_t enc_count = 0; + + /* this function doesn't accept the size of the buffer. the caller must + * ensure that the buffer is large enough */ + + if (opt & MIO_PERENCHTTPSTR_KEEP_SLASH) + { + while (*p != '\0') + { + if (IS_UNRESERVED(*p) || *p == '/') *out++ = *p; + else + { + *out++ = '%'; + *out++ = TO_HEX (*p >> 4); + *out++ = TO_HEX (*p & 15); + enc_count++; + } + p++; + } + } + else + { + while (*p != '\0') + { + if (IS_UNRESERVED(*p)) *out++ = *p; + else + { + *out++ = '%'; + *out++ = TO_HEX (*p >> 4); + *out++ = TO_HEX (*p & 15); + enc_count++; + } + p++; + } + } + *out = '\0'; + if (nencs) *nencs = enc_count; + return out - buf; +} + +mio_mchar_t* mio_perenchttpstrdup (int opt, const mio_mchar_t* str, mio_mmgr_t* mmgr) +{ + mio_mchar_t* buf; + mio_size_t len = 0; + mio_size_t count = 0; + + /* count the number of characters that should be encoded */ + if (opt & MIO_PERENCHTTPSTR_KEEP_SLASH) + { + for (len = 0; str[len] != '\0'; len++) + { + if (!IS_UNRESERVED(str[len]) && str[len] != '/') count++; + } + } + else + { + for (len = 0; str[len] != '\0'; len++) + { + if (!IS_UNRESERVED(str[len])) count++; + } + } + + /* if there are no characters to escape, just return the original string */ + if (count <= 0) return (mio_mchar_t*)str; + + /* allocate a buffer of an optimal size for escaping, otherwise */ + buf = MIO_MMGR_ALLOC (mmgr, (len + (count * 2) + 1) * MIO_SIZEOF(*buf)); + if (buf == MIO_NULL) return MIO_NULL; + + /* perform actual escaping */ + mio_perenchttpstr (opt, str, buf, MIO_NULL); + + return buf; +} diff --git a/mio/lib/mio-htrd.h b/mio/lib/mio-htrd.h new file mode 100644 index 0000000..43f514c --- /dev/null +++ b/mio/lib/mio-htrd.h @@ -0,0 +1,222 @@ +/* + Copyright (c) 2016-2020 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. + */ + +#ifndef _MIO_HTRD_H_ +#define _MIO_HTRD_H_ + +#include +#include + +typedef struct mio_htrd_t mio_htrd_t; + +enum mio_htrd_errnum_t +{ + MIO_HTRD_ENOERR, + MIO_HTRD_EOTHER, + MIO_HTRD_ENOIMPL, + MIO_HTRD_ESYSERR, + MIO_HTRD_EINTERN, + + MIO_HTRD_ENOMEM, + MIO_HTRD_EBADRE, + MIO_HTRD_EBADHDR, + MIO_HTRD_ERECBS, + MIO_HTRD_ECONCB, + MIO_HTRD_ESUSPENDED +}; + +typedef enum mio_htrd_errnum_t mio_htrd_errnum_t; + +/** + * The mio_htrd_option_t type defines various options to + * change the behavior of the mio_htrd_t reader. + */ +enum mio_htrd_option_t +{ + MIO_HTRD_SKIPEMPTYLINES = (1 << 0), /**< skip leading empty lines before the initial line */ + MIO_HTRD_SKIPINITIALLINE = (1 << 1), /**< skip processing an initial line */ + MIO_HTRD_CANONQPATH = (1 << 2), /**< canonicalize the query path */ + MIO_HTRD_PEEKONLY = (1 << 3), /**< trigger a peek callback after headers without processing contents */ + MIO_HTRD_REQUEST = (1 << 4), /**< parse input as a request */ + MIO_HTRD_RESPONSE = (1 << 5), /**< parse input as a response */ + MIO_HTRD_TRAILERS = (1 << 6), /**< store trailers in a separate table */ + MIO_HTRD_STRICT = (1 << 7) /**< be more picky */ +}; + +typedef enum mio_htrd_option_t mio_htrd_option_t; + +typedef struct mio_htrd_recbs_t mio_htrd_recbs_t; + +struct mio_htrd_recbs_t +{ + int (*peek) (mio_htrd_t* htrd, mio_htre_t* re); + int (*poke) (mio_htrd_t* htrd, mio_htre_t* re); +}; + +struct mio_htrd_t +{ + mio_mmgr_t* mmgr; + mio_htrd_errnum_t errnum; + int option; + int flags; + + const mio_htrd_recbs_t* recbs; + + struct + { + struct + { + int flags; + + int crlf; /* crlf status */ + mio_size_t plen; /* raw request length excluding crlf */ + mio_size_t need; /* number of octets needed for contents */ + + struct + { + mio_size_t len; + mio_size_t count; + int phase; + } chunk; + } s; /* state */ + + /* buffers needed for processing a request */ + struct + { + mio_htob_t raw; /* buffer to hold raw octets */ + mio_htob_t tra; /* buffer for handling trailers */ + } b; + } fed; + + mio_htre_t re; + int clean; +}; + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * The mio_htrd_open() function creates a htrd processor. + */ +MIO_EXPORT mio_htrd_t* mio_htrd_open ( + mio_mmgr_t* mmgr, /**< memory manager */ + mio_size_t xtnsize /**< extension size in bytes */ +); + +/** + * The mio_htrd_close() function destroys a htrd processor. + */ +MIO_EXPORT void mio_htrd_close ( + mio_htrd_t* htrd +); + +MIO_EXPORT int mio_htrd_init ( + mio_htrd_t* htrd, + mio_mmgr_t* mmgr +); + +MIO_EXPORT void mio_htrd_fini ( + mio_htrd_t* htrd +); + +MIO_EXPORT mio_mmgr_t* mio_htrd_getmmgr ( + mio_htrd_t* htrd +); + +MIO_EXPORT void* mio_htrd_getxtn ( + mio_htrd_t* htrd +); + +MIO_EXPORT mio_htrd_errnum_t mio_htrd_geterrnum ( + mio_htrd_t* htrd +); + +MIO_EXPORT void mio_htrd_clear ( + mio_htrd_t* htrd +); + +MIO_EXPORT int mio_htrd_getopt ( + mio_htrd_t* htrd +); + +MIO_EXPORT void mio_htrd_setopt ( + mio_htrd_t* htrd, + int opts +); + +MIO_EXPORT const mio_htrd_recbs_t* mio_htrd_getrecbs ( + mio_htrd_t* htrd +); + +MIO_EXPORT void mio_htrd_setrecbs ( + mio_htrd_t* htrd, + const mio_htrd_recbs_t* recbs +); + +/** + * The mio_htrd_feed() function accepts htrd request octets and invokes a + * callback function if it has processed a proper htrd request. + */ +MIO_EXPORT int mio_htrd_feed ( + mio_htrd_t* htrd, /**< htrd */ + const mio_mchar_t* req, /**< request octets */ + mio_size_t len /**< number of octets */ +); + +/** + * The mio_htrd_halt() function indicates the end of feeeding + * if the current response should be processed until the + * connection is closed. + */ +MIO_EXPORT int mio_htrd_halt ( + mio_htrd_t* htrd +); + +MIO_EXPORT void mio_htrd_suspend ( + mio_htrd_t* htrd +); + +MIO_EXPORT void mio_htrd_resume ( + mio_htrd_t* htrd +); + +MIO_EXPORT void mio_htrd_dummify ( + mio_htrd_t* htrd +); + +MIO_EXPORT void mio_htrd_undummify ( + mio_htrd_t* htrd +); + +MIO_EXPORT int mio_htrd_scanqparam ( + mio_htrd_t* http, + const mio_mcstr_t* cstr +); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/mio/lib/mio-htre.h b/mio/lib/mio-htre.h new file mode 100644 index 0000000..9d04d6d --- /dev/null +++ b/mio/lib/mio-htre.h @@ -0,0 +1,250 @@ +/* + * $Id$ + * + Copyright (c) 2016-2020 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. + */ + +#ifndef _MIO_HTRE_H_ +#define _MIO_HTRE_H_ + +#include +#include + +/* + * You should not manipulate an object of the #mio_htre_t + * type directly since it's complex. Use #mio_htrd_t to + * create an object of the mio_htre_t type. + */ + +/* header and contents of request/response */ +typedef struct mio_htre_t mio_htre_t; +typedef struct mio_htre_hdrval_t mio_htre_hdrval_t; + +enum mio_htre_state_t +{ + MIO_HTRE_DISCARDED = (1 << 0), /** content has been discarded */ + MIO_HTRE_COMPLETED = (1 << 1) /** complete content has been seen */ +}; +typedef enum mio_htre_state_t mio_htre_state_t; + +typedef int (*mio_htre_concb_t) ( + mio_htre_t* re, + const mio_mchar_t* ptr, + mio_size_t len, + void* ctx +); + +struct mio_htre_hdrval_t +{ + const mio_mchar_t* ptr; + mio_size_t len; + mio_htre_hdrval_t* next; +}; + +struct mio_htre_t +{ + mio_mmgr_t* mmgr; + + enum + { + MIO_HTRE_Q, + MIO_HTRE_S + } type; + + /* version */ + mio_http_version_t version; + const mio_mchar_t* verstr; /* version string include HTTP/ */ + + union + { + struct + { + struct + { + mio_http_method_t type; + const mio_mchar_t* name; + } method; + mio_mcstr_t path; + mio_mcstr_t param; + } q; + struct + { + struct + { + int val; + mio_mchar_t* str; + } code; + mio_mchar_t* mesg; + } s; + } u; + +#define MIO_HTRE_ATTR_CHUNKED (1 << 0) +#define MIO_HTRE_ATTR_LENGTH (1 << 1) +#define MIO_HTRE_ATTR_KEEPALIVE (1 << 2) +#define MIO_HTRE_ATTR_EXPECT (1 << 3) +#define MIO_HTRE_ATTR_EXPECT100 (1 << 4) +#define MIO_HTRE_ATTR_PROXIED (1 << 5) +#define MIO_HTRE_QPATH_PERDEC (1 << 6) /* the qpath has been percent-decoded */ + int flags; + + /* original query path for a request. + * meaningful if MIO_HTRE_QPATH_PERDEC is set in the flags */ + struct + { + mio_mchar_t* buf; /* buffer pointer */ + mio_size_t capa; /* buffer capacity */ + + mio_mchar_t* ptr; + mio_size_t len; + } orgqpath; + + /* special attributes derived from the header */ + struct + { + mio_size_t content_length; + const mio_mchar_t* status; /* for cgi */ + } attr; + + /* header table */ + mio_htb_t hdrtab; + mio_htb_t trailers; + + /* content octets */ + mio_mbs_t content; + + /* content callback */ + mio_htre_concb_t concb; + void* concb_ctx; + + /* bitwise-ORed of mio_htre_state_t */ + int state; +}; + +#define mio_htre_getversion(re) (&((re)->version)) +#define mio_htre_getmajorversion(re) ((re)->version.major) +#define mio_htre_getminorversion(re) ((re)->version.minor) +#define mio_htre_getverstr(re) ((re)->verstr) + +#define mio_htre_getqmethodtype(re) ((re)->u.q.method.type) +#define mio_htre_getqmethodname(re) ((re)->u.q.method.name) + +#define mio_htre_getqpath(re) ((re)->u.q.path.ptr) +#define mio_htre_getqparam(re) ((re)->u.q.param.ptr) +#define mio_htre_getorgqpath(re) ((re)->orgqpath.ptr) + +#define mio_htre_getscodeval(re) ((re)->u.s.code.val) +#define mio_htre_getscodestr(re) ((re)->u.s.code.str) +#define mio_htre_getsmesg(re) ((re)->u.s.mesg) + +#define mio_htre_getcontent(re) (&(re)->content) +#define mio_htre_getcontentxstr(re) MIO_MBS_XSTR(&(re)->content) +#define mio_htre_getcontentcstr(re) MIO_MBS_CSTR(&(re)->content) +#define mio_htre_getcontentptr(re) MIO_MBS_PTR(&(re)->content) +#define mio_htre_getcontentlen(re) MIO_MBS_LEN(&(re)->content) + +typedef int (*mio_htre_header_walker_t) ( + mio_htre_t* re, + const mio_mchar_t* key, + const mio_htre_hdrval_t* val, + void* ctx +); + +#if defined(__cplusplus) +extern "C" { +#endif + +MIO_EXPORT int mio_htre_init ( + mio_htre_t* re, + mio_mmgr_t* mmgr +); + +MIO_EXPORT void mio_htre_fini ( + mio_htre_t* re +); + +MIO_EXPORT void mio_htre_clear ( + mio_htre_t* re +); + +MIO_EXPORT const mio_htre_hdrval_t* mio_htre_getheaderval ( + const mio_htre_t* re, + const mio_mchar_t* key +); + +MIO_EXPORT const mio_htre_hdrval_t* mio_htre_gettrailerval ( + const mio_htre_t* re, + const mio_mchar_t* key +); + +MIO_EXPORT int mio_htre_walkheaders ( + mio_htre_t* re, + mio_htre_header_walker_t walker, + void* ctx +); + +MIO_EXPORT int mio_htre_walktrailers ( + mio_htre_t* re, + mio_htre_header_walker_t walker, + void* ctx +); + +/** + * The mio_htre_addcontent() function adds a content semgnet pointed to by + * @a ptr of @a len bytes to the content buffer. If @a re is already completed + * or discarded, this function returns 0 without adding the segment to the + * content buffer. + * @return 1 on success, -1 on failure, 0 if adding is skipped. + */ +MIO_EXPORT int mio_htre_addcontent ( + mio_htre_t* re, + const mio_mchar_t* ptr, + mio_size_t len +); + +MIO_EXPORT void mio_htre_completecontent ( + mio_htre_t* re +); + +MIO_EXPORT void mio_htre_discardcontent ( + mio_htre_t* re +); + +MIO_EXPORT void mio_htre_unsetconcb ( + mio_htre_t* re +); + +MIO_EXPORT void mio_htre_setconcb ( + mio_htre_t* re, + mio_htre_concb_t concb, + void* ctx +); + +MIO_EXPORT int mio_htre_perdecqpath ( + mio_htre_t* req +); + +#if defined(__cplusplus) +} +#endif + +#endif diff --git a/mio/lib/mio-http.h b/mio/lib/mio-http.h new file mode 100644 index 0000000..89b12aa --- /dev/null +++ b/mio/lib/mio-http.h @@ -0,0 +1,249 @@ +/* + Copyright (c) 2016-2020 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. + */ + +#ifndef _MIO_HTTP_H_ +#define _MIO_HTTP_H_ + +#include + +/** \file + * This file provides basic data types and functions for the http protocol. + */ + +/* octet buffer */ +typedef mio_mbs_t mio_htob_t; + +/* octet string */ +typedef mio_mcstr_t mio_htos_t; + +/** + * The mio_http_version_t type defines http version. + */ +struct mio_http_version_t +{ + short major; /**< major version */ + short minor; /**< minor version */ +}; + +typedef struct mio_http_version_t mio_http_version_t; + +/** + * The mio_http_method_t type defines http methods . + */ +enum mio_http_method_t +{ + MIO_HTTP_OTHER, + + /* rfc 2616 */ + MIO_HTTP_HEAD, + MIO_HTTP_GET, + MIO_HTTP_POST, + MIO_HTTP_PUT, + MIO_HTTP_DELETE, + MIO_HTTP_OPTIONS, + MIO_HTTP_TRACE, + MIO_HTTP_CONNECT + +#if 0 + /* rfc 2518 */ + MIO_HTTP_PROPFIND, + MIO_HTTP_PROPPATCH, + MIO_HTTP_MKCOL, + MIO_HTTP_COPY, + MIO_HTTP_MOVE, + MIO_HTTP_LOCK, + MIO_HTTP_UNLOCK, + + /* rfc 3253 */ + MIO_HTTP_VERSION_CONTROL, + MIO_HTTP_REPORT, + MIO_HTTP_CHECKOUT, + MIO_HTTP_CHECKIN, + MIO_HTTP_UNCHECKOUT, + MIO_HTTP_MKWORKSPACE, + MIO_HTTP_UPDATE, + MIO_HTTP_LABEL, + MIO_HTTP_MERGE, + MIO_HTTP_BASELINE_CONTROL, + MIO_HTTP_MKACTIVITY, + + /* microsoft */ + MIO_HTTP_BPROPFIND, + MIO_HTTP_BPROPPATCH, + MIO_HTTP_BCOPY, + MIO_HTTP_BDELETE, + MIO_HTTP_BMOVE, + MIO_HTTP_NOTIFY, + MIO_HTTP_POLL, + MIO_HTTP_SUBSCRIBE, + MIO_HTTP_UNSUBSCRIBE, +#endif +}; + +typedef enum mio_http_method_t mio_http_method_t; + +/** + * The #mio_http_range_int_t type defines an integer that can represent + * a range offset. Depening on the size of #mio_foff_t, it is defined to + * either #mio_foff_t or #mio_ulong_t. + */ +#if defined(MIO_SIZEOF_FOFF_T) && defined(MIO_SIZEOF_UINTMAX_T) && (MIO_SIZEOF_FOFF_T > MIO_SIZEOF_UINTMAX_T) +typedef mio_foff_t mio_http_range_int_t; +#else +typedef mio_uintmax_t mio_http_range_int_t; +#endif + +enum mio_http_range_type_t +{ + MIO_HTTP_RANGE_NONE, + MIO_HTTP_RANGE_PROPER, + MIO_HTTP_RANGE_SUFFIX +}; +typedef enum mio_http_range_type_t mio_http_range_type_t; +/** + * The mio_http_range_t type defines a structure that can represent + * a value for the \b Range: http header. + * + * If type is #MIO_HTTP_RANGE_NONE, this range is not valid. + * + * If type is #MIO_HTTP_RANGE_SUFFIX, 'from' is meaningleass and 'to' indicates + * the number of bytes from the back. + * - -500 => last 500 bytes + * + * You should adjust a range when the size that this range belongs to is + * made known. See this code: + * \code + * range.from = total_size - range.to; + * range.to = range.to + range.from - 1; + * \endcode + * + * If type is #MIO_HTTP_RANGE_PROPER, 'from' and 'to' represents a proper range + * where the value of 0 indicates the first byte. This doesn't require any + * adjustment. + * - 0-999 => first 1000 bytes + * - 99- => from the 100th bytes to the end. + */ +struct mio_http_range_t +{ + mio_http_range_type_t type; /**< type indicator */ + mio_http_range_int_t from; /**< starting offset */ + mio_http_range_int_t to; /**< ending offset */ +}; +typedef struct mio_http_range_t mio_http_range_t; + + +enum mio_perenchttpstr_opt_t +{ + MIO_PERENCHTTPSTR_KEEP_SLASH = (1 << 0) +}; +typedef enum mio_perenchttpstr_opt_t mio_perenchttpstr_opt_t; + +#if defined(__cplusplus) +extern "C" { +#endif + +MIO_EXPORT int mio_comparehttpversions ( + const mio_http_version_t* v1, + const mio_http_version_t* v2 +); + +MIO_EXPORT const mio_bch_t* mio_httpstatustombs ( + int code +); + +MIO_EXPORT const mio_bch_t* mio_httpmethodtombs ( + mio_http_method_t type +); + +MIO_EXPORT mio_http_method_t mio_mbstohttpmethod ( + const mio_bch_t* name +); + +MIO_EXPORT mio_http_method_t mio_mcstrtohttpmethod ( + const mio_mcstr_t* name +); + +MIO_EXPORT int mio_parsehttprange ( + const mio_bch_t* str, + mio_http_range_t* range +); + +MIO_EXPORT int mio_parsehttptime ( + const mio_bch_t* str, + mio_ntime_t* nt +); + +MIO_EXPORT mio_bch_t* mio_fmthttptime ( + const mio_ntime_t* nt, + mio_bch_t* buf, + mio_oow_t bufsz +); + +/** + * The mio_isperencedhttpstr() function determines if the given string + * contains a valid percent-encoded sequence. + */ +MIO_EXPORT int mio_isperencedhttpstr ( + const mio_bch_t* str +); + +/** + * The mio_perdechttpstr() function performs percent-decoding over a string. + * The caller must ensure that the output buffer \a buf is large enough. + * If \a ndecs is not #MIO_NULL, it is set to the number of characters + * decoded. 0 means no characters in the input string required decoding + * \return the length of the output string. + */ +MIO_EXPORT mio_oow_t mio_perdechttpstr ( + const mio_bch_t* str, + mio_bch_t* buf, + mio_oow_t* ndecs +); + +/** + * The mio_perenchttpstr() function performs percent-encoding over a string. + * The caller must ensure that the output buffer \a buf is large enough. + * If \a nencs is not #MIO_NULL, it is set to the number of characters + * encoded. 0 means no characters in the input string required encoding. + * \return the length of the output string. + */ +MIO_EXPORT mio_oow_t mio_perenchttpstr ( + int opt, /**< 0 or bitwise-OR'ed of #mio_perenchttpstr_opt_t */ + const mio_bch_t* str, + mio_bch_t* buf, + mio_oow_t* nencs +); + +MIO_EXPORT mio_bch_t* mio_perenchttpstrdup ( + int opt, /**< 0 or bitwise-OR'ed of #mio_perenchttpstr_opt_t */ + const mio_bch_t* str, + mio_mmgr_t* mmgr +); + +#if defined(__cplusplus) +} +#endif + + +#endif diff --git a/mio/lib/mio-sck.h b/mio/lib/mio-sck.h index d27ebae..c078155 100644 --- a/mio/lib/mio-sck.h +++ b/mio/lib/mio-sck.h @@ -194,8 +194,8 @@ typedef struct mio_icmphdr_t mio_icmphdr_t; # define MIO_SCKHND_INVALID (INVALID_SOCKET) */ - typedef mio_uintptr_t qse_sckhnd_t; -# define MIO_SCKHND_INVALID (~(qse_sck_hnd_t)0) + typedef mio_uintptr_t mio_sckhnd_t; +# define MIO_SCKHND_INVALID (~(mio_sck_hnd_t)0) #else typedef int mio_sckhnd_t; diff --git a/mio/lib/mio.h b/mio/lib/mio.h index d6cf363..d8c084a 100644 --- a/mio/lib/mio.h +++ b/mio/lib/mio.h @@ -27,11 +27,11 @@ #ifndef _MIO_H_ #define _MIO_H_ -#include "mio-cmn.h" +#include #include #if defined(_WIN32) - typedef mio_uintptr_t qse_syshnd_t; + typedef mio_uintptr_t mio_syshnd_t; #define MIO_SYSHND_INVALID (~(mio_uintptr_t)0) #else typedef int mio_syshnd_t;