Module: FormatParser::MP3Parser::ID3Extraction
- Extended by:
- IOUtils, ID3Extraction
- Included in:
- ID3Extraction
- Defined in:
- lib/parsers/mp3_parser/id3_extraction.rb
Constant Summary collapse
- ID3V1_TAG_SIZE_BYTES =
128- ID3V2_MINOR_TAG_VERSIONS =
it supports 2.4.x, 2.3.x, 2.2.x which are supported by the gem id3tag see id3.org/Developer%20Information for more details of each version
[2, 3, 4]
- MAX_SIZE_FOR_ID3V2 =
1 * 1024 * 1024
Instance Method Summary collapse
- #attempt_id3_v1_extraction(io) ⇒ Object
- #attempt_id3_v2_extraction(io) ⇒ Object
- #parse_id3_v2_header(io) ⇒ Object
- #read_and_unpack_packspec(io, **packspec) ⇒ Object
-
#swallow_exceptions ⇒ Object
We swallow exceptions from ID3Tag primarily because it does not have a single wrapping error class we could capture.
Methods included from IOUtils
Instance Method Details
#attempt_id3_v1_extraction(io) ⇒ Object
10 11 12 13 14 15 16 17 18 19 20 21 |
# File 'lib/parsers/mp3_parser/id3_extraction.rb', line 10 def attempt_id3_v1_extraction(io) return if io.size < ID3V1_TAG_SIZE_BYTES # Won't fit the ID3v1 regardless io.seek(io.size - 128) trailer_bytes = io.read(128) return unless trailer_bytes && trailer_bytes.bytesize == ID3V1_TAG_SIZE_BYTES return unless trailer_bytes.byteslice(0, 3) == 'TAG' buf = StringIO.new(trailer_bytes) swallow_exceptions { ID3Tag.read(buf, :v1) } end |
#attempt_id3_v2_extraction(io) ⇒ Object
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/parsers/mp3_parser/id3_extraction.rb', line 23 def attempt_id3_v2_extraction(io) io.seek(0) # Only support header ID3v2 header = parse_id3_v2_header(io) return unless header[:tag] == 'ID3' && header[:size] > 0 return unless ID3V2_MINOR_TAG_VERSIONS.include?(header[:version].unpack('C').first) id3_tag_size = io.pos + header[:size] # Here we got to pay attention. The tag size encoded in # the ID3 header is a 4-byte unsigned int. Meaning it # can hold values up to 256 MB. We do not want to read # that much since we are pulling that data into memory - # and it would also make the parser easily exploitable. # We will set a "hard" limit beyound which we will simply # refuse to read those tags at all. if id3_tag_size > MAX_SIZE_FOR_ID3V2 io.seek(id3_tag_size) # For reading the frames return end io.seek(0) blob = safe_read(io, id3_tag_size) swallow_exceptions { ID3Tag.read(StringIO.new(blob), :v2) } rescue FormatParser::IOUtils::InvalidRead nil end |
#parse_id3_v2_header(io) ⇒ Object
60 61 62 63 64 65 |
# File 'lib/parsers/mp3_parser/id3_extraction.rb', line 60 def parse_id3_v2_header(io) fields = {tag: :a3, version: :a2, flags: :a1, syncsafe_size: :N1} header_data = read_and_unpack_packspec(io, **fields) header_data[:size] = ID3Tag::SynchsafeInteger.decode(header_data.delete(:syncsafe_size)) header_data end |
#read_and_unpack_packspec(io, **packspec) ⇒ Object
51 52 53 54 55 56 57 58 |
# File 'lib/parsers/mp3_parser/id3_extraction.rb', line 51 def read_and_unpack_packspec(io, **packspec) sizes = {'a' => 1, 'N' => 4} n = packspec.values.map { |e| sizes.fetch(e[0]) * e[1].to_i }.inject(&:+) byte_str = safe_read(io, n) unpacked_values = byte_str.unpack(packspec.values.join) Hash[packspec.keys.zip(unpacked_values)] end |
#swallow_exceptions ⇒ Object
We swallow exceptions from ID3Tag primarily because it does not have a single wrapping error class we could capture. We also do not touch our original IO object when working with ID3Tag
70 71 72 73 74 75 |
# File 'lib/parsers/mp3_parser/id3_extraction.rb', line 70 def swallow_exceptions yield rescue => e warn(e) nil end |