WebSocketSharpRequest.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Text;
  5. using Emby.Server.Implementations.HttpServer.SocketSharp;
  6. using Funq;
  7. using MediaBrowser.Model.IO;
  8. using MediaBrowser.Model.Logging;
  9. using MediaBrowser.Model.Services;
  10. using ServiceStack;
  11. using ServiceStack.Host;
  12. using SocketHttpListener.Net;
  13. using IHttpFile = MediaBrowser.Model.Services.IHttpFile;
  14. using IHttpRequest = MediaBrowser.Model.Services.IHttpRequest;
  15. using IHttpResponse = MediaBrowser.Model.Services.IHttpResponse;
  16. using IResponse = MediaBrowser.Model.Services.IResponse;
  17. namespace MediaBrowser.Server.Implementations.HttpServer.SocketSharp
  18. {
  19. public partial class WebSocketSharpRequest : IHttpRequest
  20. {
  21. public Container Container { get; set; }
  22. private readonly HttpListenerRequest request;
  23. private readonly IHttpResponse response;
  24. private readonly IMemoryStreamFactory _memoryStreamProvider;
  25. public WebSocketSharpRequest(HttpListenerContext httpContext, string operationName, ILogger logger, IMemoryStreamFactory memoryStreamProvider)
  26. {
  27. this.OperationName = operationName;
  28. _memoryStreamProvider = memoryStreamProvider;
  29. this.request = httpContext.Request;
  30. this.response = new WebSocketSharpResponse(logger, httpContext.Response, this);
  31. }
  32. public HttpListenerRequest HttpRequest
  33. {
  34. get { return request; }
  35. }
  36. public object OriginalRequest
  37. {
  38. get { return request; }
  39. }
  40. public IResponse Response
  41. {
  42. get { return response; }
  43. }
  44. public IHttpResponse HttpResponse
  45. {
  46. get { return response; }
  47. }
  48. public string OperationName { get; set; }
  49. public object Dto { get; set; }
  50. public string RawUrl
  51. {
  52. get { return request.RawUrl; }
  53. }
  54. public string AbsoluteUri
  55. {
  56. get { return request.Url.AbsoluteUri.TrimEnd('/'); }
  57. }
  58. public string UserHostAddress
  59. {
  60. get { return request.UserHostAddress; }
  61. }
  62. public string XForwardedFor
  63. {
  64. get
  65. {
  66. return String.IsNullOrEmpty(request.Headers["X-Forwarded-For"]) ? null : request.Headers["X-Forwarded-For"];
  67. }
  68. }
  69. public int? XForwardedPort
  70. {
  71. get
  72. {
  73. return string.IsNullOrEmpty(request.Headers["X-Forwarded-Port"]) ? (int?)null : int.Parse(request.Headers["X-Forwarded-Port"]);
  74. }
  75. }
  76. public string XForwardedProtocol
  77. {
  78. get
  79. {
  80. return string.IsNullOrEmpty(request.Headers["X-Forwarded-Proto"]) ? null : request.Headers["X-Forwarded-Proto"];
  81. }
  82. }
  83. public string XRealIp
  84. {
  85. get
  86. {
  87. return String.IsNullOrEmpty(request.Headers["X-Real-IP"]) ? null : request.Headers["X-Real-IP"];
  88. }
  89. }
  90. private string remoteIp;
  91. public string RemoteIp
  92. {
  93. get
  94. {
  95. return remoteIp ??
  96. (remoteIp = (CheckBadChars(XForwardedFor)) ??
  97. (NormalizeIp(CheckBadChars(XRealIp)) ??
  98. (request.RemoteEndPoint != null ? NormalizeIp(request.RemoteEndPoint.IpAddress.ToString()) : null)));
  99. }
  100. }
  101. private static readonly char[] HttpTrimCharacters = new char[] { (char)0x09, (char)0xA, (char)0xB, (char)0xC, (char)0xD, (char)0x20 };
  102. //
  103. // CheckBadChars - throws on invalid chars to be not found in header name/value
  104. //
  105. internal static string CheckBadChars(string name)
  106. {
  107. if (name == null || name.Length == 0)
  108. {
  109. return name;
  110. }
  111. // VALUE check
  112. //Trim spaces from both ends
  113. name = name.Trim(HttpTrimCharacters);
  114. //First, check for correctly formed multi-line value
  115. //Second, check for absenece of CTL characters
  116. int crlf = 0;
  117. for (int i = 0; i < name.Length; ++i)
  118. {
  119. char c = (char)(0x000000ff & (uint)name[i]);
  120. switch (crlf)
  121. {
  122. case 0:
  123. if (c == '\r')
  124. {
  125. crlf = 1;
  126. }
  127. else if (c == '\n')
  128. {
  129. // Technically this is bad HTTP. But it would be a breaking change to throw here.
  130. // Is there an exploit?
  131. crlf = 2;
  132. }
  133. else if (c == 127 || (c < ' ' && c != '\t'))
  134. {
  135. throw new ArgumentException("net_WebHeaderInvalidControlChars");
  136. }
  137. break;
  138. case 1:
  139. if (c == '\n')
  140. {
  141. crlf = 2;
  142. break;
  143. }
  144. throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
  145. case 2:
  146. if (c == ' ' || c == '\t')
  147. {
  148. crlf = 0;
  149. break;
  150. }
  151. throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
  152. }
  153. }
  154. if (crlf != 0)
  155. {
  156. throw new ArgumentException("net_WebHeaderInvalidCRLFChars");
  157. }
  158. return name;
  159. }
  160. internal static bool ContainsNonAsciiChars(string token)
  161. {
  162. for (int i = 0; i < token.Length; ++i)
  163. {
  164. if ((token[i] < 0x20) || (token[i] > 0x7e))
  165. {
  166. return true;
  167. }
  168. }
  169. return false;
  170. }
  171. private string NormalizeIp(string ip)
  172. {
  173. if (!string.IsNullOrWhiteSpace(ip))
  174. {
  175. // Handle ipv4 mapped to ipv6
  176. const string srch = "::ffff:";
  177. var index = ip.IndexOf(srch, StringComparison.OrdinalIgnoreCase);
  178. if (index == 0)
  179. {
  180. ip = ip.Substring(srch.Length);
  181. }
  182. }
  183. return ip;
  184. }
  185. public bool IsSecureConnection
  186. {
  187. get { return request.IsSecureConnection || XForwardedProtocol == "https"; }
  188. }
  189. public string[] AcceptTypes
  190. {
  191. get { return request.AcceptTypes; }
  192. }
  193. private Dictionary<string, object> items;
  194. public Dictionary<string, object> Items
  195. {
  196. get { return items ?? (items = new Dictionary<string, object>()); }
  197. }
  198. private string responseContentType;
  199. public string ResponseContentType
  200. {
  201. get
  202. {
  203. return responseContentType
  204. ?? (responseContentType = GetResponseContentType(this));
  205. }
  206. set
  207. {
  208. this.responseContentType = value;
  209. HasExplicitResponseContentType = true;
  210. }
  211. }
  212. private static string GetResponseContentType(IRequest httpReq)
  213. {
  214. var specifiedContentType = GetQueryStringContentType(httpReq);
  215. if (!string.IsNullOrEmpty(specifiedContentType)) return specifiedContentType;
  216. var acceptContentTypes = httpReq.AcceptTypes;
  217. var defaultContentType = httpReq.ContentType;
  218. if (httpReq.HasAnyOfContentTypes(MimeTypes.FormUrlEncoded, MimeTypes.MultiPartFormData))
  219. {
  220. defaultContentType = HostContext.Config.DefaultContentType;
  221. }
  222. var customContentTypes = ContentTypes.Instance.ContentTypeFormats.Values;
  223. var preferredContentTypes = new string[] {};
  224. var acceptsAnything = false;
  225. var hasDefaultContentType = !string.IsNullOrEmpty(defaultContentType);
  226. if (acceptContentTypes != null)
  227. {
  228. var hasPreferredContentTypes = new bool[preferredContentTypes.Length];
  229. foreach (var acceptsType in acceptContentTypes)
  230. {
  231. var contentType = ContentFormat.GetRealContentType(acceptsType);
  232. acceptsAnything = acceptsAnything || contentType == "*/*";
  233. for (var i = 0; i < preferredContentTypes.Length; i++)
  234. {
  235. if (hasPreferredContentTypes[i]) continue;
  236. var preferredContentType = preferredContentTypes[i];
  237. hasPreferredContentTypes[i] = contentType.StartsWith(preferredContentType);
  238. //Prefer Request.ContentType if it is also a preferredContentType
  239. if (hasPreferredContentTypes[i] && preferredContentType == defaultContentType)
  240. return preferredContentType;
  241. }
  242. }
  243. for (var i = 0; i < preferredContentTypes.Length; i++)
  244. {
  245. if (hasPreferredContentTypes[i]) return preferredContentTypes[i];
  246. }
  247. if (acceptsAnything)
  248. {
  249. if (hasDefaultContentType)
  250. return defaultContentType;
  251. if (HostContext.Config.DefaultContentType != null)
  252. return HostContext.Config.DefaultContentType;
  253. }
  254. foreach (var contentType in acceptContentTypes)
  255. {
  256. foreach (var customContentType in customContentTypes)
  257. {
  258. if (contentType.StartsWith(customContentType, StringComparison.OrdinalIgnoreCase))
  259. return customContentType;
  260. }
  261. }
  262. }
  263. if (acceptContentTypes == null && httpReq.ContentType == MimeTypes.Soap11)
  264. {
  265. return MimeTypes.Soap11;
  266. }
  267. //We could also send a '406 Not Acceptable', but this is allowed also
  268. return HostContext.Config.DefaultContentType;
  269. }
  270. private static string GetQueryStringContentType(IRequest httpReq)
  271. {
  272. var format = httpReq.QueryString["format"];
  273. if (format == null)
  274. {
  275. const int formatMaxLength = 4;
  276. var pi = httpReq.PathInfo;
  277. if (pi == null || pi.Length <= formatMaxLength) return null;
  278. if (pi[0] == '/') pi = pi.Substring(1);
  279. format = pi.LeftPart('/');
  280. if (format.Length > formatMaxLength) return null;
  281. }
  282. format = format.LeftPart('.').ToLower();
  283. if (format.Contains("json")) return "application/json";
  284. if (format.Contains("xml")) return MimeTypes.Xml;
  285. string contentType;
  286. ContentTypes.Instance.ContentTypeFormats.TryGetValue(format, out contentType);
  287. return contentType;
  288. }
  289. public bool HasExplicitResponseContentType { get; private set; }
  290. private string pathInfo;
  291. public string PathInfo
  292. {
  293. get
  294. {
  295. if (this.pathInfo == null)
  296. {
  297. var mode = HostContext.Config.HandlerFactoryPath;
  298. var pos = request.RawUrl.IndexOf("?");
  299. if (pos != -1)
  300. {
  301. var path = request.RawUrl.Substring(0, pos);
  302. this.pathInfo = GetPathInfo(
  303. path,
  304. mode,
  305. mode ?? "");
  306. }
  307. else
  308. {
  309. this.pathInfo = request.RawUrl;
  310. }
  311. this.pathInfo = this.pathInfo.UrlDecode();
  312. this.pathInfo = NormalizePathInfo(pathInfo, mode);
  313. }
  314. return this.pathInfo;
  315. }
  316. }
  317. private static string GetPathInfo(string fullPath, string mode, string appPath)
  318. {
  319. var pathInfo = ResolvePathInfoFromMappedPath(fullPath, mode);
  320. if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
  321. //Wildcard mode relies on this to work out the handlerPath
  322. pathInfo = ResolvePathInfoFromMappedPath(fullPath, appPath);
  323. if (!string.IsNullOrEmpty(pathInfo)) return pathInfo;
  324. return fullPath;
  325. }
  326. private static string ResolvePathInfoFromMappedPath(string fullPath, string mappedPathRoot)
  327. {
  328. if (mappedPathRoot == null) return null;
  329. var sbPathInfo = new StringBuilder();
  330. var fullPathParts = fullPath.Split('/');
  331. var mappedPathRootParts = mappedPathRoot.Split('/');
  332. var fullPathIndexOffset = mappedPathRootParts.Length - 1;
  333. var pathRootFound = false;
  334. for (var fullPathIndex = 0; fullPathIndex < fullPathParts.Length; fullPathIndex++)
  335. {
  336. if (pathRootFound)
  337. {
  338. sbPathInfo.Append("/" + fullPathParts[fullPathIndex]);
  339. }
  340. else if (fullPathIndex - fullPathIndexOffset >= 0)
  341. {
  342. pathRootFound = true;
  343. for (var mappedPathRootIndex = 0; mappedPathRootIndex < mappedPathRootParts.Length; mappedPathRootIndex++)
  344. {
  345. if (!string.Equals(fullPathParts[fullPathIndex - fullPathIndexOffset + mappedPathRootIndex], mappedPathRootParts[mappedPathRootIndex], StringComparison.OrdinalIgnoreCase))
  346. {
  347. pathRootFound = false;
  348. break;
  349. }
  350. }
  351. }
  352. }
  353. if (!pathRootFound) return null;
  354. var path = sbPathInfo.ToString();
  355. return path.Length > 1 ? path.TrimEnd('/') : "/";
  356. }
  357. private Dictionary<string, System.Net.Cookie> cookies;
  358. public IDictionary<string, System.Net.Cookie> Cookies
  359. {
  360. get
  361. {
  362. if (cookies == null)
  363. {
  364. cookies = new Dictionary<string, System.Net.Cookie>();
  365. for (var i = 0; i < this.request.Cookies.Count; i++)
  366. {
  367. var httpCookie = this.request.Cookies[i];
  368. cookies[httpCookie.Name] = new System.Net.Cookie(httpCookie.Name, httpCookie.Value, httpCookie.Path, httpCookie.Domain);
  369. }
  370. }
  371. return cookies;
  372. }
  373. }
  374. public string UserAgent
  375. {
  376. get { return request.UserAgent; }
  377. }
  378. public QueryParamCollection Headers
  379. {
  380. get { return request.Headers; }
  381. }
  382. private QueryParamCollection queryString;
  383. public QueryParamCollection QueryString
  384. {
  385. get { return queryString ?? (queryString = MyHttpUtility.ParseQueryString(request.Url.Query)); }
  386. }
  387. private QueryParamCollection formData;
  388. public QueryParamCollection FormData
  389. {
  390. get { return formData ?? (formData = this.Form); }
  391. }
  392. public bool IsLocal
  393. {
  394. get { return request.IsLocal; }
  395. }
  396. private string httpMethod;
  397. public string HttpMethod
  398. {
  399. get
  400. {
  401. return httpMethod
  402. ?? (httpMethod = Param(HttpHeaders.XHttpMethodOverride)
  403. ?? request.HttpMethod);
  404. }
  405. }
  406. public string Verb
  407. {
  408. get { return HttpMethod; }
  409. }
  410. public string Param(string name)
  411. {
  412. return Headers[name]
  413. ?? QueryString[name]
  414. ?? FormData[name];
  415. }
  416. public string ContentType
  417. {
  418. get { return request.ContentType; }
  419. }
  420. public Encoding contentEncoding;
  421. public Encoding ContentEncoding
  422. {
  423. get { return contentEncoding ?? request.ContentEncoding; }
  424. set { contentEncoding = value; }
  425. }
  426. public Uri UrlReferrer
  427. {
  428. get { return request.UrlReferrer; }
  429. }
  430. public static Encoding GetEncoding(string contentTypeHeader)
  431. {
  432. var param = GetParameter(contentTypeHeader, "charset=");
  433. if (param == null) return null;
  434. try
  435. {
  436. return Encoding.GetEncoding(param);
  437. }
  438. catch (ArgumentException)
  439. {
  440. return null;
  441. }
  442. }
  443. public Stream InputStream
  444. {
  445. get { return request.InputStream; }
  446. }
  447. public long ContentLength
  448. {
  449. get { return request.ContentLength64; }
  450. }
  451. private IHttpFile[] httpFiles;
  452. public IHttpFile[] Files
  453. {
  454. get
  455. {
  456. if (httpFiles == null)
  457. {
  458. if (files == null)
  459. return httpFiles = new IHttpFile[0];
  460. httpFiles = new IHttpFile[files.Count];
  461. for (var i = 0; i < files.Count; i++)
  462. {
  463. var reqFile = files[i];
  464. httpFiles[i] = new HttpFile
  465. {
  466. ContentType = reqFile.ContentType,
  467. ContentLength = reqFile.ContentLength,
  468. FileName = reqFile.FileName,
  469. InputStream = reqFile.InputStream,
  470. };
  471. }
  472. }
  473. return httpFiles;
  474. }
  475. }
  476. static Stream GetSubStream(Stream stream, IMemoryStreamFactory streamProvider)
  477. {
  478. if (stream is MemoryStream)
  479. {
  480. var other = (MemoryStream)stream;
  481. try
  482. {
  483. return new MemoryStream(other.GetBuffer(), 0, (int)other.Length, false, true);
  484. }
  485. catch (UnauthorizedAccessException)
  486. {
  487. return new MemoryStream(other.ToArray(), 0, (int)other.Length, false, true);
  488. }
  489. }
  490. return stream;
  491. }
  492. public static string GetHandlerPathIfAny(string listenerUrl)
  493. {
  494. if (listenerUrl == null) return null;
  495. var pos = listenerUrl.IndexOf("://", StringComparison.InvariantCultureIgnoreCase);
  496. if (pos == -1) return null;
  497. var startHostUrl = listenerUrl.Substring(pos + "://".Length);
  498. var endPos = startHostUrl.IndexOf('/');
  499. if (endPos == -1) return null;
  500. var endHostUrl = startHostUrl.Substring(endPos + 1);
  501. return String.IsNullOrEmpty(endHostUrl) ? null : endHostUrl.TrimEnd('/');
  502. }
  503. public static string NormalizePathInfo(string pathInfo, string handlerPath)
  504. {
  505. if (handlerPath != null && pathInfo.TrimStart('/').StartsWith(
  506. handlerPath, StringComparison.InvariantCultureIgnoreCase))
  507. {
  508. return pathInfo.TrimStart('/').Substring(handlerPath.Length);
  509. }
  510. return pathInfo;
  511. }
  512. }
  513. public class HttpFile : IHttpFile
  514. {
  515. public string Name { get; set; }
  516. public string FileName { get; set; }
  517. public long ContentLength { get; set; }
  518. public string ContentType { get; set; }
  519. public Stream InputStream { get; set; }
  520. }
  521. }