タイトルが長いけど、
input[type=file, webkitdirectory]
な要素にFile
を載せてform
要素やfetch({ body: new FormData() })
でサーバーに送ると- サーバー側で取れる
File
のname
に入ってるのは - クライアント側での
File
のname
ではなくwebkitRelativePath
という話。
このご時世にwebkit
なんてプレフィックスがついてるからお察しではあるけど、いわゆるSpecにもそれらしい記述は見つけられなかった。
File and Directory Entries API https://wicg.github.io/entries-api/
バグだ!って言いたいわけではなく、単に驚いたのでメモっておくだけ。
再現手順
Svelteのコード例。
<input
type="file"
multiple
webkitdirectory
on:input={(ev) => {
const formData = new FormData();
for (const file of ev.currentTarget.files) {
formData.append("files", file);
console.log("👀", file);
}
fetch("/api/files/upload", {
method: "POST",
body: formData,
});
}}
/>
ここでconsole.log(file)
してるところでは、
name
: ファイル名のみ(パス情報なし)webkitRelativePath
: 相対パスを含むファイル名
という風にちゃんと区別できてる。
が、それをサーバー側で取得してみると、
const data = await req.formData();
for (const file of data.get(files) ?? []) {
console.log("👀", file);
}
name
がname
ではなく、webkitRelativePath
のパス情報つきになってる。
もっというと、webkitRelativePath
はundefined
になってた。(Cloudflare Workers、もといworkerd
では)
やってるのはブラウザ
そもそもPOSTされてるデータがそうなってる。
# webkitdirectoryなし
Content-Disposition: form-data; name="files"; filename="0.json"
# webkitdirectoryあり
Content-Disposition: form-data; name="files"; filename="myfiles/0.json"
フィールドは1つしかないし、パースの手間はあるけど、パス情報からファイル名は取れるし上位互換とも言えなくは・・・?
まぁサーバー側でwebkitRelativePath
を生成したいモチベーションもないやろうし、納得といえば納得ではあるけど。
webkitdirectory
で拾ったファイルを送ってます!っていちいちAPIにシグナルするのもどうかと思うし、標準化って大変ね・・・。
2023/11/15: 追記
この記事を書いた時点では、SvelteKitはv1.27.0
だった。
ところが、追記時点で最新のv1.27.6
にしたところ、サーバー側で取得できる値がwebkitRelativePath
相当ではなく、name
になってしまった。(そのせいで手元のアプリが何もしてないのに壊れた状態に)
どこに原因があるのかは追ってない、諦めた・・。