Skip to content

Commit 1ba4664

Browse files
etiennebarriebyroot
andcommitted
Don't escape JSON when unnecessary
When `render json:` is used in a controller, and there's no callback option (used for JSONP), the resulting JSON document doesn't need to be HTML-safe (no need to escape HTML entities) or embeddable into JavaScript (no need to escape U+2028 and U+2029). This both saves a costly operation and renders cleaner JSON. Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
1 parent dd44466 commit 1ba4664

File tree

2 files changed

+22
-1
lines changed

2 files changed

+22
-1
lines changed

actionpack/lib/action_controller/metal/renderers.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ def _render_to_body_with_renderer(options)
153153

154154
add :json do |json, options|
155155
json_options = options.except(:callback, :content_type, :status)
156+
json_options[:escape] ||= false unless options[:callback].present?
156157
json = json.to_json(json_options) unless json.kind_of?(String)
157158

158159
if options[:callback].present?

actionpack/test/controller/render_json_test.rb

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ def render_json_hello_world_with_callback
4646
render json: ActiveSupport::JSON.encode(hello: "world"), callback: "alert"
4747
end
4848

49+
def render_json_unsafe_chars_with_callback
50+
render json: { hello: "\u2028\u2029<script>" }, callback: "alert"
51+
end
52+
53+
def render_json_unsafe_chars_without_callback
54+
render json: { hello: "\u2028\u2029<script>" }
55+
end
56+
4957
def render_json_with_custom_content_type
5058
render json: ActiveSupport::JSON.encode(hello: "world"), content_type: "text/javascript"
5159
end
@@ -106,6 +114,18 @@ def test_render_json_with_callback
106114
assert_equal "text/javascript", @response.media_type
107115
end
108116

117+
def test_render_json_with_callback_escapes_js_chars
118+
get :render_json_unsafe_chars_with_callback, xhr: true
119+
assert_equal '/**/alert({"hello":"\\u2028\\u2029\\u003cscript\\u003e"})', @response.body
120+
assert_equal "text/javascript", @response.media_type
121+
end
122+
123+
def test_render_json_without_callback_does_not_escape_js_chars
124+
get :render_json_unsafe_chars_without_callback
125+
assert_equal %({"hello":"\u2028\u2029<script>"}), @response.body
126+
assert_equal "application/json", @response.media_type
127+
end
128+
109129
def test_render_json_with_custom_content_type
110130
get :render_json_with_custom_content_type, xhr: true
111131
assert_equal '{"hello":"world"}', @response.body
@@ -137,6 +157,6 @@ def test_render_json_calls_to_json_from_object
137157

138158
def test_render_json_avoids_view_options
139159
get :render_json_inspect_options
140-
assert_equal '{"options":{}}', @response.body
160+
assert_equal '{"options":{"escape":false}}', @response.body
141161
end
142162
end

0 commit comments

Comments
 (0)