ENECHANGE Developer Blog

ENECHANGE開発者ブログ

PDFファイルに不適切なメタデータ(「作成者」や「タイトル」など)が存在している場合にCIで検知する方法

こんにちは。ENECHANGE の gamenechange です。

朝起きたら「The Last of Us Part II」の発売が 5月 に延期になったことが発表されており、とても残念です。しばらくは IGA氏 の新作 Bloodstained: Ritual of the Night をプレイしていきたいと思います。

さて、今回はPDFファイルに保存されている「不適切な」メタデータの露出を防ぐために、CI でそのデータの検知をする方法をご紹介します。

目次

PDFのメタデータとは

PDFの主なメタデータとしては「タイトル」、「作成者」、「アプリケーション」などがあります。Acrobat Reader の「プロパティ」から容易に内容を確認することができます。

以下のメタデータは「WEB広報東京都」に掲載されている「広報東京都令和元年10月号」のPDFファイルのメタデータです。

「タイトル」に「広報東京都令和元年10月号」と入力されており、「作成者」には「東京都」と入力されていることが確認できます。

f:id:gamenechange:20191024183213p:plain

「不適切な」メタデータ(「作成者」や「タイトル」など)は除外したい

僕が担当しているプロダクトの中に、「プレスリリース」や「規約」などが書かれたPDFファイルを外部の方から頂き、それをそのまま Webサイト に掲載する、という作業が継続的に発生するものがあります。

PDFファイルを Web に掲載する際に、「公開したくない不適切なメタデータ(会社名や担当者名など)」は除外しなければいけません。それらの情報を含んだままPDFファイルを公開することは、場合によっては情報漏えいに繋がりかねないからです。

ただし、差し替えが頻繁に行われるようなPDFファイルを人力で逐一調べることは、手間がかかるだけではなくミスの原因にもなります。PDFファイルを作成された方の環境によっては、メタデータが入っているときと入っていないときが混在したりして、人間が管理することには限界があります。

チェックを自動化することでこの課題を解決しましょう。

まずは手動で検知してみる

最終的には CI に検知のロジックを組み込みますが、その前段階としてまずは手動で検知してみましょう。サンプルとして扱うPDFファイルは、前出の「広報東京都令和元年10月号(201910_re.pdf)」とします。

メタデータを抽出するためのコマンド

メタデータを抽出するためのコマンドにはいくつか選択肢があります。今回は次の 4つ を比較しました。

結果として選定したのは qpdf でしたが、それぞれのコマンドを簡単に説明します。

qpdf

PDF をコマンドで扱う場合にまず候補に入ってくるのが qpdf です。マニュアル にもあるように、PDF に対するほとんどの操作をカバーしています。メタデータの出力は JSON で行われるので取り扱いが簡単です。

インストールするためには、brew apt yum のいずれにおいてもパッケージ名として qpdf を指定すれば OK です。

cpdf

cpdf はシンプルかつ実際に必要となる操作を網羅しています。ただし、商用利用に際してはライセンスの購入が必要になります ので、候補からは除外しました。

インストールするためには GitHub のリポジトリ からバイナリをコピーすれば OK です。

pdfinfo

pdfinfo はその名の通りPDFファイルの情報を表示することに特化しています。最もシンプルですが、メタデータの出力形式が XML であるため、候補からは除外しました。

インストールするためには、brew の場合は xpdf というパッケージ名を、aptyum の場合は poppler-utils というパッケージ名を指定します。

pdftk

qpdfcpdf と同様に様々なことができます。ただ、メタデータの出力形式がプレーンテキストであり、取り扱い時に無用な苦労をしそうなので候補からは除外しました。しかし、「メタデータの書き込み」を行うためには有用なコマンドです*1

インストールするためには、Mac の場合は pkgファイルをダウンロードしてインストール し、RHEL/CentOS では libgcj をインストール後に rpm 経由でインストールします。Debian/Ubuntu では pdftk というパッケージを指定するだけです。

qpdf コマンドを用いて PDF のメタデータを取得する

qpdf コマンド には多くのオプションがあります。今回は --json というオプションを用いてメタデータを取得します。興味がある方は 他のオプションの内容 も覗いてみて下さい。

--json オプションを与えた上でファイル名を指定すると、PDFファイルの情報が JSON で以下のように出力されます。17,000行 以上ありますので、先頭と末尾とメタデータの部分のみを記載します。

