素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: すかいぷ:javascripter_  か javascripter あっと tsukkun.net skypeのほうがいいです

オープンリダイレクタ脆弱性に気をつける

たまたま通販サイトを見ていたらログインページに脆弱性を発見したので、書いておく。

下記サイトのサポート部門から、システム部門に問い合わせて早急に調査するとの連絡があった。

16 June 2014: 脆弱性を確認したので今日か明日中に修正し修正が終わったら連絡するとのメールが来た。

18 June 2014: システム改修が終了したとのメールがあった。

報告済みであるがまだ修正されていないので、ドメインは仮にwww.******.jpとしておく。

https://www.7netshopping.jp/esi.svl?start&CID=ESI997&r_url=http://www.7netshopping.jp.example.com

のように、ドメインのバリデーションを先頭一致で行っていたため、末尾に.example.comなどとつけると、ログイン成功時にユーザを任意のドメインのサイトに飛ばすことが可能になっていた。これは一見大きな問題ではないように思えるが、危険な脆弱性である。

何が問題なのか

ウェブサイトのログインページは、元のページに戻るために、

&redirect_url=http://example.com/

のようなパラメータが指定してあることが多い。例えばjavascript:alert(1)が通るようであれば危険であることはなんとなくわかるが、http/httpsのURLであっても、管理下(同一のドメインなど)にない、任意のURLを指定できると、危険である。

具体的な攻撃シナリオは、下記の通り。

http://example.com/login?redirect=http://evil.example.jp

というページにアクセスする場合を考える。 ユーザは正しいドメインのサイトにアクセスすることになるので、何も警戒せずにIDやパスワードを入力する。 しかし、ログインに成功した後、オープンリダイレクタ脆弱性によって、悪意のあるevil.example.jpに飛ばされてしまう。このページは、本物のログインページの、ログイン失敗時そっくりに作られており、「ログインに失敗しました。IDとパスワードを再入力してください」と書いてあり、ユーザが気づかずにIDとパスワードを入力してしまうと、それが盗まれてしまう。

最近のウェブブラウザは、ドメインのみを表示するUIに以降しつつあり、セキュリティに詳しい人であっても、最初のログイン時に、リダイレクト先が細工されていることに気づくことは困難である。

リダイレクト機能を実装する場合、http://やhttps://など、プロトコルを限定することは必須であるが、ドメイン名も末尾一致で検索すべきである。特に、URL全体に対して正規表現などで検索をするとバリデーション漏れになりやすいので、なるべくならURLパーサを使い、ドメイン部ごとなどに、きちんとすべて検証すべきである。

参考:

カタカナ英語の脱却に相手の唇を見ることが有効

タイトルで説明しきった感じがあるが、英語の発音の覚え方とリスニング力の鍛え方について、自分の考えを書いていこうと思う。

ネイティブは英語をリスニングしているのか?

ネイティブは、耳だけを頼りに英語を聞いているわけではないということを知っているだろうか。この動画を見てみると、そうではないようだ。

耳の錯覚についての動画だが、この中に、同じBarという音でも、唇の動きがFarだと、Farという音に聞こえる、というものがある。 非ネイティブだが英語が得意な友達にこの動画を見せたところ、Farの唇の動きをしている時でも、動画の影響をうけずに、正しくBarと聞きとれる人が多数派であった。一方、ネイティブはFarの唇の動きを見ると、Farだと思い込む。

つまり、非ネイティブスピーカーが英語を「聞こう」とする一方、ネイティブスピーカーは「聞く」一方で、同時に英語を「見ている」のである

ネイティブの子供は唇を見て英語の発音を覚えている

ネイティブスピーカーがどのように英語の発音を獲得するのか、考えたことはあるだろうか。ベイビートークと呼ばれる、幼児の英語の発音の特徴についてのWikipediaの記事がある。 http://en.wikipedia.org/wiki/Baby_talk

ネイティブの子供も、最初はvとbを間違えたり、tをdと発音したり、thの発音を間違えることが頻繁にあるようである。tとdは、唇の動きが全く同じで、違いは有声音と無声音の違いだけである。vとbの唇の動きも良く似ていて、どちらも歯が唇に触れる動きをする。thをsやfと発音するのは、目には見えづらい、「舌使い」があるからだと考えられる。

