示例#1
0
        private StreamWriteStatus BufferStreamToResponse(StreamLoaderResult fileStreamDetails, IEnumerable <RangeRequest> rangeRequests, string contentType)
        {
            bool isMultipart = RangeRequestHelpers.IsMultipartRequest(rangeRequests);
            int  bytesRead;
            long bytesToRead;
            long remainingBytes = fileStreamDetails.Metadata.Size.Value;

            byte[]     buffer  = new byte[this._bufferSizeBytes];
            TextWriter _output = this._response.Output;

            //stream each requested range to the client
            foreach (RangeRequest rangeRequest in rangeRequests)
            {
                fileStreamDetails.FileStream.Seek(rangeRequest.Start, SeekOrigin.Begin);
                bytesToRead = rangeRequest.End - rangeRequest.Start + 1;
                // If this is a multipart response, we must add certain headers before streaming the content:
                if (isMultipart)
                {
                    _output.Write(rangeRequest.GetMultipartIntermediateHeader(contentType));
                }

                while (bytesToRead > 0)
                {
                    if (!this._response.IsClientConnected)
                    {
                        return(StreamWriteStatus.ClientDisconnected);
                    }

                    //buffer length can only be a max of int32 in length -- so we know that this value will always be less than an int32
                    int toRead = Convert.ToInt32(Math.Min(Convert.ToInt64(buffer.Length), bytesToRead));
                    bytesRead = fileStreamDetails.FileStream.Read(buffer, 0, toRead);
                    this._response.OutputStream.Write(buffer, 0, bytesRead);
                    this._response.Flush();
                    bytesToRead -= bytesRead;
                }

                // In Multipart responses, mark the end of the part
                if (isMultipart)
                {
                    _output.WriteLine();
                }
            }

            if (isMultipart)
            {
                _output.Write(_MultipartFooter);
            }
            return(StreamWriteStatus.ApplicationRestarted);
        }
示例#2
0
        private bool HeadersValidate(StreamLoaderResult fileDetails, out IEnumerable <RangeRequest> rangeRequests)
        {
            rangeRequests = null;

            //we have ranges specified in the header, but they're invalid
            if (this._request.HasRangeHeaders() &&
                !this._request.TryParseRangeRequests(fileDetails.Metadata.Size.Value, out rangeRequests))
            {
                RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
                return(false);
            }
            //modified since the requested date -- passing this sessionType value back will keep everything behind the scenes w/ the browser
            else if (fileDetails.Metadata.LastWriteTimeUtc.HasValue &&
                     !this._request.IfModifiedSinceHeaderIsBeforeGiven(fileDetails.Metadata.LastWriteTimeUtc.Value))
            {
                this._response.StatusCode = (int)HttpStatusCode.NotModified;
                return(false);
            }
            else if (fileDetails.Metadata.LastWriteTimeUtc.HasValue &&
                     !this._request.IfUnmodifiedHeaderIsAfterGivenDate(fileDetails.Metadata.LastWriteTimeUtc.Value))
            {
                this._response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
                return(false);
            }
            //ETag doesn't match request
            else if (!this._request.IfMatchHeaderIsValid(fileDetails.Metadata.ExpectedMD5))
            {
                this._response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
                return(false);
            }
            // There is an If-None-Match header with "*" specified, which we don't allow
            else if (this._request.IfNoneMatchHeaderIsWildcard())
            {
                this._response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
                return(false);
            }
            // The entity does match the none-match request, the response code was set inside the IsIfNoneMatchHeaderValid function
            else if (this._request.IfNoneMatchHeaderHasMatchingETagSpecified(fileDetails.Metadata.ExpectedMD5))
            {
                this._response.StatusCode = (int)HttpStatusCode.NotModified;
                this._response.AppendHeader(HttpHeaderFields.EntityTag.ToEnumValueString(), fileDetails.Metadata.ExpectedMD5);
                return(false);
            }

            return(true);
        }
