Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
McGlockenshire
Dec 16, 2005

GOLLOCKS!

Golbez posted:

How recommended/good is it to shoehorn an existing site (and more importantly, an existing database schema) into a framework?

It depends on how opinionated the framework is.

Some will let you use only bits and pieces of their stuff, while others insist on using only their creations. If you can skip the data access layer / ORM bits, you should be OK. It's not going to be sane or feasible to hook up an existing database schema to an ORM without massive pain.

Adbot
ADBOT LOVES YOU

Spatulater bro!
Aug 19, 2003

Punch! Punch! Punch!

I'm a total PHP newbie. I understand the core concepts and am currently working on a site. What I'm wondering is where I should go to have my code critiqued/reviewed. Is this thread the right place for that?

Mister Chief
Jun 6, 2011

Good a place as any.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

caiman posted:

I'm a total PHP newbie. I understand the core concepts and am currently working on a site. What I'm wondering is where I should go to have my code critiqued/reviewed. Is this thread the right place for that?

Where are you learning from? You need to be aware that there is a lot of crap advice on the internet about how to program in PHP. But that said, if you post things in this thread I would imagine people will tell you if you are doing anything badly wrong.

McGlockenshire
Dec 16, 2005

GOLLOCKS!
How to avoid crap PHP advice:

Ignore anything written before 2006 or that mentions PHP 5.0 is new.

Ignore anything using the mysql_ family of functions.

Ignore anything that doesn't mention what XSS or CSRF are.

Spatulater bro!
Aug 19, 2003

Punch! Punch! Punch!

I've mostly been teaching myself from this tutorial: http://www.tuxradar.com/practicalphp

I'll post my code when I get home tonight.

McGlockenshire
Dec 16, 2005

GOLLOCKS!

caiman posted:

I've mostly been teaching myself from this tutorial: http://www.tuxradar.com/practicalphp

I'll post my code when I get home tonight.

Uses the mysql_ family, but partially redeems itself by trying to do something sane. Unfortunately that sanity comes in the form of PEAR's DB class, which is awful and designed for PHP4.

It speaks of register_globals and how to work around it not being there. The chapter on security doesn't mention anything about XSS, CSRF, etc. The optimization chapter contains hilarious cargo-cult voodoo that hasn't been relevant since PHP4.

Conclusion: Your resource is crap. Run from it and never look back.

McGlockenshire fucked around with this message at 18:19 on Oct 3, 2012

Spatulater bro!
Aug 19, 2003

Punch! Punch! Punch!

Would you mind elaborating on those criticisms, or point me somewhere that does? I'm especially curious why I shouldn't use mysql_ and what I should be using instead.

EDIT: Furthermore, what is the recommended source for beginners learning PHP?

Spatulater bro! fucked around with this message at 19:21 on Oct 3, 2012

qntm
Jun 17, 2009

caiman posted:

Would you mind elaborating on those criticisms, or point me somewhere that does? I'm especially curious why I shouldn't use mysql_ and what I should be using instead.

EDIT: Furthermore, what is the recommended source for beginners learning PHP?

mysql_ doesn't include a function for preparing statements.

Golbez
Oct 9, 2002

1 2 3!
If you want to take a shot at me get in line, line
1 2 3!
Baby, I've had all my shots and I'm fine

caiman posted:

Would you mind elaborating on those criticisms, or point me somewhere that does? I'm especially curious why I shouldn't use mysql_ and what I should be using instead.

EDIT: Furthermore, what is the recommended source for beginners learning PHP?

Another reason why not to use mysql_ is the PHP manual itself:

quote:

Use of this extension is discouraged. Instead, the MySQLi or PDO_MySQL extension should be used. See also MySQL: choosing an API guide and related FAQ for more information. Alternatives to this function include:

mysqli_query()
PDO::query()
There's really no reason to elaborate on the reasons you shouldn't use mysql_, the specifics are irrelevant, especially to someone starting out who doesn't have a legacy codebase to support. It's simply bad. Take our word for it.

Spatulater bro!
Aug 19, 2003

Punch! Punch! Punch!

Ok here's my code. Now, the one thing I know right off that bat that's wrong with it (aside from the mysql_ thing) is that there's WAY too much commenting. This was for my benefit to help teach myself as I went along and help avoid confusion as I read back through my code.

What this page does in a nutshell: Presents a form for the user to register for the site. Some of the fields are validated. It allows the user to upload an avatar, then the code renames the file, moves it to a directory on the server, then re-sizes it and saves it as a .jpg. Once everything is okay it adds the new user to the database.

