Link to home
Start Free TrialLog in
Avatar of Tom Beck
Tom BeckFlag for United States of America

asked on

VB.Net FTP login Class almost works, need help

I found this sample class file code online and it almost works. That is, it works when I step through the code in debug mode on my VS 2005. The output I get in debug mode looks like this:
USER ftpUser
220 Microsoft FTP Service
331 Password required for ftpUser.
PASS ftpPassword
230 User ftpUser logged in.
However, when I run it without debugging it fails and I get this:
USER ftpUser
220 Microsoft FTP Service
PASS ftpPassword
331 Password required for ftpUser.
It looks like the password is being sent before the 331 response.
Should I add code to check for the 331 response before sending the password or am I mistaken about the problem? How do I check for the 331?

Public Class ftpCreate
    Private ftpTcpClient As TcpClient
    Public ResponseStream As NetworkStream
    Public ReturnNameMessage As String
    Public ReturnPwdMessage As String

    Public Sub ftpLogin(ByVal strName As String, ByVal strPWD As String, ByVal strftpLogin As String)
        Try
            Dim strCommand As String
            Dim strReturnMessage As String
            Dim bteSendBytes() As Byte
            Dim bteReturnBytes() As Byte
            Dim intReturnByteLength As Integer
            Dim ftpTcpClient As TcpClient = New TcpClient(strftpLogin, 21)
            ResponseStream = ftpTcpClient.GetStream
            strCommand = "USER " + strName + vbCrLf
            bteSendBytes = Encoding.ASCII.GetBytes(strCommand)
            ResponseStream.Write(bteSendBytes, 0, bteSendBytes.Length)
            intReturnByteLength = ftpTcpClient.ReceiveBufferSize
            ReDim bteReturnBytes(intReturnByteLength)
            ResponseStream.Read(bteReturnBytes, 0, intReturnByteLength)
            strReturnMessage = Encoding.ASCII.GetString(bteReturnBytes) + "/ "
            ReturnNameMessage = strCommand + strReturnMessage
            strCommand = "PASS " + strPWD + vbCrLf
            Array.Clear(bteSendBytes, 0, bteSendBytes.Length)
            bteSendBytes = Encoding.ASCII.GetBytes(strCommand)
            ResponseStream.Write(bteSendBytes, 0, bteSendBytes.Length)
            intReturnByteLength = ftpTcpClient.ReceiveBufferSize
            ReDim bteReturnBytes(intReturnByteLength)
            ResponseStream.Read(bteReturnBytes, 0, intReturnByteLength)
            strReturnMessage = Encoding.ASCII.GetString(bteReturnBytes) + "/ "
            ReturnPwdMessage = strCommand + strReturnMessage + vbCrLf
        Catch ex As SocketException
            ReturnPwdMessage = ex.Message
        End Try
    End Sub

End Class

Open in new window

Avatar of Nasir Razzaq
Nasir Razzaq
Flag of United Kingdom of Great Britain and Northern Ireland image

Try adding a Thread.Sleep(500) call to add some delay before sending the password.
Avatar of Tom Beck

ASKER

CodeCruiser,
That works when the delay is added immediately after the USER is sent but before the response is read (didn't work further down than that in the process). My concern is this. I am writing a utility that will be offered to others. What if someone has a really slow connection. How much delay would be required to cover all possibilities? Instead, is there a way to check that the response on the USER request is complete with the 331 before sending the password?
ASKER CERTIFIED SOLUTION
Avatar of Nasir Razzaq
Nasir Razzaq
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
CodeCruiser,
The code below is my attempt to combine both of your suggestions. It adds a half second delay if the strReturnMessage does not contain 331 but limits the total delay to 10 seconds so it's not waiting forever. On my connection, it's adding one half second delay before sending the password. It works.
I am a novice programmer, so tell me if I don't have all bases covered.
Thanks for the help.
Public Class ftpCreate
    Private ftpTcpClient As TcpClient
    Public ResponseStream As NetworkStream
    Public ReturnNameMessage As String
    Public ReturnPwdMessage As String

    Public Sub ftpLogin(ByVal strName As String, ByVal strPWD As String, ByVal strftpLogin As String)
        Try
            Dim strCommand As String
            Dim strReturnMessage As String = ""
            Dim bteSendBytes() As Byte
            Dim bteReturnBytes() As Byte
            Dim intReturnByteLength As Integer
            Dim timeCheck As Integer = 0
            Dim ftpTcpClient As TcpClient = New TcpClient(strftpLogin, 21)
            ResponseStream = ftpTcpClient.GetStream
            strCommand = "USER " + strName + vbCrLf
            bteSendBytes = Encoding.ASCII.GetBytes(strCommand)
            ResponseStream.Write(bteSendBytes, 0, bteSendBytes.Length)
            intReturnByteLength = ftpTcpClient.ReceiveBufferSize
            ReDim bteReturnBytes(intReturnByteLength)
            Do Until strReturnMessage.Contains("331")
                ResponseStream.Read(bteReturnBytes, 0, intReturnByteLength)
                strReturnMessage = Encoding.ASCII.GetString(bteReturnBytes) + "/ "
                System.Threading.Thread.Sleep(500)
                timeCheck = timeCheck + 500
                If timeCheck > 10000 Then
                    ReturnNameMessage = "User " & strName & " not accepted, timed out."
                    Exit Try
                Else
                    ReturnNameMessage = strCommand + strReturnMessage
                End If
            Loop
            timeCheck = 0
            strCommand = "PASS " + strPWD + vbCrLf
            Array.Clear(bteSendBytes, 0, bteSendBytes.Length)
            bteSendBytes = Encoding.ASCII.GetBytes(strCommand)
            ResponseStream.Write(bteSendBytes, 0, bteSendBytes.Length)
            intReturnByteLength = ftpTcpClient.ReceiveBufferSize
            ReDim bteReturnBytes(intReturnByteLength)
            ResponseStream.Read(bteReturnBytes, 0, intReturnByteLength)
            strReturnMessage = Encoding.ASCII.GetString(bteReturnBytes) + "/ "
            ReturnPwdMessage = strCommand + strReturnMessage + vbCrLf
        Catch ex As SocketException
            ReturnPwdMessage = ex.Message
        End Try
    End Sub

End Class

Open in new window

Actually, this Do loop works better because it only adds the delay when necessary.
...
            Do Until strReturnMessage.Contains("331")
                If timeCheck > 10000 Then
                    ReturnNameMessage = "User " & strName & " not accepted, timed out."
                    Exit Try
                Else
                    ResponseStream.Read(bteReturnBytes, 0, intReturnByteLength)
                    strReturnMessage = Encoding.ASCII.GetString(bteReturnBytes) + "/ "
                    If Not strReturnMessage.Contains("331") Then
                        System.Threading.Thread.Sleep(500)
                        timeCheck = timeCheck + 500
                    End If
                    ReturnNameMessage = strCommand + strReturnMessage
                End If
            Loop
            ...

Open in new window

The approach looks good to me. You may be novice but the good thing is that you just needed a hint to write your own code. You may want to implement this logic for all of the commands that you send so that you wait for response before sending the next command.