示例#3
0
        private void RedirectOnFailedStatus(StreamLoaderResult fileDetails)
        {
            switch (fileDetails.Status)
            {
            case StreamLoadStatus.FileStoredInCloud:
                _statusLogger.Log(this._context, StreamWriteStatus.RedirectedClientToCloudUri);
                RedirectWithStatusCode(this._response, HttpStatusCode.MovedPermanently, fileDetails.CloudLocation.ToUrl());
                return;

            case StreamLoadStatus.UnauthorizedAccess:
                _statusLogger.Log(this._context, StreamWriteStatus.UnauthorizedAccessError);
                RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
                return;

            //TODO: these 2 might be the same scenario
            case StreamLoadStatus.InvalidRequest:
                _statusLogger.Log(this._context, StreamWriteStatus.UserRequestError);
                RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
                return;

            case StreamLoadStatus.FileMetadataNotFound:
                _statusLogger.Log(this._context, StreamWriteStatus.UserRequestError);
                RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
                return;

            case StreamLoadStatus.FileSizeMismatched:
                _statusLogger.Log(this._context, StreamWriteStatus.MismatchedSizeError);
                RedirectWithStatusCode(this._response, HttpStatusCode.InternalServerError, _configuration.ServerErrorUrl);
                return;

            case StreamLoadStatus.MD5Failed:
                _statusLogger.Log(this._context, StreamWriteStatus.MD5Failed);
                //log.Error(String.Format("File Corrupt -- Failed MD5 check - Identity [{0}] - FileId [{1}] - User Id [{2}] / Name [{3}] / Email [{4}]{5}File Path [ {6} ]{5}{5}{7}", WindowsIdentity.GetCurrent().Name, FileId, context.OnyxUser().Identity.OCPUser.Individual.OnyxId, context.OnyxUser().Identity.OCPUser.Individual.UserName, context.OnyxUser().Identity.OCPUser.Individual.UserEmailAddress, Environment.NewLine, properties.MappedPath, this._request.ToLogString()));
                RedirectWithStatusCode(this._response, HttpStatusCode.InternalServerError, _configuration.ServerErrorUrl);
                return;

            case StreamLoadStatus.FileNotFound:
                _statusLogger.Log(this._context, StreamWriteStatus.NotFound);
                //log.Error(String.Format("File Not Found - Identity [{0}] - FileId [{1}] - User Id [{2}] / Name [{3}] / Email [{4}]{5}File Path [ {6} ]{5}{5}{7}", WindowsIdentity.GetCurrent().Name, FileId, context.OnyxUser().Identity.OCPUser.Individual.OnyxId, context.OnyxUser().Identity.OCPUser.Individual.UserName, context.OnyxUser().Identity.OCPUser.Individual.UserEmailAddress, Environment.NewLine, properties.MappedPath, this._request.ToLogString()));
                RedirectWithStatusCode(this._response, HttpStatusCode.InternalServerError, _configuration.ServerErrorUrl);
                return;
            }
        }
