|
// ------------------------------------------------------------------------------
// <copyright file="FtpWebRequest.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// ------------------------------------------------------------------------------
//
namespace System.Net {
using System.Collections;
using System.IO;
using System.Text;
using System.Net.Sockets;
using System.Net.Cache;
using System.Threading;
using System.Security;
using System.Security.Cryptography.X509Certificates ;
using System.Security.Permissions;
using System.Security.Authentication;
using System.Globalization;
using System.Diagnostics.Tracing;
/// <summary>
/// <para>Allows us to control what the request is used for (based on the type of behavior,
/// that the command calls for)</para>
/// </summary>
internal enum FtpOperation {
DownloadFile = 0,
ListDirectory = 1,
ListDirectoryDetails = 2,
UploadFile = 3,
UploadFileUnique = 4,
AppendFile = 5,
DeleteFile = 6,
GetDateTimestamp = 7,
GetFileSize = 8,
Rename = 9,
MakeDirectory = 10,
RemoveDirectory = 11,
PrintWorkingDirectory = 12,
Other = 13,
}
[Flags]
internal enum FtpMethodFlags {
None = 0x0,
IsDownload = 0x1,
IsUpload = 0x2,
TakesParameter = 0x4,
MayTakeParameter = 0x8,
DoesNotTakeParameter = 0x10,
ParameterIsDirectory = 0x20,
ShouldParseForResponseUri = 0x40,
HasHttpCommand = 0x80,
MustChangeWorkingDirectoryToPath = 0x100
}
internal class FtpMethodInfo {
internal string Method;
internal FtpOperation Operation;
internal FtpMethodFlags Flags;
internal string HttpCommand;
internal FtpMethodInfo(string method,
FtpOperation operation,
FtpMethodFlags flags,
string httpCommand)
{
Method = method;
Operation = operation;
Flags = flags;
HttpCommand = httpCommand;
}
internal bool HasFlag(FtpMethodFlags flags) {
return (Flags & flags) != 0;
}
internal bool IsCommandOnly {
get { return (Flags & (FtpMethodFlags.IsDownload | FtpMethodFlags.IsUpload)) == 0; }
}
internal bool IsUpload {
get { return (Flags & FtpMethodFlags.IsUpload) != 0; }
}
internal bool IsDownload {
get { return (Flags & FtpMethodFlags.IsDownload) != 0; }
}
internal bool HasHttpCommand {
get { return (Flags & FtpMethodFlags.HasHttpCommand) != 0; }
}
/// <summary>
/// <para>True if we should attempt to get a response uri
/// out of a server response</para>
/// </summary>
internal bool ShouldParseForResponseUri {
get { return (Flags & FtpMethodFlags.ShouldParseForResponseUri) != 0; }
}
internal static FtpMethodInfo GetMethodInfo(string method) {
method = method.ToUpper(CultureInfo.InvariantCulture);
foreach (FtpMethodInfo methodInfo in KnownMethodInfo)
if (method == methodInfo.Method)
return methodInfo;
// We don't support generic methods
throw new ArgumentException(SR.GetString(SR.net_ftp_unsupported_method), "method");
}
static readonly FtpMethodInfo[] KnownMethodInfo =
{
new FtpMethodInfo(WebRequestMethods.Ftp.DownloadFile,
FtpOperation.DownloadFile,
FtpMethodFlags.IsDownload
| FtpMethodFlags.HasHttpCommand
| FtpMethodFlags.TakesParameter,
"GET"),
new FtpMethodInfo(WebRequestMethods.Ftp.ListDirectory,
FtpOperation.ListDirectory,
FtpMethodFlags.IsDownload
| FtpMethodFlags.MustChangeWorkingDirectoryToPath
| FtpMethodFlags.HasHttpCommand
| FtpMethodFlags.MayTakeParameter,
"GET"),
new FtpMethodInfo(WebRequestMethods.Ftp.ListDirectoryDetails,
FtpOperation.ListDirectoryDetails,
FtpMethodFlags.IsDownload
| FtpMethodFlags.MustChangeWorkingDirectoryToPath
| FtpMethodFlags.HasHttpCommand
| FtpMethodFlags.MayTakeParameter,
"GET"),
new FtpMethodInfo(WebRequestMethods.Ftp.UploadFile,
FtpOperation.UploadFile,
FtpMethodFlags.IsUpload
| FtpMethodFlags.TakesParameter,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.UploadFileWithUniqueName,
FtpOperation.UploadFileUnique,
FtpMethodFlags.IsUpload
| FtpMethodFlags.MustChangeWorkingDirectoryToPath
| FtpMethodFlags.DoesNotTakeParameter
| FtpMethodFlags.ShouldParseForResponseUri,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.AppendFile,
FtpOperation.AppendFile,
FtpMethodFlags.IsUpload
| FtpMethodFlags.TakesParameter,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.DeleteFile,
FtpOperation.DeleteFile,
FtpMethodFlags.TakesParameter,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.GetDateTimestamp,
FtpOperation.GetDateTimestamp,
FtpMethodFlags.TakesParameter,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.GetFileSize,
FtpOperation.GetFileSize,
FtpMethodFlags.TakesParameter,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.Rename,
FtpOperation.Rename,
FtpMethodFlags.TakesParameter,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.MakeDirectory,
FtpOperation.MakeDirectory,
FtpMethodFlags.TakesParameter
| FtpMethodFlags.ParameterIsDirectory,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.RemoveDirectory,
FtpOperation.RemoveDirectory,
FtpMethodFlags.TakesParameter
| FtpMethodFlags.ParameterIsDirectory,
null),
new FtpMethodInfo(WebRequestMethods.Ftp.PrintWorkingDirectory,
FtpOperation.PrintWorkingDirectory,
FtpMethodFlags.DoesNotTakeParameter,
null)
};
}
/// <summary>
/// <para>The FtpWebRequest class implements a basic FTP client
/// interface.</para>
/// </summary>
public sealed class FtpWebRequest : WebRequest {
private object m_SyncObject;
private ICredentials m_AuthInfo;
private readonly Uri m_Uri;
private FtpMethodInfo m_MethodInfo;
private string m_RenameTo = null;
private bool m_GetRequestStreamStarted;
private bool m_GetResponseStarted;
private DateTime m_StartTime;
private int m_Timeout = s_DefaultTimeout;
private int m_RemainingTimeout;
private long m_ContentLength = 0;
private long m_ContentOffset = 0;
private IWebProxy m_Proxy;
#if !FEATURE_PAL
private X509CertificateCollection m_ClientCertificates;
#endif // !FEATURE_PAL
private bool m_KeepAlive = true;
private bool m_Passive = true;
private bool m_Binary = true;
private string m_ConnectionGroupName;
private ServicePoint m_ServicePoint;
private bool m_CacheDone; // Not sure why but the command stream wants to notify the request on every pipiline closure closure by invoking RequestCallback.
// m_CacheDone is to facilitate PutConnection decision and to prevent bothering cache protocol when it's all completed.
private bool m_Async;
private bool m_Aborted;
private bool m_TimedOut;
private HttpWebRequest m_HttpWebRequest;
private Exception m_Exception;
private TimerThread.Queue m_TimerQueue = s_DefaultTimerQueue;
private TimerThread.Callback m_TimerCallback;
private bool m_EnableSsl;
private bool m_ProxyUserSet;
private ConnectionPool m_ConnectionPool;
private FtpControlStream m_Connection;
private Stream m_Stream;
private RequestStage m_RequestStage;
private bool m_OnceFailed;
private WebHeaderCollection m_FtpRequestHeaders;
private FtpWebResponse m_FtpWebResponse;
private int m_ReadWriteTimeout = 5*60*1000; //5 minutes.
private ContextAwareResult m_WriteAsyncResult;
private LazyAsyncResult m_ReadAsyncResult;
private LazyAsyncResult m_RequestCompleteAsyncResult;
private static readonly GeneralAsyncDelegate m_AsyncCallback = new GeneralAsyncDelegate(AsyncCallbackWrapper);
private static readonly CreateConnectionDelegate m_CreateConnectionCallback = new CreateConnectionDelegate(CreateFtpConnection);
private static readonly NetworkCredential DefaultFtpNetworkCredential = new NetworkCredential("anonymous", "anonymous@", String.Empty);
private static readonly int s_DefaultTimeout = WebRequest.DefaultTimeout;
private static readonly TimerThread.Queue s_DefaultTimerQueue = TimerThread.GetOrCreateQueue(s_DefaultTimeout);
// Used by FtpControlStream
internal FtpMethodInfo MethodInfo {
get {
return m_MethodInfo;
}
}
// Used by FtpControlStream
internal static NetworkCredential DefaultNetworkCredential{
get {
return DefaultFtpNetworkCredential;
}
}
// This is a shortcut that would set the default policy for HTTP/HTTPS.
// The default policy is overridden by any prefix-registered policy.
// Will demand permission for set{}
public static new RequestCachePolicy DefaultCachePolicy {
get {
RequestCachePolicy policy = RequestCacheManager.GetBinding(Uri.UriSchemeFtp).Policy;
if (policy == null)
return WebRequest.DefaultCachePolicy;
return policy;
}
set {
// This is a replacement of RequestCachePermission demand since we are not including the latest in the product.
ExceptionHelper.WebPermissionUnrestricted.Demand();
RequestCacheBinding binding = RequestCacheManager.GetBinding(Uri.UriSchemeFtp);
RequestCacheManager.SetBinding(Uri.UriSchemeFtp, new RequestCacheBinding(binding.Cache, binding.Validator, value));
}
}
/// <summary>
/// <para>
/// Selects upload or download of files. WebRequestMethods.Ftp.DownloadFile is default.
/// Not allowed to be changed once request is started.
/// </para>
/// </summary>
public override string Method {
get {
return m_MethodInfo.Method;
}
set {
if (String.IsNullOrEmpty(value)) {
throw new ArgumentException(SR.GetString(SR.net_ftp_invalid_method_name), "value");
}
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
try {
m_MethodInfo = FtpMethodInfo.GetMethodInfo(value);
} catch (ArgumentException) {
throw new ArgumentException(SR.GetString(SR.net_ftp_unsupported_method), "value");
}
}
}
/// <summary>
/// <para>
/// Sets the target name for the WebRequestMethods.Ftp.Rename command
/// Not allowed to be changed once request is started.
/// </para>
/// </summary>
public string RenameTo {
get {
return m_RenameTo;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
if (String.IsNullOrEmpty(value)) {
throw new ArgumentException(SR.GetString(SR.net_ftp_invalid_renameto), "value");
}
m_RenameTo = value;
}
}
/// <summary>
/// <para>Used for clear text authentication with FTP server</para>
/// </summary>
public override ICredentials Credentials {
get {
return m_AuthInfo;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
if (value == null) {
throw new ArgumentNullException("value");
}
if (value is SystemNetworkCredential) {
throw new ArgumentException(SR.GetString(SR.net_ftp_no_defaultcreds), "value");
}
m_AuthInfo = value;
}
}
/// <summary>
/// <para>Gets the Uri used to make the request</para>
/// </summary>
public override Uri RequestUri {
get {
return m_Uri;
}
}
/// <summary>
/// <para>Timeout of the blocking calls such as GetResponse and GetRequestStream (default 100 secs)</para>
/// </summary>
public override int Timeout {
get {
return m_Timeout;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
if (value<0 && value!=System.Threading.Timeout.Infinite) {
throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_ge_zero));
}
if (m_Timeout != value)
{
m_Timeout = value;
m_TimerQueue = null;
}
}
}
// This can always be calculaed as Remaining = Timeout - (Now - Start)
// but we are keeping this for performance reasons (To avoid unnecessary
// calculations). This can be removed if the performance gains are
// considered negligible and not necessary
internal int RemainingTimeout {
get {
return m_RemainingTimeout;
}
}
/// <devdoc>
/// <para>Used to control the Timeout when calling Stream.Read (AND) Stream.Write.
/// Effects Streams returned from GetResponse().GetResponseStream() (AND) GetRequestStream().
/// Default is 5 mins.
/// </para>
/// </devdoc>
public int ReadWriteTimeout {
get {
return m_ReadWriteTimeout;
}
set {
if (m_GetResponseStarted) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
if (value<=0 && value!=System.Threading.Timeout.Infinite) {
throw new ArgumentOutOfRangeException("value", SR.GetString(SR.net_io_timeout_use_gt_zero));
}
m_ReadWriteTimeout = value;
}
}
/// <summary>
/// <para>Used to specify what offset we will read at</para>
/// </summary>
public long ContentOffset {
get {
return m_ContentOffset;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
if (value<0) {
throw new ArgumentOutOfRangeException("value");
}
m_ContentOffset = value;
}
}
/// <summary>
/// <para>Gets or sets the data size of to-be uploaded data</para>
/// </summary>
public override long ContentLength {
get {
return m_ContentLength;
}
set {
m_ContentLength = value;
}
}
/// <summary>
/// <para>Uses an HTTP proxy if needed to send FTP request</para>
/// </summary>
public override IWebProxy Proxy {
get {
ExceptionHelper.WebPermissionUnrestricted.Demand();
return m_Proxy;
}
set {
ExceptionHelper.WebPermissionUnrestricted.Demand();
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
m_ProxyUserSet = true;
m_Proxy = value;
m_ServicePoint = null;
ServicePoint refreshIt = ServicePoint ;
}
}
/// <devdoc>
/// <para>Allows private ConnectionPool(s) to be used</para>
/// </devdoc>
public override string ConnectionGroupName {
get {
return m_ConnectionGroupName;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
m_ConnectionGroupName = value;
}
}
/// <devdoc>
/// <para>Generates a service point for this request, and allows setting of Connection settings</para>
/// </devdoc>
public ServicePoint ServicePoint {
get {
if (m_ServicePoint == null)
{
IWebProxy proxy = m_Proxy;
if (!m_ProxyUserSet)
proxy = WebRequest.InternalDefaultWebProxy;
ServicePoint servicePoint = ServicePointManager.FindServicePoint(m_Uri, proxy);
lock (m_SyncObject) {
if (m_ServicePoint == null)
{
m_ServicePoint = servicePoint;
m_Proxy = proxy;
}
}
}
return m_ServicePoint;
}
}
internal bool Aborted {
get {
return m_Aborted;
}
}
/// <summary>
/// <para>
/// Initializes a new instance of the <see cref='System.Net.FtpWebRequest'/>
/// class.
/// </para>
/// </summary>
internal FtpWebRequest(Uri uri) {
(new WebPermission(NetworkAccess.Connect, uri)).Demand();
if (Logging.On) Logging.PrintInfo(Logging.Web, this, ".ctor", uri.ToString());
if ((object)uri.Scheme != (object)Uri.UriSchemeFtp)
throw new ArgumentOutOfRangeException("uri");
m_TimerCallback = new TimerThread.Callback(TimerCallback);
m_SyncObject = new object();
NetworkCredential networkCredential = null;
m_Uri = uri;
m_MethodInfo = FtpMethodInfo.GetMethodInfo(WebRequestMethods.Ftp.DownloadFile);
if (m_Uri.UserInfo != null && m_Uri.UserInfo.Length != 0) {
string userInfo = m_Uri.UserInfo;
string username = userInfo;
string password = "";
int index = userInfo.IndexOf(':');
if (index != -1) {
username = Uri.UnescapeDataString(userInfo.Substring(0, index));
index++; // skip ':'
password = Uri.UnescapeDataString(userInfo.Substring(index, userInfo.Length - index));
}
networkCredential = new NetworkCredential(username, password);
}
if (networkCredential == null) {
networkCredential = DefaultFtpNetworkCredential;
}
m_AuthInfo = networkCredential;
SetupCacheProtocol(m_Uri);
}
//
// Used to query for the Response of an FTP request
//
public override WebResponse GetResponse()
{
if(Logging.On)Logging.Enter(Logging.Web, this, "GetResponse", "");
if(Logging.On)Logging.PrintInfo(Logging.Web, this, "GetResponse", SR.GetString(SR.net_log_method_equal, m_MethodInfo.Method));
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::GetResponse");
if (FrameworkEventSource.Log.IsEnabled()) {
LogBeginGetResponse(success: true, synchronous: true);
}
bool success = false;
int statusCode = -1;
try {
CheckError();
if (m_FtpWebResponse != null) {
success = true;
statusCode = GetStatusCode(m_FtpWebResponse);
return m_FtpWebResponse;
}
if (m_GetResponseStarted) {
throw new InvalidOperationException(SR.GetString(SR.net_repcall));
}
m_GetResponseStarted = true;
m_StartTime = DateTime.UtcNow;
m_RemainingTimeout = Timeout;
// We don't really need this variable, but we just need
// to call the property to measure its execution time
ServicePoint servicePoint = ServicePoint;
if (Timeout != System.Threading.Timeout.Infinite)
{
m_RemainingTimeout = Timeout - (int)((DateTime.UtcNow - m_StartTime).TotalMilliseconds);
if(m_RemainingTimeout <= 0){
throw new WebException(NetRes.GetWebStatusString(WebExceptionStatus.Timeout), WebExceptionStatus.Timeout);
}
}
if (ServicePoint.InternalProxyServicePoint)
{
if (EnableSsl) {
m_GetResponseStarted = false;
throw new WebException(SR.GetString(SR.net_ftp_proxy_does_not_support_ssl));
}
try {
HttpWebRequest httpWebRequest = GetHttpWebRequest();
if (Logging.On) Logging.Associate(Logging.Web, this, httpWebRequest);
m_FtpWebResponse = new FtpWebResponse((HttpWebResponse)httpWebRequest.GetResponse());
} catch (WebException webException) {
if (webException.Response != null &&
webException.Response is HttpWebResponse)
{
webException = new WebException(webException.Message,
null,
webException.Status,
new FtpWebResponse((HttpWebResponse)webException.Response),
webException.InternalStatus);
}
SetException(webException);
statusCode = GetStatusCode(webException);
throw webException;
}
// Catch added to address Bug # 545645
catch (InvalidOperationException invalidOpException)
{
SetException(invalidOpException);
FinishRequestStage(RequestStage.CheckForError);
throw;
}
}
else
{
RequestStage prev = FinishRequestStage(RequestStage.RequestStarted);
if (prev >= RequestStage.RequestStarted)
{
if (prev < RequestStage.ReadReady)
{
lock (m_SyncObject)
{
if (m_RequestStage < RequestStage.ReadReady)
m_ReadAsyncResult = new LazyAsyncResult(null, null, null);
}
// GetRequeststream or BeginGetRequestStream has not finished yet?
if (m_ReadAsyncResult != null)
m_ReadAsyncResult.InternalWaitForCompletion();
CheckError();
}
}
else
{
do
{
SubmitRequest(false);
if (m_MethodInfo.IsUpload)
FinishRequestStage(RequestStage.WriteReady);
else
FinishRequestStage(RequestStage.ReadReady);
CheckError();
} while (!CheckCacheRetrieveOnResponse());
EnsureFtpWebResponse(null);
// This may update the Stream memeber on m_FtpWebResponse based on the CacheProtocol feedback.
CheckCacheUpdateOnResponse();
if (m_FtpWebResponse.IsFromCache)
FinishRequestStage(RequestStage.ReleaseConnection);
}
}
statusCode = GetStatusCode(m_FtpWebResponse);
success = true;
}
catch (Exception exception) {
if (FrameworkEventSource.Log.IsEnabled()) {
WebException webException = exception as WebException;
if (webException != null) {
statusCode = GetStatusCode(webException);
}
}
if(Logging.On)Logging.Exception(Logging.Web, this, "GetResponse", exception);
// if m_Exception == null, we are about to throw an exception to the user
// and we haven't saved the exception, which also means we haven't dealt
// with it. So just release the connection and log this for investigation
if (m_Exception == null) {
if(Logging.On)Logging.PrintWarning(Logging.Web, SR.GetString(SR.net_log_unexpected_exception, "GetResponse()"));
if (!NclUtilities.IsFatal(exception)){
GlobalLog.Assert("Find out why we are getting an unexpected exception.");
}
SetException(exception);
FinishRequestStage(RequestStage.CheckForError);
}
throw;
} finally {
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::GetResponse", "returns #"+ValidationHelper.HashString(m_FtpWebResponse));
if(Logging.On)Logging.Exit(Logging.Web, this, "GetResponse", "");
if (FrameworkEventSource.Log.IsEnabled()) {
LogEndGetResponse(success, synchronous: true, statusCode: statusCode);
}
}
return m_FtpWebResponse;
}
/// <include file='doc\FtpWebRequest.uex' path='docs/doc[@for="FtpWebRequest.BeginGetResponse"]/*' />
/// <summary>
/// <para>Used to query for the Response of an FTP request [async version]</para>
/// </summary>
[HostProtection(ExternalThreading=true)]
public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state)
{
if(Logging.On)Logging.Enter(Logging.Web, this, "BeginGetResponse", "");
if(Logging.On)Logging.PrintInfo(Logging.Web, this, "BeginGetResponse", SR.GetString(SR.net_log_method_equal, m_MethodInfo.Method));
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetResponse");
ContextAwareResult asyncResult;
bool success = true;
try {
if (m_FtpWebResponse != null)
{
asyncResult = new ContextAwareResult(this, state, callback);
asyncResult.InvokeCallback(m_FtpWebResponse);
return asyncResult;
}
if (m_GetResponseStarted) {
throw new InvalidOperationException(SR.GetString(SR.net_repcall));
}
m_GetResponseStarted = true;
CheckError();
if (ServicePoint.InternalProxyServicePoint)
{
HttpWebRequest httpWebRequest = GetHttpWebRequest();
if (Logging.On) Logging.Associate(Logging.Web, this, httpWebRequest);
asyncResult = (ContextAwareResult)httpWebRequest.BeginGetResponse(callback, state);
}
else
{
RequestStage prev = FinishRequestStage(RequestStage.RequestStarted);
asyncResult = new ContextAwareResult(true, true, this, state, callback);
m_ReadAsyncResult = asyncResult;
if (prev >= RequestStage.RequestStarted)
{
// To make sure the context is flowed
asyncResult.StartPostingAsyncOp();
asyncResult.FinishPostingAsyncOp();
if (prev >= RequestStage.ReadReady)
asyncResult = null;
else
{
lock (m_SyncObject)
{
if (m_RequestStage >= RequestStage.ReadReady)
asyncResult = null;;
}
}
if(asyncResult == null)
{
// need to complete it now
asyncResult = (ContextAwareResult)m_ReadAsyncResult;
if (!asyncResult.InternalPeekCompleted)
asyncResult.InvokeCallback();
}
}
else
{
// Do internal processing in this handler to optimize context flowing.
lock (asyncResult.StartPostingAsyncOp())
{
SubmitRequest(true);
asyncResult.FinishPostingAsyncOp();
}
FinishRequestStage(RequestStage.CheckForError);
}
}
} catch (Exception exception) {
success = false;
if(Logging.On)Logging.Exception(Logging.Web, this, "BeginGetResponse", exception);
throw;
} finally {
if (FrameworkEventSource.Log.IsEnabled()) {
LogBeginGetResponse(success, synchronous: false);
}
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetResponse");
if(Logging.On)Logging.Exit(Logging.Web, this, "BeginGetResponse", "");
}
return asyncResult;
}
/// <summary>
/// <para>Returns result of query for the Response of an FTP request [async version]</para>
/// </summary>
public override WebResponse EndGetResponse(IAsyncResult asyncResult) {
if(Logging.On)Logging.Enter(Logging.Web, this, "EndGetResponse", "");
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetResponse");
bool success = false;
int statusCode = -1;
try {
// parameter validation
if (asyncResult==null) {
throw new ArgumentNullException("asyncResult");
}
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
if (castedAsyncResult==null) {
throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
}
if (HttpProxyMode?(castedAsyncResult.AsyncObject!=this.GetHttpWebRequest()):castedAsyncResult.AsyncObject!=this) {
throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
}
if (castedAsyncResult.EndCalled) {
throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndGetResponse"));
}
if (HttpProxyMode) {
try {
CheckError();
if (m_FtpWebResponse == null)
{
m_FtpWebResponse = new FtpWebResponse((HttpWebResponse)GetHttpWebRequest().EndGetResponse(asyncResult));
statusCode = GetStatusCode(m_FtpWebResponse);
}
} catch (WebException webException) {
statusCode = GetStatusCode(webException);
if (webException.Response != null &&
webException.Response is HttpWebResponse)
{
throw new WebException(webException.Message,
null,
webException.Status,
new FtpWebResponse((HttpWebResponse)webException.Response),
webException.InternalStatus);
}
throw;
}
}
else{
castedAsyncResult.InternalWaitForCompletion();
castedAsyncResult.EndCalled = true;
CheckError();
}
success = true;
}
catch (Exception exception) {
if (FrameworkEventSource.Log.IsEnabled()) {
WebException webException = exception as WebException;
if (webException != null) {
statusCode = GetStatusCode(webException);
}
}
if(Logging.On)Logging.Exception(Logging.Web, this, "EndGetResponse", exception);
throw;
} finally {
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetResponse");
if(Logging.On)Logging.Exit(Logging.Web, this, "EndGetResponse", "");
if (FrameworkEventSource.Log.IsEnabled()) {
LogEndGetResponse(success, synchronous: false, statusCode: statusCode);
}
}
return m_FtpWebResponse;
}
/// <summary>
/// <para>Used to query for the Request stream of an FTP Request</para>
/// </summary>
public override Stream GetRequestStream() {
if(Logging.On)Logging.Enter(Logging.Web, this, "GetRequestStream", "");
if(Logging.On)Logging.PrintInfo(Logging.Web, this, "GetRequestStream", SR.GetString(SR.net_log_method_equal, m_MethodInfo.Method));
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::GetRequestStream");
if (FrameworkEventSource.Log.IsEnabled()) {
LogBeginGetRequestStream(success: true, synchronous: true);
}
bool success = false;
try {
if (m_GetRequestStreamStarted) {
throw new InvalidOperationException(SR.GetString(SR.net_repcall));
}
m_GetRequestStreamStarted = true;
if (!m_MethodInfo.IsUpload) {
throw new ProtocolViolationException(SR.GetString(SR.net_nouploadonget));
}
CheckError();
m_StartTime = DateTime.UtcNow;
m_RemainingTimeout = Timeout;
// We don't really need this variable, but we just need
// to call the property to measure its execution time
ServicePoint servicePoint = ServicePoint;
if (Timeout != System.Threading.Timeout.Infinite)
{
m_RemainingTimeout = Timeout - (int)((DateTime.UtcNow - m_StartTime).TotalMilliseconds);
if(m_RemainingTimeout <= 0){
throw new WebException(NetRes.GetWebStatusString(WebExceptionStatus.Timeout), WebExceptionStatus.Timeout);
}
}
if (ServicePoint.InternalProxyServicePoint)
{
HttpWebRequest httpWebRequest = GetHttpWebRequest();
if (Logging.On) Logging.Associate(Logging.Web, this, httpWebRequest);
m_Stream = httpWebRequest.GetRequestStream();
} else {
FinishRequestStage(RequestStage.RequestStarted);
SubmitRequest(false);
FinishRequestStage(RequestStage.WriteReady);
CheckError();
}
if(m_Stream.CanTimeout) {
m_Stream.WriteTimeout = ReadWriteTimeout;
m_Stream.ReadTimeout = ReadWriteTimeout;
}
success = true;
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.Web, this, "GetRequestStream", exception);
throw;
} finally {
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::GetRequestStream");
if(Logging.On)Logging.Exit(Logging.Web, this, "GetRequestStream", "");
if (FrameworkEventSource.Log.IsEnabled()) {
LogEndGetRequestStream(success, synchronous: true);
}
}
return m_Stream;
}
/// <summary>
/// <para>Used to query for the Request stream of an FTP Request [async version]</para>
/// </summary>
[HostProtection(ExternalThreading=true)]
public override IAsyncResult BeginGetRequestStream(AsyncCallback callback, object state) {
if(Logging.On)Logging.Enter(Logging.Web, this, "BeginGetRequestStream", "");
if(Logging.On)Logging.PrintInfo(Logging.Web, this, "BeginGetRequestStream", SR.GetString(SR.net_log_method_equal, m_MethodInfo.Method));
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetRequestStream");
ContextAwareResult asyncResult = null;
bool success = false;
try {
if (m_GetRequestStreamStarted) {
throw new InvalidOperationException(SR.GetString(SR.net_repcall));
}
m_GetRequestStreamStarted = true;
if (!m_MethodInfo.IsUpload) {
throw new ProtocolViolationException(SR.GetString(SR.net_nouploadonget));
}
CheckError();
if (ServicePoint.InternalProxyServicePoint)
{
HttpWebRequest httpWebRequest = GetHttpWebRequest();
if (Logging.On) Logging.Associate(Logging.Web, this, httpWebRequest);
asyncResult = (ContextAwareResult)httpWebRequest.BeginGetRequestStream(callback, state);
}
else
{
FinishRequestStage(RequestStage.RequestStarted);
asyncResult = new ContextAwareResult(true, true, this, state, callback);
lock (asyncResult.StartPostingAsyncOp())
{
m_WriteAsyncResult = asyncResult;
SubmitRequest(true);
asyncResult.FinishPostingAsyncOp();
FinishRequestStage(RequestStage.CheckForError);
}
}
success = true;
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.Web, this, "BeginGetRequestStream", exception);
throw;
} finally {
if (FrameworkEventSource.Log.IsEnabled()){
LogBeginGetRequestStream(success, synchronous: false);
}
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::BeginGetRequestStream");
if(Logging.On)Logging.Exit(Logging.Web, this, "BeginGetRequestStream", "");
}
return asyncResult;
}
public override Stream EndGetRequestStream(IAsyncResult asyncResult) {
if(Logging.On)Logging.Enter(Logging.Web, this, "EndGetRequestStream", "");
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetRequestStream");
Stream requestStream = null;
bool success = false;
try {
// parameter validation
if (asyncResult==null) {
throw new ArgumentNullException("asyncResult");
}
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
if ((castedAsyncResult==null) ||
(HttpProxyMode?(castedAsyncResult.AsyncObject!=this.GetHttpWebRequest()):castedAsyncResult.AsyncObject!=this)) {
throw new ArgumentException(SR.GetString(SR.net_io_invalidasyncresult), "asyncResult");
}
if (castedAsyncResult.EndCalled) {
throw new InvalidOperationException(SR.GetString(SR.net_io_invalidendcall, "EndGetResponse"));
}
if (HttpProxyMode) {
requestStream = GetHttpWebRequest().EndGetRequestStream(asyncResult);
} else {
castedAsyncResult.InternalWaitForCompletion();
castedAsyncResult.EndCalled = true;
CheckError();
requestStream = m_Stream;
castedAsyncResult.EndCalled = true;
}
if(requestStream.CanTimeout) {
requestStream.WriteTimeout = ReadWriteTimeout;
requestStream.ReadTimeout = ReadWriteTimeout;
}
success = true;
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.Web, this, "EndGetRequestStream", exception);
throw;
} finally {
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::EndGetRequestStream");
if(Logging.On)Logging.Exit(Logging.Web, this, "EndGetRequestStream", "");
}
if (FrameworkEventSource.Log.IsEnabled()) {
LogEndGetRequestStream(success, synchronous: false);
}
return requestStream;
}
//
// NOTE1: The caller must synchronize access to SubmitRequest(), only one call is even allowed for a particular request!
// NOTE2: This method eats all exceptions so the caller must rethrow them
//
private void SubmitRequest(bool async) {
try {
m_Async = async;
if (CheckCacheRetrieveBeforeSubmit())
{
RequestCallback(null);
return;
}
// This is the only place touching m_ConnectionPool
if (m_ConnectionPool == null)
m_ConnectionPool = ConnectionPoolManager.GetConnectionPool(ServicePoint, GetConnectionGroupLine(), m_CreateConnectionCallback);
//
// FYI: Will do 2 attempts max as per AttemptedRecovery
//
Stream stream;
while(true)
{
FtpControlStream connection = m_Connection;
if (connection == null)
{
connection = QueueOrCreateConnection();
if (connection == null)
return;
}
if(!async){
if (Timeout != System.Threading.Timeout.Infinite)
{
m_RemainingTimeout = Timeout - (int)((DateTime.UtcNow - m_StartTime).TotalMilliseconds);
if(m_RemainingTimeout <= 0){
throw new WebException(NetRes.GetWebStatusString(WebExceptionStatus.Timeout), WebExceptionStatus.Timeout);
}
}
}
GlobalLog.Print("Request being submitted"+ValidationHelper.HashString(this));
connection.SetSocketTimeoutOption(SocketShutdown.Both, RemainingTimeout, false);
try {
stream = TimedSubmitRequestHelper(async);
} catch (Exception e) {
if (AttemptedRecovery(e)){
if(!async){
if (Timeout != System.Threading.Timeout.Infinite)
{
m_RemainingTimeout = Timeout - (int)((DateTime.UtcNow - m_StartTime).TotalMilliseconds);
if(m_RemainingTimeout <= 0){
throw;
}
}
}
continue;
}
throw;
}
// no retry needed
break;
}
} catch (WebException webException) {
//if this was a timeout, throw a timeout exception
IOException ioEx = webException.InnerException as IOException;
if(ioEx != null){
SocketException sEx = ioEx.InnerException as SocketException;
if(sEx != null){
if (sEx.ErrorCode == (int)SocketError.TimedOut) {
SetException(new WebException(SR.GetString(SR.net_timeout), WebExceptionStatus.Timeout));
}
}
}
SetException(webException);
}
catch (Exception exception) {
SetException(exception);
}
}
//
//
//
private FtpControlStream QueueOrCreateConnection()
{
FtpControlStream connection = (FtpControlStream) m_ConnectionPool.GetConnection((object)this, (m_Async ? m_AsyncCallback : null), (m_Async ? -1: RemainingTimeout));
if (connection == null)
{
GlobalLog.Assert(m_Async, "QueueOrCreateConnection|m_ConnectionPool.GetConnection() returned null on a Sync Request.");
return null;
}
lock (m_SyncObject)
{
if (m_Aborted)
{
if (Logging.On) Logging.PrintInfo(Logging.Web, this, "", SR.GetString(SR.net_log_releasing_connection, ValidationHelper.HashString(connection)));
m_ConnectionPool.PutConnection(connection, this, RemainingTimeout);
CheckError(); //must throw
throw new InternalException();
}
m_Connection = connection;
if (Logging.On) Logging.Associate(Logging.Web, this, m_Connection);
}
return connection;
}
//
//
private Stream TimedSubmitRequestHelper(bool async)
{
if(async) {
// non-null in the case of re-submit (recovery)
if (m_RequestCompleteAsyncResult == null)
m_RequestCompleteAsyncResult = new LazyAsyncResult(null, null, null);
return m_Connection.SubmitRequest(this, true, true);
}
Stream stream = null;
bool timedOut = false;
TimerThread.Timer timer = TimerQueue.CreateTimer(m_TimerCallback, null);
try {
stream = m_Connection.SubmitRequest(this, false, true);
}
catch (Exception exception) {
if (!(exception is SocketException || exception is ObjectDisposedException) || !timer.HasExpired) {
timer.Cancel();
throw;
}
timedOut = true;
}
if (timedOut || !timer.Cancel()) {
m_TimedOut = true;
throw new WebException(NetRes.GetWebStatusString(WebExceptionStatus.Timeout), WebExceptionStatus.Timeout);
}
if (stream != null)
{
lock (m_SyncObject)
{
if (m_Aborted)
{
((ICloseEx)stream).CloseEx(CloseExState.Abort|CloseExState.Silent);
CheckError(); //must throw
throw new InternalException(); //consider replacing this on Assert
}
m_Stream = stream;
}
}
return stream;
}
/// <summary>
/// <para>Because this is called from the timer thread, neither it nor any methods it calls can call user code.</para>
/// </summary>
private void TimerCallback(TimerThread.Timer timer, int timeNoticed, object context) {
GlobalLog.Print("FtpWebRequest#" + ValidationHelper.HashString(this) + "::TimerCallback");
FtpControlStream connection = m_Connection;
if (connection != null) {
GlobalLog.Print("FtpWebRequest#" + ValidationHelper.HashString(this) + "::TimerCallback aborting connection");
connection.AbortConnect();
}
}
private TimerThread.Queue TimerQueue {
get {
if (m_TimerQueue == null) {
m_TimerQueue = TimerThread.GetOrCreateQueue(RemainingTimeout);
}
return m_TimerQueue;
}
}
/// <summary>
/// <para>Returns true if we should restart the request after an error</para>
/// </summary>
private bool AttemptedRecovery(Exception e) {
// The first 'if' is just checking whether the exception is thrown due to the
// relogin failure which is a recoverable error
if (!(e is WebException && ((WebException)e).InternalStatus == WebExceptionInternalStatus.Isolated))
{
if (e is ThreadAbortException
|| e is StackOverflowException
|| e is OutOfMemoryException
|| m_OnceFailed
|| m_Aborted
|| m_TimedOut
|| m_Connection==null
|| !m_Connection.RecoverableFailure)
{
return false;
}
m_OnceFailed = true;
}
lock (m_SyncObject) {
if (m_ConnectionPool != null && m_Connection != null) {
m_Connection.CloseSocket();
if (Logging.On) Logging.PrintInfo(Logging.Web, this, "", SR.GetString(SR.net_log_releasing_connection, ValidationHelper.HashString(m_Connection)));
m_ConnectionPool.PutConnection(m_Connection, this, RemainingTimeout);
m_Connection = null;
} else {
return false;
}
}
return true;
}
/// <summary>
/// <para>Updates and sets our exception to be thrown</para>
/// </summary>
private void SetException(Exception exception) {
GlobalLog.Print("FtpWebRequest#" + ValidationHelper.HashString(this) + "::SetException");
if (exception is ThreadAbortException || exception is StackOverflowException || exception is OutOfMemoryException) {
m_Exception = exception;
throw exception;
}
FtpControlStream connection = m_Connection;
if (m_Exception == null) {
if (exception is WebException)
{
EnsureFtpWebResponse(exception);
m_Exception = new WebException(exception.Message, null, ((WebException)exception).Status, m_FtpWebResponse);
}
else if (exception is AuthenticationException || exception is SecurityException)
{
m_Exception = exception;
}
else if (connection!= null && connection.StatusCode != FtpStatusCode.Undefined)
{
EnsureFtpWebResponse(exception);
m_Exception = new WebException(SR.GetString(SR.net_servererror, connection.StatusLine), exception, WebExceptionStatus.ProtocolError, m_FtpWebResponse);
} else
{
m_Exception = new WebException(exception.Message, exception);
}
if (connection != null && m_FtpWebResponse != null)
m_FtpWebResponse.UpdateStatus(connection.StatusCode, connection.StatusLine, connection.ExitMessage);
}
}
/// <summary>
/// <para>Opposite of SetException, rethrows the exception</para>
/// </summary>
private void CheckError() {
if (m_Exception != null) {
throw m_Exception;
}
}
// Return null only on Sync (if we're on the Sync thread). Otherwise throw if no context is available.
//
//
internal override ContextAwareResult GetWritingContext()
{
if (m_ReadAsyncResult != null && m_ReadAsyncResult is ContextAwareResult)
return (ContextAwareResult)m_ReadAsyncResult;
else if (m_WriteAsyncResult != null)
return m_WriteAsyncResult;
// Sync.
GlobalLog.ThreadContract(ThreadKinds.User | ThreadKinds.Sync, "FtpWebRequest#" + ValidationHelper.HashString(this) + "::GetWritingContext");
return null;
}
//
// Provides an abstract way of having Async code callback into the request (saves a delegate)
//
// ATTN this method is also called on sync path when either command or data stream gets closed
// Consider: Revisit the design of ftp streams
//
internal override void RequestCallback(object obj)
{
if (m_Async)
AsyncRequestCallback(obj);
else
SyncRequestCallback(obj);
}
//
// Only executed for Sync requests when the pipline is completed
//
private void SyncRequestCallback(object obj)
{
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::SyncRequestCallback", "#"+ValidationHelper.HashString(obj));
RequestStage stageMode = RequestStage.CheckForError;
try {
bool completedRequest = obj == null;
Exception exception = obj as Exception;
GlobalLog.Print("SyncRequestCallback() exp:"+ValidationHelper.HashString(exception)+" completedRequest:"+ValidationHelper.HashString(completedRequest));
if (exception != null)
{
SetException(exception);
}
else if (!completedRequest)
{
throw new InternalException();
}
else
{
// a signal on current pipeline completion
FtpControlStream connection = m_Connection;
bool isRevalidatedOrRetried = false;
if (connection != null)
{
EnsureFtpWebResponse(null);
// This to update response status and exit message if any
// Note that due to a design of FtpControlStream the status 221 "Service closing control connection" is always suppresses.
m_FtpWebResponse.UpdateStatus(connection.StatusCode, connection.StatusLine, connection.ExitMessage);
isRevalidatedOrRetried =!m_CacheDone &&
(CacheProtocol.ProtocolStatus == CacheValidationStatus.Continue || CacheProtocol.ProtocolStatus == CacheValidationStatus.RetryResponseFromServer);
// This is for sync Upload commands that do not get chance hit GetResponse loop
if (m_MethodInfo.IsUpload)
{
CheckCacheRetrieveOnResponse();
CheckCacheUpdateOnResponse();
}
}
if (!isRevalidatedOrRetried)
stageMode = RequestStage.ReleaseConnection;
}
}
catch (Exception exception) {
SetException(exception);
}
finally {
FinishRequestStage(stageMode);
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::SyncRequestCallback");
CheckError(); //will throw on error
}
}
//
// Only executed for Async requests
//
private void AsyncRequestCallback(object obj)
{
GlobalLog.Enter("FtpWebRequest#" + ValidationHelper.HashString(this) + "::AsyncRequestCallback", "#"+ValidationHelper.HashString(obj));
RequestStage stageMode = RequestStage.CheckForError;
try {
FtpControlStream connection;
connection = obj as FtpControlStream;
FtpDataStream stream = obj as FtpDataStream;
Exception exception = obj as Exception;
bool completedRequest = (obj == null);
GlobalLog.Print("AsyncRequestCallback() stream:"+ValidationHelper.HashString(stream)+" conn:"+ValidationHelper.HashString(connection)+" exp:"+ValidationHelper.HashString(exception)+" completedRequest:"+ValidationHelper.HashString(completedRequest));
while (true)
{
if (exception != null)
{
if (AttemptedRecovery(exception))
{
connection = QueueOrCreateConnection();
if (connection == null)
return;
exception = null;
}
if (exception != null)
{
SetException(exception);
break;
}
}
if (connection != null)
{
lock(m_SyncObject)
{
if (m_Aborted)
{
if (Logging.On) Logging.PrintInfo(Logging.Web, this, "", SR.GetString(SR.net_log_releasing_connection, ValidationHelper.HashString(connection)));
m_ConnectionPool.PutConnection(connection, this, Timeout);
break;
}
m_Connection = connection;
if (Logging.On) Logging.Associate(Logging.Web, this, m_Connection);
}
try {
stream = (FtpDataStream)TimedSubmitRequestHelper(true);
} catch (Exception e) {
exception = e;
continue;
}
return;
}
else if (stream != null)
{
lock (m_SyncObject)
{
if (m_Aborted)
{
((ICloseEx)stream).CloseEx(CloseExState.Abort|CloseExState.Silent);
break;
}
m_Stream = stream;
}
stream.SetSocketTimeoutOption(SocketShutdown.Both, Timeout, true);
EnsureFtpWebResponse(null);
// This one may update the Stream member on m_FtpWebResponse based on the CacheProtocol feedback.
CheckCacheRetrieveOnResponse();
CheckCacheUpdateOnResponse();
stageMode = stream.CanRead? RequestStage.ReadReady: RequestStage.WriteReady;
}
else if (completedRequest)
{
connection = m_Connection;
bool isRevalidatedOrRetried = false;
if (connection != null)
{
EnsureFtpWebResponse(null);
// This to update response status and exit message if any
// Note that due to a design of FtpControlStream the status 221 "Service closing control connection" is always suppresses.
m_FtpWebResponse.UpdateStatus(connection.StatusCode, connection.StatusLine, connection.ExitMessage);
isRevalidatedOrRetried =!m_CacheDone &&
(CacheProtocol.ProtocolStatus == CacheValidationStatus.Continue || CacheProtocol.ProtocolStatus == CacheValidationStatus.RetryResponseFromServer);
lock (m_SyncObject)
{
if(!CheckCacheRetrieveOnResponse())
continue;
if (m_FtpWebResponse.IsFromCache)
isRevalidatedOrRetried = false;
CheckCacheUpdateOnResponse();
}
}
if (!isRevalidatedOrRetried)
stageMode = RequestStage.ReleaseConnection;
}
else
{
throw new InternalException();
}
break;
}
}
catch (Exception exception)
{
SetException(exception);
}
finally {
FinishRequestStage(stageMode);
GlobalLog.Leave("FtpWebRequest#" + ValidationHelper.HashString(this) + "::AsyncRequestCallback");
}
}
//
//
//
private enum RequestStage {
CheckForError = 0,// Do nothing except if there is an error then auto promote to ReleaseConnection
RequestStarted, // Mark this request as started
WriteReady, // First half is done, i.e. either writer or response stream. This is always assumed unless Started or CheckForError
ReadReady, // Second half is done, i.e. the read stream can be accesses.
ReleaseConnection // Release the control connection (request is read i.e. done-done)
}
//
// Returns a previous stage
//
private RequestStage FinishRequestStage(RequestStage stage)
{
GlobalLog.Print("FtpWebRequest#" + ValidationHelper.HashString(this) + "::FinishRequestStage : stage="+stage);
if (m_Exception != null)
stage = RequestStage.ReleaseConnection;
RequestStage prev;
LazyAsyncResult writeResult;
LazyAsyncResult readResult;
FtpControlStream connection;
lock (m_SyncObject)
{
prev = m_RequestStage;
if (stage == RequestStage.CheckForError)
return prev;
if (prev == RequestStage.ReleaseConnection &&
stage == RequestStage.ReleaseConnection)
{
return RequestStage.ReleaseConnection;
}
if (stage > prev)
m_RequestStage = stage;
if (stage <= RequestStage.RequestStarted)
return prev;
writeResult = m_WriteAsyncResult;
readResult = m_ReadAsyncResult;
connection = m_Connection;
if (stage == RequestStage.ReleaseConnection)
{
if (m_Exception == null &&
!m_Aborted &&
prev != RequestStage.ReadReady &&
this.m_MethodInfo.IsDownload &&
!m_FtpWebResponse.IsFromCache)
{
return prev;
}
if (m_Exception != null || !(m_FtpWebResponse.IsFromCache && !KeepAlive))
m_Connection = null;
}
}
try {
// First check to see on releasing the connection
if ((stage == RequestStage.ReleaseConnection ||
prev == RequestStage.ReleaseConnection)
&& connection != null)
{
try {
if (m_Exception != null)
{
connection.Abort(m_Exception);
}
else if (m_FtpWebResponse.IsFromCache && !KeepAlive)
{
// This means the response has been revalidated and found as good means the commands pipleline is completed.
// Now if this request was NOT KeepAlive we want to be fair and close the control connection.
// That becomes unnecessary complicated in the async case to support "QUIT" command semantic, so simply close the socket
// and let pool collect the object.
connection.Quit();
}
} finally {
if (Logging.On) Logging.PrintInfo(Logging.Web, this, "", SR.GetString(SR.net_log_releasing_connection, ValidationHelper.HashString(connection)));
m_ConnectionPool.PutConnection(connection, this, RemainingTimeout);
if (m_Async)
if (m_RequestCompleteAsyncResult != null)
m_RequestCompleteAsyncResult.InvokeCallback();
}
}
return prev;
}
finally {
try {
// In any case we want to signal the writer if came here
if (stage >= RequestStage.WriteReady) {
// If writeResult == null and this is an upload request, it means
// that the user has called GetResponse() without calling
// GetRequestStream() first. So they are not interested in a
// stream. Therefore we close the stream so that the
// request/pipeline can continue
if (m_MethodInfo.IsUpload && !m_GetRequestStreamStarted)
{
if (m_Stream != null)
m_Stream.Close();
}
else if (writeResult != null && !writeResult.InternalPeekCompleted)
writeResult.InvokeCallback();
}
}
finally {
// The response is ready either with or without a stream
if (stage >= RequestStage.ReadReady && readResult != null && !readResult.InternalPeekCompleted)
readResult.InvokeCallback();
}
}
}
//
// Used only in the async case and only for the initial callback from the pool when connection is established.
//
private static void AsyncCallbackWrapper(object request, object state) {
FtpWebRequest ftpWebRequest = (FtpWebRequest) request;
ftpWebRequest.RequestCallback(state);
}
/// <summary>
/// <para>builds networkStream from Socket</para>
/// </summary>
private static PooledStream CreateFtpConnection(ConnectionPool pool) {
return (PooledStream) new FtpControlStream(pool, TimeSpan.MaxValue, false);
}
/// <summary>
/// <para>Aborts underlying connection to FTP server (command & data)</para>
/// </summary>
public override void Abort()
{
if (m_Aborted)
return;
if(Logging.On)Logging.Enter(Logging.Web, this, "Abort", "");
try {
GlobalLog.Print("FtpWebRequest#"+ValidationHelper.HashString(this)+"::Abort()");
if (HttpProxyMode) {
GetHttpWebRequest().Abort();
return;
}
if (CacheProtocol != null)
CacheProtocol.Abort();
Stream stream;
FtpControlStream connection;
lock (m_SyncObject)
{
if (m_RequestStage >= RequestStage.ReleaseConnection)
return;
m_Aborted = true;
stream = m_Stream;
connection = m_Connection;
m_Exception = new WebException(NetRes.GetWebStatusString("net_requestaborted", WebExceptionStatus.RequestCanceled),
WebExceptionStatus.RequestCanceled) ;
}
if (stream != null)
{
GlobalLog.Assert(stream is ICloseEx, "FtpWebRequest.Abort()|The m_Stream member is not CloseEx hence the risk of connection been orphaned.");
((ICloseEx)stream).CloseEx(CloseExState.Abort|CloseExState.Silent);
}
if (connection != null)
connection.Abort(ExceptionHelper.RequestAbortedException);
} catch (Exception exception) {
if(Logging.On)Logging.Exception(Logging.Web, this, "Abort", exception);
throw;
} finally {
if(Logging.On)Logging.Exit(Logging.Web, this, "Abort", "");
}
}
/// <include file='doc\FtpWebRequest.uex' path='docs/doc[@for="FtpWebRequest.KeepAlive"]/*' />
/// <summary>
/// <para>
/// If KeepAlive is set to false, then the control connection to the server will be closed when the request completes.
/// Default is true
/// </para>
/// </summary>
public bool KeepAlive {
get {
return m_KeepAlive;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
m_KeepAlive = value;
}
}
/// <summary>
/// <para>True by default, false allows transmission using text mode</para>
/// </summary>
public bool UseBinary {
get {
return m_Binary;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
m_Binary = value;
}
}
/// <summary>
/// <para>False by default, true enables passive mode communication with server.
/// This alters the way the client talks with the server, allowing the client
/// to initiate the data connection with the server</para>
/// </summary>
public bool UsePassive {
get {
return m_Passive;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
m_Passive = value;
}
}
#if !FEATURE_PAL
/// <summary>
/// <para>
/// ClientCertificates - sets our certs for our reqest,
/// uses a hash of the collection to create a private connection
/// group, to prevent us from using the same Connection as
/// non-Client Authenticated requests.
/// </para>
/// </summary>
public X509CertificateCollection ClientCertificates {
get {
if (m_ClientCertificates == null) {
lock (m_SyncObject) {
if (m_ClientCertificates == null) {
m_ClientCertificates = new X509CertificateCollection();
}
}
}
return m_ClientCertificates;
}
set {
if (value==null) {
throw new ArgumentNullException("value");
}
m_ClientCertificates = value;
}
}
#endif // !FEATURE_PAL
/// <summary>
/// <para>Set to true if we need SSL</para>
/// </summary>
public bool EnableSsl {
get {
return m_EnableSsl;
}
set {
if (InUse) {
throw new InvalidOperationException(SR.GetString(SR.net_reqsubmitted));
}
m_EnableSsl = value;
}
}
/// <devdoc>
/// <para>
/// A collection of headers, currently nothing is return except an empty collection
/// </para>
/// </devdoc>
public override WebHeaderCollection Headers {
get {
if (HttpProxyMode) {
return GetHttpWebRequest().Headers;
}
if (m_FtpRequestHeaders == null) {
m_FtpRequestHeaders = new WebHeaderCollection(WebHeaderCollectionType.FtpWebRequest);
}
return m_FtpRequestHeaders;
}
set {
if (HttpProxyMode) {
GetHttpWebRequest().Headers = value;
}
m_FtpRequestHeaders = value;
}
}
// NOT SUPPORTED method
public override string ContentType {
get {
throw ExceptionHelper.PropertyNotSupportedException;
}
set {
throw ExceptionHelper.PropertyNotSupportedException;
}
}
// NOT SUPPORTED method
public override bool UseDefaultCredentials {
get {
throw ExceptionHelper.PropertyNotSupportedException;
}
set {
throw ExceptionHelper.PropertyNotSupportedException;
}
}
// NOT SUPPORTED method
public override bool PreAuthenticate {
get {
throw ExceptionHelper.PropertyNotSupportedException;
}
set {
throw ExceptionHelper.PropertyNotSupportedException;
}
}
/// <summary>
/// <para>True if a request has been submitted (ie already active)</para>
/// </summary>
private bool InUse {
get {
if (m_GetRequestStreamStarted || m_GetResponseStarted) {
return true;
} else {
return false;
}
}
}
/// <summary>
/// <para>True if request is just wrapping HttpWebRequest</para>
/// </summary>
private bool HttpProxyMode {
get {
return (m_HttpWebRequest != null);
}
}
/// <summary>
/// <para>Creates an FTP WebResponse based off the responseStream and our active Connection</para>
/// </summary>
private void EnsureFtpWebResponse(Exception exception)
{
if (m_FtpWebResponse == null || (m_FtpWebResponse.GetResponseStream() is FtpWebResponse.EmptyStream && m_Stream != null))
{
lock (m_SyncObject) {
if (m_FtpWebResponse == null || (m_FtpWebResponse.GetResponseStream() is FtpWebResponse.EmptyStream && m_Stream != null))
{
Stream responseStream = m_Stream;
if (m_MethodInfo.IsUpload) {
responseStream = null;
}
if(m_Stream != null && m_Stream.CanRead && m_Stream.CanTimeout)
{
m_Stream.ReadTimeout = ReadWriteTimeout;
m_Stream.WriteTimeout = ReadWriteTimeout;
}
FtpControlStream connection = m_Connection;
long contentLength = connection != null? connection.ContentLength: -1;
if (responseStream == null)
{
// If the last command was SIZE, we set the ContentLength on
// the FtpControlStream to be the size of the file returned in the
// response. We should propagate that file size to the response so
// users can access it. This also maintains the compatibility with
// HTTP when returning size instead of content.
if (contentLength < 0)
contentLength = 0;
}
if (m_FtpWebResponse != null)
{
m_FtpWebResponse.SetResponseStream(responseStream);
}
else
{
if (connection != null)
m_FtpWebResponse = new FtpWebResponse(responseStream, contentLength, connection.ResponseUri, connection.StatusCode, connection.StatusLine, connection.LastModified, connection.BannerMessage, connection.WelcomeMessage, connection.ExitMessage);
else
m_FtpWebResponse = new FtpWebResponse(responseStream, -1, m_Uri, FtpStatusCode.Undefined, null, DateTime.Now, null, null, null);
}
}
}
}
GlobalLog.Print("FtpWebRequest#"+ValidationHelper.HashString(this)+"::EnsureFtpWebResponse returns #"+ValidationHelper.HashString(m_FtpWebResponse)+" with stream#"+ValidationHelper.HashString(m_FtpWebResponse.m_ResponseStream));
return;
}
/// <summary>
/// <para>Creates a HttpWebRequest</para>
/// </summary>
private HttpWebRequest GetHttpWebRequest() {
lock (m_SyncObject) {
if (m_HttpWebRequest == null) {
if (m_ContentOffset > 0) {
throw new InvalidOperationException(SR.GetString(SR.net_ftp_no_offsetforhttp));
}
if (!m_MethodInfo.HasHttpCommand)
throw new InvalidOperationException(SR.GetString(SR.net_ftp_no_http_cmd));
m_HttpWebRequest = new HttpWebRequest(m_Uri, ServicePoint);
m_HttpWebRequest.Credentials = Credentials;
m_HttpWebRequest.InternalProxy = m_Proxy;
m_HttpWebRequest.KeepAlive = KeepAlive;
m_HttpWebRequest.Timeout = Timeout;
m_HttpWebRequest.Method = m_MethodInfo.HttpCommand;
m_HttpWebRequest.CacheProtocol = CacheProtocol;
RequestCacheLevel effectiveLevel;
if (CachePolicy == null)
effectiveLevel = RequestCacheLevel.BypassCache;
else
effectiveLevel = CachePolicy.Level;
// Cannot support revalidate through the proxy
if (effectiveLevel == RequestCacheLevel.Revalidate)
effectiveLevel = RequestCacheLevel.Reload;
m_HttpWebRequest.CachePolicy = new HttpRequestCachePolicy((HttpRequestCacheLevel)effectiveLevel);
//disable cache protocol on that class since we are proxying through HTTP
CacheProtocol = null;
}
}
return m_HttpWebRequest;
}
/// <devdoc>
/// <para>Generates a string that
/// allows a Connection to remain unique for user
/// this is needed to prevent multiple users from
/// using the same sockets after they mess with things</para>
/// </devdoc>
private string GetConnectionGroupLine() {
GlobalLog.Print("GetConnectionGroupLine");
return ConnectionGroupName + "_" + GetUserString();
}
/// <summary>
/// <para>Returns username string</para>
/// </summary>
internal string GetUserString() {
string name = null;
if (this.Credentials != null) {
NetworkCredential networkCreds = this.Credentials.GetCredential(m_Uri, "basic");
if (networkCreds != null) {
name = networkCreds.InternalGetUserName();
string domain = networkCreds.InternalGetDomain();
if (!ValidationHelper.IsBlankString(domain)) {
name = domain+"\\"+name;
}
}
}
return name == null? null: (String.Compare(name,"anonymous", StringComparison.InvariantCultureIgnoreCase) == 0? null: name);
}
//
// This method may be invoked as part of the request submission but
// before the response is received
// Return:
// - True = Use CacheProtocol properties to create the cached response
// - False = Proceed with the request submission
private bool CheckCacheRetrieveBeforeSubmit() {
if (CacheProtocol == null || m_CacheDone) {
m_CacheDone = true;
return false;
}
if (CacheProtocol.ProtocolStatus == CacheValidationStatus.CombineCachedAndServerResponse ||
CacheProtocol.ProtocolStatus == CacheValidationStatus.DoNotTakeFromCache)
{
// Re-entry into a new pipeline on failed revalidate or combining cached and live streams
return false;
}
Uri cacheUri = RequestUri;
string username = GetUserString();
if(username != null)
username = Uri.EscapeDataString(username);
if (cacheUri.Fragment.Length != 0 || username != null)
{
if (username == null)
cacheUri = new Uri(cacheUri.GetParts(UriComponents.AbsoluteUri & ~(UriComponents.Fragment|UriComponents.UserInfo), UriFormat.SafeUnescaped));
else
{
username = cacheUri.GetParts((UriComponents.Scheme | UriComponents.KeepDelimiter), UriFormat.SafeUnescaped) + username + '@';
username += cacheUri.GetParts((UriComponents.Host | UriComponents.Port | UriComponents.Path | UriComponents.Query), UriFormat.SafeUnescaped);
cacheUri = new Uri(username);
}
}
CacheProtocol.GetRetrieveStatus(cacheUri, this);
if (CacheProtocol.ProtocolStatus == CacheValidationStatus.Fail) {
throw CacheProtocol.ProtocolException;
}
if (CacheProtocol.ProtocolStatus != CacheValidationStatus.ReturnCachedResponse) {
return false;
}
if (m_MethodInfo.Operation != FtpOperation.DownloadFile) {
throw new NotSupportedException(SR.GetString(SR.net_cache_not_supported_command));
}
if (CacheProtocol.ProtocolStatus == CacheValidationStatus.ReturnCachedResponse)
{
// If we take it from cache, we have to kick in response processing
// The _CacheStream is good to return as the response stream.
FtpRequestCacheValidator ctx = (FtpRequestCacheValidator) CacheProtocol.Validator;
m_FtpWebResponse = new FtpWebResponse(CacheProtocol.ResponseStream,
CacheProtocol.ResponseStreamLength,
RequestUri,
UsePassive? FtpStatusCode.DataAlreadyOpen: FtpStatusCode.OpeningData,
(UsePassive? FtpStatusCode.DataAlreadyOpen: FtpStatusCode.OpeningData).ToString(),
ctx.CacheEntry.LastModifiedUtc == DateTime.MinValue? DateTime.Now: ctx.CacheEntry.LastModifiedUtc.ToLocalTime(),
string.Empty,
string.Empty,
string.Empty);
m_FtpWebResponse.InternalSetFromCache = true;
m_FtpWebResponse.InternalSetIsCacheFresh = (ctx.CacheFreshnessStatus != CacheFreshnessStatus.Stale);
}
return true;
}
//
// This method has to be invoked as part of the wire response processing.
// The wire response can be replaced on return
//
// ATTN: If the method returns false, the response is invalid and should be retried
//
private bool CheckCacheRetrieveOnResponse() {
if (CacheProtocol == null || m_CacheDone) {
return true;
}
if (CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue)
{
// cache has been already revalidated proceed with cache update
return true;
}
if (CacheProtocol.ProtocolStatus == CacheValidationStatus.Fail) {
if(Logging.On)Logging.Exception(Logging.Web, this, "CheckCacheRetrieveOnResponse", CacheProtocol.ProtocolException);
throw CacheProtocol.ProtocolException;
}
// At this point we dont have the real data stream, hence passing null and updating it later
CacheProtocol.GetRevalidateStatus(m_FtpWebResponse, null);
if (CacheProtocol.ProtocolStatus == CacheValidationStatus.RetryResponseFromServer)
{
if (m_FtpWebResponse != null)
m_FtpWebResponse.SetResponseStream(null); //prevent from advancing commands pipeline
// Try to resubmit or fail
return false;
}
if (CacheProtocol.ProtocolStatus != CacheValidationStatus.ReturnCachedResponse)
{
// Proceed with the requesting the real server data stream
return false;
}
if (m_MethodInfo.Operation != FtpOperation.DownloadFile)
{
// This should never happen in real life
throw new NotSupportedException(SR.GetString(SR.net_cache_not_supported_command));
}
FtpRequestCacheValidator ctx = (FtpRequestCacheValidator) CacheProtocol.Validator;
FtpWebResponse oldResponse = m_FtpWebResponse;
m_Stream = CacheProtocol.ResponseStream;
m_FtpWebResponse = new FtpWebResponse(CacheProtocol.ResponseStream,
CacheProtocol.ResponseStreamLength,
RequestUri,
UsePassive? FtpStatusCode.DataAlreadyOpen: FtpStatusCode.OpeningData,
(UsePassive? FtpStatusCode.DataAlreadyOpen: FtpStatusCode.OpeningData).ToString(),
ctx.CacheEntry.LastModifiedUtc == DateTime.MinValue? DateTime.Now: ctx.CacheEntry.LastModifiedUtc.ToLocalTime(),
string.Empty,
string.Empty,
string.Empty);
m_FtpWebResponse.InternalSetFromCache = true;
m_FtpWebResponse.InternalSetIsCacheFresh = CacheProtocol.IsCacheFresh;
oldResponse.Close();
return true;
}
//
// This will decide on cache update and construct the effective response stream
//
private void CheckCacheUpdateOnResponse()
{
if (CacheProtocol == null || m_CacheDone) {
return;
}
m_CacheDone = true;
if (m_Connection != null)
{
m_FtpWebResponse.UpdateStatus(m_Connection.StatusCode, m_Connection.StatusLine, m_Connection.ExitMessage);
if (m_Connection.StatusCode == FtpStatusCode.OpeningData && m_FtpWebResponse.ContentLength == 0)
m_FtpWebResponse.SetContentLength(m_Connection.ContentLength);
}
if (CacheProtocol.ProtocolStatus == CacheValidationStatus.CombineCachedAndServerResponse)
{
// Note we already asked for a file restart
// The only problem is that we could not create the combined stream sooner.
m_Stream = new CombinedReadStream(CacheProtocol.Validator.CacheStream, m_FtpWebResponse.GetResponseStream());
//
// For consistent user experience we always supply DataAlreadyOpen status for a cached response.
//
FtpStatusCode rightStatus = UsePassive? FtpStatusCode.DataAlreadyOpen: FtpStatusCode.OpeningData;
m_FtpWebResponse.UpdateStatus(rightStatus, rightStatus.ToString(), string.Empty);
m_FtpWebResponse.SetResponseStream(m_Stream);
}
if (CacheProtocol.GetUpdateStatus(m_FtpWebResponse, m_FtpWebResponse.GetResponseStream()) == CacheValidationStatus.UpdateResponseInformation)
{
m_Stream = CacheProtocol.ResponseStream;
m_FtpWebResponse.SetResponseStream(m_Stream);
}
else if (CacheProtocol.ProtocolStatus == CacheValidationStatus.Fail)
throw CacheProtocol.ProtocolException;
}
internal void DataStreamClosed(CloseExState closeState)
{
if ((closeState & CloseExState.Abort) == 0)
{
if (!m_Async)
{
if (m_Connection != null)
m_Connection.CheckContinuePipeline();
}
else
{
m_RequestCompleteAsyncResult.InternalWaitForCompletion();
CheckError();
}
}
else
{
FtpControlStream connection = m_Connection;
if (connection != null)
connection.Abort(ExceptionHelper.RequestAbortedException);
}
}
private static int GetStatusCode(WebException webException)
{
int result = -1;
// we are calculating statusCode only when FrameworkEventSource logging is enabled.
if (FrameworkEventSource.Log.IsEnabled() && webException != null && webException.Response != null) {
HttpWebResponse httpWebResponse = webException.Response as HttpWebResponse;
if (httpWebResponse != null) {
try {
result = (int)httpWebResponse.StatusCode;
}
catch (ObjectDisposedException) {
// ObjectDisposedException is expected here in the following sequuence: ftpWebRequest.GetResponse().Dispose() -> ftpWebRequest.GetResponse()
// on the second call to GetResponse() we cannot determine the statusCode.
}
}
else {
var ftpWebResponse = webException.Response as FtpWebResponse;
result = GetStatusCode(ftpWebResponse);
}
}
return result;
}
private static int GetStatusCode(FtpWebResponse ftpWebResponse)
{
int result = -1;
if (FrameworkEventSource.Log.IsEnabled() && ftpWebResponse != null) {
try {
result = (int)ftpWebResponse.StatusCode;
}
catch (ObjectDisposedException) {
// ObjectDisposedException is expected here in the following sequuence: ftpWebRequest.GetResponse().Dispose() -> ftpWebRequest.GetResponse()
// on the second call to GetResponse() we cannot determine the statusCode.
}
}
return result;
}
} // class FtpWebRequest
//
// Class used by the WebRequest.Create factory to create FTP requests
//
internal class FtpWebRequestCreator : IWebRequestCreate {
internal FtpWebRequestCreator() {
}
public WebRequest Create(Uri uri) {
return new FtpWebRequest(uri);
}
} // class FtpWebRequestCreator
} //namespace System.Net
|