5 #include "../diagnostics.h"
6 #include "../exceptions.h"
8 #include <c++utilities/conversion/stringbuilder.h>
9 #include <c++utilities/conversion/stringconversion.h>
23 namespace Id3v2TextEncodingBytes {
38 Id3v2Frame::Id3v2Frame()
69 for (
auto c : denotation) {
80 if (c >=
'0' && c <=
'9') {
93 if (c >=
'0' && c <=
'9') {
94 index = index * 10 + c -
'0';
109 return string(get<0>(substr), get<1>(substr));
117 u16string res(
reinterpret_cast<u16string::const_pointer
>(get<0>(substr)), get<1>(substr) / 2);
134 static const string defaultContext(
"parsing ID3v2 frame");
141 setId(reader.readUInt24BE());
142 if (
id() & 0xFFFF0000u) {
151 context =
"parsing " %
idToString() +
" frame";
154 m_dataSize = reader.readUInt24BE();
155 m_totalSize = m_dataSize + 6;
156 if (m_totalSize > maximalSize) {
157 diag.emplace_back(
DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
168 setId(reader.readUInt32BE());
169 if (
id() & 0xFF000000u) {
178 context =
"parsing " %
idToString() +
" frame";
181 m_dataSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
182 m_totalSize = m_dataSize + 10;
183 if (m_totalSize > maximalSize) {
184 diag.emplace_back(
DiagLevel::Warning,
"The frame is truncated and will be ignored.", context);
189 m_flag = reader.readUInt16BE();
201 argsToString(
"The frame is only supported in ID3v2.4 and newer but the tag's version is ID3v2.",
version,
'.'), context);
204 argsToString(
"The frame is only supported in ID3v2.3 and older but the tag's version is ID3v2.",
version,
'.'), context);
208 if (m_dataSize <= 0) {
214 unique_ptr<char[]> buffer;
218 uLongf decompressedSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
219 if (decompressedSize < m_dataSize) {
220 diag.emplace_back(
DiagLevel::Critical,
"The decompressed size is smaller than the compressed size.", context);
223 const auto bufferCompressed = make_unique<char[]>(m_dataSize);
224 reader.read(bufferCompressed.get(), m_dataSize);
225 buffer = make_unique<char[]>(decompressedSize);
227 uncompress(
reinterpret_cast<Bytef *
>(buffer.get()), &decompressedSize,
reinterpret_cast<Bytef *
>(bufferCompressed.get()), m_dataSize)) {
229 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
232 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
235 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The input data was corrupted or incomplete.", context);
244 diag.emplace_back(
DiagLevel::Critical,
"The decompressed data exceeds the maximum supported frame size.", context);
247 m_dataSize =
static_cast<std::uint32_t
>(decompressedSize);
249 buffer = make_unique<char[]>(m_dataSize);
250 reader.read(buffer.get(), m_dataSize);
259 const char *currentOffset = buffer.get() + 1;
260 for (
size_t currentIndex = 1; currentIndex < m_dataSize;) {
262 const auto substr(
parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding,
false, diag));
265 if (!get<1>(substr)) {
266 if (currentIndex == 1) {
269 currentIndex =
static_cast<size_t>(get<2>(substr) - buffer.get());
270 currentOffset = get<2>(substr);
276 if (this->
value().isEmpty()) {
277 return &this->
value();
279 m_additionalValues.emplace_back();
280 return &m_additionalValues.back();
293 }
catch (
const ConversionException &) {
294 diag.emplace_back(
DiagLevel::Warning,
"The value of track/disk position frame is not numeric and will be ignored.", context);
300 const auto milliseconds = [&] {
302 const auto parsedStringRef =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
304 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
305 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
306 return string(convertedStringData.first.get(), convertedStringData.second);
312 }
catch (
const ConversionException &) {
313 diag.emplace_back(
DiagLevel::Warning,
"The value of the length frame is not numeric and will be ignored.", context);
318 const auto genreIndex = [&] {
325 if (genreIndex != -1) {
337 currentIndex =
static_cast<size_t>(get<2>(substr) - buffer.get());
338 currentOffset = get<2>(substr);
342 if (
version < 4 && !m_additionalValues.empty()) {
344 DiagLevel::Warning,
"Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
401 void Id3v2Frame::reset()
409 m_additionalValues.clear();
415 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg()
const
417 if (m_additionalValues.size() == 1) {
418 return argsToString(
"Additional value \"", m_additionalValues.front().toString(
TagTextEncoding::Utf8),
"\" is supposed to be ignored.");
434 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t
version, Diagnostics &diag)
436 , m_frameId(m_frame.id())
439 const string context(
"making " % m_frame.idToString() +
" frame");
442 if (m_frame.isEncrypted()) {
443 diag.emplace_back(
DiagLevel::Critical,
"Cannot make an encrypted frame (isn't supported by this tagging library).", context);
444 throw InvalidDataException();
446 if (m_frame.hasPaddingReached()) {
447 diag.emplace_back(
DiagLevel::Critical,
"Cannot make a frame which is marked as padding.", context);
448 throw InvalidDataException();
450 if (
version < 3 && m_frame.isCompressed()) {
451 diag.emplace_back(
DiagLevel::Warning,
"Compression is not supported by the version of ID3v2 and won't be applied.", context);
453 if (
version < 3 && (m_frame.flag() || m_frame.group())) {
455 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
459 vector<const TagValue *> values;
460 values.reserve(1 + frame.additionalValues().size());
461 if (!frame.value().isEmpty()) {
462 values.emplace_back(&frame.value());
464 for (
const auto &
value : frame.additionalValues()) {
466 values.emplace_back(&
value);
471 if (values.empty()) {
472 throw NoDataProvidedException();
477 if (values.size() != 1) {
479 diag.emplace_back(
DiagLevel::Critical,
"Multiple values are not supported for non-text-frames.", context);
480 throw InvalidDataException();
483 DiagLevel::Warning,
"Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
493 "The short frame ID can't be converted to its long equivalent which is needed to use the frame in a newer version of ID3v2.",
495 throw InvalidDataException();
503 "The long frame ID can't be converted to its short equivalent which is needed to use the frame in the old version of ID3v2.",
505 throw InvalidDataException();
513 argsToString(
"The frame is only supported in ID3v2.4 and newer but version of the tag being written is ID3v2.",
version,
514 ". The frame is written nevertheless but other tools might not be able to deal with it."),
518 argsToString(
"The frame is only supported in ID3v2.3 and older but version of the tag being written is ID3v2.",
version,
519 ". The frame is written nevertheless but other tools might not be able to deal with it."),
527 vector<string> substrings;
528 substrings.reserve(1 + frame.additionalValues().size());
535 for (
const auto *
const value : values) {
544 }
catch (
const ConversionException &) {
546 argsToString(
"The track/disk number \"", substrings.back(),
"\" is not of the expected form, eg. \"4/10\"."), context);
553 for (
const auto *
const value : values) {
557 throw InvalidDataException();
559 substrings.emplace_back(numberToString(
static_cast<std::uint64_t
>(
duration.totalMilliseconds())));
565 for (
const auto *
const value : values) {
591 for (
const auto *
const value : values) {
606 const auto byteOrderMark = [&] {
609 return string({
'\xFF',
'\xFE' });
611 return string({
'\xFE',
'\xFF' });
616 const auto concatenatedSubstrings = joinStrings(substrings,
string(),
false, byteOrderMark,
string(terminationLength,
'\0'));
619 m_data = make_unique<char[]>(m_decompressedSize =
static_cast<std::uint32_t
>(1 + concatenatedSubstrings.size()));
621 concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
625 m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0,
version, diag);
631 m_frame.makeComment(m_data, m_decompressedSize, *values.front(),
version, diag);
635 const auto &
value(*values.front());
638 throw InvalidDataException();
640 m_data = make_unique<char[]>(m_decompressedSize =
static_cast<std::uint32_t
>(
value.
dataSize()));
643 }
catch (
const ConversionException &) {
647 argsToString(
"Assigned value(s) \"",
DiagMessage::formatList(valuesAsString),
"\" can not be converted appropriately."), context);
648 }
catch (
const ConversionException &) {
649 diag.emplace_back(
DiagLevel::Critical,
"Assigned value(s) can not be converted appropriately.", context);
651 throw InvalidDataException();
655 if (
version >= 3 && m_frame.isCompressed()) {
656 auto compressedSize = compressBound(m_decompressedSize);
657 auto compressedData = make_unique<char[]>(compressedSize);
658 switch (compress(
reinterpret_cast<Bytef *
>(compressedData.get()),
reinterpret_cast<uLongf *
>(&compressedSize),
659 reinterpret_cast<Bytef *
>(m_data.get()), m_decompressedSize)) {
661 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
662 throw InvalidDataException();
664 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
665 throw InvalidDataException();
669 diag.emplace_back(
DiagLevel::Critical,
"Compressed size exceeds maximum data size.", context);
670 throw InvalidDataException();
672 m_data.swap(compressedData);
673 m_dataSize =
static_cast<std::uint32_t
>(compressedSize);
675 m_dataSize = m_decompressedSize;
680 m_requiredSize = m_dataSize;
686 m_requiredSize += 10;
688 if (m_frame.hasGroupInformation()) {
692 if (
version >= 3 && m_frame.isCompressed()) {
708 writer.writeUInt24BE(m_frameId);
709 writer.writeUInt24BE(m_dataSize);
711 writer.writeUInt32BE(m_frameId);
712 if (m_version >= 4) {
713 writer.writeSynchsafeUInt32BE(m_dataSize);
715 writer.writeUInt32BE(m_dataSize);
717 writer.writeUInt16BE(m_frame.
flag());
719 writer.writeByte(m_frame.
group());
722 if (m_version >= 4) {
723 writer.writeSynchsafeUInt32BE(m_decompressedSize);
725 writer.writeUInt32BE(m_decompressedSize);
729 writer.write(m_data.get(), m_dataSize);
740 switch (textEncodingByte) {
761 switch (textEncoding) {
792 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
797 if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
799 diag.emplace_back(
DiagLevel::Critical,
"Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
805 const char *pos = get<0>(res);
806 for (; *pos != 0x00; ++pos) {
807 if (pos < get<2>(res)) {
817 get<2>(res) = pos + 1;
822 if (bufferSize >= 2) {
823 switch (LE::toUInt16(buffer)) {
827 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
838 const std::uint16_t *pos =
reinterpret_cast<const std::uint16_t *
>(get<0>(res));
839 for (; *pos != 0x0000; ++pos) {
840 if (pos <
reinterpret_cast<const std::uint16_t *
>(get<2>(res))) {
850 get<2>(res) =
reinterpret_cast<const char *
>(pos + 1);
891 if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
893 }
else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
898 if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
914 static const string context(
"parsing ID3v2.2 picture frame");
919 const char *end = buffer + maxSize;
921 typeInfo =
static_cast<unsigned char>(*(buffer + 4));
922 auto substr =
parseSubstring(buffer + 5,
static_cast<size_t>(end - 5 - buffer), dataEncoding,
true, diag);
923 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
924 if (get<2>(substr) >= end) {
925 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
940 static const string context(
"parsing ID3v2.3 picture frame");
941 const char *end = buffer + maxSize;
944 auto substr =
parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding,
true, diag);
945 if (get<1>(substr)) {
946 tagValue.
setMimeType(
string(get<0>(substr), get<1>(substr)));
948 if (get<2>(substr) >= end) {
949 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (type info, description and actual data are missing).", context);
952 typeInfo =
static_cast<unsigned char>(*get<2>(substr));
953 if (++get<2>(substr) >= end) {
954 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (description and actual data are missing).", context);
957 substr =
parseSubstring(get<2>(substr),
static_cast<size_t>(end - get<2>(substr)), dataEncoding,
true, diag);
958 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
959 if (get<2>(substr) >= end) {
960 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
974 static const string context(
"parsing comment/unsynchronized lyrics frame");
975 const char *end = buffer +
dataSize;
985 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
986 if (get<2>(substr) > end) {
987 diag.emplace_back(
DiagLevel::Critical,
"Comment frame is incomplete (description not terminated?).", context);
990 substr =
parseSubstring(get<2>(substr),
static_cast<size_t>(end - get<2>(substr)), dataEncoding,
false, diag);
1002 LE::getBytes(
static_cast<std::uint16_t
>(0xFEFF), buffer);
1005 BE::getBytes(
static_cast<std::uint16_t
>(0xFEFF), buffer);
1016 unique_ptr<
char[]> &buffer, std::uint32_t &bufferSize,
const TagValue &picture, std::uint8_t typeInfo,
Diagnostics &diag)
1020 StringData convertedDescription;
1021 string::size_type descriptionSize = picture.
description().find(
1023 if (descriptionSize == string::npos) {
1029 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
1030 descriptionSize = convertedDescription.second;
1035 const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1038 if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1039 diag.emplace_back(
DiagLevel::Critical,
"Required size exceeds maximum.",
"making legacy picture frame");
1042 buffer = make_unique<char[]>(bufferSize =
static_cast<std::uint32_t
>(requiredBufferSize));
1043 char *offset = buffer.get();
1049 const char *imageFormat;
1050 if (picture.
mimeType() ==
"image/jpeg") {
1051 imageFormat =
"JPG";
1052 }
else if (picture.
mimeType() ==
"image/png") {
1053 imageFormat =
"PNG";
1054 }
else if (picture.
mimeType() ==
"image/gif") {
1055 imageFormat =
"GIF";
1056 }
else if (picture.
mimeType() ==
"-->") {
1057 imageFormat = picture.
mimeType().data();
1059 imageFormat =
"UND";
1061 strncpy(++offset, imageFormat, 3);
1064 *(offset += 3) =
static_cast<char>(
typeInfo);
1067 offset +=
makeBom(offset + 1, descriptionEncoding);
1068 if (convertedDescription.first) {
1069 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1071 picture.
description().copy(++offset, descriptionSize);
1073 *(offset += descriptionSize) = 0x00;
1095 StringData convertedDescription;
1096 string::size_type descriptionSize = picture.
description().find(
1098 if (descriptionSize == string::npos) {
1104 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
1105 descriptionSize = convertedDescription.second;
1108 string::size_type mimeTypeSize = picture.
mimeType().find(
'\0');
1109 if (mimeTypeSize == string::npos) {
1110 mimeTypeSize = picture.
mimeType().length();
1115 const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1118 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1119 diag.emplace_back(
DiagLevel::Critical,
"Required size exceeds maximum.",
"making picture frame");
1122 buffer = make_unique<char[]>(bufferSize =
static_cast<uint32_t
>(requiredBufferSize));
1123 char *offset = buffer.get();
1129 picture.
mimeType().copy(++offset, mimeTypeSize);
1131 *(offset += mimeTypeSize) = 0x00;
1133 *(++offset) =
static_cast<char>(
typeInfo);
1136 offset +=
makeBom(offset + 1, descriptionEncoding);
1137 if (convertedDescription.first) {
1138 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1140 picture.
description().copy(++offset, descriptionSize);
1142 *(offset += descriptionSize) = 0x00;
1156 static const string context(
"making comment frame");
1160 if (!
comment.description().empty() && encoding !=
comment.descriptionEncoding()) {
1161 diag.emplace_back(
DiagLevel::Critical,
"Data encoding and description encoding aren't equal.", context);
1164 const string &lng =
comment.language();
1165 if (lng.length() > 3) {
1166 diag.emplace_back(
DiagLevel::Critical,
"The language must be 3 bytes long (ISO-639-2).", context);
1169 StringData convertedDescription;
1170 string::size_type descriptionSize =
comment.description().find(
1172 if (descriptionSize == string::npos) {
1173 descriptionSize =
comment.description().size();
1178 convertedDescription = convertUtf8ToUtf16LE(
comment.description().data(), descriptionSize);
1179 descriptionSize = convertedDescription.second;
1184 const auto data =
comment.toString(encoding);
1185 const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1187 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1191 buffer = make_unique<char[]>(bufferSize =
static_cast<uint32_t
>(requiredBufferSize));
1192 char *offset = buffer.get();
1198 for (
unsigned int i = 0; i < 3; ++i) {
1199 *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1203 offset +=
makeBom(offset + 1, encoding);
1204 if (convertedDescription.first) {
1205 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1207 comment.description().copy(++offset, descriptionSize);
1209 offset += descriptionSize;
1216 offset +=
makeBom(offset + 1, encoding);
1217 data.copy(++offset, data.size());