示例#4
0
        /// <summary>   Stream file. </summary>
        /// <remarks>   ebrown, 2/15/2011. </remarks>
        /// <exception cref="ArgumentNullException">    Thrown when one or more required arguments are null. </exception>
        /// <exception cref="Exception">                Thrown when exception. </exception>
        /// <param name="requestedResponseType">    Type of the requested response. </param>
        /// <param name="downloadProperties">       The download properties. </param>
        /// <param name="rangeRequests">            The range requests. </param>
        /// <param name="ifRangeEntityTag">         if range entity tag. </param>
        /// <param name="forceDownload">            true to force download. </param>
        /// <returns>   . </returns>
        public StreamWriteStatus StreamFile(HttpResponseType requestedResponseType, StreamLoaderResult downloadProperties, IEnumerable <RangeRequest> rangeRequests, string ifRangeEntityTag, bool forceDownload)
        {
            if (null == downloadProperties)
            {
                throw new ArgumentNullException("downloadProperties");
            }
            if (null == rangeRequests)
            {
                throw new ArgumentNullException("rangeRequests");
            }

            try
            {
                string contentType = !string.IsNullOrEmpty(downloadProperties.ContentType) ?
                                     downloadProperties.ContentType : MimeTypes.GetMimeTypeForFileExtension(Path.GetExtension(downloadProperties.Metadata.FileName));

                if (forceDownload)
                {
                    this._response.AppendHeader("content-disposition", String.Format(CultureInfo.InvariantCulture, "attachment; filename={0}", downloadProperties.Metadata.FileName));
                }

                bool isMultipart = RangeRequestHelpers.IsMultipartRequest(rangeRequests),
                     isActionableRangeRequest = IsActionableRangeRequest(downloadProperties.Metadata, rangeRequests, ifRangeEntityTag);

                //TODO: is this response.Clear() necessary here?
                this._response.Clear();

                long responseContentLength = CalculateContentLength(downloadProperties.Metadata, rangeRequests, ifRangeEntityTag, contentType, isMultipart);
                this._response.StatusCode = isActionableRangeRequest ? (int)HttpStatusCode.PartialContent : (int)HttpStatusCode.OK;

                if (isActionableRangeRequest && !isMultipart)
                {
                    var first = rangeRequests.First();
                    //must indicate the Response Range of in the initial HTTP Header since this isn't multipart
                    this._response.AppendHeader(HttpHeaderFields.ContentRange.ToEnumValueString(),
                                                String.Format(CultureInfo.InvariantCulture, "bytes {0}-{1}/{2}", first.Start, first.End, downloadProperties.Metadata.Size.Value));
                }

                this._response.AppendHeader(HttpHeaderFields.ContentLength.ToEnumValueString(), responseContentLength.ToString(CultureInfo.InvariantCulture));
                //don't go off the DB insert date for last modified b/c the file system file could be older (b/c multiple files records inserted at different dates could share the same physical file)
                if (downloadProperties.Metadata.LastWriteTimeUtc.HasValue)
                {
                    this._response.AppendHeader(HttpHeaderFields.LastModified.ToEnumValueString(), downloadProperties.Metadata.LastWriteTimeUtc.Value.ToString("r", CultureInfo.InvariantCulture));
                }
                this._response.AppendHeader(HttpHeaderFields.AcceptRanges.ToEnumValueString(), "bytes");
                this._response.AppendHeader(HttpHeaderFields.EntityTag.ToEnumValueString(), String.Format(CultureInfo.InvariantCulture, "\"{0}\"", downloadProperties.Metadata.ExpectedMD5));
                //not sure if we should use the Keep-Alive header?
                //this._response.AppendHeader(HttpHeaderFields.HTTP_HEADER_KEEP_ALIVE, "timeout=15, max=30");

                //multipart messages are special -> file's actual mime type written into Response later
                this._response.ContentType = (isMultipart ? MultipartNames.MultipartContentType : contentType);

                //we've dumped our HEAD and can return now
                if (HttpResponseType.HeadOnly == requestedResponseType)
                {
                    // Flush the HEAD information to the client...
                    this._response.Flush();

                    return(StreamWriteStatus.SentHttpHead);
                }

                StreamWriteStatus result = BufferStreamToResponse(downloadProperties, rangeRequests, contentType);
                return((StreamWriteStatus.ClientDisconnected == result) ? result : StreamWriteStatus.SentFile);

                //this causes a ThreadAbortException
                //Response.End();
            }
            catch (IOException ex)
            {
                string userName = string.Empty;
                using (var identity = WindowsIdentity.GetCurrent()) { userName = identity.Name; }

                //, this._request.ToLogString()
                log.Error(String.Format(CultureInfo.InvariantCulture, "File read failure for user {0}", userName), ex);
                return(StreamWriteStatus.StreamReadError);
            }
            catch (Exception ex)
            {
                HttpException httpEx = ex as HttpException;
                //suck up the remote connection failure
                if ((null != httpEx) && (HttpExceptionErrorCodes.ConnectionAborted == httpEx.ErrorCode))
                {
                    return(StreamWriteStatus.ClientDisconnected);
                }

                //bubble up to the caller, and let them log it -- this maintains the identity of our original exception as the innerexception
                throw;
                //log.Error("Unexpected failure for " + WindowsIdentity.GetCurrent().Name, ex);

                //error = "Unexpected File Transfer Error - FileId [" + FileId.ToString() + "] - User Id [" + context.OnyxUser().Identity.OCPUser.Individual.OnyxId.ToString() + "] / Name [" + ((userProperties.Name == null) ? string.Empty : userProperties.Name) + "] / Email [" + ((userProperties.Email == null) ? string.Empty : userProperties.Email) + "]" + Environment.NewLine + "File Path [ " + properties.MappedPath + " ]";
                //return DownloadFileStatus.UnexpectedError;
            }
        }
