2
0

RequestMono.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Specialized;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.Net;
  7. using System.Text;
  8. using System.Threading.Tasks;
  9. using MediaBrowser.Model.Services;
  10. namespace EmbyServer.SocketSharp
  11. {
  12. public partial class WebSocketSharpRequest : IHttpRequest
  13. {
  14. static internal string GetParameter(string header, string attr)
  15. {
  16. int ap = header.IndexOf(attr);
  17. if (ap == -1)
  18. return null;
  19. ap += attr.Length;
  20. if (ap >= header.Length)
  21. return null;
  22. char ending = header[ap];
  23. if (ending != '"')
  24. ending = ' ';
  25. int end = header.IndexOf(ending, ap + 1);
  26. if (end == -1)
  27. return ending == '"' ? null : header.Substring(ap);
  28. return header.Substring(ap + 1, end - ap - 1);
  29. }
  30. async Task LoadMultiPart(WebROCollection form)
  31. {
  32. string boundary = GetParameter(ContentType, "; boundary=");
  33. if (boundary == null)
  34. return;
  35. using (var requestStream = InputStream)
  36. {
  37. //DB: 30/01/11 - Hack to get around non-seekable stream and received HTTP request
  38. //Not ending with \r\n?
  39. var ms = new MemoryStream(32 * 1024);
  40. await requestStream.CopyToAsync(ms).ConfigureAwait(false);
  41. var input = ms;
  42. ms.WriteByte((byte)'\r');
  43. ms.WriteByte((byte)'\n');
  44. input.Position = 0;
  45. //Uncomment to debug
  46. //var content = new StreamReader(ms).ReadToEnd();
  47. //Console.WriteLine(boundary + "::" + content);
  48. //input.Position = 0;
  49. var multi_part = new HttpMultipart(input, boundary, ContentEncoding);
  50. HttpMultipart.Element e;
  51. while ((e = multi_part.ReadNextElement()) != null)
  52. {
  53. if (e.Filename == null)
  54. {
  55. byte[] copy = new byte[e.Length];
  56. input.Position = e.Start;
  57. input.Read(copy, 0, (int)e.Length);
  58. form.Add(e.Name, (e.Encoding ?? ContentEncoding).GetString(copy, 0, copy.Length));
  59. }
  60. else
  61. {
  62. //
  63. // We use a substream, as in 2.x we will support large uploads streamed to disk,
  64. //
  65. HttpPostedFile sub = new HttpPostedFile(e.Filename, e.ContentType, input, e.Start, e.Length);
  66. files[e.Name] = sub;
  67. }
  68. }
  69. }
  70. }
  71. public async Task<QueryParamCollection> GetFormData()
  72. {
  73. var form = new WebROCollection();
  74. files = new Dictionary<string, HttpPostedFile>();
  75. if (IsContentType("multipart/form-data", true))
  76. {
  77. await LoadMultiPart(form).ConfigureAwait(false);
  78. }
  79. else if (IsContentType("application/x-www-form-urlencoded", true))
  80. {
  81. await LoadWwwForm(form).ConfigureAwait(false);
  82. }
  83. #if NET_4_0
  84. if (validateRequestNewMode && !checked_form) {
  85. // Setting this before calling the validator prevents
  86. // possible endless recursion
  87. checked_form = true;
  88. ValidateNameValueCollection ("Form", query_string_nvc, RequestValidationSource.Form);
  89. } else
  90. #endif
  91. if (validate_form && !checked_form)
  92. {
  93. checked_form = true;
  94. ValidateNameValueCollection("Form", form);
  95. }
  96. return form;
  97. }
  98. public string Accept
  99. {
  100. get
  101. {
  102. return string.IsNullOrEmpty(request.Headers["Accept"]) ? null : request.Headers["Accept"];
  103. }
  104. }
  105. public string Authorization
  106. {
  107. get
  108. {
  109. return string.IsNullOrEmpty(request.Headers["Authorization"]) ? null : request.Headers["Authorization"];
  110. }
  111. }
  112. protected bool validate_cookies, validate_query_string, validate_form;
  113. protected bool checked_cookies, checked_query_string, checked_form;
  114. static void ThrowValidationException(string name, string key, string value)
  115. {
  116. string v = "\"" + value + "\"";
  117. if (v.Length > 20)
  118. v = v.Substring(0, 16) + "...\"";
  119. string msg = String.Format("A potentially dangerous Request.{0} value was " +
  120. "detected from the client ({1}={2}).", name, key, v);
  121. throw new Exception(msg);
  122. }
  123. static void ValidateNameValueCollection(string name, QueryParamCollection coll)
  124. {
  125. if (coll == null)
  126. return;
  127. foreach (var pair in coll)
  128. {
  129. var key = pair.Name;
  130. var val = pair.Value;
  131. if (val != null && val.Length > 0 && IsInvalidString(val))
  132. ThrowValidationException(name, key, val);
  133. }
  134. }
  135. internal static bool IsInvalidString(string val)
  136. {
  137. int validationFailureIndex;
  138. return IsInvalidString(val, out validationFailureIndex);
  139. }
  140. internal static bool IsInvalidString(string val, out int validationFailureIndex)
  141. {
  142. validationFailureIndex = 0;
  143. int len = val.Length;
  144. if (len < 2)
  145. return false;
  146. char current = val[0];
  147. for (int idx = 1; idx < len; idx++)
  148. {
  149. char next = val[idx];
  150. // See http://secunia.com/advisories/14325
  151. if (current == '<' || current == '\xff1c')
  152. {
  153. if (next == '!' || next < ' '
  154. || (next >= 'a' && next <= 'z')
  155. || (next >= 'A' && next <= 'Z'))
  156. {
  157. validationFailureIndex = idx - 1;
  158. return true;
  159. }
  160. }
  161. else if (current == '&' && next == '#')
  162. {
  163. validationFailureIndex = idx - 1;
  164. return true;
  165. }
  166. current = next;
  167. }
  168. return false;
  169. }
  170. public void ValidateInput()
  171. {
  172. validate_cookies = true;
  173. validate_query_string = true;
  174. validate_form = true;
  175. }
  176. bool IsContentType(string ct, bool starts_with)
  177. {
  178. if (ct == null || ContentType == null) return false;
  179. if (starts_with)
  180. return StrUtils.StartsWith(ContentType, ct, true);
  181. return string.Equals(ContentType, ct, StringComparison.OrdinalIgnoreCase);
  182. }
  183. async Task LoadWwwForm(WebROCollection form)
  184. {
  185. using (Stream input = InputStream)
  186. {
  187. using (var ms = new MemoryStream())
  188. {
  189. await input.CopyToAsync(ms).ConfigureAwait(false);
  190. ms.Position = 0;
  191. using (StreamReader s = new StreamReader(ms, ContentEncoding))
  192. {
  193. StringBuilder key = new StringBuilder();
  194. StringBuilder value = new StringBuilder();
  195. int c;
  196. while ((c = s.Read()) != -1)
  197. {
  198. if (c == '=')
  199. {
  200. value.Length = 0;
  201. while ((c = s.Read()) != -1)
  202. {
  203. if (c == '&')
  204. {
  205. AddRawKeyValue(form, key, value);
  206. break;
  207. }
  208. else
  209. value.Append((char)c);
  210. }
  211. if (c == -1)
  212. {
  213. AddRawKeyValue(form, key, value);
  214. return;
  215. }
  216. }
  217. else if (c == '&')
  218. AddRawKeyValue(form, key, value);
  219. else
  220. key.Append((char)c);
  221. }
  222. if (c == -1)
  223. AddRawKeyValue(form, key, value);
  224. }
  225. }
  226. }
  227. }
  228. void AddRawKeyValue(WebROCollection form, StringBuilder key, StringBuilder value)
  229. {
  230. string decodedKey = WebUtility.UrlDecode(key.ToString());
  231. form.Add(decodedKey,
  232. WebUtility.UrlDecode(value.ToString()));
  233. key.Length = 0;
  234. value.Length = 0;
  235. }
  236. Dictionary<string, HttpPostedFile> files;
  237. class WebROCollection : QueryParamCollection
  238. {
  239. public override string ToString()
  240. {
  241. StringBuilder result = new StringBuilder();
  242. foreach (var pair in this)
  243. {
  244. if (result.Length > 0)
  245. result.Append('&');
  246. var key = pair.Name;
  247. if (key != null && key.Length > 0)
  248. {
  249. result.Append(key);
  250. result.Append('=');
  251. }
  252. result.Append(pair.Value);
  253. }
  254. return result.ToString();
  255. }
  256. }
  257. public sealed class HttpPostedFile
  258. {
  259. string name;
  260. string content_type;
  261. Stream stream;
  262. class ReadSubStream : Stream
  263. {
  264. Stream s;
  265. long offset;
  266. long end;
  267. long position;
  268. public ReadSubStream(Stream s, long offset, long length)
  269. {
  270. this.s = s;
  271. this.offset = offset;
  272. this.end = offset + length;
  273. position = offset;
  274. }
  275. public override void Flush()
  276. {
  277. }
  278. public override int Read(byte[] buffer, int dest_offset, int count)
  279. {
  280. if (buffer == null)
  281. throw new ArgumentNullException("buffer");
  282. if (dest_offset < 0)
  283. throw new ArgumentOutOfRangeException("dest_offset", "< 0");
  284. if (count < 0)
  285. throw new ArgumentOutOfRangeException("count", "< 0");
  286. int len = buffer.Length;
  287. if (dest_offset > len)
  288. throw new ArgumentException("destination offset is beyond array size");
  289. // reordered to avoid possible integer overflow
  290. if (dest_offset > len - count)
  291. throw new ArgumentException("Reading would overrun buffer");
  292. if (count > end - position)
  293. count = (int)(end - position);
  294. if (count <= 0)
  295. return 0;
  296. s.Position = position;
  297. int result = s.Read(buffer, dest_offset, count);
  298. if (result > 0)
  299. position += result;
  300. else
  301. position = end;
  302. return result;
  303. }
  304. public override int ReadByte()
  305. {
  306. if (position >= end)
  307. return -1;
  308. s.Position = position;
  309. int result = s.ReadByte();
  310. if (result < 0)
  311. position = end;
  312. else
  313. position++;
  314. return result;
  315. }
  316. public override long Seek(long d, SeekOrigin origin)
  317. {
  318. long real;
  319. switch (origin)
  320. {
  321. case SeekOrigin.Begin:
  322. real = offset + d;
  323. break;
  324. case SeekOrigin.End:
  325. real = end + d;
  326. break;
  327. case SeekOrigin.Current:
  328. real = position + d;
  329. break;
  330. default:
  331. throw new ArgumentException();
  332. }
  333. long virt = real - offset;
  334. if (virt < 0 || virt > Length)
  335. throw new ArgumentException();
  336. position = s.Seek(real, SeekOrigin.Begin);
  337. return position;
  338. }
  339. public override void SetLength(long value)
  340. {
  341. throw new NotSupportedException();
  342. }
  343. public override void Write(byte[] buffer, int offset, int count)
  344. {
  345. throw new NotSupportedException();
  346. }
  347. public override bool CanRead
  348. {
  349. get { return true; }
  350. }
  351. public override bool CanSeek
  352. {
  353. get { return true; }
  354. }
  355. public override bool CanWrite
  356. {
  357. get { return false; }
  358. }
  359. public override long Length
  360. {
  361. get { return end - offset; }
  362. }
  363. public override long Position
  364. {
  365. get
  366. {
  367. return position - offset;
  368. }
  369. set
  370. {
  371. if (value > Length)
  372. throw new ArgumentOutOfRangeException();
  373. position = Seek(value, SeekOrigin.Begin);
  374. }
  375. }
  376. }
  377. internal HttpPostedFile(string name, string content_type, Stream base_stream, long offset, long length)
  378. {
  379. this.name = name;
  380. this.content_type = content_type;
  381. this.stream = new ReadSubStream(base_stream, offset, length);
  382. }
  383. public string ContentType
  384. {
  385. get
  386. {
  387. return content_type;
  388. }
  389. }
  390. public int ContentLength
  391. {
  392. get
  393. {
  394. return (int)stream.Length;
  395. }
  396. }
  397. public string FileName
  398. {
  399. get
  400. {
  401. return name;
  402. }
  403. }
  404. public Stream InputStream
  405. {
  406. get
  407. {
  408. return stream;
  409. }
  410. }
  411. }
  412. class Helpers
  413. {
  414. public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture;
  415. }
  416. internal sealed class StrUtils
  417. {
  418. public static bool StartsWith(string str1, string str2, bool ignore_case)
  419. {
  420. if (string.IsNullOrEmpty(str1))
  421. {
  422. return false;
  423. }
  424. var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
  425. return str1.IndexOf(str2, comparison) == 0;
  426. }
  427. public static bool EndsWith(string str1, string str2, bool ignore_case)
  428. {
  429. int l2 = str2.Length;
  430. if (l2 == 0)
  431. return true;
  432. int l1 = str1.Length;
  433. if (l2 > l1)
  434. return false;
  435. var comparison = ignore_case ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal;
  436. return str1.IndexOf(str2, comparison) == str1.Length - str2.Length - 1;
  437. }
  438. }
  439. class HttpMultipart
  440. {
  441. public class Element
  442. {
  443. public string ContentType;
  444. public string Name;
  445. public string Filename;
  446. public Encoding Encoding;
  447. public long Start;
  448. public long Length;
  449. public override string ToString()
  450. {
  451. return "ContentType " + ContentType + ", Name " + Name + ", Filename " + Filename + ", Start " +
  452. Start.ToString() + ", Length " + Length.ToString();
  453. }
  454. }
  455. Stream data;
  456. string boundary;
  457. byte[] boundary_bytes;
  458. byte[] buffer;
  459. bool at_eof;
  460. Encoding encoding;
  461. StringBuilder sb;
  462. const byte HYPHEN = (byte)'-', LF = (byte)'\n', CR = (byte)'\r';
  463. // See RFC 2046
  464. // In the case of multipart entities, in which one or more different
  465. // sets of data are combined in a single body, a "multipart" media type
  466. // field must appear in the entity's header. The body must then contain
  467. // one or more body parts, each preceded by a boundary delimiter line,
  468. // and the last one followed by a closing boundary delimiter line.
  469. // After its boundary delimiter line, each body part then consists of a
  470. // header area, a blank line, and a body area. Thus a body part is
  471. // similar to an RFC 822 message in syntax, but different in meaning.
  472. public HttpMultipart(Stream data, string b, Encoding encoding)
  473. {
  474. this.data = data;
  475. //DB: 30/01/11: cannot set or read the Position in HttpListener in Win.NET
  476. //var ms = new MemoryStream(32 * 1024);
  477. //data.CopyTo(ms);
  478. //this.data = ms;
  479. boundary = b;
  480. boundary_bytes = encoding.GetBytes(b);
  481. buffer = new byte[boundary_bytes.Length + 2]; // CRLF or '--'
  482. this.encoding = encoding;
  483. sb = new StringBuilder();
  484. }
  485. string ReadLine()
  486. {
  487. // CRLF or LF are ok as line endings.
  488. bool got_cr = false;
  489. int b = 0;
  490. sb.Length = 0;
  491. while (true)
  492. {
  493. b = data.ReadByte();
  494. if (b == -1)
  495. {
  496. return null;
  497. }
  498. if (b == LF)
  499. {
  500. break;
  501. }
  502. got_cr = b == CR;
  503. sb.Append((char)b);
  504. }
  505. if (got_cr)
  506. sb.Length--;
  507. return sb.ToString();
  508. }
  509. static string GetContentDispositionAttribute(string l, string name)
  510. {
  511. int idx = l.IndexOf(name + "=\"");
  512. if (idx < 0)
  513. return null;
  514. int begin = idx + name.Length + "=\"".Length;
  515. int end = l.IndexOf('"', begin);
  516. if (end < 0)
  517. return null;
  518. if (begin == end)
  519. return "";
  520. return l.Substring(begin, end - begin);
  521. }
  522. string GetContentDispositionAttributeWithEncoding(string l, string name)
  523. {
  524. int idx = l.IndexOf(name + "=\"");
  525. if (idx < 0)
  526. return null;
  527. int begin = idx + name.Length + "=\"".Length;
  528. int end = l.IndexOf('"', begin);
  529. if (end < 0)
  530. return null;
  531. if (begin == end)
  532. return "";
  533. string temp = l.Substring(begin, end - begin);
  534. byte[] source = new byte[temp.Length];
  535. for (int i = temp.Length - 1; i >= 0; i--)
  536. source[i] = (byte)temp[i];
  537. return encoding.GetString(source, 0, source.Length);
  538. }
  539. bool ReadBoundary()
  540. {
  541. try
  542. {
  543. string line = ReadLine();
  544. while (line == "")
  545. line = ReadLine();
  546. if (line[0] != '-' || line[1] != '-')
  547. return false;
  548. if (!StrUtils.EndsWith(line, boundary, false))
  549. return true;
  550. }
  551. catch
  552. {
  553. }
  554. return false;
  555. }
  556. string ReadHeaders()
  557. {
  558. string s = ReadLine();
  559. if (s == "")
  560. return null;
  561. return s;
  562. }
  563. bool CompareBytes(byte[] orig, byte[] other)
  564. {
  565. for (int i = orig.Length - 1; i >= 0; i--)
  566. if (orig[i] != other[i])
  567. return false;
  568. return true;
  569. }
  570. long MoveToNextBoundary()
  571. {
  572. long retval = 0;
  573. bool got_cr = false;
  574. int state = 0;
  575. int c = data.ReadByte();
  576. while (true)
  577. {
  578. if (c == -1)
  579. return -1;
  580. if (state == 0 && c == LF)
  581. {
  582. retval = data.Position - 1;
  583. if (got_cr)
  584. retval--;
  585. state = 1;
  586. c = data.ReadByte();
  587. }
  588. else if (state == 0)
  589. {
  590. got_cr = c == CR;
  591. c = data.ReadByte();
  592. }
  593. else if (state == 1 && c == '-')
  594. {
  595. c = data.ReadByte();
  596. if (c == -1)
  597. return -1;
  598. if (c != '-')
  599. {
  600. state = 0;
  601. got_cr = false;
  602. continue; // no ReadByte() here
  603. }
  604. int nread = data.Read(buffer, 0, buffer.Length);
  605. int bl = buffer.Length;
  606. if (nread != bl)
  607. return -1;
  608. if (!CompareBytes(boundary_bytes, buffer))
  609. {
  610. state = 0;
  611. data.Position = retval + 2;
  612. if (got_cr)
  613. {
  614. data.Position++;
  615. got_cr = false;
  616. }
  617. c = data.ReadByte();
  618. continue;
  619. }
  620. if (buffer[bl - 2] == '-' && buffer[bl - 1] == '-')
  621. {
  622. at_eof = true;
  623. }
  624. else if (buffer[bl - 2] != CR || buffer[bl - 1] != LF)
  625. {
  626. state = 0;
  627. data.Position = retval + 2;
  628. if (got_cr)
  629. {
  630. data.Position++;
  631. got_cr = false;
  632. }
  633. c = data.ReadByte();
  634. continue;
  635. }
  636. data.Position = retval + 2;
  637. if (got_cr)
  638. data.Position++;
  639. break;
  640. }
  641. else
  642. {
  643. // state == 1
  644. state = 0; // no ReadByte() here
  645. }
  646. }
  647. return retval;
  648. }
  649. public Element ReadNextElement()
  650. {
  651. if (at_eof || ReadBoundary())
  652. return null;
  653. Element elem = new Element();
  654. string header;
  655. while ((header = ReadHeaders()) != null)
  656. {
  657. if (StrUtils.StartsWith(header, "Content-Disposition:", true))
  658. {
  659. elem.Name = GetContentDispositionAttribute(header, "name");
  660. elem.Filename = StripPath(GetContentDispositionAttributeWithEncoding(header, "filename"));
  661. }
  662. else if (StrUtils.StartsWith(header, "Content-Type:", true))
  663. {
  664. elem.ContentType = header.Substring("Content-Type:".Length).Trim();
  665. elem.Encoding = GetEncoding(elem.ContentType);
  666. }
  667. }
  668. long start = 0;
  669. start = data.Position;
  670. elem.Start = start;
  671. long pos = MoveToNextBoundary();
  672. if (pos == -1)
  673. return null;
  674. elem.Length = pos - start;
  675. return elem;
  676. }
  677. static string StripPath(string path)
  678. {
  679. if (path == null || path.Length == 0)
  680. return path;
  681. if (path.IndexOf(":\\") != 1 && !path.StartsWith("\\\\"))
  682. return path;
  683. return path.Substring(path.LastIndexOf('\\') + 1);
  684. }
  685. }
  686. }
  687. }