これらに共通しているのは、見た目で区別がつかない音を赤ちゃんは間違えやすいということである。赤ちゃんは、発音をする時には口真似をしているのである。

リスニングという名前からくる誤解

非ネイティブスピーカーにとって英語の発音やリスニングは難しいが、耳だけを頼りにしたリスニングというのは、そもそも学習方法としては根本的に不自然だったのである。耳を頼りにするという発想から脱却すると、いろいろなものが見えてくる。

まず、カタカナ英語の不自然さである。耳で聞いている限りでは、はじめはwhatが「わっと」に聞こえることがあるかもしれない。しかし、唇の動きを見てみると、whatと「わっと」が、全く違うことにすぐ気がつくと思う。

whatは、イメージでいうと「ぅわt」のような感じで唇をすぼめてからちょっと開いて、tを発音して、最終的に唇は半開きのままである。日本語でわっと、って言うと、「と」の部分で口をすぼめた状態で終わる。唇だけを見て音を消すと、もはや真似ているのかどうかも怪しいレベルだ。

発音を覚える上では、唇の動きを見て口の動きを真似るというプロセスが、リスニングよりも先に来るべきだということを、ここで主張したい。リスニングと聞くと、CDを使ったような聞き取りをどうしても連想してしまうが、実はそれは、対面の会話よりも進んだ難易度の少し高い作業なのである。

真のスピードラーニング

タイトルはごく一般的な英語の、素早く学ぶという単語としての意味で、例のCD教材とは関係ないということを先に述べておく。 話が逸れたが、英語の発音とリスニングを素早く学ぶのに何が適しているかというと、動画である。 わざわざ書くこと自体ためらわれるが、例えばYouTube洋楽のミュージックビデオで、歌詞を一旦ざっと見てから、唇の動きをよく観察しながら、音楽を聞くと、いろいろな発見があると思う。 自分の発音での唇の動きと、動画での唇の動きとの違い。そういった違いを意識し、一致させるよう、自分のリスニングや発音の練習にフィードバックさせていく。

リスニングや発音といった生まれつきの面が多いと言われる非ネイティブにとって困難なものも、こうすることである程度は克服できる。発音は少しアクセントがあるけど意思疎通には全く支障がないレベル、リスニングに関してはほぼ完璧と言える状態が、非ネイティブ話者が目指すべきレベルだと思う。実現可能か? Yes.

感想

長くなってしまったが、非ネイティブにとって、英語のリスニング、スピーキングというのは、大きな障害になっていることが多い一方、学校や英語教室、語学学校などで適切なアドバイスがもらえることが少なすぎると思う。外国語が流暢な人の多くを見て、それぞれに共通しているのは、皆、語学を覚える手段を創意工夫してるということ。インターネット上には、英語学習に関するアドバイスや文章がたくさんあるが、根底にある「どう」覚えるのか、ということについて解説してある記事は少ない。

自分も非ネイティブとして英語を覚えるのに苦労して、今でも上達できるよう様々な方法を試していて、友達から英語の覚え方を聞かれることが多いが「こうすると良い」というはっきりとした答えは出しづらい。十分な時間を費やすこと、それから自分なりに仮説を立てて、物事の根底にある法則を発見することが、学習をしていく上で一番重要だと思う。

今までに書いた英語関連の記事のリンクを貼っておく。

nodeとstream周り、ジェネレータ、非同期処理

最近調べたnode.jsのstreamに関連した雑多な内容を、思いつくままに適当に書く。

例えば、

function* createPiGenerator() {
  var result = 0;
  for (var i = 1;; i += 4) {
    yield result * 4;
    result += 1/i - 1/(i + 2);
  }
}

こんな感じのジェネレータがあったとして、それをファイルに出力したい場合

function createPiStream() {
  var r = new require('stream').Readable();
  r.setEncoding('UTF-8'); // バッファじゃなくてstringとして処理したい
  var g = createPiGenerator(); // piの計算をするジェネレータ
  var tid;
  r._read = function () { // size指定は無視して良い
    if (!tid) {
      tid = setInterval(function () {
        r.push(g.next().value + '\n'); // gは無限に出力できるからチェックは無し
      }, 50);

    }
  };
  r.on('end', function () {
    clearInterval(tid); // 終了時はもうpushできないから消す
    tid = 0;
  });
  return r;

}

