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