/// <summary>
        /// Check If-Unmodified-Since header
        /// </summary>
        /// <param name="objRequest"></param>
        /// <param name="objDownload"></param>
        /// <returns></returns>
        private static bool CheckIfUnmodifiedSince(HttpRequest objRequest, DownloadFile objFile)
        {
            string   sSinceDate;
            DateTime dtSinceDate;
            bool     bReturn;

            // Retrieve If-Unmodified-Since Header
            sSinceDate = DownloadHandler.RetrieveHeader(objRequest, HTTP_HEADER_IF_UNMODIFIED_SINCE, String.Empty);
            if (String.IsNullOrEmpty(sSinceDate))
            {
                sSinceDate = DownloadHandler.RetrieveHeader(objRequest, HTTP_HEADER_UNLESS_MODIFIED_SINCE, String.Empty);
            }
            if (String.IsNullOrEmpty(sSinceDate))
            {
                bReturn = true;
            }
            else
            {
                bool     bTry               = DateTime.TryParse(sSinceDate, out dtSinceDate); //dtSinceDate is a local date without millisecs
                DateTime dtSinceDateUTC     = dtSinceDate.ToUniversalTime();
                DateTime dtLastWriteTimeUTC = new DateTime(
                    objFile.LastWriteTimeUTC.Year,
                    objFile.LastWriteTimeUTC.Month,
                    objFile.LastWriteTimeUTC.Day,
                    objFile.LastWriteTimeUTC.Hour,
                    objFile.LastWriteTimeUTC.Minute,
                    objFile.LastWriteTimeUTC.Second,
                    DateTimeKind.Utc);
                if (bTry)
                {
                    bReturn = (dtLastWriteTimeUTC <= dtSinceDateUTC); // True if the file was not modified
                }
                else
                {
                    bReturn = false;
                }
            }
            return(bReturn);
        }
        /// <summary>
        /// Check If-None-Match header
        /// </summary>
        /// <param name="objRequest"></param>
        /// <param name="objResponse"></param>
        /// <param name="objDownload"></param>
        /// <returns></returns>
        private static bool CheckIfNoneMatch(HttpRequest objRequest, HttpResponse objResponse, DownloadFile objFile)  //Careful ByVal
        {
            string sRequestHeaderIfNoneMatch;

            string[] arrEntityIDs;
            bool     bReturn = true;
            string   sReturn = "";

            // Get Request If-None-Match Header value
            sRequestHeaderIfNoneMatch = DownloadHandler.RetrieveHeader(objRequest, HTTP_HEADER_IF_NONE_MATCH, String.Empty);
            if (String.IsNullOrEmpty(sRequestHeaderIfNoneMatch))
            {
                bReturn = true; // Perform request normally
            }
            else if (sRequestHeaderIfNoneMatch.Equals("*"))
            {
                objResponse.StatusCode = 412; // logically invalid request
                bReturn = false;
            }
            else
            {
                arrEntityIDs = sRequestHeaderIfNoneMatch.Replace("bytes=", "").Split(",".ToCharArray());
                // EntIDs were sent - Look for a match to the current one
                for (int iLoop = arrEntityIDs.GetLowerBound(0); iLoop <= arrEntityIDs.GetUpperBound(0); iLoop++)
                {
                    if (arrEntityIDs[iLoop].Trim().Equals(objFile.EntityTag))
                    {
                        sReturn = arrEntityIDs[iLoop];
                        // One of the requested entities matches the current file's tag,
                        objResponse.AppendHeader(HTTP_HEADER_ENTITY_TAG, sReturn);
                        objResponse.StatusCode = 304; // Not Modified
                        bReturn = true;
                    }
                }
            }
            return(bReturn);
        }
        /// <summary>
        /// Check If-Modified-Since header
        /// </summary>
        /// <param name="objRequest"></param>
        /// <param name="objDownload"></param>
        /// <returns></returns>
        private static bool CheckIfModifiedSince(HttpRequest objRequest, DownloadFile objFile) //Careful ByVal
        {
            string   sSinceDate;
            DateTime dtSinceDate;
            bool     bReturn;

            // Retrieve If-Modified-Since Header
            sSinceDate = DownloadHandler.RetrieveHeader(objRequest, HTTP_HEADER_IF_MODIFIED_SINCE, String.Empty);
            if (String.IsNullOrEmpty(sSinceDate))
            {
                bReturn = true; // if (no date was sent - assume we need to re-download the whole file
            }
            else
            {
                bool     bTry               = DateTime.TryParse(sSinceDate, out dtSinceDate); //dtSinceDate is a local date without millisecs
                DateTime dtSinceDateUTC     = dtSinceDate.ToUniversalTime();
                DateTime dtLastWriteTimeUTC = new DateTime(
                    objFile.LastWriteTimeUTC.Year,
                    objFile.LastWriteTimeUTC.Month,
                    objFile.LastWriteTimeUTC.Day,
                    objFile.LastWriteTimeUTC.Hour,
                    objFile.LastWriteTimeUTC.Minute,
                    objFile.LastWriteTimeUTC.Second,
                    DateTimeKind.Utc);
                if (bTry)
                {
                    bReturn = (dtLastWriteTimeUTC > dtSinceDateUTC); // True if the file was actually modified
                }
                else
                {
                    bReturn = false;
                }
            }

            return(bReturn);
        }
        public void ProcessRequest(HttpContext context)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context"); //Very theoretical
            }
            HttpRequest objRequest = context.Request;

            System.Diagnostics.Debug.Assert(objRequest != null);
            HttpResponse objResponse = context.Response;

            System.Diagnostics.Debug.Assert(objResponse != null);

            //TODO: Maybe we can calculate a timeout in propertion of content length
            context.Server.ScriptTimeout = 24 * 60 * 60; //1 day

            string       sUserName   = context.User.Identity.Name;
            DownloadFile objDownload = null; // Custom File information object...
            Stream       objDownloadStream;

            long[] arliRequestedRangesBegin = new long[0]; // Start of Chunk
            long[] arliRequestedRangesEnd   = new long[0]; // End of Chunk
            int    iResponseContentLength   = 0;
            int    iBytesToRead;
            int    iLengthOfReadChunk;
            bool   bWasDownloadInterrupted = false; //Was download Interupted
            bool   bIsChunkRequest         = false;
            bool   bMultipart = false;

            byte[] arrBuffer = new byte[BUFFER_SIZE];

            //Get to the file that was requested.
            //ONLY FILE TYPES WITH EXTENSIONS MAPPED IN WEB.CONFIG WILL CALL THIS CODE
            try
            {
                Guid gFileGuid = new Guid(System.IO.Path.GetFileNameWithoutExtension(objRequest.FilePath));
                objDownload = new DownloadFile(gFileGuid, sUserName);
            }
            catch
            {
                //ArgumentNullException, FormatException, OverflowException, DataLayerException, BusinessLayerException
            }

            //Clear the response
            objResponse.Clear();

            //Added by JLC after the following article
            //http://support.softartisans.com/kbview.aspx?ID=766
            objResponse.Buffer = false;

            string strMethod = objRequest.HttpMethod;

            //We could support more request types
            if (!strMethod.Equals(HTTP_METHOD_GET) && !strMethod.Equals(HTTP_METHOD_HEAD))
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionRequestTypeNotImplemented, strMethod);
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorRequestTypeNotImplemented,
                    null);

                //objResponse.StatusCode = 501;  //HTTP Request Type Not implemented
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(501, sErrorMessage);
            }
            else if ((objDownload == null) || (!objDownload.Exists))
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionCannotRetrieveDownload, objRequest.FilePath);
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorCannotRetrieveDownload,
                    new HttpException(404, sErrorMessage));

                //objResponse.StatusCode = 404;  //File Not found
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(404, sErrorMessage);
            }
            else if (objDownload.IsExpired)
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionDownloadExpired, objRequest.FilePath);
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorDownloadExpired,
                    new HttpException(410, sErrorMessage));

                //objResponse.StatusCode = 410;  //File no more available. Probably expired.
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(410, sErrorMessage);
            }
            else if (!objDownload.CanDownload)
            {
                if (context.User.Identity.IsAuthenticated)
                {
                    string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionUnauthorizedDownload, sUserName, objRequest.FilePath);
                    HealthMonitoringManager.LogErrorEvent(
                        sErrorMessage,
                        objDownload,
                        DownloadRequestErrorEvent.RuntimeErrorUnauthorizedDownload,
                        new HttpException(403, sErrorMessage));

                    //objResponse.StatusCode = 403; //Forbidden
                    //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                    //and display a nice custom error message
                    throw new HttpException(403, sErrorMessage);
                }
                else
                {
                    string sErrorMessage = Resources.ExceptionAuthenticationRequired;
                    HealthMonitoringManager.LogErrorEvent(
                        sErrorMessage,
                        objDownload,
                        DownloadRequestErrorEvent.RuntimeErrorAuthenticationRequired,
                        new HttpException(401, sErrorMessage));

                    //objResponse.StatusCode = 401; //Authentication required
                    //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                    //and display a nice custom error message
                    throw new HttpException(401, sErrorMessage);
                }
            }
            else if (objDownload.Length > Int32.MaxValue)
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionResponseTooLarge, objRequest.FilePath);
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorResponseTooLarge,
                    new HttpException(413, sErrorMessage));

                //objResponse.StatusCode = 413;  //Request for too many bytes
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(413, sErrorMessage);
            }
            else if (!DownloadHandler.ParseRequestHeaderRange(objRequest, objDownload.Length, ref arliRequestedRangesBegin, ref arliRequestedRangesEnd, ref bIsChunkRequest))
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionInvalidRanges, objRequest.FilePath);
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorInvalidRanges,
                    new HttpException(400, sErrorMessage));

                //objResponse.StatusCode = 400;  //Bad request
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(400, sErrorMessage);
            }
            else if (!DownloadHandler.CheckIfModifiedSince(objRequest, objDownload))
            {
                //Not an error - just tells the browser to download from its cache
                objResponse.StatusCode = 304;  //Not modified
            }
            else if (!DownloadHandler.CheckIfUnmodifiedSince(objRequest, objDownload))
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionPreconditionFailed, objRequest.FilePath, "If-Unmodified-Since");
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorPreconditionFailed,
                    new HttpException(412, sErrorMessage));

                //objResponse.StatusCode = 412;  //modified pre-condition failed
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(412, sErrorMessage);
            }
            else if (!DownloadHandler.CheckIfMatch(objRequest, objDownload))
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionPreconditionFailed, objRequest.FilePath, "If-Match");
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorPreconditionFailed,
                    new HttpException(412, sErrorMessage));

                //objResponse.StatusCode = 412;  //Entitiy Precondition failed
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(412, sErrorMessage);
            }
            else if (!DownloadHandler.CheckIfNoneMatch(objRequest, objResponse, objDownload))
            {
                string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionPreconditionFailed, objRequest.FilePath, "If-None-Match");
                HealthMonitoringManager.LogErrorEvent(
                    sErrorMessage,
                    objDownload,
                    DownloadRequestErrorEvent.RuntimeErrorPreconditionFailed,
                    new HttpException(412, sErrorMessage));

                System.Diagnostics.Trace.WriteLine("Nothing to download");
                //objResponse.StatusCode = 412;  //Entitiy Precondition failed
                //Same issue as with upload module: we raise an exception to be redirected to Application_Error and Custom Errors
                //and display a nice custom error message
                throw new HttpException(412, sErrorMessage);
            }
            else
            {
                //Everything is good so far.
                if (bIsChunkRequest && DownloadHandler.CheckIfRange(objRequest, objDownload)) // Valid Chunk Request
                {
                    bMultipart = (bool)(arliRequestedRangesBegin.GetUpperBound(0) > 0);
                    // Loop through chunks to calculate the total length
                    for (int iLoop = arliRequestedRangesBegin.GetLowerBound(0); iLoop <= arliRequestedRangesBegin.GetUpperBound(0); iLoop++)
                    {
                        iResponseContentLength += Convert.ToInt32(arliRequestedRangesEnd[iLoop] - arliRequestedRangesBegin[iLoop]) + 1;
                        if (bMultipart)
                        {
                            // Calc length of the headers
                            iResponseContentLength += MULTIPART_BOUNDARY.Length;
                            iResponseContentLength += objDownload.ContentType.Length;
                            iResponseContentLength += arliRequestedRangesBegin[iLoop].ToString(CultureInfo.InvariantCulture).Length;
                            iResponseContentLength += arliRequestedRangesEnd[iLoop].ToString(CultureInfo.InvariantCulture).Length;
                            iResponseContentLength += objDownload.Length.ToString(CultureInfo.InvariantCulture).Length;
                            iResponseContentLength += 49; // add length needed for multipart header
                        }
                    }

                    if (bMultipart)
                    {
                        // Calc length of last intermediate header
                        iResponseContentLength += MULTIPART_BOUNDARY.Length;
                        iResponseContentLength += 8; // length of dash and line break
                    }
                    else
                    {
                        objResponse.AppendHeader(HTTP_HEADER_CONTENT_RANGE, "bytes "
                                                 + arliRequestedRangesBegin[0].ToString(CultureInfo.InvariantCulture) + "-"
                                                 + arliRequestedRangesEnd[0].ToString(CultureInfo.InvariantCulture) + "/"
                                                 + objDownload.Length.ToString(CultureInfo.InvariantCulture));
                    }
                    objResponse.StatusCode = 206; // Partial Response
                }
                else
                {
                    //Not a Chunck  request or Entity doesn't match
                    //Start a new download of the entire file
                    iResponseContentLength = Convert.ToInt32(objDownload.Length);
                    objResponse.StatusCode = 200; //Status OK
                }

                string sEncodedFileName = HttpUtility.UrlPathEncode(objDownload.FileName); //This is for Unicode file names like chinese file names
                objResponse.AppendHeader(HTTP_HEADER_CONTENT_DISPOSITION, HTTP_HEADER_CONTENT_DISPOSITION_ATTACHMENT + sEncodedFileName);
                objResponse.AppendHeader(HTTP_HEADER_CONTENT_LENGTH, iResponseContentLength.ToString(CultureInfo.InvariantCulture));
                objResponse.AppendHeader(HTTP_HEADER_LAST_MODIFIED, objDownload.LastWriteTimeUTC.ToString("r", CultureInfo.InvariantCulture));
                objResponse.AppendHeader(HTTP_HEADER_ACCEPT_RANGES, HTTP_HEADER_ACCEPT_RANGES_BYTES);
                objResponse.AppendHeader(HTTP_HEADER_ENTITY_TAG, "\"" + objDownload.EntityTag + "\""); // Entity Tag muss be Quote Enclosed

                if (bMultipart)
                {
                    // File real MIME type gets pushed into the response object later
                    objResponse.ContentType = MULTIPART_CONTENTTYPE;
                }
                else
                {
                    objResponse.ContentType = objDownload.ContentType;
                }

                if (objRequest.HttpMethod.Equals(HTTP_METHOD_HEAD))
                {
                    // Only the HEAD was requested
                }
                else
                {
                    objResponse.Flush();
                    // We're Downloading !!
                    objDownload.State = DownloadFile.DownloadState.DownloadInProgress;
                    objDownloadStream = objDownload.DownloadStream;

                    // Process all the requested chunks.
                    for (int iLoop = arliRequestedRangesBegin.GetLowerBound(0); iLoop <= arliRequestedRangesBegin.GetUpperBound(0); iLoop++)
                    {
                        //TODO: Problem is CanSeek is not supported
                        objDownloadStream.Seek(arliRequestedRangesBegin[iLoop], System.IO.SeekOrigin.Begin);
                        iBytesToRead = Convert.ToInt32(arliRequestedRangesEnd[iLoop] - arliRequestedRangesBegin[iLoop]) + 1;
                        if (bMultipart)
                        {
                            // Send Headers
                            objResponse.Output.WriteLine("--" + MULTIPART_BOUNDARY); // Indicate the part boundry
                            objResponse.Output.WriteLine(HTTP_HEADER_CONTENT_TYPE + ": " + objDownload.ContentType);
                            objResponse.Output.WriteLine(HTTP_HEADER_CONTENT_RANGE + ": bytes "
                                                         + arliRequestedRangesBegin[iLoop].ToString(CultureInfo.InvariantCulture) + "-"
                                                         + arliRequestedRangesEnd[iLoop].ToString(CultureInfo.InvariantCulture) + "/"
                                                         + objDownload.Length.ToString(CultureInfo.InvariantCulture));
                            objResponse.Output.WriteLine();
                        }

                        // Send the Data.
                        while (iBytesToRead > 0)
                        {
                            if (objResponse.IsClientConnected)
                            {
                                iLengthOfReadChunk = objDownloadStream.Read(arrBuffer, 0, Math.Min(arrBuffer.Length, iBytesToRead));
                                objResponse.OutputStream.Write(arrBuffer, 0, iLengthOfReadChunk);
                                objResponse.Flush();
                                Array.Resize <byte>(ref arrBuffer, BUFFER_SIZE);
                                iBytesToRead -= iLengthOfReadChunk;
                            }
                            else
                            {
                                // DOWNLOAD INTERRUPTED
                                iBytesToRead            = -1;
                                bWasDownloadInterrupted = true;
                            }
                        }

                        if (bMultipart)
                        {
                            objResponse.Output.WriteLine(); // Mark the end of the part
                        }

                        if (bWasDownloadInterrupted)
                        {
                            break;
                        }
                    }

                    // Download finished or cancelled...
                    if (bWasDownloadInterrupted)
                    {
                        //Download broken...
                        objDownload.State = DownloadFile.DownloadState.DownloadBroken;

                        string sErrorMessage = String.Format(Resources.Culture, Resources.ExceptionRequestAbort, objRequest.FilePath);
                        HealthMonitoringManager.LogErrorEvent(
                            sErrorMessage,
                            objDownload,
                            DownloadRequestErrorEvent.RuntimeErrorRequestAbort,
                            new System.Net.WebException(sErrorMessage, System.Net.WebExceptionStatus.ConnectionClosed));
                    }
                    else
                    {
                        if (bMultipart)
                        {
                            objResponse.Output.WriteLine("--" + MULTIPART_BOUNDARY + "--");
                            objResponse.Output.WriteLine();
                        }

                        // Download Complete
                        objDownload.State = DownloadFile.DownloadState.DownloadFinished;

                        HealthMonitoringManager.LogSucessEvent(
                            Resources.MessageDownloadCompleted,
                            objDownload,
                            DownloadRequestSuccessEvent.RuntimeDownloadSuccessCompleted);
                    }
                    objDownloadStream.Close();
                }
            }

            objResponse.End();
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="objRequest"></param>
        /// <param name="lBegin"></param>
        /// <param name="lEnd"></param>
        /// <param name="lMax"></param>
        /// <param name="bRangeRequest"></param>
        /// <returns></returns>
        private static bool ParseRequestHeaderRange(HttpRequest objRequest, long lMax, ref long[] lBegin, ref long[] lEnd, ref bool bRangeRequest) //Some ByVal some ByRef
        {
            bool   bValidRangesRet;
            string sSource;

            //int iLoop;
            string[] arrRanges;

            // Retrieve Range Header from Request
            sSource = DownloadHandler.RetrieveHeader(objRequest, HTTP_HEADER_RANGE, String.Empty);
            if (String.IsNullOrEmpty(sSource)) // No Range was requested
            {
                Array.Resize <long>(ref lBegin, 1);
                Array.Resize <long>(ref lEnd, 1);
                lBegin[0]       = 0;
                lEnd[0]         = lMax - 1;
                bValidRangesRet = true;
                bRangeRequest   = false;
            }
            else
            {
                bValidRangesRet = true;
                bRangeRequest   = true;

                // Remove "bytes=" string, and split the rest
                arrRanges = sSource.Replace("bytes=", "").Split(",".ToCharArray());

                Array.Resize <long>(ref lBegin, arrRanges.GetUpperBound(0) + 1);
                Array.Resize <long>(ref lEnd, arrRanges.GetUpperBound(0) + 1);

                // Check each Range request
                for (int iLoop = arrRanges.GetLowerBound(0); iLoop <= arrRanges.GetUpperBound(0); iLoop++)
                {
                    // arrRange(0) is the begin value - arrRange(1) is the end value.
                    string[] arrRange = arrRanges[iLoop].Split("-".ToCharArray());
                    if (String.IsNullOrEmpty(arrRange[1])) // No end was specified
                    {
                        lEnd[iLoop] = lMax - 1;
                    }
                    else
                    {
                        lEnd[iLoop] = long.Parse(arrRange[1], CultureInfo.InvariantCulture);
                    }
                    if (String.IsNullOrEmpty(arrRange[0]))
                    {
                        // Calculate the beginning and end.
                        lBegin[iLoop] = lMax - 1 - lEnd[iLoop];
                        lEnd[iLoop]   = lMax - 1;
                    }
                    else
                    {
                        lBegin[iLoop] = long.Parse(arrRange[0], CultureInfo.InvariantCulture);
                    }
                    // Begin and end must not exceed the total file size
                    if ((lBegin[iLoop] > (lMax - 1)) || (lEnd[iLoop] > (lMax - 1)))
                    {
                        bValidRangesRet = false;
                    }
                    // Begin and end cannot be < 0
                    if ((lBegin[iLoop] < 0) || (lEnd[iLoop] < 0))
                    {
                        bValidRangesRet = false;
                    }
                    // End must be larger or equal to begin value
                    if (lEnd[iLoop] < lBegin[iLoop])
                    {
                        bValidRangesRet = false;
                    }
                }
            }
            return(bValidRangesRet);
        }