| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- //
- // System.Net.WebConnectionTunnel
- //
- // Authors:
- // Gonzalo Paniagua Javier ([email protected])
- // Martin Baulig <[email protected]>
- //
- // (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;
- }
- }
- }
- }
- }
|