├── .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 | 
45 |
46 |
47 | # Plugin workflow
48 |
49 | 
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 `&` 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 |
--------------------------------------------------------------------------------