Category Archives: Tutorials

Tutorials and how-to’s, and answers to common computer questions.

These are from my notes that I took when setting up IIS 7.5 on Windows 7. It’s not supposed to be a how-to, exactly. It’s just what I need to do to get my dev server up and running classic .ASP pages.

Install IIS via Control Panel -> Programs and Features -> “Turn Windows features on or off”.

Click the box next to Internet Information Services. It will become blocked, not checked, indicating some but not all features are installed. Click OK.

Once Windows has installed IIS, browse to http://localhost/ to confirm the server has started.

If you browse to an .asp page, though, you’ll get a Server Error:

HTTP Error 404.3 – Not Found
The page you are requesting cannot be served because of the extension configuration. If the page is a script, add a handler. If the file should be downloaded, add a MIME map.

To enable the server to run classic .ASP pages, go back to Control Panel -> Programs and Features -> “Turn Windows features on or off”, then expand Internet Information Services -> World Wide Web Services -> Application Development Features. Check the box next to ASP, then click OK.

Parent Paths is disabled by default on IIS 7.5. To enable it, run the following command as administrator:

%systemroot%\system32\inetsrv\APPCMD set config "Default Web Site" -section:system.webServer/asp /enableParentPaths:"True" /commit:apphost

Credit: http://learn.iis.net/page.aspx/566/classic-asp-parent-paths-are-disabled-by-default/

Classic ASP script error messages are no longer shown in the web browser by default on IIS 7.5. Misconfigurations are hard to troubleshoot, because IIS returns only:

An error occurred on the server when processing the URL. Please contact the system administrator.
If you are the system administrator please click here to find out more about this error.

To enable sending detailed ASP script error messages to the Web browser (as was the case in IIS 6), run the following command as administrator:

%windir%\system32\inetsrv\appcmd set config -section:asp -scriptErrorSentToBrowser:true

Credit: http://blogs.iis.net/bills/archive/2007/05/21/tips-for-classic-asp-developers-on-iis7.aspx

To start the default web site from the command line, run the following command as administrator:

%systemroot%\system32\inetsrv\APPCMD start site "Default Web Site"

To stop the default web site from the command line, run the following command as administrator:

%systemroot%\system32\inetsrv\APPCMD stop site "Default Web Site"

Even better, you can make shortcuts to batch files that contain those commands, and then set the shortcuts to always run as administrator.

The c:\inetpub\wwwroot directory is UAC-protected. If you are going to leave UAC on (and it’s recommended that you do), you will probably want to change the NTFS permissions on the wwwroot folder so that you don’t have to click through a prompt each time you change a file.

The IIS Manager app is located at Control Panel -> Administrative Tools -> Internet Information Services (IIS) Manager.

I’m using Windows 7, trying to transfer photos from my digital camera to my laptop for the second time, but the Import Pictures and Videos wizard won’t let me. After I connect my camera and choose ‘Import pictures and videos’ from the AutoPlay dialog box, I get a message from the Import Pictures and Videos wizard that ‘No new pictures or videos were found on this device’.

Although it would have been simple to just treat the camera as a mass storage device and browse the files in Explorer, I wanted to use the wizard to create a folder for each date. Why can’t we have a button to import the same images again and again, as many times as we want?

Disconnecting and reconnecting the device didn’t help, and deleting the picture files and the folders that contained them from my computer didn’t help, either. Obviously, something on the computer was keeping track of which images had been transferred. So, I started digging around and found this interesting hidden file in my user folder:

C:\Users\[username]\AppData\Local\Microsoft\Photo Acquisition\PreviouslyAcquired.db

I renamed PreviouslyAcquired.db to PreviouslyAcquired.db.old, then reconnected the camera and went through the wizard again, where I was able to import the pictures a second time.

As noted in the comments below, in order to see the PreviouslyAcquired.db file, you first need to turn on Show hidden files, folders, and drives in Windows’ Folder Options dialog box. To do this, open Windows Explorer, click on Organize and choose Folder and search options to open the Folder Options dialog box. Choose the View tab, then choose the Show hidden files, folders, and drives radio button under Hidden files and folders.

Update: According to many of the comments, a number of people are finding this post when searching for help with a VM that will not power on. This post was written for a specific scenario related to missing snapshot files, but if you are merely trying to power on a VM that was working recently, you may be able to resolve the problem by simply delete any folders containing .LCK in the name from your virtual machine’s folder, which would preserve any snapshot data.

