Make PLS parser more permissive.

The PLS format is not clearly specified, some rules are just assumed
and files don't always respect them.
We now only look for 'File' entries, since that's the only thing we
actually use. We ignore the Version, NumberOfEntries, Title, Length
and any other unrecognized tags.

Task-number: QTBUG-40515
Change-Id: I9c176b7b68fd1441abbd50364f88994ad5d6236f
Reviewed-by: Christian Stromme <christian.stromme@digia.com>
This commit is contained in:
Yoann Lopes
2014-08-28 16:00:15 +02:00
parent 90fd3ac399
commit 4c5aec9bb6
3 changed files with 26 additions and 102 deletions

View File

@@ -181,27 +181,9 @@ class PLSParser : public ParserBase
public: public:
PLSParser(QObject *parent) PLSParser(QObject *parent)
: ParserBase(parent) : ParserBase(parent)
, m_state(Header)
, m_count(0)
, m_readFlags(0)
{ {
} }
enum ReadFlags
{
FileRead = 0x1,
TitleRead = 0x2,
LengthRead = 0x4,
All = FileRead | TitleRead | LengthRead
};
enum State
{
Header,
Track,
Footer
};
/* /*
* *
The format is essentially that of an INI file structured as follows: The format is essentially that of an INI file structured as follows:
@@ -240,89 +222,25 @@ NumberOfEntries=2
Version=2 Version=2
*/ */
inline bool containsFlag(const ReadFlags& flag) void parseLine(int, const QString &line, const QUrl &root)
{ {
return (m_readFlags & int(flag)) == flag; // We ignore everything but 'File' entries, since that's the only thing we care about.
if (!line.startsWith(QLatin1String("File")))
return;
QString value = getValue(line);
if (value.isEmpty())
return;
emit newItem(expandToFullPath(root, value));
} }
inline void setFlag(const ReadFlags& flag) QString getValue(const QString& line) {
{
m_readFlags |= int(flag);
}
void parseLine(int lineIndex, const QString &line, const QUrl &root)
{
switch (m_state) {
case Header:
if (line == QLatin1String("[playlist]")) {
m_state = Track;
setCount(1);
}
break;
case Track:
if (!containsFlag(FileRead) && line.startsWith(m_fileName)) {
m_item[QLatin1String("url")] = expandToFullPath(root, getValue(lineIndex, line));
setFlag(FileRead);
} else if (!containsFlag(TitleRead) && line.startsWith(m_titleName)) {
m_item[QMediaMetaData::Title] = getValue(lineIndex, line);
setFlag(TitleRead);
} else if (!containsFlag(LengthRead) && line.startsWith(m_lengthName)) {
//convert from seconds to miliseconds
int length = getValue(lineIndex, line).toInt();
if (length > 0)
m_item[QMediaMetaData::Duration] = length * 1000;
setFlag(LengthRead);
} else if (line.startsWith(QLatin1String("NumberOfEntries"))) {
m_state = Footer;
int entries = getValue(lineIndex, line).toInt();
int count = m_readFlags == 0 ? (m_count - 1) : m_count;
if (entries != count) {
emit error(QPlaylistFileParser::FormatError, tr("Error parsing playlist: %1, expected count = %2").
arg(line, QString::number(count)));
}
break;
}
if (m_readFlags == int(All)) {
emit newItem(m_item);
setCount(m_count + 1);
}
break;
case Footer:
if (line.startsWith(QLatin1String("Version"))) {
int version = getValue(lineIndex, line).toInt();
if (version != 2)
emit error(QPlaylistFileParser::FormatError, QString(tr("Error parsing playlist at line[%1], expected version = 2")).arg(line));
}
break;
}
}
QString getValue(int lineIndex, const QString& line) {
int start = line.indexOf('='); int start = line.indexOf('=');
if (start < 0) { if (start < 0)
emit error(QPlaylistFileParser::FormatError, QString(tr("Error parsing playlist at line[%1]:%2")).arg(QString::number(lineIndex), line));
return QString(); return QString();
}
return line.midRef(start + 1).trimmed().toString(); return line.midRef(start + 1).trimmed().toString();
} }
void setCount(int count) {
m_count = count;
m_fileName = QStringLiteral("File%1").arg(count);
m_titleName = QStringLiteral("Title%1").arg(count);
m_lengthName = QStringLiteral("Length%1").arg(count);
m_item.clear();
m_readFlags = 0;
}
private:
State m_state;
int m_count;
QString m_titleName;
QString m_fileName;
QString m_lengthName;
QVariantMap m_item;
int m_readFlags;
}; };
} }

View File

@@ -471,14 +471,15 @@ void tst_QMediaPlaylist::loadPLSFile()
QVERIFY(!loadFailedSpy.isEmpty()); QVERIFY(!loadFailedSpy.isEmpty());
QVERIFY(playlist.error() != QMediaPlaylist::NoError); QVERIFY(playlist.error() != QMediaPlaylist::NoError);
// Try to load bogus playlist // Try to load empty playlist
loadSpy.clear(); loadSpy.clear();
loadFailedSpy.clear(); loadFailedSpy.clear();
testFileName = QFINDTESTDATA("testdata/trash.pls"); testFileName = QFINDTESTDATA("testdata/empty.pls");
playlist.load(QUrl::fromLocalFile(testFileName)); playlist.load(QUrl::fromLocalFile(testFileName));
QTRY_VERIFY(loadSpy.isEmpty()); QTRY_VERIFY(!loadSpy.isEmpty());
QVERIFY(!loadFailedSpy.isEmpty()); QVERIFY(loadFailedSpy.isEmpty());
QVERIFY(playlist.error() == QMediaPlaylist::FormatError); QCOMPARE(playlist.error(), QMediaPlaylist::NoError);
QCOMPARE(playlist.mediaCount(), 0);
// Try to load regular playlist // Try to load regular playlist
loadSpy.clear(); loadSpy.clear();
@@ -505,13 +506,18 @@ void tst_QMediaPlaylist::loadPLSFile()
QCOMPARE(playlist.media(6).canonicalUrl(), QUrl::fromLocalFile(testFileName)); QCOMPARE(playlist.media(6).canonicalUrl(), QUrl::fromLocalFile(testFileName));
// Try to load a totem-pl generated playlist // Try to load a totem-pl generated playlist
// (Format doesn't respect the spec)
loadSpy.clear(); loadSpy.clear();
loadFailedSpy.clear(); loadFailedSpy.clear();
playlist.clear();
testFileName = QFINDTESTDATA("testdata/totem-pl-example.pls"); testFileName = QFINDTESTDATA("testdata/totem-pl-example.pls");
playlist.load(QUrl::fromLocalFile(testFileName)); playlist.load(QUrl::fromLocalFile(testFileName));
QTRY_VERIFY(loadSpy.isEmpty()); QTRY_VERIFY(!loadSpy.isEmpty());
QVERIFY(!loadFailedSpy.isEmpty()); QVERIFY(loadFailedSpy.isEmpty());
QVERIFY(playlist.error() == QMediaPlaylist::FormatError); QCOMPARE(playlist.error(), QMediaPlaylist::NoError);
QCOMPARE(playlist.mediaCount(), 1);
QCOMPARE(playlist.media(0).canonicalUrl(), QUrl(QLatin1String("http://test.host/path")));
// check ability to load from QNetworkRequest // check ability to load from QNetworkRequest
loadSpy.clear(); loadSpy.clear();