Anne van Kesteren

Quotes in PHP

The problem is quite simple and obvious. The user submits something through a post and you catch that value in $_POST['foo'] or so and you want to do something with it. Let’s say the user submits his name and you want to put it in the database (which happens to be a MySQL database):

mysql_query("INSERT INTO foo SET name = '".$_POST['name']."'");

What happens when the user enters An'ne? The resolved PHP code looks like:

mysql_query("INSERT INTO foo SET name = 'An'ne'");

Error.

PHP has some default solution for this called magic_quotes which is not really practical as it is not always enabled. Of course you could play with addslashes and stripslashes, but that will also lead to numerous problems as on magic_quotes enabled servers you might add to many slashes. Or the other way around, you remove too much. The easiest solution I have encountered so far is:

function fix_quotes($value){
 if(get_magic_quotes_gpc()==1){
  return $value;
 }else{
  return addslashes($value);
 }
}

As I was using only $_POST and wanted to solve this quickly I used:

foreach($_POST as $name => $value){
 if(get_magic_quotes_gpc()==1){
  $_POST[$name] = $value;
 }else{
  $_POST[$name] = addslashes($value);
 }
}

Probably the reason PHP is called a “hacky” scripting language. (Referring to my solution, not to the problem, although that might qualify PHP for the award too.)