From time to time, I want to copy just the minimum files for a VMware virtual machine: the two .vmdk files and the .vmx file. After moving those files to a new location or deleting a snapshot file, attempting to boot the virtual machine returns the following error message:

Cannot open the disk ‘XXXXXX.vmdk’ or one of the snapshot disks it depends on.
Reason: The system cannot find the file specified.

I’ve found that following the steps below fixes the problem and allows me to boot the virtual machine as it existed at the time of creation. DO NOT USE these steps if you need to retain any changes made to the virtual machine since the last snapshot:

Open the *.vmx file in a text editor and find the line that refers to the old snapshot file, which will look something like:
scsi0:0.fileName = “XXXXXX-000002.vmdk”
or
ide0:0.fileName = “XXXXXX-000002.vmdk”

Change the value to the filename of the ~1kb .vmdk file (which happens to be the same as the name of the VM). For example, if your virtual machine was named “Windows XP Professional”, the line would read:

scsi0:0.fileName = “Windows XP Professional.vmdk”

Power on the VM. It should boot normally, but because the snapshot file is missing, the machine will boot to an earlier state.

I’ve been using Dreamweaver for years. One thing that has always bothered me is that it wants to change my lowercase JavaScript event attributes to camelCase, but it only does this after I close the file. So when I wrote the code, it was valid XHTML 1.0 Strict, but after I saved the file, it no longer was. Dreamweaver was messing up my validation and it was making me furious.

So, if I have typed

<body onload="init()">

Once I’ve closed the file, it’s changed to

<body onLoad="init()">

It will do this for all the tags: onload, onblur, onfocus, etc…

I’ve tried setting my default document type, the validation fallback, everything I could find to XHTML 1.0 Strict, which requires lowercase attributes, but Dreamweaver isn’t smart enough to realize that I mean for this to apply to all of my files, even .php includes.

Finally, I’d had enough and started looking for a solution. I found two different ways of getting this done.

First method: click on Edit -> Preferences -> Code Format. Change “Default tag case” to lowercase, “Default attribute case” to lowercase=’value’, and in “Override case of”, place checkmarks next to Tags and Attributes.

You can also modify each of these attributes in the Tag Library Editor.

Second method: click on Edit -> Preferences -> Code Hints, and under the Menus area, click on the Tag Library Editor link.

A Tag Library Editor window will open. If you wanted to change the body’s onload attribute, for example, you would expand the HTML folder, then the body folder. Highlight onLoad and in the “Attribute case:” menu, select Lowercase, then click “Set default”.

At long last, valid XHTML code from Dreamweaver, even if I shouldn’t be using body onload and inline event handlers in the first place.

Just so there’s no confusion, Chromium is an open source web browser project. Google Chrome is a web browser from Google, based on the Chromium project. If you are familiar with Google Chrome in Windows, the Chromium browser looks very similar, but lacks the Google branding. Right now, there is no Google Chrome for Linux.

Here’s how I installed Chromium on Fedora 11.

Download the latest Chromium and v8 files for your OS (32 or 64 bit) from http://spot.fedorapeople.org/chromium/F11/ Save them to ~/Download or whatever location is convenient for you.

At the time of this post, these two files were
“chromium-4.0.204.0-0.1.20090827svn24640.fc11.x86_64.rpm” and
“v8-1.3.8-1.20090827svn2777.fc11.x86_64.rpm”, but they are certain to change frequently.

Open up a terminal window (Applications > System Tools > Terminal). You will probably need to switch to root for the entire installation process. To do this, type:

su

First, install two dependencies:

yum install minizip

minizip manipulates files from a .zip archive.

yum install nss-mdns

nss-mdns is a plugin for the GNU Name Service Switch (NSS) functionality of the GNU C Library (glibc) providing host name resolution via Multicast DNS (aka Zeroconf, aka Apple Rendezvous, aka Apple Bonjour), effectively allowing name resolution by common Unix/Linux programs in the ad-hoc mDNS domain .local. nss-mdns provides client functionality only, which means that you have to run a mDNS responder daemon separately from nss-mdns if you want to register the local host name via mDNS (e.g. Avahi).

Once those installs are complete, stay in the terminal window, navigate to the folder containing the Chromium and v8 downloads and then type:

rpm -ivh v8* chromium*

This will run the installer on the two downloads. If you see any other dependencies, just handle them as they come.