code:
<?php
/*Give all POST variables the 'var' prefix*/
import_request_variables("p", "var");
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<link rel="stylesheet" type="text/css" href="styles/styles.css" />
<title>Register</title>
</head>
<body>
<?php
/*Call header*/
include 'header.php';
?>
<!--Registration form.  The 'name' attributes will have 'var' added to 
them to become variables.  The 'value' attribute will be populated only if
there is an error, so the user doesn't have to type everything in again.-->
<form enctype="multipart/form-data" method="post">
    <table id="regTable">
        <tr>
            <td>*Desired Screen Name:</td><td> <input type="text" name="username" value="<?php print $varusername ?>" maxlength="30" autofocus /> </td>
            <td id="usernameast" class="ast">*</td>
        </tr>       
        <tr>
            <td>*Password:</td><td> <input type="password" name="password" value="<?php print $varpassword ?>" /></td>
            <td id="passwordast" class="ast">*</td>
        </tr>
        <tr>
            <td>*Verify Password: </td><td><input type="password" name="verpassword" value="<?php print $varverpassword ?>" /></td>
            <td id="verpasswordast" class="ast">*</td>
        </tr>
        <tr>
            <td>*Email address: </td><td><input type="text" name="email" value="<?php print $varemail ?>" /></td>
            <td id="emailast" class="ast">*</td>
        </tr>
        <tr>
            <td>First Name: </td><td><input type="text" name="firstname" value="<?php print $varfirstname ?>" /></td>
            <td></td>
        </tr>
        <tr>
            <td>Last Name: </td><td><input type="text" name="lastname" value="<?php print $varlastname ?>" /></td>
            <td></td>
        </tr>
        <tr>
            <td>Gender: </td><td><select name="gender">
                            <option value="">Select</option>
                            <option value="male">Male</option>
                            <option value="female">Female</option>
                            <option value="other">Other</option>
                        </select>
            </td>
            <td></td>
        </tr>
        <tr>
            <td>Birthdate: </td>
                <?php
                    include 'birthdate.php';
                ?>
            <td></td>
        </tr>
        <tr>
            <td>City: </td><td><input type="text" name="locationcity" value="<?php print $varlocationcity ?>" /></td>
            <td></td>
        </tr>
        <tr>
            <td>State/Province: </td><td><input type="text" name="locationstate" value="<?php print $varlocationstate ?>" /></td>
            <td></td>
        </tr>
        <tr>
            <td>Country: </td><td><input type="text" name="locationcountry" value="<?php print $varlocationcountry ?>" /></td>
            <td></td>
        </tr>
        <tr>
            <td>Upload an avatar: </td><td><input type="file" name="avatar" /></td>
            <td></td>
        </tr>
        <tr>
            <td></td>
            <td><span style="font-size: .75em;">(Your image should be as close to a perfect square as possible and less than
                    2mb in size.<br />
                    Accepted file types are .jpg, .gif, and .png)</span></td>
        </tr>
        <tr>
            <td><br /></td>
            <td></td>
        </tr>
        <tr>
            <td></td>
            <td><input type="submit" name="submit" value="Register"/></td>
        </tr>
    </table>
