Browse Source

Remove string encoding concerns from BinaryReader&Writer

af/merge-core
Jason Nelson 8 years ago
parent
commit
32bd209cfc
  1. 10
      src/ImageSharp/Formats/Gif/GifConstants.cs
  2. 4
      src/ImageSharp/Formats/Gif/GifEncoderCore.cs
  3. 211
      src/ImageSharp/IO/EndianBinaryReader.cs
  4. 94
      src/ImageSharp/IO/EndianBinaryWriter.cs
  5. 61
      tests/ImageSharp.Tests/IO/EndianBinaryReaderTests.cs

10
src/ImageSharp/Formats/Gif/GifConstants.cs

@ -21,6 +21,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public const string FileVersion = "89a";
/// <summary>
/// The ASCII encoded bytes used to identify the GIF file.
/// </summary>
internal static readonly byte[] MagicNumber = { 71, 73, 70, 56, 57, 97 }; // GIF89a
/// <summary>
/// The extension block introducer <value>!</value>.
/// </summary>
@ -41,6 +46,11 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// </summary>
public const string ApplicationIdentification = "NETSCAPE2.0";
/// <summary>
/// The ASCII encoded application identification bytes.
/// </summary>
internal static readonly byte[] ApplicationIdentificationBytes = Encoding.UTF8.GetBytes(ApplicationIdentification);
/// <summary>
/// The application block size.
/// </summary>

4
src/ImageSharp/Formats/Gif/GifEncoderCore.cs

@ -157,7 +157,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
/// <param name="writer">The writer to write to the stream with.</param>
private void WriteHeader(EndianBinaryWriter writer)
{
writer.Write((GifConstants.FileType + GifConstants.FileVersion).ToCharArray());
writer.Write(GifConstants.MagicNumber, 0, GifConstants.MagicNumber.Length);
}
/// <summary>
@ -213,7 +213,7 @@ namespace SixLabors.ImageSharp.Formats.Gif
writer.Write(this.buffer, 0, 3);
writer.Write(GifConstants.ApplicationIdentification.ToCharArray()); // NETSCAPE2.0
writer.Write(GifConstants.ApplicationIdentificationBytes, 0, GifConstants.ApplicationIdentificationBytes.Length); // NETSCAPE2.0
writer.Write((byte)3); // Application block length
writer.Write((byte)1); // Data sub-block index (always 1)

211
src/ImageSharp/IO/EndianBinaryReader.cs

