config: use stricter arg and config file parsing.

This commit is contained in:
Christopher Jeffrey 2017-08-09 15:28:52 -07:00
parent b892aeab2f
commit 78dfe005c7
No known key found for this signature in database
GPG Key ID: 8962AB9DE6666BBD

View File

@ -135,7 +135,8 @@ Config.prototype.set = function set(key, value) {
if (value == null) if (value == null)
return; return;
key = key.toLowerCase().replace(/-/g, ''); key = key.replace(/-/g, '');
key = key.toLowerCase();
this.options[key] = value; this.options[key] = value;
}; };
@ -156,7 +157,8 @@ Config.prototype.has = function has(key) {
assert(typeof key === 'string', 'Key must be a string.'); assert(typeof key === 'string', 'Key must be a string.');
key = key.toLowerCase().replace(/-/g, ''); key = key.replace(/-/g, '');
key = key.toLowerCase();
if (this.hash[key] != null) if (this.hash[key] != null)
return true; return true;
@ -214,7 +216,8 @@ Config.prototype.get = function get(key, fallback) {
assert(typeof key === 'string', 'Key must be a string.'); assert(typeof key === 'string', 'Key must be a string.');
key = key.toLowerCase().replace(/-/g, ''); key = key.replace(/-/g, '');
key = key.toLowerCase();
if (this.hash[key] != null) if (this.hash[key] != null)
return this.hash[key]; return this.hash[key];
@ -735,47 +738,69 @@ Config.prototype.location = function location(file) {
Config.prototype.parseConfig = function parseConfig(text) { Config.prototype.parseConfig = function parseConfig(text) {
assert(typeof text === 'string', 'Config must be text.'); assert(typeof text === 'string', 'Config must be text.');
text = text.trim(); if (text.charCodeAt(0) === 0xfeff)
text = text.substring(1);
const parts = text.split(/\n+/); text = text.replace(/\r\n/g, '\n');
text = text.replace(/\r/g, '\n');
text = text.replace(/\\\n/g, '');
for (let line of parts) { let colons = true;
line = line.trim(); let seen = false;
let num = 0;
for (const chunk of text.split('\n')) {
const line = chunk.trim();
num += 1;
if (line.length === 0) if (line.length === 0)
continue; continue;
if (/^\s*#/.test(line)) if (line[0] === '#')
continue; continue;
let eq = line.indexOf('='); const equal = line.indexOf('=');
const col = line.indexOf(':'); const colon = line.indexOf(':');
if (col !== -1 && (col < eq || eq === -1)) let index = -1;
eq = col;
let key, value; if (colon !== -1 && (colon < equal || equal === -1)) {
if (eq === -1) { if (seen && !colons)
key = line.trim(); throw new Error(`Expected \`=\` on line ${num}: "${line}".`);
value = '';
index = colon;
seen = true;
colons = true;
} else if (equal !== -1) {
if (seen && colons)
throw new Error(`Expected \`:\` on line ${num}: "${line}".`);
index = equal;
seen = true;
colons = false;
} else { } else {
key = line.substring(0, eq).trim(); const symbol = colons ? ':' : '=';
value = line.substring(eq + 1).trim(); throw new Error(`Expected \`${symbol}\` on line ${num}: "${line}".`);
} }
key = key.replace(/\-/g, '').toLowerCase(); let key = line.substring(0, index).trim();
key = key.replace(/\-/g, '')
if (!isLowerKey(key))
throw new Error(`Invalid option on line ${num}: ${key}.`);
const value = line.substring(index + 1).trim();
if (value.length === 0)
continue;
const alias = Config.alias[key]; const alias = Config.alias[key];
if (alias) if (alias)
key = alias; key = alias;
if (key.length === 0)
continue;
if (value.length === 0)
continue;
this.data[key] = value; this.data[key] = value;
} }
}; };
@ -816,19 +841,21 @@ Config.prototype.parseArg = function parseArg(argv) {
} }
if (arg.indexOf('--') === 0) { if (arg.indexOf('--') === 0) {
// e.g. --opt const index = arg.indexOf('=');
const parts = arg.split('=');
let key = parts[0]; let key = null;
let value = null; let value = null;
let empty = false; let empty = false;
if (parts.length > 1) { if (index !== -1) {
// e.g. --opt=val // e.g. --opt=val
value = parts.slice(1).join('=').trim(); key = arg.substring(2, index);
value = arg.substring(index + 1);
last = null; last = null;
empty = false; empty = false;
} else { } else {
// e.g. --opt
key = arg.substring(2);
value = 'true'; value = 'true';
last = null; last = null;
empty = true; empty = true;
@ -836,8 +863,8 @@ Config.prototype.parseArg = function parseArg(argv) {
key = key.replace(/\-/g, ''); key = key.replace(/\-/g, '');
if (key.length === 0) if (!isLowerKey(key))
continue; throw new Error(`Invalid argument: --${key}.`);
if (value.length === 0) if (value.length === 0)
continue; continue;
@ -860,19 +887,32 @@ Config.prototype.parseArg = function parseArg(argv) {
if (arg[0] === '-') { if (arg[0] === '-') {
// e.g. -abc // e.g. -abc
last = null; last = null;
for (let j = 1; j < arg.length; j++) { for (let j = 1; j < arg.length; j++) {
let key = arg[j]; let key = arg[j];
if ((key < 'a' || key > 'z')
&& (key < 'A' || key > 'Z')
&& (key < '0' || key > '9')
&& key !== '?') {
throw new Error(`Invalid argument: -${key}.`);
}
const alias = Config.alias[key]; const alias = Config.alias[key];
if (alias) if (alias)
key = alias; key = alias;
this.args[key] = 'true'; this.args[key] = 'true';
last = key; last = key;
} }
continue; continue;
} }
// e.g. foo // e.g. foo
const value = arg.trim(); const value = arg;
if (value.length === 0) { if (value.length === 0) {
last = null; last = null;
@ -908,24 +948,24 @@ Config.prototype.parseEnv = function parseEnv(env) {
assert(env && typeof env === 'object'); assert(env && typeof env === 'object');
for (let key of Object.keys(env)) { for (let key of Object.keys(env)) {
let value = env[key]; const value = env[key];
assert(typeof value === 'string'); assert(typeof value === 'string');
if (key.indexOf(prefix) !== 0) if (!util.startsWith(key, prefix))
continue; continue;
key = key.substring(prefix.length); key = key.substring(prefix.length);
key = key.replace(/_/g, '').toLowerCase(); key = key.replace(/_/g, '');
if (key.length === 0) if (!isUpperKey(key))
continue; continue;
value = value.trim();
if (value.length === 0) if (value.length === 0)
continue; continue;
key = key.toLowerCase();
// Do not allow one-letter aliases. // Do not allow one-letter aliases.
if (key.length > 1) { if (key.length > 1) {
const alias = Config.alias[key]; const alias = Config.alias[key];
@ -989,27 +1029,30 @@ Config.prototype.parseForm = function parseForm(query, map) {
if (query.length === 0) if (query.length === 0)
return; return;
if (query[0] === '?' || query[0] === '#') let ch = '?';
if (map === this.hash)
ch = '#';
if (query[0] === ch)
query = query.substring(1); query = query.substring(1);
const parts = query.split('&'); for (const pair of query.split('&')) {
for (const pair of parts) {
const index = pair.indexOf('='); const index = pair.indexOf('=');
let key, value; let key, value;
if (index === -1) { if (index !== -1) {
key = pair;
value = '';
} else {
key = pair.substring(0, index); key = pair.substring(0, index);
value = pair.substring(index + 1); value = pair.substring(index + 1);
} else {
key = pair;
value = 'true';
} }
key = unescape(key); key = unescape(key);
key = key.replace(/\-/g, '').toLowerCase(); key = key.replace(/\-/g, '');
if (key.length === 0) if (!isLowerKey(key))
continue; continue;
value = unescape(value); value = unescape(value);
@ -1055,6 +1098,24 @@ function isAlpha(str) {
return /^[a-z0-9]+$/.test(str); return /^[a-z0-9]+$/.test(str);
} }
function isKey(key) {
return /^[a-zA-Z0-9]+$/.test(key);
}
function isLowerKey(key) {
if (!isKey(key))
return false;
return !/[A-Z]/.test(key);
}
function isUpperKey(key) {
if (!isKey(key))
return false;
return !/[a-z]/.test(key);
}
/* /*
* Expose * Expose
*/ */