イベント駆動の意味がなくなると指摘されたので追記すると、この場合に限定すると下記のようにするとread()時にのみgeneratorが進むようになりリソースを無駄にしない。上でsetIntervalを使ったのは勝手に進むstreamのほうがテストとして使いやすいからである

function createPiStream() {
  var r = new require('stream').Readable();
  r.setEncoding('UTF-8');
  var g = createPiGenerator();
  r._read = function () {
        r.push(g.next().value + '\n');
  };
  return r;
}

こういう風にstreamを作っておくと

createPiStream().pipe(process.stdout);

こんな感じに使えて、この場合は出力するだけだけど、例えばファイルに出力したりとか、そういうのが簡単になる。

ところで、StreamにはobjectModeというのがあって、それを使うと文字列に直さなくても、numberとかobjectとかをそのままデータとしてstreamに流せるようになる。Node.js v0.10.28 Manual & Documentation を参照。

全く別の話で、Gruntみたいなtask runnerで、gulp.jsというのがあって、これはstreamを利用することで、中間ファイルを作らずに、minifyだとか、JSのlintだとか、concatだとの処理を、pipeでつなげて効率的に行うことができて、最近はGruntよりもgulp.jsのほうが簡潔でオシャレで、書きやすいのでおすすめである。

gulp.task('default', function () {
  gulp.src('src/*.js')
  .pipe(jshint())
  .pipe(uglify())
  .pipe(gulp.dest('build'))
});

例えばこんな感じにして

$ gulp

とするだけでbuildフォルダ内に処理されたものが作られたり。良いものである。

最近のStreamまわりの話題は、Stream2, Stream3, readableとかで検索するとでてきて、 stream-handbookとか、through2とか、event-streamとかを読むとまあまあ便利さがわかる。

node.jsでの非同期処理はStreamとか、Promiseとか、EventEmitterとか、thunkとか、callbackとかが色々あるけど、きれいに書ける場面がわりと分かれているので、最近の何でもPromiseみたいな風潮に惑わされないようにしたい。 最近おすすめの非同期ライブラリはAsyncで、本格的にはまだ使ってないけど、EventEmitterをベースにした有限オートマトンとか、非同期のロック、バッファ付きのemitterなどがあって、なかなかAPIも簡潔なので、良い。

Web Inspector/Chromeのデバッガで動画を再生する

普段、Web Inspectorはデバッガとして使っていると思うが、Web Inspectorは実は様々な物を表示できる。

例えば、コンソールでライフゲームを表示することができる。 Web Inspector Life Gameに、コードを置いた。タイミングによっては上手く動かないので、コンソール上でCmd+Rなどを押してリロードすると上手くいく。

以下に、実装に使ったハックや技術を紹介する

console.logで色付き文字を表示する

まず、console.warnやconsole.infoなどを使うと、黒字以外の文字を表示できることを思い出してほしい。console画面は、HTMLで実装されているのである。

console.log("%s", "aa");

のように、printf形式で文字を表示できる機能があることを知っているだろうか。 この中に、色指定をするためのものがある。%cである。

console.log("%chello", "color: red;");

とすると、

hello

と表示される。

CSSを悪用する

同様に、 console.logの%cには、 font-sizeやline-height、margin, backgroundなどが使用できる。

console.log("%c.", "font-size: 100px; border: 1px solid black;");

とすると、大きな文字と、ボーダーの枠が表示される。では、画像表示はできるだろうか?

console.log("%c.", "font-size: 10em; background-image: url('http://example.com/image.jpg')");

なんとできるのである。あとは、想像の通り、CSSを微調整すれば背景画像だけを表示することができる。

アニメーション

アニメーションに必要なのは、リフレッシュ、つまり次の画像を、前と同じ位置に表示することである。 これはどうするかというと、

console.clear()

を使用する。

しかし、ここで大きな問題がある。 console関数は全て非同期に動作するのだ。 すなわち、

console.clear();console.log('a');

とすると、aが表示される保障はない。

