Skip to content

Commit 337e87a

Browse files
committed
Add localInfile option to control LOAD DATA LOCAL INFILE
closes #2185
1 parent 1e2c350 commit 337e87a

12 files changed

+146
-27
lines changed

Changes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ you spot any mistakes.
66

77
## HEAD
88

9+
* Add `localInfile` option to control `LOAD DATA LOCAL INFILE`
910
* Add new error codes up to MySQL 5.7.29
1011
* Fix early detection of bad callback to `connection.query`
1112
* Support Node.js 12.x #2211

Readme.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ issue [#501](https://github.com/mysqljs/mysql/issues/501). (Default: `false`)
240240
* `trace`: Generates stack traces on `Error` to include call site of library
241241
entrance ("long stack traces"). Slight performance penalty for most calls.
242242
(Default: `true`)
243+
* `localInfile`: Allow `LOAD DATA INFILE` to use the `LOCAL` modifier. (Default: `true`)
243244
* `multipleStatements`: Allow multiple mysql statements per query. Be careful
244245
with this, it could increase the scope of SQL injection attacks. (Default: `false`)
245246
* `flags`: List of connection flags to use other than the default ones. It is
@@ -323,7 +324,8 @@ The following flags are available:
323324
- `INTERACTIVE` - Indicates to the MySQL server this is an "interactive" client. This
324325
will use the interactive timeouts on the MySQL server and report as interactive in
325326
the process list. (Default off)
326-
- `LOCAL_FILES` - Can use `LOAD DATA LOCAL`. (Default on)
327+
- `LOCAL_FILES` - Can use `LOAD DATA LOCAL`. This flag is controlled by the connection
328+
option `localInfile`. (Default on)
327329
- `LONG_FLAG` - Longer flags in Protocol::ColumnDefinition320. (Default on)
328330
- `LONG_PASSWORD` - Use the improved version of Old Password Authentication.
329331
(Default on)

lib/ConnectionConfig.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ function ConnectionConfig(options) {
3333
this.ssl = (typeof options.ssl === 'string')
3434
? ConnectionConfig.getSSLProfile(options.ssl)
3535
: (options.ssl || false);
36+
this.localInfile = (options.localInfile === undefined)
37+
? true
38+
: options.localInfile;
3639
this.multipleStatements = options.multipleStatements || false;
3740
this.typeCast = (options.typeCast === undefined)
3841
? true
@@ -114,6 +117,11 @@ ConnectionConfig.getDefaultFlags = function getDefaultFlags(options) {
114117
'+TRANSACTIONS' // Expects status flags
115118
];
116119

120+
if (options && options.localInfile !== undefined && !options.localInfile) {
121+
// Disable LOCAL modifier for LOAD DATA INFILE
122+
defaultFlags.push('-LOCAL_FILES');
123+
}
124+
117125
if (options && options.multipleStatements) {
118126
// May send multiple statements per COM_QUERY and COM_STMT_PREPARE
119127
defaultFlags.push('+MULTI_STATEMENTS');

lib/protocol/packets/EmptyPacket.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@ module.exports = EmptyPacket;
22
function EmptyPacket() {
33
}
44

5+
EmptyPacket.prototype.parse = function parse() {
6+
};
7+
58
EmptyPacket.prototype.write = function write() {
69
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
module.exports = LocalInfileRequestPacket;
2+
function LocalInfileRequestPacket(options) {
3+
options = options || {};
4+
5+
this.filename = options.filename;
6+
}
7+
8+
LocalInfileRequestPacket.prototype.parse = function parse(parser) {
9+
if (parser.parseLengthCodedNumber() !== null) {
10+
var err = new TypeError('Received invalid field length');
11+
err.code = 'PARSER_INVALID_FIELD_LENGTH';
12+
throw err;
13+
}
14+
15+
this.filename = parser.parsePacketTerminatedString();
16+
};
17+
18+
LocalInfileRequestPacket.prototype.write = function write(writer) {
19+
writer.writeLengthCodedNumber(null);
20+
writer.writeString(this.filename);
21+
};

lib/protocol/packets/ResultSetHeaderPacket.js

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,12 @@ function ResultSetHeaderPacket(options) {
33
options = options || {};
44

55
this.fieldCount = options.fieldCount;
6-
this.extra = options.extra;
76
}
87

98
ResultSetHeaderPacket.prototype.parse = function(parser) {
109
this.fieldCount = parser.parseLengthCodedNumber();
11-
12-
if (parser.reachedPacketEnd()) return;
13-
14-
this.extra = (this.fieldCount === null)
15-
? parser.parsePacketTerminatedString()
16-
: parser.parseLengthCodedNumber();
1710
};
1811

1912
ResultSetHeaderPacket.prototype.write = function(writer) {
2013
writer.writeLengthCodedNumber(this.fieldCount);
21-
22-
if (this.extra !== undefined) {
23-
writer.writeLengthCodedNumber(this.extra);
24-
}
2514
};

lib/protocol/packets/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ exports.Field = require('./Field');
1313
exports.FieldPacket = require('./FieldPacket');
1414
exports.HandshakeInitializationPacket = require('./HandshakeInitializationPacket');
1515
exports.LocalDataFilePacket = require('./LocalDataFilePacket');
16+
exports.LocalInfileRequestPacket = require('./LocalInfileRequestPacket');
1617
exports.OkPacket = require('./OkPacket');
1718
exports.OldPasswordPacket = require('./OldPasswordPacket');
1819
exports.ResultSetHeaderPacket = require('./ResultSetHeaderPacket');

lib/protocol/sequences/Query.js

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
var Sequence = require('./Sequence');
2-
var Util = require('util');
3-
var Packets = require('../packets');
4-
var ResultSet = require('../ResultSet');
5-
var ServerStatus = require('../constants/server_status');
6-
var fs = require('fs');
7-
var Readable = require('readable-stream');
1+
var ClientConstants = require('../constants/client');
2+
var fs = require('fs');
3+
var Packets = require('../packets');
4+
var ResultSet = require('../ResultSet');
5+
var Sequence = require('./Sequence');
6+
var ServerStatus = require('../constants/server_status');
7+
var Readable = require('readable-stream');
8+
var Util = require('util');
89

910
module.exports = Query;
1011
Util.inherits(Query, Sequence);
@@ -35,6 +36,7 @@ Query.prototype.determinePacket = function determinePacket(byte, parser) {
3536
if (!resultSet) {
3637
switch (byte) {
3738
case 0x00: return Packets.OkPacket;
39+
case 0xfb: return Packets.LocalInfileRequestPacket;
3840
case 0xff: return Packets.ErrorPacket;
3941
default: return Packets.ResultSetHeaderPacket;
4042
}
@@ -90,14 +92,22 @@ Query.prototype['ErrorPacket'] = function(packet) {
9092
this.end(err, results, fields);
9193
};
9294

93-
Query.prototype['ResultSetHeaderPacket'] = function(packet) {
94-
if (packet.fieldCount === null) {
95-
this._sendLocalDataFile(packet.extra);
95+
Query.prototype['LocalInfileRequestPacket'] = function(packet) {
96+
if (this._connection.config.clientFlags & ClientConstants.CLIENT_LOCAL_FILES) {
97+
this._sendLocalDataFile(packet.filename);
9698
} else {
97-
this._resultSet = new ResultSet(packet);
99+
this._loadError = new Error('Load local files command is disabled');
100+
this._loadError.code = 'LOCAL_FILES_DISABLED';
101+
this._loadError.fatal = false;
102+
103+
this.emit('packet', new Packets.EmptyPacket());
98104
}
99105
};
100106

107+
Query.prototype['ResultSetHeaderPacket'] = function(packet) {
108+
this._resultSet = new ResultSet(packet);
109+
};
110+
101111
Query.prototype['FieldPacket'] = function(packet) {
102112
this._resultSet.fieldPackets.push(packet);
103113
};

test/FakeServer.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,8 @@ FakeConnection.prototype._handleQueryPacket = function _handleQueryPacket(packet
277277
this.error('Interrupted unknown query', Errors.ER_QUERY_INTERRUPTED);
278278
};
279279

280-
FakeConnection.prototype._parsePacket = function() {
281-
var Packet = this._determinePacket();
280+
FakeConnection.prototype._parsePacket = function _parsePacket(packetHeader) {
281+
var Packet = this._determinePacket(packetHeader);
282282
var packet = new Packet({protocol41: true});
283283

284284
packet.parse(this._parser);
@@ -338,7 +338,7 @@ FakeConnection.prototype._parsePacket = function() {
338338
}
339339
};
340340

341-
FakeConnection.prototype._determinePacket = function _determinePacket() {
341+
FakeConnection.prototype._determinePacket = function _determinePacket(packetHeader) {
342342
if (this._expectedNextPacket) {
343343
var Packet = this._expectedNextPacket;
344344

@@ -353,6 +353,10 @@ FakeConnection.prototype._determinePacket = function _determinePacket() {
353353
return Packet;
354354
}
355355

356+
if (packetHeader.length === 0) {
357+
return Packets.EmptyPacket;
358+
}
359+
356360
var firstByte = this._parser.peak();
357361
switch (firstByte) {
358362
case 0x01: return Packets.ComQuitPacket;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
var assert = require('assert');
2+
var common = require('../../common');
3+
4+
var path = common.fixtures + '/data.csv';
5+
var table = 'load_data_test';
6+
var newline = common.detectNewline(path);
7+
8+
common.getTestConnection({localInfile: false}, function (err, connection) {
9+
assert.ifError(err);
10+
11+
common.useTestDb(connection);
12+
13+
connection.query([
14+
'CREATE TEMPORARY TABLE ?? (',
15+
'`id` int(11) unsigned NOT NULL AUTO_INCREMENT,',
16+
'`title` varchar(400),',
17+
'PRIMARY KEY (`id`)',
18+
') ENGINE=InnoDB DEFAULT CHARSET=utf8'
19+
].join('\n'), [table], assert.ifError);
20+
21+
var sql =
22+
'LOAD DATA LOCAL INFILE ? INTO TABLE ?? CHARACTER SET utf8 ' +
23+
'FIELDS TERMINATED BY ? ' +
24+
'LINES TERMINATED BY ? ' +
25+
'(id, title)';
26+
27+
connection.query(sql, [path, table, ',', newline], function (err) {
28+
assert.ok(err);
29+
assert.equal(err.code, 'ER_NOT_ALLOWED_COMMAND');
30+
});
31+
32+
connection.query('SELECT * FROM ??', [table], function (err, rows) {
33+
assert.ifError(err);
34+
assert.equal(rows.length, 0);
35+
});
36+
37+
connection.end(assert.ifError);
38+
});

test/integration/connection/test-load-data-infile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ common.getTestConnection(function (err, connection) {
4242
assert.equal(rows[4].title, 'this is a long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long long string');
4343
});
4444

45-
connection.query(sql, [badPath, table, ',', newline], function (err) {
45+
connection.query(sql, [badPath, table, ',', newline], function (err, result) {
4646
assert.ok(err);
4747
assert.equal(err.code, 'ENOENT');
48+
assert.equal(result.affectedRows, 0);
4849
});
4950

5051
connection.end(assert.ifError);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
var assert = require('assert');
2+
var common = require('../../common');
3+
var connection = common.createConnection({port: common.fakeServerPort, localInfile: false});
4+
var server = common.createFakeServer();
5+
6+
server.listen(common.fakeServerPort, function (err) {
7+
assert.ifError(err);
8+
9+
connection.query('LOAD DATA LOCAL INFILE ? INTO TABLE ??', ['data.csv', 'foo'], function (err, result) {
10+
assert.ok(err);
11+
assert.equal(err.code, 'LOCAL_FILES_DISABLED');
12+
assert.ok(!err.fatal);
13+
assert.equal(result.affectedRows, 0);
14+
15+
connection.destroy();
16+
server.destroy();
17+
});
18+
});
19+
20+
server.on('connection', function(conn) {
21+
conn.on('clientAuthentication', function (packet) {
22+
if (packet.clientFlags & common.ClientConstants.LOCAL_FILES) {
23+
conn.deny();
24+
} else {
25+
conn.ok();
26+
}
27+
});
28+
conn.on('query', function (packet) {
29+
if (packet.sql.indexOf('LOAD DATA LOCAL INFILE') === 0) {
30+
conn.once('EmptyPacket', function () {
31+
conn.ok();
32+
});
33+
this._sendPacket(new common.Packets.LocalInfileRequestPacket({
34+
filename: common.fixtures + '/data.csv'
35+
}));
36+
} else {
37+
this._handleQueryPacket(packet);
38+
}
39+
});
40+
conn.handshake();
41+
});

0 commit comments

Comments
 (0)