php mysql prevent duplicate inserts from rapid form submits - php

I'm testing with this code:
<?php
$db = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
if(filter_has_var(INPUT_POST, 'submit')) {
$r = $db->query('SELECT * FROM test WHERE value="'.$_POST['value'].'"');
$n = $r->rowCount();
for ($i=0; $i<=10000000; $i++) {
// do nothing
}
if (!$n) {
$db->query('INSERT INTO test (id, value) VALUES (NULL, "'.$_POST['value'].'")');
}
}
?>
<form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">
<input type="text" name="value" value="">
<input type="submit" name="submit" value="Submit">
</form>
When testing the above form in safari I am able to submit the same value into my database multiple times by rapidly clicking the submit button.
But, when I test with this code:
<?php
$db = new PDO('mysql:host=localhost;dbname=test', 'root', 'root');
if(filter_has_var(INPUT_POST, 'submit')) {
for ($i=0; $i<=10000000; $i++) {
// do nothing
}
$r = $db->query('SELECT * FROM test WHERE value="'.$_POST['value'].'"');
$n = $r->rowCount();
if (!$n) {
$db->query('INSERT INTO test (id, value) VALUES (NULL, "'.$_POST['value'].'")');
}
}
?>
The conditional works and I'm unable to submit the same value multiple times no matter how fast I click the submit button.
Is there a reliable way to prevent duplicate database inserts from occurring with the first code snippet so that it behaves just like the second snippet?

Generally a client side onsubmit handler can help stop accidental rapid-submissions, but I usually opt for a server side solution using _SESSION variables. Just create a hash of the $_POST array, store it as a session variable, and then compare the next submitted _POST to the hashed version:
session_start();
//if this is the first time they've posted, just create a placeholder session var
if (!isset($_SESSION['repost_hash'])){$_SESSION['repost_hash']="";}
//if the post array is a duplicate, nullify the submission
if (isset($_POST) && md5(serialize($_POST)) == $_SESSION['repost_hash']){
unset($_POST);
} else { //otherwise, if this is new data, update the stored hash
$_SESSION['repost_hash'] = md5(serialize($_POST));
}
...
If you only want to stop repeat-submissions for a short period of time, you can also have a second $_SESSION variable store the time of the last hash, so you can expire it after a second or two.

Related

SQL query results are different than varibles

