ローカル(file:///)上で外部ファイル読み込みのセキュリティ制約を回避するいくつかの方法


Aさん「AjaxでCSVファイルを読み込んで動かすJavaScriptのプログラムを作りました」

Bさん「HTMLファイルをPC上で直接開いても動作しますか？」

上記のような場合、ブラウザのセキュリティ制約によりファイルの読み込みがブロックされてしまい、うまく動かすことができません。

この文書では、上記のような場合にどういう方法を用いればセキュリティ制約を回避できるかの例を紹介します。

※基本的な対象ブラウザはIE11と2016/12時点のFirefox, Chrome, Edgeです。最後の方の方法は前提条件を満たさない方法になってしまっていますが、参考として記載しています。

## 読み込む外部ファイルをHTMLと同フォルダか同フォルダの下層に配置(Firefox) + Ajaxに Microsoft.XMLHTTP を使用する (IE)

Firefoxでは、外部ファイルをHTMLと同フォルダか同フォルダの下層に配置しておけばAjax (XMLHttpRequest) でファイルを読み込むことができます。([Same-origin policy for file: URIs | MDN](https://developer.mozilla.org/ja/docs/Same-origin_policy_for_file:_URIs))

IEでは、AjaxにActiveXObject("Microsoft.XMLHTTP") (以下Microsoft.XMLHTTP) を使うとセキュリティ制約を回避できます。

```html
<!DOCTYPE html>
<html lang="ja"><head><meta charset="UTF-8"></head>
<body>
<script>
var xhr = null; // 使える場合はMicrosoft.XMLHTTP, 使えない場合はXMLHttpRequest
try { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { xhr = new XMLHttpRequest(); }

var path = "test.csv"; // 読み込む外部ファイル
xhr.open("GET", path, true);
xhr.onreadystatechange = function(){
    if(xhr.readyState === 4) {
        var data = xhr.responseText; // 外部ファイルの内容
        console.log(data);
    }
};
xhr.send();
</script>
</body>
</html>
```

もし jQuery 1系 (1.9.1など) を使用している場合は、ローカル(file:///)かつIE10以下であれば $.ajax() などで自動的に Microsoft.XMLHTTP が使われます。

IE11の場合は、Ajaxに使用するオブジェクトをjQuery.ajaxSetup()で調整することで同じような対応ができます。
(標準状態では、IE11では Microsoft.XMLHTTP が使用されません)

```html
<!DOCTYPE html>
<html lang="ja"><head><meta charset="UTF-8"></head>
<body>
<script src="https://code.jquery.com/jquery-1.9.1.js"></script>
<script>
$.ajaxSetup({
    xhr: function() { // 使える場合はMicrosoft.XMLHTTP, 使えない場合はXMLHttpRequest
        try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch (e) { return new XMLHttpRequest(); }
    }
});
var path = "test.csv"; // 外部ファイル
$.ajax(path, {
    complete: function(e){
        var data = e.responseText; // 外部ファイルの内容
        console.log(data);
    }
});
</script>
</body>
</html>
```

※ jQuery 3系の場合は上記方法は使えません。(jQuery 3.1.1にはAjaxの処理中に使用中のAjaxオブジェクト(xhr).onloadにコールバック関数をセットするコードがあり、Microsoft.XMLHTTPにonloadをセットするとエラーになる)

※ Microsoft.XMLHTTP は MSXML2.XMLHTTPでもよいと思いますが、この文書では jQuery 1.9.1に合わせて Microsoft.XMLHTTP を使用しています。

## 起動オプション --allow-file-access-from-files を使用する (Chrome)

Chromeには、ローカルファイルのアクセスを許可する起動オプション「--allow-file-access-from-files」があります。

Chromeの起動オプションを設定してもよい場合は、この方法を使うと元のプログラムのコードを変えずに対応することができます。

(人のPCで動かすプログラムの場合はできるだけ避けたほうがよいです)

参考: [【小ネタ】Chromeのローカルセキュリティポリシーの回避 ｜ Developers.IO](http://dev.classmethod.jp/etc/chrome-localfile-security/)

## &lt;object&gt; や &lt;iframe&gt; で外部ファイルを読み込んで内容を取得する (IE, Firefox, Edge)

Edgeには Microsoft.XMLHTTP を生成するための ActiveXObjectがなく、XMLHttpRequestでの読み込みも制限されてしまいますが、&lt;object&gt; や &lt;iframe&gt; 経由だと外部ファイルの読み込みと取得ができるようでした。

```html
<!DOCTYPE html>
<html lang="ja"><head><meta charset="UTF-8"><title></title></head>
<body>
<!-- 外部ファイル (.csvだと保存ダイアログが表示されてしまうため.txtにしています。display:noneは外部CSSなどに移してもよいです) -->
<object data="test.txt" type="text/plain" id="obj" style="display:none"></object>
<script>
    var obj = document.getElementById("obj");
    obj.onload = function(){
        var data = obj.contentDocument.documentElement.textContent; // 外部ファイルの内容
        console.log(data);
    }
</script>
</body>
</html>
```

IE, Firefoxでも上記で読み込み可能ですが、Chromeは前述の起動オプションを付けない限りエラーになります。

## 読み込む外部ファイルのみWebサーバに配置する (全ブラウザ対応)

1. HTML等はローカル配置のままで、Ajax読み込みする外部ファイルだけ外部のWebサーバに上げておく
2. 読み込みたい外部ファイルのレスポンスヘッダに「 Access-control-allow-origin: * 」を追加して、Ajaxのアクセス制限(Same-Origin Policy)を外す

とすると、ローカルのファイルを読み込むのではなくなるためブラウザのセキュリティ制約にかからなくなります。

※インターネット接続できない環境での動作は諦める必要があります。IE9なども対応に含めようとするとXDomainRequestが必要になってきて厄介になると思います…

## HTMLにファイルの内容を埋め込む (外部ファイル化を諦める。全ブラウザ対応)

「ファイル数が少ない」「データが少ない」という場合は、外部化自体を諦めてHTMLにファイルの内容を埋め込んでしまったほうが手っ取り早いです。(ブラウザの挙動の違いを気にする必要がないです)

```html
<!DOCTYPE html>
<html lang="ja"><head><meta charset="UTF-8"></head>
<body>
<!-- 外部ファイル化したかったCSV(type指定しない場合JavaScriptとして扱われてエラーが起きます) -->
<script type="text/csv" id="csv">
test,1
</script>

<script>
    // 外部ファイル化したかった内容
    var data = document.getElementById("csv").text.trim();
    console.log(data);
</script>
</body>
</html>
```

上記は&lt;script&gt;に埋め込んでいますが、&lt;div&gt;などにして隠して(display:none)おいてもよいです。

## ローカルでWebサーバを動かす (file:/// を諦める。全ブラウザ対応)

「HTMLファイルをPC上で直接開いても動作しますか？」の回答としては「すみません、動作しません…」という回答になってしまいますが、ローカルでサーバプログラムを動かすことを許可してもらい、file:/// ではなく http://localhost で動作させるとセキュリティ制約が無くなります。

※ローカルでサーバプログラムを動かす方法は許可してもらいづらいと思うので、JavaScriptのプログラムを直すのが状況的に難しい場合の最終手段だと思います。

「動作させるPCをなるべく汚さないようにしたい」「Webサーバを自作したくない」場合は、Mongoose(Cesanta Mongoose)というファイル1つで動く軽量Webサーバプログラムを使うと便利です。

- [Mongoose Binary Web Server Application](https://www.cesanta.com/products/binary)

Windowsの場合、動作させたいHTMLと同じフォルダにexeを配置して実行するだけでローカルサーバが立ち上がってindex.htmlが表示されます。(終了したい場合はタスクトレイのアイコンからExitするだけです)