Comments

  1. Of course you could play with addslashes and removeslashes

    I’m sure you mean stripslashes. Thanks, fixed.

    I think the idea behind Magic Quotes is great, the implementation sucks though.

    Anyway, I use a slightly different function.

    function preparePost($name) {
     if (!isset($_POST[$name]))
      return false;
     if (get_magic_quotes_gpc())
      $_POST[$name] = stripslashes($_POST[$name]);
     return trim(strip_tags($_POST[$name]));
    }

    And by the way, don't forget to give your functions a name ;) Fixed that too.

    Posted by Maarten at

  2. You might want to try mysql_escape_string() when you're doing that sort of string manipulation.

    Posted by Jonathan Holst at

  3. Don't forget about arrays. You can't just use addslashes on array type.

    Posted by dusoft at

    1. Because magic_quotes is sometimes enabled, sometimes not; and because it's a pain as it escapes ALL your input (not just those you want to insert into a database), the best is to always disable it. Here's a piece of code that will do that (taken from the PHP site here Disabling Magic Quotes:

      if(get_magic_quotes_gpc()){
       function stripslashes_deep($value){
        $value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
        return $value;
       }
       $_POST = array_map('stripslashes_deep', $_POST);
       $_GET = array_map('stripslashes_deep', $_GET);
       $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
      }
    2. Anyway, one shouldn't count on magic quotes, because quotes are not always the only thing that need to be escaped. The best is to use the function that was made exactly for that purpose : mysql_real_escape_string. From the documentation: mysql_real_escape_string() calls MySQL's library function mysql_escape_string, which prepends backslashes to the following characters: NULL, x00, n, r, , ', " and x1a.
      This function must always (with few exceptions) be used to make data safe before sending a query to MySQL.

    Posted by Anselm at

  4. What you should do is just take a walk to your php.ini (or .htaccess, for that matter) and turn all the "magic" quoting off, instead of wasting cycles to decode what PHP had done for you.

    This is (as I know it) what most PHP devs do (myself included).

    For your .htaccess the following is the right thing to do

    # Disable all magic quoting.
    php_value	magic_quotes_gpc off
    php_value	magic_quotes_runtime off

    I have never met a host where this didn't work.

    When it comes to SQL quoting something much more nasty than just error can occur - people can start piping malicious SQL into your database. I use John Lim's excellent ADODB which substitutes the needed escaping routine for the database you use. Then you just use "bind variables", a bit JDBC-style:

    $recordset = $dbConn->execute(
     'SELECT * from users WHERE login = ?',
     array($_POST['foo'])
    );

    This way the data entered by the user will be escaped by the functions needed for your database (you even dont have to think which DB it is).

    IMO, "magic quoting" is targeted at inexperienced developers and is thus bad practice. Every language has bad practices as we know.

    The aforementioned doesn't make me like PHP more than Ruby though.

    Posted by Julik at

  5. Most easy solution without having to change configuration (which is the best solution, but not always possible):

    if(get_magic_quotes_gpc()){
     $_GET = array_map('stripslashes', $_GET);
     $_POST = array_map('stripslashes', $_POST);
     $_COOKIE = array_map('stripslashes', $_COOKIE);
    }

    Than whenever you use a POST, GET or COOKIE-var encode or escape according to the usage. For MySQL that would mean using mysql_(real_)escape_string, for HTML htmlspecialchars/htmlentities and for querystrings in a URL urlencode. I cannot think of a single usage where addslashes would be appropriate or sufficient.

    Posted by Tino Zijdel at

  6. You could also do it like this:

    mysql_query("INSERT INTO foo SET name = '{$_POST['name']}'");

    And as Anselm said, using mysql_real_escape_string() is the best way to solve this problem.

    Posted by Dustin Wilson at

  7. Oh yes, variable parsing within strings enclosed by double quotes is also something for the inexperienced, and thus bad practice ;) You will often even see this type of constructs:

    echo "$foo";

    Posted by Tino Zijdel at

  8. I hate that I have to escape every variable, but its a fact of life with PHP.

    I also deal quite frequently with wanting to store UTF-8 data in older installs of MySQL that don't support it, so add more functions. Then there is the problem of escaping for (X)HTML. I wonder how much of my run-time is spent [un]escaping.

    I ended up writing my own dbDecode() and dbEncode() methods, and call them all the time. Very hackish, but I don't know a better solution. (Ruby? I'll learn it when I have time.)

    Posted by Daniel Morrison at

  9. I dunno, IMO I'd rather just stick with magic_quotes_gpc. I find it counter-productive to turn off magic quoting. But I guess I'm not much for old-school PHP either, I didn't get a start in PHP till register_globals had already bit the dust, so I never saw any point in turning magic_quotes_gpc off.

    Magic quoting can also be turned on at runtime (according to the manual, since 4.2.3), so unless you need compatibility before that version, I wouldn't bother with all the extra function calls.

    function magic_quotes(){
     if(!get_magic_quotes_gpc()){
      ini_set('magic_quotes_gpc', 1);
     }
    }

    Posted by Richard York at

  10. Richard York's point also comes into play when you're writing software that could be used on many different server configurations. Such is the case for Open Bulletin Board. I can't go around expecting everyone to turn off their register globals and to turn on their magic quotes, now can I?

    Posted by Stu Schaff at

  11. Richard: Disabling Magic Quotes: The magic_quotes_gpc directive may only be disabled at the system level, and not at runtime. In otherwords, use of ini_set() is not an option.

    Even if it did work it wouldn't have any effect since at that point the GET, POST and COOKIE contents are already loaded into their (super)globals.

    Also bear in mind that despite the name gpc_ this setting also has effect on some of the _SERVER vars like the HTTP_USER_AGENT variable.

    Posted by Tino Zijdel at

  12. Tino: That page talks about disabling magic quotes at runtime. The manual page actually indicates PHP_INI_ALL in PHP <= 4.2.3 (which in my last post I misinterpreted as since 4.2.3).

    No matter anyway, I still think that the built in method is the best way to go. I have administrative access to my PHP websites and can switch it on if it isn't on in .ini, deploy a .htaccess file, or pencil it into httpd.conf.

    Also bear in mind that despite the name gpc_ this setting also has effect on some of the _SERVER vars like the HTTP_USER_AGENT variable.

    Well, I can deal with that. I think the tradeoff for simpler code that I don't have to put as much thought into securing against SQL injection attacks is worth it. I think it's unfortunate that this feature isn't more widely used.

    If I did have to do something cross platform, and my hand was forced, I'd write it into a mysql_query() wrapper. Such as: db::query( 'insert', 'table', array( 'field1' => $_POST['value'], 'field2' => $_POST['value2'] ) );

    Posted by Richard York at

  13. Oops, posted too soon... and then array_walk() the mysql_real_escape_string() onto it.

    Posted by Richard York at

  14. This is what my Framework uses. It works independent of magic_quotes and quotes used. GetString() correctly adds slashes for mysql. The class also removes all non-numeric chars if you use getNum().

    <?php
    /**
     *   Handles http request
     *
     *   Merges $_POST, $_GET, $_FILES vars into 1 array
     *   Vars can be retrieved with getString and getNum
     *
     *   @package        Framework
     *   @version
     */
    
    class Request {
     var $container = Array();
     function Request() {
      $this->init();
     }
     function init(){
      $strip = get_magic_quotes_gpc();
      $request_array = array_merge($_POST, $_GET, $_FILES); 
      foreach($request_array as $variable => $value) {
       if (is_array($value)) {
        $this->container[$variable] = $value;
       } else {  
        $this->container[$variable] = ($strip) ? stripslashes($value) : $value;
       }
      }
     }
    
     /**
       * function returns $variable from $container
       * @access private
       */
     
     function _get($variable){
      return $this->container[$variable];
     }
    
     /**
       * function sets $variable to $value
       * @access private
       */ 
    
     function set($variable, $value){
      $strip = get_magic_quotes_gpc();
      if (is_array($value)) {
       foreach($value as $key => $data) {
        $value[$key] = ($strip) ? stripslashes($data) : $data;
       }
      } else {
       $value = ($strip) ? stripslashes($value) : $value;
      }
      $this->container[$variable] = $value;
     }
    
     /**
       * function returns $variable with slashes
       */
    
     function getString($variable){
      return addslashes($this->_get($variable));
     }
    
     /**
       * function returns $variable with all non-numeric chars removed
       */
    
     function getNum($variable){
      $value = preg_replace("/[^0-9]/", "", $this->_get($variable));
      if($this->_get($variable)<0) $value = -$value;
       return $value;
      }
     }
    ?>

    Posted by Petrik at

  15. Hmm, sorry about that. The preview showed line breaks. As mentioned, PRE support sucks. I’m working on improvements.

    Posted by Petrik at

  16. Richard: if it doesn't work for disabling magic quotes, it probably also doesn't work for enabling it at execution time.
    I've just tested it and you can skip the word 'probably' in my previous sentence ;)

    Posted by Tino Zijdel at

  17. Yeah, that sucks. Whoever came up with that.

    ~Grauw

    Posted by Laurens Holst at