435 lines
14 KiB
C#
435 lines
14 KiB
C#
|
//------------------------------------------------------------------------------
|
||
|
// <copyright file="HttpInputStream.cs" company="Microsoft">
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
// </copyright>
|
||
|
//------------------------------------------------------------------------------
|
||
|
|
||
|
/*
|
||
|
* Input stream used in response and uploaded file objects
|
||
|
*
|
||
|
* Copyright (c) 1998 Microsoft Corporation
|
||
|
*/
|
||
|
|
||
|
namespace System.Web {
|
||
|
|
||
|
using System.IO;
|
||
|
using System.CodeDom.Compiler; // needed for TempFilesCollection
|
||
|
using System.Security;
|
||
|
using System.Security.Permissions;
|
||
|
using System.Web.Hosting;
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Wrapper around temporary file or byte[] for input stream
|
||
|
*
|
||
|
* Pattern of use:
|
||
|
* ctor
|
||
|
* AddBytes
|
||
|
* ...
|
||
|
* DoneAddingBytes
|
||
|
* access bytes: [] / CopyBytes / WriteBytes / GetAsByteArray
|
||
|
* Dispose
|
||
|
*/
|
||
|
internal class HttpRawUploadedContent : IDisposable {
|
||
|
private int _fileThreshold; // for sizes over this use file
|
||
|
private int _expectedLength;// content-length
|
||
|
private bool _completed; // true when all data's in
|
||
|
private int _length; // length of the data
|
||
|
private byte[] _data; // contains data (either all of it or part read from file)
|
||
|
private TempFile _file; // temporary file with content (null when using byte[])
|
||
|
private int _chunkOffset; // which part of file is cached in data - offset
|
||
|
private int _chunkLength; // which part of file is cached in data - length
|
||
|
|
||
|
internal HttpRawUploadedContent(int fileThreshold, int expectedLength) {
|
||
|
_fileThreshold = fileThreshold;
|
||
|
_expectedLength = expectedLength;
|
||
|
|
||
|
if (_expectedLength >= 0 && _expectedLength < _fileThreshold)
|
||
|
_data = new byte[_expectedLength];
|
||
|
else
|
||
|
_data = new byte[_fileThreshold];
|
||
|
}
|
||
|
|
||
|
public void Dispose() {
|
||
|
if (_file != null)
|
||
|
_file.Dispose();
|
||
|
}
|
||
|
|
||
|
internal void AddBytes(byte[] data, int offset, int length) {
|
||
|
if (_completed)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
if (length <= 0)
|
||
|
return;
|
||
|
|
||
|
if (_file == null) {
|
||
|
// fits in the existing _data
|
||
|
if (_length + length <= _data.Length) {
|
||
|
Array.Copy(data, offset, _data, _length, length);
|
||
|
_length += length;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// doesn't fit in _data but still under threshold
|
||
|
// possible if content-length is -1, or when filtering
|
||
|
if (_length + length <= _fileThreshold) {
|
||
|
byte[] newData = new byte[_fileThreshold];
|
||
|
if (_length > 0)
|
||
|
Array.Copy(_data, 0, newData, 0, _length);
|
||
|
Array.Copy(data, offset, newData, _length, length);
|
||
|
|
||
|
_data = newData;
|
||
|
_length += length;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// need to convert to file
|
||
|
_file = new TempFile();
|
||
|
_file.AddBytes(_data, 0, _length);
|
||
|
}
|
||
|
|
||
|
// using file
|
||
|
_file.AddBytes(data, offset, length);
|
||
|
_length += length;
|
||
|
}
|
||
|
|
||
|
internal void DoneAddingBytes() {
|
||
|
if (_data == null)
|
||
|
_data = new byte[0];
|
||
|
|
||
|
if (_file != null)
|
||
|
_file.DoneAddingBytes();
|
||
|
|
||
|
_completed = true;
|
||
|
}
|
||
|
|
||
|
internal int Length {
|
||
|
get { return _length; }
|
||
|
}
|
||
|
|
||
|
internal byte this[int index] {
|
||
|
get {
|
||
|
if (!_completed)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
// all data in memory
|
||
|
if (_file == null)
|
||
|
return _data[index];
|
||
|
|
||
|
// index in the chunk already read
|
||
|
if (index >= _chunkOffset && index < _chunkOffset + _chunkLength)
|
||
|
return _data[index - _chunkOffset];
|
||
|
|
||
|
// check bounds
|
||
|
if (index < 0 || index >= _length)
|
||
|
throw new ArgumentOutOfRangeException("index");
|
||
|
|
||
|
// read from file
|
||
|
_chunkLength = _file.GetBytes(index, _data.Length, _data, 0);
|
||
|
_chunkOffset = index;
|
||
|
return _data[0];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void CopyBytes(int offset, byte[] buffer, int bufferOffset, int length) {
|
||
|
if (!_completed)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
if (_file != null) {
|
||
|
if (offset >= _chunkOffset && offset+length < _chunkOffset + _chunkLength) {
|
||
|
// preloaded
|
||
|
Array.Copy(_data, offset - _chunkOffset, buffer, bufferOffset, length);
|
||
|
}
|
||
|
else {
|
||
|
if (length <= _data.Length) {
|
||
|
// read from file and remember the chunk
|
||
|
_chunkLength = _file.GetBytes(offset, _data.Length, _data, 0);
|
||
|
_chunkOffset = offset;
|
||
|
Array.Copy(_data, offset - _chunkOffset, buffer, bufferOffset, length);
|
||
|
}
|
||
|
else {
|
||
|
// read from file
|
||
|
_file.GetBytes(offset, length, buffer, bufferOffset);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
Array.Copy(_data, offset, buffer, bufferOffset, length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void WriteBytes(int offset, int length, Stream stream) {
|
||
|
if (!_completed)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
if (_file != null) {
|
||
|
int readPosition = offset;
|
||
|
int bytesRemaining = length;
|
||
|
byte[] buf = new byte[bytesRemaining > _fileThreshold ? _fileThreshold : bytesRemaining];
|
||
|
|
||
|
while (bytesRemaining > 0) {
|
||
|
int bytesToRead = bytesRemaining > _fileThreshold ? _fileThreshold : bytesRemaining;
|
||
|
int bytesRead = _file.GetBytes(readPosition, bytesToRead, buf, 0);
|
||
|
if (bytesRead == 0)
|
||
|
break;
|
||
|
|
||
|
stream.Write(buf, 0, bytesRead);
|
||
|
|
||
|
readPosition += bytesRead;
|
||
|
bytesRemaining -= bytesRead;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
stream.Write(_data, offset, length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal byte[] GetAsByteArray() {
|
||
|
// If the request is chunked, _data can be much larger than
|
||
|
// the actual number of bytes read, and FillInFormCollection
|
||
|
// will call FillFromEncodedBytes and incorrectly append a
|
||
|
// bunch of zeros to the last form value. Therefore, we copy
|
||
|
// the data into a smaller array if _length < _data.Length
|
||
|
if (_file == null && _length == _data.Length) {
|
||
|
return _data;
|
||
|
}
|
||
|
return GetAsByteArray(0, _length);
|
||
|
}
|
||
|
|
||
|
internal byte[] GetAsByteArray(int offset, int length) {
|
||
|
if (!_completed)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
if (length == 0)
|
||
|
return new byte[0];
|
||
|
|
||
|
byte[] result = new byte[length];
|
||
|
CopyBytes(offset, result, 0, length);
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
// helper class for a temp file for large posted data
|
||
|
class TempFile : IDisposable {
|
||
|
TempFileCollection _tempFiles;
|
||
|
String _filename;
|
||
|
Stream _filestream;
|
||
|
|
||
|
internal TempFile() {
|
||
|
// suspend the impersonation for the file creation
|
||
|
using (new ApplicationImpersonationContext()) {
|
||
|
String tempDir = Path.Combine(HttpRuntime.CodegenDirInternal, "uploads");
|
||
|
|
||
|
// Assert IO access to the temporary directory
|
||
|
new FileIOPermission(FileIOPermissionAccess.AllAccess, tempDir).Assert();
|
||
|
|
||
|
if (!Directory.Exists(tempDir)) {
|
||
|
try {
|
||
|
Directory.CreateDirectory(tempDir);
|
||
|
}
|
||
|
catch {
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_tempFiles = new TempFileCollection(tempDir, false /*keepFiles*/);
|
||
|
_filename = _tempFiles.AddExtension("post", false /*keepFiles*/);
|
||
|
//using 4096 as the buffer size, same as the BCL
|
||
|
_filestream = new FileStream(_filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Dispose() {
|
||
|
// suspend the impersonation for the file creation
|
||
|
using (new ApplicationImpersonationContext()) {
|
||
|
try {
|
||
|
// force filestream handle to close
|
||
|
// since we're using FILE_FLAG_DELETE_ON_CLOSE
|
||
|
// this will delete it from disk as well
|
||
|
if (_filestream != null) {
|
||
|
_filestream.Close();
|
||
|
}
|
||
|
|
||
|
_tempFiles.Delete();
|
||
|
((IDisposable)_tempFiles).Dispose();
|
||
|
}
|
||
|
catch {
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void AddBytes(byte[] data, int offset, int length) {
|
||
|
if (_filestream == null)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
_filestream.Write(data, offset, length);
|
||
|
}
|
||
|
|
||
|
internal void DoneAddingBytes() {
|
||
|
if (_filestream == null)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
_filestream.Flush();
|
||
|
_filestream.Seek(0, SeekOrigin.Begin);
|
||
|
}
|
||
|
|
||
|
internal int GetBytes(int offset, int length, byte[] buffer, int bufferOffset) {
|
||
|
if (_filestream == null)
|
||
|
throw new InvalidOperationException();
|
||
|
|
||
|
_filestream.Seek(offset, SeekOrigin.Begin);
|
||
|
return _filestream.Read(buffer, bufferOffset, length);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Stream object over HttpRawUploadedContent
|
||
|
* Not a publc class - used internally, returned as Stream
|
||
|
*/
|
||
|
internal class HttpInputStream : Stream {
|
||
|
private HttpRawUploadedContent _data; // the buffer with the content
|
||
|
private int _offset; // offset to the start of this stream
|
||
|
private int _length; // length of this stream
|
||
|
private int _pos; // current reader posision
|
||
|
|
||
|
//
|
||
|
// Internal access (from this package)
|
||
|
//
|
||
|
|
||
|
internal HttpInputStream(HttpRawUploadedContent data, int offset, int length) {
|
||
|
Init(data, offset, length);
|
||
|
}
|
||
|
|
||
|
protected void Init(HttpRawUploadedContent data, int offset, int length) {
|
||
|
_data = data;
|
||
|
_offset = offset;
|
||
|
_length = length;
|
||
|
_pos = 0;
|
||
|
}
|
||
|
|
||
|
protected void Uninit() {
|
||
|
_data = null;
|
||
|
_offset = 0;
|
||
|
_length = 0;
|
||
|
_pos = 0;
|
||
|
}
|
||
|
|
||
|
internal byte[] GetAsByteArray() {
|
||
|
if (_length == 0)
|
||
|
return null;
|
||
|
|
||
|
return _data.GetAsByteArray(_offset, _length);
|
||
|
}
|
||
|
|
||
|
internal void WriteTo(Stream s) {
|
||
|
if (_data != null && _length > 0)
|
||
|
_data.WriteBytes(_offset, _length, s);
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// BufferedStream implementation
|
||
|
//
|
||
|
|
||
|
public override bool CanRead {
|
||
|
get {return true;}
|
||
|
}
|
||
|
|
||
|
public override bool CanSeek {
|
||
|
get {return true;}
|
||
|
}
|
||
|
|
||
|
public override bool CanWrite {
|
||
|
get {return false;}
|
||
|
}
|
||
|
|
||
|
public override long Length {
|
||
|
get {return _length;}
|
||
|
}
|
||
|
|
||
|
public override long Position {
|
||
|
get {return _pos;}
|
||
|
|
||
|
set {
|
||
|
Seek(value, SeekOrigin.Begin);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
protected override void Dispose(bool disposing) {
|
||
|
try {
|
||
|
if (disposing)
|
||
|
Uninit();
|
||
|
}
|
||
|
finally {
|
||
|
base.Dispose(disposing);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void Flush() {
|
||
|
}
|
||
|
|
||
|
public override long Seek(long offset, SeekOrigin origin) {
|
||
|
int newpos = _pos;
|
||
|
int offs = (int)offset;
|
||
|
|
||
|
switch (origin) {
|
||
|
case SeekOrigin.Begin:
|
||
|
newpos = offs;
|
||
|
break;
|
||
|
case SeekOrigin.Current:
|
||
|
newpos = _pos + offs;
|
||
|
break;
|
||
|
case SeekOrigin.End:
|
||
|
newpos = _length + offs;
|
||
|
break;
|
||
|
default:
|
||
|
throw new ArgumentOutOfRangeException("origin");
|
||
|
}
|
||
|
|
||
|
if (newpos < 0 || newpos > _length)
|
||
|
throw new ArgumentOutOfRangeException("offset");
|
||
|
|
||
|
_pos = newpos;
|
||
|
return _pos;
|
||
|
}
|
||
|
|
||
|
public override void SetLength(long length) {
|
||
|
throw new NotSupportedException();
|
||
|
}
|
||
|
|
||
|
public override int Read(byte[] buffer, int offset, int count) {
|
||
|
// find the number of bytes to copy
|
||
|
int numBytes = _length - _pos;
|
||
|
if (count < numBytes)
|
||
|
numBytes = count;
|
||
|
|
||
|
// copy the bytes
|
||
|
if (numBytes > 0)
|
||
|
_data.CopyBytes(_offset + _pos, buffer, offset, numBytes);
|
||
|
|
||
|
// adjust the position
|
||
|
_pos += numBytes;
|
||
|
return numBytes;
|
||
|
}
|
||
|
|
||
|
public override void Write(byte[] buffer, int offset, int count) {
|
||
|
throw new NotSupportedException();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Stream used as the source for input filtering
|
||
|
*/
|
||
|
|
||
|
internal class HttpInputStreamFilterSource : HttpInputStream {
|
||
|
internal HttpInputStreamFilterSource() : base(null, 0, 0) {
|
||
|
}
|
||
|
|
||
|
internal void SetContent(HttpRawUploadedContent data) {
|
||
|
if (data != null)
|
||
|
base.Init(data, 0, data.Length);
|
||
|
else
|
||
|
base.Uninit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|