2014-08-13 10:39:27 +01:00
|
|
|
//
|
|
|
|
// System.Net.WebConnection
|
|
|
|
//
|
|
|
|
// Authors:
|
|
|
|
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
|
2018-04-24 09:31:23 +00:00
|
|
|
// Martin Baulig <mabaul@microsoft.com>
|
2014-08-13 10:39:27 +01:00
|
|
|
//
|
|
|
|
// (C) 2003 Ximian, Inc (http://www.ximian.com)
|
2018-04-24 09:31:23 +00:00
|
|
|
// Copyright (c) 2017 Xamarin Inc. (http://www.xamarin.com)
|
2014-08-13 10:39:27 +01:00
|
|
|
//
|
|
|
|
//
|
|
|
|
// 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;
|
2018-04-24 09:31:23 +00:00
|
|
|
using System.Threading.Tasks;
|
|
|
|
using System.Runtime.ExceptionServices;
|
2014-08-13 10:39:27 +01:00
|
|
|
using System.Diagnostics;
|
2016-02-22 11:00:01 -05:00
|
|
|
using Mono.Net.Security;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
|
|
|
namespace System.Net
|
|
|
|
{
|
|
|
|
enum ReadState
|
|
|
|
{
|
|
|
|
None,
|
|
|
|
Status,
|
|
|
|
Headers,
|
|
|
|
Content,
|
|
|
|
Aborted
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
class WebConnection : IDisposable
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
NetworkCredential ntlm_credentials;
|
|
|
|
bool ntlm_authenticated;
|
|
|
|
bool unsafe_sharing;
|
2018-04-24 09:31:23 +00:00
|
|
|
Stream networkStream;
|
|
|
|
Socket socket;
|
|
|
|
MonoTlsStream monoTlsStream;
|
|
|
|
WebConnectionTunnel tunnel;
|
|
|
|
int disposed;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
public ServicePoint ServicePoint {
|
|
|
|
get;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2016-02-22 11:00:01 -05:00
|
|
|
#if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
|
2014-08-13 10:39:27 +01:00
|
|
|
[System.Runtime.InteropServices.DllImport ("__Internal")]
|
2015-08-26 07:17:56 -04:00
|
|
|
static extern void xamarin_start_wwan (string uri);
|
2014-08-13 10:39:27 +01:00
|
|
|
#endif
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
public WebConnection (ServicePoint sPoint)
|
|
|
|
{
|
|
|
|
ServicePoint = sPoint;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
#if MONO_WEB_DEBUG
|
|
|
|
internal static bool EnableWebDebug {
|
|
|
|
get; set;
|
|
|
|
}
|
|
|
|
|
|
|
|
static WebConnection ()
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
2018-04-24 09:31:23 +00:00
|
|
|
if (Environment.GetEnvironmentVariable ("MONO_WEB_DEBUG") != null)
|
|
|
|
EnableWebDebug = true;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
#endif
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
[Conditional ("MONO_WEB_DEBUG")]
|
|
|
|
internal static void Debug (string message, params object[] args)
|
|
|
|
{
|
|
|
|
#if MONO_WEB_DEBUG
|
|
|
|
if (EnableWebDebug)
|
|
|
|
Console.Error.WriteLine (string.Format (message, args));
|
|
|
|
#endif
|
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
[Conditional ("MONO_WEB_DEBUG")]
|
|
|
|
internal static void Debug (string message)
|
|
|
|
{
|
|
|
|
#if MONO_WEB_DEBUG
|
|
|
|
if (EnableWebDebug)
|
|
|
|
Console.Error.WriteLine (message);
|
|
|
|
#endif
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CanReuse ()
|
|
|
|
{
|
|
|
|
// The real condition is !(socket.Poll (0, SelectMode.SelectRead) || socket.Available != 0)
|
|
|
|
// but if there's data pending to read (!) we won't reuse the socket.
|
|
|
|
return (socket.Poll (0, SelectMode.SelectRead) == false);
|
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
|
|
|
|
bool CheckReusable ()
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
2018-04-24 09:31:23 +00:00
|
|
|
if (socket != null && socket.Connected) {
|
|
|
|
try {
|
|
|
|
if (CanReuse ())
|
|
|
|
return true;
|
|
|
|
} catch { }
|
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
return false;
|
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
async Task Connect (WebOperation operation, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
IPHostEntry hostEntry = ServicePoint.HostEntry;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (hostEntry == null || hostEntry.AddressList.Length == 0) {
|
2016-02-22 11:00:01 -05:00
|
|
|
#if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
|
2018-04-24 09:31:23 +00:00
|
|
|
xamarin_start_wwan (ServicePoint.Address.ToString ());
|
|
|
|
hostEntry = ServicePoint.HostEntry;
|
2014-08-13 10:39:27 +01:00
|
|
|
if (hostEntry == null) {
|
|
|
|
#endif
|
2018-04-24 09:31:23 +00:00
|
|
|
throw GetException (ServicePoint.UsesProxy ? WebExceptionStatus.ProxyNameResolutionFailure :
|
|
|
|
WebExceptionStatus.NameResolutionFailure, null);
|
2016-02-22 11:00:01 -05:00
|
|
|
#if MONOTOUCH && !MONOTOUCH_TV && !MONOTOUCH_WATCH
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
Exception connectException = null;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
foreach (IPAddress address in hostEntry.AddressList) {
|
|
|
|
operation.ThrowIfDisposed (cancellationToken);
|
2014-08-13 10:39:27 +01:00
|
|
|
|
|
|
|
try {
|
2018-04-24 09:31:23 +00:00
|
|
|
socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
|
|
|
|
} catch (Exception se) {
|
|
|
|
// The Socket ctor can throw if we run out of FD's
|
|
|
|
throw GetException (WebExceptionStatus.ConnectFailure, se);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
IPEndPoint remote = new IPEndPoint (address, ServicePoint.Address.Port);
|
|
|
|
socket.NoDelay = !ServicePoint.UseNagleAlgorithm;
|
2014-08-13 10:39:27 +01:00
|
|
|
try {
|
2018-04-24 09:31:23 +00:00
|
|
|
ServicePoint.KeepAliveSetup (socket);
|
|
|
|
} catch {
|
|
|
|
// Ignore. Not supported in all platforms.
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (!ServicePoint.CallEndPointDelegate (socket, remote)) {
|
|
|
|
Interlocked.Exchange (ref socket, null)?.Close ();
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
try {
|
|
|
|
operation.ThrowIfDisposed (cancellationToken);
|
|
|
|
await socket.ConnectAsync (remote).ConfigureAwait (false);
|
|
|
|
} catch (ObjectDisposedException) {
|
|
|
|
throw;
|
|
|
|
} catch (Exception exc) {
|
|
|
|
Interlocked.Exchange (ref socket, null)?.Close ();
|
|
|
|
// Something went wrong, but we might have multiple IP Addresses
|
|
|
|
// and need to probe them all.
|
|
|
|
connectException = GetException (WebExceptionStatus.ConnectFailure, exc);
|
|
|
|
continue;
|
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (socket != null)
|
2014-08-13 10:39:27 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (connectException == null)
|
|
|
|
connectException = GetException (WebExceptionStatus.ConnectFailure, null);
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
throw connectException;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
#if MONO_WEB_DEBUG
|
|
|
|
static int nextID, nextRequestID;
|
|
|
|
readonly int id = ++nextID;
|
|
|
|
public int ID => disposed != 0 ? -id : id;
|
|
|
|
#else
|
|
|
|
internal readonly int ID;
|
|
|
|
#endif
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
async Task<bool> CreateStream (WebOperation operation, bool reused, CancellationToken cancellationToken)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
2018-04-24 09:31:23 +00:00
|
|
|
#if MONO_WEB_DEBUG
|
|
|
|
var requestID = ++nextRequestID;
|
|
|
|
#else
|
|
|
|
var requestID = 0;
|
|
|
|
#endif
|
2014-08-13 10:39:27 +01:00
|
|
|
|
|
|
|
try {
|
2018-04-24 09:31:23 +00:00
|
|
|
var stream = new NetworkStream (socket, false);
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
Debug ($"WC CREATE STREAM: Cnc={ID} {requestID} {reused} socket={socket.ID}");
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (operation.Request.Address.Scheme == Uri.UriSchemeHttps) {
|
|
|
|
if (!reused || monoTlsStream == null) {
|
|
|
|
if (ServicePoint.UseConnect) {
|
|
|
|
if (tunnel == null)
|
|
|
|
tunnel = new WebConnectionTunnel (operation.Request, ServicePoint.Address);
|
|
|
|
await tunnel.Initialize (stream, cancellationToken).ConfigureAwait (false);
|
|
|
|
if (!tunnel.Success)
|
|
|
|
return false;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
monoTlsStream = new MonoTlsStream (operation.Request, stream);
|
|
|
|
networkStream = await monoTlsStream.CreateStream (tunnel, cancellationToken).ConfigureAwait (false);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
return true;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
networkStream = stream;
|
|
|
|
return true;
|
|
|
|
} catch (Exception ex) {
|
|
|
|
ex = HttpWebRequest.FlattenException (ex);
|
|
|
|
Debug ($"WC CREATE STREAM EX: Cnc={ID} {requestID} {operation.Aborted} - {ex.Message}");
|
|
|
|
if (operation.Aborted || monoTlsStream == null)
|
|
|
|
throw GetException (WebExceptionStatus.ConnectFailure, ex);
|
|
|
|
throw GetException (monoTlsStream.ExceptionStatus, ex);
|
|
|
|
} finally {
|
|
|
|
Debug ($"WC CREATE STREAM DONE: Cnc={ID} {requestID}");
|
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
internal async Task<WebRequestStream> InitConnection (WebOperation operation, CancellationToken cancellationToken)
|
|
|
|
{
|
|
|
|
Debug ($"WC INIT CONNECTION: Cnc={ID} Req={operation.Request.ID} Op={operation.ID}");
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
bool reset = true;
|
2014-08-13 10:39:27 +01:00
|
|
|
retry:
|
2018-04-24 09:31:23 +00:00
|
|
|
operation.ThrowIfClosedOrDisposed (cancellationToken);
|
|
|
|
|
|
|
|
var reused = CheckReusable ();
|
|
|
|
Debug ($"WC INIT CONNECTION #1: Cnc={ID} Op={operation.ID} - {reused} - {operation.WriteBuffer != null} {operation.IsNtlmChallenge}");
|
|
|
|
if (!reused) {
|
|
|
|
CloseSocket ();
|
|
|
|
if (reset)
|
|
|
|
Reset ();
|
|
|
|
try {
|
|
|
|
await Connect (operation, cancellationToken).ConfigureAwait (false);
|
|
|
|
Debug ($"WC INIT CONNECTION #2: Cnc={ID} Op={operation.ID} {socket.LocalEndPoint}");
|
|
|
|
} catch (Exception ex) {
|
|
|
|
Debug ($"WC INIT CONNECTION #2 FAILED: Cnc={ID} Op={operation.ID} - {ex.Message}\n{ex}");
|
|
|
|
throw;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
var success = await CreateStream (operation, reused, cancellationToken).ConfigureAwait (false);
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
Debug ($"WC INIT CONNECTION #3: Cnc={ID} Op={operation.ID} - {success}");
|
|
|
|
if (!success) {
|
|
|
|
if (tunnel?.Challenge == null)
|
|
|
|
throw GetException (WebExceptionStatus.ProtocolError, null);
|
2016-11-10 13:04:39 +00:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (tunnel.CloseConnection)
|
|
|
|
CloseSocket ();
|
|
|
|
reset = false;
|
|
|
|
goto retry;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
networkStream.ReadTimeout = operation.Request.ReadWriteTimeout;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
return new WebRequestStream (this, operation, networkStream, tunnel);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
internal static WebException GetException (WebExceptionStatus status, Exception error)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
2018-04-24 09:31:23 +00:00
|
|
|
if (error == null)
|
|
|
|
return new WebException ($"Error: {status}", status);
|
|
|
|
if (error is WebException wex)
|
|
|
|
return wex;
|
|
|
|
return new WebException ($"Error: {status} ({error.Message})", status,
|
|
|
|
WebExceptionInternalStatus.RequestFatal, error);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
|
|
|
|
internal static bool ReadLine (byte[] buffer, ref int start, int max, ref string output)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
bool foundCR = false;
|
|
|
|
StringBuilder text = new StringBuilder ();
|
|
|
|
|
|
|
|
int c = 0;
|
|
|
|
while (start < max) {
|
2018-04-24 09:31:23 +00:00
|
|
|
c = (int)buffer[start++];
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (c == '\n') { // newline
|
|
|
|
if ((text.Length > 0) && (text[text.Length - 1] == '\r'))
|
2014-08-13 10:39:27 +01:00
|
|
|
text.Length--;
|
|
|
|
|
|
|
|
foundCR = false;
|
|
|
|
break;
|
|
|
|
} else if (foundCR) {
|
|
|
|
text.Length--;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c == '\r')
|
|
|
|
foundCR = true;
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
|
|
|
|
text.Append ((char)c);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (c != '\n' && c != '\r')
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (text.Length == 0) {
|
|
|
|
output = null;
|
|
|
|
return (c == '\n' || c == '\r');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (foundCR)
|
|
|
|
text.Length--;
|
|
|
|
|
|
|
|
output = text.ToString ();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
internal bool CanReuseConnection (WebOperation operation)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
lock (this) {
|
2018-04-24 09:31:23 +00:00
|
|
|
if (Closed || currentOperation != null)
|
|
|
|
return false;
|
|
|
|
if (!NtlmAuthenticated)
|
|
|
|
return true;
|
2018-01-19 16:41:39 +00:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
NetworkCredential cnc_cred = NtlmCredential;
|
|
|
|
var request = operation.Request;
|
2018-01-19 16:41:39 +00:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
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;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
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) {
|
|
|
|
return false;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
|
|
|
|
bool cnc_sharing = UnsafeAuthenticatedConnectionSharing;
|
|
|
|
return !(req_sharing == false || req_sharing != cnc_sharing);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
bool PrepareSharingNtlm (WebOperation operation)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
2018-04-24 09:31:23 +00:00
|
|
|
if (operation == null || !NtlmAuthenticated)
|
2014-08-13 10:39:27 +01:00
|
|
|
return true;
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
bool needs_reset = false;
|
|
|
|
NetworkCredential cnc_cred = NtlmCredential;
|
|
|
|
var request = operation.Request;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
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;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
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;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (!needs_reset) {
|
|
|
|
bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
|
|
|
|
bool cnc_sharing = UnsafeAuthenticatedConnectionSharing;
|
|
|
|
needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
return needs_reset;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
void Reset ()
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
lock (this) {
|
2018-04-24 09:31:23 +00:00
|
|
|
tunnel = null;
|
|
|
|
ResetNtlm ();
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
}
|
2018-01-19 16:41:39 +00:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
void Close (bool reset)
|
|
|
|
{
|
2018-01-19 16:41:39 +00:00
|
|
|
lock (this) {
|
2018-04-24 09:31:23 +00:00
|
|
|
CloseSocket ();
|
|
|
|
if (reset)
|
|
|
|
Reset ();
|
2018-01-19 16:41:39 +00:00
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
void CloseSocket ()
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
lock (this) {
|
2018-08-30 08:22:09 +00:00
|
|
|
Debug ($"WC CLOSE SOCKET: Cnc={ID} NS={networkStream} TLS={monoTlsStream}");
|
2018-04-24 09:31:23 +00:00
|
|
|
if (networkStream != null) {
|
|
|
|
try {
|
|
|
|
networkStream.Dispose ();
|
|
|
|
} catch { }
|
|
|
|
networkStream = null;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-08-30 08:22:09 +00:00
|
|
|
if (monoTlsStream != null) {
|
|
|
|
try {
|
|
|
|
monoTlsStream.Dispose ();
|
|
|
|
} catch { }
|
|
|
|
monoTlsStream = null;
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (socket != null) {
|
2014-08-13 10:39:27 +01:00
|
|
|
try {
|
2018-04-24 09:31:23 +00:00
|
|
|
socket.Dispose ();
|
|
|
|
} catch { }
|
|
|
|
socket = null;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
|
|
|
|
monoTlsStream = null;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DateTime idleSince;
|
|
|
|
WebOperation currentOperation;
|
|
|
|
|
|
|
|
public bool Closed => disposed != 0;
|
|
|
|
|
|
|
|
public bool Busy {
|
|
|
|
get { return currentOperation != null; }
|
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
public DateTime IdleSince {
|
|
|
|
get { return idleSince; }
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
public bool StartOperation (WebOperation operation, bool reused)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
lock (this) {
|
2018-04-24 09:31:23 +00:00
|
|
|
if (Closed)
|
2014-08-13 10:39:27 +01:00
|
|
|
return false;
|
2018-04-24 09:31:23 +00:00
|
|
|
if (Interlocked.CompareExchange (ref currentOperation, operation, null) != null)
|
2014-08-13 10:39:27 +01:00
|
|
|
return false;
|
2018-04-24 09:31:23 +00:00
|
|
|
|
|
|
|
idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
|
|
|
|
|
|
|
|
if (reused && !PrepareSharingNtlm (operation)) {
|
|
|
|
Debug ($"WC START - CAN'T REUSE: Cnc={ID} Op={operation.ID}");
|
|
|
|
Close (true);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
operation.RegisterRequest (ServicePoint, this);
|
|
|
|
Debug ($"WC START: Cnc={ID} Op={operation.ID}");
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
|
|
|
|
operation.Run ();
|
2014-08-13 10:39:27 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
public bool Continue (WebOperation next)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
lock (this) {
|
2018-04-24 09:31:23 +00:00
|
|
|
if (Closed)
|
|
|
|
return false;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
Debug ($"WC CONTINUE: Cnc={ID} connected={socket?.Connected} next={next?.ID} current={currentOperation?.ID}");
|
|
|
|
if (socket == null || !socket.Connected || !PrepareSharingNtlm (next)) {
|
|
|
|
Close (true);
|
|
|
|
return false;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
currentOperation = next;
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
if (next == null)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// Ok, we got another connection. Let's run it!
|
|
|
|
next.RegisterRequest (ServicePoint, this);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
2018-04-24 09:31:23 +00:00
|
|
|
|
|
|
|
next.Run ();
|
|
|
|
return true;
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
void Dispose (bool disposing)
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
2018-04-24 09:31:23 +00:00
|
|
|
if (Interlocked.CompareExchange (ref disposed, 1, 0) != 0)
|
|
|
|
return;
|
|
|
|
Debug ($"WC DISPOSE: Cnc={ID}");
|
|
|
|
Close (true);
|
|
|
|
}
|
2014-08-13 10:39:27 +01:00
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
public void Dispose ()
|
|
|
|
{
|
|
|
|
Dispose (true);
|
2014-08-13 10:39:27 +01:00
|
|
|
}
|
|
|
|
|
2018-04-24 09:31:23 +00:00
|
|
|
void ResetNtlm ()
|
2014-08-13 10:39:27 +01:00
|
|
|
{
|
|
|
|
ntlm_authenticated = false;
|
|
|
|
ntlm_credentials = null;
|
|
|
|
unsafe_sharing = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
internal bool NtlmAuthenticated {
|
|
|
|
get { return ntlm_authenticated; }
|
|
|
|
set { ntlm_authenticated = value; }
|
|
|
|
}
|
|
|
|
|
|
|
|
internal NetworkCredential NtlmCredential {
|
|
|
|
get { return ntlm_credentials; }
|
|
|
|
set { ntlm_credentials = value; }
|
|
|
|
}
|
|
|
|
|
|
|
|
internal bool UnsafeAuthenticatedConnectionSharing {
|
|
|
|
get { return unsafe_sharing; }
|
|
|
|
set { unsafe_sharing = value; }
|
|
|
|
}
|
|
|
|
// -
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|