Merge branch 'release' into dev
This commit is contained in:
commit
e71c585eb3
|
|
@ -11,8 +11,8 @@ namespace Microsoft.AspNet.WebUtilities
|
||||||
{
|
{
|
||||||
internal class BufferedReadStream : Stream
|
internal class BufferedReadStream : Stream
|
||||||
{
|
{
|
||||||
private const char CR = '\r';
|
private const byte CR = (byte)'\r';
|
||||||
private const char LF = '\n';
|
private const byte LF = (byte)'\n';
|
||||||
|
|
||||||
private readonly Stream _inner;
|
private readonly Stream _inner;
|
||||||
private readonly byte[] _buffer;
|
private readonly byte[] _buffer;
|
||||||
|
|
@ -310,8 +310,9 @@ namespace Microsoft.AspNet.WebUtilities
|
||||||
public string ReadLine(int lengthLimit)
|
public string ReadLine(int lengthLimit)
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
StringBuilder builder = new StringBuilder();
|
var builder = new MemoryStream(200);
|
||||||
bool foundCR = false, foundCRLF = false;
|
bool foundCR = false, foundCRLF = false;
|
||||||
|
|
||||||
while (!foundCRLF && EnsureBuffered())
|
while (!foundCRLF && EnsureBuffered())
|
||||||
{
|
{
|
||||||
if (builder.Length > lengthLimit)
|
if (builder.Length > lengthLimit)
|
||||||
|
|
@ -321,19 +322,15 @@ namespace Microsoft.AspNet.WebUtilities
|
||||||
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
|
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundCRLF)
|
return DecodeLine(builder, foundCRLF);
|
||||||
{
|
|
||||||
return builder.ToString(0, builder.Length - 2); // Drop the CRLF
|
|
||||||
}
|
|
||||||
// Stream ended with no CRLF.
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> ReadLineAsync(int lengthLimit, CancellationToken cancellationToken)
|
public async Task<string> ReadLineAsync(int lengthLimit, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
StringBuilder builder = new StringBuilder();
|
var builder = new MemoryStream(200);
|
||||||
bool foundCR = false, foundCRLF = false;
|
bool foundCR = false, foundCRLF = false;
|
||||||
|
|
||||||
while (!foundCRLF && await EnsureBufferedAsync(cancellationToken))
|
while (!foundCRLF && await EnsureBufferedAsync(cancellationToken))
|
||||||
{
|
{
|
||||||
if (builder.Length > lengthLimit)
|
if (builder.Length > lengthLimit)
|
||||||
|
|
@ -344,25 +341,20 @@ namespace Microsoft.AspNet.WebUtilities
|
||||||
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
|
ProcessLineChar(builder, ref foundCR, ref foundCRLF);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foundCRLF)
|
return DecodeLine(builder, foundCRLF);
|
||||||
{
|
|
||||||
return builder.ToString(0, builder.Length - 2); // Drop the CRLF
|
|
||||||
}
|
|
||||||
// Stream ended with no CRLF.
|
|
||||||
return builder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessLineChar(StringBuilder builder, ref bool foundCR, ref bool foundCRLF)
|
private void ProcessLineChar(MemoryStream builder, ref bool foundCR, ref bool foundCRLF)
|
||||||
{
|
{
|
||||||
char ch = (char)_buffer[_bufferOffset]; // TODO: Encoding enforcement
|
var b = _buffer[_bufferOffset];
|
||||||
builder.Append(ch);
|
builder.WriteByte(b);
|
||||||
_bufferOffset++;
|
_bufferOffset++;
|
||||||
_bufferCount--;
|
_bufferCount--;
|
||||||
if (ch == CR)
|
if (b == CR)
|
||||||
{
|
{
|
||||||
foundCR = true;
|
foundCR = true;
|
||||||
}
|
}
|
||||||
else if (ch == LF)
|
else if (b == LF)
|
||||||
{
|
{
|
||||||
if (foundCR)
|
if (foundCR)
|
||||||
{
|
{
|
||||||
|
|
@ -375,6 +367,13 @@ namespace Microsoft.AspNet.WebUtilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string DecodeLine(MemoryStream builder, bool foundCRLF)
|
||||||
|
{
|
||||||
|
// Drop the final CRLF, if any
|
||||||
|
var length = foundCRLF ? builder.Length - 2 : builder.Length;
|
||||||
|
return Encoding.UTF8.GetString(builder.ToArray(), 0, (int)length);
|
||||||
|
}
|
||||||
|
|
||||||
private void CheckDisposed()
|
private void CheckDisposed()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,19 @@ Content-Type: text/plain
|
||||||
|
|
||||||
Content of a.txt.
|
Content of a.txt.
|
||||||
|
|
||||||
|
--9051914041544843365972754266--
|
||||||
|
";
|
||||||
|
private const string TwoPartBodyWithUnicodeFileName =
|
||||||
|
@"--9051914041544843365972754266
|
||||||
|
Content-Disposition: form-data; name=""text""
|
||||||
|
|
||||||
|
text default
|
||||||
|
--9051914041544843365972754266
|
||||||
|
Content-Disposition: form-data; name=""file1""; filename=""a色.txt""
|
||||||
|
Content-Type: text/plain
|
||||||
|
|
||||||
|
Content of a.txt.
|
||||||
|
|
||||||
--9051914041544843365972754266--
|
--9051914041544843365972754266--
|
||||||
";
|
";
|
||||||
private const string ThreePartBody =
|
private const string ThreePartBody =
|
||||||
|
|
@ -147,6 +160,32 @@ Content-Type: text/html
|
||||||
Assert.Null(await reader.ReadNextSectionAsync());
|
Assert.Null(await reader.ReadNextSectionAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MutipartReader_ReadTwoPartBodyWithUnicodeFileName_Success()
|
||||||
|
{
|
||||||
|
var stream = MakeStream(TwoPartBodyWithUnicodeFileName);
|
||||||
|
var reader = new MultipartReader(Boundary, stream);
|
||||||
|
|
||||||
|
var section = await reader.ReadNextSectionAsync();
|
||||||
|
Assert.NotNull(section);
|
||||||
|
Assert.Equal(1, section.Headers.Count);
|
||||||
|
Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
|
||||||
|
var buffer = new MemoryStream();
|
||||||
|
await section.Body.CopyToAsync(buffer);
|
||||||
|
Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
|
||||||
|
|
||||||
|
section = await reader.ReadNextSectionAsync();
|
||||||
|
Assert.NotNull(section);
|
||||||
|
Assert.Equal(2, section.Headers.Count);
|
||||||
|
Assert.Equal("form-data; name=\"file1\"; filename=\"a色.txt\"", section.Headers["Content-Disposition"][0]);
|
||||||
|
Assert.Equal("text/plain", section.Headers["Content-Type"][0]);
|
||||||
|
buffer = new MemoryStream();
|
||||||
|
await section.Body.CopyToAsync(buffer);
|
||||||
|
Assert.Equal("Content of a.txt.\r\n", Encoding.ASCII.GetString(buffer.ToArray()));
|
||||||
|
|
||||||
|
Assert.Null(await reader.ReadNextSectionAsync());
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task MutipartReader_ThreePartBody_Success()
|
public async Task MutipartReader_ThreePartBody_Success()
|
||||||
{
|
{
|
||||||
|
|
@ -181,5 +220,77 @@ Content-Type: text/html
|
||||||
|
|
||||||
Assert.Null(await reader.ReadNextSectionAsync());
|
Assert.Null(await reader.ReadNextSectionAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MutipartReader_ReadInvalidUtf8Header_ReplacementCharacters()
|
||||||
|
{
|
||||||
|
var body1 =
|
||||||
|
@"--9051914041544843365972754266
|
||||||
|
Content-Disposition: form-data; name=""text"" filename=""a";
|
||||||
|
|
||||||
|
var body2 =
|
||||||
|
@".txt""
|
||||||
|
|
||||||
|
text default
|
||||||
|
--9051914041544843365972754266--
|
||||||
|
";
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(body1);
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
// Write an invalid utf-8 segment in the middle
|
||||||
|
stream.Write(new byte[] { 0xC1, 0x21 }, 0, 2);
|
||||||
|
|
||||||
|
bytes = Encoding.UTF8.GetBytes(body2);
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
var reader = new MultipartReader(Boundary, stream);
|
||||||
|
|
||||||
|
var section = await reader.ReadNextSectionAsync();
|
||||||
|
Assert.NotNull(section);
|
||||||
|
Assert.Equal(1, section.Headers.Count);
|
||||||
|
Assert.Equal("form-data; name=\"text\" filename=\"a\uFFFD!.txt\"", section.Headers["Content-Disposition"][0]);
|
||||||
|
var buffer = new MemoryStream();
|
||||||
|
await section.Body.CopyToAsync(buffer);
|
||||||
|
Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
|
||||||
|
|
||||||
|
Assert.Null(await reader.ReadNextSectionAsync());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MutipartReader_ReadInvalidUtf8SurrogateHeader_ReplacementCharacters()
|
||||||
|
{
|
||||||
|
var body1 =
|
||||||
|
@"--9051914041544843365972754266
|
||||||
|
Content-Disposition: form-data; name=""text"" filename=""a";
|
||||||
|
|
||||||
|
var body2 =
|
||||||
|
@".txt""
|
||||||
|
|
||||||
|
text default
|
||||||
|
--9051914041544843365972754266--
|
||||||
|
";
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(body1);
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
|
||||||
|
// Write an invalid utf-8 segment in the middle
|
||||||
|
stream.Write(new byte[] { 0xED, 0xA0, 85 }, 0, 3);
|
||||||
|
|
||||||
|
bytes = Encoding.UTF8.GetBytes(body2);
|
||||||
|
stream.Write(bytes, 0, bytes.Length);
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
var reader = new MultipartReader(Boundary, stream);
|
||||||
|
|
||||||
|
var section = await reader.ReadNextSectionAsync();
|
||||||
|
Assert.NotNull(section);
|
||||||
|
Assert.Equal(1, section.Headers.Count);
|
||||||
|
Assert.Equal("form-data; name=\"text\" filename=\"a\uFFFDU.txt\"", section.Headers["Content-Disposition"][0]);
|
||||||
|
var buffer = new MemoryStream();
|
||||||
|
await section.Body.CopyToAsync(buffer);
|
||||||
|
Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
|
||||||
|
|
||||||
|
Assert.Null(await reader.ReadNextSectionAsync());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue