英文:
Simple PHP contact form vs spam
问题
I understand that you'd like a translation of the code you provided. Here are the code parts without the HTML and explanations:
For the contact page:
<?php
session_start();
$_SESSION['form_time'] = time();
define('SITE_KEY', '...');
define('SECRET_KEY', '...');
if (array_key_exists('send', $_POST)) {
function getCaptcha($SecretKey) {
$Response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=" . SECRET_KEY . "&response={$SecretKey}");
$Return = json_decode($Response);
return $Return;
}
$Return = getCaptcha($_POST['g-recaptcha-response']);
// Mail processing script
$to = 'email1';
$me = 'email2';
$subject = 'Feedback From Website';
// List expected fields
$expected = array('name', 'email', 'question');
// Set required fields
$required = array('name', 'email', 'question');
// Set additional headers
$headers = 'From: Megan Roth<feedback@meganroth.com>';
// Set the include
$process = 'includes/process.inc.php';
if (file_exists($process) && is_readable($process)) {
include($process);
} else {
$mailSent = false;
mail($me, 'Server Problem', "$process cannot be read", $headers);
}
}
?>
For the mail processing script:
<?php
// 30 second minimum
session_start();
$time_limit = 30;
$suspect = false;
if (isset($_SESSION['form_time']) && is_numeric($_SESSION['form_time'])) {
$seconds_passed = time() - $_SESSION['form_time'];
if ($seconds_passed < $time_limit) {
$suspect = true;
}
} else {
$suspect = true;
}
# spam protection
if (isset($_POST["website"]) && $_POST["website"] == '') {
if (isset($_SERVER['SCRIPT_NAME']) && strpos($_SERVER['SCRIPT_NAME'], 'inc.php')) exit;
// Remove escape characters from POST array
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value) {
$value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
return $value;
}
$_POST = array_map('stripslashes_deep', $_POST);
}
// Create an empty array for any missing fields
$missing = array();
// Assume that there is nothing suspect
$suspect = false;
// Create a pattern to locate suspect phrases
$pattern = '/Content-Type:|Bcc:|CC:/i';
// Function to check for suspect phrases
function isSuspect($val, $pattern, &$suspect) {
if (is_array($val)) {
foreach ($val as $item) {
isSuspect($item, $pattern, $suspect);
if ($suspect)
break;
}
} else {
if (preg_match($pattern, $val)) {
$suspect = true;
}
}
}
// Check the $_POST array and any subarrays for suspect content
isSuspect($_POST, $pattern, $suspect);
if ($suspect) {
$mailSent = false;
unset($missing);
} else {
foreach ($_POST as $key => $value) {
$temp = is_array($value) ? $value : trim($value);
if (empty($temp) && in_array($key, $required)) {
array_push($missing, $key);
} elseif (in_array($key, $expected)) {
${$key} = $temp;
}
}
// Validate the email address
if (!empty($email)) {
$checkEmail = '/^[^@]+@[^\s\r\n\';,@%]+$/';
if (!preg_match($checkEmail, $email)) {
$suspect = true;
$mailSent = false;
unset($missing);
}
}
// Validate the comments
$linkOne = false;
$checkCommentsLinks = '/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i';
if (preg_match($checkCommentsLinks, stripcslashes($question))) {
$linkOne = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
$linkTwo = false;
$checkCommentsEmail = '/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/';
if (preg_match($checkCommentsEmail, stripcslashes($question))) {
$linkTwo = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
$linkThree = false;
if (preg_match('/http|www/i', $question)) {
$linkThree = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
if (!$suspect && empty($missing)) {
$message = '';
foreach ($expected as $item) {
if (isset(${$item})) {
$val = ${$item};
} else {
$val = 'Not selected';
}
if (is_array($val)) {
$val = implode(', ', $val);
}
$message .= ucfirst($item) . ": $val\n\n";
}
$message = wordwrap($message, 70);
if (!empty($email)) {
$headers .= "\r\nReply-To: $email";
}
$mailSent = mail($to, $subject, $message, $headers);
if ($mailSent) {
unset($missing);
}
}
}
} else {
http_response_code(400);
exit;
}
?>
I've provided a translation of the code without the HTML and explanations. If you have any specific questions about the code or need further assistance, please feel free to ask.
英文:
I realize this is probably bad practice but... I've used a simple php script I wrote with the help of a tutorial book I read years ago. I've adapted it as much as I'm able for use with multiple sites but it's largely the same across sites. I have tried and tried to eliminate spam type messages, but alas I cannot figure out what else I can do/can be done. I'm sure someone will mention that e.g. Javascript would be better but I don't have the time or drive to learn it at this point, so please stick to the PHP. The specific code follows below, suggestions will be greatly appreciated as to how to future proof this for spam elimination.
The contact page:
<?php
session_start();
$_SESSION['form_time'] = time();
define ('SITE_KEY', '...');
define ('SECRET_KEY', '...');
if (array_key_exists('send', $_POST)) {
function getCaptcha($SecretKey) {
$Response = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".SECRET_KEY."&response={$SecretKey}");
$Return = json_decode($Response);
return $Return;
}
$Return = getCaptcha($_POST['g-recaptcha-response']);
//var_dump($Return);
// mail processing script
$to = 'email1';
$me = 'email2';
$subject = 'Feedback From Website';
// list expected fields
$expected = array('name', 'email', 'question');
// set required fields
$required = array('name', 'email', 'question');
// set additional headers
$headers = 'From: Megan Roth<feedback@meganroth.com>';
// set the include
$process = 'includes/process.inc.php';
if (file_exists($process) && is_readable($process)) {
include($process);
}
else {
$mailSent = false;
mail($me, 'Server Problem', "$process cannot be read", $headers);
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:title" content="Megan Roth, Mezzo-Soprano - Contact">
<meta property="og:type" content="website">
<meta property="og:image" content="http://www.meganroth.com/EditedImages/index.jpg">
<meta property="og:url" content="http://www.meganroth.com/contact.php">
<meta property="og:description" content="Mezzo-soprano Megan Roth enjoys a career as a soloist in opera and oratorio as well as with prestigious chamber ensembles around the country.">
<title>Megan Roth, Mezzo-Soprano - Contact</title>
<meta name="description" content="Mezzo-soprano Megan Roth enjoys a career as a soloist in opera and oratorio as well as with prestigious chamber ensembles around the country.">
<meta name="author" content="Nathan Roth" >
<link href="css/w3_parallax_template.css" type="text/css" rel="stylesheet">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Crimson+Pro">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=EB+Garamond">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Domine">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="css/parallax12.css" type="text/css" rel="stylesheet">
<style>
/************* ABOVE THIS LINE GLOBAL ***************/
form { width: 100%; }
form p { margin: 0px 0px 25px 20px; }
textarea {
width: 380px;
height: 150px;
}
@media screen and (max-width: 400px) { textarea {
width: 240px;
height: 120px;
} }
.textInput { width: 300px; }
@media screen and (max-width: 400px) { .textInput { width: 125px; } }
.sendButton { border:1px solid #000000!important; color: #000; background-color: #FAC41E; }
.sendButton:hover { border:1px; color:#000000; background-color: #08748F; }
form .website{ display:none; } /* hide because is spam protection */
.conStudio {
font-family: "EB Garamond", Times, "Times New Roman", serif;
font-size: 1.4em;
line-height: 1.68em;
color: #FAC41E;
font-weight: bold;
margin: 0px 0px 0px 0px;
}
</style>
<script>
<!--
function MM_validateForm() { //v4.0
if (document.getElementById){
var i,p,q,nm,test,num,min,max,errors='',args=MM_validateForm.arguments;
for (i=0; i<(args.length-2); i+=3) { test=args[i+2]; val=document.getElementById(args[i]);
if (val) { nm=val.name; if ((val=val.value)!="") {
if (test.indexOf('isEmail')!=-1) { p=val.indexOf('@');
if (p<1 || p==(val.length-1)) errors+='- '+nm+' must contain an e-mail address.\n';
} else if (test!='R') { num = parseFloat(val);
if (isNaN(val)) errors+='- '+nm+' must contain a number.\n';
if (test.indexOf('inRange') != -1) { p=test.indexOf(':');
min=test.substring(8,p); max=test.substring(p+1);
if (num<min || max<num) errors+='- '+nm+' must contain a number between '+min+' and '+max+'.\n';
} } } else if (test.charAt(0) == 'R') errors += '- '+nm+' is required.\n'; }
} if (errors) alert('The following error(s) occurred:\n'+errors);
document.MM_returnValue = (errors == '');
} }
//-->
</script>
<script src="https://www.google.com/recaptcha/api.js?render=6LcEl-8UAAAAAMlzOfIDXmnooj34lkDNfKDTxN2m"></script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-34193066-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-34193066-1');
</script>
</head>
<body>
<?php include("includes/new_navigation12.inc.php"); ?>
<!-- Container -->
<div class="w3-content w3-container w3-padding-64">
<div class="w3-row">
<div class="hdquote">
&quot;…(her) soaring mezzo-soprano is clean and clear and her vocal glissandos precise and near perfect.&quot;<br>
- <em>Asheville Citizen-Times</em><br>
<strong>The Barber of Seville</strong>, Brevard Music Center.
<div class="decLine"></div>
</div>
</div>
<div class="w3-row">
<div class="w3-col m9 w3-padding-large">
<?php
if ($_POST && isset($missing) && !empty($missing)) {
?>
<p class="warning">Please complete the missing item(s) indicated.</p>
<?php
}
elseif ($_POST && $linkOne) {
?>
<p class="warning">Sorry, Messages that contain inappropriate data will not be sent.</p>
<?php
}
elseif ($_POST && $linkTwo) {
?>
<p class="warning">Sorry, Messages that contain inappropriate data will not be sent.</p>
<?php
}
elseif ($_POST && $linkThree) {
?>
<p class="warning">Sorry, Messages that contain inappropriate data will not be sent.</p>
<?php
}
elseif ($_POST && !$mailSent) {
?>
<p class="warning">Sorry, there was a problem sending your message. Please try again later.</p>
<?php
}
elseif ($_POST && $Return->success == true && $Return->score > 0.5 && $mailSent) {
?>
<p class="success">Your message has been sent. Thank you for your comments/questions!</p>
<?php } ?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post" name="contact" id="contact" class="w3-container w3-card-4" onSubmit="MM_validateForm('name','','R','email','','RisEmail','comments','','R');return document.MM_returnValue">
<p><input name="website" type="text" class="website"></p>
<p>
<label for="name">Name: <?php
if (isset($missing) && in_array('name', $missing)) { ?>
<span class="warning">Please enter your name</span><?php } ?>
</label>
<input name="name" type="text" class="textInput" id="name"
<?php if (isset($missing)) {
echo 'value="'.htmlentities($_POST['name'], ENT_QUOTES).'"';
} ?>
>
</p>
<p>
<label for="email">Email: <?php
if (isset($missing) && in_array('email', $missing)) { ?>
<span class="warning">Please enter your email address</span><?php } ?>
</label>
<input name="email" type="text" class="textInput" id="email"
<?php if (isset($missing)) {
echo 'value="'.htmlentities($_POST['email'], ENT_QUOTES).'"';
} ?>
>
</p>
<p>
<label for="question">Comments:<?php
if (isset($missing) && in_array('question', $missing)) { ?>
<span class="warning">Please enter your comments</span><?php } ?>
</label>
<textarea name="question" id="question" cols="25" rows="5"><?php
if (isset($missing)) {
echo htmlentities($_POST['question'], ENT_QUOTES);
} ?></textarea>
</p>
<p>
<input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response">
</p>
<p>
<input class="sendButton" type="submit" name="send" id="send" value="Click to Submit Comments">
</p>
</form>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('<?php echo SITE_KEY; ?>', {action: 'homepage'}).then(function(token) {
//console.log(token);
document.getElementById('g-recaptcha-response').value=token;
});
});
</script>
<p class="welcome">Please take this time to send comments and your email address so we can stay in touch with you!</p><br><br>
</div>
<div class="w3-col m3 w3-padding-large">
<img class="border" src="EditedImages/Contact.jpg" alt="Headshot for Megan Roth's Contact Webpage">
</div>
</div>
<div class="w3-row w3-center">
<span class="conStudio">Interested in private lessons? Please visit my <a href="http://studio.meganroth.com/" onclick="window.open(this.href, '_blank');return false;">studio site!</a></span>
</div>
</div>
<!-- Footer -->
<?php include("includes/new_footer12.inc.php"); ?>
<script>
// Change style of navbar on scroll
window.onscroll = function() {myFunction()};
function myFunction() {
var navbar = document.getElementById("myNavbar");
if (document.body.scrollTop > 100 || document.documentElement.scrollTop > 100) {
navbar.className = "w3-bar" + " w3-card" + " w3-animate-top" + " w3-white";
} else {
navbar.className = navbar.className.replace(" w3-card w3-animate-top w3-white", "");
}
}
// Used to toggle the menu on small screens when clicking on the menu button
function toggleFunction() {
var x = document.getElementById("navDemo");
if (x.className.indexOf("w3-show") == -1) {
x.className += " w3-show";
} else {
x.className = x.className.replace(" w3-show", "");
}
}
// Toggle between showing and hiding the sidebar, and add overlay effect
function w3_open() {
if (mySidebar.style.display === 'block') {
mySidebar.style.display = 'none';
overlayBg.style.display = "none";
} else {
mySidebar.style.display = 'block';
overlayBg.style.display = "block";
}
}
// Close the sidebar with the close button
function w3_close() {
mySidebar.style.display = "none";
overlayBg.style.display = "none";
}
/* When the user clicks on the button,
toggle between hiding and showing the dropdown content */
function myFunction() {
document.getElementById("myDropdown").classList.toggle("w3-show");
}
// Close the dropdown if the user clicks outside of it
window.onclick = function(e) {
if (!e.target.matches('.dropbtn')) {
var myDropdown = document.getElementById("myDropdown");
if (myDropdown.classList.contains('w3-show')) {
myDropdown.classList.remove('w3-show');
}
}
}
</script>
</body>
</html>
And now for the actual mail processing script:
<?php
// 30 second minimum
session_start();
$time_limit = 30;
$suspect = false;
if (isset($_SESSION['form_time']) && is_numeric($_SESSION['form_time'])) {
$seconds_passed = time() - $_SESSION['form_time'];
if ($seconds_passed < $time_limit) {
$suspect = true;
}
} else {
$suspect = true;
}
# spam protection
if (isset($_POST["website"]) && $_POST["website"] == "") {
if (isset($_SERVER['SCRIPT_NAME']) && strpos($_SERVER['SCRIPT_NAME'], 'inc.php')) exit;
// remove escape characters from POST array
if (get_magic_quotes_gpc()) {
function stripslashes_deep($value) {
$value = is_array($value) ? array_map('stripslashes_deep', $value) : stripslashes($value);
return $value;
}
$_POST = array_map('stripslashes_deep', $_POST);
}
// create empty array for any missing fields
$missing = array();
// assume that there is nothing suspect
$suspect = false;
// create a pattern to locate suspect phrases
$pattern = '/Content-Type:|Bcc:|CC:/i';
// function to check for suspect phrases
function isSuspect($val, $pattern, &$suspect) {
// if the variable is an array, loop through each element
// and pass it recursively back to the same function
if (is_array($val)) {
foreach ($val as $item) {
isSuspect($item, $pattern, $suspect);
if ($suspect)
break;
}
}
else {
// if one of the suspect phrases is found, set Boolean to true
if (preg_match($pattern, $val)) {
$suspect = true;
}
}
}
// check the $_POST array and any subarrays for suspect content
isSuspect($_POST, $pattern, $suspect);
if ($suspect ) {
$mailSent = false;
unset($missing);
}
else {
// process the $_POST variables
foreach ($_POST as $key => $value) {
// assign to temporary variable and strip whitespace if not an array
$temp = is_array($value) ? $value : trim($value);
// if empty and required, add to $missing array
if (empty($temp) && in_array($key, $required)) {
array_push($missing, $key);
}
// otherwise, assign to a variable of the same name as $key
elseif (in_array($key, $expected)) {
${$key} = $temp;
}
}
}
// validate the email address
if (!empty($email)) {
// regex to identify illegal characters in email address
$checkEmail = '/^[^@]+@[^\s\r\n\'";,@%]+$/';
// reject the email address if it doesn't match
if (!preg_match($checkEmail, $email)) {
$suspect = true;
$mailSent = false;
unset($missing);
}
}
// validate the comments
// regex to identify html links
$linkOne = false;
$checkCommentsLinks = '/\b(?:(?:https?|ftp):\/\/|www\.)[-a-z0-9+&@#\/%?=~_|!:,.;]*[-a-z0-9+&@#\/%=~_|]/i'; // '/(http:\/\/|www)/';
if(preg_match($checkCommentsLinks, stripcslashes($question))){
$linkOne = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
//validate comments against email addresses
$linkTwo = false;
$checkCommentsEmail = '/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/';
if(preg_match($checkCommentsEmail, stripcslashes($question))){
$linkTwo = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
//look for links in comments
$linkThree = false;
if(preg_match('/http|www/i',$question)) {
$linkThree = true;
$suspect = true;
$mailSent = false;
unset($missing);
}
// go ahead only if not suspect and all required fields OK
if (!$suspect && empty($missing)) {
// initialize the $message variable
$message = '';
// loop through the $expected array
foreach($expected as $item) {
// assign the value of the current item to $val
if (isset(${$item})) {
$val = ${$item};
}
// if it has no value, assign 'Not Selected'
else {
$val = 'Not selected';
}
// if an array, expand as comma-separated string
if (is_array($val)) {
$val = implode(', ', $val);
}
// add label and value to the message body
$message .= ucfirst($item).": $val\n\n";
}
// limit line length
$message = wordwrap($message, 70);
// create Reply-To header
if (!empty($email)) {
$headers .= "\r\nReply-To: $email";
}
// send it
$mailSent = mail($to, $subject, $message, $headers);
if ($mailSent) {
// $missing is no longer needed if the email is sent, so unset it
unset($missing);
}
}
}
else {
http_response_code(400);
exit;
}
?>
Something I'm really banging my head against lately is the latest spam messages actually are sending an email with a subject line (don't know where a subject comes into this) that starts "SPAM". Any help will be more than appreciated!! Thanks for reading!
答案1
得分: 1
没有未来的防止垃圾邮件的方法,您已经使用了三种方法来防止它:
- Captcha(验证码)
- 匹配在发送的数据中的某些垃圾词汇,您可以添加更多垃圾词汇
- 蜜罐(Honeypot)
您还可以添加以下方式:
- CSRF 令牌
- 测量发送表单所需的时间,例如,如果表单在 30 秒内发送,则被怀疑
- 生成动态的输入名称
- 使用 JavaScript 在客户端验证数据
在您的 isSuspect
函数中,当 $suspect
为真时应该跳出,这样您就不必检查所有其他值。
//...
foreach ($val as $item) {
isSuspect($item, $pattern, $suspect);
if ($suspect)
break;
}
//...
测量时间的示例
在生成表单时,保存时间在一个 session
中:
session_start();
$_SESSION['form_time'] = time();
在提交表单时,检查时间:
session_start();
$time_limit = 30;
$suspect = false;
if (isset($_SESSION['form_time']) && is_numeric($_SESSION['form_time'])) {
$seconds_passed = time() - $_SESSION['form_time'];
if ($seconds_passed < $time_limit) {
$suspect = true;
}
} else {
$suspect = true;
}
注意: 如果用户打开多个表单,值将被覆盖,将使用最后一个值,并且以前打开的表单将无效。
这只是一种基本的方法,有许多实现方式,30 秒只是我选择的任意值。
英文:
There is no future proof to prevent spam, you already use three ways to prevent it
- Captcha
- Matching certain spammy words in the data sent, you could add more spammy words
- Honeypot
You can add
- CSRF token
- Measure time that takes to send the form, eg. if the form is sent in less that 30 seconds then is suspect
- Generate dynamic input names
- Validate data in the client-side with javascript
In your isSuspect
function you should break when $suspect
is true, so you don't have to check all others values
//...
foreach ($val as $item) {
isSuspect($item, $pattern, $suspect);
if ($suspect)
break;
}
//...
Example of measuring time
When generating the form save the time in a session
:
session_start();
$_SESSION['form_time'] = time();
When submitting the form check for the time:
session_start();
$time_limit = 30;
$suspect = false;
if (isset($_SESSION['form_time']) && is_numeric($_SESSION['form_time'])) {
$seconds_passed = time() - $_SESSION['form_time'];
if ($seconds_passed < $time_limit) {
$suspect = true;
}
} else {
$suspect = true;
}
NOTE: if the user opens multiple forms the values will be overwritten, the last value will be used and the previous oppened forms will be invalidated
This is just basic way, there are many ways to implement this, the 30 seconds is just an arbitrary value that I chose
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论