That’s it, you should see Chromium Web Browser under Applications > Internet.

Installing Chromium via YUM

If you want to install Chromium via YUM, you just need to add a new repository file to the /etc/yum.repos.d directory. I’m partial to gedit, so I’ll use that editor in the instructions.

Open a terminal and type:

sudo bash

This will give you root privileges for launching gedit (and other GUI apps). Launch gedit by typing:

gedit

Copy and paste the following lines into the new document:

[chromium]
name=Chromium Test Packages
baseurl=http://spot.fedorapeople.org/chromium/F11/
enabled=1 
gpgcheck=0

Save the file as chromium.repo to /etc/yum.repos.d/. (If you are running Fedora 10, change the F11 to F10 in the baseurl path.)

Back in the terminal window, type:

yum update

YUM will pick up the new chromium file. You’re now ready to install Chromium with the line:

yum install chromium

Update 2/22/2010: It looks like changing .htaccess is no longer necessary. After you select PHP 5.x, your site will begin using version 5.2.5 without any further configuration.

The following applies to older domains. As of early 2009, newly purchased linux hosting plans are running PHP 5.2.8, while older plans, once updated, only go up to PHP 5.2.5. I’ve had Ardamis.com hosted at GoDaddy since 2005, and quite awhile ago I thought I had upgraded to PHP version 5 from 4.3.11, but tonight I happened to check with phpinfo and found I was still on version 4.

In the unheard of ten minutes that I was on hold waiting for technical support, I figured out how to really run my pages on PHP 5.x (in this case, 5.2.5).

Log in and go to your Hosting Control Center. You must be running Hosting Configuration 2.0 to go any further, so if you haven’t touched your domain in years, do that first.

Click on Content, then Add-On Languages. Next to PHP Version, select PHP 5.x and click Continue. You’ll get a message that “Changing to PHP 5.x may make your PHP files run incorrectly.” Highly unlikely these days, but OK, you’ve been warned. Notice, too that it says “PHP 5.x will be activated“. Click Update.

It may take awhile for this change to be processed by the server, but once your Account Summary is displaying PHP Version: 5.x, it’s time for the really important part.

You see, you’ve only made PHP 5.x available at this point. Your *.php files are still running in 4.x. Go ahead and check phpinfo again.

Now, you could simply edit .htaccess to change the extensions, like so:

AddHandler x-httpd-php5 .php
AddHandler x-httpd-php .php4

More details at http://help.godaddy.com/article/1082

But if you’re squeamish about changing .htaccess yourself, there’s another way to set 5.x to be the default handler for *.php files. All the following does, strangely enough, is to add the AddHandler x-httpd-php5 .php to the beginning of your .htaccess file.

Back in the Hosting Control Center, click on Settings, then File Extension. If the change to 5.x has been completed, you’ll see at the bottom of the available extensions list, “Extension -> .php | Runs Under -> PHP 5.x” If it’s not there, stop here and come back in an hour or so.

Click on Custom Extensions at the left. This should be empty, with a message stating “No custom extensions have been created.”

Click on Default Extensions and then click on the Edit button (it looks like a piece of paper and a pencil) to the right of .php | PHP 5.x. Click on Continue.

Click again on the Custom Extensions button on the left, and you should now see “Extension -> .php | Runs Under -> PHP 5.x”. Check your phpinfo page one more time, and it should report PHP 5.x.

It’s unfortunate we even have to do this for our older domains, but I asked the tech support guy if I could somehow get on to PHP 5.2.8, and he said nope, that the newer servers have the more recent version but the older servers are stuck back in 2007.

I completely hosed a few SanDisk Cruzer Micro USB 2.0 2 GB Flash Drives at work when I deleted the original contents of the drives, installed the CruzerPro software that had shipped with some older Cruzer Professional drives, and then used the CruzerPro application to password protect the drives. This process rendered the drives completely unusable and unable to be formatted.

The problems

Clicking the drive letter in Windows Explorer returns the following error message:

Please insert a disk into drive X:.

Attempting to format the drive returns the warning:

There is no disk in drive X.
Insert a disk, and then try again.

This is what the drives looked like once I’d thoroughly broken them.

SanDisk U3 Cruzer Micro USB Device Properties

SanDisk U3 Cruzer Micro USB Device Properties

The drive properties show:
Type: Removable Disk
File system: Unknown
Used space 0 bytes
Free space 0 bytes
Capacity 0 bytes

