Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

URLはどこまで変なことができるのか?

ドットが使える程度は本当に気軽なもので、もっと変なことができてしまうよという話

前提: URLの扱いはアプリによって異なる

実は URLをどう扱うかはアプリによって異なる。 例えばブラウザはかなり扱いが緩い(WHATWG準拠)。反対に、curlなどは比較的硬い(RFC準拠)。

  • RFC: 理想ベースで規定された仕様。 硬め。
  • WHATWG: ブラウザ等ですでに行われていた壊れた使い方を前提に規定された仕様。 緩め。

今回は(面白いから)WHATWGの話メインでいく。

変1: そもそもほぼなんでも受け入れる

URLのpath部分はかなり強力な正規化がかけられるため、基本的に 変なことをしようとしてもエンコードされて終わり になる。

https://example.com/こんにちは😀 (日本語や絵文字 → 単にエンコードされて終わり)
https://example.com/改
行
あ
り(改行を含んだパターン → エンコードされて終わり)
https://example.com/\uD800 (サロゲートペアの前半だけ → 単にエンコードされて終わり。というかそもそもUTF-8として入れられない)
https://example.com/\0null (NULL文字 → /%00null とエンコードされて終わり)

確認用のJS。これをdevtoolsとかで打つと確認できる↓

console.log(
  new URL('https://example.com/こんにちは😀').href,
  new URL(`https://example.com/改


り`).href,
  new URL('https://example.com/\uD800').href,
  new URL('https://example.com/\0null').href,
);

このように、ドットどころか改行だろうがNULL文字だろうが受け入れてしまう。懐が広すぎる。ガバガバ。

変2: バックスラッシュをスラッシュとして扱う

これさっき知ってなんでやねんって思ったんだけど、WHATWGではOK。当該仕様

つまり https:\\example.com\path\to\content は単に https://example.com/path/to/content と解釈される。

変3: http:example.comでもOK

実はスラッシュ2つは なくてもアクセスできる。 https:example.com でok。当該仕様

new URL('https:example.com').href == 'https://example.com/'

そも論: URLがvalidであるかとどう扱うかは別

そもそも、URLがvalidか どうかと サーバーがどのようにURLを扱うか別の問題。

例えばSPAを考えると分かりやすい。SPAの場合、どんなURLが来てもサーバーは同じ内容 を返し、フロントでルーティングする。

実際に変なサーバーアプリを作ってみよう。ディレクトリの区切り文字としてドットを使うアプリを書く。例えば /user.entries.post でアクセスするとサーバー内の /user/entries/post のファイルが返ってくる (なお、拡張子もスラッシュに変換されるので、実用性は皆無)。

const express = require('express');
const fs = require('fs/promises');
const path = require('path');

const app = express();

app.get('/*path', async (req, res) => {
  const filePath = path.join(__dirname, req.params.path[0].replaceAll('.', '/'));

  res.send(await fs.readFile(filePath, 'utf8'));
});
app.listen(3000);

反転した文字列で受け付けるサーバーも書いてみる (/elif/ot/htap/path/to/file)

app.get('/*path', async (req, res) => {
  const reversedPath = req.params.path.map(s => [...s].reverse().join('')).reverse().join('/');
  const filePath = path.join(__dirname, reversedPath);

  res.send(await fs.readFile(filePath, 'utf8'));
});
app.listen(3000);

こういう変なことだってやろうと思えばできるのだ。

まとめ: URLにドットを入れられる程度は序章に過ぎない

このように、実は URLって結構変な書き方をしても受け入れられてしまう し、サーバーがURLをどのように解釈するかは自由となっている。こうしてみると、ドットを入れられるなんてのは自明に思えてくる。