Examples tested against actual runtime. CI re-verifies continuously. Only documented examples are tested.
How to use
Install via yarn add locutus and import:
import { addcslashes } from 'locutus/php/strings/addcslashes'.
Or with CommonJS: const { addcslashes } = require('locutus/php/strings/addcslashes')
Use a bundler that supports tree-shaking so you only ship the functions you actually use.
Vite,
webpack,
Rollup, and
Parcel
all handle this. For server-side use this is less of a concern.
Examples
These examples are extracted from test cases that automatically verify our functions against their native counterparts.
#
code
expected result
1
addcslashes('foo[ ]', 'A..z'); // Escape all ASCII within capital A to lower z range, including square brackets
"\\f\\o\\o\\[ \\]"
2
addcslashes("zoo['.']", 'z..A'); // Only escape z, period, and A here since not a lower-to-higher range
"\\zoo['\\.']"
Notes
We show double backslashes in the return value example
code below because a JavaScript string will not
render them as backslashes otherwise
Here's what our current TypeScript equivalent to PHP's addcslashes looks like.
exportfunctionaddcslashes(str: string, charlist: string): string { // discuss at: https://locutus.io/php/addcslashes/ // parity verified: PHP 8.3 // original by: Brett Zamir (https://brett-zamir.me) // note 1: We show double backslashes in the return value example // note 1: code below because a JavaScript string will not // note 1: render them as backslashes otherwise // example 1: addcslashes('foo[ ]', 'A..z'); // Escape all ASCII within capital A to lower z range, including square brackets // returns 1: "\\f\\o\\o\\[ \\]" // example 2: addcslashes("zoo['.']", 'z..A'); // Only escape z, period, and A here since not a lower-to-higher range // returns 2: "\\zoo['\\.']" // _example 3: addcslashes("@a\u0000\u0010\u00A9", "\0..\37!@\177..\377"); // Escape as octals those specified and less than 32 (0x20) or greater than 126 (0x7E), but not otherwise // _returns 3: '\\@a\\000\\020\\302\\251' // _example 4: addcslashes("\u0020\u007E", "\40..\175"); // Those between 32 (0x20 or 040) and 126 (0x7E or 0176) decimal value will be backslashed if specified (not octalized) // _returns 4: '\\ ~' // _example 5: addcslashes("\r\u0007\n", '\0..\37'); // Recognize C escape sequences if specified // _returns 5: "\\r\\a\\n" // _example 6: addcslashes("\r\u0007\n", '\0'); // Do not recognize C escape sequences if not specified // _returns 6: "\r\u0007\n"
let target = '' constchrs: string[] = [] let i = 0 let j = 0 let c = '' let next = '' let rangeBegin = '' let rangeEnd = '' let chr = '' let begin = 0 let end = 0 let octalLength = 0 let postOctalPos = 0 let cca = 0 letescHexGrp: RegExpExecArray | null = null let encoded = '' const percentHex = /%([\dA-Fa-f]+)/g
const _pad = function (n: number | string, c: number): string { const nString = String(n) if (nString.length < c) { returnnewArray(c - nString.length + 1).join('0') + nString } return nString }
for (i = 0; i < charlist.length; i++) { c = charlist.charAt(i) next = charlist.charAt(i + 1) if (c === '\\' && next && /\d/.test(next)) { // Octal const rangeBeginMatch = charlist.slice(i + 1).match(/^\d+/) if (!rangeBeginMatch?.[0]) { continue } rangeBegin = rangeBeginMatch[0] octalLength = rangeBegin.length postOctalPos = i + octalLength + 1 if (charlist.charAt(postOctalPos) + charlist.charAt(postOctalPos + 1) === '..') { // Octal begins range begin = rangeBegin.charCodeAt(0) if (/\\\d/.test(charlist.charAt(postOctalPos + 2) + charlist.charAt(postOctalPos + 3))) { // Range ends with octal const rangeEndMatch = charlist.slice(postOctalPos + 3).match(/^\d+/) if (!rangeEndMatch?.[0]) { thrownewError('Range with no end point') } rangeEnd = rangeEndMatch[0] // Skip range end backslash i += 1 } elseif (charlist.charAt(postOctalPos + 2)) { // Range ends with character rangeEnd = charlist.charAt(postOctalPos + 2) } else { thrownewError('Range with no end point') } end = rangeEnd.charCodeAt(0) if (end > begin) { // Treat as a range for (j = begin; j <= end; j++) { chrs.push(String.fromCharCode(j)) } } else { // Supposed to treat period, begin and end as individual characters only, not a range chrs.push('.', rangeBegin, rangeEnd) } // Skip dots and range end (already skipped range end backslash if present) i += rangeEnd.length + 2 } else { // Octal is by itself chr = String.fromCharCode(parseInt(rangeBegin, 8)) chrs.push(chr) } // Skip range begin i += octalLength } elseif (next + charlist.charAt(i + 2) === '..') { // Character begins range rangeBegin = c begin = rangeBegin.charCodeAt(0) if (/\\\d/.test(charlist.charAt(i + 3) + charlist.charAt(i + 4))) { // Range ends with octal const rangeEndMatch = charlist.slice(i + 4).match(/^\d+/) if (!rangeEndMatch?.[0]) { thrownewError('Range with no end point') } rangeEnd = rangeEndMatch[0] // Skip range end backslash i += 1 } elseif (charlist.charAt(i + 3)) { // Range ends with character rangeEnd = charlist.charAt(i + 3) } else { thrownewError('Range with no end point') } end = rangeEnd.charCodeAt(0) if (end > begin) { // Treat as a range for (j = begin; j <= end; j++) { chrs.push(String.fromCharCode(j)) } } else { // Supposed to treat period, begin and end as individual characters only, not a range chrs.push('.', rangeBegin, rangeEnd) } // Skip dots and range end (already skipped range end backslash if present) i += rangeEnd.length + 2 } else { // Character is by itself chrs.push(c) } }
for (i = 0; i < str.length; i++) { c = str.charAt(i) if (chrs.includes(c)) { target += '\\' cca = c.charCodeAt(0) if (cca < 32 || cca > 126) { // Needs special escaping switch (c) { case'\n': target += 'n' break case'\t': target += 't' break case'\u000D': target += 'r' break case'\u0007': target += 'a' break case'\v': target += 'v' break case'\b': target += 'b' break case'\f': target += 'f' break default: // target += _pad(cca.toString(8), 3);break; // Sufficient for UTF-16 encoded = encodeURIComponent(c) percentHex.lastIndex = 0
// 3-length-padded UTF-8 octets if ((escHexGrp = percentHex.exec(encoded)) !== null) { // already added a slash above: const hexGroup = escHexGrp[1] if (hexGroup) { target += _pad(parseInt(hexGroup, 16).toString(8), 3) } } while ((escHexGrp = percentHex.exec(encoded)) !== null) { const hexGroup = escHexGrp[1] if (hexGroup) { target += '\\' + _pad(parseInt(hexGroup, 16).toString(8), 3) } } break } } else { // Perform regular backslashed escaping target += c } } else { // Just add the character unescaped target += c } }
return target }
exportfunctionaddcslashes(str, charlist) { // discuss at: https://locutus.io/php/addcslashes/ // parity verified: PHP 8.3 // original by: Brett Zamir (https://brett-zamir.me) // note 1: We show double backslashes in the return value example // note 1: code below because a JavaScript string will not // note 1: render them as backslashes otherwise // example 1: addcslashes('foo[ ]', 'A..z'); // Escape all ASCII within capital A to lower z range, including square brackets // returns 1: "\\f\\o\\o\\[ \\]" // example 2: addcslashes("zoo['.']", 'z..A'); // Only escape z, period, and A here since not a lower-to-higher range // returns 2: "\\zoo['\\.']" // _example 3: addcslashes("@a\u0000\u0010\u00A9", "\0..\37!@\177..\377"); // Escape as octals those specified and less than 32 (0x20) or greater than 126 (0x7E), but not otherwise // _returns 3: '\\@a\\000\\020\\302\\251' // _example 4: addcslashes("\u0020\u007E", "\40..\175"); // Those between 32 (0x20 or 040) and 126 (0x7E or 0176) decimal value will be backslashed if specified (not octalized) // _returns 4: '\\ ~' // _example 5: addcslashes("\r\u0007\n", '\0..\37'); // Recognize C escape sequences if specified // _returns 5: "\\r\\a\\n" // _example 6: addcslashes("\r\u0007\n", '\0'); // Do not recognize C escape sequences if not specified // _returns 6: "\r\u0007\n"
let target = '' const chrs = [] let i = 0 let j = 0 let c = '' let next = '' let rangeBegin = '' let rangeEnd = '' let chr = '' let begin = 0 let end = 0 let octalLength = 0 let postOctalPos = 0 let cca = 0 let escHexGrp = null let encoded = '' const percentHex = /%([\dA-Fa-f]+)/g
const _pad = function (n, c) { const nString = String(n) if (nString.length < c) { returnnewArray(c - nString.length + 1).join('0') + nString } return nString }
for (i = 0; i < charlist.length; i++) { c = charlist.charAt(i) next = charlist.charAt(i + 1) if (c === '\\' && next && /\d/.test(next)) { // Octal const rangeBeginMatch = charlist.slice(i + 1).match(/^\d+/) if (!rangeBeginMatch?.[0]) { continue } rangeBegin = rangeBeginMatch[0] octalLength = rangeBegin.length postOctalPos = i + octalLength + 1 if (charlist.charAt(postOctalPos) + charlist.charAt(postOctalPos + 1) === '..') { // Octal begins range begin = rangeBegin.charCodeAt(0) if (/\\\d/.test(charlist.charAt(postOctalPos + 2) + charlist.charAt(postOctalPos + 3))) { // Range ends with octal const rangeEndMatch = charlist.slice(postOctalPos + 3).match(/^\d+/) if (!rangeEndMatch?.[0]) { thrownewError('Range with no end point') } rangeEnd = rangeEndMatch[0] // Skip range end backslash i += 1 } elseif (charlist.charAt(postOctalPos + 2)) { // Range ends with character rangeEnd = charlist.charAt(postOctalPos + 2) } else { thrownewError('Range with no end point') } end = rangeEnd.charCodeAt(0) if (end > begin) { // Treat as a range for (j = begin; j <= end; j++) { chrs.push(String.fromCharCode(j)) } } else { // Supposed to treat period, begin and end as individual characters only, not a range chrs.push('.', rangeBegin, rangeEnd) } // Skip dots and range end (already skipped range end backslash if present) i += rangeEnd.length + 2 } else { // Octal is by itself chr = String.fromCharCode(parseInt(rangeBegin, 8)) chrs.push(chr) } // Skip range begin i += octalLength } elseif (next + charlist.charAt(i + 2) === '..') { // Character begins range rangeBegin = c begin = rangeBegin.charCodeAt(0) if (/\\\d/.test(charlist.charAt(i + 3) + charlist.charAt(i + 4))) { // Range ends with octal const rangeEndMatch = charlist.slice(i + 4).match(/^\d+/) if (!rangeEndMatch?.[0]) { thrownewError('Range with no end point') } rangeEnd = rangeEndMatch[0] // Skip range end backslash i += 1 } elseif (charlist.charAt(i + 3)) { // Range ends with character rangeEnd = charlist.charAt(i + 3) } else { thrownewError('Range with no end point') } end = rangeEnd.charCodeAt(0) if (end > begin) { // Treat as a range for (j = begin; j <= end; j++) { chrs.push(String.fromCharCode(j)) } } else { // Supposed to treat period, begin and end as individual characters only, not a range chrs.push('.', rangeBegin, rangeEnd) } // Skip dots and range end (already skipped range end backslash if present) i += rangeEnd.length + 2 } else { // Character is by itself chrs.push(c) } }
for (i = 0; i < str.length; i++) { c = str.charAt(i) if (chrs.includes(c)) { target += '\\' cca = c.charCodeAt(0) if (cca < 32 || cca > 126) { // Needs special escaping switch (c) { case'\n': target += 'n' break case'\t': target += 't' break case'\u000D': target += 'r' break case'\u0007': target += 'a' break case'\v': target += 'v' break case'\b': target += 'b' break case'\f': target += 'f' break default: // target += _pad(cca.toString(8), 3);break; // Sufficient for UTF-16 encoded = encodeURIComponent(c) percentHex.lastIndex = 0
// 3-length-padded UTF-8 octets if ((escHexGrp = percentHex.exec(encoded)) !== null) { // already added a slash above: const hexGroup = escHexGrp[1] if (hexGroup) { target += _pad(parseInt(hexGroup, 16).toString(8), 3) } } while ((escHexGrp = percentHex.exec(encoded)) !== null) { const hexGroup = escHexGrp[1] if (hexGroup) { target += '\\' + _pad(parseInt(hexGroup, 16).toString(8), 3) } } break } } else { // Perform regular backslashed escaping target += c } } else { // Just add the character unescaped target += c } }
return target }
Improve this function
Locutus is a community effort following
The McDonald's Theory:
we ship first iterations, hoping others will improve them.
If you see something that could be better, we'd love your contribution.