Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contextual string tags to prevent SQL injection #29

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 88 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,16 @@ console.log(sql); // UPDATE posts SET modified = CURRENT_TIMESTAMP() WHERE id =
```

To generate objects with a `toSqlString` method, the `SqlString.raw()` method can
be used. This creates an object that will be left un-touched when using in a `?`
be used. This creates an object that will be left un-touched when used in a `?`
placeholder, useful for using functions as dynamic values:

**Caution** The string provided to `SqlString.raw()` will skip all escaping
functions when used, so be careful when passing in unvalidated input.

Similarly, `SqlString.identifier(id, forbidQualified)` creates an object with a
`toSqlString` method that returns `SqlString.escapeId(id, forbidQualified)`.
Its result is not re-escaped when used in a `?` or `??` placeholder.

```js
var CURRENT_TIMESTAMP = SqlString.raw('CURRENT_TIMESTAMP()');
var sql = SqlString.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
Expand Down Expand Up @@ -150,6 +154,15 @@ var sql = 'SELECT * FROM posts ORDER BY ' + SqlString.escapeId(sorter, true);
console.log(sql); // SELECT * FROM posts ORDER BY `date.2`
```

If `escapeId` receives an object with a `toSqlString` method, then `escapeId` uses
that method's result after coercing it to a string.

```js
var sorter = SqlString.identifier('date'); // ({ toSqlString: () => '`date`' })
var sql = 'SELECT * FROM posts ORDER BY ' + sqlString.escapeId(sorter);
console.log(sql); // SELECT * FROM posts ORDER BY `date`
```

Alternatively, you can use `??` characters as placeholders for identifiers you would
like to have escaped like this:

Expand All @@ -161,7 +174,8 @@ console.log(sql); // SELECT `username`, `email` FROM `users` WHERE id = 1
```
**Please note that this last character sequence is experimental and syntax might change**

When you pass an Object to `.escape()` or `.format()`, `.escapeId()` is used to avoid SQL injection in object keys.
When you pass an Object to `.escape()` or `.format()`, `.escapeId()`
is used to avoid SQL injection in object keys.

### Formatting queries

Expand Down Expand Up @@ -191,6 +205,78 @@ var sql = SqlString.format('UPDATE ?? SET ? WHERE `id` = ?', ['users', data,
console.log(sql); // UPDATE `users` SET `email` = '[email protected]', `modified` = NOW() WHERE `id` = 1
```

### ES6 Template Tag Support

`SqlString.sql` works as a template tag in Node versions that support ES6 features
(node runtime versions 6 and later).

```es6
var column = 'users';
var userId = 1;
var data = { email: '[email protected]', modified: SqlString.raw('NOW()') };
var fromFormat = SqlString.format('UPDATE ?? SET ? WHERE `id` = ?', [column, data, userId]);
var fromTag = SqlString.sql`UPDATE \`${column}\` SET ${data} WHERE \`id\` = ${userId}`;

console.log(fromFormat);
console.log(fromTag.toSqlString());
// Both emit:
// UPDATE `users` SET `email` = '[email protected]', `modified` = NOW() WHERE `id` = 1
```


There are some differences between `SqlString.format` and `SqlString.raw`:

* The `SqlString.sql` tag returns a raw chunk SQL as if by `SqlString.raw`,
whereas `SqlString.format` returns a string.
This allows chaining:
```es6
let data = { a: 1 };
let whereClause = SqlString.sql`WHERE ${data}`;
SqlString.sql`SELECT * FROM TABLE ${whereClause}`.toSqlString();
// SELECT * FROM TABLE WHERE `a` = 1
```
* An interpolation in a quoted string will not insert excess quotes:
```es6
SqlString.sql`SELECT '${ 'foo' }' `.toSqlString() === `SELECT 'foo' `;
SqlString.sql`SELECT ${ 'foo' } `.toSqlString() === `SELECT 'foo' `;
SqlString.format("SELECT '?' ", ['foo']) === `SELECT ''foo'' `;
```
This means that you can interpolate a string into an ID thus:
```es6
SqlString.sql`SELECT * FROM \`${ 'table' }\``.toSqlString() === 'SELECT * FROM `table`'
SqlString.format('SELECT * FROM ??', ['table']) === 'SELECT * FROM `table`'
```
* Backticks end a template tag, so you need to escape backticks.
```es6
SqlString.sql`SELECT \`${ 'id' }\` FROM \`TABLE\``.toSqlString()
=== 'SELECT `id` FROM `TABLE`'
```
* Other escape sequences are raw.
```es6
SqlString.sql`SELECT "\n"`.toSqlString() === 'SELECT "\\n"'
SqlString.format('SELECT "\n"', []) === 'SELECT "\n"'
SqlString.format(String.raw`SELECT "\n"`, []) === 'SELECT "\\n"'
```
* `SqlString.format` takes options at the end, but `SqlString.sql`
takes an options object in a separate call.
```es6
let timeZone = 'GMT';
let date = new Date(Date.UTC(2000, 0, 1));
SqlString.sql({ timeZone })`SELECT ${date}`.toSqlString() ===
'SELECT \'2000-01-01 00:00:00.000\'';
SqlString.format('SELECT ?', [date], false, timezone) ===
'SELECT \'2000-01-01 00:00:00.000\'';
```
The options object can contain any of
`{ stringifyObjects, timeZone, forbidQualified }` which have the
same meaning as when used with other `SqlString` APIs.

`SqlString.sql` handles `${...}` inside quoted strings as if the tag
matched the following grammar:

[![Railroad Diagram](docs/sql-railroad.svg)](docs/sql-railroad.svg)


## License

[MIT](LICENSE)
Expand Down
Loading