</form>
<?php
/*Connect to database*/
mysql_connect("server", "username", "password");
mysql_select_db("databaseName");
/*Leading and trailing whitespace is trimmed from username.*/
$varusername = trim($varusername);
/*Query to check if username already exists in the database*/
$exists = mysql_query("SELECT * FROM users where user_name = '$varusername'");
/*Places the 'exists' query columns into an object variable named '$existsusername'*/
$existsusername = mysql_fetch_object($exists);
/*Query to check if email already exists in the database*/
$exists2 = mysql_query("SELECT * FROM users where email = '$varemail'");
/*Places the 'exists2' query columns into an object variable named '$existsemail'*/
$existsemail = mysql_fetch_object($exists2);
/*Checks if the submit button from the form above has been triggered*/
if(isset($_POST['submit'])) {
    /*Sets $fileext variable to the extension of selected file, then places the
    accepted extensions into an array.  This is used in the next section to 
    ensure the file is of the correct type and size.*/
    if (($_FILES['avatar']['tmp_name'])) {
        $name = $_FILES["avatar"]["name"];
        $srcExt = end(explode(".", $name));
        $allowedexts = array("jpeg", "JPEG", "jpg", "JPG", "gif", "GIF", "png", "PNG");
        $avatarok = true;
    }
    /*Sets the $error variable to the number of errors.*/
    $error = $_FILES['avatar']['error'];
    /*Checks that user-entered data is valid.  If not, an error
    is thrown and asterisk is shown.*/
    /*Checks that user entered both username and password*/
    if($varpassword == ''|| $varusername == '') {
        print "Error: Please input both a username and password.<br />";
        echo "<script type='text/javascript'>document.getElementById('usernameast').style.display = 'block'
        document.getElementById('passwordast').style.display = 'block';
        document.getElementById('verpasswordast').style.display = 'block';</script>";
    /*Checks that username is not taken by comparing against '$ob'.*/
    } elseif ($existsusername->user_name == $varusername){
        print "Error: Sorry, that username is already taken.  Please select a different one.<br />";
        echo "<script type='text/javascript'>document.getElementById('usernameast').style.display = 'block';</script>";
    /*Checks that passwords match.*/
    } elseif ($varpassword != $varverpassword){
        print "Error: Please ensure the passwords match.<br />";
        echo "<script type='text/javascript'>document.getElementById('verpasswordast').style.display = 'block';</script>";
    /*Checks that username contains only letters and numbers.*/
    } elseif (!preg_match("/[a-z0-9_\.-]+$/i", $varusername)) {
        print "Error: Your username can contain only letters and numbers and must be less than 30 characters long.<br />";
        echo "<script type='text/javascript'>document.getElementById('usernameast').style.display = 'block';</script>";
    /*Checks that password is at least six characters long.*/
    } elseif (strlen($varpassword) < 6) {
        print "Error: Your password must be at least six characters long.<br />";
        echo "<script type='text/javascript'>document.getElementById('passwordast').style.display = 'block';</script>";
    /*Checks that email is valid.*/
    } elseif (!preg_match("/^[a-z0-9_\.-]+@[a-z0-9_\.-]+\.[a-z0-9\.]{2,6}$/i", $varemail)) {
        print "Error: Please enter a valid email address.<br />";
        echo "<script type='text/javascript'>document.getElementById('emailast').style.display = 'block';</script>";
    /*Checks that email does not already exist in database*/
    } elseif ($existsemail->email == $varemail){
        print "Error: Sorry, that email address is already in use.<br />";
        echo "<script type='text/javascript'>document.getElementById('emailast').style.display = 'block';</script>";
    } elseif ($avatarok == true & !in_array($srcExt, $allowedexts) || $error == 1) {
            print "Your avatar must be a .jpg, .gif, .png and must be smaller than 2mb.";
            $avatarok = false;
    /*If all fields are valid, the password is hashed...*/
    } else {   
        $hashpassword = sha1($varpassword);
        /*The Month, Date and year are placed into variables, concatonated,
         and placed into a single variable to write to the database...*/
        $month = $varmonth;
        $date = $vardate;
        $year = $varyear;
        $birthdate = $year."-".$month."-".$date;
        /*Check if the user selected an avatar.  If they did, the image is moved to the avatar
        folder.  If not, the generic avatar is assigned.*/ 
        if (($_FILES['avatar']['tmp_name'])) {
            $avatarFullPath = '<img alt=Avatar src=images/avatars/' . $varusername . '_avatar />';
            $avatarThumbPath = '<img alt=Avatar src=images/avatars/' . $varusername . '_avatar width=45px height=45px />';
        } else {
            $avatarFullPath = '<img alt=Avatar src=images/avatars/generic.gif />';
            $avatarThumbPath = '<img alt=Avatar src=images/avatars/generic.gif width=45px height=45px />';
        }
        if ($avatarok == true) {
            /*Get the extension of the uploaded file*/
            $name = $_FILES["avatar"]["name"];
            $ext = end(explode(".", $name));
            /*Create full path from $varuserame*/
            $oldImagePath = "images/avatars/" . $varusername . "_avatar." . $ext;
            /*Move uploaded file to avatars directory*/
            move_uploaded_file($_FILES['avatar']['tmp_name'], $oldImagePath);
            /*Resize the image*/
            /*Get uploaded image height and width*/
            $srcSize = getimagesize($oldImagePath);
            /*Create source image based on file extension*/
            switch ($ext) {
                case "jpeg":
                case "jpg": $srcImage = imagecreatefromjpeg($oldImagePath); break;
                case "gif": $srcImage = imagecreatefromgif($oldImagePath); break;
                case "png": $srcImage = imagecreatefrompng($oldImagePath); break;
            }
            /*Create new image*/
            $destImage = imagecreatetruecolor(100, 100);
            /*Resample the image*/
            imagecopyresampled($destImage, $srcImage, 0, 0, 0, 0, 100, 100, $srcSize[0], $srcSize[1]);
            /*Create new path with .jpg extension*/
            $newImagePath = "images/avatars/" . $varusername . "_avatar.jpg";
            /*Save resized image*/
            imagejpeg($destImage, $newImagePath, 85);
            /*Remove images from memory*/
            imagedestroy($srcImage);
            imagedestroy($destImage);
            /*Delete the original file from the server as long as it has a 
             different name than the new one (since if it has the same name, the
             new one will have already overwritten it anyway and we don't want
             to delete the new file.  This also prevents the new file from being 
             deleted in the unlikely event that someone uploads an avatar in 
             the exact "username_avatar.jpg" format.)*/
            if ($oldImagePath != $newImagePath) {
                unlink($oldImagePath);
            }
        }
        /*...and the user is inserted into the users table.*/
        mysql_query("insert into users (user_name, first_name, last_name, email,
        gender, birthdate, location_city, location_state, location_country, password, avatar_full, avatar_thumb)
        values ('$varusername', '$varfirstname', '$varlastname', '$varemail',
            '$vargender', '$birthdate', '$varlocationcity', '$varlocationstate', 
            '$varlocationcountry', '$hashpassword', '$avatarFullPath', '$avatarThumbPath');");
        /*Redirect to home page*/
        print "<script type='text/javascript'>window.location = 'login.php'</script>";
    }
}   
?>
</body>
</html>

