VB.Net
C#
.NET
ftp

.NET Framework 4 の FtpWebRequest で RETR/STOR に失敗する

More than 1 year has passed since last update.

.NET Framework 4 の FtpWebRequest で RETR/STOR に失敗する

苦労するとは思っていなかった VB.NET を使った FTP 接続によるファイル送受信でハマったときの対処を残す。

症状

VB.NET で実装した FTP クライアントから FTP サーバに接続後、ディレクトリのファイル一覧を取得し (これは成功)、ファイルのダウンロードを実行するデモでエラー 501 Syntax error in parameters or arguments. を受信した。

FTP クライアント FFFTP で彼のサーバから同じファイルをダウンロードすると成功する。FFFTP のログによると CWD した後、ファイルを RETR している。
一方、私のコードはローカルパスを直接指定してダウンロードしようとする。.NET Framework 4 以降の FtpWebRequest では CWD (Change Working Direcotry) コマンドがなかったからだ (廃止されている)。

対処

次のサイトをソースに適用すると、ファイルの送受信に成功するようになった。

System.Net.FtpWebRequest class behaves differently in .Net Framework 4 vs .Net Framework 3.5

内容は、
RFC に準拠する FTP サーバはローカルパスを指定した取得方法でも動作するはずなので .NET Framework 4 以降は CWD してから RETR (Retrieve) したりはしない。
RFC に準拠しない FTP サーバと .NET Framework 4 クライアントで通信したい場合は次を試せ。実行前に1回呼べばよい。と読めた。

サイトは C# で書いていたが、VB.NET で実装する必要があったので以下のとおり読み替えた。

ただし、私が参照サイトの情報によって動作するようになる仕組みを完全に理解できているわけではないこと、したがって、動くこと、正しく動くことを保証できないこと、さらに正しく C# から読み替えできていることを保証しないことを了解し、使用者の責任において利用すること。

Private Shared Sub SetMethodRequiresCWD()
    Dim requestType = GetType(FtpWebRequest)

    Dim methodInfoField = requestType.GetField("m_MethodInfo", BindingFlags.NonPublic Or BindingFlags.Instance)
    Dim methodInfoType = methodInfoField.FieldType

    Dim knownMethodsField = methodInfoType.GetField("KnownMethodInfo", BindingFlags.Static Or BindingFlags.NonPublic)
    Dim knownMethodsArray = CType(knownMethodsField.GetValue(Nothing), Array)

    Dim flagsField = methodInfoType.GetField("Flags", BindingFlags.NonPublic Or BindingFlags.Instance)

    Dim MustChangeWorkingDirectoryToPath = &H100
    For Each knownMethod In knownMethodsArray
        Dim flags = CType(flagsField.GetValue(knownMethod), Integer)
        flags = flags Or MustChangeWorkingDirectoryToPath
        flagsField.SetValue(knownMethod, flags)
    Next
End Sub

確認した環境

OS: 日本語 Windows 10 Home 64ビット
VS: Visual Studio Community 2015
.NET Framework: 4.6.2