@ -14,26 +14,11 @@ namespace SixLabors.ImageSharp.IO
/// </summary>
internal class EndianBinaryReader : IDisposable
{
/// <summary>
/// Decoder to use for string conversions.
/// </summary>
private readonly Decoder decoder;
/// <summary>
/// Buffer used for temporary storage before conversion into primitives
/// </summary>
private readonly byte[] storageBuffer = new byte[16];
/// <summary>
/// Buffer used for temporary storage when reading a single character
/// </summary>
private readonly char[] charBuffer = new char[1];
/// <summary>
/// Minimum number of bytes used to encode a character
/// </summary>
private readonly int minBytesPerChar;
/// <summary>
/// Whether or not this reader has been disposed yet.
/// </summary>
@ -44,21 +29,6 @@ namespace SixLabors.ImageSharp.IO
/// </summary>
private readonly Endianness endianness;
/// <summary>
/// Initializes a new instance of the <see cref="EndianBinaryReader"/> class.
/// Modeled after <see cref="System.IO.BinaryWriter"/> with endian support.
/// </summary>
/// <param name="endianness">
/// Endianness to use when reading data
/// </param>
/// <param name="stream">
/// Stream to read data from
/// </param>
public EndianBinaryReader(Endianness endianness, Stream stream)
: this(endianness, stream, Encoding.UTF8)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EndianBinaryReader"/> class.
/// Constructs a new binary reader with the given bit converter, reading
@ -66,30 +36,15 @@ namespace SixLabors.ImageSharp.IO
/// </summary>
/// <param name="endianness">Endianness to use when reading data</param>
/// <param name="stream">Stream to read data from</param>
/// <param name="encoding">Encoding to use when reading character data</param>
public EndianBinaryReader(Endianness endianness, Stream stream, Encoding encoding)
public EndianBinaryReader(Endianness endianness, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(encoding, nameof(encoding));
Guard.IsTrue(stream.CanRead, nameof(stream), "Stream isn't readable");
this.BaseStream = stream;
this.Encoding = encoding;
this.decoder = encoding.GetDecoder();
this.endianness = endianness;
this.minBytesPerChar = 1;
if (encoding is UnicodeEncoding)
{
this.minBytesPerChar = 2;
}
}
/// <summary>
/// Gets the encoding used to read strings
/// </summary>
public Encoding Encoding { get; }
/// <summary>
/// Gets the underlying stream of the EndianBinaryReader.
/// </summary>
@ -253,92 +208,6 @@ namespace SixLabors.ImageSharp.IO
return *((double*)&value);
}
/// <summary>
/// Reads a single character from the stream, using the character encoding for
/// this reader. If no characters have been fully read by the time the stream ends,
/// -1 is returned.
/// </summary>
/// <returns>The character read, or -1 for end of stream.</returns>
public int Read()
{
int charsRead = this.Read(this.charBuffer, 0, 1);
if (charsRead == 0)
{
return -1;
}
else
{
return this.charBuffer[0];
}
}
/// <summary>
/// Reads the specified number of characters into the given buffer, starting at
/// the given index.
/// </summary>
/// <param name="data">The buffer to copy data into</param>
/// <param name="index">The first index to copy data into</param>
/// <param name="count">The number of characters to read</param>
/// <returns>The number of characters actually read. This will only be less than
/// the requested number of characters if the end of the stream is reached.
/// </returns>
public int Read(char[] data, int index, int count)
{
this.CheckDisposed();
Guard.NotNull(this.storageBuffer, nameof(this.storageBuffer));
Guard.MustBeGreaterThanOrEqualTo(index, 0, nameof(index));
Guard.MustBeGreaterThanOrEqualTo(count, 0, nameof(count));
Guard.IsFalse(count + index > data.Length, nameof(data.Length), "Not enough space in buffer for specified number of characters starting at specified index.");
int read = 0;
bool firstTime = true;
// Use the normal buffer if we're only reading a small amount, otherwise
// use at most 4K at a time.
byte[] byteBuffer = this.storageBuffer;
if (byteBuffer.Length < count * this.minBytesPerChar)
{
byteBuffer = new byte[4096];
}
while (read < count)
{
int amountToRead;
// First time through we know we haven't previously read any data
if (firstTime)
{
amountToRead = count * this.minBytesPerChar;
firstTime = false;
}
else
{
// After that we can only assume we need to fully read 'chars left -1' characters
// and a single byte of the character we may be in the middle of
amountToRead = ((count - read - 1) * this.minBytesPerChar) + 1;
}
if (amountToRead > byteBuffer.Length)
{
amountToRead = byteBuffer.Length;
}
int bytesRead = this.TryReadInternal(byteBuffer, amountToRead);
if (bytesRead == 0)
{
return read;
}
int decoded = this.decoder.GetChars(byteBuffer, 0, bytesRead, data, index);
read += decoded;
index += decoded;
}
return read;
}
/// <summary>
/// Reads the specified number of bytes into the given buffer, starting at
/// the given index.
@ -421,84 +290,6 @@ namespace SixLabors.ImageSharp.IO
return ret;
}
/// <summary>
/// Reads a 7-bit encoded integer from the stream. This is stored with the least significant
/// information first, with 7 bits of information per byte of value, and the top
/// bit as a continuation flag. This method is not affected by the endianness
/// of the bit converter.
/// </summary>
/// <returns>The 7-bit encoded integer read from the stream.</returns>
public int Read7BitEncodedInt()
{
this.CheckDisposed();
int ret = 0;
for (int shift = 0; shift < 35; shift += 7)
{
int b = this.BaseStream.ReadByte();
if (b == -1)
{
throw new EndOfStreamException();
}
ret = ret | ((b & 0x7f) << shift);
if ((b & 0x80) == 0)
{
return ret;
}
}
// Still haven't seen a byte with the high bit unset? Dodgy data.
throw new IOException("Invalid 7-bit encoded integer in stream.");
}
/// <summary>
/// Reads a 7-bit encoded integer from the stream. This is stored with the most significant
/// information first, with 7 bits of information per byte of value, and the top
/// bit as a continuation flag. This method is not affected by the endianness
/// of the bit converter.
/// </summary>
/// <returns>The 7-bit encoded integer read from the stream.</returns>
public int ReadBigEndian7BitEncodedInt()
{
this.CheckDisposed();
int ret = 0;
for (int i = 0; i < 5; i++)
{
int b = this.BaseStream.ReadByte();
if (b == -1)
{
throw new EndOfStreamException();
}
ret = (ret << 7) | (b & 0x7f);
if ((b & 0x80) == 0)
{
return ret;
}
}
// Still haven't seen a byte with the high bit unset? Dodgy data.
throw new IOException("Invalid 7-bit encoded integer in stream.");
}
/// <summary>
/// Reads a length-prefixed string from the stream, using the encoding for this reader.
/// A 7-bit encoded integer is first read, which specifies the number of bytes
/// to read from the stream. These bytes are then converted into a string with
/// the encoding for this reader.
/// </summary>
/// <returns>The string read from the stream.</returns>
public string ReadString()
{
int bytesToRead = this.Read7BitEncodedInt();
byte[] data = new byte[bytesToRead];
this.ReadInternal(data, bytesToRead);
return this.Encoding.GetString(data, 0, data.Length);
}
/// <summary>
/// Disposes of the underlying stream.
/// </summary>

