Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/mono.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Köplinger <alex.koeplinger@outlook.com>2019-03-01 14:20:27 +0300
committerGitHub <noreply@github.com>2019-03-01 14:20:27 +0300
commit5bf74a995465dd54ceb4a2311c2d6041258e8fe0 (patch)
tree3eea528b285091006cd5dbf1fcc9da711a3fcd2d /mcs/class/System.Net.Http
parent6c58376102564bd5940efa8d34b162334f79b2bf (diff)
[System.Net.Http] Move NSUrlSessionHandler from xamarin-macios to mono (#13229)
* [System.Net.Http] Move NSUrlSessionHandler from xamarin-macios to mono This allows us to remove the need for a special custom System.Net.Http.dll build in the xamarin-macios repository. Part of #11397 * Bump API snapshot submodule * [csproj] Update project files
Diffstat (limited to 'mcs/class/System.Net.Http')
-rw-r--r--mcs/class/System.Net.Http/Makefile16
-rw-r--r--mcs/class/System.Net.Http/NSUrlSessionHandler.cs859
-rw-r--r--mcs/class/System.Net.Http/RuntimeOptions.cs98
-rw-r--r--mcs/class/System.Net.Http/System.Net.Http.csproj40
-rw-r--r--mcs/class/System.Net.Http/Test/System.Net.Http/HttpClientTest.cs10
-rw-r--r--mcs/class/System.Net.Http/monotouch_System.Net.Http.dll.sources5
-rw-r--r--mcs/class/System.Net.Http/monotouch_tv_System.Net.Http.dll.sources5
-rw-r--r--mcs/class/System.Net.Http/monotouch_watch_System.Net.Http.dll.sources3
8 files changed, 1033 insertions, 3 deletions
diff --git a/mcs/class/System.Net.Http/Makefile b/mcs/class/System.Net.Http/Makefile
index 5c4fb101334..bb225ced69b 100644
--- a/mcs/class/System.Net.Http/Makefile
+++ b/mcs/class/System.Net.Http/Makefile
@@ -11,6 +11,22 @@ ifeq (monodroid,$(PROFILE))
LIB_MCS_FLAGS += -d:XAMARIN_MODERN
endif
+ifneq (,$(filter monotouch monotouch_tv monotouch_watch,$(PROFILE)))
+LIB_MCS_FLAGS += -D:XAMCORE_2_0 -D:XAMARIN_MODERN -D:SYSTEM_NET_HTTP -D:UNIFIED -D:__UNIFIED__
+endif
+
+ifeq (monotouch,$(PROFILE))
+API_BIN_REFS := Xamarin.iOS
+endif
+
+ifeq (monotouch_tv,$(PROFILE))
+API_BIN_REFS := Xamarin.TVOS
+endif
+
+ifeq (monotouch_watch,$(PROFILE))
+API_BIN_REFS := Xamarin.WatchOS
+endif
+
TEST_LIB_REFS = System System.Core
TEST_MCS_FLAGS =
diff --git a/mcs/class/System.Net.Http/NSUrlSessionHandler.cs b/mcs/class/System.Net.Http/NSUrlSessionHandler.cs
new file mode 100644
index 00000000000..e48129c0877
--- /dev/null
+++ b/mcs/class/System.Net.Http/NSUrlSessionHandler.cs
@@ -0,0 +1,859 @@
+//
+// NSUrlSessionHandler.cs:
+//
+// Authors:
+// Paul Betts <paul@paulbetts.org>
+// Nick Berardi <nick@nickberardi.com>
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Text;
+
+#if UNIFIED
+using CoreFoundation;
+using Foundation;
+using Security;
+#else
+using MonoTouch.CoreFoundation;
+using MonoTouch.Foundation;
+using MonoTouch.Security;
+using System.Globalization;
+using nint = System.Int32;
+using nuint = System.UInt32;
+#endif
+
+#if !MONOMAC
+using UIKit;
+#endif
+
+#if SYSTEM_NET_HTTP
+namespace System.Net.Http {
+#else
+namespace Foundation {
+#endif
+
+ // useful extensions for the class in order to set it in a header
+ static class NSHttpCookieExtensions
+ {
+ static void AppendSegment(StringBuilder builder, string name, string value)
+ {
+ if (builder.Length > 0)
+ builder.Append ("; ");
+
+ builder.Append (name);
+ if (value != null)
+ builder.Append ("=").Append (value);
+ }
+
+ // returns the header for a cookie
+ public static string GetHeaderValue (this NSHttpCookie cookie)
+ {
+ var header = new StringBuilder();
+ AppendSegment (header, cookie.Name, cookie.Value);
+ AppendSegment (header, NSHttpCookie.KeyPath.ToString (), cookie.Path.ToString ());
+ AppendSegment (header, NSHttpCookie.KeyDomain.ToString (), cookie.Domain.ToString ());
+ AppendSegment (header, NSHttpCookie.KeyVersion.ToString (), cookie.Version.ToString ());
+
+ if (cookie.Comment != null)
+ AppendSegment (header, NSHttpCookie.KeyComment.ToString (), cookie.Comment.ToString());
+
+ if (cookie.CommentUrl != null)
+ AppendSegment (header, NSHttpCookie.KeyCommentUrl.ToString (), cookie.CommentUrl.ToString());
+
+ if (cookie.Properties.ContainsKey (NSHttpCookie.KeyDiscard))
+ AppendSegment (header, NSHttpCookie.KeyDiscard.ToString (), null);
+
+ if (cookie.ExpiresDate != null) {
+ // Format according to RFC1123; 'r' uses invariant info (DateTimeFormatInfo.InvariantInfo)
+ var dateStr = ((DateTime) cookie.ExpiresDate).ToUniversalTime ().ToString("r", CultureInfo.InvariantCulture);
+ AppendSegment (header, NSHttpCookie.KeyExpires.ToString (), dateStr);
+ }
+
+ if (cookie.Properties.ContainsKey (NSHttpCookie.KeyMaximumAge)) {
+ var timeStampString = (NSString) cookie.Properties[NSHttpCookie.KeyMaximumAge];
+ AppendSegment (header, NSHttpCookie.KeyMaximumAge.ToString (), timeStampString);
+ }
+
+ if (cookie.IsSecure)
+ AppendSegment (header, NSHttpCookie.KeySecure.ToString(), null);
+
+ if (cookie.IsHttpOnly)
+ AppendSegment (header, "httponly", null); // Apple does not show the key for the httponly
+
+ return header.ToString ();
+ }
+ }
+
+ public partial class NSUrlSessionHandler : HttpMessageHandler
+ {
+ private const string SetCookie = "Set-Cookie";
+ readonly Dictionary<string, string> headerSeparators = new Dictionary<string, string> {
+ ["User-Agent"] = " ",
+ ["Server"] = " "
+ };
+
+ readonly NSUrlSession session;
+ readonly Dictionary<NSUrlSessionTask, InflightData> inflightRequests;
+ readonly object inflightRequestsLock = new object ();
+#if !MONOMAC && !MONOTOUCH_WATCH
+ readonly bool isBackgroundSession = false;
+ NSObject notificationToken; // needed to make sure we do not hang if not using a background session
+#endif
+
+ static NSUrlSessionConfiguration CreateConfig ()
+ {
+ // modifying the configuration does not affect future calls
+ var config = NSUrlSessionConfiguration.DefaultSessionConfiguration;
+ // but we want, by default, the timeout from HttpClient to have precedence over the one from NSUrlSession
+ // Double.MaxValue does not work, so default to 24 hours
+ config.TimeoutIntervalForRequest = 24 * 60 * 60;
+ config.TimeoutIntervalForResource = 24 * 60 * 60;
+ return config;
+ }
+
+ public NSUrlSessionHandler () : this (CreateConfig ())
+ {
+ }
+
+ [CLSCompliant (false)]
+ public NSUrlSessionHandler (NSUrlSessionConfiguration configuration)
+ {
+ if (configuration == null)
+ throw new ArgumentNullException (nameof (configuration));
+
+#if !MONOMAC && !MONOTOUCH_WATCH
+ // if the configuration has an identifier, we are dealing with a background session,
+ // therefore, we do not have to listen to the notifications.
+ isBackgroundSession = !string.IsNullOrEmpty (configuration.Identifier);
+#endif
+
+ AllowAutoRedirect = true;
+
+ // we cannot do a bitmask but we can set the minimum based on ServicePointManager.SecurityProtocol minimum
+ var sp = ServicePointManager.SecurityProtocol;
+ if ((sp & SecurityProtocolType.Ssl3) != 0)
+ configuration.TLSMinimumSupportedProtocol = SslProtocol.Ssl_3_0;
+ else if ((sp & SecurityProtocolType.Tls) != 0)
+ configuration.TLSMinimumSupportedProtocol = SslProtocol.Tls_1_0;
+ else if ((sp & SecurityProtocolType.Tls11) != 0)
+ configuration.TLSMinimumSupportedProtocol = SslProtocol.Tls_1_1;
+ else if ((sp & SecurityProtocolType.Tls12) != 0)
+ configuration.TLSMinimumSupportedProtocol = SslProtocol.Tls_1_2;
+
+ session = NSUrlSession.FromConfiguration (configuration, (INSUrlSessionDelegate) new NSUrlSessionHandlerDelegate (this), null);
+ inflightRequests = new Dictionary<NSUrlSessionTask, InflightData> ();
+ }
+
+#if !MONOMAC && !MONOTOUCH_WATCH
+
+ void AddNotification ()
+ {
+ if (!isBackgroundSession && notificationToken == null)
+ notificationToken = NSNotificationCenter.DefaultCenter.AddObserver (UIApplication.WillResignActiveNotification, BackgroundNotificationCb);
+ }
+
+ void RemoveNotification ()
+ {
+ if (notificationToken != null) {
+ NSNotificationCenter.DefaultCenter.RemoveObserver (notificationToken);
+ notificationToken = null;
+ }
+ }
+
+ void BackgroundNotificationCb (NSNotification obj)
+ {
+ // we do not need to call the lock, we call cancel on the source, that will trigger all the needed code to
+ // clean the resources and such
+ foreach (var r in inflightRequests.Values) {
+ r.CompletionSource.TrySetCanceled ();
+ }
+ }
+#endif
+
+ public long MaxInputInMemory { get; set; } = long.MaxValue;
+
+ void RemoveInflightData (NSUrlSessionTask task, bool cancel = true)
+ {
+ lock (inflightRequestsLock) {
+ inflightRequests.Remove (task);
+#if !MONOMAC && !MONOTOUCH_WATCH
+ // do we need to be notified? If we have not inflightData, we do not
+ if (inflightRequests.Count == 0)
+ RemoveNotification ();
+#endif
+ }
+
+ if (cancel)
+ task?.Cancel ();
+
+ task?.Dispose ();
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+#if !MONOMAC && !MONOTOUCH_WATCH
+ // remove the notification if present, method checks against null
+ RemoveNotification ();
+#endif
+ lock (inflightRequestsLock) {
+ foreach (var pair in inflightRequests) {
+ pair.Key?.Cancel ();
+ pair.Key?.Dispose ();
+ }
+
+ inflightRequests.Clear ();
+ }
+ base.Dispose (disposing);
+ }
+
+ bool disableCaching;
+
+ public bool DisableCaching {
+ get {
+ return disableCaching;
+ }
+ set {
+ EnsureModifiability ();
+ disableCaching = value;
+ }
+ }
+
+ string GetHeaderSeparator (string name)
+ {
+ string value;
+ if (!headerSeparators.TryGetValue (name, out value))
+ value = ",";
+ return value;
+ }
+
+ async Task<NSUrlRequest> CreateRequest (HttpRequestMessage request)
+ {
+ var stream = Stream.Null;
+ var headers = request.Headers as IEnumerable<KeyValuePair<string, IEnumerable<string>>>;
+
+ if (request.Content != null) {
+ stream = await request.Content.ReadAsStreamAsync ().ConfigureAwait (false);
+ headers = System.Linq.Enumerable.ToArray(headers.Union (request.Content.Headers));
+ }
+
+ var nsrequest = new NSMutableUrlRequest {
+ AllowsCellularAccess = true,
+ CachePolicy = DisableCaching ? NSUrlRequestCachePolicy.ReloadIgnoringCacheData : NSUrlRequestCachePolicy.UseProtocolCachePolicy,
+ HttpMethod = request.Method.ToString ().ToUpperInvariant (),
+ Url = NSUrl.FromString (request.RequestUri.AbsoluteUri),
+ Headers = headers.Aggregate (new NSMutableDictionary (), (acc, x) => {
+ acc.Add (new NSString (x.Key), new NSString (string.Join (GetHeaderSeparator (x.Key), x.Value)));
+ return acc;
+ })
+ };
+ if (stream != Stream.Null) {
+ // HttpContent.TryComputeLength is `protected internal` :-( but it's indirectly called by headers
+ var length = request.Content.Headers.ContentLength;
+ if (length.HasValue && (length <= MaxInputInMemory))
+ nsrequest.Body = NSData.FromStream (stream);
+ else
+ nsrequest.BodyStream = new WrappedNSInputStream (stream);
+ }
+ return nsrequest;
+ }
+
+#if SYSTEM_NET_HTTP || MONOMAC
+ internal
+#endif
+ protected override async Task<HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ Volatile.Write (ref sentRequest, true);
+
+ var nsrequest = await CreateRequest (request).ConfigureAwait(false);
+ var dataTask = session.CreateDataTask (nsrequest);
+
+ var tcs = new TaskCompletionSource<HttpResponseMessage> ();
+
+ lock (inflightRequestsLock) {
+#if !MONOMAC && !MONOTOUCH_WATCH
+ // Add the notification whenever needed
+ AddNotification ();
+#endif
+ inflightRequests.Add (dataTask, new InflightData {
+ RequestUrl = request.RequestUri.AbsoluteUri,
+ CompletionSource = tcs,
+ CancellationToken = cancellationToken,
+ Stream = new NSUrlSessionDataTaskStream (),
+ Request = request
+ });
+ }
+
+ if (dataTask.State == NSUrlSessionTaskState.Suspended)
+ dataTask.Resume ();
+
+ // as per documentation:
+ // If this token is already in the canceled state, the
+ // delegate will be run immediately and synchronously.
+ // Any exception the delegate generates will be
+ // propagated out of this method call.
+ //
+ // The execution of the register ensures that if we
+ // receive a already cancelled token or it is cancelled
+ // just before this call, we will cancel the task.
+ // Other approaches are harder, since querying the state
+ // of the token does not guarantee that in the next
+ // execution a threads cancels it.
+ cancellationToken.Register (() => {
+ RemoveInflightData (dataTask);
+ tcs.TrySetCanceled ();
+ });
+
+ return await tcs.Task.ConfigureAwait (false);
+ }
+
+#if MONOMAC
+ // Needed since we strip during linking since we're inside a product assembly.
+ [Preserve (AllMembers = true)]
+#endif
+ partial class NSUrlSessionHandlerDelegate : NSUrlSessionDataDelegate
+ {
+ readonly NSUrlSessionHandler sessionHandler;
+
+ public NSUrlSessionHandlerDelegate (NSUrlSessionHandler handler)
+ {
+ sessionHandler = handler;
+ }
+
+ InflightData GetInflightData (NSUrlSessionTask task)
+ {
+ var inflight = default (InflightData);
+
+ lock (sessionHandler.inflightRequestsLock)
+ if (sessionHandler.inflightRequests.TryGetValue (task, out inflight)) {
+ // ensure that we did not cancel the request, if we did, do cancel the task
+ if (inflight.CancellationToken.IsCancellationRequested)
+ task?.Cancel ();
+ return inflight;
+ }
+
+ // if we did not manage to get the inflight data, we either got an error or have been canceled, lets cancel the task, that will execute DidCompleteWithError
+ task?.Cancel ();
+ return null;
+ }
+
+ public override void DidReceiveResponse (NSUrlSession session, NSUrlSessionDataTask dataTask, NSUrlResponse response, Action<NSUrlSessionResponseDisposition> completionHandler)
+ {
+ var inflight = GetInflightData (dataTask);
+
+ if (inflight == null)
+ return;
+
+ try {
+ var urlResponse = (NSHttpUrlResponse)response;
+ var status = (int)urlResponse.StatusCode;
+
+ var content = new NSUrlSessionDataTaskStreamContent (inflight.Stream, () => {
+ if (!inflight.Completed) {
+ dataTask.Cancel ();
+ }
+
+ inflight.Disposed = true;
+ inflight.Stream.TrySetException (new ObjectDisposedException ("The content stream was disposed."));
+
+ sessionHandler.RemoveInflightData (dataTask);
+ }, inflight.CancellationToken);
+
+ // NB: The double cast is because of a Xamarin compiler bug
+ var httpResponse = new HttpResponseMessage ((HttpStatusCode)status) {
+ Content = content,
+ RequestMessage = inflight.Request
+ };
+ httpResponse.RequestMessage.RequestUri = new Uri (urlResponse.Url.AbsoluteString);
+
+ foreach (var v in urlResponse.AllHeaderFields) {
+ // NB: Cocoa trolling us so hard by giving us back dummy dictionary entries
+ if (v.Key == null || v.Value == null) continue;
+ // NSUrlSession tries to be smart with cookies, we will not use the raw value but the ones provided by the cookie storage
+ if (v.Key.ToString () == SetCookie) continue;
+
+ httpResponse.Headers.TryAddWithoutValidation (v.Key.ToString (), v.Value.ToString ());
+ httpResponse.Content.Headers.TryAddWithoutValidation (v.Key.ToString (), v.Value.ToString ());
+ }
+
+ var cookies = session.Configuration.HttpCookieStorage.CookiesForUrl (response.Url);
+ for (var index = 0; index < cookies.Length; index++) {
+ httpResponse.Headers.TryAddWithoutValidation (SetCookie, cookies [index].GetHeaderValue ());
+ }
+
+ inflight.Response = httpResponse;
+
+ // We don't want to send the response back to the task just yet. Because we want to mimic .NET behavior
+ // as much as possible. When the response is sent back in .NET, the content stream is ready to read or the
+ // request has completed, because of this we want to send back the response in DidReceiveData or DidCompleteWithError
+ if (dataTask.State == NSUrlSessionTaskState.Suspended)
+ dataTask.Resume ();
+
+ } catch (Exception ex) {
+ inflight.CompletionSource.TrySetException (ex);
+ inflight.Stream.TrySetException (ex);
+
+ sessionHandler.RemoveInflightData (dataTask);
+ }
+
+ completionHandler (NSUrlSessionResponseDisposition.Allow);
+ }
+
+ public override void DidReceiveData (NSUrlSession session, NSUrlSessionDataTask dataTask, NSData data)
+ {
+ var inflight = GetInflightData (dataTask);
+
+ if (inflight == null)
+ return;
+
+ inflight.Stream.Add (data);
+ SetResponse (inflight);
+ }
+
+ public override void DidCompleteWithError (NSUrlSession session, NSUrlSessionTask task, NSError error)
+ {
+ var inflight = GetInflightData (task);
+
+ // this can happen if the HTTP request times out and it is removed as part of the cancellation process
+ if (inflight != null) {
+ // set the stream as finished
+ inflight.Stream.TrySetReceivedAllData ();
+
+ // send the error or send the response back
+ if (error != null) {
+ inflight.Errored = true;
+
+ var exc = createExceptionForNSError (error);
+ inflight.CompletionSource.TrySetException (exc);
+ inflight.Stream.TrySetException (exc);
+ } else {
+ inflight.Completed = true;
+ SetResponse (inflight);
+ }
+
+ sessionHandler.RemoveInflightData (task, cancel: false);
+ }
+ }
+
+ void SetResponse (InflightData inflight)
+ {
+ lock (inflight.Lock) {
+ if (inflight.ResponseSent)
+ return;
+
+ if (inflight.CompletionSource.Task.IsCompleted)
+ return;
+
+ var httpResponse = inflight.Response;
+
+ inflight.ResponseSent = true;
+
+ // EVIL HACK: having TrySetResult inline was blocking the request from completing
+ Task.Run (() => inflight.CompletionSource.TrySetResult (httpResponse));
+ }
+ }
+
+ public override void WillCacheResponse (NSUrlSession session, NSUrlSessionDataTask dataTask, NSCachedUrlResponse proposedResponse, Action<NSCachedUrlResponse> completionHandler)
+ {
+ completionHandler (sessionHandler.DisableCaching ? null : proposedResponse);
+ }
+
+ public override void WillPerformHttpRedirection (NSUrlSession session, NSUrlSessionTask task, NSHttpUrlResponse response, NSUrlRequest newRequest, Action<NSUrlRequest> completionHandler)
+ {
+ completionHandler (sessionHandler.AllowAutoRedirect ? newRequest : null);
+ }
+
+ public override void DidReceiveChallenge (NSUrlSession session, NSUrlSessionTask task, NSUrlAuthenticationChallenge challenge, Action<NSUrlSessionAuthChallengeDisposition, NSUrlCredential> completionHandler)
+ {
+ var inflight = GetInflightData (task);
+
+ if (inflight == null)
+ return;
+
+ // case for the basic auth failing up front. As per apple documentation:
+ // The URL Loading System is designed to handle various aspects of the HTTP protocol for you. As a result, you should not modify the following headers using
+ // the addValue(_:forHTTPHeaderField:) or setValue(_:forHTTPHeaderField:) methods:
+ // Authorization
+ // Connection
+ // Host
+ // Proxy-Authenticate
+ // Proxy-Authorization
+ // WWW-Authenticate
+ // but we are hiding such a situation from our users, we can nevertheless know if the header was added and deal with it. The idea is as follows,
+ // check if we are in the first attempt, if we are (PreviousFailureCount == 0), we check the headers of the request and if we do have the Auth
+ // header, it means that we do not have the correct credentials, in any other case just do what it is expected.
+
+ if (challenge.PreviousFailureCount == 0) {
+ var authHeader = inflight.Request?.Headers?.Authorization;
+ if (!(string.IsNullOrEmpty (authHeader?.Scheme) && string.IsNullOrEmpty (authHeader?.Parameter))) {
+ completionHandler (NSUrlSessionAuthChallengeDisposition.RejectProtectionSpace, null);
+ return;
+ }
+ }
+
+ if (sessionHandler.Credentials != null && TryGetAuthenticationType (challenge.ProtectionSpace, out string authType)) {
+ NetworkCredential credentialsToUse = null;
+ if (authType != RejectProtectionSpaceAuthType) {
+ var uri = inflight.Request.RequestUri;
+ credentialsToUse = sessionHandler.Credentials.GetCredential (uri, authType);
+ }
+
+ if (credentialsToUse != null) {
+ var credential = new NSUrlCredential (credentialsToUse.UserName, credentialsToUse.Password, NSUrlCredentialPersistence.ForSession);
+ completionHandler (NSUrlSessionAuthChallengeDisposition.UseCredential, credential);
+ } else {
+ // Rejecting the challenge allows the next authentication method in the request to be delivered to
+ // the DidReceiveChallenge method. Another authentication method may have credentials available.
+ completionHandler (NSUrlSessionAuthChallengeDisposition.RejectProtectionSpace, null);
+ }
+ } else {
+ completionHandler (NSUrlSessionAuthChallengeDisposition.PerformDefaultHandling, challenge.ProposedCredential);
+ }
+ }
+
+ static readonly string RejectProtectionSpaceAuthType = "reject";
+
+ static bool TryGetAuthenticationType (NSUrlProtectionSpace protectionSpace, out string authenticationType)
+ {
+ if (protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodNTLM) {
+ authenticationType = "NTLM";
+ } else if (protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodHTTPBasic) {
+ authenticationType = "basic";
+ } else if (protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodNegotiate ||
+ protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodHTMLForm ||
+ protectionSpace.AuthenticationMethod == NSUrlProtectionSpace.AuthenticationMethodHTTPDigest) {
+ // Want to reject this authentication type to allow the next authentication method in the request to
+ // be used.
+ authenticationType = RejectProtectionSpaceAuthType;
+ } else {
+ // ServerTrust, ClientCertificate or Default.
+ authenticationType = null;
+ return false;
+ }
+ return true;
+ }
+ }
+
+#if MONOMAC
+ // Needed since we strip during linking since we're inside a product assembly.
+ [Preserve (AllMembers = true)]
+#endif
+ class InflightData
+ {
+ public readonly object Lock = new object ();
+ public string RequestUrl { get; set; }
+
+ public TaskCompletionSource<HttpResponseMessage> CompletionSource { get; set; }
+ public CancellationToken CancellationToken { get; set; }
+ public NSUrlSessionDataTaskStream Stream { get; set; }
+ public HttpRequestMessage Request { get; set; }
+ public HttpResponseMessage Response { get; set; }
+
+ public bool ResponseSent { get; set; }
+ public bool Errored { get; set; }
+ public bool Disposed { get; set; }
+ public bool Completed { get; set; }
+ public bool Done { get { return Errored || Disposed || Completed || CancellationToken.IsCancellationRequested; } }
+ }
+
+#if MONOMAC
+ // Needed since we strip during linking since we're inside a product assembly.
+ [Preserve (AllMembers = true)]
+#endif
+ class NSUrlSessionDataTaskStreamContent : StreamContent
+ {
+ Action disposed;
+
+ public NSUrlSessionDataTaskStreamContent (NSUrlSessionDataTaskStream source, Action onDisposed, CancellationToken token) : base (source, token)
+ {
+ disposed = onDisposed;
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ var action = Interlocked.Exchange (ref disposed, null);
+ action?.Invoke ();
+
+ base.Dispose (disposing);
+ }
+ }
+
+#if MONOMAC
+ // Needed since we strip during linking since we're inside a product assembly.
+ [Preserve (AllMembers = true)]
+#endif
+ class NSUrlSessionDataTaskStream : Stream
+ {
+ readonly Queue<NSData> data;
+ readonly object dataLock = new object ();
+
+ long position;
+ long length;
+
+ bool receivedAllData;
+ Exception exc;
+
+ NSData current;
+ Stream currentStream;
+
+ public NSUrlSessionDataTaskStream ()
+ {
+ data = new Queue<NSData> ();
+ }
+
+ public void Add (NSData d)
+ {
+ lock (dataLock) {
+ data.Enqueue (d);
+ length += (int)d.Length;
+ }
+ }
+
+ public void TrySetReceivedAllData ()
+ {
+ receivedAllData = true;
+ }
+
+ public void TrySetException (Exception e)
+ {
+ exc = e;
+ TrySetReceivedAllData ();
+ }
+
+ void ThrowIfNeeded (CancellationToken cancellationToken)
+ {
+ if (exc != null)
+ throw exc;
+
+ cancellationToken.ThrowIfCancellationRequested ();
+ }
+
+ public override int Read (byte [] buffer, int offset, int count)
+ {
+ return ReadAsync (buffer, offset, count).Result;
+ }
+
+ public override async Task<int> ReadAsync (byte [] buffer, int offset, int count, CancellationToken cancellationToken)
+ {
+ // try to throw on enter
+ ThrowIfNeeded (cancellationToken);
+
+ while (current == null) {
+ lock (dataLock) {
+ if (data.Count == 0 && receivedAllData && position == length)
+ return 0;
+
+ if (data.Count > 0 && current == null) {
+ current = data.Peek ();
+ currentStream = current.AsStream ();
+ break;
+ }
+ }
+
+ await Task.Delay (50).ConfigureAwait (false);
+ }
+
+ // try to throw again before read
+ ThrowIfNeeded (cancellationToken);
+
+ var d = currentStream;
+ var bufferCount = Math.Min (count, (int)(d.Length - d.Position));
+ var bytesRead = await d.ReadAsync (buffer, offset, bufferCount, cancellationToken).ConfigureAwait (false);
+
+ // add the bytes read from the pointer to the position
+ position += bytesRead;
+
+ // remove the current primary reference if the current position has reached the end of the bytes
+ if (d.Position == d.Length) {
+ lock (dataLock) {
+ // this is the same object, it was done to make the cleanup
+ data.Dequeue ();
+ currentStream?.Dispose ();
+ // We cannot use current?.Dispose. The reason is the following one:
+ // In the DidReceiveResponse, if iOS realizes that a buffer can be reused,
+ // because the data is the same, it will do so. Such a situation does happen
+ // between requests, that is, request A and request B will get the same NSData
+ // (buffer) in the delegate. In this case, we cannot dispose the NSData because
+ // it might be that a different request received it and it is present in
+ // its NSUrlSessionDataTaskStream stream. We can only trust the gc to do the job
+ // which is better than copying the data over.
+ current = null;
+ currentStream = null;
+ }
+ }
+
+ return bytesRead;
+ }
+
+ public override bool CanRead => true;
+
+ public override bool CanSeek => false;
+
+ public override bool CanWrite => false;
+
+ public override bool CanTimeout => false;
+
+ public override long Length => length;
+
+ public override void SetLength (long value)
+ {
+ throw new InvalidOperationException ();
+ }
+
+ public override long Position {
+ get { return position; }
+ set { throw new InvalidOperationException (); }
+ }
+
+ public override long Seek (long offset, SeekOrigin origin)
+ {
+ throw new InvalidOperationException ();
+ }
+
+ public override void Flush ()
+ {
+ throw new InvalidOperationException ();
+ }
+
+ public override void Write (byte [] buffer, int offset, int count)
+ {
+ throw new InvalidOperationException ();
+ }
+ }
+
+#if MONOMAC
+ // Needed since we strip during linking since we're inside a product assembly.
+ [Preserve (AllMembers = true)]
+#endif
+ class WrappedNSInputStream : NSInputStream
+ {
+ NSStreamStatus status;
+ CFRunLoopSource source;
+ readonly Stream stream;
+ bool notifying;
+
+ public WrappedNSInputStream (Stream inputStream)
+ {
+ status = NSStreamStatus.NotOpen;
+ stream = inputStream;
+ source = new CFRunLoopSource (Handle);
+ }
+
+ public override NSStreamStatus Status => status;
+
+ public override void Open ()
+ {
+ status = NSStreamStatus.Open;
+ Notify (CFStreamEventType.OpenCompleted);
+ }
+
+ public override void Close ()
+ {
+ status = NSStreamStatus.Closed;
+ }
+
+ public override nint Read (IntPtr buffer, nuint len)
+ {
+ var sourceBytes = new byte [len];
+ var read = stream.Read (sourceBytes, 0, (int)len);
+ Marshal.Copy (sourceBytes, 0, buffer, (int)len);
+
+ if (notifying)
+ return read;
+
+ notifying = true;
+ if (stream.CanSeek && stream.Position == stream.Length) {
+ Notify (CFStreamEventType.EndEncountered);
+ status = NSStreamStatus.AtEnd;
+ }
+ notifying = false;
+
+ return read;
+ }
+
+ public override bool HasBytesAvailable ()
+ {
+ return true;
+ }
+
+ protected override bool GetBuffer (out IntPtr buffer, out nuint len)
+ {
+ // Just call the base implemention (which will return false)
+ return base.GetBuffer (out buffer, out len);
+ }
+
+ // NSInvalidArgumentException Reason: *** -propertyForKey: only defined for abstract class. Define -[System_Net_Http_NSUrlSessionHandler_WrappedNSInputStream propertyForKey:]!
+ protected override NSObject GetProperty (NSString key)
+ {
+ return null;
+ }
+
+ protected override bool SetProperty (NSObject property, NSString key)
+ {
+ return false;
+ }
+
+ protected override bool SetCFClientFlags (CFStreamEventType inFlags, IntPtr inCallback, IntPtr inContextPtr)
+ {
+ // Just call the base implementation, which knows how to handle everything.
+ return base.SetCFClientFlags (inFlags, inCallback, inContextPtr);
+ }
+
+ public override void Schedule (NSRunLoop aRunLoop, string mode)
+ {
+ var cfRunLoop = aRunLoop.GetCFRunLoop ();
+ var nsMode = new NSString (mode);
+
+ cfRunLoop.AddSource (source, nsMode);
+
+ if (notifying)
+ return;
+
+ notifying = true;
+ Notify (CFStreamEventType.HasBytesAvailable);
+ notifying = false;
+ }
+
+ public override void Unschedule (NSRunLoop aRunLoop, string mode)
+ {
+ var cfRunLoop = aRunLoop.GetCFRunLoop ();
+ var nsMode = new NSString (mode);
+
+ cfRunLoop.RemoveSource (source, nsMode);
+ }
+
+ protected override void Dispose (bool disposing)
+ {
+ stream?.Dispose ();
+ }
+ }
+ }
+}
diff --git a/mcs/class/System.Net.Http/RuntimeOptions.cs b/mcs/class/System.Net.Http/RuntimeOptions.cs
new file mode 100644
index 00000000000..6e4c152ca0e
--- /dev/null
+++ b/mcs/class/System.Net.Http/RuntimeOptions.cs
@@ -0,0 +1,98 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Net.Http;
+
+#if XAMCORE_2_0
+using Foundation;
+using ObjCRuntime;
+#elif MONOMAC
+using MonoMac.Foundation;
+using MonoMac.ObjCRuntime;
+#else
+#error Unknown platform
+#endif
+
+namespace System.Net.Http {
+ class RuntimeOptions
+ {
+ const string HttpClientHandlerValue = "HttpClientHandler";
+ const string CFNetworkHandlerValue = "CFNetworkHandler";
+ const string NSUrlSessionHandlerValue = "NSUrlSessionHandler";
+
+ const string DefaultTlsProviderValue = "default";
+ const string LegacyTlsProviderValue = "legacy";
+ const string AppleTlsProviderValue = "appletls";
+
+ string http_message_handler;
+
+
+ internal static RuntimeOptions Read ()
+ {
+ // for iOS NSBundle.ResourcePath returns the path to the root of the app bundle
+ // for macOS apps NSBundle.ResourcePath returns foo.app/Contents/Resources
+ // for macOS frameworks NSBundle.ResourcePath returns foo.app/Versions/Current/Resources
+ Class bundle_finder = new Class (typeof (NSObject.NSObject_Disposer));
+ var resource_dir = NSBundle.FromClass (bundle_finder).ResourcePath;
+ var plist_path = GetFileName (resource_dir);
+
+ if (!File.Exists (plist_path))
+ return null;
+
+ using (var plist = NSDictionary.FromFile (plist_path)) {
+ var options = new RuntimeOptions ();
+ options.http_message_handler = (NSString) plist ["HttpMessageHandler"];
+ return options;
+ }
+ }
+
+#if MONOMAC
+ [Preserve]
+#endif
+ internal static HttpMessageHandler GetHttpMessageHandler ()
+ {
+ RuntimeOptions options = null;
+
+ try {
+ options = RuntimeOptions.Read ();
+ } catch (FileNotFoundException){
+ // this happens on the Mono SDKs since we don't have a real Xamarin.iOS.dll so we can't resolve NSObject
+ }
+
+ if (options == null) {
+#if MONOTOUCH_WATCH
+ return new NSUrlSessionHandler ();
+#else
+ return new HttpClientHandler ();
+#endif
+ }
+
+ // all types will be present as this is executed only when the linker is not enabled
+ var handler_name = options.http_message_handler;
+ var t = Type.GetType (handler_name, false);
+
+ HttpMessageHandler handler = null;
+ if (t != null)
+ handler = Activator.CreateInstance (t) as HttpMessageHandler;
+ if (handler != null)
+ return handler;
+#if MONOTOUCH_WATCH
+ Console.WriteLine ("{0} is not a valid HttpMessageHandler, defaulting to NSUrlSessionHandler", handler_name);
+ return new NSUrlSessionHandler ();
+#else
+ Console.WriteLine ("{0} is not a valid HttpMessageHandler, defaulting to System.Net.Http.HttpClientHandler", handler_name);
+ return new HttpClientHandler ();
+#endif
+ }
+
+ // Use either Create() or Read().
+ RuntimeOptions ()
+ {
+ }
+
+ static string GetFileName (string resource_dir)
+ {
+ return Path.Combine (resource_dir, "runtime-options.plist");
+ }
+ }
+}
diff --git a/mcs/class/System.Net.Http/System.Net.Http.csproj b/mcs/class/System.Net.Http/System.Net.Http.csproj
index e7f4273df99..a09eb91983e 100644
--- a/mcs/class/System.Net.Http/System.Net.Http.csproj
+++ b/mcs/class/System.Net.Http/System.Net.Http.csproj
@@ -39,17 +39,17 @@
<PropertyGroup Condition=" '$(Platform)' == 'monotouch' ">
<OutputPath>./../../class/lib/monotouch</OutputPath>
<IntermediateOutputPath>./../../class/obj/$(AssemblyName)-monotouch</IntermediateOutputPath>
- <DefineConstants>NET_1_1;NET_2_0;NET_2_1;NET_3_5;NET_4_0;NET_4_5;MOBILE;MOBILE_LEGACY;MONO;MONOTOUCH;DISABLE_REMOTING;DISABLE_COM;FEATURE_INTERCEPTABLE_THREADPOOL_CALLBACK;FULL_AOT_RUNTIME;LEGACY_HTTPCLIENT</DefineConstants>
+ <DefineConstants>NET_1_1;NET_2_0;NET_2_1;NET_3_5;NET_4_0;NET_4_5;MOBILE;MOBILE_LEGACY;MONO;MONOTOUCH;DISABLE_REMOTING;DISABLE_COM;FEATURE_INTERCEPTABLE_THREADPOOL_CALLBACK;FULL_AOT_RUNTIME;XAMCORE_2_0;XAMARIN_MODERN;SYSTEM_NET_HTTP;UNIFIED;__UNIFIED__;LEGACY_HTTPCLIENT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'monotouch_watch' ">
<OutputPath>./../../class/lib/monotouch_watch</OutputPath>
<IntermediateOutputPath>./../../class/obj/$(AssemblyName)-monotouch_watch</IntermediateOutputPath>
- <DefineConstants>NET_1_1;NET_2_0;NET_2_1;NET_3_5;NET_4_0;NET_4_5;MOBILE;MOBILE_LEGACY;MONO;MONOTOUCH;DISABLE_REMOTING;DISABLE_COM;FEATURE_INTERCEPTABLE_THREADPOOL_CALLBACK;FULL_AOT_RUNTIME;FEATURE_NO_BSD_SOCKETS;MONOTOUCH_WATCH;LEGACY_HTTPCLIENT</DefineConstants>
+ <DefineConstants>NET_1_1;NET_2_0;NET_2_1;NET_3_5;NET_4_0;NET_4_5;MOBILE;MOBILE_LEGACY;MONO;MONOTOUCH;DISABLE_REMOTING;DISABLE_COM;FEATURE_INTERCEPTABLE_THREADPOOL_CALLBACK;FULL_AOT_RUNTIME;FEATURE_NO_BSD_SOCKETS;MONOTOUCH_WATCH;XAMCORE_2_0;XAMARIN_MODERN;SYSTEM_NET_HTTP;UNIFIED;__UNIFIED__;LEGACY_HTTPCLIENT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'monotouch_tv' ">
<OutputPath>./../../class/lib/monotouch_tv</OutputPath>
<IntermediateOutputPath>./../../class/obj/$(AssemblyName)-monotouch_tv</IntermediateOutputPath>
- <DefineConstants>NET_1_1;NET_2_0;NET_2_1;NET_3_5;NET_4_0;NET_4_5;MOBILE;MOBILE_LEGACY;MONO;MONOTOUCH;DISABLE_REMOTING;DISABLE_COM;FEATURE_INTERCEPTABLE_THREADPOOL_CALLBACK;FULL_AOT_RUNTIME;MONOTOUCH_TV;LEGACY_HTTPCLIENT</DefineConstants>
+ <DefineConstants>NET_1_1;NET_2_0;NET_2_1;NET_3_5;NET_4_0;NET_4_5;MOBILE;MOBILE_LEGACY;MONO;MONOTOUCH;DISABLE_REMOTING;DISABLE_COM;FEATURE_INTERCEPTABLE_THREADPOOL_CALLBACK;FULL_AOT_RUNTIME;MONOTOUCH_TV;XAMCORE_2_0;XAMARIN_MODERN;SYSTEM_NET_HTTP;UNIFIED;__UNIFIED__;LEGACY_HTTPCLIENT</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'testing_aot_full_interp' ">
<OutputPath>./../../class/lib/testing_aot_full_interp</OutputPath>
@@ -845,6 +845,9 @@
<When Condition="'$(Platform)' == 'monotouch_watch'">
<ItemGroup>
<Compile Include="HeaderUtils.Legacy.cs" />
+ <Compile Include="HttpClientEx.cs" />
+ <Compile Include="NSUrlSessionHandler.cs" />
+ <Compile Include="RuntimeOptions.cs" />
<Compile Include="System.Net.Http.Headers\AuthenticationHeaderValue.cs" />
<Compile Include="System.Net.Http.Headers\CacheControlHeaderValue.cs" />
<Compile Include="System.Net.Http.Headers\CollectionExtensions.cs" />
@@ -903,11 +906,16 @@
<ItemGroup>
<Compile Include="..\..\..\external\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\CancellationHelper.cs" />
<Compile Include="..\..\..\external\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.cs" />
+ <Compile Include="CFContentStream.cs" />
+ <Compile Include="CFNetworkHandler.cs" />
<Compile Include="HeaderUtils.Legacy.cs" />
+ <Compile Include="HttpClientEx.cs" />
<Compile Include="HttpClientHandler.Legacy.cs" />
<Compile Include="HttpClientHandler.cs" />
<Compile Include="IMonoHttpClientHandler.cs" />
<Compile Include="MonoWebRequestHandler.cs" />
+ <Compile Include="NSUrlSessionHandler.cs" />
+ <Compile Include="RuntimeOptions.cs" />
<Compile Include="System.Net.Http.Headers\AuthenticationHeaderValue.cs" />
<Compile Include="System.Net.Http.Headers\CacheControlHeaderValue.cs" />
<Compile Include="System.Net.Http.Headers\CollectionExtensions.cs" />
@@ -965,11 +973,16 @@
<ItemGroup>
<Compile Include="..\..\..\external\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\CancellationHelper.cs" />
<Compile Include="..\..\..\external\corefx\src\System.Net.Http\src\System\Net\Http\SocketsHttpHandler\ConnectHelper.cs" />
+ <Compile Include="CFContentStream.cs" />
+ <Compile Include="CFNetworkHandler.cs" />
<Compile Include="HeaderUtils.Legacy.cs" />
+ <Compile Include="HttpClientEx.cs" />
<Compile Include="HttpClientHandler.Legacy.cs" />
<Compile Include="HttpClientHandler.cs" />
<Compile Include="IMonoHttpClientHandler.cs" />
<Compile Include="MonoWebRequestHandler.cs" />
+ <Compile Include="NSUrlSessionHandler.cs" />
+ <Compile Include="RuntimeOptions.cs" />
<Compile Include="System.Net.Http.Headers\AuthenticationHeaderValue.cs" />
<Compile Include="System.Net.Http.Headers\CacheControlHeaderValue.cs" />
<Compile Include="System.Net.Http.Headers\CollectionExtensions.cs" />
@@ -1094,6 +1107,27 @@
<ProjectReference Include="../System/System.csproj" />
<ProjectReference Include="../corlib/corlib.csproj" />
</ItemGroup>
+ <ItemGroup Condition=" '$(Platform)' == 'monotouch' ">
+ <Reference Include="./../../../external/binary-reference-assemblies/build/monotouch/Xamarin.iOS.dll">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>./../../../external/binary-reference-assemblies/build/monotouch/Xamarin.iOS.dll</HintPath>
+ <Private>False</Private>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup Condition=" '$(Platform)' == 'monotouch_watch' ">
+ <Reference Include="./../../../external/binary-reference-assemblies/build/monotouch/Xamarin.WatchOS.dll">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>./../../../external/binary-reference-assemblies/build/monotouch/Xamarin.WatchOS.dll</HintPath>
+ <Private>False</Private>
+ </Reference>
+ </ItemGroup>
+ <ItemGroup Condition=" '$(Platform)' == 'monotouch_tv' ">
+ <Reference Include="./../../../external/binary-reference-assemblies/build/monotouch/Xamarin.TVOS.dll">
+ <SpecificVersion>False</SpecificVersion>
+ <HintPath>./../../../external/binary-reference-assemblies/build/monotouch/Xamarin.TVOS.dll</HintPath>
+ <Private>False</Private>
+ </Reference>
+ </ItemGroup>
<ItemGroup Condition=" '$(Platform)' == 'testing_aot_full_interp' ">
<Reference Include="nunitlite">
<HintPath>..\lib\testing_aot_full_interp\nunitlite.dll</HintPath>
diff --git a/mcs/class/System.Net.Http/Test/System.Net.Http/HttpClientTest.cs b/mcs/class/System.Net.Http/Test/System.Net.Http/HttpClientTest.cs
index 68911ff8907..1b05575a6e6 100644
--- a/mcs/class/System.Net.Http/Test/System.Net.Http/HttpClientTest.cs
+++ b/mcs/class/System.Net.Http/Test/System.Net.Http/HttpClientTest.cs
@@ -178,6 +178,16 @@ namespace MonoTests.System.Net.Http
const int WaitTimeout = 5000;
[Test]
+ public void Ctor ()
+ {
+ var client = new HttpClient ();
+ Assert.IsNull (client.BaseAddress, "#1");
+ Assert.IsNotNull (client.DefaultRequestHeaders, "#2"); // TODO: full check
+ Assert.AreEqual (int.MaxValue, client.MaxResponseContentBufferSize, "#3");
+ Assert.AreEqual (TimeSpan.FromSeconds (100), client.Timeout, "#4");
+ }
+
+ [Test]
public void Ctor_Default ()
{
var client = HttpClientTestHelpers.CreateHttpClient ();
diff --git a/mcs/class/System.Net.Http/monotouch_System.Net.Http.dll.sources b/mcs/class/System.Net.Http/monotouch_System.Net.Http.dll.sources
index 704cd9f69ab..64cdbb68748 100644
--- a/mcs/class/System.Net.Http/monotouch_System.Net.Http.dll.sources
+++ b/mcs/class/System.Net.Http/monotouch_System.Net.Http.dll.sources
@@ -1 +1,6 @@
#include legacy.sources
+CFContentStream.cs
+CFNetworkHandler.cs
+HttpClientEx.cs
+NSUrlSessionHandler.cs
+RuntimeOptions.cs
diff --git a/mcs/class/System.Net.Http/monotouch_tv_System.Net.Http.dll.sources b/mcs/class/System.Net.Http/monotouch_tv_System.Net.Http.dll.sources
index 704cd9f69ab..64cdbb68748 100644
--- a/mcs/class/System.Net.Http/monotouch_tv_System.Net.Http.dll.sources
+++ b/mcs/class/System.Net.Http/monotouch_tv_System.Net.Http.dll.sources
@@ -1 +1,6 @@
#include legacy.sources
+CFContentStream.cs
+CFNetworkHandler.cs
+HttpClientEx.cs
+NSUrlSessionHandler.cs
+RuntimeOptions.cs
diff --git a/mcs/class/System.Net.Http/monotouch_watch_System.Net.Http.dll.sources b/mcs/class/System.Net.Http/monotouch_watch_System.Net.Http.dll.sources
index cc55cb95b3d..c8cb6acafb1 100644
--- a/mcs/class/System.Net.Http/monotouch_watch_System.Net.Http.dll.sources
+++ b/mcs/class/System.Net.Http/monotouch_watch_System.Net.Http.dll.sources
@@ -1,2 +1,5 @@
#include legacy.sources
System.Net.Http/HttpClientHandler.platformnotsupported.cs
+HttpClientEx.cs
+NSUrlSessionHandler.cs
+RuntimeOptions.cs