当 HTTP 请求到达 Cloudflare 全球网络时,Cloudflare 会创建一张字段–值对表,用于匹配表达式。只要当前请求仍在处理中,该表就会一直存在。
填充 Rules language 查找表的值来自多种来源:
- 原始属性直接从请求中获取(例如
http.request.uri.path)。 - 派生值是转换、组合或基本运算的结果。例如,转换
lower(http.request.uri.path)会将http.request.uri.path的值转换为小写。 - 计算值是查找、计算或其他智能处理的结果。例如,Cloudflare 使用机器学习流程动态计算攻击分数,由
cf.waf.score*字段表示。
除这些值外,表达式还可能包含字面量值。它们是你写入表达式的静态已知值,用于与来自请求/响应字段的值(无论是否经过转换)进行比较。
在规则表达式中使用值时,请牢记以下各节中的信息。
字符串是由特定定界符括起的字节序列。
Cloudflare 规则支持两种用于指定字面量字符串(包括正则表达式)的格式:带引号的字面量字符串与原始字符串。这两种格式具有不同的定界符与转义机制。
你可以在表达式中使用这两种字符串格式中的任意一种来指定正则表达式。不过,Cloudflare 建议你使用原始字符串语法,因为带引号的字符串语法具有复杂的转义规则,若未经充分测试可能导致意外行为。
正则表达式匹配使用 Rust 正则表达式引擎执行。
使用带引号的字符串语法时,字符串字面量由 "(双引号)字符定界。此格式要求你分别使用 \" 与 \\ 转义特殊字符 " 与 \。
带引号的字符串语法还有以下额外转义要求:
# Test if URI path contains 'a"b'
http.request.uri.path matches "a\"b"
# Test if URI path contains 'a"#b'
http.request.uri.path matches "a\"#b"
# Replace 'a' with '\' (backslash)
regex_replace(http.host, "a", "\\")要使用原始字符串语法指定字符串(或正则表达式),请使用特殊定界符:
- 起始定界符由字符
r组成,其后可选择性地跟随一个或多个#字符(最多 255 个),再跟随"(双引号)字符。 - 结束定界符为
"(双引号)字符,后跟与起始定界符相同数量的#字符(从 0 到 255)。
在原始字符串中没有特殊字符,因此直到结束定界符之前的所有字符都会按原样解释(没有转义序列)。
与带引号的字符串语法不同,原始字符串语法始终相同,无论使用上下文如何(例如,作为配合正则表达式运算符的正则表达式,或作为函数调用的参数)。
# Test if URI path contains 'a"b'
http.request.uri.path matches r#"a"b"#
# Test if URI path contains 'a"#b'
http.request.uri.path matches r##"a"#b"##
# Replace '\' (backslash) with 'a'
# You must still escape the '\' character in the following raw string because it has a special meaning in regular expressions
regex_replace(http.host, r"\\", "a")
# Test if URI path ends with '/api/login.aspx'
# You must still escape the '.' character in the following raw string because it has a special meaning in regular expressions ("any character")
http.request.uri.path matches r"/api/login\.aspx$"由于表达式中字符串字面量值的求值区分大小写,请考虑以下选项之一,以在表达式中捕获大小写变体:
- 使用不区分大小写的
wildcard运算符来匹配字符串字面量。 - 使用
lower()函数在比较前将字符串转换为小写。 - 使用
matches运算符(仅 Business 与 Enterprise 套餐可用)配合可匹配不同变体的正则表达式。 - 使用
eq或contains运算符编写多个子表达式,并用or运算符连接,以捕获字符串字面量的不同变体(例如<field> eq "a" or <field> eq "A")。
Cloudflare 对正则表达式设置了若干限制。其中一项是:每条规则最多支持 64 个正则表达式(regex),与你域名的套餐无关。
你可以使用以下策略减少规则中的正则表达式数量:
- 使用
contains运算符。 - 使用
wildcard/strict wildcard运算符。 - 使用
starts_with()与ends_with()函数。
使用布尔字段的简单表达式不需要运算符符号或值。你只需单独插入该字段,如下方的 ssl 示例所示。
ssl此简单表达式匹配 ssl 字段值为 true 的请求。
要匹配 ssl 为 false 的请求,请使用布尔 not 运算符:
not sslCloudflare Rules language 包含 Array 类型的字段以及带有 Array 参数与返回值的函数。
你可以使用方括号([])之间的索引(非负值)访问各个数组元素。数组索引从 0(零)开始。
在指定将对每个数组元素求值的表达式时(类似于 map 高阶函数 ↗),请使用特殊记法 [*]。此特殊索引记法会解包数组,对其所有元素分别调用外围函数,并返回包含所有各次返回值的新数组。
考虑类型为 Array<String> 的 http.request.headers.names 字段的以下示例:
-
获取数组中的第一个元素:
http.request.headers.names[0] -
检查第一个数组元素是否等于
Content-Type(区分大小写):
http.request.headers.names[0] == "Content-Type" -
检查是否有任一数组元素等于
Content-Type(区分大小写):
any(http.request.headers.names[*] == "Content-Type") -
忽略大小写,检查是否有任一数组元素等于
Content-Type:
any(lower(http.request.headers.names[*])[*] == "content-type")
在最后一个示例中,lower() 函数包含 [*] 记法,以便对该函数按每个数组元素求值。该函数与 [*] 一起使用时,会返回一个新数组,其中输入数组的每个元素都已转换为小写。然后,字符串比较使用 [*],将应用 lower() 到每个标头名称后得到的数组转换为布尔值数组。最后,若这些数组元素中至少有一个为 true,则 any() 求值为 true。
无法定义自己的数组。你只能使用由字段返回的数组,无论是直接使用还是经函数修改后使用。
访问 越界的数组索引 会产生"缺失值"。缺失值的行为如下:
- 任何比较
<expr> <op> <literal>(其中<expr>求值为缺失值)将求值为 false。 - 函数调用如
function(<expr>)(其中<expr>求值为缺失值)在大多数情况下将返回缺失值,但具体行为因函数而异。
仅当多次 [*] 应用于同一数组时,你才能在同一表达式中多次使用 [*]。此外,你只能在函数调用的第一个参数中使用 [*]。
Rules language 的运算符不直接支持数组或 [*] 运算符——但支持带索引的数组元素,例如 array_value[0]。例如,你不能在外围函数调用的上下文之外将 [*] 与 == 运算符一起使用:
http.request.headers.names[*] == "Content-Type"— 无效表达式any(http.request.headers.names[*] == "Content-Type")— 有效表达式
Map(也称为关联数组)是一种存储键值对集合的数据结构,其中键必须是 String,值可以是任意类型(例如 String 或值数组)。Map 中的所有值必须具有相同类型。
Cloudflare Rules language 包含多个 Map 数据类型的字段。Map 字段的类型记法(例如 Map<Array<String>>)表示与键关联的值的数据类型(由 String 元素组成的 Array)。这意味着当你访问键 "foo" 的值时,将得到 String 元素数组或缺失值。
要访问 map 中的值,请在方括号([])之间输入键:
<MAP_FIELD>[<KEY>]对于值类型为 Array 的 map,你不能直接对所获得的(数组)值使用运算符,因为这些运算符不直接支持数组。要对数组中的某一项使用运算符,请在指定表达式时使用特殊记法 [*]。此特殊索引记法会解包数组,对其所有元素分别调用外围函数,并返回包含所有各次返回值的新数组。
以下示例基于数据类型为 Map<Array<String>> 的 http.request.headers 字段,其中数组元素的数据类型为 String。
若传入的 HTTP 请求包含单个 Accept: application/json HTTP 标头,则以下表达式将求值为所示值:
http.request.headers["accept"] # ==> ["application/json"]
http.request.headers["accept"][0] # ==> "application/json"
any(http.request.headers["accept"][*] == "application/json") # ==> true
any(http.request.headers["accept"][*] == "text/plain") # ==> false以下示例基于数据类型为 Map<Array<String>> 的 http.request.uri.args 字段,其中数组元素的数据类型为 String。
若 HTTP 请求包含三个 filter URI 参数 waf、botm 与 cdn,则以下表达式将求值为所示值:
# Example request URL:
# https://example.com/?filter=waf&filter=botm&filter=cdn
http.request.uri.args["filter"] # ==> ["waf", "botm", "cdn"]
len(http.request.uri.args["filter"][1]) # ==> 4
# Check if the length of all 'filter' values is always 3 or 4
all(len(http.request.uri.args["filter"][*])[*] in {3 4}) # ==> true
# Check if the length of 'filter' values (if any) is never 3 or 4
all(not len(http.request.uri.args["filter"][*])[*] in {3 4}) # ==> false
# Check if the http.request.uri.args map contains a "filter" key
len(http.request.uri.args["filter"]) >= 0 # ==> true
# Check if the http.request.uri.args map does not contain an "order" key
not len(http.request.uri.args["order"]) >= 0 # ==> true有关 any()、all()、len() 及其他可用函数的更多信息,请参阅 函数。
无法定义自己的 map。你只能使用由字段返回的 map。
访问 map 中不存在的键 会产生"缺失值"。缺失值的行为如下:
- 任何比较
<expr> <op> <literal>(其中<expr>求值为缺失值)将求值为 false。 - 函数调用如
function(<expr>)(其中<expr>求值为缺失值)在大多数情况下将返回缺失值,但具体行为因函数而异。
列表允许你创建一组项目,并在表达式中按名称整体引用它们。每种列表类型支持特定数据类型的项目。列表中的所有项目必须具有相同的数据类型。有关可用列表类型的详细信息,请参阅 列表。
要在规则表达式中引用列表,请使用 $<list_name> 并指定 in 运算符。列表中只需有一个值与表达式左侧(in 运算符之前)匹配,该简单表达式就会求值为 true。若没有匹配项,表达式将求值为 false。
以下示例表达式过滤来自名为 office_network 的 IP 列表中 IP 地址的请求:
(ip.src in $office_network)列表名称只能包含小写字母、数字与下划线(_)字符。有关创建与管理列表的指导,请参阅 列表。
内联列表允许你在使用 in 运算符的简单表达式中直接包含一组值。
内联列表中的元素可以是字符串、整数或 IP 地址/范围。内联列表的所有元素必须具有相同的数据类型,且必须是字面量值。要指定内联列表元素,请逐个输入并用空格分隔。内联列表可以包含重复值。
此外,对于某些数据类型,你可以将范围用作元素:
-
对于整数值,以
<start_value>..<end_value>形式输入范围。内联列表可以同时包含整数范围与整数值。 -
对于 IP 地址,你可以输入:
- 以
<start_address>..<end_address>形式表示的显式 IP 范围(例如198.51.100.3..198.51.100.7)。 - CIDR 范围(例如
192.0.2.0/24或2001:0db8::/32)。
内联列表可以包含显式 IP 范围、CIDR 范围与单独的 IP 地址。
- 以
http.host in {"example.com" "example.net"}
ip.src in {198.51.100.1 198.51.100.3..198.51.100.7 192.0.2.0/24 2001:0db8::/32}
tcp.dstport in {8000..8009 8080..8089}