kagamihogeの日記

kagamihogeの日記です。

Commons FileUpload の getFileName にやられた

またしてもイマサラ感漂うフルクサイ話題。

「ファイルアップロードするとファイル名が小文字になっちゃうんだけど」という報告があがってきたので調査開始。

the filename case bug is the only change since the FileUpload 1.1 release,
Nabble - Commons - User - lowercase filenames

まぁバグなんで新しいバージョン使ってくれ、ってことらしい。ただ、1.1 んときだけオカシイ、ってとこがちょっと気になったのでソース追ってみることに。

まずは 1.0 のコード。


protected String getFileName(Map /* String, String */ headers)
{
String fileName = null;
String cd = getHeader(headers, CONTENT_DISPOSITION);
if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))
{
int start = cd.indexOf("filename=\"");
int end = cd.indexOf('"', start + 10);
if (start != -1 && end != -1)
{
fileName = cd.substring(start + 10, end).trim();
}
}
return fileName;
}

ヘッダーから先頭が Content-Disposition: form-data か Content-Disposition: attachment になってる文字列を探して、その文字列中から filename を抽出している。

これはまぁいい。

次は 1.1 のコード。


protected String getFileName(Map /* String, String */ headers) {
String fileName = null;
String cd = getHeader(headers, CONTENT_DISPOSITION);
if (cd != null) {
cd = cd.toLowerCase();
if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT)) {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// Parameter parser can handle null input
Map params = parser.parse(cd, ';');
if (params.containsKey("filename")) {
fileName = (String) params.get("filename");
if (fileName != null) {
fileName = fileName.trim();
} else {
// Even if there is no value, the parameter is present,
// so we return an empty file name rather than no file
// name.
fileName = "";
}
}
}
}
return fileName;
}

なんか色んなとこが変わってるけど、基本的にはパラメータの解析を ParameterParser にやらせるようになったのが一番の変更点っぽい。赤色の意味は後述。

とりあえず 1.2 のコード。


protected String getFileName(Map /* String, String */ headers) {
String fileName = null;
String cd = getHeader(headers, CONTENT_DISPOSITION);
if (cd != null) {
String cdl = cd.toLowerCase();
if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// Parameter parser can handle null input
Map params = parser.parse(cd, ';');
if (params.containsKey("filename")) {
fileName = (String) params.get("filename");
if (fileName != null) {
fileName = fileName.trim();
} else {
// Even if there is no value, the parameter is present,
// so we return an empty file name rather than no file
// name.
fileName = "";
}
}
}
}
return fileName;
}

あんまり変わってないような…?気がするが、キモは両者とも上から 5 行目の赤色をつけた部分。


1.1 cd = cd.toLowerCase();
1.2 String cdl = cd.toLowerCase();

このおかげで cd の中身はぜんぶ小文字になってしまう。たとえば下のような文字列がきたとき


Content-Disposition: form-data; name="formFile[0]"; filename="C:\HOGE.XLS"


content-disposition: form-data; name="formfile[0]"; filename="c:\hoge.xls"

となる。このため、ここから filename を抽出しようとするとファイル名は小文字になってしまう。struts は、パラメータのキー名をぜんぶ小文字で管理するようにしてる。で、form-data がどっから始まるかを調べるために、文字列を一旦すべて小文字に変換している。filename の抽出自体は、小文字変換前の文字列からやらなきゃいけないのに、小文字変換後の文字列を対象にしてるもんだからファイル名は小文字になる、といったところ。


なんつーか。横着して変数使いまわししたのか、単に凡ミスなのか…何なんだろう。