今回のハックでは、console.clear()のタイミングを20ms程度、console.logより前にずらすことで、clear()がlog()より前に呼ばれることをある程度保障している。

アニメーション自体は、canvasを使用した。toDataURL()を使用して得た画像を、background-imageとしてセットしている。 念のため以下に、全コードを載せておく。

<script>
var TheGameOfLife = function (options) {
  this.width = options.width + 1;
  this.height = options.height + 1;
  this.firstLives = options.firstLives;
  this.cells = this.clearCells([]);
  this.afterUpdates = [];
  this.randomize();
};

TheGameOfLife.prototype = {
  clearCells: function (cells) {
    var i, j;
    for (i = 0; i <= this.width; i++) {
      cells[i] = [];
      for (j = 0; j <= this.height; j++) {
        cells[i][j] = 0;
      }
    }
    return cells;
  },
  update: function () {
    var i, j, n;
    var newCells = this.clearCells([]);
    for (i = 1; i < this.width; i++) {
      for (j = 1; j < this.height; j++) {
        n = 0;
        n += this.cells[i - 1][j - 1];
        n += this.cells[i - 1][j];
        n += this.cells[i - 1][j + 1];
        n += this.cells[i][j - 1];
        n += this.cells[i][j + 1];
        n += this.cells[i + 1][j - 1];
        n += this.cells[i + 1][j];
        n += this.cells[i + 1][j + 1];
        if (this.cells[i][j] == 0 ? n == 3 : (n == 2 || n == 3)) {
          newCells[i][j] = 1;
          } else {
          newCells[i][j] = 0;
        }
      }
    }
    this.cells = newCells;
    for (i = 0; i < this.afterUpdates.length; i++) {
      this.afterUpdates[i]();
    }
  },
  afterUpdate: function (hook) {
    this.afterUpdates.push(hook);
  },
  randomize: function () {
    var remains = this.firstLives;
    var i, j;
    while (remains--) {
      i = Math.floor(Math.random() * (this.width - 1) * (this.height - 1));
      j = i % (this.height - 1);
      i = (i - j) / (this.height - 1);
      i++;
      j++;
      this.cells[i][j] = 1;
    }
  }
};

document.addEventListener("DOMContentLoaded", function () {
  var width = 16;
  var height = 16;
  function reproduce() {
    var url = canvas.toDataURL();




console.log('Reload to reset: %c.', 'font-size: 0; padding: 150px; background-image: url(' + url + '); background-repeat: no-repeat;');

  }

  function render () {
    var wScale = canvas.width / width;
    var hScale = canvas.height / height;
    context.clearRect(0, 0, canvas.width, canvas.height);
    var i, j;
    for (i = 1; i < tgol.width; i++) {
      for (j = 1; j < tgol.height; j++) {
        if (tgol.cells[i][j] & 1) {
          context.fillRect(i * wScale, j * hScale, wScale, hScale);
        }
      }
    }
  }

  var canvas = document.getElementById("canvas");
  var context = canvas.getContext("2d");
  var options = {
    width: width,
    height: height,
    firstLives: 50,
    canvas: canvas
  };
  var tgol = new TheGameOfLife(options);
  tgol.afterUpdate(function () {
    render();
    reproduce();
  });
  render();
  setInterval(function () {
    tgol.update();
  }, 200);
  document.addEventListener("click", function () {
    tgol.clearCells(tgol.cells);
    tgol.randomize();
  }, false);
}, false);

setTimeout(function () {
  setInterval(function (){ console.clear();}, 200);
}, 180);

</script>
<canvas id="canvas" width="300" height="300" style="display: none;"></canvas>

古本をAmazonで買ってスキャンサービスに送ってiPadで読む

海外に住んでいると、日本語の本を入手することの難しさもあって日本語の本を全く読まなくなってしまう。海外の書籍はお店やネット通販で気軽に買えるし、英語圏電子書籍は日本の電子書籍と比べて品揃えが格段に良いからだ。

英語の本ばかり読むことになるが少し物足りない。日本人である自分は日本の書籍を読むほうが楽で、特に難しい本や自分が勉強したことのない分野だと、日本語で書いてあるものを一度ざっと読んでから英語の本を読んだ方が、トータルで見たら時間の節約になることが多い。

