6

本番環境で見られるエッジケースを試しています。クライアントがテキスト ファイルを生成し、それを FTP でサーバーに送信するビジネス モデルがあります。これらのファイルを取り込み、Java バックエンド (CentOS マシンで実行) で処理します。クライアントのほとんど (95% 以上) は、これらのファイルを必要な UTF-8 で生成することを知っています。ただし、CP1252 文字セットを使用して Windows マシン上でこれらのファイルを生成する頑固なクライアント (しかし大規模なアカウント) がいくつかあります。問題ありませんが、魔法のブードゥーを通じて任意の文字セットで入力を処理するように、サードパーティのライブラリ (ほとんどの「処理」作業を行うもの) を構成しました。

時折、名前に不正な UTF-8 文字 (CP1252) が含まれているファイルを目にすることがあります。私たちのソフトウェアが FTP サーバーからこれらのファイルを読み取ろうとすると、ファイル読み取りの通常の方法はチョークし、次のメッセージをスローしFileNotFoundExceptionます。

File f = getFileFromFTPServer();
FileReader fReader = new FileReader(f);

String line = fReader.readLine();
// ...etc.

例外は次のようになります。

java.io.FileNotFoundException: /path/to/file/some-text-blah?blah.xml (No such file or directory) at java.io.FileInputStream.open(Native Method) at 
java.io.FileInputStream.(FileInputStream.java:120) at java.io.FileReader.(FileReader.java:55) at com.myorg.backend.app.InputFileProcessor.run(InputFileProcessor.java:60) at 
java.lang.Thread.run(Thread.java:662)

つまり、ファイル自体に不正な文字が含まれているため、そもそもそれを読み取ることさえできないということです。可能であれば、ファイルの内容に関係なく、ソフトウェアはそれを正しく処理できるはずです。したがって、これは実際には、不正な UTF-8 文字を含むファイル名を読み取る際の問題です。

テスト ケースとして、非常に単純な Java "アプリ" を作成して、サーバーの 1 つにデプロイし、いくつかのことをテストしました (ソース コードを以下に示します)。次に、Windows マシンにログインし、テスト ファイルを作成して名前を付けましたtest£.txt。ファイル名の「test」の後の文字に注意してください。これは Alt-0163 です。これを私たちのサーバーに FTP 送信し、ls -ltrその親ディレクトリで実行したとき、それが としてリストされているのを見て驚きましたtest?.txt

先に進む前に、この問題をテスト/再現するために作成した Java "アプリ" を次に示します。

public Driver {
    public static void main(String[] args) {
        Driver d = new Driver();
        d.run(args[0]);     // I know this is bad, but its fine for our purposes here
    }

    private void run(String fileName) {
        InputStreamReader isr = null;
        BufferedReader buffReader = null;
        FileInputStream fis = null;
        String firstLineOfFile = "default";

        System.out.println("Processing " + fileName);

        try {
            System.out.println("Attempting UTF-8...");

            fis = new FileInputStream(fileName);
            isr = new InputStreamReader(fis, Charset.forName("UTF-8"));
            buffReader = new BufferedReader(isr);

            firstLineOfFile = buffReader.readLine();

            System.out.println("UTF-8 worked and first line of file is : " + firstLineOfFile);
        }
        catch(IOException io1) {
            // UTF-8 failed; try CP1252.
            try {
                System.out.println("UTF-8 failed. Attempting Windows-1252...(" + io1.getMessage() + ")");

                fis = new FileInputStream(fileName);
                // I've also tried variations "WINDOWS-1252", "Windows-1252", "CP1252", "Cp1252", "cp1252"
                isr = new InputStreamReader(fis, Charset.forName("windows-1252"));
                buffReader = new BufferedReader(isr);

                firstLineOfFile = buffReader.readLine();

                System.out.println("Windows-1252 worked and first line of file is : " + firstLineOfFile);
            }
            catch(IOException io2) {
                // Both UTF-8 and CP1252 failed...
                System.out.println("Both UTF-8 and Windows-1252 failed. Could not read file. (" + io2.getMessage() + ")");
            }
        }
    }
}

これをターミナル ( java -cp . com/Driver t*) から実行すると、次の出力が得られます。

