/*
This uses the following variables in window.PAGE:
db_prefix (string, or false-y to indicate no DB prefixing)
NOTE: The validation functions in here rely on HTML's "maxlength" attribute
(DOM: "maxLength") to restrict name lengths for dbs and dbusers. (The backend
API will catch it if the frontend subverts this restriction.)
*/
(function sql_js(window) {
"use strict";
var CPANEL = window.CPANEL;
var DOM = window.DOM;
var PAGE = window.PAGE;
var NAME_LENGTH_LIMIT = {
mysql: {
database: 64,
user: null,
},
postgresql: {
database: 63,
user: 63,
},
};
var ANYTHING_BUT_PRINTABLE_7_BIT_ASCII = /[^\u0020-\u007e]/;
var MYSQL_DB_NAME_WILDCARDS = /(_|%|\\)/g;
// see note on filesystem characters here:
// https://dev.mysql.com/doc/refman/5.1/en/identifiers.html
var MYSQL_STARTED_ALLOWING_FILESYS_CHARACTERS = 50116;
var MYSQL_STARTED_ALLOWING_LONG_USERNAMES = 100000;
function verify_mysql_database_name(name) {
name = (typeof name === "object") ? DOM.get(name).value : name;
_verify_name_length_limit("mysql", "database", name);
_verify_database_name_but_not_length(name);
_verify_database_name_for_mysql(name);
}
function verify_mysql_username(name) {
name = (typeof name === "object") ? DOM.get(name).value : name;
_verify_name_length_limit("mysql", "user", name);
_verify_dbuser_name_but_not_length(name);
}
function verify_postgresql_database_name(name) {
name = (typeof name === "object") ? DOM.get(name).value : name;
_verify_name_length_limit("postgresql", "database", name);
_verify_database_name_but_not_length(name);
}
function verify_postgresql_username(name) {
name = (typeof name === "object") ? DOM.get(name).value : name;
_verify_name_length_limit("postgresql", "user", name);
_verify_dbuser_name_but_not_length(name);
}
function _verify_name_length_limit(engine, type, name) {
var max = get_name_length_limit(engine, type);
// Consider DB prefixing (if set) for MySQL database names
if (engine === "mysql" && type === "database") {
if (PAGE && PAGE.db_prefix) {
max -= (PAGE.db_prefix.length + 1); // +1 because the underscore is counted twice by MySQL
}
}
var excess = CPANEL.util.byte_length(name) - max;
if ( excess > 0 ) {
throw LOCALE.maketext("This value is too long by [quant,_1,character,characters]. The maximum length is [quant,_2,character,characters].", excess, max);
}
}
// remove if we ever work around the wildcards-count-as-two problem
function _verify_special_mysql_wildcards_in_dbnames_case(name) {
var escaped_length = name.replace(MYSQL_DB_NAME_WILDCARDS, "\\$1").length;
var limit = get_name_length_limit("mysql", "database");
// Consider DB prefixing (if set) for MySQL database names
if (PAGE && PAGE.db_prefix) {
limit -= (PAGE.db_prefix.length + 1); // +1 because the underscore is counted twice by MySQL
}
var excess = escaped_length - limit;
if (excess > 0) {
throw LOCALE.maketext("This database name has too many wildcard-sensitive characters ([list_and_quoted,_1]). The system stores each of these as two characters internally, up to a limit of [quant,_2,character,characters]. This name would take up [quant,_3,character,characters] of internal storage, which is [numf,_4] too many.", ["\\", "_", "%"], limit, escaped_length, excess);
}
}
function add_prefix(name) {
if (PAGE && PAGE.db_prefix) {
name = PAGE.db_prefix + name;
}
return name;
}
function make_mysql_dbname_validator(el_id) {
_set_maxlength(el_id, "mysql", "database");
return _setup_dbname_validator(
new CPANEL.validate.validator(LOCALE.maketext("Database Name")),
"mysql",
el_id
);
}
function make_postgresql_dbname_validator(el_id) {
_set_maxlength(el_id, "postgresql", "database");
return _setup_dbname_validator(
new CPANEL.validate.validator(LOCALE.maketext("[asis,PostgreSQL] Database Name")),
"postgresql",
el_id
);
}
function make_mysql_username_validator(el_id) {
_set_maxlength(el_id, "mysql", "user");
return _setup_username_validator(
new CPANEL.validate.validator(LOCALE.maketext("Database Username")),
"mysql",
el_id
);
}
function make_postgresql_username_validator(el_id) {
_set_maxlength(el_id, "postgresql", "user");
return _setup_username_validator(
new CPANEL.validate.validator(LOCALE.maketext("[asis,PostgreSQL] Username")),
"postgresql",
el_id
);
}
// NOTE: This returns the DB engine's native length limit, regardless of DB prefixing.
function get_name_length_limit(engine, type) {
if (engine === "mysql" && type === "user") {
_populate_name_length_limit();
}
return NAME_LENGTH_LIMIT[engine][type];
}
function _set_maxlength(el, engine, type) {
var max_length = get_name_length_limit(engine, type);
if (PAGE && PAGE.db_prefix) {
max_length -= PAGE.db_prefix.length;
}
DOM.get(el).maxLength = max_length;
}
function _setup_dbname_validator(validator, dbengine, el_id) {
_add_exception_atom_to_validator(
validator,
el_id,
CPANEL.sql["verify_" + dbengine + "_database_name"]
);
validator.attach();
return validator;
}
function _setup_username_validator(validator, dbengine, el_id) {
_add_exception_atom_to_validator(
validator,
el_id,
_verify_dbuser_name_but_not_length
);
validator.attach();
return validator;
}
function _add_exception_atom_to_validator(validator, el_id, func) {
validator.add(
el_id,
_boolean_for_cp_validator(func),
_message_for_cp_validator(func)
);
}
function _verify_dbuser_name_but_not_length(el) {
var name = (typeof el === "object") ? DOM.get(el).value : el;
if (!name) {
throw LOCALE.maketext("A username cannot be empty.");
}
if (/[^A-Za-z0-9_-]/.test(name)) {
throw LOCALE.maketext("The name of a database user on this system may include only the following characters: [join, ,_1]", "A-Z a-z 0-9 _ -".split(" "));
}
if (/^[0-9]/.test(name)) {
throw LOCALE.maketext("Username cannot begin with a number.");
}
return true;
}
function _boolean_for_cp_validator(thrower) {
return function() {
try {
thrower.apply(this, arguments);
return true;
} catch (e) {
return false;
}
};
}
function _message_for_cp_validator(thrower) {
return function() {
try {
thrower.apply(this, arguments);
return;
} catch (e) {
return e;
}
};
}
// gives the number that DBD::mysql returns in “mysql_serverversion”
function _mysql_version_string_to_number(verstr) {
// Handle 5.5.5-10.1.11-MariaDB
if (verstr.match(/mariadb/i)) {
verstr = verstr.replace(/^[^-]+-/, "");
}
// Handle 10.1.11-MariaDB
return parseInt(
verstr
.replace(/-.*/, "")
.split(/\./)
.map( function(s) {
return s.lpad(2, 0);
} )
.join("")
);
}
function _verify_database_name_for_mysql(name) {
if (name.substr(-1) === " ") {
throw LOCALE.maketext("A database name cannot end with a space character.");
}
if ( /\\/.test(name) ) {
throw LOCALE.maketext( "This system prohibits the backslash ([_1]) character in database names.", "\\" );
}
// remove if we ever work around the wildcards-count-as-two problem
_verify_special_mysql_wildcards_in_dbnames_case(name);
if (window.MYSQL_SERVER_VERSION && _mysql_version_string_to_number(window.MYSQL_SERVER_VERSION) < MYSQL_STARTED_ALLOWING_FILESYS_CHARACTERS) {
if (/[\/\\.]/.test(name)) {
throw LOCALE.maketext("This system’s database version ([_1]) prohibits the character “[_2]” in database names. Ask your administrator to upgrade to a newer version.", window.MYSQL_SERVER_VERSION, ".");
}
}
return true;
}
function _verify_database_name_but_not_length(el) {
var name = (typeof el === "object") ? DOM.get(el).value : el;
if (!name) {
throw LOCALE.maketext("A database name cannot be empty.");
}
if (ANYTHING_BUT_PRINTABLE_7_BIT_ASCII.test(name)) {
throw LOCALE.maketext("This system allows only printable [asis,ASCII] characters in database names.");
}
if (/[`'"]/.test(name)) {
throw LOCALE.maketext("This system prohibits the following [numerate,_1,character,characters] in database names: [join, ,_2]", 3, ["'", "\"", "`"]);
}
if ( /\//.test(name) ) {
throw LOCALE.maketext( "This system prohibits the slash ([_1]) character in database names.", "/" );
}
return true;
}
function _populate_name_length_limit() {
if (!window.MYSQL_SERVER_VERSION) {
alert("The system failed to populate the “window.MYSQL_SERVER_VERSION” variable.");
}
if (NAME_LENGTH_LIMIT["mysql"]["user"] !== null) {
return NAME_LENGTH_LIMIT["mysql"]["user"];
}
if (_mysql_version_string_to_number(window.MYSQL_SERVER_VERSION) >= MYSQL_STARTED_ALLOWING_LONG_USERNAMES) {
// MariaDB has a max length of 80.
// For future-proofing, we’ll plan for a world where we allow
// up to 32-byte usernames, though. The prefix underscore
// is one more character; so, the max we can allow is: 80 - 32 - 1 = 47.
NAME_LENGTH_LIMIT["mysql"]["user"] = 47;
} else {
NAME_LENGTH_LIMIT["mysql"]["user"] = 16;
}
return NAME_LENGTH_LIMIT["mysql"]["user"];
}
CPANEL.sql = {
add_prefix: add_prefix,
make_mysql_dbname_validator: make_mysql_dbname_validator,
make_mysql_username_validator: make_mysql_username_validator,
make_postgresql_dbname_validator: make_postgresql_dbname_validator,
make_postgresql_username_validator: make_postgresql_username_validator,
verify_mysql_database_name: verify_mysql_database_name,
verify_mysql_username: verify_mysql_username,
verify_postgresql_database_name: verify_postgresql_database_name,
verify_postgresql_username: verify_postgresql_username,
get_name_length_limit: get_name_length_limit,
};
}(window));