Skip to content

Commit 51da653

Browse files
thomasgauvinsidorareswellwelwel
authored
feat(disableEval): add static parsers (#3365)
* add static parser, currently disabled * fix PR comments * apply linting fixes * fix linting in tests * Update lib/connection_config.js Co-authored-by: Weslley Araújo <[email protected]> * Update lib/parsers/static_text_parser.js Co-authored-by: Weslley Araújo <[email protected]> * chore: improve assertion identification * chore: use `verbose` test reporter * ci: increase timeout * ci: test static parser through all tests and variations * chore: ensure Buffer when TypeCast is false * chore: implement `nestTables` to static parser * chore: implement `jsonStrings` to static parser * ci: check static parser coverage * ci: improve test coverage identification * chore: improve dynamic properties security * refactor: use `disableEval` instead of `useStaticParser` * feat(execute): add binary static parser * ci: adapt coverage limits --------- Co-authored-by: Andrey Sidorov <[email protected]> Co-authored-by: Weslley Araújo <[email protected]>
1 parent 409d2f9 commit 51da653

File tree

15 files changed

+506
-86
lines changed

15 files changed

+506
-86
lines changed

.github/workflows/ci-coverage.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: CI - Coverage
33
on:
44
pull_request:
55
push:
6-
branches: [ master ]
6+
branches: [master]
77

88
workflow_dispatch:
99

@@ -20,14 +20,16 @@ jobs:
2020
fail-fast: false
2121
matrix:
2222
node-version: [20.x]
23-
mysql-version: ["mysql:5.7", "mysql:8.0.33"]
23+
mysql-version: ['mysql:5.7', 'mysql:8.0.33']
2424
use-compression: [0, 1]
2525
use-tls: [0, 1]
26-
mysql_connection_url_key: [""]
26+
static-parser: [0, 1]
27+
mysql_connection_url_key: ['']
2728
env:
2829
MYSQL_CONNECTION_URL: ${{ secrets[matrix.mysql_connection_url_key] }}
30+
STATIC_PARSER: ${{ matrix.static-parser }}
2931

30-
name: Coverage ${{ matrix.node-version }} - DB ${{ matrix.mysql-version }}${{ matrix.mysql_connection_url_key }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}}
32+
name: Coverage ${{ matrix.node-version }} - DB ${{ matrix.mysql-version }}${{ matrix.mysql_connection_url_key }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}} Static Parser=${{matrix.static-parser}}
3133

3234
steps:
3335
- uses: actions/checkout@v4
@@ -62,5 +64,5 @@ jobs:
6264
uses: codecov/codecov-action@v4
6365
with:
6466
token: ${{ secrets.CODECOV_TOKEN }}
65-
flags: compression-${{ matrix.use-compression }},tls-${{ matrix.use-tls }}
66-
name: codecov-umbrella-${{ matrix.node-version }}-${{ matrix.mysql-version }}-compression-${{ matrix.use-compression }}-tls-${{ matrix.use-tls }}
67+
flags: compression-${{ matrix.use-compression }},tls-${{ matrix.use-tls }},static-parser-${{ matrix.static-parser }}
68+
name: codecov-umbrella-${{ matrix.node-version }}-${{ matrix.mysql-version }}-compression-${{ matrix.use-compression }}-tls-${{ matrix.use-tls }}-static-parser-${{ matrix.static-parser }}

.github/workflows/ci-linux.yml

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ jobs:
2222
mysql-version: ['mysql:8.0.33']
2323
use-compression: [0, 1]
2424
use-tls: [0, 1]
25+
static-parser: [0, 1]
2526
mysql_connection_url_key: ['']
2627
# TODO - add mariadb to the matrix. currently few tests are broken due to mariadb incompatibilities
28+
2729
env:
2830
MYSQL_CONNECTION_URL: ${{ secrets[matrix.mysql_connection_url_key] }}
31+
STATIC_PARSER: ${{ matrix.static-parser }}
2932

