| // Copyright 2026 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "formats/id3/reader.h" |
| |
| #include <sys/types.h> |
| |
| #include <cstdint> |
| #include <string> |
| |
| #include "absl/status/status.h" |
| #include "absl/status/status_macros.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/string_view.h" |
| #include "formats/id3/id3.h" |
| #include "riegeli/bytes/reader.h" |
| #include "riegeli/endian/endian_reading.h" |
| |
| namespace credentio { |
| |
| constexpr uint32_t kId3TagHeaderSize = 10; |
| constexpr uint32_t kId3FrameHeaderSize = 10; |
| |
| absl::StatusOr<Id3Header> IterateOverId3Frames(riegeli::Reader& reader, |
| Id3FrameProcessor processor) { |
| if (!reader.SupportsSize() || !reader.Size().has_value()) { |
| return absl::InvalidArgumentError("reader does not support size"); |
| } |
| |
| uint64_t beginning_offset = reader.pos(); |
| ABSL_ASSIGN_OR_RETURN(Id3Header id3_header, Id3Header::Parse(reader)); |
| uint64_t id3_tag_end_offset = |
| beginning_offset + kId3TagHeaderSize + id3_header.tag_size; |
| |
| if (id3_tag_end_offset > reader.Size().value()) { |
| return absl::InvalidArgumentError("tag end offset exceeds input size"); |
| } |
| |
| uint64_t current_offset = reader.pos(); |
| |
| while (current_offset + kId3FrameHeaderSize < id3_tag_end_offset) { |
| // Peek the next frame ID to see if the ID is valid. |
| uint32_t frame_id; |
| if (!reader.Seek(current_offset)) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("Failed to seek to current offset")); |
| } |
| if (!riegeli::ReadBigEndian<uint32_t>(reader, frame_id)) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("Failed to read frame ID for peeking")); |
| } |
| |
| // Check if the frame ID is all NULL bytes. This indicates the start of |
| // padding at the end of the ID3v2 tag. |
| if (frame_id == 0) { |
| break; |
| } |
| |
| if (!reader.Seek(current_offset)) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("Failed to seek back to current offset")); |
| } |
| |
| Id3Frame frame; |
| frame.offset = current_offset; |
| // Read the ID3 frame header, which is 10 bytes. |
| if (!reader.Read(4, frame.id)) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("Failed to read frame ID")); |
| } |
| uint32_t size; |
| if (!riegeli::ReadBigEndian<uint32_t>(reader, size)) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("Failed to read frame size")); |
| } |
| if (id3_header.major_version == 4) { |
| if ((size & 0x80808080) != 0) { |
| return reader.StatusOrAnnotate(absl::DataLossError( |
| "Invalid synchsafe integer in ID3v2.4 frame size")); |
| } |
| size = ((size >> 24) & 0x7f) << 21 | ((size >> 16) & 0x7f) << 14 | |
| ((size >> 8) & 0x7f) << 7 | (size & 0x7f); |
| } |
| std::string flags; |
| if (!reader.Read(2, flags)) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("Failed to read frame flags")); |
| } |
| const uint64_t remaining_tag_size = |
| id3_tag_end_offset - current_offset - kId3FrameHeaderSize; |
| if (size > remaining_tag_size) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("ID3 frame size exceeds remaining tag size")); |
| } |
| |
| frame.length = static_cast<uint64_t>(size) + kId3FrameHeaderSize; |
| |
| ABSL_ASSIGN_OR_RETURN(bool continue_iteration, processor(frame)); |
| if (!continue_iteration) { |
| break; |
| } |
| |
| // Seek to the end of the frame for the next iteration. |
| if (!reader.Seek(frame.offset + frame.length)) { |
| return reader.StatusOrAnnotate( |
| absl::DataLossError("Failed to seek to end of frame")); |
| } |
| current_offset = reader.pos(); |
| } |
| |
| return id3_header; |
| } |
| |
| } // namespace credentio |