$ qpdf --json 201910_re.pdf
{
  "acroform": {
    "fields": [],
    "hasacroform": true,
    "needappearances": false
  },
  "objects": {
    "1 0 R": {
      "/ArtBox": [
        0.0,
        0.0,
        773.858,
        1152.28
      ],
      "/BleedBox": [
...
...
      "/Type": "/Pages"
    },
    "1193 0 R": {
      "/Author": "東京都",
      "/CreationDate": "D:20190917172636+09'00'",
      "/Creator": "Adobe InDesign CS6 (Macintosh)",
      "/ModDate": "D:20190927083024+09'00'",
      "/Producer": "Adobe PDF Library 10.0.1",
      "/Title": "広報東京都令和元年10月号",
      "/Trapped": "/False"
    },
    "1194 0 R": {
...
...
          "width": 186
        }
      ],
      "label": null,
      "object": "337 0 R",
      "outlines": [],
      "pageposfrom1": 8
    }
  ],
  "parameters": {
    "decodelevel": "generalized"
  },
  "version": 1
}

このうち、メタデータの「作成者」の情報は "/Author" というキーに記録されています。そして "/Author" というキーは { "objects": { "1193 0 R": { "/Author": "東京都" } } } という位置に記録されています。1193 0 R の部分の「1193 0 R」という文字列はPDFファイルによって異なります。

jq を用いてワンライナーで "/Author" の値を取得する

得られた JSON から "/Author" キー の値を取得しましょう。JSON を操作する場合は jq コマンドを用いると便利です。jq の挙動を確認するために jq play というページを利用するのもよいでしょう。

以下のコマンドを実行することで "/Author" キー の値である「東京都」を得ることができます*2。コマンドの内容の詳細については省略しますのでぜひ調べてみて下さい。recurse を使っているのがポイントです*3

$ qpdf --json 201910_re.pdf | jq 'recurse | select(."/Author"?) | ."/Author"' | grep -E ".*"
"東京都"

CI に組み込む

ここまで出来たならばあとは CI に組み込むだけです。ここでは CircleCI を利用することとします。CircleCI 用の config.yml ファイルの該当部分は以下のようになります。

version: 2
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.3
...
...
    steps:
...
...
      - run:
          name: Install qpdf and jq
          command: |
            sudo apt-get install -y qpdf jq
      - run:
          name: Check PDF metadata (Author) existence
          command: |
            METADATA_AUTHOR_VALUE=`qpdf --json 201910_re.pdf | jq 'recurse | select(."/Author"?) | ."/Author"'`
            if [ -z "$METADATA_AUTHOR_VALUE" ] || [ "$METADATA_AUTHOR_VALUE" = \"\" ]; then
              exit 0
            else
              exit 1
            fi
...
...

if [ -z "$METADATA_AUTHOR_VALUE" ] || [ "$METADATA_AUTHOR_VALUE" = \"\" ]; then の部分がやや技巧的ですが、これは「『/Author というキーが存在しない場合』または『/Author というキーは存在するが、その値が空っぽの場合』の場合を望ましい場合」と判定しているからです。

これで、PDFファイルのメタデータの「作成者」に何らかの文字が入っている場合に CI が落ちるようになりました*4。したがって、「チェックを自動化したい」という冒頭の課題が解決できました。

メタデータを書き換える

さらに一歩進んで、メタデータを自動で書き換えることもできます*5。その際は前述の pdftk コマンドを用いて、例えば以下のように実行するとメタデータを上書きできます。ここで my-metadata は、所定の書式に則った上書きしたいメタデータのテキストです。

$ pdftk with_metadata.pdf update_info_utf8 my-metadata output without_metadata.pdf

上記のコマンドをいい具合に CI に組み込んで、例えばメタデータを上書きしたPDFファイルをS3にアップロードするようなことを行えば、公開するPDFファイルに不適切なメタデータが含まれることはなくなるでしょう。

PDF以外のファイルのメタデータにも適用する

これまでの内容を応用すれば、PDFファイル以外にメタデータを持つファイルにも対応できます。Office 製品 のファイル や 他の Adobe 製品 のファイル、デジカメで撮影した画像ファイル などが対象になるでしょう。

ENECHANGE株式会社は CI でプロダクトの品質を高めたいエンジニアを募集しています

ENECHANGE株式会社では、CI を駆使してプロダクトの品質を高めたいエンジニアを募集しています。詳しくは 採用情報 のページをご覧下さい。

*1:後述します

*2:「1193 0 R」という文字列が出てきていないことに注目して下さい

*3:他の書き方でも同じことは実現可能です

*4:「作成者」に「何らかの文字が入っていること」が直ちに「不適切なメタデータが存在していること」にはつながらないのですが、簡略化のためにここではその前提に立っています

*5:プロテクトが掛かっていない場合