前回は、
locationオブジェクト
locationオブジェクトの基本
JavaScriptにおいてURLを取り扱う最も基本的な要素の1つが、
locationオブジェクトの代表的なプロパティおよびメソッドを下表に示します。
メソッド | 説明 |
---|---|
assign | 引数で指定されたURLへ移動する |
reload | ページをリロードする。引数でtrueが指定された場合は、 |
replace | 引数で指定されたURLへ移動するが、 |
プロパティ | 説明 |
---|---|
protocol | URLのプロトコル部分 |
host | URLのホスト部分。ポート番号があればそれも含む |
hostname | URLのホスト部分。ポート番号は含まない |
port | URLのポート番号部分。デフォルトポートの場合は、 |
pathname | URLのパス名部分 |
search | URLの?以降のクエリ文字列 |
hash | URLの#以降 |
href | 完全なURL全体 |
現在、
IE10以下などではlocation.
if( location.origin === undefined ){
location.origin = location.protocol + "//" + location.hostname + (location.port ? ":" + location.port : "");
}
console.log( location.origin ); // 現在のオリジンを表示
たとえば、
- protocol → http:
- host → example.
jp:8080 - hostname → example.
jp - port → 8080
- pathname → /list/
- search → ?file=news.
html - hash → #recent
- href → http://
example. jp:8080/ list/?file=news. html#recent - origin → http://
example. jp:8080
location.href以外に現在のドキュメントのURLを取得するためのインタフェース
現在のドキュメントの完全なURLを取得するためには、
- document.
URL - document.
documentURI (IE10、 11ではサポートされていない) - document.
URLUnencoded (IE7~IE11のみサポート)
document.
余談ですが、
こういった、
URLに認証情報が含まれている場合の注意点
document.
たとえば、user
”、pass
”
- location.
href → http:// example. jp/ (認証情報を含まないURL) - document.
documentURI → http:// user:pass@example. jp/ (ユーザー名およびパスワードを含んだURL)
「Basic認証の認証情報が含まれるURLへ攻撃者が被害者を誘導することで、
if( document.documentURI && location.href !== document.documntURI ){
// URLに認証情報が含まれるときは強制的に認証なしのURLをリロードする
location.href = location.href;
}
URLオブジェクト
たとえば、
URLとしては、
function isSameOrigin( url ){
if( /* urlの指すリソースが同一オリジンのものなら */ ){
return true;
}
return false;
}
相対URLの落とし穴
まず考えられるのは、
- 「先頭に英数字、
続けて 『:』 があればスキーマを持つので、 絶対URL」 - 「絶対URLでも、
先頭が現在のオリジンと同一であれば、 trueを返す」 - 「絶対URLでなければ相対URLであるので、
trueを返す」
のようなコードを書くことです。
// bad code
function isSameOrigin( url ){
var site = location.origin + "/";
if( url.match( /^[\w]+:/ ) ){
// urlは「http:」「https:」や「javascript:」などのスキーム名を持つ絶対URL
if( site === url.substr( 0, site.length ) ){
// 現在のドキュメントのオリジンと同一オリジンを持つ絶対URL
return true;
}
}else{
// スキームを持たないので相対URL
return true;
}
return false;
}
この方法は、
じつは、
では、
このように、
ブラウザの機能でURLを絶対URLに変換する
そこで、
現在の多くのブラウザでは、
var url = new URL( "http://example.jp/foo" );
console.log( url.href ); // "http://example.jp/foo"
console.log( url.protocol ); // "http:"
console.log( url.hostname ); // "example.jp"
相対URLを表す文字列を絶対URLに変換する場合には、
var url = new URL( "/foo", "http://example.jp/" );
console.log( url.href ); // "http://example.jp/foo"
url = new URL( "../foo", "http://example.jp/bar/baz.html" );
console.log( url.href ); // "http://example.jp/foo"
console.log( url.origin ); // "http://example.jp"
このように、
function isSameOrigin( url ){
var url = new URL( url, location.href );
if( url.origin === location.origin ){
return true;
}
return false;
}
IEへの対応
URLコンストラクタの代わりに<a>要素を用いる
Internet ExplorerではURLコンストラクタは使用できませんが、
<a>要素のhref属性に
elm.getAttribute( "href" )
一方、
以下のコードでは、
function getAbsoluteUrl( url ){
var elm;
elm = document.createElement( "a" );
elm.setAttribute( "href", url );
return elm.href;
}
hrefプロパティに相対URLを設定したときに、href以外の各プロパティが正しく取得できない問題を解決する
<a>要素は、
IEにて、
// bad code
var elm = document.createElement( "a" );
elm.setAttribute( "href", "http://example.jp/fo" );
console.log( elm.protocol ); // "http:"
console.log( elm.host ); // "example.jp"
console.log( elm.hostname ); // "example.jp"
想定したとおりに正しく動いているように見えるかもしれません。しかし、
// bad code
var elm = document.createElement( "a" );
elm.setAttribute( "href", "/foo" );
console.log( elm.protocol ); // IEでは空文字列になる
console.log( elm.host ); // IEでは空文字列になる
console.log( elm.hostname ); // IEでは空文字列になる
elm.setAttribute( "href", "//example.jp/foo" );
console.log( elm.protocol ); // IEでは空文字列になる
console.log( elm.host ); // "example.jp"
console.log( elm.hostname ); // "example.jp"
そこで、
function getAbsoluteUrl( url ){
var elm = document.createElement( "a" );
elm.setAttribute( "href", url );
return elm.href;
}
// 現在のドキュメントが http://example.jp/ だとする
var url = "/foo";
var elm = document.createElement( "a" );
elm.setAttribute( "href", getAbsoluteUrl( url ) ); // URLを絶対URLに変換しhref属性に設定
console.log( elm.href ); // "http://example.jp/foo"が表示される
console.log( elm.protocol ); // "http:"が表示される
console.log( elm.hostname ); // "example.jp" が表示される
相対URLを絶対URLに変換する際に、ベースとなるURLを明示的に指定できない問題を解決する
さらに、
これを解決するためには、
- IE11ではDOMParserを、
IE10ではdocument. implementation. createHTMLDocumentを用い、 現在表示されているコンテンツとは切り離されたDOMツリーを作成する - そのDOMツリーに<base>要素を追加し、
ベースURLを指定する - そのDOMツリー内で前述の<a>要素を使った方法を用いて相対URLを絶対URLに変換する
具体的なコードは以下のようになります。
var createUrl = (function() {
var doc;
try {
new URL("/", "http://example.jp/"); // URLコンストラクタが使えるかのテスト
} catch (e) {
try {
doc = (new DOMParser).parseFromString("<html><head></head><body></body></html>", "text/html");
} catch (e) {
doc = document.implementation.createHTMLDocument("");
}
}
return function (url, base) {
var d = document, baseElm, aElm, result;
if (doc === undefined) {
if (base === undefined) {
base = location.href;
}
result = new URL(url, base);
} else {
// URLコンストラクタが使えないため<a>要素を使ってURLを解決する
if (base !== undefined) {
// baseが指定されている場合は現在のdocumentとは切り離されたHTMLDocument内に<base>要素と<a>要素を設定
d = doc;
while (d.head.firstChild) d.head.removeChild(d.head.firstChild);
baseElm = d.createElement("base");
baseElm.setAttribute( "href", base );
d.head.appendChild( baseElm );
}
aElm = d.createElement("a");
aElm.setAttribute("href", url);
aElm.setAttribute("href", aElm.href);
//d.appendChild(aElm);
result = {
protocol: aElm.protocol,
host: aElm.host,
hostname: aElm.hostname,
port: aElm.port,
pathname: aElm.pathname,
search: aElm.search,
hash: aElm.hash,
href: aElm.href,
username: "",
password: "",
origin : aElm.origin || null
};
if (result.protocol === "http:" && result.port === "80") {
// httpかつデフォルトポートの場合はポート番号の"80"を削除
result.port = "";
result.host = result.host.replace( /:80$/, "" );
}else if (result.protocol === "https:" && result.port === "443") {
// httpsかつデフォルトポートの場合はポート番号の"443"を削除
result.port = "";
result.host = result.host.replace( /:443$/, "" );
}
if (result.protocol === "http:" || result.protocol === "https:") {
if (result.pathname && result.pathname.charAt(0) !== "/") {
// pathnameの先頭の"/"が付与されないバグへの対応
result.pathname = "/" + result.pathname;
}
if (!result.origin) {
result.origin = result.protocol + "//" + result.hostname + (result.port ? ":" + result.port : "");
}
}
}
if (result.username || result.password) {
// URLにBasic認証のユーザ名、パスワードが含まれている場合は例外を発生させる
throw new URIError( result.username || result.password );
}
return result;
};
})();
var url = createUrl( "/foo?bar", "http://example.jp/baz" );
console.log( url.href ); // "http://example.jp/foo?bar"
console.log( url.search ); // "?bar"
このcreateUrlという関数は、
また、
まとめ
今回は、
繰り返しになりますが、
また、
- location.
hrefにjavascript:alert("xss");やvbscript:MsgBox "xss"といったスクリプトのスキームを代入されることによりXSSが発生する - HTML内にlocation.
hrefやlocation. hashを表示する際に、 URL内に含まれる 「<」 「>」 などがエスケープされていないことが原因でXSSが発生する
このような脆弱性の具体例や対策の詳細についても、