Golbez
Oct 9, 2002

1 2 3!
If you want to take a shot at me get in line, line
1 2 3!
Baby, I've had all my shots and I'm fine
Quick notes:
* Comments in PHP can just be // Comment, rather than /* Comment */
* import_request_variables() is deprecated in PHP 5.3 and removed in 5.4, and is unnecessary.
* It seems usually better to use echo instead of print.
* You seem to be running into a very common beginner mistake: Not knowing where to put the form logic. It's always a first instinct to write the form, then below it put the code to handle it. Then your brain twists inside itself and yells, "Wait, that's not how this works." The general flow for me is usually:

PHP code:
if ($_POST['form'])
{
    process form here
    if error, $error = 'you messed up'
}
if (!$_POST['form'] || $error)
{
    display form here, with error message if necessary
}
* You need to learn about sanitizing database input. "Trim" doesn't cut it. For mysql_, you need to run everything from the user (and most things from the database) through mysql_real_escape_string(). The other database extensions are much better about this. So why is this a problem? You have: $exists = mysql_query("SELECT * FROM users where user_name = '$varusername'"); What if someone put, "' OR 1=1;--"? Then this query becomes, SELECT * FROM users where user_name = '' OR 1=1;--, which means it gets all data for users. (the comment at the end of the line is to make the parser ignore further quotation marks and criteria). This is called a SQL injection and is a fundamental thing everyone needs to know about web development, and many people and even big companies still stuff it up.

This may not sound like a big issue for this query, but for a login query it's fundamental. It also keeps people from deleting your database.

Spatulater bro!
Aug 19, 2003

Punch! Punch! Punch!

Golbez posted:

Quick notes:
* Comments in PHP can just be // Comment, rather than /* Comment */

I knew this one. I just chose my method 'cause I'm so used to doing it that way in css.

Golbez posted:

* import_request_variables() is deprecated in PHP 5.3 and removed in 5.4, and is unnecessary.

So I simply remove that line? How do I quickly assign my POST variables the "var" prefix?

Golbez posted:


* It seems usually better to use echo instead of print.

I see. Is it a performance issue?

Golbez posted:

* You seem to be running into a very common beginner mistake: Not knowing where to put the form logic. It's always a first instinct to write the form, then below it put the code to handle it. Then your brain twists inside itself and yells, "Wait, that's not how this works." The general flow for me is usually:

PHP code:
if ($_POST['form'])
{
    process form here
    if error, $error = 'you messed up'
}
if (!$_POST['form'] || $error)
{
    display form here, with error message if necessary
}

This is quite helpful. I've been unsure how to order things.

Golbez posted:


* You need to learn about sanitizing database input. "Trim" doesn't cut it. For mysql_, you need to run everything from the user (and most things from the database) through mysql_real_escape_string(). The other database extensions are much better about this. So why is this a problem? You have: $exists = mysql_query("SELECT * FROM users where user_name = '$varusername'"); What if someone put, "' OR 1=1;--"? Then this query becomes, SELECT * FROM users where user_name = '' OR 1=1;--, which means it gets all data for users. (the comment at the end of the line is to make the parser ignore further quotation marks and criteria). This is called a SQL injection and is a fundamental thing everyone needs to know about web development, and many people and even big companies still stuff it up.

This may not sound like a big issue for this query, but for a login query it's fundamental. It also keeps people from deleting your database.

I've known about mysql_real_escape_string but sorta forgot to implement it. Your explanation makes me realize how vital it is.

Thanks for the feedback, it's exactly what I'm looking for.

Golbez
Oct 9, 2002

1 2 3!
If you want to take a shot at me get in line, line
1 2 3!
Baby, I've had all my shots and I'm fine

caiman posted:

So I simply remove that line? How do I quickly assign my POST variables the "var" prefix?
Don't. There's no harm in just using $_POST['whatever'] directly. If you REALLY want to do it, use extract().

quote:

I see. Is it a performance issue?
Not that I'm aware of, I just have never seen people use print where echo would work.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe
I think your code could probably use more vertical whitespace, to make it easier to read. You don't have to go nuts, but a few blank lines here and there to break up the PHP code into logical chunks would make it much more readable.

Mister Chief
Jun 6, 2011

If you only used prepared statements with either mysqli or PDO (I suggest PDO) you will not be susceptible to sql injection.

Tad Naff
Jul 8, 2004