30-
name: Node.js ${{ matrix.node-version }} - DB ${{ matrix.mysql-version }}${{ matrix.mysql_connection_url_key }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}}
33+
name: Node.js ${{ matrix.node-version }} - DB ${{ matrix.mysql-version }}${{ matrix.mysql_connection_url_key }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}} Static Parser=${{matrix.static-parser}}
3134

3235
steps:
3336
- uses: actions/checkout@v4
@@ -57,7 +60,7 @@ jobs:
5760

5861
- name: Run tests
5962
run: FILTER=${{matrix.filter}} MYSQL_USE_TLS=${{ matrix.use-tls }} MYSQL_USE_COMPRESSION=${{ matrix.use-compression }} npm run test
60-
timeout-minutes: 5
63+
timeout-minutes: 10
6164

6265
tests-linux-bun:
6366
runs-on: ubuntu-latest
@@ -68,8 +71,12 @@ jobs:
6871
mysql-version: ['mysql:8.0.33']
6972
use-compression: [0, 1]
7073
use-tls: [0, 1]
74+
static-parser: [0, 1]
75+
76+
env:
77+
STATIC_PARSER: ${{ matrix.static-parser }}
7178

72-
name: Bun ${{ matrix.bun-version }} - DB ${{ matrix.mysql-version }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}}
79+
name: Bun ${{ matrix.bun-version }} - DB ${{ matrix.mysql-version }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}} Static Parser=${{matrix.static-parser}}
7380

7481
steps:
7582
- uses: actions/checkout@v4
@@ -108,7 +115,7 @@ jobs:
108115
MYSQL_USE_TLS: ${{ matrix.use-tls }}
109116
FILTER: test-select-1|test-select-ssl
110117
run: bun run test:bun
111-
timeout-minutes: 1
118+
timeout-minutes: 10
112119

113120
tests-linux-deno-v1:
114121
runs-on: ubuntu-latest
@@ -118,14 +125,18 @@ jobs:
118125
deno-version: [v1.x]
119126
mysql-version: ['mysql:8.0.33']
120127
use-compression: [0, 1]
128+
static-parser: [0, 1]
121129
# TODO: investigate error when using SSL (1)
122130
#
123131
# errno: -4094
124132
# code: "UNKNOWN"
125133
# syscall: "read"
126134
use-tls: [0]
127135

128-
name: Deno ${{ matrix.deno-version }} - DB ${{ matrix.mysql-version }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}}
136+
env:
137+
STATIC_PARSER: ${{ matrix.static-parser }}
138+
139+
name: Deno ${{ matrix.deno-version }} - DB ${{ matrix.mysql-version }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}} Static Parser=${{matrix.static-parser}}
129140

130141
steps:
131142
- uses: actions/checkout@v4
@@ -162,7 +173,7 @@ jobs:
162173
MYSQL_USE_COMPRESSION: ${{ matrix.use-compression }}
163174
MYSQL_USE_TLS: ${{ matrix.use-tls }}
164175
run: deno task test:deno -- --denoCjs='.js,.cjs'
165-
timeout-minutes: 5
176+
timeout-minutes: 10
166177

167178
tests-linux-deno-v2:
168179
runs-on: ubuntu-latest
@@ -172,14 +183,18 @@ jobs:
172183
deno-version: [v2.x, canary]
173184
mysql-version: ['mysql:8.0.33']
174185
use-compression: [0, 1]
186+
static-parser: [0, 1]
175187
# TODO: investigate error when using SSL (1)
176188
#
177189
# errno: -4094
178190
# code: "UNKNOWN"
179191
# syscall: "read"
180192
use-tls: [0]
181193

182-
name: Deno ${{ matrix.deno-version }} - DB ${{ matrix.mysql-version }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}}
194+
env:
195+
STATIC_PARSER: ${{ matrix.static-parser }}
196+
197+
name: Deno ${{ matrix.deno-version }} - DB ${{ matrix.mysql-version }} - SSL=${{matrix.use-tls}} Compression=${{matrix.use-compression}} Static Parser=${{matrix.static-parser}}
183198

