diff --git a/lib/client.js b/lib/client.js index c09d95bdb..d37b0a587 100644 --- a/lib/client.js +++ b/lib/client.js @@ -213,6 +213,52 @@ Client.prototype.cancel = function(client, query) { } }; +// Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c +Client.prototype.escapeIdentifier = function(str) { + + var escaped = '"'; + + for(var i = 0; i < str.length; i++) { + var c = str[i]; + if(c === '"') { + escaped += c + c; + } else { + escaped += c; + } + } + + escaped += '"'; + + return escaped; +}; + +// Ported from PostgreSQL 9.2.4 source code in src/interfaces/libpq/fe-exec.c +Client.prototype.escapeLiteral = function(str) { + + var hasBackslash = false; + var escaped = '\''; + + for(var i = 0; i < str.length; i++) { + var c = str[i]; + if(c === '\'') { + escaped += c + c; + } else if (c === '\\') { + escaped += c + c; + hasBackslash = true; + } else { + escaped += c; + } + } + + escaped += '\''; + + if(hasBackslash === true) { + escaped = ' E' + escaped; + } + + return escaped; +}; + Client.prototype._pulseQueryQueue = function() { if(this.readyForQuery===true) { this.activeQuery = this.queryQueue.shift(); diff --git a/src/binding.cc b/src/binding.cc index 9e26b266d..ea262d068 100644 --- a/src/binding.cc +++ b/src/binding.cc @@ -67,6 +67,8 @@ class Connection : public ObjectWrap { command_symbol = NODE_PSYMBOL("command"); NODE_SET_PROTOTYPE_METHOD(t, "connect", Connect); + NODE_SET_PROTOTYPE_METHOD(t, "escapeIdentifier", EscapeIdentifier); + NODE_SET_PROTOTYPE_METHOD(t, "escapeLiteral", EscapeLiteral); NODE_SET_PROTOTYPE_METHOD(t, "_sendQuery", SendQuery); NODE_SET_PROTOTYPE_METHOD(t, "_sendQueryWithParams", SendQueryWithParams); NODE_SET_PROTOTYPE_METHOD(t, "_sendPrepare", SendPrepare); @@ -130,6 +132,48 @@ class Connection : public ObjectWrap { return Undefined(); } + //v8 entry point into Connection#escapeIdentifier + static Handle + EscapeIdentifier(const Arguments& args) + { + HandleScope scope; + Connection *self = ObjectWrap::Unwrap(args.This()); + + char* inputStr = MallocCString(args[0]); + char* escapedStr = self->EscapeIdentifier(inputStr); + free(inputStr); + + if(escapedStr == NULL) { + THROW(self->GetLastError()); + } + + Local jsStr = String::New(escapedStr, strlen(escapedStr)); + PQfreemem(escapedStr); + + return scope.Close(jsStr); + } + + //v8 entry point into Connection#escapeLiteral + static Handle + EscapeLiteral(const Arguments& args) + { + HandleScope scope; + Connection *self = ObjectWrap::Unwrap(args.This()); + + char* inputStr = MallocCString(args[0]); + char* escapedStr = self->EscapeLiteral(inputStr); + free(inputStr); + + if(escapedStr == NULL) { + THROW(self->GetLastError()); + } + + Local jsStr = String::New(escapedStr, strlen(escapedStr)); + PQfreemem(escapedStr); + + return scope.Close(jsStr); + } + //v8 entry point into Connection#_sendQuery static Handle SendQuery(const Arguments& args) @@ -307,6 +351,18 @@ class Connection : public ObjectWrap { return args.This(); } + char * EscapeIdentifier(const char *str) + { + TRACE("js::EscapeIdentifier") + return PQescapeIdentifier(connection_, str, strlen(str)); + } + + char * EscapeLiteral(const char *str) + { + TRACE("js::EscapeLiteral") + return PQescapeLiteral(connection_, str, strlen(str)); + } + int Send(const char *queryText) { TRACE("js::Send") diff --git a/test/integration/client/escape-tests.js b/test/integration/client/escape-tests.js new file mode 100644 index 000000000..40214e037 --- /dev/null +++ b/test/integration/client/escape-tests.js @@ -0,0 +1,153 @@ +var helper = require(__dirname + '/test-helper'); + +function createClient(callback) { + var client = new Client(helper.config); + client.connect(function(err) { + return callback(client); + }); +} + +test('escapeLiteral: no special characters', function() { + createClient(function(client) { + var expected = "'hello world'"; + var actual = client.escapeLiteral('hello world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains double quotes only', function() { + createClient(function(client) { + var expected = "'hello \" world'"; + var actual = client.escapeLiteral('hello " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes only', function() { + createClient(function(client) { + var expected = "'hello \'\' world'"; + var actual = client.escapeLiteral('hello \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains backslashes only', function() { + createClient(function(client) { + var expected = " E'hello \\\\ world'"; + var actual = client.escapeLiteral('hello \\ world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes and double quotes', function() { + createClient(function(client) { + var expected = "'hello '' \" world'"; + var actual = client.escapeLiteral('hello \' " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains double quotes and backslashes', function() { + createClient(function(client) { + var expected = " E'hello \\\\ \" world'"; + var actual = client.escapeLiteral('hello \\ " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes and backslashes', function() { + createClient(function(client) { + var expected = " E'hello \\\\ '' world'"; + var actual = client.escapeLiteral('hello \\ \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes, double quotes, and backslashes', function() { + createClient(function(client) { + var expected = " E'hello \\\\ '' \" world'"; + var actual = client.escapeLiteral('hello \\ \' " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: no special characters', function() { + createClient(function(client) { + var expected = '"hello world"'; + var actual = client.escapeIdentifier('hello world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains double quotes only', function() { + createClient(function(client) { + var expected = '"hello "" world"'; + var actual = client.escapeIdentifier('hello " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains single quotes only', function() { + createClient(function(client) { + var expected = '"hello \' world"'; + var actual = client.escapeIdentifier('hello \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains backslashes only', function() { + createClient(function(client) { + var expected = '"hello \\ world"'; + var actual = client.escapeIdentifier('hello \\ world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains single quotes and double quotes', function() { + createClient(function(client) { + var expected = '"hello \' "" world"'; + var actual = client.escapeIdentifier('hello \' " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains double quotes and backslashes', function() { + return createClient(function(client) { + var expected = '"hello \\ "" world"'; + var actual = client.escapeIdentifier('hello \\ " world'); + assert.equal(expected, actual); + client.end(); + return; + }); +}); + +test('escapeIdentifier: contains single quotes and backslashes', function() { + createClient(function(client) { + var expected = '"hello \\ \' world"'; + var actual = client.escapeIdentifier('hello \\ \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains single quotes, double quotes, and backslashes', function() { + createClient(function(client) { + var expected = '"hello \\ \' "" world"'; + var actual = client.escapeIdentifier('hello \\ \' " world'); + assert.equal(expected, actual); + client.end(); + }); +}); diff --git a/test/unit/client/escape-tests.js b/test/unit/client/escape-tests.js new file mode 100644 index 000000000..40214e037 --- /dev/null +++ b/test/unit/client/escape-tests.js @@ -0,0 +1,153 @@ +var helper = require(__dirname + '/test-helper'); + +function createClient(callback) { + var client = new Client(helper.config); + client.connect(function(err) { + return callback(client); + }); +} + +test('escapeLiteral: no special characters', function() { + createClient(function(client) { + var expected = "'hello world'"; + var actual = client.escapeLiteral('hello world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains double quotes only', function() { + createClient(function(client) { + var expected = "'hello \" world'"; + var actual = client.escapeLiteral('hello " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes only', function() { + createClient(function(client) { + var expected = "'hello \'\' world'"; + var actual = client.escapeLiteral('hello \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains backslashes only', function() { + createClient(function(client) { + var expected = " E'hello \\\\ world'"; + var actual = client.escapeLiteral('hello \\ world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes and double quotes', function() { + createClient(function(client) { + var expected = "'hello '' \" world'"; + var actual = client.escapeLiteral('hello \' " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains double quotes and backslashes', function() { + createClient(function(client) { + var expected = " E'hello \\\\ \" world'"; + var actual = client.escapeLiteral('hello \\ " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes and backslashes', function() { + createClient(function(client) { + var expected = " E'hello \\\\ '' world'"; + var actual = client.escapeLiteral('hello \\ \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeLiteral: contains single quotes, double quotes, and backslashes', function() { + createClient(function(client) { + var expected = " E'hello \\\\ '' \" world'"; + var actual = client.escapeLiteral('hello \\ \' " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: no special characters', function() { + createClient(function(client) { + var expected = '"hello world"'; + var actual = client.escapeIdentifier('hello world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains double quotes only', function() { + createClient(function(client) { + var expected = '"hello "" world"'; + var actual = client.escapeIdentifier('hello " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains single quotes only', function() { + createClient(function(client) { + var expected = '"hello \' world"'; + var actual = client.escapeIdentifier('hello \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains backslashes only', function() { + createClient(function(client) { + var expected = '"hello \\ world"'; + var actual = client.escapeIdentifier('hello \\ world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains single quotes and double quotes', function() { + createClient(function(client) { + var expected = '"hello \' "" world"'; + var actual = client.escapeIdentifier('hello \' " world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains double quotes and backslashes', function() { + return createClient(function(client) { + var expected = '"hello \\ "" world"'; + var actual = client.escapeIdentifier('hello \\ " world'); + assert.equal(expected, actual); + client.end(); + return; + }); +}); + +test('escapeIdentifier: contains single quotes and backslashes', function() { + createClient(function(client) { + var expected = '"hello \\ \' world"'; + var actual = client.escapeIdentifier('hello \\ \' world'); + assert.equal(expected, actual); + client.end(); + }); +}); + +test('escapeIdentifier: contains single quotes, double quotes, and backslashes', function() { + createClient(function(client) { + var expected = '"hello \\ \' "" world"'; + var actual = client.escapeIdentifier('hello \\ \' " world'); + assert.equal(expected, actual); + client.end(); + }); +});