Here is a PHP script which can be included once before any output is generated. It is not perfect, but it works well enough in most cases to avoid delivering content or code that will not be used by the client. The header comments explain how it works.
<?php
/*****************************************************************************
* JAVASCRIPT DETECTION *
*****************************************************************************/
// Progressive enhancement and graceful degradation are not sufficient if we
// want to avoid sending HTML or JavaScript code that won't be useful on the
// client side. A normal HTTP request will not include any explicit indicator
// that JavaScript is enabled in the client. So a "preflight response" is
// needed to prompt the client to provide an indicator in a follow-up request.
// Once the state of JavaScript availability has been received the state of
// data received in the original request must be restored before proceding.
// To the user, this handshake should be as invisible as possible.
//
// The most convenient place to store the original data is in a PHP session.
// The PHP session extension will try to use a cookie to pass the session ID
// but if cookies are not enabled it will insert it into the query string.
// This violates our preference for invisibility. When Javascript is not
// enabled the only way to effect a client side redirect is with a "meta"
// element with its "http-equiv" attribute set to "refresh". In this case
// modifying the URL is the only way to pass the session ID back.
//
// But when cookies are disabled and JavaScript is enabled then a client side
// redirect can be effected by setting the "window.onload" method to a function
// which submits a form. The form has a "method" attribute of "post" and an
// "action" attribute set to the original URL. The form contains two hidden
// input elements, one in which the session ID is stored and one in which the
// state of JavaScript availability is stored. Both values are thereby passed
// back to the server in a POST request while the URL remains unchanged. The
// follow-up request will be a POST even if the original request was a GET, but
// since the original request data is restored, the containing script ought to
// process the request as though it were a GET.
// In order to ensure that the constant SID is defined as the caller of this
// script would expect, call session_start if it hasn't already been called.
$session = isset($_SESSION);
if (!$session) session_start();
// Use a separate session for Javascript detection. Save the caller's session
// name and ID. If this is the followup request then close the caller's
// session and reopen the Javascript detection session. Otherwise, generate a
// new session ID, close the caller's session and create a new session for
// Javascript detection.
$session_name = session_name();
$session_id = session_id();
session_write_close();
session_name('JS_DETECT');
if (isset($_COOKIE['JS_DETECT'])) {
session_id($_COOKIE['JS_DETECT']);
} elseif (isset($_REQUEST['JS_DETECT'])) {
session_id($_REQUEST['JS_DETECT']);
} else {
session_id(sha1(mt_rand()));
}
session_start();
if (isset($_SESSION['_SERVER'])) {
// Preflight response already sent.
// Store the JavaScript availability status in a constant.
define('JS_ENABLED', 0+$_REQUEST['JS_ENABLED']);
// Store the cookie availability status in a constant.
define('COOKIES_ENABLED', isset($_COOKIE['JS_DETECT']));
// Expire the cookies if they exist.
setcookie('JS_DETECT', 0, time()-3600);
setcookie('JS_ENABLED', 0, time()-3600);
// Restore the original request data.
$_GET = $_SESSION['_GET'];
$_POST = $_SESSION['_POST'];
$_FILES = $_SESSION['_FILES'];
$_COOKIE = $_SESSION['_COOKIE'];
$_SERVER = $_SESSION['_SERVER'];
$_REQUEST = $_SESSION['_REQUEST'];
// Ensure that uploaded files will be deleted if they are not moved or renamed.
function unlink_uploaded_files () {
foreach (array_keys($_FILES) as $k)
if (file_exists($_FILES[$k]['tmp_name']))
unlink($_FILES[$k]['tmp_name']);
}
register_shutdown_function('unlink_uploaded_files');
// Reinitialize the superglobal.
$_SESSION = array();
// Destroy the Javascript detection session.
session_destroy();
// Reopen the caller's session.
session_name($session_name);
session_id($session_id);
if ($session) session_start();
unset($session, $session_name, $session_id, $tmp_name);
// Complete the request.
} else {
// Preflight response not sent so send it.
// To cover the case where cookies are enabled but JavaScript is disabled,
// initialize the cookie to indicate that JavaScript is disabled.
setcookie('JS_ENABLED', 0);
// Prepare the client side redirect used when JavaScript is disabled.
$content = '0; url='.$_SERVER['REQUEST_URI'];
if (!$_GET['JS_DETECT']) {
$content .= empty($_SERVER['QUERY_STRING']) ? '?' : '&';
$content .= 'JS_DETECT='.session_id();
}
// Remove request data which should only be used here.
unset($_GET['JS_DETECT'],$_GET['JS_ENABLED'],
$_POST['JS_DETECT'],$_POST['JS_ENABLED'],
$_COOKIE['JS_DETECT'],$_COOKIE['JS_ENABLED'],
$_REQUEST['JS_DETECT'],$_REQUEST['JS_ENABLED']);
// Save all remaining request data in session data.
$_SESSION['_GET'] = $_GET;
$_SESSION['_POST'] = $_POST;
$_SESSION['_FILES'] = $_FILES;
$_SESSION['_COOKIE'] = $_COOKIE;
$_SESSION['_SERVER'] = $_SERVER;
$_SESSION['_REQUEST'] = $_REQUEST;
// Rename any uploaded files so they won't be deleted by PHP. When using
// a clustered web server, upload_tmp_dir must point to shared storage.
foreach (array_keys($_FILES) as $k) {
$tmp_name = $_FILES[$k]['tmp_name'].'x';
if (move_uploaded_file($_FILES[$k]['tmp_name'], $tmp_name))
$_SESSION['_FILES'][$k]['tmp_name'] = $tmp_name;
}
// Have the client inform the server as to the status of Javascript.
?>
<!DOCTYPE html>
<html>
<head>
<script>
document.cookie = 'JS_ENABLED=1';
// location.reload causes a confirm box in FireFox
// if (document.cookie) { location.reload(true); }
if (document.cookie) { location.href = location; }
</script>
<meta http-equiv="refresh" content="<?=$content?>" />
</head>
<body>
<form id="formid" method="post" action="" >
<input type="hidden" name="<?=$session_name?>" value="<?=$session_id?>" />
<input type="hidden" name="JS_DETECT" value="<?=session_id()?>" />
<input type="hidden" name="JS_ENABLED" value="1" />
</form>
<script>
document.getElementById('formid').submit();
</script>
</body>
</html>
<?php
exit;
}
?>