-1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" href="StylesTSL.css" type="text/css" media="screen">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Terraria Server List</title>
</head>
<body>
<div id="page">
<div id="logobase">
<div class="filler"></div>
<center><img src="logo.png" width="400" height="100"/></center>
</div>
<div class="filler"></div>
<div id="form">
<center>
<h1>Add a server!</h1>
<form action="" method="post">
Title: <input type="text" name="title" /><br />
IP & Port: <input type="text" name="ip" />(E.G: 127.0.0.1:7777)<br />
Description:<br />
<textarea name="desc"></textarea><br />
E-Mail: <input type="text" name="email" /><br />
Type:       <select name='type'><option value="Hamachi" selected>Hamachi</option><option value="Non-Hamachi">Non-Hamachi</option></select><br />
<input type="submit" name="submit" value="Submit server!" />
</form>
</center>
</div>
<div class="filler"></div>
<?php
//Our variables
$title = $_POST['title'];
$ip = $_POST['ip'];
$desc = $_POST['desc'];
$type = $_POST['type'];
$email = $_POST['email'];
$submit = $_POST['submit'];
//Connect to our DB
$con = mysql_connect("localhost", "x", "x");   
mysql_select_db("bobrocke_users", $con) or die("Could not select database");
if ($submit) {
    if (strlen($title) == 0) {
        die("Invalid title!");
    }
    else if (strlen($title) >= 51) {
        die("Invalid title!");
    }
    else if (strlen($ip) == 0) {
        die("Invalid IP!");
    }
    else if (strlen($ip) >= 51) {
        die("Invalid IP!");
    }
    else if (strlen($desc) == 0) {
        die("Invalid description!");
    }
    else if (strlen($email) == 0) {
        die("Invalid E-Mail!");
    }
    else if (strlen($email) >= 101) {
        die("Invalid E-Mail!");
    }
    else {
    mysql_query("INSERT INTO `Servers` (`ip`, `desc`, `type`, `title`, `email`) VALUES('".$ip."', '".$desc."', '".$type."', '".$title."', '".$email."')") or die(mysql_error()); 
    }
}
$get_all = mysql_query("SELECT * FROM Servers");
while ($row = mysql_fetch_assoc($get_all)) {
?>
<div class="servbox">
<center>
<h1><?php echo $row['title']?></h1><br /></center>
IP: <span class="ip"><?php echo $row['ip']?></span><br /><hr />
<p><?php echo $row['desc']?></p><br /><br />
<a href="http://bobcraft-games.com/TSL/page.php?id=<?php echo $row['id'] ?>">Server's Page</a><br />
Type: <?php echo $row['type']?><br /><br />
</div>
<div class="filler"></div>
<?php
}
?>
</div>
</body>
</html>

Well, what I'm trying to do is restrict users from posting invalid blank fields.

For whatever reason, however, those measures if (strlen($submittedValue) == 0) or whatever.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Adam M
  • 113
  • 3
  • 13

4 Answers4

2

You could better use empty() function in PHP, which checks whether a variable is empty (like strlen(...) == 0)

And: don't forget mysql_real_escape_string() for variables!

Dion
  • 3,145
  • 20
  • 37
1

Use trim() to disappear any spaces at the beginning and the end of string. Use empty(trim($var)) to check.

Since you are using utf-8 when you want to count characters use mb_strlen() http://php.net/manual/en/function.mb-strlen.php

