using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using MediaBrowser.Common.Logging; namespace MediaBrowser.Common.Net.Handlers { public class StaticFileHandler : BaseHandler { private string _Path; public virtual string Path { get { if (!string.IsNullOrWhiteSpace(_Path)) { return _Path; } return QueryString["path"]; } set { _Path = value; } } private bool _SourceStreamEnsured = false; private Stream _SourceStream = null; private Stream SourceStream { get { EnsureSourceStream(); return _SourceStream; } } private void EnsureSourceStream() { if (!_SourceStreamEnsured) { try { _SourceStream = File.OpenRead(Path); } catch (FileNotFoundException ex) { StatusCode = 404; Logger.LogException(ex); } catch (DirectoryNotFoundException ex) { StatusCode = 404; Logger.LogException(ex); } catch (UnauthorizedAccessException ex) { StatusCode = 403; Logger.LogException(ex); } finally { _SourceStreamEnsured = true; } } } protected override bool SupportsByteRangeRequests { get { return true; } } public override bool ShouldCompressResponse(string contentType) { // Can't compress these if (IsRangeRequest) { return false; } // Don't compress media if (contentType.StartsWith("audio/", StringComparison.OrdinalIgnoreCase) || contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase)) { return false; } // It will take some work to support compression within this handler return false; } protected override long? GetTotalContentLength() { return SourceStream.Length; } protected override Task GetLastDateModified() { DateTime? value = null; EnsureSourceStream(); if (SourceStream != null) { value = File.GetLastWriteTimeUtc(Path); } return Task.FromResult(value); } public override Task GetContentType() { return Task.FromResult(MimeTypes.GetMimeType(Path)); } protected override Task PrepareResponse() { EnsureSourceStream(); return Task.FromResult(null); } protected override Task WriteResponseToOutputStream(Stream stream) { if (IsRangeRequest) { KeyValuePair requestedRange = RequestedRanges.First(); // If the requested range is "0-" and we know the total length, we can optimize by avoiding having to buffer the content into memory if (requestedRange.Value == null && TotalContentLength != null) { return ServeCompleteRangeRequest(requestedRange, stream); } else if (TotalContentLength.HasValue) { // This will have to buffer a portion of the content into memory return ServePartialRangeRequestWithKnownTotalContentLength(requestedRange, stream); } else { // This will have to buffer the entire content into memory return ServePartialRangeRequestWithUnknownTotalContentLength(requestedRange, stream); } } else { return SourceStream.CopyToAsync(stream); } } protected override void DisposeResponseStream() { base.DisposeResponseStream(); if (SourceStream != null) { SourceStream.Dispose(); } } /// /// Handles a range request of "bytes=0-" /// This will serve the complete content and add the content-range header /// private Task ServeCompleteRangeRequest(KeyValuePair requestedRange, Stream responseStream) { long totalContentLength = TotalContentLength.Value; long rangeStart = requestedRange.Key; long rangeEnd = totalContentLength - 1; long rangeLength = 1 + rangeEnd - rangeStart; // Content-Length is the length of what we're serving, not the original content HttpListenerContext.Response.ContentLength64 = rangeLength; HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); if (rangeStart > 0) { SourceStream.Position = rangeStart; } return SourceStream.CopyToAsync(responseStream); } /// /// Serves a partial range request where the total content length is not known /// private async Task ServePartialRangeRequestWithUnknownTotalContentLength(KeyValuePair requestedRange, Stream responseStream) { // Read the entire stream so that we can determine the length byte[] bytes = await ReadBytes(SourceStream, 0, null).ConfigureAwait(false); long totalContentLength = bytes.LongLength; long rangeStart = requestedRange.Key; long rangeEnd = requestedRange.Value ?? (totalContentLength - 1); long rangeLength = 1 + rangeEnd - rangeStart; // Content-Length is the length of what we're serving, not the original content HttpListenerContext.Response.ContentLength64 = rangeLength; HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); await responseStream.WriteAsync(bytes, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength)).ConfigureAwait(false); } /// /// Serves a partial range request where the total content length is already known /// private async Task ServePartialRangeRequestWithKnownTotalContentLength(KeyValuePair requestedRange, Stream responseStream) { long totalContentLength = TotalContentLength.Value; long rangeStart = requestedRange.Key; long rangeEnd = requestedRange.Value ?? (totalContentLength - 1); long rangeLength = 1 + rangeEnd - rangeStart; // Only read the bytes we need byte[] bytes = await ReadBytes(SourceStream, Convert.ToInt32(rangeStart), Convert.ToInt32(rangeLength)).ConfigureAwait(false); // Content-Length is the length of what we're serving, not the original content HttpListenerContext.Response.ContentLength64 = rangeLength; HttpListenerContext.Response.Headers["Content-Range"] = string.Format("bytes {0}-{1}/{2}", rangeStart, rangeEnd, totalContentLength); await responseStream.WriteAsync(bytes, 0, Convert.ToInt32(rangeLength)).ConfigureAwait(false); } /// /// Reads bytes from a stream /// /// The input stream /// The starting position /// The number of bytes to read, or null to read to the end. private async Task ReadBytes(Stream input, int start, int? count) { if (start > 0) { input.Position = start; } if (count == null) { byte[] buffer = new byte[16 * 1024]; using (MemoryStream ms = new MemoryStream()) { int read; while ((read = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false)) > 0) { await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false); } return ms.ToArray(); } } else { byte[] buffer = new byte[count.Value]; using (MemoryStream ms = new MemoryStream()) { int read = await input.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false); await ms.WriteAsync(buffer, 0, read).ConfigureAwait(false); return ms.ToArray(); } } } } }