@@ -20,35 +20,108 @@ function contains(items, test_str)
2020 return false
2121end
2222
23- -- When invoked during a request, captures the Origin header if present
24- -- and stores it in a private variable.
25- function cors_request (txn )
26- local headers = txn .http :req_get_headers ()
27- local origin = headers [" origin" ]
28-
23+ -- If the given origin is found within the allowed_origins string, it is returned. Otherwise, nil is returned.
24+ -- origin: The value from the 'origin' request header
25+ -- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
26+ function get_allowed_origin (origin , allowed_origins )
2927 if origin ~= nil then
30- core .Debug (" CORS: Got 'Origin' header: " .. headers [" origin" ][0 ])
31- txn :set_priv (headers [" origin" ][0 ])
28+ local allowed_origins = core .tokenize (allowed_origins , " ," )
29+
30+ -- Strip whitespace
31+ for index , value in ipairs (allowed_origins ) do
32+ allowed_origins [index ] = value :gsub (" %s+" , " " )
33+ end
34+
35+ if contains (allowed_origins , " *" ) then
36+ return " *"
37+ elseif contains (allowed_origins , origin :match (" //([^/]+)" )) then
38+ return origin
39+ end
3240 end
41+
42+ return nil
3343end
3444
35- -- Add headers for CORS preflight request
36- function preflight_request (txn , method , allowed_methods )
37- if method == " OPTIONS" then
38- core .Debug (" CORS: preflight request OPTIONS" )
39- txn .http :res_add_header (" Access-Control-Allow-Methods" , allowed_methods )
40- txn .http :res_add_header (" Access-Control-Max-Age" , 600 )
45+ -- Adds headers for CORS preflight request and then attaches them to the response
46+ -- after it comes back from the server. This works with versions of HAProxy prior to 2.2.
47+ -- The downside is that the OPTIONS request must be sent to the backend server first and can't
48+ -- be intercepted and returned immediately.
49+ -- txn: The current transaction object that gives access to response properties
50+ -- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
51+ function preflight_request_ver1 (txn , allowed_methods )
52+ core .Debug (" CORS: preflight request received" )
53+ txn .http :res_add_header (" Access-Control-Allow-Methods" , allowed_methods )
54+ txn .http :res_add_header (" Access-Control-Max-Age" , 600 )
55+ core .Debug (" CORS: attaching allowed methods to response" )
56+ end
57+
58+ -- Add headers for CORS preflight request and then returns a 204 response.
59+ -- The 'reply' function used here is available in HAProxy 2.2+. It allows HAProxy to return
60+ -- a reply without contacting the server.
61+ -- txn: The current transaction object that gives access to response properties
62+ -- origin: The value from the 'origin' request header
63+ -- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
64+ -- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
65+ function preflight_request_ver2 (txn , origin , allowed_methods , allowed_origins )
66+ core .Debug (" CORS: preflight request received" )
67+
68+ local reply = txn :reply ()
69+ reply :set_status (204 , " No Content" )
70+ reply :add_header (" Content-Type" , " text/html" )
71+ reply :add_header (" Access-Control-Allow-Methods" , allowed_methods )
72+ reply :add_header (" Access-Control-Max-Age" , 600 )
73+
74+ local allowed_origin = get_allowed_origin (origin , allowed_origins )
75+
76+ if allowed_origin == nil then
77+ core .Debug (" CORS: " .. origin .. " not allowed" )
78+ else
79+ core .Debug (" CORS: " .. origin .. " allowed" )
80+ reply :add_header (" Access-Control-Allow-Origin" , allowed_origin )
4181 end
82+
83+ core .Debug (" CORS: Returning reply to preflight request" )
84+ txn :done (reply )
4285end
4386
44- -- When invoked during a response, sets CORS headers so that the browser
45- -- can read the response from permitted domains.
46- -- txn: The current transaction object that gives access to response properties.
87+ -- When invoked during a request, captures the origin header if present and stores it in a private variable.
88+ -- If the request is OPTIONS and it is a supported version of HAProxy, returns a preflight request reply.
89+ -- Otherwise, the preflight request header is added to the response after it has returned from the server.
90+ -- txn: The current transaction object that gives access to response properties
4791-- allowed_methods: Comma-delimited list of allowed HTTP methods. (e.g. GET,POST,PUT,DELETE)
4892-- allowed_origins: Comma-delimited list of allowed origins. (e.g. localhost,localhost:8080,test.com)
49- function cors_response (txn , allowed_methods , allowed_origins )
93+ function cors_request (txn , allowed_methods , allowed_origins )
94+ local headers = txn .http :req_get_headers ()
95+ local origin = headers [" origin" ][0 ]
96+
97+ local transaction_data = {}
98+
99+ if origin ~= nil then
100+ core .Debug (" CORS: Got 'Origin' header: " .. headers [" origin" ][0 ])
101+ transaction_data [" origin" ] = origin
102+ end
103+
104+ transaction_data [" allowed_methods" ] = allowed_methods
105+ transaction_data [" allowed_origins" ] = allowed_origins
106+
107+ txn :set_priv (transaction_data )
108+
50109 local method = txn .sf :method ()
51- local origin = txn :get_priv ()
110+ transaction_data [" method" ] = method
111+
112+ if method == " OPTIONS" and txn .reply ~= nil then
113+ preflight_request_ver2 (txn , origin , allowed_methods , allowed_origins )
114+ end
115+ end
116+
117+ -- When invoked during a response, sets CORS headers so that the browser can read the response from permitted domains.
118+ -- txn: The current transaction object that gives access to response properties.
119+ function cors_response (txn )
120+ local transaction_data = txn :get_priv ()
121+ local origin = transaction_data [" origin" ]
122+ local allowed_origins = transaction_data [" allowed_origins" ]
123+ local allowed_methods = transaction_data [" allowed_methods" ]
124+ local method = transaction_data [" method" ]
52125
53126 -- Always vary on the Origin
54127 txn .http :res_add_header (" Vary" , " Accept-Encoding,Origin" )
@@ -58,26 +131,20 @@ function cors_response(txn, allowed_methods, allowed_origins)
58131 return
59132 end
60133
61- local allowed_origins = core .tokenize (allowed_origins , " ," )
62-
63- -- Strip whitespace
64- for index , value in ipairs (allowed_origins ) do
65- allowed_origins [index ] = value :gsub (" %s+" , " " )
66- end
134+ local allowed_origin = get_allowed_origin (origin , allowed_origins )
67135
68- if contains (allowed_origins , " *" ) then
69- core .Debug (" CORS: " .. " * allowed" )
70- txn .http :res_add_header (" Access-Control-Allow-Origin" , " *" )
71- preflight_request (txn , method , allowed_methods )
72- elseif contains (allowed_origins , origin :match (" //([^/]+)" )) then
73- core .Debug (" CORS: " .. origin .. " allowed" )
74- txn .http :res_add_header (" Access-Control-Allow-Origin" , origin )
75- preflight_request (txn , method , allowed_methods )
76- else
136+ if allowed_origin == nil then
77137 core .Debug (" CORS: " .. origin .. " not allowed" )
138+ else
139+ if method == " OPTIONS" and txn .reply == nil then
140+ preflight_request_ver1 (txn , allowed_methods )
141+ end
142+
143+ core .Debug (" CORS: " .. origin .. " allowed" )
144+ txn .http :res_add_header (" Access-Control-Allow-Origin" , allowed_origin )
78145 end
79146end
80147
81148-- Register the actions with HAProxy
82- core .register_action (" cors" , {" http-req" }, cors_request , 0 )
83- core .register_action (" cors" , {" http-res" }, cors_response , 2 )
149+ core .register_action (" cors" , {" http-req" }, cors_request , 2 )
150+ core .register_action (" cors" , {" http-res" }, cors_response , 0 )
0 commit comments