makriria
  • 383
  • 1
  • 9
  • Ok, thanks! But, does trim() not remove other spaces or just at the end/start of a string? – Adam M Feb 04 '12 at 12:18
  • Some info on character encodings: http://www.joelonsoftware.com/articles/Unicode.html and php with utf-8: http://www.phpwact.org/php/i18n/utf-8#strlen – JW. Feb 04 '12 at 16:58
  • [trim()](http://php.net/manual/en/function.trim.php) Strips whitespace (or other characters) from the beginning and end of a string. It leaves 'whitespace that occurs between non-whitespace characters' alone. – JW. Feb 04 '12 at 17:01
0

Why not simply use if (!$somestring) ...? An empty string will test as false, and so will null -- so as long as $somestring cannot be set to a non-string value (other than null) that should work fine. For example:

...
    if (!$title) {
        die("Invalid title!");
    }
    else if (strlen($title) >= 51) {
        die("Invalid title!");
    }
...

This will also catch cases where no $title was submitted in the form data.

You should also escape your strings when using them in your query. So:

mysql_query("INSERT INTO `Servers` (`ip`, `desc`, `type`, `title`, `email`) VALUES('".$ip."', '".$desc."', '".$type."', '".$title."', '".$email."')") or die(mysql_error()); 

..should become:

mysql_query("INSERT INTO `Servers` (`ip`, `desc`, `type`, `title`, `email`) VALUES('".
  mysql_real_escape_string($ip)."', '".
  mysql_real_escape_string($desc)."', '".
  mysql_real_escape_string($type)."', '".
  mysql_real_escape_string($title)."', '".
  mysql_real_escape_string($email)."')")
or die(mysql_error()); 

Otherwise people could mess with your query by placing SQL-significant characters and code into the form data.

Dmitri
  • 9,175
  • 2
  • 27
  • 34
  • Ok, thanks! Will it effect if I put mysql_real_escape_string(ltrim($_POST['email'])); for example as the $email variable? (Also, I've added some strpos's to see if the IP & email have some specific characters, it's at http://pastebin.com/gmMXHLWg) – Adam M Feb 04 '12 at 12:19
  • Something like `$email=mysql_real_escape_string(trim($_POST['email']));` would be fine, but if checking the format of the email address you may want to do that before you escape the string -- just be sure it's escaped before it's put into the query. Also, you don't need to use `addslashes()` **and** `mysql_real_escape_string()` -- just one will do, or you'll escape the escape sequences (it's best to use the string escape function for your database type, eg. `mysql_real_escape_string()` for MySQL). – Dmitri Feb 04 '12 at 16:11
0

So, strlen() is not co-operating.

If some code is not co-operating I do the following things:

  • Isolate the problem. By getting rid of everything that is not relevant.
  • Try and clarify what it should be doing by asking myself "What are my assertions and expectations?".
  • Write a specific script to test the misbehaving code
  • Modify the misbehaving code until it behaves as expected (making it more testable if required).

Isolate the problem. By getting rid of everything that is not relevant.

First, lets take your original example code and get rid of everything that is irrelavent to the specific question in hand. To leave the just the validation code that uses strlen. So, we can see more clearly now.

Then, try and clarify what it should be doing by asking "What are my assertions and expectations?".

I guess from your code that:

  • given
    • a field of information (title, name, ip)
    • and its minimum length
    • and its maximum length
  • when a value is submitted by a user that has a length less than the minimum length or greater than its maximum length
    • then, the value should be rejected
  • else its ok

Write a specific script to test the misbehaving code

I'd create a html/php script purely for testing out the php that's causing grief. Call it FormFieldValidatorTest.php for example. The test script sits with the web site project but is only meant to be run by me. So, I'd put it into a password protected directory or some other place that's inaccessible to the public.

I want a UTF-8 html page that submits some strings of known length. Say, the letter 'a' which we know is one character long and an 'empty field' which we know is zero characters long.

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>My Test</title>
</head>
<body>

<form action="" method="get">
<input name="singleCharacterString" type="text" value="a" size="1" maxlength="1">
<input name="emptyString" type="text" value="" size="1" >
<input type="submit" name="Submit" value="Run Tests">
</form>
...

Then, at the top of FormFieldValidatorTest.php I'd write some php code to:

  • recieve those fixed, hard-coded, form values (fixture data).
  • pass the fixture data to some tests, and then
  • give me back an array of results

To keep things clean I'd put the guts of the test code into a function, call it runTestSuite(), which will be fleshed-out below.

<?php 
//FormFieldValidatorTest.php

if ( $_REQUEST['Submit'] == 'Run Tests' ) {

    //perform the tests
    $testResultsArray = runTestSuite();

    /**
    * Lets loop through the results and count the failures
    */
    $failureCounter = 0;
    foreach ( $testResultsArray as $result ) {
        if ( $result['hasFailed'] ) {
            $failureCounter++;
        }   
    }
} else {
    //no tests were run set the results to null
    $testResultsArray = null;
}

?>
<html>
...

I'd add some php into the html part of FormFieldValidatorTest.php. This should print out the results if they were run (i.e when $testResultsArray is not identical to null):

<?php
//FormFieldValidatorTest.php
...
?>
<html>
...
<body>
<form action="" method="get">
...
</form>

<?php if ( $testResultsArray !== null ) : ?>
    <h1>Results</h1>
    <pre>
    <?php if ( $failureCounter > 0 ) : ?>
        Failed. <?php echo $failureCounter ?> tests failed.
        <?php print_r( $testResultsArray ); ?>
    <?php else : ?>
        Passed
    <?php endif; ?>
    </pre>
<?php endif; ?>
</body>
</html>

Then, I'd fill out the guts of my runTestSuite() function. This is basically two calls to an assertTrue() function which I flesh out later.

  • First, it asserts that it is True that strlen returns 1 when given the singleCharacterString.
  • Then, it asserts that it is True that strlen returns 0 when given the emptyString.

At the same time it passes a message that describes each test. These are stored in the results array to aid debugging if the tests fail.


/**
* @desc A suite of tests to excercise that troublesome code
* @return array of results for each test done
*/
function runTestSuite() {

    /**
    * Lets Run some tests
    */

    /**
    * Create the results array
    */
    $testResultsArray = array();

    /**
    * Test some known data submitted via a form parameter 
    */
    assertTrue( 
        ( strlen( $_REQUEST['singleCharacterString'] ) == 1 ), 
        'Expect it to be TRUE that singleCharacterString http parameter
        has a stringlength of 1', 
        $testResultsArray );

    /**
    * @todo Add more tests here. 
    */
    assertTrue( 
        ( strlen( $_REQUEST['emptyString'] ) == 0 ), 
        'Expect it to be TRUE that emptyString http parameter
        has a stringlength of 0', 
        $testResultsArray );

    return $testResultsArray;
}

Finally, I flesh out the assertTrue() function. This basically tests if the first argument has Failed the assertion test. It then appends that result and the message as a record in the $testResultsArray.

/**
* @desc A TRUE assertion
* @param mixed - a value that we expect to be true
* @param string - a message to help understand what we are testing
* @param array - a collection of results so far passed by reference
* @return void
*/
function assertTrue( $value, $message, &$testResultsArray ) {   

    if ( $value ) {
        //value is as asserted
        $currentResult = array(         
            'message' => $message,
            'hasFailed' => FALSE
            );
    } else {
        //value is not as asserted
        $currentResult = array(
            'message' => $message,
            'hasFailed' => TRUE
            );
    }   

    $testResultsArray[] = $currentResult;   

}

Now the test script is complete. See it here.

If I run it and see it pass, I can be sure that strlen is co-operating. If its failing I can proceed to:

Modify the misbehaving code until it behaves as expected.

In order to do this you'd probably need to be able to split the misbehaving bit into a seperate function or class which sits in its own file. This makes it re-usable so it can be invoked from both the test code and the live production code.

What we really ought to be testing is the length rule which specifies that only strings of certain lengths are allowed.

So, lets isolate that so we can test it. Maybe a function in a rules library called isCorrectLength() in a file called ServerValidationRules.php

<?php 
//ServerValidationRules.php

/**
* @desc boolean function to test string length 
* @param string to test
* @param integer defining minimum length required
* @param integer defining maximum length required
* @return TRUE if its the correct length, FALSE otherwise.
*/
function isCorrectLength( $string, $minLength, $maxLength ) {

    if ( strlen( $string ) < $minLength ) {
        //its too short
        return FALSE;
    }

    if ( strlen( $string ) > $maxLength ) {
        //its too long
        return FALSE;
    }
    return TRUE;
}

We can then replace the code in our test to use that instead of the native strlen() function.

<?php
//FormFieldValidatorTest.php

require_once('ServerValidationRules.php');

...

/**
* @desc A suite of tests to excercise that troublesome code
* @return array of results for each test done
*/
function runTestSuite() {


    $testResultsArray = array();

    /**
    * Test some known data submitted via a form parameter 
    */
    assertTrue( 
        //( strlen( $_REQUEST['singleCharacterString'] ) == 1 ), 
        isCorrectLength( $_REQUEST['singleCharacterString'], 0, 1 ),
        'Expect it to be TRUE that singleCharacterString http parameter
        has a stringlength of 1', 
        $testResultsArray );

    /**
    * @todo Add more tests here. 
    */
    assertTrue( 
        //( strlen( $_REQUEST['emptyString'] ) == 0 ), 
        isCorrectLength( $_REQUEST['emptyString'], 0, 0 ),
        'Expect it to be TRUE that emptyString http parameter
        has a stringlength of 0', 
        $testResultsArray );

    return $testResultsArray;
}
...

So, we can now replace the code in the production script to:

<title>Terraria Server List</title>
...
<?php
...
if ( $submit ) {    
    if (!( isCorrectLength( $title, 0, 50 ) )) {
        die("Invalid title!");
    }    
    elseif (!( isCorrectLength($ip, 0, 50) )) {
        die("Invalid IP!");
    }
    elseif (!( isCorrectLength( $desc, 0, 10000 ) )) {
        die("Invalid description!");
    }    
    elseif (!( isCorrectLength( $email, 0, 100 ) )) {
        die("Invalid E-Mail!");
    }
    else {
        //do the insert
    }
}
...

Or, better still, move the length rules into a single function so that the logic can re-used by other code (i.e. some code in the test suite).

We could wrap all these up into a function called isServerFieldsValid and put it into our ServerValidationRules.php file.

<?php
//ServerValidationRules.php
...
/**
* @desc tests user-submitted fields appending feedback to an array of messages upon failure.
* @param associative array of Posted values keyed by field name
* @param array of messages passed by reference
* @return boolean True if all fields are valid. False otherwise.
*/
function isServerFieldsValid( $values, &$messages ) {   

    $hasFailed = FALSE;

    if (!( isCorrectLength( $values['title'], 1, 50 ) )) {
        $hasFailed = TRUE;
        $messages[] = "Invalid title!";
    }    

    if (!( isCorrectLength($values['ip'], 1, 50) )) {
        $hasFailed = TRUE;
        $messages[] = "Invalid IP!";    
    }

    if (!( isCorrectLength( $values['desc'], 1, 1000 ) )) {
        $hasFailed = TRUE;
        $messages[] = "Invalid description!";
    }    

    if (!( isCorrectLength( $values['email'], 1, 100 ) )) {
        $hasFailed = TRUE;
        $messages[] = "Invalid E-Mail!";
    }

    if ( $hasFailed ) {
        return FALSE;
    } 
    //else
    return TRUE;
}

and then add some some more assertions to the test suite function in our test script.

...

/**
* @desc A suite of tests to excercise that troublesome code
* @return array of results for each test done
*/
function runTestSuite() {

    /**
    * Initialize the results array
    */
    $testResultsArray = array();

    ...

    /**
    * Test some variants of possible user submitted data
    * 
    * @todo We ought to invoke an assertFalse() function.
    *  In meantime, hack it by passing a negated value to assertTrue().
    */

    /**
    * When given values that are too long, 
    * expect a validation failure.
    */
    $validationMessages = array();
    $result = isServerFieldsValid(
            array(
                'title' => str_repeat( 'a' , 51 ),
                'ip' => str_repeat( 'a' , 51 ),
                'desc' => str_repeat( 'a' , 1001 ),
                //?'type' => str_repeat( 'a' , 1001 ),
                'email' => str_repeat( 'a' , 101 ),
                ),
            $validationMessages
            );

    assertTrue(                 
        (!( $result )), 
        'Expect it to be TRUE that result is False when given long values',
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid title!', $validationMessages ), 
        'Expect messages to say "Invalid title!"', 
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid IP!', $validationMessages ), 
        'Expect messages to say "Invalid IP!"', 
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid description!', $validationMessages ), 
        'Expect messages to say "Invalid description!"', 
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid E-Mail!', $validationMessages ), 
        'Expect messages to say "Invalid E-Mail!"', 
        $testResultsArray );

    /**
    * When given values that are too short,
    *  expect a validation failure.
    */
    $validationMessages = array();
    $result = isServerFieldsValid(
            array(
                'title' => null,
                'ip' => null,
                'desc' => null,             
                'email' => null,
                ),
            $validationMessages
            );

    assertTrue(                 
        (!( $result )), 
        'Expect it to be TRUE that result is False when given short values',
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid title!', $validationMessages ), 
        'Expect messages to say "Invalid title!"', 
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid IP!', $validationMessages ), 
        'Expect messages to say "Invalid IP!"', 
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid description!', $validationMessages ), 
        'Expect messages to say "Invalid description!"', 
        $testResultsArray );

    assertTrue(                 
        in_array( 'Invalid E-Mail!', $validationMessages ), 
        'Expect messages to say "Invalid E-Mail!"', 
        $testResultsArray );

    /**
    * When given values that are the correct length,
    *  expect a validation success.
    */
    $validationMessages = array();
    $result = isServerFieldsValid(
            array(
                'title' => 'a title',
                'ip' => 'an ip',
                'desc' => 'a desc',             
                'email' => 'an email',
                ),
            $validationMessages
            );

    assertTrue(                 
        ( $result ), 
        'Expect it to be TRUE that result is True when given correct values',
        $testResultsArray );

    assertTrue(                 
        (!( in_array( 'Invalid title!', $validationMessages ) )), 
        'Expect messages NOT to say "Invalid title!"', 
        $testResultsArray );

    assertTrue(                 
        (!( in_array( 'Invalid IP!', $validationMessages ) )), 
        'Expect messages NOT to say "Invalid IP!"', 
        $testResultsArray );

    assertTrue(                 
        (!( in_array( 'Invalid description!', $validationMessages ) )), 
        'Expect messages NOT to say "Invalid description!"', 
        $testResultsArray );

    assertTrue(                 
        (!( in_array( 'Invalid E-Mail!', $validationMessages ) )), 
        'Expect messages NOT to say "Invalid E-Mail!"', 
        $testResultsArray );


    return $testResultsArray;
}

...

So, the complete test script now looks like this.

So, if that passes, we can modify the production code by replacing all those if (strlen ) { die } statements with a call to that new, well tested, isServerFieldsValid() function:

<title>Terraria Server List</title>

...

if ( $submit ) {
    $messages = array();
    if (!( isServerFieldsValid( $_POST, $messages ) )) {
        echo 'Invalid data was submitted:' . PHP_EOL;

        foreach( $messages as $message ) {
            echo $message . PHP_EOL;
        }

        exit;
    } else {
        //do the insert
} 
...

Well, that's how I'd deal with code that is not co-operating anyway.

The code :

Note:

It took me a couple of hours to write this answer-up, but very little time to write the actual test. Once you get into the habit, it often takes just a few minutes to scribble out a test and find out why some code is not co-operating.

Tip: You don't need to write your own assertion functions. See: http://www.simpletest.org/en/start-testing.html

JW.
  • 4,821
  • 5
  • 43
  • 60