mirror of
https://github.com/harttle/liquidjs.git
synced 2026-09-15 04:10:40 -07:00
fix(filters): support Buffer input in base64_encode to prevent binary data corruption (#881)
* fix: support Buffer input in base64_encode filter
When binary data (e.g. images, PDFs) is passed through the template
context as a Node.js Buffer, the base64_encode filter would call
stringify() on it first, which internally does String(value). This
triggers Buffer.toString() with the default 'utf-8' encoding, which
is a lossy conversion for non-UTF-8 byte sequences — invalid bytes
get replaced with U+FFFD, permanently destroying the original data.
The fix checks for Buffer.isBuffer() before stringify, and calls
buffer.toString('base64') directly, bypassing the lossy UTF-8
intermediate step. String inputs continue through the existing path
unchanged.
Made-with: Cursor
* fix: handle Buffer in filter layer to fix browser build
Move Buffer handling from base64-impl.ts (which gets swapped for the
browser impl at build time) into base64.ts (the filter layer). This
avoids a type error during the browser rollup build where the browser
impl only accepts string.
Also guard Buffer.isBuffer() with typeof Buffer !== 'undefined' for
safety in browser environments.
Made-with: Cursor
This commit is contained in:
@@ -8,7 +8,11 @@ import { FilterImpl } from '../template'
|
||||
import { stringify } from '../util'
|
||||
import { base64Encode, base64Decode } from './base64-impl'
|
||||
|
||||
export function base64_encode (this: FilterImpl, value: string): string {
|
||||
export function base64_encode (this: FilterImpl, value: string | Buffer): string {
|
||||
if (typeof Buffer !== 'undefined' && Buffer.isBuffer(value)) {
|
||||
this.context.memoryLimit.use(value.byteLength)
|
||||
return value.toString('base64')
|
||||
}
|
||||
const str = stringify(value)
|
||||
this.context.memoryLimit.use(str.length)
|
||||
return base64Encode(str)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { test } from '../../stub/render'
|
||||
import { test, liquid } from '../../stub/render'
|
||||
|
||||
describe('filters/base64', function () {
|
||||
describe('base64_encode', function () {
|
||||
@@ -65,6 +65,33 @@ describe('filters/base64', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('base64_encode with Buffer input', function () {
|
||||
it('should encode a Buffer to base64 without data corruption', async () => {
|
||||
const buf = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0xff, 0xfe])
|
||||
const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf })
|
||||
expect(result).toBe(buf.toString('base64'))
|
||||
})
|
||||
|
||||
it('should preserve bytes that are invalid UTF-8', async () => {
|
||||
const buf = Buffer.from([0x80, 0xff, 0xfe, 0x00, 0x01])
|
||||
const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf })
|
||||
const decoded = Buffer.from(result, 'base64')
|
||||
expect(decoded).toEqual(buf)
|
||||
})
|
||||
|
||||
it('should handle an empty Buffer', async () => {
|
||||
const buf = Buffer.alloc(0)
|
||||
const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf })
|
||||
expect(result).toBe('')
|
||||
})
|
||||
|
||||
it('should handle a Buffer containing valid UTF-8 text', async () => {
|
||||
const buf = Buffer.from('Hello World', 'utf8')
|
||||
const result = await liquid.parseAndRender('{{ data | base64_encode }}', { data: buf })
|
||||
expect(result).toBe(Buffer.from('Hello World').toString('base64'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('base64 round-trip', function () {
|
||||
it('should encode and decode back to original', () => {
|
||||
return test('{{ "Hello, World!" | base64_encode | base64_decode }}', 'Hello, World!')
|
||||
|
||||
Reference in New Issue
Block a user