Stealth Submit: Using AJAX to Save Form Data Without Submitting the Form

November 21, 2008 - Reading time: 11 minutes

Ah, this is sneaky. You may think that a web site can’t read form fields until you hit the submit button, but this is not the case. Using AJAX, a site can read form data at any time. This could be used for nefarious purposes, but I’m presenting here for two reasons: 1) to let people know that it can be done and 2) to show people how to do it for legitimate purposes. What’s a legitimate use of this? Logging, mostly… a lot of work goes into studying why users may fill out only the first page of a form. If you log the data regardless of whether a user clicked submit, you can study user behavior and possibly improve your form.

This tutorial assumes the following: you have a web server running PHP 5 (or greater) and MySQL 5 (or greater) with the mysqli extension installed. There are several parts to how this works:

1. savedata.php: A processing page that grabs posted variables (e.g. savedata.php?variable=value).
2. db.php: A database interface file (usually contained above the document root, contains passwords and save logic).
3. index.php: The HTML page containing the form.
4. The prototype JavaScript library. This is a very handy library that’s pretty easy to use.

Here’s an image showing how these files are organized in their folder structure. /html is the document root of the site: all browser requests are directed in there. /lib is above the document root… only PHP can make calls inside that folder. That’s were we put our database connection logic, including usernames and passwords.

Folder structure used for Stealth Submit Files
Folder structure used for Stealth Submit Files

The files used in this demo are listed below, but because scripts tend to get buggy when they’ve undergone conversion to/from html-entities, I’m zipping up the files and uploading them here.
Stealth Submit Sample Files.zip

You should be able to unload the html and lib directories to your web server. Point your DocumentRoot at the html directory; be sure to run the SQL commands included at the top of the db.php file so your database looks the way that this script expects it to.

For the use of visibility, the contents of these files is included below:

html/savedata.php : the PHP Page that grabs and stores variables


<?php
/*
Test this page by hitting it in a browser with variables:
https://your_domain/savedata.php?first_name=Bart&last_name=Simpson&age=11&home_town=Springfield&job=Punk

*Add &debug=1 to the url to print out verbose messages.
*/

include_once($_SERVER['DOCUMENT_ROOT'] . "/../lib/db.php");
$debug = $_REQUEST['debug'];
$debug_msg ="<p>The following variables were passed:</p><hr/>";

// Harvest all form data: this works for posts and gets.
foreach ($_REQUEST as $var => $value) {
$debug_msg .= "<b>$var</b>: $value<br/>";
$form_data[$var] = $value;
}

if ($debug) {
echo $debug_msg . "<hr/>";
}

$result = save_user_data($form_data);

if ($debug) {
if ($result) {
echo "Data saved successfully. Id: $result";
} else {
echo "Error saving data.";
}
}

?>

lib/db.php : Here’s the Database Interface Page


<?php
/*
Contains database handle (username/password) and saving functions

Here's the table definition for the table used in this demo:

CREATE DATABASE ajax_demo;

CREATE TABLE `user` (
`id` int(11) NOT NULL auto_increment,
`first_name` char(64) default NULL,
`last_name` char(64) default NULL,
`age` tinyint(3) default NULL,
`home_town` char(64) default NULL,
`job` char(64) default NULL,
UNIQUE KEY `id` (`id`)
) ENGINE=MyISAM;

GRANT ALL PRIVILEGES ON ajax_demo.* TO 'peter'@'%'
IDENTIFIED BY 'abc123';
*/

// Test regex's here by uncommenting this line and executing this file.
// echo get_name_regex('Bob');
/*-------------------------------------------------------------------------
DEFINE Database constants here
---------------------------------------------------------------------------*/
define("DATABASE_HOST", 'localhost');
define("DATABASE", 'ajax_demo');
define("DEFAULT_USER", 'peter');
define("DEFAULT_USER_PASSWORD", 'abc123');

function connect_db ($user = DEFAULT_USER) {
/*
INPUT: $user
This function allows for multiple handles to be called, e.g. handles for
read-only, write-only, etc. Each user has its own permissions.
mysqli format is mysqli(DATABASE_HOST, USER, PASSWORD, DATABASE);
*/
switch($user) {
case DEFAULT_USER:
$link = new mysqli(DATABASE_HOST, DEFAULT_USER, DEFAULT_USER_PASSWORD, DATABASE);
break;
}

return $link;

}

/*-------------------------------------------------------------------------*/
function save_user_data ($input) {
/*
INPUT:
Hash with values from form, e.g. $input['first_name'].
OUTPUT:
id from database if successful insert; otherwise null.
*/

$link = connect_db();
/* check connection */
if ( mysqli_connect_errno() ) {
printf("Connect failed: %s\n", mysqli_connect_error());
exit();
}

$sql = "INSERT INTO user
(
first_name,
last_name,
home_town,
job,
age
) VALUES (
?,
?,
?,
?,
?
)";

$statement = $link->prepare($sql);

if (!$statement) {
printf('Error - SQLSTATE %s.\n', mysqli_sqlstate($db_connection));
exit();
};

// Filter data (regex's, validate, etc)
$first_name = get_name_regex($input['first_name']);
$last_name = get_name_regex($input['last_name']);
$home_town = get_name_regex($input['home_town']);
$job = get_name_regex($input['job']);
$age = get_integers_only_regex($input['age']);

// Bind-parameters: s=string, i=integer, d=double, b=blob
$statement->bind_param('ssssi', $first_name, $last_name, $home_town, $job, $age);

$statement->execute();

if ($link->error) {
echo $link->error;
}

if ($link->insert_id) {
$result = $link->insert_id;
}

echo

$link->close();

return $result;
}

