1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionview/app/assets/javascripts/rails-ujs/utils/ajax.coffee
Andrew White 31abee0341 Add support for automatic nonce generation for Rails UJS
Because the UJS library creates a script tag to process responses it
normally requires the script-src attribute of the content security
policy to include 'unsafe-inline'.

To work around this we generate a per-request nonce value that is
embedded in a meta tag in a similar fashion to how CSRF protection
embeds its token in a meta tag. The UJS library can then read the
nonce value and set it on the dynamically generated script tag to
enable it to execute without needing 'unsafe-inline' enabled.

Nonce generation isn't 100% safe - if your script tag is including
user generated content in someway then it may be possible to exploit
an XSS vulnerability which can take advantage of the nonce. It is
however an improvement on a blanket permission for inline scripts.

It is also possible to use the nonce within your own script tags by
using `nonce: true` to set the nonce value on the tag, e.g

    <%= javascript_tag nonce: true do %>
      alert('Hello, World!');
    <% end %>

Fixes #31689.
2018-02-19 15:59:34 +00:00

97 lines
3.6 KiB
CoffeeScript

#= require ./csp
#= require ./csrf
#= require ./event
{ cspNonce, CSRFProtection, fire } = Rails
AcceptHeaders =
'*': '*/*'
text: 'text/plain'
html: 'text/html'
xml: 'application/xml, text/xml'
json: 'application/json, text/javascript'
script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
Rails.ajax = (options) ->
options = prepareOptions(options)
xhr = createXHR options, ->
response = processResponse(xhr.response ? xhr.responseText, xhr.getResponseHeader('Content-Type'))
if xhr.status // 100 == 2
options.success?(response, xhr.statusText, xhr)
else
options.error?(response, xhr.statusText, xhr)
options.complete?(xhr, xhr.statusText)
if options.beforeSend? && !options.beforeSend(xhr, options)
return false
if xhr.readyState is XMLHttpRequest.OPENED
xhr.send(options.data)
prepareOptions = (options) ->
options.url = options.url or location.href
options.type = options.type.toUpperCase()
# append data to url if it's a GET request
if options.type is 'GET' and options.data
if options.url.indexOf('?') < 0
options.url += '?' + options.data
else
options.url += '&' + options.data
# Use "*" as default dataType
options.dataType = '*' unless AcceptHeaders[options.dataType]?
options.accept = AcceptHeaders[options.dataType]
options.accept += ', */*; q=0.01' if options.dataType isnt '*'
options
createXHR = (options, done) ->
xhr = new XMLHttpRequest()
# Open and setup xhr
xhr.open(options.type, options.url, true)
xhr.setRequestHeader('Accept', options.accept)
# Set Content-Type only when sending a string
# Sending FormData will automatically set Content-Type to multipart/form-data
if typeof options.data is 'string'
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') unless options.crossDomain
# Add X-CSRF-Token
CSRFProtection(xhr)
xhr.withCredentials = !!options.withCredentials
xhr.onreadystatechange = ->
done(xhr) if xhr.readyState is XMLHttpRequest.DONE
xhr
processResponse = (response, type) ->
if typeof response is 'string' and typeof type is 'string'
if type.match(/\bjson\b/)
try response = JSON.parse(response)
else if type.match(/\b(?:java|ecma)script\b/)
script = document.createElement('script')
script.nonce = cspNonce()
script.text = response
document.head.appendChild(script).parentNode.removeChild(script)
else if type.match(/\b(xml|html|svg)\b/)
parser = new DOMParser()
type = type.replace(/;.+/, '') # remove something like ';charset=utf-8'
try response = parser.parseFromString(response, type)
response
# Default way to get an element's href. May be overridden at Rails.href.
Rails.href = (element) -> element.href
# Determines if the request is a cross domain request.
Rails.isCrossDomain = (url) ->
originAnchor = document.createElement('a')
originAnchor.href = location.href
urlAnchor = document.createElement('a')
try
urlAnchor.href = url
# If URL protocol is false or is a string containing a single colon
# *and* host are false, assume it is not a cross-domain request
# (should only be the case for IE7 and IE compatibility mode).
# Otherwise, evaluate protocol and host of the URL against the origin
# protocol and host.
!(((!urlAnchor.protocol || urlAnchor.protocol == ':') && !urlAnchor.host) ||
(originAnchor.protocol + '//' + originAnchor.host == urlAnchor.protocol + '//' + urlAnchor.host))
catch e
# If there is an error parsing the URL, assume it is crossDomain.
true