Skip to content

Commit

Permalink
fix: prevent sql injection #486 (#506)
Browse files Browse the repository at this point in the history
* fix: prevent sql injection #486

* refactor: grammar escape code

* fix: regex escape

* fix: eslint config

* chore: eslint fix

* fix: tring to fix regex

* fix: better test sql injection

* fix: possible solution to injection

* chore: update package-lock.json

Co-authored-by: Ben Evans <ben@bluechimp.io>
  • Loading branch information
robertsLando and bencevans committed Jul 22, 2020
1 parent a5538f8 commit dcc2e7e
Show file tree
Hide file tree
Showing 14 changed files with 4,453 additions and 1,783 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -35,3 +35,5 @@ npm-debug.log

/doc
/lib

.vscode
6,096 changes: 4,386 additions & 1,710 deletions package-lock.json

Large diffs are not rendered by default.

29 changes: 14 additions & 15 deletions package.json
Expand Up @@ -9,6 +9,7 @@
"build:doc": "npm run clean && tsc -m es2015 -t es6 --moduleResolution node && esdoc -c esdoc.json",
"clean": "rm -rf coverage doc lib",
"prepare": "npm run clean && tsc -d",
"lint-fix": "eslint . --ext .ts --fix",
"fmt": "prettier --single-quote --trailing-comma all --print-width 100 --write \"{src,test,examples}/**/*.ts\" && npm run test:lint -- --fix",
"test": "npm-run-all --parallel test:lint test:unit test:integrate",
"test:browser": "karma start test/karma.conf.js",
Expand Down Expand Up @@ -40,27 +41,25 @@
],
"license": "MIT",
"devDependencies": {
"@types/chai": "4.2.0",
"@types/chai": "4.2.11",
"@types/freeport": "1.0.21",
"@types/mocha": "5.2.7",
"@types/node": "12.7.1",
"@types/sinon": "7.0.13",
"@types/sinon-chai": "3.2.3",
"@typescript-eslint/eslint-plugin": "1.13.0",
"@typescript-eslint/parser": "1.13.0",
"@types/mocha": "8.0.0",
"@types/node": "14.0.23",
"@types/sinon": "9.0.4",
"@types/sinon-chai": "3.2.4",
"awesome-typescript-loader": "5.2.1",
"chai": "4.2.0",
"coveralls": "3.0.6",
"coveralls": "3.1.0",
"esdoc": "1.1.0",
"esdoc-standard-plugin": "1.0.0",
"freeport": "1.0.5",
"istanbul": "0.4.5",
"json-loader": "0.5.7",
"karma": "4.2.0",
"karma-chrome-launcher": "3.0.0",
"karma-mocha": "1.3.0",
"karma": "5.1.0",
"karma-chrome-launcher": "3.1.0",
"karma-mocha": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-sauce-launcher": "2.0.2",
"karma-sauce-launcher": "4.1.5",
"karma-sourcemap-loader": "0.3.7",
"karma-webpack": "4.0.2",
"lodash": "4.17.19",
Expand All @@ -73,8 +72,8 @@
"sinon": "7.4.1",
"sinon-chai": "3.3.0",
"stream-http": "github:node-influx/stream-http",
"ts-node": "8.3.0",
"typescript": "3.5.3",
"webpack": "4.39.2"
"ts-node": "8.10.2",
"typescript": "3.9.6",
"webpack": "4.43.0"
}
}
6 changes: 3 additions & 3 deletions src/backoff/backoff.ts
Expand Up @@ -2,16 +2,16 @@ export interface IBackoffStrategy {
/**
* GetDelay returns the amount of delay of the current backoff.
*/
getDelay(): number;
getDelay: () => number;

/**
* Next is called when a failure occurs on a host to
* return the next backoff amount.
*/
next(): IBackoffStrategy;
next: () => IBackoffStrategy;

/**
* Returns a strategy with a reset backoff counter.
*/
reset(): IBackoffStrategy;
reset: () => IBackoffStrategy;
}
2 changes: 1 addition & 1 deletion src/backoff/exponential.ts
Expand Up @@ -48,7 +48,7 @@ export class ExponentialBackoff implements IBackoffStrategy {
this._counter - Math.round(Math.random() * this.options.random); // Tslint:disable-line
return Math.min(
this.options.max,
this.options.initial * Math.pow(2, Math.max(count, 0))
this.options.initial * 2 ** Math.max(count, 0)
);
}

Expand Down
10 changes: 5 additions & 5 deletions src/builder.ts
@@ -1,26 +1,26 @@
import { escape, formatDate } from "./grammar";

export interface IStringable {
toString(): string;
toString: () => string;
}

