Imported Upstream version 5.12.0.220

Former-commit-id: c477e03582759447177c6d4bf412cd2355aad476
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2018-04-24 09:31:23 +00:00
parent 8bd104cef2
commit 8fc30896db
1200 changed files with 29534 additions and 26161 deletions

File diff suppressed because it is too large Load Diff

View File

@ -39,6 +39,8 @@ using System.IO;
using System.IO.Compression;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
namespace System.Net
@ -60,16 +62,28 @@ namespace System.Net
Stream stream;
// Constructors
internal HttpWebResponse (Uri uri, string method, WebConnectionData data, CookieContainer container)
internal HttpWebResponse (Uri uri, string method, HttpStatusCode status, WebHeaderCollection headers)
{
this.uri = uri;
this.method = method;
webHeaders = data.Headers;
version = data.Version;
statusCode = (HttpStatusCode) data.StatusCode;
statusDescription = data.StatusDescription;
stream = data.stream;
this.statusCode = status;
this.statusDescription = HttpStatusDescription.Get (status);
this.webHeaders = headers;
version = HttpVersion.Version10;
contentLength = -1;
}
internal HttpWebResponse (Uri uri, string method, WebResponseStream stream, CookieContainer container)
{
this.uri = uri;
this.method = method;
this.stream = stream;
webHeaders = stream.Headers ?? new WebHeaderCollection ();
version = stream.Version;
statusCode = stream.StatusCode;
statusDescription = stream.StatusDescription ?? HttpStatusDescription.Get (statusCode);
contentLength = -1;
try {
@ -86,12 +100,12 @@ namespace System.Net
}
string content_encoding = webHeaders ["Content-Encoding"];
if (content_encoding == "gzip" && (data.request.AutomaticDecompression & DecompressionMethods.GZip) != 0) {
stream = new GZipStream (stream, CompressionMode.Decompress);
if (content_encoding == "gzip" && (stream.Request.AutomaticDecompression & DecompressionMethods.GZip) != 0) {
this.stream = new GZipStream (stream, CompressionMode.Decompress);
webHeaders.Remove (HttpRequestHeader.ContentEncoding);
}
else if (content_encoding == "deflate" && (data.request.AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
stream = new DeflateStream (stream, CompressionMode.Decompress);
else if (content_encoding == "deflate" && (stream.Request.AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
this.stream = new DeflateStream (stream, CompressionMode.Decompress);
webHeaders.Remove (HttpRequestHeader.ContentEncoding);
}
}
@ -154,6 +168,8 @@ namespace System.Net
if (contentType == null)
contentType = webHeaders ["Content-Type"];
if (contentType == null)
contentType = string.Empty;
return contentType;
}
@ -263,17 +279,6 @@ namespace System.Net
return (value != null) ? value : "";
}
internal void ReadAll ()
{
WebConnectionStream wce = stream as WebConnectionStream;
if (wce == null)
return;
try {
wce.ReadAll ();
} catch {}
}
public override Stream GetResponseStream ()
{
CheckDisposed ();
@ -310,12 +315,9 @@ namespace System.Net
public override void Close ()
{
if (stream != null) {
Stream st = stream;
stream = null;
if (st != null)
st.Close ();
}
var st = Interlocked.Exchange (ref stream, null);
if (st != null)
st.Close ();
}
void IDisposable.Dispose ()

View File

@ -1,52 +0,0 @@
//
// IWebConnectionState.cs
//
// Author:
// Martin Baulig <martin.baulig@xamarin.com>
//
// Copyright (c) 2014 Xamarin Inc. (http://www.xamarin.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.Threading;
namespace System.Net
{
interface IWebConnectionState {
WebConnectionGroup Group {
get;
}
ServicePoint ServicePoint {
get;
}
bool Busy {
get;
}
DateTime IdleSince {
get;
}
bool TrySetBusy ();
void SetIdle ();
}
}

View File

@ -38,20 +38,15 @@ using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace System.Net
namespace System.Net
{
public class ServicePoint
{
readonly Uri uri;
int connectionLimit;
int maxIdleTime;
int currentConnections;
DateTime idleSince;
DateTime lastDnsResolve;
Version protocolVersion;
IPHostEntry host;
bool usesProxy;
Dictionary<string,WebConnectionGroup> groups;
bool sendContinue = true;
bool useConnect;
object hostE = new object ();
@ -60,21 +55,22 @@ namespace System.Net
bool tcp_keepalive;
int tcp_keepalive_time;
int tcp_keepalive_interval;
Timer idleTimer;
// Constructors
internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
{
this.uri = uri;
this.connectionLimit = connectionLimit;
this.maxIdleTime = maxIdleTime;
this.currentConnections = 0;
this.idleSince = DateTime.UtcNow;
this.uri = uri;
Scheduler = new ServicePointScheduler (this, connectionLimit, maxIdleTime);
}
internal ServicePointScheduler Scheduler {
get;
}
// Properties
public Uri Address {
get { return uri; }
}
@ -84,15 +80,13 @@ namespace System.Net
return new NotImplementedException ();
}
public BindIPEndPoint BindIPEndPointDelegate
{
public BindIPEndPoint BindIPEndPointDelegate {
get { return endPointCallback; }
set { endPointCallback = value; }
}
[MonoTODO]
public int ConnectionLeaseTimeout
{
public int ConnectionLeaseTimeout {
get {
throw GetMustImplement ();
}
@ -100,54 +94,39 @@ namespace System.Net
throw GetMustImplement ();
}
}
public int ConnectionLimit {
get { return connectionLimit; }
set {
if (value <= 0)
throw new ArgumentOutOfRangeException ();
connectionLimit = value;
}
public int ConnectionLimit {
get { return Scheduler.ConnectionLimit; }
set { Scheduler.ConnectionLimit = value; }
}
public string ConnectionName {
get { return uri.Scheme; }
}
public int CurrentConnections {
get {
return currentConnections;
return Scheduler.CurrentConnections;
}
}
public DateTime IdleSince {
get {
return idleSince.ToLocalTime ();
return Scheduler.IdleSince.ToLocalTime ();
}
}
public int MaxIdleTime {
get { return maxIdleTime; }
set {
if (value < Timeout.Infinite || value > Int32.MaxValue)
throw new ArgumentOutOfRangeException ();
lock (this) {
maxIdleTime = value;
if (idleTimer != null)
idleTimer.Change (maxIdleTime, maxIdleTime);
}
}
get { return Scheduler.MaxIdleTime; }
set { Scheduler.MaxIdleTime = value; }
}
public virtual Version ProtocolVersion {
get { return protocolVersion; }
}
[MonoTODO]
public int ReceiveBufferSize
{
public int ReceiveBufferSize {
get {
throw GetMustImplement ();
}
@ -155,7 +134,7 @@ namespace System.Net
throw GetMustImplement ();
}
}
public bool SupportsPipelining {
get { return HttpVersion.Version11.Equals (protocolVersion); }
}
@ -172,8 +151,10 @@ namespace System.Net
}
internal bool SendContinue {
get { return sendContinue &&
(protocolVersion == null || protocolVersion == HttpVersion.Version11); }
get {
return sendContinue &&
(protocolVersion == null || protocolVersion == HttpVersion.Version11);
}
set { sendContinue = value; }
}
// Methods
@ -197,25 +178,25 @@ namespace System.Net
if (!tcp_keepalive)
return;
byte [] bytes = new byte [12];
PutBytes (bytes, (uint) (tcp_keepalive ? 1 : 0), 0);
PutBytes (bytes, (uint) tcp_keepalive_time, 4);
PutBytes (bytes, (uint) tcp_keepalive_interval, 8);
byte[] bytes = new byte[12];
PutBytes (bytes, (uint)(tcp_keepalive ? 1 : 0), 0);
PutBytes (bytes, (uint)tcp_keepalive_time, 4);
PutBytes (bytes, (uint)tcp_keepalive_interval, 8);
socket.IOControl (IOControlCode.KeepAliveValues, bytes, null);
}
static void PutBytes (byte [] bytes, uint v, int offset)
static void PutBytes (byte[] bytes, uint v, int offset)
{
if (BitConverter.IsLittleEndian) {
bytes [offset] = (byte) (v & 0x000000ff);
bytes [offset + 1] = (byte) ((v & 0x0000ff00) >> 8);
bytes [offset + 2] = (byte) ((v & 0x00ff0000) >> 16);
bytes [offset + 3] = (byte) ((v & 0xff000000) >> 24);
bytes[offset] = (byte)(v & 0x000000ff);
bytes[offset + 1] = (byte)((v & 0x0000ff00) >> 8);
bytes[offset + 2] = (byte)((v & 0x00ff0000) >> 16);
bytes[offset + 3] = (byte)((v & 0xff000000) >> 24);
} else {
bytes [offset + 3] = (byte) (v & 0x000000ff);
bytes [offset + 2] = (byte) ((v & 0x0000ff00) >> 8);
bytes [offset + 1] = (byte) ((v & 0x00ff0000) >> 16);
bytes [offset] = (byte) ((v & 0xff000000) >> 24);
bytes[offset + 3] = (byte)(v & 0x000000ff);
bytes[offset + 2] = (byte)((v & 0x0000ff00) >> 8);
bytes[offset + 1] = (byte)((v & 0x00ff0000) >> 16);
bytes[offset] = (byte)((v & 0xff000000) >> 24);
}
}
@ -231,107 +212,7 @@ namespace System.Net
set { useConnect = value; }
}
WebConnectionGroup GetConnectionGroup (string name)
{
if (name == null)
name = "";
/*
* Optimization:
*
* In the vast majority of cases, we only have one single WebConnectionGroup per ServicePoint, so we
* don't need to allocate a dictionary.
*
*/
WebConnectionGroup group;
if (groups != null && groups.TryGetValue (name, out group))
return group;
group = new WebConnectionGroup (this, name);
group.ConnectionClosed += (s, e) => currentConnections--;
if (groups == null)
groups = new Dictionary<string, WebConnectionGroup> ();
groups.Add (name, group);
return group;
}
void RemoveConnectionGroup (WebConnectionGroup group)
{
if (groups == null || groups.Count == 0)
throw new InvalidOperationException ();
groups.Remove (group.Name);
}
bool CheckAvailableForRecycling (out DateTime outIdleSince)
{
outIdleSince = DateTime.MinValue;
TimeSpan idleTimeSpan;
List<WebConnectionGroup> groupList = null, removeList = null;
lock (this) {
if (groups == null || groups.Count == 0) {
idleSince = DateTime.MinValue;
return true;
}
idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
/*
* WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
* copy the group dictionary if it exists.
*
* In most cases, we only have a single connection group, so we can simply store
* that in a local variable instead of copying a collection.
*
*/
groupList = new List<WebConnectionGroup> (groups.Values);
}
foreach (var group in groupList) {
if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
continue;
if (removeList == null)
removeList = new List<WebConnectionGroup> ();
removeList.Add (group);
}
lock (this) {
idleSince = outIdleSince;
if (removeList != null && groups != null) {
foreach (var group in removeList)
if (groups.ContainsKey (group.Name))
RemoveConnectionGroup (group);
}
if (groups != null && groups.Count == 0)
groups = null;
if (groups == null) {
if (idleTimer != null) {
idleTimer.Dispose ();
idleTimer = null;
}
return true;
}
return false;
}
}
void IdleTimerCallback (object obj)
{
DateTime dummy;
CheckAvailableForRecycling (out dummy);
}
private bool HasTimedOut
{
private bool HasTimedOut {
get {
int timeout = ServicePointManager.DnsRefreshTimeout;
return timeout != Timeout.Infinite &&
@ -339,8 +220,7 @@ namespace System.Net
}
}
internal IPHostEntry HostEntry
{
internal IPHostEntry HostEntry {
get {
lock (hostE) {
string uriHost = uri.Host;
@ -356,7 +236,7 @@ namespace System.Net
}
// Creates IPHostEntry
host = new IPHostEntry();
host = new IPHostEntry ();
host.AddressList = new IPAddress[] { IPAddress.Parse (uriHost) };
return host;
}
@ -382,41 +262,18 @@ namespace System.Net
protocolVersion = version;
}
internal EventHandler SendRequest (HttpWebRequest request, string groupName)
internal void SendRequest (WebOperation operation, string groupName)
{
WebConnection cnc;
lock (this) {
bool created;
WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
cnc = cncGroup.GetConnection (request, out created);
if (created) {
++currentConnections;
if (idleTimer == null)
idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
}
Scheduler.SendRequest (operation, groupName);
}
return cnc.SendRequest (request);
}
public bool CloseConnectionGroup (string connectionGroupName)
{
WebConnectionGroup cncGroup = null;
lock (this) {
cncGroup = GetConnectionGroup (connectionGroupName);
if (cncGroup != null) {
RemoveConnectionGroup (cncGroup);
}
return Scheduler.CloseConnectionGroup (connectionGroupName);
}
// WebConnectionGroup.Close() must *not* be called inside the lock
if (cncGroup != null) {
cncGroup.Close ();
return true;
}
return false;
}
//

File diff suppressed because it is too large Load Diff

View File

@ -1,232 +0,0 @@
//
// SimpleAsyncResult.cs
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
// Martin Baulig (martin.baulig@xamarin.com)
//
// (C) 2003 Ximian, Inc (http://www.ximian.com)
// Copyright (c) 2014 Xamarin Inc. (http://www.xamarin.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.IO;
using System.Threading;
namespace System.Net
{
delegate void SimpleAsyncCallback (SimpleAsyncResult result);
class SimpleAsyncResult : IAsyncResult
{
ManualResetEvent handle;
bool synch;
bool isCompleted;
readonly SimpleAsyncCallback cb;
object state;
bool callbackDone;
Exception exc;
object locker = new object ();
SimpleAsyncResult (SimpleAsyncCallback cb)
{
this.cb = cb;
}
protected SimpleAsyncResult (AsyncCallback cb, object state)
{
this.state = state;
this.cb = result => {
if (cb != null)
cb (this);
};
}
public static void Run (Func<SimpleAsyncResult, bool> func, SimpleAsyncCallback callback)
{
var result = new SimpleAsyncResult (callback);
try {
if (!func (result))
result.SetCompleted (true);
} catch (Exception ex) {
result.SetCompleted (true, ex);
}
}
public static void RunWithLock (object locker, Func<SimpleAsyncResult, bool> func, SimpleAsyncCallback callback)
{
Run (inner => {
bool running = func (inner);
if (running)
Monitor.Exit (locker);
return running;
}, inner => {
if (inner.GotException) {
if (inner.synch)
Monitor.Exit (locker);
callback (inner);
return;
}
try {
if (!inner.synch)
Monitor.Enter (locker);
callback (inner);
} finally {
Monitor.Exit (locker);
}
});
}
protected void Reset_internal ()
{
callbackDone = false;
exc = null;
lock (locker) {
isCompleted = false;
if (handle != null)
handle.Reset ();
}
}
internal void SetCompleted (bool synch, Exception e)
{
SetCompleted_internal (synch, e);
DoCallback_private ();
}
internal void SetCompleted (bool synch)
{
SetCompleted_internal (synch);
DoCallback_private ();
}
void SetCompleted_internal (bool synch, Exception e)
{
this.synch = synch;
exc = e;
lock (locker) {
isCompleted = true;
if (handle != null)
handle.Set ();
}
}
protected void SetCompleted_internal (bool synch)
{
SetCompleted_internal (synch, null);
}
void DoCallback_private ()
{
if (callbackDone)
return;
callbackDone = true;
if (cb == null)
return;
cb (this);
}
protected void DoCallback_internal ()
{
if (!callbackDone && cb != null) {
callbackDone = true;
cb (this);
}
}
internal void WaitUntilComplete ()
{
if (IsCompleted)
return;
AsyncWaitHandle.WaitOne ();
}
internal bool WaitUntilComplete (int timeout, bool exitContext)
{
if (IsCompleted)
return true;
return AsyncWaitHandle.WaitOne (timeout, exitContext);
}
public object AsyncState {
get { return state; }
}
public WaitHandle AsyncWaitHandle {
get {
lock (locker) {
if (handle == null)
handle = new ManualResetEvent (isCompleted);
}
return handle;
}
}
bool? user_read_synch;
public bool CompletedSynchronously {
get {
//
// CompletedSynchronously (for System.Net networking stack) means "was the operation completed before the first time
// that somebody asked if it was completed synchronously"? They do this because some of their asynchronous operations
// (particularly those in the Socket class) will avoid the cost of capturing and transferring the ExecutionContext
// to the callback thread by checking CompletedSynchronously, and calling the callback from within BeginXxx instead of
// on the completion port thread if the native winsock call completes quickly.
//
// TODO: racy
if (user_read_synch != null)
return user_read_synch.Value;
user_read_synch = synch;
return user_read_synch.Value;
}
}
internal bool CompletedSynchronouslyPeek {
get {
return synch;
}
}
public bool IsCompleted {
get {
lock (locker) {
return isCompleted;
}
}
}
internal bool GotException {
get { return (exc != null); }
}
internal Exception Exception {
get { return exc; }
}
}
}

View File

@ -1,132 +0,0 @@
//
// System.Net.WebAsyncResult
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (C) 2003 Ximian, Inc (http://www.ximian.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.IO;
using System.Threading;
namespace System.Net
{
class WebAsyncResult : SimpleAsyncResult
{
int nbytes;
IAsyncResult innerAsyncResult;
HttpWebResponse response;
Stream writeStream;
byte [] buffer;
int offset;
int size;
public bool EndCalled;
public bool AsyncWriteAll;
public HttpWebRequest AsyncObject;
public WebAsyncResult (AsyncCallback cb, object state)
: base (cb, state)
{
}
public WebAsyncResult (HttpWebRequest request, AsyncCallback cb, object state)
: base (cb, state)
{
this.AsyncObject = request;
}
public WebAsyncResult (AsyncCallback cb, object state, byte [] buffer, int offset, int size)
: base (cb, state)
{
this.buffer = buffer;
this.offset = offset;
this.size = size;
}
internal void Reset ()
{
this.nbytes = 0;
this.response = null;
this.buffer = null;
this.offset = 0;
this.size = 0;
Reset_internal ();
}
internal void SetCompleted (bool synch, int nbytes)
{
this.nbytes = nbytes;
SetCompleted_internal (synch);
}
internal void SetCompleted (bool synch, Stream writeStream)
{
this.writeStream = writeStream;
SetCompleted_internal (synch);
}
internal void SetCompleted (bool synch, HttpWebResponse response)
{
this.response = response;
SetCompleted_internal (synch);
}
internal void DoCallback ()
{
DoCallback_internal ();
}
internal int NBytes {
get { return nbytes; }
set { nbytes = value; }
}
internal IAsyncResult InnerAsyncResult {
get { return innerAsyncResult; }
set { innerAsyncResult = value; }
}
internal Stream WriteStream {
get { return writeStream; }
}
internal HttpWebResponse Response {
get { return response; }
}
internal byte [] Buffer {
get { return buffer; }
}
internal int Offset {
get { return offset; }
}
internal int Size {
get { return size; }
}
}
}

View File

@ -0,0 +1,155 @@
//
// WebCompletionSource.cs
//
// Author:
// Martin Baulig <mabaul@microsoft.com>
//
// Copyright (c) 2018 Xamarin Inc. (http://www.xamarin.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.Threading;
using System.Threading.Tasks;
using System.Runtime.ExceptionServices;
namespace System.Net
{
class WebCompletionSource<T>
{
TaskCompletionSource<Result> completion;
Result currentResult;
public WebCompletionSource (bool runAsync = true)
{
completion = new TaskCompletionSource<Result> (
runAsync ?
TaskCreationOptions.RunContinuationsAsynchronously :
TaskCreationOptions.None);
}
/*
* Provide a non-blocking way of getting the current status.
*
* We are using `TaskCreationOptions.RunContinuationsAsynchronously`
* to prevent any user continuations from being run during the
* `TrySet*()` methods - to make these safe to be called while holding
* internal locks.
*
*/
internal Result CurrentResult => currentResult;
internal Status CurrentStatus => currentResult?.Status ?? Status.Running;
internal Task Task => completion.Task;
public bool TrySetCompleted (T argument)
{
var result = new Result (argument);
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
return false;
return completion.TrySetResult (result);
}
public bool TrySetCompleted ()
{
var result = new Result (Status.Completed, null);
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
return false;
return completion.TrySetResult (result);
}
public bool TrySetCanceled ()
{
return TrySetCanceled (new OperationCanceledException ());
}
public bool TrySetCanceled (OperationCanceledException error)
{
var result = new Result (Status.Canceled, ExceptionDispatchInfo.Capture (error));
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
return false;
return completion.TrySetResult (result);
}
public bool TrySetException (Exception error)
{
var result = new Result (Status.Faulted, ExceptionDispatchInfo.Capture (error));
if (Interlocked.CompareExchange (ref currentResult, result, null) != null)
return false;
return completion.TrySetResult (result);
}
public void ThrowOnError ()
{
if (!completion.Task.IsCompleted)
return;
completion.Task.Result.Error?.Throw ();
}
public async Task<T> WaitForCompletion ()
{
var result = await completion.Task.ConfigureAwait (false);
if (result.Status == Status.Completed)
return (T)result.Argument;
// This will always throw once we get here.
result.Error.Throw ();
throw new InvalidOperationException ("Should never happen.");
}
internal enum Status : int {
Running,
Completed,
Canceled,
Faulted
}
internal class Result
{
public Status Status {
get;
}
public bool Success => Status == Status.Completed;
public ExceptionDispatchInfo Error {
get;
}
public T Argument {
get;
}
public Result (T argument)
{
Status = Status.Completed;
Argument = argument;
}
public Result (Status state, ExceptionDispatchInfo error)
{
Status = state;
Error = error;
}
}
}
class WebCompletionSource : WebCompletionSource<object>
{
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,80 +0,0 @@
//
// System.Net.WebConnectionData
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (C) 2003 Ximian, Inc (http://www.ximian.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.IO;
namespace System.Net
{
class WebConnectionData
{
HttpWebRequest _request;
public int StatusCode;
public string StatusDescription;
public WebHeaderCollection Headers;
public Version Version;
public Version ProxyVersion;
public Stream stream;
public string[] Challenge;
ReadState _readState;
public WebConnectionData ()
{
_readState = ReadState.None;
}
public WebConnectionData (HttpWebRequest request)
{
this._request = request;
}
public HttpWebRequest request {
get {
return _request;
}
set {
_request = value;
}
}
public ReadState ReadState {
get {
return _readState;
}
set {
lock (this) {
if ((_readState == ReadState.Aborted) && (value != ReadState.Aborted))
throw new WebException ("Aborted", WebExceptionStatus.RequestCanceled);
_readState = value;
}
}
}
}
}

View File

@ -1,292 +0,0 @@
//
// System.Net.WebConnectionGroup
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
// Martin Baulig (martin.baulig@xamarin.com)
//
// (C) 2003 Ximian, Inc (http://www.ximian.com)
// Copyright 2011-2014 Xamarin, Inc (http://www.xamarin.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.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Net.Configuration;
using System.Net.Sockets;
using System.Diagnostics;
namespace System.Net
{
class WebConnectionGroup
{
ServicePoint sPoint;
string name;
LinkedList<ConnectionState> connections;
Queue queue;
bool closing;
public WebConnectionGroup (ServicePoint sPoint, string name)
{
this.sPoint = sPoint;
this.name = name;
connections = new LinkedList<ConnectionState> ();
queue = new Queue ();
}
public event EventHandler ConnectionClosed;
void OnConnectionClosed ()
{
if (ConnectionClosed != null)
ConnectionClosed (this, null);
}
public void Close ()
{
List<WebConnection> connectionsToClose = null;
//TODO: what do we do with the queue? Empty it out and abort the requests?
//TODO: abort requests or wait for them to finish
lock (sPoint) {
closing = true;
var iter = connections.First;
while (iter != null) {
var cnc = iter.Value.Connection;
var node = iter;
iter = iter.Next;
// Closing connections inside the lock leads to a deadlock.
if (connectionsToClose == null)
connectionsToClose = new List<WebConnection>();
connectionsToClose.Add (cnc);
connections.Remove (node);
}
}
if (connectionsToClose != null) {
foreach (var cnc in connectionsToClose) {
cnc.Close (false);
OnConnectionClosed ();
}
}
}
public WebConnection GetConnection (HttpWebRequest request, out bool created)
{
lock (sPoint) {
return CreateOrReuseConnection (request, out created);
}
}
static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
{
if (!cnc.NtlmAuthenticated)
return;
bool needs_reset = false;
NetworkCredential cnc_cred = cnc.NtlmCredential;
bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
if (cnc_cred == null || req_cred == null ||
cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
cnc_cred.Password != req_cred.Password) {
needs_reset = true;
}
if (!needs_reset) {
bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
}
if (needs_reset) {
cnc.Close (false); // closes the authenticated connection
cnc.ResetNtlm ();
}
}
ConnectionState FindIdleConnection ()
{
foreach (var cnc in connections) {
if (cnc.Busy)
continue;
connections.Remove (cnc);
connections.AddFirst (cnc);
return cnc;
}
return null;
}
WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
{
var cnc = FindIdleConnection ();
if (cnc != null) {
created = false;
PrepareSharingNtlm (cnc.Connection, request);
return cnc.Connection;
}
if (sPoint.ConnectionLimit > connections.Count || connections.Count == 0) {
created = true;
cnc = new ConnectionState (this);
connections.AddFirst (cnc);
return cnc.Connection;
}
created = false;
cnc = connections.Last.Value;
connections.Remove (cnc);
connections.AddFirst (cnc);
return cnc.Connection;
}
public string Name {
get { return name; }
}
internal Queue Queue {
get { return queue; }
}
internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
{
var now = DateTime.UtcNow;
again:
bool recycled;
List<WebConnection> connectionsToClose = null;
lock (sPoint) {
if (closing) {
idleSince = DateTime.MinValue;
return true;
}
int count = 0;
var iter = connections.First;
while (iter != null) {
var cnc = iter.Value;
var node = iter;
iter = iter.Next;
++count;
if (cnc.Busy)
continue;
if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
if (cnc.IdleSince > idleSince)
idleSince = cnc.IdleSince;
continue;
}
/*
* Do not call WebConnection.Close() while holding the ServicePoint lock
* as this could deadlock when attempting to take the WebConnection lock.
*
*/
if (connectionsToClose == null)
connectionsToClose = new List<WebConnection> ();
connectionsToClose.Add (cnc.Connection);
connections.Remove (node);
}
recycled = connections.Count == 0;
}
// Did we find anything that can be closed?
if (connectionsToClose == null)
return recycled;
// Ok, let's get rid of these!
foreach (var cnc in connectionsToClose)
cnc.Close (false);
// Re-take the lock, then remove them from the connection list.
goto again;
}
class ConnectionState : IWebConnectionState {
public WebConnection Connection {
get;
private set;
}
public WebConnectionGroup Group {
get;
private set;
}
public ServicePoint ServicePoint {
get { return Group.sPoint; }
}
bool busy;
DateTime idleSince;
public bool Busy {
get { return busy; }
}
public DateTime IdleSince {
get { return idleSince; }
}
public bool TrySetBusy ()
{
lock (ServicePoint) {
if (busy)
return false;
busy = true;
idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
return true;
}
}
public void SetIdle ()
{
lock (ServicePoint) {
busy = false;
idleSince = DateTime.UtcNow;
}
}
public ConnectionState (WebConnectionGroup group)
{
Group = group;
idleSince = DateTime.UtcNow;
Connection = new WebConnection (this, group.sPoint);
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,271 @@
//
// System.Net.WebConnectionTunnel
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
// Martin Baulig <mabaul@microsoft.com>
//
// (C) 2003 Ximian, Inc (http://www.ximian.com)
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.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.IO;
using System.Collections;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.ExceptionServices;
using System.Diagnostics;
using Mono.Net.Security;
namespace System.Net
{
class WebConnectionTunnel
{
public HttpWebRequest Request {
get;
}
public Uri ConnectUri {
get;
}
public WebConnectionTunnel (HttpWebRequest request, Uri connectUri)
{
Request = request;
ConnectUri = connectUri;
}
enum NtlmAuthState
{
None,
Challenge,
Response
}
HttpWebRequest connectRequest;
NtlmAuthState ntlmAuthState;
public bool Success {
get;
private set;
}
public bool CloseConnection {
get;
private set;
}
public int StatusCode {
get;
private set;
}
public string StatusDescription {
get;
private set;
}
public string[] Challenge {
get;
private set;
}
public WebHeaderCollection Headers {
get;
private set;
}
public Version ProxyVersion {
get;
private set;
}
public byte[] Data {
get;
private set;
}
internal async Task Initialize (Stream stream, CancellationToken cancellationToken)
{
StringBuilder sb = new StringBuilder ();
sb.Append ("CONNECT ");
sb.Append (Request.Address.Host);
sb.Append (':');
sb.Append (Request.Address.Port);
sb.Append (" HTTP/");
if (Request.ProtocolVersion == HttpVersion.Version11)
sb.Append ("1.1");
else
sb.Append ("1.0");
sb.Append ("\r\nHost: ");
sb.Append (Request.Address.Authority);
bool ntlm = false;
var challenge = Challenge;
Challenge = null;
var auth_header = Request.Headers["Proxy-Authorization"];
bool have_auth = auth_header != null;
if (have_auth) {
sb.Append ("\r\nProxy-Authorization: ");
sb.Append (auth_header);
ntlm = auth_header.ToUpper ().Contains ("NTLM");
} else if (challenge != null && StatusCode == 407) {
ICredentials creds = Request.Proxy.Credentials;
have_auth = true;
if (connectRequest == null) {
// create a CONNECT request to use with Authenticate
connectRequest = (HttpWebRequest)WebRequest.Create (
ConnectUri.Scheme + "://" + ConnectUri.Host + ":" + ConnectUri.Port + "/");
connectRequest.Method = "CONNECT";
connectRequest.Credentials = creds;
}
if (creds != null) {
for (int i = 0; i < challenge.Length; i++) {
var auth = AuthenticationManager.Authenticate (challenge[i], connectRequest, creds);
if (auth == null)
continue;
ntlm = (auth.ModuleAuthenticationType == "NTLM");
sb.Append ("\r\nProxy-Authorization: ");
sb.Append (auth.Message);
break;
}
}
}
if (ntlm) {
sb.Append ("\r\nProxy-Connection: keep-alive");
ntlmAuthState++;
}
sb.Append ("\r\n\r\n");
StatusCode = 0;
byte[] connectBytes = Encoding.Default.GetBytes (sb.ToString ());
await stream.WriteAsync (connectBytes, 0, connectBytes.Length, cancellationToken).ConfigureAwait (false);
(Headers, Data, StatusCode) = await ReadHeaders (stream, cancellationToken).ConfigureAwait (false);
if ((!have_auth || ntlmAuthState == NtlmAuthState.Challenge) && Headers != null && StatusCode == 407) { // Needs proxy auth
var connectionHeader = Headers["Connection"];
if (!string.IsNullOrEmpty (connectionHeader) && connectionHeader.ToLower () == "close") {
// The server is requesting that this connection be closed
CloseConnection = true;
}
Challenge = Headers.GetValues ("Proxy-Authenticate");
Success = false;
} else {
Success = StatusCode == 200 && Headers != null;
}
if (Challenge == null && (StatusCode == 401 || StatusCode == 407)) {
var response = new HttpWebResponse (ConnectUri, "CONNECT", (HttpStatusCode)StatusCode, Headers);
throw new WebException (
StatusCode == 407 ? "(407) Proxy Authentication Required" : "(401) Unauthorized",
null, WebExceptionStatus.ProtocolError, response);
}
}
async Task<(WebHeaderCollection, byte[], int)> ReadHeaders (Stream stream, CancellationToken cancellationToken)
{
byte[] retBuffer = null;
int status = 200;
byte[] buffer = new byte[1024];
MemoryStream ms = new MemoryStream ();
while (true) {
cancellationToken.ThrowIfCancellationRequested ();
int n = await stream.ReadAsync (buffer, 0, 1024, cancellationToken).ConfigureAwait (false);
if (n == 0)
throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
ms.Write (buffer, 0, n);
int start = 0;
string str = null;
bool gotStatus = false;
WebHeaderCollection headers = new WebHeaderCollection ();
while (WebConnection.ReadLine (ms.GetBuffer (), ref start, (int)ms.Length, ref str)) {
if (str == null) {
int contentLen;
var clengthHeader = headers["Content-Length"];
if (string.IsNullOrEmpty (clengthHeader) || !int.TryParse (clengthHeader, out contentLen))
contentLen = 0;
if (ms.Length - start - contentLen > 0) {
// we've read more data than the response header and conents,
// give back extra data to the caller
retBuffer = new byte[ms.Length - start - contentLen];
Buffer.BlockCopy (ms.GetBuffer (), start + contentLen, retBuffer, 0, retBuffer.Length);
} else {
// haven't read in some or all of the contents for the response, do so now
FlushContents (stream, contentLen - (int)(ms.Length - start));
}
return (headers, retBuffer, status);
}
if (gotStatus) {
headers.Add (str);
continue;
}
string[] parts = str.Split (' ');
if (parts.Length < 2)
throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
if (String.Compare (parts[0], "HTTP/1.1", true) == 0)
ProxyVersion = HttpVersion.Version11;
else if (String.Compare (parts[0], "HTTP/1.0", true) == 0)
ProxyVersion = HttpVersion.Version10;
else
throw WebConnection.GetException (WebExceptionStatus.ServerProtocolViolation, null);
status = (int)UInt32.Parse (parts[1]);
if (parts.Length >= 3)
StatusDescription = String.Join (" ", parts, 2, parts.Length - 2);
gotStatus = true;
}
}
}
void FlushContents (Stream stream, int contentLength)
{
while (contentLength > 0) {
byte[] contentBuffer = new byte[contentLength];
int bytesRead = stream.Read (contentBuffer, 0, contentLength);
if (bytesRead > 0) {
contentLength -= bytesRead;
} else {
break;
}
}
}
}
}

View File

@ -0,0 +1,349 @@
//
// WebOperation.cs
//
// Author:
// Martin Baulig <mabaul@microsoft.com>
//
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.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.IO;
using System.Collections;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.ExceptionServices;
using System.Diagnostics;
namespace System.Net
{
class WebOperation
{
public HttpWebRequest Request {
get;
}
public WebConnection Connection {
get;
private set;
}
public ServicePoint ServicePoint {
get;
private set;
}
public BufferOffsetSize WriteBuffer {
get;
}
public bool IsNtlmChallenge {
get;
}
#if MONO_WEB_DEBUG
static int nextID;
internal readonly int ID = ++nextID;
string ME => $"WO({ID},{Connection?.ID ?? -1})";
#else
internal readonly int ID;
string ME;
#endif
public WebOperation (HttpWebRequest request, BufferOffsetSize writeBuffer, bool isNtlmChallenge, CancellationToken cancellationToken)
{
Request = request;
WriteBuffer = writeBuffer;
IsNtlmChallenge = isNtlmChallenge;
cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken);
requestTask = new WebCompletionSource<WebRequestStream> ();
requestWrittenTask = new WebCompletionSource<WebRequestStream> ();
responseTask = new WebCompletionSource<WebResponseStream> ();
finishedTask = new WebCompletionSource<(bool, WebOperation)> ();
}
CancellationTokenSource cts;
WebCompletionSource<WebRequestStream> requestTask;
WebCompletionSource<WebRequestStream> requestWrittenTask;
WebCompletionSource<WebResponseStream> responseTask;
WebCompletionSource<(bool, WebOperation)> finishedTask;
WebRequestStream writeStream;
WebResponseStream responseStream;
ExceptionDispatchInfo disposedInfo;
ExceptionDispatchInfo closedInfo;
WebOperation priorityRequest;
int requestSent;
int finished;
public bool Aborted {
get {
if (disposedInfo != null || Request.Aborted)
return true;
if (cts != null && cts.IsCancellationRequested)
return true;
return false;
}
}
public bool Closed {
get {
return Aborted || closedInfo != null;
}
}
public void Abort ()
{
var (exception, disposed) = SetDisposed (ref disposedInfo);
if (!disposed)
return;
cts?.Cancel ();
SetCanceled ();
Close ();
}
public void Close ()
{
var (exception, closed) = SetDisposed (ref closedInfo);
if (!closed)
return;
var stream = Interlocked.Exchange (ref writeStream, null);
if (stream != null) {
try {
stream.Close ();
} catch { }
}
}
void SetCanceled ()
{
WebConnection.Debug ($"{ME} SET CANCELED");
var error = new OperationCanceledException ();
requestTask.TrySetCanceled (error);
requestWrittenTask.TrySetCanceled (error);
responseTask.TrySetCanceled (error);
Finish (false, error);
}
void SetError (Exception error)
{
WebConnection.Debug ($"{ME} SET ERROR: error={error.GetType ().Name}");
requestTask.TrySetException (error);
requestWrittenTask.TrySetException (error);
responseTask.TrySetException (error);
Finish (false, error);
}
(ExceptionDispatchInfo, bool) SetDisposed (ref ExceptionDispatchInfo field)
{
var wexc = new WebException (SR.GetString (SR.net_webstatus_RequestCanceled), WebExceptionStatus.RequestCanceled);
var exception = ExceptionDispatchInfo.Capture (wexc);
var old = Interlocked.CompareExchange (ref field, exception, null);
return (old ?? exception, old == null);
}
internal ExceptionDispatchInfo CheckDisposed (CancellationToken cancellationToken)
{
if (Aborted || cancellationToken.IsCancellationRequested)
return CheckThrowDisposed (false, ref disposedInfo);
return null;
}
internal void ThrowIfDisposed ()
{
ThrowIfDisposed (CancellationToken.None);
}
internal void ThrowIfDisposed (CancellationToken cancellationToken)
{
if (Aborted || cancellationToken.IsCancellationRequested)
CheckThrowDisposed (true, ref disposedInfo);
}
internal void ThrowIfClosedOrDisposed ()
{
ThrowIfClosedOrDisposed (CancellationToken.None);
}
internal void ThrowIfClosedOrDisposed (CancellationToken cancellationToken)
{
if (Closed || cancellationToken.IsCancellationRequested)
CheckThrowDisposed (true, ref closedInfo);
}
ExceptionDispatchInfo CheckThrowDisposed (bool throwIt, ref ExceptionDispatchInfo field)
{
var (exception, disposed) = SetDisposed (ref field);
if (disposed)
cts?.Cancel ();
if (throwIt)
exception.Throw ();
return exception;
}
internal void RegisterRequest (ServicePoint servicePoint, WebConnection connection)
{
if (servicePoint == null)
throw new ArgumentNullException (nameof (servicePoint));
if (connection == null)
throw new ArgumentNullException (nameof (connection));
lock (this) {
if (Interlocked.CompareExchange (ref requestSent, 1, 0) != 0)
throw new InvalidOperationException ("Invalid nested call.");
ServicePoint = servicePoint;
Connection = connection;
}
cts.Token.Register (() => {
Request.FinishedReading = true;
SetDisposed (ref disposedInfo);
});
}
public void SetPriorityRequest (WebOperation operation)
{
lock (this) {
if (requestSent != 1 || ServicePoint == null || finished != 0)
throw new InvalidOperationException ("Should never happen.");
if (Interlocked.CompareExchange (ref priorityRequest, operation, null) != null)
throw new InvalidOperationException ("Invalid nested request.");
}
}
public async Task<Stream> GetRequestStream ()
{
return await requestTask.WaitForCompletion ().ConfigureAwait (false);
}
internal Task<WebRequestStream> GetRequestStreamInternal ()
{
return requestTask.WaitForCompletion ();
}
public Task WaitUntilRequestWritten ()
{
return requestWrittenTask.WaitForCompletion ();
}
public WebRequestStream WriteStream {
get {
ThrowIfDisposed ();
return writeStream;
}
}
public Task<WebResponseStream> GetResponseStream ()
{
return responseTask.WaitForCompletion ();
}
internal WebCompletionSource<(bool, WebOperation)> Finished => finishedTask;
internal async void Run ()
{
try {
WebConnection.Debug ($"{ME} RUN");
ThrowIfClosedOrDisposed ();
var requestStream = await Connection.InitConnection (this, cts.Token).ConfigureAwait (false);
ThrowIfClosedOrDisposed ();
writeStream = requestStream;
await requestStream.Initialize (cts.Token).ConfigureAwait (false);
ThrowIfClosedOrDisposed ();
requestTask.TrySetCompleted (requestStream);
var stream = new WebResponseStream (requestStream);
responseStream = stream;
await stream.InitReadAsync (cts.Token).ConfigureAwait (false);
WebConnection.Debug ($"{ME} RUN COMPLETE");
responseTask.TrySetCompleted (stream);
} catch (OperationCanceledException) {
WebConnection.Debug ($"{ME} RUN CANCELED");
SetCanceled ();
} catch (Exception e) {
WebConnection.Debug ($"{ME} RUN ERROR: {e.GetType ().Name}");
SetError (e);
}
}
internal void CompleteRequestWritten (WebRequestStream stream, Exception error = null)
{
WebConnection.Debug ($"{ME} COMPLETE REQUEST WRITTEN: {error != null}");
if (error != null)
SetError (error);
else
requestWrittenTask.TrySetCompleted (stream);
}
internal void Finish (bool ok, Exception error = null)
{
if (Interlocked.CompareExchange (ref finished, 1, 0) != 0)
return;
#if MONO_WEB_DEBUG
var me = $"{ME} FINISH";
#else
string me = null;
#endif
WebConnection.Debug ($"{me}: ok={ok} error={error?.GetType ()}");
WebResponseStream stream;
WebOperation next;
lock (this) {
stream = Interlocked.Exchange (ref responseStream, null);
next = Interlocked.Exchange (ref priorityRequest, null);
Request.FinishedReading = true;
}
if (error != null) {
if (next != null)
next.SetError (error);
WebConnection.Debug ($"{me} SET ERROR: {error.GetType ().Name}");
finishedTask.TrySetException (error);
return;
}
WebConnection.Debug ($"{me}: ok={ok} stream={stream != null} next={next != null}");
var keepAlive = !Aborted && ok && (stream?.KeepAlive ?? false);
if (next != null && next.Aborted) {
next = null;
keepAlive = false;
}
finishedTask.TrySetCompleted ((keepAlive, next));
WebConnection.Debug ($"{me} DONE: {keepAlive} next={next?.ID}");
}
}
}

View File

@ -0,0 +1,449 @@
//
// WebRequestStream.cs
//
// Author:
// Martin Baulig <mabaul@microsoft.com>
//
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.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.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Runtime.ExceptionServices;
using System.Net.Sockets;
namespace System.Net
{
class WebRequestStream : WebConnectionStream
{
static byte[] crlf = new byte[] { 13, 10 };
MemoryStream writeBuffer;
bool requestWritten;
bool allowBuffering;
bool sendChunked;
WebCompletionSource pendingWrite;
long totalWritten;
byte[] headers;
bool headersSent;
int completeRequestWritten;
int chunkTrailerWritten;
internal readonly string ME;
public WebRequestStream (WebConnection connection, WebOperation operation,
Stream stream, WebConnectionTunnel tunnel)
: base (connection, operation, stream)
{
allowBuffering = operation.Request.InternalAllowBuffering;
sendChunked = operation.Request.SendChunked && operation.WriteBuffer == null;
if (!sendChunked && allowBuffering && operation.WriteBuffer == null)
writeBuffer = new MemoryStream ();
KeepAlive = Request.KeepAlive;
if (tunnel?.ProxyVersion != null && tunnel?.ProxyVersion != HttpVersion.Version11)
KeepAlive = false;
#if MONO_WEB_DEBUG
ME = $"WRQ(Cnc={Connection.ID}, Op={Operation.ID})";
#endif
}
public bool KeepAlive {
get;
}
public override long Length {
get {
throw new NotSupportedException ();
}
}
public override bool CanRead => false;
public override bool CanWrite => true;
internal bool SendChunked {
get { return sendChunked; }
set { sendChunked = value; }
}
internal bool HasWriteBuffer {
get {
return Operation.WriteBuffer != null || writeBuffer != null;
}
}
internal int WriteBufferLength {
get {
if (Operation.WriteBuffer != null)
return Operation.WriteBuffer.Size;
if (writeBuffer != null)
return (int)writeBuffer.Length;
return -1;
}
}
internal BufferOffsetSize GetWriteBuffer ()
{
if (Operation.WriteBuffer != null)
return Operation.WriteBuffer;
if (writeBuffer == null || writeBuffer.Length == 0)
return null;
var buffer = writeBuffer.GetBuffer ();
return new BufferOffsetSize (buffer, 0, (int)writeBuffer.Length, false);
}
async Task FinishWriting (CancellationToken cancellationToken)
{
if (Interlocked.CompareExchange (ref completeRequestWritten, 1, 0) != 0)
return;
WebConnection.Debug ($"{ME} FINISH WRITING: {sendChunked}");
try {
Operation.ThrowIfClosedOrDisposed (cancellationToken);
if (sendChunked)
await WriteChunkTrailer_inner (cancellationToken).ConfigureAwait (false);
} catch (Exception ex) {
Operation.CompleteRequestWritten (this, ex);
throw;
} finally {
WebConnection.Debug ($"{ME} FINISH WRITING DONE");
}
Operation.CompleteRequestWritten (this);
}
public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
throw new ArgumentNullException (nameof (buffer));
int length = buffer.Length;
if (offset < 0 || length < offset)
throw new ArgumentOutOfRangeException (nameof (offset));
if (count < 0 || (length - offset) < count)
throw new ArgumentOutOfRangeException (nameof (count));
WebConnection.Debug ($"{ME} WRITE ASYNC: {buffer.Length}/{offset}/{count}");
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled (cancellationToken);
Operation.ThrowIfClosedOrDisposed (cancellationToken);
if (Operation.WriteBuffer != null)
throw new InvalidOperationException ();
var completion = new WebCompletionSource ();
if (Interlocked.CompareExchange (ref pendingWrite, completion, null) != null)
throw new InvalidOperationException (SR.GetString (SR.net_repcall));
return WriteAsyncInner (buffer, offset, count, completion, cancellationToken);
}
async Task WriteAsyncInner (byte[] buffer, int offset, int size,
WebCompletionSource completion,
CancellationToken cancellationToken)
{
try {
await ProcessWrite (buffer, offset, size, cancellationToken).ConfigureAwait (false);
WebConnection.Debug ($"{ME} WRITE ASYNC #1: {allowBuffering} {sendChunked} {Request.ContentLength} {totalWritten}");
if (Request.ContentLength > 0 && totalWritten == Request.ContentLength)
await FinishWriting (cancellationToken);
pendingWrite = null;
completion.TrySetCompleted ();
} catch (Exception ex) {
KillBuffer ();
closed = true;
WebConnection.Debug ($"{ME} WRITE ASYNC EX: {ex.Message}");
var oldError = Operation.CheckDisposed (cancellationToken);
if (oldError != null)
ex = oldError.SourceException;
else if (ex is SocketException)
ex = new IOException ("Error writing request", ex);
Operation.CompleteRequestWritten (this, ex);
pendingWrite = null;
completion.TrySetException (ex);
if (oldError != null)
oldError.Throw ();
throw;
}
}
async Task ProcessWrite (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
{
Operation.ThrowIfClosedOrDisposed (cancellationToken);
if (sendChunked) {
requestWritten = true;
string cSize = String.Format ("{0:X}\r\n", size);
byte[] head = Encoding.ASCII.GetBytes (cSize);
int chunkSize = 2 + size + head.Length;
byte[] newBuffer = new byte[chunkSize];
Buffer.BlockCopy (head, 0, newBuffer, 0, head.Length);
Buffer.BlockCopy (buffer, offset, newBuffer, head.Length, size);
Buffer.BlockCopy (crlf, 0, newBuffer, head.Length + size, crlf.Length);
if (allowBuffering) {
if (writeBuffer == null)
writeBuffer = new MemoryStream ();
writeBuffer.Write (buffer, offset, size);
}
totalWritten += size;
buffer = newBuffer;
offset = 0;
size = chunkSize;
} else {
CheckWriteOverflow (Request.ContentLength, totalWritten, size);
if (allowBuffering) {
if (writeBuffer == null)
writeBuffer = new MemoryStream ();
writeBuffer.Write (buffer, offset, size);
totalWritten += size;
if (Request.ContentLength <= 0 || totalWritten < Request.ContentLength)
return;
requestWritten = true;
buffer = writeBuffer.GetBuffer ();
offset = 0;
size = (int)totalWritten;
} else {
totalWritten += size;
}
}
await InnerStream.WriteAsync (buffer, offset, size, cancellationToken).ConfigureAwait (false);
}
void CheckWriteOverflow (long contentLength, long totalWritten, long size)
{
if (contentLength == -1)
return;
long avail = contentLength - totalWritten;
if (size > avail) {
KillBuffer ();
closed = true;
var throwMe = new ProtocolViolationException (
"The number of bytes to be written is greater than " +
"the specified ContentLength.");
Operation.CompleteRequestWritten (this, throwMe);
throw throwMe;
}
}
internal async Task Initialize (CancellationToken cancellationToken)
{
Operation.ThrowIfClosedOrDisposed (cancellationToken);
WebConnection.Debug ($"{ME} INIT: {Operation.WriteBuffer != null}");
if (Operation.WriteBuffer != null) {
if (Operation.IsNtlmChallenge)
Request.InternalContentLength = 0;
else
Request.InternalContentLength = Operation.WriteBuffer.Size;
}
await SetHeadersAsync (false, cancellationToken).ConfigureAwait (false);
Operation.ThrowIfClosedOrDisposed (cancellationToken);
if (Operation.WriteBuffer != null && !Operation.IsNtlmChallenge) {
await WriteRequestAsync (cancellationToken);
Close ();
}
}
async Task SetHeadersAsync (bool setInternalLength, CancellationToken cancellationToken)
{
Operation.ThrowIfClosedOrDisposed (cancellationToken);
if (headersSent)
return;
string method = Request.Method;
bool no_writestream = (method == "GET" || method == "CONNECT" || method == "HEAD" ||
method == "TRACE");
bool webdav = (method == "PROPFIND" || method == "PROPPATCH" || method == "MKCOL" ||
method == "COPY" || method == "MOVE" || method == "LOCK" ||
method == "UNLOCK");
if (Operation.IsNtlmChallenge)
no_writestream = true;
if (setInternalLength && !no_writestream && HasWriteBuffer)
Request.InternalContentLength = WriteBufferLength;
bool has_content = !no_writestream && (!HasWriteBuffer || Request.ContentLength > -1);
if (!(sendChunked || has_content || no_writestream || webdav))
return;
headersSent = true;
headers = Request.GetRequestHeaders ();
WebConnection.Debug ($"{ME} SET HEADERS: {Request.ContentLength}");
try {
await InnerStream.WriteAsync (headers, 0, headers.Length, cancellationToken).ConfigureAwait (false);
var cl = Request.ContentLength;
if (!sendChunked && cl == 0)
requestWritten = true;
} catch (Exception e) {
if (e is WebException || e is OperationCanceledException)
throw;
throw new WebException ("Error writing headers", WebExceptionStatus.SendFailure, WebExceptionInternalStatus.RequestFatal, e);
}
}
internal async Task WriteRequestAsync (CancellationToken cancellationToken)
{
Operation.ThrowIfClosedOrDisposed (cancellationToken);
WebConnection.Debug ($"{ME} WRITE REQUEST: {requestWritten} {sendChunked} {allowBuffering} {HasWriteBuffer}");
if (requestWritten)
return;
requestWritten = true;
if (sendChunked || !HasWriteBuffer)
return;
BufferOffsetSize buffer = GetWriteBuffer ();
if (buffer != null && !Operation.IsNtlmChallenge && Request.ContentLength != -1 && Request.ContentLength < buffer.Size) {
closed = true;
var throwMe = new WebException ("Specified Content-Length is less than the number of bytes to write", null,
WebExceptionStatus.ServerProtocolViolation, null);
Operation.CompleteRequestWritten (this, throwMe);
throw throwMe;
}
await SetHeadersAsync (true, cancellationToken).ConfigureAwait (false);
WebConnection.Debug ($"{ME} WRITE REQUEST #1: {buffer != null}");
Operation.ThrowIfClosedOrDisposed (cancellationToken);
if (buffer != null && buffer.Size > 0)
await InnerStream.WriteAsync (buffer.Buffer, 0, buffer.Size, cancellationToken);
await FinishWriting (cancellationToken);
}
async Task WriteChunkTrailer_inner (CancellationToken cancellationToken)
{
if (Interlocked.CompareExchange (ref chunkTrailerWritten, 1, 0) != 0)
return;
Operation.ThrowIfClosedOrDisposed (cancellationToken);
byte[] chunk = Encoding.ASCII.GetBytes ("0\r\n\r\n");
await InnerStream.WriteAsync (chunk, 0, chunk.Length, cancellationToken).ConfigureAwait (false);
}
async Task WriteChunkTrailer ()
{
var cts = new CancellationTokenSource ();
try {
cts.CancelAfter (WriteTimeout);
var timeoutTask = Task.Delay (WriteTimeout, cts.Token);
while (true) {
var completion = new WebCompletionSource ();
var oldCompletion = Interlocked.CompareExchange (ref pendingWrite, completion, null);
if (oldCompletion == null)
break;
var oldWriteTask = oldCompletion.WaitForCompletion ();
var ret = await Task.WhenAny (timeoutTask, oldWriteTask).ConfigureAwait (false);
if (ret == timeoutTask)
throw new WebException ("The operation has timed out.", WebExceptionStatus.Timeout);
}
await WriteChunkTrailer_inner (cts.Token).ConfigureAwait (false);
} catch {
// Intentionally eating exceptions.
} finally {
pendingWrite = null;
cts.Cancel ();
cts.Dispose ();
}
}
internal void KillBuffer ()
{
writeBuffer = null;
}
public override Task<int> ReadAsync (byte[] buffer, int offset, int size, CancellationToken cancellationToken)
{
return Task.FromException<int> (new NotSupportedException (SR.net_writeonlystream));
}
protected override void Close_internal (ref bool disposed)
{
WebConnection.Debug ($"{ME} CLOSE: {disposed} {requestWritten} {allowBuffering}");
if (disposed)
return;
disposed = true;
if (sendChunked) {
// Don't use FinishWriting() here, we need to block on 'pendingWrite' to ensure that
// any pending WriteAsync() has been completed.
//
// FIXME: I belive .NET simply aborts if you call Close() or Dispose() while writing,
// need to check this. 2017/07/17 Martin.
WriteChunkTrailer ().Wait ();
return;
}
if (!allowBuffering || requestWritten) {
Operation.CompleteRequestWritten (this);
return;
}
long length = Request.ContentLength;
if (!sendChunked && !Operation.IsNtlmChallenge && length != -1 && totalWritten != length) {
IOException io = new IOException ("Cannot close the stream until all bytes are written");
closed = true;
disposed = true;
var throwMe = new WebException ("Request was cancelled.", WebExceptionStatus.RequestCanceled, WebExceptionInternalStatus.RequestFatal, io);
Operation.CompleteRequestWritten (this, throwMe);
throw throwMe;
}
// Commented out the next line to fix xamarin bug #1512
//WriteRequest ();
disposed = true;
Operation.CompleteRequestWritten (this);
}
}
}

File diff suppressed because it is too large Load Diff