http://blog.aspcool.com/wucountry/archive/2005/11/04/3259.html
License#region License
/**//*
* SunriseUpload - Asp.net Upload Component
*
* Copyright (C) 2004 mic <mic4free@hotmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* In case your copy of SunriseUpload does not include a copy of the license, you may find it online at
* http://www.gnu.org/copyleft/gpl.html
*
* You can find new release of this component at http://athena.9966.org/SunriseUpload .
*/
#endregion
using System;
using System.Collections;
using System.IO;
using System.Text;
using System.Web;
namespace Sunrise.Web.Upload
{
internal class RequestStream
{
Fields#region Fields
private ArrayList contentBody;
private FileStatus fileStatus;
private FileStream fs;
private ArrayList readBody;
private ReadStatus readStatus;
private string originalFileName;
#endregion
// Nested Types
public enum FileStatus : byte
{
// Fields
Close = 1,
Open = 0
}
public enum ReadStatus : byte
{
// Fields
NoRead = 0,
Read = 1
}
Properties#region Properties
public ArrayList ContentBody
{
get { return this.contentBody; }
}
public FileStream FileStream
{
get { return this.fs; }
}
public FileStatus FStatus
{
get { return this.fileStatus; }
}
public string OriginalFileName
{
get { return this.originalFileName; }
}
public ArrayList ReadBody
{
get { return this.readBody; }
}
public ReadStatus RStatus
{
get { return this.readStatus; }
}
#endregion
/**//// <summary>
///
/// </summary>
/// <param name="preloadBytes">Already upload data.</param>
/// <param name="boundaryBytes"></param>
/// <param name="fileStream">Stream to output data</param>
/// <param name="fileStatus"></param>
/// <param name="readStatus"></param>
/// <param name="uploadFolder"></param>
/// <param name="writeToDisk"></param>
/// <param name="context">HttpContext,contaion the data uploaded by user.</param>
/// <param name="currFileName">current file name?</param>
public RequestStream( byte[] preloadBytes,
byte[] boundaryBytes,
FileStream fileStream,
FileStatus fileStatus,
ReadStatus readStatus,
string uploadFolder,
bool writeToDisk,
HttpContext context,
string currFileName)
{
this.readBody = new ArrayList();
this.contentBody = new ArrayList();
this.fs = null;
this.originalFileName = string.Empty;
this.fileStatus = FileStatus.Close;
this.readStatus = ReadStatus.NoRead;
this.originalFileName = currFileName;
this.fs = fileStream;
this.fileStatus = fileStatus;
this.readStatus = readStatus;
int preloadPosition = 0;
while ((preloadPosition < preloadBytes.Length))
{
ArrayList list1 = new ArrayList();
#region
//说真的,我不是很喜欢这样的算法,在没明白上传数据的结构的时候,就不知道它在干什么!!!
//当然,如果在ASP里写过上传组件的人,应该都可以在ASP.net下写出很好的上传组件。
if (preloadBytes[preloadPosition] == 13)
{
//如果是回车,那么要处理回车后的数据。这是分析的重重点。。。
#region
int boundaryPosition = 0;
while (((preloadPosition < preloadBytes.Length) && (boundaryPosition < boundaryBytes.Length)))
{
//分析数据是否是分隔字符。
#region
if (preloadBytes[preloadPosition] != boundaryBytes[boundaryPosition])
{
break;
}
list1.Add(preloadBytes[preloadPosition]);
boundaryPosition++;
preloadPosition++;
#endregion
}
if (boundaryPosition == boundaryBytes.Length)
{
//如果遇到一个分隔符,那么就处理分隔符后面的数据
#region
if (this.fileStatus == FileStatus.Open)
{
//如果文件是打开的,那么要保存文件了,因为这里可能是一个文件的结束。
//显然,在第一次执行这里的时候,是不可能有文件保存的。
this.fs.Flush();
this.fs.Close();
this.fileStatus = FileStatus.Close;
this.originalFileName = string.Empty;
}
else if (this.readStatus == ReadStatus.NoRead)
{
//否则,如果记取标记为不可读,??
//设定为可读,以便后面处理??
this.readStatus = ReadStatus.Read;
context.Items["Sunrise_Web_Upload_FileStatus"] = this.fileStatus;
}
if ((preloadPosition + 2) < preloadBytes.Length)
{
//如果遇到一个分隔符后,还有数据,那么要处理后面的数据了,
//这里可能是一个文件的开始,也可能是文本数据的开始。
#region
list1.Add(preloadBytes[preloadPosition]);
preloadPosition++;
list1.Add(preloadBytes[preloadPosition]);
preloadPosition++;
//上面把两个字节的数据存入到preloadBytes里去,因为这是可用的文本信息。
ArrayList list2 = new ArrayList();
while ((preloadPosition < preloadBytes.Length))
{
//这里要先分析分隔符后的头,看它是文本还是上传文件。
list1.Add(preloadBytes[preloadPosition]);
#region
if (preloadBytes[preloadPosition] == 13)
{
//如果遇到一个回车,就是说一个contenHead结束了。。。
//list2里记录了分隔符后到正式数据开始之间的内容。
byte[] buffer = new byte[list2.Count];
list2.CopyTo(buffer);
//Get request content from buffer
string requestContent = Utils.GetContext().Request.ContentEncoding.GetString(buffer);
//分析这个contenHead,它是否是上传的文件。
//If this line is content head
if (requestContent.IndexOf("\"; filename=\"") > 0)
{
//如果是上传的文件,而不是文本数据。。。
//处理上传的文件数据。。。。。这里是最核心的内容了。。
//因为这里可能是一个文件的一部份数据,而不是所有的内容。。
//但也有可能是一个完整的文件数据。。。
preloadPosition++;
ArrayList list3 = new ArrayList();
//list3来记录文件数据。
#region
while ((preloadPosition < preloadBytes.Length))
{
//处理文件数据,当然要在读取数据小于上传数据的时候
//在文件数据前面,还有一段文件类型的识别,因此要先处理它,
//这是一段文本,因此要添加到contentBody里去,这里用list1来记录
//出了循环后,会添加到contentBody里去。
//同时,list3记录一文件的类型信息
list1.Add(preloadBytes[preloadPosition]);
#region
if (preloadBytes[preloadPosition] == 13)
{
//再次遇到一个回车的时候,就是文件类型信息结束的时候。。。。
#region
if ((preloadPosition + 3) < preloadBytes.Length)
{
//后面还有数据,这里才是真正的文件数据。
char[] spliter = new char[1]{';'};
//分析文件名及一些相关信息。。。
//这是一个例子,requestContent里的数据可能是这样的:
//Content-Disposition: form-data; name="m_file"; filename="D:\WuCountry\Pictures\logo.png"
string[] contentArray = requestContent.Split(spliter);
string fileNameString = contentArray[2].Trim();
fileNameString = fileNameString.Substring(10, (fileNameString.Length - 11));
#region
if ((writeToDisk && (fileNameString != null)) && (fileNameString != string.Empty))
{
//如果设定为可以写入磁盘,而且给定的文件名存在,那么把数据写入磁盘,否则可以不做处理。
this.originalFileName = Path.GetFileName(fileNameString);
preloadPosition += 3;
//这里list3里记录的是什么?前面分析的好像有点问题。。。。
byte[] buffer2 = new byte[list3.Count];
list3.CopyTo(buffer2);
//没错,这里记录的是上传文件的类型。例如:Content-Type: image/x-png
string contentType = Utils.GetContext().Request.ContentEncoding.GetString(buffer2);
string guidFileName = (Guid.NewGuid().ToString() + Path.GetExtension(fileNameString));
string fileFullPath = uploadFolder;
//If upload folder deos not exist, use system temporary folder to hold the file.
if (fileFullPath == string.Empty)
{
fileFullPath = Path.GetTempPath();
}
//设定文件的临时文件名,用GUID来设定。
fileFullPath = Path.Combine(fileFullPath, guidFileName);
//Build the content head
StringBuilder sb = new StringBuilder();
string[] sbArray = new string[11];
//后面的注释为数据的可能值:或者是示例值:
sbArray[0] = "\r\n";
sbArray[1] = contentArray[0]; //Content-Disposition: form-data
sbArray[2] = ";";
sbArray[3] = contentArray[1]; //name="m_file"
sbArray[4] = "\r\n\r\n";
sbArray[5] = contentType.Trim(); //Content-Type: image/x-png
sbArray[6] = ";filename=\"";
sbArray[7] = fileNameString; //D:\WuCountry\Pictures\logo.png
sbArray[8] = "\";filepath=\"";
sbArray[9] = guidFileName; //文件的GUID文件名值。
sbArray[10] = "\"";
sb.Append(string.Concat(sbArray));
//Save boundary bytes to server file
//创建文件,准备写入文件流。。。并将该信息写入到HttpContent里去,
//因为有可能一次写不完一个文件,还要在下次读取数据的时候用它。
this.fs = new FileStream(fileFullPath, FileMode.Create);
context.Items["Sunrise_Web_Upload_FileStream"] = this.fs;
this.fileStatus = FileStatus.Open;
//为下次读取写入文件做准备
context.Items["Sunrise_Web_Upload_FileStatus"] = this.fileStatus;
//有可能为多个文件上传,这里先写入一个文件的信息到HttpContent里去,这样就可以取得上传文件信息了
Hashtable ht = ((Hashtable) context.Items["Sunrise_Web_Upload_FileList"]);
ht.Add(Path.GetFileNameWithoutExtension(guidFileName), fileFullPath);
context.Items["Sunrise_Web_Upload_FileList"] = ht;
//将信息记录在readBody里。。。
this.readBody.AddRange(boundaryBytes);
this.readBody.AddRange(Encoding.UTF8.GetBytes(sb.ToString().ToCharArray()));
sb.Remove(0, sb.Length);
break;
}
#endregion
preloadPosition++;
break;
}
#endregion
this.contentBody.AddRange(list1);
break;
}
#endregion
list3.Add(preloadBytes[preloadPosition]);
preloadPosition++;
}
#endregion
//处理完文件数据后,要跳出循环,让程序去查找下一个分隔符的位置。
break;
}
this.readStatus = ReadStatus.NoRead;
this.readBody.AddRange(list1);
break;
}
#endregion
list2.Add(preloadBytes[preloadPosition]);
preloadPosition++;
}
if (preloadPosition < preloadBytes.Length)
{
preloadPosition++;
continue;
}
this.contentBody.AddRange(list1);
preloadPosition++;
continue;
/**//*
if ((preloadBytes[preloadPosition] != 0x2d) || ((preloadPosition + 3) >= preloadBytes.Length))
{
preloadPosition++;
continue;
}
this.readBody.AddRange(boundaryBytes);
byte[] buffer3 = new byte[4] {0x2d, 0x2d, 13, 10};
this.readBody.AddRange(buffer3);
return;
*/
#endregion
}
this.contentBody.AddRange(list1);
#endregion
}
else if (preloadPosition < preloadBytes.Length)
{
//否则,如果已经分析的数据小于已经上传的数据,就继续处理数据。
#region
if (this.fileStatus == FileStatus.Open)
{
byte[] buffer4 = new byte[list1.Count];
for(int i = 0; i < list1.Count; i++)
{
buffer4[i] = ((byte) list1[i]);
}
this.fs.Write(buffer4, 0, buffer4.Length);
}
else if (this.readStatus == ReadStatus.NoRead)
{
this.readBody.AddRange(list1);
}
preloadPosition --;
#endregion
}
else
{
//否则,已经分析的数据等于上传数据,因此把数据添加到preloadBytes里去。
this.contentBody.AddRange(list1);
}
#endregion
}
else if (this.fileStatus == FileStatus.Open)
{
//这里处理文件数据
//否则,如果文件是打开的,那么可以写入到文件里去。
//这里要注意,文件的可写状态在前面设定,因为第一个字节一定不是回车,因此一开始,有很多数据都只是放在preloadBytes里去了,
//里面更多的是文本内容。。。。而实际上,preloadBytes里就是存放上传时的读取的文本内容。。。
this.fs.WriteByte(preloadBytes[preloadPosition]);
}
else if(this.readStatus == ReadStatus.NoRead)
{
//这里处理文本数据。
//如果不可读,那么只加入到preloadBytes里去。
//这里加入到preloadBytes里是为了在后面读取文本信息时用。。。
this.readBody.Add(preloadBytes[preloadPosition]);
}
#endregion
preloadPosition++;
}
}
/**//// <summary>
/// Destructor method, release all resource
/// </summary>
~ RequestStream()
{
this.readBody = null;
this.contentBody = null;
this.fs = null;
}
}
}
文章来源:
http://computer.mblogger.cn/wucountry/posts/48602.aspx