private bool HandleGet(HttpListenerContext context, WebServer server, bool sendBuffer = true) { var urlPath = context.Request.Url.LocalPath.Replace('/', Path.DirectorySeparatorChar); // adjust the path to see if we've got a default document if (urlPath.Last() == Path.DirectorySeparatorChar) urlPath = urlPath + DefaultDocument; urlPath = urlPath.TrimStart(new char[] {Path.DirectorySeparatorChar}); var localPath = Path.Combine(FileSystemPath, urlPath); var eTagValid = false; byte[] buffer = null; var fileDate = DateTime.Today; var partialHeader = context.RequestHeader(Constants.HeaderRange); var usingPartial = String.IsNullOrWhiteSpace(partialHeader) == false && partialHeader.StartsWith("bytes="); if (string.IsNullOrWhiteSpace(DefaultExtension) == false && DefaultExtension.StartsWith(".") && File.Exists(localPath) == false) { var newPath = localPath + DefaultExtension; if (File.Exists(newPath)) localPath = newPath; } if (File.Exists(localPath) == false) return false; if (usingPartial == false) { fileDate = File.GetLastWriteTime(localPath); var requestHash = context.RequestHeader(Constants.HeaderIfNotMatch); if (RamCache.ContainsKey(localPath) && RamCache[localPath].LastModified == fileDate) { server.Log.DebugFormat("RAM Cache: {0}", localPath); var currentHash = Extensions.ComputeMd5Hash(RamCache[localPath].Buffer) + '-' + fileDate.Ticks; if (String.IsNullOrWhiteSpace(requestHash) || requestHash != currentHash) { buffer = RamCache[localPath].Buffer; context.Response.AddHeader(Constants.HeaderETag, currentHash); } else { eTagValid = true; } } else { server.Log.DebugFormat("File System: {0}", localPath); if (sendBuffer) { buffer = File.ReadAllBytes(localPath); var currentHash = Extensions.ComputeMd5Hash(buffer) + '-' + fileDate.Ticks; if (String.IsNullOrWhiteSpace(requestHash) || requestHash != currentHash) { if (UseRamCache && buffer.Length <= MaxRamCacheFileSize) { RamCache[localPath] = new RamCacheEntry() {LastModified = fileDate, Buffer = buffer}; } context.Response.AddHeader(Constants.HeaderETag, currentHash); } else { eTagValid = true; } } } } // check to see if the file was modified or etag is the same var utcFileDateString = fileDate.ToUniversalTime() .ToString(Constants.BrowserTimeFormat, CultureInfo.InvariantCulture); if (usingPartial == false && (eTagValid || context.RequestHeader(Constants.HeaderIfModifiedSince).Equals(utcFileDateString))) { context.Response.AddHeader(Constants.HeaderCacheControl, "private"); context.Response.AddHeader(Constants.HeaderPragma, string.Empty); context.Response.AddHeader(Constants.HeaderExpires, string.Empty); context.Response.ContentType = string.Empty; context.Response.StatusCode = 304; } else { var extension = Path.GetExtension(localPath).ToLowerInvariant(); if (MimeTypes.ContainsKey(extension)) context.Response.ContentType = MimeTypes[extension]; context.Response.AddHeader(Constants.HeaderCacheControl, "private"); context.Response.AddHeader(Constants.HeaderPragma, string.Empty); context.Response.AddHeader(Constants.HeaderExpires, string.Empty); context.Response.AddHeader(Constants.HeaderLastModified, utcFileDateString); context.Response.AddHeader(Constants.HeaderAcceptRanges, "bytes"); if (sendBuffer) { var lrange = 0; var urange = 0; var size = (long) 0; var isPartial = false; var fileSize = new FileInfo(localPath).Length; if (usingPartial) { var range = partialHeader.Replace("bytes=", "").Split('-'); if (range.Length == 2 && int.TryParse(range[0], out lrange) && int.TryParse(range[1], out urange)) { isPartial = true; } if ((range.Length == 2 && int.TryParse(range[0], out lrange) && string.IsNullOrWhiteSpace(range[1])) || (range.Length == 1 && int.TryParse(range[0], out lrange))) { urange = (int) fileSize - 1; isPartial = true; } if (range.Length == 2 && string.IsNullOrWhiteSpace(range[0]) && int.TryParse(range[1], out urange)) { lrange = (int) fileSize - urange; urange = (int)fileSize - 1; isPartial = true; } } if (isPartial) { if (urange > fileSize) { context.Response.StatusCode = 416; context.Response.AddHeader(Constants.HeaderContentRanges, string.Format("bytes */{0}", fileSize)); return true; } size = (urange - lrange) + 1; context.Response.AddHeader(Constants.HeaderContentRanges, string.Format("bytes {0}-{1}/{2}", lrange, urange, fileSize)); context.Response.StatusCode = 206; server.Log.DebugFormat("Opening stream {0} bytes {1}-{2} size {3}", localPath, lrange, urange, size); buffer = new byte[size]; // Open FileStream with FileShare using (var fs = new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (lrange + size > fs.Length) size = fs.Length - lrange; fs.Seek(lrange, SeekOrigin.Begin); fs.Read(buffer, 0, (int) size); fs.Close(); } // Reset lower range lrange = 0; } else { size = buffer.LongLength; // Perform compression if available if (context.RequestHeader(Constants.HeaderAcceptEncoding).Contains("gzip")) { buffer = buffer.Compress(); context.Response.AddHeader(Constants.HeaderContentEncoding, "gzip"); size = buffer.LongLength; lrange = 0; } } context.Response.ContentLength64 = size; try { context.Response.OutputStream.Write(buffer, lrange, (int) size); } catch (HttpListenerException) { // Connection error, nothing else to do } } else { context.Response.ContentLength64 = buffer == null ? new FileInfo(localPath).Length : buffer.LongLength; } } return true; }
private bool HandleGet(HttpListenerContext context, WebServer server, bool sendBuffer = true) { var rootFs = FileSystemPath; var urlPath = GetUrlPath(context, ref rootFs); var localPath = Path.Combine(rootFs, urlPath); var eTagValid = false; Stream buffer = null; var partialHeader = context.RequestHeader(Constants.HeaderRange); var usingPartial = string.IsNullOrWhiteSpace(partialHeader) == false && partialHeader.StartsWith("bytes="); if (ExistsLocalPath(urlPath, ref localPath) == false) return false; var fileDate = File.GetLastWriteTime(localPath); var requestHash = context.RequestHeader(Constants.HeaderIfNotMatch); if (RamCache.ContainsKey(localPath) && RamCache[localPath].LastModified == fileDate) { #if COMPAT server.Log.DebugFormat("RAM Cache: {0}", localPath); #else $"RAM Cache: {localPath}".Debug(); #endif var currentHash = RamCache[localPath].Buffer.ComputeMD5().ToUpperHex() + '-' + fileDate.Ticks; if (string.IsNullOrWhiteSpace(requestHash) || requestHash != currentHash) { buffer = new MemoryStream(RamCache[localPath].Buffer); context.Response.AddHeader(Constants.HeaderETag, currentHash); } else { eTagValid = true; } } else { #if COMPAT server.Log.DebugFormat("File System: {0}", localPath); #else $"File System: {localPath}".Debug(); #endif if (sendBuffer) { buffer = new FileStream(localPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); if (usingPartial == false) { eTagValid = UpdateFileCache(context, buffer, fileDate, requestHash, localPath); } } } // check to see if the file was modified or e-tag is the same var utcFileDateString = fileDate.ToUniversalTime() .ToString(Constants.BrowserTimeFormat, Constants.StandardCultureInfo); if (usingPartial == false && (eTagValid || context.RequestHeader(Constants.HeaderIfModifiedSince).Equals(utcFileDateString))) { SetStatusCode304(context); return true; } SetHeaders(context, localPath, utcFileDateString); var fileSize = new FileInfo(localPath).Length; if (sendBuffer == false) { context.Response.ContentLength64 = buffer?.Length ?? fileSize; return true; } // If buffer is null something is really wrong if (buffer == null) return false; var lowerByteIndex = 0; var upperByteIndex = 0; long byteLength; var isPartial = usingPartial && CalculateRange(partialHeader, fileSize, out lowerByteIndex, out upperByteIndex); if (isPartial) { if (upperByteIndex > fileSize) { context.Response.StatusCode = 416; context.Response.AddHeader(Constants.HeaderContentRanges, $"bytes */{fileSize}"); return true; } if (upperByteIndex == fileSize) { byteLength = buffer.Length; } else { byteLength = upperByteIndex - lowerByteIndex + 1; context.Response.AddHeader(Constants.HeaderContentRanges, $"bytes {lowerByteIndex}-{upperByteIndex}/{fileSize}"); context.Response.StatusCode = 206; #if COMPAT server.Log.DebugFormat("Opening stream {0} bytes {1}-{2} size {3}", localPath, lowerByteIndex, upperByteIndex, byteLength); #else $"Opening stream {localPath} bytes {lowerByteIndex}-{upperByteIndex} size {byteLength}".Debug(); #endif } } else { if (UseGzip && context.RequestHeader(Constants.HeaderAcceptEncoding).Contains(Constants.HeaderCompressionGzip) && buffer.Length < MaxGzipInputLength && // Ignore audio/video from compression context.Response.ContentType?.StartsWith("audio") == false && context.Response.ContentType?.StartsWith("video") == false) { // Perform compression if available buffer = buffer.Compress(); context.Response.AddHeader(Constants.HeaderContentEncoding, Constants.HeaderCompressionGzip); lowerByteIndex = 0; } byteLength = buffer.Length; } context.Response.ContentLength64 = byteLength; try { WriteToOutputStream(context, byteLength, buffer, lowerByteIndex); } catch (HttpListenerException) { // Connection error, nothing else to do } finally { #if !NETCOREAPP1_1 && !NETSTANDARD1_6 buffer.Close(); #endif buffer.Dispose(); } return true; }