示例#5
0
        /// <summary>
        /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the <see cref="T:System.Web.IHttpHandler"
        /// />interface.
        /// </summary>
        /// <remarks>	ebrown, 2/10/2011. </remarks>
        /// <exception cref="ObjectDisposedException">	Thrown when a supplied object has been disposed. </exception>
        /// <exception cref="ArgumentNullException">	Thrown when one or more required arguments are null. </exception>
        /// <param name="context">	An <see cref="T:System.Web.HttpContext" /> object that provides references to the intrinsic server objects
        ///                         (for example, Request, Response, Session, and Server) used to service HTTP requests. </param>
        public override void ProcessRequest(HttpContextBase context)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException("this");
            }
            stopwatch.Start();
            //TODO: 5-1-09 -- Response.Redirect doesn't make any sense... maybe we should just give them an 'unauthorized' empty file thing
            if (null == context)
            {
                throw new ArgumentNullException("context");
            }

            this._context = context;
            //assign to local fields for micro-perf since these are virtuals
            this._request  = context.Request;
            this._response = context.Response;

            try
            {
                var method = this._request.HttpMethod;
                //we only accept GET or HEAD - don't log these
                if ((HttpMethodNames.Get.ToEnumValueString() != method) &&
                    (HttpMethodNames.Header.ToEnumValueString() != method))
                {
                    this._response.StatusCode = (int)HttpStatusCode.NotImplemented;
                    this._response.Redirect(_configuration.UnauthorizedErrorRedirectUrl, false);
                    return;
                }

                _statusLogger.Log(context, StreamWriteStatus.ApplicationRestarted);

                //now pass the HttpRequestBase to our class responsible for examining specific state and loading the Stream
                using (StreamLoaderResult fileDetails = _streamLoader.ParseRequest(this._request))
                {
                    if (fileDetails.Status != StreamLoadStatus.Success)
                    {
                        RedirectOnFailedStatus(fileDetails);
                        return;
                    }

                    IEnumerable <RangeRequest> rangeRequests;
                    if (!HeadersValidate(fileDetails, out rangeRequests))
                    {
                        return;
                    }

                    HttpResponseType responseStreamType = method == HttpMethodNames.Header.ToEnumValueString() ?
                                                          HttpResponseType.HeadOnly : HttpResponseType.Complete;
                    //we've passed our credential checking and we're ready to send out our response
                    StreamWriteStatus streamWriteStatus = new ResponseStreamWriter(this._response, this._configuration.FileTransferBufferSizeInKBytes)
                                                          .StreamFile(responseStreamType, fileDetails, rangeRequests, this._request.RetrieveHeader(HttpHeaderFields.IfRange), true);
                    HandleStreamWriteStatus(streamWriteStatus);
                }
            }
            catch (HttpException hex)
            {
                //remote host closed the connection
                if (HttpExceptionErrorCodes.ConnectionAborted == hex.ErrorCode)
                {
                    //log.Warn(String.Format("Remote Host Closed Transfer - Identity [{0}] - FileId [{1}] - User Id [{2}] / Name [{3}] / Email [{4}]{5}{5}{6}", WindowsIdentity.GetCurrent().Name, FileId, context.OnyxUser().Identity.OCPUser.Individual.OnyxId, context.OnyxUser().Identity.OCPUser.Individual.UserName, context.OnyxUser().Identity.OCPUser.Individual.UserEmailAddress, Environment.NewLine, this._request.ToLogString()), ex);
                }
            }
            //eat it -- nothing we can do
            catch (ThreadAbortException)
            { }
            //log.Error(String.Format("Unexpected Download Exception Occurred - Identity [{0}] - FileId [{1}] - User Id [{2}] / Name [{3}] / Email [{4}]{5}{5}{6}", WindowsIdentity.GetCurrent().Name, FileId, context.OnyxUser().Identity.OCPUser.Individual.OnyxId, context.OnyxUser().Identity.OCPUser.Individual.UserName, context.OnyxUser().Identity.OCPUser.Individual.UserEmailAddress, Environment.NewLine, this._request.ToLogString()), ex);
            //SafeInternalServerError(context);
        }
