読者です 読者をやめる 読者になる 読者になる

ActionWebServiceの引数にXML特殊文字を2個続けて渡すとエラーが発生する問題、の続き

の続き。原因がわかった。AWSのバージョンは1.1.6。ActionPackのバージョンは1.12.5。

エラーが発生している箇所は、ActionPack内のcgi_methods.rbで定義されている CGIMethods.parse_request_parameters(params) メソッド。以下がそのソース。

    # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" / 
    # "Somewhere cool!" are translated into a full hash hierarchy, like
    # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
    def CGIMethods.parse_request_parameters(params)
      parsed_params = {}

      for key, value in params
        value = [value] if key =~ /.*\[\]$/
        unless key.include?('[') # ← ★★★★ここでエラー! key==nil ★★★★
          # much faster to test for the most common case first (GET)
          # and avoid the call to build_deep_hash
          parsed_params[key] = get_typed_value(value[0])
        else
          build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
        end
      end
    
      parsed_params
    end

これを呼んでいるのは、cgi_process.rbのrequest_parameterメソッドで、

          CGIMethods.parse_request_parameters(@cgi.params)

@cgi.paramsが返すハッシュは、CGI::parseによって構築される。

  # Parse an HTTP query string into a hash of key=>value pairs.
  #
  #   params = CGI::parse("query_string")
  #     # {"name1" => ["value1", "value2", ...],
  #     #  "name2" => ["value1", "value2", ...], ... }
  #
  def CGI::parse(query)
    params = Hash.new([].freeze)

    query.split(/[&;]/n).each do |pairs| # ← ココ。&か;でsplitする。
      key, value = pairs.split('=',2).collect{|v| CGI::unescape(v) }
      if params.has_key?(key)
        params[key].push(value)
      else
        params[key] = [value]
      end
    end

    params
  end

CGI::parseは、POSTのbodyに文字実体参照を2個連続で含むXMLが渡されると、以下のようにパースする。

$ irb
>> CGI::parse("<?xml version=\"1.0\"><body>&lt;&lt;</body>")
=> {"<?xml version"=>[""1.0"><body>"], "</body>"=>[nil], nil=>[nil], "lt"=>[nil, nil]}

最初に&と;でsplitするので、

&lt;&lt;
↓
nil=>[nil], "lt"=>[nil, nil]

とパースされてしまう。「;&」で、キーも値もnilの要素ができる。

これがkey.include?がエラーになる原因。試しにSOAPの引数に、「;<」のような「;+特殊文字」を渡しても、エラーになることが確認できた。

とりあえず安直に、

for key, value in params
  next if key.nil? # ← この行を追加
  value = [value] if key =~ /.*\[\]$/
  unless key.include?('[')
    :
    :

とすると、エラーは出なくなった。


でも、そもそもSOAPリクエストに対して、parse_request_parametersみたいな処理は不要なはず。最新版で修正されてそうな予感がする。

(追記) rails-1.2.3で試したところ、エラーは発生しなくなっているようでした。