最終更新: 2010-04-22T02:07+0900
やってみた。Time.parseの問題がわからなかったのと、ブロック変数が外のローカル変数を変更してしまうかどうかという問題(Rubyのバージョンは?回答形式が)以外はたぶん問題なし。で、回答を送信すると一分かそれ以上待たされた後、「一度しか回答できません」だって。ええええ。たしかにそう書いてあるのは読んだけど、以前回答したことも忘れてるんですよ。そんなこと言われても。
そういう要求をするのも、それを額面通りに実施してしまうのも、ある意味感心するけど首を傾げてしまう。回答は適当に受け付けて、後でフィルタリングしてから利用すればいいじゃない。とここで「すでに受験した方は,前回の回答結果を参照できます。」とも書かれてるのに気付いた。ログインを要求されたから見られなかったけど、回答を(回答者の要求に応じて参照できるようにしながら)抱え続けるのって誰得? Ruby検定の予想問題からのピックアップに過ぎないんですよ、この問題って。
回答形式が 一つ選べという問題だったのにチェックボックスが使われていた。他は問題形式によってラジオボタンとチェックボックスが適切に使い分けられていたから、出題形式を間違えたかフォームを間違えたか後でフォームだけ変更されたか。はてさて。
最終更新: 2013-08-20T01:04+0900
「makeplex salon:あなたのスキルで飯は食えるか? 史上最大のコーディングスキル判定 (1/2) - ITmedia エンタープライズ」経由で「人生を書き換える者すらいた。: 人材獲得作戦・4 試験問題ほか」
粘菌でも迷路問題を解けるというのだから負けてはいられない。Rubyで一時間半弱かかった。
#! ruby # coding: Shift_JIS # 迷路データ maze = <<MAZE ************************** *S* * * * * * * ************* * * * * ************ * * * * ************** *********** * * ** *********************** * * G * * * *********** * * * * ******* * * * * * ************************** MAZE # マップ class Point def initialize(x, y) @x, @y = x, y end attr_reader :x, :y def ==(other) @x == other.x && @y == other.y end def to_s "(#{@x},#{@y})" end def left Point.new(@x-1, @y) end def right Point.new(@x+1, @y) end def up Point.new(@x, @y-1) end def down Point.new(@x, @y+1) end end class Map def initialize(maze) lines = maze.strip.split(/\r\n?|\n/) @height = lines.length @width = lines.first.length y = -1 @map = lines.map{|line| y += 1 x = -1 line.split(//).map{|ch| x += 1 if(ch == 'G') @goal = Point.new(x, y) nil elsif(ch == 'S') @start = Point.new(x, y) nil elsif(ch == '*') -1 else nil end } } end attr_reader :height, :width attr_reader :start, :goal def []=(point, value) @map[point.y][point.x] = value end def [](point) @map[point.y][point.x] end def up(point) return -1 if point.y-1 < 0 return @map[point.y-1][point.x] end def down(point) return -1 if self.height <= point.y+1 return @map[point.y+1][point.x] end def left(point) return -1 if point.x-1 < 0 return @map[point.y][point.x-1] end def right(point) return -1 if self.width <= point.x+1 return @map[point.y][point.x+1] end end map = Map.new(maze) #puts "width×height = #{map.width}×#{map.height}" #puts "start:#{map.start} / goal:#{map.goal}" map[map.goal] = 0; #puts "goal=#{map[map.goal]}" # 先端 nodes = [map.goal] while node = nodes.shift next if node == map.start [node.left, node.right, node.up, node.down].each{|nxt| case(map[nxt]) when -1 # 壁 when nil # 未踏 map[nxt] = map[node] + 1 nodes.push(nxt) else # 既に到達したルートがある if(map[node] + 1 < map[nxt]) map[nxt] = map[node] + 1 nodes.push(nxt) unless nodes.include?(nxt) end end } end # Map to result map.height.times{|y| map.width.times{|x| point = map[Point.new(x,y)] print point == -1 ? '***' : ("% 3d"%point) } puts } if false result = maze.strip.split(/\r\n?|\n/) node = map.start until node == map.goal [node.left, node.right, node.up, node.down].each{|nxt| if(map[nxt] == map[node] - 1) node = nxt result[nxt.y][nxt.x] = '$' break end } end puts result.join("\n")
ノードっていうのはひとマスのこと。「先端」っていうコメントが適当すぎる(植物では頂端分裂組織っていうらしい)。方針はゴールからスタートに向けて、一マス移動するごとに一ポイントを加算しながら全方位に触手を伸ばしていき、すでに他の触手が通過した道は、自分が最適な場合にのみ侵入するこの判断は不要。全ての触手が行き場を失うかスタートに到達するかしたら終了。スタートから一ずつ減っていく数字をたどると(一少ない数をもつ隣接マスが二つ以上あっても解答に求められていないので無視する)ゴールへ着き、それが最短経路(だったらいいな)。
最初は粘菌方式をまねようと思ったが、通路をすべて覆った状態からスタートして、スタートとゴールにつながっていない島を切り捨て、袋小路から撤退するのはいい。遠回りと近道を取捨選択するためにどういう評価をすればいいのかを決められなかった。どうすればいいの?粘菌は何を考えてる?例えば
二番目のリンク先のトラックバックを見てる。みんな優秀なのね、C++で一時間もかかってないし。何人かが言及している「BFS」。それって一般的知識? 幅優先探索(Breadth-First Search)か最良優先探索(Best-First Search)の略だということはわかった。
壁のもつポイントを -1 に設定したことで、壁に侵入しないことを保証する when節を省略してもよかったんじゃないだろうか。気付くのが遅れたが。
ゴールにも $ って書き込んでら。(スタートには書きこまんように気をつけたんだが)
粘菌すごい。
培地に、三個以上の餌を置く。粘菌は迷路の実験のように最短距離を結ぶか?
結果は違った。
粘菌は、丸く、複数の経路を持つ管を作った。「最短経路だと一カ所故障したら必ず孤立する場所が出ます。だから粘菌は、一カ所が故障しても全体はつながり、なおかつ距離がなるべく短い経路を作ったのです」
部分部分がどういう反応をするとそういう結果になるんだろ???
粘菌の行動原理は、量子ドット間の近接場光を介したエネルギー移動プロセスに類似している
最終更新: 2014-12-18T13:09+0900
すぐ上のセクションで出てきた(俺が書いたんだけど)こういうの、
alias :my_overwritten_by_my_ex :my def my ... end
こういう「エイリアス&上書き」が嫌いで、不完全ながらこういうものを書いたことがある。(今でも使っている。セミコロンを抑制する努力をしていない頃に書いたもののようだ。Rubyのバージョンで nameをシンボルにするか文字列にするか振り分けているが、今ならやらない。先頭要素を取り出してそれがシンボルか文字列かを調べる。そして、代入を ifで囲うのではなく ifの結果を代入する)
class ::Object def overwrite(name, &block) if('1.9.0'<=RUBY_VERSION); name = name.to_sym; else name = name.to_s; end if(self.singleton_methods(false).member?(name)) $dirty = [name, block]; class <<self name, block = *$dirty; old = instance_method(name); remove_method(name); # define_method(name){|*params| # block.call(old.bind(self), *params); # ブロックは渡せない? 1.9ならできる? # } define_method("__overwrite__#{name}__#{block.object_id}", &block); define_method(name){|*params| send("__overwrite__#{name}__#{block.object_id}", old.bind(self), *params); } end else if(true != self.class.ancestors.each{|klass| if(klass.methods(false).member?(name)) klass.instance_eval{ old = method(name); remove_method(name); # define_method(name){|*params| # block.call(old, *params); # ブロックは渡せない? 1.9ならできる? # } define_method("__overwrite__#{name}__#{block.object_id}", &block); define_method(name){|*params| send("__overwrite__#{name}__#{block.object_id}", old, *params); } } break true; end }) # failure raise; end end end private :overwrite end
グローバル変数は赤色でよく目立つね。$dirtyっていう変数はスコープの断絶を乗り越えるために使ってるんだろう。汚いやり方だ。
もはや自分でも読み解けないが、こうして使う(んだったかな? define_methodしたときはブロックで returnが使えた気がする)。
overwrite(:my){|old, *params| # 新しいmyを以下に…… # 必要なら古い定義も呼び出せる。 return old.call(*params) }
継承による overrideであれば、隠された同名のメソッドを superで呼び出せるけど、メソッドの上書きではそれができなくて古い定義を呼び出せない。かといって「alias :foo_bar :foo」「alias :foo_baz :foo」式の無秩序なやり方は見るに堪えない。overwrite.rbがやってることも機械的に名前を付ける以外は同じではあるけども(まだましでしょ?)。
<追記@2009-12-19>コメント部分がなんでコメントアウトされているのかやっと思い出せた気がする。奇しくも自分でその理由を書いていたりするのだが。>「define_methodしたときはブロックで returnが使えた気がする」つまり、新メソッド定義(overwriteメソッドに与えられたブロック)を define_methodでメソッド化しておかなければ returnが使えないからだ。かといって古いメソッド定義を渡すために一段余計な処理を挟まなければいけないから、新メソッド定義(ブロック)をそのままの名前でメソッドにすることもできない。ブロックが保持する bindingに変数(旧メソッドを指している)を注入する方法があれば、ダミーの長ったらしい名前を持つメソッドをこっそり定義する今のやり方から抜け出せるかも。</追記>
俺が知らないだけで 1.9以降では解決策があるんだろうか。こういう現実の問題に対処してほしいなあ。わりと見かける aliasの使い方で、問題含みだと思うんだけど。
一人見つけた。
super=承継元クラスの同名メソッドを呼び出す=とは別に、 overridden=上書きされたメソッドを呼び出す=というのを作るというのはどうでしょう?
こちらはちょっと違う。
aliasによるメソッドの再定義は危険なのでUnboundMethodかextendを使おう - (rubikitch loves (Emacs Ruby CUI))
再ロード対策は考えていなかった。
extendは知らないではないけど……(これが求めていたものなんだろうか?)。
新しい名前を導入しないように extendでやってみた。エラーは出ないけどリンク先のサブタイトルが取得できていない(my-exによる拡張が機能していない)。instance_evalを module_evalにしてみてもだめ。
extend Module.new{|mod| mod.instance_eval{ define_method(:my){|*params| a, str, title = *params # myプラグインを拡張し、リンク先が自分の日記のとき、 # 可能ならサブタイトルやコメントを取得して title属性にセットします。 } }}
オーソドックスにやってみても同じこと。
module MyEx def my( a, str, title = nil ) end end extend MyEx
extendする対象を間違えてるんだろうか。Pluginのインスタンスに特異メソッドを定義してるつもりでいるんだけど。いずれにしろ答えにたどり着けないのでは使えないよ。def...end と書いてメソッドの定義を上書きするよりも安全で洗練された代替が欲しいんだから、def...endと書く以上の複雑さは受け容れられない。
なんでも、aliasするときにメソッド名にコロンを付けてはいけないらしい。
http://www.loveruby.net/w/RubyCodingStyle.html
なぜ付けるかといえば、ほとんどすべてが「実行される」Rubyにおいて、クラスやメソッドの定義(class...end, def...end)ですら実行時の条件によってふりわけられる Rubyにおいて、構文であること、特別扱いされることが許せないからだ。「alias はメソッド呼び出しではなくて構文である。だからこそ引数の間にカンマがないのだ。そこはわかっていながらなぜエセメソッドのような使いかたをするのか。」それは構文であることへの抵抗なのです(俺の場合)。
「ちなみにどうしてもコロンが付けたければ alias_method を使えばいいんではないかと思う。」
そいつ( alias_method )は知らなかった。コロンだけでなくメソッド名とメソッド名の間にカンマを置くこともできるんだから、喜んでそちらを使いますとも。alias_methodと aliasは使える場所や対象が完全には一致していないけども、aliasでしかできないことをした経験はないから問題はなし。
「aliasでしかできないことをした経験はないから問題はなし」なんて書いたそばからこんな例が……
特異メソッドをalias_methodを使用して別名をつけようとするとエラーになる。(中略) 特異メソッドに別名をつけて退避したい場合は、alias_method (_chain)メソッドではなくてaliasをinstance_evalのブロック内で使用するとよい。
特異メソッドは別、でした。想像だけど extendでプラグインメソッドの上書きができなかったのも特異メソッドがらみだと思うな。
昨日の文章はおかしいな。class...endや def...endという構文を認めながら aliasだけを白眼視する説明になっていないような。
class MyClass; endと書いた場合、MyClassは Classオブジェクトの入った定数(という言語のビルディングブロック)だと認識している。def my_method; endと書いた場合、my_methodはメソッド(という言語の(略)であり、以後、my_methodや my_method()と書いた場合それはメソッドの呼び出しである、というのが Rubyという言語の約束だと認識している。「alias alias_of_my_method my_method」という構文は他の構文との共通性がなく例外的なルールに感じられ、(同じコンテクストで用いられる)includeや private、attr_accessorが Moduleのプライベートメソッドとしてあるなかで構文である必然性が認められず、メソッド名に関するルールを破ってさえいる。そこが許せない。aliasの(メソッドではないから正確には違うが)引数の「alias_of_my_method」や「my_method」を言語としてどう位置づけるのか、そこををはっきりさせないのならばそれは使えない、という主張が aliasのメソッド名部分にコロンを付ける理由。
undefや defined?にも aliasと同じ疑問がある(ただし、undefは defの対称として容易に受け入れられるし、defined?の場合、必然性だけは認められる)。JavaScriptの typeof演算子や delete演算子にすら同じ違和感を覚えている*。コンパイル型言語のように、コンパイル時に評価されるのだという言い訳ができないインタプリタ型言語の弱みだろうか。こういうものを見ると Perlに感じたように言語が突然平板なものに見えてしまって嫌なのだ。そこには対象となるメモリやオブジェクトが存在せず、ソースコードの字面しかないでしょう。
* きちんと定義はされています >「[[8.7 Reference 型 (Reference Type)|http://www2u.biglobe.ne.jp/~oz-07ams/prog/ecma262r3/8_Types.html#section-8.7]]」
最終更新: 2009-12-28T23:38+0900
もうひとつ、最初のセクションからネタ拾い。こんなのや、
strdump = lambda{|s| s.dump.gsub( /%/, '\\\\045' ) }
別の場所から持ってきたこんなの
jsstr = lambda{|s| s.gsub(/["\\]/){|x| "\\#{x}" } }
を、自分はときどき書くようだ。[1,2,3].map{|x| ... } みたいなのを書くことはもっとある。「|x| x.」の部分が冗長だね、っていう話。
省きたいけど、単純に考えるとブロックの中が x. で始まっていなければいけない。適用範囲が狭いんじゃないか?と疑問がわく。Rubyは「オブジェクト指向スクリプト言語」をうたっているからメソッドチェインを促す意味でサポートを行う名目はあるだろう。
メソッド呼び出しの構文をとりながら自身を引数にしてよそのメソッドを呼べるようにしたり(C#3.0の extension method風)、Hikiがすでにやっているように
class String def escapeHTML CGI::escapeHTML(self) end end
本当にインスタンスメソッドにしてしまったり。CGI::escapeHTML("str") より "str".escapeHTML にしておいた方が(1引数ラムダでの利用に)便利ですよ、という動機付けを言語として行ってもいいと思う。あとは引数の束縛やらラムダの .callと []以外の呼び出し方、ブロックやラムダを用いて定義したブロックを引数にしてメソッドを呼び出したりできれば何かができそうな気がしなくもない予感がなきにしもあらず。
全然関係あるのかないのか、Haskellのポイントフリースタイル(初めて目にしたのは 12月15日頃)を検索してみた。$ で順々に処理をつなげているものがあれば . を使って、途中の処理で必要な引数を押し出していくようにしながら一つの処理に書き換えるのだろうか。ポイントフリーなのにポイント塗れとはこれ如何に? 「firstNLines = (.lines).((unlines.).take)」のように明示されなくなった引数 Nはわかりにくくない? 2引数の関数はポイントフリースタイルで書けるの?『ふつける』を通し読みしただけで、モナドもわからず、一行たりとも Haskellコードを書いたことのない人間には疑問がいっぱいです。
最終更新: 2010-01-15T21:38+0900
C/C++ではもちろん0==false はtrueなわけですが・・・ もしかして、boolean型を整数型とキャスト比較しない仕様なんでしょうか?
この辺はC/C++も難しくて 本質的には 2==trueも本当は真なんですが偽になったりもします。
Rubyでビットフラグをチェックするときは 0との比較を忘れてはいけない。
if(flag & mask) # (意図に反して)必ず実行される end if(0 != flag & mask) # 期待通り end
というのはさておき、2==true に関して、「本質的には」という言葉が胡散臭い。その「本質」を表すコードは「(2!=false) == (true!=false)」であって、2==true は関係ないのでは? ==は真偽値を返す演算子ではあっても、真偽値のみをオペランドにとる演算子ではないから、2==true も 1==true も -1==true までもが暗黙にそのように解釈されて真になったりしたら、むしろ気持ち悪い。それは PHPの世界だ。(偏見)
Cと逆だな
Rubyでは非0が偽だと言いたげですね。
Safe Bool (ja.wikibooks.org)という C++のイディオムがあって、つまり裏を返すと、危険な Boolean変換が C++には存在するということだ。やーめーてー。
C++09(予定)では explicitなコンストラクタみたいに、明示的なキャストを必要とする変換を定義できるようになるらしい。
先人 > Amazon Product Advertising APIの認証の件 - zorioの日記
Ruby-1.8.7と Ruby-1.8.6では String#force_encoding("ASCII-8BIT")ができず、String#ordもない(ないのはエンコーディングの概念がないからと、String#[]で代替できるからだと思われる)。それらを使い分けるために 2種類のメソッドを用意するくらいなら、unpackで配列経由でいいです。
require 'digest/sha2' def hmac_sha256(key, message) hash = Digest::SHA256 hash_block_size = 64 # bytes (= hash.new.block_length) key = hash.digest( key ) if hash_block_size < (key.bytesize rescue key.size) ikey = Array.new( hash_block_size, 0x36 ) okey = Array.new( hash_block_size, 0x5c ) key.unpack("C*").each_with_index{|key_byte, i| ikey[i] ^= key_byte okey[i] ^= key_byte } inner_hash = hash.new.update( ikey.pack("C*") ) outer_hash = hash.new.update( okey.pack("C*") ) digest = outer_hash.update( inner_hash.update( message ).digest ).digest return digest end
短い秘密鍵は 0を補うって書いてあった。その処理が見あたらないのになぜうまくいくのかと考えたら、0を相手に排他的論理和をとったって何も変わらないのねん。
class Digest::Base
- update(str)
- self << str
- 文字列を追加する。self を返す。複数回updateを呼ぶことは文字列を連結してupdateを呼ぶことと等しい。すなわち m.update(a); m.update(b) は m.update(a + b) と、 m << a << b は m << a + b とそれぞれ等価である。
Ruby-1.9で文字列の連結は怖いので m.update(a + b) と m << a + b と Digest::SHA256.digest(ipad + message) は避けたい。
302 Foundはわかる。リバースプロキシは何するもの?
require 'uri' require 'base64' def amazon_authenticated_query_string( host, params ) re_rfc3986_unreserved = /[^A-Za-z0-9\-_.~]/ query_string = params.to_a.sort_by{|x| x.first }.map{|key, value| URI.encode(key, re_rfc3986_unreserved) +'='+ URI.encode(value, re_rfc3986_unreserved) }.join("&") string_to_sign = <<-"STRING_TO_SIGN".gsub(/^\t\t/, '').chomp GET #{host.downcase} /onca/xml #{query_string} STRING_TO_SIGN amazon_secret_access_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" signature = Base64.encode64( hmac_sha256( amazon_secret_access_key, string_to_sign ) ).chomp return "#{query_string}&Signature=#{URI.encode(signature, re_rfc3986_unreserved)}" end
globできない。どういうこと?
irb> Dir.glob("\\\\LS-WSGL420\\MediaLibrary\\*") => [] irb> Dir.entries("\\\\LS-WSGL420\\MediaLibrary") => [".", "..", "anime", "music"]
こういうことだった。
irb> Dir.entries("//LS-WSGL420/MediaLibrary/*") => ["//LS-WSGL420/MediaLibrary/anime", "//LS-WSGL420/MediaLibrary/music"]
DropHandlerから渡されたファイルパスは置換しなければいけない、と。
最終更新: 2009-09-06T20:33+0900
チキンなので日記に書きますよ。
mp3info.rbの中でこのように File.writable? でチェックしているから、renameで @filenameを書きかえていてファイルが存在しないとエラーになる。
raise(Mp3InfoError, "file is not writable") unless File.writable?(@filename)
ここを乗り越えても
file.seek(-TAG1_SIZE, File::SEEK_END)
ファイル末尾から TAG1_SIZEバイト戻ることができなくてエラー。だってそれは新しく作ったサイズ 0 のファイルだから。
I氏のつぼでタグを書き込んでいると、この二つのタグも他と同じく BOM付き UTF-16LE文字列になっているので、mp3info.rbのこの部分で BOMを数字にしようとして 0 になる。
if %w{year tracknum}.include?(key) @tag[key] = tag_value.to_i end
おそらく mp3info/id3v2.rbのこの部分
# remove padding zeros for textual tags out.sub!(/\0*$/, '')
この処理で outのサイズが奇数バイトになることがあって、さらに NKFが末尾の半端な 1バイトを無視するからではないかと。
あと、/\0*$/ というパターンは /\0+$/ でいいんじゃないかな(空文字列を空文字列に置換することもないし)。あとあと、$ でなく \z を使うとか。
(途中なので)メッセージだけ表示してタグの書き込みはしないでおこうと思っていたら、unless と書くべきところが if になっていて広範囲に mp3ファイルを書きかえてしまった。そしたらエクスプローラでジャンルやアルバム、アーティストなどの読み込みと書き込みができなくなってしまった。むむう。
最終更新: 2009-09-02T23:17+0900
/usr/local/lib/compat/libpthread.so.2: Undefined symbol "__malloc_lock" (RuntimeError) /usr/local/lib/ruby/1.8/dl/import.rb:29:in `initialize'
Rubyは 1.8.6 p287だから新しくなってないのだけど。OSが今日の早くに FreeBSDの 6.1から 7.1へ。そしてエラー。
DLを使ってるのは sqlite3-rubyだろうなあ。
Undefined symbol "__malloc_lock"で検索したら Tracが動かなくなった人が見つかった。この人は万事解決といかなかったみたいだけど、自分は libsqlite.soをコンパイルし直したらうまくいった。Rubyや DLの問題ではなく sqliteライブラリの依存関係が原因だったよう。再コンパイルは UNIX系OSのバージョンアップにはつきものの作業なんだろうか。
試してみた。SecurityErrorが出た。リンク先の ruby-1.9.1とは違い、こちらは ruby-1.8.7-p72でのエラー内容。
>irb irb(main):001:0> require 'open-uri' => true irb(main):002:0> $SAFE=1 => 1 irb(main):003:0> open 'http://vvvvvv.sn25p.dip.jp/301.rb' SecurityError: Insecure operation - [] from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:577:in `[]' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:577:in `find_proxy' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:147:in `open_loop' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:164:in `call' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:164:in `open_loop' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:162:in `catch' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:162:in `open_loop' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:132:in `open_uri' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:518:in `open' from C:/Program Files (x86)/ruby/lib/ruby/1.8/open-uri.rb:30:in `open' from (irb):3 irb(main):004:0>
301.rbはこう。
#!ruby require 'cgi' cgi = CGI.new; print cgi.header({ 'Status' => '301 Moved Permanently', 'Location' => 'http://vvvvvv.sn25p.dip.jp/index.html' });
ホストネームを指定しない場合は大丈夫だった。例えばこんな。
#!ruby require 'cgi' cgi = CGI.new; print cgi.header({ 'Status' => '301 Moved Permanently', 'Location' => '/index.html' });
どこまで by designなんだろう。
問題のコード(再掲)はこれ。あるハッシュのキーについて繰り返しているのに、ハッシュにそのキーが存在しない。(すべてのキーが見つからないわけではないが、見つからないキーはいつでも見つからない)
categorized.keys.each do |c| PStore.new(cache_file(c)).transaction do |db| categorized.fetch(c) #=> key not found (KeyError) db['category'] = {} unless db.root?('category') db['category'].update(categorized[c]) end end
fetchをブロックの最初に持って行くと、そこではエラーにならない。
categorized.keys.each do |c| categorized.fetch(c) #=> O.K. PStore.new(cache_file(c)).transaction do |db| db['category'] = {} unless db.root?('category') db['category'].update(categorized[c]) end end
cache_file(c)の呼び出しが原因。その中でも includeしてある ERB::Utilの u()メソッドが核心。
categorized.keys.each do |c| ::ERB::Util.u(c) categorized.fetch(c) #=> key not found (KeyError) PStore.new(cache_file(c)).transaction do |db| db['category'] = {} unless db.root?('category') db['category'].update(categorized[c]) end end
引数にした文字列のエンコーディングが変わってしまっている。
categorized.keys.each do |c| enc1 = c.encoding; ::ERB::Util.u(c) enc2 = c.encoding categorized.fetch(c) { raise "#{enc1} #{enc2} #{::ERB.version}" } #=> UTF-8 ASCII-8BIT erb.rb [2.1.0 2009-01-11] (RuntimeError) PStore.new(cache_file(c)).transaction do |db| db['category'] = {} unless db.root?('category') db['category'].update(categorized[c]) end end
ERB::Util.url_encodeの定義を見ると、引数の文字列を dupした後にエンコーディングを変更しているにも関わらず、呼び出し元に影響を与えてしまっている。
def url_encode(s) s.to_s.dup.force_encoding("ASCII-8BIT").gsub(/[^a-zA-Z0-9_\-.]/n) { sprintf("%%%02X", $&.unpack("C")[0]) } end alias u url_encode
そんなわけだから呼び出し側(category.rb)で
u( c.dup )
なんてやっても効果はなく、
u( ""+c )
あるいは
u( "#{c}" )
とやって初めて、今回の現象を回避することができた。
これは、文字列の複製を遅らせた結果、期せずしておこった現象にみえる。
バグのはずなんだけど、irbで再現しようと思ってもできないんだこれが。
http://redmine.ruby-lang.org/issues/show/1929
(ここに、見るべき場所を見つけることもできなかった人間がひとり)
* 正しくは ruby-1.9.2dev(2009-02-03)
この断片で理解してもらえるだろうか。
categorized.keys.each do |c| PStore.new(cache_file(c)).transaction do |db| categorized.fetch(c) #=> key not found (KeyError) db['category'] = {} unless db.root?('category') db['category'].update(categorized[c]) end end
あるハッシュのキーについて繰り返しているのに、ハッシュにはそのキーが存在しないという、この不思議。
このとき、c は、
"\xE6\x9C\xAC\xE6\x97\xA5\xE3\x81\xAE\xE8\xB3\xBC\xE5\x85\xA5" ASCII-8BIT
ハッシュのキーリストとそのエンコーディングは、
"\xE6\x9C\xAC\xE6\x97\xA5\xE3\x81\xAE\xE8\xB3\xBC\xE5\x85\xA5" ASCII-8BIT "本" UTF-8 "マンガ" UTF-8 "雑誌" UTF-8
存在しているだろうに……。
さくらインターネット上で tDiary を ruby1.9.1-p0 で動かす - まちゅダイアリー(2009-02-19)(14:57現在、日別表示が不可能な状態。最新表示は可能)
* 正しくは ruby-1.9.2dev(2009-02-03)
最終更新: 2009-08-29T06:18+0900
> URI.escape("http://exmaple.com/foo bar?a=b=c;d=e#hoge#fuga") => "http://exmaple.com/foo%20bar?a=b=c;d=e%23hoge%23fuga"
URLをパーツに分解せずに gsub一発でエスケープ(エンコード)するなんて不可能だと思うのです。仮にそうまでしても、上の例のように、「#」が CGIパラメータとして受け渡されるように、「%23」にエンコードするべきか、URLを構成する fragmentを示す記号として、「#」のまま残すべきか、判断できない(でしょう?)。
できないことをできるかのように見せかけるより、できない(のでメソッドを提供しない)という方が親切だと思う。
JavaScriptの提供する encodeURIComponent(「/」も「?」も「#」も「=」もエンコードするメソッド)相当のメソッドがあれば、それで十分だと思うんだけど。
http://exmaple.com/foo bar/baz/
なんてのは不正な URLだから、どこにも存在しないのです。アドレスバーに表示するときに人間に読みやすいように、と上のようにデコードして表示することはあるかもしれないけれど、オリジナルの正しい URLは同時に保持しておくべきで、表示用の文字列からエンコードしようなんて思っちゃダメなのです。ブラウザのアドレスバーは表示と同時に編集も可能だから話は簡単ではないけど……これまでのところ、Firefoxは(自らの責任で)よきに計らってくれています。
RFC(古いのと新しいのと二種類?)も読んでいない放言なので、実は「できる」という可能性がなきにしもあらず。
[ruby-dev:39188] Re: URI.escape_component から、
さておき、URI.escapeは独特の動きをするので、JavaScript/ECMAScript の escape とも encodeURI とも encodeURIComponent とも違います。 そういえば、現在の仕様の意図をお聞きしたかったんだった [ruby-dev:38124] なお、面倒な話が出てきた時には、とりあえず akr さんが 既に取り上げていないか調べるわけですが、やっぱり扱っているわけです。 http://www.a-k-r.org/d/d2004_07.html https://www.codeblog.org/blog/akr/20070222.html
requireの処理の中で、セーフレベルを一時的に 0 に下げる部分があって、この範囲を広げることで、SecurityErrorが出ないようになっていた。(3日前)
画像は、tDiaryの amazon.rbが、リポジトリから持ってきただけの素の Ruby-1.9.2dev(2009-02-03)上で動いているところ。
既に正式版がリリースされて*しまって*いる Ruby-1.9.1との違いを、先月の日記に書いた例で見ていくと、
irb192> RUBY_DESCRIPTION => "ruby 1.9.2dev (2009-02-03) [i386-mswin32_90]" irb192> $SAFE=1 => 1 irb192> require "a" SecurityError: cannot load from insecure path - Y:/.../Desktop/a.rb from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ruby/bin/irb192.bat:20:in `<main>' irb192> require "a.rb" SecurityError: cannot load from insecure path - Y:/.../Desktop/a.rb from (irb):4:in `require' from (irb):4 from C:/Program Files (x86)/ruby/bin/irb192.bat:20:in `<main>' irb192>
2009-02-03版の ruby-1.9.2devでは、$SAFE=1のとき、カレントディレクトリのスクリプトを requireできない。これは $:($LOAD_PATH)に汚染されていない "." が含まれていようと、require の引数の文字列が汚染されていなかろうと、requireできない。見つけた抜け道は、絶対パスで requireするか、$:($LOAD_PATH)にカレントディレクトリを絶対パスで追加すること("."が含まれる場合はそれより前に追加する必要もある)。
ruby-1.9.1の結果は Release Candidateのときと変わらず、
irb191> RUBY_DESCRIPTION => "ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-mswin32]" irb191> $SAFE=1 => 1 irb191> require "a" SecurityError: Insecure operation - require from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191> require "a.rb" a.rb required. => true irb191>
ruby-1.9.1では、拡張子を付けてやるとカレントディレクトリのスクリプトも requireできる。拡張子を付けないときに requireできないのは ruby-1.9.2devも同じだが 、ruby-1.9.1には ruby-1.9.2devにない爆弾がある。$SAFE=1のときの拡張子を付けない requireは、添付ライブラリの requireであっても失敗したりする。
irb191> $SAFE=1 => 1 irb191> require "stringio" SecurityError: Insecure operation - require from (irb):2:in `require' from (irb):2 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191> require "stringio.so" => true irb191>
これが原因で、tDiaryを ruby-1.9.1で動かすのは絶望的だと思っている*。(ruby-1.9.1はリリースされちゃったし、スクリプトで対応できる範囲を超えているから)
この部分は SecurityErrorが出ない方向に修正されると思っていたから、ruby-1.9.2devで両方のパターンが SecurityErrorになったのは意外。
ruby-1.9.2devでは、汚染されたパスが $:($LOAD_PATH)のどの位置にあるかが重要。
irb192> $SAFE=1 => 1 irb192> $:.unshift "!tainted!".taint => ["!tainted!", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2/i386-mswin32_90", "."] irb192> require "cgi" SecurityError: cannot load from insecure path - Y:/.../Desktop/!tainted!/cgi.rb from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ruby/bin/irb192.bat:20:in `<main>' irb192> $:.push $:.shift => ["C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2/i386-mswin32_90", ".", "!tainted!"] irb192> require "cgi" => true irb192>
ruby-1.9.2devでは、$SAFE=1で、汚染された LOAD_PATHからスクリプトを requireすることはできないが、汚染されていない LOAD_PATHからスクリプトを先に見つけた場合は、requireに成功する。ruby-1.9.1(とruby-1.8.7-p72)ではどうだったかというと、より厳しくて、
irb191> $SAFE=1 => 1 irb191> $:.unshift "!tainted!".taint => ["!tainted!", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/i386-mswin32", "."] irb191> require "cgi" SecurityError: Insecure operation - require from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191> $:.push $:.shift => ["C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/i386-mswin32", ".", "!tainted!"] irb191> require "cgi" SecurityError: Insecure operation - require from (irb):5:in `require' from (irb):5 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191>
ruby-1.9.1では、汚染されたパスが一つでも $:($LOAD_PATH)に含まれると、(相対パスでの) requireはできない。絶対パスならできるが、そんな書き方はしないので $:($LOAD_PATH)に追加するパスは常に untaintしなければならない。
requireしたときのカレントディレクトリの扱いに、不満が残る。
多分、従来の Ruby、ruby-1.9.1や ruby-1.8.7は $:($LOAD_PATH)の要素の汚染状況なんて気にしていなかった。$:に含まれるか含まれないかだけが重要。2005年から現在まで変わっていない、リファレンスマニュアルのこの記述がそれを物語っていないか。
Level 1以上では起動時に以下の違いがある
- 環境変数 RUBYLIB を $: に加えない
- カレントディレクトリを $: に加えない
- (以下略)
その方針に異議を唱えるものではないけれど、この方式には、セーフレベルによる分岐が起動時に限定されるという制限がある。コマンドラインオプション -T を指定したときにしか有効にならず、$SAFEを使ってセーフレベルをコントロールするシナリオでは機能しない。
ruby-1.9.2devは、$:($LOAD_PATH) の要素の汚染状況に注目することで、$SAFEを使ってセールレベルをコントロールする場合でも「環境変数 RUBYLIB を $: に加えない」「カレントディレクトリを $: に加えない」のと同等の制限を課せる可能性を持っている。つまり、「環境変数RUBYLIBとカレントディレクトリは常に $:($LOAD_PATH)に追加される。ただし汚染された状態で。」ということ。
それなのに現在の ruby-1.9.2devの、$SAFE=1の下での requireに対するカレントディレクトリの扱いは、ruby-1.8.7より、むしろ退化している。
ruby-1.8.7では $:($LOAD_PATH)に "." を追加したり取り除いたりすることでカレントディレクトリの扱いを、セーフレベルによらず、スクリプトがコントロールできた。
拡張子の有無で結果が変わる、動作に筋の通らない ruby-1.9.1の requireは論外として、
ruby-1.9.2devでは、カレントディレクトリのスクリプトの requireは $:($LOAD_PATH)によらず、SecurityErrorになる。カレントディレクトリを絶対パスで $:($LOAD_PATH)に追加することでコントロール可能だが、既に含まれているかもしれない "." が邪魔をする。ruby-1.9.2devは、$:($LOAD_PATH)に含まれる "." の汚染状況(あるいは "."が $:に含まれないこと)によってのみカレントディレクトリの扱いを変えるべきで、カレントディレクトリが特別に SecurityErrorになる、現在の ruby-1.9.2devには同意できない。
セールレベル1ではまだスクリプトを信用しているのだから、少なくとも ruby-1.8.7と同等のコントロールをスクリプトに渡して欲しい。
requireが利用する File.expand_pathの仕様によりカレントディレクトリからの相対パスが汚染されるのだろうが、File.expand_pathは requireが $:($LOAD_PATH)に依拠していることを知らないのだから requireが何とかすべき。
* その後の変更で、ruby-1.9.1らしき挙動の requireを検出したときはセーフレベルを 1から 0に下げてプラグインを実行することになっている。セーフレベル0なら今回のことは関係ないから。セーフレベルを下げるという発想は全く頭になかった。なんか、こう、負けた気がするからだろう。ruby-1.9.0だとセキュアモード(セーフレベル4)で動かないという話もあったけどどうなったんだろう。
>irb irb(main):001:0> RUBY_DESCRIPTION => "ruby 1.8.7 (2008-05-31 patchlevel 0) [i386-mswin32_90]" irb(main):002:0> $SAFE=1 => 1 irb(main):003:0> $:.unshift "hoge".taint => ["hoge", "C:/Program Files (x86)/ruby/lib/ruby/site_ruby/1.8",..., "."] irb(main):004:0> require 'cgi' SecurityError: Insecure operation - require from (irb):4:in `require' from (irb):4 from :0 irb(main):005:0>
1.8.7でもこうなのだから知らない俺が抜けているのだが、$SAFE=1のときに汚染された文字列を $LOAD_PATHに追加する(pushでも unshiftでも)と、一切の requireができなくなる。
期待したいのは、hoge/cgi.rbや hoge/cgi.soなどが存在するときには、このパスは汚染されているので SecurityError。存在しないときはファイルの探索を続けて C:/Program Files (x86)/ruby/lib/ruby/1.8/cgi.rb (このパスは汚染されていないはず)を読み込む、という動作なのだけど……。
実際はそうではないのだから tDiaryの
tdiary.rb:12: $:.insert( 1, File::dirname( __FILE__ ) + '/misc/lib' )
というのは __FILE__ が汚染されているときに、わりと危険な操作ということになる。問題が起きないのは SAKURAのレンタルサーバの Rubyが 1.8.6だからなのか、__FILE__、File.dirname(__FILE__) が汚染されていることが稀だからなのか。(汚染されていることが実際にあるのは、20090117p01で書いたように、untaintすることで状況が改善したことから推測できる)
問題が起きないのは SAKURAのレンタルサーバの Rubyが 1.8.6だからなのか、__FILE__、File.dirname(__FILE__) が汚染されていることが稀だからなのか。
両方でした。__FILE__が汚染されているのは Ruby-1.9.1RC1だから(多分)。
$SAFE=1のときに汚染された文字列を $LOAD_PATHに追加する(pushでも unshiftでも)と、一切の requireができなくなる。
書きながら誇張だとは気付いていたのだけど(一切の、の部分が)、そうでない例を自分で見つけたので追記(2009-02-02)。
Windowsで、フルパスで、あるいは拡張子(.rb)なしのフルパスでなら requireできる。
Y:\...\Desktop\a>irb irb(main):001:0> RUBY_DESCRIPTION => "ruby 1.8.7 (2008-05-31 patchlevel 0) [i386-mswin32_90]" irb(main):002:0> $SAFE=1 => 1 irb(main):003:0> $:.push "".taint => ["C:/Program Files (x86)/ruby/lib/ruby/site_ruby/1.8", "C:/Program Files (x86)/ruby/lib/ruby/site_ruby/1.8/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby/site_ruby", "C:/Program Files (x86)/ruby/lib/ruby/vendor_ruby/1.8", "C:/Program Files (x86)/ruby/lib/ruby/vendor_ruby/1.8/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby/vendor_ruby", "C:/Program Files (x86)/ruby/lib/ruby/1.8", "C:/Program Files (x86)/ruby/lib/ruby/1.8/i386-mswin32_90", ".", ""] irb(main):004:0> require "a" SecurityError: Insecure operation - require from (irb):4:in `require' from (irb):4 from :0 irb(main):005:0> require "a.rb" SecurityError: Insecure operation - require from (irb):5:in `require' from (irb):5 from :0 irb(main):006:0> require "./a.rb" SecurityError: Insecure operation - require from (irb):6:in `require' from (irb):6 from :0 irb(main):007:0> require "Y:/.../Desktop/a/a" => true irb(main):008:0> require "Y:/.../Desktop/a/a.rb" => false irb(main):009:0> require "a" SecurityError: Insecure operation - require from (irb):9:in `require' from (irb):9 from :0 irb(main):010:0>
♭ BorsJoigreeBS GE VC KX LJ UZ ZY RW MS IS ML JE AL FWUN BR EJ AV DD XQ..
♭ ds14050解析班はどこだ?