WebTecNote

[Ajax + PHP] Web2.0的にHTMLソースを隠す方法

MooToolsで遊んでる時になんとなく思いついた。

HTMLソースを完全に隠すことは無理っちゃ無理だけど、サイトの内容をパクられたりしないように防御する事は出来る。
主要な柱は以下3つ。

ロボット対策、右クリック禁止、Flashサイト化、HTMLソースそのものの暗号化するなど色々あるけど、
Web2.0とか言われている時代なのでAjaxを使う方法もアリなんじゃないかなと思ったわけです。

クローラーにキャッシュされず、メールアドレスも拾われず、右クリックしても無駄、「ソースを表示」選んでも主要部分は見えない。
それらの需要を大体カバーするサイト制作方法についての解説とサンプル。

簡単なデモを作ってみた
(デモはサンプルと構成が異なります)

サイト構成例

サンプルソースと解説

トップページ(index.html)

この手法を使うと「トップページ完結型」と呼ばれる構造になるので、コンテンツ以外の枠部分だけを入れておく。
Ajaxで読み込んだページを表示するエリアは空でもいいし、トップらしい案内を入れていてもいい。

スタイルシートのリンク
スタイルシートはXMLHttpRequestで読み込ませるのでヘッダにlinkタグは書かない。

メニュー
検索エンジンのクローラーは、アンカータグのhref属性を頼りに他のページを収集しようするので、
メニューにはアンカータグを使わずに文字だけを書いておく。これで他のページを辿れなくなる。
ただテキストを羅列しているだけではJavaScriptで処理しにくくなるので、それぞれのテキストをリストなどの要素に入れる。
サンプルは便宜的にページタイトルと同じhrefをリスト要素にクラスとして付与しているが、
これは全く無関係な文字列でもいいし、何も付与しなくても良い。
リンクとして見せるためにCSSでcursor:pointerを指定しておく。

<ul id="nav">
	<li class="about">About</li>
	<li class="gallery">Gallery</li>
	<li class="mail">Mail</li>
</ul>
<div id="content">
</div>

※クローラーにリンクを辿らせる必要もないのでAタグを使わなくても問題はないなと思ったので初稿から修正
aタグを使う場合は、hrefには任意の文字列のみ入れて拡張子はつけない。(clickしたときNotFoundになるのが良い)

制御用JavasScript (view.js)

index.htmlに表示されていないものをすべてこのファイル内で処理する。
上で書いたindex.htmlソースの中身も出力すればより秘匿性が増す。
linkタグを使う代わりにXMLHttpRequestでstyle.cssを読み込ませれば、外部スタイルシートも隠すことができる。

重要なのはrequest.phpにリクエストを送信する部分。
メニューをクリックしたらXMLHttpRequestを実行する。
サンプルではrequest.php にGETかPOSTでaction=viewとp=(LI要素のclass名)を渡すようにしている。

request.phpからページの中身を受け取ったら、返された結果をinnerHTMLにセットする。(onSuccessメソッド)
innerHTMLした内容はindex.htmlのソースには反映されないが、DOMには反映されるので、
ブラウザに搭載されたドキュメントツリー表示で参照したり、Ctrl+Aなどで選択することは出来る。
前者は対策しようがないけど、後者はJavaScriptを使えば無効にすることが可能です。

var content =$("content");
new Asset.css("css/style.css",{ "media":"screen"});

$each($("nav").getElements("li"),function(a,i){
	
	a.addEvent("click",function(e){
		e.stop();
		var myRequest = new Request({
			url:"request.php",
			method:"get",
			data:{"action":"view","p":a.get("class")},
			onRequest:function(){
				content.fade(0);
			},
			onSuccess:function(txt){
				if(txt.test("error")){
					content.set("html":"<p class="error">Page Not Found</p>");
				}else{
					content.set("html", txt);
					(function(){
						content.fade(1);
					}).delay(500);
				}
			}
		}).send();
	});
});

特定条件で表示をさせないロジックは盛り込み放題。
スクリプト無効環境なら当然なにも表示できません。

ページ出力用PHP(request.php)

view.js から渡された値によってpageディレクトリにあるコンテンツファイルを読み込む。
渡されるpの値と実際のファイル名を違うものにするなら条件分岐させる。
なおPerlでも同じことは出来ます。

if(isset($_GET["action"],$_GET["p"]) && $_GET["action"]==="view"){
	
	$request = new Request("pages");

	print $request->get_content_txt($_GET["p"].".php");

}

class Request
{
	private $dirname;
	
	function __construct($dir){
		$this->dirname = $dir;
	}
	
	function get_content_txt($name){
		
		$filename = $this->dirname."/".$name;
		
		if (file_exists($filename))
			return trim(str_replace("<?php exit; ?>","",file_get_contents($filename)));
		else
			return "error";
	}
	
}

読み込まれたファイルの内容はview.jsのonSuccessメソッドへ引数として渡される。

pageディレクトリのコンテンツphpファイル

便宜的にpageというディレクトリ名にしてますがなんでもいいです。

PHPやPerlのCGIであれば、ファイルの先頭で実行を終了させるソースを入れることで
それより後にある内容を非表示にすることができます。(ログファイル作成でよくあるテクニック)

PHPの場合はexit;を入れてから#contentに表示するhtmlソースを普通に書いておきます。

<?php exit; ?>

<h2>About this site</h2>
<p>This is sample.</p>

パブリックルートよりも上に置けば、通常のHTMLで作成してもブラウザでアクセス出来ないので不可視にできます。
無論ファイルの代わりにデータベースを使っても良いです。

考察

このように作ると、見れるのはindex.htmlとview.jsのソースだけ。
view.jsを圧縮(or暗号化)すれば実質index.htmlのソースだけ読むことが可能という状態になります。
ページの名前を示すクラス名やhrefはPHPで置換が可能なのでリンクを辿ることはまず不可能。
いっそのことそのクラス名やhrefすら無くすことだって出来る。
コンテンツファイルのURLがバレたとしてもexitしてあるから表向きは何も表示されないし、パーミッションによってはアクセスそのものが弾かれる。

Firebugなどの開発者ツールだと、ソースそのものを暗号化していてもブラウザに表示された時点でオブジェクトツリーで読めてしまう。
リクエストも追跡可能なのでツールによる解析を防ぐのは無理だけど、
そういう開発ツールを入れていない一般ユーザーに対しての秘匿はかなり効果があるんじゃないかなと。
ツール入っててもJavascriptの解読+PHPの知識が必要なので再現性は低いと思う。

クローラーに対してexitがどのように働くかよく分からないんで、
パーミッション変更やrobot.txt、.htaccessと併用するのがおすすめ。

モバイルバージョンを終了