/**
 * The OWASP CSRFGuard Project, BSD License
 * Eric Sheridan (eric.sheridan@owasp.org), Copyright (c) 2011
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    3. Neither the name of OWASP nor the names of its contributors may be used
 *       to endorse or promote products derived from this software without specific
 *       prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
(function() {

    function addEvent( obj, type, fn ) {
        if (obj.addEventListener) {
            obj.addEventListener( type, fn, false );
            EventCache.add(obj, type, fn);
        }
        else if (obj.attachEvent) {
            obj["e"+type+fn] = fn;
            obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
            obj.attachEvent( "on"+type, obj[type+fn] );
            EventCache.add(obj, type, fn);
        }
        else {
            obj["on"+type] = obj["e"+type+fn];
        }
    }

    var EventCache = function(){
        var listEvents = [];
        return {
            listEvents : listEvents,
            add : function(node, sEventName, fHandler){
                listEvents.push(arguments);
            },
            flush : function(){
                var i, item;
                for(i = listEvents.length - 1; i >= 0; i = i - 1){
                    item = listEvents[i];
                    if(item[0].removeEventListener){
                        item[0].removeEventListener(item[1], item[2], item[3]);
                    }
                    if(item[1].substring(0, 2) != "on"){
                        item[1] = "on" + item[1];
                    }
                    if(item[0].detachEvent){
                        item[0].detachEvent(item[1], item[2]);
                    }
                }
            }
        };
    }();

    /** string utility functions * */
    if(typeof String.prototype.startsWith !== "function") {
        String.prototype.startsWith = function(prefix) {
            return this.indexOf(prefix) === 0;
        };
    }
    if(typeof String.prototype.endsWith !== "function") {
        String.prototype.endsWith = function(suffix) {
            return this.match(suffix+"$") == suffix;
        };
    }

    /** hook using standards based prototype * */
    function hijackStandard() {
        XMLHttpRequest.prototype._open = XMLHttpRequest.prototype.open;
        XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
            this.url = url;

            this._open.apply(this, arguments);
        };

        XMLHttpRequest.prototype._send = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.send = function(data) {
            if(this.onsend != null) {
                this.onsend.apply(this, arguments);
            }

            this._send.apply(this, arguments);
        }
    }

    /** ie does not properly support prototype - wrap completely * */
    function hijackExplorer() {
        var _XMLHttpRequest = window.XMLHttpRequest;

        function alloc_XMLHttpRequest() {
            this.base = _XMLHttpRequest ? new _XMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
        }

        function init_XMLHttpRequest() {
            return new alloc_XMLHttpRequest;
        }

        init_XMLHttpRequest.prototype = alloc_XMLHttpRequest.prototype;

        /** constants * */
        init_XMLHttpRequest.UNSENT = 0;
        init_XMLHttpRequest.OPENED = 1;
        init_XMLHttpRequest.HEADERS_RECEIVED = 2;
        init_XMLHttpRequest.LOADING = 3;
        init_XMLHttpRequest.DONE = 4;

        /** properties * */
        init_XMLHttpRequest.prototype.status = 0;
        init_XMLHttpRequest.prototype.statusText = "";
        init_XMLHttpRequest.prototype.readyState = init_XMLHttpRequest.UNSENT;
        init_XMLHttpRequest.prototype.responseText = "";
        init_XMLHttpRequest.prototype.responseXML = null;
        init_XMLHttpRequest.prototype.onsend = null;

        init_XMLHttpRequest.url = null;
        init_XMLHttpRequest.onreadystatechange = null;

        /** methods * */
        init_XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
            var self = this;
            this.url = url;

            this.base.open(method, url, async, user, pass);

            this.base.onreadystatechange = function() {
                try { self.status = self.base.status; } catch (e) { }
                try { self.statusText = self.base.statusText; } catch (e) { }
                try { self.readyState = self.base.readyState; } catch (e) { }
                try { self.responseText = self.base.responseText; } catch(e) { }
                try { self.responseXML = self.base.responseXML; } catch(e) { }

                if(self.onreadystatechange != null) {
                    self.onreadystatechange.apply(this, arguments);
                }
            }
        };

        init_XMLHttpRequest.prototype.send = function(data) {
            if(this.onsend != null) {
                this.onsend.apply(this, arguments);
            }

            this.base.send(data);
        };

        init_XMLHttpRequest.prototype.abort = function() {
            this.base.abort();
        };

        init_XMLHttpRequest.prototype.getAllResponseHeaders = function() {
            return this.base.getAllResponseHeaders();
        };

        init_XMLHttpRequest.prototype.getResponseHeader = function(name) {
            return this.base.getResponseHeader(name);
        };

        init_XMLHttpRequest.prototype.setRequestHeader = function(name, value) {
            return this.base.setRequestHeader(name, value);
        };

        /** hook * */
        window.XMLHttpRequest = init_XMLHttpRequest;
    }

    /** check if valid domain based on domainStrict * */
    function isValidDomain(current, target) {
        var result = false;

        /** check exact or subdomain match * */
        if(current == target) {
            result = true;
        } else if(false == false) {
            if(target.charAt(0) == '.') {
                result = current.endsWith(target);
            } else {
                result = current.endsWith('.' + target);
            }
        }

        return result;
    }

    /** determine if uri/url points to valid domain * */
    function isValidUrl(src) {
        var result = false;

        /** parse out domain to make sure it points to our own * */
        if(src.substring(0, 7) == "http://" || src.substring(0, 8) == "https://") {
            var token = "://";
            var index = src.indexOf(token);
            var part = src.substring(index + token.length);
            var domain = "";

            /** parse up to end, first slash, or anchor * */
            for(i=0; i<part.length; i++) {
                var character = part.charAt(i);

                if(character == '/' || character == ':' || character == '#') {
                    break;
                } else {
                    domain += character;
                }
            }

            result = isValidDomain(document.domain, domain);
            /** explicitly skip anchors * */
        } else if(src.charAt(0) == '#') {
            result = false;
            /** ensure it is a local resource without a protocol * */
        } else if(!src.startsWith("//") && (src.charAt(0) == '/' || src.indexOf(':') == -1)) {
            result = true;
        }

        return result;
    }

    /** parse uri from url * */
    function parseUri(url) {
        var uri = "";
        var token = "://";
        var index = url.indexOf(token);
        var part = "";

        /**
         * ensure to skip protocol and prepend context path for non-qualified
         * resources (ex: "protect.html" vs
         * "/Owasp.CsrfGuard.Test/protect.html").
         */
        if(index > 0) {
            part = url.substring(index + token.length);
        } else if(url.charAt(0) != '/') {
            part = "/" + url;
        } else {
            part = url;
        }

        /** parse up to end or query string * */
        var uriContext = (index == -1);

        for(var i=0; i<part.length; i++) {
            var character = part.charAt(i);

            if(character == '/') {
                uriContext = true;
            } else if(uriContext == true && (character == '?' || character == '#')) {
                uriContext = false;
                break;
            }

            if(uriContext == true) {
                uri += character;
            }
        }

        return uri;
    }

    /** inject tokens as hidden fields into forms **/
    function injectTokenForm(form, tokenName, tokenValue, pageTokens,injectGetForms) {

        if (!injectGetForms) {
            var method = form.getAttribute("method");

            if ((typeof method != 'undefined') && method != null && method.toLowerCase() == "get") {
                return;
            }
        }

        var value = tokenValue;
        var action = form.getAttribute("action");

        if(action != null && isValidUrl(action)) {
            var uri = parseUri(action);
            value = pageTokens[uri] != null ? pageTokens[uri] : tokenValue;
        }

        var hidden = document.createElement("input");

        hidden.setAttribute("type", "hidden");
        hidden.setAttribute("name", tokenName);
        hidden.setAttribute("value", value);

        form.appendChild(hidden);
    }

    /** inject tokens as query string parameters into url **/
    function injectTokenAttribute(element, attr, tokenName, tokenValue, pageTokens) {
        var location = element.getAttribute(attr);

        if(location != null && isValidUrl(location)) {
            var uri = parseUri(location);
            var value = (pageTokens[uri] != null ? pageTokens[uri] : tokenValue);

            if(location.indexOf('?') != -1) {
                location = location + '&' + tokenName + '=' + value;
            } else {
                location = location + '?' + tokenName + '=' + value;
            }

            try {
                element.setAttribute(attr, location);
            } catch (e) {
                // attempted to set/update unsupported attribute
            }
        }
    }

    /** inject csrf prevention tokens throughout dom **/
    function injectTokens(tokenName, tokenValue) {
        /** obtain reference to page tokens if enabled **/
        var pageTokens = {};

        if(false == true) {
            pageTokens = requestPageTokens();
        }

        /** iterate over all elements and injection token **/
        var all = document.all ? document.all : document.getElementsByTagName('*');
        var len = all.length;

        for(var i=0; i<len; i++) {
            var element = all[i];

            /** inject into form **/
            if(element.tagName.toLowerCase() == "form") {
                if(true) {
                    injectTokenForm(element, tokenName, tokenValue, pageTokens,false);

                    /** adjust array length after addition of new element **/
                    len = all.length;
                }
                if (false) {
                    injectTokenAttribute(element, "action", tokenName, tokenValue, pageTokens);
                }
                /** inject into attribute **/
            } else if(false) {
                injectTokenAttribute(element, "src", tokenName, tokenValue, pageTokens);
                injectTokenAttribute(element, "href", tokenName, tokenValue, pageTokens);
            }
        }
    }

    /** obtain array of page specific tokens **/
    function requestPageTokens() {
        var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
        var pageTokens = {};

        xhr.open("POST", "/csrf_js"+(typeof(resourceAccountIdRoutingURl)!="undefined"?"?"+resourceAccountIdRoutingURl:""), false);
        xhr.send(null);

        var text = xhr.responseText;
        var name = "";
        var value = "";
        var nameContext = true;

        for(var i=0; i<text.length; i++) {
            var character = text.charAt(i);

            if(character == ':') {
                nameContext = false;
            } else if(character != ',') {
                if(nameContext == true) {
                    name += character;
                } else {
                    value += character;
                }
            }

            if(character == ',' || (i + 1) >= text.length) {
                pageTokens[name] = value;
                name = "";
                value = "";
                nameContext = true;
            }
        }

        return pageTokens;
    }


    /**
     * Only inject the tokens if the JavaScript was referenced from HTML that
     * was served by us. Otherwise, the code was referenced from malicious HTML
     * which may be trying to steal tokens using JavaScript hijacking
     * techniques.
     */
    if(isValidDomain(document.domain, "us02web.zoom.us")) {
        /** optionally include Ajax support * */
        if(true == true) {
            if(navigator.appName == "Microsoft Internet Explorer") {
                hijackExplorer();
            } else {
                hijackStandard();
            }

            var xhr = window.XMLHttpRequest ? new window.XMLHttpRequest : new window.ActiveXObject("Microsoft.XMLHTTP");
            var csrfToken = {};
            xhr.open("POST", "/csrf_js"+(typeof(resourceAccountIdRoutingURl)!="undefined"?"?"+resourceAccountIdRoutingURl:""), false);
            xhr.setRequestHeader("FETCH-CSRF-TOKEN", "1");
            xhr.send(null);

            var token_pair = xhr.responseText;
            token_pair = token_pair.split(":");
            var token_name = token_pair[0];
            var token_value = token_pair[1];

            XMLHttpRequest.prototype.onsend = function(data) {
                if(isValidUrl(this.url)) {
                    this.setRequestHeader("X-Requested-With", "XMLHttpRequest");
                    this.setRequestHeader("X-Requested-With", "OWASP CSRFGuard Project");
                    this.setRequestHeader(token_name, token_value);
                }
            };
        }

        /** update nodes in DOM after load **/
        addEvent(window,'unload',EventCache.flush);
        addEvent(window,'load', function() {
            injectTokens(token_name, token_value);
        });
    } else {
        alert("OWASP CSRFGuard JavaScript was included from within an unauthorized domain!");
    }
})();