public class FileDownload : IDisposable
readonly static string MULTIPART_BOUNDARY = "<q1w2e3r4t5y6u7i8o9p0>";
readonly static string MULTIPART_CONTENTTYPE = "multipart/byteranges; boundary=" + MULTIPART_BOUNDARY;
readonly static string HTTP_HEADER_Content_Disposition = "Content-Disposition";
readonly static string HTTP_HEADER_ACCEPT_RANGES = "Accept-Ranges";
readonly static string HTTP_HEADER_ACCEPT_RANGES_BYTES = "bytes";
readonly static string HTTP_HEADER_CONTENT_TYPE = "Content-Type";
readonly static string HTTP_HEADER_CONTENT_RANGE = "Content-Range";
readonly static string HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
readonly static string HTTP_HEADER_ENTITY_TAG = "ETag";
readonly static string HTTP_HEADER_LAST_MODIFIED = "Last-Modified";
readonly static string HTTP_HEADER_RANGE = "Range";
readonly static string HTTP_HEADER_IF_RANGE = "If-Range";
readonly static string HTTP_HEADER_IF_MATCH = "If-Match";
readonly static string HTTP_HEADER_IF_NONE_MATCH = "If-None-Match";
readonly static string HTTP_HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
readonly static string HTTP_HEADER_IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
readonly static string HTTP_HEADER_UNLESS_MODIFIED_SINCE = "Unless-Modified-Since";
readonly static string HTTP_METHOD_GET = "GET";
readonly static string HTTP_METHOD_HEAD = "HEAD";
readonly static string HTTP_METHOD_POST = "POST";
private string contentType = "application/octet-stream";
public string ContentType
{
get { return contentType; }
set { contentType = value; }
}
readonly static string MULTIPART_CONTENTTYPE = "multipart/byteranges; boundary=" + MULTIPART_BOUNDARY;
readonly static string HTTP_HEADER_Content_Disposition = "Content-Disposition";
readonly static string HTTP_HEADER_ACCEPT_RANGES = "Accept-Ranges";
readonly static string HTTP_HEADER_ACCEPT_RANGES_BYTES = "bytes";
readonly static string HTTP_HEADER_CONTENT_TYPE = "Content-Type";
readonly static string HTTP_HEADER_CONTENT_RANGE = "Content-Range";
readonly static string HTTP_HEADER_CONTENT_LENGTH = "Content-Length";
readonly static string HTTP_HEADER_ENTITY_TAG = "ETag";
readonly static string HTTP_HEADER_LAST_MODIFIED = "Last-Modified";
readonly static string HTTP_HEADER_RANGE = "Range";
readonly static string HTTP_HEADER_IF_RANGE = "If-Range";
readonly static string HTTP_HEADER_IF_MATCH = "If-Match";
readonly static string HTTP_HEADER_IF_NONE_MATCH = "If-None-Match";
readonly static string HTTP_HEADER_IF_MODIFIED_SINCE = "If-Modified-Since";
readonly static string HTTP_HEADER_IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
readonly static string HTTP_HEADER_UNLESS_MODIFIED_SINCE = "Unless-Modified-Since";
readonly static string HTTP_METHOD_GET = "GET";
readonly static string HTTP_METHOD_HEAD = "HEAD";
readonly static string HTTP_METHOD_POST = "POST";
private string contentType = "application/octet-stream";
public string ContentType
{
get { return contentType; }
set { contentType = value; }
}
Process Download
/// <summary>
/// start to download file
/// </summary>
/// <param name="fileName">download file full path</param>
/// <param name="headerFileName">as save file name</param>
/// <returns></returns>
public DownLoadState ProcessDownload(string fileName, string headerFileName)
{
HttpContext objContext = HttpContext.Current;
// The Response object from the Context
HttpResponse objResponse = objContext.Response;
// The Request object from the Context
HttpRequest objRequest = objContext.Request;
// File information object...
FileInformation fileInfo;
objResponse.BufferOutput = false;
// Begin() contains start positions for each requested Range
long[] alRequestedRangesBegin = new long[1];
// End() contains end positions for each requested Range
long[] alRequestedRangesend = new long[1];
// Response Header value: Content Length...
int iResponseContentLength = 0;
// The Stream we're using to download the file in chunks...
System.IO.Stream objStream;
// Total Bytes to read (per requested range)
int iBytesToRead;
// Size of the Buffer for chunk-wise reading
int iBufferSize = 25000;
// The Buffer itself
byte[] bBuffer = new byte[iBufferSize];
// Indicates if the download was interrupted
bool bDownloadBroken = false;
// Indicates if this is a range request
bool bIsRangeRequest = false;
// Indicates if this is a multipart range request
bool bMultipart = false;
// Loop counter used to iterate through the ranges
int iLoop;
if (string.IsNullOrEmpty(headerFileName))
headerFileName = Path.GetFileName(fileName).Replace(' ', '-');
// Content-Disposition value
string Content_Disposition_File = "attachment; filename=" + headerFileName + "";
fileInfo = new FileInformation(fileName);
fileInfo.ContentType = this.ContentType;
// Clear the current output content from the buffer
objResponse.Clear();
if (!(objRequest.HttpMethod.Equals(HTTP_METHOD_GET) || objRequest.HttpMethod.Equals(HTTP_METHOD_HEAD) || objRequest.HttpMethod.Equals(HTTP_METHOD_POST, StringComparison.OrdinalIgnoreCase)))
// Currently, only the GET and HEAD methods are supported...
objResponse.StatusCode = 501; // Not implemented
else if (!fileInfo.Exists)
// The requested file could not be retrieved...
objResponse.StatusCode = 404; // Not found
else if (fileInfo.Length > Int32.MaxValue)
// The file size is too large...
objResponse.StatusCode = 413; // Request Entity Too Large
else if (!ParseRequestHeaderRange(objRequest, ref alRequestedRangesBegin, ref alRequestedRangesend, fileInfo.Length, ref bIsRangeRequest))
// The Range request contained bad entries
objResponse.StatusCode = 400; // Bad Request
else if (!CheckIfModifiedSince(objRequest, fileInfo))
// The entity is still unmodified...
objResponse.StatusCode = 304; // Not Modified
else if (!CheckIfUnmodifiedSince(objRequest, fileInfo))
// The entity was modified since the requested date...
objResponse.StatusCode = 412; // Precondition failed
else if (!CheckIfMatch(objRequest, fileInfo))
// The entity does not match the request...
objResponse.StatusCode = 412; // Precondition failed
else if (!CheckIfNoneMatch(objRequest, objResponse, fileInfo))
{
// The entity does match the none-match request, the response
// code was set inside the CheckifNoneMatch function
}
else if (!ValidateDomain(objContext))
// To check the url is from validate domain
objResponse.StatusCode = 400;
else
{
// Preliminary checks where successful...
if (bIsRangeRequest && CheckIfRange(objRequest, fileInfo))
{
// This is a Range request...
bMultipart = (alRequestedRangesBegin.GetUpperBound(0) > 0);
// Loop through each Range to calculate the entire Response length
for (iLoop = alRequestedRangesBegin.GetLowerBound(0); iLoop <= alRequestedRangesBegin.GetUpperBound(0); iLoop++)
{
// The length of the content (for this range)
iResponseContentLength += Convert.ToInt32(alRequestedRangesend[iLoop] - alRequestedRangesBegin[iLoop]) + 1;
if (bMultipart)
{
iResponseContentLength += HTTP_HEADER_Content_Disposition.Length;
iResponseContentLength += MULTIPART_BOUNDARY.Length;
iResponseContentLength += fileInfo.ContentType.Length;
iResponseContentLength += alRequestedRangesBegin[iLoop].ToString().Length;
iResponseContentLength += alRequestedRangesend[iLoop].ToString().Length;
iResponseContentLength += fileInfo.Length.ToString().Length;
iResponseContentLength += 49;
}
}
if (bMultipart)
{
iResponseContentLength += MULTIPART_BOUNDARY.Length;
// 8 is the length of dash and line break characters
iResponseContentLength += 8;
}
else
{
// in the initial HTTP Header
objResponse.AppendHeader(HTTP_HEADER_CONTENT_RANGE, "bytes " +
alRequestedRangesBegin[0].ToString() + "-" +
alRequestedRangesend[0].ToString() + "/" +
fileInfo.Length.ToString());
}
// Range response
objResponse.StatusCode = 206; // Partial Response
}
else
{
// This is not a Range request, or the requested Range entity ID
// does not match the current entity ID, so start a new download
// Indicate the file//s complete size as content length
iResponseContentLength = Convert.ToInt32(fileInfo.Length);
// Return a normal OK status...
objResponse.StatusCode = 200;
}
// Write file name into the Response
objResponse.AppendHeader(HTTP_HEADER_Content_Disposition, Content_Disposition_File);
// Write the content length into the Response
objResponse.AppendHeader(HTTP_HEADER_CONTENT_LENGTH, iResponseContentLength.ToString());
// Write the Last-Modified Date into the Response
objResponse.AppendHeader(HTTP_HEADER_LAST_MODIFIED, fileInfo.LastWriteTimeUTC.ToString("r"));
// Tell the client software that we accept Range request
objResponse.AppendHeader(HTTP_HEADER_ACCEPT_RANGES, HTTP_HEADER_ACCEPT_RANGES_BYTES);
// Write the file//s Entity Tag into the Response (in quotes!)
objResponse.AppendHeader(HTTP_HEADER_ENTITY_TAG, "\"" + fileInfo.EntityTag + "\"");
// Write the Content Type into the Response
if (bMultipart)
// Multipart messages have this special Type.
// In this case, the file//s actual mime type is
// written into the Response at a later time...
objResponse.ContentType = MULTIPART_CONTENTTYPE;
else
// Single part messages have the files content type...
objResponse.ContentType = fileInfo.ContentType;
if (objRequest.HttpMethod.Equals(HTTP_METHOD_HEAD))
{
// Only the HEAD was requested, so we can quit the Response right here...
}
else
{
// Flush the HEAD information to the client...
objResponse.Flush();
// Download is in progress...
fileInfo.State = DownLoadState.Progressing;
// Open the file as filestream
objStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
try
{
// Now, for each requested range, stream the chunks to the client:
for (iLoop = alRequestedRangesBegin.GetLowerBound(0); iLoop <= alRequestedRangesBegin.GetUpperBound(0); iLoop++)
{
// Move the stream to the desired start position...
objStream.Seek(alRequestedRangesBegin[iLoop], SeekOrigin.Begin);
// Calculate the total amount of bytes for this range
iBytesToRead = Convert.ToInt32(alRequestedRangesend[iLoop] - alRequestedRangesBegin[iLoop]) + 1;
if (bMultipart)
{
// if this is a multipart response, we must add
// certain headers before streaming the content:
// The multipart boundary
objResponse.Output.WriteLine("--" + MULTIPART_BOUNDARY);
// The mime type of this part of the content
objResponse.Output.WriteLine(HTTP_HEADER_CONTENT_TYPE + ": " + fileInfo.ContentType);
// The actual range
objResponse.Output.WriteLine(HTTP_HEADER_CONTENT_RANGE + ": bytes " + alRequestedRangesBegin[iLoop].ToString() + "-" + alRequestedRangesend[iLoop].ToString() + "/" + fileInfo.Length.ToString());
/*objResponse.AppendHeader(HTTP_HEADER_CONTENT_RANGE,": bytes " +
alRequestedRangesBegin[iLoop].ToString() + "-" +
alRequestedRangesend[iLoop].ToString() + "/" +
objFile.Length.ToString());
*/
// Indicating the end of the intermediate headers
objResponse.Output.WriteLine();
}
if (iBytesToRead > 0)
{
// Acording to Microsoft hotfix for .NET framework 2 the TransmitFile method got one overload
// If hotfix for .NET framework 2 installed
objResponse.TransmitFile(fileName, alRequestedRangesBegin[iLoop], iBytesToRead);
// If hotfix not installed
//CallHiddenTransmitFile(objResponse, fileName, alRequestedRangesBegin[iLoop], iBytesToRead);
}
// In Multipart responses, mark the end of the part
if (bMultipart) objResponse.Output.WriteLine();
// No need to proceed to the next part if the
// client was disconnected
if (bDownloadBroken) break;
}
// At this point, the response was finished or cancelled...
if (bDownloadBroken)
// Download is broken...
fileInfo.State = DownLoadState.Broken;
else
{
if (bMultipart)
{
// In multipart responses, close the response once more with
// the boundary and line breaks
objResponse.Output.WriteLine("--" + MULTIPART_BOUNDARY + "--");
objResponse.Output.WriteLine();
}
// The download was finished
fileInfo.State = DownLoadState.Finished;
}
}
finally
{
objStream.Close();
}
}
}
return fileInfo.State;
}
/// start to download file
/// </summary>
/// <param name="fileName">download file full path</param>
/// <param name="headerFileName">as save file name</param>
/// <returns></returns>
public DownLoadState ProcessDownload(string fileName, string headerFileName)
{
HttpContext objContext = HttpContext.Current;
// The Response object from the Context
HttpResponse objResponse = objContext.Response;
// The Request object from the Context
HttpRequest objRequest = objContext.Request;
// File information object...
FileInformation fileInfo;
objResponse.BufferOutput = false;
// Begin() contains start positions for each requested Range
long[] alRequestedRangesBegin = new long[1];
// End() contains end positions for each requested Range
long[] alRequestedRangesend = new long[1];
// Response Header value: Content Length...
int iResponseContentLength = 0;
// The Stream we're using to download the file in chunks...
System.IO.Stream objStream;
// Total Bytes to read (per requested range)
int iBytesToRead;
// Size of the Buffer for chunk-wise reading
int iBufferSize = 25000;
// The Buffer itself
byte[] bBuffer = new byte[iBufferSize];
// Indicates if the download was interrupted
bool bDownloadBroken = false;
// Indicates if this is a range request
bool bIsRangeRequest = false;
// Indicates if this is a multipart range request
bool bMultipart = false;
// Loop counter used to iterate through the ranges
int iLoop;
if (string.IsNullOrEmpty(headerFileName))
headerFileName = Path.GetFileName(fileName).Replace(' ', '-');
// Content-Disposition value
string Content_Disposition_File = "attachment; filename=" + headerFileName + "";
fileInfo = new FileInformation(fileName);
fileInfo.ContentType = this.ContentType;
// Clear the current output content from the buffer
objResponse.Clear();
if (!(objRequest.HttpMethod.Equals(HTTP_METHOD_GET) || objRequest.HttpMethod.Equals(HTTP_METHOD_HEAD) || objRequest.HttpMethod.Equals(HTTP_METHOD_POST, StringComparison.OrdinalIgnoreCase)))
// Currently, only the GET and HEAD methods are supported...
objResponse.StatusCode = 501; // Not implemented
else if (!fileInfo.Exists)
// The requested file could not be retrieved...
objResponse.StatusCode = 404; // Not found
else if (fileInfo.Length > Int32.MaxValue)
// The file size is too large...
objResponse.StatusCode = 413; // Request Entity Too Large
else if (!ParseRequestHeaderRange(objRequest, ref alRequestedRangesBegin, ref alRequestedRangesend, fileInfo.Length, ref bIsRangeRequest))
// The Range request contained bad entries
objResponse.StatusCode = 400; // Bad Request
else if (!CheckIfModifiedSince(objRequest, fileInfo))
// The entity is still unmodified...
objResponse.StatusCode = 304; // Not Modified
else if (!CheckIfUnmodifiedSince(objRequest, fileInfo))
// The entity was modified since the requested date...
objResponse.StatusCode = 412; // Precondition failed
else if (!CheckIfMatch(objRequest, fileInfo))
// The entity does not match the request...
objResponse.StatusCode = 412; // Precondition failed
else if (!CheckIfNoneMatch(objRequest, objResponse, fileInfo))
{
// The entity does match the none-match request, the response
// code was set inside the CheckifNoneMatch function
}
else if (!ValidateDomain(objContext))
// To check the url is from validate domain
objResponse.StatusCode = 400;
else
{
// Preliminary checks where successful...
if (bIsRangeRequest && CheckIfRange(objRequest, fileInfo))
{
// This is a Range request...
bMultipart = (alRequestedRangesBegin.GetUpperBound(0) > 0);
// Loop through each Range to calculate the entire Response length
for (iLoop = alRequestedRangesBegin.GetLowerBound(0); iLoop <= alRequestedRangesBegin.GetUpperBound(0); iLoop++)
{
// The length of the content (for this range)
iResponseContentLength += Convert.ToInt32(alRequestedRangesend[iLoop] - alRequestedRangesBegin[iLoop]) + 1;
if (bMultipart)
{
iResponseContentLength += HTTP_HEADER_Content_Disposition.Length;
iResponseContentLength += MULTIPART_BOUNDARY.Length;
iResponseContentLength += fileInfo.ContentType.Length;
iResponseContentLength += alRequestedRangesBegin[iLoop].ToString().Length;
iResponseContentLength += alRequestedRangesend[iLoop].ToString().Length;
iResponseContentLength += fileInfo.Length.ToString().Length;
iResponseContentLength += 49;
}
}
if (bMultipart)
{
iResponseContentLength += MULTIPART_BOUNDARY.Length;
// 8 is the length of dash and line break characters
iResponseContentLength += 8;
}
else
{
// in the initial HTTP Header
objResponse.AppendHeader(HTTP_HEADER_CONTENT_RANGE, "bytes " +
alRequestedRangesBegin[0].ToString() + "-" +
alRequestedRangesend[0].ToString() + "/" +
fileInfo.Length.ToString());
}
// Range response
objResponse.StatusCode = 206; // Partial Response
}
else
{
// This is not a Range request, or the requested Range entity ID
// does not match the current entity ID, so start a new download
// Indicate the file//s complete size as content length
iResponseContentLength = Convert.ToInt32(fileInfo.Length);
// Return a normal OK status...
objResponse.StatusCode = 200;
}
// Write file name into the Response
objResponse.AppendHeader(HTTP_HEADER_Content_Disposition, Content_Disposition_File);
// Write the content length into the Response
objResponse.AppendHeader(HTTP_HEADER_CONTENT_LENGTH, iResponseContentLength.ToString());
// Write the Last-Modified Date into the Response
objResponse.AppendHeader(HTTP_HEADER_LAST_MODIFIED, fileInfo.LastWriteTimeUTC.ToString("r"));
// Tell the client software that we accept Range request
objResponse.AppendHeader(HTTP_HEADER_ACCEPT_RANGES, HTTP_HEADER_ACCEPT_RANGES_BYTES);
// Write the file//s Entity Tag into the Response (in quotes!)
objResponse.AppendHeader(HTTP_HEADER_ENTITY_TAG, "\"" + fileInfo.EntityTag + "\"");
// Write the Content Type into the Response
if (bMultipart)
// Multipart messages have this special Type.
// In this case, the file//s actual mime type is
// written into the Response at a later time...
objResponse.ContentType = MULTIPART_CONTENTTYPE;
else
// Single part messages have the files content type...
objResponse.ContentType = fileInfo.ContentType;
if (objRequest.HttpMethod.Equals(HTTP_METHOD_HEAD))
{
// Only the HEAD was requested, so we can quit the Response right here...
}
else
{
// Flush the HEAD information to the client...
objResponse.Flush();
// Download is in progress...
fileInfo.State = DownLoadState.Progressing;
// Open the file as filestream
objStream = new FileStream(fileInfo.FullName, FileMode.Open, FileAccess.Read, FileShare.Read);
try
{
// Now, for each requested range, stream the chunks to the client:
for (iLoop = alRequestedRangesBegin.GetLowerBound(0); iLoop <= alRequestedRangesBegin.GetUpperBound(0); iLoop++)
{
// Move the stream to the desired start position...
objStream.Seek(alRequestedRangesBegin[iLoop], SeekOrigin.Begin);
// Calculate the total amount of bytes for this range
iBytesToRead = Convert.ToInt32(alRequestedRangesend[iLoop] - alRequestedRangesBegin[iLoop]) + 1;
if (bMultipart)
{
// if this is a multipart response, we must add
// certain headers before streaming the content:
// The multipart boundary
objResponse.Output.WriteLine("--" + MULTIPART_BOUNDARY);
// The mime type of this part of the content
objResponse.Output.WriteLine(HTTP_HEADER_CONTENT_TYPE + ": " + fileInfo.ContentType);
// The actual range
objResponse.Output.WriteLine(HTTP_HEADER_CONTENT_RANGE + ": bytes " + alRequestedRangesBegin[iLoop].ToString() + "-" + alRequestedRangesend[iLoop].ToString() + "/" + fileInfo.Length.ToString());
/*objResponse.AppendHeader(HTTP_HEADER_CONTENT_RANGE,": bytes " +
alRequestedRangesBegin[iLoop].ToString() + "-" +
alRequestedRangesend[iLoop].ToString() + "/" +
objFile.Length.ToString());
*/
// Indicating the end of the intermediate headers
objResponse.Output.WriteLine();
}
if (iBytesToRead > 0)
{
// Acording to Microsoft hotfix for .NET framework 2 the TransmitFile method got one overload
// If hotfix for .NET framework 2 installed
objResponse.TransmitFile(fileName, alRequestedRangesBegin[iLoop], iBytesToRead);
// If hotfix not installed
//CallHiddenTransmitFile(objResponse, fileName, alRequestedRangesBegin[iLoop], iBytesToRead);
}
// In Multipart responses, mark the end of the part
if (bMultipart) objResponse.Output.WriteLine();
// No need to proceed to the next part if the
// client was disconnected
if (bDownloadBroken) break;
}
// At this point, the response was finished or cancelled...
if (bDownloadBroken)
// Download is broken...
fileInfo.State = DownLoadState.Broken;
else
{
if (bMultipart)
{
// In multipart responses, close the response once more with
// the boundary and line breaks
objResponse.Output.WriteLine("--" + MULTIPART_BOUNDARY + "--");
objResponse.Output.WriteLine();
}
// The download was finished
fileInfo.State = DownLoadState.Finished;
}
}
finally
{
objStream.Close();
}
}
}
return fileInfo.State;
}
private string[] domains = null;
public string[] Domains
{
get
{
if (null == domains)
{
string domainStr = ConfigurationManager.AppSettings["Domains"].ToLower();
domains = domainStr.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
}
return domains;
}
}
bool ValidateDomain(HttpContext context)
{
bool isValidate = true;
// Check UrlReferrer is null
if (null == context.Request.UrlReferrer || null == context.Request.UrlReferrer.Host)
{
isValidate = false;
}
else
{
// check the request url is from the domain
if (!Domains.Contains(context.Request.UrlReferrer.Host.ToLower()))
{
isValidate = false;
}
}
return isValidate;
}
void CallHiddenTransmitFile(HttpResponse objResponse, string fileName, long offset, long size)
{
Type tempType = objResponse.GetType();
FieldInfo tempInfo = tempType.GetField("_httpWriter", BindingFlags.NonPublic | BindingFlags.Instance);
HttpWriter _httpWriter = (HttpWriter)tempInfo.GetValue(objResponse);
tempType = tempInfo.FieldType;
tempInfo = tempType.GetField("_buffers", BindingFlags.NonPublic | BindingFlags.Instance);
ArrayList _buffers = (ArrayList)tempInfo.GetValue(_httpWriter);
tempType = _httpWriter.GetType();
tempInfo = tempType.GetField("_responseBufferingOn", BindingFlags.NonPublic | BindingFlags.Instance);
bool responseBufferingOn = (bool)tempInfo.GetValue(_httpWriter);
Type classType = tempType.Assembly.GetType("System.Web.HttpFileResponseElement");
ConstructorInfo[] cinf = classType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
object ret = cinf[2].Invoke(new object[] { fileName, offset, size, true, false, true });
_buffers.Add(ret);
if (!responseBufferingOn)
{
objResponse.Flush();
}
}
bool CheckIfRange(HttpRequest objRequest, FileInformation objFile)
{
string sRequestHeaderIfRange;
// Retrieve If-Range Header value from Request (objFile.EntityTag if none is indicated)
sRequestHeaderIfRange = RetrieveHeader(objRequest, HTTP_HEADER_IF_RANGE, objFile.EntityTag);
// If the requested file entity matches the current
// file entity, return true
return sRequestHeaderIfRange.Equals(objFile.EntityTag);
}
bool CheckIfMatch(HttpRequest objRequest, FileInformation objFile)
{
string sRequestHeaderIfMatch;
string[] sEntityIDs;
bool breturn = false;
// Retrieve If-Match Header value from Request (*, meaning any, if none is indicated)
sRequestHeaderIfMatch = RetrieveHeader(objRequest, HTTP_HEADER_IF_MATCH, "*");
if (sRequestHeaderIfMatch.Equals("*"))
// The server may perform the request as if the
// If-Match header does not exists...
breturn = true;
else
{
// One or more Match IDs where sent by the client software...
sEntityIDs = sRequestHeaderIfMatch.Replace("bytes=", "").Split(",".ToCharArray());
// Loop through all entity IDs, finding one
// which matches the current file's ID will
// be enough to satisfy the If-Match
for (int iLoop = sEntityIDs.GetLowerBound(0); iLoop <= sEntityIDs.GetUpperBound(0); iLoop++)
{
if (sEntityIDs[iLoop].Trim().Equals(objFile.EntityTag))
breturn = true;
}
}
return breturn;
}
bool CheckIfNoneMatch(HttpRequest objRequest, HttpResponse objResponse, FileInformation objFile)
{
string sRequestHeaderIfNoneMatch;
string[] sEntityIDs;
bool breturn = true;
string sreturn = "";
// Retrieve If-None-Match Header value from Request (*, meaning any, if none is indicated)
sRequestHeaderIfNoneMatch = RetrieveHeader(objRequest, HTTP_HEADER_IF_NONE_MATCH, String.Empty);
if (sRequestHeaderIfNoneMatch.Equals(String.Empty))
// Perform the request normally...
breturn = true;
else
{
if (sRequestHeaderIfNoneMatch.Equals("*"))
{
// The server must not perform the request
objResponse.StatusCode = 412; // Precondition failed
breturn = false;
}
else
{
// One or more Match IDs where sent by the client software...
sEntityIDs = sRequestHeaderIfNoneMatch.Replace("bytes=", "").Split(",".ToCharArray());
// Loop through all entity IDs, finding one which
// does not match the current file//s ID will be
// enough to satisfy the If-None-Match
for (int iLoop = sEntityIDs.GetLowerBound(0); iLoop <= sEntityIDs.GetUpperBound(0); iLoop++)
{
if (sEntityIDs[iLoop].Trim().Equals(objFile.EntityTag))
{
sreturn = sEntityIDs[iLoop];
breturn = false;
}
}
if (!breturn)
{
// One of the requested entities matches the current file//s tag,
objResponse.AppendHeader("ETag", sreturn);
objResponse.StatusCode = 304; // Not Modified
}
}
}
return breturn;
}
bool CheckIfModifiedSince(HttpRequest objRequest, FileInformation objFile)
{
string sDate;
DateTime dDate;
bool breturn;
// Retrieve If-Modified-Since Header value from Request (Empty if none is indicated)
sDate = RetrieveHeader(objRequest, HTTP_HEADER_IF_MODIFIED_SINCE, string.Empty);
if (sDate.Equals(String.Empty))
// No If-Modified-Since date was indicated,
// so just give this as true
breturn = true;
else
{
try
{
// to parse the indicated sDate to a datetime value
dDate = DateTime.Parse(sDate);
// return true if the file was modified since or at the indicated date...
breturn = (objFile.LastWriteTimeUTC >= DateTime.Parse(sDate));
}
catch
{
// Converting the indicated date value failed, return false
breturn = false;
}
}
return breturn;
}
bool CheckIfUnmodifiedSince(HttpRequest objRequest, FileInformation objFile)
{
string sDate;
DateTime dDate;
bool breturn;
// Retrieve If-Unmodified-Since Header value from Request (Empty if none is indicated)
sDate = RetrieveHeader(objRequest, HTTP_HEADER_IF_UNMODIFIED_SINCE, String.Empty);
if (sDate.Equals(String.Empty))
// If-Unmodified-Since was not sent, check Unless-Modified-Since...
sDate = RetrieveHeader(objRequest, HTTP_HEADER_UNLESS_MODIFIED_SINCE, String.Empty);
if (sDate.Equals(String.Empty))
// No date was indicated,
// so just give this as true
breturn = true;
else
{
try
{
// to parse the indicated sDate to a datetime value
dDate = DateTime.Parse(sDate);
// return true if the file was not modified since the indicated date...
breturn = objFile.LastWriteTimeUTC < DateTime.Parse(sDate);
}
catch
{
// Converting the indicated date value failed, return false
breturn = false;
}
}
return breturn;
}
bool ParseRequestHeaderRange(HttpRequest objRequest, ref long[] lBegin, ref long[] lEnd, long lMax, ref bool bRangeRequest)
{
bool bValidRanges;
string sSource;
int iLoop;
string[] sRanges;
// Retrieve Range Header value from Request (Empty if none is indicated)
sSource = RetrieveHeader(objRequest, HTTP_HEADER_RANGE, String.Empty);
if (sSource.Equals(String.Empty))
{
// No Range was requested, return the entire file range...
lBegin = new long[1];
//ReDim lBegin(0);
lEnd = new long[1];
//ReDim lEnd(0);
lBegin[0] = 0;
lEnd[0] = lMax - 1;
// A valid range is returned
bValidRanges = true;
// no Range request
bRangeRequest = false;
}
else
{
// A Range was requested...
bValidRanges = true;
// return true for the bRange parameter, telling the caller
// that the Request is indeed a Range request...
bRangeRequest = true;
// Remove "bytes=" from the beginning, and split the remaining
// string by comma characters
sRanges = sSource.Replace("bytes=", "").Split(",".ToCharArray());
lBegin = new long[sRanges.GetUpperBound(0) + 1];
//ReDim lBegin(sRanges.GetUpperBound(0));
lEnd = new long[sRanges.GetUpperBound(0) + 1];
//ReDim lEnd(sRanges.GetUpperBound(0));
// Check each found Range request for consistency
for (iLoop = sRanges.GetLowerBound(0); iLoop <= sRanges.GetUpperBound(0); iLoop++)
{
// Split this range request by the dash character,
// sRange(0) contains the requested begin-value,
// sRange(1) contains the requested end-value...
string[] sRange = sRanges[iLoop].Split("-".ToCharArray());
// Determine the end of the requested range
if (sRange[1].Equals(String.Empty))
// No end was specified, take the entire range
lEnd[iLoop] = lMax - 1;
else
// An end was specified...
lEnd[iLoop] = long.Parse(sRange[1]);
// Determine the begin of the requested range
if (sRange[0].Equals(String.Empty))
{
// No begin was specified, which means that
// the end value indicated to return the last n
// bytes of the file:
// Calculate the begin
lBegin[iLoop] = lMax - 1 - lEnd[iLoop];
// ... to the end of the file...
lEnd[iLoop] = lMax - 1;
}
else
// A normal begin value was indicated...
lBegin[iLoop] = long.Parse(sRange[0]);
// Begin and end must not exceed the file size
if ((lBegin[iLoop] > (lMax - 1)) || (lEnd[iLoop] > (lMax - 1)))
bValidRanges = false;
// Begin and end cannot be < 0
if ((lBegin[iLoop] < 0) || (lEnd[iLoop] < 0))
bValidRanges = false;
// End must be larger or equal to begin value
if (lEnd[iLoop] < lBegin[iLoop])
// The requested Range is invalid...
bValidRanges = false;
}
}
return bValidRanges;
}
string RetrieveHeader(HttpRequest objRequest, string sHeader, string sDefault)
{
string sreturn;
// Retrieves the indicated Header//s value from the Request,
// if the header was not sent, sDefault is returned.
// If the value contains quote characters, they are removed.
sreturn = objRequest.Headers[sHeader];
if ((sreturn == null) || (sreturn.Equals(string.Empty)))
// The Header wos not found in the Request,
// return the indicated default value...
return sDefault;
else
// return the found header value, stripped of any quote characters...
return sreturn.Replace("\"", "");
}
string GenerateHash(System.IO.Stream objStream, long lBegin, long lEnd)
{
byte[] bByte = new byte[Convert.ToInt32(lEnd)];
objStream.Read(bByte, Convert.ToInt32(lBegin), Convert.ToInt32(lEnd - lBegin) + 1);
//Instantiate an MD5 Provider object
MD5CryptoServiceProvider Md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
//Compute the hash value from the source
byte[] ByteHash = Md5.ComputeHash(bByte);
//And convert it to String format for return
return Convert.ToBase64String(ByteHash);
}
#region IDisposable Members
void IDisposable.Dispose()
{
throw new NotImplementedException();
}
#endregion
public string[] Domains
{
get
{
if (null == domains)
{
string domainStr = ConfigurationManager.AppSettings["Domains"].ToLower();
domains = domainStr.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
}
return domains;
}
}
bool ValidateDomain(HttpContext context)
{
bool isValidate = true;
// Check UrlReferrer is null
if (null == context.Request.UrlReferrer || null == context.Request.UrlReferrer.Host)
{
isValidate = false;
}
else
{
// check the request url is from the domain
if (!Domains.Contains(context.Request.UrlReferrer.Host.ToLower()))
{
isValidate = false;
}
}
return isValidate;
}
void CallHiddenTransmitFile(HttpResponse objResponse, string fileName, long offset, long size)
{
Type tempType = objResponse.GetType();
FieldInfo tempInfo = tempType.GetField("_httpWriter", BindingFlags.NonPublic | BindingFlags.Instance);
HttpWriter _httpWriter = (HttpWriter)tempInfo.GetValue(objResponse);
tempType = tempInfo.FieldType;
tempInfo = tempType.GetField("_buffers", BindingFlags.NonPublic | BindingFlags.Instance);
ArrayList _buffers = (ArrayList)tempInfo.GetValue(_httpWriter);
tempType = _httpWriter.GetType();
tempInfo = tempType.GetField("_responseBufferingOn", BindingFlags.NonPublic | BindingFlags.Instance);
bool responseBufferingOn = (bool)tempInfo.GetValue(_httpWriter);
Type classType = tempType.Assembly.GetType("System.Web.HttpFileResponseElement");
ConstructorInfo[] cinf = classType.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);
object ret = cinf[2].Invoke(new object[] { fileName, offset, size, true, false, true });
_buffers.Add(ret);
if (!responseBufferingOn)
{
objResponse.Flush();
}
}
bool CheckIfRange(HttpRequest objRequest, FileInformation objFile)
{
string sRequestHeaderIfRange;
// Retrieve If-Range Header value from Request (objFile.EntityTag if none is indicated)
sRequestHeaderIfRange = RetrieveHeader(objRequest, HTTP_HEADER_IF_RANGE, objFile.EntityTag);
// If the requested file entity matches the current
// file entity, return true
return sRequestHeaderIfRange.Equals(objFile.EntityTag);
}
bool CheckIfMatch(HttpRequest objRequest, FileInformation objFile)
{
string sRequestHeaderIfMatch;
string[] sEntityIDs;
bool breturn = false;
// Retrieve If-Match Header value from Request (*, meaning any, if none is indicated)
sRequestHeaderIfMatch = RetrieveHeader(objRequest, HTTP_HEADER_IF_MATCH, "*");
if (sRequestHeaderIfMatch.Equals("*"))
// The server may perform the request as if the
// If-Match header does not exists...
breturn = true;
else
{
// One or more Match IDs where sent by the client software...
sEntityIDs = sRequestHeaderIfMatch.Replace("bytes=", "").Split(",".ToCharArray());
// Loop through all entity IDs, finding one
// which matches the current file's ID will
// be enough to satisfy the If-Match
for (int iLoop = sEntityIDs.GetLowerBound(0); iLoop <= sEntityIDs.GetUpperBound(0); iLoop++)
{
if (sEntityIDs[iLoop].Trim().Equals(objFile.EntityTag))
breturn = true;
}
}
return breturn;
}
bool CheckIfNoneMatch(HttpRequest objRequest, HttpResponse objResponse, FileInformation objFile)
{
string sRequestHeaderIfNoneMatch;
string[] sEntityIDs;
bool breturn = true;
string sreturn = "";
// Retrieve If-None-Match Header value from Request (*, meaning any, if none is indicated)
sRequestHeaderIfNoneMatch = RetrieveHeader(objRequest, HTTP_HEADER_IF_NONE_MATCH, String.Empty);
if (sRequestHeaderIfNoneMatch.Equals(String.Empty))
// Perform the request normally...
breturn = true;
else
{
if (sRequestHeaderIfNoneMatch.Equals("*"))
{
// The server must not perform the request
objResponse.StatusCode = 412; // Precondition failed
breturn = false;
}
else
{
// One or more Match IDs where sent by the client software...
sEntityIDs = sRequestHeaderIfNoneMatch.Replace("bytes=", "").Split(",".ToCharArray());
// Loop through all entity IDs, finding one which
// does not match the current file//s ID will be
// enough to satisfy the If-None-Match
for (int iLoop = sEntityIDs.GetLowerBound(0); iLoop <= sEntityIDs.GetUpperBound(0); iLoop++)
{
if (sEntityIDs[iLoop].Trim().Equals(objFile.EntityTag))
{
sreturn = sEntityIDs[iLoop];
breturn = false;
}
}
if (!breturn)
{
// One of the requested entities matches the current file//s tag,
objResponse.AppendHeader("ETag", sreturn);
objResponse.StatusCode = 304; // Not Modified
}
}
}
return breturn;
}
bool CheckIfModifiedSince(HttpRequest objRequest, FileInformation objFile)
{
string sDate;
DateTime dDate;
bool breturn;
// Retrieve If-Modified-Since Header value from Request (Empty if none is indicated)
sDate = RetrieveHeader(objRequest, HTTP_HEADER_IF_MODIFIED_SINCE, string.Empty);
if (sDate.Equals(String.Empty))
// No If-Modified-Since date was indicated,
// so just give this as true
breturn = true;
else
{
try
{
// to parse the indicated sDate to a datetime value
dDate = DateTime.Parse(sDate);
// return true if the file was modified since or at the indicated date...
breturn = (objFile.LastWriteTimeUTC >= DateTime.Parse(sDate));
}
catch
{
// Converting the indicated date value failed, return false
breturn = false;
}
}
return breturn;
}
bool CheckIfUnmodifiedSince(HttpRequest objRequest, FileInformation objFile)
{
string sDate;
DateTime dDate;
bool breturn;
// Retrieve If-Unmodified-Since Header value from Request (Empty if none is indicated)
sDate = RetrieveHeader(objRequest, HTTP_HEADER_IF_UNMODIFIED_SINCE, String.Empty);
if (sDate.Equals(String.Empty))
// If-Unmodified-Since was not sent, check Unless-Modified-Since...
sDate = RetrieveHeader(objRequest, HTTP_HEADER_UNLESS_MODIFIED_SINCE, String.Empty);
if (sDate.Equals(String.Empty))
// No date was indicated,
// so just give this as true
breturn = true;
else
{
try
{
// to parse the indicated sDate to a datetime value
dDate = DateTime.Parse(sDate);
// return true if the file was not modified since the indicated date...
breturn = objFile.LastWriteTimeUTC < DateTime.Parse(sDate);
}
catch
{
// Converting the indicated date value failed, return false
breturn = false;
}
}
return breturn;
}
bool ParseRequestHeaderRange(HttpRequest objRequest, ref long[] lBegin, ref long[] lEnd, long lMax, ref bool bRangeRequest)
{
bool bValidRanges;
string sSource;
int iLoop;
string[] sRanges;
// Retrieve Range Header value from Request (Empty if none is indicated)
sSource = RetrieveHeader(objRequest, HTTP_HEADER_RANGE, String.Empty);
if (sSource.Equals(String.Empty))
{
// No Range was requested, return the entire file range...
lBegin = new long[1];
//ReDim lBegin(0);
lEnd = new long[1];
//ReDim lEnd(0);
lBegin[0] = 0;
lEnd[0] = lMax - 1;
// A valid range is returned
bValidRanges = true;
// no Range request
bRangeRequest = false;
}
else
{
// A Range was requested...
bValidRanges = true;
// return true for the bRange parameter, telling the caller
// that the Request is indeed a Range request...
bRangeRequest = true;
// Remove "bytes=" from the beginning, and split the remaining
// string by comma characters
sRanges = sSource.Replace("bytes=", "").Split(",".ToCharArray());
lBegin = new long[sRanges.GetUpperBound(0) + 1];
//ReDim lBegin(sRanges.GetUpperBound(0));
lEnd = new long[sRanges.GetUpperBound(0) + 1];
//ReDim lEnd(sRanges.GetUpperBound(0));
// Check each found Range request for consistency
for (iLoop = sRanges.GetLowerBound(0); iLoop <= sRanges.GetUpperBound(0); iLoop++)
{
// Split this range request by the dash character,
// sRange(0) contains the requested begin-value,
// sRange(1) contains the requested end-value...
string[] sRange = sRanges[iLoop].Split("-".ToCharArray());
// Determine the end of the requested range
if (sRange[1].Equals(String.Empty))
// No end was specified, take the entire range
lEnd[iLoop] = lMax - 1;
else
// An end was specified...
lEnd[iLoop] = long.Parse(sRange[1]);
// Determine the begin of the requested range
if (sRange[0].Equals(String.Empty))
{
// No begin was specified, which means that
// the end value indicated to return the last n
// bytes of the file:
// Calculate the begin
lBegin[iLoop] = lMax - 1 - lEnd[iLoop];
// ... to the end of the file...
lEnd[iLoop] = lMax - 1;
}
else
// A normal begin value was indicated...
lBegin[iLoop] = long.Parse(sRange[0]);
// Begin and end must not exceed the file size
if ((lBegin[iLoop] > (lMax - 1)) || (lEnd[iLoop] > (lMax - 1)))
bValidRanges = false;
// Begin and end cannot be < 0
if ((lBegin[iLoop] < 0) || (lEnd[iLoop] < 0))
bValidRanges = false;
// End must be larger or equal to begin value
if (lEnd[iLoop] < lBegin[iLoop])
// The requested Range is invalid...
bValidRanges = false;
}
}
return bValidRanges;
}
string RetrieveHeader(HttpRequest objRequest, string sHeader, string sDefault)
{
string sreturn;
// Retrieves the indicated Header//s value from the Request,
// if the header was not sent, sDefault is returned.
// If the value contains quote characters, they are removed.
sreturn = objRequest.Headers[sHeader];
if ((sreturn == null) || (sreturn.Equals(string.Empty)))
// The Header wos not found in the Request,
// return the indicated default value...
return sDefault;
else
// return the found header value, stripped of any quote characters...
return sreturn.Replace("\"", "");
}
string GenerateHash(System.IO.Stream objStream, long lBegin, long lEnd)
{
byte[] bByte = new byte[Convert.ToInt32(lEnd)];
objStream.Read(bByte, Convert.ToInt32(lBegin), Convert.ToInt32(lEnd - lBegin) + 1);
//Instantiate an MD5 Provider object
MD5CryptoServiceProvider Md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
//Compute the hash value from the source
byte[] ByteHash = Md5.ComputeHash(bByte);
//And convert it to String format for return
return Convert.ToBase64String(ByteHash);
}
#region IDisposable Members
void IDisposable.Dispose()
{
throw new NotImplementedException();
}
#endregion
File Information
1 public class FileInformation
2 {
3 private DownLoadState state;
4 private DateTime createTime;
5 private string fileName = "";
6 private string contentType = "application/octet-stream";
7 private long? fileLength = null;
8
9 public FileInformation(string fileName)
10 {
11 this.fileName = fileName;
12 createTime = DateTime.Now;
13 }
14
15 public bool Exists
16 {
17 get
18 {
19 return File.Exists(fileName);
20 }
21 }
22
23 public string FullName
24 {
25 get { return fileName;}
26 }
27
28 public DateTime LastWriteTimeUTC
29 {
30 get { return createTime.ToUniversalTime(); }
31 }
32
33 public long Length
34 {
35 get
36 {
37 if (fileLength.HasValue == false)
38 {
39 FileInfo info = new FileInfo(fileName);
40 fileLength = info.Length;
41 }
42 return fileLength.Value;
43 }
44 }
45
46 public string ContentType
47 {
48 get
49 {
50 return contentType;
51 }
52 set
53 {
54 contentType = value;
55 }
56 }
57 public string EntityTag
58 {
59 get
60 {
61 return fileName.GetHashCode().ToString();;
62 }
63 }
64
65 public virtual DownLoadState State
66 {
67 get
68 {
69 return state;
70 }
71 set
72 {
73 state = value;
74 }
75 }
76 }
2 {
3 private DownLoadState state;
4 private DateTime createTime;
5 private string fileName = "";
6 private string contentType = "application/octet-stream";
7 private long? fileLength = null;
8
9 public FileInformation(string fileName)
10 {
11 this.fileName = fileName;
12 createTime = DateTime.Now;
13 }
14
15 public bool Exists
16 {
17 get
18 {
19 return File.Exists(fileName);
20 }
21 }
22
23 public string FullName
24 {
25 get { return fileName;}
26 }
27
28 public DateTime LastWriteTimeUTC
29 {
30 get { return createTime.ToUniversalTime(); }
31 }
32
33 public long Length
34 {
35 get
36 {
37 if (fileLength.HasValue == false)
38 {
39 FileInfo info = new FileInfo(fileName);
40 fileLength = info.Length;
41 }
42 return fileLength.Value;
43 }
44 }
45
46 public string ContentType
47 {
48 get
49 {
50 return contentType;
51 }
52 set
53 {
54 contentType = value;
55 }
56 }
57 public string EntityTag
58 {
59 get
60 {
61 return fileName.GetHashCode().ToString();;
62 }
63 }
64
65 public virtual DownLoadState State
66 {
67 get
68 {
69 return state;
70 }
71 set
72 {
73 state = value;
74 }
75 }
76 }
DownLoad Status
1 public enum DownLoadState
2 {
3 /// Clear: No download in progress,
4 /// the file can be manipulated
5 Clear = 1,
6
7 /// Locked: A dynamically created file must
8 /// not be changed
9 Locked = 2,
10
11 /// In Progress: File is locked, and download
12 /// is currently in progress
13 Progressing = 6,
14
15 /// Broken: File is locked, download was in
16 /// progress, but was cancelled
17 Broken = 10,
18
19 /// Finished: File is locked, download
20 /// was completed
21 Finished = 18
22 }
2 {
3 /// Clear: No download in progress,
4 /// the file can be manipulated
5 Clear = 1,
6
7 /// Locked: A dynamically created file must
8 /// not be changed
9 Locked = 2,
10
11 /// In Progress: File is locked, and download
12 /// is currently in progress
13 Progressing = 6,
14
15 /// Broken: File is locked, download was in
16 /// progress, but was cancelled
17 Broken = 10,
18
19 /// Finished: File is locked, download
20 /// was completed
21 Finished = 18
22 }