mirror of
https://github.com/cesanta/mongoose.git
synced 2026-10-14 10:20:01 +00:00
424 lines
11 KiB
C
424 lines
11 KiB
C
#include "internal.h"
|
|
|
|
static void mg_strlcpy(register char *dst, register const char *src, size_t n) {
|
|
for (; *src != '\0' && n > 1; n--) {
|
|
*dst++ = *src++;
|
|
}
|
|
*dst = '\0';
|
|
}
|
|
|
|
static int lowercase(const char *s) {
|
|
return tolower(* (const unsigned char *) s);
|
|
}
|
|
|
|
static int mg_strncasecmp(const char *s1, const char *s2, size_t len) {
|
|
int diff = 0;
|
|
|
|
if (len > 0)
|
|
do {
|
|
diff = lowercase(s1++) - lowercase(s2++);
|
|
} while (diff == 0 && s1[-1] != '\0' && --len > 0);
|
|
|
|
return diff;
|
|
}
|
|
|
|
static int mg_strcasecmp(const char *s1, const char *s2) {
|
|
int diff;
|
|
|
|
do {
|
|
diff = lowercase(s1++) - lowercase(s2++);
|
|
} while (diff == 0 && s1[-1] != '\0');
|
|
|
|
return diff;
|
|
}
|
|
|
|
static char * mg_strndup(const char *ptr, size_t len) {
|
|
char *p;
|
|
|
|
if ((p = (char *) malloc(len + 1)) != NULL) {
|
|
mg_strlcpy(p, ptr, len + 1);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
static char * mg_strdup(const char *str) {
|
|
return mg_strndup(str, strlen(str));
|
|
}
|
|
|
|
static const char *mg_strcasestr(const char *big_str, const char *small_str) {
|
|
int i, big_len = strlen(big_str), small_len = strlen(small_str);
|
|
|
|
for (i = 0; i <= big_len - small_len; i++) {
|
|
if (mg_strncasecmp(big_str + i, small_str, small_len) == 0) {
|
|
return big_str + i;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Like snprintf(), but never returns negative value, or a value
|
|
// that is larger than a supplied buffer.
|
|
// Thanks to Adam Zeldis to pointing snprintf()-caused vulnerability
|
|
// in his audit report.
|
|
static int mg_vsnprintf(char *buf, size_t buflen, const char *fmt, va_list ap) {
|
|
int n;
|
|
|
|
if (buflen == 0) {
|
|
return 0;
|
|
}
|
|
|
|
n = vsnprintf(buf, buflen, fmt, ap);
|
|
|
|
if (n < 0) {
|
|
n = 0;
|
|
} else if (n >= (int) buflen) {
|
|
n = (int) buflen - 1;
|
|
}
|
|
buf[n] = '\0';
|
|
|
|
return n;
|
|
}
|
|
|
|
static int mg_snprintf(char *buf, size_t buflen,
|
|
PRINTF_FORMAT_STRING(const char *fmt), ...) PRINTF_ARGS(3, 4);
|
|
|
|
static int mg_snprintf(char *buf, size_t buflen, const char *fmt, ...) {
|
|
va_list ap;
|
|
int n;
|
|
|
|
va_start(ap, fmt);
|
|
n = mg_vsnprintf(buf, buflen, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return n;
|
|
}
|
|
|
|
// Skip the characters until one of the delimiters characters found.
|
|
// 0-terminate resulting word. Skip the delimiter and following whitespaces.
|
|
// Advance pointer to buffer to the next word. Return found 0-terminated word.
|
|
// Delimiters can be quoted with quotechar.
|
|
static char *skip_quoted(char **buf, const char *delimiters,
|
|
const char *whitespace, char quotechar) {
|
|
char *p, *begin_word, *end_word, *end_whitespace;
|
|
|
|
begin_word = *buf;
|
|
end_word = begin_word + strcspn(begin_word, delimiters);
|
|
|
|
// Check for quotechar
|
|
if (end_word > begin_word) {
|
|
p = end_word - 1;
|
|
while (*p == quotechar) {
|
|
// If there is anything beyond end_word, copy it
|
|
if (*end_word == '\0') {
|
|
*p = '\0';
|
|
break;
|
|
} else {
|
|
size_t end_off = strcspn(end_word + 1, delimiters);
|
|
memmove (p, end_word, end_off + 1);
|
|
p += end_off; // p must correspond to end_word - 1
|
|
end_word += end_off + 1;
|
|
}
|
|
}
|
|
for (p++; p < end_word; p++) {
|
|
*p = '\0';
|
|
}
|
|
}
|
|
|
|
if (*end_word == '\0') {
|
|
*buf = end_word;
|
|
} else {
|
|
end_whitespace = end_word + 1 + strspn(end_word + 1, whitespace);
|
|
|
|
for (p = end_word; p < end_whitespace; p++) {
|
|
*p = '\0';
|
|
}
|
|
|
|
*buf = end_whitespace;
|
|
}
|
|
|
|
return begin_word;
|
|
}
|
|
|
|
// Simplified version of skip_quoted without quote char
|
|
// and whitespace == delimiters
|
|
static char *skip(char **buf, const char *delimiters) {
|
|
return skip_quoted(buf, delimiters, delimiters, 0);
|
|
}
|
|
|
|
|
|
// Return HTTP header value, or NULL if not found.
|
|
static const char *get_header(const struct mg_request_info *ri,
|
|
const char *name) {
|
|
int i;
|
|
|
|
for (i = 0; i < ri->num_headers; i++)
|
|
if (!mg_strcasecmp(name, ri->http_headers[i].name))
|
|
return ri->http_headers[i].value;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
const char *mg_get_header(const struct mg_connection *conn, const char *name) {
|
|
return get_header(&conn->request_info, name);
|
|
}
|
|
|
|
// A helper function for traversing a comma separated list of values.
|
|
// It returns a list pointer shifted to the next value, or NULL if the end
|
|
// of the list found.
|
|
// Value is stored in val vector. If value has form "x=y", then eq_val
|
|
// vector is initialized to point to the "y" part, and val vector length
|
|
// is adjusted to point only to "x".
|
|
static const char *next_option(const char *list, struct vec *val,
|
|
struct vec *eq_val) {
|
|
if (list == NULL || *list == '\0') {
|
|
// End of the list
|
|
list = NULL;
|
|
} else {
|
|
val->ptr = list;
|
|
if ((list = strchr(val->ptr, ',')) != NULL) {
|
|
// Comma found. Store length and shift the list ptr
|
|
val->len = list - val->ptr;
|
|
list++;
|
|
} else {
|
|
// This value is the last one
|
|
list = val->ptr + strlen(val->ptr);
|
|
val->len = list - val->ptr;
|
|
}
|
|
|
|
if (eq_val != NULL) {
|
|
// Value has form "x=y", adjust pointers and lengths
|
|
// so that val points to "x", and eq_val points to "y".
|
|
eq_val->len = 0;
|
|
eq_val->ptr = (const char *) memchr(val->ptr, '=', val->len);
|
|
if (eq_val->ptr != NULL) {
|
|
eq_val->ptr++; // Skip over '=' character
|
|
eq_val->len = val->ptr + val->len - eq_val->ptr;
|
|
val->len = (eq_val->ptr - val->ptr) - 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
// Perform case-insensitive match of string against pattern
|
|
static int match_prefix(const char *pattern, int pattern_len, const char *str) {
|
|
const char *or_str;
|
|
int i, j, len, res;
|
|
|
|
if ((or_str = (const char *) memchr(pattern, '|', pattern_len)) != NULL) {
|
|
res = match_prefix(pattern, or_str - pattern, str);
|
|
return res > 0 ? res :
|
|
match_prefix(or_str + 1, (pattern + pattern_len) - (or_str + 1), str);
|
|
}
|
|
|
|
i = j = 0;
|
|
res = -1;
|
|
for (; i < pattern_len; i++, j++) {
|
|
if (pattern[i] == '?' && str[j] != '\0') {
|
|
continue;
|
|
} else if (pattern[i] == '$') {
|
|
return str[j] == '\0' ? j : -1;
|
|
} else if (pattern[i] == '*') {
|
|
i++;
|
|
if (pattern[i] == '*') {
|
|
i++;
|
|
len = (int) strlen(str + j);
|
|
} else {
|
|
len = (int) strcspn(str + j, "/");
|
|
}
|
|
if (i == pattern_len) {
|
|
return j + len;
|
|
}
|
|
do {
|
|
res = match_prefix(pattern + i, pattern_len - i, str + j + len);
|
|
} while (res == -1 && len-- > 0);
|
|
return res == -1 ? -1 : j + res + len;
|
|
} else if (lowercase(&pattern[i]) != lowercase(&str[j])) {
|
|
return -1;
|
|
}
|
|
}
|
|
return j;
|
|
}
|
|
|
|
// Protect against directory disclosure attack by removing '..',
|
|
// excessive '/' and '\' characters
|
|
static void remove_double_dots_and_double_slashes(char *s) {
|
|
char *p = s;
|
|
|
|
while (*s != '\0') {
|
|
*p++ = *s++;
|
|
if (s[-1] == '/' || s[-1] == '\\') {
|
|
// Skip all following slashes, backslashes and double-dots
|
|
while (s[0] != '\0') {
|
|
if (s[0] == '/' || s[0] == '\\') {
|
|
s++;
|
|
} else if (s[0] == '.' && s[1] == '.') {
|
|
s += 2;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
*p = '\0';
|
|
}
|
|
|
|
void mg_url_encode(const char *src, char *dst, size_t dst_len) {
|
|
static const char *dont_escape = "._-$,;~()";
|
|
static const char *hex = "0123456789abcdef";
|
|
const char *end = dst + dst_len - 1;
|
|
|
|
for (; *src != '\0' && dst < end; src++, dst++) {
|
|
if (isalnum(*(const unsigned char *) src) ||
|
|
strchr(dont_escape, * (const unsigned char *) src) != NULL) {
|
|
*dst = *src;
|
|
} else if (dst + 2 < end) {
|
|
dst[0] = '%';
|
|
dst[1] = hex[(* (const unsigned char *) src) >> 4];
|
|
dst[2] = hex[(* (const unsigned char *) src) & 0xf];
|
|
dst += 2;
|
|
}
|
|
}
|
|
|
|
*dst = '\0';
|
|
}
|
|
|
|
int mg_url_decode(const char *src, int src_len, char *dst,
|
|
int dst_len, int is_form_url_encoded) {
|
|
int i, j, a, b;
|
|
#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W')
|
|
|
|
for (i = j = 0; i < src_len && j < dst_len - 1; i++, j++) {
|
|
if (src[i] == '%' && i < src_len - 2 &&
|
|
isxdigit(* (const unsigned char *) (src + i + 1)) &&
|
|
isxdigit(* (const unsigned char *) (src + i + 2))) {
|
|
a = tolower(* (const unsigned char *) (src + i + 1));
|
|
b = tolower(* (const unsigned char *) (src + i + 2));
|
|
dst[j] = (char) ((HEXTOI(a) << 4) | HEXTOI(b));
|
|
i += 2;
|
|
} else if (is_form_url_encoded && src[i] == '+') {
|
|
dst[j] = ' ';
|
|
} else {
|
|
dst[j] = src[i];
|
|
}
|
|
}
|
|
|
|
dst[j] = '\0'; // Null-terminate the destination
|
|
|
|
return i >= src_len ? j : -1;
|
|
}
|
|
|
|
// Check whether full request is buffered. Return:
|
|
// -1 if request is malformed
|
|
// 0 if request is not yet fully buffered
|
|
// >0 actual request length, including last \r\n\r\n
|
|
static int get_request_len(const char *buf, int buf_len) {
|
|
int i;
|
|
|
|
for (i = 0; i < buf_len; i++) {
|
|
// Control characters are not allowed but >=128 is.
|
|
// Abort scan as soon as one malformed character is found;
|
|
// don't let subsequent \r\n\r\n win us over anyhow
|
|
if (!isprint(* (const unsigned char *) &buf[i]) && buf[i] != '\r' &&
|
|
buf[i] != '\n' && * (const unsigned char *) &buf[i] < 128) {
|
|
return -1;
|
|
} else if (buf[i] == '\n' && i + 1 < buf_len && buf[i + 1] == '\n') {
|
|
return i + 2;
|
|
} else if (buf[i] == '\n' && i + 2 < buf_len && buf[i + 1] == '\r' &&
|
|
buf[i + 2] == '\n') {
|
|
return i + 3;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mg_get_cookie(const char *cookie_header, const char *var_name,
|
|
char *dst, size_t dst_size) {
|
|
const char *s, *p, *end;
|
|
int name_len, len = -1;
|
|
|
|
if (dst == NULL || dst_size == 0) {
|
|
len = -2;
|
|
} else if (var_name == NULL || (s = cookie_header) == NULL) {
|
|
len = -1;
|
|
dst[0] = '\0';
|
|
} else {
|
|
name_len = (int) strlen(var_name);
|
|
end = s + strlen(s);
|
|
dst[0] = '\0';
|
|
|
|
for (; (s = mg_strcasestr(s, var_name)) != NULL; s += name_len) {
|
|
if (s[name_len] == '=') {
|
|
s += name_len + 1;
|
|
if ((p = strchr(s, ' ')) == NULL)
|
|
p = end;
|
|
if (p[-1] == ';')
|
|
p--;
|
|
if (*s == '"' && p[-1] == '"' && p > s + 1) {
|
|
s++;
|
|
p--;
|
|
}
|
|
if ((size_t) (p - s) < dst_size) {
|
|
len = p - s;
|
|
mg_strlcpy(dst, s, (size_t) len + 1);
|
|
} else {
|
|
len = -3;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return len;
|
|
}
|
|
|
|
int mg_get_var(const char *data, size_t data_len, const char *name,
|
|
char *dst, size_t dst_len) {
|
|
const char *p, *e, *s;
|
|
size_t name_len;
|
|
int len;
|
|
|
|
if (dst == NULL || dst_len == 0) {
|
|
len = -2;
|
|
} else if (data == NULL || name == NULL || data_len == 0) {
|
|
len = -1;
|
|
dst[0] = '\0';
|
|
} else {
|
|
name_len = strlen(name);
|
|
e = data + data_len;
|
|
len = -1;
|
|
dst[0] = '\0';
|
|
|
|
// data is "var1=val1&var2=val2...". Find variable first
|
|
for (p = data; p + name_len < e; p++) {
|
|
if ((p == data || p[-1] == '&') && p[name_len] == '=' &&
|
|
!mg_strncasecmp(name, p, name_len)) {
|
|
|
|
// Point p to variable value
|
|
p += name_len + 1;
|
|
|
|
// Point s to the end of the value
|
|
s = (const char *) memchr(p, '&', (size_t)(e - p));
|
|
if (s == NULL) {
|
|
s = e;
|
|
}
|
|
assert(s >= p);
|
|
|
|
// Decode variable into destination buffer
|
|
len = mg_url_decode(p, (size_t)(s - p), dst, dst_len, 1);
|
|
|
|
// Redirect error code from -1 to -2 (destination buffer too small).
|
|
if (len == -1) {
|
|
len = -2;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return len;
|
|
}
|