Skip to content

Commit 21a8223

Browse files
committed
fix: parser throws on non-string input, #726
1 parent fce2899 commit 21a8223

File tree

2 files changed

+53
-48
lines changed

2 files changed

+53
-48
lines changed

src/parser/parser.ts

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class Parser {
2626
this.parseLimit = new Limiter('parse length', liquid.options.parseLimit)
2727
}
2828
public parse (html: string, filepath?: string): Template[] {
29+
html = String(html)
2930
this.parseLimit.use(html.length)
3031
const tokenizer = new Tokenizer(html, this.liquid.options.operators, filepath)
3132
const tokens = tokenizer.readTopLevelTokens(this.liquid.options)

test/e2e/issues.spec.ts

+52-48
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,25 @@ import { TopLevelToken, TagToken, Tokenizer, Context, Liquid, Drop, toValueSync
22
const LiquidUMD = require('../../dist/liquid.browser.umd.js').Liquid
33

44
describe('Issues', function () {
5-
it('#221 unicode blanks are not properly treated', async () => {
5+
it('unicode blanks are not properly treated #221', async () => {
66
const engine = new Liquid({ strictVariables: true, strictFilters: true })
77
const html = engine.parseAndRenderSync('{{huh | truncate: 11}}', { huh: 'fdsafdsafdsafdsaaaaa' })
88
expect(html).toBe('fdsafdsa...')
99
})
10-
it('#252 "Not valid identifier" error for a quotes-containing identifier', async () => {
10+
it('"Not valid identifier" error for a quotes-containing identifier #252', async () => {
1111
const template = `{% capture "form_classes" -%}
1212
foo
1313
{%- endcapture %}{{form_classes}}`
1414
const engine = new Liquid()
1515
const html = await engine.parseAndRender(template)
1616
expect(html).toBe('foo')
1717
})
18-
it('#259 complex property access with braces is not supported', async () => {
18+
it('complex property access with braces is not supported #259', async () => {
1919
const engine = new Liquid()
2020
const html = engine.parseAndRenderSync('{{ ["complex key"] }}', { 'complex key': 'foo' })
2121
expect(html).toBe('foo')
2222
})
23-
it('#243 Potential for ReDoS through string replace function', async () => {
23+
it('Potential for ReDoS through string replace function #243', async () => {
2424
const engine = new Liquid()
2525
const INPUT = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'
2626
const BROKEN_REGEX = /([a-z]+)+$/
@@ -33,13 +33,13 @@ describe('Issues', function () {
3333
// should stringify the regexp rather than execute it
3434
expect(html).toBe(INPUT)
3535
})
36-
it('#263 raw/endraw block not ignoring {% characters', () => {
36+
it('raw/endraw block not ignoring {% characters #263', () => {
3737
const template = `{% raw %}This is a code snippet showing how {% breaks the raw block.{% endraw %}`
3838
const engine = new Liquid()
3939
const html = engine.parseAndRenderSync(template)
4040
expect(html).toBe('This is a code snippet showing how {% breaks the raw block.')
4141
})
42-
it('#268 elsif is not supported for unless', () => {
42+
it('elsif is not supported for unless #268', () => {
4343
const template = `{%- unless condition1 -%}
4444
<div>X</div>
4545
{%- elsif condition2 -%}
@@ -51,7 +51,7 @@ describe('Issues', function () {
5151
const html = engine.parseAndRenderSync(template, { condition1: true, condition2: true })
5252
expect(html).toBe('<div>Y</div>')
5353
})
54-
it('#277 Passing liquid in FilterImpl', () => {
54+
it('Passing liquid in FilterImpl #277', () => {
5555
const engine = new Liquid()
5656
engine.registerFilter('render', function (this: any, template: string, name: string) {
5757
return this.liquid.parseAndRenderSync(decodeURIComponent(template), { name })
@@ -62,55 +62,55 @@ describe('Issues', function () {
6262
)
6363
expect(html).toBe('hello foo')
6464
})
65-
it('#288 Unexpected behavior when string literals contain }}', async () => {
65+
it('Unexpected behavior when string literals contain }} #288', async () => {
6666
const engine = new Liquid()
6767
const html = await engine.parseAndRender(`{{ '{{' }}{{ '}}' }}`)
6868
expect(html).toBe('{{}}')
6969
})
70-
it('#222 Support function calls', async () => {
70+
it('Support function calls #222', async () => {
7171
const engine = new Liquid()
7272
const html = await engine.parseAndRender(
7373
`{{ obj.property }}`,
7474
{ obj: { property: () => 'BAR' } }
7575
)
7676
expect(html).toBe('BAR')
7777
})
78-
it('#313 lenientIf not working as expected in umd', async () => {
78+
it('lenientIf not working as expected in umd #313', async () => {
7979
const engine = new LiquidUMD({
8080
strictVariables: true,
8181
lenientIf: true
8282
})
8383
const html = await engine.parseAndRender(`{{ name | default: "default name" }}`)
8484
expect(html).toBe('default name')
8585
})
86-
it('#321 comparison for empty/nil', async () => {
86+
it('comparison for empty/nil #321', async () => {
8787
const engine = new Liquid()
8888
const html = await engine.parseAndRender(
8989
'{% if empty == nil %}true{%else%}false{%endif%}' +
9090
'{% if nil == empty %}true{%else%}false{%endif%}'
9191
)
9292
expect(html).toBe('falsefalse')
9393
})
94-
it('#320 newline_to_br filter should output <br /> instead of <br/>', async () => {
94+
it('newline_to_br filter should output <br /> instead of <br/> #320', async () => {
9595
const engine = new Liquid()
9696
const html = await engine.parseAndRender(
9797
`{{ 'a \n b \n c' | newline_to_br | split: '<br />' }}`
9898
)
9999
expect(html).toBe('a \n b \n c')
100100
})
101-
it('#342 New lines in logical operator', async () => {
101+
it('New lines in logical operator #342', async () => {
102102
const engine = new Liquid()
103103
const tpl = `{%\r\nif\r\ntrue\r\nor\r\nfalse\r\n%}\r\ntrue\r\n{%\r\nendif\r\n%}`
104104
const html = await engine.parseAndRender(tpl)
105105
expect(html).toBe('\r\ntrue\r\n')
106106
})
107-
it('#401 Timezone Offset Issue', async () => {
107+
it('Timezone Offset Issue #401', async () => {
108108
const engine = new Liquid({ timezoneOffset: -600 })
109109
const tpl = engine.parse('{{ date | date: "%Y-%m-%d %H:%M %p %z" }}')
110110
const html = await engine.render(tpl, { date: '2021-10-06T15:31:00+08:00' })
111111
expect(html).toBe('2021-10-06 17:31 PM +1000')
112112
})
113-
it('#412 Pass root as it is to `resolve`', async () => {
113+
it('Pass root as it is to `resolve` #412', async () => {
114114
const engine = new Liquid({
115115
root: '/tmp',
116116
relativeReference: false,
@@ -126,7 +126,7 @@ describe('Issues', function () {
126126
const html = await engine.renderSync(tpl)
127127
expect(html).toBe('/tmp/foo.liquid')
128128
})
129-
it('#416 Templates imported by {% render %} not cached for concurrent async render', async () => {
129+
it('Templates imported by {% render %} not cached for concurrent async render #416', async () => {
130130
const readFile = jest.fn(() => Promise.resolve('HELLO'))
131131
const exists = jest.fn(() => 'HELLO')
132132
const engine = new Liquid({
@@ -148,15 +148,15 @@ describe('Issues', function () {
148148
expect(exists).toHaveBeenCalledTimes(1)
149149
expect(readFile).toHaveBeenCalledTimes(1)
150150
})
151-
it('#431 Error when using Date timezoneOffset in 9.28.5', () => {
151+
it('Error when using Date timezoneOffset in 9.28.5 #431', () => {
152152
const engine = new Liquid({
153153
timezoneOffset: 0,
154154
preserveTimezones: true
155155
})
156156
const tpl = engine.parse('Welcome to {{ now | date: "%Y-%m-%d" }}')
157157
return expect(engine.render(tpl, { now: new Date('2019-02-01T00:00:00.000Z') })).resolves.toBe('Welcome to 2019-02-01')
158158
})
159-
it('#433 Support Jekyll-like includes', async () => {
159+
it('Support Jekyll-like includes #433', async () => {
160160
const engine = new Liquid({
161161
dynamicPartials: false,
162162
relativeReference: false,
@@ -173,7 +173,7 @@ describe('Issues', function () {
173173
const html = await engine.render(tpl, { my_variable: 'foo' })
174174
expect(html).toBe('CONTENT for /tmp/prefix/foo-bar/suffix')
175175
})
176-
it('#428 Implement liquid/echo tags', () => {
176+
it('Implement liquid/echo tags #428', () => {
177177
const template = `{%- liquid
178178
for value in array
179179
assign double_value = value | times: 2
@@ -190,25 +190,25 @@ describe('Issues', function () {
190190
const html = engine.parseAndRenderSync(template, { array: [1, 2, 3] })
191191
expect(html).toBe('4#8#12#6')
192192
})
193-
it('#454 leaking JS prototype getter functions in evaluation', async () => {
193+
it('leaking JS prototype getter functions in evaluation #454', async () => {
194194
const engine = new Liquid({ ownPropertyOnly: true })
195195
const html = engine.parseAndRenderSync('{{foo | size}}-{{bar.coo}}', { foo: 'foo', bar: Object.create({ coo: 'COO' }) })
196196
expect(html).toBe('3-')
197197
})
198-
it('#465 Liquidjs divided_by not compatible with Ruby/Shopify Liquid', () => {
198+
it('Liquidjs divided_by not compatible with Ruby/Shopify Liquid #465', () => {
199199
const engine = new Liquid({ ownPropertyOnly: true })
200200
const html = engine.parseAndRenderSync('{{ 5 | divided_by: 3, true }}')
201201
expect(html).toBe('1')
202202
})
203-
it('#479 url_encode throws on undefined value', async () => {
203+
it('url_encode throws on undefined value #479', async () => {
204204
const engine = new Liquid({
205205
strictVariables: false
206206
})
207207
const tpl = engine.parse('{{ v | url_encode }}')
208208
const html = await engine.render(tpl, { v: undefined })
209209
expect(html).toBe('')
210210
})
211-
it('#481 filters that should not throw', async () => {
211+
it('filters that should not throw #481', async () => {
212212
const engine = new Liquid()
213213
const tpl = engine.parse(`
214214
{{ foo | join }}
@@ -223,17 +223,17 @@ describe('Issues', function () {
223223
const html = await engine.render(tpl, { foo: undefined })
224224
expect(html.trim()).toBe('[]')
225225
})
226-
it('#481 concat should always return an array', async () => {
226+
it('concat should always return an array #481', async () => {
227227
const engine = new Liquid()
228228
const html = await engine.parseAndRender(`{{ foo | concat | json }}`)
229229
expect(html).toBe('[]')
230230
})
231-
it('#486 Access array items from the right with negative indexes', async () => {
231+
it('Access array items from the right with negative indexes #486', async () => {
232232
const engine = new Liquid()
233233
const html = await engine.parseAndRender(`{% assign a = "x,y,z" | split: ',' -%}{{ a[-1] }} {{ a[-3] }} {{ a[-8] }}`)
234234
expect(html).toBe('z x ')
235235
})
236-
it('#492 contains operator does not support Drop', async () => {
236+
it('contains operator does not support Drop #492', async () => {
237237
class TemplateDrop extends Drop {
238238
valueOf () { return 'product' }
239239
}
@@ -242,44 +242,44 @@ describe('Issues', function () {
242242
const html = await engine.parseAndRender(`{% if template contains "product" %}contains{%endif%}`, ctx)
243243
expect(html).toBe('contains')
244244
})
245-
it('#513 should support large number of templates [async]', async () => {
245+
it('should support large number of templates [async] #513', async () => {
246246
const engine = new Liquid()
247247
const html = await engine.parseAndRender(`{% for i in (1..10000) %}{{ i }}{% endfor %}`)
248248
expect(html).toHaveLength(38894)
249249
})
250-
it('#513 should support large number of templates [sync]', () => {
250+
it('should support large number of templates [sync] #513', () => {
251251
const engine = new Liquid()
252252
const html = engine.parseAndRenderSync(`{% for i in (1..10000) %}{{ i }}{% endfor %}`)
253253
expect(html).toHaveLength(38894)
254254
})
255-
it('#519 should throw parse error for invalid assign expression', () => {
255+
it('should throw parse error for invalid assign expression #519', () => {
256256
const engine = new Liquid()
257257
expect(() => engine.parse('{% assign headshot = https://testurl.com/not_enclosed_in_quotes.jpg %}')).toThrow(/expected "|" before filter, line:1, col:27/)
258258
})
259-
it('#527 export Liquid Expression', () => {
259+
it('export Liquid Expression #527', () => {
260260
const tokenizer = new Tokenizer('a > b')
261261
const expression = tokenizer.readExpression()
262262
const result = toValueSync(expression.evaluate(new Context({ a: 1, b: 2 })))
263263
expect(result).toBe(false)
264264
})
265-
it('#527 export Liquid Expression (evalValue)', async () => {
265+
it('export Liquid Expression (evalValue) #527', async () => {
266266
const liquid = new Liquid()
267267
const result = await liquid.evalValue('a > b', { a: 1, b: 2 })
268268
expect(result).toBe(false)
269269
})
270-
it('#527 export Liquid Expression (evalValueSync)', async () => {
270+
it('export Liquid Expression (evalValueSync) #527', async () => {
271271
const liquid = new Liquid()
272272
const result = liquid.evalValueSync('a > b', { a: 1, b: 2 })
273273
expect(result).toBe(false)
274274
})
275-
it('#276 Promise support in expressions', async () => {
275+
it('Promise support in expressions #276', async () => {
276276
const liquid = new Liquid()
277277
const tpl = '{%if name == "alice" %}true{%endif%}'
278278
const ctx = { name: Promise.resolve('alice') }
279279
const html = await liquid.parseAndRender(tpl, ctx)
280280
expect(html).toBe('true')
281281
})
282-
it('#533 Nested Promise support for scope object', async () => {
282+
it('Nested Promise support for scope object #533', async () => {
283283
const liquid = new Liquid()
284284
const context = {
285285
a: 1,
@@ -325,7 +325,7 @@ describe('Issues', function () {
325325
expect(await liquid.parseAndRender('{{i.i}}', context)).toBe('1')
326326
expect(await liquid.parseAndRender('{{j.j}}', context)).toBe('1')
327327
})
328-
it('#559 Case/When should evaluate multiple When statements', async () => {
328+
it('Case/When should evaluate multiple When statements #559', async () => {
329329
const liquid = new Liquid()
330330
const tpl = `
331331
{% assign tag = 'Love' %}
@@ -342,7 +342,7 @@ describe('Issues', function () {
342342
const html = await liquid.parseAndRender(tpl)
343343
expect(html).toMatch(/^\s*This is a love or luck potion.\s+This is a strength or health or love potion.\s*$/)
344344
})
345-
it('#570 tag registration compatible to v9', async () => {
345+
it('tag registration compatible to v9 #570', async () => {
346346
const liquid = new Liquid()
347347
liquid.registerTag('metadata_file', {
348348
parse (tagToken: TagToken, remainTokens: TopLevelToken[]) {
@@ -358,7 +358,7 @@ describe('Issues', function () {
358358
const html = await liquid.parseAndRender(tpl, ctx)
359359
expect(html).toBe('FOO')
360360
})
361-
it('#573 date filter should return parsed input when no format is provided', async () => {
361+
it('date filter should return parsed input when no format is provided #573', async () => {
362362
const liquid = new Liquid()
363363
liquid.registerTag('metadata_file', {
364364
parse (tagToken: TagToken, remainTokens: TopLevelToken[]) {
@@ -374,7 +374,7 @@ describe('Issues', function () {
374374
// sample: Thursday, February 2, 2023 at 6:25 pm +0000
375375
expect(html).toMatch(/\w+, \w+ \d+, \d\d\d\d at \d+:\d\d [ap]m [-+]\d\d\d\d/)
376376
})
377-
it('#575 Add support for Not operator', async () => {
377+
it('Add support for Not operator #575', async () => {
378378
const liquid = new Liquid()
379379
const tpl = `
380380
{% if link and not button %}
@@ -386,7 +386,7 @@ describe('Issues', function () {
386386
const html = await liquid.parseAndRender(tpl, ctx)
387387
expect(html.trim()).toBe('<a href="https://example.com">Lot more code here</a>')
388388
})
389-
it('#70 strip multiline content of <style>', async () => {
389+
it('strip multiline content of <style> #70', async () => {
390390
const str = `
391391
<style type="text/css">
392392
.test-one-line {display: none;}
@@ -396,7 +396,7 @@ describe('Issues', function () {
396396
const html = await engine.parseAndRender(template, { str })
397397
expect(html).toMatch(/^\s*$/)
398398
})
399-
it('#589 Arrays should compare values', async () => {
399+
it('Arrays should compare values #589', async () => {
400400
const engine = new Liquid()
401401
const template = `
402402
{% assign people1 = "alice, bob, carol" | split: ", " -%}
@@ -406,7 +406,7 @@ describe('Issues', function () {
406406
const html = await engine.parseAndRender(template)
407407
expect(html).toContain('true')
408408
})
409-
it('#604 date filter appears to add DST correction to UTC dates', () => {
409+
it('date filter appears to add DST correction to UTC dates #604', () => {
410410
const engine = new Liquid({
411411
timezoneOffset: 'Etc/GMT'
412412
})
@@ -426,12 +426,12 @@ describe('Issues', function () {
426426
'2023-01-05T12:00:00+0000'
427427
expect(html).toEqual(expected)
428428
})
429-
it('#610 should throw missing ":" after filter name', () => {
429+
it('should throw missing ":" after filter name #610', () => {
430430
const engine = new Liquid()
431431
const fn = () => engine.parseAndRenderSync("{%- assign module = '' | split '' -%}")
432432
expect(fn).toThrow(/expected ":" after filter name/)
433433
})
434-
it('#628 Single or double quote breaks comments', () => {
434+
it('Single or double quote breaks comments #628', () => {
435435
const template = `{%- liquid
436436
# Show a message that's customized to the product type
437437
@@ -454,7 +454,7 @@ describe('Issues', function () {
454454
const result = engine.parseAndRenderSync(template, { product })
455455
expect(result).toEqual('This is a love potion!')
456456
})
457-
it('#643 Error When Accessing Subproperty of Bracketed Reference', () => {
457+
it('Error When Accessing Subproperty of Bracketed Reference #643', () => {
458458
const engine = new Liquid()
459459
const tpl = '{{ ["Key String with Spaces"].subpropertyKey }}'
460460
const ctx = {
@@ -464,20 +464,20 @@ describe('Issues', function () {
464464
}
465465
expect(engine.parseAndRenderSync(tpl, ctx)).toEqual('FOO')
466466
})
467-
it('#655 Error in the tokenization process due to an invalid value expression', () => {
467+
it('Error in the tokenization process due to an invalid value expression #655', () => {
468468
const engine = new Liquid()
469469
const result = engine.parseAndRenderSync('{{ÜLKE}}', { ÜLKE: 'Türkiye' })
470470
expect(result).toEqual('Türkiye')
471471
})
472-
it('#670 Should not render anything after an else branch', () => {
472+
it('Should not render anything after an else branch #670', () => {
473473
const engine = new Liquid()
474474
expect(() => engine.parseAndRenderSync('{% assign value = "this" %}{% if false %}{% else %}{% else %}{% endif %}')).toThrow('duplicated else')
475475
})
476-
it('#672 Should not render an elseif after an else branch', () => {
476+
it('Should not render an elseif after an else branch #672', () => {
477477
const engine = new Liquid()
478478
expect(() => engine.parseAndRenderSync('{% if false %}{% else %}{% elsif true %}{% endif %}')).toThrow('unexpected elsif after else')
479479
})
480-
it('#675 10.10.1 Operator: contains regression', () => {
480+
it('10.10.1 Operator: contains regression #675', () => {
481481
const engine = new Liquid()
482482
class StrictStringForLiquid {
483483
constructor (private value: string) {}
@@ -491,4 +491,8 @@ describe('Issues', function () {
491491
})
492492
expect(result).toEqual('true')
493493
})
494+
it('parse length limit exceeded on versions >= 10.15.0 #726', () => {
495+
const liquid = new Liquid()
496+
expect(() => liquid.parse({} as any)).not.toThrow()
497+
})
494498
})

0 commit comments

Comments
 (0)