Processing test�.txt
Attempting UTF-8...
UTF-8 failed. Attempting Windows-1252...(test�.txt (No such file or directory))
Both UTF-8 and Windows-1252 failed. Could not read file.(test�.txt (No such file or directory))

test�.txt?!?! 調査を行ったところ、「�」が Unicode 置換文字であることがわかりました\uFFFD。したがって、CentOS FTP サーバーが Alt-0163 ( ) の処理方法を認識していないため、 ( )に置き換えられていることが原因だと思います。しかし、なぜ...というファイルが表示されるのかわかりません。£\uFFFD�ls -ltrtest?.txt

いずれにせよ、解決策は、ファイル名にこの文字が含まれているかどうかを検索するロジックを追加し、見つかった場合はファイルの名前を別の名前に変更することであるようです (おそらく、文字列ごとreplaceAll("\uFFFD", "_")など)。システムが読み取って処理できること。

問題は、Java がファイル システム上のこのファイルを認識さえしないことです。CentOS はファイルがそこにある ( test?.txt) ことを認識していますが、そのファイルが Java に渡されると、Java はそれtest�.txtを何らかの理由でNo such file or directory...と解釈します。

Java にこのファイルを表示させて、実行できるようにするにはどうすればよいFile::renameTo(String)ですか? ここでの裏話で申し訳ありませんが、このシナリオではすべての詳細が重要であるため、関連性があると思います. 前もって感謝します!

4

2 に答える 2

6

テキストエンコーディングの素晴らしい世界へようこそ。問題にはいくつかのレベルがあり、それぞれを個別に分類する必要があります。

まず、ディスク上のファイル名は何ですか?有効なUTF-8エスケープシーケンスが含まれていますか、それとも他のものですか?

ここでの問題は、正しいファイル名が必要なことです。そうしないと、Windowsファイルシステムがファイルを見つけることができなくなります。その上、Windowsはファイル名の不正な文字をUnicodeに変換しようとする可能性がある\uFFFDため、何をしようとしても、ファイルをロードできません(\uFFFDディスク上にファイルがないため)。

どうしてそれができるのでしょうか?これは、マッピングが双方向ではないために発生します。Windowsがディスクからファイル名をロードすると、その名前に置き換えtest�.txtられtest\uFFFD.txt、その名前が付けられます。Windowsに開くように指示するtest\uFFFD.txtと、そのような名前のファイルがないため、ファイルを見つけることができません(のみありますtest�.txt)。ファイルの実際の名前を知る方法はありません。

ソリューション?dosプロンプトを開き、ファイルの名前をパターンに変更できますren test*.txt test.txt。パターンは単一のファイルにのみ一致するため、それは機能します。ただし、ファイルが見つからないため、たとえばWindowsエクスプローラーから同じことを行うことはできません。

次のステップ:FTP。FTPは人間向けのプロトコルであり、自動データ交換には適していません。FTPを取り除きます。それがあなたにいくらかかるかはわかりませんが、それは常に価値があります。SFTP、scp、またはFTAPIを使用します。

問題の原因の1つは、FTPがファイル名をASCIIとして転送することである可能性があります。FTPプロトコルではウムラウトは許可されていません...というか、FTPはウムラウトを期待していません。運が良ければ、FTPクライアントはファイルの転送を拒否しますが、最も単純にバグアウトします。しかし、それらが存在する場合、FTPはただ...何かをします。それが何であれ。?ここでの通常の効果は、名前にUnicodeが含まれるファイルは、UTF-8またはUnicodeが( )に置き換えられるため、2回エンコードされることです\u003f

または、Java FTPクライアントを使用new String( bytes )して、FTPファイル名から文字列を作成することもできます。これにより、システムのデフォルトのエンコーディングで貧弱なバイトがレイプされます。

ソリューション:

  1. 名前に不正な文字が含まれているファイルを拒否するか、これらの文字をファイルシステム/OSを混乱させないものに置き換えるFTPサーバーを使用してください。
  2. 奇妙な名前のファイルを適切に処理するファイルシステムを使用してください。これは通常、サーバー上のWindowsを取り除くことを意味します。
  3. ユーザーがアップロードできるのは1つのディレクトリのみであり、このディレクトリには1つのファイルしか含めることができないことを確認してください。そうすれば、小さなシェルスクリプトとパターンを使用して、読み取り可能な名前に名前を変更できます。
于 2012-08-24T13:24:02.890 に答える