I told you you'd be sorry buying an emoticon, but no, you were hung over. Well look at you now. It's not catching on at all!
:backtowork:
Question about PDO, if I want to do a query like
code:
SELECT * FROM `foo` WHERE `column` IN('arbitrary','junk','i','want');
Is there a way to pull that off? Right now I'm doing a mysql_ish string building thing, granted I only do this with integers but it would be nice to do something safer than
code:
$db=new PDO(...);
$ids=some array, what if it's crap from the user maybe;
$sql='SELECT * FROM `foo` WHERE `column` IN('.implode($ids,',').')';
$stmt=$db->prepare($sql);
$result=$stmt->execute();

Mister Chief
Jun 6, 2011

code:
$STH = $DBH->prepare("SELECT * FROM :foo WHERE :column IN(:array)");
$STH->bindParam(':foo', $foo);  
$STH->bindParam(':column', $column);  
$STH->bindParam(':array', $array);  
$STH->execute();

$result = $STH->fetch();

$DBH = null;
?

Tad Naff
Jul 8, 2004

I told you you'd be sorry buying an emoticon, but no, you were hung over. Well look at you now. It's not catching on at all!
:backtowork:

Mister Chief posted:

code:
$STH = $DBH->prepare("SELECT * FROM :foo WHERE :column IN(:array)");
$STH->bindParam(':foo', $foo);  
$STH->bindParam(':column', $column);  
$STH->bindParam(':array', $array);  
$STH->execute();

$result = $STH->fetch();

$DBH = null;
?

That looks like... something I was trying to grok a long time ago and couldn't make work, probably because I was still in an ADO frame of mind. So you can put in an array... would this work (sorry it's late here, can't fire up the test environment):
code:
$STH = $DBH->prepare("SELECT * FROM :foo WHERE :column IN(:array)");
$STH->execute(array('foo'=>$foo,'column'=>$column,'array'=>$array));
?

Mister Chief
Jun 6, 2011

I don't see how it could.

fletcher
Jun 27, 2003

ken park is my favorite movie

Cybernetic Crumb
PDOStatement::execute will take an array for binding multiple parameters at once, but it doesn't work like you are wanting it to for building the IN clause. I think you have to construct that portion of the query string yourself, and concatenate it in there.

bobthecheese
Jun 7, 2006
Although I've never met Martha Stewart, I'll probably never birth her child.
You could use a little trickery to build your ON clause:

php:
<?
$STH = $DBH->prepare("SELECT * FROM :foo WHERE :column IN(:ar".implode(', :ar',array_keys($array)).")");
$STH->bindParam(':foo', $foo);
$STH->bindParam(':column', $column);
foreach ($array as $k=>$v) {
    $STH->bindValue(':ar'.$k, $v);
}
$STH->execute();

$result = $STH->fetch();

$DBH = null;
?>
Also: bindParam isn't bindValue - they behave differently, and could cause some... unexpected consequences...

karms
Jan 22, 2006

by Nyc_Tattoo
Yam Slacker
PDO is better than the alternatives but having lines of bindparam as the defacto usage pattern is jarring. Using reference passing instead of value? Really?? ugh.

I've got some code that automatically 'explodes' arrays into sql lists and it does the same thing that bobthecheese has posted. But it's also little wrapper around PDO so I can only have to specify the bare minimum like:

php:
<?
$db = new Db();
$result = $db->query("SELECT * FROM :foo WHERE :column IN(:list)", array(
     'foo'    => 'users'
    ,'column' => 'id'
    ,'list'   => array(1,4,9,33)
));

foreach($result as $row)
{
    // hellooo nurse
}
?>
You can have a peek at it if you want. It would free you up from having to define params all over the place.

Peanut and the Gang
Aug 24, 2009

by exmarx
I agree that extending PDO to have your own query function makes coding easy as hell and is something that everyone should do. But I think you're going too far with automatically parsing arrays. Since most people use $_GET['id'] instead of using filter_input() with the FILTER_REQUIRE_SCALAR flag, someone is liable to pass an array as input when you'd expect a scalar, thus formulating an invalid query such as SELECT * FROM users WHERE id = '123', '456';. I'm still trying to figure out if this would be vulnerable to some type of injection though. Maybe it's not a big deal, since it'll just throw an error if someone purposely puts in invalid input. But potentially you could gently caress up a strangely written INSERT command and write into arbitrary columns. Or maybe if you have two inputs in a row, one you set as a blank array, and the second as an array with two elements. That's might let you bypass any bounds checking code on the first variable.

For IN clauses, I eventually settled on this. It's not the prettiest, but I think it's the best option
code:
$userIds = get this as an array from somewhere
$userIds = array_map('intval', $userIds);
if(!$userIds){
    echo 'No user ids passed';
    exit;
}
$query = sprintf(
    'SELECT * FROM users WHERE id IN(%s);',
    implode(',', $userIds)
);
and even this can gently caress up if you allow strings in it, since you could pass ':blah' or '?', which could potentially have them interact with the binded parameter names.

