├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── dist.ini ├── lib └── resty │ └── cors.lua ├── lua-resty-cors-0.2-1.rockspec └── t └── cors.t /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [Makefile] 13 | indent_style = tab 14 | indent_size = 4 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.lua] 21 | indent_style = space 22 | indent_size = 4 23 | end_of_line = lf 24 | charset = utf-8 25 | trim_trailing_whitespace = true 26 | insert_final_newline = true 27 | 28 | [*.py] 29 | indent_style = tab 30 | indent_size = 4 31 | end_of_line = lf 32 | charset = utf-8 33 | trim_trailing_whitespace = true 34 | insert_final_newline = true 35 | 36 | [*.html] 37 | indent_style = space 38 | indent_size = 8 39 | end_of_line = lf 40 | charset = utf-8 41 | trim_trailing_whitespace = true 42 | insert_final_newline = true 43 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Lua sources 2 | luac.out 3 | 4 | # luarocks build files 5 | *.src.rock 6 | *.zip 7 | *.tar.gz 8 | 9 | # Object files 10 | *.o 11 | *.os 12 | *.ko 13 | *.obj 14 | *.elf 15 | 16 | # Precompiled Headers 17 | *.gch 18 | *.pch 19 | 20 | # Libraries 21 | *.lib 22 | *.a 23 | *.la 24 | *.lo 25 | *.def 26 | *.exp 27 | 28 | # Shared objects (inc. Windows DLLs) 29 | *.dll 30 | *.so 31 | *.so.* 32 | *.dylib 33 | 34 | # Executables 35 | *.exe 36 | *.out 37 | *.app 38 | *.i*86 39 | *.x86_64 40 | *.hex 41 | 42 | t/servroot 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: c 3 | before_install: 4 | - sudo apt-get install -y libev-dev build-essential cpanminus perlmagick libcpanplus-perl 5 | - wget https://github.com/lighttpd/weighttp/archive/weighttp-0.4.tar.gz 6 | - tar -zxf weighttp-0.4.tar.gz 7 | - (cd weighttp-weighttp-0.4 && ./autogen.sh && ./configure && make && sudo make install) 8 | - wget https://openresty.org/download/openresty-1.11.2.1.tar.gz 9 | - tar -zxf openresty-1.11.2.1.tar.gz 10 | script: 11 | - (cd openresty-1.11.2.1 && ./configure && make -j4 && sudo make install) 12 | - sudo cpanm Test::Nginx 13 | - (export PERL_MM_USE_DEFAULT=1 && git clone https://github.com/openresty/test-nginx.git && cd test-nginx && perl Makefile.PL && sudo make uninstall && sudo make install) 14 | - make test 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 detailyang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PATH := /opt/openresty/nginx/sbin:/usr/local/openresty/nginx/sbin:/usr/local/bin:$(PATH) 2 | 3 | test: 4 | @WORKDIR=$(shell pwd) /usr/bin/prove 5 | 6 | .PHONY: test 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | lua-resty-cors 4 | 5 | # lua-resty-cors 6 | It's the implement of [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) on OpenResty and 7 | It backports the [nginx-http-cors](https://github.com/x-v8/ngx_http_cors_filter) to OpenResty 8 | 9 | Table of Contents 10 | ----------------- 11 | * [Name](#name) 12 | * [Status](#status) 13 | * [Usage](#usage) 14 | * [API](#api) 15 | * [Contributing](#contributing) 16 | * [Author](#author) 17 | * [License](#license) 18 | 19 | 20 | Status 21 | ==== 22 | [![Build Status](https://travis-ci.org/detailyang/lua-resty-cors.svg?branch=master)](https://travis-ci.org/detailyang/lua-resty-cors) 23 | 24 | Usage 25 | ==== 26 | It may be placed on the nginx http block for a global CORS config or in each server block to configure a different CORS for each virtual host as the following: 27 | 28 | ```bash 29 | 30 | http { 31 | init_by_lua_block { 32 | local cors = require('lib.resty.cors'); 33 | 34 | cors.allow_host([==[.*\.google\.com]==]) 35 | cors.allow_host([==[.*\.facebook\.com]==]) 36 | cors.expose_header('x-custom-field1') 37 | cors.expose_header('x-custom-field2') 38 | cors.allow_method('GET') 39 | cors.allow_method('POST') 40 | cors.allow_method('PUT') 41 | cors.allow_method('DELETE') 42 | cors.allow_header('x-custom-field1') 43 | cors.allow_header('x-custom-field2') 44 | cors.max_age(7200) 45 | cors.allow_credentials(false) 46 | } 47 | 48 | header_filter_by_lua_block { 49 | local cors = require('lib.resty.cors'); 50 | cors.run() 51 | } 52 | } 53 | ``` 54 | 55 | API 56 | ==== 57 | 58 | allow_host 59 | --- 60 | `syntax: cors.allow_host(host)` 61 | 62 | This will match the host from cors request then be added to the header Access-Control-Allow-Origin like as the following: 63 | 64 | ```bash 65 | Request: 66 | Origin: https://www.google.com 67 | 68 | Response: 69 | Access-Control-Allow-Origin: http://www.google.com 70 | ``` 71 | 72 | expose_header 73 | --- 74 | `syntax: cors.expose_header(header)` 75 | 76 | This will be added to the header Access-Control-Expose-Headers like as the following: 77 | 78 | ```bash 79 | Request: 80 | Origin: https://www.google.com 81 | 82 | Response: 83 | Access-Control-Expose-Headers: x-custom-field1,x-custom-field2 84 | ``` 85 | 86 | allow_method 87 | --- 88 | `syntax: cors.allow_method(method)` 89 | 90 | This will be added to the header Access-Control-Allow-Methods like as the following: 91 | 92 | ```bash 93 | Request: 94 | Origin: https://www.google.com 95 | 96 | Response: 97 | Access-Control-Allow-Methods:GET,POST,PUT 98 | ``` 99 | 100 | allow_header 101 | --- 102 | `syntax: cors.allow_header(header)` 103 | 104 | This will be added to the header Access-Control-Allow-Headers like as the following: 105 | 106 | ```bash 107 | Request: 108 | Origin: https://www.google.com 109 | 110 | Response: 111 | Access-Control-Allow-Headers:x-custom-field1,x-custom-field2 112 | ``` 113 | 114 | max_age 115 | --- 116 | `syntax: cors.max_age(age)` 117 | 118 | This will be added to the header Access-Control-Max-Age like as the following: 119 | 120 | ```bash 121 | Request: 122 | Origin: https://www.google.com 123 | 124 | Response: 125 | Access-Control-Max-Age: 7200 126 | ``` 127 | 128 | Allow-Credentials 129 | --- 130 | `syntax: cors.allow_credentials(true or false)` 131 | 132 | This will be added to the header Access-Control-Allow-Credentials like as the following: 133 | 134 | ```bash 135 | Request: 136 | Origin: https://www.google.com 137 | 138 | Response: 139 | Access-Control-Allow-Credentials: true 140 | ``` 141 | 142 | run 143 | --- 144 | `syntax: cors.run()` 145 | 146 | This is the entry for lua-resty-cors to run 147 | 148 | 149 | Contributing 150 | ------------ 151 | 152 | To contribute to lua-resty-cors, clone this repo locally and commit your code on a separate branch. 153 | 154 | PS: PR Welcome :rocket: :rocket: :rocket: :rocket: 155 | 156 | 157 | Author 158 | ------ 159 | 160 | > GitHub [@detailyang](https://github.com/detailyang) 161 | 162 | License 163 | ------- 164 | lua-resty-cors is licensed under the [MIT] license. 165 | 166 | [MIT]: https://github.com/detailyang/ybw/blob/master/licenses/MIT 167 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = lua-resty-cors 2 | abstract = It's the implement of CORS on OpenResty (code is version 0.2.1~c78b9ea) 3 | version = 0.2.1.5 4 | author = Bingwu Yang (detailyang) 5 | license = mit 6 | requires = ngx_http_lua 7 | repo_link = https://github.com/detailyang/lua-resty-cors 8 | is_original = no 9 | -------------------------------------------------------------------------------- /lib/resty/cors.lua: -------------------------------------------------------------------------------- 1 | -- @Author: detailyang 2 | -- @Date: 2016-10-10 15:45:33 3 | -- @Last Modified by: detailyang 4 | -- @Last Modified time: 2017-02-19 13:29:41 5 | 6 | -- https://www.w3.org/TR/cors/ 7 | 8 | local re_match = ngx.re.match 9 | 10 | local _M = { _VERSION = '0.2.1.5'} 11 | 12 | local Origin = 'Origin' 13 | local AccessControlAllowOrigin = 'Access-Control-Allow-Origin' 14 | local AccessControlExposeHeaders = 'Access-Control-Expose-Headers' 15 | local AccessControlMaxAge = 'Access-Control-Max-Age' 16 | local AccessControlAllowCredentials = 'Access-Control-Allow-Credentials' 17 | local AccessControlAllowMethods = 'Access-Control-Allow-Methods' 18 | local AccessControlAllowHeaders = 'Access-Control-Allow-Headers' 19 | 20 | local mt = { __index = _M } 21 | 22 | local allow_hosts = {} 23 | local allow_headers = {} 24 | local allow_methods = {} 25 | local expose_headers = {} 26 | local max_age = 3600 27 | local allow_credentials = true 28 | local join = table.concat 29 | 30 | 31 | function _M.allow_host(host) 32 | if allow_hosts[host] == nil then 33 | allow_hosts[host] = host 34 | allow_hosts[#allow_hosts + 1] = host 35 | end 36 | end 37 | 38 | function _M.allow_method(method) 39 | if allow_methods[method] == nil then 40 | allow_methods[method] = method 41 | allow_methods[#allow_methods + 1] = method 42 | end 43 | end 44 | 45 | function _M.allow_header(header) 46 | if allow_headers[header] == nil then 47 | allow_headers[header] = header 48 | allow_headers[#allow_headers + 1] = header 49 | end 50 | end 51 | 52 | function _M.expose_header(header) 53 | if expose_headers[header] == nil then 54 | expose_headers[header] = header 55 | expose_headers[#expose_headers + 1] = header 56 | end 57 | end 58 | 59 | function _M.max_age(age) 60 | max_age = age 61 | end 62 | 63 | function _M.allow_credentials(credentials) 64 | allow_credentials = credentials 65 | end 66 | 67 | function _M.run() 68 | local origin = ngx.req.get_headers()[Origin] 69 | if not origin then 70 | return 71 | end 72 | 73 | local matched = false 74 | for k, v in pairs(allow_hosts) do 75 | local from, to, err = ngx.re.find(origin, v, "jo") 76 | if from then 77 | matched = true 78 | break 79 | end 80 | end 81 | 82 | if matched == false then 83 | return 84 | end 85 | 86 | ngx.header[AccessControlAllowOrigin] = origin 87 | ngx.header[AccessControlMaxAge] = max_age 88 | 89 | if #expose_headers >= 0 then 90 | ngx.header[AccessControlExposeHeaders] = join(expose_headers, ',') 91 | end 92 | 93 | if #allow_headers >= 0 then 94 | ngx.header[AccessControlAllowHeaders] = join(allow_headers, ',') 95 | end 96 | 97 | if #allow_methods >= 0 then 98 | ngx.header[AccessControlAllowMethods] = join(allow_methods, ',') 99 | end 100 | 101 | if allow_credentials == true then 102 | ngx.header[AccessControlAllowCredentials] = "true" 103 | else 104 | ngx.header[AccessControlAllowCredentials] = "false" 105 | end 106 | end 107 | 108 | return _M 109 | -------------------------------------------------------------------------------- /lua-resty-cors-0.2-1.rockspec: -------------------------------------------------------------------------------- 1 | package = "lua-resty-cors" 2 | version = "0.2-1" 3 | source = { 4 | url = "git+https://github.com/detailyang/lua-resty-cors.git" 5 | } 6 | description = { 7 | detailed = [[ 8 | # lua-resty-cors 9 | It's the implement of [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS) on OpenResty and 10 | It backports the [nginx-http-cors](https://github.com/x-v8/ngx_http_cors_filter) to OpenResty]], 11 | homepage = "https://github.com/detailyang/lua-resty-cors", 12 | license = "MIT" 13 | } 14 | dependencies = {} 15 | build = { 16 | type = "builtin", 17 | modules = { 18 | ["lib.resty.cors"] = "lib/resty/cors.lua" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /t/cors.t: -------------------------------------------------------------------------------- 1 | use Test::Nginx::Socket 'no_plan'; 2 | 3 | my $workdir = $ENV{WORKDIR}; 4 | 5 | our $http_config = <<"_EOC_"; 6 | lua_package_path '$workdir/?.lua;;'; 7 | _EOC_ 8 | 9 | repeat_each(1); 10 | no_shuffle(); 11 | run_tests(); 12 | 13 | __DATA__ 14 | 15 | === Test1: test one allow_host google.com 16 | --- http_config eval: $::http_config 17 | 18 | --- config 19 | location = /1 { 20 | content_by_lua_block { 21 | ngx.say("hello world") 22 | } 23 | 24 | header_filter_by_lua_block { 25 | local cors = require('lib.resty.cors'); 26 | 27 | cors.allow_host([==[.*\.google\.com]==]) 28 | 29 | cors.run() 30 | } 31 | } 32 | 33 | --- request 34 | GET /1 35 | 36 | --- more_headers 37 | Origin: http://www.google.com 38 | 39 | --- response_headers 40 | Access-Control-Allow-Origin: http://www.google.com 41 | 42 | 43 | === Test2: test one allow_host google.com 44 | --- http_config eval: $::http_config 45 | 46 | --- config 47 | location = /1 { 48 | content_by_lua_block { 49 | ngx.say("hello world") 50 | } 51 | 52 | header_filter_by_lua_block { 53 | local cors = require('lib.resty.cors'); 54 | 55 | cors.allow_host([==[.*\.google\.com]==]) 56 | 57 | cors.run() 58 | } 59 | } 60 | 61 | --- request 62 | GET /1 63 | 64 | --- more_headers 65 | 66 | --- response_headers 67 | Access-Control-Allow-Origin: 68 | 69 | 70 | 71 | === Test3: test multi allow_host google.com 72 | --- http_config eval: $::http_config 73 | 74 | --- config 75 | location = /1 { 76 | content_by_lua_block { 77 | ngx.say("hello world") 78 | } 79 | 80 | header_filter_by_lua_block { 81 | local cors = require('lib.resty.cors'); 82 | 83 | cors.allow_host([==[.*\.google\.com]==]) 84 | cors.allow_host([==[.*\.facebook\.com]==]) 85 | 86 | cors.run() 87 | } 88 | } 89 | 90 | --- request 91 | GET /1 92 | 93 | --- more_headers 94 | Origin: https://www.facebook.com 95 | 96 | --- response_headers 97 | Access-Control-Allow-Origin: https://www.facebook.com 98 | 99 | 100 | 101 | === Test4: test one expose_header 102 | --- http_config eval: $::http_config 103 | 104 | --- config 105 | location = /1 { 106 | content_by_lua_block { 107 | ngx.say("hello world") 108 | } 109 | 110 | header_filter_by_lua_block { 111 | local cors = require('lib.resty.cors'); 112 | 113 | cors.allow_host([==[.*\.facebook\.com]==]) 114 | cors.expose_header('x-custom-field1') 115 | 116 | cors.run() 117 | } 118 | } 119 | 120 | --- request 121 | GET /1 122 | 123 | --- more_headers 124 | Origin: https://www.facebook.com 125 | 126 | --- response_headers 127 | Access-Control-Allow-Origin: https://www.facebook.com 128 | Access-Control-Expose-Headers: x-custom-field1 129 | 130 | 131 | 132 | === Test5: test multi expose_header 133 | --- http_config eval: $::http_config 134 | 135 | --- config 136 | location = /1 { 137 | content_by_lua_block { 138 | ngx.say("hello world") 139 | } 140 | 141 | header_filter_by_lua_block { 142 | local cors = require('lib.resty.cors'); 143 | 144 | cors.allow_host([==[.*\.facebook\.com]==]) 145 | cors.expose_header('x-custom-field1') 146 | cors.expose_header('x-custom-field2') 147 | 148 | cors.run() 149 | } 150 | } 151 | 152 | --- request 153 | GET /1 154 | 155 | --- more_headers 156 | Origin: https://www.facebook.com 157 | 158 | --- response_headers 159 | Access-Control-Allow-Origin: https://www.facebook.com 160 | Access-Control-Expose-Headers: x-custom-field1,x-custom-field2 161 | 162 | 163 | 164 | === Test6: test one allow_method 165 | --- http_config eval: $::http_config 166 | 167 | --- config 168 | location = /1 { 169 | content_by_lua_block { 170 | ngx.say("hello world") 171 | } 172 | 173 | header_filter_by_lua_block { 174 | local cors = require('lib.resty.cors'); 175 | 176 | cors.allow_host([==[.*\.facebook\.com]==]) 177 | cors.allow_method('GET') 178 | 179 | cors.run() 180 | } 181 | } 182 | 183 | --- request 184 | GET /1 185 | 186 | --- more_headers 187 | Origin: https://www.facebook.com 188 | 189 | --- response_headers 190 | Access-Control-Allow-Origin: https://www.facebook.com 191 | Access-Control-Allow-Methods: GET 192 | 193 | 194 | 195 | === Test7: test multi expose_header 196 | --- http_config eval: $::http_config 197 | 198 | --- config 199 | location = /1 { 200 | content_by_lua_block { 201 | ngx.say("hello world") 202 | } 203 | 204 | header_filter_by_lua_block { 205 | local cors = require('lib.resty.cors'); 206 | 207 | cors.allow_host([==[.*\.facebook\.com]==]) 208 | cors.allow_method('GET') 209 | cors.allow_method('POST') 210 | 211 | cors.run() 212 | } 213 | } 214 | 215 | --- request 216 | GET /1 217 | 218 | --- more_headers 219 | Origin: https://www.facebook.com 220 | 221 | --- response_headers 222 | Access-Control-Allow-Origin: https://www.facebook.com 223 | Access-Control-Allow-Methods: GET,POST 224 | 225 | 226 | 227 | === Test8: test one allow_method 228 | --- http_config eval: $::http_config 229 | 230 | --- config 231 | location = /1 { 232 | content_by_lua_block { 233 | ngx.say("hello world") 234 | } 235 | 236 | header_filter_by_lua_block { 237 | local cors = require('lib.resty.cors'); 238 | 239 | cors.allow_host([==[.*\.facebook\.com]==]) 240 | cors.allow_header('x-custom-field1') 241 | 242 | cors.run() 243 | } 244 | } 245 | 246 | --- request 247 | GET /1 248 | 249 | --- more_headers 250 | Origin: https://www.facebook.com 251 | 252 | --- response_headers 253 | Access-Control-Allow-Origin: https://www.facebook.com 254 | Access-Control-Allow-Headers: x-custom-field1 255 | 256 | 257 | 258 | === Test9: test multi expose_header 259 | --- http_config eval: $::http_config 260 | 261 | --- config 262 | location = /1 { 263 | content_by_lua_block { 264 | ngx.say("hello world") 265 | } 266 | 267 | header_filter_by_lua_block { 268 | local cors = require('lib.resty.cors'); 269 | 270 | cors.allow_host([==[.*\.facebook\.com]==]) 271 | cors.allow_header('x-custom-field1') 272 | cors.allow_header('x-custom-field2') 273 | 274 | cors.run() 275 | } 276 | } 277 | 278 | --- request 279 | GET /1 280 | 281 | --- more_headers 282 | Origin: https://www.facebook.com 283 | 284 | --- response_headers 285 | Access-Control-Allow-Origin: https://www.facebook.com 286 | Access-Control-Allow-Headers: x-custom-field1,x-custom-field2 287 | 288 | 289 | 290 | === Test10: test max_age 291 | --- http_config eval: $::http_config 292 | 293 | --- config 294 | location = /1 { 295 | content_by_lua_block { 296 | ngx.say("hello world") 297 | } 298 | 299 | header_filter_by_lua_block { 300 | local cors = require('lib.resty.cors'); 301 | 302 | cors.allow_host([==[.*\.facebook\.com]==]) 303 | cors.max_age(7200) 304 | 305 | cors.run() 306 | } 307 | } 308 | 309 | --- request 310 | GET /1 311 | 312 | --- more_headers 313 | Origin: https://www.facebook.com 314 | 315 | --- response_headers 316 | Access-Control-Allow-Origin: https://www.facebook.com 317 | Access-Control-Max-Age: 7200 318 | 319 | 320 | 321 | === Test11: test allow_credentials 322 | --- http_config eval: $::http_config 323 | 324 | --- config 325 | location = /1 { 326 | content_by_lua_block { 327 | ngx.say("hello world") 328 | } 329 | 330 | header_filter_by_lua_block { 331 | local cors = require('lib.resty.cors'); 332 | 333 | cors.allow_host([==[.*\.facebook\.com]==]) 334 | cors.allow_credentials(false) 335 | 336 | cors.run() 337 | } 338 | } 339 | 340 | --- request 341 | GET /1 342 | 343 | --- more_headers 344 | Origin: https://www.facebook.com 345 | 346 | --- response_headers 347 | Access-Control-Allow-Origin: https://www.facebook.com 348 | Access-Control-Allow-Credentials: false 349 | --------------------------------------------------------------------------------