I've searched about a dozen answers on here relating to:
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
}
Yet I still haven't found an answer to why.
Why is this done if we've already set the <form method="post">?
Doesn't that mean that it's the only form method here?
If the user comes from the previous form then the request method is POST indeed. But anyone can make a request to your server, for example via CURL or a custom program. There is no stopping people making random request to your pages.
Therefore you cannot be sure that the request method on the server is indeed POST, and all data is present.
In another context it can be used to check if the form has actually been submitted. For example:
<?php if($_SERVER['REQUEST_METHOD'] == 'POST') { ?> <!-- The server has recieved something via POST! -->
Thank you for submitting the form!
<?php } else { ?> <!-- No postdata, lets show the form! -->
<form method='POST'> <!-- By setting the method we ask that the client does a post request. -->
<input type='submit' />
</form>
<?php } ?>
There are two ways you can send forms from the client to the server: GET and POST. They are defined in RFC 2616 (HTTP), but the difference you can directly see is that GET gets displayed in the URL and POST doesn't.
Keep in mind that this is only for the browser on the client side to decide which way they send content to the server.
Regarding $_SERVER['REQUEST_METHOD']:
Which request method was used to access the page; i.e. 'GET', 'HEAD', 'POST', 'PUT'.
Note: PHP script is terminated after sending headers (it means after producing any output without output buffering) if the request method was HEAD.
One reason why you might want to use
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
}
might be to check if a form was submitted. But keep in mind: People can send POST requests without actually using your form! So you have to check the other data anyway.
Related
Page one contains an HTML form. Page two - the code that handles the submitted data.
The form in page one gets submitted. The browser gets redirected to page two. Page two handles the submitted data.
At this point, if page two gets refreshed, a "Confirm Form Resubmission" alert pops up.
Can this be prevented?
There are 2 approaches people used to take here:
Method 1: Use AJAX + Redirect
This way you post your form in the background using JQuery or something similar to Page2, while the user still sees page1 displayed. Upon successful posting, you redirect the browser to Page2.
Method 2: Post + Redirect to self
This is a common technique on forums. Form on Page1 posts the data to Page2, Page2 processes the data and does what needs to be done, and then it does a HTTP redirect on itself. This way the last "action" the browser remembers is a simple GET on page2, so the form is not being resubmitted upon F5.
You need to use PRG - Post/Redirect/Get pattern and you have just implemented the P of PRG. You need to Redirect. (Now days you do not need redirection at all. See this)
PRG is a web development design pattern that prevents some duplicate form submissions which means, Submit form (Post Request 1) -> Redirect -> Get (Request 2)
Under the hood
Redirect status code - HTTP 1.0 with HTTP 302 or HTTP 1.1 with HTTP 303
An HTTP response with redirect status code will additionally provide a URL in the location header field. The user agent (e.g. a web browser) is invited by a response with this code to make a second, otherwise identical, request to the new URL specified in the location field.
The redirect status code is to ensure that in this situation, the web user's browser can safely refresh the server response without causing the initial HTTP POST request to be resubmitted.
Double Submit Problem
Post/Redirect/Get Solution
Source
Directly, you can't, and that's a good thing. The browser's alert is there for a reason. This thread should answer your question:
Prevent Back button from showing POST confirmation alert
Two key workarounds suggested were the PRG pattern, and an AJAX submit followed by a scripting relocation.
Note that if your method allows for a GET and not a POST submission method, then that would both solve the problem and better fit with convention. Those solutions are provided on the assumption you want/need to POST data.
The only way to be 100% sure the same form never gets submitted twice is to embed a unique identifier in each one you issue and track which ones have been submitted at the server. The pitfall there is that if the user backs up to the page where the form was and enters new data, the same form won't work.
There are two parts to the answer:
Ensure duplicate posts don't mess with your data on the server side. To do this, embed a unique identifier in the post so that you can reject subsequent requests server side. This pattern is called Idempotent Receiver in messaging terms.
Ensure the user isn't bothered by the possibility of duplicate submits by both
redirecting to a GET after the POST (POST redirect GET pattern)
disabling the button using javascript
Nothing you do under 2. will totally prevent duplicate submits. People can click very fast and hackers can post anyway. You always need 1. if you want to be absolutely sure there are no duplicates.
You can use replaceState method of JQuery:
<script>
$(document).ready(function(){
window.history.replaceState('','',window.location.href)
});
</script>
This is the most elegant way to prevent data again after submission due to post back.
Hope this helps.
If you refresh a page with POST data, the browser will confirm your resubmission. If you use GET data, the message will not be displayed. You could also have the second page, after saving the submission, redirect to a third page with no data.
Well I found nobody mentioned this trick.
Without redirection, you can still prevent the form confirmation when refresh.
By default, form code is like this:
<form method="post" action="test.php">
now, change it to
<form method="post" action="test.php?nonsense=1">
You will see the magic.
I guess its because browsers won't trigger the confirmation alert popup if it gets a GET method (query string) in the url.
The PRG pattern can only prevent the resubmission caused by page refreshing. This is not a 100% safe measure.
Usually, I will take actions below to prevent resubmission:
Client Side - Use javascript to prevent duplicate clicks on a button which will trigger form submission. You can just disable the button after the first click.
Server Side - I will calculate a hash on the submitted parameters and save that hash in session or database, so when the duplicated submission was received we can detect the duplication then proper response to the client. However, you can manage to generate a hash at the client side.
In most of the occasions, these measures can help to prevent resubmission.
I really like #Angelin's answer. But if you're dealing with some legacy code where this is not practical, this technique might work for you.
At the top of the file
// Protect against resubmits
if (empty($_POST)) {
$_POST['last_pos_sub'] = time();
} else {
if (isset($_POST['last_pos_sub'])){
if ($_POST['last_pos_sub'] == $_SESSION['curr_pos_sub']) {
redirect back to the file so POST data is not preserved
}
$_SESSION['curr_pos_sub'] = $_POST['last_pos_sub'];
}
}
Then at the end of the form, stick in last_pos_sub as follows:
<input type="hidden" name="last_pos_sub" value=<?php echo $_POST['last_pos_sub']; ?>>
Try tris:
function prevent_multi_submit($excl = "validator") {
$string = "";
foreach ($_POST as $key => $val) {
// this test is to exclude a single variable, f.e. a captcha value
if ($key != $excl) {
$string .= $key . $val;
}
}
if (isset($_SESSION['last'])) {
if ($_SESSION['last'] === md5($string)) {
return false;
} else {
$_SESSION['last'] = md5($string);
return true;
}
} else {
$_SESSION['last'] = md5($string);
return true;
}
}
How to use / example:
if (isset($_POST)) {
if ($_POST['field'] != "") { // place here the form validation and other controls
if (prevent_multi_submit()) { // use the function before you call the database or etc
mysql_query("INSERT INTO table..."); // or send a mail like...
mail($mailto, $sub, $body); // etc
} else {
echo "The form is already processed";
}
} else {
// your error about invalid fields
}
}
Font: https://www.tutdepot.com/prevent-multiple-form-submission/
use js to prevent add data:
if ( window.history.replaceState ) {
window.history.replaceState( null, null, window.location.href );
}
Having some trouble. New to web development. I'm trying to make a simple application that receives data from a Particle Photon board - using a webhook, everytime a particular event occurs on the board's end, a JSON is sent via POST. I'm running an Apache webserver. I want to process the POST request with PHP, and then have that PHP update what the user sees somehow. Right now I have only index.php, with the following:
<html>
<head>
</head>
<body>
<div>
<?php
if ($_SERVER["REQUEST_METHOD"] == "POST")
{
$data = json_decode(file_get_contents("php://input"));
// do something with $data
}
?>
</div>
</body>
</html>
I'm not worried about input validation, I just want the rudimentary functionality. My thought process is that the webhook sends this POST request (it sends it to index.php, and appears to be working), and then the index.php will be triggered and then display the POST request information. I can't seem to get the index.php to receive the POST request. I've tried var_dump, echo, but it all comes back either blank or NULL. Is there something inherently flawed with my understanding of this setup?
This is the JSON that is being sent:
The only file I have on the server is index.php.
From comments:
First of all, prepare a process file, eg.: process.php, in this file you need only php code that check for a post, process it and store relevant data in your database.
<?php
if(isset($_POST)){
// Process post, store relevant data on database
}
Then, with data properly stored on database, you can show html pages to your users, retrieving data from database and showing to them accordingly.
Is it possible to check that the $_GET or $_POST values are submitted from specific page?
For example, there is an ajax in page1 submitted value to page2.php?q=abc, and the page2 only accept the q when it is submitted from page1.
If I directly browse to the page page2.php?q=abc, the php will not run unless I submitted the value from page1.
Is it possible to do that?
Edit 1:
Because I can access the page2 and get the result. Don't mention about the session, because I can validate the session to match my needs and the values submitted to php is valid or not.
What I want is to check if the request is sent from specific page or not. If true, then accept the values and process it, else, redirect to homepage or something else.
Edit 2:
My question is, not only values submitted through Ajax, but also direct access, such as href="page2.php?q=abc". I guess token will be the best way to do that, and the query part will validate again.
There are two security checks you could perform while dealing with AJAX:
1) Check if the request it sent through AJAX:
if ( !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest' )
{
//AJAX Request Detected
}
2) Hashed tokens:
On the page that's holding the AJAX Request, create a token:
session_start();
$hashed='';
$_SESSION['token'] = microtime();
if (defined("CRYPT_BLOWFISH") && CRYPT_BLOWFISH) {
$salt = '$2y$11$' . substr(md5(uniqid(mt_rand(), true)), 0, 22);
$hashed = crypt($_SESSION['token'], $salt);
}
This is using the blowfish algorithm with the crypt() to create hashed string.
Your AJAX function would be like:
$.ajax({
type: "POST",
url: 'page2.php',
data: {
action: '<?php echo $hashed;?>', //pasted the hashed string created in PHP
q: 'test'
},
success: function (data) {}
});
Upto you whether you want to use $_GET or $_POST method.
And then on the second page which is receiving the AJAX request, you do:
session_start();
if(crypt($_SESSION['token'], $_POST['action']) == $_POST['action']){
//Hashed string matches. Request has come from page1.
echo $_POST['q'];
}
in your form you can just add a hidden field and add a page id. On the page that should send post or get request you can do something like
<form action='phpscript.php'>
<input type='hidden' name='page' value='valid_page'>
<input name='your_other_info'>
</form>
In the phpscript.php you can do something like
<?php
//If you have a request, it can be either post or get method
if(isset($_SERVER['REQUEST_METHOD']) && (isset($_POST['page']) || isset($_GET['page']))){
}else{
//Post or get is not from the valid page
}
?>
You cannot restrict the "origin" of the request, because there's no such thing per se. Your "page" isn't sending a request, it's the browser that does it. And the browser may have any number of reasons why it's sending you a request; be that because one of your pages has instructed it to do so or because a user is fiddling around with the Javascript console manually.
All you get on your end is an HTTP request. Go ahead, inspect it. In the browser, look at the network tab and inspect the raw request being sent. It's just a bunch of HTTP headers, nothing more. Anyone can send an HTTP request with arbitrary HTTP headers any time from anywhere and make it look like anything they want. Even the Referer HTTP header is not going to "protect" you.
If you need any sort of protection, you can use session tokens or user authentication to restrict the set of potential entities that can send queries somewhat. But what you have is still by definition a public URL endpoint which answers to arbitrary HTTP queries. You cannot restrict their "origin page". The best you can do is obfuscate it by requiring certain headers to be set (like Referer or X-Requested-With).
When my page does a post, I store all the $_POST data in a separate $_SESSION var. I know that a back button to this same page is by design to show the Document Expired message. My hope is to fool the browser into thinking that there was really never any $_POST data and so not to display the Document Expired message when coming back to it. I am forcing a complete refresh of the page so I am not worried about receiving old data, since I have it stored in the session.
I have tried to unset($_POST) hoping this will stay with the page. but the $_POST data must be cached / stored and returns with a refresh or back button. Is what I am trying to do possible? Any ideas?
* UPDATE *
My solution / answer is posted below. It posts to a separate form, which redirects back to the original form for processing. Not sure why the down vote. It has been working great for months and I no longer receive Document Expired messages. It also prevents duplicate posting.
You can achieve this using the post - redirect - get design pattern.
http://en.wikipedia.org/wiki/Post/Redirect/Get
A more standard way to implement this pattern is to display, validate and save the form on one page.
The following solution has these advantages :
1. All form related code is in one place.
2. Server side validation is simple
I have two pages, form.php and after.php.
form.php :
if(isPosted()){
if(dataIsValid($postedData)){
// dataIsValid should set $message to show to the user
saveData($postedData);
redirect('after.php');
die();
}
} else {
$postedData = $defaultValues;
}
showForm($postedData, $message);
You can add the following to the beginning of your script to solve this issue.
header("Cache-Control: max-age=300, must-revalidate");
Simplest solution that comes to mind? Don't directly do the post, catch the event and submit the form through AJAX. Then on success, redirect.
An example using jQuery:
$('#some_form').submit(function() {
$.post($(this).attr('action'), $(this).serialize(), function() {
window.location = "/some/success/url.php";
});
return false; // Prevent the form submission & any other events triggered on submit
});
Because the POST never got added to the browser history, you won't have that issue.
However, note that the POST url is now different than the one you load; you can make it the same by checking whether a POST or a GET was done server-side, but either way, you have to do a bit of extra work to "remember" the results of the POST.
* UPDATE *
Here's my solution.
I am aware that with post-redirect-get the POST is usually back to the same page for processing before being redirected away from the form with a GET to another destination. However, I need to be able to return to the original page for re-editing (as in a document model where the user can SAVE work in progress). Therefore doing the POST to a second page and redirecting back to the original was my idea of getting rid of the "EXPIRED" message since the editing form would not have post data associated with it. I have extended this (not shown) to include $_FILE and other situations (e.g. using it with a href as well). Not sure why the downvote. This has been working great for months now and accomplishes the task. I no longer receive "Document Expired" messages. In addition, all $_POST processing is accomplished in the original file.
testform.php
<?php
session_start();
if (isset($_GET) && count($_GET)>0){
// process get
var_dump($_GET);
}
if (isset($_SESSION['post-copy'])){
// return post vars to $_POST variable so can process as normal
$_POST = $_SESSION['post-copy'];
// unset the session var - with refresh can't double process
unset($_SESSION['post-copy']);
// process post vars
var_dump($_POST);
}
?>
<form method='post' action='__b.php?redirect=<?php echo $_SERVER['PHP_SELF'] ?>&help=me' enctype='multipart/form-data'>
<textarea name='descr' id='descr'>ABCD</textarea>
<input type='submit' value='Go'>
</form>
redirect.php
<?php
if (!isset($_SESSION)){
session_start();
}
if (isset($_POST)){
$_SESSION['post-copy'] = $_POST;
}
// retrieve the url to return to
if (isset($_GET['redirect'])){
$url = $_GET['redirect'];
}
// if multiple query string parameters passed in get, isolate the redirect so can build querystring with the rest
if (isset($_GET) && count($_GET) > 1){
$get = $_GET;
foreach ($get as $key => $val){
if ($key == 'redirect'){
// remove from rest of passed get query string
unset($get[$key]);
}
}
if (count($get) > 0){
$url .= (strpos($url,"?")===false ? "?" : "&") . http_build_query($get);
}
}
if ($url!==""){
header("Location: " . $url);
}
?>
I want to make sure I'm understanding and applying the token correctly to avoid CSRF?
My data line in my jQuery ajax request:
data:{ Id:getParameterByName("id"), Token:"<?php echo $csfrToken; ?>" },
My PHP check inside the file that handles the posted data:
if (isset($_SESSION['TOKEN']) && $_SESSION['TOKEN'] == $_POST['Token']) { }
Is there anything else I need to do with an ajax request. All I'm doing is creating a token on the page that contains the ajax request. I then post that created token to my ajax handler page and then check to make sure they are the same. Is there anything else that needs to be done?
Looks good what you did. That is how Zend Framework's Zend_Form_Element_Hash does it for forms and this also applys to AJAX requests.