I don't really think any solution to this is particularly nice. I've considered making a $DB->buildInStr() method, but you end up having to write a whole bunch of code everywhere just to be able to correctly handle these requirements:
1) work if a regular array is passed
2) be able to build a NOT IN() query or (big sql code instead of just a column name) IN (blah)
3) correctly handle if the array is empty (maybe by writing WHERE 1=0 when a blank array is passed)
Even things like CodeIgniter's active record does it wrong. where_in() will build an invalid query and throw a database error if the array is blank.

Basically, PDO doesnt have this ability, and no matter how you try to do it, it's going to be ugly, and has the possibility of making your input query string unstable, which kinda breaks the use of PDO.

Peanut and the Gang fucked around with this message at 14:12 on Oct 4, 2012

Flaggy
Jul 6, 2007

Grandpa Cthulu needs his napping chair



Grimey Drawer
So we started getting this error on our website today:

Fatal error: require() [function.require]: Failed opening required '../Sorting Systems/sortingheader.php' (include_path='.;C:\php5\pear') in D:\Hosting\2630349\html\Products\Sorting-Systems\sorting.php on line 43

It happened out of no where with nothing changing as far as us putting any new files or changing any directories. Talked with the host provider, they said they had upgraded their php to 5.3 and we were still on 5.2, so we did the upgrade and it was still broken the line 43 it is refering to is this

<?php require("../Sorting Systems/sortingheader.php"); ?>

Did some function change in the php upgrade? I am at a complete loss here.

Flaggy fucked around with this message at 15:38 on Oct 4, 2012

Knyteguy
Jul 6, 2005

YES to love
NO to shirts


Toilet Rascal

Flaggy posted:

So we started getting this error on our website today:

Fatal error: require() [function.require]: Failed opening required '../Sorting Systems/sortingheader.php' (include_path='.;C:\php5\pear') in D:\Hosting\2630349\html\Products\Sorting-Systems\sorting.php on line 43

It happened out of no where with nothing changing as far as us putting any new files or changing any directories. Talked with the host provider, they said they had upgraded their php to 5.3 and we were still on 5.2, so we did the upgrade and it was still broken the line 43 it is refering to is this

<?php require("../Sorting Systems/sortingheader.php"); ?>

Did some function change in the php upgrade? I am at a complete loss here.

This may work: <?php require(__DIR__."../Sorting Systems/sortingheader.php"); ?>

Flaggy
Jul 6, 2007

Grandpa Cthulu needs his napping chair



Grimey Drawer

Knyteguy posted:

This may work: <?php require(__DIR__."../Sorting Systems/sortingheader.php"); ?>

Still get the same error.

musclecoder
Oct 23, 2006

I'm all about meeting girls. I'm all about meeting guys.

Flaggy posted:

Still get the same error.

Try

php:
<?php require(__DIR__."/../Sorting Systems/sortingheader.php"); ?>
as __DIR__ might not add the trailing slash.

Flaggy
Jul 6, 2007

Grandpa Cthulu needs his napping chair



Grimey Drawer

musclecoder posted:

Try

php:
<?php require(__DIR__."/../Sorting Systems/sortingheader.php"); ?>
as __DIR__ might not add the trailing slash.

Tried that as well, still getting the same error, bad week to quit smoking this is frustrating.

karms
Jan 22, 2006

by Nyc_Tattoo
Yam Slacker
The folder name has a dash where the require() command assumes a space.

karms
Jan 22, 2006

by Nyc_Tattoo
Yam Slacker

barbarianbob posted:

I agree that extending PDO to have your own query function makes coding easy as hell and is something that everyone should do. But I think you're going too far with automatically parsing arrays. Since most people use $_GET['id'] instead of using filter_input() with the FILTER_REQUIRE_SCALAR flag, someone is liable to pass an array as input when you'd expect a scalar, thus formulating an invalid query such as SELECT * FROM users WHERE id = '123', '456';. I'm still trying to figure out if this would be vulnerable to some type of injection though. Maybe it's not a big deal, since it'll just throw an error if someone purposely puts in invalid input. But potentially you could gently caress up a strangely written INSERT command and write into arbitrary columns. Or maybe if you have two inputs in a row, one you set as a blank array, and the second as an array with two elements. That's might let you bypass any bounds checking code on the first variable.

For IN clauses, I eventually settled on this. It's not the prettiest, but I think it's the best option
code:
$userIds = get this as an array from somewhere
$userIds = array_map('intval', $userIds);
if(!$userIds){
    echo 'No user ids passed';
    exit;
}
$query = sprintf(
    'SELECT * FROM users WHERE id IN(%s);',
    implode(',', $userIds)
);
and even this can gently caress up if you allow strings in it, since you could pass ':blah' or '?', which could potentially have them interact with the binded parameter names.