I want to set a url parameter by using uniqid function in php, I get the unique numbers and place them in my database by useing them in a hidden input form. I try to make it so, at the start of the script $number is set to a uniqid which I placed in the hidden input so it will be posted into the database and I can use the same variable to create a href link.
The problem I'm having is that the value stored in my database is not the same as the value stored in the number variable used in the href link which renders the link useless. How do I get both the values equal is there a better way to do what I'm trying to do?
I have tried putting uniqid() in a function
<?php
$servername = "localhost";
$username = "root";
$password = "";
$homeDB = "homeDB";
$conn = new mysqli($servername, $username, $password, $homeDB);
if($conn->connect_error) {
die("failed to connect to server".$conn->connect_error);
}
$number = uniqid();
if(isset($_POST["namn"])) {
$sql = "INSERT INTO information (firstname, lastname, urlID)
VALUES ('".$_POST["namn"]."','".$_POST["efternamn"]."',
'".$_POST["hide"]."')";
if($conn->query($sql)== TRUE){
$link = "http://localhost/sqltutorial/execute.php?id=".$number;
} else {
echo "failed";
}
echo $link;
}
html
<html>
<body>
<form method="post" action="home.php">
<input type="text" name="namn"> <br>
<input type="text" name= "efternamn"><br>
<input type="hidden" value="<?php $number ?>" name="hide">
<input type="submit" >
</form>
<br>
</body>
</html>
I get different values on the link that is echoed and the value stored in my database ( I know this form is not secure )
I think you just need to use the $_POST['hide'] value on the link.
It would also be better to echo the link only if it has been created.
Where you have the echo currently, it is possible to echo the $link variable even if it was not been created!
<?php
$servername = "localhost";
$username = "root";
$password = "";
$homeDB = "homeDB";
$conn = new mysqli($servername, $username, $password, $homeDB);
if($conn->connect_error) {
die("failed to connect to server".$conn->connect_error);
}
$number = uniqid();
if(isset($_POST["namn"])) {
$sql = "INSERT INTO information (firstname, lastname, urlID)
VALUES ('".$_POST["namn"]."','".$_POST["efternamn"]."',
'".$_POST["hide"]."')";
if($conn->query($sql)== TRUE){
$link = "http://localhost/sqltutorial/execute.php?id=$_POST[hide]";
// line moved to here
echo $link;
} else {
echo "failed";
}
}
The problem is that when the postback runs, you also run the line $number = uniqid(); again. So the final number which is output is not the one you placed in the hidden field.
Now, you could write
$link = "http://localhost/sqltutorial/execute.php?id=".$_POST["hide"];
and it would output the number which was passed in the POST variable.
Or you could just wait until the postback has happened to generate the unique ID, and use that in both the database call and the output. This saves a) a round-trip for the variable to the browser and back to the server, and b) anyone trying to tamper with the form data. So move the number creation code inside the if:
if(isset($_POST["namn"])) {
$number = uniqid();
...and then replace both references to $_POST["hide"] with $number instead. You can also remove the hidden field from your form.
One final alternative suggestion: Do you even need to do this? I assume your database table has an auto_increment integer field as the primary key? Why not just use the value already being generated by the database as the value for your link?
if($conn->query($sql)== TRUE){
$link = "http://localhost/sqltutorial/execute.php?id=".$conn->insert_id;
would get the auto-generated ID of the last row you inserted and use that in the link instead. See also documentation
I don't see any great purpose in creating a second ID for your row (especially since uniqid() does not promise to always give you a completely unique value), unless you have some specific reason?
So, you want to create a row and redirect on that link after creating.
Steps:
1) First get the next auto increment value for this informations table by this function and store it in $number.
$stmt = $this->db->prepare("SHOW TABLE STATUS LIKE 'informations'");
$stmt->execute();
$result = $stmt->fetchAll();
foreach ($result as $row) {
$number = $row[10];
}
2) Now do inserting, and after insert you'll get the same autoincrement ID and do everything with that.
Hope, it will help.
NB: You can make a function to grab that auto Increment ID for any table also.

Accessing a sql database with two different functions

