-
Notifications
You must be signed in to change notification settings - Fork 221
/
jsonp.rb
118 lines (94 loc) · 3.74 KB
/
jsonp.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# frozen_string_literal: true
module Rack
# A Rack middleware for providing JSON-P support.
#
# Full credit to Flinn Mueller (http://actsasflinn.com/) for this contribution.
#
class JSONP
include Rack::Utils
VALID_CALLBACK = /\A[a-zA-Z_$](?:\.?[\w$])*\z/
# These hold the Unicode characters \u2028 and \u2029.
#
# They are defined in constants for Ruby 1.8 compatibility.
#
# In 1.8
# "\u2028" # => "u2028"
# "\u2029" # => "u2029"
# In 1.9
# "\342\200\250" # => "\u2028"
# "\342\200\251" # => "\u2029"
U2028, U2029 = ("\u2028" == 'u2028') ? ["\342\200\250", "\342\200\251"] : ["\u2028", "\u2029"]
HEADERS_KLASS = Rack.release < "3" ? Utils::HeaderHash : Headers
private_constant :HEADERS_KLASS
def initialize(app)
@app = app
end
# Proxies the request to the application, stripping out the JSON-P callback
# method and padding the response with the appropriate callback format if
# the returned body is application/json
#
# Changes nothing if no <tt>callback</tt> param is specified.
#
def call(env)
request = Rack::Request.new(env)
status, headers, response = @app.call(env)
if STATUS_WITH_NO_ENTITY_BODY.include?(status)
return status, headers, response
end
headers = HEADERS_KLASS.new.merge(headers)
if is_json?(headers) && has_callback?(request)
callback = request.params['callback']
return bad_request unless valid_callback?(callback)
response = pad(callback, response)
# No longer json, its javascript!
headers['Content-Type'] = headers['Content-Type'].gsub('json', 'javascript')
# Set new Content-Length, if it was set before we mutated the response body
if headers['Content-Length']
length = response.map(&:bytesize).reduce(0, :+)
headers['Content-Length'] = length.to_s
end
end
[status, headers, response]
end
private
def is_json?(headers)
headers.key?('Content-Type') && headers['Content-Type'].include?('application/json')
end
def has_callback?(request)
request.params.include?('callback') and not request.params['callback'].to_s.empty?
end
# See:
# http://stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names
#
# NOTE: Supports dots (.) since callbacks are often in objects:
#
def valid_callback?(callback)
callback =~ VALID_CALLBACK
end
# Pads the response with the appropriate callback format according to the
# JSON-P spec/requirements.
#
# The Rack response spec indicates that it should be enumerable. The
# method of combining all of the data into a single string makes sense
# since JSON is returned as a full string.
#
def pad(callback, response)
body = response.to_enum.map do |s|
# U+2028 and U+2029 are allowed inside strings in JSON (as all literal
# Unicode characters) but JavaScript defines them as newline
# seperators. Because no literal newlines are allowed in a string, this
# causes a ParseError in the browser. We work around this issue by
# replacing them with the escaped version. This should be safe because
# according to the JSON spec, these characters are *only* valid inside
# a string and should therefore not be present any other places.
s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
end.join
# https://github.com/rack/rack-contrib/issues/46
response.close if response.respond_to?(:close)
["/**/#{callback}(#{body})"]
end
def bad_request(body = "Bad Request")
[ 400, { 'content-type' => 'text/plain', 'content-length' => body.bytesize.to_s }, [body] ]
end
end
end