diff --git a/index.js b/index.js index 4c66afa..0329aa3 100644 --- a/index.js +++ b/index.js @@ -63,8 +63,16 @@ exports.string = function(val){ * Dollar-Quoted String Constants */ -var randomTags = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'g', 'j', 'k', - 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't']; +var randomTags = [ + // Upper case alpha sans vowels + 'B', 'C', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'M', + 'N', 'P', 'Q', 'R', 'S', 'T', 'V', 'W', 'X', 'Z', + // Lower case alpha sans vowels + 'b', 'c', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'm', + 'n', 'p', 'q', 'r', 's', 't', 'v', 'w', 'x', 'z', + // Numeric two through nine + '2', '3', '4', '5', '6', '7', '8', '9', +]; /** * produces a random number from a given range @@ -84,6 +92,9 @@ function random(start, end) { * Format as dollar quoted string. * see: http://www.postgresql.org/docs/8.3/interactive/sql-syntax-lexical.html#SQL-SYNTAX-DOLLAR-QUOTING * + * Initially tries to use a tagless $$ as the quote wrapper. If it appears in the string to be quoted, then + * successively longer random tags are chosen until one is found that is not contained within the string. + * * @param {Mixed} val * @return {String} * @api public @@ -91,8 +102,20 @@ function random(start, end) { exports.dollarQuotedString = function(val) { if (val === undefined || val === null || val === '') return ''; - var randomTag = '$'+ randomTags[ random(0, randomTags.length) ] +'$'; - return randomTag + val + randomTag; + // Ensure val is coerced to a string + val = '' + val; + // Start with an empty tag: $$ + var tag = ''; + while (true) { + var dollarQuote = '$'+ tag + '$'; + // Check if val contains our selected dollar quote tag + if (val.indexOf(dollarQuote) < 0) { + // Not contained so dollarQuote is safe to use + return dollarQuote + val + dollarQuote; + } + // Tag was contained within val so add random character to it + tag += randomTags[ random(0, randomTags.length) ]; + } } /** diff --git a/test/index.js b/test/index.js index 10baaa8..a5c6b58 100644 --- a/test/index.js +++ b/test/index.js @@ -42,7 +42,7 @@ describe('escape(fmt, ...)', function(){ describe('%Q', function(){ it('should format as a dollar quoted string', function(){ escape('%Q', "Tobi's") - .should.match(/\$[a-z]{1}\$Tobi's\$[a-z]\$/); + .should.match("$$Tobi's$$"); }) }) }) @@ -59,12 +59,29 @@ describe('escape.string(val)', function(){ describe('escape.dollarQuotedString(val)', function() { it('should coerce to a dollar quoted string', function(){ escape.dollarQuotedString().should.equal(''); - escape.dollarQuotedString(0).should.match(/\$[a-z]{1}\$0\$[a-z]\$/); - escape.dollarQuotedString(15).should.match(/\$[a-z]{1}\$15\$[a-z]\$/); - escape.dollarQuotedString('something').should.match(/\$[a-z]{1}\$something\$[a-z]\$/); + escape.dollarQuotedString(0).should.equal('$$0$$'); + escape.dollarQuotedString(15).should.equal('$$15$$'); + escape.dollarQuotedString('something').should.equal('$$something$$'); }) }) +describe('escape.dollarQuotedString(val)', function() { + it('should handle quoting strings with dollar quotes in them', function(){ + escape.dollarQuotedString('$$').should.match(/\$[a-zA-Z0-9]+\$\$\$\$[a-zA-Z0-9]+\$/); + }) + + it('should handle dollar quotes in the string being escaped without resorting to luck', function(){ + var sql = 'SELECT $a$test$a$'; + // The only occurrences of $a$ in the string should be from the string itself. + // The repeated test from the for loop is to catch $a$ randomly being chosen as the tag. + for(var i=0;i<1000;i++) { + var escapedSql = escape.dollarQuotedString(sql); + var count = escapedSql.match(/\$a\$/g).length; + assert(count === 2); + } + }); +}) + describe('escape.ident(val)', function(){ it('should quote when necessary', function(){ escape.ident('foo').should.equal('foo');