最近、2013-08-27 - 登 大遊@筑波大学大学院コンピュータサイエンス専攻の SoftEther VPN 日記というページを見つけて、まさにこれは自分が求めていたものだと思ったので、いくつか本を買ってみたら非常にすばらしかったので、ほとんどさきほどのページと書いてあることと同じだが、手順をここに記しておく。

まず、書籍のスキャンサービスでアカウントを作成する。自分はBOOKSCAN(ブックスキャン) 本・蔵書電子書籍化サービス - 大和印刷を使った。一月1万円のプレミアムコースで50冊無料、OCR、名前変更などができスキャンからpdfまでの納期が1週間以内に保証されており、Aamzonなどから直接送れる住所を割り当ててくれるので、割とおすすめである。50冊も読みたい本をまとめて購入するわけがないという場合、少し割高になるが別のサービスを探すと良い。

登録や支払いが済んだら、日本のAmazonで、読みたい本を探す。多くの場合、古本が出回っているので、状態が「良」のうち最安値のものをショッピングカートに入れる。1円の本の場合、送料をいれても一冊250円程度で非常に安いので、バンバン買う。10冊買ってやっと2500円で、技術書1冊分だと考えると、すばらしい。

あとは、注文するだけだ。発送先を先ほどのスキャン業者で発行された住所にして、purchaseすると、数日以内にスキャン業者に届いて、あとは勝手にpdfにしてくれる。

pdfができたらiPadに入れるなりなんなりをする。古本であってもpdfにすれば少しの汚れは自動的に除去されるし、変な匂いがすることもないので、快適そのものである。

今のところ、Amazonで買ってから1週間くらいでpdfが出来上がっている。満足。

Capy CAPTCHAは一瞬で突破できる

Capy CAPTCHA

早速、実証コードが(CAPY IS A VERY READABLE CAPTCHA)出たようだ。このように一瞬で突破されてしまい意味がない。

さきほどインターネットを見ていたらスパム防止用の「読みづらい画像認証」に、日本人が終止符を打った技術が斬新過ぎる!経由で、Capy - 低コストで導入も簡単な不正ログイン対策という、パズルを使った新しい新しくないCAPTCHAを知った。 コンテストに優勝するなど肯定的な反応が多いので、この記事では、このCAPTCHAのセキュリティ上の問題点について簡単に書いておこうと思う。

まず、Capy - デモにデモが乗っているので、タイプ別に問題点を示す。

パズルタイプの破り方

ジグソーパズルの空白を埋めるタイプのCAPTCHAである。話にならない。 まず、縦横が5pxごとに吸い付くようになっているので、縦横400px*300pxだと、4800通りしか答えがない。この時点で既にセキュリティ上問題である。

で、具体的な解き方であるが、二通りが考えられる。

まずは、背景の画像のバリエーションが少ないことを利用した方法。 例えば、このCAPTCHAであれば、

f:id:javascripter:20131206203035p:plain

背景画像がせいぜい10個程度しかないため、正解状態の画像を用意しておくことが可能。

f:id:javascripter:20131206203154p:plain

このような画像を用意して、元の画像とピクセルが一致しないところを抽出する。一致しないピクセルのうち、xが最小のものとyが最小のものの座標をx, yとするとx - x % 5, y - y % 5の地点が答えである。

背景画像の数が増えてきた場合、この手法では解く事が難しくなるが、その場合でも、単に背景を切り取ったタイプのジグソーパズルを解くことは簡単である。ジグソーパズルのピースのエッジを抽出し

f:id:javascripter:20131206203719p:plain

エッジ部と、それに隣接する背景の色の彩度の差の合計が、最も小さくなる場所が、正しいピース位置である。

まとめると、ジグソーパズルはコンピュータで自動的に解く事が非常に簡単なので、CAPTCHAとしてまともに機能しない。

テキストタイプの破り方

まず、元画像を見てほしい。

f:id:javascripter:20131206204030p:plain

ちょっとミスったので別の画像に対するものだが、コントラストを最大にしただけでこうなった。 このパターンの画像では、露出とコントラストを変化させるだけで文字列を100%抜き出せる。

f:id:javascripter:20131206204229p:plain

