Body Rewrite
Body rewrite rules change the content of request or response bodies: either with regular-expression replacements on the text, or with a jq program on JSON. They are mostly used to remove ads or flags from JSON API responses.
How bodies are processed
Section titled “How bodies are processed”- Buffering. When a rule (or a script with
requires-body) needs a body, MeowX reads the whole body into memory before forwarding it. Without such rules, bodies are streamed through piece by piece and never held in memory. - Decompression. Bodies compressed with
gzip,deflate,br(Brotli) orzstdare decompressed first. - Rules. Every matching body rule runs, in order (local rules first, then modules from the top of the list down), followed by matching scripts.
- Sending. If the body changed, MeowX sends it uncompressed:
Content-Encodingis removed and the length is recalculated. If nothing changed, the original bytes (still compressed) are sent exactly as received.
Rules match against the original URL (see How URLs are matched). Locally answered requests (rejects, redirects, Map Local) are not processed in either direction.
Size limits
Section titled “Size limits”| iOS | macOS | |
|---|---|---|
| Largest body that is buffered | 512 KB | 10 MB |
| All bodies being buffered at once (whole process) | 4 MB | 64 MB |
| Largest decompressed body | less than 2× the buffer limit (1 MB) | less than 2× the buffer limit (20 MB) |
| Body rewrites running at the same time | 1 | 4 |
| Time budget for all regex replacements on one body | 100 ms | 300 ms |
| jq evaluation limit | 3 million steps, nesting depth 256 | 20 million steps, nesting depth 512 |
When rules are skipped
Section titled “When rules are skipped”Body rules don’t apply, and the body is forwarded unchanged, when:
- the body is declared (
Content-Length) larger than the buffer limit; - the body turns out to be larger than the limit while it is being read, or the process-wide buffer budget is used up. What was read so far is sent on, the rest is streamed, and no rules or scripts run on that message;
- the response is a server-sent event stream (
Content-Type: text/event-stream), which must stay a stream; - the request carries
Expect: 100-continue; - the body can’t be decompressed (unknown encoding, truncated data) or decompresses to more than twice the limit;
- for regex rules, the body isn’t valid UTF-8 text;
- for jq rules, the body isn’t valid JSON, the program fails or produces no output;
- a rule’s regex replacements run past the time budget: that whole rule is skipped and a warning is written to the engine log, so a badly written regex can’t hold up the connection;
- the connection is upgraded to WebSocket, after which data is relayed unchanged.
Surge [Body Rewrite]
Section titled “Surge [Body Rewrite]”Regex replacements
Section titled “Regex replacements”[Body Rewrite]# type url-regex regex1 replacement1 [regex2 replacement2 …]http-response ^https:\/\/api\.example\.com\/v1\/home '"show_ad":true' '"show_ad":false'http-response ^https:\/\/api\.example\.com\/v1\/feed "ad_list":\[[^\]]*\] "ad_list":[]http-request ^https:\/\/api\.example\.com\/v1\/log "debug":false "debug":truehttp-requestorhttp-response, then the URL regex, then one or more pairs ofregex replacement.- The body is treated as UTF-8 text; each pair is applied in turn to the result of the previous one. Replacements may use
$1… for capture groups. Patterns are ICU regular expressions. - Arguments may be wrapped in single or double quotes, which is how you include spaces or the other kind of quote. A quote is only taken as closing when it is followed by a space or the end of the line, so
'"is_vip":\w+'works. An opening quote with no closing partner is treated as part of the value. - A rule only counts as applied if at least one pattern matched.
[Body Rewrite]http-response-jq ^https:\/\/api\.example\.com\/v1\/home 'del(.data.ads) | .data.banner = []'http-response-jq ^https:\/\/api\.example\.com\/v1\/feed jq-path="https://example.com/jq/feed.jq"http-request-jqorhttp-response-jq, the URL regex, and one jq expression (quote it if it contains spaces), orjq-path="https://…"to load the program from a URL. Remote jq files are downloaded by the app like other remote resources.- The body is parsed as JSON, the program runs, and its first output replaces the body (serialized compactly). If the program outputs nothing or fails, the body is left unchanged.
- An empty expression is ignored. A program that uses something MeowX’s jq doesn’t support is reported when the module is compiled (“jq expression not supported: …”) and that rule doesn’t take effect.
- An extra direction word after the type, as some community modules write it (
http-response-jq http-response ^url '…'), is skipped.
Loon plugins
Section titled “Loon plugins”Classic [Rewrite]
Section titled “Classic [Rewrite]”[Rewrite]^https:\/\/api\.example\.com\/v1\/home response-body-replace-regex "show_ad":true "show_ad":false^https:\/\/api\.example\.com\/v1\/home response-body-json-jq '.data.ads = []'^https:\/\/api\.example\.com\/v1\/home response-body-json-del data.ads data.banner^https:\/\/api\.example\.com\/v1\/home response-body-json-replace data.vip true data.level 9^https:\/\/api\.example\.com\/v1\/home response-body-json-add data.flag "on"Type (request- or response-) |
Arguments |
|---|---|
…-body-replace-regex |
Pairs of regex replacement, as in Surge. Write spaces as \x20. |
…-body-json-jq |
One jq expression (the rest of the line; outer quotes removed). |
…-body-json-del |
One or more JSON paths to delete. |
…-body-json-replace |
Pairs of path value. Only changes keys that already exist with a non-null value. |
…-body-json-add |
Pairs of path value. Creates the path if it doesn’t exist. |
New syntax
Section titled “New syntax”[Rewrite]response if ${url} ~= /^https:\/\/api\.example\.com\/v1\/home/ then response.body.replace(/"show_ad":true/, "\"show_ad\":false")response if ${url} ~= /^https:\/\/api\.example\.com\/v1\/home/ then response.json.delete(["data.ads", "data.banner"])response if ${url} ~= /^https:\/\/api\.example\.com\/v1\/home/ then response.json.replace("data.vip", true)response if ${url} ~= /^https:\/\/api\.example\.com\/v1\/home/ then response.json.add(["data.a", "data.b"], [1, "x"])response if ${url} ~= /^https:\/\/api\.example\.com\/v1\/home/ then response.json.jq(".data.ads = []")response if ${url} ~= /^https:\/\/api\.example\.com\/v1\/home/ then response.json.jq_file("https://example.com/jq/home.jq")Action (request. or response.) |
Arguments |
|---|---|
body.replace(/re/flags, "replacement", …) |
Pairs of regex and replacement. The i flag makes that pattern case-insensitive. |
json.jq("program") |
A jq program. |
json.jq_file("https://…") |
A jq program downloaded from a URL. |
json.delete(path) or json.delete([paths]) (also json.del) |
Delete one or more paths. |
json.replace(path, value) |
Replace existing keys only. Path and value may both be arrays of the same length. |
json.add(path, value) |
Set, creating the path if needed. Path and value may both be arrays. |
response.body.mock(type, content) |
A local response; see Map Local. |
Strings in arguments use JSON escapes (\", \\, \n, \uXXXX). Values that aren’t quoted are read as JSON (true, false, null, numbers); quoted values are always strings.
JSON paths
Section titled “JSON paths”Loon’s JSON operations accept paths like data.list[0].title, $.data.title or data["a.b"]:
- A leading
$or.is optional. - Dots separate object keys. Array indexes must be written in brackets (
[0]); a dotted segment made of digits is an object key. - Keys containing dots go in quoted brackets:
["a.b"]. - Each path is applied on its own, so a path that doesn’t fit the document (wrong type, missing parent) doesn’t stop the others.
jq compatibility
Section titled “jq compatibility”MeowX includes its own jq interpreter, written to behave like jq 1.7. It supports the language and most of the standard library, including:
- paths, iteration, optional
?, alternative//,try … catch,if … elif … else … end; - assignment operators (
=,|=,+=,-=,*=,/=,%=,//=) anddel,getpath,setpath,delpaths,paths,leaf_paths,pick,walk; reduce,foreach,def(including recursion),label/break, variables and destructuring;map,map_values,select,to_entries,from_entries,with_entries,add,any,all,flatten,range,limit,first,last,nth,until,while,repeat,group_by,unique_by,sort_by,min_by,max_by,IN,INDEX,tostream,fromstream,truncate_stream;- string functions such as
test,match,capture,scan,split,splits,sub,gsub,ascii_downcase,ltrimstr,trim,explode,implode,tojson,fromjson, and string interpolation; - the formats
@text,@json,@html,@uri,@csv,@tsv,@sh,@base64,@base64d,@base32,@base32d; - date functions (
now,strftime,strptime,mktime,gmtime,todate,fromdate…) and math functions (floor,ceil,round,sqrt,log,exp,pow,atan2and more).
Not supported:
inputandinputs(there is only one input: the body);- modules (
import,include); - the
?//destructuring alternative operator; - environment access:
$ENVandenvare empty objects;input_filenamereturnsnull.
Any other unknown function is reported when the module is compiled, so you can see which rule doesn’t work.