I'm making a movie rating website for a project and how to do the rating system has left me at a blank. Please let me know of a proper way to this if you know.
This gets the movie number from the url and displays the relevant information in the page
<body>
<?php
global $conn;
$conn = mysqli_connect('localhost','root','','filmsdb');
function show()
{
global $film;
global $conn;
$film = $_GET['fm'];
$sql = "SELECT * FROM movies WHERE m_No='$film'";
$ok = mysqli_query($conn,$sql);
$data = mysqli_fetch_array($ok);
$c_r= $data[8];
$c_rc= $data[9];
?>
//displays the movie information and uses radio buttons to get user rating
Then this lets the user rate the movie
<?php
}
function act1()
{
if(isset($_POST['rsub']))
{
global $film;
global $conn;
$rate = $_POST['rate'];
$sqlr= "UPDATE movies SET rating=rating+$rate, rate_count=rate_count+1 WHERE m_No='$film'";
$output = mysqli_query($conn,$sqlr);
}
if($output==1)
{
echo 'Data Stored';
}
else
{
echo 'Data Not Stored ';
echo mysqli_error($conn);
}
}
$conn = null;
?>
</body>
</html>
when only the first function is being used, it works, but when I try to use the rating system, this error comes in the browser, mysqli_query() expects parameter 1 to be mysqli, null given... Any idea on a workaround for this?
Your issue is that the two variables you're relying on with the DB connection, $conn and $film, do not exist when the page has posted back the user rating data.
Your application's lifecycle goes like this:
1) User makes initial request. PHP starts and runs the first code block, it echoes some values to the page, page is returned to the user. Once the page is returned, the request is complete and PHP stops executing. All variables declared and in memory are lost because the process has stopped running.
2) The page returned from the PHP script arrives in the user's browser. User enters their rating and posts the data back to the server. This constitutes an entirely new request.
3) The new request arrives at the server. PHP starts up again. The web is inherently stateless, so by default it remembers nothing of the previous request. Certainly not the names or values in any in-memory variables - the process that contained them died long ago and has no association with the new one.
Therefore, if you have any values that you need to use again in PHP for the second request, you can either create them again, or receive them in the request data, or the first PHP script must have stored them somewhere persistent that you can retrieve them from, such as a session variable or cookie, or database.
It's not clear from your posted code, but presumably in the second request the function act1() gets called somehow and tries to insert the data into the database. It fails because neither $film or $conn have any values in them in this new request.
I suggest you solve it like this:
1) Create your connection object again, this is easy, and you need to re-connect to MySQL for this request anyway.
2) the film you're rating should be passed back from the browser in the form data.
This is the first script, to get the initial film data and render the ratings form to the page.
//re-usable function to connect to DB. Maybe move this out to a separate file so all pages can use it.
function getDBConn() {
return mysqli_connect('localhost','root','','filmsdb');
}
function show()
{
$conn = getDBConn();
$film = $_GET['fm'];
$sql = "SELECT * FROM movies WHERE m_No='$film'";
$ok = mysqli_query($conn,$sql);
$data = mysqli_fetch_array($ok);
$c_r= $data[8];
$c_rc= $data[9];
$conn = null;
}
Your latest update doesn't show the form but I'm going to assume it's something like this, with an additional film hidden field. There should be suitable form tags around it as well.
<input type="radio" value="1" name="rate">
<input type="radio" value="2" name="rate">
<input type="radio" value="3" name="rate">
<input type="radio" value="4" name="rate"><input type="radio" value="5" name="rate">
<input type="hidden" name="film" value="<?php echo $film;?>"/>
<input type="submit" value="Rate" name="rsub">
Now is the second script, to be run when the rating data is submitted. You haven't shown how act1() is called but I'll assume you've got that covered.
function act1()
{
if(isset($_POST['rsub']))
{
$film = $_POST['film']; //get the film ID from the submitted form
$conn = getDBConn(); //assuming this script is in the same .php file as the first block, otherwise you'll need to move getDBConn into a separate php file and then include the file in each script.
$rate = $_POST['rate'];
$sqlr = "UPDATE movies SET rating=rating+$rate, rate_count=rate_count+1 WHERE m_No='$film'";
$output = mysqli_query($conn, $sqlr);
}
if ($output==1)
{
echo 'Data Stored';
}
else
{
echo 'Data Not Stored';
echo mysqli_error($conn);
}
$conn = null;
}
P.S. I know it's just an example project, but if you make a real-life site please heed the comments above re SQL injection, and don't let your applications and websites log into your DB as "root" either - give them only the privileges they actually need.

Sending data to database only after submit has been clicked on a form that posts to same page

