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();
199 if (m_dataSize <= 0) {
205 unique_ptr<char[]> buffer;
209 uLongf decompressedSize =
version >= 4 ? reader.readSynchsafeUInt32BE() : reader.readUInt32BE();
210 if (decompressedSize < m_dataSize) {
211 diag.emplace_back(
DiagLevel::Critical,
"The decompressed size is smaller than the compressed size.", context);
214 const auto bufferCompressed = make_unique<char[]>(m_dataSize);
215 reader.read(bufferCompressed.get(), m_dataSize);
216 buffer = make_unique<char[]>(decompressedSize);
218 uncompress(reinterpret_cast<Bytef *>(buffer.get()), &decompressedSize, reinterpret_cast<Bytef *>(bufferCompressed.get()), m_dataSize)) {
220 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
223 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
226 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The input data was corrupted or incomplete.", context);
235 diag.emplace_back(
DiagLevel::Critical,
"The decompressed data exceeds the maximum supported frame size.", context);
238 m_dataSize = static_cast<std::uint32_t>(decompressedSize);
240 buffer = make_unique<char[]>(m_dataSize);
241 reader.read(buffer.get(), m_dataSize);
250 const char *currentOffset = buffer.get() + 1;
251 for (
size_t currentIndex = 1; currentIndex < m_dataSize;) {
253 const auto substr(
parseSubstring(currentOffset, m_dataSize - currentIndex, dataEncoding,
false, diag));
256 if (!get<1>(substr)) {
257 if (currentIndex == 1) {
260 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
261 currentOffset = get<2>(substr);
267 if (this->
value().isEmpty()) {
268 return &this->
value();
270 m_additionalValues.emplace_back();
271 return &m_additionalValues.back();
284 }
catch (
const ConversionException &) {
285 diag.emplace_back(
DiagLevel::Warning,
"The value of track/disk position frame is not numeric and will be ignored.", context);
291 const auto milliseconds = [&] {
293 const auto parsedStringRef =
parseSubstring(buffer.get() + 1, m_dataSize - 1, dataEncoding,
false, diag);
295 ? convertUtf16BEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef))
296 : convertUtf16LEToUtf8(get<0>(parsedStringRef), get<1>(parsedStringRef));
297 return string(convertedStringData.first.get(), convertedStringData.second);
303 }
catch (
const ConversionException &) {
304 diag.emplace_back(
DiagLevel::Warning,
"The value of the length frame is not numeric and will be ignored.", context);
309 const auto genreIndex = [&] {
316 if (genreIndex != -1) {
328 currentIndex = static_cast<size_t>(get<2>(substr) - buffer.get());
329 currentOffset = get<2>(substr);
333 if (
version < 4 && !m_additionalValues.empty()) {
335 DiagLevel::Warning,
"Multiple strings found though the tag is pre-ID3v2.4. " + ignoreAdditionalValuesDiagMsg(), context);
392 void Id3v2Frame::reset()
400 m_additionalValues.clear();
406 std::string Id3v2Frame::ignoreAdditionalValuesDiagMsg()
const
408 if (m_additionalValues.size() == 1) {
409 return argsToString(
"Additional value \"", m_additionalValues.front().toString(
TagTextEncoding::Utf8),
"\" is supposed to be ignored.");
425 Id3v2FrameMaker::Id3v2FrameMaker(Id3v2Frame &frame, std::uint8_t
version, Diagnostics &diag)
427 , m_frameId(m_frame.id())
430 const string context(
"making " % m_frame.idToString() +
" frame");
433 if (m_frame.isEncrypted()) {
434 diag.emplace_back(
DiagLevel::Critical,
"Cannot make an encrypted frame (isn't supported by this tagging library).", context);
435 throw InvalidDataException();
437 if (m_frame.hasPaddingReached()) {
438 diag.emplace_back(
DiagLevel::Critical,
"Cannot make a frame which is marked as padding.", context);
439 throw InvalidDataException();
441 if (
version < 3 && m_frame.isCompressed()) {
442 diag.emplace_back(
DiagLevel::Warning,
"Compression is not supported by the version of ID3v2 and won't be applied.", context);
444 if (
version < 3 && (m_frame.flag() || m_frame.group())) {
446 "The existing flag and group information is not supported by the version of ID3v2 and will be ignored/discarted.", context);
450 vector<const TagValue *> values;
451 values.reserve(1 + frame.additionalValues().size());
452 if (!frame.value().isEmpty()) {
453 values.emplace_back(&frame.value());
455 for (
const auto &
value : frame.additionalValues()) {
457 values.emplace_back(&
value);
462 if (values.empty()) {
463 throw NoDataProvidedException();
468 if (values.size() != 1) {
470 diag.emplace_back(
DiagLevel::Critical,
"Multiple values are not supported for non-text-frames.", context);
471 throw InvalidDataException();
474 DiagLevel::Warning,
"Multiple strings assigned to pre-ID3v2.4 text frame. " + frame.ignoreAdditionalValuesDiagMsg(), context);
484 "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.",
486 throw InvalidDataException();
494 "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.",
496 throw InvalidDataException();
505 vector<string> substrings;
506 substrings.reserve(1 + frame.additionalValues().size());
513 for (
const auto *
const value : values) {
522 }
catch (
const ConversionException &) {
524 argsToString(
"The track/disk number \"", substrings.back(),
"\" is not of the expected form, eg. \"4/10\"."), context);
531 for (
const auto *
const value : values) {
535 throw InvalidDataException();
537 substrings.emplace_back(numberToString(static_cast<std::uint64_t>(
duration.totalMilliseconds())));
543 for (
const auto *
const value : values) {
569 for (
const auto *
const value : values) {
584 const auto byteOrderMark = [&] {
587 return string({
'\xFF',
'\xFE' });
589 return string({
'\xFE',
'\xFF' });
594 const auto concatenatedSubstrings = joinStrings(substrings,
string(),
false, byteOrderMark,
string(terminationLength,
'\0'));
597 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(1 + concatenatedSubstrings.size()));
599 concatenatedSubstrings.copy(&m_data[1], concatenatedSubstrings.size());
603 m_frame.makePicture(m_data, m_decompressedSize, *values.front(), m_frame.isTypeInfoAssigned() ? m_frame.typeInfo() : 0,
version, diag);
609 m_frame.makeComment(m_data, m_decompressedSize, *values.front(),
version, diag);
613 const auto &
value(*values.front());
616 throw InvalidDataException();
618 m_data = make_unique<char[]>(m_decompressedSize = static_cast<std::uint32_t>(
value.
dataSize()));
621 }
catch (
const ConversionException &) {
625 argsToString(
"Assigned value(s) \"",
DiagMessage::formatList(valuesAsString),
"\" can not be converted appropriately."), context);
626 }
catch (
const ConversionException &) {
627 diag.emplace_back(
DiagLevel::Critical,
"Assigned value(s) can not be converted appropriately.", context);
629 throw InvalidDataException();
633 if (
version >= 3 && m_frame.isCompressed()) {
634 auto compressedSize = compressBound(m_decompressedSize);
635 auto compressedData = make_unique<char[]>(compressedSize);
636 switch (compress(reinterpret_cast<Bytef *>(compressedData.get()), reinterpret_cast<uLongf *>(&compressedSize),
637 reinterpret_cast<Bytef *>(m_data.get()), m_decompressedSize)) {
639 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The source buffer was too small.", context);
640 throw InvalidDataException();
642 diag.emplace_back(
DiagLevel::Critical,
"Decompressing failed. The destination buffer was too small.", context);
643 throw InvalidDataException();
647 diag.emplace_back(
DiagLevel::Critical,
"Compressed size exceeds maximum data size.", context);
648 throw InvalidDataException();
650 m_data.swap(compressedData);
651 m_dataSize = static_cast<std::uint32_t>(compressedSize);
653 m_dataSize = m_decompressedSize;
658 m_requiredSize = m_dataSize;
664 m_requiredSize += 10;
666 if (m_frame.hasGroupInformation()) {
670 if (
version >= 3 && m_frame.isCompressed()) {
686 writer.writeUInt24BE(m_frameId);
687 writer.writeUInt24BE(m_dataSize);
689 writer.writeUInt32BE(m_frameId);
690 if (m_version >= 4) {
691 writer.writeSynchsafeUInt32BE(m_dataSize);
693 writer.writeUInt32BE(m_dataSize);
695 writer.writeUInt16BE(m_frame.
flag());
697 writer.writeByte(m_frame.
group());
700 if (m_version >= 4) {
701 writer.writeSynchsafeUInt32BE(m_decompressedSize);
703 writer.writeUInt32BE(m_decompressedSize);
707 writer.write(m_data.get(), m_dataSize);
718 switch (textEncodingByte) {
739 switch (textEncoding) {
770 tuple<const char *, size_t, const char *> res(buffer, 0, buffer + bufferSize);
775 if ((bufferSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
777 diag.emplace_back(
DiagLevel::Critical,
"Denoted character set is Latin-1 but an UTF-8 BOM is present - assuming UTF-8.",
783 const char *pos = get<0>(res);
784 for (; *pos != 0x00; ++pos) {
785 if (pos < get<2>(res)) {
795 get<2>(res) = pos + 1;
800 if (bufferSize >= 2) {
801 switch (LE::toUInt16(buffer)) {
805 "Denoted character set is UTF-16 Big Endian but UTF-16 Little Endian BOM is present - assuming UTF-16 LE.",
816 const std::uint16_t *pos = reinterpret_cast<const std::uint16_t *>(get<0>(res));
817 for (; *pos != 0x0000; ++pos) {
818 if (pos < reinterpret_cast<const std::uint16_t *>(get<2>(res))) {
828 get<2>(res) = reinterpret_cast<const char *>(pos + 1);
869 if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFFFE)) {
871 }
else if ((maxSize >= 2) && (BE::toUInt16(buffer) == 0xFEFF)) {
876 if ((maxSize >= 3) && (BE::toUInt24(buffer) == 0x00EFBBBF)) {
892 static const string context(
"parsing ID3v2.2 picture frame");
897 const char *end = buffer + maxSize;
899 typeInfo = static_cast<unsigned char>(*(buffer + 4));
900 auto substr =
parseSubstring(buffer + 5, static_cast<size_t>(end - 5 - buffer), dataEncoding,
true, diag);
901 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
902 if (get<2>(substr) >= end) {
903 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
918 static const string context(
"parsing ID3v2.3 picture frame");
919 const char *end = buffer + maxSize;
922 auto substr =
parseSubstring(buffer + 1, maxSize - 1, mimeTypeEncoding,
true, diag);
923 if (get<1>(substr)) {
924 tagValue.
setMimeType(
string(get<0>(substr), get<1>(substr)));
926 if (get<2>(substr) >= end) {
927 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (type info, description and actual data are missing).", context);
930 typeInfo = static_cast<unsigned char>(*get<2>(substr));
931 if (++get<2>(substr) >= end) {
932 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (description and actual data are missing).", context);
935 substr =
parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding,
true, diag);
936 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
937 if (get<2>(substr) >= end) {
938 diag.emplace_back(
DiagLevel::Critical,
"Picture frame is incomplete (actual data is missing).", context);
952 static const string context(
"parsing comment/unsynchronized lyrics frame");
953 const char *end = buffer +
dataSize;
963 tagValue.
setDescription(
string(get<0>(substr), get<1>(substr)), dataEncoding);
964 if (get<2>(substr) > end) {
965 diag.emplace_back(
DiagLevel::Critical,
"Comment frame is incomplete (description not terminated?).", context);
968 substr =
parseSubstring(get<2>(substr), static_cast<size_t>(end - get<2>(substr)), dataEncoding,
false, diag);
980 LE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
983 BE::getBytes(static_cast<std::uint16_t>(0xFEFF), buffer);
994 unique_ptr<
char[]> &buffer, std::uint32_t &bufferSize,
const TagValue &picture, std::uint8_t typeInfo,
Diagnostics &diag)
998 StringData convertedDescription;
999 string::size_type descriptionSize = picture.
description().find(
1001 if (descriptionSize == string::npos) {
1007 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
1008 descriptionSize = convertedDescription.second;
1013 const auto requiredBufferSize = 1 + 3 + 1 + descriptionSize
1016 if (requiredBufferSize > numeric_limits<std::uint32_t>::max()) {
1017 diag.emplace_back(
DiagLevel::Critical,
"Required size exceeds maximum.",
"making legacy picture frame");
1020 buffer = make_unique<char[]>(bufferSize = static_cast<std::uint32_t>(requiredBufferSize));
1021 char *offset = buffer.get();
1027 const char *imageFormat;
1028 if (picture.
mimeType() ==
"image/jpeg") {
1029 imageFormat =
"JPG";
1030 }
else if (picture.
mimeType() ==
"image/png") {
1031 imageFormat =
"PNG";
1032 }
else if (picture.
mimeType() ==
"image/gif") {
1033 imageFormat =
"GIF";
1034 }
else if (picture.
mimeType() ==
"-->") {
1035 imageFormat = picture.
mimeType().data();
1037 imageFormat =
"UND";
1039 strncpy(++offset, imageFormat, 3);
1042 *(offset += 3) = static_cast<char>(
typeInfo);
1045 offset +=
makeBom(offset + 1, descriptionEncoding);
1046 if (convertedDescription.first) {
1047 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1049 picture.
description().copy(++offset, descriptionSize);
1051 *(offset += descriptionSize) = 0x00;
1073 StringData convertedDescription;
1074 string::size_type descriptionSize = picture.
description().find(
1076 if (descriptionSize == string::npos) {
1082 convertedDescription = convertUtf8ToUtf16LE(picture.
description().data(), descriptionSize);
1083 descriptionSize = convertedDescription.second;
1086 string::size_type mimeTypeSize = picture.
mimeType().find(
'\0');
1087 if (mimeTypeSize == string::npos) {
1088 mimeTypeSize = picture.
mimeType().length();
1093 const auto requiredBufferSize = 1 + mimeTypeSize + 1 + 1 + descriptionSize
1096 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1097 diag.emplace_back(
DiagLevel::Critical,
"Required size exceeds maximum.",
"making picture frame");
1100 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1101 char *offset = buffer.get();
1107 picture.
mimeType().copy(++offset, mimeTypeSize);
1109 *(offset += mimeTypeSize) = 0x00;
1111 *(++offset) = static_cast<char>(
typeInfo);
1114 offset +=
makeBom(offset + 1, descriptionEncoding);
1115 if (convertedDescription.first) {
1116 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1118 picture.
description().copy(++offset, descriptionSize);
1120 *(offset += descriptionSize) = 0x00;
1134 static const string context(
"making comment frame");
1138 if (!
comment.description().empty() && encoding !=
comment.descriptionEncoding()) {
1139 diag.emplace_back(
DiagLevel::Critical,
"Data encoding and description encoding aren't equal.", context);
1142 const string &lng =
comment.language();
1143 if (lng.length() > 3) {
1144 diag.emplace_back(
DiagLevel::Critical,
"The language must be 3 bytes long (ISO-639-2).", context);
1147 StringData convertedDescription;
1148 string::size_type descriptionSize =
comment.description().find(
1150 if (descriptionSize == string::npos) {
1151 descriptionSize =
comment.description().size();
1156 convertedDescription = convertUtf8ToUtf16LE(
comment.description().data(), descriptionSize);
1157 descriptionSize = convertedDescription.second;
1162 const auto data =
comment.toString(encoding);
1163 const auto requiredBufferSize = 1 + 3 + descriptionSize + data.size()
1165 if (requiredBufferSize > numeric_limits<uint32_t>::max()) {
1169 buffer = make_unique<char[]>(bufferSize = static_cast<uint32_t>(requiredBufferSize));
1170 char *offset = buffer.get();
1176 for (
unsigned int i = 0; i < 3; ++i) {
1177 *(++offset) = (lng.length() > i) ? lng[i] : 0x00;
1181 offset +=
makeBom(offset + 1, encoding);
1182 if (convertedDescription.first) {
1183 copy(convertedDescription.first.get(), convertedDescription.first.get() + descriptionSize, ++offset);
1185 comment.description().copy(++offset, descriptionSize);
1187 offset += descriptionSize;
1194 offset +=
makeBom(offset + 1, encoding);
1195 data.copy(++offset, data.size());