From 4561ba3d53167b9d33e02c388f0fd590b3cc546b Mon Sep 17 00:00:00 2001 From: Yang Jun Date: Sun, 12 May 2024 14:57:50 +0800 Subject: [PATCH] feat: escape filters from Jekyll, #443 --- docs/source/_data/sidebar.yml | 3 ++ docs/source/filters/cgi_escape.md | 17 +++++++++ docs/source/filters/overview.md | 2 +- docs/source/filters/uri_escape.md | 19 ++++++++++ docs/source/filters/xml_escape.md | 17 +++++++++ docs/source/zh-cn/filters/cgi_escape.md | 17 +++++++++ docs/source/zh-cn/filters/overview.md | 2 +- docs/source/zh-cn/filters/uri_escape.md | 19 ++++++++++ docs/source/zh-cn/filters/xml_escape.md | 17 +++++++++ src/filters/html.ts | 4 ++ src/filters/url.ts | 10 ++++- test/integration/filters/html.spec.ts | 6 +++ test/integration/filters/url.spec.ts | 50 ++++++++++++++++++++----- 13 files changed, 169 insertions(+), 14 deletions(-) create mode 100644 docs/source/filters/cgi_escape.md create mode 100644 docs/source/filters/uri_escape.md create mode 100644 docs/source/filters/xml_escape.md create mode 100644 docs/source/zh-cn/filters/cgi_escape.md create mode 100644 docs/source/zh-cn/filters/uri_escape.md create mode 100644 docs/source/zh-cn/filters/xml_escape.md diff --git a/docs/source/_data/sidebar.yml b/docs/source/_data/sidebar.yml index b44bef785c..bf6affcb97 100644 --- a/docs/source/_data/sidebar.yml +++ b/docs/source/_data/sidebar.yml @@ -33,6 +33,7 @@ filters: at_most: at_most.html capitalize: capitalize.html ceil: ceil.html + cgi_escape: cgi_escape.html compact: compact.html concat: concat.html date: date.html @@ -93,10 +94,12 @@ filters: uniq: uniq.html unshift: unshift.html upcase: upcase.html + uri_escape: uri_escape.html url_decode: url_decode.html url_encode: url_encode.html where: where.html where_exp: where_exp.html + xml_escape: xml_escape.html tags: overview: overview.html diff --git a/docs/source/filters/cgi_escape.md b/docs/source/filters/cgi_escape.md new file mode 100644 index 0000000000..2544b841e6 --- /dev/null +++ b/docs/source/filters/cgi_escape.md @@ -0,0 +1,17 @@ +--- +title: cgi_escape +--- + +{% since %}v10.13.0{% endsince %} + +CGI escape a string for use in a URL. Replaces any special characters with appropriate `%XX` replacements. CGI escape normally replaces a space with a plus `+` sign. + +Input +```liquid +{{ "foo, bar; baz?" | cgi_escape }} +``` + +Output +```text +foo%2C+bar%3B+baz%3F +``` diff --git a/docs/source/filters/overview.md b/docs/source/filters/overview.md index ded8bdb553..c531cd281c 100644 --- a/docs/source/filters/overview.md +++ b/docs/source/filters/overview.md @@ -11,7 +11,7 @@ Categories | Filters --- | --- Math | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most String | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last,remove, remove_first, remove_last, truncate, truncatewords, normalize_whitespace -HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br +HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br, xml_escape, cgi_escape, uri_escape Array | slice, map, sort, sort_natural, uniq, where, where_exp, group_by, group_by_exp, find, find_exp, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift Date | date, date_to_xmlschema, date_to_rfc822, date_to_string, date_to_long_string Misc | default, json, jsonify, inspect, raw, to_integer diff --git a/docs/source/filters/uri_escape.md b/docs/source/filters/uri_escape.md new file mode 100644 index 0000000000..dbfd79a213 --- /dev/null +++ b/docs/source/filters/uri_escape.md @@ -0,0 +1,19 @@ +--- +title: uri_escape +--- + +{% since %}v10.13.0{% endsince %} + +Percent encodes any special characters in a URI. URI escape normally replaces a space with `%20`. [Reserved characters][reserved] will not be escaped. + +Input +```liquid +{{ "http://foo.com/?q=foo, \bar?" | uri_escape }} +``` + +Output +```text +http://foo.com/?q=foo,%20%5Cbar? +``` + +[reserved]: https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters diff --git a/docs/source/filters/xml_escape.md b/docs/source/filters/xml_escape.md new file mode 100644 index 0000000000..33f5f61a6a --- /dev/null +++ b/docs/source/filters/xml_escape.md @@ -0,0 +1,17 @@ +--- +title: xml_escape +--- + +{% since %}v10.13.0{% endsince %} + +Escape some text for use in XML. + +Input +```liquid +{{ "Have you read \'James & the Giant Peach\'?" | xml_escape }} +``` + +Output +```text +Have you read 'James & the Giant Peach'? +``` diff --git a/docs/source/zh-cn/filters/cgi_escape.md b/docs/source/zh-cn/filters/cgi_escape.md new file mode 100644 index 0000000000..159dd5bdb7 --- /dev/null +++ b/docs/source/zh-cn/filters/cgi_escape.md @@ -0,0 +1,17 @@ +--- +title: cgi_escape +--- + +{% since %}v10.13.0{% endsince %} + +把字符串 CGI 转义,用于 URL。用对应的 `%XX` 替换特殊字符,空格会被转义为 `+` 号。 + +输入 +```liquid +{{ "foo, bar; baz?" | cgi_escape }} +``` + +输出 +```text +foo%2C+bar%3B+baz%3F +``` diff --git a/docs/source/zh-cn/filters/overview.md b/docs/source/zh-cn/filters/overview.md index c4bbfc7125..58b08b5323 100644 --- a/docs/source/zh-cn/filters/overview.md +++ b/docs/source/zh-cn/filters/overview.md @@ -11,7 +11,7 @@ LiquidJS 共支持 40+ 个过滤器,可以分为如下几类: --- | --- 数学 | plus, minus, modulo, times, floor, ceil, round, divided_by, abs, at_least, at_most 字符串 | append, prepend, capitalize, upcase, downcase, strip, lstrip, rstrip, strip_newlines, split, replace, replace_first, replace_last, remove, remove_first, remove_last, truncate, truncatewords, normalize_whitespace -HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br +HTML/URI | escape, escape_once, url_encode, url_decode, strip_html, newline_to_br, xml_escape, cgi_escape, uri_escape 数组 | slice, map, sort, sort_natural, uniq, where, where_exp, group_by, group_by_exp, find, find_exp, first, last, join, reverse, concat, compact, size, push, pop, shift, unshift 日期 | date, date_to_xmlschema, date_to_rfc822, date_to_string, date_to_long_string 其他 | default, json, jsonify, inspect, raw, to_integer diff --git a/docs/source/zh-cn/filters/uri_escape.md b/docs/source/zh-cn/filters/uri_escape.md new file mode 100644 index 0000000000..0caec0c706 --- /dev/null +++ b/docs/source/zh-cn/filters/uri_escape.md @@ -0,0 +1,19 @@ +--- +title: uri_escape +--- + +{% since %}v10.13.0{% endsince %} + +把 URI 中的特殊字符做百分号编码,空格会变成 `%20`。[保留字][reserved] 不会被转义。 + +输入 +```liquid +{{ "http://foo.com/?q=foo, \bar?" | uri_escape }} +``` + +输出 +```text +http://foo.com/?q=foo,%20%5Cbar? +``` + +[reserved]: https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters diff --git a/docs/source/zh-cn/filters/xml_escape.md b/docs/source/zh-cn/filters/xml_escape.md new file mode 100644 index 0000000000..3fa6d70140 --- /dev/null +++ b/docs/source/zh-cn/filters/xml_escape.md @@ -0,0 +1,17 @@ +--- +title: xml_escape +--- + +{% since %}v10.13.0{% endsince %} + +把文本做 XML 转义。 + +输入 +```liquid +{{ "Have you read \'James & the Giant Peach\'?" | xml_escape }} +``` + +输出 +```text +Have you read 'James & the Giant Peach'? +``` diff --git a/src/filters/html.ts b/src/filters/html.ts index b296dc63db..0dd14d8845 100644 --- a/src/filters/html.ts +++ b/src/filters/html.ts @@ -19,6 +19,10 @@ export function escape (str: string) { return stringify(str).replace(/&|<|>|"|'/g, m => escapeMap[m]) } +export function xml_escape (str: string) { + return escape(str) +} + function unescape (str: string) { return stringify(str).replace(/&(amp|lt|gt|#34|#39);/g, m => unescapeMap[m]) } diff --git a/src/filters/url.ts b/src/filters/url.ts index c5719099e6..d2e3b49897 100644 --- a/src/filters/url.ts +++ b/src/filters/url.ts @@ -1,4 +1,10 @@ import { stringify } from '../util/underscore' -export const url_decode = (x: string) => stringify(x).split('+').map(decodeURIComponent).join(' ') -export const url_encode = (x: string) => stringify(x).split(' ').map(encodeURIComponent).join('+') +export const url_decode = (x: string) => decodeURIComponent(stringify(x)).replace(/\+/g, ' ') +export const url_encode = (x: string) => encodeURIComponent(stringify(x)).replace(/%20/g, '+') +export const cgi_escape = (x: string) => encodeURIComponent(stringify(x)) + .replace(/%20/g, '+') + .replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase()) +export const uri_escape = (x: string) => encodeURI(stringify(x)) + .replace(/%5B/g, '[') + .replace(/%5D/g, ']') diff --git a/test/integration/filters/html.spec.ts b/test/integration/filters/html.spec.ts index 460a7aeb19..fce809d3ad 100644 --- a/test/integration/filters/html.spec.ts +++ b/test/integration/filters/html.spec.ts @@ -26,6 +26,12 @@ describe('filters/html', function () { it('should escape nil value to empty string', () => test('{{ undefinedValue | escape_once }}', '')) }) + describe('xml_escape', function () { + it('should xml_escape \' and &', function () { + return test('{{ "Have you read \'James & the Giant Peach\'?" | xml_escape }}', + 'Have you read 'James & the Giant Peach'?') + }) + }) describe('newline_to_br', function () { it('should support string_with_newlines', function () { const src = '{% capture string_with_newlines %}\n' + diff --git a/test/integration/filters/url.spec.ts b/test/integration/filters/url.spec.ts index 6c21d6acca..47b2e5e4d4 100644 --- a/test/integration/filters/url.spec.ts +++ b/test/integration/filters/url.spec.ts @@ -1,15 +1,45 @@ -import { test } from '../../stub/render' +import { Liquid } from '../../../src' -describe('filters/url', function () { - describe('url_decode', function () { - it('should decode %xx and +', - () => test('{{ "%27Stop%21%27+said+Fred" | url_decode }}', "'Stop!' said Fred")) +describe('filters/url', () => { + const liquid = new Liquid() + describe('url_decode', () => { + it('should decode %xx and +', () => { + const html = liquid.parseAndRenderSync('{{ "%27Stop%21%27+said+Fred" | url_decode }}') + expect(html).toEqual("'Stop!' said Fred") + }) }) - describe('url_encode', function () { - it('should encode @', - () => test('{{ "john@liquid.com" | url_encode }}', 'john%40liquid.com')) - it('should encode ', - () => test('{{ "Tetsuro Takara" | url_encode }}', 'Tetsuro+Takara')) + describe('url_encode', () => { + it('should encode @', () => { + const html = liquid.parseAndRenderSync('{{ "john@liquid.com" | url_encode }}') + expect(html).toEqual('john%40liquid.com') + }) + it('should encode ', () => { + const html = liquid.parseAndRenderSync('{{ "Tetsuro Takara" | url_encode }}') + expect(html).toEqual('Tetsuro+Takara') + }) + }) + + describe('cgi_escape', () => { + it('should escape CGI chars', () => { + const html = liquid.parseAndRenderSync('{{ "!\',()*\\"!" | cgi_escape }}') + expect(html).toEqual('%21%27%2C%28%29%2A%22%21') + }) + it('should escape space as +', () => { + const html = liquid.parseAndRenderSync('{{ "foo, bar; baz?" | cgi_escape }}') + expect(html).toEqual('foo%2C+bar%3B+baz%3F') + }) + }) + + describe('uri_escape', () => { + it('should escape unsupported chars for uri', () => { + const html = liquid.parseAndRenderSync('{{ "http://foo.com/?q=foo, \\\\bar?" | uri_escape }}') + expect(html).toEqual('http://foo.com/?q=foo,%20%5Cbar?') + }) + it('should not escape reserved characters', () => { + const reserved = "!#$&'()*+,/:;=?@[]" + const html = liquid.parseAndRenderSync('{{ reserved | uri_escape }}', { reserved }) + expect(html).toEqual(reserved) + }) }) })