はらへり日記

腹に弾丸

HTMLのイベントハンドラ属性における文字実体参照、数字文字参照

文字参照とは

基礎的な話だけどきちんと調べたことなかったので適当に調べてみた。

文字参照とはHTML等のマークアップ文書において直接参照できない文字(例えば文章中に<を入れるとタグが崩れちゃったりする)を表現するために用いられる文字列です。 PHPだとhtmlspecialchars()を一度は使用すると思うのですが、その時に出力される文字達が文字参照です。

この文字参照には以下の二種類あります。

それぞれ説明します。

数値文字参照(文字参照)

数値文字参照は特定の文字を10進数、もしくは16進数によって指定する方法です。 例えばを10進数の数値文字参照で表した文字列です。

文字実体参照(実体参照)

こちらは数値文字参照と違い、特定のキーワード文字列でHTML等に使用される文字集合の該当文字列を表現する方法です。 代表的なものだと<, >, &, "等があります。 以下のような文章を打ちたいときには文字実体参照を使うことで実現できます。

<html>
  <head>
    <title>sample</title>
  </head>
  <body>
    <!-- これだとブラウザによってはタグが破壊される -->
    <p><title>タグはページのタイトルをつけるのに用います</p>
    <!-- こう書けば見た目上は<title>になる -->
    <p><title>タグはページのタイトルをつけるのに用います</p>
  </body>
</html>

知っとくと面白いイベントハンドラ属性における実体参照

そもそもこの話を調べることになったキッカケがHTML内にhtmlspecialchars()を使わずにサニタイズしたURLを出力したかったからです。 その際にセキュリティ最強マンの友人にいろいろ聞いた過程で出てきた話がイベントハンドラ属性における実体参照の話です。

そもそもイベントハンドラ属性とは、特定のHTMLタグにつけることのできる特定のイベント時の処理を書くための属性値です。 具体的には以下の様なものがイベントハンドラ属性値。

<button onclick="alert('ボタンがクリックされたよ!');">
<img src="x" onerror="alert('画像読み込みエラーだよ!');">

これはしばしばXSSの温床になる属性だったりするのですが、このイベントハンドラ内での実体参照の解釈がすこし特殊。

例えば以下の様なPHPのエスケープは有効です。

<p class="<?php echo htmlspecialchar($className, ENT_QUOTES); ?>">ほげ~</p>

このようにエスケープすれば通常は$className"onclick="alert('xss');みたいな文字列を入れられても出力時点で実体参照に置き換えられるので問題はないです。 じゃあ以下のような時はどうなるでしょう。

<button onclick="alert('<?php echo htmlspecialchars($onClickMessage, ENT_QUOTES); ?>');">押せよオラオラ</button>

この際、$onClickMessage');location.href='http://evil.com';alert('のような文字列を混ぜます。 すると出力されるのは以下のHTMLです。

<button onclick="alert('');location.href='http://evil.com';alert('');">押せよオラオラ</button>

数値文字参照に置き換えられて、一見問題がないように思えます。 しかしブラウザがイベントハンドラ属性の値を評価する際、文字参照を解釈した上でコードの実行を試みます。 つまり見た目上はエスケープされていても実際に攻撃が成功します。

<!-- 「ソースを読む」で見たらこうなってるのに -->
<button onclick="alert('');location.href='http://evil.com';alert('');">押せよオラオラ</button>
<!-- ブラウザの解釈はこう -->
<button onclick="alert('');location.href='http://evil.com';alert('');">押せよオラオラ</button>

コワイですね。

どうすればいいの

この記事に全て書いてあります。

対策遅らせるHTMLエンコーディングの「神話」

1行でまとめるなら「全ての文字列をHTMLエスケープしよう」です。 XSS対策する上で一番の敵はブラウザの仕様やコードでなく、人です。 「危なそうなところはエスケープ」といった運用ではヒューマンエラーでうっかりエスケープ忘れ、なんてことが必ず起こります。 半年くらいなら起きないかもしれませんが、10年開発して0件なんてことはないですよね…? 文字列は必ずエスケープしましょう。 どうしてもエスケープできない場面はエスケープする実装方法に差し替えられないか検討し、それでもダメならセキュリティできる人、ないしはチームメンバーに一言相談するのがマストだと思います。

electronでテキストフォームが動かない

環境

現象

普通にelectronのチュートリアル通りのアプリケーションスクリプトを書いているのにテキストフォームが入力できずにめっちゃ困ってた。 Issue立ててみて何個かアドバイスもらったものの動かず。。。

