| // 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 |