Skip to content

Commit

Permalink
Basic support for single and multi-line comments. Full testing code-c…
Browse files Browse the repository at this point in the history
…overage.
  • Loading branch information
Undre4m committed May 12, 2018
1 parent 9edddaa commit 9622f8c
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 10 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## Changelog

### 1.2.0 ~ _08 Mar 2018_
### 1.3.0 ~ _12 May 2018_
- Basic support for single and multi-line comments
- Extended support for latin script unicode characters

#### 1.2.0 ~ _08 Mar 2018_
- Support for local variables and parameters

#### 1.1.0 ~ _26 Feb 2018_
Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Igniculus
SQL Syntax Highlighter and Logger. Unadorned and customizable.

[![version](https://img.shields.io/npm/v/igniculus.svg?style=flat-square)](https://www.npmjs.com/package/igniculus)
[![build status](https://img.shields.io/travis/Undre4m/igniculus.svg?style=flat-square)](https://travis-ci.org/Undre4m/igniculus)
[![node](https://img.shields.io/node/v/igniculus.svg?style=flat-square)](https://nodejs.org/en/download/releases)
[![downloads](https://img.shields.io/npm/dt/igniculus.svg?style=flat-square)](https://www.npmjs.com/package/igniculus)
[![license](https://img.shields.io/npm/l/igniculus.svg?style=flat-square)](https://github.com/Undre4m/igniculus/blob/master/LICENSE)
[![version](https://img.shields.io/npm/v/igniculus.svg)](https://www.npmjs.com/package/igniculus)
[![license](https://img.shields.io/npm/l/igniculus.svg)](https://github.com/Undre4m/igniculus/blob/master/LICENSE)
[![downloads](https://img.shields.io/npm/dt/igniculus.svg?colorB=ffdf00)](https://www.npmjs.com/package/igniculus)
[![build status](https://img.shields.io/travis/Undre4m/igniculus.svg?logo=travis&logoWidth=15)](https://travis-ci.org/Undre4m/igniculus)
[![maintainability](https://api.codeclimate.com/v1/badges/30482d982a79b253aed9/maintainability)](https://codeclimate.com/github/Undre4m/igniculus/maintainability)
[![test Coverage](https://api.codeclimate.com/v1/badges/30482d982a79b253aed9/test_coverage)](https://codeclimate.com/github/Undre4m/igniculus/test_coverage)

## Install

Expand Down Expand Up @@ -86,6 +87,7 @@ The _options_ argument is optional and each property should be one of the follow

### Rules

- options.**comments** - Single and multi-line comments. _E.g:_ `-- comments` or `/* Author: undre4m */`
- options.**constants** - Values surrounded by single quotes. _E.g:_ `'static'`
- options.**numbers** - Numeric values. _E.g:_ `2.5`
- options.**operators** - Arithmetic, Bitwise and Comparison operators. _E.g:_ `+` or `>=`
Expand Down Expand Up @@ -169,6 +171,7 @@ These can be one of the following.
```js
/* Predifined style */
const defaults = {
comments: { mode: 'dim', fg: 'white' },
constants: { mode: 'dim', fg: 'red' },
delimitedIdentifiers: { mode: 'dim', fg: 'yellow' },
variables: { mode: 'dim', fg: 'magenta' },
Expand Down
27 changes: 27 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ const ANSIColours = {
};

const defaults = {
comments: { mode: 'dim', fg: 'white' },
constants: { mode: 'dim', fg: 'red' },
delimitedIdentifiers: { mode: 'dim', fg: 'yellow' },
variables: { mode: 'dim', fg: 'magenta' },
Expand Down Expand Up @@ -124,6 +125,12 @@ function illumine(text) {
output = output.replace(/(\B@[@#$_\w\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u00ff\u0100-\u017f\u0180-\u024f]*)/g, '↪※↩');
}

// Extract comment sections so no subsequent operations alter them. Mark their positions for reinsertion.
let __comments = output.match(/(-{2}.*)|(\/\*(.|[\r\n])*?\*\/)/g);
if (__comments && __comments.length) {
output = output.replace(/(-{2}.*)|(\/\*(.|[\r\n])*?\*\/)/g, '⥤※⥢');
}

if (runestone.dataTypes && runestone.dataTypes.sequence) {
let regex = new RegExp('\\b' + '(' + dataTypes.join('|') + ')' + '\\b' + '(?![\'"\\]])', 'gi');
output = output.replace(regex, (match, g1) => {
Expand Down Expand Up @@ -171,6 +178,17 @@ function illumine(text) {
output = output.replace(/(\+|-|\*|\/|%|&|\||\^|=|>|<)+/g, runestone.operators.sequence + '$&' + ANSIModes.reset);
}

// If comment sections were found and extracted, reinsert them on the marked positions and cordon off the area for reference.
if (__comments && __comments.length) {
for (let i of __comments) {
// If comment sections were to be formatted, apply the provided style.
if (runestone.comments && runestone.comments.sequence)
output = output.replace('⥤※⥢', runestone.comments.sequence + 'c†s' + i + 'c‡e' + ANSIModes.reset);
else
output = output.replace('⥤※⥢', 'c†s' + i + 'c‡e');
}
}

// If local variables were found and extracted, reinsert them on the marked positions.
if (__variables && __variables.length) {
for (let i of __variables) {
Expand Down Expand Up @@ -209,6 +227,11 @@ function illumine(text) {
return voidFormatting(match);
});

// Comment sections are to be formatted as a whole and no other format should exist inside them. Void any that could have been applied and remove cordon.
output = output.replace(/(cs)((-{2}.*)|(\/\*(.|[\r\n])*?\*\/))(ce)/g, (match, p1, p2) => {
return voidFormatting(p2);
});

// If the given prefix was found and a replacement pattern was provided, substitute it.
if (__prefix && runestone.prefix.text) {
output = __prefix + output;
Expand Down Expand Up @@ -238,6 +261,10 @@ function igniculus(options) {
*/
runestone = options || defaults;

if (runestone.comments) {
runestone.comments.sequence = forgeANSISequence(runestone.comments);
}

if (runestone.constants) {
runestone.constants.sequence = forgeANSISequence(runestone.constants);
}
Expand Down
191 changes: 187 additions & 4 deletions test/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ const statement_g = dedent`declare @@ char(2) = 'B0'
from [ARCs]
where domain=@# and exposure<=@0 and g@ is null`;

const statement_i = dedent`declare @Æőn char(2) = 'ƀ2'
const statement_h = dedent`declare @Æőn char(2) = 'ƀ2'
declare @nìma_ünûlak char(36) = '1147D009-466B-4B50-BCA0-DFF1F1573899'
declare @ŴāłŤż@Đėśiğnator varchar(32) = '@ÕçÝ'
declare @Ø decimal(3,3) = 0.901
Expand All @@ -128,19 +128,189 @@ const statement_i = dedent`declare @Æőn char(2) = 'ƀ2'
from "ÁRCs"
where [@nimüid_2] = @nìma_ünûlak and domain=@ŴāłŤż@Đėśiğnator and exposure>=@Ø and [ƶ@]>@ĶValue`;

const statement_i = dedent`/*
THE SCRIPT - BUILDING A QUERY WITHOUT SANITIZING DATA
$sql = "SELECT first_name, last_name, email FROM members WHERE username='".$value."' AND showpublic=1"
*/
/*
ATTACKER INPUT USING LINE COMMENTING
admin' --
*/
-- QUERY GENERATED --
SELECT first_name, last_name, email
FROM members
WHERE username='admin' -- '
AND showpublic=1`;

const statement_j = dedent`CREATE OR ALTER VIEW "---"."vw_A/*" AS
SELECT [*/], [--], [-]
FROM [/*d*/v].[dbo].[/*]
UNION ALL
SELECT U.[*/], TODATETIMEOFFSET(C.[--], '-03:00'),
CASE
WHEN C.[-] COLLATE DATABASE_DEFAULT IN ('O','o') THEN '--E'
WHEN C.[-] COLLATE DATABASE_DEFAULT IN ('I','i') THEN '--I'
WHEN ISNUMERIC(C.[-]) = 1 THEN
CASE C.[-]
WHEN 0 THEN '/*E*/'
WHEN 1 THEN '/*I*/'
END
END
FROM [téngtòng].[dbo].[/*CHECK--] C
INNER JOIN [téngtòng].[dbo].[--USER*/] U ON -C.[USERID] = -U.[USERID]`;

test('object input', t => {
const options = {
comments: { mode: 'dim', fg: 'white' },
constants: { mode: 'dim', fg: 'red' },
delimitedIdentifiers: { mode: 'dim', fg: 'yellow' },
variables: { mode: 'dim', fg: 'magenta' },
dataTypes: { mode: 'dim', fg: 'green', casing: 'uppercase' },
standardKeywords: { mode: 'dim', fg: 'cyan', casing: 'uppercase' },
lesserKeywords: { mode: 'bold', fg: 'black', casing: 'uppercase' },
prefix: { replace: /.*?: / }
};

const input = {
toString: () => '/* GENERATED */ ' + statement_b
};

const print = igniculus(Object.assign(options, echo));

const output = print(input);
const expected = `${m.dim}${c.fg.white}/* GENERATED */${m.reset} ${m.dim}${c.fg.cyan}SELECT${m.reset} CURRENT_TIMESTAMP`;

t.is(output, expected);
});

test('null input', t => {
const print = igniculus();

const output = print(null);

t.is(output, undefined);
});

test('custom output', t => {
const options = {
output: out => out.split('').reduce((a, c) => a += c.charCodeAt(0), 0)
};

const print = igniculus(Object.assign(options));
const print = igniculus(options);

const output = print(statement_a);
const expected = 36876;

t.is(output, expected);
});

test('comments', t => {
const options = {
comments: { mode: 'bold', fg: 'black' },
constants: { fg: 'red' },
dataTypes: { fg: 'blue' },
standardKeywords: { fg: 'blue' },
lesserKeywords: { fg: 'blue' }
};

const print = igniculus(Object.assign(options, echo));

const output = print(statement_i);
const expected =
dedent`${m.bold}${c.fg.black}/*
THE SCRIPT - BUILDING A QUERY WITHOUT SANITIZING DATA
$sql = "SELECT first_name, last_name, email FROM members WHERE username='".$value."' AND showpublic=1"
*/${m.reset}
${m.bold}${c.fg.black}/*
ATTACKER INPUT USING LINE COMMENTING
admin' --
*/${m.reset}
${m.bold}${c.fg.black}-- QUERY GENERATED --${m.reset}
${c.fg.blue}SELECT${m.reset} first_name, last_name, email
${c.fg.blue}FROM${m.reset} members
${c.fg.blue}WHERE${m.reset} username=${c.fg.red}'admin'${m.reset} ${m.bold}${c.fg.black}-- '${m.reset}
${c.fg.blue}AND${m.reset} showpublic=1`;

t.is(output, expected);
});

test('not comments', t => {
const options = {
comments: { mode: 'bold', fg: 'black' },
constants: { fg: 'red' },
dataTypes: { fg: 'blue' },
standardKeywords: { fg: 'blue' },
lesserKeywords: { fg: 'blue' }
};

const print = igniculus(Object.assign(options, echo));

const output = print(statement_j);
const expected =
dedent`${c.fg.blue}CREATE${m.reset} ${c.fg.blue}OR${m.reset} ${c.fg.blue}ALTER${m.reset} ${c.fg.blue}VIEW${m.reset} "---"."vw_A/*" ${c.fg.blue}AS${m.reset}
${c.fg.blue}SELECT${m.reset} [*/], [--], [-]
${c.fg.blue}FROM${m.reset} [/*d*/v].[dbo].[/*]
${c.fg.blue}UNION${m.reset} ${c.fg.blue}ALL${m.reset}
${c.fg.blue}SELECT${m.reset} U.[*/], TODATETIMEOFFSET(C.[--], ${c.fg.red}'-03:00'${m.reset}),
${c.fg.blue}CASE${m.reset}
${c.fg.blue}WHEN${m.reset} C.[-] ${c.fg.blue}COLLATE${m.reset} DATABASE_DEFAULT ${c.fg.blue}IN${m.reset} (${c.fg.red}'O'${m.reset},${c.fg.red}'o'${m.reset}) ${c.fg.blue}THEN${m.reset} ${c.fg.red}'--E'${m.reset}
${c.fg.blue}WHEN${m.reset} C.[-] ${c.fg.blue}COLLATE${m.reset} DATABASE_DEFAULT ${c.fg.blue}IN${m.reset} (${c.fg.red}'I'${m.reset},${c.fg.red}'i'${m.reset}) ${c.fg.blue}THEN${m.reset} ${c.fg.red}'--I'${m.reset}
${c.fg.blue}WHEN${m.reset} ISNUMERIC(C.[-]) = 1 ${c.fg.blue}THEN${m.reset}
${c.fg.blue}CASE${m.reset} C.[-]
${c.fg.blue}WHEN${m.reset} 0 ${c.fg.blue}THEN${m.reset} ${c.fg.red}'/*E*/'${m.reset}
${c.fg.blue}WHEN${m.reset} 1 ${c.fg.blue}THEN${m.reset} ${c.fg.red}'/*I*/'${m.reset}
${c.fg.blue}END${m.reset}
${c.fg.blue}END${m.reset}
${c.fg.blue}FROM${m.reset} [téngtòng].[dbo].[/*CHECK--] C
${c.fg.blue}INNER${m.reset} ${c.fg.blue}JOIN${m.reset} [téngtòng].[dbo].[--USER*/] U ${c.fg.blue}ON${m.reset} -C.[USERID] = -U.[USERID]`;

t.is(output, expected);
});

test('no comment style', t => {
const options = {
numbers: { mode: 'italic' },
constants: { fg: 'yellow' },
delimitedIdentifiers: { bg: 'yellow', fg: 'black' },
operators: { fg: 'red' },
dataTypes: { fg: 'blue' },
standardKeywords: { fg: 'blue' },
lesserKeywords: { fg: 'blue' }
};

const print = igniculus(Object.assign(options, echo));

const output = print(statement_i);
const expected =
dedent`/*
THE SCRIPT - BUILDING A QUERY WITHOUT SANITIZING DATA
$sql = "SELECT first_name, last_name, email FROM members WHERE username='".$value."' AND showpublic=1"
*/
/*
ATTACKER INPUT USING LINE COMMENTING
admin' --
*/
-- QUERY GENERATED --
${c.fg.blue}SELECT${m.reset} first_name, last_name, email
${c.fg.blue}FROM${m.reset} members
${c.fg.blue}WHERE${m.reset} username${c.fg.red}=${m.reset}${c.fg.yellow}'admin'${m.reset} -- '
${c.fg.blue}AND${m.reset} showpublic${c.fg.red}=${m.reset}${m.italic}1${m.reset}`;

t.is(output, expected);
});

test('constants', t => {
const options = {
constants: { fg: 'red' }
Expand Down Expand Up @@ -621,7 +791,7 @@ test('extended latin variables, constants and identifiers', t => {

const print = igniculus(Object.assign(options, echo));

const output = print(statement_i);
const output = print(statement_h);
const expected =
dedent`${c.fg.yellow}declare${m.reset} ${c.fg.red}@Æőn${m.reset} char(${m.italic}2${m.reset}) = ${m.dim}${c.fg.white}'ƀ2'${m.reset}
${c.fg.yellow}declare${m.reset} ${c.fg.red}@nìma_ünûlak${m.reset} char(${m.italic}36${m.reset}) = ${m.dim}${c.fg.white}'1147D009-466B-4B50-BCA0-DFF1F1573899'${m.reset}
Expand All @@ -636,7 +806,20 @@ test('extended latin variables, constants and identifiers', t => {
t.is(output, expected);
});

test('prefix replace', t => {
test('prefix string replace', t => {
const options = {
prefix: { fg: 'red', replace: 'SELECT CURRENT' }
};

const print = igniculus(Object.assign(options, echo));

const output = print(statement_b);
const expected = '_TIMESTAMP';

t.is(output, expected);
});

test('prefix regexp replace', t => {
const options = {
constants: { fg: 'red' },
dataTypes: { fg: 'magenta' },
Expand Down
2 changes: 2 additions & 0 deletions test/styles.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ test('background colors', t => {

test('default style', t => {
const options = {
comments: { mode: 'dim', fg: 'white' },
constants: { mode: 'dim', fg: 'red' },
delimitedIdentifiers: { mode: 'dim', fg: 'yellow' },
variables: { mode: 'dim', fg: 'magenta' },
Expand Down Expand Up @@ -187,6 +188,7 @@ test('default style', t => {

test('default style 2', t => {
const options = {
comments: { mode: 'dim', fg: 'white' },
constants: { mode: 'dim', fg: 'red' },
delimitedIdentifiers: { mode: 'dim', fg: 'yellow' },
variables: { mode: 'dim', fg: 'magenta' },
Expand Down

0 comments on commit 9622f8c

Please sign in to comment.