The Volumes tab shows:
Type: Removable
Status: No Media
Partition style: Not Applicable
Capacity: 0 MB
Unallocated space: 0 MB
Reserved space: 0 MB

Opening the Disk Management component of the Computer Management console shows that the drive is connected, but there is no unallocated space to partition or format.

Other things about the disk look normal. It shows up in the Device Manager as working correctly, without any warnings, for example.

I Googled around and found that many, many people were running into this problem where the drive starts reporting 0 bytes capacity and can not be formatted. Of the dozens of pages that I read, no one found a fix for the problem. The most common solution offered was to return the drive to the manufacturer for replacement. Well, I wasn’t going to publicize my mistake and return the drives, I was going to repair them.

Software that didn’t help

Feel free to skip this part if you’re not interested in reading about the many dead-ends I explored.

I knew of one nifty program that had helped me out a few times before, so I tried running the HP USB Disk Storage Format Tool v2.1.8, but attempting to format the drive with this utility returned the following error message:

There is no media in the specified device.

Someone suggested using this thing called “Apacer Repair v2.9.1.1” to reformat the drive, so I tried that, but the software only reported “USB Flash Disk not found!” when I ran it.

Someone else recommended FreeCommander, but that failed to open the drive, too.

I tried the free trial of the utility from http://www.flashmemorytoolkit.com/, but it reported the same information as Windows XP – that the device contained a disk with 0 bytes capacity. Maybe the full version could have done more, but I put that on the back burner.

A number of people suggested attacking it with partitioning software, which I wasn’t looking forward to doing, but was willing to try.

Another last resort was going to be using the Windows XP Recovery Console’s fixboot and fixmbr commands, which got me out of a pinch when I screwed up a partition.

What I should have tried to begin with

Then I had an idea. I had a clean drive that had escaped my earlier bungling. I plugged it in, copied the contents to my desktop and tried to run the U3 LaunchPad software. Nothing happened, so I started looking more closely at the files. One of the files was called SanDiskFormatExtension.dll, which sounded promising. Now I just needed to figure out how to run the SanDisk installer to reformat the drive. I tried all of the .exe’s and .msi’s that shipped with the drive, but nothing wanted to run from the folder on my desktop.

Just as I was running out of options, I opened the autorun.inf file and found a very interesting entry:

[Update]
URL=http://u3.sandisk.com/download/lp_installer.asp?custom=1.6.1.2&brand=PelicanBFG

The fix

So, with nothing to lose, I pasted http://u3.sandisk.com/download/lp_installer.asp?custom=1.6.1.2&brand=PelicanBFG into Internet Explorer, thinking that it would at least get me some new files that might allow me to reformat the drive. I followed a few prompts and lo, the U3 Launchpad Installer software launched and restored the drive to its factory settings of 2 GB capacity formatted as FAT. It even replaced the original U3 files, making it truly good-as-new.

I’m astonished that this information isn’t more widely available, particularly on the SanDisk support site and forums, as this 0 capacity problem seems to affect a good number of drives and there are many threads where this issue remains unresolved.

Note that the page at http://u3.sandisk.com/download/lp_installer.asp?custom=1.6.1.2&brand=PelicanBFG requires you to install an ActiveX component, so you must use Internet Explorer.

Otherwise, you can download the latest version of the U3 Launchpad Installer executable from the Sandisk KB.

Of course, if you’re not using a SanDisk drive, it’s rather unlikely that this software will fix your drive, but maybe your device’s manufacturer has something similar. There are also a number of good ideas in the comments below, so definitely read through them for more options.

If you’re trying to restore the drive’s contents or recover files, the all of the methods described on this page will format (erase) the drive and are not for you. Good luck.

I was trying to set up a friend’s Xbox 360 on my home network that uses a D-Link DI-624 router (Rev. C) with version 2.76 firmware and a brand new Motorola Netopia 2210-02 ADSL modem, but I wasn’t able to connect to Xbox Live.

My Xbox, which had been connected for months with an Open NAT while using a old Siemens Speedstream 4100 modem, never had any problems connecting.

I double checked all the connections, powercycled the Xbox, then ran the network tests from the System blade. It would pass all of the tests up until the Xbox Live test, at which point it would fail spectacularly and restart the tests – but this time displaying a “Disconnected” message at the Network Adapter test.

I bypassed the router and plugged the Xbox straight into the DSL modem and was able to connect, but with a Strict NAT. (I should have realized the significance of this right away, but I didn’t.)

