Hack 87 A Universal Flash Plugin Sniffer

figs/beginner.gif figs/hack87.gif

Build a non-JavaScript-based Flash plugin sniffer that won't need to be updated each time Macromedia releases a new version of the Flash Player.

A Flash plugin sniffer is something that tries to detect whether or not the Flash content within the current web page is viewable by the browser. It does this by looking for a particular installation of the Flash Player. The problem is that prerequisites may have to be addressed before the sniffer itself works correctly. Flash content that would have worked perfectly well without a sniffer may never be reached if the sniffer itself fails! This hack shows how to build a sniffer that has fewer prerequisites and is thus very likely to work.

Traditional sniffers usually have to be updated whenever a new version of the Flash Player is released. Worse still, traditional sniffers are written in JavaScript and have notorious browser dependencies. As the Hacks series editor Rael Dornfest once said, "We contemplated a JavaScript Hacks book, but everything in JavaScript is a hack." I can't even look at a JavaScript manual any more without giggling.

The upshot is that a JavaScript-based Flash sniffer has a number of Achilles' heels:

  • The variability of JavaScript support in different browsers and the differences in ways browsers identify (or fail to identify) the presence of a given plugin. For example, detecting an ActiveX control in Windows Internet Explorer is different than detecting a Netscape-style plugin in Netscape and other browsers that don't support ActiveX.

  • Variability in the Flash plugin. Your sniffer may not recognize more recent versions of the Flash plugin or versions for alternative platforms, even if they can actually run your SWF.

  • Whether the user has turned JavaScript support off in the current browser (quite common in commercial organizations because it reduces the probability of malicious JavaScript-based virus infections or hacker Trojans), which will defeat any JavaScript-based sniffer script altogether!

What we would really like is a Flash sniffer that doesn't rely on JavaScript at all. Wouldn't that be a cool hack to know?

Five Seconds and Counting...