94
src/ImageSharp/IO/EndianBinaryWriter.cs

@ -4,7 +4,6 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Text;
namespace SixLabors.ImageSharp.IO
{
@ -18,11 +17,6 @@ namespace SixLabors.ImageSharp.IO
/// </summary>
private readonly byte[] buffer = new byte[16];
/// <summary>
/// Buffer used for Write(char)
/// </summary>
private readonly char[] charBuffer = new char[1];
/// <summary>
/// The endianness used to write the data
/// </summary>
@ -33,42 +27,21 @@ namespace SixLabors.ImageSharp.IO
/// </summary>
private bool disposed;
/// <summary>
/// Initializes a new instance of the <see cref="EndianBinaryWriter"/> class
/// with the given bit converter, writing to the given stream, using UTF-8 encoding.
/// </summary>
/// <param name="endianness">Endianness to use when writing data</param>
/// <param name="stream">Stream to write data to</param>
public EndianBinaryWriter(Endianness endianness, Stream stream)
: this(endianness, stream, Encoding.UTF8)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="EndianBinaryWriter"/> class
/// with the given bit converter, writing to the given stream, using the given encoding.
/// </summary>
/// <param name="endianness">Endianness to use when writing data</param>
/// <param name="stream">Stream to write data to</param>
/// <param name="encoding">
/// Encoding to use when writing character data
/// </param>
public EndianBinaryWriter(Endianness endianness, Stream stream, Encoding encoding)
public EndianBinaryWriter(Endianness endianness, Stream stream)
{
Guard.NotNull(stream, nameof(stream));
Guard.NotNull(stream, nameof(encoding));
Guard.IsTrue(stream.CanWrite, nameof(stream), "Stream isn't writable");
this.BaseStream = stream;
this.Encoding = encoding;
this.endianness = endianness;
}
/// <summary>
/// Gets the encoding used to write strings
/// </summary>
public Encoding Encoding { get; }
/// <summary>
/// Gets the underlying stream of the EndianBinaryWriter.
/// </summary>
@ -291,71 +264,6 @@ namespace SixLabors.ImageSharp.IO
this.BaseStream.Write(value, offset, count);
}
/// <summary>
/// Writes a single character to the stream, using the encoding for this writer.
/// </summary>
/// <param name="value">The value to write</param>
public void Write(char value)
{
this.charBuffer[0] = value;
this.Write(this.charBuffer);
}
/// <summary>
/// Writes an array of characters to the stream, using the encoding for this writer.
/// </summary>
/// <param name="value">An array containing the characters to write</param>
/// <exception cref="ArgumentNullException">value is null</exception>
public void Write(char[] value)
{
Guard.NotNull(value, nameof(value));
this.CheckDisposed();
byte[] data = this.Encoding.GetBytes(value, 0, value.Length);
this.WriteInternal(data, data.Length);
}
/// <summary>
/// Writes a length-prefixed string to the stream, using the encoding for this writer.
/// </summary>
/// <param name="value">The value to write. Must not be null.</param>
/// <exception cref="ArgumentNullException">value is null</exception>
public void Write(string value)
{
Guard.NotNull(value, nameof(value));
this.CheckDisposed();
byte[] data = this.Encoding.GetBytes(value);
this.Write7BitEncodedInt(data.Length);
this.WriteInternal(data, data.Length);
}
/// <summary>
/// Writes a 7-bit encoded integer from the stream. This is stored with the least significant
/// information first, with 7 bits of information per byte of value, and the top
/// bit as a continuation flag.
/// </summary>
/// <param name="value">The 7-bit encoded integer to write to the stream</param>
public void Write7BitEncodedInt(int value)
{
this.CheckDisposed();
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value), "Value must be greater than or equal to 0.");
}
int index = 0;
while (value >= 128)
{
this.buffer[index++] = (byte)((value & 0x7f) | 0x80);
value = value >> 7;
index++;
}
this.buffer[index++] = (byte)value;
this.BaseStream.Write(this.buffer, 0, index);
}
/// <summary>
/// Disposes of the underlying stream.
/// </summary>