示例#6
0
 private void RedirectOnFailedStatus(StreamLoaderResult fileDetails)
 {
     switch (fileDetails.Status)
     {
         case StreamLoadStatus.FileStoredInCloud:
             _statusLogger.Log(this._context, StreamWriteStatus.RedirectedClientToCloudUri);
             RedirectWithStatusCode(this._response, HttpStatusCode.MovedPermanently, fileDetails.CloudLocation.ToUrl());
             return;
         case StreamLoadStatus.UnauthorizedAccess:
             _statusLogger.Log(this._context, StreamWriteStatus.UnauthorizedAccessError);
             RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
             return;
         //TODO: these 2 might be the same scenario
         case StreamLoadStatus.InvalidRequest:
             _statusLogger.Log(this._context, StreamWriteStatus.UserRequestError);
             RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
             return;
         case StreamLoadStatus.FileMetadataNotFound:
             _statusLogger.Log(this._context, StreamWriteStatus.UserRequestError);
             RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
             return;
         case StreamLoadStatus.FileSizeMismatched:
             _statusLogger.Log(this._context, StreamWriteStatus.MismatchedSizeError);
             RedirectWithStatusCode(this._response, HttpStatusCode.InternalServerError, _configuration.ServerErrorUrl);
             return;
         case StreamLoadStatus.MD5Failed:
             _statusLogger.Log(this._context, StreamWriteStatus.MD5Failed);
             //log.Error(String.Format("File Corrupt -- Failed MD5 check - Identity [{0}] - FileId [{1}] - User Id [{2}] / Name [{3}] / Email [{4}]{5}File Path [ {6} ]{5}{5}{7}", WindowsIdentity.GetCurrent().Name, FileId, context.OnyxUser().Identity.OCPUser.Individual.OnyxId, context.OnyxUser().Identity.OCPUser.Individual.UserName, context.OnyxUser().Identity.OCPUser.Individual.UserEmailAddress, Environment.NewLine, properties.MappedPath, this._request.ToLogString()));
             RedirectWithStatusCode(this._response, HttpStatusCode.InternalServerError, _configuration.ServerErrorUrl);
             return;
         case StreamLoadStatus.FileNotFound:
             _statusLogger.Log(this._context, StreamWriteStatus.NotFound);
             //log.Error(String.Format("File Not Found - Identity [{0}] - FileId [{1}] - User Id [{2}] / Name [{3}] / Email [{4}]{5}File Path [ {6} ]{5}{5}{7}", WindowsIdentity.GetCurrent().Name, FileId, context.OnyxUser().Identity.OCPUser.Individual.OnyxId, context.OnyxUser().Identity.OCPUser.Individual.UserName, context.OnyxUser().Identity.OCPUser.Individual.UserEmailAddress, Environment.NewLine, properties.MappedPath, this._request.ToLogString()));
             RedirectWithStatusCode(this._response, HttpStatusCode.InternalServerError, _configuration.ServerErrorUrl);
             return;
     }
 }
