BaseHandler.cs 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. using MediaBrowser.Common.Extensions;
  2. using MediaBrowser.Common.Kernel;
  3. using MediaBrowser.Common.Logging;
  4. using MediaBrowser.Model.Logging;
  5. using System;
  6. using System.Collections;
  7. using System.Collections.Generic;
  8. using System.Collections.Specialized;
  9. using System.Globalization;
  10. using System.IO;
  11. using System.IO.Compression;
  12. using System.Linq;
  13. using System.Net;
  14. using System.Text;
  15. using System.Threading.Tasks;
  16. using System.Web;
  17. namespace MediaBrowser.Common.Net.Handlers
  18. {
  19. /// <summary>
  20. /// Class BaseHandler
  21. /// </summary>
  22. public abstract class BaseHandler<TKernelType> : IHttpServerHandler
  23. where TKernelType : IKernel
  24. {
  25. /// <summary>
  26. /// Gets the logger.
  27. /// </summary>
  28. /// <value>The logger.</value>
  29. protected ILogger Logger { get; private set; }
  30. /// <summary>
  31. /// Initializes the specified kernel.
  32. /// </summary>
  33. /// <param name="kernel">The kernel.</param>
  34. public void Initialize(IKernel kernel)
  35. {
  36. Kernel = (TKernelType)kernel;
  37. Logger = SharedLogger.Logger;
  38. }
  39. /// <summary>
  40. /// Gets or sets the kernel.
  41. /// </summary>
  42. /// <value>The kernel.</value>
  43. protected TKernelType Kernel { get; private set; }
  44. /// <summary>
  45. /// Gets the URL suffix used to determine if this handler can process a request.
  46. /// </summary>
  47. /// <value>The URL suffix.</value>
  48. protected virtual string UrlSuffix
  49. {
  50. get
  51. {
  52. var name = GetType().Name;
  53. const string srch = "Handler";
  54. if (name.EndsWith(srch, StringComparison.OrdinalIgnoreCase))
  55. {
  56. name = name.Substring(0, name.Length - srch.Length);
  57. }
  58. return "api/" + name;
  59. }
  60. }
  61. /// <summary>
  62. /// Handleses the request.
  63. /// </summary>
  64. /// <param name="request">The request.</param>
  65. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  66. public virtual bool HandlesRequest(HttpListenerRequest request)
  67. {
  68. var name = '/' + UrlSuffix.TrimStart('/');
  69. var url = Kernel.WebApplicationName + name;
  70. return request.Url.LocalPath.EndsWith(url, StringComparison.OrdinalIgnoreCase);
  71. }
  72. /// <summary>
  73. /// Gets or sets the compressed stream.
  74. /// </summary>
  75. /// <value>The compressed stream.</value>
  76. private Stream CompressedStream { get; set; }
  77. /// <summary>
  78. /// Gets a value indicating whether [use chunked encoding].
  79. /// </summary>
  80. /// <value><c>null</c> if [use chunked encoding] contains no value, <c>true</c> if [use chunked encoding]; otherwise, <c>false</c>.</value>
  81. public virtual bool? UseChunkedEncoding
  82. {
  83. get
  84. {
  85. return null;
  86. }
  87. }
  88. /// <summary>
  89. /// The original HttpListenerContext
  90. /// </summary>
  91. /// <value>The HTTP listener context.</value>
  92. protected HttpListenerContext HttpListenerContext { get; set; }
  93. /// <summary>
  94. /// The _query string
  95. /// </summary>
  96. private NameValueCollection _queryString;
  97. /// <summary>
  98. /// The original QueryString
  99. /// </summary>
  100. /// <value>The query string.</value>
  101. public NameValueCollection QueryString
  102. {
  103. get
  104. {
  105. // HttpListenerContext.Request.QueryString is not decoded properly
  106. return _queryString ?? (_queryString = HttpUtility.ParseQueryString(HttpListenerContext.Request.Url.Query));
  107. }
  108. }
  109. /// <summary>
  110. /// The _requested ranges
  111. /// </summary>
  112. private List<KeyValuePair<long, long?>> _requestedRanges;
  113. /// <summary>
  114. /// Gets the requested ranges.
  115. /// </summary>
  116. /// <value>The requested ranges.</value>
  117. protected IEnumerable<KeyValuePair<long, long?>> RequestedRanges
  118. {
  119. get
  120. {
  121. if (_requestedRanges == null)
  122. {
  123. _requestedRanges = new List<KeyValuePair<long, long?>>();
  124. if (IsRangeRequest)
  125. {
  126. // Example: bytes=0-,32-63
  127. var ranges = HttpListenerContext.Request.Headers["Range"].Split('=')[1].Split(',');
  128. foreach (var range in ranges)
  129. {
  130. var vals = range.Split('-');
  131. long start = 0;
  132. long? end = null;
  133. if (!string.IsNullOrEmpty(vals[0]))
  134. {
  135. start = long.Parse(vals[0]);
  136. }
  137. if (!string.IsNullOrEmpty(vals[1]))
  138. {
  139. end = long.Parse(vals[1]);
  140. }
  141. _requestedRanges.Add(new KeyValuePair<long, long?>(start, end));
  142. }
  143. }
  144. }
  145. return _requestedRanges;
  146. }
  147. }
  148. /// <summary>
  149. /// Gets a value indicating whether this instance is range request.
  150. /// </summary>
  151. /// <value><c>true</c> if this instance is range request; otherwise, <c>false</c>.</value>
  152. protected bool IsRangeRequest
  153. {
  154. get
  155. {
  156. return HttpListenerContext.Request.Headers.AllKeys.Contains("Range");
  157. }
  158. }
  159. /// <summary>
  160. /// Gets a value indicating whether [client supports compression].
  161. /// </summary>
  162. /// <value><c>true</c> if [client supports compression]; otherwise, <c>false</c>.</value>
  163. protected bool ClientSupportsCompression
  164. {
  165. get
  166. {
  167. var enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
  168. return enc.Equals("*", StringComparison.OrdinalIgnoreCase) ||
  169. enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 ||
  170. enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1;
  171. }
  172. }
  173. /// <summary>
  174. /// Gets the compression method.
  175. /// </summary>
  176. /// <value>The compression method.</value>
  177. private string CompressionMethod
  178. {
  179. get
  180. {
  181. var enc = HttpListenerContext.Request.Headers["Accept-Encoding"] ?? string.Empty;
  182. if (enc.IndexOf("deflate", StringComparison.OrdinalIgnoreCase) != -1 || enc.Equals("*", StringComparison.OrdinalIgnoreCase))
  183. {
  184. return "deflate";
  185. }
  186. if (enc.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) != -1)
  187. {
  188. return "gzip";
  189. }
  190. return null;
  191. }
  192. }
  193. /// <summary>
  194. /// Processes the request.
  195. /// </summary>
  196. /// <param name="ctx">The CTX.</param>
  197. /// <returns>Task.</returns>
  198. public virtual async Task ProcessRequest(HttpListenerContext ctx)
  199. {
  200. HttpListenerContext = ctx;
  201. ctx.Response.AddHeader("Access-Control-Allow-Origin", "*");
  202. ctx.Response.KeepAlive = true;
  203. try
  204. {
  205. await ProcessRequestInternal(ctx).ConfigureAwait(false);
  206. }
  207. catch (InvalidOperationException ex)
  208. {
  209. HandleException(ctx.Response, ex, 422);
  210. throw;
  211. }
  212. catch (ResourceNotFoundException ex)
  213. {
  214. HandleException(ctx.Response, ex, 404);
  215. throw;
  216. }
  217. catch (FileNotFoundException ex)
  218. {
  219. HandleException(ctx.Response, ex, 404);
  220. throw;
  221. }
  222. catch (DirectoryNotFoundException ex)
  223. {
  224. HandleException(ctx.Response, ex, 404);
  225. throw;
  226. }
  227. catch (UnauthorizedAccessException ex)
  228. {
  229. HandleException(ctx.Response, ex, 401);
  230. throw;
  231. }
  232. catch (ArgumentException ex)
  233. {
  234. HandleException(ctx.Response, ex, 400);
  235. throw;
  236. }
  237. catch (Exception ex)
  238. {
  239. HandleException(ctx.Response, ex, 500);
  240. throw;
  241. }
  242. finally
  243. {
  244. DisposeResponseStream();
  245. }
  246. }
  247. /// <summary>
  248. /// Appends the error message.
  249. /// </summary>
  250. /// <param name="response">The response.</param>
  251. /// <param name="ex">The ex.</param>
  252. /// <param name="statusCode">The status code.</param>
  253. private void HandleException(HttpListenerResponse response, Exception ex, int statusCode)
  254. {
  255. response.StatusCode = statusCode;
  256. response.Headers.Add("Status", statusCode.ToString(new CultureInfo("en-US")));
  257. response.Headers.Remove("Age");
  258. response.Headers.Remove("Expires");
  259. response.Headers.Remove("Cache-Control");
  260. response.Headers.Remove("Etag");
  261. response.Headers.Remove("Last-Modified");
  262. response.ContentType = "text/plain";
  263. Logger.ErrorException("Error processing request", ex);
  264. if (!string.IsNullOrEmpty(ex.Message))
  265. {
  266. response.AddHeader("X-Application-Error-Code", ex.Message);
  267. }
  268. var bytes = Encoding.UTF8.GetBytes(ex.Message);
  269. var stream = CompressedStream ?? response.OutputStream;
  270. // This could fail, but try to add the stack trace as the body content
  271. try
  272. {
  273. stream.Write(bytes, 0, bytes.Length);
  274. }
  275. catch (Exception ex1)
  276. {
  277. Logger.ErrorException("Error dumping stack trace", ex1);
  278. }
  279. }
  280. /// <summary>
  281. /// Processes the request internal.
  282. /// </summary>
  283. /// <param name="ctx">The CTX.</param>
  284. /// <returns>Task.</returns>
  285. private async Task ProcessRequestInternal(HttpListenerContext ctx)
  286. {
  287. var responseInfo = await GetResponseInfo().ConfigureAwait(false);
  288. // Let the client know if byte range requests are supported or not
  289. if (responseInfo.SupportsByteRangeRequests)
  290. {
  291. ctx.Response.Headers["Accept-Ranges"] = "bytes";
  292. }
  293. else if (!responseInfo.SupportsByteRangeRequests)
  294. {
  295. ctx.Response.Headers["Accept-Ranges"] = "none";
  296. }
  297. if (responseInfo.IsResponseValid && responseInfo.SupportsByteRangeRequests && IsRangeRequest)
  298. {
  299. // Set the initial status code
  300. // When serving a range request, we need to return status code 206 to indicate a partial response body
  301. responseInfo.StatusCode = 206;
  302. }
  303. ctx.Response.ContentType = responseInfo.ContentType;
  304. if (responseInfo.Etag.HasValue)
  305. {
  306. ctx.Response.Headers["ETag"] = responseInfo.Etag.Value.ToString("N");
  307. }
  308. var isCacheValid = true;
  309. // Validate If-Modified-Since
  310. if (ctx.Request.Headers.AllKeys.Contains("If-Modified-Since"))
  311. {
  312. DateTime ifModifiedSince;
  313. if (DateTime.TryParse(ctx.Request.Headers["If-Modified-Since"], out ifModifiedSince))
  314. {
  315. isCacheValid = IsCacheValid(ifModifiedSince.ToUniversalTime(), responseInfo.CacheDuration,
  316. responseInfo.DateLastModified);
  317. }
  318. }
  319. // Validate If-None-Match
  320. if (isCacheValid &&
  321. (responseInfo.Etag.HasValue || !string.IsNullOrEmpty(ctx.Request.Headers["If-None-Match"])))
  322. {
  323. Guid ifNoneMatch;
  324. if (Guid.TryParse(ctx.Request.Headers["If-None-Match"] ?? string.Empty, out ifNoneMatch))
  325. {
  326. if (responseInfo.Etag.HasValue && responseInfo.Etag.Value == ifNoneMatch)
  327. {
  328. responseInfo.StatusCode = 304;
  329. }
  330. }
  331. }
  332. LogResponse(ctx, responseInfo);
  333. if (responseInfo.IsResponseValid)
  334. {
  335. await OnProcessingRequest(responseInfo).ConfigureAwait(false);
  336. }
  337. if (responseInfo.IsResponseValid)
  338. {
  339. await ProcessUncachedRequest(ctx, responseInfo).ConfigureAwait(false);
  340. }
  341. else
  342. {
  343. if (responseInfo.StatusCode == 304)
  344. {
  345. AddAgeHeader(ctx.Response, responseInfo);
  346. AddExpiresHeader(ctx.Response, responseInfo);
  347. }
  348. ctx.Response.StatusCode = responseInfo.StatusCode;
  349. ctx.Response.SendChunked = false;
  350. }
  351. }
  352. /// <summary>
  353. /// The _null task result
  354. /// </summary>
  355. private readonly Task<bool> _nullTaskResult = Task.FromResult(true);
  356. /// <summary>
  357. /// Called when [processing request].
  358. /// </summary>
  359. /// <param name="responseInfo">The response info.</param>
  360. /// <returns>Task.</returns>
  361. protected virtual Task OnProcessingRequest(ResponseInfo responseInfo)
  362. {
  363. return _nullTaskResult;
  364. }
  365. /// <summary>
  366. /// Logs the response.
  367. /// </summary>
  368. /// <param name="ctx">The CTX.</param>
  369. /// <param name="responseInfo">The response info.</param>
  370. private void LogResponse(HttpListenerContext ctx, ResponseInfo responseInfo)
  371. {
  372. // Don't log normal 200's
  373. if (responseInfo.StatusCode == 200)
  374. {
  375. return;
  376. }
  377. var log = new StringBuilder();
  378. log.AppendLine(string.Format("Url: {0}", ctx.Request.Url));
  379. log.AppendLine("Headers: " + string.Join(",", ctx.Response.Headers.AllKeys.Select(k => k + "=" + ctx.Response.Headers[k])));
  380. var msg = "Http Response Sent (" + responseInfo.StatusCode + ") to " + ctx.Request.RemoteEndPoint;
  381. if (Kernel.Configuration.EnableHttpLevelLogging)
  382. {
  383. Logger.LogMultiline(msg, LogSeverity.Debug, log);
  384. }
  385. }
  386. /// <summary>
  387. /// Processes the uncached request.
  388. /// </summary>
  389. /// <param name="ctx">The CTX.</param>
  390. /// <param name="responseInfo">The response info.</param>
  391. /// <returns>Task.</returns>
  392. private async Task ProcessUncachedRequest(HttpListenerContext ctx, ResponseInfo responseInfo)
  393. {
  394. var totalContentLength = GetTotalContentLength(responseInfo);
  395. // By default, use chunked encoding if we don't know the content length
  396. var useChunkedEncoding = UseChunkedEncoding == null ? (totalContentLength == null) : UseChunkedEncoding.Value;
  397. // Don't force this to true. HttpListener will default it to true if supported by the client.
  398. if (!useChunkedEncoding)
  399. {
  400. ctx.Response.SendChunked = false;
  401. }
  402. // Set the content length, if we know it
  403. if (totalContentLength.HasValue)
  404. {
  405. ctx.Response.ContentLength64 = totalContentLength.Value;
  406. }
  407. var compressResponse = responseInfo.CompressResponse && ClientSupportsCompression;
  408. // Add the compression header
  409. if (compressResponse)
  410. {
  411. ctx.Response.AddHeader("Content-Encoding", CompressionMethod);
  412. ctx.Response.AddHeader("Vary", "Accept-Encoding");
  413. }
  414. // Don't specify both last modified and Etag, unless caching unconditionally. They are redundant
  415. // https://developers.google.com/speed/docs/best-practices/caching#LeverageBrowserCaching
  416. if (responseInfo.DateLastModified.HasValue && (!responseInfo.Etag.HasValue || responseInfo.CacheDuration.Ticks > 0))
  417. {
  418. ctx.Response.Headers[HttpResponseHeader.LastModified] = responseInfo.DateLastModified.Value.ToString("r");
  419. AddAgeHeader(ctx.Response, responseInfo);
  420. }
  421. // Add caching headers
  422. ConfigureCaching(ctx.Response, responseInfo);
  423. // Set the status code
  424. ctx.Response.StatusCode = responseInfo.StatusCode;
  425. if (responseInfo.IsResponseValid)
  426. {
  427. // Finally, write the response data
  428. var outputStream = ctx.Response.OutputStream;
  429. if (compressResponse)
  430. {
  431. if (CompressionMethod.Equals("deflate", StringComparison.OrdinalIgnoreCase))
  432. {
  433. CompressedStream = new DeflateStream(outputStream, CompressionLevel.Fastest, true);
  434. }
  435. else
  436. {
  437. CompressedStream = new GZipStream(outputStream, CompressionLevel.Fastest, true);
  438. }
  439. outputStream = CompressedStream;
  440. }
  441. await WriteResponseToOutputStream(outputStream, responseInfo, totalContentLength).ConfigureAwait(false);
  442. }
  443. else
  444. {
  445. ctx.Response.SendChunked = false;
  446. }
  447. }
  448. /// <summary>
  449. /// Configures the caching.
  450. /// </summary>
  451. /// <param name="response">The response.</param>
  452. /// <param name="responseInfo">The response info.</param>
  453. private void ConfigureCaching(HttpListenerResponse response, ResponseInfo responseInfo)
  454. {
  455. if (responseInfo.CacheDuration.Ticks > 0)
  456. {
  457. response.Headers[HttpResponseHeader.CacheControl] = "public, max-age=" + Convert.ToInt32(responseInfo.CacheDuration.TotalSeconds);
  458. }
  459. else if (responseInfo.Etag.HasValue)
  460. {
  461. response.Headers[HttpResponseHeader.CacheControl] = "public";
  462. }
  463. else
  464. {
  465. response.Headers[HttpResponseHeader.CacheControl] = "no-cache, no-store, must-revalidate";
  466. response.Headers[HttpResponseHeader.Pragma] = "no-cache, no-store, must-revalidate";
  467. }
  468. AddExpiresHeader(response, responseInfo);
  469. }
  470. /// <summary>
  471. /// Adds the expires header.
  472. /// </summary>
  473. /// <param name="response">The response.</param>
  474. /// <param name="responseInfo">The response info.</param>
  475. private void AddExpiresHeader(HttpListenerResponse response, ResponseInfo responseInfo)
  476. {
  477. if (responseInfo.CacheDuration.Ticks > 0)
  478. {
  479. response.Headers[HttpResponseHeader.Expires] = DateTime.UtcNow.Add(responseInfo.CacheDuration).ToString("r");
  480. }
  481. else if (!responseInfo.Etag.HasValue)
  482. {
  483. response.Headers[HttpResponseHeader.Expires] = "-1";
  484. }
  485. }
  486. /// <summary>
  487. /// Adds the age header.
  488. /// </summary>
  489. /// <param name="response">The response.</param>
  490. /// <param name="responseInfo">The response info.</param>
  491. private void AddAgeHeader(HttpListenerResponse response, ResponseInfo responseInfo)
  492. {
  493. if (responseInfo.DateLastModified.HasValue)
  494. {
  495. response.Headers[HttpResponseHeader.Age] = Convert.ToInt32((DateTime.UtcNow - responseInfo.DateLastModified.Value).TotalSeconds).ToString(CultureInfo.InvariantCulture);
  496. }
  497. }
  498. /// <summary>
  499. /// Writes the response to output stream.
  500. /// </summary>
  501. /// <param name="stream">The stream.</param>
  502. /// <param name="responseInfo">The response info.</param>
  503. /// <param name="contentLength">Length of the content.</param>
  504. /// <returns>Task.</returns>
  505. protected abstract Task WriteResponseToOutputStream(Stream stream, ResponseInfo responseInfo, long? contentLength);
  506. /// <summary>
  507. /// Disposes the response stream.
  508. /// </summary>
  509. protected virtual void DisposeResponseStream()
  510. {
  511. if (CompressedStream != null)
  512. {
  513. try
  514. {
  515. CompressedStream.Dispose();
  516. }
  517. catch (Exception ex)
  518. {
  519. Logger.ErrorException("Error disposing compressed stream", ex);
  520. }
  521. }
  522. try
  523. {
  524. //HttpListenerContext.Response.OutputStream.Dispose();
  525. HttpListenerContext.Response.Close();
  526. }
  527. catch (Exception ex)
  528. {
  529. Logger.ErrorException("Error disposing response", ex);
  530. }
  531. }
  532. /// <summary>
  533. /// Determines whether [is cache valid] [the specified if modified since].
  534. /// </summary>
  535. /// <param name="ifModifiedSince">If modified since.</param>
  536. /// <param name="cacheDuration">Duration of the cache.</param>
  537. /// <param name="dateModified">The date modified.</param>
  538. /// <returns><c>true</c> if [is cache valid] [the specified if modified since]; otherwise, <c>false</c>.</returns>
  539. private bool IsCacheValid(DateTime ifModifiedSince, TimeSpan cacheDuration, DateTime? dateModified)
  540. {
  541. if (dateModified.HasValue)
  542. {
  543. DateTime lastModified = NormalizeDateForComparison(dateModified.Value);
  544. ifModifiedSince = NormalizeDateForComparison(ifModifiedSince);
  545. return lastModified <= ifModifiedSince;
  546. }
  547. DateTime cacheExpirationDate = ifModifiedSince.Add(cacheDuration);
  548. if (DateTime.UtcNow < cacheExpirationDate)
  549. {
  550. return true;
  551. }
  552. return false;
  553. }
  554. /// <summary>
  555. /// When the browser sends the IfModifiedDate, it's precision is limited to seconds, so this will account for that
  556. /// </summary>
  557. /// <param name="date">The date.</param>
  558. /// <returns>DateTime.</returns>
  559. private DateTime NormalizeDateForComparison(DateTime date)
  560. {
  561. return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, date.Kind);
  562. }
  563. /// <summary>
  564. /// Gets the total length of the content.
  565. /// </summary>
  566. /// <param name="responseInfo">The response info.</param>
  567. /// <returns>System.Nullable{System.Int64}.</returns>
  568. protected virtual long? GetTotalContentLength(ResponseInfo responseInfo)
  569. {
  570. return null;
  571. }
  572. /// <summary>
  573. /// Gets the response info.
  574. /// </summary>
  575. /// <returns>Task{ResponseInfo}.</returns>
  576. protected abstract Task<ResponseInfo> GetResponseInfo();
  577. /// <summary>
  578. /// Gets a bool query string param.
  579. /// </summary>
  580. /// <param name="name">The name.</param>
  581. /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
  582. protected bool GetBoolQueryStringParam(string name)
  583. {
  584. var val = QueryString[name] ?? string.Empty;
  585. return val.Equals("1", StringComparison.OrdinalIgnoreCase) || val.Equals("true", StringComparison.OrdinalIgnoreCase);
  586. }
  587. /// <summary>
  588. /// The _form values
  589. /// </summary>
  590. private Hashtable _formValues;
  591. /// <summary>
  592. /// Gets a value from form POST data
  593. /// </summary>
  594. /// <param name="name">The name.</param>
  595. /// <returns>Task{System.String}.</returns>
  596. protected async Task<string> GetFormValue(string name)
  597. {
  598. if (_formValues == null)
  599. {
  600. _formValues = await GetFormValues(HttpListenerContext.Request).ConfigureAwait(false);
  601. }
  602. if (_formValues.ContainsKey(name))
  603. {
  604. return _formValues[name].ToString();
  605. }
  606. return null;
  607. }
  608. /// <summary>
  609. /// Extracts form POST data from a request
  610. /// </summary>
  611. /// <param name="request">The request.</param>
  612. /// <returns>Task{Hashtable}.</returns>
  613. private async Task<Hashtable> GetFormValues(HttpListenerRequest request)
  614. {
  615. var formVars = new Hashtable();
  616. if (request.HasEntityBody)
  617. {
  618. if (request.ContentType.IndexOf("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) != -1)
  619. {
  620. using (var requestBody = request.InputStream)
  621. {
  622. using (var reader = new StreamReader(requestBody, request.ContentEncoding))
  623. {
  624. var s = await reader.ReadToEndAsync().ConfigureAwait(false);
  625. var pairs = s.Split('&');
  626. foreach (var pair in pairs)
  627. {
  628. var index = pair.IndexOf('=');
  629. if (index != -1)
  630. {
  631. var name = pair.Substring(0, index);
  632. var value = pair.Substring(index + 1);
  633. formVars.Add(name, value);
  634. }
  635. }
  636. }
  637. }
  638. }
  639. }
  640. return formVars;
  641. }
  642. }
  643. internal static class SharedLogger
  644. {
  645. /// <summary>
  646. /// The logger
  647. /// </summary>
  648. internal static ILogger Logger = LogManager.GetLogger("Http Handler");
  649. }
  650. /// <summary>
  651. /// Class ResponseInfo
  652. /// </summary>
  653. public class ResponseInfo
  654. {
  655. /// <summary>
  656. /// Gets or sets the type of the content.
  657. /// </summary>
  658. /// <value>The type of the content.</value>
  659. public string ContentType { get; set; }
  660. /// <summary>
  661. /// Gets or sets the etag.
  662. /// </summary>
  663. /// <value>The etag.</value>
  664. public Guid? Etag { get; set; }
  665. /// <summary>
  666. /// Gets or sets the date last modified.
  667. /// </summary>
  668. /// <value>The date last modified.</value>
  669. public DateTime? DateLastModified { get; set; }
  670. /// <summary>
  671. /// Gets or sets the duration of the cache.
  672. /// </summary>
  673. /// <value>The duration of the cache.</value>
  674. public TimeSpan CacheDuration { get; set; }
  675. /// <summary>
  676. /// Gets or sets a value indicating whether [compress response].
  677. /// </summary>
  678. /// <value><c>true</c> if [compress response]; otherwise, <c>false</c>.</value>
  679. public bool CompressResponse { get; set; }
  680. /// <summary>
  681. /// Gets or sets the status code.
  682. /// </summary>
  683. /// <value>The status code.</value>
  684. public int StatusCode { get; set; }
  685. /// <summary>
  686. /// Gets or sets a value indicating whether [supports byte range requests].
  687. /// </summary>
  688. /// <value><c>true</c> if [supports byte range requests]; otherwise, <c>false</c>.</value>
  689. public bool SupportsByteRangeRequests { get; set; }
  690. /// <summary>
  691. /// Initializes a new instance of the <see cref="ResponseInfo" /> class.
  692. /// </summary>
  693. public ResponseInfo()
  694. {
  695. CacheDuration = TimeSpan.FromTicks(0);
  696. CompressResponse = true;
  697. StatusCode = 200;
  698. }
  699. /// <summary>
  700. /// Gets a value indicating whether this instance is response valid.
  701. /// </summary>
  702. /// <value><c>true</c> if this instance is response valid; otherwise, <c>false</c>.</value>
  703. public bool IsResponseValid
  704. {
  705. get
  706. {
  707. return StatusCode >= 200 && StatusCode < 300;
  708. }
  709. }
  710. }
  711. }