I don't really think any solution to this is particularly nice. I've considered making a $DB->buildInStr() method, but you end up having to write a whole bunch of code everywhere just to be able to correctly handle these requirements:
1) work if a regular array is passed
2) be able to build a NOT IN() query or (big sql code instead of just a column name) IN (blah)
3) correctly handle if the array is empty (maybe by writing WHERE 1=0 when a blank array is passed)
Even things like CodeIgniter's active record does it wrong. where_in() will build an invalid query and throw a database error if the array is blank.

Basically, PDO doesnt have this ability, and no matter how you try to do it, it's going to be ugly, and has the possibility of making your input query string unstable, which kinda breaks the use of PDO.

Generate :bind_names for every array element, you doofus! :)

php:
<?
// look for any array and flatten it into an sql list
foreach($bind as $name=>$bind_value)
{
    if(is_array($bind_value))
    {
        $added_parameters = array();
        // you cannot bind multiple values to one parameter; we have to add parameters to the query
        // TODO make this also work robustly with positional parameters (these things: ? )
        $new_name = $name;
        if($name[0] !== ':') $new_name = ':'.$name;

        $count = 1;
        foreach($bind_value as $v) $added_parameters[$new_name.($count++)] = $v;

        $q = preg_replace('/\(*'.quotemeta($new_name).'\)*/', //find :param or (:param)
            '('.join(',',array_keys($added_parameters)).')',  //replace with: (:param1,:param2, ...)
             $q);
        // remove old parameter that we used as a template
        unset($bind[$name]);
        $bind = array_merge($bind, $added_parameters);
    }
}
?>
result:
php:
<?
$q = "select * from :table where id in (:ids)";
$bind = array(
     'table' => 'users'
    ,'ids'   => array(5,3,9,2)
);

// turns into this
$q = "select * from :table where id in (:ids1,:ids2,:ids3,:ids4)";
$bind = array(
     ':table' => 'users'
    ,':ids1'  => 5
    ,':ids2'  => 3
    ,':ids3'  => 9
    ,':ids4'  => 2
);

// so you can pipe them though PDO like so:
$statement = A_PDO_OBJECT->prepare($q);
$statement->execute($bind);
?>
As you can see, I haven't bothered to do positional parameters but that should be easy as pie to hack together.

Flaggy
Jul 6, 2007

Grandpa Cthulu needs his napping chair



Grimey Drawer

KARMA! posted:

The folder name has a dash where the require() command assumes a space.

I tried putting a dash in the name as well, everywhere it was supposed to be but I am still getting that error after I put it back in the folder. And still getting the same error I really do appreciate the help.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

KARMA! posted:

Generate :bind_names for every array element, you doofus! :)

He might have to deal with arrays of various lengths, you doofus! :)

edit: oh. You attempted to deal with that. But your solution doesn't get around the fact that you have to assemble a query based on the data you're going to send, when the basic idea of parameterised queries is that you should have a query that is set in stone and then you bind values for a particular case. If you have to create the query on-the-fly based on the number of parameters then that's not ideal.

Hammerite fucked around with this message at 19:08 on Oct 4, 2012

Tad Naff
Jul 8, 2004

I told you you'd be sorry buying an emoticon, but no, you were hung over. Well look at you now. It's not catching on at all!
:backtowork:
Wow, I thought I had a simple question. Good to see it's causing some discussion. I wonder if making a query like "... WHERE ((`column`=:arrayval1) OR (`column`=:arrayval2) OR (`column`=:arrayvalN))" would be better or different in any way. Probably not, and harder on the DB I suppose.

KARMA! posted:

Generate :bind_names for every array element, you doofus! :)

php:
<?
        $q = preg_replace('/\(*'.quotemeta($new_name).'\)*/', //find :param or (:param)
?>

I have never seem quotemeta() before, is that better/different than preg_quote()?

karms
Jan 22, 2006

by Nyc_Tattoo
Yam Slacker

Flaggy posted:

I tried putting a dash in the name as well, everywhere it was supposed to be but I am still getting that error after I put it back in the folder. And still getting the same error I really do appreciate the help.

I'm sorry man, this is really hard to debug over the wire. All I can suggest is to a exit(__DIR__); and see if that actually returns D:\Hosting\2630349\html\Products\Sorting-Systems. Is that's the case, then welp. :shobon:

Hammerite posted:

He might have to deal with arrays of various lengths, you doofus! :)

edit: oh. You attempted to deal with that. But your solution doesn't get around the fact that you have to assemble a query based on the data you're going to send, when the basic idea of parameterised queries is that you should have a query that is set in stone and then you bind values for a particular case. If you have to create the query on-the-fly based on the number of parameters then that's not ideal.