I have a form that posts to the same page because I need the values to display below after submit has been clicked, which it does. The problem is that as soon as the page is loaded, the php runs and sends the data to the database instantly, so it sends an empty value to the database since the user has not submitted anything.
$servername = "localhost";
$username = "my_username";
$password = "my_password";
$dbname = "my_database";
try {
$conn = new PDO("mysql:host=$servername;dbname=$dbname", $username, $password);
// set the PDO error mode to exception
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// prepare sql and bind parameters
$stmt = $conn->prepare("INSERT INTO my_table (firstname)
VALUES (:firstname)");
$stmt->bindParam(':firstname', $firstname);
// insert a row
$firstname = $name;
$stmt->execute();
echo "New records created successfully";
}
catch(PDOException $e) {
echo "Error: " . $e->getMessage();
}
$conn = null;
?>
<form method="post" id="nick-form" action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]);?>">
Name: <input type="text" name="name" value="<?php echo $name;?>">
<input type="submit" name="submit" value="Submit">
</form>
<?php
echo "<h2>Your Input:</h2>";
echo $name;
?>
I would like the $name variable to only get sent when the user hits submit, if possible.
"I would like the $name variable to only get sent when the user hits submit, if possible."
Use a conditional isset() with your submit button.
<?php
if(isset($_POST['submit']))
{
// code to execute
}
Sidenote: You could/should also add an !empty() on your inputs also, which is highly recommended in order to prevent empty submissions.
You could also implement a header upon successful submission to redirect to another page:
header('Location: http://www.example.com/');
exit; // avoid further execution of code, if any resides below that
http://php.net/manual/en/function.header.php
Just make sure you're not outputting before header if you plan on using it.
Here's an article on Stack about this:
How to fix "Headers already sent" error in PHP
There is also a good article on how to prevent multiple submits using sessions and tokens:
http://www.phpro.org/tutorials/Preventing-Multiple-Submits.html
Something I have used in the past with success which could be useful.
What you have is a possible checking clause with an if statement using
if (count($_POST) > 0) {
//code runs if POST is submitted data
if (!empty($_POST['name'])){
///process the name form field value
}
}
Which would solve your issue, BUT when the page is refreshed by the user, the refreshed page will also resubmit the POSTED data , this is why on database activity pages it is HIGHLY advisable to send the data to another page, and then once the data is saved, return the browser to the original page, so refreshing the original page does not resubmit the POSTED data
To illustrate further, make another PHP file called "save.php" and everything in the PHP tags ABOVE the <form> element, put in the save.php file, then set the form to goto save.php and at the bottom of the save.php set a header("location:formpage.php");die(); to return to the form.
You will still need a database call on the form page to display the desired output. But this will prevent resubmitting of data upon page refresh
You can use if :
if(isset($_POST['name']) && $_POST['name'] != null) {
// Your code
}
You should also check $_POST['submit'].

Prevent Double Form Submit using Tokens

