3

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.

  • Obviously this server doesn't allow the command CWD (change working directory).
  • Now downloading a file using .NET 2.0 -for unknown reasons- it sends this command followed by / after logging in, to change the working directory to the current working directory!! (as shown below, and I also confirmed that using FileZilla with another server).
  • Also as shown below, that's not the case with the recent versions of .NET, which is why it works on .NET 4.x.

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.

  • Try setting `.UsePassive = False` and see if that work's for `2.0`... – Trevor Jan 25 '17 at 18:36
  • @Zaggler, Already tried that. Changing `.UsePassive`, `.UseBinary`, or removing `.Proxy = Nothing` doesn't fix the problem. – 41686d6564 stands w. Palestine Jan 25 '17 at 18:42
  • Try using %2f rather than a / in the file url. – Mitchel Sellers Jan 25 '17 at 18:46
  • @MitchelSellers, That would throw a `UriFormatException` (tried it). I think that `character (/) not allowed in object name` isn't referring to the file path, I think it refers to the request `CWD FTPCAS/` which is sent only from `.NET 2.0`! – 41686d6564 stands w. Palestine Jan 25 '17 at 18:54
  • @AhmedAbdelhameed that is why I was questioning it. Per the documentation, you can use %2f to make it an absolute path. Relevant info from the docs "he URI may be relative or absolute. If the URI is of the form "ftp://contoso.com/%2fpath" (%2f is an escaped '/'), then the URI is absolute, and the current directory is /path. If, however, the URI is of the form "ftp://contoso.com/path", first the .NET Framework logs into the FTP server (using the user name and password set by the Credentials property), then the current directory is set to /path." – Mitchel Sellers Jan 25 '17 at 19:04
  • @MitchelSellers, sorry I followed your previous comment literally (I used "%2f" not "/%2f"). Now when using "/%2f", the exception message is `(550) File unavailable..` not `(501) Syntax error..`. I do believe that the server redirects to another *current directory* after logging in. Can this lead to some kind of a problem in .NET 2.0? – 41686d6564 stands w. Palestine Jan 25 '17 at 19:23
  • 1
    It looks like it. I just can't find any specific documentation on why. It is the CWD call that is giving you issues – Mitchel Sellers Jan 25 '17 at 19:27
  • @MitchelSellers, actually that server has some security restrictions (e.g. it doesn't allow listing directories, changing directory,.. etc). Only allows downloading & uploading. Why do the requests of `.NET 2.0` include `CWD` while the requests of `.NET 4` don't?! – 41686d6564 stands w. Palestine Jan 25 '17 at 19:32
  • Honest question, but is this not somewhat academic? .NET 4.0 was released in April 2010. Customers have had ~7 years to upgrade. – ta.speot.is Feb 02 '17 at 09:30
  • @ta.speot.is, IKR, it's just a client who needed to integrate this with an existing old VS2005/.NET 2.0 application. – 41686d6564 stands w. Palestine Feb 02 '17 at 21:16

2 Answers2

1

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.

Yahia
  • 69,653
  • 9
  • 115
  • 144
  • Unfortunately, I can't use 3rd party libraries for this project. +1 for your suggestion. It's really a good idea. Sadly, it doesn't work :/. I believe the reason why it doesn't work is: For any number `n` where `n < 0x100`, `n &= ~0x100 = n`, and all numbers returned by the `foreach` loop were below 256 (0x100). Any ideas? – 41686d6564 stands w. Palestine Jan 28 '17 at 00:55
  • @AhmedAbdelhameed : the idea with this is to "unset" the flag so behaviour (returning < 0x0100 when n < 0x0100) is correct. if it does not work then it means that .NET 2 does not honor this flag. I suspect you are out of luck with .NET 2 - see my update regardings the reason. – Yahia Jan 28 '17 at 20:16
  • I understand that this is about unsetting the flag, but since each value of each flag doesn't get changed, there is nothing being unset then. Am I missing something here? Edit: maybe I wan't clear in my previous comment. What is happening is: If n < 0x100 then n & (~0x100) will always be equal to n. – 41686d6564 stands w. Palestine Jan 28 '17 at 20:20
  • 1
    @AhmedAbdelhameed it just means that the flag is not part of .NET2 - see my update above – Yahia Jan 28 '17 at 20:23
  • you are welcome - if you really can't use anything other than .NET2 you would have to automate/use the ftp DOS-command (which is possible thought nothing I would recommend). – Yahia Jan 28 '17 at 20:29
  • I already thought of that but was trying to find another way because I also wouldn't recommend doing so :). Thanks again. – 41686d6564 stands w. Palestine Jan 28 '17 at 20:31
0

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.

Elmar
  • 55
  • 3
  • 15
  • That has already been suggested by MitchelSellers in the comments above, and it does not fix the problem. – 41686d6564 stands w. Palestine Feb 02 '17 at 12:28
  • In that case you need to give more information in terms of the error. Use try catches in your code to get exception info such as the stacktrace. 550 errors can mean several things: file doesn't exist, permission issues, etc. Is the server running Apache? IIS? Using **%2f** was certainly the first step in solving this issue. – Elmar Feb 02 '17 at 14:58
  • It returns error:550 because the file is actually unavailable in the root directory. When using a relative path, the server changes the working directory to where the file does exist. – 41686d6564 stands w. Palestine Feb 02 '17 at 15:04