IT Share you

참조 : MySQL 확장을 사용하는 완벽한 코드 샘플은 무엇입니까?

shareyou 2020. 12. 5. 10:56
반응형

참조 : MySQL 확장을 사용하는 완벽한 코드 샘플은 무엇입니까?


이것은 커뮤니티 학습 리소스 를 만드는 것 입니다 . 목표는 PHP 코드 복사 / 붙여 넣기에서 자주 볼 수있는 끔찍한 실수를 반복하지 않는 좋은 코드의 예를 갖는 것입니다. 커뮤니티 위키로 만들어달라고 요청했습니다.

이것은 코딩 콘테스트아닙니다. 쿼리를 수행하는 가장 빠르거나 가장 간결한 방법을 찾는 것이 아닙니다. 특히 초보자에게 좋고 읽기 쉬운 참조를 제공하는 것입니다.

매일 Stack Overflow의 함수 제품군을 사용하는 정말 나쁜 코드 조각 과 함께 엄청난 질문이 mysql_*쏟아집니다. 일반적으로 이러한 사람들을 PDO로 안내하는 것이 가장 좋지만 때로는 가능하지 않거나 (예 : 상속 된 레거시 소프트웨어) 현실적인 기대 (사용자가 이미 프로젝트에서 사용하고 있음)가 불가능합니다.

mysql_*라이브러리를 사용하는 코드의 일반적인 문제는 다음과 같습니다.

  • 값에 SQL 주입
  • LIMIT 절 및 동적 테이블 이름에 SQL 삽입
  • 오류보고 없음 ( "이 쿼리가 작동하지 않는 이유는 무엇입니까?")
  • 손상된 오류보고 (즉, 코드가 프로덕션에 들어간 경우에도 항상 오류가 발생 함)
  • 가치 출력에 교차 사이트 스크립팅 (XSS) 삽입

mySQL_ * 함수 계열을 사용하여 다음을 수행하는 PHP 코드 샘플을 작성해 보겠습니다 .

  • 두 개의 POST 값 id(숫자) 및 name(문자열) 허용
  • 테이블 tablename에서 UPDATE 쿼리 name를 수행하여 ID가있는 행 열을 변경합니다.id
  • 실패하면 정중하게 종료하되 프로덕션 모드에서만 자세한 오류를 표시하십시오. trigger_error()충분합니다. 또는 선택한 방법을 사용하십시오.
  • " $name업데이트 됨" 메시지를 출력합니다 .

그리고 위에 나열된 약점을 표시 하지 않습니다 .

가능한 한 간단 해야 합니다 . 이상적으로는 함수 나 클래스를 포함하지 않습니다. 목표는 복사 / 붙여 넣기 가능한 라이브러리를 만드는 것이 아니라 데이터베이스 쿼리를 안전하게 만들기 위해 수행해야하는 최소한의 작업표시하는 것입니다.

좋은 댓글에 대한 보너스 포인트.

목표는이 질문을 사용자가 잘못된 코드를 가지고 있거나 (질문의 초점이 아님에도 불구하고) 실패한 쿼리에 직면하고 그렇지 않은 질문 요청자를 만날 때 연결할 수있는 리소스로 만드는 것입니다. 그것을 고치는 방법을 알고 있습니다.

PDO 토론을 선점하려면 :

예, 이러한 질문을 작성하는 개인을 PDO로 안내하는 것이 선호 될 수 있습니다. 옵션 일 때 그렇게해야합니다. 그러나 항상 가능한 것은 아닙니다. 때때로 질문 asker가 레거시 코드에서 작업 중이거나 이미이 라이브러리를 사용하여 먼 길을 왔으며 지금 변경하지 않을 것입니다. 또한 mysql_*기능 군은 올바르게 사용하면 완벽하게 안전합니다. 따라서 여기에 "사용 PDO"답변이 없습니다.


내 찌르다. 실제 편의를 유지하면서 가능한 한 단순하게 유지하려고했습니다.

유니 코드를 처리하고 가독성을 위해 느슨한 비교를 사용합니다. 착하게 굴 어라 ;-)

<?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' ?>

Looks like my other answer missed the aim of the question.
(this one doesn't meet some requirements either, but as it can be seen, no safe solution can be achieved without implementing a function to process placeholders, which are being the cornerstone of the safe queries)

So, here is another attempt to post concise solution to make mysql queries safe yet handy.

A function I wrote long time ago and it served me well until I moved to the corporative standard OOP-based solution.
There was 2 goals to pursue for: security and ease of use.

First one achieved by implementing placeholders.
Second one achieved by implementing placeholders and different result types.

The function surely not ideal one. Some drawbacks are:

  • no % chars have to be placed in the query directly as it's using printf syntax.
  • no multiple connections supported.
  • no placeholder for the identifiers (as well as many other handy placeholders).
  • again, no identifier placeholder!. "ORDER BY $field" case have to be handled manually!
  • of course an OOP implementation would be much more flexible, having neat distinct methods instead ugly "mode" variable as well other necessary methods.

Yet it is good, safe and concise, no need to install a whole library.

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;
}
?>

usage examples

$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);

As it can be seen from the above examples, the main difference from all the codes ever posted in Stackoverflow, both safety and data retrieval routines are encapsulated in the function code. So, no manual binding, escaping/quoting or casting, as well as no manual data retrieval.

combined with other helper function

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); 
}

used like this

$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
}

it may cover almost every need, including the example case from the OP.

참고URL : https://stackoverflow.com/questions/6198104/reference-what-is-a-perfect-code-sample-using-the-mysql-extension

반응형