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
|
|
|
|
2015-09-06 19:57:33 +02:00
|
|
|
#include "../id3/id3v2frame.h"
|
2015-04-22 19:22:01 +02:00
|
|
|
|
2018-03-05 17:49:29 +01: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
|
|
|
|
2018-03-06 23:09:15 +01:00
|
|
|
namespace TagParser {
|
2015-04-22 19:22:01 +02:00
|
|
|
|
|
|
|
/*!
|
2018-06-03 20:38:32 +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) {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Field expected.", 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
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "The field ID 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);
|
|
|
|
bufferStream.rdbuf()->pubsetbuf(reinterpret_cast<char *>(decoded.first.get()), decoded.second);
|
|
|
|
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 &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
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 &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
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) {
|
2016-01-17 19:32:58 +01:00
|
|
|
// extract other values (as string)
|
2016-05-14 00:24:01 +02:00
|
|
|
setValue(TagValue(string(data.get() + idSize + 1, size - idSize - 1), TagTextEncoding::Utf8));
|
2015-08-16 23:39:42 +02:00
|
|
|
}
|
2016-01-17 19:32:58 +01:00
|
|
|
} else {
|
2018-03-05 17:49:29 +01:00
|
|
|
diag.emplace_back(DiagLevel::Critical, "Field 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.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \throws Throws TagParser::Failure or a derived exception when a parsing
|
2016-05-16 20:56:53 +02:00
|
|
|
* error occurs.
|
|
|
|
*/
|
2018-03-05 17:49:29 +01:00
|
|
|
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();
|
2018-03-05 17:49:29 +01:00
|
|
|
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.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \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
|
|
|
{
|
2018-03-05 17:49:29 +01: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.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \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
|
|
|
{
|
2018-03-05 17:49:29 +01: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.
|
2018-06-03 20:38:32 +02:00
|
|
|
* \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
|
|
|
*/
|
2018-03-05 17:49:29 +01: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()) {
|
2018-03-05 17:49:29 +01:00
|
|
|
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) {
|
2018-03-05 17:49:29 +01:00
|
|
|
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);
|
|
|
|
bufferStream.rdbuf()->pubsetbuf(buffer.get(), requiredSize);
|
|
|
|
|
|
|
|
pictureBlock.make(bufferStream);
|
2019-03-13 19:06:42 +01:00
|
|
|
valueString = encodeBase64(reinterpret_cast<std::uint8_t *>(buffer.get()), requiredSize);
|
2018-06-02 22:56:08 +02:00
|
|
|
} 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();
|
|
|
|
}
|
2015-04-22 19:22:01 +02:00
|
|
|
} else {
|
|
|
|
// make normal string value
|
|
|
|
valueString = value().toString();
|
|
|
|
}
|
2018-06-02 22:56:08 +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()) {
|
2018-06-02 22:56:08 +02:00
|
|
|
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 &) {
|
2018-03-05 17:49:29 +01:00
|
|
|
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
|