Jump to content

Sanitizer help


SamC
 Share

Recommended Posts

So I've got my search form working ok now. I haven't run any of this through sanitizer yet though and could do with help choosing which functions are required. I'm over here having a read through them: https://processwire.com/api/variables/sanitizer/

The items  I GET are:

// search.php
// GET towns array
$towns = $input->get->town; // array - maybe $sanitizer->array($value)
// GET building type
$buildingType = $input->get->type; // dropdown select. Is a string? $sanitizer->text($value) perhaps or $sanitizer->string($value)
// GET min area
$minArea = $input->get->minArea; // integer - maybe $sanitizer->int($value)
// GET max area
$maxArea = $input->get->maxArea; // integer - maybe $sanitizer->int($value)

Not entirely sure which to choose. The checkboxes and dropdowns are created from values entered in the admin area so I guess they could be entered incorrectly. Does that mean I should run sanitizer on the select list options too on my actual form before they are populated? 

Thanks for any advice. I can post the entire code of search-form.php and search.php if necessary, I thought that might distract a bit in this post.

 

search-form.php:

search-form.png

 

search.php:

search-results.png

Link to comment
Share on other sites

Thanks guys. Not sure I get how to use this though:

// Return $value if it exists in the $allowed array of values, or null if it doesn't.
$sanitizer->option($value, $allowed)

// Return array of given $values that that also exist in $allowed array whitelist of values.
$sanitizer->options($values, $allowed)

Do I make a whitelist?

The code for the search.php is:

<?php
    // catchall selector for empty fields
    $theSelector = "template=building-entry";

    // start building selectors
    $sorting = "sort=addressBuildingTown";
    $townSelector = "addressBuildingTown";
    $buildingTypeSelector = "buildingType";
    $areaSelector = "buildingArea";


    // used to output 'filtered by' above results
    $summary = [
        "towns" => "", 
        "type" => "",
        "min" => "", 
        "max" => "", 
        ];
    
    // GET towns array
    $towns = $input->get->town;
    // GET building type
    $buildingType = $input->get->type;
    // GET min area
    $minArea = $input->get->minArea;
    // GET max area
    $maxArea = $input->get->maxArea;

    // if a town has been selected
    if (count($towns)) {
        
        foreach ($towns as $town) {
            $townSelectorPart .= $town . '|';
        }

        $townSelectorPart = rtrim($townSelectorPart, '|');
        $summary["towns"] = $townSelectorPart;
        $theSelector .= "," . $townSelector . "=" . $townSelectorPart;
    }

    // if a building type has been selected
    if ($buildingType) {
        $summary["type"] = $buildingType;
        $theSelector .= "," . $buildingTypeSelector . "=" . $buildingType;
    }

    // if min or max area has been selected
    if ($minArea || $maxArea) {
        // if min area selected
        if ($minArea) {
            $summary["min"] = $minArea;
            $minAreaSelectorPart = $areaSelector . ">={$minArea}";
            $theSelector .= "," . $minAreaSelectorPart;
        }

        // if max area selected
        if ($maxArea) {
            $summary["max"] = $maxArea;
            $maxAreaSelectorPart = $areaSelector . "<={$maxArea}";
            $theSelector .= "," . $maxAreaSelectorPart;
        }
    }

    echo "<h1>Summary array: ";
    print_r($summary);
    echo "</h1>";

?>



<?php
    echo "Selector used for this page: " . $theSelector;
?>

<h1>Filtered by: <br>

<?php

    $str = "";
    
    if ($summary["towns"]) {
        $str .= "Towns: " . str_replace("|", ", ", $summary["towns"]) . "<br>";
    }

    if($summary["type"]) {
        $str .= "Type: " . $summary["type"] . "<br>";
    }

    if ($summary["min"]) {
        $str .= "Min area: " . $summary["min"] . "<br>";
    }

    if($summary["max"]) {
        $str .= "Max area: " . $summary["max"] . "<br>";
    }

    echo $str;
?>

</h1>  

<?php
    $buildings = $pages->find($theSelector . "," . $sorting);
    foreach ($buildings as $building):
?>

<h2><?php echo $building->title; ?></h2>

<?php if ($building->addressBuildingName): ?>
    <p>BUILDING NAME: <?php echo $building->addressBuildingName; ?></p>
<?php endif; ?>

<p>ADDRESS: 
<?php echo $building->addressBuildingNumber . ' ' . $building->addressBuildingStreet . ', '; ?>
<a href='#'><?php echo $building->addressBuildingTown; ?></a>
</p>

<p>TYPE: <a href='#'><?php echo $building->buildingType->title; ?></a></p>

<p>AREA: <?php echo $building->buildingArea; ?></p>

