memorandum
thinking about creation

PDF.jsで印刷やダウンロードができないPDFを公開する

Entry Date _ 2020.01.31
/
ModifiedDate _ 2020.01.31

PDFの閲覧だけを許可して、印刷やダウンロードはさせたくないという要望があった場合の対応方法です。

URLを指定すればブラウザで簡単にPDFを表示できますが、この状態でPDFの印刷やダウンロードを制御するのはむずかしいので、「PDF.js」というjavaScriptで書かれたライブラリを使って目的に対応します。


■PDF.jsの簡単な使い方

PDF.jsのビュワーでPDFを表示させるのはとても簡単です。

まず下記から「Stable(安定版)」をダウンロードしてください。ここではバージョン「2.2.228」をダウンロードしたことにします。
https://mozilla.github.io/pdf.js/getting_started/#download

展開したものをサーバに設置します(ここでは「http://localhost/」にアップしたとして表記し、リンクは「http://chaordic.co.jp/memorandum/article/」にアップされたものが表示されます」)。

例えばローカル・サーバ環境のルートにバージョン「2.2.228」を設置した場合、下記であらかじめ用意されているサンプルPDF(compressed.tracemonkey-pldi-09.pdf)を表示することができます。
http://localhost/pdfjs-2.2.228-dist/web/viewer.html

指定のPDFを表示させたい場合は、「viewer.html」の後に「?file=PDFのファイル名」と指定することで表示できます。ここには、「相対パス」「絶対パス」「URL」を指定することができます。
http://localhost/pdfjs-2.2.228-dist/web/viewer.html?file=compressed.tracemonkey-pldi-09.pdf

ここでは、これ以上PDF.jsについての詳しい説明はしません。
PDF.jsは、とてもすぐれたライブラリで、PDFビュワーであるばかりでなく、PDFレンダーでもあります。実はブラウザに搭載されてるPDFビューアにも、このPDF.jsが使われていたりします。
PDFレンダーでいちからPDFビューワを自作するような方法をとれば、もっと完全に目的に対応できるのかもしれませんが、かなりの労力が必要になりそうなので、ここではあらかじめ用意されているPDFビューワを改造して目的に対応します。


■印刷できないようにCSSで指定する

それでは、CSSとjavaScriptとjQueryとPHPのファイルを追加して、PDF.jsで印刷やダウンロードができないようにしていきます。

まずは「.../pdfjs-2.2.228-dist/web/」の配下にある「viewer.html」をコピーして「viewer_custom.html」とリネームします。

次に同じディレクトリに「_custom.css」を作って以下を記述します。

@media print{
	body{
		display: none;
	}
}

これは単純にCSSのメディアクエリによる指定ですね。これによって印刷時にはbody配下のすべてが空白になります。


■印刷やダウンロードのボタンを消す

印刷のボタンが不要になりますので、続けてCSSでボタンを消します。
同じく「_custom.css」に追記します。

#print, #secondaryPrint,
#download, #secondaryDownload{
	display: none !important;
}

これで、このファイルを「viewer_custom.html」に読み込ませると印刷とダウンロードのボタンを消すことができます。「viewer.css」を読み込んでいる箇所の下あたりがいいと思います。

同時にファイルオープンやブックマークのボタンも消してしまいたい場合は、以下のようにします。

#print, #secondaryPrint,
#download, #secondaryDownload,
#openFile, #secondaryOpenFile,		
#viewBookmark, #secondaryViewBookmark{
	display: none !important;
}


■PDFのパスを隠蔽する

ここまでで、とりあえずPDF.jsのUIによる印刷やダウンロードができなくなりました。

また、右クリックで「印刷」を選択して印刷プレビューを表示させてみても、そこには空白が表示されていると思います。さらに「名前をつけて保存」してみても、そのファイルでPDFは再現されないでしょう。

一般のユーザを対象にしたものならば、ここまでで十分だと思いますが、ビュワーを起動するのに「?file=pdf-1.pdf」のように「PDFのパス」が晒されてしまっています。これを見れば、元のPDFファイルがどこにあるのか一目瞭然で、そこにアクセスされてしまえば印刷もダウンロードも容易にできてしまいます。

この「PDFのパス」をJavaScriptとPHPをつかってなんとか隠してみます。

