Cloudflare Rules 语言提供用于在表达式中处理和验证值的函数:
Rules 语言支持多种用于转换从 HTTP 请求中提取的值的函数。转换函数的常见用例是将字符串转为大写或小写,因为默认情况下字符串比较区分大小写。
例如,lower() 函数会将字符串中的所有大写字符转换为小写。
在下面的表达式中,lower() 函数将 http.host 的值转换为小写,以便与目标值 "www.cloudflare.com" 匹配:
lower(http.host) == "www.cloudflare.com"不以数组为参数类型的转换函数需要使用 [*] 索引记法。更多信息请参阅 Arrays。
Rules 语言支持以下转换函数:
any(: Array<Boolean>)Boolean
当参数中的比较运算符对参数数组中的任意值返回 true 时,返回 true。否则返回 false。
示例:
any(url_decode(http.request.body.form.values[*])[*] contains "an xss attack")all(: Array<Boolean>)Boolean
当参数中的比较运算符对参数数组中的所有值返回 true 时,返回 true。否则返回 false。
示例:
all(http.request.headers["content-type"][*] == "application/json")encode_base64(input : String | Bytes [, flags String])String
将 input 字符串或字节数组编码为 Base64 格式。
flags 参数为可选。可以以单个字符串形式提供一个或多个标志。可用标志如下:
u:使用 URL 安全的 Base64 编码(使用-和_代替+和/)。p:添加填充(追加=字符,使输出长度为 4 的倍数,部分系统有此要求)。
默认情况下,输出使用标准 Base64 编码且不添加填充。
示例:
encode_base64("hello world") will return "aGVsbG8gd29ybGQ"
encode_base64("hello world", "p") will return "aGVsbG8gd29ybGQ="
encode_base64("hello world", "u") will return "aGVsbG8gd29ybGQ"
encode_base64("hello world", "up") will return "aGVsbG8gd29ybGQ="可以将 encode_base64() 与其他函数组合,以创建签名请求标头:
encode_base64(sha256(concat(to_string(ip.src), http.host, "my-secret")))cidr(address : IP address, ipv4_network_bits Integer, ipv6_network_bits Integer)IP address
根据所提供的 IPv4 与 IPv6 网络位数(用于确定对应的网络掩码),返回与某个 IP 地址(IPv4 或 IPv6)对应的网络地址。
address 参数必须是字段,也就是说,不能是字面量 String。
ipv4_network_bits 的值必须在 1 到 32 之间,ipv6_network_bits 的值必须在 1 到 128 之间。
示例:
- 如果
ip.src为113.10.0.2,cidr(ip.src, 24, 24)将返回113.10.0.0。 - 如果
ip.src为2001:0000:130F:0000:0000:09C0:876A:130B,cidr(ip.src, 24, 24)将返回2001:0000:0000:0000:0000:0000:0000:0000。
cidr6(address : IP address, ipv6_network_bits Integer)IP address
根据所提供的网络位数(用于确定网络掩码),返回与某个 IPv6 地址对应的 IPv6 网络地址。如果第一个参数提供的是 IPv4 地址,则原样返回。
address 参数必须是字段,也就是说,不能是字面量 String。
ipv6_network_bits 的值必须在 1 到 128 之间。
此函数等价于:cidr(<address>, 32, <ipv6_network_bits>)。
示例:
- 如果
ip.src为2001:0000:130F:0000:0000:09C0:876A:130B,cidr6(ip.src, 24)将返回2001:0000:0000:0000:0000:0000:0000:0000。 - 如果
ip.src为113.10.0.2,cidr6(ip.src, 24)将返回113.10.0.2(不变)。
concat(: String | Bytes | Array)String | Array
接受以逗号分隔的值列表。将参数值连接为单个 String 或数组。
返回类型取决于输入参数的类型。例如,如果连接的是数组,函数将返回数组。
例如,concat("String1", " ", "String", "2") 将返回 "String1 String2"。
decode_base64(source : String)String
解码在 source 中指定的 Base64 编码字符串。
source 必须是字段,也就是说,不能是字面量 String。
例如,对于以下 HTTP 请求标头:client_id: MTIzYWJj,(any(decode_base64(http.request.headers["client_id"][*])[*] eq "123abc")) 将返回 true。
ends_with(source : String, substring String)Boolean
当 source 以给定子字符串结尾时返回 true。否则返回 false。source 不能是字面量值(例如 "foo")。
例如,如果 http.request.uri.path 为 "/welcome.html",则 ends_with(http.request.uri.path, ".html") 将返回 true。
join(items : Array<String>, separator String)String
返回一个字符串,该字符串是将 items 中的字符串用 separator 连接后的结果。
如果任一参数为 nil,返回值将为 nil。
如果 items 数组为空,返回值将为空字符串。
如果 items 数组仅包含一个元素,则不会进行连接,该(单个)元素将原样返回。
此函数与 split() 函数作用相反。
示例:
# Joins all HTTP request header names into a single string, with names separated by commas
join(http.request.headers.names, ",")has_key(map: : Map<T>, key: String)Boolean
如果第二个参数中指定的 key(可以是字面量或动态字符串)是作为第一个参数提供的 map 中已存在的键,则返回 true;否则返回 false。
map 中值的数据类型(由 T 表示)可以是任意类型。
如果任一参数为 nil,返回值将为 nil。
示例:
# Check if an HTTP request header exists:
has_key(http.request.headers, "x-my-header")
# Check if a request header exists based on the name of the first query argument:
has_key(http.request.headers, lower(http.request.uri.args.names[0]))has_value(collection: : Map<T> | Array<T>, value: T)Boolean
如果第二个参数中指定的 value(可以是字面量或动态值)存在于作为第一个参数提供的 collection 中,则返回 true;否则返回 false。
collection 中值的数据类型(由 T 表示)必须与所提供的 value 的数据类型匹配。此外,T 必须是原始数据类型,即必须是 Boolean、Integer、String、Bytes 或 IP address 之一。
如果任一参数为 nil,返回值将为 nil。
示例:
# Check if there is an HTTP request header with the exact name 'X-My-Header'
has_value(http.request.headers.names, "X-My-Header")
# Check if there is a request header with the exact name provided as the first query argument:
has_value(http.request.headers.names, http.request.uri.args.names[0])is_jwt_present(token_configuration_id: : String)Boolean
如果请求中存在按 ID 为 token_configuration_id 的令牌配置所配置的令牌,则返回 true。
token_configuration_id 必须是现有 token configuration 的 ID。
示例:
is_jwt_present("51231d16-01f1-48e3-93f8-91c99e81288e")is_jwt_valid(token_configuration_id: : String)Boolean
如果请求中存在根据 ID 为 token_configuration_id 的令牌配置判定为有效的令牌,则返回 true。
token_configuration_id 必须是现有 token configuration 的 ID。如果请求中缺少该令牌,函数返回 false。
is_jwt_valid("51231d16-01f1-48e3-93f8-91c99e81288e")len(: String | Bytes | Array)Integer
返回 String 或 Bytes 值的字节长度,或数组中的元素数量。
例如,如果 http.host 的值为 "example.com",则 len(http.host) 将返回 11。
lookup_json_integer(field : String, key String | Integer, key String | Integeroptional, ...)Integer
返回 field 中与所提供 key 关联的整数值。
field 必须是有效 JSON 文档的字符串表示形式。
key 可以是属性名、JSON 数组中从零开始的位置编号,或这两种选项的组合(作为额外的函数参数),同时遵循 JSON 文档的层级结构以获取特定整数值。
注意:此函数仅适用于普通整数。例如,它不适用于小数部分为零的浮点数,如 42.0。
示例:
-
给定
http.request.body.raw字段中包含的以下 JSON 对象:
{ "record_id": "aed53a", "version": 2 }
则lookup_json_integer(http.request.body.raw, "version")将返回2。 -
给定以下嵌套对象:
{ "product": { "id": 356 } }
则lookup_json_integer(http.request.body.raw, "product", "id")将返回356。 -
给定以下根级 JSON 数组:
["first_item", -234]
则lookup_json_integer(http.request.body.raw, 1)将返回-234。 -
给定 JSON 对象属性中的以下数组:
{ "network_ids": [123, 456] }
则lookup_json_integer(http.request.body.raw, "network_ids", 0)将返回123。 -
给定以下根级 JSON 对象数组:
[{ "product_id": 123 }, { "product_id": 456 }]
则lookup_json_integer(http.request.body.raw, 1, "product_id")将返回456。
lookup_json_string(field : String, key String | Integer, key String | Integeroptional, ...)String
返回 field 中与所提供 key 关联的字符串值。
field 必须是有效 JSON 文档的字符串表示形式。
key 可以是属性名、JSON 数组中从零开始的位置编号,或这两种选项的组合(作为额外的函数参数),同时遵循 JSON 文档的层级结构以获取特定值。
示例:
-
给定
http.request.body.raw字段中包含的以下 JSON 对象:
{ "company": "cloudflare", "product": "rulesets" }
则lookup_json_string(http.request.body.raw, "company") == "cloudflare"将返回true。 -
给定以下嵌套对象:
{ "network": { "name": "cloudflare" } }
则lookup_json_string(http.request.body.raw, "network", "name") == "cloudflare"将返回true。 -
给定以下根级 JSON 数组:
["other_company", "cloudflare"]
则lookup_json_string(http.request.body.raw, 1) == "cloudflare"将返回true。 -
给定 JSON 对象属性中的以下数组:
{ "networks": ["other_company", "cloudflare"] }
则lookup_json_string(http.request.body.raw, "networks", 1) == "cloudflare"将返回true。 -
给定以下根级 JSON 对象数组:
[{ "network": "other_company" }, { "network": "cloudflare" }]
则lookup_json_string(http.request.body.raw, 1, "network") == "cloudflare"将返回true。
lower(: String)String
将字符串字段转换为小写。仅转换大写 ASCII 字节。所有其他字节不受影响。
例如,如果 http.host 为 "WWW.cloudflare.com",则 lower(http.host) == "www.cloudflare.com" 将返回 true。
regex_replace(source : String, regular_expression String, replacement String)String
使用替换字符串替换源字符串中由正则表达式匹配到的部分,并返回结果。替换字符串可以包含对正则表达式捕获组的引用(例如 ${1} 和 ${2}),最多支持八个替换引用。
示例:
-
字面量匹配替换:
regex_replace("/foo/bar", "/bar$", "/baz") == "/foo/baz" -
如果没有匹配,输入字符串不会改变:
regex_replace("/x", "^/y$", "/mumble") == "/x" -
默认情况下匹配区分大小写:
regex_replace("/foo", "^/FOO$", "/x") == "/foo" -
存在多个匹配时,仅发生一次替换(第一次):
regex_replace("/a/a", "/a", "/b") == "/b/a" -
通过在
$前再加一个$来转义替换字符串中的$:
regex_replace("/b", "^/b$", "/b$$") == "/b$" -
使用捕获组进行替换:
regex_replace("/foo/a/path", "^/foo/([^/]*)/(.*)$", "/bar/${2}/${1}") == "/bar/path/a/"
通过将正则表达式的一部分放在括号中来创建捕获组。然后,在替换字符串中使用 ${<NUMBER>} 引用捕获组,其中 <NUMBER> 为捕获组的编号。
在表达式中只能使用一次 regex_replace() 函数,并且不能将其与 wildcard_replace() 函数嵌套使用。
remove_bytes(: Bytes)Bytes
返回一个新的字节数组,其中已移除所有出现的给定字节。
例如,如果 http.host 为 "www.cloudflare.com",则 remove_bytes(http.host, "\x2e\x77") 将返回 "cloudflarecom"。
remove_query_args(field : String, query_param1 String, query_param2 String, ...)String
从 URI 查询字符串中移除一个或多个查询字符串参数。返回不包含指定参数的字符串。
field 必须是以下之一:
http.request.uri.queryraw.http.request.uri.query
field 不能是字面量值,例如 "search=foo&order=asc"。
remove_query_args() 函数将移除所有指定的参数(如 query_param1、query_param2 等),包括同一参数的重复出现。
未受影响的查询参数的顺序将被保留。
示例:
// If http.request.uri.query is "order=asc&country=GB":
remove_query_args(http.request.uri.query, "country") will return "order=asc"
remove_query_args(http.request.uri.query, "order") will return "country=GB"
remove_query_args(http.request.uri.query, "search") will return "order=asc&country=GB" (unchanged)
// If http.request.uri.query is "category=Foo&order=desc&category=Bar":
remove_query_args(http.request.uri.query, "order") will return "category=Foo&category=Bar"
remove_query_args(http.request.uri.query, "category") will return "order=desc"sha256(input : String | Bytes)Bytes
计算 input 字符串或字节数组的 SHA-256 加密哈希。返回 32 字节的哈希值。
使用此函数可直接在规则表达式中生成签名请求标头、验证请求完整性,或创建安全令牌。
示例:
sha256("my-token")上面的示例返回 32 字节哈希,源站可验证该哈希以对请求进行身份验证。
可以将 sha256() 与 encode_base64() 组合,以创建 Base64 编码的签名:
encode_base64(sha256("my-token"))要从请求属性创建签名标头值:
encode_base64(sha256(concat(to_string(ip.src), to_string(http.request.timestamp.sec), "my-secret-key")))split(input : String, separator String, limit Integer)Array<String>
通过在 separator 字符串的每次出现处拆分初始字符串,将 input 字符串拆分为字符串数组。返回的数组最多包含 limit 个元素。
如果提供的 limit 值小于拆分字符串中的实际子字符串数量,返回数组的最后一个元素将包含字符串的剩余部分。
separator 必须是非空的字面量字符串。
limit 为必填项,且必须是介于 1 和 128 之间的字面量整数。
如果 input 为 nil,返回值将为 nil。
此函数与 join() 函数作用相反。
示例:
# Split a comma-separated list of categories obtained from an HTTP request header.
# A) Consider the following HTTP request header:
x-categories: groceries,electronics,diy,auto
split(http.request.headers["x-categories"][0], ",", 64) will return ["groceries", "electronics", "diy", "auto"]
split(http.request.headers["x-categories"][0], ",", 3) will return ["groceries", "electronics", "diy,auto"]
# B) Consider the following HTTP request header:
x-categories: groceries,,electronics
split(http.request.headers["x-categories"][0], ",", 64) will return ["groceries", "", "electronics"]starts_with(source : String, substring String)Boolean
当 source 以给定子字符串开头时返回 true。否则返回 false。source 不能是字面量值(例如 "foo")。
例如,如果 http.request.uri.path 为 "/blog/first-post",则 starts_with(http.request.uri.path, "/blog") 将返回 true。
substring(field : String | Bytes, start Integer, end Integeroptional)String
返回 field 值(String 或 Bytes 字段 的值)从 start 字节索引到(但不包括)end 字节索引的部分。field 中的第一个字节索引为 0。如果未提供可选的 end 索引,函数将返回从 start 索引到字符串末尾的部分。
start 和 end 索引可以是负整数值,这样可以从字符串末尾而不是开头访问字符。
示例:
// If http.request.body.raw is "asdfghjk":
substring(http.request.body.raw, 2, 5) will return "dfg"
substring(http.request.body.raw, 2) will return "dfghjk"
substring(http.request.body.raw, -2) will return "jk"
substring(http.request.body.raw, 0, -2) will return "asdfgh"to_string(: Integer | Boolean | IP address)String
返回 Integer、Boolean 或 IP address 值的字符串表示形式。
示例:
// If cf.bot_management.score is 5:
to_string(cf.bot_management.score) will return "5"
// If ssl is true:
to_string(ssl) will return "true"upper(: String)String
将字符串字段转换为大写。仅转换小写 ASCII 字节。所有其他字节不受影响。
例如,如果 http.host 为 "www.cloudflare.com",则 upper(http.host) 将返回 "WWW.CLOUDFLARE.COM"。
url_decode(source : String, options Stringoptional)String
解码在 source 中定义的 URL 格式字符串,如下所示:
-
%20和+解码为空格字符()。 -
%E4%BD解码为ä½。
source 必须是字段,也就是说,不能是字面量字符串。
options 参数为可选。必须以引号包裹的单个字符串形式提供任何选项,例如 "r" 或 "ur"。可用选项如下:
r:应用递归解码。例如,%2520将被解码两次(递归)为空格字符()。u:启用 Unicode 百分比解码。结果将以 UTF-8 编码。例如,"%u2601"将被解码为以 UTF-8 编码的云 emoji(☁️)("\xe2\x98\x81",大小为 3 字节)。
示例:
url_decode("John%20Doe") will return "John Doe"
url_decode("John+Doe") will return "John Doe"
url_decode("%2520") will return "%20"
url_decode("%2520", "r") will return " "
// Using url_decode() with the any() function:
any(url_decode(http.request.body.form.values[*])[*] contains "an xss attack")
// Using the u option to match a specific alphabet
url_decode(http.request.uri.path) matches "(?u)\p{Hangul}+"uuidv4(source : Bytes)String
根据给定参数(随机源)生成随机 UUIDv4(通用唯一标识符,版本 4)。要获取随机字节数组,请使用 cf.random_seed 字段。
例如,uuidv4(cf.random_seed) 将返回类似于 49887398-6bcf-485f-8899-f15dbef4d1d5 的 UUIDv4。
wildcard_replace(source : Bytes, wildcard_pattern Bytes, replacement Bytes, flags Bytesoptional)String
使用替换字符串替换由包含零个或多个 * 通配符元字符的字面量所匹配的 source 字符串,并返回结果。替换字符串可以包含对通配符捕获组的引用(例如 ${1} 和 ${2}),最多支持八个替换引用。
如果没有匹配,函数将原样返回 source。
source 参数必须是字段(不能是字面量字符串)。此外,整个 source 值必须与 wildcard_pattern 参数匹配(不能只匹配字段值的一部分)。
要在 wildcard_pattern 参数中输入字面量 * 字符,必须使用 \* 进行转义。此外,还必须使用 \\ 转义 \。此参数中连续两个未转义的 * 字符(**)视为无效,不能使用。如果需要执行字符转义,建议对 wildcard_pattern 参数使用 raw string syntax。
要在 replacement 参数中输入字面量 $ 字符,必须使用 $$ 进行转义。
要执行区分大小写的通配符匹配,请将 flags 参数设置为 "s"。
此函数使用惰性匹配,即它会尝试使每个 * 元字符匹配尽可能短的字符串。
在表达式中只能使用一次 wildcard_replace() 函数,并且不能将其与 regex_replace() 函数嵌套使用。
示例:
-
如果完整 URI 为
https://apps.example.com/calendar/admin?expand=true,
wildcard_replace(http.request.full_uri, "https://*.example.com/*/*", "https://example.com/${1}/${2}/${3}")将返回https://example.com/apps/calendar/admin?expand=true -
如果完整 URI 为
https://example.com/applications/app1,
wildcard_replace(http.request.full_uri, "/applications/*", "/apps/${1}")将返回https://example.com/applications/app1(值不变,因为完整 URI 值没有匹配;应使用http.request.uri.path字段进行 URI 路径匹配)。 -
如果 URI 路径为
/calendar,
wildcard_replace(http.request.uri.path, "/*", "/apps/${1}")将返回/apps/calendar。 -
如果 URI 路径为
/Apps/calendar,
wildcard_replace(http.request.uri.path, "/apps/*", "/${1}")将返回/calendar(默认不区分大小写匹配)。 -
如果 URI 路径为
/Apps/calendar,
wildcard_replace(http.request.uri.path, "/apps/*", "/${1}", "s")将返回/Apps/calendar(值不变),因为没有区分大小写的匹配。 -
如果 URI 路径为
/apps/calendar/login,
wildcard_replace(http.request.uri.path, "/apps/*/login", "/${1}/login")将返回/calendar/login。
有关通配符匹配的更多示例,请参阅 Wildcard matching。
bit_slice(protocol : String, offset_start Number, offset_end Number)Number
此函数在给定的比特切片上查找匹配。
偏移量从给定协议标头开始。例如,要匹配 UDP 数据包有效载荷的第一位,必须将 offset_start 设置为 64。
此函数主要与 ip、udp 和 tcp 一起使用。
切片(offset_end – offset_start)不能超过 32 位,但可以通过逻辑表达式将多次调用连接在一起。
bit_slice 偏移量不能超过 2,040 位。
您可以在规则表达式中通过 is_timed_hmac_valid_v0() 函数验证基于哈希的消息认证码(HMAC)令牌,该函数的签名如下:
is_timed_hmac_valid_v0(
<String literal as Key>,
<String field as MessageMAC>,
<Integer literal as ttl>,
<Integer as currentTimeStamp>,
<Optional Integer literal as lengthOfSeparator, default: 0>,
<Optional String literal as flags>
) -> <Bool as result>is_timed_hmac_valid_v0() 函数的参数定义如下:
KeyString literal- 指定用于验证 HMAC 的密钥。
MessageMACString- 包含以下 HMAC 元素的连接:
message、separator、timestamp、mac。有关定义和示例,请参阅 MessageMAC。
- 包含以下 HMAC 元素的连接:
ttlInteger literal- 定义 HMAC 令牌的生存时间,以秒表示。决定令牌自签发起的有效时长。
currentTimeStampInteger- 表示 Cloudflare 接收到请求时的 UNIX 时间戳,以秒表示。可将
http.request.timestamp.sec字段作为近似值传递给此参数。
- 表示 Cloudflare 接收到请求时的 UNIX 时间戳,以秒表示。可将
lengthOfSeparatorInteger literaloptional- 指定
MessageMAC中timestamp与message之间separator的长度。以字节表示,默认值为0。
- 指定
flagsString literaloptional-
当将此可选参数设置为
's'时,函数期望MessageMAC参数中 Base64 编码的mac值使用无填充的 URL 安全字符集。 -
当未将
flags的值设置为's'时,必须对MessageMAC参数中mac的 Base64 值进行 URL 编码。
-
is_timed_hmac_valid_v0() 函数使用所提供的 Key,从 MessageMAC 的 message 和 timestamp 区域生成消息认证码(MAC)。当生成的 MAC 与 MessageMAC 的 mac 区域匹配且令牌尚未过期时,HMAC 有效,函数返回 true。
例如,以下表达式匹配发往 downloads.example.com 且不包含有效 HMAC 令牌的请求:
http.host == "downloads.example.com"
and not is_timed_hmac_valid_v0("mysecretkey", http.request.uri, 100000, http.request.timestamp.sec, 8)有关使用 HMAC 验证的规则示例,请参阅 WAF 文档中的 Configure token authentication。
有效的 MessageMAC 满足以下正则表达式:
(.+)(.*)(\d{10})-(.{43,})并由以下用括号分隔的表达式组成:
| 表达式 | 说明 | 示例 |
|---|---|---|
(.+) |
要验证的 message。 |
/download/cat.jpg |
(.*) |
message 与 timestamp 之间的 separator,通常是参数名。 |
&verify= |
(\d{10}) |
签发 MAC 时的 10 位 UNIX timestamp,以秒表示。 |
1484063137 |
(.{43,}) |
Base64 编码的 mac。当您未将 HMAC 验证函数中 urlSafe 参数的值设置为 's' 时,必须对 mac 的 Base64 值进行 URL 编码。当 Base64 MAC 编码为 URL 安全时,mac 值包含 43 字节。否则,由于 URL 编码,该值将为 44 字节或更多。 |
IaLGSmELTvlhfd0ItdN6PhhHTFhzx73EX8uy%2FcSDiIU%3D |
有关生成 MessageMAC 的详细信息,请参阅 HMAC token generation。
考虑 MessageMAC 完全包含在单个字段中的情况,如下面的 URI 路径示例:
/download/cat.jpg?verify=1484063787-IaLGSmELTvlhfd0ItdN6PhhHTFhzx73EX8uy%2FcSDiIU%3D注意 URI 如何映射到 MessageMAC 的各个元素:
| 元素 | 值 |
|---|---|
message |
/download/cat.jpg |
separator |
?verify=(长度为 8) |
timestamp |
1484063787 |
mac |
IaLGSmELTvlhfd0ItdN6PhhHTFhzx73EX8uy%2FcSDiIU%3D |
当 MessageMAC 完全包含在单个字段(例如 http.request.uri)中时,将该字段名传递给 HMAC 验证函数的 MessageMAC 参数:
is_timed_hmac_valid_v0(
"mysecretkey",
http.request.uri,
100000,
http.request.timestamp.sec,
8
)要从多个字段组成 MessageMAC,请使用 concat() 函数。
此示例通过连接请求 URI 和两个标头字段来构造 MessageMAC 参数的值:
is_timed_hmac_valid_v0(
"mysecretkey",
concat(
http.request.uri,
http.request.headers["timestamp"][0],
"-",
http.request.headers["mac"][0]),
100000,
http.request.timestamp.sec,
0
)