<?php echo $building->buidingDescription; ?>

<?php endforeach; ?>

 

Link to comment
Share on other sites

$allowed is a simple array, where you put the values, which the form does present, e.g.

<?php
$values = [500 => "500 sq ft.", 1000 => "1000 sq ft."];
?>
<select>
  <?php foreach($values as $value => $label) : ?>
  <option value="<?=$value?>"><?=$label?></option>
</select>
<?php
// With the same $values array
$allowed = array_keys($values); // [500, 1000]
$minArea = $input->get->options("minArea", $allowed);

 

Link to comment
Share on other sites

My selects are populated with this data in search-form.php

<?php
    // get all buildings
    $buildings = $pages->find("template=building-entry");

    // set up variables
    $towns = [];
    $buildingTypes = [];
    $minAreas = [
        '500',
        '1000',
        '2000',
        '3000',
        '4000',
        '5000',
        '6000',
        '7000',
        '8000',
        '9000',
        '10000',
        '50000'
        ];

    $maxAreas = [
        '500',
        '1000',
        '2000',
        '3000',
        '4000',
        '5000',
        '6000',
        '7000',
        '8000',
        '9000',
        '10000',
        '60000',
        '100000'
        ];


    //get unique towns into array
    foreach ($buildings as $building) {
  
        if(!in_array($building->addressBuildingTown, $towns)) {
            $towns[] = $building->addressBuildingTown;
        }

        $uniqueTowns = $towns;
        sort($uniqueTowns);
  
        //get unique building types into array
        if(!in_array($building->buildingType->title, $buildingTypes)) {
            $buildingTypes[] = $building->buildingType->title;
        }

        $uniqueBuildingTypes = $buildingTypes;
        sort($uniqueBuildingTypes);
    }
?>

<form id='building_search' method='get' action='<?php echo $config->urls->root?>search/'>

    <h3>Building search</h3>
    <p>
        <label for='search_town'>Filter by town</label><br>
            <?php
                foreach ($uniqueTowns as $town) {
                    echo "<input type='checkbox' name='town[]' value='{$town}'>{$town}<br>";
                }
            ?>
    </p>


    <p>
        <label for='search_type'>Building type</label>
        <select id='search_type' name='type'>
            <option value=''>Any</option>

            <?php
                foreach ($uniqueBuildingTypes as $type) {
                    echo "<option value='{$type}'>{$type}</option>";            
                }
            ?>
        </select>
    </p>


    <p>
    <label for='search_min_area'>Min area</label>
    <select id='search_min_area' name='minArea'>
        <option value=''>No min</option>

        <?php 
            foreach($minAreas as $minArea) {
                $formatMinArea = number_format($minArea);
                echo "<option value='{$minArea}'>{$formatMinArea} sq ft.</option>";
            }
        ?>

    </select>
    </p>


    <p>
    <label for='search_max_area'>Max area</label>
    <select id='search_max_area' name='maxArea'>
        <option value=''>No max</option>

        <?php 
            foreach($maxAreas as $maxArea) {
                $formatMaxArea = number_format($maxArea);
                echo "<option value='{$maxArea}'>{$formatMaxArea} sq ft.</option>";
            }
        ?>

    </select>
    </p>


    <p><button type='submit' id='search_submit' name='submit' value='1'>Search</button></</p>

</form>

Looks like I'm missing something in my $minAreas and $maxAreas arrays. So are we saying I should change the key/values to:

// FROM
   $minAreas = [
        '500',
        '1000',
        '2000',
        '3000',
        '4000',
        '5000',
        '6000',
        '7000',
        '8000',
        '9000',
        '10000',
        '50000'
        ];

// TO
$minAreas = [
        500 => '500 sq ft',
        1000 => '1000 sq ft',
        2000 => '2000 sq ft',
        3000 => '3000 sq ft',
        4000 => '4000 sq ft',
        5000 => '5000 sq ft',
        6000 => '6000 sq ft',
        7000 => '7000 sq ft',
        8000 => '8000 sq ft',
        9000 => '9000 sq ft',
        10000 => '10000 sq ft',
        50000 => '50000 sq ft'
        ];

// use keys to make whitelist
$allowed = array_keys($minAreas); // [500, 1000]

// on search.php
$minArea = $input->get->options("minArea", $allowed);

Where does sanitizer come in here? Maybe I'm just being slow but still not fully getting this.
 

 

Link to comment
Share on other sites

array_keys() will just revert your array back to the form of just the sq ft "[500, 1000, …]" so no need to change that one. Just make sure you have all the possible keys of your form inputs accessable when sanitizing. It doesn't matter where those keys come from or how the arrays are created.

