hio/mio/lib/http.c

646 lines
15 KiB
C
Raw Normal View History

2020-03-03 13:47:51 +00:00
/*
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.
*/
2020-05-25 08:04:30 +00:00
#include <mio-http.h>
#include <mio-chr.h>
#include <mio-utl.h>
2020-03-03 13:47:51 +00:00
#include "mio-prv.h"
2020-05-01 14:00:27 +00:00
#include <time.h>
2020-03-03 13:47:51 +00:00
2020-05-01 14:00:27 +00:00
int mio_comp_http_versions (const mio_http_version_t* v1, const mio_http_version_t* v2)
2020-03-03 13:47:51 +00:00
{
if (v1->major == v2->major) return v1->minor - v2->minor;
return v1->major - v2->major;
}
int mio_comp_http_version_numbers (const mio_http_version_t* v1, int v2_major, int v2_minor)
{
if (v1->major == v2_major) return v1->minor - v2_minor;
return v1->major - v2_major;
}
2020-05-01 14:00:27 +00:00
const mio_bch_t* mio_http_status_to_bcstr (int code)
2020-03-03 13:47:51 +00:00
{
2020-04-30 16:20:31 +00:00
const mio_bch_t* msg;
2020-03-03 13:47:51 +00:00
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;
}
2020-05-01 14:00:27 +00:00
const mio_bch_t* mio_http_method_to_bcstr (mio_http_method_t type)
2020-03-03 13:47:51 +00:00
{
/* keep this table in the same order as mio_httpd_method_t enumerators */
2020-04-30 16:20:31 +00:00
static mio_bch_t* names[] =
2020-03-03 13:47:51 +00:00
{
"OTHER",
"HEAD",
"GET",
"POST",
"PUT",
"DELETE",
"OPTIONS",
"TRACE",
"CONNECT"
};
return (type < 0 || type >= MIO_COUNTOF(names))? MIO_NULL: names[type];
}
struct mtab_t
{
2020-04-30 16:20:31 +00:00
const mio_bch_t* name;
2020-03-03 13:47:51 +00:00
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 }
};
2020-04-30 16:20:31 +00:00
mio_http_method_t mio_bcstr_to_http_method (const mio_bch_t* name)
2020-03-03 13:47:51 +00:00
{
/* 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];
2020-05-01 14:00:27 +00:00
n = mio_comp_bcstr(name, entry->name, 1);
2020-03-03 13:47:51 +00:00
if (n < 0)
{
2020-04-30 16:20:31 +00:00
/* if left, right, mid were of mio_oow_t,
2020-03-03 13:47:51 +00:00
* 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;
}
2020-04-30 16:20:31 +00:00
mio_http_method_t mio_bchars_to_http_method (const mio_bch_t* nameptr, mio_oow_t namelen)
2020-03-03 13:47:51 +00:00
{
/* 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];
2020-05-01 14:00:27 +00:00
n = mio_comp_bchars_bcstr(nameptr, namelen, entry->name, 1);
2020-03-03 13:47:51 +00:00
if (n < 0)
{
2020-04-30 16:20:31 +00:00
/* if left, right, mid were of mio_oow_t,
2020-03-03 13:47:51 +00:00
* 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;
}
2020-04-30 16:20:31 +00:00
int mio_parse_http_range_bcstr (const mio_bch_t* str, mio_http_range_t* range)
2020-03-03 13:47:51 +00:00
{
/* 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;
2020-05-01 14:00:27 +00:00
if (mio_is_bch_digit(*str))
2020-03-03 13:47:51 +00:00
{
do
{
from = from * 10 + (*str - '0');
str++;
}
2020-05-01 14:00:27 +00:00
while (mio_is_bch_digit(*str));
2020-03-03 13:47:51 +00:00
}
else type = MIO_HTTP_RANGE_SUFFIX;
if (*str != '-') return -1;
str++;
2020-05-01 14:00:27 +00:00
if (mio_is_bch_digit(*str))
2020-03-03 13:47:51 +00:00
{
to = 0;
do
{
to = to * 10 + (*str - '0');
str++;
}
2020-05-01 14:00:27 +00:00
while (mio_is_bch_digit(*str));
2020-03-03 13:47:51 +00:00
}
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
{
2020-04-30 16:20:31 +00:00
const mio_bch_t* s;
const mio_bch_t* l;
2020-03-03 13:47:51 +00:00
};
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" }
};
2020-04-30 16:20:31 +00:00
int mio_parse_http_time_bcstr (const mio_bch_t* str, mio_ntime_t* nt)
2020-03-03 13:47:51 +00:00
{
2020-05-01 14:00:27 +00:00
struct tm bt;
2020-04-30 16:20:31 +00:00
const mio_bch_t* word;
mio_oow_t wlen, i;
2020-03-03 13:47:51 +00:00
/* TODO: support more formats */
MIO_MEMSET (&bt, 0, MIO_SIZEOF(bt));
/* weekday */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
for (word = str; mio_is_bch_alpha(*str); str++);
2020-03-03 13:47:51 +00:00
wlen = str - word;
for (i = 0; i < MIO_COUNTOF(wday_name); i++)
{
2020-05-01 14:00:27 +00:00
if (mio_comp_bchars_bcstr(word, wlen, wday_name[i].s, 1) == 0)
2020-03-03 13:47:51 +00:00
{
2020-05-01 14:00:27 +00:00
bt.tm_wday = i;
2020-03-03 13:47:51 +00:00
break;
}
}
if (i >= MIO_COUNTOF(wday_name)) return -1;
/* comma - i'm just loose as i don't care if it doesn't exist */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
2020-03-03 13:47:51 +00:00
if (*str == ',') str++;
/* day */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
if (!mio_is_bch_digit(*str)) return -1;
do bt.tm_mday = bt.tm_mday * 10 + *str++ - '0'; while (mio_is_bch_digit(*str));
2020-03-03 13:47:51 +00:00
/* month */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
for (word = str; mio_is_bch_alpha(*str); str++);
2020-03-03 13:47:51 +00:00
wlen = str - word;
for (i = 0; i < MIO_COUNTOF(mon_name); i++)
{
2020-05-01 14:00:27 +00:00
if (mio_comp_bchars_bcstr(word, wlen, mon_name[i].s, 1) == 0)
2020-03-03 13:47:51 +00:00
{
2020-05-01 14:00:27 +00:00
bt.tm_mon = i;
2020-03-03 13:47:51 +00:00
break;
}
}
if (i >= MIO_COUNTOF(mon_name)) return -1;
/* year */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
if (!mio_is_bch_digit(*str)) return -1;
do bt.tm_year = bt.tm_year * 10 + *str++ - '0'; while (mio_is_bch_digit(*str));
bt.tm_year -= 1900;
2020-03-03 13:47:51 +00:00
/* hour */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
if (!mio_is_bch_digit(*str)) return -1;
do bt.tm_hour = bt.tm_hour * 10 + *str++ - '0'; while (mio_is_bch_digit(*str));
2020-03-03 13:47:51 +00:00
if (*str != ':') return -1;
str++;
/* min */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
if (!mio_is_bch_digit(*str)) return -1;
do bt.tm_min = bt.tm_min * 10 + *str++ - '0'; while (mio_is_bch_digit(*str));
2020-03-03 13:47:51 +00:00
if (*str != ':') return -1;
str++;
/* sec */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
if (!mio_is_bch_digit(*str)) return -1;
do bt.tm_sec = bt.tm_sec * 10 + *str++ - '0'; while (mio_is_bch_digit(*str));
2020-03-03 13:47:51 +00:00
/* GMT */
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
for (word = str; mio_is_bch_alpha(*str); str++);
2020-03-03 13:47:51 +00:00
wlen = str - word;
2020-05-01 14:00:27 +00:00
if (mio_comp_bchars_bcstr(word, wlen, "GMT", 1) != 0) return -1;
2020-03-03 13:47:51 +00:00
2020-05-01 14:00:27 +00:00
while (mio_is_bch_space(*str)) str++;
2020-03-03 13:47:51 +00:00
if (*str != '\0') return -1;
2020-05-01 14:00:27 +00:00
nt->sec = timegm(&bt);
nt->nsec = 0;
return 0;
2020-03-03 13:47:51 +00:00
}
2020-05-01 14:00:27 +00:00
mio_bch_t* mio_fmt_http_time_to_bcstr (const mio_ntime_t* nt, mio_bch_t* buf, mio_oow_t bufsz)
2020-03-03 13:47:51 +00:00
{
2020-05-01 14:00:27 +00:00
time_t t;
struct tm bt;
t = nt->sec;
gmtime_r (&t, &bt); /* TODO: create mio_sys_gmtime() and make it system dependent */
2020-05-01 14:00:27 +00:00
mio_fmttobcstr (MIO_NULL, buf, bufsz,
"%hs, %d %hs %d %02d:%02d:%02d GMT",
wday_name[bt.tm_wday].s,
bt.tm_mday,
mon_name[bt.tm_mon].s,
bt.tm_year + 1900,
bt.tm_hour, bt.tm_min, bt.tm_sec
2020-03-03 13:47:51 +00:00
);
return buf;
}
2020-04-30 16:20:31 +00:00
int mio_is_perenced_http_bcstr (const mio_bch_t* str)
2020-03-03 13:47:51 +00:00
{
2020-04-30 16:20:31 +00:00
const mio_bch_t* p = str;
2020-03-03 13:47:51 +00:00
while (*p != '\0')
{
if (*p == '%' && *(p + 1) != '\0' && *(p + 2) != '\0')
{
2020-05-01 18:01:29 +00:00
int q = MIO_XDIGIT_TO_NUM(*(p + 1));
2020-03-03 13:47:51 +00:00
if (q >= 0)
{
/* return true if the first valid percent-encoded sequence is found */
2020-05-01 18:01:29 +00:00
int w = MIO_XDIGIT_TO_NUM(*(p + 2));
2020-03-03 13:47:51 +00:00
if (w >= 0) return 1;
}
}
p++;
}
return 1;
}
mio_oow_t mio_perdec_http_bcstr (const mio_bch_t* str, mio_bch_t* buf, mio_oow_t* ndecs)
2020-03-03 13:47:51 +00:00
{
2020-04-30 16:20:31 +00:00
const mio_bch_t* p = str;
mio_bch_t* out = buf;
mio_oow_t dec_count = 0;
2020-03-03 13:47:51 +00:00
while (*p != '\0')
{
if (*p == '%' && *(p + 1) != '\0' && *(p + 2) != '\0')
{
2020-05-01 18:01:29 +00:00
int q = MIO_XDIGIT_TO_NUM(*(p + 1));
2020-03-03 13:47:51 +00:00
if (q >= 0)
{
2020-05-01 18:01:29 +00:00
int w = MIO_XDIGIT_TO_NUM(*(p + 2));
2020-03-03 13:47:51 +00:00
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_oow_t mio_perenc_http_bcstr (int opt, const mio_bch_t* str, mio_bch_t* buf, mio_oow_t* nencs)
2020-03-03 13:47:51 +00:00
{
2020-04-30 16:20:31 +00:00
const mio_bch_t* p = str;
mio_bch_t* out = buf;
mio_oow_t enc_count = 0;
2020-03-03 13:47:51 +00:00
/* this function doesn't accept the size of the buffer. the caller must
* ensure that the buffer is large enough */
if (opt & MIO_PERENC_HTTP_KEEP_SLASH)
2020-03-03 13:47:51 +00:00
{
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;
}
#if 0
mio_bch_t* mio_perenc_http_bcstrdup (int opt, const mio_bch_t* str, mio_mmgr_t* mmgr)
2020-03-03 13:47:51 +00:00
{
2020-04-30 16:20:31 +00:00
mio_bch_t* buf;
mio_oow_t len = 0;
mio_oow_t count = 0;
2020-03-03 13:47:51 +00:00
/* count the number of characters that should be encoded */
if (opt & MIO_PERENC_HTTP_KEEP_SLASH)
2020-03-03 13:47:51 +00:00
{
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 */
2020-04-30 16:20:31 +00:00
if (count <= 0) return (mio_bch_t*)str;
2020-03-03 13:47:51 +00:00
/* allocate a buffer of an optimal size for escaping, otherwise */
buf = MIO_MMGR_ALLOC(mmgr, (len + (count * 2) + 1) * MIO_SIZEOF(*buf));
if (!buf) return MIO_NULL;
2020-03-03 13:47:51 +00:00
/* perform actual escaping */
mio_perenc_http_bcstr (opt, str, buf, MIO_NULL);
2020-03-03 13:47:51 +00:00
return buf;
}
#endif
#if 0
int mio_scan_http_qparam (mio_htrd_t* htrd, const mio_bch_t* qparam)
{
mio_bcs_t key, val;
const mio_bch_t* p, * end;
mio_bch_t* out;
p = qparam
if (!p) return 0; /* no param string to scan */
end = p + mio_count_bcstr(qparam);
/* 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_becs_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_BECS_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 (htrd->mio, 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 (htrd->mio, val.len == 0);
}
/* set request parameter string callback before scanning */
MIO_ASSERT (htrd->mio, htrd->recbs.qparamstr != MIO_NULL);
if (htrd->recbs.qparamstr(htrd, &key, &val) <= -1) return -1;
next_octet:
if (p >= end) break;
p++;
out = MIO_BECS_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_becs_clear (&htrd->tmp.qparam);
return 0;
}
#endif