tagparser/vorbis/vorbiscommentfield.cpp

244 lines
10 KiB
C++
Raw Permalink Normal View History

2015-09-06 19:57:33 +02:00
#include "./vorbiscommentfield.h"
#include "./vorbiscommentids.h"
2015-04-22 19:22:01 +02:00
2016-05-14 00:24:01 +02:00
#include "../flac/flacmetadata.h"
2015-09-06 19:57:33 +02:00
#include "../ogg/oggiterator.h"
2015-04-22 19:22:01 +02:00
#include "../diagnostics.h"
2018-03-07 01:17:50 +01:00
#include "../exceptions.h"
2015-04-22 19:22:01 +02:00
2018-03-07 01:17:50 +01:00
#include <c++utilities/conversion/binaryconversion.h>
2019-03-13 19:06:42 +01:00
#include <c++utilities/conversion/stringbuilder.h>
2018-03-07 01:17:50 +01:00
#include <c++utilities/conversion/stringconversion.h>
2015-04-22 19:22:01 +02:00
#include <c++utilities/io/binaryreader.h>
#include <c++utilities/io/binarywriter.h>
2015-08-16 23:39:42 +02:00
#include <iostream>
2017-02-05 21:02:40 +01:00
#include <memory>
2015-08-16 23:39:42 +02:00
2015-04-22 19:22:01 +02:00
using namespace std;
2019-06-10 22:49:11 +02:00
using namespace CppUtilities;
2015-04-22 19:22:01 +02:00
namespace TagParser {
2015-04-22 19:22:01 +02:00
/*!
* \class TagParser::VorbisCommentField
2015-04-22 19:22:01 +02:00
* \brief The VorbisCommentField class is used by VorbisComment to store the fields.
*/
/*!
* \brief Constructs a new Vorbis comment field.
*/
VorbisCommentField::VorbisCommentField()
2018-03-07 01:17:50 +01:00
{
}
2015-04-22 19:22:01 +02:00
/*!
* \brief Constructs a new Vorbis comment with the specified \a id and \a value.
*/
2018-03-07 01:17:50 +01:00
VorbisCommentField::VorbisCommentField(const IdentifierType &id, const TagValue &value)
: TagField<VorbisCommentField>(id, value)
{
}
2015-04-22 19:22:01 +02:00
/*!
2016-05-16 20:56:53 +02:00
* \brief Internal implementation for parsing.
2015-04-22 19:22:01 +02:00
*/
2019-03-13 19:06:42 +01:00
template <class StreamType> void VorbisCommentField::internalParse(StreamType &stream, std::uint64_t &maxSize, Diagnostics &diag)
2015-04-22 19:22:01 +02:00
{
static const string context("parsing Vorbis comment field");
char buff[4];
2018-03-07 01:17:50 +01:00
if (maxSize < 4) {
diag.emplace_back(DiagLevel::Critical, argsToString("Field expected at ", static_cast<std::streamoff>(stream.tellg()), '.'), context);
2016-05-16 20:56:53 +02:00
throw TruncatedDataException();
} else {
maxSize -= 4;
}
stream.read(buff, 4);
2018-03-07 01:17:50 +01:00
if (const auto size = LE::toUInt32(buff)) { // read size
if (size <= maxSize) {
2016-05-16 20:56:53 +02:00
maxSize -= size;
2016-01-17 19:32:58 +01:00
// read data
2018-03-07 01:17:50 +01:00
auto data = make_unique<char[]>(size);
2016-05-16 20:56:53 +02:00
stream.read(data.get(), size);
2019-03-13 19:06:42 +01:00
std::uint32_t idSize = 0;
2018-03-07 01:17:50 +01:00
for (const char *i = data.get(), *end = data.get() + size; i != end && *i != '='; ++i, ++idSize)
;
2016-01-17 19:32:58 +01:00
// extract id
setId(string(data.get(), idSize));
2018-03-07 01:17:50 +01:00
if (!idSize) {
2016-01-17 19:32:58 +01:00
// empty field ID
diag.emplace_back(
DiagLevel::Critical, argsToString("The field ID at ", static_cast<std::streamoff>(stream.tellg()), " is empty."), context);
2015-08-16 23:39:42 +02:00
throw InvalidDataException();
2018-03-07 01:17:50 +01:00
} else if (id() == VorbisCommentIds::cover()) {
2016-01-17 19:32:58 +01:00
// extract cover value
try {
auto decoded = decodeBase64(data.get() + idSize + 1, size - idSize - 1);
2016-05-14 00:24:01 +02:00
stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
2016-05-14 00:24:01 +02:00
bufferStream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
#else
bufferStream.write(reinterpret_cast<const char *>(decoded.first.get()), decoded.second);
#endif
2016-05-14 00:24:01 +02:00
FlacMetaDataBlockPicture pictureBlock(value());
2016-05-16 20:56:53 +02:00
pictureBlock.parse(bufferStream, decoded.second);
2016-05-14 00:24:01 +02:00
setTypeInfo(pictureBlock.pictureType());
2018-03-07 01:17:50 +01:00
} catch (const TruncatedDataException &) {
diag.emplace_back(DiagLevel::Critical, "METADATA_BLOCK_PICTURE is truncated.", context);
2016-05-16 20:56:53 +02:00
throw;
2018-03-07 01:17:50 +01:00
} catch (const ConversionException &) {
diag.emplace_back(DiagLevel::Critical, "Base64 coding of METADATA_BLOCK_PICTURE is invalid.", context);
2016-01-17 19:32:58 +01:00
throw InvalidDataException();
2019-03-13 19:06:42 +01:00
} catch (const std::ios_base::failure &failure) {
diag.emplace_back(DiagLevel::Critical,
argsToString("An IO error occurred when reading the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
2016-06-14 22:53:43 +02:00
throw Failure();
2016-01-17 19:32:58 +01:00
}
2018-03-07 01:17:50 +01:00
} else if (id().size() + 1 < size) {
const auto str = std::string_view(data.get() + idSize + 1, size - idSize - 1);
if (id() == VorbisCommentIds::rating()) {
try {
// set rating as Popularity to preserve the scale information
value().assignPopularity(Popularity{ .rating = stringToNumber<double>(str), .scale = TagType::VorbisComment });
} catch (const ConversionException &) {
// fallback to text
value().assignText(str, TagTextEncoding::Utf8);
diag.emplace_back(DiagLevel::Warning, argsToString("The rating is not a number."), context);
}
} else {
// extract other values (as string)
value().assignText(str, TagTextEncoding::Utf8);
}
2015-08-16 23:39:42 +02:00
}
2016-01-17 19:32:58 +01:00
} else {
diag.emplace_back(DiagLevel::Critical, argsToString("Field at ", static_cast<std::streamoff>(stream.tellg()), " is truncated."), context);
2016-01-17 19:32:58 +01:00
throw TruncatedDataException();
2015-04-22 19:22:01 +02:00
}
}
}
2016-05-16 20:56:53 +02:00
/*!
* \brief Parses a field using the specified \a iterator.
*
* The currentCharacterOffset() of the iterator is expected to be
* at the beginning of the field to be parsed.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing
2016-05-16 20:56:53 +02:00
* error occurs.
*/
void VorbisCommentField::parse(OggIterator &iterator, Diagnostics &diag)
2016-05-16 20:56:53 +02:00
{
2019-03-13 19:06:42 +01:00
std::uint64_t maxSize = iterator.streamSize() - iterator.currentCharacterOffset();
internalParse(iterator, maxSize, diag);
2016-05-16 20:56:53 +02:00
}
/*!
* \brief Parses a field using the specified \a iterator.
*
* The currentCharacterOffset() of the iterator is expected to be
* at the beginning of the field to be parsed.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing
2016-05-16 20:56:53 +02:00
* error occurs.
*/
2019-03-13 19:06:42 +01:00
void VorbisCommentField::parse(OggIterator &iterator, std::uint64_t &maxSize, Diagnostics &diag)
2016-05-16 20:56:53 +02:00
{
internalParse(iterator, maxSize, diag);
2016-05-16 20:56:53 +02:00
}
/*!
* \brief Parses a field from the specified \a stream.
*
* The position of the current character in the input stream is expected to be
* at the beginning of the field to be parsed.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a parsing
2016-05-16 20:56:53 +02:00
* error occurs.
*/
2019-03-13 19:06:42 +01:00
void VorbisCommentField::parse(istream &stream, std::uint64_t &maxSize, Diagnostics &diag)
2016-05-16 20:56:53 +02:00
{
internalParse(stream, maxSize, diag);
2016-05-16 20:56:53 +02:00
}
2015-04-22 19:22:01 +02:00
/*!
* \brief Writes the field to a stream using the specified \a writer.
*
* \throws Throws std::ios_base::failure when an IO error occurs.
* \throws Throws TagParser::Failure or a derived exception when a making
2015-04-22 19:22:01 +02:00
* error occurs.
2016-05-16 20:56:53 +02:00
* \returns Returns whether the field has been written. (Some fields might be skipped
* when specific \a flags are set.)
2015-04-22 19:22:01 +02:00
*/
bool VorbisCommentField::make(BinaryWriter &writer, VorbisCommentFlags flags, Diagnostics &diag)
2015-04-22 19:22:01 +02:00
{
static const string context("making Vorbis comment field");
2018-03-07 01:17:50 +01:00
if (id().empty()) {
diag.emplace_back(DiagLevel::Critical, "The field ID is empty.", context);
2015-04-22 19:22:01 +02:00
}
try {
2015-08-16 23:39:42 +02:00
// try to convert value to string
2015-04-22 19:22:01 +02:00
string valueString;
2018-03-07 01:17:50 +01:00
if (id() == VorbisCommentIds::cover()) {
if (flags & VorbisCommentFlags::NoCovers) {
2016-05-16 20:56:53 +02:00
return false;
}
2015-04-22 19:22:01 +02:00
// make cover
2018-03-07 01:17:50 +01:00
if (value().type() != TagDataType::Picture) {
diag.emplace_back(DiagLevel::Critical, "Assigned value of cover field is not picture data.", context);
2015-08-16 23:39:42 +02:00
throw InvalidDataException();
}
try {
2016-05-14 00:24:01 +02:00
FlacMetaDataBlockPicture pictureBlock(value());
pictureBlock.setPictureType(typeInfo());
const auto requiredSize = pictureBlock.requiredSize();
auto buffer = make_unique<char[]>(requiredSize);
stringstream bufferStream(ios_base::in | ios_base::out | ios_base::binary);
bufferStream.exceptions(ios_base::failbit | ios_base::badbit);
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
2016-05-14 00:24:01 +02:00
bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize);
#endif
2016-05-14 00:24:01 +02:00
pictureBlock.make(bufferStream);
#if defined(__GLIBCXX__) && !defined(_LIBCPP_VERSION)
bufferStream.read(buffer.get(), static_cast<std::streamsize>(requiredSize));
#endif
2019-03-13 19:06:42 +01:00
valueString = encodeBase64(reinterpret_cast<std::uint8_t *>(buffer.get()), requiredSize);
} catch (const Failure &) {
diag.emplace_back(DiagLevel::Critical, "Unable to make METADATA_BLOCK_PICTURE struct from the assigned value.", context);
throw;
2019-03-13 19:06:42 +01:00
} catch (const std::ios_base::failure &failure) {
diag.emplace_back(DiagLevel::Critical,
argsToString("An IO error occurred when writing the METADATA_BLOCK_PICTURE struct: ", failure.what()), context);
2015-08-16 23:39:42 +02:00
throw Failure();
}
} else if (value().type() == TagDataType::Popularity) {
valueString = value().toScaledPopularity(TagType::VorbisComment).toString();
2015-04-22 19:22:01 +02:00
} else {
// make normal string value
valueString = value().toString(TagTextEncoding::Utf8);
2015-04-22 19:22:01 +02:00
}
const auto size(valueString.size() + id().size() + 1);
2019-03-13 19:06:42 +01:00
if (size > numeric_limits<std::uint32_t>::max()) {
diag.emplace_back(DiagLevel::Critical, "Assigned value exceeds the maximum size.", context);
throw InvalidDataException();
}
2019-03-13 19:06:42 +01:00
writer.writeUInt32LE(static_cast<std::uint32_t>(size));
2015-04-22 19:22:01 +02:00
writer.writeString(id());
writer.writeChar('=');
writer.writeString(valueString);
2018-03-07 01:17:50 +01:00
} catch (const ConversionException &) {
diag.emplace_back(DiagLevel::Critical, "Assigned value can not be converted appropriately.", context);
2015-04-22 19:22:01 +02:00
throw InvalidDataException();
}
2016-05-16 20:56:53 +02:00
return true;
2015-04-22 19:22:01 +02:00
}
2018-03-07 01:17:50 +01:00
} // namespace TagParser