-
Notifications
You must be signed in to change notification settings - Fork 262
/
Copy pathresource.rb
142 lines (117 loc) · 4.5 KB
/
resource.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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# frozen_string_literal: true
module Rack
class Cors
class Resource
# All CORS routes need to accept CORS simple headers at all times
# {https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers}
CORS_SIMPLE_HEADERS = %w[accept accept-language content-language content-type].freeze
attr_accessor :path, :methods, :headers, :expose, :max_age, :credentials, :pattern, :if_proc, :vary_headers
def initialize(public_resource, path, opts = {})
raise CorsMisconfigurationError if public_resource && opts[:credentials] == true
self.path = path
self.credentials = public_resource ? false : (opts[:credentials] == true)
self.max_age = opts[:max_age] || 7200
self.pattern = compile(path)
self.if_proc = opts[:if]
self.vary_headers = opts[:vary] && [opts[:vary]].flatten
@public_resource = public_resource
self.headers = case opts[:headers]
when :any then :any
when nil then nil
else
[opts[:headers]].flatten.collect(&:downcase)
end
self.methods = case opts[:methods]
when :any then %i[get head post put patch delete options]
else
ensure_enum(opts[:methods]) || [:get]
end.map(&:to_s)
self.expose = opts[:expose] ? [opts[:expose]].flatten : nil
end
def matches_path?(path)
pattern =~ path
end
def match?(path, env)
matches_path?(path) && (if_proc.nil? || if_proc.call(env))
end
def process_preflight(env, result)
headers = {}
request_method = env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_METHOD]
result.miss(Result::MISS_NO_METHOD) && (return headers) if request_method.nil?
result.miss(Result::MISS_DENY_METHOD) && (return headers) unless methods.include?(request_method.downcase)
request_headers = env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
result.miss(Result::MISS_DENY_HEADER) && (return headers) if request_headers && !allow_headers?(request_headers)
result.hit = true
headers.merge(to_preflight_headers(env))
end
def to_headers(env)
h = {
'access-control-allow-origin' => origin_for_response_header(env[Rack::Cors::HTTP_ORIGIN]),
'access-control-allow-methods' => methods.collect { |m| m.to_s.upcase }.join(', '),
'access-control-expose-headers' => expose.nil? ? '' : expose.join(', '),
'access-control-max-age' => max_age.to_s
}
h['access-control-allow-credentials'] = 'true' if credentials
header_proc.call(h)
end
protected
def public_resource?
@public_resource
end
def origin_for_response_header(origin)
return '*' if public_resource?
origin
end
def to_preflight_headers(env)
h = to_headers(env)
h.merge!('access-control-allow-headers' => env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_HEADERS]) if env[Rack::Cors::HTTP_ACCESS_CONTROL_REQUEST_HEADERS]
h
end
def allow_headers?(request_headers)
headers = self.headers || []
return true if headers == :any
request_headers = request_headers.split(/,\s*/) if request_headers.is_a?(String)
request_headers.all? do |header|
header = header.downcase
CORS_SIMPLE_HEADERS.include?(header) || headers.include?(header)
end
end
def ensure_enum(var)
return nil if var.nil?
[var].flatten
end
def compile(path)
if path.respond_to? :to_str
special_chars = %w[. + ( ) $]
pattern =
path.to_str.gsub(%r{((:\w+)|/\*|[\*#{special_chars.join}])}) do |match|
case match
when '/*'
'\\/?(.*?)'
when '*'
'(.*?)'
when *special_chars
Regexp.escape(match)
else
'([^/?&#]+)'
end
end
/^#{pattern}$/
elsif path.respond_to? :match
path
else
raise TypeError, path
end
end
def header_proc
@header_proc ||= begin
if defined?(Rack::Headers)
->(h) { h }
else
->(h) { Rack::Utils::HeaderHash.new(h) }
end
end
end
end
end
end