internal static void ProcessRequestInternal(HttpContext context, string pathOverride) { 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); * } */ if (pathOverride != null) { physicalPath = pathOverride; } else { physicalPath = context.Request.PhysicalPath; } // Debug.Trace("StaticFileHandler", "Path= " + virtualPathWithPathInfo + ", PhysicalPath= " + physicalPath); try { fileInfo = GetFileInfo(physicalPath); } catch (HttpException hex) { int httpCode = hex.GetHttpCode(); string httpError = String.Format("{0} ({1})", hex.Message, physicalPath); throw new HttpException(httpCode, httpError, hex); } // 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(lastModifiedInUtc, utcNow); fileLength = fileInfo.Length; // is this a Range request? rangeHeader = request.Headers["Range"]; if (rangeHeader != null && rangeHeader.StartsWith("bytes", StringComparison.OrdinalIgnoreCase) && 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.GetMapping(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); /* * NOTE: This was the code originally in the handler: * // Set an expires in the future. * response.Cache.SetExpires(utcNow.AddMinutes(5)); * // 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.ServerAndPrivate); */ // set cache headers HttpCachePolicy cachePolicy = response.Cache; SetCachingPolicy(cachePolicy); // set cache file info cachePolicy.SetETag(etag); cachePolicy.SetLastModified(lastModifiedInUtc); }
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]); Buffer.BlockCopy(byteRanges, 0, buffer, 0, byteCount); /*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.GetMapping(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", FormatHttpDateTime(lastModified)); response.AppendHeader("Accept-Ranges", "bytes"); response.AppendHeader("ETag", etag); response.AppendHeader("Cache-Control", "private"); handled = true; return(handled); }