6 minutes ago, SamC said:

Where does sanitizer come in here? Maybe I'm just being slow but still not fully getting this.

It's just a shortcut. Both versions below do the same.

$minArea = $input->get->options("minArea", $allowed);
$minArea = $sanitizer->options($input->get->minArea, $allowed);

Edit: Also there's no magic whitelist anywhere. This is all plain php and using arrays.

  • Like 1
Link to comment
Share on other sites

Ok, so I did this (parts from two different files but run in the following order):

$minAreas = [
    500 => '500 sq ft',
    1000 => '1,000 sq ft',
    2000 => '2,000 sq ft',
    3000 => '3,000 sq ft',
    4000 => '4,000 sq ft',
    5000 => '5,000 sq ft',
    6000 => '6,000 sq ft',
    7000 => '7,000 sq ft',
    8000 => '8,000 sq ft',
    9000 => '9,000 sq ft',
    10000 => '10,000 sq ft',
    50000 => '50,000 sq ft'
    ];

<label for='search_min_area'>Min area</label>
    <select id='search_min_area' name='minArea'>
        <option value=''>No min</option>
        <?php 
            foreach($minAreas as $value => $label) {
                echo "<option value='{$value}'>{$label}</option>";
            }
        ?>
    </select>

// GET (sanitized) min area
$allowedMinAreas = array_keys($minAreas);
// Return $input->get->minArea if it exists in the $allowedMinAreas array of values, or null if it doesn't.
$minArea = $sanitizer->option($input->get->minArea, $allowedMinAreas);

Now, I get the following results after submitting the form, and secondly after changing the value in the URL itself:
 

search/?type=&minArea=4000
// Selector used for this page: template=building-entry,buildingArea>=4000


// after changing URL manually to a number NOT in the 'Min area' select list
// a user can't change the values in the list, but they can modify the URL
search/?type=&minArea=4200
// Selector used for this page: template=building-entry

So I think it's working correctly :)

Two things I still don't understand. What am I sanitizing? There's no input from a user on a select list. Am I correct in assuming it's to stop someone from changing the value in the URL (like I did above in my second result).

Secondly, in the case of my checkboxes, those town names are obtained from text fields within the CMS. If a building-entry exists, and has a unique town name, it ends up as a new checkbox (the building-entry becomes searchable). So in this case, when do I sanitize? BEFORE the checkboxes are even rendered on the screen to a user? Or only when obtaining the values via $input->get? Or both even?

Quote from $sanitizer api page: https://processwire.com/api/variables/sanitizer/#options-that-may-be-provided-to-the-text-and-textarea-functions

Quote

Always sanitize/filter any data you get from $input->get, $input->post, $input->cookie (and PHP's $_GET, $_POST, $_COOKIE if you use them).

I think these are the only issues I have with sanitizer so far, otherwise, pleased to have learned something new! Getting there, slowly but surely.

 

Link to comment
Share on other sites

45 minutes ago, SamC said:

So in this case, when do I sanitize?

You only sanitize the user input. So anything coming in via GET/POST/… data. Anything else comes from your system and your system should never have data in it, which aren't "save", so no need to sanitize anything there. 

48 minutes ago, SamC said:

Am I correct in assuming it's to stop someone from changing the value in the URL (like I did above in my second result).

It's not only about plainly changing the number to another one. People try to inject all manner of things into websites, like sql statements or else. So it's for security as well as for preventing errors in your system because of values you didn't expect.

  • Like 2
Link to comment
Share on other sites

@LostKobrakai thanks you're a gent. I applied similar functions to my other GET values:

// GET towns array
//
// this one freaks if no towns are selected
// Fatal error: Uncaught TypeError: Argument 1 passed to ProcessWire\Sanitizer::options() must
// be of the type array, null given
// 
$allowedTowns = array_values($uniqueTowns);
$towns = $sanitizer->options($input->get->town, $allowedTowns);

// GET building type
$allowedTypes = array_values($uniqueBuildingTypes);
$buildingType = $sanitizer->option($input->get->type, $allowedTypes);

// GET (sanitized) min area
$allowedMinAreas = array_keys($minAreas);
$minArea = $sanitizer->option($input->get->minArea, $allowedMinAreas);

// GET max area
$allowedMaxAreas = array_keys($maxAreas);
$maxArea = $sanitizer->option($input->get->maxArea, $allowedMaxAreas);

So, nearly works.

--EDIT--

I was thinking:

// GET towns array
if($input->get->town) {
    $allowedTowns = array_values($uniqueTowns);
    $towns = $sanitizer->options($input->get->town, $allowedTowns);
}

...but the the code inside the if() statement isn't sanitized.

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...