Getting the position of elements on the screen

I have a game where I move a target and drop it on foils. I know where I put the foils, but I wanted to know where they are in relation to the window if the window is scrolled or resized. This isn’t exactly what I was needing, but it’s useful enough that I might need it in the future.


// Find the position of elements
function getOffset(element) {
    element = element.getBoundingClientRect();
  return {
    left: element.left + window.scrollX,
    right: element.right + window.scrollX,
    top: element.top + window.scrollY,
    bottom: element.bottom + window.scrollY
  }
}
function whereAreTheFoils() {
    // Find out where things are for initial setup and debugging
    var foil0 = document.getElementById('foil_0');
    var foil1 = document.getElementById('foil_1');
    var foil2 = document.getElementById('foil_2');
    var foil3 = document.getElementById('foil_3');
    console.log('Foil 0 is at LR ' + getOffset(foil0).left + ', ' + getOffset(foil0).right );
    console.log('Foil 0 is at TB ' + getOffset(foil0).top + ', ' + getOffset(foil0).bottom );

    console.log('Foil 1 is at ' + getOffset(foil1).left + ', ' + getOffset(foil1).top );
    console.log('Foil 2 is at ' + getOffset(foil2).left + ', ' + getOffset(foil2).top );
    console.log('Foil 3 is at ' + getOffset(foil3).left + ', ' + getOffset(foil3).top );
}

Redirecting missing links

I just updated a site so that it works better on phones and tablets. While updating I also cleaned up a lot of old code. In the process of cleaning up many of the old pages are no longer valid. Rather than redirecting to a generic page, I can redirect certain links to specific missing link pages. This is what I did for old manuals pages that I moved.


<?php
$oldurl = $_SERVER['REQUEST_URI'];
// Redirect requests for manuals
switch ($oldurl) {
  case '/products/mobile/manual_1.php';
  case '/products/mobile/manual_2.php';
  case '/products/mobile/manual_3.php';
  case '/products/mobile/manual_4.php';
  etc.

    header("Location: https://www.wellgolly.com/products/manuals_mobile.php",TRUE,301);
    break;
}
?>
<div  class="centered">
<h1>Error 404<br />Document not found</h1>
    <img class='pure-img-responsive centered' src='/images/sad.png' alt='Sad' />
    <p>We’re sorry, but the page you requested could not be found.<br />
    Please use the menus at the top or bottom of this page to find what you are looking for.</p>

</div>

By parsing the $oldurl, you can redirect missing files from sections of your site to a missing page that can help your visitors find what they are looking for rather than just a generic page.

Apache error.log Warning

I get a bunch of these warnings in my logs and wondered what they were.


Command line: '/usr/sbin/apache2'
Loading CGI at runtime.  You could increase shared memory between Apache processes by preloading it in your httpd.conf or handler.pl file

From a Mason mailing list it looks like it is caused when Mason is loaded each time it is needed rather than when Apache starts.
“may be a recommendation that comes from HTML::Mason::ApacheHandler2.”

I have one set of pages only site that use Mason so I’m not going to worry about it.

A note on PDO prepared statements

It wasn’t obvious to me that you need to have a different bindParameter statement each time you use a variable in the query. This won’t work.


// query for title information
$qry  = "SELECT * ";
$qry .= "FROM `website_database`.`product`, `website_database`.`product_instance` ";
$qry .= "WHERE product.id = :productID ";
$qry .= "AND product_instance.product_id = :productID";

$stmt = $dbWG->prepare($qry);
$stmt->bindParam(':productID', $productID);
$stmt->execute();

MySql doesn’t look for every instance of :productID in the query and substitute $productID. This is how you do it:


// query for title information
$qry  = "SELECT * ";
$qry .= "FROM `website_database`.`product`, `website_database`.`product_instance` ";
$qry .= "WHERE product.id = :productID1 ";
$qry .= "AND product_instance.product_id = :productID2";

