Code:
/ Dotnetfx_Vista_SP2 / Dotnetfx_Vista_SP2 / 8.0.50727.4016 / DEVDIV / depot / DevDiv / releases / whidbey / NetFxQFE / ndp / fx / src / Net / System / Net / _FtpControlStream.cs / 1 / _FtpControlStream.cs
// ------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// -----------------------------------------------------------------------------
//
namespace System.Net {
using System.Collections;
using System.IO;
using System.Security.Cryptography.X509Certificates ;
using System.Net.Sockets;
using System.Security.Permissions;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using System.Net.Cache;
internal enum FtpPrimitive {
Upload = 0,
Download = 1,
CommandOnly = 2
};
internal enum FtpLoginState:byte {
NotLoggedIn,
LoggedIn,
LoggedInButNeedsRelogin,
ReloginFailed
};
///
///
/// The FtpControlStream class implements a basic FTP connection,
/// This means basic command sending and parsing.
/// Queuing is handled by the ConnectionPool, so that a Request is guarenteed
/// exclusive access to the Connection.
/// This is a pooled object, that will be stored in a pool when idle.
///
///
internal class FtpControlStream : CommandStream {
private Socket m_DataSocket;
private IPEndPoint m_PassiveEndPoint;
private TlsStream m_TlsStream;
private StringBuilder m_BannerMessage;
private StringBuilder m_WelcomeMessage;
private StringBuilder m_ExitMessage;
private WeakReference m_Credentials;
private string m_Alias = null;
private bool m_IsRootPath;
private long m_ContentLength = -1;
private DateTime m_LastModified;
private bool m_DataHandshakeStarted = false;
private string m_LoginDirectory;
private string m_PreviousServerPath;
private string m_NewServerPath;
private Uri m_ResponseUri;
private bool m_LastRequestWasUnknownMethod;
private FtpLoginState m_LoginState = FtpLoginState.NotLoggedIn;
internal FtpStatusCode StatusCode;
internal string StatusLine;
internal NetworkCredential Credentials {
get {
if (m_Credentials != null && m_Credentials.IsAlive) {
return (NetworkCredential) m_Credentials.Target;
} else {
return null;
}
}
set {
if (m_Credentials == null) {
m_Credentials = new WeakReference(null);
}
m_Credentials.Target = value;
}
}
private static readonly AsyncCallback m_AcceptCallbackDelegate = new AsyncCallback(AcceptCallback);
private static readonly AsyncCallback m_ConnectCallbackDelegate = new AsyncCallback(ConnectCallback);
private static readonly AsyncCallback m_SSLHandshakeCallback = new AsyncCallback(SSLHandshakeCallback);
///
///
/// Setups and Creates a NetworkStream connection to the server
/// perform any initalization if needed
///
///
internal FtpControlStream(
ConnectionPool connectionPool,
TimeSpan lifetime,
bool checkLifetime
) : base(connectionPool, lifetime, checkLifetime) {
}
///
/// Closes the connecting socket to generate an error.
///
internal void AbortConnect() {
Socket socket = m_DataSocket;
if (socket != null) {
try {
socket.Close();
}
catch (ObjectDisposedException) {
}
}
}
///
/// Provides a wrapper for the async accept operations
///
private static void AcceptCallback(IAsyncResult asyncResult) {
FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
Socket listenSocket = (Socket)castedAsyncResult.AsyncObject;
try {
connection.m_DataSocket = listenSocket.EndAccept(asyncResult);
if (!connection.ServerAddress.Equals(((IPEndPoint)connection.m_DataSocket.RemoteEndPoint).Address))
{
connection.m_DataSocket.Close();
throw new WebException(SR.GetString(SR.net_ftp_active_address_different), WebExceptionStatus.ProtocolError);
}
connection.ContinueCommandPipeline();
} catch (Exception e) {
connection.CloseSocket();
connection.InvokeRequestCallback(e);
} finally {
listenSocket.Close();
}
}
///
/// Provides a wrapper for the async accept operations
///
private static void ConnectCallback(IAsyncResult asyncResult) {
FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
try {
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
Socket dataSocket = (Socket)castedAsyncResult.AsyncObject;
dataSocket.EndConnect(asyncResult);
connection.ContinueCommandPipeline();
} catch (Exception e) {
connection.CloseSocket();
connection.InvokeRequestCallback(e);
}
}
//
// We issue a dummy read on the the SSL data stream to force SSL handshake
// This callback will will get stream to the user.
//
private static void SSLHandshakeCallback(IAsyncResult asyncResult)
{
FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
try {
connection.ContinueCommandPipeline();
} catch (Exception e) {
connection.CloseSocket();
connection.InvokeRequestCallback(e);
}
}
// Creates a FtpDataStream object, constructs a TLS stream if needed.
// In case SSL we issue a 0 bytes read on that stream to force handshake.
// In case SSL and ASYNC we delay sigaling the user stream until the handshake is done.
//
private PipelineInstruction QueueOrCreateFtpDataStream(ref Stream stream)
{
if (m_DataSocket == null)
throw new InternalException();
//
// Re-entered pipeline with completed read on the TlsStream
//
if (this.m_TlsStream != null )
{
stream = new FtpDataStream(this.m_TlsStream, (FtpWebRequest) m_Request, IsFtpDataStreamWriteable());
this.m_TlsStream = null;
return PipelineInstruction.GiveStream;
}
NetworkStream networkStream = new NetworkStream(m_DataSocket, true);
#if !FEATURE_PAL
if (UsingSecureStream)
{
FtpWebRequest request = (FtpWebRequest) m_Request;
TlsStream tlsStream = new TlsStream(request.RequestUri.Host, networkStream, request.ClientCertificates, Pool.ServicePoint, request, m_Async ? request.GetWritingContext().ContextCopy : null);
networkStream = tlsStream;
if (m_Async)
{
this.m_TlsStream = tlsStream;
LazyAsyncResult handshakeResult = new LazyAsyncResult(null, this, m_SSLHandshakeCallback);
tlsStream.ProcessAuthentication(handshakeResult);
return PipelineInstruction.Pause;
}
else
{
tlsStream.ProcessAuthentication(null);
}
}
#endif // !FEATURE_PAL
stream = new FtpDataStream(networkStream, (FtpWebRequest) m_Request, IsFtpDataStreamWriteable());
return PipelineInstruction.GiveStream;
}
///
/// Cleans up state variables for reuse of the connection
///
protected override void ClearState() {
m_ContentLength = -1;
m_LastModified = DateTime.MinValue;
m_ResponseUri = null;
m_DataHandshakeStarted = false;
StatusCode = FtpStatusCode.Undefined;
StatusLine = null;
m_DataSocket = null;
m_PassiveEndPoint = null;
m_TlsStream = null;
base.ClearState();
}
//
// This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed.
// This function controls the seting up of a data socket/connection, and of saving off the server responses
//
protected override PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream)
{
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + (entry == null? "null" : entry.Command));
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + ((response == null) ? "null" : response.StatusDescription));
// null response is not expected
if (response == null)
return PipelineInstruction.Abort;
FtpStatusCode status = (FtpStatusCode) response.Status;
//
// Update global "current status" for FtpWebRequest
//
if (status != FtpStatusCode.ClosingControl)
{
// A 221 status won't be reflected on the user FTP response
// Anything else will (by design?)
StatusCode = status;
StatusLine = response.StatusDescription;
}
// If the status code is outside the range defined in RFC (1xx to 5xx) throw
if (response.InvalidStatusCode)
throw new WebException(SR.GetString(SR.net_InvalidStatusCode), WebExceptionStatus.ProtocolError);
// Update the banner message if any, this is a little hack because the "entry" param is null
if (m_Index == -1) {
if (status == FtpStatusCode.SendUserCommand)
{
m_BannerMessage = new StringBuilder();
m_BannerMessage.Append(StatusLine);
return PipelineInstruction.Advance;
}
else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable)
{
return PipelineInstruction.Reread;
}
else
throw GenerateException(status,response.StatusDescription, null);
}
//
// Check for the result of our attempt to use UTF8
// Condsider: optimize this for speed (avoid string compare) as that is the only command that may fail
//
if (entry.Command == "OPTS utf8 on\r\n")
{
if (response.PositiveCompletion) {
Encoding = Encoding.UTF8;
} else {
Encoding = Encoding.Default;
}
return PipelineInstruction.Advance;
}
// If we are already logged in and the server returns 530 then
// the server does not support re-issuing a USER command,
// tear down the connection and start all over again
if (entry.Command.IndexOf("USER") != -1)
{
// The server may not require a password for this user, so bypass the password command
if (status == FtpStatusCode.LoggedInProceed)
{
m_LoginState = FtpLoginState.LoggedIn;
m_Index++;
}
// The server does not like re-login
// (We are logged in already but want to re-login under a different user)
else if (status == FtpStatusCode.NotLoggedIn &&
m_LoginState != FtpLoginState.NotLoggedIn)
{
m_LoginState = FtpLoginState.ReloginFailed;
throw ExceptionHelper.IsolatedException;
}
}
//
// Throw on an error with possibe recovery option
//
if (response.TransientFailure || response.PermanentFailure) {
if (status == FtpStatusCode.ServiceNotAvailable) {
MarkAsRecoverableFailure();
}
throw GenerateException(status,response.StatusDescription, null);
}
if (m_LoginState != FtpLoginState.LoggedIn
&& entry.Command.IndexOf("PASS") != -1)
{
// Note the fact that we logged in
if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed)
m_LoginState = FtpLoginState.LoggedIn;
else
throw GenerateException(status,response.StatusDescription, null);
}
//
// Parse special cases
//
if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate))
{
bool isSocketReady;
PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady);
if (!isSocketReady)
return result;
// otheriwse we have a stream to create
}
//
// This is part of the above case and it's all about giving data stream back
//
if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen)
{
if (m_DataSocket == null)
{
// a better diagnostic?
return PipelineInstruction.Abort;
}
if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream))
{
m_AbortReason = SR.GetString(SR.net_ftp_invalid_status_response, status, entry.Command);
return PipelineInstruction.Abort;
}
// Parse out the Content length, if we can
TryUpdateContentLength(response.StatusDescription);
// Parse out the file name, when it is returned and use it for our ResponseUri
if (status == FtpStatusCode.OpeningData)
{
FtpWebRequest request = (FtpWebRequest) m_Request;
if (request.MethodInfo.ShouldParseForResponseUri)
{
TryUpdateResponseUri(response.StatusDescription, request);
}
}
return QueueOrCreateFtpDataStream(ref stream);
}
//
// Parse responses by status code exclusivelly
//
//Update our command list if we have an alias
if (status == FtpStatusCode.LoggedInProceed)
{
if(StatusLine.ToLower(CultureInfo.InvariantCulture).IndexOf("alias") > 0){
//find start of alias
//skip first status code
int i = StatusLine.IndexOf("230-",3);
if(i > 0)
{
i+=4;
//eat white space
while(i
/// Creates an array of commands, that will be sent to the server
///
protected override PipelineEntry [] BuildCommandsList(WebRequest req) {
FtpWebRequest request = (FtpWebRequest) req;
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "BuildCommandsList");
m_ResponseUri = request.RequestUri;
ArrayList commandList = new ArrayList();
if ((m_LastRequestWasUnknownMethod && !request.MethodInfo.IsUnknownMethod)
|| Credentials == null
|| !Credentials.IsEqualTo(request.Credentials.GetCredential(request.RequestUri, "basic"))) {
m_PreviousServerPath = null;
m_NewServerPath = null;
m_LoginDirectory = null;
if (m_LoginState == FtpLoginState.LoggedIn)
m_LoginState = FtpLoginState.LoggedInButNeedsRelogin;
}
m_LastRequestWasUnknownMethod = request.MethodInfo.IsUnknownMethod;
if (request.EnableSsl && !UsingSecureStream) {
commandList.Add(new PipelineEntry(FormatFtpCommand("AUTH", "TLS")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PBSZ", "0")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PROT", "P")));
// According to RFC we need to re-authorize with USER/PASS after we re-authenticate.
if (m_LoginState == FtpLoginState.LoggedIn)
m_LoginState = FtpLoginState.LoggedInButNeedsRelogin;
}
if (m_LoginState != FtpLoginState.LoggedIn) {
Credentials = request.Credentials.GetCredential(request.RequestUri, "basic");
m_WelcomeMessage = new StringBuilder();
m_ExitMessage = new StringBuilder();
string domainUserName = string.Empty;
string password = string.Empty;
if (Credentials != null)
{
domainUserName = Credentials.InternalGetDomainUserName();
password = Credentials.InternalGetPassword();
}
if (domainUserName.Length == 0 && password.Length == 0)
{
domainUserName = "anonymous";
password = "anonymous@";
}
commandList.Add(new PipelineEntry(FormatFtpCommand("USER", domainUserName)));
commandList.Add(new PipelineEntry(FormatFtpCommand("PASS", password), PipelineEntryFlags.DontLogParameter));
commandList.Add(new PipelineEntry(FormatFtpCommand("OPTS", "utf8 on")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PWD", null)));
}
GetPathOption getPathOption = GetPathOption.Normal;
if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter))
{
getPathOption = GetPathOption.AssumeNoFilename;
}
else if (request.MethodInfo.HasFlag(FtpMethodFlags.ParameterIsDirectory))
{
getPathOption = GetPathOption.AssumeFilename;
}
string requestPath = null;
string requestFilename = null;
GetPathAndFilename(getPathOption, request.RequestUri, ref requestPath, ref requestFilename, ref m_IsRootPath);
if (requestFilename.Length == 0 && request.MethodInfo.HasFlag(FtpMethodFlags.TakesParameter))
throw new WebException(SR.GetString(SR.net_ftp_invalid_uri));
string newServerPath = requestPath;
if (m_PreviousServerPath != newServerPath) {
if (!m_IsRootPath
&& m_LoginState == FtpLoginState.LoggedIn
&& m_LoginDirectory != null)
{
newServerPath = m_LoginDirectory+newServerPath;
}
m_NewServerPath = newServerPath;
commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", newServerPath), PipelineEntryFlags.UserCommand));
}
if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.DoNotTakeFromCache && request.MethodInfo.Operation == FtpOperation.DownloadFile)
commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename)));
if (!request.MethodInfo.IsCommandOnly)
{
// This is why having a protocol logic on the connection is a bad idea
if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue)
{
if (request.UseBinary) {
commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "I")));
} else {
commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "A")));
}
if (request.UsePassive) {
string passiveCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PASV" : "EPSV";
commandList.Add(new PipelineEntry(FormatFtpCommand(passiveCommand, null), PipelineEntryFlags.CreateDataConnection));
} else {
string portCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PORT" : "EPRT";
CreateFtpListenerSocket(request);
commandList.Add(new PipelineEntry(FormatFtpCommand(portCommand, GetPortCommandLine(request))));
}
if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.CombineCachedAndServerResponse)
{
// Combining partial cache with the reminder using "REST"
if (request.CacheProtocol.Validator.CacheEntry.StreamSize > 0)
commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.CacheProtocol.Validator.CacheEntry.StreamSize.ToString(CultureInfo.InvariantCulture))));
}
else if (request.ContentOffset > 0) {
// REST command must always be the last sent before the main file command is sent.
commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.ContentOffset.ToString(CultureInfo.InvariantCulture))));
}
}
else
{
// revalidating GetFileSize = "SIZE" GetDateTimeStamp = "MDTM"
commandList.Add(new PipelineEntry(FormatFtpCommand("SIZE", requestFilename)));
commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename)));
}
}
//
// Suppress the data file if this is a revalidation request
//
if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue)
{
PipelineEntryFlags flags = PipelineEntryFlags.UserCommand;
if (!request.MethodInfo.IsCommandOnly)
{
flags |= PipelineEntryFlags.GiveDataStream;
if (!request.UsePassive)
flags |= PipelineEntryFlags.CreateDataConnection;
}
if (request.MethodInfo.Operation == FtpOperation.Rename)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("RNFR", requestFilename), flags));
commandList.Add(new PipelineEntry(FormatFtpCommand("RNTO", request.RenameTo), flags));
} else {
commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestFilename), flags));
}
if (!request.KeepAlive) {
commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null)));
}
}
return (PipelineEntry []) commandList.ToArray(typeof(PipelineEntry));
}
private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream, out bool isSocketReady)
{
isSocketReady = false;
if (m_DataHandshakeStarted)
{
isSocketReady = true;
return PipelineInstruction.Pause; //if we already started then this is re-entering into the callback where we proceed with the stream
}
m_DataHandshakeStarted = true;
// handle passive responses by parsing the port and later doing a Connect(...)
bool isPassive = false;
int port = -1;
if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n")
{
if (!response.PositiveCompletion)
{
m_AbortReason = SR.GetString(SR.net_ftp_server_failed_passive, response.Status);
return PipelineInstruction.Abort;
}
if (entry.Command == "PASV\r\n")
{
IPAddress serverReturnedAddress = null;
port = GetAddressAndPort(response.StatusDescription,
ref serverReturnedAddress);
if (!ServerAddress.Equals(serverReturnedAddress))
throw new WebException(SR.GetString(SR.net_ftp_passive_address_different));
} else {
port = GetPortV6(response.StatusDescription);
}
isPassive = true;
}
new SocketPermission(PermissionState.Unrestricted).Assert();
try {
if (isPassive)
{
GlobalLog.Assert(port != -1, "FtpControlStream#{0}|'port' not set.", ValidationHelper.HashString(this));
try {
m_DataSocket = CreateFtpDataSocket((FtpWebRequest)m_Request, Socket);
} catch (ObjectDisposedException) {
throw ExceptionHelper.RequestAbortedException;
}
m_PassiveEndPoint = new IPEndPoint(ServerAddress, port);
}
PipelineInstruction result;
if (m_PassiveEndPoint != null)
{
IPEndPoint passiveEndPoint = m_PassiveEndPoint;
m_PassiveEndPoint = null;
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Connect()");
if (m_Async)
{
m_DataSocket.BeginConnect(passiveEndPoint, m_ConnectCallbackDelegate, this);
result = PipelineInstruction.Pause;
}
else
{
m_DataSocket.Connect(passiveEndPoint);
result = PipelineInstruction.Advance; // for passive mode we end up going to the next command
}
}
else
{
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Accept()");
if (m_Async)
{
m_DataSocket.BeginAccept(m_AcceptCallbackDelegate, this);
result = PipelineInstruction.Pause;
}
else
{
Socket listenSocket = m_DataSocket;
try {
m_DataSocket = m_DataSocket.Accept();
if (!ServerAddress.Equals(((IPEndPoint)m_DataSocket.RemoteEndPoint).Address))
{
m_DataSocket.Close();
throw new WebException(SR.GetString(SR.net_ftp_active_address_different), WebExceptionStatus.ProtocolError);
}
isSocketReady = true; // for active mode we end up creating a stream before advancing the pipeline
result = PipelineInstruction.Pause;
} finally {
listenSocket.Close();
}
}
}
return result;
} finally {
SocketPermission.RevertAssert();
}
}
//
// A door into protected CloseSocket() method
//
internal void Quit()
{
CloseSocket();
}
private enum GetPathOption {
Normal,
AssumeFilename,
AssumeNoFilename
}
///
/// Gets the path componet of the Uri
///
private static void GetPathAndFilename(GetPathOption pathOption,
Uri uri,
ref string path,
ref string filename,
ref bool isRoot)
{
string tempPath = uri.GetParts(
UriComponents.Path|UriComponents.KeepDelimiter,
UriFormat.Unescaped);
isRoot = false;
if (tempPath.StartsWith("//")) {
isRoot = true;
tempPath = tempPath.Substring(1, tempPath.Length-1);
}
int index = tempPath.LastIndexOf('/');
switch (pathOption) {
case GetPathOption.AssumeFilename:
if (index != -1 && index == tempPath.Length-1) {
// Remove last '/' and continue normal processing
tempPath = tempPath.Substring(0, tempPath.Length-1);
index = tempPath.LastIndexOf('/');
}
path = tempPath.Substring(0, index+1);
filename = tempPath.Substring(index+1, tempPath.Length-(index+1));
break;
case GetPathOption.AssumeNoFilename:
path = tempPath;
filename = "";
break;
case GetPathOption.Normal:
default:
path = tempPath.Substring(0, index+1);
filename = tempPath.Substring(index+1, tempPath.Length-(index+1));
break;
}
if (path.Length == 0)
path = "/";
}
//
///
/// Formats an IP address (contained in a UInt32) to a FTP style command string
///
private String FormatAddress(IPAddress address, int Port )
{
byte [] localAddressInBytes = address.GetAddressBytes();
// produces a string in FTP IPAddress/Port encoding (a1, a2, a3, a4, p1, p2), for sending as a parameter
// to the port command.
StringBuilder sb = new StringBuilder(32);
foreach (byte element in localAddressInBytes) {
sb.Append(element);
sb.Append(',');
}
sb.Append(Port / 256 );
sb.Append(',');
sb.Append(Port % 256 );
return sb.ToString();
}
///
/// Formats an IP address (v6) to a FTP style command string
/// Looks something in this form: |2|1080::8:800:200C:417A|5282|
/// |2|4567::0123:5678:0123:5678|0123|
///
private string FormatAddressV6(IPAddress address, int port) {
StringBuilder sb = new StringBuilder(43); // based on max size of IPv6 address + port + seperators
String addressString = address.ToString();
sb.Append("|2|");
sb.Append(addressString);
sb.Append('|');
sb.Append(port.ToString(NumberFormatInfo.InvariantInfo));
sb.Append('|');
return sb.ToString();
}
internal long ContentLength {
get {
return m_ContentLength;
}
}
internal DateTime LastModified {
get {
return m_LastModified;
}
}
internal Uri ResponseUri {
get {
return m_ResponseUri;
}
}
///
/// Returns the server message sent before user credentials are sent
///
internal string BannerMessage {
get {
return (m_BannerMessage != null) ? m_BannerMessage.ToString() : null;
}
}
///
/// Returns the server message sent after user credentials are sent
///
internal string WelcomeMessage {
get {
return (m_WelcomeMessage != null) ? m_WelcomeMessage.ToString() : null;
}
}
///
/// Returns the exit sent message on shutdown
///
internal string ExitMessage {
get {
return (m_ExitMessage != null) ? m_ExitMessage.ToString() : null;
}
}
///
/// Parses a response string for content length
///
private long GetContentLengthFrom213Response(string responseString) {
string [] parsedList = responseString.Split(new char [] {' '});
if (parsedList.Length < 2)
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
return Convert.ToInt64(parsedList[1], NumberFormatInfo.InvariantInfo);
}
///
/// Parses a response string for last modified time
///
private DateTime GetLastModifiedFrom213Response(string str) {
DateTime dateTime = m_LastModified;
string [] parsedList = str.Split(new char [] {' ', '.'});
if (parsedList.Length < 2) {
return dateTime;
}
string dateTimeLine = parsedList[1];
if (dateTimeLine.Length < 14) {
return dateTime;
}
int year = Convert.ToInt32(dateTimeLine.Substring(0, 4), NumberFormatInfo.InvariantInfo);
int month = Convert.ToInt16(dateTimeLine.Substring(4, 2), NumberFormatInfo.InvariantInfo);
int day = Convert.ToInt16(dateTimeLine.Substring(6, 2), NumberFormatInfo.InvariantInfo);
int hour = Convert.ToInt16(dateTimeLine.Substring(8, 2), NumberFormatInfo.InvariantInfo);
int minute = Convert.ToInt16(dateTimeLine.Substring(10, 2), NumberFormatInfo.InvariantInfo);
int second = Convert.ToInt16(dateTimeLine.Substring(12, 2), NumberFormatInfo.InvariantInfo);
int millisecond = 0;
if (parsedList.Length > 2) {
millisecond = Convert.ToInt16(parsedList[2], NumberFormatInfo.InvariantInfo);
}
try {
dateTime = new DateTime(year, month, day, hour, minute, second, millisecond);
dateTime = dateTime.ToLocalTime(); // must be handled in local time
} catch (ArgumentOutOfRangeException) {
} catch (ArgumentException) {
}
return dateTime;
}
///
/// Attempts to find the response Uri
/// Typical string looks like this, need to get trailing filename
/// "150 Opening BINARY mode data connection for FTP46.tmp."
///
private void TryUpdateResponseUri(string str, FtpWebRequest request)
{
Uri baseUri = request.RequestUri;
//
// Not sure what we are doing here but I guess the logic is IIS centric
//
int start = str.IndexOf("for ");
if (start == -1)
return;
start += 4;
int end = str.LastIndexOf('(');
if (end == -1)
end = str.Length;
if (end <= start)
return;
string filename = str.Substring(start, end-start);
filename = filename.TrimEnd(new char [] {' ', '.','\r','\n'});
// Do minimal escaping that we need to get a valid Uri
// when combined with the baseUri
string escapedFilename;
escapedFilename = filename.Replace("%", "%25");
escapedFilename = escapedFilename.Replace("#", "%23");
// help us out if the user forgot to add a slash to the directory name
string orginalPath = baseUri.AbsolutePath;
if (orginalPath.Length > 0 && orginalPath[orginalPath.Length-1] != '/') {
UriBuilder uriBuilder = new UriBuilder(baseUri);
uriBuilder.Path = orginalPath + "/";
baseUri = uriBuilder.Uri;
}
Uri newUri;
if (!Uri.TryCreate(baseUri, escapedFilename, out newUri))
{
throw new FormatException(SR.GetString(SR.net_ftp_invalid_response_filename, filename));
} else {
if (!baseUri.IsBaseOf(newUri) ||
baseUri.Segments.Length != newUri.Segments.Length-1)
{
throw new FormatException(SR.GetString(SR.net_ftp_invalid_response_filename, filename));
}
else
{
m_ResponseUri = newUri;
}
}
}
///
/// Parses a response string for content length
///
private void TryUpdateContentLength(string str)
{
int pos1 = str.LastIndexOf("(");
if (pos1 != -1)
{
int pos2 = str.IndexOf(" bytes).");
if (pos2 != -1 && pos2 > pos1)
{
pos1++;
long result;
if (Int64.TryParse (str.Substring(pos1, pos2-pos1),
NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite,
NumberFormatInfo.InvariantInfo, out result))
{
m_ContentLength = result;
}
}
}
}
///
/// Parses a response string for a an IP Address
///
/*
private string GetIPAddress(string str)
{
StringBuilder IPstr=new StringBuilder(32);
string Substr = null;
int pos1 = str.IndexOf("(")+1;
int pos2 = str.IndexOf(",");
for(int i =0; i<3;i++)
{
Substr = str.Substring(pos1,pos2-pos1)+".";
IPstr.Append(Substr);
pos1 = pos2+1;
pos2 = str.IndexOf(",",pos1);
}
Substr = str.Substring(pos1,pos2-pos1);
IPstr.Append(Substr);
return IPstr.ToString();
}
*/
///
/// Parses a response string for our login dir in " "
///
private string GetLoginDirectory(string str) {
int firstQuote = str.IndexOf('"');
int lastQuote = str.LastIndexOf('"');
if (firstQuote != -1 && lastQuote != -1 && firstQuote != lastQuote) {
return str.Substring(firstQuote+1, lastQuote-firstQuote-1);
} else {
return String.Empty;
}
}
///
/// Parses a response string for a port number
///
private int GetAddressAndPort(string responseString, ref IPAddress ipAddress)
{
int port = 0;
const int firstPortDigit = 5;
const int secondPortDigit = 6;
string [] parsedList = responseString.Split(new char [] {'(', ',', ')'});
if (secondPortDigit>=parsedList.Length) {
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
}
port = Convert.ToInt32(parsedList[firstPortDigit], NumberFormatInfo.InvariantInfo)*256;
port = port + Convert.ToInt32(parsedList[secondPortDigit], NumberFormatInfo.InvariantInfo);
Int64 address = 0;
try {
for (int i=4; i>0; i--) {
address = (address<<8) +
Convert.ToByte(parsedList[i], NumberFormatInfo.InvariantInfo);
}
}
catch {
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
}
ipAddress = new IPAddress(address);
return port;
}
///
/// Parses a response string for a port number
///
private int GetPortV6(string responseString)
{
int pos1 = responseString.LastIndexOf("(");
int pos2 = responseString.LastIndexOf(")");
if (pos1 == -1 || pos2 <= pos1)
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
// addressInfo will contain a string of format "||||"
string addressInfo = responseString.Substring(pos1+1, pos2-pos1-1);
// Although RFC2428 recommends using "|" as the delimiter,
// It allows ASCII characters in range 33-126 inclusive.
// We should consider allowing the full range.
string [] parsedList = addressInfo.Split(new char [] {'|'});
if (parsedList.Length < 4)
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
return Convert.ToInt32(parsedList[3], NumberFormatInfo.InvariantInfo);
}
///
/// Creates the Listener socket
///
private void CreateFtpListenerSocket(FtpWebRequest request) {
// see \\index1\sdnt\inetcore\wininet\ftp
// gets an IPEndPoint for the local host for the data socket to bind to.
IPEndPoint epListener = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint).Address, 0);
try {
m_DataSocket = CreateFtpDataSocket(request, Socket);
} catch (ObjectDisposedException) {
throw ExceptionHelper.RequestAbortedException;
}
// SECURITY:
// Since we are doing WebRequest, we don't require SocketPermissions
// Consider V.Next: Change to declarative form (10x faster) but
// SocketPermission must be moved out of System.dll for this to work
new SocketPermission(PermissionState.Unrestricted).Assert();
try {
// binds the data socket to the local end point.
m_DataSocket.Bind(epListener);
m_DataSocket.Listen(1); // Put the dataSocket * & in Listen mode
} finally {
SocketPermission.RevertAssert();
}
}
///
/// Builds a command line to send to the server with proper port and IP address of client
///
private string GetPortCommandLine(FtpWebRequest request) {
try
{
// retrieves the IP address of the local endpoint
IPEndPoint localEP = (IPEndPoint) m_DataSocket.LocalEndPoint;
if (ServerAddress.AddressFamily == AddressFamily.InterNetwork) {
return FormatAddress(localEP.Address, localEP.Port);
} else if (ServerAddress.AddressFamily == AddressFamily.InterNetworkV6) {
return FormatAddressV6(localEP.Address, localEP.Port);
} else {
throw new InternalException();
}
}
catch(Exception e)
{
throw GenerateException(WebExceptionStatus.ProtocolError, e); // could not open data connection
}
catch
{
throw GenerateException(WebExceptionStatus.ProtocolError, new Exception(SR.GetString(SR.net_nonClsCompliantException))); // could not open data connection
}
}
///
/// Formats a simple FTP command + parameter in correct pre-wire format
///
private string FormatFtpCommand(string command, string parameter)
{
StringBuilder stringBuilder = new StringBuilder(command.Length + ((parameter != null) ? parameter.Length : 0) + 3 /*size of ' ' \r\n*/);
stringBuilder.Append(command);
if(!ValidationHelper.IsBlankString(parameter)) {
stringBuilder.Append(' ');
stringBuilder.Append(parameter);
}
stringBuilder.Append("\r\n");
return stringBuilder.ToString();
}
///
///
/// This will handle either connecting to a port or listening for one
///
///
protected Socket CreateFtpDataSocket(FtpWebRequest request, Socket templateSocket)
{
// Safe to be called under an Assert.
Socket socket = new Socket( templateSocket.AddressFamily, templateSocket.SocketType, templateSocket.ProtocolType );
return socket;
}
///
/// This function is called by the GeneralWebRequest superclass to determine whether a response is valid, and when it is complete.
/// It also gives the response description a
///
protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) {
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "CheckValid(" + response.StatusBuffer.ToString() + ")" );
// If the response is less than 4 bytes long, it is too short to tell, so return true, valid so far.
if(response.StatusBuffer.Length < 4) {
return true;
}
string responseString = response.StatusBuffer.ToString();
// Otherwise, if there is no status code for this response yet, get one.
if(response.Status == ResponseDescription.NoStatus)
{
// If the response does not start with three digits, then it is not a valid response from an FTP server.
if(!(Char.IsDigit(responseString[0]) && Char.IsDigit(responseString[1]) && Char.IsDigit(responseString[2]) && (responseString[3] == ' ' || responseString[3] == '-'))) {
return false;
} else {
response.StatusCodeString = responseString.Substring(0, 3);
response.Status = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo);
}
// IF a hyphen follows the status code on the first line of the response, then we have a multiline response coming.
if (responseString[3] == '-') {
response.Multiline = true;
}
}
// If a complete line of response has been received from the server, then see if the
// overall response is complete.
// If this was not a multiline response, then the response is complete at the end of the line.
// If this was a multiline response (indicated by three digits followed by a '-' in the first line,
// then we see if the last line received started with the same three digits followed by a space.
// If it did, then this is the sign of a complete multiline response.
// If the line contained three other digits followeed by the response, then this is a violation of the
// FTP protocol for multiline responses.
// All other cases indicate that the response is not yet complete.
int index = 0;
while((index = responseString.IndexOf("\r\n", validThrough)) != -1) // gets the end line.
{
int lineStart = validThrough;
validThrough = index + 2; // validThrough now marks the end of the line being examined.
if(!response.Multiline)
{
completeLength = validThrough;
return true;
} // same here
if(responseString.Length > lineStart + 4)
{
// if the first three characters of the the response line currently being examined
// match the status code, then if they are followed by a space, then we
// have reached the end of the reply.
if(responseString.Substring(lineStart, 3) == response.StatusCodeString)
{
if(responseString[lineStart + 3] == ' ')
{
completeLength = validThrough;
return true;
}
}
}
}
return true;
}
///
/// Determnines whether the stream we return is Writeable or Readable
///
private TriState IsFtpDataStreamWriteable() {
FtpWebRequest request = m_Request as FtpWebRequest;
if (request != null) {
if (request.MethodInfo.IsUpload) {
return TriState.True;
} else if (request.MethodInfo.IsDownload) {
return TriState.False;
}
}
return TriState.Unspecified;
}
} // class FtpControlStream
} // namespace System.Net
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// ------------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// -----------------------------------------------------------------------------
//
namespace System.Net {
using System.Collections;
using System.IO;
using System.Security.Cryptography.X509Certificates ;
using System.Net.Sockets;
using System.Security.Permissions;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using System.Net.Cache;
internal enum FtpPrimitive {
Upload = 0,
Download = 1,
CommandOnly = 2
};
internal enum FtpLoginState:byte {
NotLoggedIn,
LoggedIn,
LoggedInButNeedsRelogin,
ReloginFailed
};
///
///
/// The FtpControlStream class implements a basic FTP connection,
/// This means basic command sending and parsing.
/// Queuing is handled by the ConnectionPool, so that a Request is guarenteed
/// exclusive access to the Connection.
/// This is a pooled object, that will be stored in a pool when idle.
///
///
internal class FtpControlStream : CommandStream {
private Socket m_DataSocket;
private IPEndPoint m_PassiveEndPoint;
private TlsStream m_TlsStream;
private StringBuilder m_BannerMessage;
private StringBuilder m_WelcomeMessage;
private StringBuilder m_ExitMessage;
private WeakReference m_Credentials;
private string m_Alias = null;
private bool m_IsRootPath;
private long m_ContentLength = -1;
private DateTime m_LastModified;
private bool m_DataHandshakeStarted = false;
private string m_LoginDirectory;
private string m_PreviousServerPath;
private string m_NewServerPath;
private Uri m_ResponseUri;
private bool m_LastRequestWasUnknownMethod;
private FtpLoginState m_LoginState = FtpLoginState.NotLoggedIn;
internal FtpStatusCode StatusCode;
internal string StatusLine;
internal NetworkCredential Credentials {
get {
if (m_Credentials != null && m_Credentials.IsAlive) {
return (NetworkCredential) m_Credentials.Target;
} else {
return null;
}
}
set {
if (m_Credentials == null) {
m_Credentials = new WeakReference(null);
}
m_Credentials.Target = value;
}
}
private static readonly AsyncCallback m_AcceptCallbackDelegate = new AsyncCallback(AcceptCallback);
private static readonly AsyncCallback m_ConnectCallbackDelegate = new AsyncCallback(ConnectCallback);
private static readonly AsyncCallback m_SSLHandshakeCallback = new AsyncCallback(SSLHandshakeCallback);
///
///
/// Setups and Creates a NetworkStream connection to the server
/// perform any initalization if needed
///
///
internal FtpControlStream(
ConnectionPool connectionPool,
TimeSpan lifetime,
bool checkLifetime
) : base(connectionPool, lifetime, checkLifetime) {
}
///
/// Closes the connecting socket to generate an error.
///
internal void AbortConnect() {
Socket socket = m_DataSocket;
if (socket != null) {
try {
socket.Close();
}
catch (ObjectDisposedException) {
}
}
}
///
/// Provides a wrapper for the async accept operations
///
private static void AcceptCallback(IAsyncResult asyncResult) {
FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
Socket listenSocket = (Socket)castedAsyncResult.AsyncObject;
try {
connection.m_DataSocket = listenSocket.EndAccept(asyncResult);
if (!connection.ServerAddress.Equals(((IPEndPoint)connection.m_DataSocket.RemoteEndPoint).Address))
{
connection.m_DataSocket.Close();
throw new WebException(SR.GetString(SR.net_ftp_active_address_different), WebExceptionStatus.ProtocolError);
}
connection.ContinueCommandPipeline();
} catch (Exception e) {
connection.CloseSocket();
connection.InvokeRequestCallback(e);
} finally {
listenSocket.Close();
}
}
///
/// Provides a wrapper for the async accept operations
///
private static void ConnectCallback(IAsyncResult asyncResult) {
FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
try {
LazyAsyncResult castedAsyncResult = asyncResult as LazyAsyncResult;
Socket dataSocket = (Socket)castedAsyncResult.AsyncObject;
dataSocket.EndConnect(asyncResult);
connection.ContinueCommandPipeline();
} catch (Exception e) {
connection.CloseSocket();
connection.InvokeRequestCallback(e);
}
}
//
// We issue a dummy read on the the SSL data stream to force SSL handshake
// This callback will will get stream to the user.
//
private static void SSLHandshakeCallback(IAsyncResult asyncResult)
{
FtpControlStream connection = (FtpControlStream)asyncResult.AsyncState;
try {
connection.ContinueCommandPipeline();
} catch (Exception e) {
connection.CloseSocket();
connection.InvokeRequestCallback(e);
}
}
// Creates a FtpDataStream object, constructs a TLS stream if needed.
// In case SSL we issue a 0 bytes read on that stream to force handshake.
// In case SSL and ASYNC we delay sigaling the user stream until the handshake is done.
//
private PipelineInstruction QueueOrCreateFtpDataStream(ref Stream stream)
{
if (m_DataSocket == null)
throw new InternalException();
//
// Re-entered pipeline with completed read on the TlsStream
//
if (this.m_TlsStream != null )
{
stream = new FtpDataStream(this.m_TlsStream, (FtpWebRequest) m_Request, IsFtpDataStreamWriteable());
this.m_TlsStream = null;
return PipelineInstruction.GiveStream;
}
NetworkStream networkStream = new NetworkStream(m_DataSocket, true);
#if !FEATURE_PAL
if (UsingSecureStream)
{
FtpWebRequest request = (FtpWebRequest) m_Request;
TlsStream tlsStream = new TlsStream(request.RequestUri.Host, networkStream, request.ClientCertificates, Pool.ServicePoint, request, m_Async ? request.GetWritingContext().ContextCopy : null);
networkStream = tlsStream;
if (m_Async)
{
this.m_TlsStream = tlsStream;
LazyAsyncResult handshakeResult = new LazyAsyncResult(null, this, m_SSLHandshakeCallback);
tlsStream.ProcessAuthentication(handshakeResult);
return PipelineInstruction.Pause;
}
else
{
tlsStream.ProcessAuthentication(null);
}
}
#endif // !FEATURE_PAL
stream = new FtpDataStream(networkStream, (FtpWebRequest) m_Request, IsFtpDataStreamWriteable());
return PipelineInstruction.GiveStream;
}
///
/// Cleans up state variables for reuse of the connection
///
protected override void ClearState() {
m_ContentLength = -1;
m_LastModified = DateTime.MinValue;
m_ResponseUri = null;
m_DataHandshakeStarted = false;
StatusCode = FtpStatusCode.Undefined;
StatusLine = null;
m_DataSocket = null;
m_PassiveEndPoint = null;
m_TlsStream = null;
base.ClearState();
}
//
// This is called by underlying base class code, each time a new response is received from the wire or a protocol stage is resumed.
// This function controls the seting up of a data socket/connection, and of saving off the server responses
//
protected override PipelineInstruction PipelineCallback(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream)
{
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + (entry == null? "null" : entry.Command));
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + ">" + ((response == null) ? "null" : response.StatusDescription));
// null response is not expected
if (response == null)
return PipelineInstruction.Abort;
FtpStatusCode status = (FtpStatusCode) response.Status;
//
// Update global "current status" for FtpWebRequest
//
if (status != FtpStatusCode.ClosingControl)
{
// A 221 status won't be reflected on the user FTP response
// Anything else will (by design?)
StatusCode = status;
StatusLine = response.StatusDescription;
}
// If the status code is outside the range defined in RFC (1xx to 5xx) throw
if (response.InvalidStatusCode)
throw new WebException(SR.GetString(SR.net_InvalidStatusCode), WebExceptionStatus.ProtocolError);
// Update the banner message if any, this is a little hack because the "entry" param is null
if (m_Index == -1) {
if (status == FtpStatusCode.SendUserCommand)
{
m_BannerMessage = new StringBuilder();
m_BannerMessage.Append(StatusLine);
return PipelineInstruction.Advance;
}
else if (status == FtpStatusCode.ServiceTemporarilyNotAvailable)
{
return PipelineInstruction.Reread;
}
else
throw GenerateException(status,response.StatusDescription, null);
}
//
// Check for the result of our attempt to use UTF8
// Condsider: optimize this for speed (avoid string compare) as that is the only command that may fail
//
if (entry.Command == "OPTS utf8 on\r\n")
{
if (response.PositiveCompletion) {
Encoding = Encoding.UTF8;
} else {
Encoding = Encoding.Default;
}
return PipelineInstruction.Advance;
}
// If we are already logged in and the server returns 530 then
// the server does not support re-issuing a USER command,
// tear down the connection and start all over again
if (entry.Command.IndexOf("USER") != -1)
{
// The server may not require a password for this user, so bypass the password command
if (status == FtpStatusCode.LoggedInProceed)
{
m_LoginState = FtpLoginState.LoggedIn;
m_Index++;
}
// The server does not like re-login
// (We are logged in already but want to re-login under a different user)
else if (status == FtpStatusCode.NotLoggedIn &&
m_LoginState != FtpLoginState.NotLoggedIn)
{
m_LoginState = FtpLoginState.ReloginFailed;
throw ExceptionHelper.IsolatedException;
}
}
//
// Throw on an error with possibe recovery option
//
if (response.TransientFailure || response.PermanentFailure) {
if (status == FtpStatusCode.ServiceNotAvailable) {
MarkAsRecoverableFailure();
}
throw GenerateException(status,response.StatusDescription, null);
}
if (m_LoginState != FtpLoginState.LoggedIn
&& entry.Command.IndexOf("PASS") != -1)
{
// Note the fact that we logged in
if (status == FtpStatusCode.NeedLoginAccount || status == FtpStatusCode.LoggedInProceed)
m_LoginState = FtpLoginState.LoggedIn;
else
throw GenerateException(status,response.StatusDescription, null);
}
//
// Parse special cases
//
if (entry.HasFlag(PipelineEntryFlags.CreateDataConnection) && (response.PositiveCompletion || response.PositiveIntermediate))
{
bool isSocketReady;
PipelineInstruction result = QueueOrCreateDataConection(entry, response, timeout, ref stream, out isSocketReady);
if (!isSocketReady)
return result;
// otheriwse we have a stream to create
}
//
// This is part of the above case and it's all about giving data stream back
//
if (status == FtpStatusCode.OpeningData || status == FtpStatusCode.DataAlreadyOpen)
{
if (m_DataSocket == null)
{
// a better diagnostic?
return PipelineInstruction.Abort;
}
if (!entry.HasFlag(PipelineEntryFlags.GiveDataStream))
{
m_AbortReason = SR.GetString(SR.net_ftp_invalid_status_response, status, entry.Command);
return PipelineInstruction.Abort;
}
// Parse out the Content length, if we can
TryUpdateContentLength(response.StatusDescription);
// Parse out the file name, when it is returned and use it for our ResponseUri
if (status == FtpStatusCode.OpeningData)
{
FtpWebRequest request = (FtpWebRequest) m_Request;
if (request.MethodInfo.ShouldParseForResponseUri)
{
TryUpdateResponseUri(response.StatusDescription, request);
}
}
return QueueOrCreateFtpDataStream(ref stream);
}
//
// Parse responses by status code exclusivelly
//
//Update our command list if we have an alias
if (status == FtpStatusCode.LoggedInProceed)
{
if(StatusLine.ToLower(CultureInfo.InvariantCulture).IndexOf("alias") > 0){
//find start of alias
//skip first status code
int i = StatusLine.IndexOf("230-",3);
if(i > 0)
{
i+=4;
//eat white space
while(i
/// Creates an array of commands, that will be sent to the server
///
protected override PipelineEntry [] BuildCommandsList(WebRequest req) {
FtpWebRequest request = (FtpWebRequest) req;
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "BuildCommandsList");
m_ResponseUri = request.RequestUri;
ArrayList commandList = new ArrayList();
if ((m_LastRequestWasUnknownMethod && !request.MethodInfo.IsUnknownMethod)
|| Credentials == null
|| !Credentials.IsEqualTo(request.Credentials.GetCredential(request.RequestUri, "basic"))) {
m_PreviousServerPath = null;
m_NewServerPath = null;
m_LoginDirectory = null;
if (m_LoginState == FtpLoginState.LoggedIn)
m_LoginState = FtpLoginState.LoggedInButNeedsRelogin;
}
m_LastRequestWasUnknownMethod = request.MethodInfo.IsUnknownMethod;
if (request.EnableSsl && !UsingSecureStream) {
commandList.Add(new PipelineEntry(FormatFtpCommand("AUTH", "TLS")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PBSZ", "0")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PROT", "P")));
// According to RFC we need to re-authorize with USER/PASS after we re-authenticate.
if (m_LoginState == FtpLoginState.LoggedIn)
m_LoginState = FtpLoginState.LoggedInButNeedsRelogin;
}
if (m_LoginState != FtpLoginState.LoggedIn) {
Credentials = request.Credentials.GetCredential(request.RequestUri, "basic");
m_WelcomeMessage = new StringBuilder();
m_ExitMessage = new StringBuilder();
string domainUserName = string.Empty;
string password = string.Empty;
if (Credentials != null)
{
domainUserName = Credentials.InternalGetDomainUserName();
password = Credentials.InternalGetPassword();
}
if (domainUserName.Length == 0 && password.Length == 0)
{
domainUserName = "anonymous";
password = "anonymous@";
}
commandList.Add(new PipelineEntry(FormatFtpCommand("USER", domainUserName)));
commandList.Add(new PipelineEntry(FormatFtpCommand("PASS", password), PipelineEntryFlags.DontLogParameter));
commandList.Add(new PipelineEntry(FormatFtpCommand("OPTS", "utf8 on")));
commandList.Add(new PipelineEntry(FormatFtpCommand("PWD", null)));
}
GetPathOption getPathOption = GetPathOption.Normal;
if (request.MethodInfo.HasFlag(FtpMethodFlags.DoesNotTakeParameter))
{
getPathOption = GetPathOption.AssumeNoFilename;
}
else if (request.MethodInfo.HasFlag(FtpMethodFlags.ParameterIsDirectory))
{
getPathOption = GetPathOption.AssumeFilename;
}
string requestPath = null;
string requestFilename = null;
GetPathAndFilename(getPathOption, request.RequestUri, ref requestPath, ref requestFilename, ref m_IsRootPath);
if (requestFilename.Length == 0 && request.MethodInfo.HasFlag(FtpMethodFlags.TakesParameter))
throw new WebException(SR.GetString(SR.net_ftp_invalid_uri));
string newServerPath = requestPath;
if (m_PreviousServerPath != newServerPath) {
if (!m_IsRootPath
&& m_LoginState == FtpLoginState.LoggedIn
&& m_LoginDirectory != null)
{
newServerPath = m_LoginDirectory+newServerPath;
}
m_NewServerPath = newServerPath;
commandList.Add(new PipelineEntry(FormatFtpCommand("CWD", newServerPath), PipelineEntryFlags.UserCommand));
}
if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.DoNotTakeFromCache && request.MethodInfo.Operation == FtpOperation.DownloadFile)
commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename)));
if (!request.MethodInfo.IsCommandOnly)
{
// This is why having a protocol logic on the connection is a bad idea
if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue)
{
if (request.UseBinary) {
commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "I")));
} else {
commandList.Add(new PipelineEntry(FormatFtpCommand("TYPE", "A")));
}
if (request.UsePassive) {
string passiveCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PASV" : "EPSV";
commandList.Add(new PipelineEntry(FormatFtpCommand(passiveCommand, null), PipelineEntryFlags.CreateDataConnection));
} else {
string portCommand = (ServerAddress.AddressFamily == AddressFamily.InterNetwork) ? "PORT" : "EPRT";
CreateFtpListenerSocket(request);
commandList.Add(new PipelineEntry(FormatFtpCommand(portCommand, GetPortCommandLine(request))));
}
if (request.CacheProtocol != null && request.CacheProtocol.ProtocolStatus == CacheValidationStatus.CombineCachedAndServerResponse)
{
// Combining partial cache with the reminder using "REST"
if (request.CacheProtocol.Validator.CacheEntry.StreamSize > 0)
commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.CacheProtocol.Validator.CacheEntry.StreamSize.ToString(CultureInfo.InvariantCulture))));
}
else if (request.ContentOffset > 0) {
// REST command must always be the last sent before the main file command is sent.
commandList.Add(new PipelineEntry(FormatFtpCommand("REST", request.ContentOffset.ToString(CultureInfo.InvariantCulture))));
}
}
else
{
// revalidating GetFileSize = "SIZE" GetDateTimeStamp = "MDTM"
commandList.Add(new PipelineEntry(FormatFtpCommand("SIZE", requestFilename)));
commandList.Add(new PipelineEntry(FormatFtpCommand("MDTM", requestFilename)));
}
}
//
// Suppress the data file if this is a revalidation request
//
if (request.CacheProtocol == null || request.CacheProtocol.ProtocolStatus != CacheValidationStatus.Continue)
{
PipelineEntryFlags flags = PipelineEntryFlags.UserCommand;
if (!request.MethodInfo.IsCommandOnly)
{
flags |= PipelineEntryFlags.GiveDataStream;
if (!request.UsePassive)
flags |= PipelineEntryFlags.CreateDataConnection;
}
if (request.MethodInfo.Operation == FtpOperation.Rename)
{
commandList.Add(new PipelineEntry(FormatFtpCommand("RNFR", requestFilename), flags));
commandList.Add(new PipelineEntry(FormatFtpCommand("RNTO", request.RenameTo), flags));
} else {
commandList.Add(new PipelineEntry(FormatFtpCommand(request.Method, requestFilename), flags));
}
if (!request.KeepAlive) {
commandList.Add(new PipelineEntry(FormatFtpCommand("QUIT", null)));
}
}
return (PipelineEntry []) commandList.ToArray(typeof(PipelineEntry));
}
private PipelineInstruction QueueOrCreateDataConection(PipelineEntry entry, ResponseDescription response, bool timeout, ref Stream stream, out bool isSocketReady)
{
isSocketReady = false;
if (m_DataHandshakeStarted)
{
isSocketReady = true;
return PipelineInstruction.Pause; //if we already started then this is re-entering into the callback where we proceed with the stream
}
m_DataHandshakeStarted = true;
// handle passive responses by parsing the port and later doing a Connect(...)
bool isPassive = false;
int port = -1;
if (entry.Command == "PASV\r\n" || entry.Command == "EPSV\r\n")
{
if (!response.PositiveCompletion)
{
m_AbortReason = SR.GetString(SR.net_ftp_server_failed_passive, response.Status);
return PipelineInstruction.Abort;
}
if (entry.Command == "PASV\r\n")
{
IPAddress serverReturnedAddress = null;
port = GetAddressAndPort(response.StatusDescription,
ref serverReturnedAddress);
if (!ServerAddress.Equals(serverReturnedAddress))
throw new WebException(SR.GetString(SR.net_ftp_passive_address_different));
} else {
port = GetPortV6(response.StatusDescription);
}
isPassive = true;
}
new SocketPermission(PermissionState.Unrestricted).Assert();
try {
if (isPassive)
{
GlobalLog.Assert(port != -1, "FtpControlStream#{0}|'port' not set.", ValidationHelper.HashString(this));
try {
m_DataSocket = CreateFtpDataSocket((FtpWebRequest)m_Request, Socket);
} catch (ObjectDisposedException) {
throw ExceptionHelper.RequestAbortedException;
}
m_PassiveEndPoint = new IPEndPoint(ServerAddress, port);
}
PipelineInstruction result;
if (m_PassiveEndPoint != null)
{
IPEndPoint passiveEndPoint = m_PassiveEndPoint;
m_PassiveEndPoint = null;
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Connect()");
if (m_Async)
{
m_DataSocket.BeginConnect(passiveEndPoint, m_ConnectCallbackDelegate, this);
result = PipelineInstruction.Pause;
}
else
{
m_DataSocket.Connect(passiveEndPoint);
result = PipelineInstruction.Advance; // for passive mode we end up going to the next command
}
}
else
{
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "starting Accept()");
if (m_Async)
{
m_DataSocket.BeginAccept(m_AcceptCallbackDelegate, this);
result = PipelineInstruction.Pause;
}
else
{
Socket listenSocket = m_DataSocket;
try {
m_DataSocket = m_DataSocket.Accept();
if (!ServerAddress.Equals(((IPEndPoint)m_DataSocket.RemoteEndPoint).Address))
{
m_DataSocket.Close();
throw new WebException(SR.GetString(SR.net_ftp_active_address_different), WebExceptionStatus.ProtocolError);
}
isSocketReady = true; // for active mode we end up creating a stream before advancing the pipeline
result = PipelineInstruction.Pause;
} finally {
listenSocket.Close();
}
}
}
return result;
} finally {
SocketPermission.RevertAssert();
}
}
//
// A door into protected CloseSocket() method
//
internal void Quit()
{
CloseSocket();
}
private enum GetPathOption {
Normal,
AssumeFilename,
AssumeNoFilename
}
///
/// Gets the path componet of the Uri
///
private static void GetPathAndFilename(GetPathOption pathOption,
Uri uri,
ref string path,
ref string filename,
ref bool isRoot)
{
string tempPath = uri.GetParts(
UriComponents.Path|UriComponents.KeepDelimiter,
UriFormat.Unescaped);
isRoot = false;
if (tempPath.StartsWith("//")) {
isRoot = true;
tempPath = tempPath.Substring(1, tempPath.Length-1);
}
int index = tempPath.LastIndexOf('/');
switch (pathOption) {
case GetPathOption.AssumeFilename:
if (index != -1 && index == tempPath.Length-1) {
// Remove last '/' and continue normal processing
tempPath = tempPath.Substring(0, tempPath.Length-1);
index = tempPath.LastIndexOf('/');
}
path = tempPath.Substring(0, index+1);
filename = tempPath.Substring(index+1, tempPath.Length-(index+1));
break;
case GetPathOption.AssumeNoFilename:
path = tempPath;
filename = "";
break;
case GetPathOption.Normal:
default:
path = tempPath.Substring(0, index+1);
filename = tempPath.Substring(index+1, tempPath.Length-(index+1));
break;
}
if (path.Length == 0)
path = "/";
}
//
///
/// Formats an IP address (contained in a UInt32) to a FTP style command string
///
private String FormatAddress(IPAddress address, int Port )
{
byte [] localAddressInBytes = address.GetAddressBytes();
// produces a string in FTP IPAddress/Port encoding (a1, a2, a3, a4, p1, p2), for sending as a parameter
// to the port command.
StringBuilder sb = new StringBuilder(32);
foreach (byte element in localAddressInBytes) {
sb.Append(element);
sb.Append(',');
}
sb.Append(Port / 256 );
sb.Append(',');
sb.Append(Port % 256 );
return sb.ToString();
}
///
/// Formats an IP address (v6) to a FTP style command string
/// Looks something in this form: |2|1080::8:800:200C:417A|5282|
/// |2|4567::0123:5678:0123:5678|0123|
///
private string FormatAddressV6(IPAddress address, int port) {
StringBuilder sb = new StringBuilder(43); // based on max size of IPv6 address + port + seperators
String addressString = address.ToString();
sb.Append("|2|");
sb.Append(addressString);
sb.Append('|');
sb.Append(port.ToString(NumberFormatInfo.InvariantInfo));
sb.Append('|');
return sb.ToString();
}
internal long ContentLength {
get {
return m_ContentLength;
}
}
internal DateTime LastModified {
get {
return m_LastModified;
}
}
internal Uri ResponseUri {
get {
return m_ResponseUri;
}
}
///
/// Returns the server message sent before user credentials are sent
///
internal string BannerMessage {
get {
return (m_BannerMessage != null) ? m_BannerMessage.ToString() : null;
}
}
///
/// Returns the server message sent after user credentials are sent
///
internal string WelcomeMessage {
get {
return (m_WelcomeMessage != null) ? m_WelcomeMessage.ToString() : null;
}
}
///
/// Returns the exit sent message on shutdown
///
internal string ExitMessage {
get {
return (m_ExitMessage != null) ? m_ExitMessage.ToString() : null;
}
}
///
/// Parses a response string for content length
///
private long GetContentLengthFrom213Response(string responseString) {
string [] parsedList = responseString.Split(new char [] {' '});
if (parsedList.Length < 2)
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
return Convert.ToInt64(parsedList[1], NumberFormatInfo.InvariantInfo);
}
///
/// Parses a response string for last modified time
///
private DateTime GetLastModifiedFrom213Response(string str) {
DateTime dateTime = m_LastModified;
string [] parsedList = str.Split(new char [] {' ', '.'});
if (parsedList.Length < 2) {
return dateTime;
}
string dateTimeLine = parsedList[1];
if (dateTimeLine.Length < 14) {
return dateTime;
}
int year = Convert.ToInt32(dateTimeLine.Substring(0, 4), NumberFormatInfo.InvariantInfo);
int month = Convert.ToInt16(dateTimeLine.Substring(4, 2), NumberFormatInfo.InvariantInfo);
int day = Convert.ToInt16(dateTimeLine.Substring(6, 2), NumberFormatInfo.InvariantInfo);
int hour = Convert.ToInt16(dateTimeLine.Substring(8, 2), NumberFormatInfo.InvariantInfo);
int minute = Convert.ToInt16(dateTimeLine.Substring(10, 2), NumberFormatInfo.InvariantInfo);
int second = Convert.ToInt16(dateTimeLine.Substring(12, 2), NumberFormatInfo.InvariantInfo);
int millisecond = 0;
if (parsedList.Length > 2) {
millisecond = Convert.ToInt16(parsedList[2], NumberFormatInfo.InvariantInfo);
}
try {
dateTime = new DateTime(year, month, day, hour, minute, second, millisecond);
dateTime = dateTime.ToLocalTime(); // must be handled in local time
} catch (ArgumentOutOfRangeException) {
} catch (ArgumentException) {
}
return dateTime;
}
///
/// Attempts to find the response Uri
/// Typical string looks like this, need to get trailing filename
/// "150 Opening BINARY mode data connection for FTP46.tmp."
///
private void TryUpdateResponseUri(string str, FtpWebRequest request)
{
Uri baseUri = request.RequestUri;
//
// Not sure what we are doing here but I guess the logic is IIS centric
//
int start = str.IndexOf("for ");
if (start == -1)
return;
start += 4;
int end = str.LastIndexOf('(');
if (end == -1)
end = str.Length;
if (end <= start)
return;
string filename = str.Substring(start, end-start);
filename = filename.TrimEnd(new char [] {' ', '.','\r','\n'});
// Do minimal escaping that we need to get a valid Uri
// when combined with the baseUri
string escapedFilename;
escapedFilename = filename.Replace("%", "%25");
escapedFilename = escapedFilename.Replace("#", "%23");
// help us out if the user forgot to add a slash to the directory name
string orginalPath = baseUri.AbsolutePath;
if (orginalPath.Length > 0 && orginalPath[orginalPath.Length-1] != '/') {
UriBuilder uriBuilder = new UriBuilder(baseUri);
uriBuilder.Path = orginalPath + "/";
baseUri = uriBuilder.Uri;
}
Uri newUri;
if (!Uri.TryCreate(baseUri, escapedFilename, out newUri))
{
throw new FormatException(SR.GetString(SR.net_ftp_invalid_response_filename, filename));
} else {
if (!baseUri.IsBaseOf(newUri) ||
baseUri.Segments.Length != newUri.Segments.Length-1)
{
throw new FormatException(SR.GetString(SR.net_ftp_invalid_response_filename, filename));
}
else
{
m_ResponseUri = newUri;
}
}
}
///
/// Parses a response string for content length
///
private void TryUpdateContentLength(string str)
{
int pos1 = str.LastIndexOf("(");
if (pos1 != -1)
{
int pos2 = str.IndexOf(" bytes).");
if (pos2 != -1 && pos2 > pos1)
{
pos1++;
long result;
if (Int64.TryParse (str.Substring(pos1, pos2-pos1),
NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite,
NumberFormatInfo.InvariantInfo, out result))
{
m_ContentLength = result;
}
}
}
}
///
/// Parses a response string for a an IP Address
///
/*
private string GetIPAddress(string str)
{
StringBuilder IPstr=new StringBuilder(32);
string Substr = null;
int pos1 = str.IndexOf("(")+1;
int pos2 = str.IndexOf(",");
for(int i =0; i<3;i++)
{
Substr = str.Substring(pos1,pos2-pos1)+".";
IPstr.Append(Substr);
pos1 = pos2+1;
pos2 = str.IndexOf(",",pos1);
}
Substr = str.Substring(pos1,pos2-pos1);
IPstr.Append(Substr);
return IPstr.ToString();
}
*/
///
/// Parses a response string for our login dir in " "
///
private string GetLoginDirectory(string str) {
int firstQuote = str.IndexOf('"');
int lastQuote = str.LastIndexOf('"');
if (firstQuote != -1 && lastQuote != -1 && firstQuote != lastQuote) {
return str.Substring(firstQuote+1, lastQuote-firstQuote-1);
} else {
return String.Empty;
}
}
///
/// Parses a response string for a port number
///
private int GetAddressAndPort(string responseString, ref IPAddress ipAddress)
{
int port = 0;
const int firstPortDigit = 5;
const int secondPortDigit = 6;
string [] parsedList = responseString.Split(new char [] {'(', ',', ')'});
if (secondPortDigit>=parsedList.Length) {
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
}
port = Convert.ToInt32(parsedList[firstPortDigit], NumberFormatInfo.InvariantInfo)*256;
port = port + Convert.ToInt32(parsedList[secondPortDigit], NumberFormatInfo.InvariantInfo);
Int64 address = 0;
try {
for (int i=4; i>0; i--) {
address = (address<<8) +
Convert.ToByte(parsedList[i], NumberFormatInfo.InvariantInfo);
}
}
catch {
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
}
ipAddress = new IPAddress(address);
return port;
}
///
/// Parses a response string for a port number
///
private int GetPortV6(string responseString)
{
int pos1 = responseString.LastIndexOf("(");
int pos2 = responseString.LastIndexOf(")");
if (pos1 == -1 || pos2 <= pos1)
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
// addressInfo will contain a string of format "||||"
string addressInfo = responseString.Substring(pos1+1, pos2-pos1-1);
// Although RFC2428 recommends using "|" as the delimiter,
// It allows ASCII characters in range 33-126 inclusive.
// We should consider allowing the full range.
string [] parsedList = addressInfo.Split(new char [] {'|'});
if (parsedList.Length < 4)
throw new FormatException(SR.GetString(SR.net_ftp_response_invalid_format, responseString));
return Convert.ToInt32(parsedList[3], NumberFormatInfo.InvariantInfo);
}
///
/// Creates the Listener socket
///
private void CreateFtpListenerSocket(FtpWebRequest request) {
// see \\index1\sdnt\inetcore\wininet\ftp
// gets an IPEndPoint for the local host for the data socket to bind to.
IPEndPoint epListener = new IPEndPoint(((IPEndPoint)Socket.LocalEndPoint).Address, 0);
try {
m_DataSocket = CreateFtpDataSocket(request, Socket);
} catch (ObjectDisposedException) {
throw ExceptionHelper.RequestAbortedException;
}
// SECURITY:
// Since we are doing WebRequest, we don't require SocketPermissions
// Consider V.Next: Change to declarative form (10x faster) but
// SocketPermission must be moved out of System.dll for this to work
new SocketPermission(PermissionState.Unrestricted).Assert();
try {
// binds the data socket to the local end point.
m_DataSocket.Bind(epListener);
m_DataSocket.Listen(1); // Put the dataSocket * & in Listen mode
} finally {
SocketPermission.RevertAssert();
}
}
///
/// Builds a command line to send to the server with proper port and IP address of client
///
private string GetPortCommandLine(FtpWebRequest request) {
try
{
// retrieves the IP address of the local endpoint
IPEndPoint localEP = (IPEndPoint) m_DataSocket.LocalEndPoint;
if (ServerAddress.AddressFamily == AddressFamily.InterNetwork) {
return FormatAddress(localEP.Address, localEP.Port);
} else if (ServerAddress.AddressFamily == AddressFamily.InterNetworkV6) {
return FormatAddressV6(localEP.Address, localEP.Port);
} else {
throw new InternalException();
}
}
catch(Exception e)
{
throw GenerateException(WebExceptionStatus.ProtocolError, e); // could not open data connection
}
catch
{
throw GenerateException(WebExceptionStatus.ProtocolError, new Exception(SR.GetString(SR.net_nonClsCompliantException))); // could not open data connection
}
}
///
/// Formats a simple FTP command + parameter in correct pre-wire format
///
private string FormatFtpCommand(string command, string parameter)
{
StringBuilder stringBuilder = new StringBuilder(command.Length + ((parameter != null) ? parameter.Length : 0) + 3 /*size of ' ' \r\n*/);
stringBuilder.Append(command);
if(!ValidationHelper.IsBlankString(parameter)) {
stringBuilder.Append(' ');
stringBuilder.Append(parameter);
}
stringBuilder.Append("\r\n");
return stringBuilder.ToString();
}
///
///
/// This will handle either connecting to a port or listening for one
///
///
protected Socket CreateFtpDataSocket(FtpWebRequest request, Socket templateSocket)
{
// Safe to be called under an Assert.
Socket socket = new Socket( templateSocket.AddressFamily, templateSocket.SocketType, templateSocket.ProtocolType );
return socket;
}
///
/// This function is called by the GeneralWebRequest superclass to determine whether a response is valid, and when it is complete.
/// It also gives the response description a
///
protected override bool CheckValid(ResponseDescription response, ref int validThrough, ref int completeLength) {
GlobalLog.Print("FtpControlStream#" + ValidationHelper.HashString(this) + "CheckValid(" + response.StatusBuffer.ToString() + ")" );
// If the response is less than 4 bytes long, it is too short to tell, so return true, valid so far.
if(response.StatusBuffer.Length < 4) {
return true;
}
string responseString = response.StatusBuffer.ToString();
// Otherwise, if there is no status code for this response yet, get one.
if(response.Status == ResponseDescription.NoStatus)
{
// If the response does not start with three digits, then it is not a valid response from an FTP server.
if(!(Char.IsDigit(responseString[0]) && Char.IsDigit(responseString[1]) && Char.IsDigit(responseString[2]) && (responseString[3] == ' ' || responseString[3] == '-'))) {
return false;
} else {
response.StatusCodeString = responseString.Substring(0, 3);
response.Status = Convert.ToInt16(response.StatusCodeString, NumberFormatInfo.InvariantInfo);
}
// IF a hyphen follows the status code on the first line of the response, then we have a multiline response coming.
if (responseString[3] == '-') {
response.Multiline = true;
}
}
// If a complete line of response has been received from the server, then see if the
// overall response is complete.
// If this was not a multiline response, then the response is complete at the end of the line.
// If this was a multiline response (indicated by three digits followed by a '-' in the first line,
// then we see if the last line received started with the same three digits followed by a space.
// If it did, then this is the sign of a complete multiline response.
// If the line contained three other digits followeed by the response, then this is a violation of the
// FTP protocol for multiline responses.
// All other cases indicate that the response is not yet complete.
int index = 0;
while((index = responseString.IndexOf("\r\n", validThrough)) != -1) // gets the end line.
{
int lineStart = validThrough;
validThrough = index + 2; // validThrough now marks the end of the line being examined.
if(!response.Multiline)
{
completeLength = validThrough;
return true;
} // same here
if(responseString.Length > lineStart + 4)
{
// if the first three characters of the the response line currently being examined
// match the status code, then if they are followed by a space, then we
// have reached the end of the reply.
if(responseString.Substring(lineStart, 3) == response.StatusCodeString)
{
if(responseString[lineStart + 3] == ' ')
{
completeLength = validThrough;
return true;
}
}
}
}
return true;
}
///
/// Determnines whether the stream we return is Writeable or Readable
///
private TriState IsFtpDataStreamWriteable() {
FtpWebRequest request = m_Request as FtpWebRequest;
if (request != null) {
if (request.MethodInfo.IsUpload) {
return TriState.True;
} else if (request.MethodInfo.IsDownload) {
return TriState.False;
}
}
return TriState.Unspecified;
}
} // class FtpControlStream
} // namespace System.Net
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- ExpressionCopier.cs
- remotingproxy.cs
- FormCollection.cs
- TreeViewCancelEvent.cs
- Soap12ProtocolImporter.cs
- CommandEventArgs.cs
- SafeThreadHandle.cs
- CodeRegionDirective.cs
- DictionaryEntry.cs
- HelpProvider.cs
- ClientOptions.cs
- ContainerParaClient.cs
- FixedHyperLink.cs
- VectorAnimationUsingKeyFrames.cs
- ValidatingReaderNodeData.cs
- HttpVersion.cs
- diagnosticsswitches.cs
- XmlStreamedByteStreamReader.cs
- DesignTimeResourceProviderFactoryAttribute.cs
- NumericUpDown.cs
- HashMembershipCondition.cs
- ContextMenuAutomationPeer.cs
- FloatMinMaxAggregationOperator.cs
- Privilege.cs
- RepeatButton.cs
- AuthorizationPolicyTypeElementCollection.cs
- CodeDirectionExpression.cs
- ExcludePathInfo.cs
- EntityDesignerUtils.cs
- ButtonBase.cs
- DrawTreeNodeEventArgs.cs
- Light.cs
- EditableRegion.cs
- SwitchLevelAttribute.cs
- UnauthorizedWebPart.cs
- Thickness.cs
- Registry.cs
- mansign.cs
- EntityDataSourceUtil.cs
- StrongNameKeyPair.cs
- RemoteWebConfigurationHostServer.cs
- AlphabeticalEnumConverter.cs
- _ConnectStream.cs
- HttpServerProtocol.cs
- TypedDataSetSchemaImporterExtension.cs
- TimeSpanStorage.cs
- FieldBuilder.cs
- PageCache.cs
- FlowDocumentScrollViewerAutomationPeer.cs
- ProcessHostServerConfig.cs
- UnsafeNativeMethods.cs
- PolicyManager.cs
- HttpModulesSection.cs
- Object.cs
- KoreanCalendar.cs
- DbProviderFactory.cs
- SQLString.cs
- UInt64Storage.cs
- ExpressionContext.cs
- BindingUtils.cs
- UnsafeNetInfoNativeMethods.cs
- StorageInfo.cs
- NamespaceMapping.cs
- returneventsaver.cs
- HttpStreamXmlDictionaryReader.cs
- ObjectSelectorEditor.cs
- SchemaImporterExtensionsSection.cs
- SafeArrayTypeMismatchException.cs
- NamespaceList.cs
- DrawingBrush.cs
- QueueProcessor.cs
- ArraySegment.cs
- ForceCopyBuildProvider.cs
- SerializationEventsCache.cs
- BitStream.cs
- EntityClassGenerator.cs
- updateconfighost.cs
- OutputCache.cs
- ContextQuery.cs
- WizardForm.cs
- ParseElement.cs
- TypeToken.cs
- EventListenerClientSide.cs
- GlyphingCache.cs
- BamlLocalizer.cs
- DocumentPageTextView.cs
- FloaterParagraph.cs
- ProfessionalColors.cs
- DataSourceHelper.cs
- ConnectionPointCookie.cs
- ComNativeDescriptor.cs
- COAUTHINFO.cs
- UrlMappingsModule.cs
- SoapElementAttribute.cs
- DbFunctionCommandTree.cs
- COM2EnumConverter.cs
- IntegerValidatorAttribute.cs
- Process.cs
- DiscoveryDocumentSerializer.cs
- FacetValues.cs