You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
13 KiB
315 lines
13 KiB
<?php |
|
|
|
/** |
|
* Functions to globally validate and prepare data for sql database insertion. |
|
* |
|
* @package OpenEMR |
|
* @link https://www.open-emr.org |
|
* @author Rod Roark <rod@sunsetsystems.com> |
|
* @author Brady Miller <brady.g.miller@gmail.com> |
|
* @copyright Copyright (c) 2009 Rod Roark <rod@sunsetsystems.com> |
|
* @copyright Copyright (c) 2018 Brady Miller <brady.g.miller@gmail.com> |
|
* @license https://github.com/openemr/openemr/blob/master/LICENSE GNU General Public License 3 |
|
*/ |
|
|
|
|
|
/** |
|
* Escape a parameter to prepare for a sql query. |
|
* |
|
* @param string $s Parameter to be escaped. |
|
* @return string Escaped parameter. |
|
*/ |
|
function add_escape_custom($s) |
|
{ |
|
//prepare for safe mysql insertion |
|
$s = mysqli_real_escape_string($GLOBALS['dbh'], ($s ?? '')); |
|
return $s; |
|
} |
|
|
|
/** |
|
* Escape a sql limit variable to prepare for a sql query. |
|
* |
|
* This will escape integers within the LIMIT ?, ? part of a sql query. |
|
* Note that there is a maximum value to these numbers, which is why |
|
* should only use for the LIMIT ? , ? part of the sql query and why |
|
* this is centralized to a function (in case need to upgrade this |
|
* function to support larger numbers in the future). |
|
* |
|
* @param string $s Limit variable to be escaped. |
|
* @return string Escaped limit variable. |
|
*/ |
|
function escape_limit($s) |
|
{ |
|
//prepare for safe mysql insertion |
|
$s = (int)$s; |
|
return $s; |
|
} |
|
|
|
/** |
|
* Escape/sanitize a sql sort order keyword variable to prepare for a sql query. |
|
* |
|
* This will escape/sanitize the sort order keyword. It is done by whitelisting |
|
* only certain keywords(asc,desc). If the keyword is illegal, then will default |
|
* to asc. |
|
* |
|
* @param string $s Sort order keyword variable to be escaped/sanitized. |
|
* @return string Escaped sort order keyword variable. |
|
*/ |
|
function escape_sort_order($s) |
|
{ |
|
return escape_identifier(strtolower($s), array("asc","desc")); |
|
} |
|
|
|
/** |
|
* If parameter string contains comma(,) delimeter |
|
* Splits parameter string into an array, using comma(,) as delimeter |
|
* else it returns original string |
|
* |
|
* @param string $s string to be processed |
|
* @return array $columns an array formed by spliting $s with comma(,) delimeter |
|
*/ |
|
|
|
function process_cols_escape($s) |
|
{ |
|
//returns an array of columns |
|
$columns = explode(",", $s); |
|
if (count($columns) > 1) { |
|
return $columns; |
|
} |
|
|
|
return $s; |
|
} |
|
|
|
/** |
|
* Escape/sanitize a table sql column name for a sql query.. |
|
* |
|
* This will escape/sanitize the sql column name for a sql query. It is done by whitelisting |
|
* all of the current sql column names in the openemr database from a table(s). Note that if |
|
* there is no match, then it will die() and a error message will be sent to the screen and |
|
* the error log. This function should not be used for escaping tables outside the openemr |
|
* database (should use escape_identifier() function below for that scenario) |
|
* |
|
* @param string|array $s sql column name(s) variable to be escaped/sanitized. |
|
* @param array $tables The table(s) that the sql columns is from (in an array). |
|
* @param boolean $long Use long form (ie. table.colname) vs short form (ie. colname). |
|
* @param boolean $throwException Whether to throw a SQL exception instead of dieing |
|
* @return string Escaped table name variable. |
|
*/ |
|
function escape_sql_column_name($s, $tables, $long = false, $throwException = false) |
|
{ |
|
// If $s is asterisk return asterisk to select all columns |
|
if ($s === "*") { |
|
return "*"; |
|
} |
|
|
|
// If $s is an array process then use recursion to check each column |
|
if (is_array($s)) { |
|
$multiple_columns = []; |
|
foreach ($s as $column) { |
|
$multiple_columns[] = escape_sql_column_name(trim($column), $tables); |
|
} |
|
return implode(", ", $multiple_columns); |
|
} |
|
|
|
// If the $tables is empty, then process them all |
|
if (empty($tables)) { |
|
$res = sqlStatementNoLog("SHOW TABLES"); |
|
$tables = array(); |
|
while ($row = sqlFetchArray($res)) { |
|
$keys_return = array_keys($row); |
|
$tables[] = $row[$keys_return[0]]; |
|
} |
|
} |
|
|
|
// First need to escape the $tables |
|
$tables_escaped = array(); |
|
foreach ($tables as $table) { |
|
$tables_escaped[] = escape_table_name($table); |
|
} |
|
|
|
// Collect all the possible sql columns from the tables |
|
$columns_options = array(); |
|
foreach ($tables_escaped as $table_escaped) { |
|
$res = sqlStatementNoLog("SHOW COLUMNS FROM " . $table_escaped); |
|
while ($row = sqlFetchArray($res)) { |
|
if ($long) { |
|
$columns_options[] = $table_escaped . "." . $row['Field']; |
|
} else { |
|
$columns_options[] = $row['Field']; |
|
} |
|
} |
|
} |
|
|
|
// Now can escape(via whitelisting) the sql column name |
|
$dieIfNoMatch = !$throwException; |
|
return escape_identifier($s, $columns_options, $dieIfNoMatch, true, $throwException); |
|
} |
|
|
|
/** |
|
* Escape/sanitize a table name for a sql query. This function can also can be used to |
|
* process tables that contain any upper case letters. |
|
* |
|
* This will escape/sanitize the table name for a sql query. It is done by whitelisting |
|
* all of the current tables in the openemr database. The matching is not case sensitive, |
|
* although it will attempt a case sensitive match before proceeding to a case insensitive |
|
* match (see below escape_identifier() function for more details on this). Note that if |
|
* there is no match, then it will die() and a error message will be sent to the screen |
|
* and the error log. This function should not be used for escaping tables outside the |
|
* openemr database (should use escape_identifier() function below for that scenario). |
|
* Another use of this function is to deal with casing issues that arise in tables that |
|
* contain upper case letter(s) (these tables can be huge issues when transferring databases |
|
* from Windows to Linux and vice versa); this function can avoid this issues if run the |
|
* table name through this function (To avoid confusion, there is a wrapper function |
|
* entitled mitigateSqlTableUpperCase() that is used when just need to mitigate casing |
|
* for table names that contain any uppercase letters). |
|
* @param string $s sql table name variable to be escaped/sanitized. |
|
* @return string Escaped table name variable. |
|
*/ |
|
function escape_table_name($s) |
|
{ |
|
$res = sqlStatementNoLog("SHOW TABLES"); |
|
$tables_array = array(); |
|
while ($row = sqlFetchArray($res)) { |
|
$keys_return = array_keys($row); |
|
$tables_array[] = $row[$keys_return[0]]; |
|
} |
|
|
|
// Now can escape(via whitelisting) the sql table name |
|
return escape_identifier($s, $tables_array, true, false); |
|
} |
|
|
|
/** |
|
* Process tables that contain any upper case letters; this is simple a wrapper function of |
|
* escape_table_name() above when using it for the sole purpose of mitigating sql table names |
|
* that contain upper case letters. |
|
* |
|
* @param string $s sql table name variable to be escaped/sanitized. |
|
* @return string Escaped table name variable. |
|
*/ |
|
function mitigateSqlTableUpperCase($s) |
|
{ |
|
return escape_table_name($s); |
|
} |
|
|
|
/** |
|
* Escape/sanitize a sql identifier variable to prepare for a sql query. |
|
* |
|
* This will escape/sanitize a sql identifier. There are two options provided by this |
|
* function. |
|
* The first option is done by whitelisting ($whitelist_items is array) and in this case |
|
* only certain identifiers (listed in the $whitelist_items array) can be used; if |
|
* there is no match, then it will either default to the first item in the $whitelist_items |
|
* (if $die_if_no_match is FALSE) or it will die() and send an error message to the screen |
|
* and log (if $die_if_no_match is TRUE). Note there is an option to allow case insensitive |
|
* matching; if this option is chosen, it will first attempt a case sensitive match and if this |
|
* fails, then attempt a case insensitive match. |
|
* The second option is done by checking against a regex expression, which would use as a string |
|
* in $whitelist_items (for example, 'a-zA-Z0-9_'). If $die_if_no_match is true, then will die |
|
* if any illegal characters are found. If $die_if_no_match is false, then will remove the illegal |
|
* characters and send back string of only the legal characters. |
|
* The first option is ideal if all the possible identifiers are known, however we realize this |
|
* may not always be the case. |
|
* |
|
* @param string $s Sql identifier variable to be escaped/sanitized. |
|
* @param array/string $whitelist_items Items used in whitelisting method (See function description for details of whitelisting method). |
|
* Standard use is to use a array. If use a string, then should be regex expression of allowed |
|
* characters (for example 'a-zA-Z0-9_'). |
|
* @param boolean $die_if_no_match If there is no match in the whitelist, then die and echo an error to screen and log. |
|
* @param boolean $case_sens_match Use case sensitive match (this is default). |
|
* @param boolean $throw_exception_if_no_match If there is no match in the whitelist then throw an exception |
|
* @return string Escaped/sanitized sql identifier variable. |
|
*/ |
|
function escape_identifier($s, $whitelist_items, $die_if_no_match = false, $case_sens_match = true, $throw_exception_if_no_match = false) |
|
{ |
|
if (is_array($whitelist_items)) { |
|
// Only return an item within the whitelist_items |
|
$ok = $whitelist_items; |
|
// First, search for case sensitive match |
|
$key = array_search($s, $ok); |
|
if ($key === false) { |
|
// No match |
|
if (!$case_sens_match) { |
|
// Attempt a case insensitive match |
|
$ok_UPPER = array_map("strtoupper", $ok); |
|
$key = array_search(strtoupper($s), $ok_UPPER); |
|
} |
|
|
|
if ($key === false) { |
|
// Still no match |
|
if ($die_if_no_match) { |
|
// No match and $die_if_no_match is set, so die() and send error messages to screen and log |
|
error_Log("ERROR: OpenEMR SQL Escaping ERROR of the following string: " . errorLogEscape($s), 0); |
|
die("<br /><span style='color:red;font-weight:bold;'>" . xlt("There was an OpenEMR SQL Escaping ERROR of the following string") . " " . text($s) . "</span><br />"); |
|
} else if ($throw_exception_if_no_match) { |
|
throw new \OpenEMR\Common\Database\SqlQueryException("", "ERROR: OpenEMR SQL Escaping ERROR of the following string: " . errorLogEscape($s)); |
|
} else { |
|
// Return first token since no match |
|
$key = 0; |
|
} |
|
} |
|
} |
|
|
|
return $ok[$key]; |
|
} else { |
|
if ($die_if_no_match) { |
|
if (preg_match('/[^' . $whitelist_items . ']/', $s)) { |
|
// Contains illegal character and $die_if_no_match is set, so die() and send error messages to screen and log |
|
error_Log("ERROR: OpenEMR SQL Escaping ERROR of the following string: " . errorLogEscape($s), 0); |
|
die("<br /><span style='color:red;font-weight:bold;'>" . xlt("There was an OpenEMR SQL Escaping ERROR of the following string") . " " . text($s) . "</span><br />"); |
|
} else if ($throw_exception_if_no_match) { |
|
throw new \OpenEMR\Common\Database\SqlQueryException("", "ERROR: OpenEMR SQL Escaping ERROR of the following string: " . errorLogEscape($s)); |
|
} else { |
|
// Contains all legal characters, so return the legal string |
|
return $s; |
|
} |
|
} else { |
|
// Since not using $die_if_no_match, then will remove the illegal characters and send back a legal string |
|
return preg_replace('/[^' . $whitelist_items . ']/', '', $s); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* (Note this function is deprecated for new scripts and is only utilized to support legacy scripts) |
|
* Function to manage POST, GET, and REQUEST variables. |
|
* |
|
* @param string $name name of the variable requested. |
|
* @param string $type 'P', 'G' for post or get data, otherwise uses request. |
|
* @param bool $istrim whether to use trim() on the data. |
|
* @return string variable requested, or empty string |
|
*/ |
|
function formData($name, $type = 'P', $isTrim = false) |
|
{ |
|
if ($type == 'P') { |
|
$s = isset($_POST[$name]) ? $_POST[$name] : ''; |
|
} elseif ($type == 'G') { |
|
$s = isset($_GET[$name]) ? $_GET[$name] : ''; |
|
} else { |
|
$s = isset($_REQUEST[$name]) ? $_REQUEST[$name] : ''; |
|
} |
|
|
|
return formDataCore($s, $isTrim); |
|
} |
|
|
|
/** |
|
* (Note this function is deprecated for new scripts and is only utilized to support legacy scripts) |
|
* NEED TO KEEP THIS FUNCTION TO ENSURE LEGACY FORMS ARE SUPPORTED |
|
* Core function that will be called by formData. |
|
* Note it can also be called directly if preparing |
|
* normal variables (not GET,POST, or REQUEST) |
|
* |
|
* @param string $s |
|
* @param bool $istrim whether to use trim() on the data. |
|
* @return string |
|
*/ |
|
function formDataCore($s, $isTrim = false) |
|
{ |
|
//trim if selected |
|
if ($isTrim) { |
|
$s = trim($s); |
|
} |
|
|
|
//add escapes for safe database insertion |
|
$s = add_escape_custom($s); |
|
return $s; |
|
}
|
|
|