/*====== DATA FILTERING and VALIDATION =============================*/
function get_name_regex ($input) {
$pattern = '/(;|\||`|=|--|\/|\.|>|<|&|^|"|'."\n|\r".'|{|}|[|]|\)|\(|[0-9])/i';
$input = preg_replace($pattern, ' ', $input);
return trim(ucfirst($input));
}

// Given any input, we only want a valid integer here, e.g. 2.24 --> 2
// Specify the length desired.
function get_integers_only_regex ($input, $len = 100) {
// $input = (int)$input; // typecast will fail if you have a zip w/ a leading 0, e.g. 09921
if ($len > 0) {
$pattern = '/\D/';
$input = preg_replace($pattern, '', $input);
$input = substr($input, 0, $len);
}
return $input;
}
?>

html/index.php : the HTML Page Containing the Form

<html>
<head><title>Stealth Submit</title>

<script src="/js/prototype-1.6.0.3.js"></script>
<script language = "Javascript">
function saveData() {
new Ajax.Request('savedata.php', {
method: 'post',
parameters: {
// $('form_id').serialize(true)
first_name: document.getElementById('first_name').value,
last_name: document.getElementById('last_name').value,
home_town: document.getElementById('home_town').value,
age: document.getElementById('age').value,
job: document.getElementById('job').value
}
});
}

</script>

</head>
<body onunload="saveData()">
<form id="form_id" method="post" action="action.php">
First Name: <input type="text" id="first_name" name="first_name"><br/>
Last Name: <input type="text" id="last_name" name="last_name"><br/>
Age: <input type="text" id="age" name="age"><br/>
Hometown: <input type="text" id="home_town" name="home_town"><br/>
Job: <input type="text" id="job" name="job"><br/>
<input type="submit" value="Submit" />
</form>

<a href="https://www.google.com/">Arbitrary Other Page... FORM NOT SUBMITTED</a>

</body>
</html>


Quick Tip – Force Off-Screen Windows to Return to Your Desktop

November 18, 2008 - Reading time: 2 minutes

We’ve all encountered this problem at one time or another. Perhaps a change of screen resolution caused it, or maybe you disconnected a second monitor without paying attention to window placement. Either way, the problem is that you have one or more windows that are off the screen, with no easy way to click-and-drag them back into place. Frustrating!

Of course, you could just reboot, but what self-respecting nerd would do that? Surely there must be a better way!

There is. The next time you find yourself in that situation, just try this: Read more


Rip DVDs to Video Files Easily with FormatFactory (Windows)

November 17, 2008 - Reading time: 7 minutes

So, you want to rip DVDs to video files on Windows, but you’re not sure where to begin? While there are a ton of guides on the Internet on how to accomplish this task, most of them get complicated really quickly.

In this simple guide, I’m going to show you how to easily rip a DVD to a video file in as few steps as possible. Ready? Let’s get started.

Tools Required

  • A DVD drive for your computer (obviously). If you bought your computer anytime after the year 2000, it probably has one.
  • FormatFactory – FREE media converting software. This is what will do the ripping for us. Version as of this writing – 1.60.
  • DVD43 – FREE decrypting software. Basically, DVD43’s job is to unlock a DVD so that FormatFactory can access it. Read more

Add Watermarks to Video for Free with MPEG Streamclip

November 11, 2008 - Reading time: 3 minutes

UPDATE: Looks like MPEG Streamclip is dead. Pity!

MPEG-Streamclip is a powerful FREE tool for working with video. Not only can it encode/convert between formats, it can also cut, trim, and join movies together. As an added bonus, it can also directly download videos from YouTube and Google Video.

These features alone are enough to make MPEG Streamclip an essential tool for both Windows and Mac users, but the program has another trick up its sleeve: it can also add watermarks to video. I’ll show you how.

Special note for Windows users: MPEG Streamclip requires either Quicktime OR Quicktime Alternative (but not both!). Please see their site for details. Read more


Create Screencasts Easily with Capture Fox (for Firefox)

November 8, 2008 - Reading time: 3 minutes

UPDATE: Capture Fox is dead, Jim.

A while ago I wrote an article on Creating Screencasts on (nearly) Any Operating System. There have been several new developments since I wrote the original article.

One slick program is called Capture Fox. As you might guess from the name, this is a FREE Add-on for Mozilla Firefox. Though limited, it allows you to record the action on your screen in two ways:

  1. Record video of just the Firefox window
  2. Record video of the entire screen

There is currently not an option to record a custom-size area of the screen. On the other hand, it does record audio.

Capture Fox is currently available only for Windows XP/Vista/Server 2008. Read more


Stop SPAM with Disposable E-mail Addresses

October 25, 2008 - Reading time: 10 minutes

I think we can all agree that spam is evil. It’s awful. Deplorable. We all hate it, unless you are a spammer. And if you’re a spammer, you deserve swift, repetitive, merciless kicks in the junk.

Though we may never win the war against spam, we can still fight to reduce it. One effective way is to use a disposable e-mail address. Here’s how it works.

Let’s say you need to provide a functional e-mail address for a temporary purpose – a web form, online shopping at a random store, posting in forums – but don’t want your e-mail address harvested and spammed to death. All you have to do is use a disposable e-mail address from one of the many services listed below, use it temporarily, and then forget about it! The possibilities, and the number of disposable addresses are endless. Read more


About

Tech tips, reviews, tutorials, occasional rants.

Seldom updated.