Keyboard input doesn't seem to work · Issue #5355 · electron/electron · GitHub

結論

tmux環境下でelectronを起動しているのが原因だった。

tmux下でElectronがうまく動作しない - Qiita

LSOpenURLsWithRole() strikes back · Issue #3093 · atom/atom · GitHub

面倒なのできちんと読んでないけどちょろっと調べるとYosemiteのtmux下でのopenコマンドの挙動が原因っぽいのかな。

プログラミング芸術論:OS X Yosemite における LSOpenURLsWithRole エラーの解決

なんにせよ素のzshで起動することで問題解決!

まとめ

これで3日くらい溶かしたので同じ現象になった人が時間を溶かさないことを願って記事書いた。

electronで幸せになろう(願望)

PHP BLT#4でLTしました

LTした

今年入って2回目のLTをした。

怖いぺちぱーの人がたくさん来ていたので相当緊張したがなんとか喋った。

 

実践DIコンテナ

speakerdeck.com

 

話した内容を3行で書くと

  • DIコンテナとはなんぞや
  • LaravelにおけるDIコンテナ
  • DIコンテナ活用例

を話した。

この一年間、仕事で縁あってLaravelを触ってる時間が長く、その過程でDIコンテナに非常にお世話になった。

ただ、一番最初にDIパターンを学んだ時、具体的にどんな場面で役に立つのかピンと来なかったし実際触ってからもDIパターンやDIコンテナのありがたみを実感するのは結構後半だった気がする(Laravelではサービスコンテナと呼ばれてたりします)。

なので具体的にどんな場面で「ありがたや〜」となるのかを伝えることを目標に資料を作りました。

 

発表してみて

まず5分以内に全部しゃべりきれなかったことを反省。。。

あとは3分くらい喋ったところで「あ、これあんまり伝わってない」というのを肌で感じた。

DIコンテナをわかりやすく例えたつもりだったけど終わった後の懇親会で@sotarokさんに「玄人感あったけどわかりづらいね!」と爽やかに評していただきました!

精進します!

 

PHP BLTに関して

1回目参加して以来だったがごはんおいしいしお酒飲めるし(病み上がりだから今日は飲まなかったですが)面白いLT多いしとてもオススメ。

弱小ぺちぱーながら勇気を出して発表したけど発表するとやっぱり懇親会で話しやすいし間違い指摘してもらえたりもっといい方法が聞けたりするので次回もぜひ発表したい。

 

まとめ

次回リベンジするぞい

これからのJSの非同期処理関数は全てPromiseを返させるべき

はじめに

JSで非同期関数を書く時、個人的に意識してる話です。

別にTipsとかじゃないです。

要するにポエムです。