So I reconnected the router and kept experimenting. After a while, I noticed that my computers connected to the router also lost their connections when I tried to sign in to Xbox Live.

As it turns out, the suspicious-looking disconnection message was accurate – something the Xbox was doing was causing the router to reboot.

I Googled around and found a few good posts about this problem.

First, I disabled UPnP on the D-Link router thanks to the advice in this Ars Technica forum post. Then I configured it to assign the Xbox a static IP address and then put that IP address in the DMZ. Now the Xbox was able to connect to Xbox Live, but the NAT status was Strict.

I wasn’t going to settle for that, though. I wanted to get an Open NAT.

So I took it out of the DMZ and port forwarded UDP 88 and both UDP & TCP 3074 ports to the static IP address, but the NAT status was still Strict.

Giving the Xbox a static IP address and forwarding the ports had fixed similar connection issues and permitted an Open NAT for almost everyone else, why wasn’t it working for this setup?

More Googling finally turned up the explanation. The Motorola Netopia 2210 contains a NAT router, so no matter what I did with the D-Link’s settings, I was going to keep getting the Strict NAT from the modem as long as it was handling the PPPoE. (This is what I should have realized earlier, when I was connecting the Xbox directly to the modem.)

The Motorola/Netopia 2210 is also a router with full DHCP functions and may not function correctly when connected directly to another router. Not changing the modem to Bridged Ethernet may result in double NAT’ing, increased latency, possible IP conflicts, or possibly a network that doesn’t work at all.
http://www.dslreports.com/faq/15855

The solution was to configure the modem to use “Bridged Ethernet” mode and set up PPPoE on the router.

As long as you’re setting up PPPoE on the router, you may want to select “Keep Alive” or “Always On”, if those options are available, or set the Maximum Idle Time to “0”. You should also confirm that the MTU value is “1492” and that value is used on all the devices on the network.

Also note that the Motorola Netopia 2210 has an “Internet” light that lights up green whenever there is an active PPPoE session initiated by it. The light will stay off when the PPPoE session is initiated by a router or other device.

A client wanted me to implement a drop down contact form that was triggered when the cursor hovered over a button in the site’s navigation. This was no problem, and I delivered some code using script.aculo.us‘s blind effect. A few hours later, the client contacted me and said that as he moved his cursor around the screen, he kept accidently triggering the contact form as the cursor moved through the element, and couldn’t I find a way to make the action a little more deliberate.

As far as I can tell, there is a way to delay a blind effect in script.aculo.us, but no way to stop the effect once the delay’s countdown has begun.

I searched around and came upon a w3schools JavaScript tutorial about timing events and the setTimeout() and clearTimeout() methods. This was exactly what I needed. By calling a function that started a countdown to the event rather than the event itself, and later calling a function that cancelled the countdown, it is possible to abort the drop down action if the mouse was moved out of the element within a specified time interval.

The code snippet below assumes you have links to the script.aculo.us and prototype libraries in place already. As the cursor enters the link, the startForm() function is called and a 500 ms timer begins. If the cursor moves out of the link, the stopForm() function is called, and the timer is cancelled. If stopForm() is called before the timer finishes counting, the effect never happens.

The client’s happy and I learned something useful.

The Code

<script type="text/javascript"><!--//--><![CDATA[//><!--
var t
function startForm() {
	t=setTimeout("Effect.toggle('contactform','blind')",500);
}
function stopForm() {
	clearTimeout(t);
}
//--><!]]></script>

A simple demonstration of how to implement this using inline JavaScript (a no-no).

<ul id="nav">
	<li><a href="#">a link</a></li>
	<li><a href="#">a link</a></li>
	<li><a href="#" onmouseover="startForm()" onmouseout="stopForm()">Contact Us</a></li>
</ul>
<div id="contactform">
        <!-- the form goes here -->
</div>

Comment spam comes from humans who are paid to post it and robots/scripts that do it automatically. The majority of spam comes from the bots. There’s very little one can do to defend against a determined human being, but bots tend to behave predictably, and that allows us to develop countermeasures.

From my observations, it seems that the spambots are first given a keyword phrase to hunt down. They go through the search engine results for pages with that keyword phrase, and follow the link to each page. If the page happens to be a WordPress post, they pass their spammy content to the comment form. Apparently, they do this in a few different ways. Some bots seem to inject their content directly into the form processing agent, a file in the blog root named wp-comments-post.php, using the WordPress default form field IDs. Other bots seem to fill in any fields they come across before submitting the form. Still others seem to fill out the form, but ignore any unexpected text input fields. All of these behaviors can be used against the spammers.

