41686d6564
41686d6564

Reputation: 19661

Download FTP file from a (specific) server works on .NET 4+, but doesn't work on .NET 2.0

Update (the reason of this issue):

Okay, I managed -with the help of @MitchelSellers's comment- to figure out where the problem exactly is, and I'm trying to find a solution for it.

So, I'm trying to find a way to make it work on .NET 2.0.


The original question:

Here's my code:

Private Sub DownloadTestFile()
    Dim filePath As String = "ftp://ftp.kohlandfrisch.com/testfile"
    Dim request As FtpWebRequest

    Dim buffer(1023) As Byte
    Dim bytesIn As Integer

    request = DirectCast(WebRequest.Create(filePath), FtpWebRequest)
    request.Method = WebRequestMethods.Ftp.DownloadFile
    request.Credentials = New NetworkCredential("username", "password")
    request.UseBinary = False
    request.UsePassive = True
    request.Proxy = Nothing

    Using stream As IO.Stream = request.GetResponse.GetResponseStream
        Using output = IO.File.Create(localFilePath)
            bytesIn = 1
            Do Until bytesIn < 1
                bytesIn = stream.Read(buffer, 0, 1024)
                If bytesIn > 0 Then output.Write(buffer, 0, bytesIn)
            Loop
        End Using
    End Using
End Sub

When running that code on .NET 4 or 4.x, it works perfectly fine. However when running it on .NET 2.0 (I have to use .NET 2.0), it throws an exception when calling request.GetResponse. Here's the exception message is:

The remote server returned an error: (501) Syntax error in parameters or arguments.

I figured out that there must be something wrong with the request sent from .NET 2.0, so I decided to capture requests and responses using Wireshark, and my assumption was correct, but I still don't understand what the problem exactly is since I'm using the same code on both .NET versions.

Wireshark results

.NET 4.5.2

Wireshark results <code>.NET 4.5.2</code>

.NET 2.0

Wireshark results <code>.NET 2.0</code>

Any ideas?

Side note: Although my code is in VB, any answer with C# code is welcomed.

Upvotes: 3

Views: 564

Answers (2)

Elmar
Elmar

Reputation: 65

Have you tried making the filePath an absolute path? i.e

Dim filePath As String = "ftp://ftp.kohlandfrisch.com/%2ftestfile"

%2f represents the / character.

This might be a workaround.

Upvotes: 0

Yahia
Yahia

Reputation: 70379

UPDATE according to comment:

.NET 2 does not honour that flag :-( It contains a method called BuildCommandsList() - part the source in .NET 2 looks like this:

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)); 
}

So basically the CWD is hardcoded in .NET 2. This leaves you with 2 undesirable "options": either make the FTP server RFC compliant (which it currently is not!) or user some other library/way to access it.

A very unusual way might be to call/automate the DOS-command ftp from your .NET application...

Left for reference:

MS has documented this change of behaviour between .NET 2 and .NET 4 here: https://support.microsoft.com/en-au/help/2134299/system.net.ftpwebrequest-class-behaves-differently-in-.net-framework-4-vs-.net-framework-3.5

I suspect that it is worth a try to remove the described flag in .NET 2.0 - something similar to this:

private static void SetMethodRequiresCWD()
{
        Type requestType = typeof(FtpWebRequest);
        FieldInfo methodInfoField = requestType.GetField("m_MethodInfo", BindingFlags.NonPublic | BindingFlags.Instance);
        Type methodInfoType = methodInfoField.FieldType;


        FieldInfo knownMethodsField = methodInfoType.GetField("KnownMethodInfo", BindingFlags.Static | BindingFlags.NonPublic);
        Array knownMethodsArray = (Array)knownMethodsField.GetValue(null);

        FieldInfo flagsField = methodInfoType.GetField("Flags", BindingFlags.NonPublic | BindingFlags.Instance);

        int MustChangeWorkingDirectoryToPath = 0x100;
        foreach (object knownMethod in knownMethodsArray)
        {
            int flags = (int)flagsField.GetValue(knownMethod);
            flags &= (~MustChangeWorkingDirectoryToPath);
            flagsField.SetValue(knownMethod, flags);
        }
}

Sorry - I can't try this right now so this more like a suggestion...

EDIT: Alternatively you could use some ftp library which behaves similar to what .NET 4 does.

Upvotes: 1

Related Questions