odeの開発メモ日記

プログラマーやってます。今までの道のり ポケコンbasic→C(DirectX5 ネットやろうぜ)→Perl(ちょろっと)→Java→C#→Ruby→Android(Java)

クッキーを使わないでリクエストパラメータ(params)でセッションIDを渡す場合

諸事情でセッションIDがクッキーで渡されなく、リクエストパラメータ(クエリーストリングやpost等)
で渡される場合がたまーにあります。
(外部アプリとの連携をする場合等に発生するケースが多いかなと思います。)


こういう場合はsessionメソッドで:cookie_only=>falseを指定すれば
クッキーだけでなく、リクエストパラメータにある場合でも取得できるはずでした。。
ええ、過去形です。
rails2からは:cookie_only=>falseにしてもリクエストパラメータを参照しないように
変更されてました。。バグ?


問題の箇所
query_extension.rb

class CGI #:nodoc:
  module QueryExtension
    # Remove the old initialize_query method before redefining it.
    remove_method :initialize_query

    # Neuter CGI parameter parsing.
    def initialize_query
      # Fix some strange request environments.
      env_table['REQUEST_METHOD'] ||= 'GET'

      # POST assumes missing Content-Type is application/x-www-form-urlencoded.
      if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
        env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
      end

      @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
      @params = {}
    end
  end
end

@params = {}としているのが原因です。
ここが実際にセッションIDを特定するのに参照している部分だからです。
標準ライブラリのセッションクラスはセッションIDを特定するのに最初にparamsを、なかったらクッキーを見に行く実装になってます。
なので@paramsが消されるとパラメータで見ることはできなくなってしまいます。

cgi/session.rb

	if request.key?(session_key)
	  session_id = request[session_key]
	  session_id = session_id.read if session_id.respond_to?(:read)
	end
	unless session_id
	  session_id, = request.cookies[session_key]
	end
	unless session_id
	  unless option.fetch('new_session', true)
	    raise ArgumentError, "session_key `%s' should be supplied"%session_key
	  end
	  session_id = create_new_id
	end




皆さんこの問題にはまっているみたいで特にjpmobileでは
携帯のためクッキーが使えないので常に:cookie_only=>falseを内部的に使用していました。
そのためrails2が出た当初はうまくうごかなかったようです。
すぐにパッチが出て、直ったみたいですが。
とうことで、このパッチを適用すれば大体は解決できます。

trans_sid.rb

        unless ::CGI::Session.private_method_defined?(:initialize_without_session_key_fixation)
          ::CGI::Session.class_eval do
            alias_method :initialize_without_session_key_fixation, :initialize
            def initialize(cgi, options = {})
              key = options['session_key']
              if cgi.cookies[key].empty?
                session_id = (CGI.parse(cgi.env_table['RAW_POST_DATA'])[key] rescue nil) ||
                  (CGI.parse(ENV['QUERY_STRING'] || cgi.query_string)[key] rescue nil)
                cgi.params[key] = session_id unless session_id.blank?
              end
              initialize_without_session_key_fixation(cgi, options)
            end
          end
        end

大抵は問題ないんですが、私はひっかかりました。。
ファイルアップロード等のmultipartなpostに対応してないみたいですorz
いろいろ対応してみたのですが(railsによるinitialize_queryの上書きをやめたり、rails1のにしてみたり)結局すぐは直し方はわからなかったのであきらめました。
(時間かければなおせそうですが)



なので取り急ぎの対応的には
直接sessionライブラリを使用して取得しちゃいました。

      session_options = HashWithIndifferentAccess.new
      session_options.merge!({
        :session_id => params[:session]
      }).merge!(ActionController::Base.session_options)
      session_tmp = CGI::Session.new(CGI.new, session_options )
      session_tmp[:hoge] = "hogehoge value"
      session_tmp.update
      session_tmp.close

(独自クラスをセッションにいれている場合は
ロード時にクラスがないという感じのエラーになる場合がありました。
その場合は手前でHoge.newとかで一度クラスを認識させると問題なくなります。)



multipartに対応しなくていいならjpmobileのパッチ適用後に下記が使えます。
特定のactionのみで、かつ特定のパラメータが来てた場合のみparamsの値をsession_idにする指定の仕方
(例:actionはhogeのみ。paramsにくるsession_idのkeyはsession_id_dayo。paramsのmodeがtestの場合のみ)

  session :only=>:hoge, :cookie_only=>false, :session_key => 'session_id_dayo',
  :if => Proc.new { |req|
    req.parameters[:mode] == "test"
    }

実行環境

rails(2.1.2)