One anti-spam technique that has been used for years is to rename the script that handles the form processing. If you look at the HTML of a WordPress post with comments enabled, you’ll see a line that reads:

<form action="http://yourdomain.com/wp-comments-post.php" method="post" id="commentform">

The ‘wp-comments-post.php’ file handles the comment form processing, and a good amount of spam can be avoided by simply renaming it. Many bots will try to pass their content to directly to that script, even if it no longer exists, to no effect.

More sophisticated bots will look through the HTML for the URL of the form processing script, and in doing so will learn the URL of the newly renamed script and pass their contents to that script instead. The trick is to prevent these smarter bots from discovering the URL of the new script. Because it seems that the bots aren’t looking through external JavaScript files yet, that’s where we will hide the URL. (If you do use this technique, it would be very considerate to tell your visitors that JavaScript is required to post comments.)

Step 1

Rename the wp-comments-post.php file to anything else. Using a string of random hexadecimal characters would be ideal. Once you’ve renamed the file, enter the address of the file in your browser. The page should be blank; if you get a 404 error, something is wrong. Make a note of that address, because you’ll need it later. Verify that the wp-comments-post.php file is empty or is no longer on your server.

(Because I was curious about how many bots were hitting the wp-comments-post.php file directly, I replaced the code with a hitcounter. Sure enough, bots are still hitting the file directly, even though there is no longer any path leading to it.)

Step 2

Open up the ‘comments.php’ file in your theme directory. Find the line:

<form action="http://yourdomain.com/wp-comments-post.php" method="post" id="commentform">

and change the value of the action attribute to a number sign (or pound sign, or hash), like so:

<form action="#" method="post" id="commentform">

Any bots that come to the page and search for the path to your comment processing script will just see the hash, so they will never discover the URL to the real script. This change also means that if a bot or a visitor tries to submit the form, the form will fail, because a WordPress single post page isn’t designed to process forms. We want the bots to fail, but we’ll need to put things right for humans.

If you are tempted to designate a separate page for the action value, note that the only people likely to ever see this page are visitors without JavaScript enabled who fill out the form.

Step 3

Create a new JavaScript file with the following code.

function commentScriptReveal() {
	// enter the URL of your renamed wp-comments-post.php file below
	var scriptPath = "http://yourdomain.com/renamed-wp-comments-post.php";
	document.getElementById("commentform").setAttribute("action", scriptPath);
}

Enter the address of your renamed file as the value for the variable scriptPath. The function commentScriptReveal, when called, will find the element with the ID ‘commentform’ (that’s the comment form) and change its action attribute to the URL of the renamed file, allowing the form to be successfully sent to the processing agent.

Save the file as ‘commentrevealer.js’ and upload it to the /scripts/ directory in your blog’s root. Add the script to your theme’s header.php file:

<script src="<?php echo bloginfo('url'); ?>/scripts/commentrevealer.js" type="text/javascript"></script>

Now we just need to decide how to call the commentScriptReveal function.

Step 4

The ideal method of calling the function would be the one where the human visitor always calls the function, and the bot never calls it. To do this, we need to know something about how the bots work.

Step 4a — For spam bots that ignore unexpected text input fields:

If the bots ignore unexpected text input fields, we can simply add a field, label it ‘required’, and attach the script revealer to that field with one of the following event handlers:

  • onchange triggered when the user changes the content of a field
  • onkeypress triggered when a keyboard key is pressed or held down
  • onkeydown triggered when a keyboard key is pressed
  • onkeyup triggered when a keyboard key is released
  • onfocus triggered when an element gets focus
  • onblur triggered when an element loses focus

I’m intentionally vague about how to trigger the function commentScriptReveal because this technique will be efficacious longer if different people use different events. Furthermore, the text input field doesn’t necessarily need to do anything, its contents will just be discarded when the form is processed. In fact, it doesn’t even need to be a text input field. It can be any form control—a button, a checkbox, a radio button, a menu, etc. We just need human visitors to interact with it somehow. Those bots that skip over the control won’t trigger the revealer event, and your visitors (who always follow directions) will.

If everyone goes about implementing this method in a slightly different way, the spammers should find it much more difficult to counter.

