2015-03-29 01:55:10 +01:00
|
|
|
/*
|
|
|
|
* liberty-proto.c: the ultimate C unlibrary: protocols
|
|
|
|
*
|
2020-08-01 14:02:25 +02:00
|
|
|
* Copyright (c) 2014 - 2016, Přemysl Eric Janouch <p@janouch.name>
|
2015-03-29 01:55:10 +01:00
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
2018-06-21 23:57:25 +02:00
|
|
|
* purpose with or without fee is hereby granted.
|
2015-03-29 01:55:10 +01:00
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
|
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
|
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
|
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
// Mostly parsers and various utilities relating to various protocols
|
|
|
|
|
|
|
|
#ifdef LIBERTY_WANT_PROTO_IRC
|
|
|
|
|
|
|
|
// --- IRC utilities -----------------------------------------------------------
|
|
|
|
|
|
|
|
struct irc_message
|
|
|
|
{
|
|
|
|
struct str_map tags; ///< IRC 3.2 message tags
|
|
|
|
char *prefix; ///< Message prefix
|
|
|
|
char *command; ///< IRC command
|
2017-01-23 23:01:20 +01:00
|
|
|
struct strv params; ///< Command parameters
|
2015-03-29 01:55:10 +01:00
|
|
|
};
|
|
|
|
|
2015-06-11 21:00:00 +02:00
|
|
|
static char *
|
|
|
|
irc_unescape_message_tag (const char *value)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
struct str s = str_make ();
|
2015-06-11 21:00:00 +02:00
|
|
|
bool escape = false;
|
|
|
|
for (const char *p = value; *p; p++)
|
|
|
|
{
|
|
|
|
if (escape)
|
|
|
|
{
|
|
|
|
switch (*p)
|
|
|
|
{
|
|
|
|
case ':': str_append_c (&s, ';'); break;
|
|
|
|
case 's': str_append_c (&s, ' '); break;
|
|
|
|
case 'r': str_append_c (&s, '\r'); break;
|
|
|
|
case 'n': str_append_c (&s, '\n'); break;
|
|
|
|
default: str_append_c (&s, *p);
|
|
|
|
}
|
|
|
|
escape = false;
|
|
|
|
}
|
|
|
|
else if (*p == '\\')
|
|
|
|
escape = true;
|
|
|
|
else
|
|
|
|
str_append_c (&s, *p);
|
|
|
|
}
|
|
|
|
return str_steal (&s);
|
|
|
|
}
|
|
|
|
|
2015-03-29 01:55:10 +01:00
|
|
|
static void
|
|
|
|
irc_parse_message_tags (const char *tags, struct str_map *out)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
struct strv v = strv_make ();
|
2016-10-09 09:45:27 +02:00
|
|
|
cstr_split (tags, ";", true, &v);
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
for (size_t i = 0; i < v.len; i++)
|
|
|
|
{
|
|
|
|
char *key = v.vector[i], *equal_sign = strchr (key, '=');
|
|
|
|
if (equal_sign)
|
|
|
|
{
|
|
|
|
*equal_sign = '\0';
|
2015-06-11 21:00:00 +02:00
|
|
|
str_map_set (out, key, irc_unescape_message_tag (equal_sign + 1));
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
str_map_set (out, key, xstrdup (""));
|
|
|
|
}
|
2017-01-23 23:01:20 +01:00
|
|
|
strv_free (&v);
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
irc_parse_message (struct irc_message *msg, const char *line)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
msg->tags = str_map_make (free);
|
2015-03-29 01:55:10 +01:00
|
|
|
msg->prefix = NULL;
|
|
|
|
msg->command = NULL;
|
2017-06-22 18:14:49 +02:00
|
|
|
msg->params = strv_make ();
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
// IRC 3.2 message tags
|
|
|
|
if (*line == '@')
|
|
|
|
{
|
|
|
|
size_t tags_len = strcspn (++line, " ");
|
|
|
|
char *tags = xstrndup (line, tags_len);
|
|
|
|
irc_parse_message_tags (tags, &msg->tags);
|
|
|
|
free (tags);
|
|
|
|
|
|
|
|
line += tags_len;
|
|
|
|
while (*line == ' ')
|
|
|
|
line++;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prefix
|
|
|
|
if (*line == ':')
|
|
|
|
{
|
|
|
|
size_t prefix_len = strcspn (++line, " ");
|
|
|
|
msg->prefix = xstrndup (line, prefix_len);
|
|
|
|
line += prefix_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Command name
|
|
|
|
{
|
|
|
|
while (*line == ' ')
|
|
|
|
line++;
|
|
|
|
|
|
|
|
size_t cmd_len = strcspn (line, " ");
|
|
|
|
msg->command = xstrndup (line, cmd_len);
|
|
|
|
line += cmd_len;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Arguments
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
while (*line == ' ')
|
|
|
|
line++;
|
|
|
|
|
|
|
|
if (*line == ':')
|
|
|
|
{
|
2017-01-23 23:07:24 +01:00
|
|
|
strv_append (&msg->params, ++line);
|
2015-03-29 01:55:10 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t param_len = strcspn (line, " ");
|
|
|
|
if (!param_len)
|
|
|
|
break;
|
|
|
|
|
2017-01-23 23:07:24 +01:00
|
|
|
strv_append_owned (&msg->params, xstrndup (line, param_len));
|
2015-03-29 01:55:10 +01:00
|
|
|
line += param_len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
irc_free_message (struct irc_message *msg)
|
|
|
|
{
|
|
|
|
str_map_free (&msg->tags);
|
|
|
|
free (msg->prefix);
|
|
|
|
free (msg->command);
|
2017-01-23 23:01:20 +01:00
|
|
|
strv_free (&msg->params);
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
irc_process_buffer (struct str *buf,
|
2021-10-30 03:10:17 +02:00
|
|
|
void (*callback) (const struct irc_message *, const char *, void *),
|
2015-03-29 01:55:10 +01:00
|
|
|
void *user_data)
|
|
|
|
{
|
|
|
|
char *start = buf->str, *end = start + buf->len;
|
|
|
|
for (char *p = start; p + 1 < end; p++)
|
|
|
|
{
|
|
|
|
// Split the input on newlines
|
|
|
|
if (p[0] != '\r' || p[1] != '\n')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
*p = 0;
|
|
|
|
|
|
|
|
struct irc_message msg;
|
|
|
|
irc_parse_message (&msg, start);
|
|
|
|
callback (&msg, start, user_data);
|
|
|
|
irc_free_message (&msg);
|
|
|
|
|
|
|
|
start = p + 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX: we might want to just advance some kind of an offset to avoid
|
|
|
|
// moving memory around unnecessarily.
|
|
|
|
str_remove_slice (buf, 0, start - buf->str);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
irc_tolower (int c)
|
|
|
|
{
|
|
|
|
if (c == '[') return '{';
|
|
|
|
if (c == ']') return '}';
|
|
|
|
if (c == '\\') return '|';
|
|
|
|
if (c == '~') return '^';
|
|
|
|
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
|
|
|
|
}
|
|
|
|
|
2015-07-11 17:44:37 +02:00
|
|
|
static int
|
|
|
|
irc_tolower_strict (int c)
|
2015-03-29 01:55:10 +01:00
|
|
|
{
|
2015-07-11 17:44:37 +02:00
|
|
|
if (c == '[') return '{';
|
|
|
|
if (c == ']') return '}';
|
|
|
|
if (c == '\\') return '|';
|
|
|
|
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
2015-07-11 17:44:37 +02:00
|
|
|
TRIVIAL_STRXFRM (irc_strxfrm, irc_tolower)
|
|
|
|
TRIVIAL_STRXFRM (irc_strxfrm_strict, irc_tolower_strict)
|
|
|
|
|
2015-03-29 01:55:10 +01:00
|
|
|
static int
|
|
|
|
irc_strcmp (const char *a, const char *b)
|
|
|
|
{
|
|
|
|
int x;
|
|
|
|
while (*a || *b)
|
|
|
|
if ((x = irc_tolower (*a++) - irc_tolower (*b++)))
|
|
|
|
return x;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
irc_fnmatch (const char *pattern, const char *string)
|
|
|
|
{
|
|
|
|
size_t pattern_size = strlen (pattern) + 1;
|
|
|
|
size_t string_size = strlen (string) + 1;
|
|
|
|
char x_pattern[pattern_size], x_string[string_size];
|
|
|
|
irc_strxfrm (x_pattern, pattern, pattern_size);
|
|
|
|
irc_strxfrm (x_string, string, string_size);
|
2018-10-11 21:00:00 +02:00
|
|
|
// FIXME: this supports [], which is not mentioned in RFC 2812
|
2015-03-29 01:55:10 +01:00
|
|
|
return fnmatch (x_pattern, x_string, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef LIBERTY_WANT_PROTO_HTTP
|
|
|
|
|
|
|
|
// --- HTTP parsing ------------------------------------------------------------
|
|
|
|
|
|
|
|
// Basic tokenizer for HTTP header field values, to be used in various parsers.
|
|
|
|
// The input should already be unwrapped.
|
|
|
|
|
|
|
|
// Recommended literature:
|
|
|
|
// http://tools.ietf.org/html/rfc7230#section-3.2.6
|
|
|
|
// http://tools.ietf.org/html/rfc7230#appendix-B
|
|
|
|
// http://tools.ietf.org/html/rfc5234#appendix-B.1
|
|
|
|
|
|
|
|
#define HTTP_TOKENIZER_CLASS(name, definition) \
|
|
|
|
static inline bool \
|
|
|
|
http_tokenizer_is_ ## name (int c) \
|
|
|
|
{ \
|
|
|
|
return (definition); \
|
|
|
|
}
|
|
|
|
|
|
|
|
HTTP_TOKENIZER_CLASS (vchar, c >= 0x21 && c <= 0x7E)
|
|
|
|
HTTP_TOKENIZER_CLASS (delimiter, !!strchr ("\"(),/:;<=>?@[\\]{}", c))
|
|
|
|
HTTP_TOKENIZER_CLASS (whitespace, c == '\t' || c == ' ')
|
|
|
|
HTTP_TOKENIZER_CLASS (obs_text, c >= 0x80 && c <= 0xFF)
|
|
|
|
|
|
|
|
HTTP_TOKENIZER_CLASS (tchar,
|
|
|
|
http_tokenizer_is_vchar (c) && !http_tokenizer_is_delimiter (c))
|
|
|
|
|
|
|
|
HTTP_TOKENIZER_CLASS (qdtext,
|
|
|
|
c == '\t' || c == ' ' || c == '!'
|
|
|
|
|| (c >= 0x23 && c <= 0x5B)
|
|
|
|
|| (c >= 0x5D && c <= 0x7E)
|
|
|
|
|| http_tokenizer_is_obs_text (c))
|
|
|
|
|
|
|
|
HTTP_TOKENIZER_CLASS (quoted_pair,
|
|
|
|
c == '\t' || c == ' '
|
|
|
|
|| http_tokenizer_is_vchar (c)
|
|
|
|
|| http_tokenizer_is_obs_text (c))
|
|
|
|
|
|
|
|
#undef HTTP_TOKENIZER_CLASS
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
enum http_tokenizer_token
|
|
|
|
{
|
|
|
|
HTTP_T_EOF, ///< Input error
|
|
|
|
HTTP_T_ERROR, ///< End of input
|
|
|
|
|
|
|
|
HTTP_T_TOKEN, ///< "token"
|
|
|
|
HTTP_T_QUOTED_STRING, ///< "quoted-string"
|
|
|
|
HTTP_T_DELIMITER, ///< "delimiters"
|
|
|
|
HTTP_T_WHITESPACE ///< RWS/OWS/BWS
|
|
|
|
};
|
|
|
|
|
|
|
|
struct http_tokenizer
|
|
|
|
{
|
|
|
|
const unsigned char *input; ///< The input string
|
|
|
|
size_t input_len; ///< Length of the input
|
|
|
|
size_t offset; ///< Position in the input
|
|
|
|
|
|
|
|
char delimiter; ///< The delimiter character
|
|
|
|
struct str string; ///< "token" / "quoted-string" content
|
|
|
|
};
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
static struct http_tokenizer
|
|
|
|
http_tokenizer_make (const char *input, size_t len)
|
2015-03-29 01:55:10 +01:00
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
return (struct http_tokenizer)
|
|
|
|
{
|
|
|
|
.input = (const unsigned char *) input,
|
|
|
|
.input_len = len,
|
|
|
|
.string = str_make (),
|
|
|
|
};
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
http_tokenizer_free (struct http_tokenizer *self)
|
|
|
|
{
|
|
|
|
str_free (&self->string);
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum http_tokenizer_token
|
|
|
|
http_tokenizer_quoted_string (struct http_tokenizer *self)
|
|
|
|
{
|
|
|
|
bool quoted_pair = false;
|
|
|
|
while (self->offset < self->input_len)
|
|
|
|
{
|
|
|
|
int c = self->input[self->offset++];
|
|
|
|
if (quoted_pair)
|
|
|
|
{
|
|
|
|
if (!http_tokenizer_is_quoted_pair (c))
|
|
|
|
return HTTP_T_ERROR;
|
|
|
|
|
|
|
|
str_append_c (&self->string, c);
|
|
|
|
quoted_pair = false;
|
|
|
|
}
|
|
|
|
else if (c == '\\')
|
|
|
|
quoted_pair = true;
|
|
|
|
else if (c == '"')
|
|
|
|
return HTTP_T_QUOTED_STRING;
|
|
|
|
else if (http_tokenizer_is_qdtext (c))
|
|
|
|
str_append_c (&self->string, c);
|
|
|
|
else
|
|
|
|
return HTTP_T_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Premature end of input
|
|
|
|
return HTTP_T_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum http_tokenizer_token
|
|
|
|
http_tokenizer_next (struct http_tokenizer *self, bool skip_ows)
|
|
|
|
{
|
|
|
|
str_reset (&self->string);
|
|
|
|
if (self->offset >= self->input_len)
|
|
|
|
return HTTP_T_EOF;
|
|
|
|
|
|
|
|
int c = self->input[self->offset++];
|
|
|
|
|
|
|
|
if (skip_ows)
|
|
|
|
while (http_tokenizer_is_whitespace (c))
|
|
|
|
{
|
|
|
|
if (self->offset >= self->input_len)
|
|
|
|
return HTTP_T_EOF;
|
|
|
|
c = self->input[self->offset++];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '"')
|
|
|
|
return http_tokenizer_quoted_string (self);
|
|
|
|
|
|
|
|
if (http_tokenizer_is_delimiter (c))
|
|
|
|
{
|
|
|
|
self->delimiter = c;
|
|
|
|
return HTTP_T_DELIMITER;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Simple variable-length tokens
|
|
|
|
enum http_tokenizer_token result;
|
|
|
|
bool (*eater) (int c) = NULL;
|
|
|
|
if (http_tokenizer_is_whitespace (c))
|
|
|
|
{
|
|
|
|
eater = http_tokenizer_is_whitespace;
|
|
|
|
result = HTTP_T_WHITESPACE;
|
|
|
|
}
|
|
|
|
else if (http_tokenizer_is_tchar (c))
|
|
|
|
{
|
|
|
|
eater = http_tokenizer_is_tchar;
|
|
|
|
result = HTTP_T_TOKEN;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return HTTP_T_ERROR;
|
|
|
|
|
|
|
|
str_append_c (&self->string, c);
|
|
|
|
while (self->offset < self->input_len)
|
|
|
|
{
|
|
|
|
if (!eater (c = self->input[self->offset]))
|
|
|
|
break;
|
|
|
|
|
|
|
|
str_append_c (&self->string, c);
|
|
|
|
self->offset++;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
static bool
|
|
|
|
http_parse_media_type_parameter
|
|
|
|
(struct http_tokenizer *t, struct str_map *parameters)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
char *attribute = NULL;
|
|
|
|
|
|
|
|
if (http_tokenizer_next (t, true) != HTTP_T_TOKEN)
|
|
|
|
goto end;
|
|
|
|
attribute = xstrdup (t->string.str);
|
|
|
|
|
|
|
|
if (http_tokenizer_next (t, false) != HTTP_T_DELIMITER
|
|
|
|
|| t->delimiter != '=')
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
switch (http_tokenizer_next (t, false))
|
|
|
|
{
|
|
|
|
case HTTP_T_TOKEN:
|
|
|
|
case HTTP_T_QUOTED_STRING:
|
2016-10-09 09:43:46 +02:00
|
|
|
if (parameters)
|
|
|
|
str_map_set (parameters, attribute, xstrdup (t->string.str));
|
2015-03-29 01:55:10 +01:00
|
|
|
result = true;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
free (attribute);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2016-10-09 09:43:46 +02:00
|
|
|
/// Parser for "Content-Type". @a type and @a subtype may end up non-NULL
|
|
|
|
/// even if the function fails. @a parameters should be case-insensitive,
|
|
|
|
/// and may be NULL for validation only.
|
2015-03-29 01:55:10 +01:00
|
|
|
static bool
|
|
|
|
http_parse_media_type (const char *media_type,
|
|
|
|
char **type, char **subtype, struct str_map *parameters)
|
|
|
|
{
|
|
|
|
bool result = false;
|
2017-06-22 18:14:49 +02:00
|
|
|
struct http_tokenizer t =
|
|
|
|
http_tokenizer_make (media_type, strlen (media_type));
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
if (http_tokenizer_next (&t, true) != HTTP_T_TOKEN)
|
|
|
|
goto end;
|
|
|
|
*type = xstrdup (t.string.str);
|
|
|
|
|
|
|
|
if (http_tokenizer_next (&t, false) != HTTP_T_DELIMITER
|
|
|
|
|| t.delimiter != '/')
|
|
|
|
goto end;
|
|
|
|
|
|
|
|
if (http_tokenizer_next (&t, false) != HTTP_T_TOKEN)
|
|
|
|
goto end;
|
|
|
|
*subtype = xstrdup (t.string.str);
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
switch (http_tokenizer_next (&t, true))
|
|
|
|
{
|
|
|
|
case HTTP_T_DELIMITER:
|
|
|
|
if (t.delimiter != ';')
|
|
|
|
goto end;
|
|
|
|
if (!http_parse_media_type_parameter (&t, parameters))
|
|
|
|
goto end;
|
|
|
|
break;
|
|
|
|
case HTTP_T_EOF:
|
|
|
|
result = true;
|
|
|
|
default:
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
http_tokenizer_free (&t);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
struct http_protocol
|
|
|
|
{
|
|
|
|
LIST_HEADER (struct http_protocol)
|
|
|
|
|
|
|
|
char *name; ///< The protocol to upgrade to
|
|
|
|
char *version; ///< Version of the protocol, if any
|
|
|
|
};
|
|
|
|
|
|
|
|
static void
|
|
|
|
http_protocol_destroy (struct http_protocol *self)
|
|
|
|
{
|
|
|
|
free (self->name);
|
|
|
|
free (self->version);
|
|
|
|
free (self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
http_parse_upgrade (const char *upgrade, struct http_protocol **out)
|
|
|
|
{
|
|
|
|
// HTTP grammar makes this more complicated than it should be
|
|
|
|
|
|
|
|
bool result = false;
|
|
|
|
struct http_protocol *list = NULL;
|
|
|
|
struct http_protocol *tail = NULL;
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
struct http_tokenizer t = http_tokenizer_make (upgrade, strlen (upgrade));
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
enum {
|
|
|
|
STATE_PROTOCOL_NAME,
|
|
|
|
STATE_SLASH,
|
|
|
|
STATE_PROTOCOL_VERSION,
|
|
|
|
STATE_EXPECT_COMMA
|
|
|
|
} state = STATE_PROTOCOL_NAME;
|
|
|
|
struct http_protocol *proto = NULL;
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
switch (state)
|
|
|
|
{
|
|
|
|
case STATE_PROTOCOL_NAME:
|
|
|
|
switch (http_tokenizer_next (&t, false))
|
|
|
|
{
|
|
|
|
case HTTP_T_DELIMITER:
|
|
|
|
if (t.delimiter != ',')
|
|
|
|
goto end;
|
|
|
|
case HTTP_T_WHITESPACE:
|
|
|
|
break;
|
|
|
|
case HTTP_T_TOKEN:
|
|
|
|
proto = xcalloc (1, sizeof *proto);
|
|
|
|
proto->name = xstrdup (t.string.str);
|
|
|
|
LIST_APPEND_WITH_TAIL (list, tail, proto);
|
|
|
|
state = STATE_SLASH;
|
|
|
|
break;
|
|
|
|
case HTTP_T_EOF:
|
|
|
|
result = true;
|
|
|
|
default:
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_SLASH:
|
|
|
|
switch (http_tokenizer_next (&t, false))
|
|
|
|
{
|
|
|
|
case HTTP_T_DELIMITER:
|
|
|
|
if (t.delimiter == '/')
|
|
|
|
state = STATE_PROTOCOL_VERSION;
|
|
|
|
else if (t.delimiter == ',')
|
|
|
|
state = STATE_PROTOCOL_NAME;
|
|
|
|
else
|
|
|
|
goto end;
|
|
|
|
break;
|
|
|
|
case HTTP_T_WHITESPACE:
|
|
|
|
state = STATE_EXPECT_COMMA;
|
|
|
|
break;
|
|
|
|
case HTTP_T_EOF:
|
|
|
|
result = true;
|
|
|
|
default:
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_PROTOCOL_VERSION:
|
|
|
|
switch (http_tokenizer_next (&t, false))
|
|
|
|
{
|
|
|
|
case HTTP_T_TOKEN:
|
|
|
|
proto->version = xstrdup (t.string.str);
|
|
|
|
state = STATE_EXPECT_COMMA;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case STATE_EXPECT_COMMA:
|
|
|
|
switch (http_tokenizer_next (&t, false))
|
|
|
|
{
|
|
|
|
case HTTP_T_DELIMITER:
|
|
|
|
if (t.delimiter != ',')
|
|
|
|
goto end;
|
|
|
|
state = STATE_PROTOCOL_NAME;
|
|
|
|
case HTTP_T_WHITESPACE:
|
|
|
|
break;
|
|
|
|
case HTTP_T_EOF:
|
|
|
|
result = true;
|
|
|
|
default:
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
end:
|
|
|
|
if (result)
|
|
|
|
*out = list;
|
|
|
|
else
|
|
|
|
LIST_FOR_EACH (struct http_protocol, iter, list)
|
|
|
|
http_protocol_destroy (iter);
|
|
|
|
|
|
|
|
http_tokenizer_free (&t);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef LIBERTY_WANT_PROTO_SCGI
|
|
|
|
|
|
|
|
// --- SCGI --------------------------------------------------------------------
|
|
|
|
|
|
|
|
enum scgi_parser_state
|
|
|
|
{
|
|
|
|
SCGI_READING_NETSTRING_LENGTH, ///< The length of the header netstring
|
|
|
|
SCGI_READING_NAME, ///< Header name
|
|
|
|
SCGI_READING_VALUE, ///< Header value
|
|
|
|
SCGI_READING_CONTENT ///< Incoming data
|
|
|
|
};
|
|
|
|
|
|
|
|
struct scgi_parser
|
|
|
|
{
|
|
|
|
enum scgi_parser_state state; ///< Parsing state
|
|
|
|
struct str input; ///< Input buffer
|
|
|
|
|
|
|
|
struct str_map headers; ///< Headers parsed
|
|
|
|
|
|
|
|
size_t headers_len; ///< Length of the netstring contents
|
|
|
|
struct str name; ///< Header name so far
|
|
|
|
struct str value; ///< Header value so far
|
|
|
|
|
|
|
|
/// Finished parsing request headers.
|
|
|
|
/// Return false to abort further processing of input.
|
|
|
|
bool (*on_headers_read) (void *user_data);
|
|
|
|
|
|
|
|
/// Content available; len == 0 means end of file.
|
|
|
|
/// Return false to abort further processing of input.
|
|
|
|
bool (*on_content) (void *user_data, const void *data, size_t len);
|
|
|
|
|
|
|
|
void *user_data; ///< User data passed to callbacks
|
|
|
|
};
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
static struct scgi_parser
|
|
|
|
scgi_parser_make (void)
|
2015-03-29 01:55:10 +01:00
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
return (struct scgi_parser)
|
|
|
|
{
|
|
|
|
.input = str_make (),
|
|
|
|
.headers = str_map_make (free),
|
|
|
|
.name = str_make (),
|
|
|
|
.value = str_make (),
|
|
|
|
};
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
scgi_parser_free (struct scgi_parser *self)
|
|
|
|
{
|
|
|
|
str_free (&self->input);
|
|
|
|
str_map_free (&self->headers);
|
|
|
|
str_free (&self->name);
|
|
|
|
str_free (&self->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
scgi_parser_push (struct scgi_parser *self,
|
|
|
|
const void *data, size_t len, struct error **e)
|
|
|
|
{
|
|
|
|
if (!len)
|
|
|
|
{
|
|
|
|
if (self->state != SCGI_READING_CONTENT)
|
2016-10-10 07:43:57 +02:00
|
|
|
return error_set (e, "premature EOF");
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
// Indicate end of file
|
|
|
|
return self->on_content (self->user_data, NULL, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Notice that this madness is significantly harder to parse than FastCGI;
|
|
|
|
// this procedure could also be optimized significantly
|
|
|
|
str_append_data (&self->input, data, len);
|
|
|
|
|
|
|
|
bool keep_running = true;
|
|
|
|
while (keep_running)
|
|
|
|
switch (self->state)
|
|
|
|
{
|
|
|
|
case SCGI_READING_NETSTRING_LENGTH:
|
|
|
|
{
|
|
|
|
if (self->input.len < 1)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
char digit = *self->input.str;
|
|
|
|
// XXX: this allows for omitting the netstring length altogether
|
|
|
|
if (digit == ':')
|
|
|
|
{
|
|
|
|
self->state = SCGI_READING_NAME;
|
2018-10-18 06:34:16 +02:00
|
|
|
str_remove_slice (&self->input, 0, 1);
|
2015-03-29 01:55:10 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-10-18 06:34:16 +02:00
|
|
|
if (digit < '0' || digit > '9')
|
2016-10-10 07:43:57 +02:00
|
|
|
return error_set (e, "invalid header netstring");
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
size_t new_len = self->headers_len * 10 + (digit - '0');
|
|
|
|
if (new_len < self->headers_len)
|
2016-10-10 07:43:57 +02:00
|
|
|
return error_set (e, "header netstring is too long");
|
|
|
|
|
2015-03-29 01:55:10 +01:00
|
|
|
self->headers_len = new_len;
|
|
|
|
str_remove_slice (&self->input, 0, 1);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SCGI_READING_NAME:
|
|
|
|
{
|
|
|
|
if (self->input.len < 1)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
char c = *self->input.str;
|
|
|
|
if (!self->headers_len)
|
|
|
|
{
|
|
|
|
// The netstring is ending but we haven't finished parsing it,
|
|
|
|
// or the netstring doesn't end with a comma
|
|
|
|
if (self->name.len || c != ',')
|
2016-10-10 07:43:57 +02:00
|
|
|
return error_set (e, "invalid header netstring");
|
|
|
|
|
2015-03-29 01:55:10 +01:00
|
|
|
self->state = SCGI_READING_CONTENT;
|
|
|
|
keep_running = self->on_headers_read (self->user_data);
|
|
|
|
}
|
|
|
|
else if (c != '\0')
|
|
|
|
str_append_c (&self->name, c);
|
|
|
|
else
|
|
|
|
self->state = SCGI_READING_VALUE;
|
|
|
|
|
|
|
|
str_remove_slice (&self->input, 0, 1);
|
2018-10-18 06:34:16 +02:00
|
|
|
self->headers_len--;
|
2015-03-29 01:55:10 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SCGI_READING_VALUE:
|
|
|
|
{
|
|
|
|
if (self->input.len < 1)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
char c = *self->input.str;
|
|
|
|
if (!self->headers_len)
|
|
|
|
{
|
|
|
|
// The netstring is ending but we haven't finished parsing it
|
2016-10-10 07:43:57 +02:00
|
|
|
return error_set (e, "invalid header netstring");
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
else if (c != '\0')
|
|
|
|
str_append_c (&self->value, c);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// We've got a name-value pair, let's put it in the map
|
|
|
|
str_map_set (&self->headers,
|
|
|
|
self->name.str, str_steal (&self->value));
|
|
|
|
|
|
|
|
str_reset (&self->name);
|
2017-06-22 18:14:49 +02:00
|
|
|
self->value = str_make ();
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
self->state = SCGI_READING_NAME;
|
|
|
|
}
|
|
|
|
|
|
|
|
str_remove_slice (&self->input, 0, 1);
|
2018-10-18 06:34:16 +02:00
|
|
|
self->headers_len--;
|
2015-03-29 01:55:10 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SCGI_READING_CONTENT:
|
|
|
|
keep_running = self->on_content
|
|
|
|
(self->user_data, self->input.str, self->input.len);
|
|
|
|
str_remove_slice (&self->input, 0, self->input.len);
|
|
|
|
return keep_running;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef LIBERTY_WANT_PROTO_FASTCGI
|
|
|
|
|
|
|
|
// --- FastCGI -----------------------------------------------------------------
|
|
|
|
|
|
|
|
// Constants from the FastCGI specification document
|
|
|
|
|
|
|
|
#define FCGI_HEADER_LEN 8
|
|
|
|
|
|
|
|
#define FCGI_VERSION_1 1
|
|
|
|
#define FCGI_NULL_REQUEST_ID 0
|
|
|
|
#define FCGI_KEEP_CONN 1
|
|
|
|
|
|
|
|
enum fcgi_type
|
|
|
|
{
|
|
|
|
FCGI_BEGIN_REQUEST = 1,
|
|
|
|
FCGI_ABORT_REQUEST = 2,
|
|
|
|
FCGI_END_REQUEST = 3,
|
|
|
|
FCGI_PARAMS = 4,
|
|
|
|
FCGI_STDIN = 5,
|
|
|
|
FCGI_STDOUT = 6,
|
|
|
|
FCGI_STDERR = 7,
|
|
|
|
FCGI_DATA = 8,
|
|
|
|
FCGI_GET_VALUES = 9,
|
|
|
|
FCGI_GET_VALUES_RESULT = 10,
|
|
|
|
FCGI_UNKNOWN_TYPE = 11,
|
|
|
|
FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE
|
|
|
|
};
|
|
|
|
|
|
|
|
enum fcgi_role
|
|
|
|
{
|
|
|
|
FCGI_RESPONDER = 1,
|
|
|
|
FCGI_AUTHORIZER = 2,
|
|
|
|
FCGI_FILTER = 3
|
|
|
|
};
|
|
|
|
|
|
|
|
enum fcgi_protocol_status
|
|
|
|
{
|
|
|
|
FCGI_REQUEST_COMPLETE = 0,
|
|
|
|
FCGI_CANT_MPX_CONN = 1,
|
|
|
|
FCGI_OVERLOADED = 2,
|
|
|
|
FCGI_UNKNOWN_ROLE = 3
|
|
|
|
};
|
|
|
|
|
|
|
|
#define FCGI_MAX_CONNS "FCGI_MAX_CONNS"
|
|
|
|
#define FCGI_MAX_REQS "FCGI_MAX_REQS"
|
|
|
|
#define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS"
|
|
|
|
|
|
|
|
// - - Message stream parser - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
struct fcgi_parser;
|
|
|
|
|
2018-10-18 04:08:47 +02:00
|
|
|
/// Message handler, returns false if further processing should be stopped
|
|
|
|
typedef bool (*fcgi_message_fn)
|
2015-03-29 01:55:10 +01:00
|
|
|
(const struct fcgi_parser *parser, void *user_data);
|
|
|
|
|
|
|
|
enum fcgi_parser_state
|
|
|
|
{
|
|
|
|
FCGI_READING_HEADER, ///< Reading the fixed header portion
|
|
|
|
FCGI_READING_CONTENT, ///< Reading the message content
|
|
|
|
FCGI_READING_PADDING ///< Reading the padding
|
|
|
|
};
|
|
|
|
|
|
|
|
struct fcgi_parser
|
|
|
|
{
|
|
|
|
enum fcgi_parser_state state; ///< Parsing state
|
|
|
|
struct str input; ///< Input buffer
|
|
|
|
|
|
|
|
// The next block of fields is considered public:
|
|
|
|
|
|
|
|
uint8_t version; ///< FastCGI protocol version
|
|
|
|
uint8_t type; ///< FastCGI record type
|
|
|
|
uint16_t request_id; ///< FastCGI request ID
|
|
|
|
struct str content; ///< Message data
|
|
|
|
|
|
|
|
uint16_t content_length; ///< Message content length
|
|
|
|
uint8_t padding_length; ///< Message padding length
|
|
|
|
|
|
|
|
fcgi_message_fn on_message; ///< Callback on message
|
|
|
|
void *user_data; ///< User data
|
|
|
|
};
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
static struct fcgi_parser
|
|
|
|
fcgi_parser_make (void)
|
2015-03-29 01:55:10 +01:00
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
return (struct fcgi_parser)
|
|
|
|
{ .input = str_make (), .content = str_make () };
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fcgi_parser_free (struct fcgi_parser *self)
|
|
|
|
{
|
|
|
|
str_free (&self->input);
|
|
|
|
str_free (&self->content);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fcgi_parser_unpack_header (struct fcgi_parser *self)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
struct msg_unpacker unpacker =
|
|
|
|
msg_unpacker_make (self->input.str, self->input.len);
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
bool success = true;
|
|
|
|
uint8_t reserved;
|
|
|
|
success &= msg_unpacker_u8 (&unpacker, &self->version);
|
|
|
|
success &= msg_unpacker_u8 (&unpacker, &self->type);
|
|
|
|
success &= msg_unpacker_u16 (&unpacker, &self->request_id);
|
|
|
|
success &= msg_unpacker_u16 (&unpacker, &self->content_length);
|
|
|
|
success &= msg_unpacker_u8 (&unpacker, &self->padding_length);
|
|
|
|
success &= msg_unpacker_u8 (&unpacker, &reserved);
|
|
|
|
hard_assert (success);
|
|
|
|
|
|
|
|
str_remove_slice (&self->input, 0, unpacker.offset);
|
|
|
|
}
|
|
|
|
|
2018-10-18 04:08:47 +02:00
|
|
|
static bool
|
2015-03-29 01:55:10 +01:00
|
|
|
fcgi_parser_push (struct fcgi_parser *self, const void *data, size_t len)
|
|
|
|
{
|
|
|
|
// This could be made considerably faster for high-throughput applications
|
|
|
|
// if we use a circular buffer instead of constantly calling memmove()
|
|
|
|
str_append_data (&self->input, data, len);
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
switch (self->state)
|
|
|
|
{
|
|
|
|
case FCGI_READING_HEADER:
|
|
|
|
if (self->input.len < FCGI_HEADER_LEN)
|
2018-10-18 04:08:47 +02:00
|
|
|
return true;
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
fcgi_parser_unpack_header (self);
|
|
|
|
self->state = FCGI_READING_CONTENT;
|
|
|
|
break;
|
|
|
|
case FCGI_READING_CONTENT:
|
|
|
|
if (self->input.len < self->content_length)
|
2018-10-18 04:08:47 +02:00
|
|
|
return true;
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
// Move an appropriate part of the input buffer to the content buffer
|
|
|
|
str_reset (&self->content);
|
|
|
|
str_append_data (&self->content, self->input.str, self->content_length);
|
|
|
|
str_remove_slice (&self->input, 0, self->content_length);
|
|
|
|
self->state = FCGI_READING_PADDING;
|
|
|
|
break;
|
|
|
|
case FCGI_READING_PADDING:
|
|
|
|
if (self->input.len < self->padding_length)
|
2018-10-18 04:08:47 +02:00
|
|
|
return true;
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
// Call the callback to further process the message
|
2018-10-18 04:08:47 +02:00
|
|
|
if (!self->on_message (self, self->user_data))
|
|
|
|
return false;
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
// Remove the padding from the input buffer
|
|
|
|
str_remove_slice (&self->input, 0, self->padding_length);
|
|
|
|
self->state = FCGI_READING_HEADER;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - Name-value pair parser - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
enum fcgi_nv_parser_state
|
|
|
|
{
|
|
|
|
FCGI_NV_PARSER_NAME_LEN, ///< The first name length octet
|
|
|
|
FCGI_NV_PARSER_NAME_LEN_FULL, ///< Remaining name length octets
|
|
|
|
FCGI_NV_PARSER_VALUE_LEN, ///< The first value length octet
|
|
|
|
FCGI_NV_PARSER_VALUE_LEN_FULL, ///< Remaining value length octets
|
|
|
|
FCGI_NV_PARSER_NAME, ///< Reading the name
|
|
|
|
FCGI_NV_PARSER_VALUE ///< Reading the value
|
|
|
|
};
|
|
|
|
|
|
|
|
struct fcgi_nv_parser
|
|
|
|
{
|
|
|
|
struct str_map *output; ///< Where the pairs will be stored
|
|
|
|
|
|
|
|
enum fcgi_nv_parser_state state; ///< Parsing state
|
|
|
|
struct str input; ///< Input buffer
|
|
|
|
|
|
|
|
uint32_t name_len; ///< Length of the name
|
|
|
|
uint32_t value_len; ///< Length of the value
|
|
|
|
|
|
|
|
char *name; ///< The current name, 0-terminated
|
|
|
|
char *value; ///< The current value, 0-terminated
|
|
|
|
};
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
static struct fcgi_nv_parser
|
|
|
|
fcgi_nv_parser_make (void)
|
2015-03-29 01:55:10 +01:00
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
return (struct fcgi_nv_parser) { .input = str_make () };
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fcgi_nv_parser_free (struct fcgi_nv_parser *self)
|
|
|
|
{
|
|
|
|
str_free (&self->input);
|
|
|
|
free (self->name);
|
|
|
|
free (self->value);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fcgi_nv_parser_push (struct fcgi_nv_parser *self, const void *data, size_t len)
|
|
|
|
{
|
|
|
|
// This could be optimized significantly; I'm not even trying
|
|
|
|
str_append_data (&self->input, data, len);
|
|
|
|
|
|
|
|
while (true)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
struct msg_unpacker unpacker =
|
|
|
|
msg_unpacker_make (self->input.str, self->input.len);
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
switch (self->state)
|
|
|
|
{
|
|
|
|
uint8_t len;
|
|
|
|
uint32_t len_full;
|
|
|
|
|
|
|
|
case FCGI_NV_PARSER_NAME_LEN:
|
|
|
|
if (!msg_unpacker_u8 (&unpacker, &len))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (len >> 7)
|
|
|
|
self->state = FCGI_NV_PARSER_NAME_LEN_FULL;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->name_len = len;
|
|
|
|
str_remove_slice (&self->input, 0, unpacker.offset);
|
|
|
|
self->state = FCGI_NV_PARSER_VALUE_LEN;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FCGI_NV_PARSER_NAME_LEN_FULL:
|
|
|
|
if (!msg_unpacker_u32 (&unpacker, &len_full))
|
|
|
|
return;
|
|
|
|
|
|
|
|
self->name_len = len_full & ~(1U << 31);
|
|
|
|
str_remove_slice (&self->input, 0, unpacker.offset);
|
|
|
|
self->state = FCGI_NV_PARSER_VALUE_LEN;
|
|
|
|
break;
|
|
|
|
case FCGI_NV_PARSER_VALUE_LEN:
|
|
|
|
if (!msg_unpacker_u8 (&unpacker, &len))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (len >> 7)
|
|
|
|
self->state = FCGI_NV_PARSER_VALUE_LEN_FULL;
|
|
|
|
else
|
|
|
|
{
|
|
|
|
self->value_len = len;
|
|
|
|
str_remove_slice (&self->input, 0, unpacker.offset);
|
|
|
|
self->state = FCGI_NV_PARSER_NAME;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case FCGI_NV_PARSER_VALUE_LEN_FULL:
|
|
|
|
if (!msg_unpacker_u32 (&unpacker, &len_full))
|
|
|
|
return;
|
|
|
|
|
|
|
|
self->value_len = len_full & ~(1U << 31);
|
|
|
|
str_remove_slice (&self->input, 0, unpacker.offset);
|
|
|
|
self->state = FCGI_NV_PARSER_NAME;
|
|
|
|
break;
|
|
|
|
case FCGI_NV_PARSER_NAME:
|
|
|
|
if (self->input.len < self->name_len)
|
|
|
|
return;
|
|
|
|
|
|
|
|
self->name = xmalloc (self->name_len + 1);
|
|
|
|
self->name[self->name_len] = '\0';
|
|
|
|
memcpy (self->name, self->input.str, self->name_len);
|
|
|
|
str_remove_slice (&self->input, 0, self->name_len);
|
|
|
|
self->state = FCGI_NV_PARSER_VALUE;
|
|
|
|
break;
|
|
|
|
case FCGI_NV_PARSER_VALUE:
|
|
|
|
if (self->input.len < self->value_len)
|
|
|
|
return;
|
|
|
|
|
|
|
|
self->value = xmalloc (self->value_len + 1);
|
|
|
|
self->value[self->value_len] = '\0';
|
|
|
|
memcpy (self->value, self->input.str, self->value_len);
|
|
|
|
str_remove_slice (&self->input, 0, self->value_len);
|
|
|
|
self->state = FCGI_NV_PARSER_NAME_LEN;
|
|
|
|
|
|
|
|
// The map takes ownership of the value
|
|
|
|
str_map_set (self->output, self->name, self->value);
|
|
|
|
free (self->name);
|
|
|
|
|
|
|
|
self->name = NULL;
|
|
|
|
self->value = NULL;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
static void
|
|
|
|
fcgi_nv_convert_len (size_t len, struct str *output)
|
|
|
|
{
|
|
|
|
if (len < 0x80)
|
|
|
|
str_pack_u8 (output, len);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
len |= (uint32_t) 1 << 31;
|
|
|
|
str_pack_u32 (output, len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
fcgi_nv_convert (struct str_map *map, struct str *output)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
struct str_map_iter iter = str_map_iter_make (map);
|
2015-03-29 01:55:10 +01:00
|
|
|
while (str_map_iter_next (&iter))
|
|
|
|
{
|
|
|
|
const char *name = iter.link->key;
|
|
|
|
const char *value = iter.link->data;
|
|
|
|
size_t name_len = iter.link->key_length;
|
|
|
|
size_t value_len = strlen (value);
|
|
|
|
|
|
|
|
fcgi_nv_convert_len (name_len, output);
|
|
|
|
fcgi_nv_convert_len (value_len, output);
|
|
|
|
str_append_data (output, name, name_len);
|
|
|
|
str_append_data (output, value, value_len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef LIBERTY_WANT_PROTO_WS
|
|
|
|
|
|
|
|
// --- WebSockets --------------------------------------------------------------
|
|
|
|
|
|
|
|
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
|
|
|
|
|
|
#define SEC_WS_KEY "Sec-WebSocket-Key"
|
|
|
|
#define SEC_WS_ACCEPT "Sec-WebSocket-Accept"
|
|
|
|
#define SEC_WS_PROTOCOL "Sec-WebSocket-Protocol"
|
|
|
|
#define SEC_WS_EXTENSIONS "Sec-WebSocket-Extensions"
|
|
|
|
#define SEC_WS_VERSION "Sec-WebSocket-Version"
|
|
|
|
|
|
|
|
#define WS_MAX_CONTROL_PAYLOAD_LEN 125
|
|
|
|
|
|
|
|
static char *
|
|
|
|
ws_encode_response_key (const char *key)
|
|
|
|
{
|
|
|
|
char *response_key = xstrdup_printf ("%s" WS_GUID, key);
|
|
|
|
unsigned char hash[SHA_DIGEST_LENGTH];
|
|
|
|
SHA1 ((unsigned char *) response_key, strlen (response_key), hash);
|
|
|
|
free (response_key);
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
struct str base64 = str_make ();
|
2015-03-29 01:55:10 +01:00
|
|
|
base64_encode (hash, sizeof hash, &base64);
|
|
|
|
return str_steal (&base64);
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ws_status
|
|
|
|
{
|
2015-05-02 22:33:00 +02:00
|
|
|
// Named according to the meaning specified in RFC 6455, section 11.7
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
WS_STATUS_NORMAL_CLOSURE = 1000,
|
|
|
|
WS_STATUS_GOING_AWAY = 1001,
|
|
|
|
WS_STATUS_PROTOCOL_ERROR = 1002,
|
|
|
|
WS_STATUS_UNSUPPORTED_DATA = 1003,
|
|
|
|
WS_STATUS_INVALID_PAYLOAD_DATA = 1007,
|
|
|
|
WS_STATUS_POLICY_VIOLATION = 1008,
|
|
|
|
WS_STATUS_MESSAGE_TOO_BIG = 1009,
|
|
|
|
WS_STATUS_MANDATORY_EXTENSION = 1010,
|
|
|
|
WS_STATUS_INTERNAL_SERVER_ERROR = 1011,
|
|
|
|
|
|
|
|
// Reserved for internal usage
|
|
|
|
WS_STATUS_NO_STATUS_RECEIVED = 1005,
|
|
|
|
WS_STATUS_ABNORMAL_CLOSURE = 1006,
|
|
|
|
WS_STATUS_TLS_HANDSHAKE = 1015
|
|
|
|
};
|
|
|
|
|
|
|
|
// - - Frame parser - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
enum ws_parser_state
|
|
|
|
{
|
|
|
|
WS_PARSER_FIXED, ///< Parsing fixed length part
|
|
|
|
WS_PARSER_PAYLOAD_LEN_16, ///< Parsing extended payload length
|
|
|
|
WS_PARSER_PAYLOAD_LEN_64, ///< Parsing extended payload length
|
|
|
|
WS_PARSER_MASK, ///< Parsing masking-key
|
|
|
|
WS_PARSER_PAYLOAD ///< Parsing payload
|
|
|
|
};
|
|
|
|
|
|
|
|
enum ws_opcode
|
|
|
|
{
|
|
|
|
// Non-control
|
|
|
|
WS_OPCODE_CONT = 0,
|
|
|
|
WS_OPCODE_TEXT = 1,
|
|
|
|
WS_OPCODE_BINARY = 2,
|
|
|
|
|
|
|
|
// Control
|
|
|
|
WS_OPCODE_CLOSE = 8,
|
|
|
|
WS_OPCODE_PING = 9,
|
|
|
|
WS_OPCODE_PONG = 10
|
|
|
|
};
|
|
|
|
|
|
|
|
static bool
|
|
|
|
ws_is_control_frame (int opcode)
|
|
|
|
{
|
|
|
|
return opcode >= WS_OPCODE_CLOSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ws_parser
|
|
|
|
{
|
|
|
|
struct str input; ///< External input buffer
|
|
|
|
enum ws_parser_state state; ///< Parsing state
|
|
|
|
|
|
|
|
unsigned is_fin : 1; ///< Final frame of a message?
|
|
|
|
unsigned is_masked : 1; ///< Is the frame masked?
|
|
|
|
unsigned reserved_1 : 1; ///< Reserved
|
|
|
|
unsigned reserved_2 : 1; ///< Reserved
|
|
|
|
unsigned reserved_3 : 1; ///< Reserved
|
|
|
|
enum ws_opcode opcode; ///< Opcode
|
|
|
|
uint32_t mask; ///< Frame mask
|
|
|
|
uint64_t payload_len; ///< Payload length
|
|
|
|
|
|
|
|
bool (*on_frame_header) (void *user_data, const struct ws_parser *self);
|
|
|
|
|
|
|
|
/// Callback for when a message is successfully parsed.
|
|
|
|
/// The actual payload is stored in "input", of length "payload_len".
|
|
|
|
bool (*on_frame) (void *user_data, const struct ws_parser *self);
|
|
|
|
|
|
|
|
void *user_data; ///< User data for callbacks
|
|
|
|
};
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
static struct ws_parser
|
|
|
|
ws_parser_make (void)
|
2015-03-29 01:55:10 +01:00
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
return (struct ws_parser) { .input = str_make () };
|
2015-03-29 01:55:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ws_parser_free (struct ws_parser *self)
|
|
|
|
{
|
|
|
|
str_free (&self->input);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
ws_parser_unmask (char *payload, uint64_t len, uint32_t mask)
|
|
|
|
{
|
|
|
|
// This could be made faster. For example by reading the mask in
|
|
|
|
// native byte ordering and applying it directly here.
|
|
|
|
|
|
|
|
uint64_t end = len & ~(uint64_t) 3;
|
|
|
|
for (uint64_t i = 0; i < end; i += 4)
|
|
|
|
{
|
|
|
|
payload[i + 3] ^= mask & 0xFF;
|
|
|
|
payload[i + 2] ^= (mask >> 8) & 0xFF;
|
|
|
|
payload[i + 1] ^= (mask >> 16) & 0xFF;
|
|
|
|
payload[i ] ^= (mask >> 24) & 0xFF;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (len - end)
|
|
|
|
{
|
|
|
|
case 3:
|
|
|
|
payload[end + 2] ^= (mask >> 8) & 0xFF;
|
2018-10-11 21:00:00 +02:00
|
|
|
// Fall-through
|
2015-03-29 01:55:10 +01:00
|
|
|
case 2:
|
|
|
|
payload[end + 1] ^= (mask >> 16) & 0xFF;
|
2018-10-11 21:00:00 +02:00
|
|
|
// Fall-through
|
2015-03-29 01:55:10 +01:00
|
|
|
case 1:
|
|
|
|
payload[end ] ^= (mask >> 24) & 0xFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
ws_parser_push (struct ws_parser *self, const void *data, size_t len)
|
|
|
|
{
|
|
|
|
bool success = false;
|
|
|
|
str_append_data (&self->input, data, len);
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
struct msg_unpacker unpacker =
|
|
|
|
msg_unpacker_make (self->input.str, self->input.len);
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
while (true)
|
|
|
|
switch (self->state)
|
|
|
|
{
|
|
|
|
uint8_t u8;
|
|
|
|
uint16_t u16;
|
|
|
|
|
|
|
|
case WS_PARSER_FIXED:
|
|
|
|
if (unpacker.len - unpacker.offset < 2)
|
|
|
|
goto need_data;
|
|
|
|
|
|
|
|
(void) msg_unpacker_u8 (&unpacker, &u8);
|
|
|
|
self->is_fin = (u8 >> 7) & 1;
|
|
|
|
self->reserved_1 = (u8 >> 6) & 1;
|
|
|
|
self->reserved_2 = (u8 >> 5) & 1;
|
|
|
|
self->reserved_3 = (u8 >> 4) & 1;
|
|
|
|
self->opcode = u8 & 15;
|
|
|
|
|
|
|
|
(void) msg_unpacker_u8 (&unpacker, &u8);
|
|
|
|
self->is_masked = (u8 >> 7) & 1;
|
|
|
|
self->payload_len = u8 & 127;
|
|
|
|
|
|
|
|
if (self->payload_len == 127)
|
|
|
|
self->state = WS_PARSER_PAYLOAD_LEN_64;
|
|
|
|
else if (self->payload_len == 126)
|
|
|
|
self->state = WS_PARSER_PAYLOAD_LEN_16;
|
|
|
|
else
|
|
|
|
self->state = WS_PARSER_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WS_PARSER_PAYLOAD_LEN_16:
|
|
|
|
if (!msg_unpacker_u16 (&unpacker, &u16))
|
|
|
|
goto need_data;
|
|
|
|
self->payload_len = u16;
|
|
|
|
|
|
|
|
self->state = WS_PARSER_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WS_PARSER_PAYLOAD_LEN_64:
|
|
|
|
if (!msg_unpacker_u64 (&unpacker, &self->payload_len))
|
|
|
|
goto need_data;
|
|
|
|
|
|
|
|
self->state = WS_PARSER_MASK;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WS_PARSER_MASK:
|
|
|
|
if (!self->is_masked)
|
|
|
|
goto end_of_header;
|
|
|
|
if (!msg_unpacker_u32 (&unpacker, &self->mask))
|
|
|
|
goto need_data;
|
|
|
|
|
|
|
|
end_of_header:
|
|
|
|
self->state = WS_PARSER_PAYLOAD;
|
|
|
|
if (!self->on_frame_header (self->user_data, self))
|
|
|
|
goto fail;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case WS_PARSER_PAYLOAD:
|
|
|
|
// Move the buffer so that payload data is at the front
|
|
|
|
str_remove_slice (&self->input, 0, unpacker.offset);
|
2017-06-22 18:14:49 +02:00
|
|
|
unpacker = msg_unpacker_make (self->input.str, self->input.len);
|
2015-03-29 01:55:10 +01:00
|
|
|
|
|
|
|
if (self->input.len < self->payload_len)
|
|
|
|
goto need_data;
|
|
|
|
if (self->is_masked)
|
|
|
|
ws_parser_unmask (self->input.str, self->payload_len, self->mask);
|
|
|
|
if (!self->on_frame (self->user_data, self))
|
|
|
|
goto fail;
|
|
|
|
|
2017-02-06 19:45:03 +01:00
|
|
|
// And continue unpacking frames past the payload
|
|
|
|
unpacker.offset = self->payload_len;
|
2015-03-29 01:55:10 +01:00
|
|
|
self->state = WS_PARSER_FIXED;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
need_data:
|
|
|
|
success = true;
|
|
|
|
fail:
|
|
|
|
str_remove_slice (&self->input, 0, unpacker.offset);
|
|
|
|
return success;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
#ifdef LIBERTY_WANT_PROTO_MPD
|
|
|
|
|
|
|
|
#include <sys/un.h>
|
|
|
|
|
|
|
|
// --- MPD client interface ----------------------------------------------------
|
|
|
|
|
|
|
|
// This is a rather thin MPD client interface intended for basic tasks
|
|
|
|
|
|
|
|
#define MPD_SUBSYSTEM_TABLE(XX) \
|
|
|
|
XX (DATABASE, 0, "database") \
|
|
|
|
XX (UPDATE, 1, "update") \
|
|
|
|
XX (STORED_PLAYLIST, 2, "stored_playlist") \
|
|
|
|
XX (PLAYLIST, 3, "playlist") \
|
|
|
|
XX (PLAYER, 4, "player") \
|
|
|
|
XX (MIXER, 5, "mixer") \
|
|
|
|
XX (OUTPUT, 6, "output") \
|
|
|
|
XX (OPTIONS, 7, "options") \
|
|
|
|
XX (STICKER, 8, "sticker") \
|
|
|
|
XX (SUBSCRIPTION, 9, "subscription") \
|
|
|
|
XX (MESSAGE, 10, "message")
|
|
|
|
|
|
|
|
enum mpd_subsystem
|
|
|
|
{
|
|
|
|
#define XX(a, b, c) MPD_SUBSYSTEM_ ## a = (1 << b),
|
|
|
|
MPD_SUBSYSTEM_TABLE (XX)
|
|
|
|
#undef XX
|
|
|
|
};
|
|
|
|
|
|
|
|
static const char *mpd_subsystem_names[] =
|
|
|
|
{
|
|
|
|
#define XX(a, b, c) [b] = c,
|
|
|
|
MPD_SUBSYSTEM_TABLE (XX)
|
|
|
|
#undef XX
|
|
|
|
};
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
enum mpd_client_state
|
|
|
|
{
|
|
|
|
MPD_DISCONNECTED, ///< Not connected
|
|
|
|
MPD_CONNECTING, ///< Currently connecting
|
|
|
|
MPD_CONNECTED ///< Connected
|
|
|
|
};
|
|
|
|
|
|
|
|
struct mpd_response
|
|
|
|
{
|
|
|
|
bool success; ///< OK or ACK
|
|
|
|
|
|
|
|
// ACK-only fields:
|
|
|
|
|
|
|
|
int error; ///< Numeric error value (ack.h)
|
|
|
|
int list_offset; ///< Offset of command in list
|
|
|
|
char *current_command; ///< Name of the erroring command
|
|
|
|
char *message_text; ///< Error message
|
|
|
|
};
|
|
|
|
|
2017-06-04 00:49:56 +02:00
|
|
|
/// Task completion callback; on connection abortion most fields are 0
|
2016-10-11 09:37:22 +02:00
|
|
|
typedef void (*mpd_client_task_cb) (const struct mpd_response *response,
|
2017-01-23 23:01:20 +01:00
|
|
|
const struct strv *data, void *user_data);
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
struct mpd_client_task
|
|
|
|
{
|
|
|
|
LIST_HEADER (struct mpd_client_task)
|
|
|
|
|
|
|
|
mpd_client_task_cb callback; ///< Callback on completion
|
|
|
|
void *user_data; ///< User data
|
|
|
|
};
|
|
|
|
|
|
|
|
struct mpd_client
|
|
|
|
{
|
|
|
|
struct poller *poller; ///< Poller
|
|
|
|
|
|
|
|
// Connection:
|
|
|
|
|
|
|
|
enum mpd_client_state state; ///< Connection state
|
|
|
|
struct connector *connector; ///< Connection establisher
|
|
|
|
|
|
|
|
int socket; ///< MPD socket
|
|
|
|
struct str read_buffer; ///< Input yet to be processed
|
|
|
|
struct str write_buffer; ///< Outut yet to be be sent out
|
|
|
|
struct poller_fd socket_event; ///< We can read from the socket
|
|
|
|
|
|
|
|
struct poller_timer timeout_timer; ///< Connection seems to be dead
|
|
|
|
|
|
|
|
// Protocol:
|
|
|
|
|
2024-08-07 22:03:08 +02:00
|
|
|
char *got_hello; ///< Version from OK MPD hello message
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
bool idling; ///< Sent idle as the last command
|
|
|
|
unsigned idling_subsystems; ///< Subsystems we're idling for
|
|
|
|
bool in_list; ///< We're inside a command list
|
|
|
|
|
|
|
|
struct mpd_client_task *tasks; ///< Task queue
|
|
|
|
struct mpd_client_task *tasks_tail; ///< Tail of task queue
|
2017-01-23 23:01:20 +01:00
|
|
|
struct strv data; ///< Data from last command
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
// User configuration:
|
|
|
|
|
|
|
|
void *user_data; ///< User data for callbacks
|
|
|
|
|
|
|
|
/// Callback after connection has been successfully established
|
|
|
|
void (*on_connected) (void *user_data);
|
|
|
|
|
|
|
|
/// Callback for general failures or even normal disconnection;
|
|
|
|
/// the interface is reinitialized
|
|
|
|
void (*on_failure) (void *user_data);
|
|
|
|
|
|
|
|
/// Callback to receive "idle" updates.
|
|
|
|
/// Remember to restart the idle if needed.
|
|
|
|
void (*on_event) (unsigned subsystems, void *user_data);
|
|
|
|
|
|
|
|
/// Callback to trace protocol I/O
|
|
|
|
void (*on_io_hook) (void *user_data, bool outgoing, const char *line);
|
|
|
|
};
|
|
|
|
|
|
|
|
static void mpd_client_reset (struct mpd_client *self);
|
|
|
|
static void mpd_client_destroy_connector (struct mpd_client *self);
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
static struct mpd_client
|
|
|
|
mpd_client_make (struct poller *poller)
|
2016-10-11 09:37:22 +02:00
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
return (struct mpd_client)
|
|
|
|
{
|
|
|
|
.poller = poller,
|
|
|
|
.socket = -1,
|
|
|
|
.read_buffer = str_make (),
|
|
|
|
.write_buffer = str_make (),
|
|
|
|
.data = strv_make (),
|
|
|
|
.socket_event = poller_fd_make (poller, -1),
|
|
|
|
.timeout_timer = poller_timer_make (poller),
|
|
|
|
};
|
2016-10-11 09:37:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_free (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
// So that we don't have to repeat most of the stuff
|
|
|
|
mpd_client_reset (self);
|
|
|
|
|
|
|
|
str_free (&self->read_buffer);
|
|
|
|
str_free (&self->write_buffer);
|
|
|
|
|
2017-01-23 23:01:20 +01:00
|
|
|
strv_free (&self->data);
|
2016-10-11 09:37:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
2017-06-04 00:49:56 +02:00
|
|
|
static void
|
|
|
|
mpd_client_dispatch (struct mpd_client *self, struct mpd_response *response)
|
|
|
|
{
|
|
|
|
struct mpd_client_task *task;
|
|
|
|
if (!(task = self->tasks))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (task->callback)
|
|
|
|
task->callback (response, &self->data, task->user_data);
|
|
|
|
strv_reset (&self->data);
|
|
|
|
|
|
|
|
LIST_UNLINK_WITH_TAIL (self->tasks, self->tasks_tail, task);
|
|
|
|
free (task);
|
|
|
|
}
|
|
|
|
|
2016-10-11 09:37:22 +02:00
|
|
|
/// Reinitialize the interface so that you can reconnect anew
|
|
|
|
static void
|
|
|
|
mpd_client_reset (struct mpd_client *self)
|
|
|
|
{
|
2017-06-04 00:49:56 +02:00
|
|
|
// Get rid of all pending tasks to release resources etc.
|
|
|
|
strv_reset (&self->data);
|
|
|
|
struct mpd_response aborted = { .message_text = "Disconnected" };
|
|
|
|
while (self->tasks)
|
|
|
|
mpd_client_dispatch (self, &aborted);
|
|
|
|
|
2016-10-11 09:37:22 +02:00
|
|
|
if (self->state == MPD_CONNECTING)
|
|
|
|
mpd_client_destroy_connector (self);
|
|
|
|
|
|
|
|
if (self->socket != -1)
|
|
|
|
xclose (self->socket);
|
|
|
|
self->socket = -1;
|
|
|
|
|
2017-05-06 21:09:04 +02:00
|
|
|
// FIXME: this is not robust wrt. forking
|
2016-10-11 09:37:22 +02:00
|
|
|
self->socket_event.closed = true;
|
|
|
|
poller_fd_reset (&self->socket_event);
|
|
|
|
poller_timer_reset (&self->timeout_timer);
|
|
|
|
|
|
|
|
str_reset (&self->read_buffer);
|
|
|
|
str_reset (&self->write_buffer);
|
|
|
|
|
2024-08-07 22:03:08 +02:00
|
|
|
cstr_set (&self->got_hello, NULL);
|
2016-10-11 09:37:22 +02:00
|
|
|
self->idling = false;
|
|
|
|
self->idling_subsystems = 0;
|
|
|
|
self->in_list = false;
|
|
|
|
|
|
|
|
self->state = MPD_DISCONNECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_fail (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
mpd_client_reset (self);
|
|
|
|
if (self->on_failure)
|
|
|
|
self->on_failure (self->user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_client_parse_response (const char *p, struct mpd_response *response)
|
|
|
|
{
|
|
|
|
if (!strcmp (p, "OK"))
|
|
|
|
return response->success = true;
|
|
|
|
|
|
|
|
char *end = NULL;
|
|
|
|
if (*p++ != 'A' || *p++ != 'C' || *p++ != 'K' || *p++ != ' ' || *p++ != '[')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
response->error = strtoul (p, &end, 10);
|
|
|
|
if (errno != 0 || end == p)
|
|
|
|
return false;
|
|
|
|
p = end;
|
|
|
|
if (*p++ != '@')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
response->list_offset = strtoul (p, &end, 10);
|
|
|
|
if (errno != 0 || end == p)
|
|
|
|
return false;
|
|
|
|
p = end;
|
|
|
|
if (*p++ != ']' || *p++ != ' ' || *p++ != '{' || !(end = strchr (p, '}')))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
response->current_command = xstrndup (p, end - p);
|
|
|
|
p = end + 1;
|
|
|
|
|
|
|
|
if (*p++ != ' ')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
response->message_text = xstrdup (p);
|
|
|
|
response->success = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_client_parse_hello (struct mpd_client *self, const char *line)
|
|
|
|
{
|
|
|
|
const char hello[] = "OK MPD ";
|
|
|
|
if (strncmp (line, hello, sizeof hello - 1))
|
|
|
|
{
|
|
|
|
print_debug ("invalid MPD hello message");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: call "on_connected" now. We should however also set up a timer
|
|
|
|
// so that we don't wait on this message forever.
|
2024-08-07 22:03:08 +02:00
|
|
|
cstr_set (&self->got_hello, xstrdup (line + sizeof hello - 1));
|
|
|
|
return true;
|
2016-10-11 09:37:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_client_parse_line (struct mpd_client *self, const char *line)
|
|
|
|
{
|
|
|
|
if (self->on_io_hook)
|
|
|
|
self->on_io_hook (self->user_data, false, line);
|
|
|
|
|
|
|
|
if (!self->got_hello)
|
|
|
|
return mpd_client_parse_hello (self, line);
|
|
|
|
|
|
|
|
struct mpd_response response;
|
|
|
|
memset (&response, 0, sizeof response);
|
2016-10-13 01:12:34 +02:00
|
|
|
if (!strcmp (line, "list_OK"))
|
2017-01-23 23:07:24 +01:00
|
|
|
strv_append_owned (&self->data, NULL);
|
2016-10-13 01:12:34 +02:00
|
|
|
else if (mpd_client_parse_response (line, &response))
|
2016-10-11 09:37:22 +02:00
|
|
|
mpd_client_dispatch (self, &response);
|
|
|
|
else
|
2017-01-23 23:07:24 +01:00
|
|
|
strv_append (&self->data, line);
|
2020-10-12 02:05:51 +02:00
|
|
|
|
|
|
|
free (response.current_command);
|
|
|
|
free (response.message_text);
|
2016-10-11 09:37:22 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// All output from MPD commands seems to be in a trivial "key: value" format
|
|
|
|
static char *
|
|
|
|
mpd_client_parse_kv (char *line, char **value)
|
|
|
|
{
|
|
|
|
char *sep;
|
|
|
|
if (!(sep = strstr (line, ": ")))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
*sep = 0;
|
|
|
|
*value = sep + 2;
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_update_poller (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
poller_fd_set (&self->socket_event,
|
|
|
|
self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_client_process_input (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
// Split socket input at newlines and process them separately
|
|
|
|
struct str *rb = &self->read_buffer;
|
|
|
|
char *start = rb->str, *end = start + rb->len;
|
|
|
|
for (char *p = start; p < end; p++)
|
|
|
|
{
|
|
|
|
if (*p != '\n')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
*p = 0;
|
|
|
|
if (!mpd_client_parse_line (self, start))
|
|
|
|
return false;
|
|
|
|
start = p + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
str_remove_slice (rb, 0, start - rb->str);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_on_ready (const struct pollfd *pfd, void *user_data)
|
|
|
|
{
|
|
|
|
(void) pfd;
|
|
|
|
|
|
|
|
struct mpd_client *self = user_data;
|
|
|
|
if (socket_io_try_read (self->socket, &self->read_buffer) != SOCKET_IO_OK
|
|
|
|
|| !mpd_client_process_input (self)
|
|
|
|
|| socket_io_try_write (self->socket, &self->write_buffer) != SOCKET_IO_OK)
|
|
|
|
mpd_client_fail (self);
|
|
|
|
else
|
|
|
|
mpd_client_update_poller (self);
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_client_must_quote (const char *s)
|
|
|
|
{
|
|
|
|
if (!*s)
|
|
|
|
return true;
|
|
|
|
for (; *s; s++)
|
2024-08-07 22:04:00 +02:00
|
|
|
if ((unsigned char) *s <= ' ' || *s == '"' || *s == '\'')
|
2016-10-11 09:37:22 +02:00
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-08-07 22:04:00 +02:00
|
|
|
static bool
|
|
|
|
mpd_client_must_escape_in_quote (char c)
|
|
|
|
{
|
|
|
|
return c == '"' || c == '\'' || c == '\\';
|
|
|
|
}
|
|
|
|
|
2016-10-11 09:37:22 +02:00
|
|
|
static void
|
|
|
|
mpd_client_quote (const char *s, struct str *output)
|
|
|
|
{
|
|
|
|
str_append_c (output, '"');
|
|
|
|
for (; *s; s++)
|
|
|
|
{
|
2024-08-07 22:04:00 +02:00
|
|
|
if (mpd_client_must_escape_in_quote (*s))
|
2016-10-11 09:37:22 +02:00
|
|
|
str_append_c (output, '\\');
|
|
|
|
str_append_c (output, *s);
|
|
|
|
}
|
|
|
|
str_append_c (output, '"');
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Beware that delivery of the event isn't deferred and you musn't make
|
|
|
|
/// changes to the interface while processing the event!
|
|
|
|
static void
|
|
|
|
mpd_client_add_task
|
|
|
|
(struct mpd_client *self, mpd_client_task_cb cb, void *user_data)
|
|
|
|
{
|
|
|
|
// This only has meaning with command_list_ok_begin, and then it requires
|
|
|
|
// special handling (all in-list tasks need to be specially marked and
|
|
|
|
// later flushed if an early ACK or OK arrives).
|
|
|
|
hard_assert (!self->in_list);
|
|
|
|
|
|
|
|
struct mpd_client_task *task = xcalloc (1, sizeof *self);
|
|
|
|
task->callback = cb;
|
|
|
|
task->user_data = user_data;
|
|
|
|
LIST_APPEND_WITH_TAIL (self->tasks, self->tasks_tail, task);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Send a command. Remember to call mpd_client_add_task() to handle responses,
|
|
|
|
/// unless the command is being sent in a list.
|
|
|
|
static void mpd_client_send_command
|
|
|
|
(struct mpd_client *self, const char *command, ...) ATTRIBUTE_SENTINEL;
|
|
|
|
|
2017-06-24 01:43:31 +02:00
|
|
|
/// Avoid calling this method directly if you don't want things to explode
|
2016-10-11 09:37:22 +02:00
|
|
|
static void
|
2017-06-24 01:43:31 +02:00
|
|
|
mpd_client_send_command_raw (struct mpd_client *self, const char *raw)
|
2016-10-11 09:37:22 +02:00
|
|
|
{
|
|
|
|
// Automatically interrupt idle mode
|
|
|
|
if (self->idling)
|
|
|
|
{
|
|
|
|
poller_timer_reset (&self->timeout_timer);
|
|
|
|
|
|
|
|
self->idling = false;
|
|
|
|
self->idling_subsystems = 0;
|
|
|
|
mpd_client_send_command (self, "noidle", NULL);
|
|
|
|
}
|
|
|
|
|
2017-06-24 01:43:31 +02:00
|
|
|
if (self->on_io_hook)
|
|
|
|
self->on_io_hook (self->user_data, true, raw);
|
|
|
|
|
|
|
|
str_append (&self->write_buffer, raw);
|
|
|
|
str_append_c (&self->write_buffer, '\n');
|
|
|
|
|
|
|
|
mpd_client_update_poller (self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_send_commandv (struct mpd_client *self, char **fields)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
struct str line = str_make ();
|
2017-06-24 01:43:31 +02:00
|
|
|
for (; *fields; fields++)
|
2016-10-11 09:37:22 +02:00
|
|
|
{
|
|
|
|
if (line.len)
|
|
|
|
str_append_c (&line, ' ');
|
|
|
|
|
2017-06-24 01:43:31 +02:00
|
|
|
if (mpd_client_must_quote (*fields))
|
|
|
|
mpd_client_quote (*fields, &line);
|
2016-10-11 09:37:22 +02:00
|
|
|
else
|
2017-06-24 01:43:31 +02:00
|
|
|
str_append (&line, *fields);
|
2016-10-11 09:37:22 +02:00
|
|
|
}
|
2017-06-24 01:43:31 +02:00
|
|
|
mpd_client_send_command_raw (self, line.str);
|
2016-10-11 09:37:22 +02:00
|
|
|
str_free (&line);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_send_command (struct mpd_client *self, const char *command, ...)
|
|
|
|
{
|
2017-06-22 18:14:49 +02:00
|
|
|
struct strv v = strv_make ();
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
va_list ap;
|
|
|
|
va_start (ap, command);
|
|
|
|
for (; command; command = va_arg (ap, const char *))
|
2017-01-23 23:07:24 +01:00
|
|
|
strv_append (&v, command);
|
2016-10-11 09:37:22 +02:00
|
|
|
va_end (ap);
|
|
|
|
|
|
|
|
mpd_client_send_commandv (self, v.vector);
|
2017-01-23 23:01:20 +01:00
|
|
|
strv_free (&v);
|
2016-10-11 09:37:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
2018-10-11 21:00:00 +02:00
|
|
|
/// "On success for all commands, OK is returned. If a command fails, no more
|
|
|
|
/// commands are executed and the appropriate ACK error is returned"
|
2016-10-11 09:37:22 +02:00
|
|
|
static void
|
|
|
|
mpd_client_list_begin (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
hard_assert (!self->in_list);
|
|
|
|
mpd_client_send_command (self, "command_list_begin", NULL);
|
|
|
|
self->in_list = true;
|
|
|
|
}
|
|
|
|
|
2016-10-13 01:12:34 +02:00
|
|
|
/// Beware that "list_OK" turns into NULL values in the output vector
|
|
|
|
static void
|
|
|
|
mpd_client_list_ok_begin (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
hard_assert (!self->in_list);
|
|
|
|
mpd_client_send_command (self, "command_list_ok_begin", NULL);
|
|
|
|
self->in_list = true;
|
|
|
|
}
|
|
|
|
|
2016-10-11 09:37:22 +02:00
|
|
|
/// End a list of commands. Remember to call mpd_client_add_task()
|
|
|
|
/// to handle the summary response.
|
|
|
|
static void
|
|
|
|
mpd_client_list_end (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
hard_assert (self->in_list);
|
|
|
|
mpd_client_send_command (self, "command_list_end", NULL);
|
|
|
|
self->in_list = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_resolve_subsystem (const char *name, unsigned *output)
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < N_ELEMENTS (mpd_subsystem_names); i++)
|
|
|
|
if (!strcasecmp_ascii (name, mpd_subsystem_names[i]))
|
|
|
|
{
|
|
|
|
*output |= 1 << i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_on_idle_return (const struct mpd_response *response,
|
2017-01-23 23:01:20 +01:00
|
|
|
const struct strv *data, void *user_data)
|
2016-10-11 09:37:22 +02:00
|
|
|
{
|
|
|
|
(void) response;
|
|
|
|
|
|
|
|
struct mpd_client *self = user_data;
|
|
|
|
unsigned subsystems = 0;
|
|
|
|
for (size_t i = 0; i < data->len; i++)
|
|
|
|
{
|
|
|
|
char *value, *key;
|
|
|
|
if (!(key = mpd_client_parse_kv (data->vector[i], &value)))
|
|
|
|
print_debug ("%s: %s", "erroneous MPD output", data->vector[i]);
|
|
|
|
else if (strcasecmp_ascii (key, "changed"))
|
|
|
|
print_debug ("%s: %s", "unexpected idle key", key);
|
|
|
|
else if (!mpd_resolve_subsystem (value, &subsystems))
|
|
|
|
print_debug ("%s: %s", "unknown subsystem", value);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Not resetting "idling" here, we may send an extra "noidle" no problem
|
|
|
|
if (self->on_event && subsystems)
|
|
|
|
self->on_event (subsystems, self->user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void mpd_client_idle (struct mpd_client *self, unsigned subsystems);
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_on_timeout (void *user_data)
|
|
|
|
{
|
|
|
|
struct mpd_client *self = user_data;
|
|
|
|
|
|
|
|
// Abort and immediately restore the current idle so that MPD doesn't
|
|
|
|
// disconnect us, even though the documentation says this won't happen.
|
|
|
|
// Just sending this out should bring a dead connection down over TCP.
|
|
|
|
// TODO: set another timer to make sure we get a reply
|
|
|
|
mpd_client_idle (self, self->idling_subsystems);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// When not expecting to send any further commands, you should call this
|
|
|
|
/// in order to keep the connection alive. Or to receive updates.
|
|
|
|
static void
|
|
|
|
mpd_client_idle (struct mpd_client *self, unsigned subsystems)
|
|
|
|
{
|
|
|
|
hard_assert (!self->in_list);
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
struct strv v = strv_make ();
|
2017-01-23 23:07:24 +01:00
|
|
|
strv_append (&v, "idle");
|
2016-10-11 09:37:22 +02:00
|
|
|
for (size_t i = 0; i < N_ELEMENTS (mpd_subsystem_names); i++)
|
|
|
|
if (subsystems & (1 << i))
|
2017-01-23 23:07:24 +01:00
|
|
|
strv_append (&v, mpd_subsystem_names[i]);
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
mpd_client_send_commandv (self, v.vector);
|
2017-01-23 23:01:20 +01:00
|
|
|
strv_free (&v);
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
self->timeout_timer.dispatcher = mpd_client_on_timeout;
|
|
|
|
self->timeout_timer.user_data = self;
|
|
|
|
poller_timer_set (&self->timeout_timer, 5 * 60 * 1000);
|
|
|
|
|
|
|
|
mpd_client_add_task (self, mpd_client_on_idle_return, self);
|
|
|
|
self->idling = true;
|
|
|
|
self->idling_subsystems = subsystems;
|
|
|
|
}
|
|
|
|
|
|
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_finish_connection (struct mpd_client *self, int socket)
|
|
|
|
{
|
|
|
|
set_blocking (socket, false);
|
|
|
|
self->socket = socket;
|
|
|
|
self->state = MPD_CONNECTED;
|
|
|
|
|
2017-06-22 18:14:49 +02:00
|
|
|
self->socket_event = poller_fd_make (self->poller, self->socket);
|
2016-10-11 09:37:22 +02:00
|
|
|
self->socket_event.dispatcher = mpd_client_on_ready;
|
|
|
|
self->socket_event.user_data = self;
|
|
|
|
|
|
|
|
mpd_client_update_poller (self);
|
|
|
|
|
|
|
|
if (self->on_connected)
|
|
|
|
self->on_connected (self->user_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_destroy_connector (struct mpd_client *self)
|
|
|
|
{
|
|
|
|
if (self->connector)
|
|
|
|
connector_free (self->connector);
|
|
|
|
free (self->connector);
|
|
|
|
self->connector = NULL;
|
|
|
|
|
|
|
|
// Not connecting anymore
|
|
|
|
self->state = MPD_DISCONNECTED;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_on_connector_failure (void *user_data)
|
|
|
|
{
|
|
|
|
struct mpd_client *self = user_data;
|
|
|
|
mpd_client_destroy_connector (self);
|
|
|
|
mpd_client_fail (self);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
mpd_client_on_connector_connected
|
|
|
|
(void *user_data, int socket, const char *host)
|
|
|
|
{
|
|
|
|
(void) host;
|
|
|
|
|
|
|
|
struct mpd_client *self = user_data;
|
|
|
|
mpd_client_destroy_connector (self);
|
|
|
|
mpd_client_finish_connection (self, socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_client_connect_unix (struct mpd_client *self, const char *address,
|
|
|
|
struct error **e)
|
|
|
|
{
|
|
|
|
int fd = socket (AF_UNIX, SOCK_STREAM, 0);
|
|
|
|
if (fd == -1)
|
|
|
|
return error_set (e, "%s: %s", "socket", strerror (errno));
|
|
|
|
|
|
|
|
// Expand tilde if needed
|
|
|
|
char *expanded = resolve_filename (address, xstrdup);
|
|
|
|
|
2023-07-24 08:32:54 +02:00
|
|
|
struct sockaddr_un sau;
|
|
|
|
sau.sun_family = AF_UNIX;
|
|
|
|
strncpy (sau.sun_path, expanded, sizeof sau.sun_path);
|
|
|
|
sau.sun_path[sizeof sau.sun_path - 1] = 0;
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
free (expanded);
|
|
|
|
|
2023-07-24 08:32:54 +02:00
|
|
|
if (connect (fd, (struct sockaddr *) &sau, sizeof sau))
|
2017-01-18 16:34:25 +01:00
|
|
|
{
|
|
|
|
error_set (e, "%s: %s", "connect", strerror (errno));
|
|
|
|
xclose (fd);
|
|
|
|
return false;
|
|
|
|
}
|
2016-10-11 09:37:22 +02:00
|
|
|
|
|
|
|
mpd_client_finish_connection (self, fd);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool
|
|
|
|
mpd_client_connect (struct mpd_client *self, const char *address,
|
|
|
|
const char *service, struct error **e)
|
|
|
|
{
|
|
|
|
hard_assert (self->state == MPD_DISCONNECTED);
|
|
|
|
|
|
|
|
// If it looks like a path, assume it's a UNIX socket
|
|
|
|
if (strchr (address, '/'))
|
|
|
|
return mpd_client_connect_unix (self, address, e);
|
|
|
|
|
|
|
|
struct connector *connector = xmalloc (sizeof *connector);
|
|
|
|
connector_init (connector, self->poller);
|
|
|
|
self->connector = connector;
|
|
|
|
|
|
|
|
connector->user_data = self;
|
|
|
|
connector->on_connected = mpd_client_on_connector_connected;
|
|
|
|
connector->on_failure = mpd_client_on_connector_failure;
|
|
|
|
|
|
|
|
connector_add_target (connector, address, service);
|
|
|
|
self->state = MPD_CONNECTING;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|