But you're not sending a query plan ID of some sort to the database so that it can go "hey I don't need to parse the SQL to know what you want, thanks man". You're ALWAYS going to send SQL in a string that the database has to parse! The only thing the database can do speed-wise is keep a query plan cache of recently parsed (parameterized queries), but then it's trivial to store an arraylength amount of query plans instead of 1.

If you really want to get speedy results you might need to look into stored procedures, index tuning, and such.

To me, the biggest help in using a wrapper around PDOs is fullproof escaping and enabling me to write as little code as possible to get whereever I need to go.

karms
Jan 22, 2006

by Nyc_Tattoo
Yam Slacker

FeloniousDrunk posted:

Wow, I thought I had a simple question. Good to see it's causing some discussion. I wonder if making a query like "... WHERE ((`column`=:arrayval1) OR (`column`=:arrayval2) OR (`column`=:arrayvalN))" would be better or different in any way. Probably not, and harder on the DB I suppose.

Any database worth its salt would come up with the same queryplan (the steps a database would take to get you a result). the ... IN (...) Is clearer to read however, which is pretty important for us with a meat brain. :)

FeloniousDrunk posted:

I have never seem quotemeta() before, is that better/different than preg_quote()?

To be honest I found quotemeta when I searched for a suitable function and never looked any further :shobon:. preg_quote's a better option.

Although that does make me wonder what characters are allowed as a parameter name and whether I should automatically filter those or let PDO error out... hmmm...

Flaggy
Jul 6, 2007

Grandpa Cthulu needs his napping chair



Grimey Drawer
Finally got it fixed, thanks guys you saved my day. It was basically the /../ thing, which took a bit of fixing. Thanks again.

Flaggy fucked around with this message at 21:26 on Oct 4, 2012

Zamujasa
Oct 27, 2010



Bread Liar

Golbez posted:

quote:

print vs. echo
Not that I'm aware of, I just have never seen people use print where echo would work.

If I remember right, "echo" is very very very very very very very slightly faster than "print" -- trivial.

There's also some other bullshit that this StackOverflow question explains.


However, in general, for basic poo poo like echo "Some poo poo here."; use whatever works best. I did a lot of stuff in QBASIC when I was younger and "print" got stuck into my brain, so it's what I end up using more often when I'm not mixing them up.


Short version: The only difference you're really likely to run into as a sane developer is that you can do "... or print('yyy')", but not "... or echo('yyy')".

Knyteguy
Jul 6, 2005

YES to love
NO to shirts


Toilet Rascal
Hopefully I didn't miss too many things here, but does anyone see any vulnerabilities in this code besides bot spamming vulnerability?

php:
<?php
/*PHP Mailer
Anthony Hill
10/7/12
*/

// Get their email
if(isset($_GET['email']))
{
    $email $_GET['email'];
} else {
    echo 'No e-mail set';
    exit;
}
// Fill these in
// $to is your e-mail address that you want people to contact you on
// Example: $to = "youremail@gmail.com"
$to "";
// $from should preferably be an e-mail on your website to help messages get past spam filters
$from "";

/* Don't modify below this point if you don't know what you're doing */
$subject "$email has contacted you";
$website $_GET['website'];
$name $_GET['name'];

$headers "From: $from"\r\n" .
    "Reply-To: $email"\r\n" .
    'X-Mailer: PHP/' phpversion();

// Validate
if(!filter_var("$email"FILTER_VALIDATE_EMAIL))
{
    echo 'Please enter a valid email address.';
    exit;
}

// Make sure the message isn't null
if($_GET['message'] != '')
{
    $message "E-Mail: $email\r\nWebsite: $website\r\nContact Name: $name\r\n\r\nMessage:\r\n";
    $message .= $_GET['message'];
} else {
    echo 'Please enter a valid message.';
    exit;
}

echo 'Your e-mail has been sent';
mail($to$subject$message$headers);

?>

code:
function contactMe() {
	var email = $('#email').attr('value');
	var message = $('#message').attr('value');
	var website = $('#website').attr('value');
	var name = $('#name').attr('value');
	$.get("email.php", { email: email, message: message, website: website, name: name },
	function(data){
		alert("" + data);
	});
}
Thank you.

E: Mostly XSS or something. I'm not extremely familiar with anything besides SQL injection.

Knyteguy fucked around with this message at 06:38 on Oct 8, 2012

Adbot
ADBOT LOVES YOU

McGlockenshire
Dec 16, 2005

GOLLOCKS!
Stick the creation of the headers after the email format validation. Nearly wrote an essay on how header injection works.

The built-in filter is OK, but use isemail if you can. It's so RFC compliant that they had to file an erratum just to clear things up.

Looks fine otherwise, with the exception of the whole spambot thing. Watch out, your host might not let you send using an arbitrary From, and doing so will get that mail dropped in the spam folder if the From's domain tries to implement SPF or DKIM.

McGlockenshire fucked around with this message at 07:07 on Oct 8, 2012

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply