///
/// Copyright © 2003-2008 JetBrains s.r.o.
/// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder).
///
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Diagnostics;
using System.Text;
using System.Threading;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using JetBrains.Omea.Base;
using JetBrains.Omea.Diagnostics;
using Microsoft.Web.Services.Security.X509;
using JetBrains.Omea.OpenAPI;
namespace JetBrains.Omea.HttpTools
{
public class HttpReader : AbstractJob
{
/**
* size of byte buffer for async reading of http stream
*/
public const int BufferSize = 0x4000;
/**
* state of http reader
* is set (and may asked) after execution of each async operation
*/
public enum State
{
NotStarted,
Connecting,
Reading,
Done,
Error
}
/**
* type of URL
*/
public enum URLType
{
Undefined,
File,
Web
}
public static string UserAgent
{
get
{
return _userAgent;
}
set
{
_userAgent = Encoding.ASCII.GetString(Encoding.ASCII.GetBytes(value)); // Suppress non-ASCII characters
}
}
public static IWebProxy DefaultProxy
{
get
{
if ( _defaultProxy == null )
{
_defaultProxy = GlobalProxySelection.GetEmptyWebProxy();
}
return _defaultProxy;
}
}
public ICookieProvider CookieProvider
{
get { return _cookieProvider; }
set { _cookieProvider = value; }
}
/**
* sets proxy for all webrequests
* opens current user's SSL certificates store
*/
public static void LoadHttpConfig()
{
Trace.WriteLine( "Loading proxy settings" );
try
{
_defaultProxy = LoadDefaultProxy();
}
catch( Exception ex )
{
Trace.WriteLine( "Error loading default proxy settings: " + ex );
}
GlobalProxySelection.Select = DefaultProxy;
X509CertificateStore store =
X509CertificateStore.CurrentUserStore( X509CertificateStore.MyStore );
if( store.OpenRead() )
{
_certificates = store.Certificates;
Trace.WriteLine( "Number of current user's certificates: " + _certificates.Count, "HttpReader" );
}
Trace.WriteLine( "Default connection limit: " + ServicePointManager.DefaultConnectionLimit );
Trace.WriteLine( "Max service point idle time: " + ServicePointManager.MaxServicePointIdleTime );
Trace.WriteLine( "Max service points: " + ServicePointManager.MaxServicePoints );
}
private static WebProxy LoadDefaultProxy()
{
WebProxy proxy;
ISettingStore ini = Core.SettingStore;
string address = ini.ReadString( "HttpProxy", "Address" );
bool haveProxy = false;
if( address.Length == 0 )
{
proxy = WebProxy.GetDefaultProxy();
proxy.Credentials = CredentialCache.DefaultCredentials;
if ( proxy.Address != null )
{
Trace.WriteLine( "Using proxy settings from IE: address " + proxy.Address +
", bypass on local addresses " + proxy.BypassProxyOnLocal );
haveProxy = true;
}
else
{
Trace.WriteLine( "No proxy configured in IE" );
}
}
else
{
do
{
int port = ini.ReadInt( "HttpProxy", "Port", 3128 );
try
{
proxy = new WebProxy( address, port );
}
catch
{
proxy = WebProxy.GetDefaultProxy();
}
string user = ini.ReadString( "HttpProxy", "User" );
if( user.Length == 0 )
{
proxy.Credentials = CredentialCache.DefaultCredentials;
}
else
{
string password = ini.ReadString( "HttpProxy", "Password" );
string[] userParts = user.Split( '\\', '/' );
if( userParts.Length == 2 )
{
proxy.Credentials = new NetworkCredential( userParts[ 1 ], password, userParts[ 0 ] );
}
else
{
proxy.Credentials = new NetworkCredential( user, password );
}
}
proxy.BypassProxyOnLocal = ini.ReadBool( "HttpProxy", "BypassLocal", true );
string bypassList = ini.ReadString( "HttpProxy", "BypassList" );
if ( bypassList.Trim().Length > 0 )
{
try
{
proxy.BypassList = bypassList.Split( ';' );
}
catch{}
}
if ( proxy.Address != null )
{
Trace.WriteLine( "Using proxy settings specified by user: address " + proxy.Address +
", bypass on local addresses " + proxy.BypassProxyOnLocal );
haveProxy = true;
}
else
{
Trace.WriteLine( "No proxy configured by user" );
}
} while( false );
}
if ( haveProxy )
{
ServicePointManager.DefaultConnectionLimit = 5;
}
return proxy;
}
///
/// Requests an URL.
///
/// URL to web resource.
public HttpReader( string url )
{
Guard.EmptyStringArgument( url, "URL" );
_urlType = SupportedProtocol( url );
_state = State.NotStarted;
_url = url;
_certfId = -1;
_ifModifiedSince = DateTime.MinValue;
if( ICore.Instance != null && Core.SettingStore.ReadBool( "HttpReader", "Trace", false ) )
{
_tracer = new Tracer( "HttpReader" );
}
}
///
/// Requests an URL sending specified request stream using the POST method.
///
/// URL to web resource.
/// Stream to send with request.
public HttpReader( string url, JetMemoryStream requestStream, string requestContentType )
: this( url )
{
if( _urlType != URLType.Web )
{
throw new InvalidOperationException( "Only web URL can be requested using POST method" );
}
_requestStream = requestStream;
_requestContentType = requestContentType;
}
public static bool IsSupportedProtocol( string url )
{
Guard.NullArgument( url, "url" );
return SupportedProtocol( url ) != URLType.Undefined;
}
public static URLType SupportedProtocol( string url )
{
Guard.NullArgument( url, "url" );
URLType urlType = URLType.Undefined;
if( Utils.StartsWith( url, "http:", true ) || Utils.StartsWith( url, "https:", true ))
urlType = URLType.Web;
else
if( Utils.StartsWith( url, "file:", true ) )
urlType = URLType.File;
return urlType;
}
/**
* read-only properties
* can be asked for specialized controlling of downloading, say,
* when HttpReader switches from one state to another
*/
public State CurrentState { get { return _state; } }
public string URL { get { return _url; } }
public URLType Type { get { return _urlType; } }
public X509Certificate X509Certificate { get { return _X509Certificate; } }
public HttpWebResponse WebResponse { get { return _response; } }
public Stream ReadStream { get { return _readStream; } }
public FileInfo fileInfo { get { return _fileInfo; } }
public string ETag { get { return _ETag; } }
public string IfNoneMatch { set { _ifNoneMatch = value; } }
public DateTime IfModifiedSince { set { _ifModifiedSince = value; } }
public Exception LastException { get { return _lastException; } }
public ICredentials Credentials
{
get { return _credentials; }
set { _credentials = value; }
}
public string AcceptInstanceManipulation
{
get { return _acceptInstanceManipulation; }
set { _acceptInstanceManipulation = value; }
}
public string AcceptEncoding
{
get { return _acceptEncoding; }
set { _acceptEncoding = value; }
}
public string RedirectUrl
{
get { return _redirectUrl; }
}
public string CharacterSet
{
get
{
if ( _response == null )
{
return null;
}
int i = _response.ContentType.IndexOf( "charset=" );
if( i > 0 )
{
i += 8;
string charset = _response.ContentType;
int separator = charset.IndexOfAny( new char[] { ';', ',', ' ' }, i );
charset = ( separator > 0 ) ? charset.Substring( i, separator - i ) : charset.Substring( i );
charset = charset.Trim();
if( charset.Length > 0 )
{
return charset.Replace( '_', '-' ).Trim( '"' );
}
}
return null;
}
}
#region implementation details
protected override void Execute()
{
if( _urlType == URLType.File )
{
ProcessFileURL();
}
else
if( _urlType == URLType.Web )
{
if( _requestStream != null )
{
StartRequestWebURL();
}
else
{
StartProcessingWebURL();
}
}
}
private void ProcessFileURL()
{
try
{
Uri uri = new Uri( _url );
string localPath = uri.LocalPath.Replace( '|', ':' );
_fileInfo = new FileInfo( localPath );
TraceString( "Reading " + localPath );
_readStream = _fileInfo.OpenRead();
_state = State.Done;
}
catch( Exception e )
{
_lastException = e;
TraceException( e );
_state = State.Error;
}
}
private void StartRequestWebURL()
{
_state = State.Connecting;
try
{
PrepareRequest();
_request.Method = "POST";
_request.ContentType = _requestContentType;
_request.ContentLength = _requestStream.Length;
_asyncResult = _request.BeginGetRequestStream( null, null );
InvokeAfterWait( WriteRequestStream, _asyncResult.AsyncWaitHandle );
}
catch( Exception e )
{
_lastException = e;
TraceException( e );
_state = State.Error;
}
}
private void WriteRequestStream()
{
try
{
Stream requestStream = _request.EndGetRequestStream( _asyncResult );
_requestStream.WriteTo( requestStream );
requestStream.Close();
CloseAsyncWaitHandle();
_asyncResult = _request.BeginGetResponse( null, null );
InvokeAfterWait( ProcessWebResponse, _asyncResult.AsyncWaitHandle );
}
catch( Exception e )
{
_lastException = e;
TraceException( e );
_state = State.Error;
}
}
private void StartProcessingWebURL()
{
_state = State.Connecting;
try
{
PrepareRequest();
int wrkTh, complTh;
ThreadPool.GetAvailableThreads( out wrkTh, out complTh );
_asyncResult = _request.BeginGetResponse( null, null );
InvokeAfterWait( ProcessWebResponse, _asyncResult.AsyncWaitHandle );
}
catch( Exception e )
{
_lastException = e;
TraceException( e );
_state = State.Error;
}
}
protected virtual void PrepareRequest()
{
if( _certfId < 0 || _certificates == null )
{
_X509Certificate = null;
TraceString( "Requesting " + _url );
}
else
{
_X509Certificate = _certificates[ _certfId ];
TraceString( "Requesting " + _url + " using certificate " + _X509Certificate.GetName() );
}
_request = ( HttpWebRequest ) WebRequest.Create( _url );
_request.Proxy = DefaultProxy;
Uri requestUri = new Uri( _url );
Uri proxyUri = _request.Proxy.GetProxy( requestUri );
if ( proxyUri != null && !proxyUri.Equals( requestUri ) )
{
TraceString( "Using proxy " + proxyUri );
}
else if ( _request.Proxy.IsBypassed( requestUri ) )
{
TraceString( "Bypassing proxy" );
}
else
{
TraceString( "Using no proxy" );
}
if( _tracer != null )
{
TraceString( String.Format( "Service point connection limit: {0}, current connections: {1}",
_request.ServicePoint.ConnectionLimit, _request.ServicePoint.CurrentConnections ) );
}
_request.AllowAutoRedirect = false;
_request.UserAgent = _userAgent;
if( _X509Certificate != null )
{
_request.ClientCertificates.Clear();
_request.ClientCertificates.Add( _X509Certificate );
}
if( _cookieProvider != null )
{
string cookies = _cookieProvider.GetCookies( requestUri.AbsoluteUri );
if( cookies != null )
{
_request.CookieContainer = new CookieContainer();
// fix OM-14781 - do not interrupt processing of the feed if the
// cookies format is illegal - continue as if there is all OK.
try { _request.CookieContainer.SetCookies( requestUri, cookies ); }
catch( CookieException ) { /* May be we can just ignore them? */ }
}
}
if( _ifNoneMatch != null )
{
_request.Headers[ "If-None-Match" ] = _ifNoneMatch;
}
if( _ifModifiedSince != DateTime.MinValue )
{
_request.IfModifiedSince = _ifModifiedSince;
}
if( _credentials != null )
{
_request.Credentials = _credentials;
}
else
{
_request.Credentials = CredentialCache.DefaultCredentials;
}
if( _acceptInstanceManipulation != null )
{
_request.Headers[ "A-IM" ] = _acceptInstanceManipulation;
}
// to avoid problems with 'deflate' encoding support (some servers send
// the zlib header in the deflated stream and some don't), tell that we only
// support gzip encoding
if ( _acceptEncoding != null )
{
_request.Headers ["Accept-Encoding"] = _acceptEncoding;
}
_request.Pipelined = false;
}
private static byte[] _lastBuffer = null;
protected virtual byte[] CreateBuffer()
{
byte[] result = _lastBuffer ?? new byte[ BufferSize ];
_lastBuffer = null;
return result;
}
protected virtual Stream CreateReadStream()
{
return new JetMemoryStream( BufferSize );
}
private void ProcessWebResponse()
{
try
{
_response = ( HttpWebResponse ) _request.EndGetResponse( _asyncResult );
string responseUrl = _response.ResponseUri.AbsoluteUri;
WebHeaderCollection headers = _response.Headers;
TraceString( "Reading " + responseUrl + " ContentType=" + _response.ContentType );
if ( _response.StatusCode == HttpStatusCode.Moved ||
_response.StatusCode == HttpStatusCode.Redirect ||
_response.StatusCode == HttpStatusCode.RedirectKeepVerb )
{
_redirectCount++;
if ( _redirectCount > _maximumRedirectCount )
{
throw new Exception( "Exceeded maximum redirect count" );
}
Uri baseUri = new Uri( _url );
_url = new Uri( baseUri, headers ["Location"] ).ToString();
if( _response.StatusCode == HttpStatusCode.Moved ||
_response.StatusCode == HttpStatusCode.Redirect )
{
_redirectUrl = _url;
}
CloseAsyncWaitHandle();
_response.Close();
StartProcessingWebURL();
return;
}
_state = State.Reading;
_ETag = headers[ "ETag" ];
if( _ETag == null )
{
_ETag = string.Empty;
}
if( _cookieProvider != null )
{
string cookie = headers[ "Set-Cookie" ];
if( cookie != null )
{
_cookieProvider.SetCookies( responseUrl, cookie );
}
}
_responseStream = _response.GetResponseStream();
_responseBytes = CreateBuffer();
_readStream = CreateReadStream();
CloseAsyncWaitHandle();
_asyncResult = _responseStream.BeginRead( _responseBytes, 0, BufferSize, null, null );
InvokeAfterWait( ReadWebStream, _asyncResult.AsyncWaitHandle );
return;
}
catch( WebException e )
{
TraceString( "Error loading " + _url + ", WebExceptionStatus = " + e.Status );
TraceException( e );
if( e.Status == WebExceptionStatus.SecureChannelFailure &&
_certificates != null && ++_certfId < _certificates.Count )
{
if ( e.Response != null )
{
e.Response.Close();
}
CloseAsyncWaitHandle();
StartProcessingWebURL();
return;
}
_response = ( HttpWebResponse ) e.Response;
_state = ( _response == null || _response.StatusCode != HttpStatusCode.NotModified ) ?
State.Error : State.Done;
_lastException = e;
}
catch( Exception e )
{
_lastException = e;
TraceException( e );
_state = State.Error;
}
CloseAsyncWaitHandle();
if( _response != null )
{
try
{
_response.Close();
}
catch( Exception ex )
{
TraceException( ex );
}
}
}
public int GetDownloadedSize()
{
if ( _readStream == null )
{
return 0;
}
return (int)_readStream.Position;
}
public int GetLength()
{
if ( _response == null )
{
return 0;
}
return (int)_response.ContentLength;
}
private void ReadWebStream()
{
try
{
int readBytes = _responseStream.EndRead( _asyncResult );
// if at least one byte is read we are to continue
if( readBytes > 0 )
{
_readStream.Write( _responseBytes, 0, readBytes );
CloseAsyncWaitHandle();
_asyncResult = _responseStream.BeginRead( _responseBytes, 0, BufferSize, null, null );
InvokeAfterWait( ReadWebStream, _asyncResult.AsyncWaitHandle );
return;
}
string contentEncoding = _response.ContentEncoding;
if ( contentEncoding != null )
{
DecodeReadStream( contentEncoding );
}
_state = State.Done;
_readStream.Position = 0;
}
catch ( Exception e )
{
_lastException = e;
TraceException( e );
_state = State.Error;
}
_lastBuffer = _responseBytes;
_responseBytes = null;
CloseAsyncWaitHandle();
_response.Close();
}
private void CloseAsyncWaitHandle()
{
_asyncResult.AsyncWaitHandle.Close();
}
private void DecodeReadStream( string encoding )
{
try
{
_readStream.Position = 0;
Stream encodedStream;
if ( encoding == "gzip" )
{
encodedStream = new GZipInputStream( _readStream );
}
else if ( encoding == "deflate" )
{
encodedStream = new InflaterInputStream( _readStream );
}
else
{
return;
}
JetMemoryStream decodedStream = new JetMemoryStream( BufferSize );
while( true )
{
int bytesRead = encodedStream.Read( _responseBytes, 0, BufferSize );
if ( bytesRead == 0 )
break;
decodedStream.Write( _responseBytes, 0, bytesRead );
}
encodedStream.Close();
_readStream.Close();
_readStream = decodedStream;
}
catch( Exception ex )
{
throw new HttpDecompressException( "Decompression failed", ex );
}
}
protected HttpWebRequest Request { get { return _request; } }
protected void TraceString( string str )
{
if( _tracer != null )
{
_tracer.Trace( str );
}
}
protected void TraceException( Exception e )
{
if( _tracer != null )
{
_tracer.TraceException( e );
}
}
private const int _maximumRedirectCount = 10;
private static X509CertificateCollection _certificates;
private static string _userAgent = String.Empty;
private State _state;
private string _url;
private readonly URLType _urlType;
private int _certfId;
private X509Certificate _X509Certificate;
private HttpWebRequest _request;
private readonly JetMemoryStream _requestStream;
private readonly string _requestContentType;
private HttpWebResponse _response;
private Stream _responseStream;
private IAsyncResult _asyncResult;
private byte[] _responseBytes;
private Stream _readStream;
private FileInfo _fileInfo;
private string _ETag = string.Empty;
private string _ifNoneMatch;
private DateTime _ifModifiedSince;
private Exception _lastException;
private int _redirectCount = 0;
private string _redirectUrl;
private ICredentials _credentials = null;
private string _acceptInstanceManipulation = null;
private ICookieProvider _cookieProvider = null;
private string _acceptEncoding = "gzip";
private readonly Tracer _tracer;
private static IWebProxy _defaultProxy;
#endregion
}
public class HttpReaderToFile : HttpReader
{
private readonly FileStream _file;
private readonly int _startPosition = 0;
public HttpReaderToFile( string URL, FileStream fileStream ) : base ( URL )
{
Guard.NullArgument( fileStream, "fileStream" );
_file = fileStream;
}
public HttpReaderToFile( string URL, FileStream fileStream, int startPosition ) : base ( URL )
{
Guard.NullArgument( fileStream, "fileStream" );
_file = fileStream;
_startPosition = startPosition;
}
protected override Stream CreateReadStream()
{
return _file;
}
protected override void PrepareRequest()
{
base.PrepareRequest();
Request.AddRange( _startPosition );
}
}
public class HttpDecompressException: Exception
{
public HttpDecompressException( string message, Exception innerException )
: base( message, innerException )
{
}
}
}