📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

 
(同じ利用者による、間の3版が非表示)
10行目: 10行目:
<br>
<br>
一般的に、FTPは2つの接続を使用する。
一般的に、FTPは2つの接続を使用する。
* 制御接続
* 制御接続 (TCPポート : 21番)
*: コマンドの送信
*: コマンドの送信
* データ接続
* データ接続 (TCPポート : 20番)
*: ファイル転送
*: ファイル転送
<br>
<br>
279行目: 279行目:
       // 複数のファイルを同時に受信
       // 複数のファイルを同時に受信
       await ftpDownload.DownloadMultipleFilesAsync(filesToDownload);
       await ftpDownload.DownloadMultipleFilesAsync(filesToDownload);
    }
}
</syntaxhighlight>
<br><br>
== FTPS : データの送信 ==
以下の例では、FTPS (SSL / TLS証明書) を使用して、ASCIIモード (テキストファイル) とバイナリモード (画像ファイル等) で複数のファイルを非同期に送信している。<br>
<br>
<u>※注意</u><br>
<u>実務では、適切なSSL / TLS証明書の検証を行うこと。</u><br>
<u>ファイアウォールやネットワーク設定が、FTPSトラフィック (通常は、990番ポート) を許可していることを確認する。</u><br>
<br>
<u>また、大量のファイルや大きなファイルを送信する場合は、タイムアウト設定やメモリ使用量に注意する。</u><br>
<br>
<syntaxhighlight lang="c#">
using System;
using System.IO;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
class AsyncFtpsFileUpload
{
    private string host;
    private string username;
    private string password;
    private int port;
    // FTPS接続に必要な情報
    public AsyncFtpsFileUpload(string host, string username, string password, int port = 990)
    {
      this.host    = host;
      this.username = username;
      this.password = password;
      this.port    = port;
    }
    // 単一のファイルを非同期でアップロード
    public async Task UploadFileAsync(string localFilePath, string remoteFilePath, bool useAscii)
    {
      try
      {
          // FTPS要求の作成
          FtpWebRequest request = (FtpWebRequest)WebRequest.Create($"ftps://{host}:{port}/{remoteFilePath}");
          request.Method = WebRequestMethods.Ftp.UploadFile;  // ファイルの送信
          request.Credentials = new NetworkCredential(username, password);
          request.UsePassive = true;      // パッシブモードを使用するかどうかの可否
          request.UseBinary = !useAscii;  // ASCIIモードとバイナリモードの切り替え
          request.KeepAlive = false;
          request.EnableSsl = true;  // SSLを有効化
          // SSL / TLS証明書の検証
          // 注意: 実務では適切に証明書を検証すること
          System.Net.ServicePointManager.ServerCertificateValidationCallback = ValidateServerCertificate;
          using (FileStream fileStream = File.OpenRead(localFilePath))
          using (Stream ftpStream = await request.GetRequestStreamAsync())
          {
            // ファイルは10[KB]のバッファを使用して読み取り、FTPストリームに書き込む
            byte[] buffer = new byte[10240];
            int read;
            while ((read = await fileStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await ftpStream.WriteAsync(buffer, 0, read);
            }
          }
          using (FtpWebResponse response = (FtpWebResponse)await request.GetResponseAsync())
          {
            Console.WriteLine($"ファイルの送信が完了: {localFilePath} -> {remoteFilePath}");
            Console.WriteLine($"ステータス: {response.StatusDescription}");
          }
      }
      catch (WebException ex)
      {
          FtpWebResponse response = (FtpWebResponse)ex.Response;
          if (response != null)
          {
            Console.WriteLine($"FTPS送信エラー: {response.StatusDescription}");
          }
          else
          {
            Console.WriteLine($"Web例外が発生: {ex.Message}");
          }
      }
      catch (Exception ex)
      {
          Console.WriteLine($"予期せぬエラーが発生: {ex.Message}");
      }
    }
    // 複数のファイルを同時に非同期でアップロードするメソッド
    public async Task UploadMultipleFilesAsync(List<(string localFilePath, string remoteFilePath, bool useAscii)> files)
    {
      List<Task> uploadTasks = new List<Task>();
      // 各ファイルのアップロードタスクを生成して、リストに追加
      foreach (var file in files)
      {
          uploadTasks.Add(UploadFileAsync(file.localFilePath, file.remoteFilePath, file.useAscii));
      }
      // 全てのアップロードタスクが完了するまで待機
      await Task.WhenAll(uploadTasks);
      Console.WriteLine("全てのファイルの送信が完了");
    }
    public static async Task Main()
    {
      // FTPS接続情報
      string host    = "<ホスト名またはIPアドレス  例: ftps.example.com>";
      string username = "<ユーザ名>";
      string password = "<パスワード>";
      int port        = 990;  // FTPSの標準ポート
      AsyncFtpsFileUpload ftpsUpload = new AsyncFtpsFileUpload(host, username, password, port);
      // 送信するファイル群
      List<(string localFilePath, string remoteFilePath, bool useAscii)> filesToUpload = new List<(string, string, bool)>
      {
          ("localTextFile1.txt", "remoteTextFile1.txt", true),    // テキストファイル(ASCIIモード)
          ("localTextFile2.txt", "remoteTextFile2.txt", true),    // テキストファイル(ASCIIモード)
          ("localImageFile1.jpg", "remoteImageFile1.jpg", false),  // 画像ファイル(バイナリモード)
          ("localImageFile2.jpg", "remoteImageFile2.jpg", false)  // 画像ファイル(バイナリモード)
      };
      // 複数のファイルを同時に送信
      await ftpsUpload.UploadMultipleFilesAsync(filesToUpload);
    }
}
</syntaxhighlight>
<br>
<syntaxhighlight lang="c#">
// ※注意
// 実務では、使用環境に応じて、さらに厳密な検証ロジックを追加することを推奨する。
// また、可能な限り信頼された認証局によって発行された証明書を使用することを推奨する。
//
// 信頼された証明書のリストは定期的に更新して、不要になった証明書は速やかに削除すること。
using System;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
public class SslCertificateValidator
{
    // 信頼された証明書のサムプリント
    // 実際の環境に合わせて更新すること
    private static readonly string[] TrustedCertificates = new string[]
    {
      "A1B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0",
      "B2C3D4E5F6G7H8I9J0K1L2M3N4O5P6Q7R8S9T0U1"
    };
    public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
      if (sslPolicyErrors == SslPolicyErrors.None)
      {  // 証明書チェーンとポリシーエラーが無い場合
          LogMessage("情報", "証明書の検証に成功");
          return true;
      }
      LogMessage("警告", $"証明書エラー: {sslPolicyErrors}");
      if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateChainErrors))
      {
          return HandleChainErrors(chain);
      }
      if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNameMismatch))
      {
          LogMessage("エラー", "証明書の名前が不一致");
          return false;
      }
      if (sslPolicyErrors.HasFlag(SslPolicyErrors.RemoteCertificateNotAvailable))
      {
          LogMessage("エラー", "リモート証明書が利用不可");
          return false;
      }
      // その他のエラーの場合
      return false;
    }
    private static bool HandleChainErrors(X509Chain chain)
    {
      foreach (X509ChainStatus status in chain.ChainStatus)
      {
          if (status.Status == X509ChainStatusFlags.UntrustedRoot)
          {
            X509Certificate2 rootCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
            if (ValidateSelfSignedCertificate(rootCert))
            {
                LogMessage("情報", "信頼されたルート証明書を検証しました。");
                return true;
            }
          }
          LogMessage("エラー", $"証明書チェーンエラー: {status.StatusInformation}");
      }
      return false;
    }
    private static bool ValidateSelfSignedCertificate(X509Certificate certificate)
    {
      string certHash = certificate.GetCertHashString();
      if (TrustedCertificates.Contains(certHash, StringComparer.OrdinalIgnoreCase))
      {
          LogMessage("情報", "信頼された自己署名証明書を検証しました。");
          return true;
      }
      LogMessage("エラー", $"未知の自己署名証明書です。サムプリント: {certHash}");
      return false;
    }
    private static void LogMessage(string level, string message)
    {
      string logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] {message}";
      Console.WriteLine(logMessage);
      // ファイルやデータベースへのログ記録処理等を追加
     }
     }
  }
  }