RequestMono.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.IO;
  5. using System.Net;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using MediaBrowser.Model.Services;
  9. using Microsoft.Extensions.Primitives;
  10. namespace Emby.Server.Implementations.SocketSharp
  11. {
  12. public partial class WebSocketSharpRequest : IHttpRequest
  13. {
  14. internal static string GetParameter(string header, string attr)
  15. {
  16. int ap = header.IndexOf(attr, StringComparison.Ordinal);
  17. if (ap == -1)
  18. {
  19. return null;
  20. }
  21. ap += attr.Length;
  22. if (ap >= header.Length)
  23. {
  24. return null;
  25. }
  26. char ending = header[ap];
  27. if (ending != '"')
  28. {
  29. ending = ' ';
  30. }
  31. int end = header.IndexOf(ending, ap + 1);
  32. if (end == -1)
  33. {
  34. return ending == '"' ? null : header.Substring(ap);
  35. }
  36. return header.Substring(ap + 1, end - ap - 1);
  37. }
  38. private async Task LoadMultiPart(WebROCollection form)
  39. {
  40. string boundary = GetParameter(ContentType, "; boundary=");
  41. if (boundary == null)
  42. {
  43. return;
  44. }
  45. using (var requestStream = InputStream)
  46. {
  47. // DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
  48. // Not ending with \r\n?
  49. var ms = new MemoryStream(32 * 1024);
  50. await requestStream.CopyToAsync(ms).ConfigureAwait(false);
  51. var input = ms;
  52. ms.WriteByte((byte)'\r');
  53. ms.WriteByte((byte)'\n');
  54. input.Position = 0;
  55. // Uncomment to debug
  56. // var content = new StreamReader(ms).ReadToEnd();
  57. // Console.WriteLine(boundary + "::" + content);
  58. // input.Position = 0;
  59. var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
  60. HttpMultipart.Element e;
  61. while ((e = multi_part.ReadNextElement()) != null)
  62. {
  63. if (e.Filename == null)
  64. {
  65. byte[] copy = new byte[e.Length];
  66. input.Position = e.Start;
  67. input.Read(copy, 0, (int)e.Length);
  68. form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length));
  69. }
  70. else
  71. {
  72. // We use a substream, as in 2.x we will support large uploads streamed to disk,
  73. var sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
  74. files[e.Name] = sub;
  75. }
  76. }
  77. }
  78. }
  79. public async Task<QueryParamCollection> GetFormData()
  80. {
  81. var form = new WebROCollection();
  82. files = new Dictionary<string, HttpPostedFile>();
  83. if (IsContentType("multipart/form-data", true))
  84. {
  85. await LoadMultiPart(form).ConfigureAwait(false);
  86. }
  87. else if (IsContentType("application/x-www-form-urlencoded", true))
  88. {
  89. await LoadWwwForm(form).ConfigureAwait(false);
  90. }
  91. if (validate_form && !checked_form)
  92. {
  93. checked_form = true;
  94. ValidateNameValueCollection("Form", form);
  95. }
  96. return form;
  97. }
  98. public string Accept => StringValues.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"].ToString();
  99. public string Authorization => StringValues.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"].ToString();
  100. protected bool validate_form { get; set; }
  101. protected bool checked_form { get; set; }
  102. private static void ThrowValidationException(string name, string key, string value)
  103. {
  104. string v = "\"" + value + "\"";
  105. if (v.Length > 20)
  106. {
  107. v = v.Substring(0, 16) + "...\"";
  108. }
  109. string msg = string.Format(
  110. CultureInfo.InvariantCulture,
  111. "A potentially dangerous Request.{0} value was detected from the client ({1}={2}).",
  112. name,
  113. key,
  114. v);
  115. throw new Exception(msg);
  116. }
  117. private static void ValidateNameValueCollection(string name, QueryParamCollection coll)
  118. {
  119. if (coll == null)
  120. {
  121. return;
  122. }
  123. foreach (var pair in coll)
  124. {
  125. var key = pair.Name;
  126. var val = pair.Value;
  127. if (val != null && val.Length > 0 && IsInvalidString(val))
  128. {
  129. ThrowValidationException(name, key, val);
  130. }
  131. }
  132. }
  133. internal static bool IsInvalidString(string val)
  134. => IsInvalidString(val, out var validationFailureIndex);
  135. internal static bool IsInvalidString(string val, out int validationFailureIndex)
  136. {
  137. validationFailureIndex = 0;
  138. int len = val.Length;
  139. if (len < 2)
  140. {
  141. return false;
  142. }
  143. char current = val[0];
  144. for (int idx = 1; idx < len; idx++)
  145. {
  146. char next = val[idx];
  147. // See http://secunia.com/advisories/14325
  148. if (current == '<' || current == '\xff1c')
  149. {
  150. if (next == '!' || next < ' '
  151. || (next >= 'a' && next <= 'z')
  152. || (next >= 'A' && next <= 'Z'))
  153. {
  154. validationFailureIndex = idx - 1;
  155. return true;
  156. }
  157. }
  158. else if (current == '&' && next == '#')
  159. {
  160. validationFailureIndex = idx - 1;
  161. return true;
  162. }
  163. current = next;
  164. }
  165. return false;
  166. }
  167. private bool IsContentType(string ct, bool starts_with)
  168. {
  169. if (ct == null || ContentType == null)
  170. {
  171. return false;
  172. }
  173. if (starts_with)
  174. {
  175. return ContentType.StartsWith(ct, StringComparison.OrdinalIgnoreCase);
  176. }
  177. return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
  178. }
  179. private async Task LoadWwwForm(WebROCollection form)
  180. {
  181. using (var input = InputStream)
  182. {
  183. using (var ms = new MemoryStream())
  184. {
  185. await input.CopyToAsync(ms).ConfigureAwait(false);
  186. ms.Position = 0;
  187. using (var s = new StreamReader(ms, ContentEncoding))
  188. {
  189. var key = new StringBuilder();
  190. var value = new StringBuilder();
  191. int c;
  192. while ((c = s.Read()) != -1)
  193. {
  194. if (c == '=')
  195. {
  196. value.Length = 0;
  197. while ((c = s.Read()) != -1)
  198. {
  199. if (c == '&')
  200. {
  201. AddRawKeyValue(form, key, value);
  202. break;
  203. }
  204. else
  205. {
  206. value.Append((char)c);
  207. }
  208. }
  209. if (c == -1)
  210. {
  211. AddRawKeyValue(form, key, value);
  212. return;
  213. }
  214. }
  215. else if (c == '&')
  216. {
  217. AddRawKeyValue(form, key, value);
  218. }
  219. else
  220. {
  221. key.Append((char)c);
  222. }
  223. }
  224. if (c == -1)
  225. {
  226. AddRawKeyValue(form, key, value);
  227. }
  228. }
  229. }
  230. }
  231. }
  232. private static void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
  233. {
  234. form.Add(WebUtility.UrlDecode(key.ToString()), WebUtility.UrlDecode(value.ToString()));
  235. key.Length = 0;
  236. value.Length = 0;
  237. }
  238. private Dictionary<string, HttpPostedFile> files;
  239. private class WebROCollection : QueryParamCollection
  240. {
  241. public override string ToString()
  242. {
  243. var result = new StringBuilder();
  244. foreach (var pair in this)
  245. {
  246. if (result.Length > 0)
  247. {
  248. result.Append('&');
  249. }
  250. var key = pair.Name;
  251. if (key != null && key.Length > 0)
  252. {
  253. result.Append(key);
  254. result.Append('=');
  255. }
  256. result.Append(pair.Value);
  257. }
  258. return result.ToString();
  259. }
  260. }
  261. private class HttpMultipart
  262. {
  263. public class Element
  264. {
  265. public string ContentType { get; set; }
  266. public string Name { get; set; }
  267. public string Filename { get; set; }
  268. public Encoding Encoding { get; set; }
  269. public long Start { get; set; }
  270. public long Length { get; set; }
  271. public override string ToString()
  272. {
  273. return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
  274. Start.ToString(CultureInfo.CurrentCulture) + ", Length " + Length.ToString(CultureInfo.CurrentCulture);
  275. }
  276. }
  277. private const byte LF = (byte)'\n';
  278. private const byte CR = (byte)'\r';
  279. private Stream data;
  280. private string boundary;
  281. private byte[] boundaryBytes;
  282. private byte[] buffer;
  283. private bool atEof;
  284. private Encoding encoding;
  285. private StringBuilder sb;
  286. // See RFC 2046
  287. // In the case of multipart entities, in which one or more different
  288. // sets of data are combined in a single body, a "multipart" media type
  289. // field must appear in the entity's header. The body must then contain
  290. // one or more body parts, each preceded by a boundary delimiter line,
  291. // and the last one followed by a closing boundary delimiter line.
  292. // After its boundary delimiter line, each body part then consists of a
  293. // header area, a blank line, and a body area. Thus a body part is
  294. // similar to an RFC 822 message in syntax, but different in meaning.
  295. public HttpMultipart(Stream data, string b, Encoding encoding)
  296. {
  297. this.data = data;
  298. boundary = b;
  299. boundaryBytes = encoding.GetBytes(b);
  300. buffer = new byte[boundaryBytes.Length + 2]; // CRLF or '--'
  301. this.encoding = encoding;
  302. sb = new StringBuilder();
  303. }
  304. public Element ReadNextElement()
  305. {
  306. if (atEof || ReadBoundary())
  307. {
  308. return null;
  309. }
  310. var elem = new Element();
  311. string header;
  312. while ((header = ReadHeaders()) != null)
  313. {
  314. if (header.StartsWith("Content-Disposition:", StringComparison.OrdinalIgnoreCase))
  315. {
  316. elem.Name = GetContentDispositionAttribute(header, "name");
  317. elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
  318. }
  319. else if (header.StartsWith("Content-Type:", StringComparison.OrdinalIgnoreCase))
  320. {
  321. elem.ContentType = header.Substring("Content-Type:".Length).Trim();
  322. elem.Encoding = GetEncoding(elem.ContentType);
  323. }
  324. }
  325. long start = data.Position;
  326. elem.Start = start;
  327. long pos = MoveToNextBoundary();
  328. if (pos == -1)
  329. {
  330. return null;
  331. }
  332. elem.Length = pos - start;
  333. return elem;
  334. }
  335. private string ReadLine()
  336. {
  337. // CRLF or LF are ok as line endings.
  338. bool got_cr = false;
  339. int b = 0;
  340. sb.Length = 0;
  341. while (true)
  342. {
  343. b = data.ReadByte();
  344. if (b == -1)
  345. {
  346. return null;
  347. }
  348. if (b == LF)
  349. {
  350. break;
  351. }
  352. got_cr = b == CR;
  353. sb.Append((char)b);
  354. }
  355. if (got_cr)
  356. {
  357. sb.Length--;
  358. }
  359. return sb.ToString();
  360. }
  361. private static string GetContentDispositionAttribute(string l, string name)
  362. {
  363. int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal);
  364. if (idx < 0)
  365. {
  366. return null;
  367. }
  368. int begin = idx + name.Length + "=\"".Length;
  369. int end = l.IndexOf('"', begin);
  370. if (end < 0)
  371. {
  372. return null;
  373. }
  374. if (begin == end)
  375. {
  376. return string.Empty;
  377. }
  378. return l.Substring(begin, end - begin);
  379. }
  380. private string GetContentDispositionAttributeWithEncoding(string l, string name)
  381. {
  382. int idx = l.IndexOf(name + "=\"", StringComparison.Ordinal);
  383. if (idx < 0)
  384. {
  385. return null;
  386. }
  387. int begin = idx + name.Length + "=\"".Length;
  388. int end = l.IndexOf('"', begin);
  389. if (end < 0)
  390. {
  391. return null;
  392. }
  393. if (begin == end)
  394. {
  395. return string.Empty;
  396. }
  397. string temp = l.Substring(begin, end - begin);
  398. byte[] source = new byte[temp.Length];
  399. for (int i = temp.Length - 1; i >= 0; i--)
  400. {
  401. source[i] = (byte)temp[i];
  402. }
  403. return encoding.GetString(source, 0, source.Length);
  404. }
  405. private bool ReadBoundary()
  406. {
  407. try
  408. {
  409. string line;
  410. do
  411. {
  412. line = ReadLine();
  413. }
  414. while (line.Length == 0);
  415. if (line[0] != '-' || line[1] != '-')
  416. {
  417. return false;
  418. }
  419. if (!line.EndsWith(boundary, StringComparison.Ordinal))
  420. {
  421. return true;
  422. }
  423. }
  424. catch
  425. {
  426. }
  427. return false;
  428. }
  429. private string ReadHeaders()
  430. {
  431. string s = ReadLine();
  432. if (s.Length == 0)
  433. {
  434. return null;
  435. }
  436. return s;
  437. }
  438. private static bool CompareBytes(byte[] orig, byte[] other)
  439. {
  440. for (int i = orig.Length - 1; i >= 0; i--)
  441. {
  442. if (orig[i] != other[i])
  443. {
  444. return false;
  445. }
  446. }
  447. return true;
  448. }
  449. private long MoveToNextBoundary()
  450. {
  451. long retval = 0;
  452. bool got_cr = false;
  453. int state = 0;
  454. int c = data.ReadByte();
  455. while (true)
  456. {
  457. if (c == -1)
  458. {
  459. return -1;
  460. }
  461. if (state == 0 && c == LF)
  462. {
  463. retval = data.Position - 1;
  464. if (got_cr)
  465. {
  466. retval--;
  467. }
  468. state = 1;
  469. c = data.ReadByte();
  470. }
  471. else if (state == 0)
  472. {
  473. got_cr = c == CR;
  474. c = data.ReadByte();
  475. }
  476. else if (state == 1 && c == '-')
  477. {
  478. c = data.ReadByte();
  479. if (c == -1)
  480. {
  481. return -1;
  482. }
  483. if (c != '-')
  484. {
  485. state = 0;
  486. got_cr = false;
  487. continue; // no ReadByte() here
  488. }
  489. int nread = data.Read(buffer, 0, buffer.Length);
  490. int bl = buffer.Length;
  491. if (nread != bl)
  492. {
  493. return -1;
  494. }
  495. if (!CompareBytes(boundaryBytes, buffer))
  496. {
  497. state = 0;
  498. data.Position = retval + 2;
  499. if (got_cr)
  500. {
  501. data.Position++;
  502. got_cr = false;
  503. }
  504. c = data.ReadByte();
  505. continue;
  506. }
  507. if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
  508. {
  509. atEof = true;
  510. }
  511. else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
  512. {
  513. state = 0;
  514. data.Position = retval + 2;
  515. if (got_cr)
  516. {
  517. data.Position++;
  518. got_cr = false;
  519. }
  520. c = data.ReadByte();
  521. continue;
  522. }
  523. data.Position = retval + 2;
  524. if (got_cr)
  525. {
  526. data.Position++;
  527. }
  528. break;
  529. }
  530. else
  531. {
  532. // state == 1
  533. state = 0; // no ReadByte() here
  534. }
  535. }
  536. return retval;
  537. }
  538. private static string StripPath(string path)
  539. {
  540. if (path == null || path.Length == 0)
  541. {
  542. return path;
  543. }
  544. if (path.IndexOf(":\\", StringComparison.Ordinal) != 1
  545. && !path.StartsWith("\\\\", StringComparison.Ordinal))
  546. {
  547. return path;
  548. }
  549. return path.Substring(path.LastIndexOf('\\') + 1);
  550. }
  551. }
  552. }
  553. }