├── .gitignore ├── LICENSE ├── README.md ├── docs ├── img │ ├── architecture.png │ └── architecture2.png └── plugins │ ├── ip-restriction.md │ ├── redirect.md │ ├── referer-restriction.md │ └── uri-blocker.md ├── example ├── Dockerfile-proxy ├── Dockerfile-web-service ├── README.md ├── docker-compose.yaml └── envoy.yaml ├── licenses ├── LICENSE-envoy.txt ├── LICENSE-lua-resty-ipmatcher.txt └── LICENSE-neturl.txt └── lua ├── apisix ├── core.lua ├── core │ ├── ctx.lua │ ├── json.lua │ ├── lrucache.lua │ ├── plugin.lua │ ├── re.lua │ ├── schema.lua │ ├── string.lua │ ├── table.lua │ └── version.lua ├── entry.lua ├── plugins │ ├── ip-restriction.lua │ ├── redirect.lua │ ├── referer-restriction.lua │ └── uri-blocker.lua └── schema_def.lua └── deps ├── net └── url.lua └── resty ├── ipmatcher.lua ├── lrucache.lua └── lrucache └── pureffi.lua /.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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | ======================================================================== 204 | MIT licenses 205 | ======================================================================== 206 | 207 | The following components are provided under the MIT License. See project link for details. 208 | The text of each license is also included at licenses/LICENSE-[project].txt. 209 | 210 | files from golgote/neturl: https://github.com/golgote/neturl MIT 211 | 212 | ======================================================================== 213 | Apache 2.0 licenses 214 | ======================================================================== 215 | 216 | The following components are provided under the Apache License. See project link for details. 217 | The text of each license is the standard Apache 2.0 license. 218 | 219 | files from envoyproxy/envoy: https://github.com/envoyproxy/envoy Apache 2.0 220 | files from api7/lua-resty-ipmatcher: https://github.com/api7/lua-resty-ipmatcher Apache 2.0 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | A Lua framework that support [Apache APISIX](https://github.com/apache/apisix) plugins run directly in [Envoy](https://github.com/envoyproxy/envoy) Lua filter without modify Envoy. 4 | 5 | 6 | # Example 7 | 8 | 1. clone the code 9 | ```shell 10 | git clone https://github.com/api7/envoy-apisix.git 11 | ``` 12 | 13 | 2. build and run the example 14 | ```shell 15 | $ cd envoy-apisix/example 16 | $ docker-compose pull 17 | $ docker-compose up --build -d 18 | ``` 19 | 20 | 3. test the example 21 | 22 | ```shell 23 | $ curl 127.0.0.1:8000/foo/root.exe -i 24 | 25 | HTTP/1.1 403 Forbidden 26 | server: envoy 27 | content-length: 0 28 | date: Wed, 18 Nov 2020 00:08:54 GMT 29 | ``` 30 | 31 | 32 | # Plugins 33 | 34 | * [redirect](docs/plugins/redirect.md): URI redirect. 35 | * [referer-restriction](docs/plugins/referer-restriction.md): Referer whitelist. 36 | * [uri-blocker](docs/plugins/uri-blocker.md): Block client request by URI. 37 | * [ip-restriction](docs/plugins/ip-restriction.md): Restrict access by IP addresses. 38 | 39 | 40 | # How does it work 41 | 42 | We shield platform differences for the plugin layer. All interfaces that need to be used are abstracted in the underlying framework, which we call apisix.core, so that all plugins can run on Envoy and Apache APISIX at the same time. 43 | 44 | ![Architecture](https://lh5.googleusercontent.com/iwtuR_yrBWeWLVM7Hfo1BTMNkP3-CY7iVqzvNwgIslnFc2GsUgr1_BRWi2em_fFhuqg8l3MuDAYMa7zM1mrkYzb4ynqg62WV6CvkOaY_wl4D-vRZKpUYayKh4DrgqDRl2NiTKOdq) 45 | 46 | 47 | # Plugin workflow 48 | 49 | ![Architecture](https://lh5.googleusercontent.com/_zb5QqAqtyN0H3brtdRUFEPKMuat3dR5y3J-2V79yFBY7yU5VYfGHCT9cjxbsIVUbvbTgyE0bgfks6hHiIkawjXKXvN0bP_Fre8byMLTx-uPBbAkufDxlsV0GkqrOhZzHUvF3b9-) 50 | 51 | We use the example to show how the plugin runs: 52 | 53 | ## First step, read configuration 54 | 55 | We configure through metadata to determine what plugins need to run on each route and what configuration is for each plugin. 56 | In the example, we configured plugin `uri-blocker` for the route whose prefix is ​​`/foo`, as well as the block rule of the plugin and the response status when a block is required. 57 | 58 | ## Second step, parse request 59 | 60 | We encapsulated the client request data into `ctx` so that it can be used directly in the entire process. 61 | 62 | ## Third step, run the plugin 63 | 64 | We determine whether we need to block this request by matching the configured rules and the obtained uri. If a block is needed, we call `respond` to directly respond, otherwise we let it go. 65 | -------------------------------------------------------------------------------- /docs/img/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/api7/envoy-apisix/2077312527fb69e7c7dbc8ffe4b5494e4e38de20/docs/img/architecture.png -------------------------------------------------------------------------------- /docs/img/architecture2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/api7/envoy-apisix/2077312527fb69e7c7dbc8ffe4b5494e4e38de20/docs/img/architecture2.png -------------------------------------------------------------------------------- /docs/plugins/ip-restriction.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Summary 21 | - [**Name**](#name) 22 | - [**Attributes**](#attributes) 23 | - [**How To Enable**](#how-to-enable) 24 | - [**Test Plugin**](#test-plugin) 25 | - [**Disable Plugin**](#disable-plugin) 26 | 27 | ## Name 28 | 29 | The `ip-restriction` can restrict access to a Service or a Route by either 30 | whitelisting or blacklisting IP addresses. Single IPs, multiple IPs or ranges 31 | in CIDR notation like 10.10.10.0/24 can be used. 32 | 33 | ## Attributes 34 | 35 | | Name | Type | Requirement | Valid | Description | 36 | | --------- | ------------- | ----------- | ----- | ---------------------------------------- | 37 | | whitelist | array[string] | optional | | List of IPs or CIDR ranges to whitelist. | 38 | | blacklist | array[string] | optional | | List of IPs or CIDR ranges to blacklist. | 39 | 40 | One of `whitelist` or `blacklist` must be specified, and they can not work together. 41 | 42 | ## How To Enable 43 | 44 | Creates a route or service object, and enable plugin `ip-restriction`. 45 | 46 | 47 | 48 | ```yaml 49 | - match: 50 | prefix: "/bar" 51 | route: 52 | cluster: web_service 53 | typed_per_filter_config: 54 | envoy.filters.http.lua: 55 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 56 | name: entry.lua 57 | metadata: 58 | filter_metadata: 59 | envoy.filters.http.lua: 60 | plugins: 61 | - name: ip-restriction 62 | conf: 63 | whitelist: 64 | - 127.0.0.1 65 | - 113.74.26.106/24 66 | ``` 67 | 68 | 69 | ## Test Plugin 70 | 71 | Requests from `127.0.0.1`: 72 | 73 | ```shell 74 | $ curl http://127.0.0.1:9080/index.html -i 75 | HTTP/1.1 200 OK 76 | ... 77 | ``` 78 | 79 | Requests from `127.0.0.2`: 80 | 81 | ```shell 82 | $ curl http://127.0.0.1:9080/index.html -i --interface 127.0.0.2 83 | HTTP/1.1 403 Forbidden 84 | ... 85 | {"message":"Your IP address is not allowed"} 86 | ``` 87 | 88 | 89 | ## Disable Plugin 90 | 91 | When you want to disable the `ip-restriction` plugin, it is very simple, 92 | you can delete the corresponding yaml configuration in the route metadata. 93 | -------------------------------------------------------------------------------- /docs/plugins/redirect.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Summary 21 | - [**Name**](#name) 22 | - [**Attributes**](#attributes) 23 | - [**How To Enable**](#how-to-enable) 24 | - [**Test Plugin**](#test-plugin) 25 | - [**Disable Plugin**](#disable-plugin) 26 | 27 | ## Name 28 | 29 | URI redirect. 30 | 31 | ## Attributes 32 | 33 | | Name | Type | Requirement | Valid | Description | 34 | | ------------- | ------- | ----------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 35 | | http_to_https | boolean | optional | | When it is set to `true` and the request is HTTP, will be automatically redirected to HTTPS with 301 response code, and the URI will keep the same as client request.| 36 | | uri | string | optional | | New URL which redirect to. | 37 | | ret_code | string | optional | [200, ...] | Response code | 38 | 39 | One of `http_to_https` and `uri` need to be specified. 40 | 41 | ## How To Enable 42 | 43 | Here's a mini example, enable the `redirect` plugin on the specified route: 44 | 45 | ```yaml 46 | - match: 47 | prefix: "/bar" 48 | route: 49 | cluster: web_service 50 | typed_per_filter_config: 51 | envoy.filters.http.lua: 52 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 53 | name: entry.lua 54 | metadata: 55 | filter_metadata: 56 | envoy.filters.http.lua: 57 | plugins: 58 | - name: redirect 59 | conf: 60 | ret_code: 301 61 | uri: "/redirected/path" 62 | ``` 63 | 64 | 65 | ## Test Plugin 66 | 67 | Testing based on the above examples : 68 | 69 | ```shell 70 | $ curl http://127.0.0.1:8000/bar -i 71 | HTTP/1.1 301 Moved Permanently 72 | location: /redirected/path 73 | server: envoy 74 | content-length: 4 75 | date: Mon, 21 Dec 2020 00:16:08 GMT 76 | 77 | ``` 78 | 79 | We can check the response code and the response header `Location`. 80 | 81 | It shows that the `redirect` plugin is in effect. 82 | 83 | Here is an example of redirect HTTP to HTTPS: 84 | ```yaml 85 | - match: 86 | prefix: "/bar" 87 | route: 88 | cluster: web_service 89 | typed_per_filter_config: 90 | envoy.filters.http.lua: 91 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 92 | name: entry.lua 93 | metadata: 94 | filter_metadata: 95 | envoy.filters.http.lua: 96 | plugins: 97 | - name: redirect 98 | conf: 99 | ret_code: 301 100 | http_to_https: true 101 | ``` 102 | 103 | ## Disable Plugin 104 | 105 | When you want to disable the `redirect` plugin, it is very simple, 106 | you can delete the corresponding yaml configuration in the route metadata. 107 | -------------------------------------------------------------------------------- /docs/plugins/referer-restriction.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Summary 21 | - [**Name**](#name) 22 | - [**Attributes**](#attributes) 23 | - [**How To Enable**](#how-to-enable) 24 | - [**Test Plugin**](#test-plugin) 25 | - [**Disable Plugin**](#disable-plugin) 26 | 27 | 28 | ## Name 29 | 30 | The `referer-restriction` can restrict access to a Route by whitelisting request header Referers. 31 | 32 | ## Attributes 33 | 34 | | Name | Type | Requirement | Default | Valid | Description | 35 | | --------- | ------------- | ----------- | ------- | ----- | ---------------------------------------- | 36 | | whitelist | array[string] | required | | | List of hostname to whitelist. The hostname can be started with `*` as a wildcard | 37 | | bypass_missing | boolean | optional | false | | Whether to bypass the check when the Referer header is missing or malformed | 38 | 39 | ## How To Enable 40 | 41 | Here's a mini example, enable the `referer-restriction` plugin on the specified route: 42 | 43 | ```yaml 44 | - match: 45 | prefix: "/hello" 46 | route: 47 | cluster: web_service 48 | typed_per_filter_config: 49 | envoy.filters.http.lua: 50 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 51 | name: entry.lua 52 | metadata: 53 | filter_metadata: 54 | envoy.filters.http.lua: 55 | plugins: 56 | - name: referer-restriction 57 | conf: 58 | bypass_missing: false 59 | whitelist: 60 | - "127.0.0.1" 61 | ``` 62 | 63 | ## Test Plugin 64 | 65 | Request with `Referer: http://xx.com/x`: 66 | 67 | ```shell 68 | $ curl 127.0.0.1:8000/hello -i -H "Referer: http://127.0.0.1" 69 | HTTP/1.1 200 OK 70 | x-powered-by: Express 71 | content-type: application/json; charset=utf-8 72 | content-length: 566 73 | etag: W/"236-exxhnQXbbhWjM6uv+UNfhdV7NWU" 74 | date: Mon, 21 Dec 2020 00:25:47 GMT 75 | x-envoy-upstream-service-time: 2 76 | server: envoy 77 | 78 | ... 79 | ``` 80 | 81 | Request with `Referer: http://yy.com/x`: 82 | 83 | ```shell 84 | $ curl 127.0.0.1:8000/hello -i -H "Referer: http://127.0.0.2" 85 | HTTP/1.1 403 Forbidden 86 | server: envoy 87 | content-length: 46 88 | date: Mon, 21 Dec 2020 00:24:56 GMT 89 | 90 | {"message":"Your referer host is not allowed"}% 91 | ``` 92 | 93 | Request without `Referer`: 94 | 95 | ```shell 96 | $ curl 127.0.0.1:8000/hello -i 97 | HTTP/1.1 200 OK 98 | x-powered-by: Express 99 | content-type: application/json; charset=utf-8 100 | content-length: 566 101 | etag: W/"236-exxhnQXbbhWjM6uv+UNfhdV7NWU" 102 | date: Mon, 21 Dec 2020 00:25:47 GMT 103 | x-envoy-upstream-service-time: 2 104 | server: envoy 105 | 106 | ... 107 | ``` 108 | 109 | ## Disable Plugin 110 | 111 | When you want to disable the `referer-restriction` plugin, it is very simple, 112 | you can delete the corresponding yaml configuration in the route metadata. 113 | -------------------------------------------------------------------------------- /docs/plugins/uri-blocker.md: -------------------------------------------------------------------------------- 1 | 19 | 20 | # Summary 21 | 22 | - [**Name**](#name) 23 | - [**Attributes**](#attributes) 24 | - [**How To Enable**](#how-to-enable) 25 | - [**Test Plugin**](#test-plugin) 26 | - [**Disable Plugin**](#disable-plugin) 27 | 28 | ## Name 29 | 30 | The plugin helps we intercept user requests, we only need to indicate the `referer-restriction`. 31 | 32 | ## Attributes 33 | 34 | | Name | Type | Requirement | Default | Valid | Description | 35 | | ------------- | ------------- | ----------- | ------- | ---------- | --------------------------------------------------------------------------- | 36 | | block_rules | array[string] | required | | | Regular filter rule array. Each of these items is a regular rule. If the current request URI hits any one of them, set the response code to rejected_code to exit the current user request. Example: `["root.exe", "root.m+"]`. | 37 | | rejected_code | integer | optional | 403 | [200, ...] | The HTTP status code returned when the request URI hit any of `block_rules` | 38 | 39 | ## How To Enable 40 | 41 | Here's a mini example, enable the `uri-blocker` plugin on the specified route: 42 | 43 | ```yaml 44 | - match: 45 | prefix: "/foo" 46 | route: 47 | cluster: web_service 48 | typed_per_filter_config: 49 | envoy.filters.http.lua: 50 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 51 | name: entry.lua 52 | metadata: 53 | filter_metadata: 54 | envoy.filters.http.lua: 55 | plugins: 56 | - name: uri-blocker 57 | conf: 58 | rejected_code: 403 59 | block_rules: 60 | - root.exe 61 | - root.m+ 62 | ``` 63 | 64 | ## Test Plugin 65 | 66 | ```shell 67 | $ curl "127.0.0.1:8000/foo?name=root.exe" -i 68 | HTTP/1.1 403 Forbidden 69 | server: envoy 70 | content-length: 0 71 | date: Mon, 21 Dec 2020 00:45:26 GMT 72 | 73 | 74 | ... 75 | ``` 76 | 77 | ## Disable Plugin 78 | 79 | When you want to disable the `uri-blocker` plugin, it is very simple, 80 | you can delete the corresponding yaml configuration in the route metadata. 81 | -------------------------------------------------------------------------------- /example/Dockerfile-proxy: -------------------------------------------------------------------------------- 1 | FROM envoyproxy/envoy-dev:latest 2 | ADD lua/apisix/ /apisix/ 3 | ADD lua/deps/ /usr/local/share/lua/5.1/ 4 | COPY example/envoy.yaml /etc/envoy.yaml 5 | RUN chmod go+r /etc/envoy.yaml /apisix/core.lua 6 | CMD /usr/local/bin/envoy -c /etc/envoy.yaml -l debug --service-cluster proxy 7 | -------------------------------------------------------------------------------- /example/Dockerfile-web-service: -------------------------------------------------------------------------------- 1 | FROM solsson/http-echo 2 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | To learn about this sandbox and for instructions on how to run it please head over 2 | to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/lua.html). 3 | -------------------------------------------------------------------------------- /example/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | 4 | proxy: 5 | build: 6 | context: ../ 7 | dockerfile: example/Dockerfile-proxy 8 | networks: 9 | - envoymesh 10 | expose: 11 | - "8000" 12 | - "8001" 13 | ports: 14 | - "8000:8000" 15 | - "8001:8001" 16 | 17 | web_service: 18 | build: 19 | context: . 20 | dockerfile: Dockerfile-web-service 21 | networks: 22 | envoymesh: 23 | aliases: 24 | - web_service 25 | expose: 26 | - "80" 27 | ports: 28 | - "8080:80" 29 | 30 | networks: 31 | envoymesh: {} 32 | -------------------------------------------------------------------------------- /example/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 8000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: local_route 17 | virtual_hosts: 18 | - name: local_service 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/foo" 24 | route: 25 | cluster: web_service 26 | typed_per_filter_config: 27 | envoy.filters.http.lua: 28 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 29 | name: entry.lua 30 | metadata: 31 | filter_metadata: 32 | envoy.filters.http.lua: 33 | plugins: 34 | - name: uri-blocker 35 | conf: 36 | rejected_code: 403 37 | block_rules: 38 | - root.exe 39 | - root.m+ 40 | 41 | - match: 42 | prefix: "/ip" 43 | route: 44 | cluster: web_service 45 | typed_per_filter_config: 46 | envoy.filters.http.lua: 47 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 48 | name: entry.lua 49 | metadata: 50 | filter_metadata: 51 | envoy.filters.http.lua: 52 | plugins: 53 | - name: ip-restriction 54 | conf: 55 | whitelist: 56 | - 127.0.0.2 57 | - 113.74.26.106/24 58 | 59 | - match: 60 | prefix: "/bar" 61 | route: 62 | cluster: web_service 63 | typed_per_filter_config: 64 | envoy.filters.http.lua: 65 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 66 | name: entry.lua 67 | metadata: 68 | filter_metadata: 69 | envoy.filters.http.lua: 70 | plugins: 71 | - name: redirect 72 | conf: 73 | ret_code: 301 74 | uri: "/redirected/path" 75 | 76 | - match: 77 | prefix: "/hello" 78 | route: 79 | cluster: web_service 80 | typed_per_filter_config: 81 | envoy.filters.http.lua: 82 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.LuaPerRoute 83 | name: entry.lua 84 | metadata: 85 | filter_metadata: 86 | envoy.filters.http.lua: 87 | plugins: 88 | - name: referer-restriction 89 | conf: 90 | bypass_missing: false 91 | whitelist: 92 | - "127.0.0.1" 93 | 94 | - match: 95 | prefix: "/" 96 | route: 97 | cluster: web_service 98 | http_filters: 99 | - name: entry.lua 100 | typed_config: 101 | "@type": type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua 102 | inline_code: | 103 | function envoy_on_request(request_handle) 104 | end 105 | source_codes: 106 | entry.lua: 107 | filename: /apisix/entry.lua 108 | - name: envoy.router 109 | typed_config: {} 110 | 111 | clusters: 112 | - name: web_service 113 | connect_timeout: 0.25s 114 | type: strict_dns 115 | lb_policy: round_robin 116 | load_assignment: 117 | cluster_name: web_service 118 | endpoints: 119 | - lb_endpoints: 120 | - endpoint: 121 | address: 122 | socket_address: 123 | address: web_service 124 | port_value: 80 125 | admin: 126 | access_log_path: "/dev/null" 127 | address: 128 | socket_address: 129 | address: 0.0.0.0 130 | port_value: 8001 131 | -------------------------------------------------------------------------------- /licenses/LICENSE-envoy.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner]. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /licenses/LICENSE-lua-resty-ipmatcher.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /licenses/LICENSE-neturl.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-2013 Bertrand Mansion 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /lua/apisix/core.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | return { 19 | ctx = require("apisix.core.ctx"), 20 | json = require("apisix.core.json"), -- need a better json lib 21 | lrucache = require("apisix.core.lrucache"), 22 | plugin = require("apisix.core.plugin"), 23 | re = require("apisix.core.re"), 24 | schema = require("apisix.schema_def"), 25 | string = require("apisix.core.string"), 26 | table = require("apisix.core.table"), 27 | version = require("apisix.core.version"), 28 | 29 | empty_tab= {}, 30 | } 31 | -------------------------------------------------------------------------------- /lua/apisix/core/ctx.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local _M = {version = 0.2} 18 | 19 | 20 | local function get_client_ip(handler) 21 | local streamInfo = handler:streamInfo() 22 | if not streamInfo then 23 | return nil 24 | end 25 | 26 | local ip = streamInfo:downstreamLocalAddress() 27 | if ip then 28 | return ip 29 | end 30 | 31 | ip = streamInfo:downstreamDirectRemoteAddress() 32 | if ip then 33 | return ip 34 | end 35 | end 36 | 37 | --- Note: envoy doesn't support context for lua currently. using a global var as ctx temporarily. 38 | --- TODO: need a better implement and more vars 39 | local var = {} 40 | function _M.set_vars_meta(ctx, handler) 41 | table.clear(var) 42 | local headers = handler:headers() 43 | for key, value in pairs(headers) do 44 | if key == ":authority" then 45 | var.host = value 46 | elseif key == ":path" then 47 | var.request_uri = value 48 | elseif key == ":method" then 49 | var.method = value 50 | elseif key == ":scheme" or key == "x-forwarded-proto" then 51 | var.scheme = value 52 | else 53 | var[key] = value 54 | end 55 | end 56 | 57 | var.remote_addr = get_client_ip(handler) 58 | 59 | ctx.var = var 60 | end 61 | 62 | 63 | return _M 64 | -------------------------------------------------------------------------------- /lua/apisix/core/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2020 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | local string = string 25 | local error = error 26 | local rawget = rawget 27 | local next = next 28 | local pairs = pairs 29 | local type = type 30 | local ipairs = ipairs 31 | local table = table 32 | local math = math 33 | local tostring = tostring 34 | local select = select 35 | local tonumber = tonumber 36 | 37 | local json = { _version = "0.1.2" } 38 | 39 | ------------------------------------------------------------------------------- 40 | -- Encode 41 | ------------------------------------------------------------------------------- 42 | 43 | local encode 44 | 45 | local escape_char_map = { 46 | [ "\\" ] = "\\", 47 | [ "\"" ] = "\"", 48 | [ "\b" ] = "b", 49 | [ "\f" ] = "f", 50 | [ "\n" ] = "n", 51 | [ "\r" ] = "r", 52 | [ "\t" ] = "t", 53 | } 54 | 55 | local escape_char_map_inv = { [ "/" ] = "/" } 56 | for k, v in pairs(escape_char_map) do 57 | escape_char_map_inv[v] = k 58 | end 59 | 60 | 61 | local function escape_char(c) 62 | return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) 63 | end 64 | 65 | 66 | local function encode_nil(val) 67 | return "null" 68 | end 69 | 70 | 71 | local function encode_table(val, stack) 72 | local res = {} 73 | stack = stack or {} 74 | 75 | -- Circular reference? 76 | if stack[val] then error("circular reference") end 77 | 78 | stack[val] = true 79 | 80 | if rawget(val, 1) ~= nil or next(val) == nil then 81 | -- Treat as array -- check keys are valid and it is not sparse 82 | local n = 0 83 | for k in pairs(val) do 84 | if type(k) ~= "number" then 85 | error("invalid table: mixed or invalid key types") 86 | end 87 | n = n + 1 88 | end 89 | if n ~= #val then 90 | error("invalid table: sparse array") 91 | end 92 | -- Encode 93 | for i, v in ipairs(val) do 94 | table.insert(res, encode(v, stack)) 95 | end 96 | stack[val] = nil 97 | return "[" .. table.concat(res, ",") .. "]" 98 | 99 | else 100 | -- Treat as an object 101 | for k, v in pairs(val) do 102 | if type(k) ~= "string" then 103 | error("invalid table: mixed or invalid key types") 104 | end 105 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 106 | end 107 | stack[val] = nil 108 | return "{" .. table.concat(res, ",") .. "}" 109 | end 110 | end 111 | 112 | 113 | local function encode_string(val) 114 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 115 | end 116 | 117 | 118 | local function encode_number(val) 119 | -- Check for NaN, -inf and inf 120 | if val ~= val or val <= -math.huge or val >= math.huge then 121 | error("unexpected number value '" .. tostring(val) .. "'") 122 | end 123 | return string.format("%.14g", val) 124 | end 125 | 126 | 127 | local type_func_map = { 128 | [ "nil" ] = encode_nil, 129 | [ "table" ] = encode_table, 130 | [ "string" ] = encode_string, 131 | [ "number" ] = encode_number, 132 | [ "boolean" ] = tostring, 133 | } 134 | 135 | 136 | encode = function(val, stack) 137 | local t = type(val) 138 | local f = type_func_map[t] 139 | if f then 140 | return f(val, stack) 141 | end 142 | error("unexpected type '" .. t .. "'") 143 | end 144 | 145 | 146 | function json.encode(val) 147 | return ( encode(val) ) 148 | end 149 | 150 | 151 | ------------------------------------------------------------------------------- 152 | -- Decode 153 | ------------------------------------------------------------------------------- 154 | 155 | local parse 156 | 157 | local function create_set(...) 158 | local res = {} 159 | for i = 1, select("#", ...) do 160 | res[ select(i, ...) ] = true 161 | end 162 | return res 163 | end 164 | 165 | local space_chars = create_set(" ", "\t", "\r", "\n") 166 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 167 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 168 | local literals = create_set("true", "false", "null") 169 | 170 | local literal_map = { 171 | [ "true" ] = true, 172 | [ "false" ] = false, 173 | [ "null" ] = nil, 174 | } 175 | 176 | 177 | local function next_char(str, idx, set, negate) 178 | for i = idx, #str do 179 | if set[str:sub(i, i)] ~= negate then 180 | return i 181 | end 182 | end 183 | return #str + 1 184 | end 185 | 186 | 187 | local function decode_error(str, idx, msg) 188 | local line_count = 1 189 | local col_count = 1 190 | for i = 1, idx - 1 do 191 | col_count = col_count + 1 192 | if str:sub(i, i) == "\n" then 193 | line_count = line_count + 1 194 | col_count = 1 195 | end 196 | end 197 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 198 | end 199 | 200 | 201 | local function codepoint_to_utf8(n) 202 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 203 | local f = math.floor 204 | if n <= 0x7f then 205 | return string.char(n) 206 | elseif n <= 0x7ff then 207 | return string.char(f(n / 64) + 192, n % 64 + 128) 208 | elseif n <= 0xffff then 209 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 210 | elseif n <= 0x10ffff then 211 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 212 | f(n % 4096 / 64) + 128, n % 64 + 128) 213 | end 214 | error( string.format("invalid unicode codepoint '%x'", n) ) 215 | end 216 | 217 | 218 | local function parse_unicode_escape(s) 219 | local n1 = tonumber( s:sub(1, 4), 16 ) 220 | local n2 = tonumber( s:sub(7, 10), 16 ) 221 | -- Surrogate pair? 222 | if n2 then 223 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 224 | else 225 | return codepoint_to_utf8(n1) 226 | end 227 | end 228 | 229 | 230 | local function parse_string(str, i) 231 | local res = "" 232 | local j = i + 1 233 | local k = j 234 | 235 | while j <= #str do 236 | local x = str:byte(j) 237 | 238 | if x < 32 then 239 | decode_error(str, j, "control character in string") 240 | 241 | elseif x == 92 then -- `\`: Escape 242 | res = res .. str:sub(k, j - 1) 243 | j = j + 1 244 | local c = str:sub(j, j) 245 | if c == "u" then 246 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) 247 | or str:match("^%x%x%x%x", j + 1) 248 | or decode_error(str, j - 1, "invalid unicode escape in string") 249 | res = res .. parse_unicode_escape(hex) 250 | j = j + #hex 251 | else 252 | if not escape_chars[c] then 253 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") 254 | end 255 | res = res .. escape_char_map_inv[c] 256 | end 257 | k = j + 1 258 | 259 | elseif x == 34 then -- `"`: End of string 260 | res = res .. str:sub(k, j - 1) 261 | return res, j + 1 262 | end 263 | 264 | j = j + 1 265 | end 266 | 267 | decode_error(str, i, "expected closing quote for string") 268 | end 269 | 270 | 271 | local function parse_number(str, i) 272 | local x = next_char(str, i, delim_chars) 273 | local s = str:sub(i, x - 1) 274 | local n = tonumber(s) 275 | if not n then 276 | decode_error(str, i, "invalid number '" .. s .. "'") 277 | end 278 | return n, x 279 | end 280 | 281 | 282 | local function parse_literal(str, i) 283 | local x = next_char(str, i, delim_chars) 284 | local word = str:sub(i, x - 1) 285 | if not literals[word] then 286 | decode_error(str, i, "invalid literal '" .. word .. "'") 287 | end 288 | return literal_map[word], x 289 | end 290 | 291 | 292 | local function parse_array(str, i) 293 | local res = {} 294 | local n = 1 295 | i = i + 1 296 | while 1 do 297 | local x 298 | i = next_char(str, i, space_chars, true) 299 | -- Empty / end of array? 300 | if str:sub(i, i) == "]" then 301 | i = i + 1 302 | break 303 | end 304 | -- Read token 305 | x, i = parse(str, i) 306 | res[n] = x 307 | n = n + 1 308 | -- Next token 309 | i = next_char(str, i, space_chars, true) 310 | local chr = str:sub(i, i) 311 | i = i + 1 312 | if chr == "]" then break end 313 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 314 | end 315 | return res, i 316 | end 317 | 318 | 319 | local function parse_object(str, i) 320 | local res = {} 321 | i = i + 1 322 | while 1 do 323 | local key, val 324 | i = next_char(str, i, space_chars, true) 325 | -- Empty / end of object? 326 | if str:sub(i, i) == "}" then 327 | i = i + 1 328 | break 329 | end 330 | -- Read key 331 | if str:sub(i, i) ~= '"' then 332 | decode_error(str, i, "expected string for key") 333 | end 334 | key, i = parse(str, i) 335 | -- Read ':' delimiter 336 | i = next_char(str, i, space_chars, true) 337 | if str:sub(i, i) ~= ":" then 338 | decode_error(str, i, "expected ':' after key") 339 | end 340 | i = next_char(str, i + 1, space_chars, true) 341 | -- Read value 342 | val, i = parse(str, i) 343 | -- Set 344 | res[key] = val 345 | -- Next token 346 | i = next_char(str, i, space_chars, true) 347 | local chr = str:sub(i, i) 348 | i = i + 1 349 | if chr == "}" then break end 350 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 351 | end 352 | return res, i 353 | end 354 | 355 | 356 | local char_func_map = { 357 | [ '"' ] = parse_string, 358 | [ "0" ] = parse_number, 359 | [ "1" ] = parse_number, 360 | [ "2" ] = parse_number, 361 | [ "3" ] = parse_number, 362 | [ "4" ] = parse_number, 363 | [ "5" ] = parse_number, 364 | [ "6" ] = parse_number, 365 | [ "7" ] = parse_number, 366 | [ "8" ] = parse_number, 367 | [ "9" ] = parse_number, 368 | [ "-" ] = parse_number, 369 | [ "t" ] = parse_literal, 370 | [ "f" ] = parse_literal, 371 | [ "n" ] = parse_literal, 372 | [ "[" ] = parse_array, 373 | [ "{" ] = parse_object, 374 | } 375 | 376 | 377 | parse = function(str, idx) 378 | local chr = str:sub(idx, idx) 379 | local f = char_func_map[chr] 380 | if f then 381 | return f(str, idx) 382 | end 383 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 384 | end 385 | 386 | 387 | function json.decode(str) 388 | if type(str) ~= "string" then 389 | error("expected argument of type string, got " .. type(str)) 390 | end 391 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 392 | idx = next_char(str, idx, space_chars, true) 393 | if idx <= #str then 394 | decode_error(str, idx, "trailing garbage") 395 | end 396 | return res 397 | end 398 | 399 | 400 | return json -------------------------------------------------------------------------------- /lua/apisix/core/lrucache.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | local lru_new = require("resty.lrucache").new 19 | -- local resty_lock = require("resty.lock") 20 | -- local log = require("apisix.core.log") 21 | local tostring = tostring 22 | local concat = table.concat 23 | 24 | 25 | -- local lock_shdict_name = "lrucache-lock" 26 | -- if ngx.config.subsystem == "stream" then 27 | -- lock_shdict_name = lock_shdict_name .. "-" .. ngx.config.subsystem 28 | -- end 29 | 30 | 31 | local can_yield_phases = { 32 | ssl_session_fetch = true, 33 | ssl_session_store = true, 34 | rewrite = true, 35 | access = true, 36 | content = true, 37 | timer = true 38 | } 39 | 40 | local GLOBAL_ITEMS_COUNT = 1024 41 | local GLOBAL_TTL = 60 * 60 -- 60 min 42 | local PLUGIN_TTL = 5 * 60 -- 5 min 43 | local PLUGIN_ITEMS_COUNT = 8 44 | local global_lru_fun 45 | 46 | 47 | local function fetch_valid_cache(lru_obj, invalid_stale, item_ttl, 48 | item_release, key, version) 49 | local obj, stale_obj = lru_obj:get(key) 50 | if obj and obj.ver == version then 51 | return obj 52 | end 53 | 54 | if not invalid_stale and stale_obj and stale_obj.ver == version then 55 | lru_obj:set(key, stale_obj, item_ttl) 56 | return stale_obj 57 | end 58 | 59 | if item_release and obj then 60 | item_release(obj.val) 61 | end 62 | 63 | return nil 64 | end 65 | 66 | 67 | local function new_lru_fun(opts) 68 | local item_count, item_ttl 69 | if opts and opts.type == 'plugin' then 70 | item_count = opts.count or PLUGIN_ITEMS_COUNT 71 | item_ttl = opts.ttl or PLUGIN_TTL 72 | else 73 | item_count = opts and opts.count or GLOBAL_ITEMS_COUNT 74 | item_ttl = opts and opts.ttl or GLOBAL_TTL 75 | end 76 | 77 | local item_release = opts and opts.release 78 | local invalid_stale = opts and opts.invalid_stale 79 | local serial_creating = opts and opts.serial_creating 80 | local lru_obj = lru_new(item_count) 81 | 82 | return function (key, version, create_obj_fun, ...) 83 | -- local lock, err = resty_lock:new(lock_shdict_name) 84 | -- if not lock then 85 | -- return nil, "failed to create lock: " .. err 86 | -- end 87 | 88 | -- local key_s = tostring(key) 89 | -- log.info("try to lock with key ", key_s) 90 | 91 | -- local elapsed, err = lock:lock(key_s) 92 | -- if not elapsed then 93 | -- return nil, "failed to acquire the lock: " .. err 94 | -- end 95 | 96 | cache_obj = fetch_valid_cache(lru_obj, invalid_stale, item_ttl, 97 | nil, key, version) 98 | if cache_obj then 99 | -- lock:unlock() 100 | -- log.info("unlock with key ", key_s) 101 | return cache_obj.val 102 | end 103 | 104 | local obj, err = create_obj_fun(...) 105 | if obj ~= nil then 106 | lru_obj:set(key, {val = obj, ver = version}, item_ttl) 107 | end 108 | -- lock:unlock() 109 | -- log.info("unlock with key ", key_s) 110 | 111 | return obj, err 112 | end 113 | end 114 | 115 | 116 | global_lru_fun = new_lru_fun() 117 | 118 | 119 | local plugin_ctx 120 | do 121 | local key_buf = { 122 | nil, 123 | nil, 124 | nil, 125 | } 126 | 127 | function plugin_ctx(lrucache, api_ctx, extra_key, create_obj_func, ...) 128 | key_buf[1] = api_ctx.conf_type 129 | key_buf[2] = api_ctx.conf_id 130 | 131 | local key 132 | if extra_key then 133 | key_buf[3] = extra_key 134 | key = concat(key_buf, "#", 1, 3) 135 | else 136 | key = concat(key_buf, "#", 1, 2) 137 | end 138 | 139 | return lrucache(key, api_ctx.conf_version, create_obj_func, ...) 140 | end 141 | end 142 | 143 | 144 | local _M = { 145 | version = 0.1, 146 | new = new_lru_fun, 147 | global = global_lru_fun, 148 | plugin_ctx = plugin_ctx, 149 | } 150 | 151 | 152 | return _M 153 | -------------------------------------------------------------------------------- /lua/apisix/core/plugin.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local _M = {version = 0.2} 18 | 19 | local json_encode = require("apisix.core.json").encode 20 | 21 | function _M.run(ctx, plugins) 22 | local handle = ctx.handle 23 | if not ctx then 24 | handle:logWarn("no ctx" ) 25 | return 26 | end 27 | 28 | plugins = plugins or ctx.plugins 29 | if not plugins or #plugins == 0 then 30 | handle:logWarn("no plugins" ) 31 | return ctx 32 | end 33 | 34 | local phase = ctx.phase 35 | local phases = { 36 | request = { 37 | 'access', 38 | 'rewrite' 39 | }, 40 | response = { 41 | 'header_filter', 42 | 'body_filter' 43 | } 44 | } 45 | 46 | for _, plugin in ipairs(plugins) do 47 | local ok, plugin_object = pcall(require, "apisix.plugins." .. plugin.name) 48 | if ok then 49 | local apisix_phases = phases[phase] 50 | for _, phase_name in ipairs(apisix_phases) do 51 | local phase_func = plugin_object[phase_name] 52 | if phase_func then 53 | local conf = plugin.conf 54 | local status, body = phase_func(conf, ctx) 55 | handle:logWarn("phase_name:" .. phase_name) 56 | handle:logWarn("status" .. status) 57 | if status then 58 | handle:logWarn("resp") 59 | if type(body) == "table" then 60 | body = json_encode(body) 61 | end 62 | handle:respond( 63 | {[":status"] = status, 64 | -- ["Location"] = uri, 65 | ["server"] = "apisix"}, 66 | body or "" ) 67 | return 68 | end 69 | end 70 | end 71 | else 72 | handle:logWarn("fail to load plugin:" .. plugin.name) 73 | end 74 | end 75 | 76 | return ctx 77 | end 78 | 79 | 80 | return _M 81 | -------------------------------------------------------------------------------- /lua/apisix/core/re.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local _M = {version = 0.2} 18 | 19 | local string_match = string.match 20 | 21 | function _M.parse_uri(_, uri, query_in_path) 22 | if query_in_path == nil then query_in_path = true end 23 | 24 | local m, err = string_match(uri, [[^(?:(http[s]?):)?//([^:/\?]+)(?::(\d+))?([^\?]*)\??(.*)]]) 25 | 26 | if not m then 27 | if err then 28 | return nil, "failed to match the uri: " .. uri .. ", " .. err 29 | end 30 | 31 | return nil, "bad uri: " .. uri 32 | else 33 | -- If the URI is schemaless (i.e. //example.com) try to use our current 34 | -- request scheme. 35 | if not m[1] then 36 | return nil, "schemaless URIs require a request context: " .. uri 37 | end 38 | 39 | if m[3] then 40 | m[3] = tonumber(m[3]) 41 | else 42 | if m[1] == "https" then 43 | m[3] = 443 44 | else 45 | m[3] = 80 46 | end 47 | end 48 | if not m[4] or "" == m[4] then m[4] = "/" end 49 | 50 | if query_in_path and m[5] and m[5] ~= "" then 51 | m[4] = m[4] .. "?" .. m[5] 52 | m[5] = nil 53 | end 54 | 55 | return m, nil 56 | end 57 | end 58 | 59 | return _M 60 | -------------------------------------------------------------------------------- /lua/apisix/core/schema.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | 18 | local jsonschema = {generate_validator = {}} 19 | local lrucache = require("apisix.core.lrucache") 20 | local cached_validator = lrucache.new({count = 1000, ttl = 0}) 21 | local pcall = pcall 22 | 23 | local _M = { 24 | version = 0.3, 25 | TYPE_CONSUMER = 1, 26 | } 27 | 28 | 29 | local function create_validator(schema) 30 | -- local code = jsonschema.generate_validator_code(schema, opts) 31 | -- local file2=io.output("/tmp/2.txt") 32 | -- file2:write(code) 33 | -- file2:close() 34 | local ok, res = pcall(jsonschema.generate_validator, schema) 35 | if ok then 36 | return res 37 | end 38 | 39 | return nil, res -- error message 40 | end 41 | 42 | local function get_validator(schema) 43 | local validator, err = cached_validator(schema, nil, 44 | create_validator, schema) 45 | 46 | if not validator then 47 | return nil, err 48 | end 49 | 50 | return validator, nil 51 | end 52 | 53 | function _M.check(schema, json) 54 | local validator, err = get_validator(schema) 55 | 56 | if not validator then 57 | return false, err 58 | end 59 | 60 | return validator(json) 61 | end 62 | 63 | _M.valid = get_validator 64 | 65 | return _M 66 | -------------------------------------------------------------------------------- /lua/apisix/core/string.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local error = error 18 | local type = type 19 | local str_find = string.find 20 | local ffi = require("ffi") 21 | local C = ffi.C 22 | local ffi_cast = ffi.cast 23 | 24 | 25 | ffi.cdef[[ 26 | int memcmp(const void *s1, const void *s2, size_t n); 27 | ]] 28 | 29 | 30 | local _M = { 31 | version = 0.1, 32 | } 33 | 34 | 35 | setmetatable(_M, {__index = string}) 36 | 37 | 38 | -- find a needle from a haystack in the plain text way 39 | function _M.find(haystack, needle, from) 40 | return str_find(haystack, needle, from or 1, true) 41 | end 42 | 43 | 44 | function _M.has_prefix(s, prefix) 45 | if type(s) ~= "string" or type(prefix) ~= "string" then 46 | error("unexpected type: s:" .. type(s) .. ", prefix:" .. type(prefix)) 47 | end 48 | if #s < #prefix then 49 | return false 50 | end 51 | local rc = C.memcmp(s, prefix, #prefix) 52 | return rc == 0 53 | end 54 | 55 | 56 | function _M.has_suffix(s, suffix) 57 | if type(s) ~= "string" or type(suffix) ~= "string" then 58 | error("unexpected type: s:" .. type(s) .. ", suffix:" .. type(suffix)) 59 | end 60 | if #s < #suffix then 61 | return false 62 | end 63 | local rc = C.memcmp(ffi_cast("char *", s) + #s - #suffix, suffix, #suffix) 64 | return rc == 0 65 | end 66 | 67 | 68 | return _M 69 | -------------------------------------------------------------------------------- /lua/apisix/core/table.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local newproxy = newproxy 18 | local getmetatable = getmetatable 19 | local setmetatable = setmetatable 20 | local select = select 21 | local new_tab = require("table.new") 22 | local pairs = pairs 23 | local type = type 24 | local string = string 25 | 26 | 27 | local _M = { 28 | version = 0.2, 29 | new = new_tab, 30 | clear = require("table.clear"), 31 | insert = table.insert, 32 | concat = table.concat, 33 | sort = table.sort, 34 | } 35 | 36 | 37 | setmetatable(_M, {__index = table}) 38 | 39 | 40 | local nkeys 41 | do 42 | local ok, table_nkeys = pcall(require, 'table.nkeys') 43 | if ok then 44 | nkeys = table_nkeys 45 | else 46 | nkeys = function(t) 47 | local count = 0 48 | for _, _ in pairs(t) do 49 | count = count + 1 50 | end 51 | return count 52 | end 53 | end 54 | end 55 | _M.nkeys = nkeys 56 | 57 | 58 | function _M.insert_tail(tab, ...) 59 | local idx = #tab 60 | for i = 1, select('#', ...) do 61 | idx = idx + 1 62 | tab[idx] = select(i, ...) 63 | end 64 | 65 | return idx 66 | end 67 | 68 | 69 | function _M.set(tab, ...) 70 | for i = 1, select('#', ...) do 71 | tab[i] = select(i, ...) 72 | end 73 | end 74 | 75 | 76 | -- only work under lua51 or luajit 77 | function _M.setmt__gc(t, mt) 78 | local prox = newproxy(true) 79 | getmetatable(prox).__gc = function() mt.__gc(t) end 80 | t[prox] = true 81 | return setmetatable(t, mt) 82 | end 83 | 84 | 85 | local function deepcopy(orig) 86 | local orig_type = type(orig) 87 | if orig_type ~= 'table' then 88 | return orig 89 | end 90 | 91 | -- If the array-like table contains nil in the middle, 92 | -- the len might be smaller than the expected. 93 | -- But it doesn't affect the correctness. 94 | local len = #orig 95 | local copy = new_tab(len, nkeys(orig) - len) 96 | for orig_key, orig_value in pairs(orig) do 97 | copy[orig_key] = deepcopy(orig_value) 98 | end 99 | 100 | return copy 101 | end 102 | _M.deepcopy = deepcopy 103 | 104 | local ngx_null = nil 105 | local function merge(origin, extend) 106 | for k,v in pairs(extend) do 107 | if type(v) == "table" then 108 | if type(origin[k] or false) == "table" then 109 | if _M.nkeys(origin[k]) ~= #origin[k] then 110 | merge(origin[k] or {}, extend[k] or {}) 111 | else 112 | origin[k] = v 113 | end 114 | else 115 | origin[k] = v 116 | end 117 | elseif v == ngx_null then 118 | origin[k] = nil 119 | else 120 | origin[k] = v 121 | end 122 | end 123 | 124 | return origin 125 | end 126 | _M.merge = merge 127 | 128 | 129 | local function patch(node_value, sub_path, conf) 130 | local sub_value = node_value 131 | local sub_paths = string.split(sub_path, "/") 132 | for i = 1, #sub_paths - 1 do 133 | local sub_name = sub_paths[i] 134 | if sub_value[sub_name] == nil then 135 | sub_value[sub_name] = {} 136 | end 137 | 138 | sub_value = sub_value[sub_name] 139 | 140 | if type(sub_value) ~= "table" then 141 | return 400, "invalid sub-path: /" 142 | .. _M.concat(sub_paths, 1, i) 143 | end 144 | end 145 | 146 | if type(sub_value) ~= "table" then 147 | return 400, "invalid sub-path: /" .. sub_path 148 | end 149 | 150 | local sub_name = sub_paths[#sub_paths] 151 | if sub_name and sub_name ~= "" then 152 | sub_value[sub_name] = conf 153 | else 154 | node_value = conf 155 | end 156 | 157 | return nil, nil, node_value 158 | end 159 | _M.patch = patch 160 | 161 | 162 | return _M 163 | -------------------------------------------------------------------------------- /lua/apisix/core/version.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | return { 18 | VERSION = "0.1.0" 19 | } 20 | -------------------------------------------------------------------------------- /lua/apisix/entry.lua: -------------------------------------------------------------------------------- 1 | local core = require("apisix.core") 2 | local ctx = {} 3 | 4 | function envoy_on_request(request_handle) 5 | core.ctx.set_vars_meta(ctx, request_handle) 6 | 7 | local metadata = request_handle:metadata() 8 | local plugins = metadata:get("plugins") 9 | 10 | ctx.phase = "request" 11 | ctx.handle = request_handle 12 | 13 | core.plugin.run(ctx, plugins) 14 | end 15 | 16 | function envoy_on_response(response_handle) 17 | local metadata = response_handle:metadata() 18 | local plugins = metadata:get("plugins") 19 | 20 | ctx.phase = "response" 21 | ctx.handle = response_handle 22 | 23 | core.plugin.run(ctx, plugins) 24 | end -------------------------------------------------------------------------------- /lua/apisix/plugins/ip-restriction.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local ipairs = ipairs 18 | local core = require("apisix.core") 19 | local ipmatcher = require("resty.ipmatcher") 20 | local str_sub = string.sub 21 | local str_find = string.find 22 | local tonumber = tonumber 23 | local lrucache = core.lrucache.new({ 24 | ttl = 300, count = 512 25 | }) 26 | 27 | 28 | local schema = { 29 | type = "object", 30 | oneOf = { 31 | { 32 | title = "whitelist", 33 | properties = { 34 | whitelist = { 35 | type = "array", 36 | items = {anyOf = core.schema.ip_def}, 37 | minItems = 1 38 | }, 39 | }, 40 | required = {"whitelist"}, 41 | additionalProperties = false, 42 | }, 43 | { 44 | title = "blacklist", 45 | properties = { 46 | blacklist = { 47 | type = "array", 48 | items = {anyOf = core.schema.ip_def}, 49 | minItems = 1 50 | } 51 | }, 52 | required = {"blacklist"}, 53 | additionalProperties = false, 54 | } 55 | } 56 | } 57 | 58 | 59 | local plugin_name = "ip-restriction" 60 | 61 | 62 | local _M = { 63 | version = 0.1, 64 | priority = 3000, -- TODO: add a type field, may be a good idea 65 | name = plugin_name, 66 | schema = schema, 67 | } 68 | 69 | 70 | local function valid_ip(ip) 71 | local mask = 0 72 | local sep_pos = str_find(ip, "/") 73 | if sep_pos then 74 | mask = str_sub(ip, sep_pos + 1) 75 | mask = tonumber(mask) 76 | if mask < 0 or mask > 128 then 77 | return false 78 | end 79 | ip = str_sub(ip, 1, sep_pos - 1) 80 | end 81 | 82 | if ipmatcher.parse_ipv4(ip) then 83 | if mask < 0 or mask > 32 then 84 | return false 85 | end 86 | return true 87 | end 88 | 89 | if mask < 0 or mask > 128 then 90 | return false 91 | end 92 | return ipmatcher.parse_ipv6(ip) 93 | end 94 | 95 | 96 | function _M.check_schema(conf) 97 | local ok, err = core.schema.check(schema, conf) 98 | 99 | if not ok then 100 | return false, err 101 | end 102 | 103 | if conf.whitelist and #conf.whitelist > 0 then 104 | for _, cidr in ipairs(conf.whitelist) do 105 | if not valid_ip(cidr) then 106 | return false, "invalid ip address: " .. cidr 107 | end 108 | end 109 | end 110 | 111 | if conf.blacklist and #conf.blacklist > 0 then 112 | for _, cidr in ipairs(conf.blacklist) do 113 | if not valid_ip(cidr) then 114 | return false, "invalid ip address: " .. cidr 115 | end 116 | end 117 | end 118 | 119 | return true 120 | end 121 | 122 | 123 | local function create_ip_matcher(ip_list) 124 | local ip, err = ipmatcher.new(ip_list) 125 | if not ip then 126 | core.log.error("failed to create ip matcher: ", err, 127 | " ip list: ", core.json.delay_encode(ip_list)) 128 | return nil 129 | end 130 | 131 | return ip 132 | end 133 | 134 | 135 | function _M.access(conf, ctx) 136 | local block = false 137 | local remote_addr = ctx.var.remote_addr 138 | 139 | if conf.blacklist and #conf.blacklist > 0 then 140 | local matcher = lrucache(conf.blacklist, nil, 141 | create_ip_matcher, conf.blacklist) 142 | if matcher then 143 | block = matcher:match(remote_addr) 144 | end 145 | end 146 | 147 | if conf.whitelist and #conf.whitelist > 0 then 148 | local matcher = lrucache(conf.whitelist, nil, 149 | create_ip_matcher, conf.whitelist) 150 | if matcher then 151 | block = not matcher:match(remote_addr) 152 | end 153 | end 154 | 155 | if block then 156 | return 403, { message = "Your IP address is not allowed" } 157 | end 158 | end 159 | 160 | 161 | return _M 162 | -------------------------------------------------------------------------------- /lua/apisix/plugins/redirect.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local core = require("apisix.core") 18 | local tab_insert = table.insert 19 | local tab_concat = table.concat 20 | -- need to implement core.re 21 | local re_gmatch = string.gmatch 22 | local ipairs = ipairs 23 | 24 | 25 | -- need to implement core.log 26 | -- can't use lrucache currently 27 | -- local lrucache = core.lrucache.new({ 28 | -- ttl = 300, count = 100 29 | -- }) 30 | 31 | 32 | local schema = { 33 | type = "object", 34 | properties = { 35 | ret_code = {type = "integer", minimum = 200, default = 302}, 36 | uri = {type = "string", minLength = 2}, 37 | http_to_https = {type = "boolean"}, -- default is false 38 | }, 39 | oneOf = { 40 | {required = {"uri"}}, 41 | {required = {"http_to_https"}} 42 | } 43 | } 44 | 45 | 46 | local plugin_name = "rewrite" 47 | 48 | local _M = { 49 | version = 0.1, 50 | priority = 900, 51 | name = plugin_name, 52 | schema = schema, 53 | } 54 | 55 | 56 | local function parse_uri(uri) 57 | -- regular expression doesn't work here now 58 | local reg = [[ (\\\$[0-9a-zA-Z_]+) | ]] -- \$host 59 | .. [[ \$\{([0-9a-zA-Z_]+)\} | ]] -- ${host} 60 | .. [[ \$([0-9a-zA-Z_]+) | ]] -- $host 61 | .. [[ (\$|[^$\\]+) ]] -- $ or others 62 | local iterator, err = re_gmatch(uri, reg, "jiox") 63 | if not iterator then 64 | return nil, err 65 | end 66 | 67 | local t = {} 68 | while true do 69 | local m, err = iterator() 70 | if err then 71 | return nil, err 72 | end 73 | 74 | if not m then 75 | break 76 | end 77 | 78 | tab_insert(t, m) 79 | end 80 | 81 | return t 82 | end 83 | 84 | 85 | function _M.check_schema(conf) 86 | -- not works now. need to implement core.schema 87 | local ok, err = core.schema.check(schema, conf) 88 | if not ok then 89 | return false, err 90 | end 91 | 92 | if conf.uri then 93 | local uri_segs, err = parse_uri(conf.uri) 94 | if not uri_segs then 95 | return false, err 96 | end 97 | end 98 | 99 | return true 100 | end 101 | 102 | 103 | local tmp = {} 104 | local function concat_new_uri(uri, ctx) 105 | local passed_uri_segs, err = lrucache(uri, nil, parse_uri, uri) 106 | if not passed_uri_segs then 107 | return nil, err 108 | end 109 | 110 | core.table.clear(tmp) 111 | 112 | for _, uri_segs in ipairs(passed_uri_segs) do 113 | local pat1 = uri_segs[1] -- \$host 114 | local pat2 = uri_segs[2] -- ${host} 115 | local pat3 = uri_segs[3] -- $host 116 | local pat4 = uri_segs[4] -- $ or others 117 | 118 | if pat2 or pat3 then 119 | tab_insert(tmp, ctx.var[pat2 or pat3]) 120 | else 121 | tab_insert(tmp, pat1 or pat4) 122 | end 123 | end 124 | 125 | return tab_concat(tmp, "") 126 | end 127 | 128 | 129 | function _M.rewrite(conf, ctx) 130 | local ret_code = conf.ret_code 131 | local uri = conf.uri 132 | local handle = ctx.handle 133 | 134 | if conf.http_to_https and ctx.var.scheme == "http" then 135 | -- TODO: add test case 136 | -- PR: https://github.com/apache/apisix/pull/1958 137 | uri = "https://$host$request_uri" 138 | ret_code = 301 139 | end 140 | 141 | if uri and ret_code then 142 | -- local new_uri, err = concat_new_uri(uri, ctx) 143 | -- if not new_uri then 144 | -- core.log.error("failed to generate new uri by: ", uri, " error: ", 145 | -- err) 146 | -- return 500 147 | -- end 148 | 149 | -- todo 150 | handle:respond( 151 | {[":status"] = ret_code, 152 | ["Location"] = uri, 153 | ["server"] = "apisix"}, 154 | 155 | "nope") 156 | 157 | --- need to implement core.respond 158 | -- core.response.set_header("Location", new_uri) 159 | 160 | return ret_code 161 | end 162 | end 163 | 164 | 165 | return _M 166 | -------------------------------------------------------------------------------- /lua/apisix/plugins/referer-restriction.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local ipairs = ipairs 18 | local core = require("apisix.core") 19 | local net_url = require("net.url") 20 | local lrucache = core.lrucache.new({ 21 | ttl = 300, count = 512 22 | }) 23 | 24 | 25 | local schema = { 26 | type = "object", 27 | properties = { 28 | bypass_missing = { 29 | type = "boolean", 30 | default = false, 31 | }, 32 | whitelist = { 33 | type = "array", 34 | items = core.schema.host_def, 35 | minItems = 1 36 | }, 37 | }, 38 | required = {"whitelist"}, 39 | additionalProperties = false, 40 | } 41 | 42 | 43 | local plugin_name = "referer-restriction" 44 | 45 | 46 | local _M = { 47 | version = 0.1, 48 | priority = 2990, 49 | name = plugin_name, 50 | schema = schema, 51 | } 52 | 53 | 54 | function _M.check_schema(conf) 55 | return core.schema.check(schema, conf) 56 | end 57 | 58 | 59 | local function match_host(matcher, host) 60 | if matcher.map[host] then 61 | return true 62 | end 63 | for _, h in ipairs(matcher.suffixes) do 64 | if core.string.has_suffix(host, h) then 65 | return true 66 | end 67 | end 68 | return false 69 | end 70 | 71 | 72 | local function create_host_matcher(hosts) 73 | local hosts_suffix = {} 74 | local hosts_map = {} 75 | 76 | for _, h in ipairs(hosts) do 77 | if h:byte(1) == 42 then -- start with '*' 78 | core.table.insert(hosts_suffix, h:sub(2)) 79 | else 80 | hosts_map[h] = true 81 | end 82 | end 83 | 84 | return { 85 | suffixes = hosts_suffix, 86 | map = hosts_map, 87 | } 88 | end 89 | 90 | 91 | function _M.access(conf, ctx) 92 | local block = false 93 | local referer = ctx.var.referer 94 | local handle = ctx.handle 95 | handle:logWarn("referer:" .. referer) 96 | if referer then 97 | -- parse_uri doesn't support IPv6 literal, it is OK since we only 98 | -- expect hostname in the whitelist. 99 | -- See https://github.com/ledgetech/lua-resty-http/pull/104 100 | local uri = net_url.parse(referer) 101 | if not uri then 102 | -- malformed Referer 103 | referer = nil 104 | else 105 | -- take host part only 106 | referer = uri.host 107 | end 108 | end 109 | 110 | handle:logWarn("referer parsed:" .. referer) 111 | 112 | 113 | if not referer then 114 | block = not conf.bypass_missing 115 | 116 | elseif conf.whitelist then 117 | local matcher = lrucache(conf.whitelist, nil, 118 | create_host_matcher, conf.whitelist) 119 | block = not match_host(matcher, referer) 120 | end 121 | 122 | if block then 123 | return 403, { message = "Your referer host is not allowed" } 124 | end 125 | end 126 | 127 | 128 | return _M 129 | -------------------------------------------------------------------------------- /lua/apisix/plugins/uri-blocker.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local core = require("apisix.core") 18 | -- local re_compile = require("resty.core.regex").re_match_compile 19 | local re_find = string.find -- ngx.re.find 20 | local ipairs = ipairs 21 | 22 | local schema = { 23 | type = "object", 24 | properties = { 25 | block_rules = { 26 | type = "array", 27 | items = { 28 | type = "string", 29 | minLength = 1, 30 | maxLength = 4096, 31 | }, 32 | uniqueItems = true 33 | }, 34 | rejected_code = { 35 | type = "integer", 36 | minimum = 200, 37 | default = 403 38 | }, 39 | }, 40 | required = {"block_rules"}, 41 | } 42 | 43 | 44 | local plugin_name = "uri-blocker" 45 | 46 | local _M = { 47 | version = 0.1, 48 | priority = 2900, 49 | name = plugin_name, 50 | schema = schema, 51 | } 52 | 53 | 54 | function _M.check_schema(conf) 55 | local ok, err = core.schema.check(schema, conf) 56 | if not ok then 57 | return false, err 58 | end 59 | 60 | for i, re_rule in ipairs(conf.block_rules) do 61 | local ok, err = re_compile(re_rule, "j") 62 | -- core.log.warn("ok: ", tostring(ok), " err: ", tostring(err), 63 | -- " re_rule: ", re_rule) 64 | if not ok then 65 | return false, err 66 | end 67 | end 68 | 69 | return true 70 | end 71 | 72 | 73 | function _M.rewrite(conf, ctx) 74 | -- core.log.info("uri: ", ctx.var.request_uri) 75 | -- core.log.info("block uri rules: ", conf.block_rules_concat) 76 | 77 | if not conf.block_rules_concat then 78 | local block_rules = {} 79 | for i, re_rule in ipairs(conf.block_rules) do 80 | block_rules[i] = re_rule 81 | end 82 | 83 | conf.block_rules_concat = core.table.concat(block_rules, "|") 84 | -- core.log.info("concat block_rules: ", conf.block_rules_concat) 85 | end 86 | 87 | local handle = ctx.handle 88 | handle:logWarn("request_uri:" .. ctx.var.request_uri .. " conf.block_rules_concat: " .. conf.block_rules_concat) 89 | 90 | local from = re_find(ctx.var.request_uri, "[" .. conf.block_rules_concat .. "]") -- , "jo" 91 | 92 | handle:logWarn("from:" .. from) 93 | 94 | if from then 95 | return conf.rejected_code 96 | end 97 | end 98 | 99 | 100 | return _M 101 | -------------------------------------------------------------------------------- /lua/apisix/schema_def.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Licensed to the Apache Software Foundation (ASF) under one or more 3 | -- contributor license agreements. See the NOTICE file distributed with 4 | -- this work for additional information regarding copyright ownership. 5 | -- The ASF licenses this file to You under the Apache License, Version 2.0 6 | -- (the "License"); you may not use this file except in compliance with 7 | -- the License. You may obtain a copy of the License at 8 | -- 9 | -- http://www.apache.org/licenses/LICENSE-2.0 10 | -- 11 | -- Unless required by applicable law or agreed to in writing, software 12 | -- distributed under the License is distributed on an "AS IS" BASIS, 13 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | -- See the License for the specific language governing permissions and 15 | -- limitations under the License. 16 | -- 17 | local schema = require('apisix.core.schema') 18 | local setmetatable = setmetatable 19 | local error = error 20 | 21 | local _M = {version = 0.5} 22 | 23 | local plugins_schema = { 24 | type = "object" 25 | } 26 | 27 | local id_schema = { 28 | anyOf = { 29 | { 30 | type = "string", minLength = 1, maxLength = 64, 31 | pattern = [[^[a-zA-Z0-9-_.]+$]] 32 | }, 33 | {type = "integer", minimum = 1} 34 | } 35 | } 36 | 37 | local host_def_pat = "^\\*?[0-9a-zA-Z-.]+$" 38 | local host_def = { 39 | type = "string", 40 | pattern = host_def_pat, 41 | } 42 | _M.host_def = host_def 43 | 44 | 45 | local ipv4_def = "[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}" 46 | local ipv6_def = "([a-fA-F0-9]{0,4}:){0,8}(:[a-fA-F0-9]{0,4}){0,8}" 47 | .. "([a-fA-F0-9]{0,4})?" 48 | local ip_def = { 49 | {title = "IPv4", type = "string", pattern = "^" .. ipv4_def .. "$"}, 50 | {title = "IPv4/CIDR", type = "string", pattern = "^" .. ipv4_def .. "/[0-9]{1,2}$"}, 51 | {title = "IPv6", type = "string", pattern = "^" .. ipv6_def .. "$"}, 52 | {title = "IPv6/CIDR", type = "string", pattern = "^" .. ipv6_def .. "/[0-9]{1,3}$"}, 53 | } 54 | _M.ip_def = ip_def 55 | 56 | _M.uri_def = {type = "string", pattern = [=[^[^\/]+:\/\/([\da-zA-Z.-]+|\[[\da-fA-F:]+\])(:\d+)?]=]} 57 | 58 | _M.id_schema = id_schema 59 | 60 | _M.plugin_disable_schema = { 61 | disable = {type = "boolean"} 62 | } 63 | 64 | 65 | setmetatable(_M, { 66 | __index = schema, 67 | __newindex = function() error("no modification allowed") end, 68 | }) 69 | 70 | 71 | return _M 72 | -------------------------------------------------------------------------------- /lua/deps/net/url.lua: -------------------------------------------------------------------------------- 1 | -- neturl.lua - a robust url parser and builder 2 | -- 3 | -- Bertrand Mansion, 2011-2013; License MIT 4 | -- @module neturl 5 | -- @alias M 6 | 7 | local M = {} 8 | M.version = "0.9.0" 9 | 10 | --- url options 11 | -- separator is set to `&` by default but could be anything like `&amp;` or `;` 12 | -- @todo Add an option to limit the size of the argument table 13 | M.options = { 14 | separator = '&' 15 | } 16 | 17 | --- list of known and common scheme ports 18 | -- as documented in IANA URI scheme list 19 | M.services = { 20 | acap = 674, 21 | cap = 1026, 22 | dict = 2628, 23 | ftp = 21, 24 | gopher = 70, 25 | http = 80, 26 | https = 443, 27 | iax = 4569, 28 | icap = 1344, 29 | imap = 143, 30 | ipp = 631, 31 | ldap = 389, 32 | mtqp = 1038, 33 | mupdate = 3905, 34 | news = 2009, 35 | nfs = 2049, 36 | nntp = 119, 37 | rtsp = 554, 38 | sip = 5060, 39 | snmp = 161, 40 | telnet = 23, 41 | tftp = 69, 42 | vemmi = 575, 43 | afs = 1483, 44 | jms = 5673, 45 | rsync = 873, 46 | prospero = 191, 47 | videotex = 516 48 | } 49 | 50 | local legal = { 51 | ["-"] = true, ["_"] = true, ["."] = true, ["!"] = true, 52 | ["~"] = true, ["*"] = true, ["'"] = true, ["("] = true, 53 | [")"] = true, [":"] = true, ["@"] = true, ["&"] = true, 54 | ["="] = true, ["+"] = true, ["$"] = true, [","] = true, 55 | [";"] = true -- can be used for parameters in path 56 | } 57 | 58 | local function decode(str, path) 59 | local str = str 60 | if not path then 61 | str = str:gsub('+', ' ') 62 | end 63 | return (str:gsub("%%(%x%x)", function(c) 64 | return string.char(tonumber(c, 16)) 65 | end)) 66 | end 67 | 68 | local function encode(str) 69 | return (str:gsub("([^A-Za-z0-9%_%.%-%~])", function(v) 70 | return string.upper(string.format("%%%02x", string.byte(v))) 71 | end)) 72 | end 73 | 74 | -- for query values, prefer + instead of %20 for spaces 75 | local function encodeValue(str) 76 | local str = encode(str) 77 | return str:gsub('%%20', '+') 78 | end 79 | 80 | local function encodeSegment(s) 81 | local legalEncode = function(c) 82 | if legal[c] then 83 | return c 84 | end 85 | return encode(c) 86 | end 87 | return s:gsub('([^a-zA-Z0-9])', legalEncode) 88 | end 89 | 90 | local function concat(s, u) 91 | return s .. u:build() 92 | end 93 | 94 | --- builds the url 95 | -- @return a string representing the built url 96 | function M:build() 97 | local url = '' 98 | if self.path then 99 | local path = self.path 100 | path:gsub("([^/]+)", function (s) return encodeSegment(s) end) 101 | url = url .. tostring(path) 102 | end 103 | if self.query then 104 | local qstring = tostring(self.query) 105 | if qstring ~= "" then 106 | url = url .. '?' .. qstring 107 | end 108 | end 109 | if self.host then 110 | local authority = self.host 111 | if self.port and self.scheme and M.services[self.scheme] ~= self.port then 112 | authority = authority .. ':' .. self.port 113 | end 114 | local userinfo 115 | if self.user and self.user ~= "" then 116 | userinfo = self.user 117 | if self.password then 118 | userinfo = userinfo .. ':' .. self.password 119 | end 120 | end 121 | if userinfo and userinfo ~= "" then 122 | authority = userinfo .. '@' .. authority 123 | end 124 | if authority then 125 | if url ~= "" then 126 | url = '//' .. authority .. '/' .. url:gsub('^/+', '') 127 | else 128 | url = '//' .. authority 129 | end 130 | end 131 | end 132 | if self.scheme then 133 | url = self.scheme .. ':' .. url 134 | end 135 | if self.fragment then 136 | url = url .. '#' .. self.fragment 137 | end 138 | return url 139 | end 140 | 141 | --- builds the querystring 142 | -- @param tab The key/value parameters 143 | -- @param sep The separator to use (optional) 144 | -- @param key The parent key if the value is multi-dimensional (optional) 145 | -- @return a string representing the built querystring 146 | function M.buildQuery(tab, sep, key) 147 | local query = {} 148 | if not sep then 149 | sep = M.options.separator or '&' 150 | end 151 | local keys = {} 152 | for k in pairs(tab) do 153 | keys[#keys+1] = k 154 | end 155 | table.sort(keys) 156 | for _,name in ipairs(keys) do 157 | local value = tab[name] 158 | name = encode(tostring(name)) 159 | if key then 160 | name = string.format('%s[%s]', tostring(key), tostring(name)) 161 | end 162 | if type(value) == 'table' then 163 | query[#query+1] = M.buildQuery(value, sep, name) 164 | else 165 | local value = encodeValue(tostring(value)) 166 | if value ~= "" then 167 | query[#query+1] = string.format('%s=%s', name, value) 168 | else 169 | query[#query+1] = name 170 | end 171 | end 172 | end 173 | return table.concat(query, sep) 174 | end 175 | 176 | --- Parses the querystring to a table 177 | -- This function can parse multidimensional pairs and is mostly compatible 178 | -- with PHP usage of brackets in key names like ?param[key]=value 179 | -- @param str The querystring to parse 180 | -- @param sep The separator between key/value pairs, defaults to `&` 181 | -- @todo limit the max number of parameters with M.options.max_parameters 182 | -- @return a table representing the query key/value pairs 183 | function M.parseQuery(str, sep) 184 | if not sep then 185 | sep = M.options.separator or '&' 186 | end 187 | 188 | local values = {} 189 | for key,val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do 190 | local key = decode(key) 191 | local keys = {} 192 | key = key:gsub('%[([^%]]*)%]', function(v) 193 | -- extract keys between balanced brackets 194 | if string.find(v, "^-?%d+$") then 195 | v = tonumber(v) 196 | else 197 | v = decode(v) 198 | end 199 | table.insert(keys, v) 200 | return "=" 201 | end) 202 | key = key:gsub('=+.*$', "") 203 | key = key:gsub('%s', "_") -- remove spaces in parameter name 204 | val = val:gsub('^=+', "") 205 | 206 | if not values[key] then 207 | values[key] = {} 208 | end 209 | if #keys > 0 and type(values[key]) ~= 'table' then 210 | values[key] = {} 211 | elseif #keys == 0 and type(values[key]) == 'table' then 212 | values[key] = decode(val) 213 | end 214 | 215 | local t = values[key] 216 | for i,k in ipairs(keys) do 217 | if type(t) ~= 'table' then 218 | t = {} 219 | end 220 | if k == "" then 221 | k = #t+1 222 | end 223 | if not t[k] then 224 | t[k] = {} 225 | end 226 | if i == #keys then 227 | t[k] = decode(val) 228 | end 229 | t = t[k] 230 | end 231 | end 232 | setmetatable(values, { __tostring = M.buildQuery }) 233 | return values 234 | end 235 | 236 | --- set the url query 237 | -- @param query Can be a string to parse or a table of key/value pairs 238 | -- @return a table representing the query key/value pairs 239 | function M:setQuery(query) 240 | local query = query 241 | if type(query) == 'table' then 242 | query = M.buildQuery(query) 243 | end 244 | self.query = M.parseQuery(query) 245 | return query 246 | end 247 | 248 | --- set the authority part of the url 249 | -- The authority is parsed to find the user, password, port and host if available. 250 | -- @param authority The string representing the authority 251 | -- @return a string with what remains after the authority was parsed 252 | function M:setAuthority(authority) 253 | self.authority = authority 254 | self.port = nil 255 | self.host = nil 256 | self.userinfo = nil 257 | self.user = nil 258 | self.password = nil 259 | 260 | authority = authority:gsub('^([^@]*)@', function(v) 261 | self.userinfo = v 262 | return '' 263 | end) 264 | authority = authority:gsub("^%[[^%]]+%]", function(v) 265 | -- ipv6 266 | self.host = v 267 | return '' 268 | end) 269 | authority = authority:gsub(':([^:]*)$', function(v) 270 | self.port = tonumber(v) 271 | return '' 272 | end) 273 | if authority ~= '' and not self.host then 274 | self.host = authority:lower() 275 | end 276 | if self.userinfo then 277 | local userinfo = self.userinfo 278 | userinfo = userinfo:gsub(':([^:]*)$', function(v) 279 | self.password = v 280 | return '' 281 | end) 282 | self.user = userinfo 283 | end 284 | return authority 285 | end 286 | 287 | --- Parse the url into the designated parts. 288 | -- Depending on the url, the following parts can be available: 289 | -- scheme, userinfo, user, password, authority, host, port, path, 290 | -- query, fragment 291 | -- @param url Url string 292 | -- @return a table with the different parts and a few other functions 293 | function M.parse(url) 294 | local comp = {} 295 | M.setAuthority(comp, "") 296 | M.setQuery(comp, "") 297 | 298 | local url = tostring(url or '') 299 | url = url:gsub('#(.*)$', function(v) 300 | comp.fragment = v 301 | return '' 302 | end) 303 | url =url:gsub('^([%w][%w%+%-%.]*)%:', function(v) 304 | comp.scheme = v:lower() 305 | return '' 306 | end) 307 | url = url:gsub('%?(.*)', function(v) 308 | M.setQuery(comp, v) 309 | return '' 310 | end) 311 | url = url:gsub('^//([^/]*)', function(v) 312 | M.setAuthority(comp, v) 313 | return '' 314 | end) 315 | comp.path = decode(url, true) 316 | 317 | setmetatable(comp, { 318 | __index = M, 319 | __concat = concat, 320 | __tostring = M.build} 321 | ) 322 | return comp 323 | end 324 | 325 | --- removes dots and slashes in urls when possible 326 | -- This function will also remove multiple slashes 327 | -- @param path The string representing the path to clean 328 | -- @return a string of the path without unnecessary dots and segments 329 | function M.removeDotSegments(path) 330 | local fields = {} 331 | if string.len(path) == 0 then 332 | return "" 333 | end 334 | local startslash = false 335 | local endslash = false 336 | if string.sub(path, 1, 1) == "/" then 337 | startslash = true 338 | end 339 | if (string.len(path) > 1 or startslash == false) and string.sub(path, -1) == "/" then 340 | endslash = true 341 | end 342 | 343 | path:gsub('[^/]+', function(c) table.insert(fields, c) end) 344 | 345 | local new = {} 346 | local j = 0 347 | 348 | for i,c in ipairs(fields) do 349 | if c == '..' then 350 | if j > 0 then 351 | j = j - 1 352 | end 353 | elseif c ~= "." then 354 | j = j + 1 355 | new[j] = c 356 | end 357 | end 358 | local ret = "" 359 | if #new > 0 and j > 0 then 360 | ret = table.concat(new, '/', 1, j) 361 | else 362 | ret = "" 363 | end 364 | if startslash then 365 | ret = '/'..ret 366 | end 367 | if endslash then 368 | ret = ret..'/' 369 | end 370 | return ret 371 | end 372 | 373 | local function absolutePath(base_path, relative_path) 374 | if string.sub(relative_path, 1, 1) == "/" then 375 | return '/' .. string.gsub(relative_path, '^[%./]+', '') 376 | end 377 | local path = base_path 378 | if relative_path ~= "" then 379 | path = '/'..path:gsub("[^/]*$", "") 380 | end 381 | path = path .. relative_path 382 | path = path:gsub("([^/]*%./)", function (s) 383 | if s ~= "./" then return s else return "" end 384 | end) 385 | path = string.gsub(path, "/%.$", "/") 386 | local reduced 387 | while reduced ~= path do 388 | reduced = path 389 | path = string.gsub(reduced, "([^/]*/%.%./)", function (s) 390 | if s ~= "../../" then return "" else return s end 391 | end) 392 | end 393 | path = string.gsub(path, "([^/]*/%.%.?)$", function (s) 394 | if s ~= "../.." then return "" else return s end 395 | end) 396 | local reduced 397 | while reduced ~= path do 398 | reduced = path 399 | path = string.gsub(reduced, '^/?%.%./', '') 400 | end 401 | return '/' .. path 402 | end 403 | 404 | --- builds a new url by using the one given as parameter and resolving paths 405 | -- @param other A string or a table representing a url 406 | -- @return a new url table 407 | function M:resolve(other) 408 | if type(self) == "string" then 409 | self = M.parse(self) 410 | end 411 | if type(other) == "string" then 412 | other = M.parse(other) 413 | end 414 | if other.scheme then 415 | return other 416 | else 417 | other.scheme = self.scheme 418 | if not other.authority or other.authority == "" then 419 | other:setAuthority(self.authority) 420 | if not other.path or other.path == "" then 421 | other.path = self.path 422 | local query = other.query 423 | if not query or not next(query) then 424 | other.query = self.query 425 | end 426 | else 427 | other.path = absolutePath(self.path, other.path) 428 | end 429 | end 430 | return other 431 | end 432 | end 433 | 434 | --- normalize a url path following some common normalization rules 435 | -- described on The URL normalization page of Wikipedia 436 | -- @return the normalized path 437 | function M:normalize() 438 | if type(self) == 'string' then 439 | self = M.parse(self) 440 | end 441 | if self.path then 442 | local path = self.path 443 | path = absolutePath(path, "") 444 | -- normalize multiple slashes 445 | path = string.gsub(path, "//+", "/") 446 | self.path = path 447 | end 448 | return self 449 | end 450 | 451 | return M -------------------------------------------------------------------------------- /lua/deps/resty/ipmatcher.lua: -------------------------------------------------------------------------------- 1 | local bit = require("bit") 2 | local clear_tab = require("table.clear") 3 | local new_tab = require("table.new") 4 | local find_str = string.find 5 | local tonumber = tonumber 6 | local ipairs = ipairs 7 | local pairs = pairs 8 | local ffi = require "ffi" 9 | local ffi_cdef = ffi.cdef 10 | local ffi_copy = ffi.copy 11 | local ffi_new = ffi.new 12 | local C = ffi.C 13 | local insert_tab = table.insert 14 | local string = string 15 | local setmetatable=setmetatable 16 | local type = type 17 | local error = error 18 | local str_sub = string.sub 19 | local str_byte = string.byte 20 | 21 | 22 | local AF_INET = 2 23 | local AF_INET6 = 10 24 | if ffi.os == "OSX" then 25 | AF_INET6 = 30 26 | end 27 | 28 | 29 | local _M = {_VERSION = 0.3} 30 | 31 | 32 | ffi_cdef[[ 33 | int inet_pton(int af, const char * restrict src, void * restrict dst); 34 | uint32_t ntohl(uint32_t netlong); 35 | ]] 36 | 37 | 38 | local parse_ipv4 39 | do 40 | local inet = ffi_new("unsigned int [1]") 41 | 42 | function parse_ipv4(ip) 43 | if not ip then 44 | return false 45 | end 46 | 47 | if C.inet_pton(AF_INET, ip, inet) ~= 1 then 48 | return false 49 | end 50 | 51 | return C.ntohl(inet[0]) 52 | end 53 | end 54 | _M.parse_ipv4 = parse_ipv4 55 | 56 | local parse_bin_ipv4 57 | do 58 | local inet = ffi_new("unsigned int [1]") 59 | 60 | function parse_bin_ipv4(ip) 61 | if not ip or #ip ~= 4 then 62 | return false 63 | end 64 | 65 | ffi_copy(inet, ip, 4) 66 | return C.ntohl(inet[0]) 67 | end 68 | end 69 | 70 | local parse_ipv6 71 | do 72 | local inets = ffi_new("unsigned int [4]") 73 | 74 | function parse_ipv6(ip) 75 | if not ip then 76 | return false 77 | end 78 | 79 | if str_byte(ip, 1, 1) == str_byte('[') 80 | and str_byte(ip, #ip) == str_byte(']') then 81 | 82 | -- strip square brackets around IPv6 literal if present 83 | ip = str_sub(ip, 2, #ip - 1) 84 | end 85 | 86 | if C.inet_pton(AF_INET6, ip, inets) ~= 1 then 87 | return false 88 | end 89 | 90 | local inets_arr = new_tab(4, 0) 91 | for i = 0, 3 do 92 | insert_tab(inets_arr, C.ntohl(inets[i])) 93 | end 94 | return inets_arr 95 | end 96 | end 97 | _M.parse_ipv6 = parse_ipv6 98 | 99 | local parse_bin_ipv6 100 | do 101 | local inets = ffi_new("unsigned int [4]") 102 | 103 | function parse_bin_ipv6(ip) 104 | if not ip or #ip ~= 16 then 105 | return false 106 | end 107 | 108 | ffi_copy(inets, ip, 16) 109 | local inets_arr = new_tab(4, 0) 110 | for i = 0, 3 do 111 | insert_tab(inets_arr, C.ntohl(inets[i])) 112 | end 113 | return inets_arr 114 | end 115 | end 116 | 117 | 118 | local mt = {__index = _M} 119 | 120 | 121 | 122 | local function split_ip(ip_addr_org) 123 | local idx = find_str(ip_addr_org, "/", 1, true) 124 | if not idx then 125 | return ip_addr_org 126 | end 127 | 128 | local ip_addr = str_sub(ip_addr_org, 1, idx - 1) 129 | local ip_addr_mask = str_sub(ip_addr_org, idx + 1) 130 | return ip_addr, tonumber(ip_addr_mask) 131 | end 132 | _M.split_ip = split_ip 133 | 134 | 135 | local idxs = {} 136 | local function gen_ipv6_idxs(inets_ipv6, mask) 137 | clear_tab(idxs) 138 | 139 | for _, inet in ipairs(inets_ipv6) do 140 | local valid_mask = mask 141 | if valid_mask > 32 then 142 | valid_mask = 32 143 | end 144 | 145 | if valid_mask == 32 then 146 | insert_tab(idxs, inet) 147 | else 148 | insert_tab(idxs, bit.rshift(inet, 32 - valid_mask)) 149 | end 150 | 151 | mask = mask - 32 152 | if mask <= 0 then 153 | break 154 | end 155 | end 156 | 157 | return idxs 158 | end 159 | 160 | 161 | local function new(ips, with_value) 162 | if not ips or type(ips) ~= "table" then 163 | error("missing valid ip argument", 2) 164 | end 165 | 166 | local parsed_ipv4s = {} 167 | local parsed_ipv4s_mask = {} 168 | local ipv4_match_all_value 169 | 170 | local parsed_ipv6s = {} 171 | local parsed_ipv6s_mask = {} 172 | local ipv6_values = {} 173 | local ipv6s_values_idx = 1 174 | local ipv6_match_all_value 175 | 176 | local iter = with_value and pairs or ipairs 177 | for a, b in iter(ips) do 178 | local ip_addr_org, value 179 | if with_value then 180 | ip_addr_org = a 181 | value = b 182 | 183 | else 184 | ip_addr_org = b 185 | value = true 186 | end 187 | 188 | local ip_addr, ip_addr_mask = split_ip(ip_addr_org) 189 | 190 | local inet_ipv4 = parse_ipv4(ip_addr) 191 | if inet_ipv4 then 192 | ip_addr_mask = ip_addr_mask or 32 193 | if ip_addr_mask == 32 then 194 | parsed_ipv4s[inet_ipv4] = value 195 | 196 | elseif ip_addr_mask == 0 then 197 | ipv4_match_all_value = value 198 | 199 | else 200 | local valid_inet_addr = bit.rshift(inet_ipv4, 32 - ip_addr_mask) 201 | 202 | parsed_ipv4s_mask[ip_addr_mask] = parsed_ipv4s_mask[ip_addr_mask] or {} 203 | parsed_ipv4s_mask[ip_addr_mask][valid_inet_addr] = value 204 | end 205 | 206 | goto continue 207 | end 208 | 209 | local inets_ipv6 = parse_ipv6(ip_addr) 210 | if inets_ipv6 then 211 | ip_addr_mask = ip_addr_mask or 128 212 | if ip_addr_mask == 128 then 213 | parsed_ipv6s[ip_addr] = value 214 | 215 | elseif ip_addr_mask == 0 then 216 | ipv6_match_all_value = value 217 | end 218 | 219 | parsed_ipv6s[ip_addr_mask] = parsed_ipv6s[ip_addr_mask] or {} 220 | 221 | local inets_idxs = gen_ipv6_idxs(inets_ipv6, ip_addr_mask) 222 | local node = parsed_ipv6s[ip_addr_mask] 223 | for i, inet in ipairs(inets_idxs) do 224 | if i == #inets_idxs then 225 | if with_value then 226 | ipv6_values[ipv6s_values_idx] = value 227 | node[inet] = ipv6s_values_idx 228 | ipv6s_values_idx = ipv6s_values_idx + 1 229 | else 230 | node[inet] = true 231 | end 232 | 233 | elseif not node[inet] then 234 | node[inet] = {} 235 | node = node[inet] 236 | end 237 | end 238 | 239 | parsed_ipv6s_mask[ip_addr_mask] = true 240 | 241 | goto continue 242 | end 243 | 244 | if not inet_ipv4 and not inets_ipv6 then 245 | return nil, "invalid ip address: " .. ip_addr 246 | end 247 | 248 | ::continue:: 249 | end 250 | 251 | local ipv4_mask_arr = {} 252 | for k, _ in pairs(parsed_ipv4s_mask) do 253 | insert_tab(ipv4_mask_arr, k) 254 | end 255 | 256 | local ipv6_mask_arr = {} 257 | for k, _ in pairs(parsed_ipv6s_mask) do 258 | insert_tab(ipv6_mask_arr, k) 259 | end 260 | 261 | return setmetatable({ 262 | ipv4 = parsed_ipv4s, 263 | ipv4_mask = parsed_ipv4s_mask, 264 | ipv4_mask_arr = ipv4_mask_arr, 265 | ipv4_match_all_value = ipv4_match_all_value, 266 | 267 | ipv6 = parsed_ipv6s, 268 | ipv6_mask = parsed_ipv6s_mask, 269 | ipv6_mask_arr = ipv6_mask_arr, 270 | ipv6_values = ipv6_values, 271 | ipv6_match_all_value = ipv6_match_all_value, 272 | }, mt) 273 | end 274 | 275 | function _M.new(ips) 276 | return new(ips, false) 277 | end 278 | 279 | function _M.new_with_value(ips) 280 | return new(ips, true) 281 | end 282 | 283 | 284 | local function match_ipv4(self, ip) 285 | local ipv4s = self.ipv4 286 | local value = ipv4s[ip] 287 | if value ~= nil then 288 | return value 289 | end 290 | 291 | local ipv4_mask = self.ipv4_mask 292 | if self.ipv4_match_all_value ~= nil then 293 | return self.ipv4_match_all_value -- match any ip 294 | end 295 | 296 | for _, mask in ipairs(self.ipv4_mask_arr) do 297 | local valid_inet_addr = bit.rshift(ip, 32 - mask) 298 | value = ipv4_mask[mask][valid_inet_addr] 299 | if value ~= nil then 300 | return value 301 | end 302 | end 303 | 304 | return false 305 | end 306 | 307 | local function match_ipv6(self, ip) 308 | local ipv6s = self.ipv6 309 | if self.ipv6_match_all_value ~= nil then 310 | return self.ipv6_match_all_value -- match any ip 311 | end 312 | 313 | for _, mask in ipairs(self.ipv6_mask_arr) do 314 | local node = ipv6s[mask] 315 | local inet_idxs = gen_ipv6_idxs(ip, mask) 316 | for _, inet in ipairs(inet_idxs) do 317 | if not node[inet] then 318 | break 319 | else 320 | node = node[inet] 321 | if node == true then 322 | return true 323 | end 324 | if type(node) == "number" then 325 | -- fetch with the ipv6s_values_idx 326 | return self.ipv6_values[node] 327 | end 328 | end 329 | end 330 | end 331 | 332 | return false 333 | end 334 | 335 | function _M.match(self, ip) 336 | local inet_ipv4 = parse_ipv4(ip) 337 | if inet_ipv4 then 338 | return match_ipv4(self, inet_ipv4) 339 | end 340 | 341 | local inets_ipv6 = parse_ipv6(ip) 342 | if not inets_ipv6 then 343 | return false, "invalid ip address, not ipv4 and ipv6" 344 | end 345 | 346 | local ipv6s = self.ipv6 347 | local value = ipv6s[ip] 348 | if value ~= nil then 349 | return value 350 | end 351 | 352 | return match_ipv6(self, inets_ipv6) 353 | end 354 | 355 | 356 | function _M.match_bin(self, bin_ip) 357 | local inet_ipv4 = parse_bin_ipv4(bin_ip) 358 | if inet_ipv4 then 359 | return match_ipv4(self, inet_ipv4) 360 | end 361 | 362 | local inets_ipv6 = parse_bin_ipv6(bin_ip) 363 | if not inets_ipv6 then 364 | return false, "invalid ip address, not ipv4 and ipv6" 365 | end 366 | 367 | return match_ipv6(self, inets_ipv6) 368 | end 369 | 370 | 371 | return _M 372 | -------------------------------------------------------------------------------- /lua/deps/resty/lrucache.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | 3 | 4 | local ffi = require "ffi" 5 | local ffi_new = ffi.new 6 | local ffi_sizeof = ffi.sizeof 7 | local ffi_cast = ffi.cast 8 | local ffi_fill = ffi.fill 9 | local ngx_now = os.time() 10 | local uintptr_t = ffi.typeof("uintptr_t") 11 | local setmetatable = setmetatable 12 | local tonumber = tonumber 13 | local type = type 14 | local new_tab 15 | do 16 | local ok 17 | ok, new_tab = pcall(require, "table.new") 18 | if not ok then 19 | new_tab = function(narr, nrec) return {} end 20 | end 21 | end 22 | 23 | 24 | local ok, tb_clear = pcall(require, "table.clear") 25 | if not ok then 26 | local pairs = pairs 27 | tb_clear = function (tab) 28 | for k, _ in pairs(tab) do 29 | tab[k] = nil 30 | end 31 | end 32 | end 33 | 34 | 35 | -- queue data types 36 | -- 37 | -- this queue is a double-ended queue and the first node 38 | -- is reserved for the queue itself. 39 | -- the implementation is mostly borrowed from nginx's ngx_queue_t data 40 | -- structure. 41 | 42 | ffi.cdef[[ 43 | typedef struct lrucache_queue_s lrucache_queue_t; 44 | struct lrucache_queue_s { 45 | double expire; /* in seconds */ 46 | lrucache_queue_t *prev; 47 | lrucache_queue_t *next; 48 | uint32_t user_flags; 49 | }; 50 | ]] 51 | 52 | local queue_arr_type = ffi.typeof("lrucache_queue_t[?]") 53 | local queue_type = ffi.typeof("lrucache_queue_t") 54 | local NULL = ffi.null 55 | 56 | 57 | -- queue utility functions 58 | 59 | local function queue_insert_tail(h, x) 60 | local last = h[0].prev 61 | x.prev = last 62 | last.next = x 63 | x.next = h 64 | h[0].prev = x 65 | end 66 | 67 | 68 | local function queue_init(size) 69 | if not size then 70 | size = 0 71 | end 72 | local q = ffi_new(queue_arr_type, size + 1) 73 | ffi_fill(q, ffi_sizeof(queue_type, size + 1), 0) 74 | 75 | if size == 0 then 76 | q[0].prev = q 77 | q[0].next = q 78 | 79 | else 80 | local prev = q[0] 81 | for i = 1, size do 82 | local e = q + i 83 | e.user_flags = 0 84 | prev.next = e 85 | e.prev = prev 86 | prev = e 87 | end 88 | 89 | local last = q[size] 90 | last.next = q 91 | q[0].prev = last 92 | end 93 | 94 | return q 95 | end 96 | 97 | 98 | local function queue_is_empty(q) 99 | -- print("q: ", tostring(q), "q.prev: ", tostring(q), ": ", q == q.prev) 100 | return q == q[0].prev 101 | end 102 | 103 | 104 | local function queue_remove(x) 105 | local prev = x.prev 106 | local next = x.next 107 | 108 | next.prev = prev 109 | prev.next = next 110 | 111 | -- for debugging purpose only: 112 | x.prev = NULL 113 | x.next = NULL 114 | end 115 | 116 | 117 | local function queue_insert_head(h, x) 118 | x.next = h[0].next 119 | x.next.prev = x 120 | x.prev = h 121 | h[0].next = x 122 | end 123 | 124 | 125 | local function queue_last(h) 126 | return h[0].prev 127 | end 128 | 129 | 130 | local function queue_head(h) 131 | return h[0].next 132 | end 133 | 134 | 135 | -- true module stuffs 136 | 137 | local _M = { 138 | _VERSION = '0.10' 139 | } 140 | local mt = { __index = _M } 141 | 142 | 143 | local function ptr2num(ptr) 144 | return tonumber(ffi_cast(uintptr_t, ptr)) 145 | end 146 | 147 | 148 | function _M.new(size) 149 | if size < 1 then 150 | return nil, "size too small" 151 | end 152 | 153 | local self = { 154 | hasht = {}, 155 | free_queue = queue_init(size), 156 | cache_queue = queue_init(), 157 | key2node = {}, 158 | node2key = {}, 159 | num_items = 0, 160 | max_items = size, 161 | } 162 | return setmetatable(self, mt) 163 | end 164 | 165 | 166 | function _M.count(self) 167 | return self.num_items 168 | end 169 | 170 | 171 | function _M.capacity(self) 172 | return self.max_items 173 | end 174 | 175 | 176 | function _M.get(self, key) 177 | local hasht = self.hasht 178 | local val = hasht[key] 179 | if val == nil then 180 | return nil 181 | end 182 | 183 | local node = self.key2node[key] 184 | 185 | -- print(key, ": moving node ", tostring(node), " to cache queue head") 186 | local cache_queue = self.cache_queue 187 | queue_remove(node) 188 | queue_insert_head(cache_queue, node) 189 | 190 | if node.expire >= 0 and node.expire < ngx_now() then 191 | -- print("expired: ", node.expire, " > ", ngx_now()) 192 | return nil, val, node.user_flags 193 | end 194 | 195 | return val, nil, node.user_flags 196 | end 197 | 198 | 199 | function _M.delete(self, key) 200 | self.hasht[key] = nil 201 | 202 | local key2node = self.key2node 203 | local node = key2node[key] 204 | 205 | if not node then 206 | return false 207 | end 208 | 209 | key2node[key] = nil 210 | self.node2key[ptr2num(node)] = nil 211 | 212 | queue_remove(node) 213 | queue_insert_tail(self.free_queue, node) 214 | self.num_items = self.num_items - 1 215 | return true 216 | end 217 | 218 | 219 | function _M.set(self, key, value, ttl, flags) 220 | local hasht = self.hasht 221 | hasht[key] = value 222 | 223 | local key2node = self.key2node 224 | local node = key2node[key] 225 | if not node then 226 | local free_queue = self.free_queue 227 | local node2key = self.node2key 228 | 229 | if queue_is_empty(free_queue) then 230 | -- evict the least recently used key 231 | -- assert(not queue_is_empty(self.cache_queue)) 232 | node = queue_last(self.cache_queue) 233 | 234 | local oldkey = node2key[ptr2num(node)] 235 | -- print(key, ": evicting oldkey: ", oldkey, ", oldnode: ", 236 | -- tostring(node)) 237 | if oldkey then 238 | hasht[oldkey] = nil 239 | key2node[oldkey] = nil 240 | end 241 | 242 | else 243 | -- take a free queue node 244 | node = queue_head(free_queue) 245 | -- only add count if we are not evicting 246 | self.num_items = self.num_items + 1 247 | -- print(key, ": get a new free node: ", tostring(node)) 248 | end 249 | 250 | node2key[ptr2num(node)] = key 251 | key2node[key] = node 252 | end 253 | 254 | queue_remove(node) 255 | queue_insert_head(self.cache_queue, node) 256 | 257 | if ttl then 258 | node.expire = ngx_now() + ttl 259 | else 260 | node.expire = -1 261 | end 262 | 263 | if type(flags) == "number" and flags >= 0 then 264 | node.user_flags = flags 265 | 266 | else 267 | node.user_flags = 0 268 | end 269 | end 270 | 271 | 272 | function _M.get_keys(self, max_count, res) 273 | if not max_count or max_count == 0 then 274 | max_count = self.num_items 275 | end 276 | 277 | if not res then 278 | res = new_tab(max_count + 1, 0) -- + 1 for trailing hole 279 | end 280 | 281 | local cache_queue = self.cache_queue 282 | local node2key = self.node2key 283 | 284 | local i = 0 285 | local node = queue_head(cache_queue) 286 | 287 | while node ~= cache_queue do 288 | if i >= max_count then 289 | break 290 | end 291 | 292 | i = i + 1 293 | res[i] = node2key[ptr2num(node)] 294 | node = node.next 295 | end 296 | 297 | res[i + 1] = nil 298 | 299 | return res 300 | end 301 | 302 | 303 | function _M.flush_all(self) 304 | tb_clear(self.hasht) 305 | tb_clear(self.node2key) 306 | tb_clear(self.key2node) 307 | 308 | self.num_items = 0 309 | 310 | local cache_queue = self.cache_queue 311 | local free_queue = self.free_queue 312 | 313 | -- splice the cache_queue into free_queue 314 | if not queue_is_empty(cache_queue) then 315 | local free_head = free_queue[0] 316 | local free_last = free_head.prev 317 | 318 | local cache_head = cache_queue[0] 319 | local cache_first = cache_head.next 320 | local cache_last = cache_head.prev 321 | 322 | free_last.next = cache_first 323 | cache_first.prev = free_last 324 | 325 | cache_last.next = free_head 326 | free_head.prev = cache_last 327 | 328 | cache_head.next = cache_queue 329 | cache_head.prev = cache_queue 330 | end 331 | end 332 | 333 | 334 | return _M 335 | -------------------------------------------------------------------------------- /lua/deps/resty/lrucache/pureffi.lua: -------------------------------------------------------------------------------- 1 | -- Copyright (C) Yichun Zhang (agentzh) 2 | -- Copyright (C) Shuxin Yang 3 | 4 | --[[ 5 | This module implements a key/value cache store. We adopt LRU as our 6 | replace/evict policy. Each key/value pair is tagged with a Time-to-Live (TTL); 7 | from user's perspective, stale pairs are automatically removed from the cache. 8 | 9 | Why FFI 10 | ------- 11 | In Lua, expression "table[key] = nil" does not *PHYSICALLY* remove the value 12 | associated with the key; it just set the value to be nil! So the table will 13 | keep growing with large number of the key/nil pairs which will be purged until 14 | resize() operator is called. 15 | 16 | This "feature" is terribly ill-suited to what we need. Therefore we have to 17 | rely on FFI to build a hash-table where any entry can be physically deleted 18 | immediately. 19 | 20 | Under the hood: 21 | -------------- 22 | In concept, we introduce three data structures to implement the cache store: 23 | 1. key/value vector for storing keys and values. 24 | 2. a queue to mimic the LRU. 25 | 3. hash-table for looking up the value for a given key. 26 | 27 | Unfortunately, efficiency and clarity usually come at each other cost. The 28 | data strucutres we are using are slightly more complicated than what we 29 | described above. 30 | 31 | o. Lua does not have efficient way to store a vector of pair. So, we use 32 | two vectors for key/value pair: one for keys and the other for values 33 | (_M.key_v and _M.val_v, respectively), and i-th key corresponds to 34 | i-th value. 35 | 36 | A key/value pair is identified by the "id" field in a "node" (we shall 37 | discuss node later) 38 | 39 | o. The queue is nothing more than a doubly-linked list of "node" linked via 40 | lrucache_pureffi_queue_s::{next|prev} fields. 41 | 42 | o. The hash-table has two parts: 43 | - the _M.bucket_v[] a vector of bucket, indiced by hash-value, and 44 | - a bucket is a singly-linked list of "node" via the 45 | lrucache_pureffi_queue_s::conflict field. 46 | 47 | A key must be a string, and the hash value of a key is evaluated by: 48 | crc32(key-cast-to-pointer) % size(_M.bucket_v). 49 | We mandate size(_M.bucket_v) being a power-of-two in order to avoid 50 | expensive modulo operation. 51 | 52 | At the heart of the module is an array of "node" (of type 53 | lrucache_pureffi_queue_s). A node: 54 | - keeps the meta-data of its corresponding key/value pair 55 | (embodied by the "id", and "expire" field); 56 | - is a part of LRU queue (embodied by "prev" and "next" fields); 57 | - is a part of hash-table (embodied by the "conflict" field). 58 | ]] 59 | 60 | local ffi = require "ffi" 61 | local bit = require "bit" 62 | 63 | 64 | local ffi_new = ffi.new 65 | local ffi_sizeof = ffi.sizeof 66 | local ffi_cast = ffi.cast 67 | local ffi_fill = ffi.fill 68 | local ngx_now = ngx.now 69 | local uintptr_t = ffi.typeof("uintptr_t") 70 | local c_str_t = ffi.typeof("const char*") 71 | local int_t = ffi.typeof("int") 72 | local int_array_t = ffi.typeof("int[?]") 73 | 74 | 75 | local crc_tab = ffi.new("const unsigned int[256]", { 76 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 77 | 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 78 | 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 79 | 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 80 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 81 | 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 82 | 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 83 | 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 84 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 85 | 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 86 | 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, 87 | 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 88 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 89 | 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 90 | 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 91 | 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 92 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 93 | 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 94 | 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 95 | 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 96 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 97 | 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 98 | 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 99 | 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 100 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 101 | 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 102 | 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 103 | 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 104 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 105 | 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 106 | 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 107 | 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, 108 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 109 | 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 110 | 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 111 | 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 112 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 113 | 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 114 | 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 115 | 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 116 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 117 | 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 118 | 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D }); 119 | 120 | local setmetatable = setmetatable 121 | local tonumber = tonumber 122 | local tostring = tostring 123 | local type = type 124 | 125 | local brshift = bit.rshift 126 | local bxor = bit.bxor 127 | local band = bit.band 128 | 129 | local new_tab 130 | do 131 | local ok 132 | ok, new_tab = pcall(require, "table.new") 133 | if not ok then 134 | new_tab = function(narr, nrec) return {} end 135 | end 136 | end 137 | 138 | -- queue data types 139 | -- 140 | -- this queue is a double-ended queue and the first node 141 | -- is reserved for the queue itself. 142 | -- the implementation is mostly borrowed from nginx's ngx_queue_t data 143 | -- structure. 144 | 145 | ffi.cdef[[ 146 | /* A lrucache_pureffi_queue_s node hook together three data structures: 147 | * o. the key/value store as embodied by the "id" (which is in essence the 148 | * indentifier of key/pair pair) and the "expire" (which is a metadata 149 | * of the corresponding key/pair pair). 150 | * o. The LRU queue via the prev/next fields. 151 | * o. The hash-tabble as embodied by the "conflict" field. 152 | */ 153 | typedef struct lrucache_pureffi_queue_s lrucache_pureffi_queue_t; 154 | struct lrucache_pureffi_queue_s { 155 | /* Each node is assigned a unique ID at construction time, and the 156 | * ID remain immutatble, regardless the node is in active-list or 157 | * free-list. The queue header is assigned ID 0. Since queue-header 158 | * is a sentinel node, 0 denodes "invalid ID". 159 | * 160 | * Intuitively, we can view the "id" as the identifier of key/value 161 | * pair. 162 | */ 163 | int id; 164 | 165 | /* The bucket of the hash-table is implemented as a singly-linked list. 166 | * The "conflict" refers to the ID of the next node in the bucket. 167 | */ 168 | int conflict; 169 | 170 | uint32_t user_flags; 171 | 172 | double expire; /* in seconds */ 173 | 174 | lrucache_pureffi_queue_t *prev; 175 | lrucache_pureffi_queue_t *next; 176 | }; 177 | ]] 178 | 179 | local queue_arr_type = ffi.typeof("lrucache_pureffi_queue_t[?]") 180 | --local queue_ptr_type = ffi.typeof("lrucache_pureffi_queue_t*") 181 | local queue_type = ffi.typeof("lrucache_pureffi_queue_t") 182 | local NULL = ffi.null 183 | 184 | 185 | --======================================================================== 186 | -- 187 | -- Queue utility functions 188 | -- 189 | --======================================================================== 190 | 191 | -- Append the element "x" to the given queue "h". 192 | local function queue_insert_tail(h, x) 193 | local last = h[0].prev 194 | x.prev = last 195 | last.next = x 196 | x.next = h 197 | h[0].prev = x 198 | end 199 | 200 | 201 | --[[ 202 | Allocate a queue with size + 1 elements. Elements are linked together in a 203 | circular way, i.e. the last element's "next" points to the first element, 204 | while the first element's "prev" element points to the last element. 205 | ]] 206 | local function queue_init(size) 207 | if not size then 208 | size = 0 209 | end 210 | local q = ffi_new(queue_arr_type, size + 1) 211 | ffi_fill(q, ffi_sizeof(queue_type, size + 1), 0) 212 | 213 | if size == 0 then 214 | q[0].prev = q 215 | q[0].next = q 216 | 217 | else 218 | local prev = q[0] 219 | for i = 1, size do 220 | local e = q[i] 221 | e.id = i 222 | e.user_flags = 0 223 | prev.next = e 224 | e.prev = prev 225 | prev = e 226 | end 227 | 228 | local last = q[size] 229 | last.next = q 230 | q[0].prev = last 231 | end 232 | 233 | return q 234 | end 235 | 236 | 237 | local function queue_is_empty(q) 238 | -- print("q: ", tostring(q), "q.prev: ", tostring(q), ": ", q == q.prev) 239 | return q == q[0].prev 240 | end 241 | 242 | 243 | local function queue_remove(x) 244 | local prev = x.prev 245 | local next = x.next 246 | 247 | next.prev = prev 248 | prev.next = next 249 | 250 | -- for debugging purpose only: 251 | x.prev = NULL 252 | x.next = NULL 253 | end 254 | 255 | 256 | -- Insert the element "x" the to the given queue "h" 257 | local function queue_insert_head(h, x) 258 | x.next = h[0].next 259 | x.next.prev = x 260 | x.prev = h 261 | h[0].next = x 262 | end 263 | 264 | 265 | local function queue_last(h) 266 | return h[0].prev 267 | end 268 | 269 | 270 | local function queue_head(h) 271 | return h[0].next 272 | end 273 | 274 | 275 | --======================================================================== 276 | -- 277 | -- Miscellaneous Utility Functions 278 | -- 279 | --======================================================================== 280 | 281 | local function ptr2num(ptr) 282 | return tonumber(ffi_cast(uintptr_t, ptr)) 283 | end 284 | 285 | 286 | local function crc32_ptr(ptr) 287 | local p = brshift(ptr2num(ptr), 3) 288 | local b = band(p, 255) 289 | local crc32 = crc_tab[b] 290 | 291 | b = band(brshift(p, 8), 255) 292 | crc32 = bxor(brshift(crc32, 8), crc_tab[band(bxor(crc32, b), 255)]) 293 | 294 | b = band(brshift(p, 16), 255) 295 | crc32 = bxor(brshift(crc32, 8), crc_tab[band(bxor(crc32, b), 255)]) 296 | 297 | --b = band(brshift(p, 24), 255) 298 | --crc32 = bxor(brshift(crc32, 8), crc_tab[band(bxor(crc32, b), 255)]) 299 | return crc32 300 | end 301 | 302 | 303 | --======================================================================== 304 | -- 305 | -- Implementation of "export" functions 306 | -- 307 | --======================================================================== 308 | 309 | local _M = { 310 | _VERSION = '0.10' 311 | } 312 | local mt = { __index = _M } 313 | 314 | 315 | -- "size" specifies the maximum number of entries in the LRU queue, and the 316 | -- "load_factor" designates the 'load factor' of the hash-table we are using 317 | -- internally. The default value of load-factor is 0.5 (i.e. 50%); if the 318 | -- load-factor is specified, it will be clamped to the range of [0.1, 1](i.e. 319 | -- if load-factor is greater than 1, it will be saturated to 1, likewise, 320 | -- if load-factor is smaller than 0.1, it will be clamped to 0.1). 321 | function _M.new(size, load_factor) 322 | if size < 1 then 323 | return nil, "size too small" 324 | end 325 | 326 | -- Determine bucket size, which must be power of two. 327 | local load_f = load_factor 328 | if not load_factor then 329 | load_f = 0.5 330 | elseif load_factor > 1 then 331 | load_f = 1 332 | elseif load_factor < 0.1 then 333 | load_f = 0.1 334 | end 335 | 336 | local bs_min = size / load_f 337 | -- The bucket_sz *MUST* be a power-of-two. See the hash_string(). 338 | local bucket_sz = 1 339 | repeat 340 | bucket_sz = bucket_sz * 2 341 | until bucket_sz >= bs_min 342 | 343 | local self = { 344 | size = size, 345 | bucket_sz = bucket_sz, 346 | free_queue = queue_init(size), 347 | cache_queue = queue_init(0), 348 | node_v = nil, 349 | key_v = new_tab(size, 0), 350 | val_v = new_tab(size, 0), 351 | bucket_v = ffi_new(int_array_t, bucket_sz), 352 | num_items = 0, 353 | } 354 | -- "node_v" is an array of all the nodes used in the LRU queue. Exprpession 355 | -- node_v[i] evaluates to the element of ID "i". 356 | self.node_v = self.free_queue 357 | 358 | -- Allocate the array-part of the key_v, val_v, bucket_v. 359 | --local key_v = self.key_v 360 | --local val_v = self.val_v 361 | --local bucket_v = self.bucket_v 362 | ffi_fill(self.bucket_v, ffi_sizeof(int_t, bucket_sz), 0) 363 | 364 | return setmetatable(self, mt) 365 | end 366 | 367 | 368 | function _M.count(self) 369 | return self.num_items 370 | end 371 | 372 | 373 | function _M.capacity(self) 374 | return self.size 375 | end 376 | 377 | 378 | local function hash_string(self, str) 379 | local c_str = ffi_cast(c_str_t, str) 380 | 381 | local hv = crc32_ptr(c_str) 382 | hv = band(hv, self.bucket_sz - 1) 383 | -- Hint: bucket is 0-based 384 | return hv 385 | end 386 | 387 | 388 | -- Search the node associated with the key in the bucket, if found returns 389 | -- the the id of the node, and the id of its previous node in the conflict list. 390 | -- The "bucket_hdr_id" is the ID of the first node in the bucket 391 | local function _find_node_in_bucket(key, key_v, node_v, bucket_hdr_id) 392 | if bucket_hdr_id ~= 0 then 393 | local prev = 0 394 | local cur = bucket_hdr_id 395 | 396 | while cur ~= 0 and key_v[cur] ~= key do 397 | prev = cur 398 | cur = node_v[cur].conflict 399 | end 400 | 401 | if cur ~= 0 then 402 | return cur, prev 403 | end 404 | end 405 | end 406 | 407 | 408 | -- Return the node corresponding to the key/val. 409 | local function find_key(self, key) 410 | local key_hash = hash_string(self, key) 411 | return _find_node_in_bucket(key, self.key_v, self.node_v, 412 | self.bucket_v[key_hash]) 413 | end 414 | 415 | 416 | --[[ This function tries to 417 | 1. Remove the given key and the associated value from the key/value store, 418 | 2. Remove the entry associated with the key from the hash-table. 419 | 420 | NOTE: all queues remain intact. 421 | 422 | If there was a node bound to the key/val, return that node; otherwise, 423 | nil is returned. 424 | ]] 425 | local function remove_key(self, key) 426 | local key_v = self.key_v 427 | local val_v = self.val_v 428 | local node_v = self.node_v 429 | local bucket_v = self.bucket_v 430 | 431 | local key_hash = hash_string(self, key) 432 | local cur, prev = 433 | _find_node_in_bucket(key, key_v, node_v, bucket_v[key_hash]) 434 | 435 | if cur then 436 | -- In an attempt to make key and val dead. 437 | key_v[cur] = nil 438 | val_v[cur] = nil 439 | self.num_items = self.num_items - 1 440 | 441 | -- Remove the node from the hash table 442 | local next_node = node_v[cur].conflict 443 | if prev ~= 0 then 444 | node_v[prev].conflict = next_node 445 | else 446 | bucket_v[key_hash] = next_node 447 | end 448 | node_v[cur].conflict = 0 449 | 450 | return cur 451 | end 452 | end 453 | 454 | 455 | --[[ Bind the key/val with the given node, and insert the node into the Hashtab. 456 | NOTE: this function does not touch any queue 457 | ]] 458 | local function insert_key(self, key, val, node) 459 | -- Bind the key/val with the node 460 | local node_id = node.id 461 | self.key_v[node_id] = key 462 | self.val_v[node_id] = val 463 | 464 | -- Insert the node into the hash-table 465 | local key_hash = hash_string(self, key) 466 | local bucket_v = self.bucket_v 467 | node.conflict = bucket_v[key_hash] 468 | bucket_v[key_hash] = node_id 469 | self.num_items = self.num_items + 1 470 | end 471 | 472 | 473 | function _M.get(self, key) 474 | if type(key) ~= "string" then 475 | key = tostring(key) 476 | end 477 | 478 | local node_id = find_key(self, key) 479 | if not node_id then 480 | return nil 481 | end 482 | 483 | -- print(key, ": moving node ", tostring(node), " to cache queue head") 484 | local cache_queue = self.cache_queue 485 | local node = self.node_v + node_id 486 | queue_remove(node) 487 | queue_insert_head(cache_queue, node) 488 | 489 | local expire = node.expire 490 | if expire >= 0 and expire < ngx_now() then 491 | -- print("expired: ", node.expire, " > ", ngx_now()) 492 | return nil, self.val_v[node_id], node.user_flags 493 | end 494 | 495 | return self.val_v[node_id], nil, node.user_flags 496 | end 497 | 498 | 499 | function _M.delete(self, key) 500 | if type(key) ~= "string" then 501 | key = tostring(key) 502 | end 503 | 504 | local node_id = remove_key(self, key); 505 | if not node_id then 506 | return false 507 | end 508 | 509 | local node = self.node_v + node_id 510 | queue_remove(node) 511 | queue_insert_tail(self.free_queue, node) 512 | return true 513 | end 514 | 515 | 516 | function _M.set(self, key, value, ttl, flags) 517 | if type(key) ~= "string" then 518 | key = tostring(key) 519 | end 520 | 521 | local node_id = find_key(self, key) 522 | local node 523 | if not node_id then 524 | local free_queue = self.free_queue 525 | if queue_is_empty(free_queue) then 526 | -- evict the least recently used key 527 | -- assert(not queue_is_empty(self.cache_queue)) 528 | node = queue_last(self.cache_queue) 529 | remove_key(self, self.key_v[node.id]) 530 | else 531 | -- take a free queue node 532 | node = queue_head(free_queue) 533 | -- print(key, ": get a new free node: ", tostring(node)) 534 | end 535 | 536 | -- insert the key 537 | insert_key(self, key, value, node) 538 | else 539 | node = self.node_v + node_id 540 | self.val_v[node_id] = value 541 | end 542 | 543 | queue_remove(node) 544 | queue_insert_head(self.cache_queue, node) 545 | 546 | if ttl then 547 | node.expire = ngx_now() + ttl 548 | else 549 | node.expire = -1 550 | end 551 | 552 | if type(flags) == "number" and flags >= 0 then 553 | node.user_flags = flags 554 | 555 | else 556 | node.user_flags = 0 557 | end 558 | end 559 | 560 | 561 | function _M.get_keys(self, max_count, res) 562 | if not max_count or max_count == 0 then 563 | max_count = self.num_items 564 | end 565 | 566 | if not res then 567 | res = new_tab(max_count + 1, 0) -- + 1 for trailing hole 568 | end 569 | 570 | local cache_queue = self.cache_queue 571 | local key_v = self.key_v 572 | 573 | local i = 0 574 | local node = queue_head(cache_queue) 575 | 576 | while node ~= cache_queue do 577 | if i >= max_count then 578 | break 579 | end 580 | 581 | i = i + 1 582 | res[i] = key_v[node.id] 583 | node = node.next 584 | end 585 | 586 | res[i + 1] = nil 587 | 588 | return res 589 | end 590 | 591 | 592 | function _M.flush_all(self) 593 | local cache_queue = self.cache_queue 594 | local key_v = self.key_v 595 | 596 | local node = queue_head(cache_queue) 597 | 598 | while node ~= cache_queue do 599 | local key = key_v[node.id] 600 | node = node.next 601 | _M.delete(self, key) 602 | end 603 | end 604 | 605 | 606 | return _M 607 | --------------------------------------------------------------------------------