For further reading on JavaScript events: QuirksMode – Javascript – Introduction to Events.

Step 4b — For spam bots that add text to every input field they come across:

If the bots are hitting every text input field with some text, follow Step 4a, and then create a second JavaScript file, named ‘commentconcealer.js’, with the following code:

function commentScriptConceal() {
	// enter the URL of your renamed wp-comments-post.php file below
	var scriptPath = "#";
	document.getElementById("commentform").setAttribute("action", scriptPath);
}

The function commentScriptConceal re-rewrites the action attribute back to “#“.

Upload the file and add the script to your theme’s header.php file:

<script src="<?php echo bloginfo('url'); ?>/scripts/commentconcealer.js" type="text/javascript"></script>

Add another text input field somewhere below the one you added in Step 4a. Hide this field from visitors with {display: none;}. Call the function with an onfocus (or onblur, etc.) event on the second input field:

<p style="display: none;"><input type="text" name="reconceal" id="reconceal" value="" size="22" onfocus="return commentScriptConceal()" />
<label for="reconceal"><small>OMG Don't Touch This Field!?!?</small></label></p>

The legitimate visitors will never trigger this, but any bot that interacts with every field on a page will.

Step 5

If you chose to use a text input field in Step 4a, consider making that a challenge-response test. It’s always a good idea to use a server-side check to back up a client-side check. I like to ask the question “What color is an orange?” as a tip of the hat to Eric Meyer. This challenge-response test can be integrated into wp-comments-post.php, so that if the user fails the test, the form submission dies in the same way it would if a required field were left blank.

For this example, let’s ask the question “What color is Kermit the Frog?” The answer, of course, is green.

Open your wp-comments-post.php file, and (in WP version 2.2.2) somewhere around line 31 add:

$comment_verify = strtolower(trim($_POST['verify']));

This will trim any whitespace from the answer to the challenge question, and convert the characters to lowercase.

Around line 45, find the lines:

} else {
	if ( get_option('comment_registration') )
		wp_die( __('Sorry, you must be logged in to post a comment.') );
}

And add the following lines to them, as so:

} else {
	if ( get_option('comment_registration') )
		wp_die( __('Sorry, you must be logged in to post a comment.') );
	if ( $comment_verify != 'green' )
		wp_die( __('Sorry, you must correctly answer the "Kermit" question to post a comment.') );
}

This will check the visitor’s answer against the correct answer, and if they don’t match, the script will stop and the comment won’t be submitted. The visitor can hit the back button to change the answer.

Now we need to add the challenge question to the theme file comments.php. Find the lines:

<p><input type="text" name="url" id="url" value="<?php echo $comment_author_url; ?>" size="22" tabindex="3" />
<label for="url"><small>Website</small></label></p>

And right below them, add the following lines:

<p><input type="text" name="verify" id="verify" value="" size="22" tabindex="4" />
<label for="verify"><small>What color is Kermit the Frog? (anti-spam) (required)</small></label></p>

(You may need to update all the tabindex numbers after you add this field.)

That’s it, your visitors will now have to correctly answer the Kermit the Frog question to submit the form.

Further customization

The methods described here just scratch the surface of what can be done to obfuscate the comment handling script. For example, the URL could be broken up into parts and saved as multiple variables. It could have a name comprised of numbers, and the commentScriptReveal JavaScript could perform math to assemble it.

I can’t imagine I’m the first person to come up with this, but… the user could be required to complete a challenge-response question, the correct answer to which would be used as part of the name of the script—the URL to the script doesn’t exist anywhere until the visitor creates it.

But, there are some people who don’t want to impose even a minor inconvenience on their visitors. If you don’t like challenge-response tests, what about using JavaScript to invisibly check the screen resolution of the user agent? And we haven’t even considered using cookies yet.

Credits

Many thanks to Jeff Barr for demonstrating how to put a challenge-response test into the wp-comments-post.php file in his post WordPress Comment Verification (With Source Code). I had been using only JavaScript for validation up until that point, shame on me.

Many thanks also to Will Bontrager for writing a provocative explanation of how to temporarily hide the form processing script using JavaScript in Spamming You Through Your Own Forms.

Huge thanks to Eric Meyer, who wrote a pre-plugin, challenge-response test script called WP-Gatekeeper for a very early version of WordPress.

I’ve described two similar methods for defeating contact form spam by hiding the webmail script in an eariler post.