$stmt = $dbWG->prepare($qry);
$stmt->bindParam(':productID1', $productID);
$stmt->bindParam(':productID2', $productID);
$stmt->execute();

Sanitizing Database Query Input

As part of my site rewrite and migration to MySQL PDO I am making sure that all of the input is sanitized before using—which has the side effect of stopping injection attempts, as discussed in the previous post—and either using prepared statements or whitelisted inputs.

Here’s the sanitizing portion of a crossword solver page. The input is the number of letters in the word and up to 14 letters. There should only be one letter in each space, the space can be empty, and the numbers can be from 1 to 14. I haven’t had any attacks on my forms yet, so I’ll assume any invalid input is due to fat fingers and make reasonable changes.


<?php
$MAX_LETTERS = 14;
$letters = array();

// Read in the letters and number of letters first so you can repopulate the fields.
// If it’s not a single letter in the letter field, or a valid number in the number field,
// don’t let it into the query.
// Log it in case we get lots of injection attempts.
if(isset($_POST)) {
    $submitType = $_POST['submitType'];

    if ($submitType != 'Clear') {
        // Get and validate letters
        for($i = 1; $i <= $MAX_LETTERS; $i++) {
            $letters[$i] = $_POST['letter' . $i];
            $letters[$i] = str_replace(" ","",$letters[$i]);

            if (!preg_match("/^[a-zA-Z]$/",$letters[$i]) && ($letters[$i] <> '') ) {
                if ($showError) error_log("Not a letter {$letters[$i]} in $calledFileName");
                $letters[$i] = "";
            }
        }
        // Get and validate the number of letters
        $num_letters = (integer)$_POST['num_letters'];
        if (!is_integer($num_letters) ) {
            if ($showError) error_log("Not a number in $calledFileName");
        }
        $num_letters = (integer)$num_letters;
        if ( $num_letters > $MAX_LETTERS ) {
            $num_letters = $MAX_LETTERS;
            if ($showError) error_log("Too many numbers in $calledFileName");
        } else if ($num_letters < 0 ) {
            $num_letters = $num_letters * -1;
            if ($showError) error_log("Negative number in $calledFileName");
        }
    }
}

Note that I don’t use htmlspecialchars or mysql_real_escape_string when getting input because I explicitly allow only letters or numbers when validating the output. I don’t think that they would hurt anything, but they aren’t necessary.


$letters[$i] = htmlspecialchars($_POST['letter' . $i]);
$letters[$i] = mysql_real_escape_string($_POST['letter' . $i]);

Some of my pages allow more than one letter in each input space. I just add .* to the pre_match to allow more than one letter. I also allow a wildcard, *, in the web page so I need to escape it in the pre_match.


if (!preg_match("/[a-zA-Z\*].*/",$letters[$i]) && ($letters[$i] <> '') ) {

The safest way to sanitize input is to whitelist the query. There are lots of ways to do this. One way is to construct the query based on the input.


switch ($loc) {
    case "I":
    case "i":
        $location = "Initial";
        $searchString = "^{$letters}[A-Z ]*";
        break;
    case "M":
    case "m":
        $location = "Medial";
        $searchString = "[A-Z ]+{$letters}[A-Z ]+";
        break;
    case "F":
    case "f":
        $location = "Final";
        $searchString = "[A-Z ]*{$letters}\$";
        break;
.....

The user provides a location—initial, medial, or final—and I construct the search string based on their input. No injection is possible because the user input is not seen by the search query.

Another example of whitelisting is to only allow certain values. It works if the list is small and not changed often.


if ($input == 'first value' || $input == 'second value' || $input == 'third value') {
    -- do stuff with the database
} else {
   include_once('dieInAFire');
}

You can do something similar by checking whether the input is part of a hard-coded array. The problem with the last two approaches is that they only work well if the list of acceptable values doesn’t change often. They can work to sanitize user input for things like states and provinces, occupation, and taxable status.