class Rack::JSONP

A Rack middleware for providing JSON-P support.

Full credit to Flinn Mueller (actsasflinn.com/) for this contribution.

Constants

U2029
VALID_CALLBACK
VALID_JS_VAR

Public Class Methods

new(app) click to toggle source
# File lib/rack/contrib/jsonp.rb, line 25
def initialize(app)
  @app = app
end

Public Instance Methods

call(env) click to toggle source

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 callback param is specified.

# File lib/rack/contrib/jsonp.rb, line 35
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 = HeaderHash.new(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.to_ary.inject(0) { |len, part| len + bytesize(part) }
      headers['Content-Length'] = length.to_s
    end
  end

  [status, headers, response]
end

Private Instance Methods

bad_request(body = "Bad Request") click to toggle source
# File lib/rack/contrib/jsonp.rb, line 109
def bad_request(body = "Bad Request")
  [ 400, { 'Content-Type' => 'text/plain', 'Content-Length' => body.size.to_s }, [body] ]
end
has_callback?(request) click to toggle source
# File lib/rack/contrib/jsonp.rb, line 71
def has_callback?(request)
  request.params.include?('callback') and not request.params['callback'].to_s.empty?
end
is_json?(headers) click to toggle source
# File lib/rack/contrib/jsonp.rb, line 67
def is_json?(headers)
  headers.key?('Content-Type') && headers['Content-Type'].include?('application/json')
end
pad(callback, response, body = "") click to toggle source

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.

# File lib/rack/contrib/jsonp.rb, line 91
def pad(callback, response, body = "")
  response.each 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.
    body << s.to_s.gsub(U2028, '\u2028').gsub(U2029, '\u2029')
  end
  
  # https://github.com/rack/rack-contrib/issues/46
  response.close if response.respond_to?(:close)

  ["/**/#{callback}(#{body})"]
end
valid_callback?(callback) click to toggle source

See: stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names

NOTE: Supports dots (.) since callbacks are often in objects:

# File lib/rack/contrib/jsonp.rb, line 80
def valid_callback?(callback)
  callback =~ VALID_CALLBACK
end