MonoWebRequestHandler.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. //
  2. // MonoWebRequestHandler.cs
  3. //
  4. // Authors:
  5. // Marek Safar <[email protected]>
  6. // Martin Baulig <[email protected]>
  7. //
  8. // Copyright (C) 2011-2018 Xamarin Inc (http://www.xamarin.com)
  9. //
  10. // Permission is hereby granted, free of charge, to any person obtaining
  11. // a copy of this software and associated documentation files (the
  12. // "Software"), to deal in the Software without restriction, including
  13. // without limitation the rights to use, copy, modify, merge, publish,
  14. // distribute, sublicense, and/or sell copies of the Software, and to
  15. // permit persons to whom the Software is furnished to do so, subject to
  16. // the following conditions:
  17. //
  18. // The above copyright notice and this permission notice shall be
  19. // included in all copies or substantial portions of the Software.
  20. //
  21. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  22. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  23. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  24. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  25. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  26. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  27. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  28. //
  29. using System.Collections.Generic;
  30. using System.Security.Authentication;
  31. using System.Security.Cryptography.X509Certificates;
  32. using System.Security.Principal;
  33. using System.Threading;
  34. using System.Threading.Tasks;
  35. using System.Collections.Specialized;
  36. using System.Net.Http.Headers;
  37. using System.Net.Cache;
  38. using System.Net.Security;
  39. using System.Linq;
  40. namespace System.Net.Http
  41. {
  42. class MonoWebRequestHandler : IMonoHttpClientHandler
  43. {
  44. static long groupCounter;
  45. bool allowAutoRedirect;
  46. DecompressionMethods automaticDecompression;
  47. CookieContainer cookieContainer;
  48. ICredentials credentials;
  49. int maxAutomaticRedirections;
  50. long maxRequestContentBufferSize;
  51. bool preAuthenticate;
  52. IWebProxy proxy;
  53. bool useCookies;
  54. bool useProxy;
  55. SslClientAuthenticationOptions sslOptions;
  56. bool allowPipelining;
  57. RequestCachePolicy cachePolicy;
  58. AuthenticationLevel authenticationLevel;
  59. TimeSpan continueTimeout;
  60. TokenImpersonationLevel impersonationLevel;
  61. int maxResponseHeadersLength;
  62. int readWriteTimeout;
  63. RemoteCertificateValidationCallback serverCertificateValidationCallback;
  64. bool unsafeAuthenticatedConnectionSharing;
  65. bool sentRequest;
  66. string connectionGroupName;
  67. bool disposed;
  68. internal MonoWebRequestHandler ()
  69. {
  70. allowAutoRedirect = true;
  71. maxAutomaticRedirections = 50;
  72. maxRequestContentBufferSize = int.MaxValue;
  73. useCookies = true;
  74. useProxy = true;
  75. allowPipelining = true;
  76. authenticationLevel = AuthenticationLevel.MutualAuthRequested;
  77. cachePolicy = System.Net.WebRequest.DefaultCachePolicy;
  78. continueTimeout = TimeSpan.FromMilliseconds (350);
  79. impersonationLevel = TokenImpersonationLevel.Delegation;
  80. maxResponseHeadersLength = HttpWebRequest.DefaultMaximumResponseHeadersLength;
  81. readWriteTimeout = 300000;
  82. serverCertificateValidationCallback = null;
  83. unsafeAuthenticatedConnectionSharing = false;
  84. connectionGroupName = "HttpClientHandler" + Interlocked.Increment (ref groupCounter);
  85. }
  86. internal void EnsureModifiability ()
  87. {
  88. if (sentRequest)
  89. throw new InvalidOperationException (
  90. "This instance has already started one or more requests. " +
  91. "Properties can only be modified before sending the first request.");
  92. }
  93. public bool AllowAutoRedirect {
  94. get {
  95. return allowAutoRedirect;
  96. }
  97. set {
  98. EnsureModifiability ();
  99. allowAutoRedirect = value;
  100. }
  101. }
  102. public DecompressionMethods AutomaticDecompression {
  103. get {
  104. return automaticDecompression;
  105. }
  106. set {
  107. EnsureModifiability ();
  108. automaticDecompression = value;
  109. }
  110. }
  111. public CookieContainer CookieContainer {
  112. get {
  113. return cookieContainer ?? (cookieContainer = new CookieContainer ());
  114. }
  115. set {
  116. EnsureModifiability ();
  117. cookieContainer = value;
  118. }
  119. }
  120. public ICredentials Credentials {
  121. get {
  122. return credentials;
  123. }
  124. set {
  125. EnsureModifiability ();
  126. credentials = value;
  127. }
  128. }
  129. public int MaxAutomaticRedirections {
  130. get {
  131. return maxAutomaticRedirections;
  132. }
  133. set {
  134. EnsureModifiability ();
  135. if (value <= 0)
  136. throw new ArgumentOutOfRangeException ();
  137. maxAutomaticRedirections = value;
  138. }
  139. }
  140. public long MaxRequestContentBufferSize {
  141. get {
  142. return maxRequestContentBufferSize;
  143. }
  144. set {
  145. EnsureModifiability ();
  146. if (value < 0)
  147. throw new ArgumentOutOfRangeException ();
  148. maxRequestContentBufferSize = value;
  149. }
  150. }
  151. public bool PreAuthenticate {
  152. get {
  153. return preAuthenticate;
  154. }
  155. set {
  156. EnsureModifiability ();
  157. preAuthenticate = value;
  158. }
  159. }
  160. public IWebProxy Proxy {
  161. get {
  162. return proxy;
  163. }
  164. set {
  165. EnsureModifiability ();
  166. if (!UseProxy)
  167. throw new InvalidOperationException ();
  168. proxy = value;
  169. }
  170. }
  171. public virtual bool SupportsAutomaticDecompression {
  172. get {
  173. return true;
  174. }
  175. }
  176. public virtual bool SupportsProxy {
  177. get {
  178. return true;
  179. }
  180. }
  181. public virtual bool SupportsRedirectConfiguration {
  182. get {
  183. return true;
  184. }
  185. }
  186. public bool UseCookies {
  187. get {
  188. return useCookies;
  189. }
  190. set {
  191. EnsureModifiability ();
  192. useCookies = value;
  193. }
  194. }
  195. public bool UseProxy {
  196. get {
  197. return useProxy;
  198. }
  199. set {
  200. EnsureModifiability ();
  201. useProxy = value;
  202. }
  203. }
  204. public bool AllowPipelining {
  205. get { return allowPipelining; }
  206. set {
  207. EnsureModifiability ();
  208. allowPipelining = value;
  209. }
  210. }
  211. public RequestCachePolicy CachePolicy {
  212. get { return cachePolicy; }
  213. set {
  214. EnsureModifiability ();
  215. cachePolicy = value;
  216. }
  217. }
  218. public AuthenticationLevel AuthenticationLevel {
  219. get { return authenticationLevel; }
  220. set {
  221. EnsureModifiability ();
  222. authenticationLevel = value;
  223. }
  224. }
  225. [MonoTODO]
  226. public TimeSpan ContinueTimeout {
  227. get { return continueTimeout; }
  228. set {
  229. EnsureModifiability ();
  230. continueTimeout = value;
  231. }
  232. }
  233. public TokenImpersonationLevel ImpersonationLevel {
  234. get { return impersonationLevel; }
  235. set {
  236. EnsureModifiability ();
  237. impersonationLevel = value;
  238. }
  239. }
  240. public int MaxResponseHeadersLength {
  241. get { return maxResponseHeadersLength; }
  242. set {
  243. EnsureModifiability ();
  244. maxResponseHeadersLength = value;
  245. }
  246. }
  247. public int ReadWriteTimeout {
  248. get { return readWriteTimeout; }
  249. set {
  250. EnsureModifiability ();
  251. readWriteTimeout = value;
  252. }
  253. }
  254. public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
  255. get { return serverCertificateValidationCallback; }
  256. set {
  257. EnsureModifiability ();
  258. serverCertificateValidationCallback = value;
  259. }
  260. }
  261. public bool UnsafeAuthenticatedConnectionSharing {
  262. get { return unsafeAuthenticatedConnectionSharing; }
  263. set {
  264. EnsureModifiability ();
  265. unsafeAuthenticatedConnectionSharing = value;
  266. }
  267. }
  268. public SslClientAuthenticationOptions SslOptions {
  269. get => sslOptions ?? (sslOptions = new SslClientAuthenticationOptions ());
  270. set {
  271. EnsureModifiability ();
  272. sslOptions = value;
  273. }
  274. }
  275. public void Dispose ()
  276. {
  277. Dispose (true);
  278. }
  279. protected virtual void Dispose (bool disposing)
  280. {
  281. if (disposing && !disposed) {
  282. Volatile.Write (ref disposed, true);
  283. ServicePointManager.CloseConnectionGroup (connectionGroupName);
  284. }
  285. }
  286. bool GetConnectionKeepAlive (HttpRequestHeaders headers)
  287. {
  288. return headers.Connection.Any (l => string.Equals (l, "Keep-Alive", StringComparison.OrdinalIgnoreCase));
  289. }
  290. internal virtual HttpWebRequest CreateWebRequest (HttpRequestMessage request)
  291. {
  292. var wr = new HttpWebRequest (request.RequestUri);
  293. wr.ThrowOnError = false;
  294. wr.AllowWriteStreamBuffering = false;
  295. if (request.Version == HttpVersion.Version20)
  296. wr.ProtocolVersion = HttpVersion.Version11;
  297. else
  298. wr.ProtocolVersion = request.Version;
  299. wr.ConnectionGroupName = connectionGroupName;
  300. wr.Method = request.Method.Method;
  301. if (wr.ProtocolVersion == HttpVersion.Version10) {
  302. wr.KeepAlive = GetConnectionKeepAlive (request.Headers);
  303. } else {
  304. wr.KeepAlive = request.Headers.ConnectionClose != true;
  305. }
  306. if (allowAutoRedirect) {
  307. wr.AllowAutoRedirect = true;
  308. wr.MaximumAutomaticRedirections = maxAutomaticRedirections;
  309. } else {
  310. wr.AllowAutoRedirect = false;
  311. }
  312. wr.AutomaticDecompression = automaticDecompression;
  313. wr.PreAuthenticate = preAuthenticate;
  314. if (useCookies) {
  315. // It cannot be null or allowAutoRedirect won't work
  316. wr.CookieContainer = CookieContainer;
  317. }
  318. wr.Credentials = credentials;
  319. if (useProxy) {
  320. wr.Proxy = proxy;
  321. } else {
  322. // Disables default WebRequest.DefaultWebProxy value
  323. wr.Proxy = null;
  324. }
  325. wr.ServicePoint.Expect100Continue = request.Headers.ExpectContinue == true;
  326. // Add request headers
  327. var headers = wr.Headers;
  328. foreach (var header in request.Headers) {
  329. var values = header.Value;
  330. if (header.Key == "Host") {
  331. //
  332. // Host must be explicitly set for HttpWebRequest
  333. //
  334. wr.Host = request.Headers.Host;
  335. continue;
  336. }
  337. if (header.Key == "Transfer-Encoding") {
  338. //
  339. // Chunked Transfer-Encoding is set for HttpWebRequest later when Content length is checked
  340. //
  341. values = values.Where (l => l != "chunked");
  342. }
  343. var values_formated = HeaderUtils.GetSingleHeaderString (header.Key, values);
  344. if (values_formated == null)
  345. continue;
  346. headers.AddInternal (header.Key, values_formated);
  347. }
  348. return wr;
  349. }
  350. HttpResponseMessage CreateResponseMessage (HttpWebResponse wr, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
  351. {
  352. var response = new HttpResponseMessage (wr.StatusCode);
  353. response.RequestMessage = requestMessage;
  354. response.ReasonPhrase = wr.StatusDescription;
  355. #if LEGACY_HTTPCLIENT
  356. response.Content = new StreamContent (wr.GetResponseStream (), cancellationToken);
  357. #else
  358. response.Content = new StreamContent (wr.GetResponseStream ());
  359. #endif
  360. var headers = wr.Headers;
  361. for (int i = 0; i < headers.Count; ++i) {
  362. var key = headers.GetKey (i);
  363. var value = headers.GetValues (i);
  364. HttpHeaders item_headers;
  365. if (HeaderUtils.IsContentHeader (key))
  366. item_headers = response.Content.Headers;
  367. else
  368. item_headers = response.Headers;
  369. item_headers.TryAddWithoutValidation (key, value);
  370. }
  371. requestMessage.RequestUri = wr.ResponseUri;
  372. return response;
  373. }
  374. static bool MethodHasBody (HttpMethod method)
  375. {
  376. switch (method.Method) {
  377. case "HEAD":
  378. case "GET":
  379. case "MKCOL":
  380. case "CONNECT":
  381. case "TRACE":
  382. return false;
  383. default:
  384. return true;
  385. }
  386. }
  387. public async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
  388. {
  389. if (disposed)
  390. throw new ObjectDisposedException (GetType ().ToString ());
  391. Volatile.Write (ref sentRequest, true);
  392. var wrequest = CreateWebRequest (request);
  393. HttpWebResponse wresponse = null;
  394. try {
  395. using (cancellationToken.Register (l => ((HttpWebRequest)l).Abort (), wrequest)) {
  396. var content = request.Content;
  397. if (content != null) {
  398. var headers = wrequest.Headers;
  399. foreach (var header in content.Headers) {
  400. foreach (var value in header.Value) {
  401. headers.AddInternal (header.Key, value);
  402. }
  403. }
  404. if (request.Headers.TransferEncodingChunked == true) {
  405. wrequest.SendChunked = true;
  406. } else {
  407. //
  408. // Content length has to be set because HttpWebRequest is running without buffering
  409. //
  410. var contentLength = content.Headers.ContentLength;
  411. if (contentLength != null) {
  412. wrequest.ContentLength = contentLength.Value;
  413. } else {
  414. if (MaxRequestContentBufferSize == 0)
  415. throw new InvalidOperationException ("The content length of the request content can't be determined. Either set TransferEncodingChunked to true, load content into buffer, or set MaxRequestContentBufferSize.");
  416. await content.LoadIntoBufferAsync (MaxRequestContentBufferSize).ConfigureAwait (false);
  417. wrequest.ContentLength = content.Headers.ContentLength.Value;
  418. }
  419. }
  420. wrequest.ResendContentFactory = content.CopyToAsync;
  421. using (var stream = await wrequest.GetRequestStreamAsync ().ConfigureAwait (false)) {
  422. await request.Content.CopyToAsync (stream).ConfigureAwait (false);
  423. }
  424. } else if (MethodHasBody (request.Method)) {
  425. // Explicitly set this to make sure we're sending a "Content-Length: 0" header.
  426. // This fixes the issue that's been reported on the forums:
  427. // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
  428. wrequest.ContentLength = 0;
  429. }
  430. wresponse = (HttpWebResponse)await wrequest.GetResponseAsync ().ConfigureAwait (false);
  431. }
  432. } catch (WebException we) {
  433. if (we.Status != WebExceptionStatus.RequestCanceled)
  434. throw new HttpRequestException ("An error occurred while sending the request", we);
  435. } catch (System.IO.IOException ex) {
  436. throw new HttpRequestException ("An error occurred while sending the request", ex);
  437. }
  438. if (cancellationToken.IsCancellationRequested) {
  439. var cancelled = new TaskCompletionSource<HttpResponseMessage> ();
  440. cancelled.SetCanceled ();
  441. return await cancelled.Task;
  442. }
  443. return CreateResponseMessage (wresponse, request, cancellationToken);
  444. }
  445. public ICredentials DefaultProxyCredentials {
  446. get {
  447. throw new NotImplementedException ();
  448. }
  449. set {
  450. throw new NotImplementedException ();
  451. }
  452. }
  453. public int MaxConnectionsPerServer {
  454. get {
  455. throw new NotImplementedException ();
  456. }
  457. set {
  458. throw new NotImplementedException ();
  459. }
  460. }
  461. public IDictionary<string, object> Properties {
  462. get {
  463. throw new NotImplementedException ();
  464. }
  465. }
  466. }
  467. }