フォントをゆがめているわけでもなく、フォントのバリエーションも少ない。また、フォントサイズも固定であるように思われる。つまり、OCRを使わなくても、ピクセル一致でテキストを割り出せる。

まとめ

現時点で、Capy CAPTCHAにはセキュリティに問題がある。破るのが簡単であり、根本的にジグソーパズルという発想自体が、そもそもコンピュータから解きやすく、CAPTCHAとして成立していないように思われる。 ポイントサイトなどで既に使われはじめているようだが、キャッシュバックがあるようなサイトでこういったCAPTCHAを使うのは、危険だと思う。

ルーターの脆弱性を探して侵入する手順

更新し忘れたが、既に下記の脆弱性は修正されている

4/11/2013 6:42 PM 追記: 明日エンジニアと調査するとカスタマーサポートから連絡があり、また近日アップデートパッチを用意するとのことだ。

先日紹介したSatechi Smart Travel Routerだが、ふと直感的にセキュリティに問題があるような予感がしたので、自分のルーターをアタックしてみた。

結果から言うとCSRFが存在し、外部からインターネット越しに細工をしてあるURLを踏ませることで、ルーターのパスワード、SSIDを書き換えたり、WiFi to WiFiのリピータ機能のソースとなるWiFiを勝手に別の場所に書き換えて、Man in the middle攻撃を成立させたりできることが発覚した。

自分がどのようにSatechi Smart Travel Routerの脆弱性を発見したのかを動画にとったので、無編集で載せるので、自分のルータの脆弱性を調査するときに参考にしてほしい。

動画内で使用した脆弱性の実証コードは、An exploit of hijacking Satechi Smart Travel Routerに載せてある。

ウェブサイトが脆弱であるのも問題だが、ルータの脆弱性は、ウェブサイトの脆弱性より圧倒的に危険である。気になったので、家にあった別のルータを調査したところ、これにもクロスサイトスクリプティング脆弱性が存在することがわかった。 そのルータではSSID名が

<script>alert(1)</script>

であるWiFiを機器の近くで流すと、管理画面アクセス時にXSSが発生する。

上記動画を見てわかる通り、家庭用のルーター脆弱性を探すことはそれほど難しいことではない。ファームウェアを常に最新にし、ユーザ名、パスワードなどを必ず設定し、脆弱性がないか自分でも調査してみて、安全を確かめると良い。

なお、上記脆弱性についてはメーカーにメールを既に出しており、近日対策が行われると思われる。 現状での最大限の対策は、ルータのプラグを抜くことである。

モナドについて考えた記録

Promiseってなんとなくモナドっぽいなと思って、ジェネレータ使ってHaskellのdoを再現できないかなあというのがはじまり。結論からいうと、できない。

そもそもPromiseがモナドであるかを考える。

return :: Monad m => a -> m a

値を受け取ってfulfilledされたPromiseを返すものだ Promise.fulfill / Promise.resolveがこれにあたる。

Promise.mreturn = Promise.fulfill || Promise.resolve;

>>= :: Monad m => m a -> (a -> m b) -> m b

bindとも呼ばれたりする。この関数に、値を受け取ってモナドを返す関数を渡すと、その値が結果になる。

Promise.prototype.mbind = Promise.prototype.then;

つまり、Promise#thenである。

モナド

  • return a >>= f === f a
  • m >>= return === m
  • (m >>= f) >>= g === m >>= (\x -> f x >>= g)

これらを満たしていればモナドであると言える。 return a >>= f === f a は

Promise.mreturn(1).then(function (v) { console.log(v); }); // 1
(function (v) { console.log(v); })(1);

であり、満たしている

m >>= return === m これは (>>=) m returnで

var m = Promise.mreturn(1);
m.then(Promise.mreturn)
.then(function (v) { console.log(v); });
m.then(function (v) { console.log(v); }); // 1

であり、同一なので満たす。

(m >>= f) >>= g === m >>= (\x -> f x >>= g)は

var m = Promise.mreturn(2);

function f(v) {
  return Promise.mreturn(v * 3);

}

function g(v) {
  return Promise.mreturn(v + 2);
}

var m = Promise.mreturn(1);

