From da67260c955ebe1bc590b461e5ee8c9665f26f5b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 20 Jan 2014 17:20:57 +0100 Subject: new developer mailing list --- configure.ac | 2 +- doc/developer.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index b176aeedd..fc94f4c1e 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ AC_PREREQ(2.60) -AC_INIT(mpd, 0.18.8, musicpd-dev-team@lists.sourceforge.net) +AC_INIT(mpd, 0.18.8, mpd-devel@musicpd.org) VERSION_MAJOR=0 VERSION_MINOR=18 diff --git a/doc/developer.xml b/doc/developer.xml index e9022172c..729e6a513 100644 --- a/doc/developer.xml +++ b/doc/developer.xml @@ -155,7 +155,7 @@ foo(const char *abc, int xyz) Send your patches to the mailing list: - musicpd-dev-team@lists.sourceforge.net + mpd-devel@musicpd.org -- cgit v1.2.3 From f7eb2b697ef6ac523632ad27b43b185f5901438c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Mon, 27 Jan 2014 09:51:31 +0100 Subject: test/test_icy_parser: unit test for IcyMetaDataParser.cxx --- Makefile.am | 11 ++++++++ test/test_icy_parser.cxx | 70 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 test/test_icy_parser.cxx diff --git a/Makefile.am b/Makefile.am index 7240cb3f1..c7cf631bb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1061,6 +1061,7 @@ C_TESTS = \ test/test_util \ test/test_byte_reverse \ test/test_mixramp \ + test/test_icy_parser \ test/test_pcm \ test/test_queue_priority @@ -1496,6 +1497,16 @@ test_test_mixramp_LDADD = \ $(GLIB_LIBS) \ $(CPPUNIT_LIBS) +test_test_icy_parser_SOURCES = \ + src/Log.cxx \ + test/test_icy_parser.cxx +test_test_icy_parser_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0 +test_test_icy_parser_CXXFLAGS = $(AM_CXXFLAGS) -Wno-error=deprecated-declarations +test_test_icy_parser_LDADD = \ + libtag.a \ + $(GLIB_LIBS) \ + $(CPPUNIT_LIBS) + test_test_pcm_SOURCES = \ test/test_pcm_util.hxx \ test/test_pcm_dither.cxx \ diff --git a/test/test_icy_parser.cxx b/test/test_icy_parser.cxx new file mode 100644 index 000000000..83925cc99 --- /dev/null +++ b/test/test_icy_parser.cxx @@ -0,0 +1,70 @@ +/* + * Unit tests for class IcyMetaDataParser. + */ + +#include "config.h" + +/* include the .cxx file to get access to internal functions */ +#include "IcyMetaDataParser.cxx" + +#include +#include +#include +#include + +#include + +static void +CompareTagTitle(const Tag &tag, const std::string &title) +{ + CPPUNIT_ASSERT_EQUAL(1u, tag.num_items); + + const TagItem &item = *tag.items[0]; + CPPUNIT_ASSERT_EQUAL(TAG_TITLE, item.type); + CPPUNIT_ASSERT_EQUAL(title, std::string(item.value)); +} + +static void +TestIcyParserTitle(const char *input, const char *title) +{ + Tag *tag = icy_parse_tag(input); + CompareTagTitle(*tag, title); + delete tag; +} + +static void +TestIcyParserEmpty(const char *input) +{ + Tag *tag = icy_parse_tag(input); + CPPUNIT_ASSERT_EQUAL(0u, tag->num_items); + delete tag; +} + +class IcyTest : public CppUnit::TestFixture { + CPPUNIT_TEST_SUITE(IcyTest); + CPPUNIT_TEST(TestIcyMetadataParser); + CPPUNIT_TEST_SUITE_END(); + +public: + void TestIcyMetadataParser() { + TestIcyParserEmpty("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"); + } +}; + +CPPUNIT_TEST_SUITE_REGISTRATION(IcyTest); + +int +main(gcc_unused int argc, gcc_unused char **argv) +{ + CppUnit::TextUi::TestRunner runner; + auto ®istry = CppUnit::TestFactoryRegistry::getRegistry(); + runner.addTest(registry.makeTest()); + return runner.run() ? EXIT_SUCCESS : EXIT_FAILURE; +} -- cgit v1.2.3 From 2b10ecfa37e273c752c3f87e2491e2a1a5f0ae58 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Tue, 21 Jan 2014 14:51:35 +0100 Subject: 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. --- NEWS | 1 + src/IcyMetaDataParser.cxx | 92 ++++++++++++++++++++++++++++++++++++----------- test/test_icy_parser.cxx | 15 ++++++++ 3 files changed, 87 insertions(+), 21 deletions(-) diff --git a/NEWS b/NEWS index 68efb8a85..1a0c5c415 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ 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) diff --git a/src/IcyMetaDataParser.cxx b/src/IcyMetaDataParser.cxx index 8861efb2e..bfa2e8558 100644 --- a/src/IcyMetaDataParser.cxx +++ b/src/IcyMetaDataParser.cxx @@ -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 */ diff --git a/test/test_icy_parser.cxx b/test/test_icy_parser.cxx index 83925cc99..2abf60f9e 100644 --- a/test/test_icy_parser.cxx +++ b/test/test_icy_parser.cxx @@ -14,6 +14,17 @@ #include +#include + +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"); } }; -- cgit v1.2.3