あしからず(´・ω・`)

非同期関数を使いこなす

JSを書いたことがある人なら知ってるであろう非同期処理ですが、僕は非同期処理はなるべくPromiseオブジェクトを返す関数に切り分けていくべきだと思っています。

極端な例として「APIその1を叩いて、その結果からAPIその2を叩いて、その結果からAPIその3を叩いて、それを画面に描画するぞい!」というコードを書いてみます。

悪い例:関数で処理をラップしない

APIが綺麗に整っていれば最高ですが、現実は得てしてあまくありません。

3種類のAPIを叩くわけですがそれらのレスポンススキーマは全て違うとしましょう。

const firstApiUrl = 'http://api1.com';
const secondApiUrl = 'http://api2.com';
const thirdApiUrl = 'http://api3.com';

// 自前で定義したajax()関数をがあるとします
ajax(firstApiUrl, { data: 'data' }, (err, res) => {
  if (err) {
    alert('APIその1のエラーだよ');
    return;
  }
  
  if (res.status) {
    // APIその1から返ったデータを使う
    ajax(secondApiUrl, { data: res.body.data }, (err, res) => {
      if (err) {
        alert('APIその2のエラーだよ');
        return;
      }
      
      if (res.result) {
        // APIその2から返ったデータを使う
        ajax(thirdApiUrl, { data: res.count }, (err, res) => {
          if (err) {
            alert('APIその3のエラーだよ!');
          } 
          
          $('body').append(res.html);
        });
      }
    });
  }
});

はい。いわゆるネスト地獄ですね。

このコードの悪いところをざっとあげてみると

  • ネストが深すぎて可読性が低い
  • レスポンススキーマAPIごとに違うので同じような処理なのにデータの取り出し方が違ってなんか気持ち悪い(res.body.data, res.result, res.count.....)
  • 『上司「APIその2のレスポンススキーマ変わるから対応してよ」』って時にその処理部分を探すのが大変(grepかけますか???)

悪い例:コールバック関数を引数に取る関数を書く

先ほどあがっていた悪い点のうち、2点目を解決してみましょう。

処理を関数でわけてみます。

/**
 * @description APIその1と通信する
 * @param {mixed} data
 * @param {Function} callback
 */
const ajaxFirstApi = (data, callback) => {
  let baseUrl = 'http://api1.com';
  ajax(baseUrl, { data: data }, (err, res) => {
    if (err) {
      alert('APIその1の通信エラーです');
      callback(err, null);
    }
    
    callback(null, res.body.data);
  })
};

/**
 * @description APIその2と通信する
 * @param {mixed} data
 * @param {Function} callback
 */
const ajaxSecondApi = (data, callback) => {
  let baseUrl = 'http://api2.com';
  ajax(baseUrl, { data: data }, (err, res) => {
    if (err) {
      alert('APIその2の通信エラーです');
      callback(err, null);
    }
    
    callback(null, res.result);
  })
};

/**
 * @description APIその3と通信する
 * @param {mixed} data
 * @param {Function} callback
 */
const ajaxThirdApi = (data, callback) => {
  let baseUrl = 'http://api3.com';
  ajax(baseUrl, { data: data }, (err, res) => {
    if (err) {
      alert('APIその3の通信エラーです');
      callback(err, null);
    }
    
    callback(null, res.count);
  })
};

ajaxFirstApi('data', (err, result) => {
  if (result) {
    ajaxSecondApi(result, (err, result) => {
      if (result) {
        ajaxThirdApi(result, (err, result) => {
          if (result) {
            $('body').append(result);
          }
        }
      }
    });
  }
});

コード量が増えたものの、先程よりは多少よくなったのではないでしょうか。

最初の例と比べると以下の点が改善されたかと思います。

  • それぞれのAPIへのアクセスを関数化したので使い回しがきく/修正が容易
  • レスポンススキーマについては関数の中で完結してるので実際に関数を使う時に意識しなくてよい

しかし、以下の問題については未だ未解決です。

  • ネストが深くて可読性が低い

また、このアプローチだと以下のような関数が混ざった時に混乱します。

/**
 * @description APIその4と通信
 * @param {mixed} data
 * @param {Function} calllback
 */
const ajaxForthApi = (data, callback) => {
  ajax('http://api4.com', { data: data }, (err, res) => {
    callback(res, err); // ここに注目!!!
  });
};

先ほど定義したajaxFirstApi(), ajaxSecondApi(), ajaxThirdApi()ではcallbackに渡される変数の順番が最初にerr, ついでresとなっていました。

しかしここで定義されている関数ではその順番が逆になっています。

これには2つの問題があり、

  • 関数の使用方法が統一されない コーディングルールで決めることは可能だがLint等の自動ツールでのチェックはほぼ不可能であり、実装者を完全に信用しなければいけない

  • コールバック関数に渡される引数の順番を知らないと関数が使えない ajaxFirstApi('data', (err, res) => {});というように使いたい時、第二引数の関数に最終的に何が渡されるかを実装者は知る必要があります。

丁寧にJSDoc等でコメントが書かれていればそれで済みますが、世のコードの9割はそんなに親切ではなく結局実装を追う羽目になることが多いと思います。(せっかく関数化したのに…)

良い例:全ての関数にPromiseを返させる

待たせたな!ここでPromiseを使ってみましょう!

/**
 * @description APIその1と通信する
 * @param {mixed} data
 * @return {Promise}
 */
const ajaxFirstApi = (data) => {
  let baseUrl = 'http://api1.com';
  return new Promise((resolve, reject) => {
    ajax(baseUrl, { data: data }, (err, res) => {
      if (err) {
        alert('APIその1の通信エラーです');
        reject(err);
      }
    
      resolve(res.body.data);
    });
  })
};

/**
 * @description APIその2と通信する
 * @param {mixed} data
 * @return {Promise}
 */
const ajaxSecondApi = (data) => {
  let baseUrl = 'http://api2.com';
  return new Promise((resolve, reject) => {
    ajax(baseUrl, { data: data }, (err, res) => {
      if (err) {
        alert('APIその2の通信エラーです');
        reject(err);
      }
    
      resolve(res.result);
    });
  })
};

/**
 * @description APIその3と通信する
 * @param {mixed} data
 * @return {Promise}
 */
const ajaxThirdApi = (data) => {
  let baseUrl = 'http://api3.com';
  return new Promise((resolve, reject) => {
    ajax(baseUrl, { data: data }, (err, res) => {
      if (err) {
        alert('APIその3の通信エラーです');
        reject(err);
      }
    
      resolve(res.count);
    });
  })
};

ajaxFirstApi('data') 
  .then((result) => {
    return ajaxSecondApi(result);
  })
  .then((result) => {
    return ajaxThirdApi(result);
  })
  .then((result) => {
    $('body').append(result);
  })
  .catch((err) => {
    alert('通信エラーです');
  });

// 省略してこう書くことも可能

ajaxFirstApi('data') 
  .then(ajaxSecondApi)
  .then(ajaxThirdApi)
  .then((result) => {
    $('body').append(result);
  })
  .catch((err) => {
    alert('通信エラーです');
  });

さぁ!前回の例での問題が解決されました。

前回の問題として以下のものがありました。

  • ネストが深い
  • コールバック関数に渡される引数の順番を知る必要がある

コード量は増えましたが、Promiseを返す関数はthen()catch()を使ってつなげることができます。

最新のJSではAPIでもこのthenableの考え方が浸透しており、これを使って非同期処理の順番をネストの深さを変えずにつなげることが可能になります。

コールバック関数に渡される引数の順番についても知る必要はなくなりました。

Promiseを返す関数ではresolve()reject()の2種類に縛られます。

前者は成功時の返り値、後者はエラー時の返り値となります。

そしてそれらをthen()catch()で捉えて、行いたい処理を書きます。

ね、簡単でしょう?

何が嬉しいのか

嬉しさポイントは見た目が綺麗になることもありますが、個人的にはコールバック関数の形式が強制的にresolve(), reject()で縛られるのがいいかなと思います。

今まではコールバック関数に何が返ってくるのかドキュメントを読む必要がありましたが、Promise化されていれば成功か失敗かで考えるだけで済みます。

あるAPIを叩く関数をPromise化しておけば内部実装がどうなっていようと使う側はthen()で結果を受け取り、catch()でエラーハンドリングを行えばよいのです。

ある程度冗長には見えますがこれからはPromiseが常識になっていきますし非同期関数は全部Promiseでいいんじゃないかなと思ってます。

実際に使いたいけど動かないんでしょう?

いいえ!動きます。

IE8まではpolyfillライブラリであるes6-promiseを使用することで問題なく使うことができます。

もっと知りたい

Promiseの本がおすすめです。

azu.github.io

まとめ

みんなPromiseで幸せになろう!!!!!!!!!必ずなろう!!!!!!!

Babel + Browserifyで環境変数を使用する

前提

Babel6系でES2015のJSをBrowserifyを使用してコンパイルします。

願い

JSをコンパイルする際、Ajax通信で使用するURIを開発と本番で分けたい場面がありました。

なので以下の様なことがしたい。

let uriPrefix = '/api';

if (process.env.APP_ENV === 'production') {
  uriPrefix = '/api/production';
}

export const apiUri = `${uriPrefix}/get/sushi`;

ただ、普通にやると process.env.APP_ENVなんてブラウザに存在しないのでundefinedとなってしまう。

やり方

Babelのプラグインであるbabel-plugin-transform-inline-environment-variables を使用します。

babel/packages/babel-plugin-transform-inline-environment-variables at master · babel/babel · GitHub

プラグインの導入方法はよしなに。

ひとまず必須なのはプラグインのインストール。以下のコマンドを叩く。

$ npm i babel-plugin-transform-inline-environment-variables --save-dev

その後、それぞれのコンパイル方法でプラグインを指定する。

以下はGulpを使用した場合の例です。

import gulp       from 'gulp';
import browserify from 'browserify';
import source     from 'vinyl-source-stream';

gulp.task('js', () => {
  browserify
    .transform(babelify, {
      entries: ['js/app.js'],
      presets: ['es2015'],
      plugins: ['transform-inline-environment-variables']
    })
    .bundle()
    .pipe(source('main.js'))
    .pipe(gul.dest('./public/js'));
});

こうすることでprocess.envが参照できるようになります。

ぐう便利!!!