m.mbind(f).mbind(g)
// (m >>= f) >>= gの部分
.then(console.log.bind(console)); // 5
m.mbind(function (x) {
    return f(x).mbind(g);
})
// m >>= (\x -> f x >>= g)の部分
.then(console.log.bind(console)); // 5

となり、一致するので満たしている。

よって、Promiseはモナドである。

拡張

Promise.do(function* () {
    var a = yield Proimse.mreturn(1);
    var b = yield Promise.mreturn(2);
    return a + b;
}).then(console.log.bind(console)); // 3

この記法をモナド全般に拡張できないのか?というのが今回のテーマなのだが、結論から言うと、できない。

一見、yield時に得たモナドをmbind(function (v) { iter.next(v) // 再帰的に終わるまで繰り返す }) で正しそうに見えるが、実際は、モナド全般に一般化するとmbind(f)でfは複数回呼ばれる可能性があるので、動かない。

わかりづらいので、想定通りに動かないモナドの例でいくと リストモナド

Array.prototype.mbind = function (f) {
  return [].concat.apply([], [].concat(this.map(f)));
}

Array.mreturn = function (x) {
  return [x];
};

のように定義すると、

[1, 2].mbind(function (x) {
  return [3, 4].mbind(function (y) {
    return Array.mreturn([x, y]);
  });
}); // [[1,3],[1,4],[2,3],[2,4]]

このようなことができる。しかし

Array.do(function* () {
  var x = yield [1, 2];
  var y = yield [1, 2];
  return [x, y];
});

とすると、イテレータの状態(=yield時の継続)をコピーできないので、x = 1、y = 1の後に、x = 2に戻す部分が実装できない。

うーん

ということで何らかのaltJSでdo <-をmreturnとmbindにdesugarするようにしたらいろいろなことを統一的にできるようになっていいのではないか? と思うのだが、ES6などの方向性とマッチしていないので(当然、リスト内包はMonadのsyntax sugarではない)、Monadなどのパラダイムを活用するのは難しいように思う

持ち運び出来る小型トラベル用ルーターがすごく便利

Pocket Wifiの話ではなく、電源を使うタイプの、Airport Expressのようなタイプの小型ルーターの話である。

先日、Satechi Smart Travel Routerという、持ち運びできる小型ルータを購入したら予想よりも大幅に便利だったので、紹介しようと思う。

まず、これが何なのかというと、

  • 世界中の電源プラグの形に対応した、旅行用の電源プラグ変換器
  • USBポートからの充電
  • 無線ルータ機能

がついた、片手におさまるサイズの軽いガジェットである。

各機能を詳しく説明する

電源プラグ変換の機能

自分はオーストラリア、日本間の行き来を毎年のようにしているのだが、電源の形が日本とオーストラリアでは違う。パソコンのAC充電器やひげ剃り、ヘアアイロンなど、日本で買ったものやオーストラリアで買ったものが混在しているので、旅行するたびに、数百円程度の変換器をいくつか持っていっていた。

大半の変換器は、インプットが1カ国限定で、アウトプットがグローバルに使えるようになっていると。つまり、日本用の変換器を持っていくと、オーストラリア->日本に変換する用途には使えないので、オーストラリア用のものと両方持っている必要がある。

しかし、このルータは、なんとインプットもアウトプットも両方150カ国対応なので、複数の変換器を持ち歩く必要がない。 もっと言うと、日本->日本やオーストラリア->オーストラリアのように、変換せずに使うことも可能なので、電源を塞がないという意味でもとても良い。

電圧変換はないので、掃除機とドライヤーはつないではいけない。それを除けば、最近の製品はどれもACアダプタは100-240に対応しているので、大丈夫である。

USBポートからの充電

これは普通のUSBポートで、iPhoneiPadやいろいろなものを充電できる。

説明書によるとスマートTVとつないでワイヤレス化などができるらしいのだが、スマートTVは使ったことがないので詳細は知らない。

無線ルータ機能

メインの機能の無線ルータ機能だが、これは単なるしょぼいルータではなく様々な機能が詰まった、本格的な高機能ルータである。

例えば下記のような機能がある

普通の無線ルータ

イーサネット端子からWifiを飛ばす普通のルータである。 小型なので、例えばホテルに有線のインターネットしかない場合などにWifiを飛ばせる。

WifiからWifiにするリピータWifiの電波を強くする)

Wifi<->Wifiリピータ機能である。例えば階段の下にある無線ルータが飛ばしているWifiに、階段上の部屋からつなぎたい場合に電波が不安定になりがちであるが、中間地点(階段上、部屋外の掃除機とかをつなぐ用の場所)にこの小型ルータをおくことで、電波強度をあげられる。

もう一つの使い方として、ホテルのWifiなどで同時接続の個数が制限されている場合、このルータをホテルのWifiにつないで、自分の端末はルータのWifiにつなげば、個数制限が関係なくなって快適である。

自分はiPad miniをワイヤレスのセカンドモニタとして使っていて、フレームレートが低いとカクカクなって不便なのだが、このリピータ機能を使うとフレームレートがあがるので、実用度が増す。

何にもつながないでルータ機能を使う

インターネット接続が必要ない、閉じたネットワークを作りたい場合に使える。

その他ルータの細かい部分

[10/30/13, 6:45:45 PM] javascripter: Satechi Travel Routerの良いところをさらに補足します
SSIDを複数設定できるので
自分で使う用の固定のやつ+ゲストが来たときにオンにする、パスワード別のやつ
[10/30/13, 6:45:50 PM] javascripter: を作成できる
[10/30/13, 6:46:30 PM] javascripter: isolatedとか言う設定があってゲスト用にセキィリティのために分離できる
[10/30/13, 6:56:58 PM] javascripter: MAC/IP/Port Filteringとかのfirewallも普通にあって
Wifi->Ethernetへの変換もできて、WifiのないパソコンでWifiに接続するときにも使える
VPNも使える
DHCPとかstatic tableとかも普通にある
WPA2-PSKとかのごくまともなencryptionが使える
WPSが使える
Wifi->Wifiのリピータが自動scanでボタンクリックとパスワード入力だけでおわる、ほかの設定もウィザードがあって楽
Content Filterとかいって特定のURLを見れなくする機能とかProxy/Java/ActiveXをブロックする機能とかそういう何故存在するのか不明な高機能なものが搭載されてる
DDNS(dyndns.org,freedns.afraid.org,www.zoneedit.com,www.no-ip.com)を標準対応

今日、実験的に6台つないでみたけど、平気だったから割と接続人数増えても動くっぽい

MutationObserverを使った高速setImmediate/nextTick

MessageChannel / setTimeout / requestAnimationFrame / postMessageを使ったものより異様に速い。 というのも、setTimeout等は、次のサイクル(すなわちnextTick)の開始時に実行する関数を登録し、DOMや画面の更新後に実行されるのだが、 MutationObserverのコールバックは、現在の(同期的な)JS実行が完了した時点でコードを走らせるためである。さらに、MutationObserverは同じ実行サイクルでのDOM上の変更を次回のサイクルにすべてまとめてrecordsとしてコールバックに渡すので、これもsetImmediateの実装には非常に都合が良い。

MutationObserverのような実行タイミングをmicrotaskといい、setTimeout / postMessage / MessageChannelなどの実行タイミングをmacrotaskというらしい。

div.id = 'a';
div.id = 'b';
var setImmediate;
var clearImmediate;

(function () {

  var id = 0;

  var div = document.createElement('div');

  var queue = [];

  var observer = new MutationObserver(function (records) {

    queue.forEach(function (opts) {
      var callback = opts.args[0];
      var params = Array.prototype.slice.call(opts.args, 1);
      callback.apply(null, params);
    });

    // clear the queue
    queue.length = 0;
  });

  observer.observe(div, { attributes: true });

  setImmediate = function () {
    queue.push({
      id: ++id,
      args: arguments
    });
    div.id = Math.random();
    return id;
  };

  clearImmediate = function (id) {
    var i = queue.length;
    while (i--) {
      if (queue[i].id == id) {
        queue.splice(i, 1);
      }
    }
  };

})();



setImmediate(console.log.bind(console, 2));
setImmediate(console.log.bind(console, 3));

clearImmediate(setImmediate(console.log.bind(console, 0)));

console.log(1);

/*
1
2
3
*/

参考: Twitter