export interface IBaseExpression<T> {
/**
* Inserts a tag name in the expression.
*/
tag(name: string): T;
tag: (name: string) => T;

/**
* Inserts a field name in the expression.
*/
field(name: string): T;
field: (name: string) => T;

/**
* Chains on a value to the expression. An error will be thrown if the
* value is a type we can't represent in InfluxQL, primarily `null` or
* `undefined.`
*/
value(value: any): T;
value: (value: any) => T;
}

export interface IExpressionHead extends IBaseExpression<IBinaryOp> {}
Expand Down Expand Up @@ -128,7 +128,7 @@ function regexHasFlags(re: RegExp): boolean {
* // ("county" = 'US' AND "state" = 'WA')
*/
export class Expression implements IExpressionHead, IExpressionTail, IBinaryOp {
private _query: string[] = [];
private readonly _query: string[] = [];

/**
* Inserts a tag reference into the expression; the name will be
Expand Down
2 changes: 1 addition & 1 deletion src/grammar/ds.ts
Expand Up @@ -39,7 +39,7 @@ export class Raw {
* @example
* influx.createDatabase(new Influx.Raw('This won\'t be escaped!'));
*/
constructor(private value: string) {}
constructor(private readonly value: string) {}

/**
* Returns the wrapped string.
Expand Down
13 changes: 8 additions & 5 deletions src/grammar/escape.ts
Expand Up @@ -38,8 +38,8 @@ class Escaper {

constructor(
chars: string[],
private wrap: string = "",
private escaper: string = "\\"
private readonly wrap: string = "",
private readonly escaper: string = "\\"
) {
const patterns = chars.join("").replace(reEscape, "\\$&");
this._re = new RegExp("[" + patterns + "]", "g");
Expand All @@ -57,13 +57,16 @@ class Escaper {
this._re.lastIndex = 0;
let chunkIndex = this._re.lastIndex;
let escapedVal = "";
let match = this._re.exec(val);

while (match) {
let match;

val = val.replace(/\\'/, "\\\\'");

/* eslint-disable no-cond-assign */
while ((match = this._re.exec(val))) {
escapedVal +=
val.slice(chunkIndex, match.index) + this.escaper + match[0];
chunkIndex = this._re.lastIndex;
match = this._re.exec(val);
}

if (chunkIndex === 0) {
Expand Down
28 changes: 10 additions & 18 deletions src/grammar/times.ts
Expand Up @@ -31,11 +31,7 @@ import { isNumeric } from "./ds";
* Influx timestamp = 'Influx time', abbreviated as 'Influx'
*/

function leftPad(
str: number | string,
length: number,
pad: string = "0"
): string {
function leftPad(str: number | string, length: number, pad = "0"): string {
if (typeof str === "number") {
str = String(str);
}
Expand All @@ -47,11 +43,7 @@ function leftPad(
return str;
}

function rightPad(
str: number | string,
length: number,
pad: string = "0"
): string {
function rightPad(str: number | string, length: number, pad = "0"): string {
if (typeof str === "number") {
str = String(str);
}
Expand All @@ -67,12 +59,12 @@ export interface INanoDate extends Date {
/**
* Returns the unix nanoseconds timestamp as a string.
*/
getNanoTime(): string;
getNanoTime: () => string;

/**
* Formats the date as an ISO RFC3339 timestamp with nanosecond precision.
*/
toNanoISOString(): string;
toNanoISOString: () => string;
}

export type TimePrecision = "n" | "u" | "ms" | "s" | "m" | "h";
Expand Down Expand Up @@ -108,23 +100,23 @@ interface IDateManipulator<T> {
/**
* FormatDate converts the Date instance to Influx's date query format.
*/
format(date: T): string;
format: (date: T) => string;

/**
* Converts a Date instance to a numeric unix
* timestamp with the specified time precision.
*/
toTime(date: T, precision: TimePrecision): string;
toTime: (date: T, precision: TimePrecision) => string;

/**
* Converts an ISO timestamp to a Date instance.
*/
isoToDate(timestamp: string): T;
isoToDate: (timestamp: string) => T;

/**
* Converts a numeric timestamp with the specified precision to a date.
*/
timetoDate(timestamp: number, precision: TimePrecision): T;
timetoDate: (timestamp: number, precision: TimePrecision) => T;
}

class MillisecondDateManipulator implements IDateManipulator<Date> {
Expand Down Expand Up @@ -200,8 +192,8 @@ class MillisecondDateManipulator implements IDateManipulator<Date> {
}

const nsPer = {
ms: Math.pow(10, 6),
s: Math.pow(10, 9),
ms: 10 ** 6,
s: 10 ** 9,
};

function nanoIsoToTime(iso: string): string {
Expand Down
13 changes: 6 additions & 7 deletions src/index.ts
@@ -1,5 +1,4 @@
/* eslint-disable @typescript-eslint/unified-signatures */
/* eslint-disable no-dupe-class-members */
/* eslint-disable no-prototype-builtins */

import { RequestOptions } from "https";
Expand All @@ -15,7 +14,7 @@ const defaultHost: IHostConfig = Object.freeze({
host: "127.0.0.1",
port: 8086,
path: "",
protocol: "http" as "http",
protocol: "http" as const,
});

const defaultOptions: IClusterConfig = Object.freeze({
Expand Down Expand Up @@ -310,13 +309,13 @@ export class InfluxDB {
* Connect pool for making requests.
* @private
*/
private _pool: Pool;
private readonly _pool: Pool;

/**
* Config options for Influx.
* @private
*/
private _options: IClusterConfig;
private readonly _options: IClusterConfig;

/**
* Map of Schema instances defining measurements in Influx.
Expand Down Expand Up @@ -698,7 +697,7 @@ export class InfluxDB {
public createUser(
username: string,
password: string,
admin: boolean = false
admin = false
): Promise<void> {
return this._pool
.json(
Expand Down Expand Up @@ -879,7 +878,7 @@ export class InfluxDB {
name: string,
query: string,
database: string = this._defaultDB(),
resample: string = ""
resample = ""
): Promise<void> {
return this._pool
.json(
Expand Down Expand Up @@ -1458,7 +1457,7 @@ export class InfluxDB {
* Creates options to be passed into the pool to query databases.
* @private
*/
private _getQueryOpts(params: any, method: string = "GET"): any {
private _getQueryOpts(params: any, method = "GET"): any {
return {
method,
path: "/query",
Expand Down
13 changes: 6 additions & 7 deletions src/pool.ts
Expand Up @@ -113,7 +113,6 @@ export class RequestError extends Error {
* through the first call of any function that it generated.
*/
function doOnce<T extends Function>(): (arg: T) => <T>(arg: T) => any {
// eslint-disable-line @typescript-eslint/ban-types
let handled = false;

return (fn) => {
Expand Down Expand Up @@ -163,15 +162,15 @@ const request = (
* host for a period of time.
*/
export class Pool {
private _options: IPoolOptions;
private readonly _options: IPoolOptions;

private _index: number;

private _timeout: number;
private readonly _timeout: number;

private _hostsAvailable: Set<Host>;
private readonly _hostsAvailable: Set<Host>;

private _hostsDisabled: Set<Host>;
private readonly _hostsDisabled: Set<Host>;

/**
* Creates a new Pool instance.
Expand Down Expand Up @@ -280,7 +279,7 @@ export class Pool {
* Ping sends out a request to all available Influx servers, reporting on
* their response time and version number.
*/
public ping(timeout: number, path: string = "/ping"): Promise<IPingStats[]> {
public ping(timeout: number, path = "/ping"): Promise<IPingStats[]> {
const todo: Array<Promise<IPingStats>> = [];

setToArray(this._hostsAvailable)
Expand Down Expand Up @@ -388,7 +387,7 @@ export class Pool {
}

if (res.statusCode >= 300) {
return RequestError.Create(req, res, (err) => callback(err, res)); // eslint-disable-line new-cap
return RequestError.Create(req, res, (err) => callback(err, res));
}

host.success();
Expand Down
4 changes: 2 additions & 2 deletions src/results.ts
Expand Up @@ -156,7 +156,7 @@ export interface IResults<T> extends Array<T> {
* ])
* })
*/
group(matcher: Tags): T[];
group: (matcher: Tags) => T[];

/**
* Returns the data grouped into nested arrays, similarly to how it was
Expand Down Expand Up @@ -186,7 +186,7 @@ export interface IResults<T> extends Array<T> {
* ])
* })
*/
groups(): Array<{ name: string; tags: Tags; rows: T[] }>;
groups: () => Array<{ name: string; tags: Tags; rows: T[] }>;
}

/**
Expand Down
7 changes: 2 additions & 5 deletions src/schema.ts
@@ -1,6 +1,3 @@
/* eslint-disable @typescript-eslint/require-array-sort-compare */
/* eslint-disable no-prototype-builtins */

import { escape, FieldType, isNumeric } from "./grammar";

export interface ISchemaOptions {
Expand Down Expand Up @@ -33,11 +30,11 @@ export type FieldMap = { [name: string]: string | number | boolean };
* @private
*/
export class Schema {
private _fieldNames: string[];
private readonly _fieldNames: string[];

private _tagHash: { [tag: string]: true } = {};

constructor(private options: ISchemaOptions) {
constructor(private readonly options: ISchemaOptions) {
// FieldNames are sorted for performance: when coerceFields is run the
// fields will be added to the output in order.
this._fieldNames = Object.keys(options.fields).sort();
Expand Down

0 comments on commit dcc2e7e

Please sign in to comment.