I am trying to prevent the user from double submitting the forum by adding token hidden field.
So here is what I have done so far (before the forum loads I have this code to create a token with the current time as a value.
$token = time();
setcookie('formToken', $token, time() + 3600);
in my forum I have a hidden input like this
<form method="post" action="'.$PHP_SELF.'?action=update">
<input type="hidden" name="token" value="'.$token.'" />
<input type="submit" value="go" />
</form>
now on the top of my page where $action == "update" I have this code
if(isset($_POST) && ($_POST['token'] != $_COOKIE['formToken'])){
$error_list .= '<li>You can not submit this forum twise.</li>';
}
if i hit F5 to refresh the page it submit the form again without displaying my error.
I suggest you to use use the PRG pattern (Post/Redirect/Get), which is also implemented by forums like phpbb.
Post/Redirect/Get (PRG) is a web development design pattern that
prevents some duplicate form submissions, creating a more intuitive
interface for user agents (users). PRG implements bookmarks and the
refresh button in a predictable way that does not create duplicate
form submissions.
gd1 answer will not prevent double click submission or accidental double submit by various jQuery bindings on a complex javascript form code.
Double click may be even faster then disabling submit button, or hiding it with javascript, so this would not be a full answer either.
The session token will not work either because session is not yet written and thus available or updated for the second process which may be just milliseconds away sharing the same session ID. The session is stored only upon completion of the fist process.
Cookie technique could be an answer as far as both processes are able to communicate over cookie in a blocking way, which may result to the same problems as the session sharing above.
The best solution would be to use server's shared memory access to check if the other process had already processed the data (order, payment, etc..) with the pregenerated data hash, or use database table blocking select and insert to check if the pregenerated hash has been already submitted.
Why not just set a session when the form is successfully submitted?
so $_SESSION['submitted'] = 1;
Then you can check for it.
Or Do
if(isset($_POST['submit']) && ($_POST['token'] != $_COOKIE['formToken'])){
$error_list .= '<li>You can not submit this forum twice.</li>';
}
Suggestion 1)
on Successful Submission Delete the cookies (removeTokens)
function removeToken()
{
//set formToken cookie val to "" (or any default xxxx) and the past expiry date for it
setcookie("formToken", "", time()-3600);
//try to unset - this is not needed ,we may try it
unset($_COOKIE['formToken']);
}
ie simply on your page if(isset($_POST)) removeToken();
Suggestion 2)
Perform a redirect as suggested by Tom Wright here Avoiding form resubmit in php when pressing f5
header('Location: formsubmitSucess.php');
I use this way of preventing double form submissions, it has worked on all occasions so far. Let me know if you need additional questions as this tutorial assumes you have intermediate knowledge on database and PHP.
STEP 1 : add a field on your database like this:
replace YOUR-TABLE with the name of your database table.
ALTER TABLE `YOUR-TABLE` ADD `token` VARCHAR(35) NULL DEFAULT NULL AFTER `creationtoken`, ADD UNIQUE (`token`) ;
STEP 2 on your form page you add this to the very first line:
it will create a unique toke that will be inserted into your database table along with you query, so that it can be checked for later to make sure no other like it is submitted into your database, meaning no double form submissions.
<?php
session_start();
date_default_timezone_set('America/Chicago');
$_SESSION['token'] = md5(session_id() . time());
?>
then just before your submit button add this:
// add this before the submit button
// this will post the unique token to the processing page.
<div style="width:100%; color:#C00; font-weight:normal;">Session Token: <?php echo strtolower($_SESSION['token']) ?></div>
<input type="hidden" name="token" id="token" value="<?php echo $_SESSION['token']?>" />
// add this before the submit button
<input type="submit" id="submit" name="submit" class="button" value="Submit" />
STEP 3: now on your process.php page
//this is where all of your form processing takes place.
// this is where you call the database
// if you need the database file let me know...
include("../common/databaseclass.php");
$db= new database();
//here the token is posted then the database table is checked and
//if the form has already been added it will return a 1 and will
//cause the query to die and echo the error message.
$token = $_POST['token'];
$query = "SELECT token FROM YOURTABLE WHERE token = '$token' LIMIT 1";
$result = $db->query($query);
$num = mysql_num_rows($result);
if ($num>0) {die('your form has already been submitted, thank you');}
else {
$host = "localhost";
$user = "user";
$pass = "password";
$db_name = "database";
mysql_connect($host,$user,$pass);
#mysql_select_db($db_name) or die( "Unable to select database");
// table query
$sql1="INSERT INTO YOURTABLE (
`token`,
`user`,
`email`,
`password`,
`newaccount`,
`zipcode`,
`city`,
`state`,
`country`,
`telephone`,
`creationip`,
`createdaccount`
)
VALUES (
'$token',
'$username',
'$email',
'$password',
'$newaccount',
'$zipcode',
'$city',
'$state',
'$country',
'$phone',
'$ipadress',
'$createdaccount'
)";
$db->query($sql1);
header("location:" http://home.php ");
}
For the same issue I made a code to use it for my own stuff. It has the PRG pattern and flexible to use it on same page or with extern PHP file for redirection - Easy to use and safe, maybe this might help you.
class unPOSTer {
private
$post = "KEEP_POST";
public function __construct(string $name = null) {
if (version_compare(PHP_VERSION, "5.4.0") >= 0) {
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
} else {
if (!$_SESSION) {
session_start();
}
}
$this->post = $name;
}
public function unPost() {
if (session_status() !== PHP_SESSION_ACTIVE) {
session_start();
} elseif (strcasecmp($_SERVER["REQUEST_METHOD"],"POST") === 0) {
$_SESSION[$this->post] = $_POST;
header("Location: " . $_SERVER["PHP_SELF"] . "?" . $_SERVER["QUERY_STRING"]);
exit;
} elseif (isset($_SESSION[$this->post])) {
$_POST = $_SESSION[$this->post];
}
}
public function retrieve($data) {
if (isset($_SESSION[$this->post])) {
$posts = #$_SESSION[$this->post][$data];
if (isset($posts)) {
return $posts;
} else {
return null;
}
}
}
public function reset() {
if (isset($_SESSION[$this->post])) {
unset($_SESSION[$this->post]);
}
}
}
Then use it like this:
<?php
require_once "unPOSTer.class.php";
$unpost = new unPOSTer();
$unpost->unPost();
?>
<form action='' method=POST>
<input type=text name=fname value="<?php echo $unpost->retrieve("fname"); ?>" placeholder="First Name">
<input type=text name=lname value="<?php echo $unpost->retrieve("lname"); ?>" placeholder="Last Name">
<input type=submit name=send value=Send>
</form>
<?php echo $unpost->reset(); ?>
Not much to configure, do it on every page you send form data if you like. The retrieve() method spits out the data you have sent, in case if you might go back and fix something. Feel free to fork/pull it at my GitHub page I added 2 demos there.
I had the same problem, here is a simple fix:
if(!empty($_SESSION['form_token']) && time() - $_SESSION['form_token'] < 3){
$data['message'] = 'try again later';
return;
}
$_SESSION['form_token'] = time();
In my case the PRG pattern didn't have any effect since form submitted multiple times at the same time and the code had not been executed and there is no data saved to compare it against.

