Commit 2b10ecfa authored by Max Kellermann's avatar Max Kellermann

IcyMetadataParser: more robust tag parser

Allow semicolons and single quotes in the stream title. This is not part of any specification, but found in real life.
parent f7eb2b69
ver 0.18.8 (not yet released)
* decoder
- ffmpeg: support libav v10_alpha1
* more robust Icy-Metadata parser
* fix Solaris build failure
ver 0.18.7 (2013/01/13)
......
......@@ -81,31 +81,85 @@ icy_add_item(Tag &tag, TagType type, const char *value)
}
static void
icy_parse_tag_item(Tag &tag, const char *item)
icy_parse_tag_item(Tag &tag, const char *name, const char *value)
{
gchar **p = g_strsplit(item, "=", 0);
if (p[0] != nullptr && p[1] != nullptr) {
if (strcmp(p[0], "StreamTitle") == 0)
icy_add_item(tag, TAG_TITLE, p[1]);
else
FormatDebug(icy_metadata_domain,
"unknown icy-tag: '%s'", p[0]);
}
if (strcmp(name, "StreamTitle") == 0)
icy_add_item(tag, TAG_TITLE, value);
else
FormatDebug(icy_metadata_domain,
"unknown icy-tag: '%s'", name);
}
/**
* Find a single quote that is followed by a semicolon (or by the end
* of the string). If that fails, return the first single quote. If
* that also fails, return #end.
*/
static char *
find_end_quote(char *p, char *const end)
{
char *fallback = std::find(p, end, '\'');
if (fallback >= end - 1 || fallback[1] == ';')
return fallback;
g_strfreev(p);
p = fallback + 1;
while (true) {
p = std::find(p, end, '\'');
if (p == end)
return fallback;
if (p == end - 1 || p[1] == ';')
return p;
++p;
}
}
static Tag *
icy_parse_tag(const char *p)
icy_parse_tag(char *p, char *const end)
{
assert(p != nullptr);
assert(end != nullptr);
assert(p <= end);
Tag *tag = new Tag();
gchar **items = g_strsplit(p, ";", 0);
for (unsigned i = 0; items[i] != nullptr; ++i)
icy_parse_tag_item(*tag, items[i]);
while (p != end) {
const char *const name = p;
char *eq = std::find(p, end, '=');
if (eq == end)
break;
*eq = 0;
p = eq + 1;
if (*p != '\'') {
/* syntax error; skip to the next semicolon,
try to recover */
char *semicolon = std::find(p, end, ';');
if (semicolon == end)
break;
p = semicolon + 1;
continue;
}
++p;
const char *const value = p;
char *quote = find_end_quote(p, end);
if (quote == end)
break;
*quote = 0;
p = quote + 1;
g_strfreev(items);
icy_parse_tag_item(*tag, name, value);
char *semicolon = std::find(p, end, ';');
if (semicolon == end)
break;
p = semicolon + 1;
}
return tag;
}
......@@ -152,15 +206,11 @@ IcyMetaDataParser::Meta(const void *data, size_t length)
++length;
if (meta_position == meta_size) {
/* null-terminate the string */
meta_data[meta_size] = 0;
/* parse */
delete tag;
tag = icy_parse_tag(meta_data);
tag = icy_parse_tag(meta_data, meta_data + meta_size);
g_free(meta_data);
/* change back to normal data mode */
......
......@@ -14,6 +14,17 @@
#include <string>
#include <string.h>
static Tag *
icy_parse_tag(const char *p)
{
char *q = strdup(p);
Tag *tag = icy_parse_tag(q, q + strlen(q));
free(q);
return tag;
}
static void
CompareTagTitle(const Tag &tag, const std::string &title)
{
......@@ -51,10 +62,14 @@ public:
TestIcyParserTitle("StreamTitle='foo bar'", "foo bar");
TestIcyParserTitle("StreamTitle='foo bar';", "foo bar");
TestIcyParserTitle("StreamTitle='foo\"bar';", "foo\"bar");
TestIcyParserTitle("StreamTitle='foo=bar';", "foo=bar");
TestIcyParserTitle("a=b;StreamTitle='foo';", "foo");
TestIcyParserTitle("a=;StreamTitle='foo';", "foo");
TestIcyParserTitle("a=b;StreamTitle='foo';c=d", "foo");
TestIcyParserTitle("a=b;StreamTitle='foo'", "foo");
TestIcyParserTitle("a='b;c';StreamTitle='foo;bar'", "foo;bar");
TestIcyParserTitle("a='b'c';StreamTitle='foo'bar'", "foo'bar");
TestIcyParserTitle("StreamTitle='fo'o'b'ar';a='b'c'd'", "fo'o'b'ar");
}
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment