Ruby の Mechanize の wiki のよてい

Mechanize::Chain::URIResolver


サーバ接続より前に呼ばれる Chain クラスのひとつです。
接続先 URI の絶対 URI 化や非 ASCII 文字のエンコードを行います。ユーザーが利用することはありません。



アクセス先 URL を示す params[:uri] を、パーセントエンコード済みで絶対 URL な URI オブジェクトに変換します。
メソッド引数を由来とするかどうかやHTMLに書かれていたもの由来であるかどうかの区別なく、あらゆる「取得先URL」を妥当な絶対URLに変換します。

URI オブジェクトではなく URL 文字列だった場合(get の文字列引数、リンクのclick)、以下の処理の順番で URI オブジェクトにまず変換します。
  • 「アスキー文字」でない文字(アスキーコードが127かそれ以上)があればパーセントエンコードする
  • パーセントエンコードされていない部分があれば 一度だけ URI.escape を試す
  • HTML の実体参照があれば Mechanize::Util.html_unescape で解決する
  • 全体を URI.parse するが、失敗する場合は全体を再度 URI.escape する
そもそもの引数または上記でできた URI オブジェクトが相対 URL だった場合、
  • 現在のページに base 要素があって絶対 URL が記述されていた場合、最後の base 要素の記述を元に 絶対 URL 化
  • base 要素がない場合は現在のページの URL を元に絶対 URL 化
します。
host 部分だけで path が空な状態の URL だった場合、path に明示的に / が追加されます。
すべてのエンコードが済んだあと、path 部分に /../ の形のドットの循環が存在した場合には取り除きます。

Mechanize#get に URL を指定しなかったような場合、引数の params[:uri] が nil になって 'uri must be specified' という ArgumentError が発生します。
相対 URL にアクセスさせようとしたにもかかわらず絶対 URL 化に必要な「現在のページ」がなかった場合、'need absolute URL' とい RuntimeError が発生します。
作られた絶対 URI のスキーム部分が http:// でも https:// でも file:// でも無い場合、"unsupported scheme: #{uri.scheme}"という RuntimeError が発生します。


…という処理を目指しているようで、大部分はうまく動作するのですが、Mechanize 1.0.0 現在、かなり適当です。
  • 【Ruby1.8】 URL 文字列にマルチバイト文字がそのまま書かれていた場合、パーセントエンコーディングに失敗する

(warn 'run under Ruby 1.8.x'; exit) if '1.9.0' <= RUBY_VERSION
require 'rubygems'
require 'mechanize'
require 'kconv'
require 'logger'

agent = Mechanize.new
agent.log = Logger.new($stdout)
agent.log.level = Logger::INFO
agent.user_agent_alias = "Windows IE 7"
agent.get('http://ja.wikipedia.org/wiki/鋸'.toutf8) rescue puts $!
agent.get(URI.escape('http://ja.wikipedia.org/wiki/鋸'.toutf8))
結果:
I, [2010-09-11T11:33:39]  INFO -- : Net::HTTP::Get: /wiki/%25..FE9%25..F8B%25..FB8
I, [2010-09-11T11:33:39]  INFO -- : status: 404
404 => Net::HTTPNotFound
I, [2010-09-11T11:32:22]  INFO -- : Net::HTTP::Get: /wiki/%E9%8B%B8
I, [2010-09-11T11:32:23]  INFO -- : status: 200

Ruby1.8.x におけるパーセントエンコーディングの処理が自前なので、うまく動作していません(Ruby1.9.xは平気)。get などの引数にしたい場合は自力で URI.escape したものを渡せばいいだけなのですが、click の場合は若干面倒です。Mechanize::Page::Link#href を URI.escape して get するなどの処置が必要でしょう。
  • 【Ruby共通】パーセントエンコード対象の文字になる実体参照入りの文字列 URL の場合、既存の % がすべて %25 になる
require 'rubygems'
require 'mechanize'
require 'kconv'
require 'logger'

agent = Mechanize.new
agent.log = Logger.new($stdout)
agent.log.level = Logger::INFO
agent.get('http://example.com/book%20review&lt;1&gt;')
結果:
I, [2010-09-11T12:02:30]  INFO -- : Net::HTTP::Get: /book%2520review%3C1%3E
I, [2010-09-11T12:02:30]  INFO -- : status: 404
Mechanize::ResponseCodeError: 404 => Net::HTTPNotFound
本来は %20 はパーセントエンコーディング済みとしてスルーされるべきですが、%20 の % の部分が未パーセントエンコーディングと誤解釈されて %25 になってしまっています。この %2520 が首尾よく %2520 → %20 → " " と解釈されるかどうかは相手のサーバのアンエンコード処理とエラー訂正動作に依存します。
Mechanize#get の URL やクエリ文字列、post/submit系の送信文字列に実体参照が含まれていた場合に起きる可能性があります(文字実体参照だけではなく、数値実体参照でも起こります)。通常の HTML のリンクでは Mechanize::Page::Link#href の時点で実体参照が解決されているのでそもそも起きないはずです。

管理人/副管理人のみ編集できます