Functions reference
The Cloudflare Rules language provides functions for manipulating and validating values in an expression:
- Transformation functions manipulate values extracted from an HTTP request.
- The HMAC validation function tests the validity of an HMAC token. Use it to write expressions that target requests based on the presence of a valid HMAC token.
Transformation functions
The Rules language supports several functions that transform values extracted from HTTP requests. A common use case for transformation functions is the conversion of a string of characters to uppercase or lowercase, since by default, string evaluation is case sensitive.
For example, the lower()
function converts all uppercase characters in a string to lowercase.
In the expression below, thelower()
function transforms http.host
values to lowercase so that they match the target value "www.cloudflare.com"
:
lower(http.host) == "www.cloudflare.com"
Transformation functions that do not take arrays as an argument type require the [*]
index notation. Refer to Arrays for more information.
The Rules language supports these transformation functions:
any
(Array
<Boolean>)Boolean
Returns
true
when the comparison operator in the argument returnstrue
for any of the values in the argument array. Returnsfalse
otherwise.Example:
any(url_decode(http.request.body.form.values[*])[*] contains "an xss attack")
all
(Array
<Boolean>)Boolean
Returns
true
when the comparison operator in the argument returnstrue
for all values in the argument array. Returnsfalse
otherwise.Example:
all(http.request.headers["content-type"][*] == "application/json")
concat
(String | Integer | bytes | Array elements
)String
Takes a comma-separated list of values. Concatenates the argument values into a single String.
Example:
concat("String1"," ","String",2) == "String1 String2"
ends_with
(sourceString, substringString)Boolean
Returns
true
when the source ends with a given substring. Returnsfalse
otherwise. The source cannot be a literal value (for example,"foo"
).Example:
Ifhttp.request.uri.path
is"/welcome.html"
, thenends_with(http.request.uri.path, ".html")
will returntrue
.
len
(String | bytes
)Integer
Returns the byte length of a String or Bytes field.
Example:
len(http.host)
lookup_json_integer
(fieldString, keyString | Integer [, keyString | Integer, …])Integer
Returns the integer value associated with the supplied
key
infield
.
Thefield
must be a string representation of a valid JSON document.
Thekey
can be an attribute name, a zero-based position number in a JSON array, or a combination of these two options (as extra function parameters), while following the hierarchy of the JSON document to obtain a specific integer value.
Note: This function only works for plain integers. For example, it will not work for floating numbers with a zero decimal part such as42.0
.Examples:
A) Given the following JSON object contained in the
http.request.body.raw
field:{ "record_id": "aed53a", "version": 2 }
The following function call will return2
:lookup_json_integer(http.request.body.raw, "version") == 2
B) Given the following nested object:
{ "product": { "id": 356 } }
The following function call will return356
:lookup_json_integer(http.request.body.raw, "product", "id") == 356
C) Given the following JSON array at the root level:
["first_item", -234]
The following function call will return-234
:lookup_json_integer(http.request.body.raw, 1) == -234
D) Given the following array in a JSON object attribute:
{ "network_ids": [123, 456] }
The following function call will return123
:lookup_json_integer(http.request.body.raw, "network_ids", 0) == 123
E) Given the following root-level array of JSON objects:
[{ "product_id": 123 }, { "product_id": 456 }]
The following function call will return456
:lookup_json_integer(http.request.body.raw, 1, "product_id") == 456
lookup_json_string
(fieldString, keyString | Integer [, keyString | Integer, …])String
Returns the string value associated with the supplied
key
infield
.
Thefield
must be a string representation of a valid JSON document.
Thekey
can be an attribute name, a zero-based position number in a JSON array, or a combination of these two options (as extra function parameters), while following the hierarchy of the JSON document to obtain a specific value.Examples:
A) Given the following JSON object contained in the
http.request.body.raw
field:{ "company": "cloudflare", "product": "rulesets" }
The following expression will returntrue
:lookup_json_string(http.request.body.raw, "company") == "cloudflare"
B) Given the following nested object:
{ "network": { "name": "cloudflare" } }
The following expression will returntrue
:lookup_json_string(http.request.body.raw, "network", "name") == "cloudflare"
C) Given the following JSON array at the root level:
["other_company", "cloudflare"]
The following expression will returntrue
:lookup_json_string(http.request.body.raw, 1) == "cloudflare"
D) Given the following array in a JSON object attribute:
{ "networks": ["other_company", "cloudflare"] }
The following expression will returntrue
:lookup_json_string(http.request.body.raw, "networks", 1) == "cloudflare"
E) Given the following root-level array of JSON objects:
[{ "network": "other_company" }, { "network": "cloudflare" }]
The following expression will returntrue
:lookup_json_string(http.request.body.raw, 1, "network") == "cloudflare"
lower
(String
)String
Converts a string field to lowercase. Only uppercase ASCII bytes are converted. All other bytes are unaffected.
Example:
lower(http.host) == "www.cloudflare.com"
regex_replace
(sourceString, regular_expressionString, replacementString)String
Replaces a part of a source string matched by a regular expression with a replacement string, returning the result. The replacement string can contain references to regular expression capture groups.
Examples:
Literal match replace:
regex_replace("/foo/bar", "/bar$", "/baz") == "/foo/baz"
If there is no match, the input string does not change:
regex_replace("/x", "^/y$", "/mumble") == "/x"
Match is case sensitive by default:
regex_replace("/foo", "^/FOO$", "/x") == "/foo"
When there are multiple matches, only one replacement occurs (the first one):
regex_replace("/a/a", "/a", "/b") == "/b/a"
Escape a
$
in the replacement string by prefixing it with another$
:regex_replace("/b", "^/b$", "/b$$") == "/b$"
Replace with capture groups:
regex_replace("/foo/a/path", "^/foo/([^/]*)/(.*)$", "/bar/${2}/${1}") == "/bar/path/a/"
Create capture groups by putting part of the regular expression in parentheses. Then, reference a capture group using
${<num>}
in the replacement string, where<num>
is the number of the capture group.
remove_bytes
(bytes
)bytes
Returns a new byte array with all the occurrences of the given bytes removed.
Example:
// With http.host = "www.cloudflare.com":remove_bytes(http.host, "\x2e\x77") == "cloudflarecom"
starts_with
(sourceString, substringString)Boolean
Returns
true
when the source starts with a given substring. Returnsfalse
otherwise. The source cannot be a literal value (for example,"foo"
).Example:
// With http.request.uri.path = "/blog/first-post":starts_with(http.request.uri.path, "/blog") == true
substring
(fieldString | Bytes, startInteger [, endInteger])String
Returns part of the
field
value (the value of a String or Bytes field) from thestart
byte index up to (but excluding) theend
byte index. The first byte infield
has index0
. If you do not provide the optionalend
index, the function returns the part of the string fromstart
index to the end of the string.The
end
index must be greater than thestart
index. Thestart
andend
indexes can be negative integer values, which allows you to access characters from the end of the string instead of the beginning.Examples:
// With http.request.body.raw = "asdfghjk":substring(http.request.body.raw, 2, 5) == "dfg"substring(http.request.body.raw, 2) == "dfghjk"substring(http.request.body.raw, -2) == "jk"
to_string
(Integer | Boolean | IP address
)String
Returns the string representation of an Integer, Boolean, or IP address value.
Examples:
to_string(cf.bot_management.score) == "5"to_string(ssl) == "true"
upper
(String
)String
Converts a string field to uppercase. Only lowercase ASCII bytes are converted. All other bytes are unaffected.
Example:
upper(http.host) == “WWW.CLOUDFLARE.COM”
url_decode
(sourceString[, optionsString])String
Decodes a URL-formatted string defined in
source
, as in the following:%20
and+
decode to a space character (%E4%BD
decodes toä½
.
The
options
parameter is optional. You must provide any options as a single string wrapped in quotes, such as"r"
or"ur"
. The available options are the following:r
: Applies recursive decoding. For example,%2520
will be decoded twice (recursively) to a space character (u
: Enables Unicode percent decoding. For example,%E2%98%81%EF%B8%8F
will be decoded to a cloud emoji (☁️
).
Examples:
url_decode("John%20Doe") == "John Doe"url_decode("John+Doe") == "John Doe"url_decode("%2520") == "%20"url_decode("%2520", "r") == " "// With the any() function:any(url_decode(http.request.body.form.values[*])[*] contains "an xss attack")
uuidv4
(Bytes
)String
Generates a random UUIDv4 (Universally Unique Identifier, version 4) based on the given argument (a source of randomness). To obtain an array of random bytes, use the
cf.random_seed
field.Example:
uuidv4(cf.random_seed)
returns a UUIDv4 similar to49887398-6bcf-485f-8899-f15dbef4d1d5
Magic Firewall Functions
bit_slice
(protocolString
, offset_startNumber
, offset_endNumber
)Number
- This function looks for matches on a given slice of bits.
- The offset starts on the given protocol header. For example, to match on the first bit of payload for a UDP packet, you must set
offset_start
to64
. - This is primarily intended for use with
ip
,udp
, andtcp
. - The slice (
offset_end
—offset_start
) cannot be longer than 32 bits, but multiple calls can be joined together via logical expressions. - The
bit_slice
offset cannot exceed 2,040 bits.
HMAC validation
Overview
You can validate hash-based message authentication code (HMAC) tokens in a rule expression by using the is_timed_hmac_valid_v0()
function, which has this signature:
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>
The is_timed_hmac_valid_v0()
function has these parameter definitions:
Key
String literal
- Specifies the secret cryptographic key for validating the HMAC.
MessageMAC
String
- Contains a concatenation of these HMAC elements:
message
,separator
,timestamp
,mac
. For a definition and an example, refer to MessageMAC.
- Contains a concatenation of these HMAC elements:
ttl
Integer literal
- Defines the time-to-live for the HMAC token, expressed in seconds. Determines how long the token is valid, relative to the time it was issued.
currentTimeStamp
Integer
- Represents the UNIX timestamp when Cloudflare received the request, expressed in seconds. Pass the
http.request.timestamp.sec
field as an approximate value to this argument.
- Represents the UNIX timestamp when Cloudflare received the request, expressed in seconds. Pass the
lengthOfSeparator
Integer literal
- Specifies the length of the
separator
between thetimestamp
and themessage
in theMessageMAC
. Expressed in bytes, with a default value of0
.
- Specifies the length of the
flags
String literal
When you set this optional argument to
's'
, the function expects the value of the Base64-encodedmac
in theMessageMAC
argument to use the URL-safe character set with no padding.When you do not set the value of
flags
to's'
, you must URL encode the Base64 value formac
in theMessageMAC
argument.
Usage
The is_timed_hmac_valid_v0()
function uses the supplied Key to generate a message authentication code (MAC) from the message
and the timestamp
regions of the MessageMAC. When the generated MAC matches the mac
region of the MessageMAC and the token has not expired, the HMAC is valid and the function returns true
.
For example, the following expression matches requests to download.example.com
that do not include valid HMAC tokens:
http.host == "download.example.com"and not is_timed_hmac_valid_v0("mysecretkey", http.request.uri, 100000, http.request.timestamp.sec, 8)
For examples of rules that use HMAC validation, refer to Require a valid HMAC token in the WAF documentation.
MessageMAC
A valid MessageMAC satisfies the following regular expression:
(.+)(.*)(\d{10})-(.{43,})
and is composed of these parentheses-delimited expressions:
Expression | Description | Example |
---|---|---|
(.+) | The message to validate. | /download/cat.jpg |
(.*) | The separator between message and timestamp, commonly a parameter name. | &verify= |
(\d{10}) | The 10-digit UNIX timestamp when the MAC was issued, expressed in seconds. | 1484063137 |
(.{43,}) | A Base64-encoded version of the When the Base64 MAC encoding is URL-safe, the | IaLGSmELTvlhfd0ItdN6PhhHTFhzx |
For details on generating a MessageMAC, refer to Implement token creation.
HMAC validation examples
MessageMAC in a single field
Consider the case where the MessageMAC is contained entirely within a single field, as in this example URI path:
/download/cat.jpg?verify=1484063787-IaLGSmELTvlhfd0ItdN6PhhHTFhzx73EX8uy%2FcSDiIU%3D
Note how the URI maps to the elements of the MessageMAC:
Element | Value |
---|---|
message | /download/cat.jpg |
separator | ?verify= (with length 8 ) |
timestamp | 1484063787 |
mac | IaLGSmELTvlhfd0ItdN6PhhHTFhzx73EX8uy%2FcSDiIU%3D |
When the MessageMAC is contained entirely within a single field such as http.request.uri
, pass the field name to the MessageMAC
argument of the HMAC validation function:
is_timed_hmac_valid_v0("mysecretkey",http.request.uri,100000,http.request.timestamp.sec,8)
Concatenated MessageMAC argument
To compose a MessageMAC from more than one field, use the concat()
function.
This example constructs the value of the MessageMAC
argument by concatenating the request URI and two header fields:
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)