PHP resource error #6

as4s1n

New Member
Messages
174
Reaction score
4
Points
0
I was working on a private message system and I ran into a huge problem. Whenever they go to send a message, they get a custom error message and I get an error that reads Resource error #6.

Here is my code:
PHP:
<?php
$toUsers = explode(',',$_REQUEST['toUser']);
$subject = $_REQUEST['subject'];
$message = $_REQUEST['message'];
$date = date('m/d/y @ h:i:s');

$badMessage = 0;
$error = '';

if(trim($toUsers) == '') {
	$badMessage++;
	$error .= "Invalid 'TO'";
}

if(trim($subject) == '') {
	$badMessage++;
	$error .= "Invalid 'SUBJECT'";
}

if(trim($message) == '') {
	$badMessage++;
	$error .= "Invalid 'MESSAGE'";
}

if($badMessage > 0)
	echo "Error with your message: $error";
else {
	$curUser = $_SESSION['loggedin'];
	$query = "SELECT id FROM users WHERE username = '$curUser'";
	$result = mysql_query($result);
	
	if(!$result) 
		writeError(mysql_error());
	else {
		while($row=mysql_fetch_array($result, MYSQL_ASSOC)) {
			$senderID = $row['id'];
		}
	}
	if(sizeof($toUsers) != 1) {
		$query = "SELECT id FROM users WHERE username = '$toUsers'";
		$result = mysql_query($query);
		
		if(!$result) 
			writeError(mysql_error());
		else if(mysql_num_rows($result) === 0)
			echo "Cannot find user";
		else {
			while($row=mysql_fetch_array($result, MYSQL_ASSOC)) {
				$toID = $row['id'];
				
				$query = "INSERT INTO mail VALUES(0,'$toID','$senderID','$subject','$message','$date')";
				$result = @mysql_query($query);
				
				if(!$result)
					writeError(mysql_error());
				else {
					echo "Message(s) sent!";
				}
			}
		}
	} else {
	for($i=0;$i<sizeof($toUsers);$1++) {
		$users = $toUsers[i];
		$query = "SELECT id FROM users WHERE username = '$users'";
		$result = @mysql_query($query);
		
		if(!$result) 
			writeError(mysql_error());
		else if(mysql_num_rows($result) === 0)
			echo "Cannot find user";
		else {
			while($row=@mysql_fetch_array($result, MYSQL_ASSOC)) {
				$toID = $row['id'];
				
				$query = "INSERT INTO mail VALUES(0,'$toID','$senderID','$subject','$message','$date')";
				$result = @mysql_query($query);
				
				if(!$result)
					writeError(mysql_error());
				else {
					echo "Message(s) sent!";
				}
			}
		}
	}
}
}
?>

FYI:
writeError() is a PHP function to handle errors that writes a message to a file and displays a custom error message.
 

descalzo

Grim Squeaker
Community Support
Messages
9,373
Reaction score
326
Points
83
Add a second parameter to writeError so you can call it this way:

PHP:
writeError(mysql_error() ,  " at location #1" );

and include the location in your report to yourself. The way you have it now, you have no way of knowing where the error is happening.
 

as4s1n

New Member
Messages
174
Reaction score
4
Points
0
Is there a way to return the line it is on like when you get an unhandled error?
 

misson

Community Paragon
Community Support
Messages
2,572
Reaction score
72
Points
48
Is there a way to return the line it is on like when you get an unhandled error?

You can get information about callers, including line numbers, using (e.g.) debug_backtrace or apd_callstack (the latter is part of the APD extension and may not be installed on the X10 servers).