示例#7
0
        private bool HeadersValidate(StreamLoaderResult fileDetails, out IEnumerable<RangeRequest> rangeRequests)
        {
            rangeRequests = null;

            //we have ranges specified in the header, but they're invalid
            if (this._request.HasRangeHeaders() &&
                !this._request.TryParseRangeRequests(fileDetails.Metadata.Size.Value, out rangeRequests))
            {
                RedirectWithStatusCode(this._response, HttpStatusCode.BadRequest, _configuration.UnauthorizedErrorRedirectUrl);
                return false;
            }
            //modified since the requested date -- passing this sessionType value back will keep everything behind the scenes w/ the browser
            else if (fileDetails.Metadata.LastWriteTimeUtc.HasValue &&
                !this._request.IfModifiedSinceHeaderIsBeforeGiven(fileDetails.Metadata.LastWriteTimeUtc.Value))
            {
                this._response.StatusCode = (int)HttpStatusCode.NotModified;
                return false;
            }
            else if (fileDetails.Metadata.LastWriteTimeUtc.HasValue &&
                !this._request.IfUnmodifiedHeaderIsAfterGivenDate(fileDetails.Metadata.LastWriteTimeUtc.Value))
            {
                this._response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
                return false;
            }
            //ETag doesn't match request
            else if (!this._request.IfMatchHeaderIsValid(fileDetails.Metadata.ExpectedMD5))
            {
                this._response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
                return false;
            }
            // There is an If-None-Match header with "*" specified, which we don't allow
            else if (this._request.IfNoneMatchHeaderIsWildcard())
            {
                this._response.StatusCode = (int)HttpStatusCode.PreconditionFailed;
                return false;
            }
            // The entity does match the none-match request, the response code was set inside the IsIfNoneMatchHeaderValid function
            else if (this._request.IfNoneMatchHeaderHasMatchingETagSpecified(fileDetails.Metadata.ExpectedMD5))
            {
                this._response.StatusCode = (int)HttpStatusCode.NotModified;
                this._response.AppendHeader(HttpHeaderFields.EntityTag.ToEnumValueString(), fileDetails.Metadata.ExpectedMD5);
                return false;
            }

            return true;
        }
        /// <summary>   Stream file. </summary>
        /// <remarks>   ebrown, 2/15/2011. </remarks>
        /// <exception cref="ArgumentNullException">    Thrown when one or more required arguments are null. </exception>
        /// <exception cref="Exception">                Thrown when exception. </exception>
        /// <param name="requestedResponseType">    Type of the requested response. </param>
        /// <param name="downloadProperties">       The download properties. </param>
        /// <param name="rangeRequests">            The range requests. </param>
        /// <param name="ifRangeEntityTag">         if range entity tag. </param>
        /// <param name="forceDownload">            true to force download. </param>
        /// <returns>   . </returns>
        public StreamWriteStatus StreamFile(HttpResponseType requestedResponseType, StreamLoaderResult downloadProperties, IEnumerable<RangeRequest> rangeRequests, string ifRangeEntityTag, bool forceDownload)
        {
            if (null == downloadProperties) { throw new ArgumentNullException("downloadProperties"); }
            if (null == rangeRequests) { throw new ArgumentNullException("rangeRequests"); }

            try
            {
                string contentType = !string.IsNullOrEmpty(downloadProperties.ContentType) ?
                    downloadProperties.ContentType : MimeTypes.GetMimeTypeForFileExtension(Path.GetExtension(downloadProperties.Metadata.FileName));

                if (forceDownload)
                {
                    this._response.AppendHeader("content-disposition", String.Format(CultureInfo.InvariantCulture, "attachment; filename={0}", downloadProperties.Metadata.FileName));
                }

                bool isMultipart = RangeRequestHelpers.IsMultipartRequest(rangeRequests),
                    isActionableRangeRequest = IsActionableRangeRequest(downloadProperties.Metadata, rangeRequests, ifRangeEntityTag);

                //TODO: is this response.Clear() necessary here?
                this._response.Clear();

                long responseContentLength = CalculateContentLength(downloadProperties.Metadata, rangeRequests, ifRangeEntityTag, contentType, isMultipart);
                this._response.StatusCode = isActionableRangeRequest ? (int)HttpStatusCode.PartialContent : (int)HttpStatusCode.OK;

                if (isActionableRangeRequest && !isMultipart)
                {
                    var first = rangeRequests.First();
                    //must indicate the Response Range of in the initial HTTP Header since this isn't multipart
                    this._response.AppendHeader(HttpHeaderFields.ContentRange.ToEnumValueString(),
                        String.Format(CultureInfo.InvariantCulture, "bytes {0}-{1}/{2}", first.Start, first.End, downloadProperties.Metadata.Size.Value));
                }

                this._response.AppendHeader(HttpHeaderFields.ContentLength.ToEnumValueString(), responseContentLength.ToString(CultureInfo.InvariantCulture));
                //don't go off the DB insert date for last modified b/c the file system file could be older (b/c multiple files records inserted at different dates could share the same physical file)
                if (downloadProperties.Metadata.LastWriteTimeUtc.HasValue)
                    this._response.AppendHeader(HttpHeaderFields.LastModified.ToEnumValueString(), downloadProperties.Metadata.LastWriteTimeUtc.Value.ToString("r", CultureInfo.InvariantCulture));
                this._response.AppendHeader(HttpHeaderFields.AcceptRanges.ToEnumValueString(), "bytes");
                this._response.AppendHeader(HttpHeaderFields.EntityTag.ToEnumValueString(), String.Format(CultureInfo.InvariantCulture, "\"{0}\"", downloadProperties.Metadata.ExpectedMD5));
                //not sure if we should use the Keep-Alive header?
                //this._response.AppendHeader(HttpHeaderFields.HTTP_HEADER_KEEP_ALIVE, "timeout=15, max=30");

                //multipart messages are special -> file's actual mime type written into Response later
                this._response.ContentType = (isMultipart ? MultipartNames.MultipartContentType : contentType);

                //we've dumped our HEAD and can return now
                if (HttpResponseType.HeadOnly == requestedResponseType)
                {
                    // Flush the HEAD information to the client...
                    this._response.Flush();

                    return StreamWriteStatus.SentHttpHead;
                }

                StreamWriteStatus result = BufferStreamToResponse(downloadProperties, rangeRequests, contentType);
                return (StreamWriteStatus.ClientDisconnected == result) ? result : StreamWriteStatus.SentFile;

                //this causes a ThreadAbortException
                //Response.End();
            }
            catch (IOException ex)
            {
                string userName = string.Empty;
                using (var identity = WindowsIdentity.GetCurrent()) { userName = identity.Name; }

                //, this._request.ToLogString()
                log.Error(String.Format(CultureInfo.InvariantCulture, "File read failure for user {0}", userName), ex);
                return StreamWriteStatus.StreamReadError;
            }
            catch (Exception ex)
            {
                HttpException httpEx = ex as HttpException;
                //suck up the remote connection failure
                if ((null != httpEx) && (HttpExceptionErrorCodes.ConnectionAborted == httpEx.ErrorCode))
                    return StreamWriteStatus.ClientDisconnected;

                //bubble up to the caller, and let them log it -- this maintains the identity of our original exception as the innerexception
                throw;
                //log.Error("Unexpected failure for " + WindowsIdentity.GetCurrent().Name, ex);

                //error = "Unexpected File Transfer Error - FileId [" + FileId.ToString() + "] - User Id [" + context.OnyxUser().Identity.OCPUser.Individual.OnyxId.ToString() + "] / Name [" + ((userProperties.Name == null) ? string.Empty : userProperties.Name) + "] / Email [" + ((userProperties.Email == null) ? string.Empty : userProperties.Email) + "]" + Environment.NewLine + "File Path [ " + properties.MappedPath + " ]";
                //return DownloadFileStatus.UnexpectedError;
            }
        }
        private StreamWriteStatus BufferStreamToResponse(StreamLoaderResult fileStreamDetails, IEnumerable<RangeRequest> rangeRequests, string contentType)
        {
            bool isMultipart = RangeRequestHelpers.IsMultipartRequest(rangeRequests);
            int bytesRead;
            long bytesToRead;
            long remainingBytes = fileStreamDetails.Metadata.Size.Value;
            byte[] buffer = new byte[this._bufferSizeBytes];
            TextWriter _output = this._response.Output;

            //stream each requested range to the client
            foreach (RangeRequest rangeRequest in rangeRequests)
            {
                fileStreamDetails.FileStream.Seek(rangeRequest.Start, SeekOrigin.Begin);
                bytesToRead = rangeRequest.End - rangeRequest.Start + 1;
                // If this is a multipart response, we must add certain headers before streaming the content:
                if (isMultipart)
                {
                    _output.Write(rangeRequest.GetMultipartIntermediateHeader(contentType));
                }

                while (bytesToRead > 0)
                {
                    if (!this._response.IsClientConnected)
                    {
                        return StreamWriteStatus.ClientDisconnected;
                    }

                    //buffer length can only be a max of int32 in length -- so we know that this value will always be less than an int32
                    int toRead = Convert.ToInt32(Math.Min(Convert.ToInt64(buffer.Length), bytesToRead));
                    bytesRead = fileStreamDetails.FileStream.Read(buffer, 0, toRead);
                    this._response.OutputStream.Write(buffer, 0, bytesRead);
                    this._response.Flush();
                    bytesToRead -= bytesRead;
                }

                // In Multipart responses, mark the end of the part
                if (isMultipart)
                {
                    _output.WriteLine();
                }
            }

            if (isMultipart)
            {
                _output.Write(_MultipartFooter);
            }
            return StreamWriteStatus.ApplicationRestarted;
        }