source: other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/FileRequestHandler.cs@ 28897

Last change on this file since 28897 was 28897, checked in by davidb, 10 years ago

GUI front-end to server base plus web page content

File size: 8.3 KB
Line 
1// -----------------------------------------------------------------------
2// <copyright file="FileRequestHandler.cs" company="Microsoft">
3// Copyright (c) Microsoft Corporation. All rights reserved.
4// </copyright>
5// -----------------------------------------------------------------------
6
7namespace Microsoft.Samples.Kinect.Webserver
8{
9 using System;
10 using System.Diagnostics;
11 using System.IO;
12 using System.Net;
13 using System.Threading.Tasks;
14 using System.Web;
15
16 /// <summary>
17 /// Implementation of IHttpRequestHandler used to serve static file content.
18 /// </summary>
19 public class FileRequestHandler : IHttpRequestHandler
20 {
21 /// <summary>
22 /// Root directory in server's file system from which we're serving files.
23 /// </summary>
24 private readonly DirectoryInfo rootDirectory;
25
26 /// <summary>
27 /// Origin used as a helper for URI parsing and canonicalization.
28 /// </summary>
29 private readonly Uri parseHelperOrigin = new Uri("http://a");
30
31 /// <summary>
32 /// Initializes a new instance of the <see cref="FileRequestHandler"/> class.
33 /// </summary>
34 /// <param name="rootDirectory">
35 /// Root directory in server's file system from which files should be served.
36 /// </param>
37 internal FileRequestHandler(DirectoryInfo rootDirectory)
38 {
39 if (rootDirectory == null)
40 {
41 throw new ArgumentNullException("rootDirectory");
42 }
43
44 // Re-create directory name to ensure equivalence between directory names
45 // that end in "\" character and directory names that are identical except
46 // that they don't end in "\" character.
47 var normalizedDirectoryName = rootDirectory.Parent != null
48 ? Path.Combine(rootDirectory.Parent.FullName, rootDirectory.Name)
49 : rootDirectory.Name;
50 this.rootDirectory = new DirectoryInfo(normalizedDirectoryName);
51 }
52
53 /// <summary>
54 /// Prepares handler to start receiving HTTP requests.
55 /// </summary>
56 /// <returns>
57 /// Await-able task.
58 /// </returns>
59 /// <remarks>
60 /// Return value should never be null. Implementations should use Task.FromResult(0)
61 /// if function is implemented synchronously so that callers can await without
62 /// needing to check for null.
63 /// </remarks>
64 public Task InitializeAsync()
65 {
66 return SharedConstants.EmptyCompletedTask;
67 }
68
69 /// <summary>
70 /// Handle an http request.
71 /// </summary>
72 /// <param name="requestContext">
73 /// Context containing HTTP request data, which will also contain associated
74 /// response upon return.
75 /// </param>
76 /// <param name="subpath">
77 /// Request URI path relative to the URI prefix associated with this request
78 /// handler in the HttpListener.
79 /// </param>
80 /// <returns>
81 /// Await-able task.
82 /// </returns>
83 /// <remarks>
84 /// Return value should never be null. Implementations should use Task.FromResult(0)
85 /// if function is implemented synchronously so that callers can await without
86 /// needing to check for null.
87 /// </remarks>
88 public async Task HandleRequestAsync(HttpListenerContext requestContext, string subpath)
89 {
90 if (requestContext == null)
91 {
92 throw new ArgumentNullException("requestContext");
93 }
94
95 if (subpath == null)
96 {
97 throw new ArgumentNullException("subpath");
98 }
99
100 try
101 {
102 var statusCode = await this.ServeFileAsync(requestContext.Response, subpath);
103 CloseResponse(requestContext, statusCode);
104 }
105 catch (HttpListenerException e)
106 {
107 Trace.TraceWarning(
108 "Problem encountered while serving file at subpath {0}. Client might have aborted request. Cause: \"{1}\"", subpath, e.Message);
109 }
110 }
111
112 /// <summary>
113 /// Cancel all pending operations.
114 /// </summary>
115 public void Cancel()
116 {
117 }
118
119 /// <summary>
120 /// Lets handler know that no more HTTP requests will be received, so that it can
121 /// clean up resources associated with request handling.
122 /// </summary>
123 /// <returns>
124 /// Await-able task.
125 /// </returns>
126 /// <remarks>
127 /// Return value should never be null. Implementations should use Task.FromResult(0)
128 /// if function is implemented synchronously so that callers can await without
129 /// needing to check for null.
130 /// </remarks>
131 public Task UninitializeAsync()
132 {
133 return SharedConstants.EmptyCompletedTask;
134 }
135
136 /// <summary>
137 /// Close response stream and associate a status code with response.
138 /// </summary>
139 /// <param name="context">
140 /// Context whose response we should close.
141 /// </param>
142 /// <param name="statusCode">
143 /// Status code.
144 /// </param>
145 internal static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
146 {
147 context.Response.StatusCode = (int)statusCode;
148 context.Response.Close();
149 }
150
151 /// <summary>
152 /// Determine if the specified directory is an ancestor of the specified file.
153 /// </summary>
154 /// <param name="ancestorDirectory">
155 /// Information for potential ancestor directory.
156 /// </param>
157 /// <param name="file">
158 /// File for which to determine directory ancestry.
159 /// </param>
160 /// <returns>
161 /// True if <paramref name="ancestorDirectory"/> is an ancestor of <paramref name="file"/>.
162 /// </returns>
163 internal static bool IsAncestorDirectory(DirectoryInfo ancestorDirectory, FileInfo file)
164 {
165 var parentDirectory = file.Directory;
166
167 while (parentDirectory != null)
168 {
169 if (string.Compare(parentDirectory.FullName, ancestorDirectory.FullName, StringComparison.OrdinalIgnoreCase) == 0)
170 {
171 return true;
172 }
173
174 parentDirectory = parentDirectory.Parent;
175 }
176
177 return false;
178 }
179
180 /// <summary>
181 /// Serve the file corresponding to the specified URI path.
182 /// </summary>
183 /// <param name="response">
184 /// HTTP response where file will be streamed, on success.
185 /// </param>
186 /// <param name="subpath">
187 /// Request URI path relative to the URI prefix associated with this request
188 /// handler in the HttpListener.
189 /// </param>
190 /// <returns>
191 /// Await-able task holding an HTTP status code that should be part of response.
192 /// </returns>
193 private async Task<HttpStatusCode> ServeFileAsync(HttpListenerResponse response, string subpath)
194 {
195 Uri uri;
196
197 try
198 {
199 uri = new Uri(this.parseHelperOrigin, subpath);
200 }
201 catch (UriFormatException)
202 {
203 return HttpStatusCode.Forbidden;
204 }
205
206 var filePath = Path.Combine(this.rootDirectory.FullName, uri.AbsolutePath.Substring(1));
207 var fileInfo = new FileInfo(filePath);
208
209 if (!IsAncestorDirectory(this.rootDirectory, fileInfo))
210 {
211 return HttpStatusCode.Forbidden;
212 }
213
214 if (!fileInfo.Exists)
215 {
216 return HttpStatusCode.NotFound;
217 }
218
219 response.ContentType = MimeMapping.GetMimeMapping(fileInfo.FullName);
220
221 using (var fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
222 {
223 await fileStream.CopyToAsync(response.OutputStream);
224 }
225
226 return HttpStatusCode.OK;
227 }
228 }
229}
Note: See TracBrowser for help on using the repository browser.