The <meta> tag is an HTML header tag that allows you to send information to the browser or specialized browser plugins (such as screen readers for the sight impaired). It is also used to present information for search engine indexing purposes. This hack uses a particular ability of the <meta> tag, the client-pull. For more information, see HTML & XHTML: The Definitive Guide, fourth edition, by Chuck Musciano and Bill Kennedy (O'Reilly), especially section 13.2.1, "Uniquely Refreshing."

Here is the syntax for the <meta> tag to implement a client-pull:

<meta http-equiv="refresh" content="n; URL=newdoc.html">

A client-pull is simply a request for the browser to load a new HTML document, in this case newdoc.html (which may also include the full path to a new document, such as http://www.myDomain.com/mydoc.html) after a certain time delay, n, in seconds. Its effect is much the same as if the user clicked on a hyperlink to newdoc.html after the n second time delay. For this hack, we set the time delay to five seconds and specify that the browser should load the document noflash.html.

We also embed a SWF inside our original HTML file. The SWF uses the getURL( ) action to load an HTML page, flash.html, containing our final Flash content. Figure 11-2 shows the general process at work.

Figure 11-2. Flash sniffer
figs/flhk_1102.gif


So what we have is an attempt to run a Flash SWF that executes a getURL( ) action, which loads the Flash content embedded in the flash.html web page. If that works, we know Flash Player is installed. If it doesn't execute, we know the appropriate Flash Player is not present. But no worries?if Flash doesn't change the current web page (using getURL( ) to load flash.html), the client-pull fires after five seconds, loading our non-Flash HTML page, noflash.html. Best of all, none of it is dependent on JavaScript?the browser, OS platform, or user preferences.

Asking Flash to Run Itself

Let's look more closely at the requirements for this hack.

Here are the steps:

  1. Create a new Flash document and add the following script to frame 1 of the main timeline:

    getURL("flash.html", "_self");

  2. Save the file as index.fla.

  3. Publish the document with FilePublish PreviewDefault (HTML). This option creates the files index.html and index.swf in the same folder where you saved index.fla. The two together will form our Flash sniffer.

  4. Open index.html in your HTML or text editor of choice.

  5. The <head> section of the file will look like this:

    <head>
    
    <meta http-equiv=Content-Type content="text/html;  charset=ISO-8859-1">
    
    <title>index</title>
    
    </head>

  6. Add a new <meta> tag as shown in bold:

    <head>
    
    <meta http-equiv=Content-Type content="text/html;  charset=ISO-8859-1">
    
    <meta http-equiv="refresh" content="5; URL=noflash.html">
    
    <title>index</title>
    
    </head>

Create your noflash.html file. It should be one of the following:

  • An HTML-only version of your site

  • An HTML-only page advising the user to update to the latest Flash plugin

  • A page prompting the user to click a button to go directly to the Macromedia Flash plugin download site (http://www.macromedia.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash)

In any case, your page should explain that Flash was not detected. The page should also include a link for the user to run your Flash-based site if she is sure she has the plugin installed. Nothing so frustrates an end user as being told she doesn't have the plugin because your automatic detection failed. So don't unceremoniously send the user to the Macromedia site to obtain the Flash plugin; leave it up to the user to choose an appropriate course of action.

Place your flash.html and someFlashMovie.swf (your Flash site) in the same folder as index.html. To test your sniffer locally, open index.html in a web browser. If you're having trouble, see the basic files provided on this book's web site.

Once that seems to be working, upload the files to your web server and point your web browser to the URL where you uploaded the HTML page, such as http://www.yourdomain.com/sniffertest/index.html.

To emulate a basic browser without any plugins, visit http://www.anybrowser.com. This site emulates a plugin-free HTML 3.2-compatible browser?a perfect place to test a Flash sniffer.

Making It a Bit More Discerning

Sometimes, you may find that early versions of the Flash Player recognize the getURL( ) command, which has been supported since Flash Player 2, but can't play all the content in your Flash SWF-based site. An example of this might be if you want to use Flash video, supported in Flash Player 6 and later, but the user has Flash Player 5 installed. Flash Player 5 supports getURL( ) but will not play the video embedded in a SWF file. In this case, you need to check for a minimum Flash Player plugin version.

Your Flash movie that checks the Flash Player version should itself be a Flash Player 4- or Flash Player 5-format .swf file. It can check the Flash Player version and then load another .swf file, perhaps in Flash Player 6 or Flash Player 7 format, that contains your real Flash content.

To check the Flash Player version, use the Flash environment variable $version. You can see the version in the Variables tab of the Debugger panel, as shown in Figure 11-3, if you test a movie in debug mode.

Figure 11-3. The Variables tab of the Debugger panel, showing the Flash version
figs/flhk_1103.gif


The $version property is supported in Flash Player 4.0.11.0 and later. It returns a version string of the form "platform major,minor,build,patch", where platform is either WIN, MAC, or UNIX and the other items are numbers, such as "WIN 6,0,21,0". Don't use more modern methods to retrieve the version, such as the getVersion( ) function (supported in Flash 5 and later) or the System.capabilities.version property (supported in Flash 6 and later), unless you can ensure that the user has a more recent version of the plugin. It is a pretty safe bet that users have at least Flash Player 5 if they have any version installed, and more than 90% of users have Flash Player 6 or later as shown in the Flash Player census (http://www.macromedia.com/software/player_census/flashplayer/version_penetration.html).

Regardless, $version, getVersion( ), and System.capabilities.version, when supported, all return the same string on a given platform.

The major revision number is at an offset of 4 (the fifth character) in the $version string for Mac and Windows. Here we use String.substr( ) to check whether the user has at least Version 5 of the Flash Player. If so, it replaces the current page with the flash.html site. Otherwise, it goes to an error page that says, "You need to update your Flash Player to at least Version 5."

if ($version.substr(4, 1) >= 5) {

  getURL("flash.html", "_self");

} else {

  getURL("old_flash.html", + "_self");

}

This script will fail and simply continue playing the SWF on Flash Player 1, 2, and 3, but those versions are extremely old and represent less than 1% of the installed base. The cool thing about this hack is that you don't have to update this detection system for users with Flash Player 6, Flash Player 7, or future versions. If you want to support platforms that return "UNIX" in the version string, extract the major version number following the first space in the string rather than assuming it starts at the fifth character.

Final Thoughts

The hack here relies on the absence of the Flash Player, which is easier than actively determining the presence of the Flash Player. We check the Flash Player version (if we need to) once we know that Flash Player is present. Instead of trying to check the Flash Player via JavaScript, we ask the Flash Player to identify itself.

Our sniffer is not susceptible to either JavaScript support or revisions nor dependent on the version of the Flash Player plugin. At a minimum, it relies only on the <meta> tag, supported by Version 3.x browsers and later. At a maximum, it requires a Flash Player plugin that can recognize the if statement and $version property, namely Flash Player 4 or higher.

Admittedly, the approach presented here is a hack?this is Flash Hacks, after all?and is not necessarily foolproof. Depending on their configuration, Internet Explorer users without the Flash Player ActiveX control may be prompted to install it. Depending on the timing (how long it takes for the prompt to appear and for the user to respond), the user might not be forwarded to your no-Flash page.

Plugin-detection scripts are always controversial and none are perfect. Consider this technique one more arrow in your quiver rather than a silver bullet (how's that for a mixed metaphor!). See the resources in the introduction to this chapter for more traditional browser-sniffing techniques.