61
tests/ImageSharp.Tests/IO/EndianBinaryReaderTests.cs

@ -1,61 +0,0 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.IO;
using System.Text;
using SixLabors.ImageSharp.IO;
using Xunit;
namespace SixLabors.ImageSharp.Tests.IO
{
/// <summary>
/// The endian binary reader tests.
/// </summary>
public class EndianBinaryReaderTests
{
/// <summary>
/// The test string.
/// </summary>
private const string TestString = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmopqrstuvwxyz";
/// <summary>
/// The test bytes.
/// </summary>
private static readonly byte[] TestBytes = Encoding.ASCII.GetBytes(TestString);
/// <summary>
/// Tests to ensure that the reader can read beyond internal buffer size.
/// </summary>
[Fact]
public void ReadCharsBeyondInternalBufferSize()
{
var stream = new MemoryStream(TestBytes);
using (var subject = new EndianBinaryReader(Endianness.LittleEndian, stream))
{
char[] chars = new char[TestString.Length];
subject.Read(chars, 0, chars.Length);
Assert.Equal(TestString, new string(chars));
}
}
/// <summary>
/// Tests to ensure that the reader cannot read beyond the provided buffer size.
/// </summary>
[Fact]
public void ReadCharsBeyondProvidedBufferSize()
{
Assert.Throws<ArgumentException>(
() =>
{
var stream = new MemoryStream(TestBytes);
using (var subject = new EndianBinaryReader(Endianness.LittleEndian, stream))
{
char[] chars = new char[TestString.Length - 1];
subject.Read(chars, 0, TestString.Length);
}
});
}
}
}
Loading…
Cancel
Save