URLのpathに / を連続して入れたとき

## tl;dr

- リクエストURLのpathを`/foo///bar`のように設定した場合、そのレスポンスはサーバの実装によって異なり、面白い挙動をする実装もある
- 連続した`/`をpathに含んだURLもRFC 3986に違反しない

## URLとURI

URIは、URLとURNに分類される。
つまり、URLはURIの部分集合である。
URLを含む、URIの仕様は[RFC 3986](https://datatracker.ietf.org/doc/html/rfc3986)にある。

## URIの構成

RFC 3986の3章には、以下のURI全体の構成図がある。
この記事ではpathに注目している。

>      foo://example.com:8042/over/there?name=ferret#nose
>      \_/   \______________/\_________/ \_________/ \__/
>       |           |            |            |        |
>     scheme     authority       path        query   fragment

### URIのpath

pathについては3.3章に記述がある。

pathは、authorityの後の `/`から始まり、queryの先頭である`?`かfragment先頭である`#`がくる直前までの部分のことである。
queryもfragmentもそのURIに含まれていない場合は、URIの終端がpathの終端である。
また、pathは空であることもありえる。（例: https://example.com#hoge)

本稿に関係のある部分だけ、pathについてのABNFを抜き出すと、次のようになる。

```
path = path-abempty / 省略
path-abempty  = *( "/" segment )
segment = *pchar
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
```

pathは1つの`/`で開始し、続くsegmentには`/`は含まれていない。
segmentは空でもよいため`/////`のようなpathも仕様に反してはいない。

その一方で、Linuxのpath探索のように、`/path////to/file` を `path/to/file` と解釈するといった規定はない。そのため、URIにおいて`/` と `//` は別物である。
 
## 実際の挙動

いくつかのサイトに対して、path連続した `/` を含むリクエストを送って確認する。
正常なpathに対し、先頭と末尾に余分な `/`を追加する。

Firefoxの開発者ツールのネットワークの項目を利用して実験する。

### MDN Web Docs

| URL                                           | status code | location      | 
| --------------------------------------------- | ----------- | ------------- | 
| https://developer.mozilla.org/ja/docs/Web     | 200         |               | 
| https://developer.mozilla.org////ja/docs/Web  | 302         | /ja/docs/Web  | 
| https://developer.mozilla.org/ja/docs/Web//   | 301         | /ja/docs/Web_ | 
| https://developer.mozilla.org/ja/docs/Web/    | 302         | /ja/docs/Web  | 

### IETF Datatracker

※ RFCのページ

| URL                                             | status code | location | 
| ----------------------------------------------- | ----------- | -------- | 
| https://datatracker.ietf.org/doc/html/rfc3986   | 200         |          | 
| https://datatracker.ietf.org/doc/html/rfc3986/  | 200         |          | 
| https://datatracker.ietf.org/doc/html/rfc3986// | 404         |          | 
| https://datatracker.ietf.org//doc/html/rfc3986  | 404         |          | 

### Qiita

| URL                          | status code  | location                  | 
| ---------------------------- | ------------ | ------------------------- | 
| https://qiita.com/tags/url   | 200          |                           | 
| https://qiita.com/tags/url/  | 301          | http://qiita.com/tags/url | 
| https://qiita.com/tags/url// | 301          | http://qiita.com/tags/url | 
| https://qiita.com///tags/url | 200          |                           | 

### Google

| URL                                       | status code  | location | 
| ----------------------------------------- | ------------ | -------- | 
| https://www.google.com/search?q=rfc3986   | 200          |          | 
| https://www.google.com/search/?q=rfc3986  | 404          |          | 
| https://www.google.com/search//?q=rfc3986 | 404          |          | 
| https://www.google.com//search?q=rfc3986  | 200          |          | 

### GitHub

| URL                               | status code  | location | 
| --------------------------------- | ------------ | -------- | 
| https://github.com/github/docs    | 200          |          | 
| https://github.com/github/docs/   | 200          |          | 
| https://github.com/github/docs/// | 200          |          | 
| https://github.com////github/docs | 200          |          | 

## まとめ

はじめに、RFC 3986の3章を参照しつつ、URIのpathについて仕様を確認した。
Linuxのpath探索とは異なり，複数連続した`/` (例: `/////`) を1つの`/`とみなすと定まってはいなかった。

次に、現実のWebサービスのpathに対する処理を何件か調査した。
結果、全てStatus Code 200で同じコンテンツを返すサービス、1つでも余分に`/`があると404を返すサービス、301や302で正規のURLへリダイレクトさせるサービスがあった。また、先頭に`/`を挿入する場合と末尾に挿入する場合で挙動が異なるサービス多かった。

特徴的であったのがMDNで、pathに含まれていると`//`を`_` に置き換えてリダイレクトされる（リダイレクト先で404になる）。
多数の`/`を加えた場合、末尾から4つずつ (`////`を`__`に)置き換え、連続した`//`がpathからなくなるまで再帰的にリダイレクトし続ける。

今回はWebサービスを対象に実験をしたが、サーバアプリケーションを対象に実験をしてみると面白いかもしれないと思った。