Это делается для создания ресурса обучения сообщества . Цель состоит в том, чтобы иметь примеры хорошего кода, которые не повторяют ужасных ошибок, которые так часто можно найти в скопированном / вставленном PHP-коде. Я попросил его сделать Community Wiki.
Это не означает соревнование по кодированию. Речь идет не о том, чтобы найти самый быстрый или самый компактный способ сделать запрос – это обеспечить хорошую, читаемую ссылку, особенно для новичков.
- Исправить PHP-метод для хранения специальных символов в MySQL DB
- Что такое PDO-эквивалент функции mysql_real_escape_string?
- Что делает mysql_real_escape_string (), что делает addlashes ()?
- Каковы наилучшие методы предотвращения инъекций sql?
- эта динамика (столбец & таблица) PHP выбирает запрос безопасным?
Каждый день возникает огромный поток вопросов с действительно плохими фрагментами кода, использующими семейство функций mysql_*
в Stack Overflow. В то время как обычно лучше всего направлять этих людей к PDO, иногда это не возможно (например, унаследованное унаследованное программное обеспечение) и реалистичное ожидание (пользователи уже используют его в своем проекте).
Общие проблемы с кодом, использующим библиотеку mysql_*
включают:
Давайте напишем образец кода PHP, который делает следующее с помощью семейства функций mySQL_ * :
id
(числовые) и name
(строка) tablename
, изменив столбец name
в строке с идентификатором id
trigger_error()
будет достаточным; альтернативно используйте метод по вашему выбору $name
updated». И не показывает никаких недостатков, перечисленных выше.
Это должно быть как можно проще . Он идеально не содержит никаких функций или классов. Цель состоит не в создании библиотеки для копирования и вставки, а в том, чтобы показать минимум того, что нужно сделать, чтобы сделать запрос к базе данных безопасным.
Бонусные баллы за хорошие комментарии.
Цель состоит в том, чтобы сделать этот вопрос ресурсом, с которым пользователь может ссылаться, когда сталкивается с вопросом, у которого есть плохой код (даже если он вообще не является предметом внимания) или сталкивается с неудачным запросом и не делает знаете, как это исправить.
Чтобы упредить обсуждение PDO:
Да, часто будет предпочтительнее направить лиц, которые пишут эти вопросы в PDO. Когда это вариант, мы должны это сделать. Это, однако, не всегда возможно – иногда вопрос, который задает вопрос, работает над устаревшим кодом или уже прошел долгий путь с этой библиотекой и вряд ли изменит его сейчас. Кроме того, семейство функций mysql_*
совершенно безопасно при правильном использовании. Поэтому здесь нет ответов на использование PDO.
Мой удар в него. Пытался держать его как можно проще, сохраняя при этом некоторые реальные удобства.
Обрабатывает unicode и использует свободное сравнение для удобочитаемости. Будьте милы 😉
<?php header('Content-type: text/html; charset=utf-8'); error_reporting(E_ALL | E_STRICT); ini_set('display_errors', 1); // display_errors can be changed to 0 in production mode to // suppress PHP's error messages /* Can be used for testing $_POST['id'] = 1; $_POST['name'] = 'Markus'; */ $config = array( 'host' => '127.0.0.1', 'user' => 'my_user', 'pass' => 'my_pass', 'db' => 'my_database' ); # Connect and disable mysql error output $connection = @mysql_connect($config['host'], $config['user'], $config['pass']); if (!$connection) { trigger_error('Unable to connect to database: ' . mysql_error(), E_USER_ERROR); } if (!mysql_select_db($config['db'])) { trigger_error('Unable to select db: ' . mysql_error(), E_USER_ERROR); } if (!mysql_set_charset('utf8')) { trigger_error('Unable to set charset for db connection: ' . mysql_error(), E_USER_ERROR); } $result = mysql_query( 'UPDATE tablename SET name = "' . mysql_real_escape_string($_POST['name']) . '" WHERE id = "' . mysql_real_escape_string($_POST['id']) . '"' ); if ($result) { echo htmlentities($_POST['name'], ENT_COMPAT, 'utf-8') . ' updated.'; } else { trigger_error('Unable to update db: ' . mysql_error(), E_USER_ERROR); }
Я решил поднять оружие и просто что-то наделать. С чего начать. Выдает исключение при ошибке.
function executeQuery($query, $args) { $cleaned = array_map('mysql_real_escape_string', $args); if($result = mysql_query(vsprintf($query, $cleaned))) { return $result; } else { throw new Exception('MySQL Query Error: ' . mysql_error()); } } function updateTablenameName($id, $name) { $query = "UPDATE tablename SET name = '%s' WHERE id = %d"; return executeQuery($query, array($name, $id)); } try { updateTablenameName($_POST['id'], $_POST['name']); } catch(Exception $e) { echo $e->getMessage(); exit(); }
/** * Rule #0: never trust users input! */ //sanitize integer value $id = intval($_GET['id']); //sanitize string value; $name = mysql_real_escape_string($_POST['name']); //1. using `dbname`. is better than using mysql_select_db() //2. names of tables and columns should be quoted by "`" symbol //3. each variable should be sanitized (even in LIMIT clause) $q = mysql_query("UPDATE `dbname`.`tablename` SET `name`='".$name."' WHERE `id`='".$id."' LIMIT 0,1 "); if ($q===false) { trigger_error('Error in query: '.mysql_error(), E_USER_WARNING); } else { //be careful! $name contains user's data, remember Rule #0 //always use htmlspecialchars() to sanitize user's data in output print htmlspecialchars($name).' updated'; } ######################################################################## //Example, how easily is to use set_error_handler() and trigger_error() //to control error reporting in production and dev-code //Do NOT use error_reporting(0) or error_reporting(~E_ALL) - each error //should be fixed, not muted function err_handler($errno, $errstr, $errfile, $errline) { $hanle_errors_print = E_ALL & ~E_NOTICE; //if we want to print this type of errors (other types we can just write in log-file) if ($errno & $hanle_errors_print) { //$errstr can contain user's data, so... Rule #0 print PHP_EOL.'Error ['.$errno.'] in file '.$errfile.' in line '.$errline .': '.htmlspecialchars($errstr).PHP_EOL; } //here you can write error into log-file } set_error_handler('err_handler', E_ALL & ~E_NOTICE & E_USER_NOTICE & ~E_STRICT & ~E_DEPRECATED);
И некоторое объяснение комментариев:
//1. using `dbname`. is better than using mysql_select_db()
С помощью mysql_select_db вы можете создавать ошибки, и их будет не так просто найти и исправить.
Например, в каком-то скрипте вы установите db1 как базу данных, но в некоторой функции вам нужно установить db2 в качестве базы данных.
После вызова этой функции база данных будет переключена, и все последующие запросы в скрипте будут сломаны или сломаются некоторые данные в неправильной базе данных (если имена таблиц и столбцов совпадут).
//2. names of tables and columns should be quoted by "`" symbol
Некоторые имена столбцов могут быть также SQL-ключевыми словами, и использование символа « ` »поможет с этим.
Кроме того, все строковые значения, вставленные в запрос, должны быть указаны символом ' .
//always use htmlspecialchars() to sanitize user's data in output
Это поможет вам предотвратить атаки XSS .
<? mysql_connect(); mysql_select_db("new"); $table = "test"; if($_SERVER['REQUEST_METHOD']=='POST') { $name = mysql_real_escape_string($_POST['name']); if ($id = intval($_POST['id'])) { $query="UPDATE $table SET name='$name' WHERE id=$id"; } else { $query="INSERT INTO $table SET name='$name'"; } mysql_query($query) or trigger_error(mysql_error()." in ".$query); header("Location: http://".$_SERVER['HTTP_HOST'].$_SERVER['PHP_SELF']); exit; } if (!isset($_GET['id'])) { $LIST=array(); $query="SELECT * FROM $table"; $res=mysql_query($query); while($row=mysql_fetch_assoc($res)) $LIST[]=$row; include 'list.php'; } else { if ($id=intval($_GET['id'])) { $query="SELECT * FROM $table WHERE id=$id"; $res=mysql_query($query); $row=mysql_fetch_assoc($res); foreach ($row as $k => $v) $row[$k]=htmlspecialchars($v); } else { $row['name']=''; $row['id']=0; } include 'form.php'; } ?>
form.php
<? include 'tpl_top.php' ?> <form method="POST"> <input type="text" name="name" value="<?=$row['name']?>"><br> <input type="hidden" name="id" value="<?=$row['id']?>"> <input type="submit"><br> <a href="?">Return to the list</a> </form> <? include 'tpl_bottom.php' ?>
list.php
<? include 'tpl_top.php' ?> <a href="?id=0">Add item</a> <? foreach ($LIST as $row): ?> <li><a href="?id=<?=$row['id']?>"><?=$row['name']?></a> <? endforeach ?> <? include 'tpl_bottom.php' ?>
Похоже, мой другой ответ пропустил цель вопроса.
(это тоже не соответствует некоторым требованиям, но, как видно, не может быть достигнуто безопасного решения без реализации функции обработки заполнителей, которые являются краеугольным камнем безопасных запросов)
Итак, вот еще одна попытка опубликовать краткое решение, чтобы сделать запросы mysql безопасными, но удобными.
Функция, которую я написал давно, и она хорошо меня обслуживала, пока я не перешел на корпоративное стандартное решение на основе ООП.
Для достижения этой цели было 2 цели: безопасность и простота использования .
Первый из них достигается путем внедрения заполнителей.
Второе достигается путем внедрения заполнителей и разных типов результатов.
Функция, безусловно, не идеальна. Некоторые недостатки:
%
chars должны быть помещены в запрос напрямую, так как используется синтаксис printf. "ORDER BY $field"
должен обрабатываться вручную! Тем не менее, это хорошо, безопасно и красно, не нужно устанавливать целую библиотеку.
function dbget() { /* usage: dbget($mode, $query, $param1, $param2,...); $mode - "dimension" of result: 0 - resource 1 - scalar 2 - row 3 - array of rows */ $args = func_get_args(); if (count($args) < 2) { trigger_error("dbget: too few arguments"); return false; } $mode = array_shift($args); $query = array_shift($args); $query = str_replace("%s","'%s'",$query); foreach ($args as $key => $val) { $args[$key] = mysql_real_escape_string($val); } $query = vsprintf($query, $args); if (!$query) return false; $res = mysql_query($query); if (!$res) { trigger_error("dbget: ".mysql_error()." in ".$query); return false; } if ($mode === 0) return $res; if ($mode === 1) { if ($row = mysql_fetch_row($res)) return $row[0]; else return NULL; } $a = array(); if ($mode === 2) { if ($row = mysql_fetch_assoc($res)) return $row; } if ($mode === 3) { while($row = mysql_fetch_assoc($res)) $a[]=$row; } return $a; } ?>
примеры использования
$name = dbget(1,"SELECT name FROM users WHERE id=%d",$_GET['id']); $news = dbget(3,"SELECT * FROM news WHERE title LIKE %s LIMIT %d,%d", "%$_GET[search]%",$start,$per_page);
Как видно из приведенных выше примеров, основное отличие от всех кодов, когда-либо опубликованных в Stackoverflow, как безопасности, так и процедуры поиска данных инкапсулированы в код функции. Таким образом, никакое ручное связывание, экранирование / кавычка или литье, а также отсутствие ручного поиска данных.
в сочетании с другой вспомогательной функцией
function dbSet($fields,$source=array()) { $set = ''; if (!$source) $source = &$_POST; foreach ($fields as $field) { if (isset($source[$field])) { $set.="`$field`='".mysql_real_escape_string($source[$field])."', "; } } return substr($set, 0, -2); }
используется так
$fields = explode(" ","name surname lastname address zip phone regdate"); $_POST['regdate'] = $_POST['y']."-".$_POST['m']."-".$_POST['d']; $sql = "UPDATE $table SET ".dbSet($fields).", stamp=NOW() WHERE id=%d"; $res = dbget(0,$sql, $_POST['id']); if (!$res) { _503;//calling generic 503 error function }
он может охватывать почти все потребности, включая примерный пример из ОП.