public void ProcessRequest(HttpContext context) { try { if (!SecurityContext.IsAuthenticated && !CoreContext.UserManager.GetUsers(SecurityContext.CurrentAccount.ID).IsAdmin()) { throw new HttpException(403, "Access denied."); } var pid = context.Request.QueryString["pid"]; if (string.IsNullOrEmpty(pid)) { throw new HttpException(400, "Bad request."); } if (CoreContext.PaymentManager.GetTariffPayments(TenantProvider.CurrentTenantID).All(p => p.CartId != pid)) { throw new HttpException(403, "Access denied."); } var invoice = CoreContext.PaymentManager.GetPaymentInvoice(pid); if (invoice == null || string.IsNullOrEmpty(invoice.Sale)) { throw new HttpException(404, "Not found."); } var pdf = Convert.FromBase64String(invoice.Sale); context.Response.Clear(); context.Response.ContentType = MimeMapping.GetMimeMapping(".pdf"); context.Response.AddHeader("Content-Disposition", "inline; filename=\"" + pid + ".pdf\""); context.Response.AddHeader("Content-Length", pdf.Length.ToString()); for (int i = 0, count = 1024; i < pdf.Length; i += count) { context.Response.OutputStream.Write(pdf, i, Math.Min(count, pdf.Length - i)); } context.Response.Flush(); } catch (HttpException he) { context.Response.StatusCode = he.GetHttpCode(); context.Response.Write(HttpUtility.HtmlEncode(he.Message)); } catch (Exception error) { log.ErrorFormat("Url: {0} {1}", context.Request.Url, error); context.Response.StatusCode = 500; context.Response.Write(HttpUtility.HtmlEncode(error.Message)); } }
private static void TempFile(HttpContext context) { var fileName = context.Request[FilesLinkUtility.FileTitle]; var auth = context.Request[FilesLinkUtility.AuthKey]; var validateResult = EmailValidationKeyProvider.ValidateEmailKey(fileName, auth ?? "", Global.StreamUrlExpire); if (validateResult != EmailValidationKeyProvider.ValidationResult.Ok) { var exc = new HttpException((int)HttpStatusCode.Forbidden, FilesCommonResource.ErrorMassage_SecurityException); Global.Logger.Error(string.Format("{0} {1}: {2}", FilesLinkUtility.AuthKey, validateResult, context.Request.Url), exc); context.Response.StatusCode = (int)HttpStatusCode.Forbidden; context.Response.Write(FilesCommonResource.ErrorMassage_SecurityException); return; } context.Response.Clear(); context.Response.ContentType = MimeMapping.GetMimeMapping(fileName); context.Response.AddHeader("Content-Disposition", ContentDispositionUtil.GetHeaderValue(fileName)); var store = Global.GetStore(); var path = Path.Combine("temp_stream", fileName); if (!store.IsFile(FileConstant.StorageDomainTmp, path)) { context.Response.StatusCode = (int)HttpStatusCode.NotFound; context.Response.Write(FilesCommonResource.ErrorMassage_FileNotFound); return; } using (var readStream = store.GetReadStream(FileConstant.StorageDomainTmp, path)) { context.Response.AddHeader("Content-Length", readStream.Length.ToString()); readStream.StreamCopyTo(context.Response.OutputStream); } store.Delete(FileConstant.StorageDomainTmp, path); try { context.Response.Flush(); context.Response.End(); } catch (HttpException) { } }
private static void DownloadTry(HttpContext context) { FileType tryType; try { tryType = (FileType)Enum.Parse(typeof(FileType), context.Request[CommonLinkUtility.TryParam]); } catch { tryType = FileType.Document; } var title = string.IsNullOrEmpty(context.Request[CommonLinkUtility.FileTitle]) ? "Demo" : "new"; title += FileUtility.InternalExtension[tryType]; context.Response.Clear(); context.Response.ContentType = MimeMapping.GetMimeMapping(title); context.Response.Charset = "utf-8"; var browser = context.Request.Browser.Browser; var format = browser == "IE" || browser == "Safari" ? "{0}; filename=\"{1}\"" : "{0}; filename*=utf-8''{1}"; var filename = browser == "Safari" ? title : HttpUtility.UrlPathEncode(title); var contentDisposition = string.Format(format, "attachment", filename); context.Response.AddHeader("Content-Disposition", contentDisposition); //NOTE: always pass files through handler using (var readStream = Global.GetStoreTemplate().IronReadStream("", title, 10)) { context.Response.AddHeader("Content-Length", readStream.Length.ToString()); //BUG:Can be bugs readStream.StreamCopyTo(context.Response.OutputStream); } try { context.Response.Flush(); context.Response.End(); } catch (HttpException) { } }
private static void DownloadFile(HttpContext context) { var flushed = false; try { var id = context.Request[FilesLinkUtility.FileId]; var doc = context.Request[FilesLinkUtility.DocShareKey] ?? ""; using (var fileDao = Global.DaoFactory.GetFileDao()) { File file; var readLink = FileShareLink.Check(doc, true, fileDao, out file); if (!readLink && file == null) { fileDao.InvalidateCache(id); int version; file = int.TryParse(context.Request[FilesLinkUtility.Version], out version) && version > 0 ? fileDao.GetFile(id, version) : fileDao.GetFile(id); } if (file == null) { context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } if (!readLink && !Global.GetFilesSecurity().CanRead(file)) { context.Response.StatusCode = (int)HttpStatusCode.Forbidden; return; } if (!string.IsNullOrEmpty(file.Error)) { throw new Exception(file.Error); } if (!fileDao.IsExistOnStorage(file)) { Global.Logger.ErrorFormat("Download file error. File is not exist on storage. File id: {0}.", file.ID); context.Response.StatusCode = (int)HttpStatusCode.NotFound; return; } FileMarker.RemoveMarkAsNew(file); context.Response.Clear(); context.Response.ClearHeaders(); context.Response.Charset = "utf-8"; var title = file.Title.Replace(',', '_'); var ext = FileUtility.GetFileExtension(file.Title); var outType = context.Request[FilesLinkUtility.OutType]; if (!string.IsNullOrEmpty(outType)) { outType = outType.Trim(); if (FileUtility.ExtsConvertible[ext].Contains(outType)) { ext = outType; title = FileUtility.ReplaceFileExtension(title, ext); } } context.Response.AddHeader("Content-Disposition", ContentDispositionUtil.GetHeaderValue(title)); context.Response.ContentType = MimeMapping.GetMimeMapping(title); //// Download file via nginx //if (CoreContext.Configuration.Standalone && // WorkContext.IsMono && // Global.GetStore() is DiscDataStore && // !file.ProviderEntry && // !FileConverter.EnableConvert(file, ext) // ) //{ // var diskDataStore = (DiscDataStore)Global.GetStore(); // var pathToFile = diskDataStore.GetPhysicalPath(String.Empty, FileDao.GetUniqFilePath(file)); // context.Response.Headers.Add("X-Accel-Redirect", "/filesData" + pathToFile); // FilesMessageService.Send(file, context.Request, MessageAction.FileDownloaded, file.Title); // return; //} if (string.Equals(context.Request.Headers["If-None-Match"], GetEtag(file))) { //Its cached. Reply 304 context.Response.StatusCode = (int)HttpStatusCode.NotModified; context.Response.Cache.SetETag(GetEtag(file)); } else { context.Response.CacheControl = "public"; context.Response.Cache.SetETag(GetEtag(file)); context.Response.Cache.SetCacheability(HttpCacheability.Public); Stream fileStream = null; try { if (file.ContentLength <= SetupInfo.AvailableFileSize) { if (!FileConverter.EnableConvert(file, ext)) { if (!readLink && fileDao.IsSupportedPreSignedUri(file)) { context.Response.Redirect(fileDao.GetPreSignedUri(file, TimeSpan.FromHours(1)).ToString(), true); return; } fileStream = fileDao.GetFileStream(file); context.Response.AddHeader("Content-Length", file.ContentLength.ToString(CultureInfo.InvariantCulture)); } else { fileStream = FileConverter.Exec(file, ext); context.Response.AddHeader("Content-Length", fileStream.Length.ToString(CultureInfo.InvariantCulture)); } fileStream.StreamCopyTo(context.Response.OutputStream); if (!context.Response.IsClientConnected) { Global.Logger.Warn(String.Format("Download file error {0} {1} Connection is lost. Too long to buffer the file", file.Title, file.ID)); } FilesMessageService.Send(file, context.Request, MessageAction.FileDownloaded, file.Title); context.Response.Flush(); flushed = true; } else { context.Response.Buffer = false; context.Response.ContentType = "application/octet-stream"; long offset = 0; if (context.Request.Headers["Range"] != null) { context.Response.StatusCode = 206; var range = context.Request.Headers["Range"].Split(new[] { '=', '-' }); offset = Convert.ToInt64(range[1]); } if (offset > 0) { Global.Logger.Info("Starting file download offset is " + offset); } context.Response.AddHeader("Connection", "Keep-Alive"); context.Response.AddHeader("Accept-Ranges", "bytes"); if (offset > 0) { context.Response.AddHeader("Content-Range", String.Format(" bytes {0}-{1}/{2}", offset, file.ContentLength - 1, file.ContentLength)); } var dataToRead = file.ContentLength; const int bufferSize = 8 * 1024; // 8KB var buffer = new Byte[bufferSize]; if (!FileConverter.EnableConvert(file, ext)) { if (!readLink && fileDao.IsSupportedPreSignedUri(file)) { context.Response.Redirect(fileDao.GetPreSignedUri(file, TimeSpan.FromHours(1)).ToString(), true); return; } fileStream = fileDao.GetFileStream(file, offset); context.Response.AddHeader("Content-Length", (file.ContentLength - offset).ToString(CultureInfo.InvariantCulture)); } else { fileStream = FileConverter.Exec(file, ext); if (offset > 0) { var startBytes = offset; while (startBytes > 0) { long readCount; if (bufferSize >= startBytes) { readCount = startBytes; } else { readCount = bufferSize; } var length = fileStream.Read(buffer, 0, (int)readCount); startBytes -= length; } } } while (dataToRead > 0) { int length; try { length = fileStream.Read(buffer, 0, bufferSize); } catch (HttpException exception) { Global.Logger.Error( String.Format("Read from stream is error. Download file {0} {1}. Maybe Connection is lost.?? Error is {2} ", file.Title, file.ID, exception )); throw; } if (context.Response.IsClientConnected) { context.Response.OutputStream.Write(buffer, 0, length); context.Response.Flush(); flushed = true; dataToRead = dataToRead - length; } else { dataToRead = -1; Global.Logger.Warn(String.Format("IsClientConnected is false. Why? Download file {0} {1} Connection is lost. ", file.Title, file.ID)); } } } } catch (ThreadAbortException) { } catch (HttpException e) { throw new HttpException((int)HttpStatusCode.BadRequest, e.Message); } finally { if (fileStream != null) { fileStream.Close(); fileStream.Dispose(); } } try { context.Response.End(); flushed = true; } catch (HttpException) { } } } } catch (ThreadAbortException) { } catch (Exception ex) { // Get stack trace for the exception with source file information var st = new StackTrace(ex, true); // Get the top stack frame var frame = st.GetFrame(0); // Get the line number from the stack frame var line = frame.GetFileLineNumber(); Global.Logger.ErrorFormat("Url: {0} {1} IsClientConnected:{2}, line number:{3} frame:{4}", context.Request.Url, ex, context.Response.IsClientConnected, line, frame); if (!flushed && context.Response.IsClientConnected) { context.Response.StatusCode = 400; context.Response.Write(HttpUtility.HtmlEncode(ex.Message)); } } }
private static void DownloadFile(HttpContext context, bool inline) { if (!string.IsNullOrEmpty(context.Request[CommonLinkUtility.TryParam])) { DownloadTry(context); return; } try { var id = context.Request[CommonLinkUtility.FileId]; var shareLinkKey = context.Request[CommonLinkUtility.DocShareKey] ?? ""; using (var fileDao = Global.DaoFactory.GetFileDao()) { File file; var checkLink = FileShareLink.Check(shareLinkKey, true, fileDao, out file); if (!checkLink && file == null) { int version; file = int.TryParse(context.Request[CommonLinkUtility.Version], out version) && version > 0 ? fileDao.GetFile(id, version) : fileDao.GetFile(id); } if (file == null) { context.Response.Redirect("~/404.htm"); return; } if (!checkLink && !Global.GetFilesSecurity().CanRead(file)) { context.Response.Redirect((context.Request.UrlReferrer != null ? context.Request.UrlReferrer.ToString() : PathProvider.StartURL) + "#" + UrlConstant.Error + "/" + HttpUtility.UrlEncode(FilesCommonResource.ErrorMassage_SecurityException_ReadFile)); return; } if (!fileDao.IsExistOnStorage(file)) { Global.Logger.ErrorFormat("Download file error. File is not exist on storage. File id: {0}.", file.ID); context.Response.Redirect("~/404.htm"); return; } FileMarker.RemoveMarkAsNew(file); context.Response.Clear(); context.Response.ContentType = MimeMapping.GetMimeMapping(file.Title); context.Response.Charset = "utf-8"; var browser = context.Request.Browser.Browser; var title = file.Title.Replace(',', '_'); var ext = FileUtility.GetFileExtension(file.Title); var outType = string.Empty; var curQuota = TenantExtra.GetTenantQuota(); if (curQuota.DocsEdition || FileUtility.InternalExtension.Values.Contains(ext)) { outType = context.Request[CommonLinkUtility.OutType]; } if (!string.IsNullOrEmpty(outType) && !inline) { outType = outType.Trim(); if (FileUtility.ExtsConvertible[ext].Contains(outType)) { ext = outType; title = FileUtility.ReplaceFileExtension(title, ext); } } context.Response.AddHeader("Content-Disposition", ContentDispositionUtil.GetHeaderValue(title, inline)); if (inline && string.Equals(context.Request.Headers["If-None-Match"], GetEtag(file))) { //Its cached. Reply 304 context.Response.StatusCode = (int)HttpStatusCode.NotModified; context.Response.Cache.SetETag(GetEtag(file)); } else { context.Response.CacheControl = "public"; context.Response.Cache.SetETag(GetEtag(file)); context.Response.Cache.SetCacheability(HttpCacheability.Public); Stream fileStream = null; try { if (file.ContentLength <= SetupInfo.AvailableFileSize) { if (file.ConvertedType == null && (string.IsNullOrEmpty(outType) || inline)) { context.Response.AddHeader("Content-Length", file.ContentLength.ToString(CultureInfo.InvariantCulture)); if (fileDao.IsSupportedPreSignedUri(file)) { context.Response.Redirect(fileDao.GetPreSignedUri(file, TimeSpan.FromHours(1)).ToString(), true); return; } fileStream = fileDao.GetFileStream(file); } else { fileStream = FileConverter.Exec(file, ext); } fileStream.StreamCopyTo(context.Response.OutputStream); if (!context.Response.IsClientConnected) { Global.Logger.Error(String.Format("Download file error {0} {1} Connection is lost. Too long to buffer the file", file.Title, file.ID)); } context.Response.Flush(); } else { long offset = 0; if (context.Request.Headers["Range"] != null) { context.Response.StatusCode = 206; var range = context.Request.Headers["Range"].Split(new[] { '=', '-' }); offset = Convert.ToInt64(range[1]); } if (offset > 0) { Global.Logger.Info("Starting file download offset is " + offset); } context.Response.AddHeader("Connection", "Keep-Alive"); context.Response.AddHeader("Accept-Ranges", "bytes"); if (offset > 0) { context.Response.AddHeader("Content-Range", String.Format(" bytes {0}-{1}/{2}", offset, file.ContentLength - 1, file.ContentLength)); } var dataToRead = file.ContentLength; const int bufferSize = 1024; var buffer = new Byte[bufferSize]; if (file.ConvertedType == null && (string.IsNullOrEmpty(outType) || inline)) { if (fileDao.IsSupportedPreSignedUri(file)) { context.Response.Redirect(fileDao.GetPreSignedUri(file, TimeSpan.FromHours(1)).ToString(), true); return; } fileStream = fileDao.GetFileStream(file, offset); context.Response.AddHeader("Content-Length", (file.ContentLength - offset).ToString(CultureInfo.InvariantCulture)); } else { fileStream = FileConverter.Exec(file, ext); if (offset > 0) { var startBytes = offset; while (startBytes > 0) { long readCount; if (bufferSize >= startBytes) { readCount = startBytes; } else { readCount = bufferSize; } var length = fileStream.Read(buffer, 0, (int)readCount); startBytes -= length; } } } while (dataToRead > 0) { int length; try { length = fileStream.Read(buffer, 0, bufferSize); } catch (HttpException exception) { Global.Logger.Error( String.Format("Read from stream is error. Download file {0} {1}. Maybe Connection is lost.?? Error is {2} ", file.Title, file.ID, exception )); throw; } if (context.Response.IsClientConnected) { context.Response.OutputStream.Write(buffer, 0, length); dataToRead = dataToRead - length; } else { dataToRead = -1; Global.Logger.Error(String.Format("IsClientConnected is false. Why? Download file {0} {1} Connection is lost. ", file.Title, file.ID)); } } } } catch (HttpException e) { throw new HttpException((int)HttpStatusCode.BadRequest, e.Message); } finally { if (fileStream != null) { fileStream.Flush(); fileStream.Close(); fileStream.Dispose(); } } try { context.Response.End(); } catch (HttpException) { } } } } catch (Exception ex) { // Get stack trace for the exception with source file information var st = new StackTrace(ex, true); // Get the top stack frame var frame = st.GetFrame(0); // Get the line number from the stack frame var line = frame.GetFileLineNumber(); Global.Logger.ErrorFormat("Url: {0} {1} IsClientConnected:{2}, line number:{3} frame:{4}", context.Request.Url, ex, context.Response.IsClientConnected, line, frame); context.Response.StatusCode = 400; context.Response.Write(HttpUtility.HtmlEncode(ex.Message)); } }
internal static void ProcessRequestInternal(HttpContext context, string overrideVirtualPath) { HttpRequest request = context.Request; HttpResponse response = context.Response; string virtualPathWithPathInfo; string physicalPath; FileInfo fileInfo; long fileLength; DateTime lastModifiedInUtc; string etag; string rangeHeader; // custom virtual path providers that don't yeild a MapPathBasedVirtualFile // are a special case, and do not support TransmitFile, WriteFile, Range requests, // or the cache policy that we apply below if (ProcessRequestForNonMapPathBasedVirtualFile(request, response, overrideVirtualPath)) { return; } if (overrideVirtualPath == null) { virtualPathWithPathInfo = request.Path; physicalPath = request.PhysicalPath; } else { virtualPathWithPathInfo = overrideVirtualPath; physicalPath = request.MapPath(overrideVirtualPath); } Debug.Trace("StaticFileHandler", "Path= " + virtualPathWithPathInfo + ", PhysicalPath= " + physicalPath); fileInfo = GetFileInfo(virtualPathWithPathInfo, physicalPath, response); // Determine Last Modified Time. We might need it soon // if we encounter a Range: and If-Range header // Using UTC time to avoid daylight savings time bug 83230 lastModifiedInUtc = new DateTime(fileInfo.LastWriteTimeUtc.Year, fileInfo.LastWriteTimeUtc.Month, fileInfo.LastWriteTimeUtc.Day, fileInfo.LastWriteTimeUtc.Hour, fileInfo.LastWriteTimeUtc.Minute, fileInfo.LastWriteTimeUtc.Second, 0, DateTimeKind.Utc); // Because we can't set a "Last-Modified" header to any time // in the future, check the last modified time and set it to // DateTime.Now if it's in the future. // This is to fix VSWhidbey #402323 DateTime utcNow = DateTime.UtcNow; if (lastModifiedInUtc > utcNow) { // use 1 second resolution lastModifiedInUtc = new DateTime(utcNow.Ticks - (utcNow.Ticks % TimeSpan.TicksPerSecond), DateTimeKind.Utc); } etag = GenerateETag(context, lastModifiedInUtc, utcNow); fileLength = fileInfo.Length; // is this a Range request? rangeHeader = request.Headers["Range"]; if (StringUtil.StringStartsWithIgnoreCase(rangeHeader, "bytes") && ProcessRangeRequest(context, physicalPath, fileLength, rangeHeader, etag, lastModifiedInUtc)) { return; } // if we get this far, we're sending the entire file SendFile(physicalPath, 0, fileLength, fileLength, context); // Specify content type. Use extension to do the mapping response.ContentType = MimeMapping.GetMimeMapping(physicalPath); // Static file handler supports byte ranges response.AppendHeader("Accept-Ranges", "bytes"); // We want to flush cache entry when static file has changed response.AddFileDependency(physicalPath); // Set IgnoreRangeRequests to avoid serving Range requests from the output cache. // Note that the kernel cache always ignores Range requests. response.Cache.SetIgnoreRangeRequests(); // Set an expires in the future. response.Cache.SetExpires(utcNow.AddDays(1)); // always set Last-Modified response.Cache.SetLastModified(lastModifiedInUtc); // always set ETag response.Cache.SetETag(etag); // always set Cache-Control to public response.Cache.SetCacheability(HttpCacheability.Public); }
internal static bool ProcessRangeRequest(HttpContext context, string physicalPath, long fileLength, string rangeHeader, string etag, DateTime lastModified) { HttpRequest request = context.Request; HttpResponse response = context.Response; bool handled = false; // return "416 Requested range not satisfiable" if the file length is zero. if (fileLength <= 0) { SendRangeNotSatisfiable(response, fileLength); handled = true; return(handled); } string ifRangeHeader = request.Headers["If-Range"]; if (ifRangeHeader != null && ifRangeHeader.Length > 1) { // Is this an ETag or a Date? We only need to check two // characters; an ETag either begins with W/ or it is quoted. if (ifRangeHeader[0] == '"') { // it's a strong ETag if (ifRangeHeader != etag) { // the etags do not match, and we will therefore return the entire response return(handled); } } else if (ifRangeHeader[0] == 'W' && ifRangeHeader[1] == '/') { // it's a weak ETag, and is therefore not usable for sub-range retrieval and // we will return the entire response return(handled); } else { // It's a date. If it is greater than or equal to the last-write time of the file, we can send the range. if (IsOutDated(ifRangeHeader, lastModified)) { return(handled); } } } // the expected format is "bytes = <range1>[, <range2>, ...]" // where <range> is "<first_byte_pos>-[<last_byte_pos>]" or "-<last_n_bytes>". int indexOfEquals = rangeHeader.IndexOf('='); if (indexOfEquals == -1 || indexOfEquals == rangeHeader.Length - 1) { //invalid syntax return(handled); } // iterate through the byte ranges and write each satisfiable range to the response int startIndex = indexOfEquals + 1; bool isRangeHeaderSyntacticallyValid = true; long offset; long length; bool isSatisfiable; bool exceededMax = false; ByteRange[] byteRanges = null; int byteRangesCount = 0; long totalBytes = 0; while (startIndex < rangeHeader.Length && isRangeHeaderSyntacticallyValid) { isRangeHeaderSyntacticallyValid = GetNextRange(rangeHeader, ref startIndex, fileLength, out offset, out length, out isSatisfiable); if (!isRangeHeaderSyntacticallyValid) { break; } if (!isSatisfiable) { continue; } if (byteRanges == null) { byteRanges = new ByteRange[16]; } if (byteRangesCount >= byteRanges.Length) { // grow byteRanges array ByteRange[] buffer = new ByteRange[byteRanges.Length * 2]; int byteCount = byteRanges.Length * Marshal.SizeOf(byteRanges[0]); unsafe { fixed(ByteRange *src = byteRanges, dst = buffer) { StringUtil.memcpyimpl((byte *)src, (byte *)dst, byteCount); } } byteRanges = buffer; } byteRanges[byteRangesCount].Offset = offset; byteRanges[byteRangesCount].Length = length; byteRangesCount++; // IIS imposes this limitation too, and sends "400 Bad Request" if exceeded totalBytes += length; if (totalBytes > fileLength * MAX_RANGE_ALLOWED) { exceededMax = true; break; } } if (!isRangeHeaderSyntacticallyValid) { return(handled); } if (exceededMax) { SendBadRequest(response); handled = true; return(handled); } if (byteRangesCount == 0) { // we parsed the Range header and found no satisfiable byte ranges, so return "416 Requested Range Not Satisfiable" SendRangeNotSatisfiable(response, fileLength); handled = true; return(handled); } string contentType = MimeMapping.GetMimeMapping(physicalPath); if (byteRangesCount == 1) { offset = byteRanges[0].Offset; length = byteRanges[0].Length; response.ContentType = contentType; string contentRange = String.Format(CultureInfo.InvariantCulture, CONTENT_RANGE_FORMAT, offset, offset + length - 1, fileLength); response.AppendHeader("Content-Range", contentRange); SendFile(physicalPath, offset, length, fileLength, context); } else { response.ContentType = MULTIPART_CONTENT_TYPE; string contentRange; string partialContentType = "Content-Type: " + contentType + "\r\n"; for (int i = 0; i < byteRangesCount; i++) { offset = byteRanges[i].Offset; length = byteRanges[i].Length; response.Write(MULTIPART_RANGE_DELIMITER); response.Write(partialContentType); response.Write("Content-Range: "); contentRange = String.Format(CultureInfo.InvariantCulture, CONTENT_RANGE_FORMAT, offset, offset + length - 1, fileLength); response.Write(contentRange); response.Write("\r\n\r\n"); SendFile(physicalPath, offset, length, fileLength, context); response.Write("\r\n"); } response.Write(MULTIPART_RANGE_END); } // if we make it here, we're sending a "206 Partial Content" status response.StatusCode = 206; response.AppendHeader("Last-Modified", HttpUtility.FormatHttpDateTime(lastModified)); response.AppendHeader("Accept-Ranges", "bytes"); response.AppendHeader("ETag", etag); response.AppendHeader("Cache-Control", "public"); handled = true; return(handled); }