// ----------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // ----------------------------------------------------------------------- namespace Microsoft.Samples.Kinect.Webserver { using System; using System.Diagnostics; using System.IO; using System.Net; using System.Threading.Tasks; using System.Web; /// /// Implementation of IHttpRequestHandler used to serve static file content. /// public class FileRequestHandler : IHttpRequestHandler { /// /// Root directory in server's file system from which we're serving files. /// private readonly DirectoryInfo rootDirectory; /// /// Origin used as a helper for URI parsing and canonicalization. /// private readonly Uri parseHelperOrigin = new Uri("http://a"); /// /// Initializes a new instance of the class. /// /// /// Root directory in server's file system from which files should be served. /// internal FileRequestHandler(DirectoryInfo rootDirectory) { if (rootDirectory == null) { throw new ArgumentNullException("rootDirectory"); } // Re-create directory name to ensure equivalence between directory names // that end in "\" character and directory names that are identical except // that they don't end in "\" character. var normalizedDirectoryName = rootDirectory.Parent != null ? Path.Combine(rootDirectory.Parent.FullName, rootDirectory.Name) : rootDirectory.Name; this.rootDirectory = new DirectoryInfo(normalizedDirectoryName); } /// /// Prepares handler to start receiving HTTP requests. /// /// /// Await-able task. /// /// /// Return value should never be null. Implementations should use Task.FromResult(0) /// if function is implemented synchronously so that callers can await without /// needing to check for null. /// public Task InitializeAsync() { return SharedConstants.EmptyCompletedTask; } /// /// Handle an http request. /// /// /// Context containing HTTP request data, which will also contain associated /// response upon return. /// /// /// Request URI path relative to the URI prefix associated with this request /// handler in the HttpListener. /// /// /// Await-able task. /// /// /// Return value should never be null. Implementations should use Task.FromResult(0) /// if function is implemented synchronously so that callers can await without /// needing to check for null. /// public async Task HandleRequestAsync(HttpListenerContext requestContext, string subpath) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } if (subpath == null) { throw new ArgumentNullException("subpath"); } try { var statusCode = await this.ServeFileAsync(requestContext.Response, subpath); CloseResponse(requestContext, statusCode); } catch (HttpListenerException e) { Trace.TraceWarning( "Problem encountered while serving file at subpath {0}. Client might have aborted request. Cause: \"{1}\"", subpath, e.Message); } } /// /// Cancel all pending operations. /// public void Cancel() { } /// /// Lets handler know that no more HTTP requests will be received, so that it can /// clean up resources associated with request handling. /// /// /// Await-able task. /// /// /// Return value should never be null. Implementations should use Task.FromResult(0) /// if function is implemented synchronously so that callers can await without /// needing to check for null. /// public Task UninitializeAsync() { return SharedConstants.EmptyCompletedTask; } /// /// Close response stream and associate a status code with response. /// /// /// Context whose response we should close. /// /// /// Status code. /// internal static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode) { context.Response.StatusCode = (int)statusCode; context.Response.Close(); } /// /// Determine if the specified directory is an ancestor of the specified file. /// /// /// Information for potential ancestor directory. /// /// /// File for which to determine directory ancestry. /// /// /// True if is an ancestor of . /// internal static bool IsAncestorDirectory(DirectoryInfo ancestorDirectory, FileInfo file) { var parentDirectory = file.Directory; while (parentDirectory != null) { if (string.Compare(parentDirectory.FullName, ancestorDirectory.FullName, StringComparison.OrdinalIgnoreCase) == 0) { return true; } parentDirectory = parentDirectory.Parent; } return false; } /// /// Serve the file corresponding to the specified URI path. /// /// /// HTTP response where file will be streamed, on success. /// /// /// Request URI path relative to the URI prefix associated with this request /// handler in the HttpListener. /// /// /// Await-able task holding an HTTP status code that should be part of response. /// private async Task ServeFileAsync(HttpListenerResponse response, string subpath) { Uri uri; try { uri = new Uri(this.parseHelperOrigin, subpath); } catch (UriFormatException) { return HttpStatusCode.Forbidden; } var filePath = Path.Combine(this.rootDirectory.FullName, uri.AbsolutePath.Substring(1)); var fileInfo = new FileInfo(filePath); if (!IsAncestorDirectory(this.rootDirectory, fileInfo)) { return HttpStatusCode.Forbidden; } if (!fileInfo.Exists) { return HttpStatusCode.NotFound; } response.ContentType = MimeMapping.GetMimeMapping(fileInfo.FullName); using (var fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) { await fileStream.CopyToAsync(response.OutputStream); } return HttpStatusCode.OK; } } }