private static ApeItem GetItem(ApeVersion version, string key, int flags) { ApeItemType itemType = (ApeItemType)((flags & HeaderFlags.ItemType) >> 1); ApeItem item; switch (itemType) { case ApeItemType.ContainsBinary: item = new ApeBinaryItem(version, key); break; case ApeItemType.CodedUTF8: item = new ApeUtf8Item(version, key); break; case ApeItemType.IsLocator: item = new ApeLocatorItem(version, key); break; case ApeItemType.Reserved: item = new ApeItem(version, key) { IsReadOnly = true }; break; default: item = new ApeItem(version, key) { IsReadOnly = true }; break; } return item; }
/// <summary> /// Reads an <see cref="ApeItem" /> from a <see cref="Stream" /> at the current position. /// </summary> /// <param name="version">The version.</param> /// <param name="stream">The stream.</param> /// <param name="maximumItemSize">Maximum size of the item.</param> /// <returns> /// An <see cref="ApeItem" /> if found; otherwise, null. /// </returns> /// <exception cref="System.ArgumentNullException">stream</exception> public static ApeItem ReadFromStream(ApeVersion version, Stream stream, long maximumItemSize) { if (stream == null) throw new ArgumentNullException("stream"); return ReadItem(version, stream as StreamBuffer ?? new StreamBuffer(stream), maximumItemSize); }
/// <summary> /// Initializes a new instance of the <see cref="ApeUtf8Item"/> class. /// </summary> /// <param name="version">The <see cref="ApeVersion"/> of the <see cref="ApeTag"/>.</param> /// <param name="key">The key.</param> public ApeUtf8Item(ApeVersion version, ApeItemKey key) : base(version, key) { }
////------------------------------------------------------------------------------------------------------------------------------ /// <summary> /// Initializes a new instance of the <see cref="ApeUtf8Item"/> class. /// </summary> /// <param name="version">The <see cref="ApeVersion"/> of the <see cref="ApeTag"/>.</param> /// <param name="key">The name of the item.</param> public ApeUtf8Item(ApeVersion version, string key) : base(version, key) { }
private static bool UseHeaderFlag(ApeVersion version, int flags) { return (version >= ApeVersion.Version2) && ((flags & ApeHeaderFlags.ContainsHeader) != 0); }
private static bool UseFooterFlag(ApeVersion version, int flags) { return (version < ApeVersion.Version2) || ((flags & ApeHeaderFlags.ContainsNoFooter) == 0); }
private static bool IsReadOnlyFlag(ApeVersion version, int flags) { return (version >= ApeVersion.Version2) && ((flags & ApeHeaderFlags.IsReadOnly) != 0); }
private static long GetBytesUntilNextItem(ApeVersion version, Stream stream, long maximumNextItemSize) { long startPosition = stream.Position; long initialPosition = startPosition; long nextItemSizeValue = 0; int nextItemKeyLength = 0; long nextItemFlags = 0; int nextItemHeaderLengthRead = 0; long totalBytesRead = 0; List<byte> nextItemKeyBytes = new List<byte>(); while (totalBytesRead++ < Math.Min(stream.Length, maximumNextItemSize)) { byte b = (byte)stream.ReadByte(); if (nextItemHeaderLengthRead < 4) { // This byte is part of the size field of the next item. long val = b; // The first byte is the most right byte (size field length is an int) val <<= 8 * nextItemHeaderLengthRead; nextItemSizeValue |= val; nextItemHeaderLengthRead++; continue; } else if (nextItemHeaderLengthRead < (4 + 4)) { // This byte is part of the flags field of the next item. long val = b; // The first byte is the most right byte (size field length is an int) val <<= 8 * (nextItemHeaderLengthRead - 4); nextItemFlags |= val; nextItemHeaderLengthRead++; continue; } else if (nextItemHeaderLengthRead <= (4 + 4 + Math.Max(nextItemKeyLength, MinKeyLengthCharacters))) { if (b != 0x00) { nextItemKeyBytes.Add(b); nextItemKeyLength++; nextItemHeaderLengthRead++; continue; } if ((b == 0x00) && (nextItemKeyLength >= 2)) { nextItemHeaderLengthRead++; continue; } } else if (nextItemHeaderLengthRead == (4 + 4 + nextItemKeyLength + 1)) { if (IsValidUtf8(nextItemKeyBytes)) { string nextItemKey = Encoding.UTF8.GetString(nextItemKeyBytes.ToArray()); bool isValidSizeValue = (nextItemSizeValue > 0) && (nextItemSizeValue < ApeTag.MaxAllowedSize); bool isValidNextItemKey = (nextItemKey.Length >= MinKeyLengthCharacters) && (nextItemKeyBytes.Count <= MaxKeyLengthBytes); bool areValidFlags = AreValidFlags((int)nextItemFlags); // Flags - Bit 28...3: Undefined, must be zero if (isValidSizeValue && isValidNextItemKey && areValidFlags) { // Found the start of the next frame; calculate the amount of bytes between the last item and the next item. return (stream.Position - startPosition) - (nextItemHeaderLengthRead + 1); } } } stream.Position = ++initialPosition; nextItemSizeValue = 0; nextItemHeaderLengthRead = 0; nextItemKeyLength = 0; nextItemFlags = 0; nextItemKeyBytes.Clear(); } return -1; }
private bool ReadItem(ApeVersion version, int valueSize, StreamBuffer stream, long maximumItemSize) { if (stream == null) throw new ArgumentNullException("stream"); // Check if the item size indicated is really the size of the item. // Some files just aren't... properly written. byte[] data = new byte[valueSize]; int dataBytesRead = stream.Read(data, valueSize); int bytesLeftInStream = (int)(maximumItemSize - dataBytesRead); if ((valueSize > 0) && (bytesLeftInStream > 0) && (dataBytesRead >= valueSize)) { using (StreamBuffer dataBuffer = new StreamBuffer(data)) { // See if there's a next item. // Try to find the start of the next item. long startPositionNextItem = stream.Position; long bytesUntilNextItem = GetBytesUntilNextItem(version, stream, maximumItemSize - valueSize); stream.Position = startPositionNextItem; // Seems that the size indicated by the item is not the total size of the item; read the extra bytes here. if (bytesUntilNextItem > 0) { data = new byte[bytesUntilNextItem]; stream.Read(data, data.Length); dataBuffer.Write(data); } data = dataBuffer.ToByteArray(); } } Data = data; return true; }
////------------------------------------------------------------------------------------------------------------------------------ private static ApeItem ReadItem(ApeVersion version, StreamBuffer sb, long maximumItemSize) { if (sb == null) throw new ArgumentNullException("sb"); // Find the item. long startPosition = sb.Position; long bytesToSkip = GetBytesUntilNextItem(version, sb, maximumItemSize); // Not found; return null. if (bytesToSkip == -1) return null; // Move the stream position to the start of the frame. sb.Position = startPosition + bytesToSkip; int valueSize = sb.ReadLittleEndianInt32(); if ((valueSize <= 0) || (valueSize > ApeTag.MaxAllowedSize)) return null; int flags = sb.ReadLittleEndianInt32(); maximumItemSize -= 8; int itemKeyLengthBytes = 0; long maximumBytesToRead = maximumItemSize; if (maximumBytesToRead > 0) { for (int y = 0; y < maximumBytesToRead; y++) { int character = sb.ReadByte(); if (character == 0x00) // 0x00 byte - string terminator. break; itemKeyLengthBytes++; } } // Name sb.Seek(-(itemKeyLengthBytes + 1), SeekOrigin.Current); string key = sb.ReadString(itemKeyLengthBytes, Encoding.UTF8); if ((key.Length < MinKeyLengthCharacters) || (itemKeyLengthBytes > MaxKeyLengthBytes)) return null; // Skip Item key terminator (0x00 byte) int keyTerminator = sb.ReadByte(); if (keyTerminator != 0x00) return null; ApeItem item = GetItem(version, key, flags); return item.ReadItem(version, valueSize, sb, maximumItemSize) ? item : null; }
/// <summary> /// Initializes a new instance of the <see cref="ApeBinaryItem" /> class. /// </summary> /// <param name="version">The <see cref="ApeVersion" /> of the <see cref="ApeTag" />.</param> /// <param name="key">The key.</param> public ApeBinaryItem(ApeVersion version, ApeItemKey key) : base(version, key) { }
/// <summary> /// Initializes a new instance of the <see cref="ApeBinaryItem" /> class. /// </summary> /// <param name="version">The <see cref="ApeVersion" /> of the <see cref="ApeTag" />.</param> /// <param name="key">The key of the item.</param> /// <remarks> /// All characters in the key should be in the range of 0x20 to 0x7E, and may not be one of the following: ID3, TAG, OggS or MP+ /// <para /> /// If encoding the key in the <see cref="Encoding.UTF8" /> encoding exceeds 255 bytes, /// the key will be cut to the max character count which fits within 255 bytes. /// </remarks> public ApeBinaryItem(ApeVersion version, string key) : base(version, key) { }
/// <summary> /// Initializes a new instance of the <see cref="ApeLocatorItem"/> class. /// </summary> /// <param name="version">The <see cref="ApeVersion"/> of the <see cref="ApeTag"/>.</param> /// <param name="key">The key.</param> public ApeLocatorItem(ApeVersion version, ApeItemKey key) : base(version, key) { BindEvents(); }
////------------------------------------------------------------------------------------------------------------------------------ /// <summary> /// Initializes a new instance of the <see cref="ApeLocatorItem"/> class. /// </summary> /// <param name="version">The <see cref="ApeVersion"/> of the <see cref="ApeTag"/>.</param> /// <param name="key">The name of the item.</param> public ApeLocatorItem(ApeVersion version, string key) : base(version, key) { BindEvents(); }