pdfjs-2.2.228-dist/」の直下に「_hide/」というディレクリをつくって、そこに「no*.pdf」というファイル名のPDFを3つ入れておきます。このPDFをパスを隠蔽して表示させます。これから追加するファイルを含め下記に示します。

pdfjs-2.2.228-dist
	├─ _hide
	│	├ no1.pdf
	│	├ no2.pdf
	│	└ no3.pdf
	├─ build
	└─ web
		├ viewer_custom.html
		├ _custom.css
		├ _custom.js
		└ _custom.php


web/」の配下に以下の内容の「_custom.js」を用意します。

$(function(){

	// -- 指定されたパラメータを取得する
	var getUrlParam = function(prm){
		var _prm  = new Object;
		var pair = location.search.substring(1).split('&');
		for(var i=0;pair[i];i++) {
			var kv = pair[i].split('=');
			_prm[kv[0]]=kv[1];
		}
		return decodeURIComponent(_prm[prm]);
	}

	// -- パラメータの「no」の値を取り出す
	var no = getUrlParam('no');

	if (no){
		// -- 「no」に値があったら、それをPHPにajaxで送る
		$.ajax({
			url: './_custom.php',
			type: 'POST',	
			dataType: 'json',
			data:{"no": no}
		}).done(function(_json){
			// -- エラー処理など
		}).fail(function(data){
			console.error(data);
		});
	}
});

同じディレクトリに「_custom.php」を以下の内容で用意します。

<?php
session_start();

$no	= $_REQUEST["no"]; // JSから送られた「no」を「$no」に格納

if ($no){

	/*
	 * 「$no」を持っていたらセッションにnoを格納
	 */
	$_SESSION['no'] = $no;

}else{

	/*
	 * 「$no」を持っていなかったらPDFを出力する
	 */
	
	// -- 「$no」に対応したPDFのパスを指定する
	$files[1] = '../_hide/no1.pdf';
	$files[2] = '../_hide/no2.pdf';
	$files[3] = '../_hide/no3.pdf';

	// -- PDF出力
	$file = $files[$_SESSION['no']];
	$filename = pathinfo($file);
	$filename = $filename['basename'];
	
	$pdf = file_get_contents($file);
	header("Content-Type: application/pdf");
	header("Content-Disposition: inline; filename={$filename}");
	echo $pdf;
	
}

exit();
?>

それから「viewer_custom.html」に上記のJSとCSS、またjQueryを読み込むように追記します。先に追記した「_custom.css」を読み込んでいる箇所の下あたりがいいと思います。





ここまで準備できましたら、「viewer_custom.html」に「?file=_custom.php&no=1」のようにパラメータを付与してアクセスしてみてください。

http://localhost/pdfjs-2.2.228-dist/web/viewer_custom.html?file=_custom.php&no=1
http://localhost/pdfjs-2.2.228-dist/web/viewer_custom.html?file=_custom.php&no=2
http://localhost/pdfjs-2.2.228-dist/web/viewer_custom.html?file=_custom.php&no=3

これで、URLのパラメータから、表示するPDFのパスを類推するのが難しくなりました。


■仕組み

仕組みとしては、まずURLパラメータに付加された「no」の値を「_custom.js」で取得し、ajaxで「_custom.php」に送り、それをPHPのセッションに格納します。

この後、指定されたパラメータ「_custom.php&no=1」をPDF.jsはPDFだと思って取りにいくわけですが、パスとして評価されるのは「_custom.php」だけです。

つまり、ひとつのPDFを出力するのに、二度「_custom.php」が叩かれるというわけです。

二度目の「_custom.php」へのアクセスは、センションに格納されたnoを頼りに、指定されたPDFを読み込んで出力します。

このような、あまりスマートな方法とはいえない方法ですが、そんなことをしています。

これで一通り動作を確認できますが、実際につかうとなると、セキュリティの配慮や、エラー処理など、様々な追加が必要になると思います。実装できそうにない場合は弊社にご相談ください。

また、ブラウザのデベロッパー・ツールをつかってCSSを編集すれば、PDF.jsのボタンを復活させることは可能なので、あくまで完全ではない、一般ユーザを対象とした対策とお考えください。

Category

memorandum index