184199
steps:
185200
- uses: actions/checkout@v4
@@ -216,4 +231,4 @@ jobs:
216231
MYSQL_USE_COMPRESSION: ${{ matrix.use-compression }}
217232
MYSQL_USE_TLS: ${{ matrix.use-tls }}
218233
run: deno task test:deno
219-
timeout-minutes: 5
234+
timeout-minutes: 10

.nycrc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"include": ["index.js", "promise.js", "lib/**/*.js"],
44
"exclude": ["mysqldata/**", "node_modules/**", "test/**"],
55
"reporter": ["text", "lcov", "cobertura"],
6-
"statements": 86,
7-
"branches": 84,
6+
"statements": 80,
7+
"branches": 80,
88
"functions": 77,
9-
"lines": 86,
9+
"lines": 80,
1010
"checkCoverage": true,
1111
"clean": true
1212
}

lib/commands/execute.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const Query = require('./query.js');
55
const Packets = require('../packets/index.js');
66

77
const getBinaryParser = require('../parsers/binary_parser.js');
8+
const getStaticBinaryParser = require('../parsers/static_binary_parser.js');
89

910
class Execute extends Command {
1011
constructor(options, callback) {
@@ -25,12 +26,16 @@ class Execute extends Command {
2526
this._executeOptions = options;
2627
this._resultIndex = 0;
2728
this._localStream = null;
28-
this._unpipeStream = function() {};
29+
this._unpipeStream = function () {};
2930
this._streamFactory = options.infileStreamFactory;
3031
this._connection = null;
3132
}
3233

3334
buildParserFromFields(fields, connection) {
35+
if (this.options.disableEval) {
36+
return getStaticBinaryParser(fields, this.options, connection.config);
37+
}
38+
3439
return getBinaryParser(fields, this.options, connection.config);
3540
}
3641

@@ -42,7 +47,7 @@ class Execute extends Command {
4247
this.statement.id,
4348
this.parameters,
4449
connection.config.charsetNumber,
45-
connection.config.timezone
50+
connection.config.timezone,
4651
);
4752
//For reasons why this try-catch is here, please see
4853
// https://github.com/sidorares/node-mysql2/pull/689
@@ -68,7 +73,7 @@ class Execute extends Command {
6873
// this.statement.columns[this._receivedFieldsCount] : new Packets.ColumnDefinition(packet);
6974
const field = new Packets.ColumnDefinition(
7075
packet,
71-
connection.clientEncoding
76+
connection.clientEncoding,
7277
);
7378
this._receivedFieldsCount++;
7479
this._fields[this._resultIndex].push(field);
@@ -87,7 +92,7 @@ class Execute extends Command {
8792
}
8893
this._rowParser = new (this.buildParserFromFields(
8994
this._fields[this._resultIndex],
90-
connection
95+
connection,
9196
))();
9297
return Execute.prototype.row;
9398
}

lib/commands/query.js

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const Readable = require('stream').Readable;
88
const Command = require('./command.js');
99
const Packets = require('../packets/index.js');
1010
const getTextParser = require('../parsers/text_parser.js');
11+
const staticParser = require('../parsers/static_text_parser.js');
1112
const ServerStatus = require('../constants/server_status.js');
1213

1314
const EmptyPacket = new Packets.Packet(0, Buffer.allocUnsafe(4), 0, 4);
@@ -30,7 +31,7 @@ class Query extends Command {
3031
this._receivedFieldsCount = 0;
3132
this._resultIndex = 0;
3233
this._localStream = null;
33-
this._unpipeStream = function () { };
34+
this._unpipeStream = function () {};
3435
this._streamFactory = options.infileStreamFactory;
3536
this._connection = null;
3637
}
@@ -55,7 +56,7 @@ class Query extends Command {
5556

5657
const cmdPacket = new Packets.Query(
5758
this.sql,
58-
connection.config.charsetNumber
59+
connection.config.charsetNumber,
5960
);
6061
connection.writePacket(cmdPacket.toPacket(1));
6162
return Query.prototype.resultsetHeader;
@@ -120,7 +121,7 @@ class Query extends Command {
120121
if (connection.config.debug) {
121122
// eslint-disable-next-line
122123
console.log(
123-
` Resultset header received, expecting ${rs.fieldCount} column definition packets`
124+
` Resultset header received, expecting ${rs.fieldCount} column definition packets`,
124125
);
125126
}
126127
if (this._fieldCount === 0) {
@@ -140,7 +141,7 @@ class Query extends Command {
140141
this._localStream = this._streamFactory(path);
141142
} else {
142143
this._localStreamError = new Error(
143-
`As a result of LOCAL INFILE command server wants to read ${path} file, but as of v2.0 you must provide streamFactory option returning ReadStream.`
144+
`As a result of LOCAL INFILE command server wants to read ${path} file, but as of v2.0 you must provide streamFactory option returning ReadStream.`,
144145
);
145146
connection.writePacket(EmptyPacket);
146147
return this.infileOk;
@@ -159,14 +160,14 @@ class Query extends Command {
159160
const dataWithHeader = Buffer.allocUnsafe(data.length + 4);
160161
data.copy(dataWithHeader, 4);
161162
connection.writePacket(
162-
new Packets.Packet(0, dataWithHeader, 0, dataWithHeader.length)
163+
new Packets.Packet(0, dataWithHeader, 0, dataWithHeader.length),
163164
);
164165
};
165166
const onEnd = () => {
166167
connection.removeListener('error', onConnectionError);
167168
connection.writePacket(EmptyPacket);
168169
};
169-
const onError = err => {
170+
const onError = (err) => {
170171
this._localStreamError = err;
171172
connection.removeListener('error', onConnectionError);
172173
connection.writePacket(EmptyPacket);
@@ -196,7 +197,7 @@ class Query extends Command {
196197
if (this._fields[this._resultIndex].length !== this._fieldCount) {
197198
const field = new Packets.ColumnDefinition(
198199
packet,
199-
connection.clientEncoding
200+
connection.clientEncoding,
200201
);
201202
this._fields[this._resultIndex].push(field);
202203
if (connection.config.debug) {
@@ -212,7 +213,15 @@ class Query extends Command {
212213
if (this._receivedFieldsCount === this._fieldCount) {
213214
const fields = this._fields[this._resultIndex];
214215
this.emit('fields', fields);
215-
this._rowParser = new (getTextParser(fields, this.options, connection.config))(fields);
216+
if (this.options.disableEval) {
217+
this._rowParser = staticParser(fields, this.options, connection.config);
218+
} else {
219+
this._rowParser = new (getTextParser(
220+
fields,
221+
this.options,
222+
connection.config,
223+
))(fields);
224+
}
216225
return Query.prototype.fieldsEOF;
217226
}
218227
return Query.prototype.readField;
@@ -242,7 +251,7 @@ class Query extends Command {
242251
row = this._rowParser.next(
243252
packet,
244253
this._fields[this._resultIndex],
245-
this.options
254+
this.options,
246255
);
247256
} catch (err) {
248257
this._localStreamError = err;
@@ -274,13 +283,13 @@ class Query extends Command {
274283
}
275284
stream.emit('result', row, resultSetIndex); // replicate old emitter
276285
});
277-
this.on('error', err => {
286+
this.on('error', (err) => {
278287
stream.emit('error', err); // Pass on any errors
279288
});
280289
this.on('end', () => {
281290
stream.push(null); // pushing null, indicating EOF
282291
});
283-
this.on('fields', fields => {
292+
this.on('fields', (fields) => {
284293
stream.emit('fields', fields); // replicate old emitter
285294
});
286295
stream.on('end', () => {
@@ -292,10 +301,7 @@ class Query extends Command {
292301
_setTimeout() {
293302
if (this.timeout) {
294303
const timeoutHandler = this._handleTimeoutError.bind(this);
295-
this.queryTimeout = Timers.setTimeout(
296-
timeoutHandler,
297-
this.timeout
298-
);
304+
this.queryTimeout = Timers.setTimeout(timeoutHandler, this.timeout);
299305
}
300306
}
301307

0 commit comments

Comments
 (0)