オリジンとサイトの違い
同じURLのパス、サブドメイン、ポート、方式を一つずつ変えて比較します。次に公開サフィックスの資料を使い、co.ukやgithub.ioを含む名前で、末尾二つを取るだけでは判定できないことを確認します。
説明
ブラウザが使うオリジンとサイトは、URLをまとめる基準が異なります。オリジンは方式・ホスト・ポートを基準にし、スキームを考慮するサイトは方式と登録可能ドメインを基準にします。
同じURLのパス、サブドメイン、ポート、方式を一つずつ変えて比較します。次に公開サフィックスの資料を使い、co.ukやgithub.ioを含む名前で、末尾二つを取るだけでは判定できないことを確認します。
1基準となるURLとオリジン
httpsの既定ポート443を明示したURLを解析します。
var base = new URL基準URLを解析("https://shop.example.com:443/items?q=1#detail"); ({href:base.href, origin:base.originオリジン, hostname:base.hostname, port:base.portホストと明示ポートの表現})既定ポート443は正規化されたURLでは省略され、portは空文字列です。originはhttps://shop.example.comで、パス・クエリ・フラグメントは含みません。
2変更する部分ごとのオリジン
パス、サブドメイン、ポート、方式の異なるURLを用意して比較します。
var urls =一部分ずつ異なるURL ["https://shop.example.com/other","https://api.example.com/","https://shop.example.com:8443/","http://shop.example.com/"]; urls.map(value => { var u = new URL(value); return [u.href,u.origin,u.origin === base.origin基準と同じオリジンか比較]; })パスだけが違う最初のURLは同じオリジンです。apiという別ホスト、8443という別ポート、httpという別方式は、それぞれ異なるオリジンになります。
3公開サフィックスと登録可能な範囲
今回使う資料の抜粋はcom、co.uk、github.ioです。比較対象のURLで使う、この三つの規則を用意します。
var suffixes = ["github.io","co.uk","com"]この演習で使う既知の公開サフィックス; suffixes登録可能ドメインは通常、公開サフィックスに一つ左のラベルを加えた部分です。github.ioのようなホスティング用の境界も資料に含まれます。完全なPublic Suffix Listにはワイルドカードや例外もあり、この抜粋だけを一般用途の判定へ使うものではありません。
4このURL一覧のサイトを求める
三つの規則に一致するURLだけを受け付ける関数を作ります。該当しないホストや、サフィックス自体だけの名前は対象外として拒否します。
function site(value)この演習のURLを判定する関数 {
var u = new URL(value);
var suffix = suffixes.find対応する既知のサフィックス(s => u.hostname.endsWith("." + s));
if (!suffix) throw new Error("hostname outside lesson rules");
var count = suffix.split(".").length + 1左へ一つ広げたラベル数;
return u.protocol + "//"方式も含める + u.hostname.split(".").slice(-count).join(".");
}この限定した一覧では、方式と登録可能ドメインを組にしてサイトを表します。URL APIのoriginのように、単にサイトを返す標準のURLプロパティがあるわけではありません。
5同じURL一覧をサイトで比較する
先ほどの四つのURLを、オリジンとサイトの両方で比較します。
urls.map(value => [value, new URL(value).origin === base.originオリジンの一致, site(value)このURLのサイト, site(value) === site(base.href)サイトの一致])httpsのままならapiサブドメインやポート8443も、サイトはhttps://example.comで一致します。httpへ変えるとサイトも異なります。同じサイトだから同じオリジンの権限になる、という意味ではありません。
6co.ukの登録可能ドメイン
co.ukを使う二つの例で、末尾二ラベルを取るだけの場合と比較します。
["https://shop.example.co.uk/","https://shop.other.co.uk/"].map(value => [new URL(value).hostname.split(".").slice(-2).join(".")末尾二ラベルだけの誤った単純化,site(value)既知のサフィックスに基づく結果])末尾二ラベルだけなら両方co.ukですが、登録可能ドメインはexample.co.ukとother.co.ukです。サイトも別になります。ドメインの境界は単純なドット数では決まりません。
7同じホスティング基盤の別利用者
github.ioの別利用者と、同じ利用者配下のサブドメインを比較します。
["https://alice.github.io/","https://bob.github.io/","https://docs.alice.github.io/"].map(value => [new URL(value).originホスト単位のオリジン,site(value)公開サフィックスに基づくサイト])alice.github.ioとbob.github.ioは別サイトです。docs.alice.github.ioはalice.github.ioと別オリジンですが、サイトは同じです。共通のサービス名github.ioだけを見て一つのサイトと判定すると、利用者間の境界を見落とします。
8パスでオリジンは分離されない
同じホストの別パスを二つ比較します。
var a = new URL("https://shop.example.com/user-a/"); var b = new URL("https://shop.example.com/user-b/"); ({sameOrigin:a.origin === b.originパスが違う二つのオリジン, sameSite:site(a.href) === site(b.href)サイトの比較})どちらもTrueです。URLのパスを利用者ごとに分けても、それだけでブラウザのオリジン境界を作ることにはなりません。サーバー側のアクセス制御など、別の仕組みとして設計します。
9判定の基準を一覧にする
基準URLについて、オリジンとサイトの材料を並べます。
({origin:{scheme:base.protocol,host:base.hostname,effectivePort:base.port || "443"httpsの実効ポートとホスト}, site:{scheme:base.protocol,registrableDomain:"example.com"サイトで用いるドメイン}})オリジンにはホスト全体とポートが入り、サイトには登録可能ドメインが入ります。ここで確認したのは境界の定義であり、特定のCookieが送信されるか、別オリジンの応答を読めるかは、それぞれの追加条件も含めて判断します。
まとめ
オリジンは方式・ホスト・ポートで判定し、パスでは分かれません。
スキームを考慮するサイトは方式と登録可能ドメインでまとめるため、別オリジンでも同じサイトになる場合があります。
登録可能ドメインの判定には公開サフィックスの規則が必要です。単純に末尾二ラベルを取る方法では不十分です。
操作履歴と解説を振り返れます。
JavaScript / URL API
var base = new URL("https://shop.example.com:443/items?q=1#detail"); ({href:base.href, origin:base.origin, hostname:base.hostname, port:base.port}){
href: 'https://shop.example.com/items?q=1#detail',
origin: 'https://shop.example.com',
hostname: 'shop.example.com',
port: ''
}var urls = ["https://shop.example.com/other","https://api.example.com/","https://shop.example.com:8443/","http://shop.example.com/"]; urls.map(value => { var u = new URL(value); return [u.href,u.origin,u.origin === base.origin]; })[ [ 'https://shop.example.com/other', 'https://shop.example.com', true ], [ 'https://api.example.com/', 'https://api.example.com', false ], [ 'https://shop.example.com:8443/', 'https://shop.example.com:8443', false ], [ 'http://shop.example.com/', 'http://shop.example.com', false ] ]
var suffixes = ["github.io","co.uk","com"]; suffixes[ 'github.io', 'co.uk', 'com' ]
function site(value) {
var u = new URL(value);
var suffix = suffixes.find(s => u.hostname.endsWith("." + s));
if (!suffix) throw new Error("hostname outside lesson rules");
var count = suffix.split(".").length + 1;
return u.protocol + "//" + u.hostname.split(".").slice(-count).join(".");
}undefined
urls.map(value => [value, new URL(value).origin === base.origin, site(value), site(value) === site(base.href)])[ [ 'https://shop.example.com/other', true, 'https://example.com', true ], [ 'https://api.example.com/', false, 'https://example.com', true ], [ 'https://shop.example.com:8443/', false, 'https://example.com', true ], [ 'http://shop.example.com/', false, 'http://example.com', false ] ]
["https://shop.example.co.uk/","https://shop.other.co.uk/"].map(value => [new URL(value).hostname.split(".").slice(-2).join("."),site(value)])[ [ 'co.uk', 'https://example.co.uk' ], [ 'co.uk', 'https://other.co.uk' ] ]
["https://alice.github.io/","https://bob.github.io/","https://docs.alice.github.io/"].map(value => [new URL(value).origin,site(value)])[ [ 'https://alice.github.io', 'https://alice.github.io' ], [ 'https://bob.github.io', 'https://bob.github.io' ], [ 'https://docs.alice.github.io', 'https://alice.github.io' ] ]
var a = new URL("https://shop.example.com/user-a/"); var b = new URL("https://shop.example.com/user-b/"); ({sameOrigin:a.origin === b.origin, sameSite:site(a.href) === site(b.href)}){ sameOrigin: true, sameSite: true }({origin:{scheme:base.protocol,host:base.hostname,effectivePort:base.port || "443"}, site:{scheme:base.protocol,registrableDomain:"example.com"}}){
origin: { scheme: 'https:', host: 'shop.example.com', effectivePort: '443' },
site: { scheme: 'https:', registrableDomain: 'example.com' }
}演習完了