I have a website with a simple contact form. The validation is somewhat minimal because it doesn't go into a database; just an email. The form works as such:
There are 5 fields - 4 of which are required. The submit is disabled until the 4 fields are valid, and then you can submit it. Then everything is validated on the server again, including the recaptcha (which is not validated by me client side). The whole process is done with ajax, and there are multiple tests that must pass on the server side or 4** headers are returned, and the fail callback handler is called.
Everything works like gangbusters on Chrome on the desktop (I haven't tried other browsers, but I can't imagine why they'd be different), but on the iPhone the reCaptcha always validates even if I don't check the box for the test.
In other words: I still have to fill out the four values correctly in order to submit, but if I don't check the box for the reCaptcha, the request still succeeds.
I can post some code if anyone thinks that would be helpful, but it appears that the problem is with the device and not with the code. Does anyone have any insight into this?
Note: The server side is PHP/Apache if this is helpful.
Update: 5/28/2015:
I'm still debugging this, but it seems like Mobile Safari is ignoring my response headers on my iPhone. When I output the response to the page what I get on Desktop for (data,status,xhr)
is:
data
: my response which at this point just says error or success -> error
status
: error
xhr
: {'error',400,'error'}
On Mobile safari:
data
: error
status
: success
xhr
: {'error',200,'success'}
So - it seems like it's just ignoring my response headers. I tried explicitly setting {"headers":{"cache-control":"no-cache"}}
but to no avail.
Update: 6/3/2015
Per Request, here is the code. This is almost certainly more than you need. It has also become more obtuse because of the changes I've made to try and fix it. Also note that, while it may appear that there are variables that haven't been defined, they (should) have been defined in other files.
The client side
$('#submit').on('click', function(e) {
$(this).parents('form').find('input').each(function() {
$(this).trigger('blur');
})
var $btn = $(this);
$btn = $btn.button('loading');
var dfr = $.Deferred();
if ($(this).attr('disabled') || $(this).hasClass('disabled')) {
e.preventDefault();
e.stopImmediatePropagation();
dfr.reject();
return false;
} else {
var input = $('form').serializeArray();
var obj = {},
j;
$.each(input, function(i, a) {
if (a.name === 'person-name') {
obj.name = a.value;
} else if (a.name === 'company-name') {
obj.company_name = a.value;
} else {
j = a.name.replace(/(g-)(.*)(-response)/g, '$2');
obj[j] = a.value;
}
});
obj.action = 'recaptcha-js';
obj.remoteIp = rc.remoteiP;
rc.data = obj;
var request = $.ajax({
url: rc.ajaxurl,
type: 'post',
data: obj,
headers: {
'cache-control': 'no-cache'
}
});
var success = function(data) {
$btn.data('loadingText', 'Success');
$btn.button('reset');
$('#submit').addClass('btn-success').removeClass('btn-default');
$btn.button('loading');
dfr.resolve(data);
};
var fail = function(data) {
var reason = JSON.parse(data.responseText).reason;
$btn.delay(1000).button('reset');
switch (reason) {
case 'Recaptcha Failed':
case 'Recaptcha Not Checked':
case 'One Or more validator fields not valid or not filled out':
case 'One Or more validator fields is invalid':
// reset recaptcha
if ($('#submit').data('tries')) {
$('#submit').remove();
$('.g-recaptcha').parent().addBack().remove();
myPopover('Your request is invalid. Please reload the page to try again.');
} else {
$('#submit').data('tries', 1);
grecaptcha.reset();
myPopover('One or more of your entries are invalid. Please make corrections and try again.');
}
break;
default:
// reset page
$('#submit').remove();
$('.g-recaptcha').remove();
myPopover('There was a problem with your request. Please reload the page and try again.');
break;
}
dfr.reject(data);
};
request.done(success);
request.fail(fail);
}
The Server:
function _send_email(){
$recaptcha=false;
/* * */
if(isset($_POST['recaptcha'])):
$gRecaptchaResponse=$_POST['recaptcha'];
$remoteIp=isset($_POST['remoteIp']) ? $_POST['remoteIp'] : false;
/* ** */
if(!$remoteIp):
$response=array('status_code'=>'409','reason'=>'remoteIP not set');
echo json_encode($response);
http_response_code(409);
exit();
endif;
/* ** */
/* ** */
if($gRecaptchaResponse==''):
$response=array('status_code'=>'400','reason'=>'Recaptcha Failed');
echo json_encode($response);
http_response_code(400);
exit();
endif;
/* ** */
if($recaptcha=recaptcha_test($gRecaptchaResponse,$remoteIp)):
$recaptcha=true;
/* ** */
else:
$response=array('status_code'=>'400','reason'=>'Recaptcha Failed');
echo json_encode($response);
http_response_code(400);
exit();
endif;
/* ** */
/* * */
else:
$response=array('status_code'=>'400','reason'=>'Recaptcha Not Checked');
echo json_encode($response);
http_response_code(400);
exit();
endif;
/* * */
/* * */
if($recaptcha==1):
$name=isset($_POST['name']) ? $_POST['name'] : false;
$company_name=isset($_POST['company_name']) ? $_POST['company_name'] : false;
$phone=isset($_POST['phone']) ? $_POST['phone'] : false;
$email=isset($_POST['email']) ? $_POST['email'] : false;
/* ** */
if(isset($_POST['questions'])):
$questions=$_POST['questions']=='' ? 1 : $_POST['questions'];
/* *** */
if(!$questions=filter_var($questions,FILTER_SANITIZE_SPECIAL_CHARS)):
$response=array('status_code'=>'400','reason'=>'$questions could not be sanitized');
echo json_encode($response);
http_response_code(400);
exit();
endif;
/* *** */
/* ** */
else:
$questions=true;
endif;
/* ** */
/* ** */
if( count( array_filter( array( $name,$company_name,$phone,$email ),"filter_false" ) ) !=4 ):
$response=array('status_code'=>'400','reason'=>'One Or more validator fields not valid or not filled out');
echo json_encode($response);
http_response_code(400);
exit();
endif;
/* ** */
$company_name=filter_var($company_name,FILTER_SANITIZE_SPECIAL_CHARS);
$name=filter_var($name,FILTER_SANITIZE_SPECIAL_CHARS);
$phone=preg_replace('/[^0-9+-]/', '', $phone);
$email=filter_var($email,FILTER_VALIDATE_EMAIL);
/* ** */
if($company_name && $recaptcha && $name && $phone && $email && $questions):
$phone_str='Phone: ' . $phone;
$company_str='Company: ' . $company_name;
$email_str='Email String: ' . $email;
$name_str='Name: '.$name;
$questions=$questions==1 ? '' : $questions;
$body="$name_str\r\n\r\n$company_str\r\n\r\n$email_str\r\n\r\n$phone_str\r\n\r\n____________________\r\n\r\n$questions";
$mymail='[email protected]';
$headers = array();
$headers[] = "MIME-Version: 1.0";
$headers[] = "Content-type: text/plain; charset=\"utf-8\"";
$headers[] = "From: $email";
$headers[] = "X-Mailer: PHP/" . phpversion();
/* *** */
if(mail('$mymail', 'Information Request from: ' . $name,$body,implode("\r\n",$headers))):
$response=array('status_code'=>'200','reason'=>'Sent !');
echo json_encode($response);
http_response_code(200);
exit();
/* *** */
else:
$response=array('status_code'=>'400','reason'=>'One Or more validator fields is invalid');
echo json_encode($response);
http_response_code(400);
exit();
endif;
/* *** */
endif;
/* ** */
endif;
/* * */
$response=array('status_code'=>'412','reason'=>'There was an unknown error');
echo json_encode($response);
http_response_code(412);
exit();
}
function recaptcha_test($gRecaptchaResponse,$remoteIp){
$secret=$itsasecret; //removed for security;
require TEMPLATE_DIR . '/includes/lib/recaptcha/src/autoload.php';
$recaptcha = new \ReCaptcha\ReCaptcha($secret);
$resp = $recaptcha->verify($gRecaptchaResponse, $remoteIp);
if ($resp->isSuccess()) {
return true;
// verified!
} else {
$errors = $resp->getErrorCodes();
return false;
}
}
Like that question iOS: Authentication using XMLHttpRequest - Handling 401 reponse the easiest way to solve that is disregard natural headers validation and, on the callback sucess, validate with some flag.
I've saw some cases like that and never smell good.