LoginSignup
15
11

More than 5 years have passed since last update.

空白スペースが+になる理由(railsソースコードリーディング)

Last updated at Posted at 2016-02-10

環境

  • rails 4.2.0
  • ruby 2.2.3

状況

railsにおいて

index.erb
= link_to new_xxx_path(title: 'なぜか デコードできない')

とすると、htmlにおいて

index.erb
<a href="/xxx/new?title=%E3%81%AA%E3%81%9C%E3%81%8B+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">

というリンクになっている。このままこのリンクを踏んで、遷移先において

new.erb
= params[:title]

とすると、

なぜか+デコードできない

と表示されてしまう。空白スペースがいつの間にかplus(+)に変わってしまい、デコードする時に、plus(+)がそのままになってしまっているという問題。

背景

そもそもHTTPにおいて、GETやPOSTのパラメーターはapplication/x-www-form-urlencodedという形式に変換されるものである。

application/x-www-form-urlencodedという形式に関しては
https://wiki.suikawiki.org/n/application%2Fx-www-form-urlencoded
あたりを参考に。

大事なことは、application/x-www-form-urlencodedという形式に沿ってエンコードすると、 空白スペースが「+」に変換されるということである。

原因

さて、railsにおいていつapplication/x-www-form-urlencodedという形式にparameterが変換されてしまうかを探る旅に出るとしよう。

rails自体を探るためにrailsのソースコードを引っ張ってきた。
https://github.com/rails/rails

.
├── actioncable
├── actionmailer
├── actionpack
├── actionview
├── activejob
├── activemodel
├── activerecord
├── activesupport

メインっぽいのをあげたが、この中で今回見るのは、 actionpack

actionpackの中は以下のようになっていて、このうち、 action_dispatch という部分に着目して原因の追究を進める。

.
├── CHANGELOG.md
├── MIT-LICENSE
├── README.rdoc
├── Rakefile
├── actionpack.gemspec
├── bin
│   └── test
├── lib
│   ├── abstract_controller
│   ├── abstract_controller.rb
│   ├── action_controller
│   ├── action_controller.rb
│   ├── action_dispatch
│   ├── action_dispatch.rb
│   ├── action_pack
│   └── action_pack.rb
└── test
    ├── abstract
    ├── abstract_unit.rb
    ├── assertions
    ├── controller
    ├── dispatch
    ├── fixtures
    ├── journey
    ├── lib
    ├── routing
    └── tmp

action_dispatchとは

Action Dispatchはwebリクエストに関する情報を解析して、ユーザによって定義された通りルーティングの処理をする。 そして

  • MIME-type negotiation
  • POSTやPATCH、PUT本体内のパラメータのデコード
  • HTTP キャッシングロジック、クッキー、セッションの処理

といったようなHTTPに関連したより高度な処理をする

ざっと言うと、こんな感じの役割を果たしているそうなので、まさに今回の問題に絡む張本人といった感じである。

ソースコードリーディング

そして、関連するソースコードは、

├── lib
│   ├── action_dispatch
│   │   └── testing
│   │       ├── integration.rb

ここにある。integration.rbの中にある。
integration.rbを読んでいくと、関連する部分は以下のようになっている。

action_dispatch.rb
module ActionDispatch
  module Integration
    module RequestHelpers
      def get(path, *args)
        process_with_kwargs(:get, path, *args)
      end

      class Session
        private
          def process_with_kwargs(http_method, path, *args)
            if kwarg_request?(args)
              process(http_method, path, *args)
            else
              non_kwarg_request_warning if args.any?
              process(http_method, path, { params: args[0], headers: args[1] })
            end
          end
          REQUEST_KWARGS = %i(params headers env xhr)
          def kwarg_request?(args)
            args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) }
          end
          def process(method, path, params: nil, headers: nil, env: nil, xhr: false)
            ...

            request_env = {
              ...

              :params => params,

              ...

              "CONTENT_TYPE" => "application/x-www-form-urlencoded",

              ...
            }
          end
      end
    end
  end
end

というわけで、ここでencodeに関することを規定していたわけである。

原因のまとめ

以上のことからわかったのは、

index.erb
new_xxx_path(title: 'なぜか デコードできない')

と記述すると、gemの方に送られて、application/x-www-form-urlencodedの形式でエンコードされるという指定を受けたurlが返ってくるわけである。

なので、空白スペースが勝手に+に変換されてしまったように見えたのである。

まとめ

+を無理やり変更するには、

index.erb
new_xxx_path(title: 'なぜか デコードできない')

で返ってきた値の中の+を %20 に変えるコードを記述する

index.erb
= link_to '新規作成', new_xxx_path(title: 'なぜか デコードできない').gusb("+", "%20")

とか、デコードするコードをapplication/x-www-form-urlencodedの形式でデコードするものに修正

= URI.decode_www_form_component("%E3%81%AA%E3%81%9C%E3%81%8B+%E3%83%87%E3%82%B3%E3%83%BC%E3%83%89%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84")

# => "なぜか デコードできない"

すればなんとかできなくもないということである。

15
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11