How to reserve a MySQL row and update

I was asked for a feature that I'm not sure how to accomplish. Have a form page where a phone number is entered - I need to get that phone number into a row on a button submit, and in the same form about 5-10 minutes later fill out the rest of the options and update that same row. Not sure how to do that. I was thinking to use mysql_insert_id() / last_insert_id()..
">
<?php
$hostname = "*";
$username = "*";
$password = "*";
$EmpID = $_COOKIE["dsmkttrackerid"];
mysql_connect($hostname, $username, $password) or die(mysql_error());
mysql_select_db("*");
$sql="SELECT * FROM User WHERE EmpID='".$EmpID."'";
$r = mysql_query($sql);
if(mysql_affected_rows()==0){
}
else {
$row = mysql_fetch_array($r);
$NT_Login = $row['NT_Login'];
$Job = $row['Job'];
if ($Job == "MADMIN" || $Job == "ADMIN" || $Job == "MPLT") {
$Pilot=true;
}
}
date_default_timezone_set('America/Chicago');
$date = date("Y-m-d",time());
$time = date("H:i:s",time());
if ($_POST[Comment]) {
$_POST[Comment] = mysql_real_escape_string($_POST[Comment]);
$PHONE = mysql_real_escape_string($_POST[PHONE]);
//check if dup post, caused by refreshing page.
$sql="Select * from Data where Comment='$_POST[Comment]'";
if (mysql_num_rows(mysql_query($sql)) <1) {
$sql="INSERT INTO Data(`Date`, `Time`, `EmpID`, `PHONE`, `Comment`,
`OrigMention`, `OrigSent`, `PostMention`, `Mod`, `Pilot`)
VALUES('$date','$time','$_POST[EmpID]','$_POST[PHONE]','$_POST[Comment]',
'$_POST[OrigMention]','$_POST[OrigSent]','$_POST[PostMention]',
'$_POST[Mod]','$_POST[Pilot]')";
if (!mysql_query($sql))
{
die('Please report this error to your supervisor: <br />'
. mysql_error());
}
else {
$Msg = "Post #".mysql_insert_id()." Tracked Successfully.";
}
}
else {
//if dup is found:
$Msg ="Duplicate Entry Detected.";
}
}
else {
//if no post was sent to the server:
$Msg ="";
}
?>
Get the ID using mysql_insert_id() as you've done. Store it in the session, and you can reference it for the rest of the form.
As tandu said, you can use mysql_insert_id() to get the id, then store it in the session, and reference it later. I caution, however, if you use the default PHP sessions (via session_start() and $_SESSION and the like), the session will not exist forever, so it's possible, if they leave their computer and come back a few hours later (eg, coming back from lunch), the would have to start the process all over.
I think it's best to have your own session handlers (in my personal experience, this has proven to be more controllable, and secure anyway). Another option, however, is to send the id back to the user as a hidden value in the form, such as:
<form ...>
<input name="dbid" value="<?=$id?>" type="hidden" />
<!-- the rest of the form.... -->
If you don't want to reveal the dbid to a potential malicious user, then this is out of the question. And, strictly speaking, I think this is bad practice. However, it is another option that works.
EDIT: Yet another option is to use cookies

Categories