If you use PDO rather than the old mysql driver, you can use exceptions, which also record line number and can be used to get a stack trace. Exceptions also let you call your error handler once, rather than at each point an error might happen:
PHP:
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
try {
    $query = $db->prepare(...);
    $query->execute(...);
    // no need to handle errors here,
    foreach ($query as $row) {
    ...
    $query = $db->prepare(...);
    $query->execute(...);
    // or here,
    foreach ($query as $row) {
    ...
} catch (PDOException $exc) {
    // because errors are handled here.
    writeError($exc);
}
For a tutorial on PDO, read: "Writing MySQL Scripts with PHP and PDO".

PHP:
<?php
$toUsers = explode(',',$_REQUEST['toUser']);
...
if(trim($toUsers) == '') {
trim works on strings, not arrays. You could try:
PHP:
$toUsers = array_filter(array_map('trim', explode(',',$_REQUEST['toUser'])));
...
if (! $toUsers) {

PHP:
	$query = "SELECT id FROM users WHERE username = '$curUser'";
	$result = mysql_query($result);
	
	if(!$result) 
		writeError(mysql_error());
	else {
		while($row=mysql_fetch_array($result, MYSQL_ASSOC)) {
			$senderID = $row['id'];
		}
	}
There's no need for a loop here as there should be only one row in the result, unless $curUser might not be a valid username (the result might have 0 or 1 rows) and you're using the loop to conditionally assign $senderID. However, later code doesn't follow up on the latter, so I'll assume this isn't the purpose of the while loop. Fetch the row and get the ID without the while.

PHP:
	if(sizeof($toUsers) != 1) {
		$query = "SELECT id FROM users WHERE username = '$toUsers'";
Two issues here: first, this branch is entered if $toUsers has zero items (which shouldn't happen in practice, due to earlier tests) or more than one item, you're running a single query. Change the != to a == to correct this.

Second, these queries are open to SQL injection. If you switch to PDO and use prepared statements, you won't have to worry about injection via parameters.
PHP:
$lookupUID = $db->prepare("SELECT id FROM users WHERE username = :to";
foreach ($toUsers as $to) {
    $lookupUID->bindValue(':to', $to);
    $lookupUID->execute();
    ..
}
This will also be more performant, as the statement won't need to be parsed each time you execute it.

Alternatively, you can send a message to valid users all at once using the query:
Code:
INSERT INTO mail (`to`, `from`, `subject`, `message`)
  SELECT users.id AS `to`, msg.*
    FROM users
      JOIN (
               SELECT :sender AS from, :subject AS subject, :message AS message
        ) AS msg
    WHERE users.username IN ($to);
Note I've explicitly specified which columns to set and dropped the `date` field, as MySQL can automatically set that to the current time if column `date` is defined in a certain way:
Code:
CREATE TABLE `mail` (
  `to` int(11) NOT NULL,
  `from` int(11) NOT NULL,
   ...
  `date` timestamp DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (`to`) REFERENCES `users` (`id`),
  FOREIGN KEY (`sender`) REFERENCES `users` (`id`)
) ENGINE=InnoDB;

This shortens the PHP code to send a message:
PHP:
<?php
$msg = array();
$errors = array();

function safifiy_username($name) {
    return preg_replace('/^\s+|(\W.*)?\s*)$/g', '', $name);
}
$toUsers = array_filter(array_map('safify_username', explode(',', $_REQUEST['to']));
/* alternate for PHP >= 5.3. 
$toUsers = array_filter(array_map(function ($name) {
            return preg_replace('/^\s+|(\W.*)\s*$/', '', $name);
        }, explode(',', $_REQUEST['to'])));
*/

$msg[':subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
$msg[':message'] = isset($_REQUEST['message']) ? trim($_REQUEST['message']) : '';
// tests should be refactored as a validation function.
if (!$toUsers) {
	$errors[] = 'No recipients.';
}
if (empty($msg[':subject'])) {
	$errors[] = "Empty subject.";
}
if (empty($msg[':message'])) {
	$errors[] = "Empty message.";
}

// don't print $errors here, as we might wish to record errors when adding the message to the mail table.
if (!$errors) {
    // $to is a vector for SQL injection, which is why $toUsers is filtered earlier.
    $to = "'" . implode("', '", $toUsers) . "'";
    $sendMsgQuery = $db->prepare("INSERT INTO mail (`to`, `from`, `subject`, `message`)
        SELECT users.id AS `to`, msg.*
          FROM users
          JOIN (SELECT :sender AS from, :subject AS subject, :message AS message) AS msg
          WHERE users.username IN ($to)");
  $sendMsgQuery->execute($msg);
}
if ($errors) {
    ?>Errors: <ul><li><?php
    echo implode('</li><li>', $errors);
    ?></li></ul><?php
}

The one issue with this is it doesn't expose unknown usernames to the script, which could then inform the sender. You can do this by issuing another query in the block that inserts the message:
PHP:
    $usersQuery = $db->query("SELECT username FROM users WHERE username IN ($to)");
    $unknownUsers = array_diff($toUsers, $usersQuery->fetchAll(PDO::FETCH_COLUMN));
    if ($unknownUsers) {
        $errors[] = 'Unknown recipients: ' . implode(', ', $unknownUsers);
    }
 
Last edited:

as4s1n

New Member
Messages
174
Reaction score
4
Points
0
Thanks for the help. I do, however, have a few questions on what you did. Now, I notice when you define $msg as :subject and again at $msg as :message, what does the : do? From my guess it is the same as the superglobal $_REQUEST[]. Also, later in that statement, you use the ?, I have seen that before and I never fully understood what that meant. As well, I have never used the ->, is that similar to the javascript prototype statement?

Thanks for clarification.
 
Last edited:

marshian

New Member
Messages
526
Reaction score
9
Points
0
I suppose you mean one of these?
Code:
$msg[':subject'] = isset($_REQUEST['subject']) ? trim($_REQUEST['subject']) : '';
That's the ternary operator.

If you would have something like this
$var = expression ? value1 : value2;
$var will contain value1 when expression is true and value2 when expression is false.

PHP:
$variable = true : "Line one\n" ? "Line two\n";
echo $variable;
$variable = false : "Line one\n" ? "Line two\n";
echo $variable;
Will return
Code:
Line one
Line two
 

as4s1n

New Member
Messages
174
Reaction score
4
Points
0
OHH, thank you, that makes much more sense now. Please ignore my previous questions
 

misson

Community Paragon
Community Support
Messages
2,572
Reaction score
72
Points
48
Thanks for the help. I do, however, have a few questions on what you did.
The PHP manual has most of the answers you seek.

Now, I notice when you define $msg as :subject and again at $msg as :message, what does the : do? From my guess it is the same as the superglobal $_REQUEST[].
In both cases, $msg is defined as an array; this doesn't change. All that's happening is different indices in $msg are being assigned to. The colon in the indices is used because PDO prepared statements use the colon for named parameters; it has no other use. Read that tutorial I mentioned earlier for more info on prepared statements and parameters.

Also, later in that statement, you use the ?, I have seen that before and I never fully understood what that meant.
For reference (since marshian already explained it): Ternary Operator. Note the operator is "?:", rather than "?".

As well, I have never used the ->, is that similar to the javascript prototype statement?
No. Prototypes are how JS handles inheritance. PHP uses extends for that. The arrow operator is used to access properties and methods. Its equivalent in JS would be the dot operator, though PHP also has the double colon ("::") scope-resolution operator for some situations.
 

as4s1n

New Member
Messages
174
Reaction score
4
Points
0
For future reference. Would it have been OK to separate each user into an array, run a FOR loop and include the entire function?

PHP:
$toUsers = explode(",",$toUsers);
$i;
for($i=0;$i<sizeof($toUsers);$i++) {
        $query = "SELECT id FROM users WHERE username = '".$toUsers[$i]."'";
        $result_sending_users = @mysql_query($query);
        
        if(!$result_sending_users) 
            writeError(mysql_error());
        else if(mysql_num_rows($result_sending_users) === 0)
            echo "Cannot find user";
        else {
            while($row=mysql_fetch_array($result_sending_users, MYSQL_ASSOC)) {
                $toID.$i = $row['id'];
                        }
              }
		
	$query = "INSERT INTO mail VALUES(0,'".$toID.$i."','$senderID','$subject','$message',0)";
	$result_submit_mail = @mysql_query($query);
				
	if(!$result_submit_mail)
		writeError(mysql_error());
	else {
		echo "Message(s) sent!";
	}
}
 
Last edited:

misson

Community Paragon
Community Support
Messages
2,572
Reaction score
72
Points
48
A single query will be faster than multiple queries. Note also that while the query is more complex, overall the code for a single query is simpler. Prepared statements will be faster than interpolating the value into the query string. If you interpolate a value into the query string, you must first sanitize the value.

I'd say whether it's acceptable to loop over the usernames and executing a prepared statement with each iteration, rather than using a single query could only be answered by profiling the code and use that in a simulation of site load to determine if the site will be responsive enough. As for interpolating values, it should only be done for the parts of statements that don't support parameters (which is anything that isn't a simple value, such as identifiers, lists and MySQL keywords).
 
Last edited:
Top