8fc30896db
Former-commit-id: c477e03582759447177c6d4bf412cd2355aad476
271 lines
7.8 KiB
C#
271 lines
7.8 KiB
C#
//
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |